mirror of
https://github.com/ApfelTeeSaft/SplitNotes.git
synced 2026-08-26 19:33:39 +00:00
Add Multi Platform Support
MacOS and Linux Support Refined Build Setup
This commit is contained in:
@@ -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"
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
## 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 "\<split\>") 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).*
|
||||
+75
-5
@@ -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.
|
||||
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
|
||||
@@ -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")
|
||||
# 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."
|
||||
+22
-11
@@ -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
|
||||
+113
-50
@@ -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("<Configure>", (lambda e: adjust_content(root, box1, box2)))
|
||||
root.bind("<Button-3>", (lambda e: show_popup(e, popup)))
|
||||
root.bind("<Right>", (lambda e: right_arrow(root, com_socket, text1, text2)))
|
||||
root.bind("<Left>", (lambda e: left_arrow(root, com_socket, text1, text2)))
|
||||
root.bind("<Configure>", lambda e: adjust_content(root, box1, box2) if e.widget == root else None)
|
||||
|
||||
# Platform-specific right-click handling
|
||||
if config.IS_MACOS:
|
||||
root.bind("<Button-2>", lambda e: show_popup(e, popup))
|
||||
root.bind("<Control-Button-1>", lambda e: show_popup(e, popup))
|
||||
else:
|
||||
root.bind("<Button-3>", lambda e: show_popup(e, popup))
|
||||
|
||||
root.bind("<Right>", lambda e: right_arrow(root, com_socket, text1, text2))
|
||||
root.bind("<Left>", 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()
|
||||
+67
-40
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
+97
-77
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'])]
|
||||
)
|
||||
+108
@@ -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
|
||||
@@ -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',
|
||||
)
|
||||
@@ -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'
|
||||
])
|
||||
]
|
||||
)
|
||||
"""
|
||||
Reference in New Issue
Block a user