diff --git a/assets/create_assets_dir.sh b/assets/create_assets_dir.sh deleted file mode 100644 index 664d6cf..0000000 --- a/assets/create_assets_dir.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# This is just a placeholder to create the assets directory -mkdir -p /home/enne2/Development/win9xman/assets diff --git a/logo.png b/assets/logo.png similarity index 100% rename from logo.png rename to assets/logo.png diff --git a/config/dosbox.conf b/config/dosbox.conf deleted file mode 100644 index baeca36..0000000 --- a/config/dosbox.conf +++ /dev/null @@ -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 diff --git a/win98_launcher.sh b/win98_launcher.sh deleted file mode 100755 index 6674b75..0000000 --- a/win98_launcher.sh +++ /dev/null @@ -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 diff --git a/win9xman.py b/win9xman.py index a88f263..e9baec4 100644 --- a/win9xman.py +++ b/win9xman.py @@ -3,791 +3,22 @@ Windows 9x Manager - A GUI tool to launch Windows 95/98 in DOSBox-X """ +import tkinter as tk +from tkinter import ttk, messagebox import os import sys -import subprocess -import shutil -import time -import configparser -import tkinter as tk -from tkinter import ttk, filedialog, messagebox -from tkinter.simpledialog import askstring -from datetime import datetime -from pathlib import Path -import string - -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.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(): - self._create_default_template() - - # Create config from template - template_vars = { - 'memsize': '64', - 'cycles': 'max 80% limit 33000', - 'machine': 'svga_s3', - 'windowresolution': '1024x768', - 'output': 'opengl' - } - self._generate_config_from_template('dosbox_template.conf', self.dosbox_conf, template_vars) - - def _create_default_template(self): - """Create the default DOSBox-X template file""" - template_path = self.templates_dir / "dosbox_template.conf" - - # Make sure templates directory exists - self.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(self, template_name, output_path, variables): - """Generate a configuration file from a template with variable substitution - - Args: - 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 = self.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_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("", 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() - - # 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(self.root) - progress_window.title("Creating HDD Image") - progress_window.geometry("400x100") - progress_window.transient(self.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() - - # 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) - progress_window.destroy() - messagebox.showinfo("Success", f"Disk image of {hdd_size}MB created successfully.") - return True - except subprocess.CalledProcessError: - progress_window.destroy() - messagebox.showerror("Error", "Failed to create HDD image. Please check your permissions.") - return False - - self.root.after(100, create_task) - self.root.wait_window(progress_window) - - return hdd_image.exists() - - def create_temp_config(self, autoexec_content): - """Create a temporary DOSBox-X configuration file with custom autoexec section""" - # Make sure the base config exists - if not self.dosbox_conf.exists(): - self._create_default_config() - - # Create a temp config based on current settings - temp_conf = self.base_dir / "temp_dosbox.conf" - - # Read current config to preserve settings - current_config = {} - config = configparser.ConfigParser() - config.read(self.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 - self._generate_config_from_template('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 - - 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 = self.create_temp_config(autoexec) - - # 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 = self.create_temp_config(autoexec) - - # 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 = self.create_temp_config(autoexec) - - # 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) +# Aggiungi la directory corrente al path per importare i moduli locali +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -def check_requirements(): - """Check if required programs are installed""" - # Check for DOSBox-X - try: - subprocess.run(['flatpak run com.dosbox_x.DOSBox-X', '-version'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) - except FileNotFoundError: - tk.messagebox.showerror("Error", "DOSBox-X is not installed or not in PATH.\n" - "Please install DOSBox-X from https://dosbox-x.com/") - return False - - return True +from win9xman.ui.manager import Win9xManager +from win9xman.utils.system import check_requirements def main(): + """Main entry point for the application""" + # Check if required dependencies are installed + if not check_requirements(): + return root = tk.Tk() root.title("Windows 9x Manager") diff --git a/win9xman/__init__.py b/win9xman/__init__.py new file mode 100644 index 0000000..afbc724 --- /dev/null +++ b/win9xman/__init__.py @@ -0,0 +1,5 @@ +""" +Windows 9x Manager Python Package +""" + +__version__ = "1.0.0" diff --git a/win9xman/core/__init__.py b/win9xman/core/__init__.py new file mode 100644 index 0000000..5b6511f --- /dev/null +++ b/win9xman/core/__init__.py @@ -0,0 +1,3 @@ +""" +Core functionality for Windows 9x Manager +""" diff --git a/win9xman/core/disk.py b/win9xman/core/disk.py new file mode 100644 index 0000000..aac8a49 --- /dev/null +++ b/win9xman/core/disk.py @@ -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] diff --git a/win9xman/ui/__init__.py b/win9xman/ui/__init__.py new file mode 100644 index 0000000..ebf7b06 --- /dev/null +++ b/win9xman/ui/__init__.py @@ -0,0 +1,3 @@ +""" +UI Components for Windows 9x Manager +""" diff --git a/win9xman/ui/manager.py b/win9xman/ui/manager.py new file mode 100644 index 0000000..5ab1a11 --- /dev/null +++ b/win9xman/ui/manager.py @@ -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("", 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) diff --git a/win9xman/utils/__init__.py b/win9xman/utils/__init__.py new file mode 100644 index 0000000..ef110e3 --- /dev/null +++ b/win9xman/utils/__init__.py @@ -0,0 +1,3 @@ +""" +Utility functions for Windows 9x Manager +""" diff --git a/win9xman/utils/config.py b/win9xman/utils/config.py new file mode 100644 index 0000000..d674eba --- /dev/null +++ b/win9xman/utils/config.py @@ -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 diff --git a/win9xman/utils/system.py b/win9xman/utils/system.py new file mode 100644 index 0000000..e5cf3f0 --- /dev/null +++ b/win9xman/utils/system.py @@ -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