mirror of https://github.com/Enne2/win9xman
Browse Source
- Created the main package structure for Windows 9x Manager. - Added core functionality for disk image management in `disk.py`. - Implemented HDD image creation with a progress dialog. - Developed the main UI manager class in `manager.py` for user interactions. - Included utility functions for configuration management and system checks. - Established a template for DOSBox-X configuration. - Created snapshot management features for saving and restoring system states.main
13 changed files with 869 additions and 1188 deletions
@ -1,3 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
# This is just a placeholder to create the assets directory |
|
||||||
mkdir -p /home/enne2/Development/win9xman/assets |
|
||||||
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
@ -1,71 +0,0 @@ |
|||||||
# DOSBox-X configuration file for Windows 9x Manager |
|
||||||
|
|
||||||
[sdl] |
|
||||||
fullscreen=false |
|
||||||
fulldouble=true |
|
||||||
fullresolution=desktop |
|
||||||
windowresolution=1024x768 |
|
||||||
output=opengl |
|
||||||
autolock=true |
|
||||||
|
|
||||||
[dosbox] |
|
||||||
language= |
|
||||||
machine=svga_s3 |
|
||||||
captures=capture |
|
||||||
memsize=64 |
|
||||||
|
|
||||||
[render] |
|
||||||
frameskip=0 |
|
||||||
aspect=true |
|
||||||
scaler=normal3x |
|
||||||
|
|
||||||
[cpu] |
|
||||||
core=dynamic |
|
||||||
cputype=pentium_mmx |
|
||||||
cycles=max 80% limit 33000 |
|
||||||
cycleup=500 |
|
||||||
cycledown=500 |
|
||||||
|
|
||||||
[mixer] |
|
||||||
nosound=false |
|
||||||
rate=44100 |
|
||||||
blocksize=1024 |
|
||||||
prebuffer=40 |
|
||||||
|
|
||||||
[midi] |
|
||||||
mpu401=intelligent |
|
||||||
mididevice=default |
|
||||||
|
|
||||||
[sblaster] |
|
||||||
sbtype=sb16 |
|
||||||
sbbase=220 |
|
||||||
irq=7 |
|
||||||
dma=1 |
|
||||||
hdma=5 |
|
||||||
sbmixer=true |
|
||||||
oplmode=auto |
|
||||||
oplemu=default |
|
||||||
oplrate=44100 |
|
||||||
|
|
||||||
[gus] |
|
||||||
gus=false |
|
||||||
gusrate=44100 |
|
||||||
gusbase=240 |
|
||||||
irq1=5 |
|
||||||
dma1=1 |
|
||||||
|
|
||||||
[speaker] |
|
||||||
pcspeaker=true |
|
||||||
pcrate=44100 |
|
||||||
tandy=auto |
|
||||||
tandyrate=44100 |
|
||||||
disney=true |
|
||||||
|
|
||||||
[dos] |
|
||||||
xms=true |
|
||||||
ems=true |
|
||||||
umb=true |
|
||||||
keyboardlayout=auto |
|
||||||
|
|
||||||
[ipx] |
|
||||||
ipx=false |
|
||||||
@ -1,335 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
# Win98 DOSBox-X Launcher |
|
||||||
# A GUI tool to launch Windows 98 in DOSBox-X with different scenarios |
|
||||||
|
|
||||||
# Check if zenity is installed |
|
||||||
if ! command -v zenity &> /dev/null; then |
|
||||||
echo "Zenity is not installed. Please install it using:" |
|
||||||
echo "sudo apt-get install zenity" |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# Base directory |
|
||||||
BASE_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) |
|
||||||
DOSBOX_CONF="$BASE_DIR/dosbox.conf" |
|
||||||
WIN98_DRIVE="$BASE_DIR/win98_drive" |
|
||||||
ISO_DIR="$BASE_DIR/iso" |
|
||||||
IMG_DIR="$BASE_DIR/disks" |
|
||||||
HDD_IMAGE="$IMG_DIR/win98.img" |
|
||||||
SNAPSHOT_DIR="$BASE_DIR/snapshots" |
|
||||||
DEFAULT_HDD_SIZE=2000 # Default size in MB for HDD image |
|
||||||
MIN_HDD_SIZE=500 # Minimum size in MB |
|
||||||
MAX_HDD_SIZE=4000 # Maximum size in MB |
|
||||||
|
|
||||||
# Make sure directories exist |
|
||||||
mkdir -p "$WIN98_DRIVE" "$ISO_DIR" "$IMG_DIR" "$SNAPSHOT_DIR" |
|
||||||
|
|
||||||
# Function to create HDD image if it doesn't exist |
|
||||||
create_hdd_image() { |
|
||||||
if [ ! -f "$HDD_IMAGE" ]; then |
|
||||||
# Let user choose the size with slider |
|
||||||
HDD_SIZE=$(zenity --scale --title="Select HDD Size" \ |
|
||||||
--text="Choose the size of your Windows 98 hard disk (MB):" \ |
|
||||||
--min-value=$MIN_HDD_SIZE --max-value=$MAX_HDD_SIZE --value=$DEFAULT_HDD_SIZE \ |
|
||||||
--step=100) |
|
||||||
|
|
||||||
# Check if user cancelled the dialog |
|
||||||
if [ $? -ne 0 ]; then |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# If no size was selected, use default |
|
||||||
if [ -z "$HDD_SIZE" ]; then |
|
||||||
HDD_SIZE=$DEFAULT_HDD_SIZE |
|
||||||
fi |
|
||||||
|
|
||||||
zenity --question --text="Create new disk image of ${HDD_SIZE}MB?" --title="Confirm HDD Creation" --ok-label="Yes" --cancel-label="No" |
|
||||||
if [ $? -ne 0 ]; then |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# Show progress dialog while creating the image |
|
||||||
( |
|
||||||
echo "# Creating disk image of ${HDD_SIZE}MB..." |
|
||||||
# Create disk image using DOSBox-X's imgmake command |
|
||||||
dosbox-x -c "imgmake \"$HDD_IMAGE\" -size ${HDD_SIZE} -fat 32 -t hd" -c "exit" > /dev/null 2>&1 |
|
||||||
if [ $? -ne 0 ]; then |
|
||||||
zenity --error --text="Failed to create HDD image. Please check your permissions." |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
echo "100" |
|
||||||
echo "# Disk image created successfully!" |
|
||||||
) | zenity --progress --title="Creating HDD Image" --text="Creating disk image..." --percentage=0 --auto-close --no-cancel |
|
||||||
|
|
||||||
return 0 |
|
||||||
fi |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
# Function to create temporary config file based on scenario |
|
||||||
create_temp_config() { |
|
||||||
local autoexec_content="$1" |
|
||||||
local temp_conf="$BASE_DIR/temp_dosbox.conf" |
|
||||||
|
|
||||||
# Copy the original config file |
|
||||||
cp "$DOSBOX_CONF" "$temp_conf" |
|
||||||
|
|
||||||
# Replace the [autoexec] section |
|
||||||
sed -i '/\[autoexec\]/,$ d' "$temp_conf" |
|
||||||
echo "[autoexec]" >> "$temp_conf" |
|
||||||
echo "$autoexec_content" >> "$temp_conf" |
|
||||||
|
|
||||||
echo "$temp_conf" |
|
||||||
} |
|
||||||
|
|
||||||
# Function to start Windows 98 if installed |
|
||||||
start_win98() { |
|
||||||
# Check if HDD image exists |
|
||||||
if [ ! -f "$HDD_IMAGE" ]; then |
|
||||||
zenity --error --text="HDD image not found. Please create one first." |
|
||||||
return |
|
||||||
fi |
|
||||||
|
|
||||||
local autoexec=$(cat << EOF |
|
||||||
# Mount the Windows 98 HDD image as drive C |
|
||||||
imgmount c "$HDD_IMAGE" -t hdd -fs fatù |
|
||||||
# Mount the Windows 98 drive as E |
|
||||||
mount e win98_drive |
|
||||||
boot c: |
|
||||||
EOF |
|
||||||
) |
|
||||||
local temp_conf=$(create_temp_config "$autoexec") |
|
||||||
dosbox-x -conf "$temp_conf" |
|
||||||
rm "$temp_conf" |
|
||||||
} |
|
||||||
|
|
||||||
# Function to browse and mount an ISO |
|
||||||
mount_iso() { |
|
||||||
# Check if HDD image exists |
|
||||||
if [ ! -f "$HDD_IMAGE" ]; then |
|
||||||
if ! create_hdd_image; then |
|
||||||
return |
|
||||||
fi |
|
||||||
fi |
|
||||||
|
|
||||||
local iso_path=$(zenity --file-selection --title="Select ISO file" --file-filter="ISO files (*.iso) | *.iso" --filename="$ISO_DIR/") |
|
||||||
|
|
||||||
if [ -z "$iso_path" ]; then |
|
||||||
zenity --error --text="No ISO file selected." |
|
||||||
return |
|
||||||
fi |
|
||||||
|
|
||||||
local autoexec=$(cat << EOF |
|
||||||
# Mount the Windows 98 HDD image as drive C |
|
||||||
imgmount c "$HDD_IMAGE" -t hdd -fs fat |
|
||||||
|
|
||||||
# Mount the ISO as drive D |
|
||||||
imgmount d "$iso_path" -t iso |
|
||||||
mount e win98_drive |
|
||||||
boot c: |
|
||||||
EOF |
|
||||||
) |
|
||||||
local temp_conf=$(create_temp_config "$autoexec") |
|
||||||
dosbox-x -conf "$temp_conf" |
|
||||||
rm "$temp_conf" |
|
||||||
} |
|
||||||
|
|
||||||
# Function to boot from ISO |
|
||||||
boot_iso() { |
|
||||||
# Check if we should create a new HDD image |
|
||||||
if [ ! -f "$HDD_IMAGE" ]; then |
|
||||||
if ! create_hdd_image; then |
|
||||||
return |
|
||||||
fi |
|
||||||
fi |
|
||||||
|
|
||||||
local iso_path=$(zenity --file-selection --title="Select Windows 98 Installation ISO" --file-filter="ISO files (*.iso) | *.iso" --filename="$ISO_DIR/") |
|
||||||
|
|
||||||
if [ -z "$iso_path" ]; then |
|
||||||
zenity --error --text="No ISO file selected." |
|
||||||
return |
|
||||||
fi |
|
||||||
|
|
||||||
local autoexec=$(cat << EOF |
|
||||||
# Mount the Windows 98 HDD image as drive C |
|
||||||
imgmount c "$HDD_IMAGE" -t hdd -fs fat |
|
||||||
|
|
||||||
# Mount the ISO as drive D |
|
||||||
imgmount d "$iso_path" -t iso |
|
||||||
# Run the setup program |
|
||||||
d: |
|
||||||
# Start the Windows 98 setup |
|
||||||
setup.exe |
|
||||||
EOF |
|
||||||
) |
|
||||||
local temp_conf=$(create_temp_config "$autoexec") |
|
||||||
dosbox-x -conf "$temp_conf" |
|
||||||
rm "$temp_conf" |
|
||||||
} |
|
||||||
|
|
||||||
# Function to format C drive (the disk image) |
|
||||||
format_c() { |
|
||||||
# Check if HDD image exists |
|
||||||
if [ ! -f "$HDD_IMAGE" ]; then |
|
||||||
if ! create_hdd_image; then |
|
||||||
return |
|
||||||
fi |
|
||||||
zenity --info --text="New disk image created. It's already blank and ready to use." |
|
||||||
return |
|
||||||
fi |
|
||||||
|
|
||||||
local confirm=$(zenity --question --text="This will delete the existing disk image and create a new blank one.\nAll data will be lost.\nDo you want to continue?" --title="Confirm Format" --ok-label="Yes" --cancel-label="No") |
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then |
|
||||||
return |
|
||||||
fi |
|
||||||
|
|
||||||
# Remove existing image |
|
||||||
rm -f "$HDD_IMAGE" |
|
||||||
|
|
||||||
# Create new image with user-selected size |
|
||||||
create_hdd_image |
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then |
|
||||||
zenity --info --text="Format completed. A new blank disk image has been created." |
|
||||||
fi |
|
||||||
} |
|
||||||
|
|
||||||
# Function to create a snapshot of the current disk image |
|
||||||
create_snapshot() { |
|
||||||
# Check if HDD image exists |
|
||||||
if [ ! -f "$HDD_IMAGE" ]; then |
|
||||||
zenity --error --text="HDD image not found. Cannot create snapshot." |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# Get snapshot name from user |
|
||||||
local snapshot_name=$(zenity --entry --title="Create Snapshot" \ |
|
||||||
--text="Enter a name for this snapshot:" \ |
|
||||||
--entry-text="Windows98_Snapshot") |
|
||||||
|
|
||||||
# Check if user cancelled |
|
||||||
if [ $? -ne 0 ] || [ -z "$snapshot_name" ]; then |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# Create a valid filename (replace spaces and special chars) |
|
||||||
snapshot_name=$(echo "$snapshot_name" | tr ' ' '_' | tr -cd '[:alnum:]_-') |
|
||||||
|
|
||||||
# Add timestamp to snapshot name to make it unique |
|
||||||
local timestamp=$(date "+%Y%m%d_%H%M%S") |
|
||||||
local snapshot_file="$SNAPSHOT_DIR/${timestamp}_${snapshot_name}.img" |
|
||||||
|
|
||||||
# Show progress while copying the image |
|
||||||
( |
|
||||||
echo "# Creating snapshot: $snapshot_name..." |
|
||||||
cp "$HDD_IMAGE" "$snapshot_file" |
|
||||||
if [ $? -ne 0 ]; then |
|
||||||
zenity --error --text="Failed to create snapshot. Check disk space and permissions." |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
echo "100" |
|
||||||
echo "# Snapshot created successfully!" |
|
||||||
) | zenity --progress --title="Creating Snapshot" --text="Creating snapshot..." --percentage=0 --auto-close --no-cancel |
|
||||||
|
|
||||||
zenity --info --title="Snapshot Created" --text="Snapshot '$snapshot_name' created successfully.\nLocation: $snapshot_file" |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
# Function to restore a snapshot |
|
||||||
restore_snapshot() { |
|
||||||
# Check if snapshots directory exists and has at least one snapshot |
|
||||||
if [ ! -d "$SNAPSHOT_DIR" ] || [ -z "$(ls -A "$SNAPSHOT_DIR")" ]; then |
|
||||||
zenity --error --text="No snapshots found." |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# Create a list of available snapshots |
|
||||||
local snapshots=() |
|
||||||
for snap in "$SNAPSHOT_DIR"/*.img; do |
|
||||||
local snap_name=$(basename "$snap") |
|
||||||
snapshots+=("$snap" "$snap_name") |
|
||||||
done |
|
||||||
|
|
||||||
# Let user select a snapshot |
|
||||||
local selected_snapshot=$(zenity --list --title="Restore Snapshot" \ |
|
||||||
--text="Select a snapshot to restore:" \ |
|
||||||
--column="Path" --column="Snapshot Name" \ |
|
||||||
"${snapshots[@]}" \ |
|
||||||
--hide-column=1 --width=500 --height=300) |
|
||||||
|
|
||||||
# Check if user cancelled |
|
||||||
if [ $? -ne 0 ] || [ -z "$selected_snapshot" ]; then |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# Confirm before restoring |
|
||||||
zenity --question --title="Confirm Restore" \ |
|
||||||
--text="This will replace your current disk image with the selected snapshot.\nAll unsaved changes will be lost.\n\nContinue?" \ |
|
||||||
--ok-label="Restore" --cancel-label="Cancel" |
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# Show progress while restoring the snapshot |
|
||||||
( |
|
||||||
echo "# Restoring snapshot..." |
|
||||||
# Create backup of current image first |
|
||||||
if [ -f "$HDD_IMAGE" ]; then |
|
||||||
mv "$HDD_IMAGE" "${HDD_IMAGE}.bak" |
|
||||||
fi |
|
||||||
|
|
||||||
# Copy the snapshot to the disk image location |
|
||||||
cp "$selected_snapshot" "$HDD_IMAGE" |
|
||||||
if [ $? -ne 0 ]; then |
|
||||||
zenity --error --text="Failed to restore snapshot. Check permissions." |
|
||||||
# Try to restore the backup |
|
||||||
if [ -f "${HDD_IMAGE}.bak" ]; then |
|
||||||
mv "${HDD_IMAGE}.bak" "$HDD_IMAGE" |
|
||||||
fi |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
# Remove backup if restore was successful |
|
||||||
rm -f "${HDD_IMAGE}.bak" |
|
||||||
|
|
||||||
echo "100" |
|
||||||
echo "# Snapshot restored successfully!" |
|
||||||
) | zenity --progress --title="Restoring Snapshot" --text="Restoring snapshot..." --percentage=0 --auto-close --no-cancel |
|
||||||
|
|
||||||
zenity --info --title="Snapshot Restored" --text="Snapshot restored successfully." |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
# Main menu function |
|
||||||
main_menu() { |
|
||||||
while true; do |
|
||||||
local choice=$(zenity --list --title="Windows 98 DOSBox-X Launcher" \ |
|
||||||
--text="Select an option:" \ |
|
||||||
--column="Option" --column="Description" \ |
|
||||||
1 "Start Windows 98 (if installed)" \ |
|
||||||
2 "Mount ISO and start Windows 98" \ |
|
||||||
3 "Boot from Windows 98 ISO (for installation)" \ |
|
||||||
4 "Format C: drive" \ |
|
||||||
5 "Create snapshot of current disk image" \ |
|
||||||
6 "Restore snapshot" \ |
|
||||||
7 "Exit" \ |
|
||||||
--width=500 --height=350) |
|
||||||
|
|
||||||
case "$choice" in |
|
||||||
1) start_win98 ;; |
|
||||||
2) mount_iso ;; |
|
||||||
3) boot_iso ;; |
|
||||||
4) format_c ;; |
|
||||||
5) create_snapshot ;; |
|
||||||
6) restore_snapshot ;; |
|
||||||
7|"") exit 0 ;; |
|
||||||
esac |
|
||||||
done |
|
||||||
} |
|
||||||
|
|
||||||
# Start the main menu |
|
||||||
main_menu |
|
||||||
@ -0,0 +1,5 @@ |
|||||||
|
""" |
||||||
|
Windows 9x Manager Python Package |
||||||
|
""" |
||||||
|
|
||||||
|
__version__ = "1.0.0" |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
""" |
||||||
|
Core functionality for Windows 9x Manager |
||||||
|
""" |
||||||
@ -0,0 +1,55 @@ |
|||||||
|
""" |
||||||
|
Disk image management functions |
||||||
|
""" |
||||||
|
|
||||||
|
import subprocess |
||||||
|
import tkinter as tk |
||||||
|
from tkinter import ttk, messagebox |
||||||
|
|
||||||
|
def create_hdd_image(root, hdd_image, hdd_size): |
||||||
|
"""Create a new HDD image file |
||||||
|
|
||||||
|
Args: |
||||||
|
root: The Tkinter root window |
||||||
|
hdd_image: Path to the HDD image file to create |
||||||
|
hdd_size: Size of the HDD in MB |
||||||
|
|
||||||
|
Returns: |
||||||
|
bool: True if the image was created successfully, False otherwise |
||||||
|
""" |
||||||
|
# Confirm creation |
||||||
|
if not messagebox.askyesno("Confirm HDD Creation", |
||||||
|
f"Create new disk image of {hdd_size}MB?"): |
||||||
|
return False |
||||||
|
|
||||||
|
# Show progress while creating the image |
||||||
|
progress_window = tk.Toplevel(root) |
||||||
|
progress_window.title("Creating HDD Image") |
||||||
|
progress_window.geometry("400x100") |
||||||
|
progress_window.transient(root) |
||||||
|
progress_window.grab_set() |
||||||
|
|
||||||
|
ttk.Label(progress_window, text=f"Creating disk image of {hdd_size}MB...").pack(pady=10) |
||||||
|
progress = ttk.Progressbar(progress_window, mode="indeterminate", length=300) |
||||||
|
progress.pack(pady=10, padx=20) |
||||||
|
progress.start() |
||||||
|
|
||||||
|
result = [False] # Use list for result |
||||||
|
|
||||||
|
# Schedule the actual creation task |
||||||
|
def create_task(): |
||||||
|
try: |
||||||
|
# Create disk image using DOSBox-X's imgmake command |
||||||
|
cmd = ["dosbox-x", "-c", f"imgmake \"{hdd_image}\" -size {hdd_size} -fat 32 -t hd", "-c", "exit"] |
||||||
|
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
||||||
|
result[0] = True |
||||||
|
progress_window.destroy() |
||||||
|
messagebox.showinfo("Success", f"Disk image of {hdd_size}MB created successfully.") |
||||||
|
except subprocess.CalledProcessError: |
||||||
|
progress_window.destroy() |
||||||
|
messagebox.showerror("Error", "Failed to create HDD image. Please check your permissions.") |
||||||
|
|
||||||
|
root.after(100, create_task) |
||||||
|
root.wait_window(progress_window) |
||||||
|
|
||||||
|
return result[0] |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
""" |
||||||
|
UI Components for Windows 9x Manager |
||||||
|
""" |
||||||
@ -0,0 +1,595 @@ |
|||||||
|
""" |
||||||
|
Main UI manager class for Windows 9x Manager |
||||||
|
""" |
||||||
|
|
||||||
|
import os |
||||||
|
import tkinter as tk |
||||||
|
from tkinter import ttk, filedialog, messagebox |
||||||
|
from tkinter.simpledialog import askstring |
||||||
|
from datetime import datetime |
||||||
|
from pathlib import Path |
||||||
|
import configparser |
||||||
|
import subprocess |
||||||
|
import shutil |
||||||
|
|
||||||
|
from win9xman.core.disk import create_hdd_image |
||||||
|
from win9xman.utils.config import create_temp_config, generate_config_from_template, create_default_template |
||||||
|
|
||||||
|
class Win9xManager: |
||||||
|
def __init__(self, root): |
||||||
|
self.root = root |
||||||
|
self.root.title("Windows 9x Manager") |
||||||
|
self.root.geometry("600x450") |
||||||
|
self.root.minsize(600, 450) |
||||||
|
|
||||||
|
# Set up base paths |
||||||
|
self.base_dir = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) |
||||||
|
self.templates_dir = self.base_dir / "templates" |
||||||
|
self.dosbox_conf = self.base_dir / "config" / "dosbox.conf" |
||||||
|
self.win98_drive = self.base_dir / "win98_drive" |
||||||
|
self.win95_drive = self.base_dir / "win95_drive" |
||||||
|
self.iso_dir = self.base_dir / "iso" |
||||||
|
self.img_dir = self.base_dir / "disks" |
||||||
|
self.win98_hdd = self.img_dir / "win98.img" |
||||||
|
self.win95_hdd = self.img_dir / "win95.img" |
||||||
|
self.snapshot_dir = self.base_dir / "snapshots" |
||||||
|
self.snapshot_win95_dir = self.base_dir / "snapshots_win95" |
||||||
|
|
||||||
|
# Default settings |
||||||
|
self.default_hdd_size = 2000 # Default size in MB for HDD image |
||||||
|
self.min_hdd_size = 500 # Minimum size in MB |
||||||
|
self.max_hdd_size = 4000 # Maximum size in MB |
||||||
|
|
||||||
|
# Current OS selection |
||||||
|
self.current_os = tk.StringVar(value="win98") |
||||||
|
|
||||||
|
# Create necessary directories |
||||||
|
self._create_directories() |
||||||
|
|
||||||
|
# Create UI |
||||||
|
self._create_ui() |
||||||
|
|
||||||
|
def _create_directories(self): |
||||||
|
"""Create necessary directories if they don't exist""" |
||||||
|
directories = [ |
||||||
|
self.win98_drive, |
||||||
|
self.win95_drive, |
||||||
|
self.iso_dir, |
||||||
|
self.img_dir, |
||||||
|
self.snapshot_dir, |
||||||
|
self.snapshot_win95_dir, |
||||||
|
self.base_dir / "config", |
||||||
|
self.templates_dir |
||||||
|
] |
||||||
|
|
||||||
|
for directory in directories: |
||||||
|
directory.mkdir(exist_ok=True, parents=True) |
||||||
|
|
||||||
|
# Create default DOSBox-X config if it doesn't exist |
||||||
|
if not self.dosbox_conf.exists(): |
||||||
|
self._create_default_config() |
||||||
|
|
||||||
|
def _create_default_config(self): |
||||||
|
"""Create a default DOSBox-X configuration file from template""" |
||||||
|
config_dir = self.base_dir / "config" |
||||||
|
config_dir.mkdir(exist_ok=True) |
||||||
|
|
||||||
|
# Check if template exists |
||||||
|
template_path = self.templates_dir / "dosbox_template.conf" |
||||||
|
if not template_path.exists(): |
||||||
|
create_default_template(self.templates_dir) |
||||||
|
|
||||||
|
# Create config from template |
||||||
|
template_vars = { |
||||||
|
'memsize': '64', |
||||||
|
'cycles': 'max 80% limit 33000', |
||||||
|
'machine': 'svga_s3', |
||||||
|
'windowresolution': '1024x768', |
||||||
|
'output': 'opengl' |
||||||
|
} |
||||||
|
generate_config_from_template(self.templates_dir, 'dosbox_template.conf', self.dosbox_conf, template_vars) |
||||||
|
|
||||||
|
def _create_ui(self): |
||||||
|
"""Create the UI elements""" |
||||||
|
# OS Selection frame |
||||||
|
os_frame = ttk.LabelFrame(self.root, text="Select Windows Version") |
||||||
|
os_frame.pack(fill=tk.X, padx=10, pady=5) |
||||||
|
|
||||||
|
ttk.Radiobutton(os_frame, text="Windows 98", variable=self.current_os, |
||||||
|
value="win98").pack(side=tk.LEFT, padx=20, pady=5) |
||||||
|
ttk.Radiobutton(os_frame, text="Windows 95", variable=self.current_os, |
||||||
|
value="win95").pack(side=tk.LEFT, padx=20, pady=5) |
||||||
|
|
||||||
|
# Main actions frame |
||||||
|
actions_frame = ttk.Frame(self.root) |
||||||
|
actions_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) |
||||||
|
|
||||||
|
# Create buttons with descriptions |
||||||
|
button_data = [ |
||||||
|
("Start Windows", "Launch Windows if already installed", self.start_windows), |
||||||
|
("Mount ISO & Start Windows", "Mount ISO as CD-ROM drive and start Windows", self.mount_iso), |
||||||
|
("Install Windows from ISO", "Boot from ISO to install Windows", self.boot_iso), |
||||||
|
("Format Hard Disk", "Create or reset disk image", self.format_disk), |
||||||
|
("Create Snapshot", "Save current system state", self.create_snapshot), |
||||||
|
("Restore Snapshot", "Restore previous system state", self.restore_snapshot), |
||||||
|
("Settings", "Configure DOSBox-X settings", self.open_settings), |
||||||
|
("Exit", "Close the application", self.root.quit) |
||||||
|
] |
||||||
|
|
||||||
|
for i, (text, desc, command) in enumerate(button_data): |
||||||
|
frame = ttk.Frame(actions_frame) |
||||||
|
frame.pack(fill=tk.X, pady=5) |
||||||
|
|
||||||
|
btn = ttk.Button(frame, text=text, command=command, width=20) |
||||||
|
btn.pack(side=tk.LEFT, padx=5) |
||||||
|
|
||||||
|
ttk.Label(frame, text=desc).pack(side=tk.LEFT, padx=5) |
||||||
|
|
||||||
|
def get_current_hdd(self): |
||||||
|
"""Get current HDD path based on selected OS""" |
||||||
|
return self.win98_hdd if self.current_os.get() == "win98" else self.win95_hdd |
||||||
|
|
||||||
|
def get_current_drive_dir(self): |
||||||
|
"""Get current drive directory based on selected OS""" |
||||||
|
return self.win98_drive if self.current_os.get() == "win98" else self.win95_drive |
||||||
|
|
||||||
|
def get_snapshot_dir(self): |
||||||
|
"""Get snapshot directory based on selected OS""" |
||||||
|
return self.snapshot_dir if self.current_os.get() == "win98" else self.snapshot_win95_dir |
||||||
|
|
||||||
|
def create_hdd_image(self): |
||||||
|
"""Create HDD image if it doesn't exist""" |
||||||
|
hdd_image = self.get_current_hdd() |
||||||
|
|
||||||
|
if hdd_image.exists(): |
||||||
|
return True |
||||||
|
|
||||||
|
# Create slider dialog for HDD size |
||||||
|
size_dialog = tk.Toplevel(self.root) |
||||||
|
size_dialog.title("Select HDD Size") |
||||||
|
size_dialog.geometry("400x150") |
||||||
|
size_dialog.resizable(False, False) |
||||||
|
size_dialog.transient(self.root) |
||||||
|
size_dialog.grab_set() |
||||||
|
|
||||||
|
ttk.Label(size_dialog, text="Choose the size of your Windows hard disk (MB):").pack(pady=10) |
||||||
|
|
||||||
|
size_var = tk.IntVar(value=self.default_hdd_size) |
||||||
|
slider = ttk.Scale(size_dialog, from_=self.min_hdd_size, to=self.max_hdd_size, |
||||||
|
variable=size_var, orient=tk.HORIZONTAL, length=300) |
||||||
|
slider.pack(pady=10, padx=20) |
||||||
|
|
||||||
|
size_label = ttk.Label(size_dialog, text=f"{self.default_hdd_size} MB") |
||||||
|
size_label.pack() |
||||||
|
|
||||||
|
def update_label(*args): |
||||||
|
size_label.config(text=f"{size_var.get()} MB") |
||||||
|
|
||||||
|
slider.bind("<Motion>", update_label) |
||||||
|
|
||||||
|
result = [False] # Use list to store result (Python 3.x closure behavior) |
||||||
|
|
||||||
|
def on_ok(): |
||||||
|
result[0] = True |
||||||
|
size_dialog.destroy() |
||||||
|
|
||||||
|
def on_cancel(): |
||||||
|
size_dialog.destroy() |
||||||
|
|
||||||
|
button_frame = ttk.Frame(size_dialog) |
||||||
|
button_frame.pack(fill=tk.X, pady=10) |
||||||
|
ttk.Button(button_frame, text="Create", command=on_ok).pack(side=tk.LEFT, padx=20) |
||||||
|
ttk.Button(button_frame, text="Cancel", command=on_cancel).pack(side=tk.RIGHT, padx=20) |
||||||
|
|
||||||
|
self.root.wait_window(size_dialog) |
||||||
|
|
||||||
|
if not result[0]: |
||||||
|
return False |
||||||
|
|
||||||
|
hdd_size = size_var.get() |
||||||
|
|
||||||
|
# Call the core function to create the HDD image |
||||||
|
return create_hdd_image(self.root, hdd_image, hdd_size) |
||||||
|
|
||||||
|
def start_windows(self): |
||||||
|
"""Start Windows if installed""" |
||||||
|
hdd_image = self.get_current_hdd() |
||||||
|
drive_dir = self.get_current_drive_dir() |
||||||
|
|
||||||
|
# Check if HDD image exists |
||||||
|
if not hdd_image.exists(): |
||||||
|
if not messagebox.askyesno("HDD Missing", |
||||||
|
"HDD image not found. Do you want to create one?"): |
||||||
|
return |
||||||
|
|
||||||
|
if not self.create_hdd_image(): |
||||||
|
return |
||||||
|
|
||||||
|
# Create autoexec content |
||||||
|
autoexec = f""" |
||||||
|
# Mount the Windows HDD image as drive C |
||||||
|
imgmount c "{hdd_image}" -t hdd -fs fat |
||||||
|
# Mount the local directory as drive E |
||||||
|
mount e "{drive_dir}" |
||||||
|
boot c: |
||||||
|
""" |
||||||
|
|
||||||
|
# Create temporary config |
||||||
|
temp_conf = create_temp_config(self.dosbox_conf, autoexec, self.templates_dir, self.base_dir) |
||||||
|
|
||||||
|
# Launch DOSBox-X with the config |
||||||
|
try: |
||||||
|
subprocess.run(["dosbox-x", "-conf", temp_conf], check=True) |
||||||
|
finally: |
||||||
|
# Clean up temp config |
||||||
|
if temp_conf.exists(): |
||||||
|
temp_conf.unlink() |
||||||
|
|
||||||
|
def mount_iso(self): |
||||||
|
"""Mount ISO and start Windows""" |
||||||
|
hdd_image = self.get_current_hdd() |
||||||
|
drive_dir = self.get_current_drive_dir() |
||||||
|
|
||||||
|
# Check if HDD image exists |
||||||
|
if not hdd_image.exists(): |
||||||
|
if messagebox.askyesno("HDD Missing", |
||||||
|
"HDD image not found. Do you want to create one?"): |
||||||
|
if not self.create_hdd_image(): |
||||||
|
return |
||||||
|
else: |
||||||
|
return |
||||||
|
|
||||||
|
# Open file dialog to select ISO |
||||||
|
iso_path = filedialog.askopenfilename( |
||||||
|
title="Select ISO file", |
||||||
|
filetypes=[("ISO files", "*.iso")], |
||||||
|
initialdir=self.iso_dir |
||||||
|
) |
||||||
|
|
||||||
|
if not iso_path: |
||||||
|
messagebox.showinfo("Cancelled", "No ISO file selected.") |
||||||
|
return |
||||||
|
|
||||||
|
# Create autoexec content |
||||||
|
autoexec = f""" |
||||||
|
# Mount the Windows HDD image as drive C |
||||||
|
imgmount c "{hdd_image}" -t hdd -fs fat |
||||||
|
# Mount the ISO as drive D |
||||||
|
imgmount d "{iso_path}" -t iso |
||||||
|
# Mount the local directory as drive E |
||||||
|
mount e "{drive_dir}" |
||||||
|
boot c: |
||||||
|
""" |
||||||
|
|
||||||
|
# Create temporary config |
||||||
|
temp_conf = create_temp_config(self.dosbox_conf, autoexec, self.templates_dir, self.base_dir) |
||||||
|
|
||||||
|
# Launch DOSBox-X with the config |
||||||
|
try: |
||||||
|
subprocess.run(["dosbox-x", "-conf", temp_conf], check=True) |
||||||
|
finally: |
||||||
|
# Clean up temp config |
||||||
|
if temp_conf.exists(): |
||||||
|
temp_conf.unlink() |
||||||
|
|
||||||
|
def boot_iso(self): |
||||||
|
"""Boot from ISO to install Windows""" |
||||||
|
hdd_image = self.get_current_hdd() |
||||||
|
|
||||||
|
# Create or confirm HDD image exists |
||||||
|
if not hdd_image.exists(): |
||||||
|
if messagebox.askyesno("HDD Missing", |
||||||
|
"HDD image not found. Do you want to create one?"): |
||||||
|
if not self.create_hdd_image(): |
||||||
|
return |
||||||
|
else: |
||||||
|
return |
||||||
|
|
||||||
|
# Open file dialog to select ISO |
||||||
|
iso_path = filedialog.askopenfilename( |
||||||
|
title="Select Windows Installation ISO", |
||||||
|
filetypes=[("ISO files", "*.iso")], |
||||||
|
initialdir=self.iso_dir |
||||||
|
) |
||||||
|
|
||||||
|
if not iso_path: |
||||||
|
messagebox.showinfo("Cancelled", "No ISO file selected.") |
||||||
|
return |
||||||
|
|
||||||
|
# Create autoexec content for booting from ISO |
||||||
|
autoexec = f""" |
||||||
|
# Mount the Windows HDD image as drive C |
||||||
|
imgmount c "{hdd_image}" -t hdd -fs fat |
||||||
|
# Mount the ISO as drive D |
||||||
|
imgmount d "{iso_path}" -t iso |
||||||
|
# Start the setup program |
||||||
|
d: |
||||||
|
setup.exe |
||||||
|
""" |
||||||
|
|
||||||
|
# Create temporary config |
||||||
|
temp_conf = create_temp_config(self.dosbox_conf, autoexec, self.templates_dir, self.base_dir) |
||||||
|
|
||||||
|
# Launch DOSBox-X with the config |
||||||
|
try: |
||||||
|
subprocess.run(["dosbox-x", "-conf", temp_conf], check=True) |
||||||
|
finally: |
||||||
|
# Clean up temp config |
||||||
|
if temp_conf.exists(): |
||||||
|
temp_conf.unlink() |
||||||
|
|
||||||
|
def format_disk(self): |
||||||
|
"""Format hard disk image (creates a new one)""" |
||||||
|
hdd_image = self.get_current_hdd() |
||||||
|
|
||||||
|
if hdd_image.exists(): |
||||||
|
if not messagebox.askyesno("Confirm Format", |
||||||
|
"This will delete the existing disk image and create a new blank one.\n" |
||||||
|
"All data will be lost.\nDo you want to continue?"): |
||||||
|
return |
||||||
|
|
||||||
|
# Remove existing image |
||||||
|
hdd_image.unlink() |
||||||
|
|
||||||
|
# Create new disk image |
||||||
|
if self.create_hdd_image(): |
||||||
|
messagebox.showinfo("Format Complete", |
||||||
|
"Format completed. A new blank disk image has been created.") |
||||||
|
|
||||||
|
def create_snapshot(self): |
||||||
|
"""Create a snapshot of the current disk image""" |
||||||
|
hdd_image = self.get_current_hdd() |
||||||
|
snapshot_dir = self.get_snapshot_dir() |
||||||
|
|
||||||
|
# Check if HDD image exists |
||||||
|
if not hdd_image.exists(): |
||||||
|
messagebox.showerror("Error", "HDD image not found. Cannot create snapshot.") |
||||||
|
return |
||||||
|
|
||||||
|
# Get snapshot name |
||||||
|
snapshot_name = askstring("Create Snapshot", "Enter a name for this snapshot:") |
||||||
|
|
||||||
|
if not snapshot_name: |
||||||
|
return # User cancelled |
||||||
|
|
||||||
|
# Create valid filename |
||||||
|
snapshot_name = ''.join(c if c.isalnum() or c in '_-' else '_' for c in snapshot_name) |
||||||
|
|
||||||
|
# Add timestamp |
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
||||||
|
snapshot_file = snapshot_dir / f"{timestamp}_{snapshot_name}.img" |
||||||
|
|
||||||
|
# Show progress dialog |
||||||
|
progress_window = tk.Toplevel(self.root) |
||||||
|
progress_window.title("Creating Snapshot") |
||||||
|
progress_window.geometry("400x100") |
||||||
|
progress_window.transient(self.root) |
||||||
|
progress_window.grab_set() |
||||||
|
|
||||||
|
ttk.Label(progress_window, text=f"Creating snapshot: {snapshot_name}...").pack(pady=10) |
||||||
|
progress = ttk.Progressbar(progress_window, mode="indeterminate", length=300) |
||||||
|
progress.pack(pady=10, padx=20) |
||||||
|
progress.start() |
||||||
|
|
||||||
|
def copy_task(): |
||||||
|
try: |
||||||
|
shutil.copy2(hdd_image, snapshot_file) |
||||||
|
progress_window.destroy() |
||||||
|
messagebox.showinfo("Snapshot Created", |
||||||
|
f"Snapshot '{snapshot_name}' created successfully.\n" |
||||||
|
f"Location: {snapshot_file}") |
||||||
|
except Exception as e: |
||||||
|
progress_window.destroy() |
||||||
|
messagebox.showerror("Error", f"Failed to create snapshot: {str(e)}") |
||||||
|
|
||||||
|
self.root.after(100, copy_task) |
||||||
|
|
||||||
|
def restore_snapshot(self): |
||||||
|
"""Restore a snapshot""" |
||||||
|
hdd_image = self.get_current_hdd() |
||||||
|
snapshot_dir = self.get_snapshot_dir() |
||||||
|
|
||||||
|
# Check if snapshots exist |
||||||
|
snapshots = list(snapshot_dir.glob("*.img")) |
||||||
|
if not snapshots: |
||||||
|
messagebox.showerror("Error", "No snapshots found.") |
||||||
|
return |
||||||
|
|
||||||
|
# Create snapshot selection dialog |
||||||
|
select_dialog = tk.Toplevel(self.root) |
||||||
|
select_dialog.title("Select Snapshot") |
||||||
|
select_dialog.geometry("500x300") |
||||||
|
select_dialog.transient(self.root) |
||||||
|
select_dialog.grab_set() |
||||||
|
|
||||||
|
ttk.Label(select_dialog, text="Select a snapshot to restore:").pack(pady=10) |
||||||
|
|
||||||
|
# Create a listbox for snapshots |
||||||
|
listbox_frame = ttk.Frame(select_dialog) |
||||||
|
listbox_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) |
||||||
|
|
||||||
|
scrollbar = ttk.Scrollbar(listbox_frame) |
||||||
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y) |
||||||
|
|
||||||
|
snapshot_listbox = tk.Listbox(listbox_frame) |
||||||
|
snapshot_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) |
||||||
|
|
||||||
|
snapshot_listbox.config(yscrollcommand=scrollbar.set) |
||||||
|
scrollbar.config(command=snapshot_listbox.yview) |
||||||
|
|
||||||
|
# Add snapshots to listbox |
||||||
|
snapshot_paths = [] |
||||||
|
for snap in snapshots: |
||||||
|
snapshot_paths.append(snap) |
||||||
|
snapshot_listbox.insert(tk.END, snap.name) |
||||||
|
|
||||||
|
selected_index = [-1] # Use list for closure |
||||||
|
|
||||||
|
def on_select(): |
||||||
|
selected_index[0] = snapshot_listbox.curselection() |
||||||
|
if selected_index[0]: |
||||||
|
select_dialog.destroy() |
||||||
|
|
||||||
|
def on_cancel(): |
||||||
|
selected_index[0] = -1 |
||||||
|
select_dialog.destroy() |
||||||
|
|
||||||
|
button_frame = ttk.Frame(select_dialog) |
||||||
|
button_frame.pack(fill=tk.X, pady=10) |
||||||
|
ttk.Button(button_frame, text="Restore", command=on_select).pack(side=tk.LEFT, padx=20) |
||||||
|
ttk.Button(button_frame, text="Cancel", command=on_cancel).pack(side=tk.RIGHT, padx=20) |
||||||
|
|
||||||
|
self.root.wait_window(select_dialog) |
||||||
|
|
||||||
|
# Check if user selected something |
||||||
|
if selected_index[0] == -1 or not selected_index[0]: |
||||||
|
return |
||||||
|
|
||||||
|
selected_snapshot = snapshot_paths[selected_index[0][0]] |
||||||
|
|
||||||
|
# Confirm restore |
||||||
|
if not messagebox.askyesno("Confirm Restore", |
||||||
|
"This will replace your current disk image with the selected snapshot.\n" |
||||||
|
"All unsaved changes will be lost.\n\nContinue?"): |
||||||
|
return |
||||||
|
|
||||||
|
# Show progress dialog |
||||||
|
progress_window = tk.Toplevel(self.root) |
||||||
|
progress_window.title("Restoring Snapshot") |
||||||
|
progress_window.geometry("400x100") |
||||||
|
progress_window.transient(self.root) |
||||||
|
progress_window.grab_set() |
||||||
|
|
||||||
|
ttk.Label(progress_window, text="Restoring snapshot...").pack(pady=10) |
||||||
|
progress = ttk.Progressbar(progress_window, mode="indeterminate", length=300) |
||||||
|
progress.pack(pady=10, padx=20) |
||||||
|
progress.start() |
||||||
|
|
||||||
|
def restore_task(): |
||||||
|
try: |
||||||
|
# Create backup of current image |
||||||
|
backup_file = None |
||||||
|
if hdd_image.exists(): |
||||||
|
backup_file = hdd_image.with_suffix('.img.bak') |
||||||
|
shutil.copy2(hdd_image, backup_file) |
||||||
|
|
||||||
|
# Copy snapshot to disk image location |
||||||
|
shutil.copy2(selected_snapshot, hdd_image) |
||||||
|
|
||||||
|
# Remove backup if restoration was successful |
||||||
|
if backup_file and backup_file.exists(): |
||||||
|
backup_file.unlink() |
||||||
|
|
||||||
|
progress_window.destroy() |
||||||
|
messagebox.showinfo("Snapshot Restored", "Snapshot restored successfully.") |
||||||
|
|
||||||
|
except Exception as e: |
||||||
|
progress_window.destroy() |
||||||
|
messagebox.showerror("Error", f"Failed to restore snapshot: {str(e)}") |
||||||
|
|
||||||
|
# Try to restore backup |
||||||
|
if backup_file and backup_file.exists() and not hdd_image.exists(): |
||||||
|
shutil.copy2(backup_file, hdd_image) |
||||||
|
backup_file.unlink() |
||||||
|
|
||||||
|
self.root.after(100, restore_task) |
||||||
|
|
||||||
|
def open_settings(self): |
||||||
|
"""Open DOSBox-X settings editor""" |
||||||
|
if not self.dosbox_conf.exists(): |
||||||
|
self._create_default_config() |
||||||
|
|
||||||
|
# Create settings dialog |
||||||
|
settings_dialog = tk.Toplevel(self.root) |
||||||
|
settings_dialog.title("DOSBox-X Settings") |
||||||
|
settings_dialog.geometry("600x400") |
||||||
|
settings_dialog.transient(self.root) |
||||||
|
settings_dialog.grab_set() |
||||||
|
|
||||||
|
# Read current configuration |
||||||
|
config = configparser.ConfigParser() |
||||||
|
config.read(self.dosbox_conf) |
||||||
|
|
||||||
|
# Create a notebook for different configuration sections |
||||||
|
notebook = ttk.Notebook(settings_dialog) |
||||||
|
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) |
||||||
|
|
||||||
|
# Create frames for important config sections |
||||||
|
sections = { |
||||||
|
'System': ['dosbox', 'cpu', 'dos'], |
||||||
|
'Graphics': ['sdl', 'render'], |
||||||
|
'Sound': ['mixer', 'sblaster', 'midi'], |
||||||
|
} |
||||||
|
|
||||||
|
# Track all setting variables |
||||||
|
setting_vars = {} |
||||||
|
|
||||||
|
for section_name, config_sections in sections.items(): |
||||||
|
frame = ttk.Frame(notebook) |
||||||
|
notebook.add(frame, text=section_name) |
||||||
|
|
||||||
|
# Add settings for each configuration section |
||||||
|
row = 0 |
||||||
|
for config_section in config_sections: |
||||||
|
if config_section in config: |
||||||
|
ttk.Label(frame, text=f"[{config_section}]", font=("", 11, "bold")).grid( |
||||||
|
row=row, column=0, columnspan=2, sticky="w", padx=5, pady=(10, 5)) |
||||||
|
row += 1 |
||||||
|
|
||||||
|
# Add each option in the section |
||||||
|
for option in config[config_section]: |
||||||
|
ttk.Label(frame, text=f"{option}:").grid( |
||||||
|
row=row, column=0, sticky="w", padx=5, pady=2) |
||||||
|
|
||||||
|
# Create variable for this setting |
||||||
|
var = tk.StringVar(value=config[config_section][option]) |
||||||
|
setting_vars[(config_section, option)] = var |
||||||
|
|
||||||
|
entry = ttk.Entry(frame, textvariable=var, width=30) |
||||||
|
entry.grid(row=row, column=1, sticky="w", padx=5, pady=2) |
||||||
|
|
||||||
|
row += 1 |
||||||
|
|
||||||
|
# Create buttons frame |
||||||
|
button_frame = ttk.Frame(settings_dialog) |
||||||
|
button_frame.pack(fill=tk.X, padx=10, pady=10) |
||||||
|
|
||||||
|
def save_settings(): |
||||||
|
# Update config with values from variables |
||||||
|
for (section, option), var in setting_vars.items(): |
||||||
|
config[section][option] = var.get() |
||||||
|
|
||||||
|
# Save configuration |
||||||
|
with open(self.dosbox_conf, 'w') as f: |
||||||
|
config.write(f) |
||||||
|
|
||||||
|
settings_dialog.destroy() |
||||||
|
messagebox.showinfo("Success", "Settings saved successfully") |
||||||
|
|
||||||
|
def cancel(): |
||||||
|
settings_dialog.destroy() |
||||||
|
|
||||||
|
ttk.Button(button_frame, text="Save", command=save_settings).pack(side=tk.RIGHT, padx=5) |
||||||
|
ttk.Button(button_frame, text="Cancel", command=cancel).pack(side=tk.RIGHT, padx=5) |
||||||
|
|
||||||
|
# Alternative: open in text editor |
||||||
|
def open_in_editor(): |
||||||
|
settings_dialog.destroy() |
||||||
|
|
||||||
|
# Check if system has a GUI text editor |
||||||
|
editors = [ |
||||||
|
('xdg-open', [str(self.dosbox_conf)]), # Linux |
||||||
|
('notepad.exe', [str(self.dosbox_conf)]), # Windows |
||||||
|
('open', ['-t', str(self.dosbox_conf)]) # macOS |
||||||
|
] |
||||||
|
|
||||||
|
for editor, args in editors: |
||||||
|
try: |
||||||
|
subprocess.run([editor] + args) |
||||||
|
break |
||||||
|
except (subprocess.SubprocessError, FileNotFoundError): |
||||||
|
continue |
||||||
|
else: |
||||||
|
messagebox.showerror("Error", f"Could not open editor. The config file is at:\n{self.dosbox_conf}") |
||||||
|
|
||||||
|
ttk.Button(button_frame, text="Open in Text Editor", command=open_in_editor).pack(side=tk.LEFT, padx=5) |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
""" |
||||||
|
Utility functions for Windows 9x Manager |
||||||
|
""" |
||||||
@ -0,0 +1,171 @@ |
|||||||
|
""" |
||||||
|
Configuration utilities for Win9xManager |
||||||
|
""" |
||||||
|
|
||||||
|
import configparser |
||||||
|
import string |
||||||
|
from pathlib import Path |
||||||
|
|
||||||
|
def create_default_template(templates_dir): |
||||||
|
"""Create the default DOSBox-X template file""" |
||||||
|
template_path = templates_dir / "dosbox_template.conf" |
||||||
|
|
||||||
|
# Make sure templates directory exists |
||||||
|
templates_dir.mkdir(exist_ok=True, parents=True) |
||||||
|
|
||||||
|
# Write template content |
||||||
|
with open(template_path, 'w') as f: |
||||||
|
f.write("""# DOSBox-X configuration file for Windows 9x Manager |
||||||
|
|
||||||
|
[sdl] |
||||||
|
fullscreen=false |
||||||
|
fulldouble=true |
||||||
|
fullresolution=desktop |
||||||
|
windowresolution=${windowresolution} |
||||||
|
output=${output} |
||||||
|
autolock=true |
||||||
|
|
||||||
|
[dosbox] |
||||||
|
language= |
||||||
|
machine=${machine} |
||||||
|
captures=capture |
||||||
|
memsize=${memsize} |
||||||
|
|
||||||
|
[render] |
||||||
|
frameskip=0 |
||||||
|
aspect=true |
||||||
|
scaler=normal3x |
||||||
|
|
||||||
|
[cpu] |
||||||
|
core=dynamic |
||||||
|
cputype=pentium_mmx |
||||||
|
cycles=${cycles} |
||||||
|
cycleup=500 |
||||||
|
cycledown=500 |
||||||
|
|
||||||
|
[mixer] |
||||||
|
nosound=false |
||||||
|
rate=44100 |
||||||
|
blocksize=1024 |
||||||
|
prebuffer=40 |
||||||
|
|
||||||
|
[midi] |
||||||
|
mpu401=intelligent |
||||||
|
mididevice=default |
||||||
|
|
||||||
|
[sblaster] |
||||||
|
sbtype=sb16 |
||||||
|
sbbase=220 |
||||||
|
irq=7 |
||||||
|
dma=1 |
||||||
|
hdma=5 |
||||||
|
sbmixer=true |
||||||
|
oplmode=auto |
||||||
|
oplemu=default |
||||||
|
oplrate=44100 |
||||||
|
|
||||||
|
[gus] |
||||||
|
gus=false |
||||||
|
gusrate=44100 |
||||||
|
gusbase=240 |
||||||
|
irq1=5 |
||||||
|
dma1=1 |
||||||
|
|
||||||
|
[speaker] |
||||||
|
pcspeaker=true |
||||||
|
pcrate=44100 |
||||||
|
tandy=auto |
||||||
|
tandyrate=44100 |
||||||
|
disney=true |
||||||
|
|
||||||
|
[dos] |
||||||
|
xms=true |
||||||
|
ems=true |
||||||
|
umb=true |
||||||
|
keyboardlayout=auto |
||||||
|
|
||||||
|
[ipx] |
||||||
|
ipx=false |
||||||
|
""") |
||||||
|
|
||||||
|
def generate_config_from_template(templates_dir, template_name, output_path, variables): |
||||||
|
"""Generate a configuration file from a template with variable substitution |
||||||
|
|
||||||
|
Args: |
||||||
|
templates_dir: Directory containing templates |
||||||
|
template_name: Name of the template file in templates directory |
||||||
|
output_path: Path where to save the generated config |
||||||
|
variables: Dictionary of variables to substitute in the template |
||||||
|
""" |
||||||
|
template_path = templates_dir / template_name |
||||||
|
|
||||||
|
# Read template content |
||||||
|
if not template_path.exists(): |
||||||
|
raise FileNotFoundError(f"Template file {template_path} not found") |
||||||
|
|
||||||
|
with open(template_path, 'r') as f: |
||||||
|
template_content = f.read() |
||||||
|
|
||||||
|
# Use string.Template for variable substitution |
||||||
|
template = string.Template(template_content) |
||||||
|
output_content = template.safe_substitute(variables) |
||||||
|
|
||||||
|
# Write the output file |
||||||
|
with open(output_path, 'w') as f: |
||||||
|
f.write(output_content) |
||||||
|
|
||||||
|
def create_temp_config(dosbox_conf, autoexec_content, templates_dir, base_dir): |
||||||
|
"""Create a temporary DOSBox-X configuration file with custom autoexec section""" |
||||||
|
# Make sure the base config exists |
||||||
|
if not dosbox_conf.exists(): |
||||||
|
# Create default config |
||||||
|
config_dir = base_dir / "config" |
||||||
|
config_dir.mkdir(exist_ok=True) |
||||||
|
|
||||||
|
# Check if template exists |
||||||
|
template_path = templates_dir / "dosbox_template.conf" |
||||||
|
if not template_path.exists(): |
||||||
|
create_default_template(templates_dir) |
||||||
|
|
||||||
|
# Create config from template |
||||||
|
template_vars = { |
||||||
|
'memsize': '64', |
||||||
|
'cycles': 'max 80% limit 33000', |
||||||
|
'machine': 'svga_s3', |
||||||
|
'windowresolution': '1024x768', |
||||||
|
'output': 'opengl' |
||||||
|
} |
||||||
|
generate_config_from_template(templates_dir, 'dosbox_template.conf', dosbox_conf, template_vars) |
||||||
|
|
||||||
|
# Create a temp config based on current settings |
||||||
|
temp_conf = base_dir / "temp_dosbox.conf" |
||||||
|
|
||||||
|
# Read current config to preserve settings |
||||||
|
current_config = {} |
||||||
|
config = configparser.ConfigParser() |
||||||
|
config.read(dosbox_conf) |
||||||
|
|
||||||
|
for section in config.sections(): |
||||||
|
if section != 'autoexec': |
||||||
|
current_config[section] = dict(config[section]) |
||||||
|
|
||||||
|
# Generate the temporary config file from template |
||||||
|
template_vars = { |
||||||
|
'memsize': current_config.get('dosbox', {}).get('memsize', '64'), |
||||||
|
'cycles': current_config.get('cpu', {}).get('cycles', 'max 80% limit 33000'), |
||||||
|
'machine': current_config.get('dosbox', {}).get('machine', 'svga_s3'), |
||||||
|
'windowresolution': current_config.get('sdl', {}).get('windowresolution', '1024x768'), |
||||||
|
'output': current_config.get('sdl', {}).get('output', 'opengl') |
||||||
|
} |
||||||
|
|
||||||
|
# Create temp config |
||||||
|
generate_config_from_template(templates_dir, 'dosbox_template.conf', temp_conf, template_vars) |
||||||
|
|
||||||
|
# Add autoexec section to the generated config |
||||||
|
with open(temp_conf, 'a') as f: |
||||||
|
f.write("\n[autoexec]\n") |
||||||
|
for line in autoexec_content.split('\n'): |
||||||
|
if line.strip(): |
||||||
|
f.write(f"{line}\n") |
||||||
|
|
||||||
|
return temp_conf |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
""" |
||||||
|
System-related utility functions for Win9xManager |
||||||
|
""" |
||||||
|
|
||||||
|
import subprocess |
||||||
|
from tkinter import messagebox |
||||||
|
|
||||||
|
def check_requirements(): |
||||||
|
"""Check if required programs are installed""" |
||||||
|
# Check for DOSBox-X |
||||||
|
try: |
||||||
|
subprocess.run(['dosbox-x', '-version'], |
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) |
||||||
|
return True |
||||||
|
except FileNotFoundError: |
||||||
|
# Try with flatpak |
||||||
|
try: |
||||||
|
subprocess.run(['flatpak', 'run', 'com.dosbox_x.DOSBox-X', '-version'], |
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) |
||||||
|
return True |
||||||
|
except FileNotFoundError: |
||||||
|
messagebox.showerror("Error", "DOSBox-X is not installed or not in PATH.\n" |
||||||
|
"Please install DOSBox-X from https://dosbox-x.com/") |
||||||
|
return False |
||||||
Loading…
Reference in new issue