From ca2a6d93a60399305a0f1c7bf30776088fda7568 Mon Sep 17 00:00:00 2001 From: ApfelTeeSaft <91074565+ApfelTeeSaft@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:07:32 +0200 Subject: [PATCH] Add Multi Platform Support MacOS and Linux Support Refined Build Setup --- Makefile | 191 +++++++++++++++++++++ README.md | 283 +++++++++++++++++++++++-------- build_instructions.txt | 80 ++++++++- config.py | 65 ++++++-- ls_connection.py | 33 ++-- main_window.py | 163 ++++++++++++------ note_reader.py | 107 +++++++----- requirements.txt | 26 +++ run_splitnoes.py | 94 +++++++++++ setting_handler.py | 174 ++++++++++--------- setup.bat | 237 ++++++++++++++++++++++++++ setup.sh | 368 +++++++++++++++++++++++++++++++++++++++++ setup_exe.py | 11 -- setup_linux.py | 108 ++++++++++++ setup_mac.py | 55 ++++++ setup_windos.py | 75 +++++++++ 16 files changed, 1799 insertions(+), 271 deletions(-) create mode 100644 Makefile create mode 100644 requirements.txt create mode 100644 run_splitnoes.py create mode 100644 setup.bat create mode 100644 setup.sh delete mode 100644 setup_exe.py create mode 100644 setup_linux.py create mode 100644 setup_mac.py create mode 100644 setup_windos.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..38f9d36 --- /dev/null +++ b/Makefile @@ -0,0 +1,191 @@ +# Makefile for SplitNotes cross-platform builds + +# Variables +PYTHON := python3 +PIP := pip3 +APP_NAME := SplitNotes +VERSION := 1.0.0 + +# Default target +.PHONY: help +help: + @echo "SplitNotes Build System" + @echo "======================" + @echo "" + @echo "Available targets:" + @echo " setup - Install build dependencies" + @echo " run - Run from source" + @echo " clean - Clean build artifacts" + @echo " build-windows - Build Windows executable" + @echo " build-macos - Build macOS application" + @echo " build-linux - Build Linux executable" + @echo " build-all - Build for all platforms (if tools available)" + @echo " package-linux - Create Linux package structure" + @echo " test - Run basic tests" + @echo " lint - Run code linting" + @echo " format - Format code with black" + @echo "" + +# Setup development environment +.PHONY: setup +setup: + @echo "Setting up development environment..." + $(PIP) install --upgrade pip + @echo "Basic setup complete." + @echo "" + @echo "For building, install platform-specific tools:" + @echo " Windows: pip install cx_Freeze py2exe" + @echo " macOS: pip install py2app" + @echo " Linux: pip install cx_Freeze" + +# Run from source +.PHONY: run +run: + @echo "Running SplitNotes from source..." + $(PYTHON) main_window.py + +# Clean build artifacts +.PHONY: clean +clean: + @echo "Cleaning build artifacts..." + rm -rf build/ + rm -rf dist/ + rm -rf *.egg-info/ + rm -rf __pycache__/ + rm -rf resources/__pycache__/ + find . -name "*.pyc" -delete + find . -name "*.pyo" -delete + @echo "Clean complete." + +# Build Windows executable +.PHONY: build-windows +build-windows: + @echo "Building Windows executable..." + @if command -v python >/dev/null 2>&1; then \ + python setup_windows.py build; \ + else \ + echo "Error: Python not found. Install Python for Windows."; \ + exit 1; \ + fi + @echo "Windows build complete. Check build/ directory." + +# Build macOS application +.PHONY: build-macos +build-macos: + @echo "Building macOS application..." + @if [ "$$(uname)" = "Darwin" ]; then \ + $(PYTHON) setup_mac.py py2app; \ + echo "macOS build complete. Check dist/ directory."; \ + else \ + echo "Error: macOS build must be run on macOS."; \ + exit 1; \ + fi + +# Build Linux executable +.PHONY: build-linux +build-linux: + @echo "Building Linux executable..." + $(PYTHON) setup_linux.py build + @echo "Linux build complete. Check build/ directory." + +# Create Linux package structure +.PHONY: package-linux +package-linux: build-linux + @echo "Creating Linux package..." + $(PYTHON) setup_linux.py package + @echo "Linux package complete." + +# Build for all platforms (if tools are available) +.PHONY: build-all +build-all: + @echo "Building for all available platforms..." + @if command -v python >/dev/null 2>&1 && python -c "import cx_Freeze" 2>/dev/null; then \ + echo "Building Windows..."; \ + make build-windows; \ + else \ + echo "Skipping Windows build (cx_Freeze not available)"; \ + fi + @if [ "$$(uname)" = "Darwin" ] && $(PYTHON) -c "import py2app" 2>/dev/null; then \ + echo "Building macOS..."; \ + make build-macos; \ + else \ + echo "Skipping macOS build (not on macOS or py2app not available)"; \ + fi + @if $(PYTHON) -c "import cx_Freeze" 2>/dev/null; then \ + echo "Building Linux..."; \ + make build-linux; \ + else \ + echo "Skipping Linux build (cx_Freeze not available)"; \ + fi + +# Basic functionality test +.PHONY: test +test: + @echo "Running basic tests..." + @echo "Testing imports..." + $(PYTHON) -c "import config; import ls_connection; import note_reader; import setting_handler; print('All modules imported successfully')" + @echo "Testing configuration..." + $(PYTHON) -c "import config; print(f'App: {config.APP_NAME} v{config.APP_VERSION}')" + @echo "Testing note parsing..." + @echo "Test note 1\n\nTest note 2" > test_notes.txt + $(PYTHON) -c "import note_reader; notes = note_reader.get_notes('test_notes.txt', 'new_line'); print(f'Parsed {len(notes)} notes')" + rm -f test_notes.txt + @echo "Basic tests passed." + +# Code linting (if flake8 is available) +.PHONY: lint +lint: + @if $(PYTHON) -c "import flake8" 2>/dev/null; then \ + echo "Running flake8 linting..."; \ + $(PYTHON) -m flake8 *.py --max-line-length=100 --ignore=E501,W503; \ + else \ + echo "flake8 not available. Install with: pip install flake8"; \ + fi + +# Code formatting (if black is available) +.PHONY: format +format: + @if $(PYTHON) -c "import black" 2>/dev/null; then \ + echo "Formatting code with black..."; \ + $(PYTHON) -m black *.py --line-length=100; \ + else \ + echo "black not available. Install with: pip install black"; \ + fi + +# Create resources directory with placeholder files +.PHONY: setup-resources +setup-resources: + @echo "Setting up resources directory..." + @mkdir -p resources + @if [ ! -f resources/green.png ]; then \ + echo "Creating placeholder green.png..."; \ + touch resources/green.png; \ + fi + @if [ ! -f resources/red.png ]; then \ + echo "Creating placeholder red.png..."; \ + touch resources/red.png; \ + fi + @if [ ! -f resources/settings_icon.png ]; then \ + echo "Creating placeholder settings_icon.png..."; \ + touch resources/settings_icon.png; \ + fi + @echo "Resources directory ready. Replace placeholder files with actual icons." + +# Development server (just runs the app) +.PHONY: dev +dev: run + +# Show system information +.PHONY: info +info: + @echo "System Information:" + @echo "==================" + @echo "OS: $$(uname -s)" + @echo "Architecture: $$(uname -m)" + @echo "Python: $$($(PYTHON) --version)" + @echo "Python Path: $$(which $(PYTHON))" + @echo "" + @echo "Python Modules:" + @$(PYTHON) -c "import sys; print('tkinter:', 'available' if 'tkinter' in sys.modules or __import__('tkinter') else 'missing')" 2>/dev/null || echo "tkinter: missing" + @$(PYTHON) -c "import socket; print('socket: available')" 2>/dev/null || echo "socket: missing" + @$(PYTHON) -c "import threading; print('threading: available')" 2>/dev/null || echo "threading: missing" \ No newline at end of file diff --git a/README.md b/README.md index 3975d5c..166a6ba 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,225 @@ # SplitNotes -Software for syncing notes with LiveSplit using the LiveSplit server component. - -Splitnotes automatically shows notes for the split you are currently on. + +Cross-platform software for syncing notes with LiveSplit using the LiveSplit server component. + +SplitNotes automatically shows notes for the split you are currently on, with support for Windows, macOS, and Linux. ![Screenshot 1](http://i.imgur.com/CMlF2pj.png) ![Screenshot 2](http://i.imgur.com/4Ei2IiJ.png) -## Install -1. Download the latest version of LiveSplit Server Component from [this](https://github.com/LiveSplit/LiveSplit.Server/releases) site. -2. Unzip and move the files to the component folder in your LiveSplit install (...\LiveSplit\Components) -3. Download the latest version of SplitNotes from [this](https://github.com/joelnir/SplitNotes/releases) page. -4. Unzip SplitNotes anywhere. +## Features -## How To Use -**Connect to LiveSplit** -1. Launch Splitnotes.exe -2. Launch LiveSplit -3. Go to "Edit Layout" -> "+" -> "Control" -> "LiveSplit Server". Hit ok. -4. In LiveSplit, select "Control" -> "Start Server". -5. SplitNotes should now be connected to LiveSplit. If connection is active the Icon for SplitNotes is green. +* **Cross-platform support** - Works on Windows, macOS, and Linux +* **Automatic note display** based on the active split in LiveSplit +* **Preview functionality** - Use arrow keys to preview notes when no run is active +* **Dual layout modes** - Single or double layout to show current and next split notes +* **Customizable appearance** - Adjustable fonts, colors, and window size +* **Flexible note format** - Support for custom split separators +* **Live connection status** - Visual indicator of LiveSplit connection -**Format your notes** -It is recommended to use a .txt file to store your notes. -Note files should be formatted as following: - -* Normal text is treated as notes -* Text in bracket ([some text]) is ignored from notes, this could be used to write down titles or other comments to keep the note file tidy. -* A line only containing a split separator, such as a word (for example "\") or a simple newline, signals that notes for a specific split is over. You can set what you want to use as a split separator in the settings menu of SplitNotes. - -Example with newline as separator: - ->[Split1] ->these are some notes for split 1. -> ->These are some notes for split2. ->As you can see a title in brackets is not neccesary. ->A simple new line is enough to separate notes for different splits. -> ->Also some notes for split 3. ->You don't have to [ worry abut using brackets [ in the middle of a row]. +## System Requirements -Example with "-end-" as separator: - ->[Split1] ->these are some notes for split 1. ->-end- ->These are some notes for split2. -> ->Now it is fine to use linebreaks in the middle of the notes. ->This will still be on the notes for split 2 ->-end- ->Also some notes for split 3. ->You don't have to [ worry abut using brackets [ in the middle of a row]. - -**Load Notes** -1. Right-Click in SplitNotes and select "Load Notes". -2. Choose your text file. -3. Make sure that notes for the right amount of splits have been loaded. - -**Change Settings** -1. Right-Click in SplitNotes and select "Settings" -2. The settings menu will show up. Here you can set things like font, font size, text and background color, split separator and the server port. - -## Features - -* Automatically displayed notes based on the active split in LiveSplit. -* Ability to preview notes in the software when no run is going on by using the right and left arrow keys. -* Two different font sizes to make sure that notes are easy to read. -* Double layout to preview notes for both current and next split. -* Multiple settings to change the look of the text. -* Ability to set a custom port for interaction with the LiveSplit Server. +- **Python 3.6+** (for running from source) +- **LiveSplit** with Server Component +- **Operating System**: Windows 7+, macOS 10.12+, or Linux with X11 -#### Development -Written in mainly procedural Python using tkinter GUI library. -Made by Joelnir. +### Platform-Specific Requirements + +**Windows:** +- No additional requirements for compiled version +- For source: Python with tkinter (included in standard installation) + +**macOS:** +- No additional requirements for .app bundle +- For source: Python 3 with tkinter + +**Linux:** +- For compiled version: glibc 2.17+ +- For source: `python3`, `python3-tk` + ```bash + # Ubuntu/Debian + sudo apt install python3 python3-tk + + # Fedora/RHEL + sudo dnf install python3 python3-tkinter + + # Arch + sudo pacman -S python python-tkinter + ``` + +## Installation + +### Method 1: Download Compiled Version +1. Download the latest release for your platform from the [releases page](https://github.com/apfelteesaft/SplitNotes/releases) +2. Extract the archive +3. Run the executable (`SplitNotes.exe`, `SplitNotes.app`, or `splitnotes`) + +### Method 2: Install LiveSplit Server Component +1. Download the latest version of LiveSplit Server Component from [this site](https://github.com/LiveSplit/LiveSplit.Server/releases) +2. Unzip and move the files to the component folder in your LiveSplit install (`...\LiveSplit\Components`) + +### Method 3: Run from Source +1. Ensure Python 3.6+ is installed +2. Download/clone this repository +3. Create a `resources/` folder with the icon files +4. Run: `python main_window.py` (or `python3 main_window.py` on Linux/macOS) + +## Usage + +### Connecting to LiveSplit + +1. **Launch SplitNotes** +2. **Launch LiveSplit** +3. In LiveSplit: **Edit Layout** → **+** → **Control** → **LiveSplit Server** → **OK** +4. In LiveSplit: **Control** → **Start Server** +5. SplitNotes should connect automatically (green icon = connected, red = disconnected) + +### Loading Notes + +**Right-click** in SplitNotes and select **"Load Notes"** to choose your text file. + +### Note File Format + +Notes should be stored in a text file with the following format: + +#### Using Newline Separator (Default) +``` +[Split 1 Title] +These are notes for the first split. +You can write multiple lines. + +[Split 2 Title] +Notes for split 2 go here. +Empty line above separates the splits. + +[Split 3 Title] +Final split notes. +``` + +#### Using Custom Separator +``` +[Split 1 Title] +Notes for split 1. +Multiple lines are fine. +-end- +[Split 2 Title] +Notes for split 2. +-end- +[Split 3 Title] +Notes for split 3. +``` + +**Format Rules:** +- Text in brackets `[like this]` is ignored (use for titles/comments) +- Empty lines (or custom separator) divide notes between splits +- All other text becomes part of the notes +- Encoding: UTF-8 recommended, with fallback support for other encodings + +### Controls + +- **Right-click**: Open context menu +- **Left/Right Arrow Keys**: Preview notes when timer is not running +- **Context Menu Options**: + - Load Notes + - Settings (customize appearance, layout, server port, separator) + +### Settings + +Access via right-click → **Settings**: + +- **Font & Size**: Choose from system fonts +- **Colors**: Customize text and background colors +- **Layout**: Single or double (shows current + next split) +- **Server Port**: Change if using non-default LiveSplit port (default: 16834) +- **Split Separator**: Use newlines or custom text to separate splits + +## Platform-Specific Notes + +### Windows +- Windows Defender might flag the executable initially - add an exception if needed +- Supports all Windows versions from 7 onwards + +### macOS +- First launch might require right-click → Open due to Gatekeeper security +- Supports both Intel and Apple Silicon Macs +- Integrates with system dark/light mode + +### Linux +- Requires X11 display server (most desktop environments) +- Tested on Ubuntu, Fedora, and Arch Linux +- Application follows system theme where possible + +## Building from Source + +See `build_instructions.txt` for detailed build instructions for each platform. + +**Quick Build:** +```bash +# Windows +pip install cx_Freeze +python setup_windows.py build + +# macOS +pip install py2app +python setup_mac.py py2app + +# Linux +pip install cx_Freeze +python setup_linux.py build +python setup_linux.py package +``` + +## Troubleshooting + +### Connection Issues +- Ensure LiveSplit Server component is installed and started +- Check that port 16834 is not blocked by firewall +- Verify SplitNotes and LiveSplit are on the same machine + +### Display Issues +- Update graphics drivers if experiencing rendering problems +- Try different font settings if text appears corrupted +- Ensure display scaling is set appropriately + +### File Loading Issues +- Verify the notes file is not corrupted or locked by another program +- Try saving the file with UTF-8 encoding +- Check file permissions (especially on Linux/macOS) + +### Performance Issues +- Close unnecessary applications if SplitNotes becomes slow +- Try reducing font size or window size +- Ensure adequate system memory is available + +## Development + +**Language**: Python 3.6+ +**GUI Framework**: tkinter (cross-platform) +**Architecture**: Event-driven with threaded network communication +**License**: MIT + +**Key Files:** +- `main_window.py` - Main application and GUI +- `ls_connection.py` - LiveSplit server communication +- `note_reader.py` - Note file parsing +- `setting_handler.py` - Configuration management +- `config.py` - Application constants and platform detection + +## Contributing + +Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests. + +## License + +MIT License - see `license.txt` for details. + +## Credits + +**Originally Created by**: joeloskarsson +**Ported to MacOS, Linux and refind Build Setup by**: ApfelTeeSaft +**Original Version**: Windows-only Python application +**Cross-Platform Version**: Enhanced with macOS and Linux support + +--- + +*For more information and updates, visit the [GitHub repository](https://github.com/apfelteesaft/SplitNotes).* \ No newline at end of file diff --git a/build_instructions.txt b/build_instructions.txt index a329bc3..8c90636 100644 --- a/build_instructions.txt +++ b/build_instructions.txt @@ -1,6 +1,76 @@ ---- Build Instructions --- -Using py2exe and python 3.4 +--- Cross-Platform Build Instructions --- -From the main directory run "python setup_exe.py py2exe". -This creates a new "dist" folder with the SplitNotes .exe and needed data files. -Zip up this folder for distribution. \ No newline at end of file +REQUIREMENTS: +- Python 3.6 or higher +- All source files in the same directory +- resources/ folder with icon files + +=== WINDOWS === + +Option 1: Using cx_Freeze (Recommended) +1. Install cx_Freeze: pip install cx_Freeze +2. Run: python setup_windows.py build +3. Executable will be in build/exe.win-amd64-3.x/ (or similar) +4. Distribute the entire build folder + +Option 2: Using py2exe (Alternative) +1. Install py2exe: pip install py2exe +2. Uncomment the py2exe section in setup_windows.py +3. Run: python setup_windows.py py2exe +4. Executable will be in dist/ folder + +=== macOS === + +Using py2app: +1. Install py2app: pip install py2app +2. Run: python setup_mac.py py2app +3. Application bundle will be in dist/SplitNotes.app +4. You can distribute the .app bundle or create a DMG + +To create a DMG (optional): +1. Run: hdiutil create -volname "SplitNotes" -srcfolder dist/SplitNotes.app -ov -format UDZO SplitNotes.dmg + +=== LINUX === + +Using cx_Freeze: +1. Install cx_Freeze: pip install cx_Freeze +2. Run: python setup_linux.py build +3. Run: python setup_linux.py package (creates proper Linux structure) +4. Executable will be in build/splitnotes/ + +Alternative - Run directly: +- Ensure Python 3 and tkinter are installed +- Run: python3 main_window.py + +=== DEVELOPMENT SETUP === + +1. Clone/download all source files +2. Create resources/ directory with icon files: + - green.png (connection active icon) + - red.png (connection inactive icon) + - settings_icon.png (settings window icon) +3. Run directly: python main_window.py + +=== TROUBLESHOOTING === + +Common Issues: +- Missing tkinter: Install python3-tk (Linux) or ensure full Python installation +- Icon loading errors: Ensure resources/ folder is in the same directory +- Socket errors: Check that LiveSplit Server component is installed and running +- Font issues: The app will fallback to system default fonts if specified fonts aren't available + +Platform-specific Notes: +- Windows: Antivirus might flag the executable, add exception if needed +- macOS: First run might require right-click > Open due to Gatekeeper +- Linux: Ensure X11 display is available for GUI + +=== DISTRIBUTION === + +Windows: Distribute the entire build folder or use an installer creator +macOS: Distribute .app bundle or .dmg file +Linux: Distribute build folder or create .deb/.rpm package + +For all platforms, include: +- README with installation/usage instructions +- Sample notes file +- LiveSplit Server component download link \ No newline at end of file diff --git a/config.py b/config.py index 8bfcd61..ce2c9cb 100644 --- a/config.py +++ b/config.py @@ -1,4 +1,6 @@ # CONFIG FILE WITH CONSTANTS +import platform +import os # Livesplit connection HOST = "localhost" @@ -56,7 +58,7 @@ RUNNING_ALERT = "RUNNING" # Font for gui widgets GUI_FONT = ("arial", 12) -# Files tht should be displayed and opened a notes +# Files that should be displayed and opened as notes TEXT_FILES = [ ("Text Files", ("*.txt", "*.log", "*.asc", "*.conf", "*.cfg")), ('All', '*') @@ -98,12 +100,55 @@ SETTINGS_OPTIONS = {"FONT": "Font", "NEW_LINE_SEPARATOR": "Newline as split separator", "CUSTOM_SEPARATOR": "Custom split separator"} -# Fonts that can be selected -AVAILABLE_FONTS = ("arial", - "courier new", - "fixedsys", - "ms sans serif", - "ms serif", - "system", - "times new roman", - "verdana") \ No newline at end of file +# Platform-specific fonts +def get_available_fonts(): + """Returns available fonts based on the platform.""" + system = platform.system() + + if system == "Darwin": # macOS + return ("Arial", + "Courier New", + "Georgia", + "Helvetica", + "Monaco", + "Times New Roman", + "Verdana", + "SF Pro Display") + elif system == "Linux": + return ("Arial", + "Courier New", + "DejaVu Sans", + "Liberation Sans", + "Liberation Serif", + "Ubuntu", + "Times New Roman", + "Verdana") + else: # Windows + return ("arial", + "courier new", + "fixedsys", + "ms sans serif", + "ms serif", + "system", + "times new roman", + "verdana") + +AVAILABLE_FONTS = get_available_fonts() + +# Platform-specific configurations +PLATFORM = platform.system() +IS_WINDOWS = PLATFORM == "Windows" +IS_MACOS = PLATFORM == "Darwin" +IS_LINUX = PLATFORM == "Linux" + +# Default font adjustments for different platforms +if IS_MACOS: + GUI_FONT = ("SF Pro Display", 12) +elif IS_LINUX: + GUI_FONT = ("Ubuntu", 12) + +# Application info for packaging +APP_NAME = "SplitNotes" +APP_VERSION = "1.0.0" +APP_AUTHOR = "ApfelTeeSaft" +APP_DESCRIPTION = "Software for syncing notes with LiveSplit using the LiveSplit server component." \ No newline at end of file diff --git a/ls_connection.py b/ls_connection.py index a607f03..28a3c72 100644 --- a/ls_connection.py +++ b/ls_connection.py @@ -11,6 +11,7 @@ import select # used for checking if socket has data pending def ls_connect(ls_socket, call_func, window, server_port): """Connects given socket to the livesplit server.""" con_thread = Thread(target=try_connection, args=(ls_socket, call_func, window, server_port)) + con_thread.daemon = True # Make thread daemon so it doesn't prevent app exit con_thread.start() @@ -36,7 +37,10 @@ def try_connection(ls_socket, call_func, window, server_port): def close_socket(com_socket): """Closes given socket.""" - com_socket.close() + try: + com_socket.close() + except: + pass # Socket might already be closed def check_connection(ls_socket): @@ -56,7 +60,7 @@ def send_to_ls(ls_socket, command): Sends given command to ls using given socket. If connected is False, tries to send without socket being connected to ls. Returns the response, or False if an error occurs. - Check config.LS_COMMANDS for avaiable commands. + Check config.LS_COMMANDS for available commands. """ try: @@ -64,13 +68,17 @@ def send_to_ls(ls_socket, command): except: return False - socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT) - if socket_ready[0]: - try: - return (ls_socket.recv(1000)).decode("utf-8") - except: + # Use select to check if data is available, with timeout + try: + socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT) + if socket_ready[0]: + try: + return (ls_socket.recv(1000)).decode("utf-8") + except: + return False + else: return False - else: + except: return False @@ -85,7 +93,10 @@ def get_split_index(ls_socket): ls_data = send_to_ls(ls_socket, "cur_split_index") if not isinstance(ls_data, bool): - return int(ls_data) + try: + return int(ls_data.strip()) + except: + return False else: return False @@ -98,6 +109,6 @@ def get_split_name(ls_socket): ls_data = send_to_ls(ls_socket, "cur_split_name") if ls_data: - return ls_data + return ls_data.strip() else: - return False + return False \ No newline at end of file diff --git a/main_window.py b/main_window.py index 1c02d7c..5e40a09 100644 --- a/main_window.py +++ b/main_window.py @@ -3,6 +3,7 @@ from tkinter import messagebox import os import sys +import platform import config import ls_connection as con @@ -22,18 +23,28 @@ runtime_info = { root = tkinter.Tk() -red_path = os.path.join( - str(os.path.dirname(os.path.realpath(sys.argv[0]))), - config.RESOURCE_FOLDER, - config.ICONS["RED"] -) -green_path = os.path.join( - str(os.path.dirname(os.path.realpath(sys.argv[0]))), - config.RESOURCE_FOLDER, - config.ICONS["GREEN"] -) -red_icon = tkinter.Image("photo", file=red_path) -green_icon = tkinter.Image("photo", file=green_path) +# Cross-platform path handling +if getattr(sys, 'frozen', False): + # Running as compiled executable + application_path = os.path.dirname(sys.executable) +else: + # Running as script + application_path = os.path.dirname(os.path.realpath(__file__)) + +red_path = os.path.join(application_path, config.RESOURCE_FOLDER, config.ICONS["RED"]) +green_path = os.path.join(application_path, config.RESOURCE_FOLDER, config.ICONS["GREEN"]) + +# Initialize icons +red_icon = None +green_icon = None + +try: + if os.path.exists(red_path): + red_icon = tkinter.PhotoImage(file=red_path) + if os.path.exists(green_path): + green_icon = tkinter.PhotoImage(file=green_path) +except Exception as e: + print(f"Warning: Could not load icons: {e}") def update(window, com_socket, text1, text2): @@ -60,7 +71,7 @@ def update(window, com_socket, text1, text2): # Connection error com_socket = test_connection(com_socket, window, text1, text2) else: - # index retrieved succesfully + # index retrieved successfully if new_index == -1: # timer not running if runtime_info["timer_running"]: @@ -158,10 +169,13 @@ def server_found(window): def update_icon(active, window): """Updates icon of window depending on "active" variable""" - if active: - window.tk.call('wm', 'iconphoto', window._w, green_icon) - else: - window.tk.call('wm', 'iconphoto', window._w, red_icon) + try: + if active and green_icon: + window.iconphoto(False, green_icon) + elif not active and red_icon: + window.iconphoto(False, red_icon) + except Exception: + pass # Icon update failed, continue without icon def update_title(name, window): @@ -205,7 +219,10 @@ def set_single_layout(window, box1, box2): def show_popup(event, menu): """Displays given popup menu at cursor position.""" - menu.post(event.x_root, event.y_root) + try: + menu.post(event.x_root, event.y_root) + except Exception: + pass def menu_load_notes(window, text1, text2, com_socket): @@ -248,10 +265,13 @@ def show_info(info, warning=False): Displays an info popup window. if warning is True window has a warning triangle. """ - if warning: - messagebox.showwarning(info[0], info[1]) - else: - messagebox.showinfo(info[0], info[1]) + try: + if warning: + messagebox.showwarning(info[0], info[1]) + else: + messagebox.showinfo(info[0], info[1]) + except Exception: + print(f"Info: {info[1]}") def update_notes(text1, text2, index): @@ -277,7 +297,7 @@ def update_notes(text1, text2, index): if index <= max_index: text1.insert(tkinter.END, runtime_info["notes"][index]) - # cant disply notes for index+1 + # can't display notes for index+1 if index < max_index: text2.insert(tkinter.END, runtime_info["notes"][index + 1]) @@ -297,7 +317,7 @@ def left_arrow(window, com_socket, text1, text2): def change_preview(window, com_socket, text1, text2, move): """ - Chnges notes that are currently displayed. + Changes notes that are currently displayed. Move is either 1 for next or -1 for previous. """ if runtime_info["notes"] and (not runtime_info["timer_running"]): @@ -311,6 +331,8 @@ def change_preview(window, com_socket, text1, text2, move): if index > max_index: index = max_index + elif index < 0: + index = 0 runtime_info["active_split"] = index @@ -340,10 +362,10 @@ def menu_open_settings(root_wnd, box1, box2, text1, text2, com_socket): Opens the settings menu. """ setting_handler.edit_settings(root_wnd, - (lambda settings: apply_settings(settings, + lambda settings: apply_settings(settings, root_wnd, box1, box2, - text1, text2, com_socket))) + text1, text2, com_socket)) def apply_settings(settings, window, box1, box2, text1, text2, com_socket): @@ -376,26 +398,26 @@ def apply_settings(settings, window, box1, box2, text1, text2, com_socket): new_notes = noter.get_notes(settings["notes"], settings["separator"]) if new_notes: - # Notes loaded correctly - runtime_info["notes"] = new_notes + # Notes loaded correctly + runtime_info["notes"] = new_notes - new_note_length = len(new_notes) + new_note_length = len(new_notes) - if not (new_note_length == old_note_length): - show_info(("Notes Loaded", - ("Loaded notes with " + str(new_note_length) + " splits."))) + if not (new_note_length == old_note_length): + show_info(("Notes Loaded", + ("Loaded notes with " + str(new_note_length) + " splits."))) - if not runtime_info["timer_running"]: - runtime_info["active_split"] = -1 + if not runtime_info["timer_running"]: + runtime_info["active_split"] = -1 - update_GUI(window, com_socket, text1, text2) + update_GUI(window, com_socket, text1, text2) else: show_info(config.ERRORS["NOTES_EMPTY"], True) def save_geometry_settings(width, height): """ - Saves given width and height to settigns file. + Saves given width and height to settings file. """ settings = setting_handler.load_settings() settings["width"] = str(width) @@ -409,7 +431,10 @@ def do_on_close(root_wnd): Saves root_wnd's width and height to the settings file and then closes the window. """ - save_geometry_settings(root_wnd.winfo_width(), root_wnd.winfo_height()) + try: + save_geometry_settings(root_wnd.winfo_width(), root_wnd.winfo_height()) + except: + pass root_wnd.destroy() @@ -427,6 +452,9 @@ def init_UI(root): # Graphical components root.geometry(settings["width"] + "x" + settings["height"]) + # Set minimum window size + root.minsize(300, 200) + box1 = tkinter.Frame(root) box2 = tkinter.Frame(root) @@ -475,20 +503,18 @@ def init_UI(root): popup = tkinter.Menu(root, tearoff=0) popup.add_command( label=config.MENU_OPTIONS["LOAD"], - command=(lambda: menu_load_notes(root, text1, text2, com_socket)) + command=lambda: menu_load_notes(root, text1, text2, com_socket) ) popup.add_command( label=config.MENU_OPTIONS["SETTINGS"], - command=(lambda: menu_open_settings(root, box1, box2, text1, text2, com_socket)) + command=lambda: menu_open_settings(root, box1, box2, text1, text2, com_socket) ) # Set default window icon and title - root.tk.call('wm', 'iconphoto', root._w, red_icon) + update_icon(False, root) update_title(config.DEFAULT_WINDOW["TITLE"], root) # Check if notes can be loaded from settings - settings = setting_handler.load_settings() - if settings["notes"] and noter.file_exists(settings["notes"]): notes = noter.get_notes(settings["notes"], settings["separator"]) @@ -497,19 +523,56 @@ def init_UI(root): update_GUI(root, com_socket, text1, text2) # Event binds - root.bind("", (lambda e: adjust_content(root, box1, box2))) - root.bind("", (lambda e: show_popup(e, popup))) - root.bind("", (lambda e: right_arrow(root, com_socket, text1, text2))) - root.bind("", (lambda e: left_arrow(root, com_socket, text1, text2))) + root.bind("", lambda e: adjust_content(root, box1, box2) if e.widget == root else None) + + # Platform-specific right-click handling + if config.IS_MACOS: + root.bind("", lambda e: show_popup(e, popup)) + root.bind("", lambda e: show_popup(e, popup)) + else: + root.bind("", lambda e: show_popup(e, popup)) + + root.bind("", lambda e: right_arrow(root, com_socket, text1, text2)) + root.bind("", lambda e: left_arrow(root, com_socket, text1, text2)) # Window close bind - root.protocol("WM_DELETE_WINDOW", (lambda: do_on_close(root))) + root.protocol("WM_DELETE_WINDOW", lambda: do_on_close(root)) # call update loop update(root, com_socket, text1, text2) - root.geometry(settings["width"] + "x" + settings["height"]) -init_UI(root) +def main(): + """Main entry point for the application.""" + try: + # Set up the main window + root.title(config.DEFAULT_WINDOW["TITLE"]) + + # Platform-specific optimizations + if config.IS_MACOS: + # Use native look on macOS + try: + root.tk.call('tk', 'scaling', 1.0) + except: + pass + + # Initialize UI + init_UI(root) + + # Start the main loop + root.mainloop() + + except KeyboardInterrupt: + print("\nApplication interrupted by user") + except Exception as e: + print(f"An error occurred: {e}") + messagebox.showerror("Error", f"An unexpected error occurred:\n{e}") + finally: + try: + root.destroy() + except: + pass -root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/note_reader.py b/note_reader.py index f0edefe..258ec40 100644 --- a/note_reader.py +++ b/note_reader.py @@ -25,28 +25,38 @@ def get_note_lines(file_path): """ # check so file isn't too big - if path.getsize(file_path) > config.MAX_FILE_SIZE: + try: + if path.getsize(file_path) > config.MAX_FILE_SIZE: + return False + except: return False try: - notes_file = open(file_path, "r") - except: + # Try different encodings for cross-platform compatibility + encodings = ['utf-8', 'utf-8-sig', 'latin-1', 'cp1252'] + notes_file = None + + for encoding in encodings: + try: + notes_file = open(file_path, "r", encoding=encoding) + break + except UnicodeDecodeError: + continue + + if notes_file is None: + return False + + except Exception: return False # read file line per line f_lines = [] - keep_reading = True - while keep_reading: - - try: - cur_line = notes_file.readline() - except: - return False - - if cur_line: - f_lines.append(cur_line) - else: - keep_reading = False + try: + with notes_file: + for line in notes_file: + f_lines.append(line) + except Exception: + return False return f_lines @@ -58,28 +68,25 @@ def decode_notes(note_lines, separator): Returns the list containing the notes for every split. """ - #Check if newline is being used as separator - + # Check if newline is being used as separator if separator == config.NEWLINE_CONSTANT: - separator = "" # left after stripping newline + separator = "" # left after stripping newline def is_title(line): if not line: return False - - return (line[0] == "[") and (line[-1] == "]") + stripped = line.strip() + return stripped.startswith("[") and stripped.endswith("]") def is_separator(line): - return (line == separator) + return line.strip() == separator.strip() def is_newline(s): - return (s == "\n") + return s.strip() == "" def remove_new_line(line): - if (len(line) >= 1) and (is_newline(line[-1])): - return line[:-1] - else: - return line + # Remove trailing newline characters + return line.rstrip('\n\r') note_list = [] cur_notes = "" @@ -87,16 +94,25 @@ def decode_notes(note_lines, separator): for line in note_lines: line = remove_new_line(line) - if is_separator(line): - if cur_notes: - note_list.append(cur_notes) + if separator == "" and is_newline(line): + # Using newline as separator + if cur_notes.strip(): + note_list.append(cur_notes.strip()) + cur_notes = "" + elif separator != "" and is_separator(line): + # Using custom separator + if cur_notes.strip(): + note_list.append(cur_notes.strip()) cur_notes = "" else: if not is_title(line): - cur_notes += line + "\n" # newline + if cur_notes: + cur_notes += "\n" + cur_notes += line - if cur_notes: - note_list.append(cur_notes) + # Add the last notes if any + if cur_notes.strip(): + note_list.append(cur_notes.strip()) return note_list @@ -104,10 +120,13 @@ def decode_notes(note_lines, separator): def get_notes(file_path, separator): """ Takes a path to a file and returns a list with the notes - in the file encoded according to the note fromatting. + in the file encoded according to the note formatting. Returns False if file is empty. """ + if not file_exists(file_path): + return False + note_lines = get_note_lines(file_path) if not note_lines: @@ -115,7 +134,7 @@ def get_notes(file_path, separator): note_list = decode_notes(note_lines, separator) - return note_list + return note_list if note_list else False def select_file(): @@ -124,15 +143,23 @@ def select_file(): Returns False upon no file selection. Otherwise returns absolute path to selected file. """ + try: + file = file_dia.askopenfilename( + title="Select Notes File", + filetypes=config.TEXT_FILES + ) - file = file_dia.askopenfilename(filetypes=config.TEXT_FILES) - - if file: - return file - else: + if file: + return file + else: + return False + except Exception: return False def file_exists(file): """Checks if given path leads to an existing file.""" - return path.isfile(file) + try: + return path.isfile(file) + except: + return False \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..641559c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,26 @@ +# Core requirements for SplitNotes +# No external dependencies required for basic functionality +# All required modules are part of Python standard library: +# - tkinter (GUI) +# - socket (network communication) +# - threading (background connections) +# - select (socket polling) +# - os, sys, platform (system integration) + +# Build requirements (install only when building executables) + +# For Windows builds: +# cx-Freeze>=6.14.0 +# py2exe>=0.12.0.1 # Alternative for Windows + +# For macOS builds: +# py2app>=0.28.0 + +# For Linux builds: +# cx-Freeze>=6.14.0 + +# Development requirements: +# flake8>=4.0.0 # Code linting +# black>=22.0.0 # Code formatting + +# Note: Uncomment the build requirements you need based on your target platform \ No newline at end of file diff --git a/run_splitnoes.py b/run_splitnoes.py new file mode 100644 index 0000000..bba8d6d --- /dev/null +++ b/run_splitnoes.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +""" +Cross-platform launcher for SplitNotes +This file can be used as an alternative entry point +""" + +import sys +import os + +# Add the current directory to Python path +current_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, current_dir) + +def check_requirements(): + """Check if all required modules are available""" + required_modules = ['tkinter', 'socket', 'threading', 'select'] + missing_modules = [] + + for module in required_modules: + try: + __import__(module) + except ImportError: + missing_modules.append(module) + + if missing_modules: + print("Error: Missing required modules:") + for module in missing_modules: + print(f" - {module}") + + print("\nInstallation help:") + if 'tkinter' in missing_modules: + print(" Ubuntu/Debian: sudo apt install python3-tk") + print(" Fedora/RHEL: sudo dnf install python3-tkinter") + print(" Arch: sudo pacman -S python-tkinter") + print(" macOS: tkinter should be included with Python") + print(" Windows: tkinter should be included with Python") + + return False + + return True + +def main(): + """Main entry point""" + print("SplitNotes - Cross-platform LiveSplit notes viewer") + print("=" * 50) + + # Check Python version + if sys.version_info < (3, 6): + print("Error: Python 3.6 or higher is required") + print(f"Current version: {sys.version}") + sys.exit(1) + + # Check requirements + if not check_requirements(): + sys.exit(1) + + # Check if main_window.py exists + main_window_path = os.path.join(current_dir, 'main_window.py') + if not os.path.exists(main_window_path): + print("Error: main_window.py not found in current directory") + sys.exit(1) + + # Check if resources directory exists + resources_path = os.path.join(current_dir, 'resources') + if not os.path.exists(resources_path): + print("Warning: resources/ directory not found") + print("Creating resources directory...") + try: + os.makedirs(resources_path) + print("Please add icon files to resources/ directory:") + print(" - green.png (connection active)") + print(" - red.png (connection inactive)") + print(" - settings_icon.png (settings window)") + except OSError as e: + print(f"Could not create resources directory: {e}") + + print("Starting SplitNotes...") + print("Platform:", sys.platform) + + try: + # Import and run the main application + import main_window + # main_window.main() is called automatically when imported + + except KeyboardInterrupt: + print("\nApplication interrupted by user") + except Exception as e: + print(f"Error starting application: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/setting_handler.py b/setting_handler.py index 7d9cab8..76115bf 100644 --- a/setting_handler.py +++ b/setting_handler.py @@ -6,14 +6,22 @@ import sys import config +# Cross-platform path handling +if getattr(sys, 'frozen', False): + # Running as compiled executable + application_path = os.path.dirname(sys.executable) +else: + # Running as script + application_path = os.path.dirname(os.path.realpath(__file__)) + settings_path = os.path.join( - str(os.path.dirname(os.path.realpath(sys.argv[0]))), + application_path, config.RESOURCE_FOLDER, config.SETTINGS_FILE ) settings_icon_path = os.path.join( - str(os.path.dirname(os.path.realpath(sys.argv[0]))), + application_path, config.RESOURCE_FOLDER, config.ICONS["SETTINGS"] ) @@ -27,10 +35,14 @@ def load_settings(): returns a dictionary with all settings. """ + # Ensure resources directory exists + os.makedirs(os.path.dirname(settings_path), exist_ok=True) + # try to open default settings file try: - settings_file = open(settings_path, "r+") - settings_content = get_file_lines(settings_file) + with open(settings_path, "r", encoding='utf-8') as settings_file: + settings_content = settings_file.readlines() + settings_content = [line.strip() for line in settings_content] except: # File not found settings_content = set_default_settings() @@ -65,41 +77,24 @@ def format_settings(file_rows): settings = {} for row in file_rows: - row = row.strip("\n") - parts = row.split("=", 1) + row = row.strip() + if '=' in row: + parts = row.split("=", 1) - if len(parts) == SETTING_PART_LENGTH: - # Strip to remove whitespace at end and beginning - settings[parts[0].strip()] = parts[1].strip() + if len(parts) == SETTING_PART_LENGTH: + # Strip to remove whitespace at end and beginning + settings[parts[0].strip()] = parts[1].strip() return settings -def get_file_lines(file): - """ - Returns a list containing all the lines of the gicen file. - """ - # read file line per line - f_lines = [] - keep_reading = True - while keep_reading: - cur_line = file.readline() - - if cur_line: - f_lines.append(cur_line) - else: - keep_reading = False - - return f_lines - - def validate_settings(settings): """ Checks a settings dictionary so that all the needed settings are present. """ for req_setting in config.REQUIRED_SETTINGS: - if not (req_setting in settings): + if req_setting not in settings: return False if not validate_font_size(settings["font_size"]): @@ -114,11 +109,10 @@ def validate_settings(settings): if not validate_color(settings["background_color"]): return False - if not (settings["font"] in config.AVAILABLE_FONTS): + if settings["font"] not in config.AVAILABLE_FONTS: return False - if not ((settings["double_layout"] == "True") or - (settings["double_layout"] == "False")): + if settings["double_layout"] not in ["True", "False"]: return False if not validate_pixels(settings["width"]): @@ -137,9 +131,14 @@ def set_settings_file_content(content): """ Saves given content to the config file, config.cfg, in the resources directory. """ - settings_file = open(settings_path, "w") - settings_file.write(content) - settings_file.close() + # Ensure directory exists + os.makedirs(os.path.dirname(settings_path), exist_ok=True) + + try: + with open(settings_path, "w", encoding='utf-8') as settings_file: + settings_file.write(content) + except Exception as e: + print(f"Error saving settings: {e}") def edit_settings(root_wnd, apply_method): @@ -152,12 +151,27 @@ def edit_settings(root_wnd, apply_method): width=config.SETTINGS_WINDOW["WIDTH"], height=config.SETTINGS_WINDOW["HEIGHT"]) settings_wnd.title(config.SETTINGS_WINDOW["TITLE"]) - settings_icon = tkinter.Image("photo", file=settings_icon_path) - settings_wnd.tk.call('wm', 'iconphoto', settings_wnd._w, settings_icon) + + # Try to load settings icon + try: + if os.path.exists(settings_icon_path): + settings_icon = tkinter.PhotoImage(file=settings_icon_path) + settings_wnd.iconphoto(False, settings_icon) + except: + pass # Icon loading failed, continue without icon settings = load_settings() settings_wnd.resizable(0, 0) + settings_wnd.transient(root_wnd) # Make it a dialog + settings_wnd.grab_set() # Make it modal + + # Center the window + settings_wnd.geometry(f"{config.SETTINGS_WINDOW['WIDTH']}x{config.SETTINGS_WINDOW['HEIGHT']}") + settings_wnd.update_idletasks() + x = (settings_wnd.winfo_screenwidth() // 2) - (config.SETTINGS_WINDOW['WIDTH'] // 2) + y = (settings_wnd.winfo_screenheight() // 2) - (config.SETTINGS_WINDOW['HEIGHT'] // 2) + settings_wnd.geometry(f"+{x}+{y}") font_label = tkinter.Label(settings_wnd, text=config.SETTINGS_OPTIONS["FONT"], @@ -197,26 +211,24 @@ def edit_settings(root_wnd, apply_method): font_dropdown.configure(font=config.GUI_FONT) # Font Size Selection - font_size_entry = tkinter.Entry(settings_wnd, width=2, font=config.GUI_FONT) + font_size_entry = tkinter.Entry(settings_wnd, width=5, font=config.GUI_FONT) font_size_entry.insert(0, settings["font_size"]) # Text Color Selection text_color = tkinter.Button(settings_wnd, width=3, - height=1, - ) + height=1) if validate_color(settings["text_color"]): - text_color.configure(background=settings["text_color"]) + text_color.configure(background=settings["text_color"]) else: text_color.configure(background="#000000") def text_color_selection(): - choosen_color = colorchooser.askcolor() - if choosen_color[1]: - settings["text_color"] = choosen_color[1] + chosen_color = colorchooser.askcolor(parent=settings_wnd) + if chosen_color[1]: + settings["text_color"] = chosen_color[1] text_color.configure(background=settings["text_color"]) - settings_wnd.focus_force() text_color.configure(command=text_color_selection) @@ -227,22 +239,21 @@ def edit_settings(root_wnd, apply_method): height=1) if validate_color(settings["background_color"]): - bg_color.configure(background=settings["background_color"]) + bg_color.configure(background=settings["background_color"]) else: bg_color.configure(background="#FFFFFF") def bg_color_selection(): - choosen_color = colorchooser.askcolor() - if choosen_color[1]: - settings["background_color"] = choosen_color[1] + chosen_color = colorchooser.askcolor(parent=settings_wnd) + if chosen_color[1]: + settings["background_color"] = chosen_color[1] bg_color.configure(background=settings["background_color"]) - settings_wnd.focus_force() bg_color.configure(command=bg_color_selection) # Server port Selection - port_entry = tkinter.Entry(settings_wnd, width=6, font=config.GUI_FONT) + port_entry = tkinter.Entry(settings_wnd, width=8, font=config.GUI_FONT) port_entry.insert(0, settings["server_port"]) # Double Layout Selection @@ -264,13 +275,11 @@ def edit_settings(root_wnd, apply_method): use_newline = tkinter.BooleanVar() newline_btn = tkinter.Checkbutton(settings_wnd, variable=use_newline, - command= - (lambda: set_separator_active(not use_newline.get())) - ) + command=lambda: set_separator_active(not use_newline.get())) if settings["separator"] == config.NEWLINE_CONSTANT: - newline_btn.select() - set_separator_active(False); + newline_btn.select() + set_separator_active(False) else: separator_entry.insert(0, settings["separator"]) @@ -291,19 +300,19 @@ def edit_settings(root_wnd, apply_method): settings["double_layout"] = encode_boolean_setting(double_layout.get()) if not validate_font_size(chosen_font_size): - msgbox.showerror(config.ERRORS["FONT_SIZE"][0], config.ERRORS["FONT_SIZE"][1]) + msgbox.showerror(config.ERRORS["FONT_SIZE"][0], config.ERRORS["FONT_SIZE"][1], parent=settings_wnd) errors_found = True else: settings["font_size"] = chosen_font_size if not validate_server_port(chosen_port): - msgbox.showerror(config.ERRORS["SERVER_PORT"][0], config.ERRORS["SERVER_PORT"][1]) + msgbox.showerror(config.ERRORS["SERVER_PORT"][0], config.ERRORS["SERVER_PORT"][1], parent=settings_wnd) errors_found = True else: settings["server_port"] = chosen_port if not validate_separator(chosen_separator): - msgbox.showerror(config.ERRORS["SEPARATOR"][0], config.ERRORS["SEPARATOR"][1]) + msgbox.showerror(config.ERRORS["SEPARATOR"][0], config.ERRORS["SEPARATOR"][1], parent=settings_wnd) errors_found = True else: settings["separator"] = chosen_separator @@ -316,9 +325,9 @@ def edit_settings(root_wnd, apply_method): settings_wnd.focus_force() save_btn = tkinter.Button(settings_wnd, - command=control_and_save, - text=config.SETTINGS_WINDOW["SAVE"], - font=config.GUI_FONT) + command=control_and_save, + text=config.SETTINGS_WINDOW["SAVE"], + font=config.GUI_FONT) cancel_btn = tkinter.Button(settings_wnd, command=settings_wnd.destroy, text=config.SETTINGS_WINDOW["CANCEL"], @@ -347,12 +356,21 @@ def edit_settings(root_wnd, apply_method): save_btn.place(x=110, y=350) cancel_btn.place(x=190, y=350) + # Handle window close + def on_closing(): + settings_wnd.grab_release() + settings_wnd.destroy() + + settings_wnd.protocol("WM_DELETE_WINDOW", on_closing) + def validate_color(color): """ Returns whether or not given color is valid in the hexadecimal format. """ - return isinstance(color, str) and len(color) == 7 and color[0] == "#" + if not isinstance(color, str): + return False + return len(color) == 7 and color[0] == "#" and all(c in '0123456789ABCDEFabcdef' for c in color[1:]) def validate_font_size(size): @@ -364,16 +382,16 @@ def validate_font_size(size): except: return False - return 0 < size < 70 + return 6 <= size <= 72 def validate_server_port(port): """ - Returns Whether or not gicen port is a valid server port. + Returns Whether or not given port is a valid server port. """ try: - int(port) - return True + port_num = int(port) + return 1024 <= port_num <= 65535 except: return False @@ -385,7 +403,7 @@ def save_settings(settings): file_content = "" for key in settings.keys(): - file_content += key + "=" + settings[key] + "\n" + file_content += key + "=" + str(settings[key]) + "\n" set_settings_file_content(file_content) @@ -393,33 +411,35 @@ def save_settings(settings): def decode_boolean_setting(setting): """ Decodes a boolean string of "True" or "False" - to the coorect boolean value. + to the correct boolean value. """ - return setting == "True" + return str(setting) == "True" def encode_boolean_setting(value): """ - Encodes a boolean to the string "True" or "False". + Encodes a boolean to the string "True" or "False". """ - if value: - return "True" - else: - return "False" + return "True" if value else "False" def validate_pixels(pixels): """ Checks if given string can be used as a pixel value for height or width. - Height or Width or assumed to never surpass 10000 + Height or Width are assumed to never surpass 10000 """ try: pixels = int(pixels) except: return False - return 0 < pixels < 10000 + return 200 <= pixels <= 10000 def validate_separator(separator): - return separator.strip() + """ + Validates the separator string. + """ + if separator == config.NEWLINE_CONSTANT: + return True + return len(separator.strip()) > 0 \ No newline at end of file diff --git a/setup.bat b/setup.bat new file mode 100644 index 0000000..310362c --- /dev/null +++ b/setup.bat @@ -0,0 +1,237 @@ +@echo off +setlocal enabledelayedexpansion + +REM SplitNotes Windows Setup Script +REM ================================ + +echo. +echo SplitNotes Windows Setup +echo ======================== +echo. + +REM Parse command line argument +set "COMMAND=%~1" +if "%COMMAND%"=="" set "COMMAND=setup" + +REM Jump to appropriate section +if /i "%COMMAND%"=="setup" goto :main_setup +if /i "%COMMAND%"=="check" goto :check_system +if /i "%COMMAND%"=="build" goto :build_app +if /i "%COMMAND%"=="run" goto :run_app +if /i "%COMMAND%"=="test" goto :test_app +if /i "%COMMAND%"=="clean" goto :clean_build +if /i "%COMMAND%"=="help" goto :show_help +if /i "%COMMAND%"=="-h" goto :show_help +if /i "%COMMAND%"=="--help" goto :show_help + +echo Error: Unknown command "%COMMAND%" +goto :show_help + +:main_setup +echo [INFO] Starting full setup... +call :check_system +if errorlevel 1 exit /b 1 +call :setup_resources +call :test_app +if errorlevel 1 exit /b 1 +echo. +echo [SUCCESS] Setup complete! +echo. +echo Next steps: +echo 1. Replace placeholder icons in resources\ with actual PNG files +echo 2. Install LiveSplit Server component +echo 3. Run: setup.bat run +echo. +echo To build executable: setup.bat build +goto :end + +:check_system +echo [INFO] Checking system requirements... + +REM Check for Python +set "PYTHON_CMD=" +python --version >nul 2>&1 +if !errorlevel! equ 0 ( + for /f "tokens=2" %%i in ('python --version 2^>^&1') do set "PYTHON_VERSION=%%i" + set "PYTHON_CMD=python" +) else ( + python3 --version >nul 2>&1 + if !errorlevel! equ 0 ( + for /f "tokens=2" %%i in ('python3 --version 2^>^&1') do set "PYTHON_VERSION=%%i" + set "PYTHON_CMD=python3" + ) +) + +if "%PYTHON_CMD%"=="" ( + echo [ERROR] Python not found! + echo Please install Python 3.6+ from python.org + echo Make sure to check "Add Python to PATH" during installation + exit /b 1 +) + +echo [SUCCESS] Python found: %PYTHON_CMD% ^(%PYTHON_VERSION%^) + +REM Check Python version (basic check for 3.x) +echo %PYTHON_VERSION% | findstr /r "^3\." >nul +if errorlevel 1 ( + echo [ERROR] Python 3.6+ required, found %PYTHON_VERSION% + exit /b 1 +) + +REM Check required modules +echo [INFO] Checking required modules... + +%PYTHON_CMD% -c "import tkinter" >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] tkinter not available + echo tkinter should be included with Python. Try reinstalling Python. + exit /b 1 +) else ( + echo [SUCCESS] tkinter available +) + +for %%m in (socket threading select os sys platform) do ( + %PYTHON_CMD% -c "import %%m" >nul 2>&1 + if errorlevel 1 ( + echo [ERROR] %%m not available + exit /b 1 + ) else ( + echo [SUCCESS] %%m available + ) +) + +goto :eof + +:setup_resources +echo [INFO] Setting up resources directory... + +if not exist "resources" ( + mkdir resources + echo [SUCCESS] Created resources directory +) + +REM Create placeholder files if they don't exist +for %%f in (green.png red.png settings_icon.png) do ( + if not exist "resources\%%f" ( + echo. > "resources\%%f" + echo [WARNING] Created placeholder resources\%%f + ) else ( + echo [SUCCESS] resources\%%f exists + ) +) + +echo [WARNING] Icon files are placeholders +echo [INFO] Replace with actual PNG icons for proper functionality + +goto :eof + +:test_app +echo [INFO] Testing application... + +echo [INFO] Testing module imports... +%PYTHON_CMD% -c "import config; import ls_connection; import note_reader; import setting_handler; print('All modules imported successfully')" >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Module import failed + exit /b 1 +) else ( + echo [SUCCESS] Module imports working +) + +echo [INFO] Testing note parsing... +echo Test note 1> test_notes.txt +echo.>> test_notes.txt +echo Test note 2>> test_notes.txt + +%PYTHON_CMD% -c "import note_reader; notes = note_reader.get_notes('test_notes.txt', 'new_line'); print(f'Parsed {len(notes)} notes successfully')" >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Note parsing failed + del test_notes.txt >nul 2>&1 + exit /b 1 +) else ( + echo [SUCCESS] Note parsing working +) + +del test_notes.txt >nul 2>&1 +echo [SUCCESS] Application tests passed + +goto :eof + +:build_app +echo [INFO] Building Windows executable... + +call :check_system +if errorlevel 1 exit /b 1 + +echo [INFO] Installing build dependencies... +%PYTHON_CMD% -m pip install cx_Freeze +if errorlevel 1 ( + echo [ERROR] Failed to install cx_Freeze + exit /b 1 +) + +echo [INFO] Building executable... +%PYTHON_CMD% setup_windows.py build +if errorlevel 1 ( + echo [ERROR] Build failed + exit /b 1 +) + +echo [SUCCESS] Windows build complete! +echo Check the build\ directory for your executable + +goto :end + +:run_app +echo [INFO] Running SplitNotes from source... +call :check_system +if errorlevel 1 exit /b 1 + +echo [INFO] Starting SplitNotes... (Press Ctrl+C to stop) +%PYTHON_CMD% main_window.py + +goto :end + +:clean_build +echo [INFO] Cleaning build artifacts... + +if exist "build" rmdir /s /q "build" +if exist "dist" rmdir /s /q "dist" +if exist "*.egg-info" rmdir /s /q "*.egg-info" + +for /r . %%d in (__pycache__) do ( + if exist "%%d" rmdir /s /q "%%d" +) + +del /s /q "*.pyc" >nul 2>&1 +del /s /q "*.pyo" >nul 2>&1 + +echo [SUCCESS] Build artifacts cleaned + +goto :end + +:show_help +echo SplitNotes Windows Setup Script +echo ================================ +echo. +echo Usage: setup.bat [command] +echo. +echo Commands: +echo setup - Full setup ^(check deps, create resources, test^) +echo check - Check system requirements only +echo build - Install build dependencies and build +echo run - Run the application +echo test - Test the application +echo clean - Clean build artifacts +echo help - Show this help message +echo. +echo Examples: +echo setup.bat setup # First time setup +echo setup.bat run # Run the application +echo setup.bat build # Build executable +echo. + +goto :end + +:end +echo. +pause \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..0862b5f --- /dev/null +++ b/setup.sh @@ -0,0 +1,368 @@ +#!/bin/bash + +# SplitNotes Cross-Platform Setup Script +# This script helps set up SplitNotes on different platforms + +set -e # Exit on any error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Utility functions +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}================================${NC}" +} + +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +print_info() { + echo -e "${BLUE}ℹ $1${NC}" +} + +# Detect operating system +detect_os() { + case "$OSTYPE" in + darwin*) OS="macos" ;; + linux*) OS="linux" ;; + msys*|cygwin*|mingw*) OS="windows" ;; + *) OS="unknown" ;; + esac + + print_info "Detected OS: $OS" +} + +# Check if command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Check Python installation +check_python() { + print_header "Checking Python Installation" + + PYTHON_CMD="" + + if command_exists python3; then + PYTHON_CMD="python3" + elif command_exists python; then + PYTHON_VERSION=$(python --version 2>&1 | cut -d' ' -f2 | cut -d'.' -f1) + if [ "$PYTHON_VERSION" = "3" ]; then + PYTHON_CMD="python" + fi + fi + + if [ -z "$PYTHON_CMD" ]; then + print_error "Python 3 not found!" + echo "Please install Python 3.6 or higher:" + case $OS in + "linux") + echo " Ubuntu/Debian: sudo apt install python3 python3-pip python3-tk" + echo " Fedora/RHEL: sudo dnf install python3 python3-pip python3-tkinter" + echo " Arch: sudo pacman -S python python-pip python-tkinter" + ;; + "macos") + echo " Install from python.org or use Homebrew: brew install python-tk" + ;; + "windows") + echo " Download from python.org (make sure to check 'Add to PATH')" + ;; + esac + exit 1 + fi + + PYTHON_VERSION=$($PYTHON_CMD --version 2>&1 | cut -d' ' -f2) + print_success "Python found: $PYTHON_CMD ($PYTHON_VERSION)" + + # Check if version is 3.6+ + MAJOR=$(echo $PYTHON_VERSION | cut -d'.' -f1) + MINOR=$(echo $PYTHON_VERSION | cut -d'.' -f2) + + if [ "$MAJOR" -eq 3 ] && [ "$MINOR" -ge 6 ]; then + print_success "Python version is compatible" + else + print_error "Python 3.6+ required, found $PYTHON_VERSION" + exit 1 + fi +} + +# Check required modules +check_modules() { + print_header "Checking Required Modules" + + MISSING_MODULES="" + + # Check tkinter + if ! $PYTHON_CMD -c "import tkinter" 2>/dev/null; then + MISSING_MODULES="$MISSING_MODULES tkinter" + print_error "tkinter not available" + else + print_success "tkinter available" + fi + + # Check other standard library modules + for module in socket threading select os sys platform; do + if ! $PYTHON_CMD -c "import $module" 2>/dev/null; then + MISSING_MODULES="$MISSING_MODULES $module" + print_error "$module not available" + else + print_success "$module available" + fi + done + + if [ -n "$MISSING_MODULES" ]; then + print_error "Missing required modules:$MISSING_MODULES" + + case $OS in + "linux") + if [[ $MISSING_MODULES == *"tkinter"* ]]; then + echo "Install tkinter with:" + echo " Ubuntu/Debian: sudo apt install python3-tk" + echo " Fedora/RHEL: sudo dnf install python3-tkinter" + echo " Arch: sudo pacman -S python-tkinter" + fi + ;; + "macos"|"windows") + echo "tkinter should be included with Python. Try reinstalling Python." + ;; + esac + exit 1 + fi +} + +# Create resources directory +setup_resources() { + print_header "Setting Up Resources" + + if [ ! -d "resources" ]; then + mkdir -p resources + print_success "Created resources directory" + fi + + # Create placeholder icon files if they don't exist + for icon in green.png red.png settings_icon.png; do + if [ ! -f "resources/$icon" ]; then + touch "resources/$icon" + print_warning "Created placeholder resources/$icon" + else + print_success "resources/$icon exists" + fi + done + + if [ -f "resources/green.png" ] && [ ! -s "resources/green.png" ]; then + print_warning "Icon files are empty placeholders" + print_info "Replace with actual PNG icons for proper functionality" + fi +} + +# Install build dependencies +install_build_deps() { + print_header "Installing Build Dependencies" + + PIP_CMD="" + if command_exists pip3; then + PIP_CMD="pip3" + elif command_exists pip; then + PIP_CMD="pip" + else + print_error "pip not found!" + exit 1 + fi + + case $OS in + "windows") + print_info "Installing Windows build tools..." + $PIP_CMD install cx_Freeze + print_success "Windows build tools installed" + ;; + "macos") + print_info "Installing macOS build tools..." + $PIP_CMD install py2app + print_success "macOS build tools installed" + ;; + "linux") + print_info "Installing Linux build tools..." + $PIP_CMD install cx_Freeze + print_success "Linux build tools installed" + ;; + esac +} + +# Test the application +test_app() { + print_header "Testing Application" + + print_info "Testing module imports..." + if $PYTHON_CMD -c " +import config +import ls_connection +import note_reader +import setting_handler +print('All modules imported successfully') +" 2>/dev/null; then + print_success "Module imports working" + else + print_error "Module import failed" + exit 1 + fi + + print_info "Testing note parsing..." + echo -e "Test note 1\n\nTest note 2" > test_notes.txt + if $PYTHON_CMD -c " +import note_reader +notes = note_reader.get_notes('test_notes.txt', 'new_line') +print(f'Parsed {len(notes)} notes successfully') +" 2>/dev/null; then + print_success "Note parsing working" + else + print_error "Note parsing failed" + fi + rm -f test_notes.txt + + print_success "Application tests passed" +} + +# Build application +build_app() { + print_header "Building Application" + + case $OS in + "windows") + print_info "Building Windows executable..." + $PYTHON_CMD setup_windows.py build + print_success "Windows build complete (check build/ directory)" + ;; + "macos") + print_info "Building macOS application..." + $PYTHON_CMD setup_mac.py py2app + print_success "macOS build complete (check dist/ directory)" + ;; + "linux") + print_info "Building Linux executable..." + $PYTHON_CMD setup_linux.py build + $PYTHON_CMD setup_linux.py package + print_success "Linux build complete (check build/ directory)" + ;; + esac +} + +# Run the application +run_app() { + print_header "Running SplitNotes" + + print_info "Starting SplitNotes from source..." + print_info "Press Ctrl+C to stop" + $PYTHON_CMD main_window.py +} + +# Show usage information +show_usage() { + echo "SplitNotes Setup Script" + echo "======================" + echo "" + echo "Usage: $0 [command]" + echo "" + echo "Commands:" + echo " setup - Full setup (check deps, create resources, test)" + echo " check - Check system requirements only" + echo " build - Install build dependencies and build" + echo " run - Run the application" + echo " test - Test the application" + echo " clean - Clean build artifacts" + echo " help - Show this help message" + echo "" + echo "Examples:" + echo " $0 setup # First time setup" + echo " $0 run # Run the application" + echo " $0 build # Build executable" + echo "" +} + +# Clean build artifacts +clean_build() { + print_header "Cleaning Build Artifacts" + + rm -rf build/ + rm -rf dist/ + rm -rf *.egg-info/ + find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true + find . -name "*.pyc" -delete 2>/dev/null || true + find . -name "*.pyo" -delete 2>/dev/null || true + + print_success "Build artifacts cleaned" +} + +# Main setup function +main_setup() { + print_header "SplitNotes Setup" + + detect_os + check_python + check_modules + setup_resources + test_app + + print_header "Setup Complete!" + print_success "SplitNotes is ready to use" + echo "" + print_info "Next steps:" + echo " 1. Replace placeholder icons in resources/ with actual PNG files" + echo " 2. Install LiveSplit Server component" + echo " 3. Run: $0 run" + echo "" + print_info "To build executable: $0 build" +} + +# Parse command line arguments +case "${1:-setup}" in + "setup") + main_setup + ;; + "check") + detect_os + check_python + check_modules + ;; + "build") + detect_os + check_python + install_build_deps + build_app + ;; + "run") + detect_os + check_python + run_app + ;; + "test") + detect_os + check_python + test_app + ;; + "clean") + clean_build + ;; + "help"|"-h"|"--help") + show_usage + ;; + *) + print_error "Unknown command: $1" + show_usage + exit 1 + ;; +esac \ No newline at end of file diff --git a/setup_exe.py b/setup_exe.py deleted file mode 100644 index 49bdfa4..0000000 --- a/setup_exe.py +++ /dev/null @@ -1,11 +0,0 @@ -from distutils.core import setup -import py2exe - -setup( - windows=[r'main_window.py'], - options = {'py2exe': {'bundle_files': 2, 'compressed': True}}, - zipfile = None, - data_files = [('resources', ['resources/green.png']), - ('resources', ['resources/red.png']), - ('resources', ['resources/settings_icon.png'])] - ) \ No newline at end of file diff --git a/setup_linux.py b/setup_linux.py new file mode 100644 index 0000000..ddee48e --- /dev/null +++ b/setup_linux.py @@ -0,0 +1,108 @@ +""" +Linux build setup using cx_Freeze +Run: python setup_linux.py build +""" + +import sys +import os +from cx_Freeze import setup, Executable + +# Add the current directory to the path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import config + +# Dependencies are automatically detected, but some modules need help +build_options = { + 'packages': ['tkinter', 'socket', 'threading', 'select', 'os', 'sys', 'platform'], + 'excludes': ['matplotlib', 'numpy', 'scipy', 'PIL', 'pygame', 'PyQt5', 'PyQt6'], + 'include_files': [ + ('resources/', 'resources/'), + ], + 'optimize': 2, + 'build_exe': 'build/linux', +} + +executables = [ + Executable( + 'main_window.py', + base=None, # Console application base for Linux + target_name='splitnotes', + copyright=f'Copyright (c) 2024 {config.APP_AUTHOR}', + ) +] + +setup( + name=config.APP_NAME.lower(), + version=config.APP_VERSION, + description=config.APP_DESCRIPTION, + author=config.APP_AUTHOR, + options={'build_exe': build_options}, + executables=executables +) + +# Alternative setup for creating a proper Linux package structure +def create_linux_package(): + """Create a proper Linux application structure""" + import shutil + import stat + + app_dir = f"build/{config.APP_NAME.lower()}" + + # Create directory structure + os.makedirs(f"{app_dir}/bin", exist_ok=True) + os.makedirs(f"{app_dir}/share/{config.APP_NAME.lower()}", exist_ok=True) + os.makedirs(f"{app_dir}/share/applications", exist_ok=True) + os.makedirs(f"{app_dir}/share/pixmaps", exist_ok=True) + + # Copy executable + if os.path.exists("build/linux/splitnotes"): + shutil.copy2("build/linux/splitnotes", f"{app_dir}/bin/") + + # Make executable + st = os.stat(f"{app_dir}/bin/splitnotes") + os.chmod(f"{app_dir}/bin/splitnotes", st.st_mode | stat.S_IEXEC) + + # Copy resources + if os.path.exists("resources"): + shutil.copytree("resources", f"{app_dir}/share/{config.APP_NAME.lower()}/resources", dirs_exist_ok=True) + + # Create .desktop file for Linux desktop integration + desktop_content = f"""[Desktop Entry] +Version=1.0 +Type=Application +Name={config.APP_NAME} +Comment={config.APP_DESCRIPTION} +Exec={app_dir}/bin/splitnotes +Icon={config.APP_NAME.lower()} +Categories=Utility; +Terminal=false +StartupNotify=true +""" + + with open(f"{app_dir}/share/applications/{config.APP_NAME.lower()}.desktop", 'w') as f: + f.write(desktop_content) + + # Create launch script + launch_script = f"""#!/bin/bash +DIR="$( cd "$( dirname "${{BASH_SOURCE[0]}}" )" &> /dev/null && pwd )" +cd "$DIR" +./bin/splitnotes "$@" +""" + + with open(f"{app_dir}/{config.APP_NAME.lower()}", 'w') as f: + f.write(launch_script) + + # Make launch script executable + st = os.stat(f"{app_dir}/{config.APP_NAME.lower()}") + os.chmod(f"{app_dir}/{config.APP_NAME.lower()}", st.st_mode | stat.S_IEXEC) + + print(f"Linux package created in {app_dir}/") + print(f"Run with: ./{app_dir}/{config.APP_NAME.lower()}") + +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == "package": + create_linux_package() + else: + # Standard build process + pass \ No newline at end of file diff --git a/setup_mac.py b/setup_mac.py new file mode 100644 index 0000000..124d86b --- /dev/null +++ b/setup_mac.py @@ -0,0 +1,55 @@ +""" +macOS build setup using py2app +Run: python setup_mac.py py2app +""" + +import sys +import os +from setuptools import setup + +# Add the current directory to the path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import config + +APP = ['main_window.py'] +DATA_FILES = [ + ('resources', [ + 'resources/green.png', + 'resources/red.png', + 'resources/settings_icon.png' + ]) +] + +OPTIONS = { + 'argv_emulation': True, + 'plist': { + 'CFBundleName': config.APP_NAME, + 'CFBundleDisplayName': config.APP_NAME, + 'CFBundleGetInfoString': config.APP_DESCRIPTION, + 'CFBundleIdentifier': 'com.ApfelTeeSaft.splitnotes', + 'CFBundleVersion': config.APP_VERSION, + 'CFBundleShortVersionString': config.APP_VERSION, + 'NSHumanReadableCopyright': f'Copyright (c) 2024 {config.APP_AUTHOR}', + 'NSHighResolutionCapable': True, + 'LSApplicationCategoryType': 'public.app-category.utilities', + 'NSRequiresAquaSystemAppearance': False, # Support dark mode + }, + 'packages': ['tkinter'], + 'excludes': ['matplotlib', 'numpy', 'scipy', 'PIL', 'pygame'], + 'resources': DATA_FILES, + 'optimize': 2, +} + +setup( + name=config.APP_NAME, + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], + author=config.APP_AUTHOR, + version=config.APP_VERSION, + description=config.APP_DESCRIPTION, + license='MIT', + url='https://github.com/ApfelTeeSaft/SplitNotes', +) \ No newline at end of file diff --git a/setup_windos.py b/setup_windos.py new file mode 100644 index 0000000..1569fcb --- /dev/null +++ b/setup_windos.py @@ -0,0 +1,75 @@ +""" +Windows build setup using cx_Freeze (preferred) or py2exe +Run: python setup_windows.py build +""" + +import sys +import os +from cx_Freeze import setup, Executable + +# Add the current directory to the path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import config + +# Dependencies are automatically detected, but some modules need help +build_options = { + 'packages': ['tkinter', 'socket', 'threading', 'select', 'os', 'sys', 'platform'], + 'excludes': ['matplotlib', 'numpy', 'scipy', 'PIL', 'pygame'], + 'include_files': [ + ('resources/', 'resources/'), + ], + 'optimize': 2, +} + +# Base for Windows GUI application (no console window) +base = 'Win32GUI' if sys.platform == 'win32' else None + +executables = [ + Executable( + 'main_window.py', + base=base, + target_name='SplitNotes.exe', + icon=None, # Add icon path here if you have one + copyright=f'Copyright (c) 2024 {config.APP_AUTHOR}', + ) +] + +setup( + name=config.APP_NAME, + version=config.APP_VERSION, + description=config.APP_DESCRIPTION, + author=config.APP_AUTHOR, + options={'build_exe': build_options}, + executables=executables +) + +# Alternative py2exe setup (uncomment if cx_Freeze is not available) +""" +from distutils.core import setup +import py2exe + +setup( + windows=[{ + 'script': 'main_window.py', + 'dest_base': 'SplitNotes', + 'copyright': f'Copyright (c) 2024 {config.APP_AUTHOR}', + }], + options={ + 'py2exe': { + 'bundle_files': 2, + 'compressed': True, + 'optimize': 2, + 'excludes': ['matplotlib', 'numpy', 'scipy', 'PIL', 'pygame'], + } + }, + zipfile=None, + data_files=[ + ('resources', [ + 'resources/green.png', + 'resources/red.png', + 'resources/settings_icon.png' + ]) + ] +) +""" \ No newline at end of file