mirror of https://github.com/Enne2/win9xman
Browse Source
- Removed win95_launcher.sh script and replaced it with a new Python application (win9xman.py) for better functionality and user experience. - Added a new assets directory creation script (create_assets_dir.sh) to ensure necessary directories are set up. - Created a default DOSBox-X configuration file (dosbox.conf) and a template for future configurations (dosbox_template.conf). - Added templates for autoexec configurations for Windows 95 and 98. - Updated requirements.txt to indicate no external dependencies beyond the Python standard library. - Implemented various features in the GUI including HDD image creation, ISO mounting, snapshot management, and settings configuration. - Enhanced user interaction with progress dialogs and confirmation prompts for critical actions.main
10 changed files with 1054 additions and 425 deletions
@ -0,0 +1,3 @@
|
||||
#!/bin/bash |
||||
# This is just a placeholder to create the assets directory |
||||
mkdir -p /home/enne2/Development/win9xman/assets |
||||
@ -0,0 +1,71 @@
|
||||
# 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 |
||||
@ -0,0 +1,2 @@
|
||||
# No external dependencies required beyond Python standard library |
||||
# Python 3.6+ with Tkinter (usually included with Python) |
||||
@ -0,0 +1,71 @@
|
||||
# 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 |
||||
@ -0,0 +1,9 @@
|
||||
# Mount the Windows 95 HDD image as drive C |
||||
imgmount c "${hdd_image}" -t hdd -fs fat |
||||
|
||||
# Mount the Windows 95 drive as E |
||||
mount e "${drive_dir}" |
||||
|
||||
${iso_mount} |
||||
|
||||
${boot_command} |
||||
@ -0,0 +1,9 @@
|
||||
# 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 "${drive_dir}" |
||||
|
||||
${iso_mount} |
||||
|
||||
${boot_command} |
||||
@ -1,383 +0,0 @@
|
||||
#!/bin/bash |
||||
|
||||
# Win95 DOSBox-X Launcher |
||||
# A GUI tool to launch Windows 95 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" |
||||
WIN95_DRIVE="$BASE_DIR/win95_drive" |
||||
ISO_DIR="$BASE_DIR/iso" |
||||
IMG_DIR="$BASE_DIR/disks" |
||||
HDD_IMAGE="$IMG_DIR/win95.img" |
||||
SNAPSHOT_DIR="$BASE_DIR/snapshots_win95" |
||||
DEFAULT_HDD_SIZE=1000 # Default size in MB for HDD image (smaller for Win95) |
||||
MIN_HDD_SIZE=300 # Minimum size in MB (Win95 needs less space) |
||||
MAX_HDD_SIZE=2000 # Maximum size in MB |
||||
|
||||
# Make sure directories exist |
||||
mkdir -p "$WIN95_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 95 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 16 -t hd" > /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_win95.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 95 if installed |
||||
start_win95() { |
||||
# 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 95 HDD image as drive C |
||||
imgmount c "$HDD_IMAGE" -t hdd -fs fat |
||||
c: |
||||
# Start Windows 95 |
||||
win |
||||
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 95 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 |
||||
|
||||
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 95 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 95 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 95 setup (might be different paths for different Win95 CDs) |
||||
if exist setup.exe setup.exe |
||||
if exist win95\setup.exe cd win95 && 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="Windows95_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 |
||||
} |
||||
|
||||
# Function to tune DOSBox-X settings for optimal Windows 95 performance |
||||
tune_settings() { |
||||
local choice=$(zenity --list --title="Tune Windows 95 Settings" \ |
||||
--text="Select a performance profile:" \ |
||||
--column="Profile" --column="Description" \ |
||||
1 "Standard (Balanced settings)" \ |
||||
2 "Fast (Better performance but less accurate)" \ |
||||
3 "Accurate (Slower but more accurate emulation)" \ |
||||
--width=500 --height=200) |
||||
|
||||
if [ -z "$choice" ]; then |
||||
return |
||||
fi |
||||
|
||||
local temp_conf="$BASE_DIR/temp_config_tune.conf" |
||||
cp "$DOSBOX_CONF" "$temp_conf" |
||||
|
||||
case "$choice" in |
||||
1) # Standard profile |
||||
sed -i 's/^cycles=.*/cycles=max 80% limit 15000/' "$temp_conf" |
||||
sed -i 's/^core=.*/core=normal/' "$temp_conf" |
||||
;; |
||||
2) # Fast profile |
||||
sed -i 's/^cycles=.*/cycles=max 100% limit 30000/' "$temp_conf" |
||||
sed -i 's/^core=.*/core=dynamic/' "$temp_conf" |
||||
;; |
||||
3) # Accurate profile |
||||
sed -i 's/^cycles=.*/cycles=max 70% limit 10000/' "$temp_conf" |
||||
sed -i 's/^core=.*/core=normal/' "$temp_conf" |
||||
;; |
||||
esac |
||||
|
||||
# Ask if user wants to make the changes permanent |
||||
zenity --question --title="Save Changes" \ |
||||
--text="Would you like to make these settings permanent?\nIf you select 'No', the changes will only apply to the next session." \ |
||||
--ok-label="Yes" --cancel-label="No" |
||||
|
||||
if [ $? -eq 0 ]; then |
||||
cp "$temp_conf" "$DOSBOX_CONF" |
||||
zenity --info --title="Settings Saved" --text="Performance settings have been saved." |
||||
else |
||||
zenity --info --title="Temporary Settings" --text="Settings will be applied for the next session only." |
||||
fi |
||||
} |
||||
|
||||
# Main menu function |
||||
main_menu() { |
||||
while true; do |
||||
local choice=$(zenity --list --title="Windows 95 DOSBox-X Launcher" \ |
||||
--text="Select an option:" \ |
||||
--column="Option" --column="Description" \ |
||||
1 "Start Windows 95 (if installed)" \ |
||||
2 "Mount ISO and start Windows 95" \ |
||||
3 "Boot from Windows 95 ISO (for installation)" \ |
||||
4 "Format C: drive" \ |
||||
5 "Create snapshot of current disk image" \ |
||||
6 "Restore snapshot" \ |
||||
7 "Tune performance settings" \ |
||||
8 "Exit" \ |
||||
--width=500 --height=380) |
||||
|
||||
case "$choice" in |
||||
1) start_win95 ;; |
||||
2) mount_iso ;; |
||||
3) boot_iso ;; |
||||
4) format_c ;; |
||||
5) create_snapshot ;; |
||||
6) restore_snapshot ;; |
||||
7) tune_settings ;; |
||||
8|"") exit 0 ;; |
||||
esac |
||||
done |
||||
} |
||||
|
||||
# Start the main menu |
||||
main_menu |
||||
@ -0,0 +1,814 @@
|
||||
#!/usr/bin/env python3 |
||||
""" |
||||
Windows 9x Manager - A GUI tool to launch Windows 95/98 in DOSBox-X |
||||
""" |
||||
|
||||
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("<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 |
||||
|
||||
def main(): |
||||
|
||||
root = tk.Tk() |
||||
root.title("Windows 9x Manager") |
||||
|
||||
# Use system theme |
||||
try: |
||||
ttk.Style().theme_use('clam') # A decent cross-platform theme |
||||
except tk.TclError: |
||||
pass # If theme not available, use default |
||||
|
||||
# Create the app |
||||
app = Win9xManager(root) |
||||
|
||||
# Add program icon if available |
||||
icon_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets", "icon.png") |
||||
if os.path.exists(icon_path): |
||||
img = tk.PhotoImage(file=icon_path) |
||||
root.tk.call('wm', 'iconphoto', root._w, img) |
||||
|
||||
# Start the main loop |
||||
root.mainloop() |
||||
|
||||
if __name__ == "__main__": |
||||
main() |
||||
Loading…
Reference in new issue