mirror of
https://github.com/Enne2/win9xman.git
synced 2026-08-06 01:15:44 +00:00
feat: Initialize Windows 9x Manager package and core functionality
- 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.
This commit is contained in:
@@ -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
|
||||
+10
-779
@@ -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
|
||||
# Aggiungi la directory corrente al path per importare i moduli locali
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
[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("<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()
|
||||
|
||||
# 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)
|
||||
|
||||
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")
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user