9 Commits
Author SHA1 Message Date
ApfelTeeSaft 44ff009021 lol 2025-08-03 12:27:51 +02:00
ApfelTeeSaft d33dbd523e Merge branch 'master' of https://github.com/ApfelTeeSaft/SplitNotes 2025-08-03 11:25:12 +02:00
ApfelTeeSaft 59f075c247 yay 2025-08-03 11:25:10 +02:00
ApfelTeeSaft fdbbc707fc Update README.md 2025-08-01 10:09:30 +02:00
ApfelTeeSaft e19c33b166 Update README.md 2025-08-01 10:08:59 +02:00
ApfelTeeSaft ca2a6d93a6 Add Multi Platform Support
MacOS and Linux Support
Refined Build Setup
2025-08-01 10:07:32 +02:00
joelnir 199acd4847 Added build configuration file and build instructions. 2017-05-16 18:30:08 +02:00
Joel 3ae68fa291 Added MIT license 2017-05-09 22:34:56 +02:00
Joel 9ba8edc8d9 Updated readme with information about 1.2 changes
Updated readme with explanations and examples for the formatting changes in version 1.2.
2016-04-15 22:28:59 +02:00
19 changed files with 3630 additions and 413 deletions
+191
View File
@@ -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"
+219 -54
View File
@@ -1,63 +1,228 @@
# SplitNotes # 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 1](http://i.imgur.com/CMlF2pj.png)
![Screenshot 2](http://i.imgur.com/4Ei2IiJ.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.
## How To Use
**Connect to LiveSplit**
1. Launch Splitnotes.exe
2. Launch LiveSplit
3. Go to "Edit Layout" -> "+" -> "Control" -> "LiveSplit Server". Hit ok. (Let the server use the default port: 16834)
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.
**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
* New Line means notes for a specific split is over.
* 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.
Example:
>[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 som 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 and the server port.
## Features ## Features
* Automatically displayed notes based on the active split in LiveSplit. * **Cross-platform support** - Works on Windows, macOS, and Linux
* Ability to preview notes in the software when no run is going on by using the right and left arrow keys. * **Automatic note display** based on the active split in LiveSplit
* Two different font sizes to make sure that notes are easy to read. * **Preview functionality** - Use arrow keys to preview notes when no run is active
* Double layout to preview notes for both current and next split. * **Dual layout modes** - Single or double layout to show current and next split notes
* Multiple settings to change the look of the text. * **Customizable appearance** - Adjustable fonts, colors, and window size
* Ability to set a custom port for interaction with the LiveSplit Server. * **Flexible note format** - Support for custom split separators
* **Live connection status** - Visual indicator of LiveSplit connection
#### Development ## System Requirements
Written in mainly procedural Python using tkinter GUI library.
Made by Joelnir. - **Python 3.6+** (for running from source)
- **LiveSplit** with Server Component
- **Operating System**: Windows 7+, macOS 10.12+, or Linux with X11
### 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).*
+173
View File
@@ -0,0 +1,173 @@
@echo off
REM Windows Build Script for SplitNotes
REM Requires PyInstaller: pip install pyinstaller
echo Building SplitNotes for Windows...
echo.
REM Check if PyInstaller is installed
python -c "import PyInstaller" 2>nul
if errorlevel 1 (
echo Error: PyInstaller not found. Installing...
pip install pyinstaller
if errorlevel 1 (
echo Failed to install PyInstaller. Please install manually: pip install pyinstaller
pause
exit /b 1
)
)
REM Create build directory
if not exist "build" mkdir build
if not exist "dist" mkdir dist
REM Clean previous builds
if exist "build\*" (
echo Cleaning previous builds...
rmdir /s /q build
mkdir build
)
if exist "dist\*" (
rmdir /s /q dist
mkdir dist
)
REM Check if resources folder exists and has files
if not exist "resources" (
echo Creating resources folder...
mkdir resources
echo Note: Add icon files to resources folder for better appearance:
echo - app_icon.ico (Windows icon)
echo - green.png (connection active)
echo - red.png (connection inactive)
echo - settings_icon.png (settings window)
set RESOURCES_EXIST=false
) else (
dir /b "resources\*" >nul 2>&1
if errorlevel 1 (
echo Resources folder is empty...
set RESOURCES_EXIST=false
) else (
echo Found resources folder with files...
set RESOURCES_EXIST=true
)
)
REM Build the application
echo Building executable...
REM Set icon parameter if icon exists
if exist "resources\app_icon.ico" (
echo Found app icon, including in build...
set ICON_PARAM=--icon="%CD%\resources\app_icon.ico"
) else (
echo No app icon found, building without custom icon...
set ICON_PARAM=
)
REM Build with or without resources depending on availability
if "%RESOURCES_EXIST%"=="true" (
echo Including resources in build...
pyinstaller ^
--onedir ^
--windowed ^
--name "SplitNotes" ^
%ICON_PARAM% ^
--add-data "%CD%\resources;resources" ^
--hidden-import "tkinter" ^
--hidden-import "tkinter.ttk" ^
--hidden-import "tkinter.colorchooser" ^
--hidden-import "tkinter.filedialog" ^
--hidden-import "tkinter.messagebox" ^
--hidden-import "socket" ^
--hidden-import "threading" ^
--hidden-import "json" ^
--hidden-import "time" ^
--hidden-import "select" ^
--hidden-import "platform" ^
--distpath "dist/windows" ^
--workpath "build/windows" ^
--specpath "build" ^
main_window.py
) else (
echo Building without resources...
pyinstaller ^
--onedir ^
--windowed ^
--name "SplitNotes" ^
%ICON_PARAM% ^
--hidden-import "tkinter" ^
--hidden-import "tkinter.ttk" ^
--hidden-import "tkinter.colorchooser" ^
--hidden-import "tkinter.filedialog" ^
--hidden-import "tkinter.messagebox" ^
--hidden-import "socket" ^
--hidden-import "threading" ^
--hidden-import "json" ^
--hidden-import "time" ^
--hidden-import "select" ^
--hidden-import "platform" ^
--distpath "dist/windows" ^
--workpath "build/windows" ^
--specpath "build" ^
main_window.py
)
if errorlevel 1 (
echo Build failed!
pause
exit /b 1
)
REM Copy additional files
echo Copying additional files...
if exist "README.md" copy "README.md" "dist\windows\SplitNotes\" 2>nul
if exist "LICENSE" copy "LICENSE" "dist\windows\SplitNotes\" 2>nul
REM Create resources directory in dist and copy files if they exist
if not exist "dist\windows\SplitNotes\resources" mkdir "dist\windows\SplitNotes\resources"
if "%RESOURCES_EXIST%"=="true" (
echo Copying resource files to distribution...
if exist "resources\*.png" copy "resources\*.png" "dist\windows\SplitNotes\resources\" 2>nul
if exist "resources\*.ico" copy "resources\*.ico" "dist\windows\SplitNotes\resources\" 2>nul
if exist "resources\*.cfg" copy "resources\*.cfg" "dist\windows\SplitNotes\resources\" 2>nul
) else (
echo No resource files to copy...
)
REM Create batch file for easy launching
echo @echo off > "dist\windows\SplitNotes\SplitNotes.bat"
echo cd /d "%%~dp0" >> "dist\windows\SplitNotes\SplitNotes.bat"
echo start "" "SplitNotes.exe" >> "dist\windows\SplitNotes\SplitNotes.bat"
REM Create ZIP package
echo Creating ZIP package...
if exist "dist\SplitNotes-Windows.zip" del "dist\SplitNotes-Windows.zip"
powershell -command "Compress-Archive -Path 'dist\windows\SplitNotes\*' -DestinationPath 'dist\SplitNotes-Windows.zip'"
if errorlevel 1 (
echo Warning: Failed to create ZIP package. Please create manually.
)
echo.
echo ===============================================
echo Build completed successfully!
echo.
echo Executable location: dist\windows\SplitNotes\SplitNotes.exe
echo ZIP package: dist\SplitNotes-Windows.zip
echo.
echo To distribute:
echo 1. Copy the entire 'dist\windows\SplitNotes' folder
echo 2. Or use the ZIP package
echo.
echo Requirements for target systems:
echo - Windows 7 or later
echo - No Python installation required
echo ===============================================
echo.
REM Optional: Open the dist folder
set /p OPEN="Open build folder? (y/n): "
if /i "%OPEN%"=="y" explorer "dist\windows\SplitNotes"
pause
+76
View File
@@ -0,0 +1,76 @@
--- Cross-Platform Build Instructions ---
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
+371
View File
@@ -0,0 +1,371 @@
#!/bin/bash
# Linux Build Script for SplitNotes
# Requires PyInstaller: pip install pyinstaller
echo "Building SplitNotes for Linux..."
echo
# Detect architecture
ARCH=$(uname -m)
case $ARCH in
x86_64)
ARCH_NAME="x64"
;;
i386|i686)
ARCH_NAME="x86"
;;
aarch64|arm64)
ARCH_NAME="arm64"
;;
armv7l)
ARCH_NAME="armv7"
;;
*)
ARCH_NAME="$ARCH"
;;
esac
echo "Detected architecture: $ARCH ($ARCH_NAME)"
# Check if we're on Linux
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
echo "Warning: This script is designed for Linux. Continuing anyway..."
fi
# Check Python version
PYTHON_CMD="python3"
if ! command -v python3 &> /dev/null; then
if command -v python &> /dev/null; then
PYTHON_CMD="python"
else
echo "Error: Python not found. Please install Python 3.6 or later."
exit 1
fi
fi
PYTHON_VERSION=$($PYTHON_CMD --version 2>&1 | grep -Po '(?<=Python )[0-9]+\.[0-9]+')
if ! $PYTHON_CMD -c "import sys; sys.exit(0 if sys.version_info >= (3,6) else 1)"; then
echo "Error: Python 3.6 or later is required. Found: $($PYTHON_CMD --version)"
exit 1
fi
# Check if PyInstaller is installed
if ! $PYTHON_CMD -c "import PyInstaller" 2>/dev/null; then
echo "PyInstaller not found. Installing..."
$PYTHON_CMD -m pip install pyinstaller --user
if [ $? -ne 0 ]; then
echo "Failed to install PyInstaller. Trying with sudo..."
sudo $PYTHON_CMD -m pip install pyinstaller
if [ $? -ne 0 ]; then
echo "Failed to install PyInstaller. Please install manually:"
echo " pip3 install pyinstaller"
exit 1
fi
fi
fi
# Check for required system packages
echo "Checking system dependencies..."
MISSING_DEPS=()
# Check for tkinter
if ! $PYTHON_CMD -c "import tkinter" 2>/dev/null; then
MISSING_DEPS+=("python3-tk")
fi
if [ ${#MISSING_DEPS[@]} -gt 0 ]; then
echo "Missing dependencies detected:"
for dep in "${MISSING_DEPS[@]}"; do
echo " - $dep"
done
echo
echo "Install with your package manager:"
echo " Ubuntu/Debian: sudo apt install ${MISSING_DEPS[*]}"
echo " Fedora/RHEL: sudo dnf install python3-tkinter"
echo " Arch: sudo pacman -S tk"
echo " openSUSE: sudo zypper install python3-tk"
exit 1
fi
# Create build directories
mkdir -p build dist
# Clean previous builds
echo "Cleaning previous builds..."
rm -rf build/*
rm -rf dist/*
# Check if resources folder exists, create if not
if [ ! -d "resources" ]; then
echo "Creating resources folder..."
mkdir -p resources
echo "Note: Add icon files to resources folder for better appearance:"
echo " - app_icon.png (Linux icon, 256x256px recommended)"
echo " - green.png (connection active)"
echo " - red.png (connection inactive)"
echo " - settings_icon.png (settings window)"
fi
# Build the application
echo "Building Linux executable..."
# Build with or without resources
if [ -d "resources" ] && [ "$(ls -A resources 2>/dev/null)" ]; then
echo "Including resources folder in build..."
$PYTHON_CMD -m PyInstaller \
--onedir \
--console \
else
echo "Building without resources folder..."
$PYTHON_CMD -m PyInstaller \
--onedir \
--console \
--name "SplitNotes" \
--hidden-import "tkinter" \
--hidden-import "tkinter.ttk" \
--hidden-import "tkinter.colorchooser" \
--hidden-import "tkinter.filedialog" \
--hidden-import "tkinter.messagebox" \
--hidden-import "socket" \
--hidden-import "threading" \
--hidden-import "json" \
--hidden-import "time" \
--hidden-import "select" \
--hidden-import "platform" \
--distpath "dist/linux" \
--workpath "build/linux" \
--specpath "build" \
main_window.py
fi
--add-data "resources:resources" \
--hidden-import "tkinter" \
--hidden-import "tkinter.ttk" \
--hidden-import "tkinter.colorchooser" \
--hidden-import "tkinter.filedialog" \
--hidden-import "tkinter.messagebox" \
--hidden-import "socket" \
--hidden-import "threading" \
--hidden-import "json" \
--hidden-import "time" \
--hidden-import "select" \
--hidden-import "platform" \
--distpath "dist/linux" \
--workpath "build/linux" \
--specpath "build" \
main_window.py
else
echo "Building without resources folder..."
$PYTHON_CMD -m PyInstaller \
--onedir \
--console \
--name "SplitNotes" \
--hidden-import "tkinter" \
--hidden-import "tkinter.ttk" \
--hidden-import "tkinter.colorchooser" \
--hidden-import "tkinter.filedialog" \
--hidden-import "tkinter.messagebox" \
--hidden-import "socket" \
--hidden-import "threading" \
--hidden-import "json" \
--hidden-import "time" \
--hidden-import "select" \
--hidden-import "platform" \
--distpath "dist/linux" \
--workpath "build/linux" \
--specpath "build" \
main_window.py
if [ $? -ne 0 ]; then
echo "Build failed!"
exit 1
fi
# Create proper directory structure
DIST_DIR="dist/linux/SplitNotes-linux-$ARCH_NAME"
mkdir -p "$DIST_DIR"
# Move the built application
mv "dist/linux/SplitNotes"/* "$DIST_DIR/"
rmdir "dist/linux/SplitNotes"
# Copy additional files
echo "Copying additional files..."
cp README.md "$DIST_DIR/" 2>/dev/null || true
cp LICENSE "$DIST_DIR/" 2>/dev/null || true
# Copy resources if they exist
if [ "$RESOURCES_EXIST" = true ]; then
echo "Copying resource files to distribution..."
cp -r resources "$DIST_DIR/" 2>/dev/null || true
else
echo "No resource files to copy..."
# Create empty resources directory for app to use
mkdir -p "$DIST_DIR/resources"
fi
# Create launch script
cat > "$DIST_DIR/splitnotes.sh" << 'EOF'
#!/bin/bash
# SplitNotes Launch Script
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
# Change to the script directory
cd "$SCRIPT_DIR"
# Set library path for the executable
export LD_LIBRARY_PATH="$SCRIPT_DIR:$LD_LIBRARY_PATH"
# Launch SplitNotes
if [ -f "./SplitNotes" ]; then
./SplitNotes "$@"
else
echo "Error: SplitNotes executable not found!"
exit 1
fi
EOF
# Make scripts executable
chmod +x "$DIST_DIR/splitnotes.sh"
chmod +x "$DIST_DIR/SplitNotes"
# Create desktop entry
cat > "$DIST_DIR/SplitNotes.desktop" << EOF
[Desktop Entry]
Type=Application
Name=SplitNotes
Comment=LiveSplit notes synchronization tool
Exec=$DIST_DIR/splitnotes.sh
Icon=$DIST_DIR/resources/app_icon.png
Terminal=false
Categories=Utility;Game;
StartupWMClass=SplitNotes
EOF
# Create installation script
cat > "$DIST_DIR/install.sh" << 'EOF'
#!/bin/bash
# SplitNotes Installation Script
INSTALL_DIR="$HOME/.local/share/SplitNotes"
BIN_DIR="$HOME/.local/bin"
DESKTOP_DIR="$HOME/.local/share/applications"
echo "Installing SplitNotes..."
# Create directories
mkdir -p "$INSTALL_DIR"
mkdir -p "$BIN_DIR"
mkdir -p "$DESKTOP_DIR"
# Copy files
cp -r * "$INSTALL_DIR/"
# Create symlink in bin directory
ln -sf "$INSTALL_DIR/splitnotes.sh" "$BIN_DIR/splitnotes"
# Install desktop entry
cp "$INSTALL_DIR/SplitNotes.desktop" "$DESKTOP_DIR/"
sed -i "s|$PWD|$INSTALL_DIR|g" "$DESKTOP_DIR/SplitNotes.desktop"
# Update desktop database
if command -v update-desktop-database &> /dev/null; then
update-desktop-database "$DESKTOP_DIR"
fi
echo "Installation completed!"
echo "SplitNotes installed to: $INSTALL_DIR"
echo "You can now run 'splitnotes' from terminal or find it in your applications menu."
EOF
chmod +x "$DIST_DIR/install.sh"
# Create AppImage (if appimagetool is available)
echo "Checking for AppImage creation tools..."
if command -v appimagetool &> /dev/null; then
echo "Creating AppImage..."
APPDIR="dist/SplitNotes.AppDir"
mkdir -p "$APPDIR/usr/bin"
mkdir -p "$APPDIR/usr/share/applications"
mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps"
# Copy files to AppDir
cp -r "$DIST_DIR"/* "$APPDIR/usr/bin/"
# Create AppRun
cat > "$APPDIR/AppRun" << 'EOF'
#!/bin/bash
SELF=$(readlink -f "$0")
HERE=${SELF%/*}
export PATH="${HERE}/usr/bin/:${PATH}"
export LD_LIBRARY_PATH="${HERE}/usr/bin/:${LD_LIBRARY_PATH}"
cd "${HERE}/usr/bin"
exec ./SplitNotes "$@"
EOF
chmod +x "$APPDIR/AppRun"
# Copy desktop file and icon
cp "$DIST_DIR/SplitNotes.desktop" "$APPDIR/"
if [ -f "resources/app_icon.png" ]; then
cp "resources/app_icon.png" "$APPDIR/splitnotes.png"
cp "resources/app_icon.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/"
fi
# Update desktop file paths
sed -i 's|Exec=.*|Exec=SplitNotes|g' "$APPDIR/SplitNotes.desktop"
sed -i 's|Icon=.*|Icon=splitnotes|g' "$APPDIR/SplitNotes.desktop"
# Create AppImage
appimagetool "$APPDIR" "dist/SplitNotes-linux-$ARCH_NAME.AppImage"
if [ $? -eq 0 ]; then
echo "AppImage created successfully!"
chmod +x "dist/SplitNotes-linux-$ARCH_NAME.AppImage"
fi
fi
# Create tarball
echo "Creating tarball..."
cd dist/linux
tar -czf "../SplitNotes-linux-$ARCH_NAME.tar.gz" "SplitNotes-linux-$ARCH_NAME"
cd ../..
echo
echo "==============================================="
echo "Build completed successfully!"
echo
echo "Executable location: $DIST_DIR/SplitNotes"
echo "Launch script: $DIST_DIR/splitnotes.sh"
echo "Tarball: dist/SplitNotes-linux-$ARCH_NAME.tar.gz"
if [ -f "dist/SplitNotes-linux-$ARCH_NAME.AppImage" ]; then
echo "AppImage: dist/SplitNotes-linux-$ARCH_NAME.AppImage"
fi
echo
echo "To install locally:"
echo " cd '$DIST_DIR' && ./install.sh"
echo
echo "To distribute:"
echo "1. Copy the entire '$DIST_DIR' folder"
echo "2. Or use the tarball/AppImage"
echo
echo "Requirements for target systems:"
echo "- Linux with glibc 2.17+ (RHEL 7+, Ubuntu 14.04+)"
echo "- X11 display server"
echo "- No Python installation required"
echo "==============================================="
echo
# Optional: Open the dist folder
read -p "Open build folder? (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
if command -v xdg-open &> /dev/null; then
xdg-open "dist/linux"
elif command -v nautilus &> /dev/null; then
nautilus "dist/linux"
else
echo "Build folder: $(pwd)/dist/linux"
fi
fi
+270
View File
@@ -0,0 +1,270 @@
#!/bin/bash
# macOS Build Script for SplitNotes
# Requires PyInstaller: pip install pyinstaller
echo "Building SplitNotes for macOS..."
echo
# Check if we're on macOS
if [[ "$OSTYPE" != "darwin"* ]]; then
echo "Error: This script is for macOS only."
exit 1
fi
# Check if PyInstaller is installed
if ! python3 -c "import PyInstaller" 2>/dev/null; then
echo "PyInstaller not found. Installing..."
pip3 install pyinstaller
if [ $? -ne 0 ]; then
echo "Failed to install PyInstaller. Please install manually: pip3 install pyinstaller"
exit 1
fi
fi
# Create build directories
mkdir -p build dist
# Clean previous builds
echo "Cleaning previous builds..."
rm -rf build/*
rm -rf dist/*
# Check if resources folder exists, create if not
if [ ! -d "resources" ]; then
echo "Creating resources folder..."
mkdir -p resources
echo "Note: Add icon files to resources folder for better appearance:"
echo " - app_icon.icns (macOS icon)"
echo " - green.png (connection active)"
echo " - red.png (connection inactive)"
echo " - settings_icon.png (settings window)"
fi
# Build the application
echo "Building macOS app bundle..."
# Set icon parameter if icon exists
if [ -f "resources/app_icon.icns" ]; then
echo "Found app icon, including in build..."
ICON_PARAM="--icon=resources/app_icon.icns"
else
echo "No app icon found, building without custom icon..."
ICON_PARAM=""
fi
# Build with or without resources
if [ -d "resources" ] && [ "$(ls -A resources 2>/dev/null)" ]; then
echo "Including resources folder in build..."
python3 -m PyInstaller \
--onedir \
--windowed \
--name "SplitNotes" \
else
echo "Building without resources folder..."
python3 -m PyInstaller \
--onedir \
--windowed \
--name "SplitNotes" \
$ICON_PARAM \
--hidden-import "tkinter" \
--hidden-import "tkinter.ttk" \
--hidden-import "tkinter.colorchooser" \
--hidden-import "tkinter.filedialog" \
--hidden-import "tkinter.messagebox" \
--hidden-import "socket" \
--hidden-import "threading" \
--hidden-import "json" \
--hidden-import "time" \
--hidden-import "select" \
--hidden-import "platform" \
--distpath "dist/macos" \
--workpath "build/macos" \
--specpath "build" \
main_window.py
fi
--add-data "resources:resources" \
--hidden-import "tkinter" \
--hidden-import "tkinter.ttk" \
--hidden-import "tkinter.colorchooser" \
--hidden-import "tkinter.filedialog" \
--hidden-import "tkinter.messagebox" \
--hidden-import "socket" \
--hidden-import "threading" \
--hidden-import "json" \
--hidden-import "time" \
--hidden-import "select" \
--hidden-import "platform" \
--distpath "dist/macos" \
--workpath "build/macos" \
--specpath "build" \
main_window.py
else
echo "Building without resources folder..."
python3 -m PyInstaller \
--onedir \
--windowed \
--name "SplitNotes" \
$ICON_PARAM \
--hidden-import "tkinter" \
--hidden-import "tkinter.ttk" \
--hidden-import "tkinter.colorchooser" \
--hidden-import "tkinter.filedialog" \
--hidden-import "tkinter.messagebox" \
--hidden-import "socket" \
--hidden-import "threading" \
--hidden-import "json" \
--hidden-import "time" \
--hidden-import "select" \
--hidden-import "platform" \
--distpath "dist/macos" \
--workpath "build/macos" \
--specpath "build" \
main_window.py
if [ $? -ne 0 ]; then
echo "Build failed!"
exit 1
fi
# Copy additional files to the app bundle
echo "Copying additional files..."
APP_DIR="dist/macos/SplitNotes.app"
CONTENTS_DIR="$APP_DIR/Contents"
RESOURCES_DIR="$CONTENTS_DIR/Resources"
# Create proper app bundle structure
mkdir -p "$RESOURCES_DIR"
# Copy resources if they exist
if [ "$RESOURCES_EXIST" = true ]; then
echo "Copying resource files to app bundle..."
cp -r resources/* "$RESOURCES_DIR/" 2>/dev/null || true
else
echo "No resource files to copy..."
# Create empty resources directory for app to use
mkdir -p "$RESOURCES_DIR"
fi
# Copy documentation
cp README.md "$RESOURCES_DIR/" 2>/dev/null || true
cp LICENSE "$RESOURCES_DIR/" 2>/dev/null || true
# Create Info.plist if it doesn't exist
INFO_PLIST="$CONTENTS_DIR/Info.plist"
if [ ! -f "$INFO_PLIST" ]; then
cat > "$INFO_PLIST" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>SplitNotes</string>
<key>CFBundleDisplayName</key>
<string>SplitNotes</string>
<key>CFBundleIdentifier</key>
<string>com.apfelteesaft.splitnotes</string>
<key>CFBundleVersion</key>
<string>1.1.0</string>
<key>CFBundleShortVersionString</key>
<string>1.1.0</string>
<key>CFBundleExecutable</key>
<string>SplitNotes</string>
<key>CFBundleIconFile</key>
<string>app_icon.icns</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>
EOF
fi
# Set correct permissions
chmod +x "$APP_DIR/Contents/MacOS/SplitNotes"
# Code signing (optional, requires Apple Developer account)
echo "Checking for code signing..."
if command -v codesign >/dev/null 2>&1; then
echo "Note: For distribution, you may want to code sign the app:"
echo " codesign --force --sign 'Developer ID Application: Your Name' '$APP_DIR'"
echo " Use --deep flag for embedded frameworks"
fi
# Create DMG (requires create-dmg tool)
echo "Creating DMG package..."
if command -v create-dmg >/dev/null 2>&1; then
# Use icon if available, otherwise skip
if [ -f "resources/app_icon.icns" ]; then
VOLICON_PARAM="--volicon resources/app_icon.icns"
else
VOLICON_PARAM=""
fi
create-dmg \
--volname "SplitNotes" \
$VOLICON_PARAM \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "SplitNotes.app" 175 120 \
--hide-extension "SplitNotes.app" \
--app-drop-link 425 120 \
"dist/SplitNotes-macOS.dmg" \
"dist/macos/"
if [ $? -eq 0 ]; then
echo "DMG created successfully!"
else
echo "DMG creation failed. Creating ZIP instead..."
cd dist/macos
zip -r "../SplitNotes-macOS.zip" "SplitNotes.app"
cd ../..
fi
else
echo "create-dmg not found. Creating ZIP package..."
cd dist/macos
zip -r "../SplitNotes-macOS.zip" "SplitNotes.app"
cd ../..
fi
echo
echo "==============================================="
echo "Build completed successfully!"
echo
echo "App bundle location: dist/macos/SplitNotes.app"
if [ -f "dist/SplitNotes-macOS.dmg" ]; then
echo "DMG package: dist/SplitNotes-macOS.dmg"
fi
if [ -f "dist/SplitNotes-macOS.zip" ]; then
echo "ZIP package: dist/SplitNotes-macOS.zip"
fi
echo
echo "To distribute:"
echo "1. Copy the SplitNotes.app bundle"
echo "2. Or use the DMG/ZIP package"
echo
echo "Requirements for target systems:"
echo "- macOS 10.13 (High Sierra) or later"
echo "- No Python installation required"
echo
echo "Note: For distribution outside the App Store,"
echo "you may need to code sign and notarize the app."
echo "==============================================="
echo
# Optional: Open the dist folder
read -p "Open build folder? (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
open "dist/macos"
fi
+154 -14
View File
@@ -1,9 +1,16 @@
# CONFIG FILE WITH CONSTANTS # CONFIG FILE WITH CONSTANTS - Complete Bridge Server Support
import platform
import os
# Livesplit connection # Livesplit connection
HOST = "localhost" HOST = "localhost"
PORT = 16834 PORT = 16834
# Bridge server settings
BRIDGE_HOST = "localhost"
BRIDGE_PORT = 16835
BRIDGE_ENABLED = False
# In network communication, time out after this time. (in seconds) # In network communication, time out after this time. (in seconds)
COM_TIMEOUT = 0.5 COM_TIMEOUT = 0.5
@@ -31,21 +38,27 @@ SETTINGS_FILE = "config.cfg"
# Default Scrollbar Width # Default Scrollbar Width
SCROLLBAR_WIDTH = 16 SCROLLBAR_WIDTH = 16
# Popup menu options # Popup menu options - Enhanced with bridge settings
MENU_OPTIONS = { MENU_OPTIONS = {
"SINGLE": "Set Single Layout", "SINGLE": "Set Single Layout",
"DOUBLE": "Set Double Layout", "DOUBLE": "Set Double Layout",
"LOAD": "Load Notes", "LOAD": "Load Notes",
"BIG": "Big Font", "BIG": "Big Font",
"SMALL": "Small Font", "SMALL": "Small Font",
"SETTINGS": "Settings" "SETTINGS": "Settings",
"BRIDGE": "Bridge Settings"
} }
# Error messages # Error messages - Enhanced with bridge server errors
ERRORS = {"NOTES_EMPTY": ("Error!", "Notes empty or can't be loaded!"), ERRORS = {
"NOTES_EMPTY": ("Error!", "Notes empty or can't be loaded!"),
"FONT_SIZE": ("Error!", "Invalid Font Size!"), "FONT_SIZE": ("Error!", "Invalid Font Size!"),
"SERVER_PORT": ("Error!", "Invalid server port!"), "SERVER_PORT": ("Error!", "Invalid server port!"),
"SEPARATOR": ("Error!", "Invalid split separator!")} "SEPARATOR": ("Error!", "Invalid split separator!"),
"BRIDGE_PORT": ("Error!", "Invalid bridge server port!"),
"BRIDGE_START": ("Error!", "Failed to start bridge server!"),
"BRIDGE_CONNECTION": ("Error!", "Bridge server connection failed!")
}
# Max file size for notes # Max file size for notes
MAX_FILE_SIZE = 1000000000 # 1 Giga-Byte MAX_FILE_SIZE = 1000000000 # 1 Giga-Byte
@@ -56,19 +69,31 @@ RUNNING_ALERT = "RUNNING"
# Font for gui widgets # Font for gui widgets
GUI_FONT = ("arial", 12) 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 = [
("Text Files", ("*.txt", "*.log", "*.asc", "*.conf", "*.cfg")), ("Text Files", ("*.txt", "*.log", "*.asc", "*.conf", "*.cfg")),
('All', '*') ('All', '*')
] ]
# Default content of config.cfg file # Default content of config.cfg file - COMPLETE with proper bridge settings
DEFAULT_CONFIG = "notes=\nfont_size=12\nfont=arial\ntext_color=#000000\nbackground_color=#FFFFFF\ndouble_layout=False\nserver_port=16834\nwidth=400\nheight=300\nseparator=new_line" DEFAULT_CONFIG = """notes=
font_size=12
font=arial
text_color=#000000
background_color=#FFFFFF
double_layout=false
server_port=16834
width=400
height=300
separator=new_line
bridge_enabled=false
bridge_port=16835"""
NEWLINE_CONSTANT = "new_line" NEWLINE_CONSTANT = "new_line"
# Required settings # Required settings - COMPLETE with bridge settings
REQUIRED_SETTINGS = ("notes", REQUIRED_SETTINGS = (
"notes",
"font", "font",
"font_size", "font_size",
"text_color", "text_color",
@@ -77,7 +102,9 @@ REQUIRED_SETTINGS = ("notes",
"double_layout", "double_layout",
"width", "width",
"height", "height",
"separator" "separator",
"bridge_enabled",
"bridge_port"
) )
# Settings window options # Settings window options
@@ -87,6 +114,15 @@ SETTINGS_WINDOW = {"TITLE": "Settings",
"CANCEL": "Cancel", "CANCEL": "Cancel",
"SAVE": "Save"} "SAVE": "Save"}
# Bridge settings window options
BRIDGE_SETTINGS_WINDOW = {
"TITLE": "TCP Bridge Server Settings",
"WIDTH": 520,
"HEIGHT": 500,
"CANCEL": "Cancel",
"APPLY": "Apply"
}
# OPTIONS IN THE SETTINGS WINDOW # OPTIONS IN THE SETTINGS WINDOW
SETTINGS_OPTIONS = {"FONT": "Font", SETTINGS_OPTIONS = {"FONT": "Font",
"FONT_SIZE": "Font Size", "FONT_SIZE": "Font Size",
@@ -98,8 +134,67 @@ SETTINGS_OPTIONS = {"FONT": "Font",
"NEW_LINE_SEPARATOR": "Newline as split separator", "NEW_LINE_SEPARATOR": "Newline as split separator",
"CUSTOM_SEPARATOR": "Custom split separator"} "CUSTOM_SEPARATOR": "Custom split separator"}
# Fonts that can be selected # Bridge server settings options
AVAILABLE_FONTS = ("arial", BRIDGE_OPTIONS = {
"ENABLE_BRIDGE": "Enable TCP Bridge Server for Browser Extensions",
"BRIDGE_PORT": "Bridge Server Port",
"DEFAULT_BRIDGE_PORT": "(Default is 16835)",
"BRIDGE_STATUS": "Server Status",
"CONNECTED_BROWSERS": "Connected Browsers",
"LAST_UPDATE": "Last Update",
"SAVE_SETTINGS": "Save Settings",
"TEST_CONNECTION": "Test Connection",
"HELP_TEXT": """TCP Bridge Server Help:
The TCP Bridge Server allows browser extensions to connect to SplitNotes and send timer state information from LiveSplit One.
SETUP:
1. Check 'Enable TCP Bridge Server' checkbox
2. Set the port (default: 16835)
3. Click 'Save Settings'
4. Verify status shows 'RUNNING ✓'
BROWSER EXTENSION:
Browser extensions should connect to localhost:16835 and send JSON messages like:
{
"type": "timer_state",
"running": true,
"currentSplit": 2,
"splitName": "Split Name"
}
TROUBLESHOOTING:
• Make sure port is not blocked by firewall
• Check that no other application is using the port
• Use 'Test Connection' to verify server is working
• Restart SplitNotes if server fails to start"""
}
# 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", "courier new",
"fixedsys", "fixedsys",
"ms sans serif", "ms sans serif",
@@ -107,3 +202,48 @@ AVAILABLE_FONTS = ("arial",
"system", "system",
"times new roman", "times new roman",
"verdana") "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.1.0" # Incremented for bridge server support
APP_AUTHOR = "ApfelTeeSaft"
APP_DESCRIPTION = "Software for syncing notes with LiveSplit using the LiveSplit server component and browser extensions."
# Bridge server message types
BRIDGE_MESSAGE_TYPES = {
"TIMER_STATE": "timer_state",
"SPLITS_UPDATED": "splits_updated",
"CONNECTION_TEST": "connection_test",
"STATUS_REQUEST": "status_request",
"SETTINGS_UPDATE": "settings_update"
}
# Bridge server status messages
BRIDGE_STATUS_MESSAGES = {
"STARTING": "Starting TCP bridge server...",
"RUNNING": "TCP bridge server running",
"STOPPED": "TCP bridge server stopped",
"ERROR": "TCP bridge server error",
"NO_CLIENTS": "No browser clients connected",
"CLIENT_CONNECTED": "Browser client connected",
"CLIENT_DISCONNECTED": "Browser client disconnected",
"SETTINGS_SAVED": "Settings saved successfully",
"SERVER_STARTED": "TCP Bridge server started successfully",
"SERVER_FAILED": "Failed to start TCP bridge server",
"CONNECTION_TEST_OK": "Connection test successful",
"CONNECTION_TEST_FAIL": "Connection test failed"
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Joel Oskarsson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+14 -3
View File
@@ -11,6 +11,7 @@ import select # used for checking if socket has data pending
def ls_connect(ls_socket, call_func, window, server_port): def ls_connect(ls_socket, call_func, window, server_port):
"""Connects given socket to the livesplit server.""" """Connects given socket to the livesplit server."""
con_thread = Thread(target=try_connection, args=(ls_socket, call_func, window, server_port)) 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() con_thread.start()
@@ -36,7 +37,10 @@ def try_connection(ls_socket, call_func, window, server_port):
def close_socket(com_socket): def close_socket(com_socket):
"""Closes given socket.""" """Closes given socket."""
try:
com_socket.close() com_socket.close()
except:
pass # Socket might already be closed
def check_connection(ls_socket): def check_connection(ls_socket):
@@ -56,7 +60,7 @@ def send_to_ls(ls_socket, command):
Sends given command to ls using given socket. Sends given command to ls using given socket.
If connected is False, tries to send without socket being connected to ls. If connected is False, tries to send without socket being connected to ls.
Returns the response, or False if an error occurs. Returns the response, or False if an error occurs.
Check config.LS_COMMANDS for avaiable commands. Check config.LS_COMMANDS for available commands.
""" """
try: try:
@@ -64,6 +68,8 @@ def send_to_ls(ls_socket, command):
except: except:
return False return False
# Use select to check if data is available, with timeout
try:
socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT) socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT)
if socket_ready[0]: if socket_ready[0]:
try: try:
@@ -72,6 +78,8 @@ def send_to_ls(ls_socket, command):
return False return False
else: else:
return False return False
except:
return False
def get_split_index(ls_socket): def get_split_index(ls_socket):
@@ -85,7 +93,10 @@ def get_split_index(ls_socket):
ls_data = send_to_ls(ls_socket, "cur_split_index") ls_data = send_to_ls(ls_socket, "cur_split_index")
if not isinstance(ls_data, bool): if not isinstance(ls_data, bool):
return int(ls_data) try:
return int(ls_data.strip())
except:
return False
else: else:
return False return False
@@ -98,6 +109,6 @@ def get_split_name(ls_socket):
ls_data = send_to_ls(ls_socket, "cur_split_name") ls_data = send_to_ls(ls_socket, "cur_split_name")
if ls_data: if ls_data:
return ls_data return ls_data.strip()
else: else:
return False return False
+831 -151
View File
File diff suppressed because it is too large Load Diff
+58 -31
View File
@@ -25,29 +25,39 @@ def get_note_lines(file_path):
""" """
# check so file isn't too big # check so file isn't too big
try:
if path.getsize(file_path) > config.MAX_FILE_SIZE: if path.getsize(file_path) > config.MAX_FILE_SIZE:
return False return False
except:
return False
try: try:
notes_file = open(file_path, "r") # Try different encodings for cross-platform compatibility
except: 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 return False
# read file line per line # read file line per line
f_lines = [] f_lines = []
keep_reading = True
while keep_reading:
try: try:
cur_line = notes_file.readline() with notes_file:
except: for line in notes_file:
f_lines.append(line)
except Exception:
return False return False
if cur_line:
f_lines.append(cur_line)
else:
keep_reading = False
return f_lines return f_lines
@@ -59,27 +69,24 @@ def decode_notes(note_lines, separator):
""" """
# Check if newline is being used as separator # Check if newline is being used as separator
if separator == config.NEWLINE_CONSTANT: if separator == config.NEWLINE_CONSTANT:
separator = "" # left after stripping newline separator = "" # left after stripping newline
def is_title(line): def is_title(line):
if not line: if not line:
return False return False
stripped = line.strip()
return (line[0] == "[") and (line[-1] == "]") return stripped.startswith("[") and stripped.endswith("]")
def is_separator(line): def is_separator(line):
return (line == separator) return line.strip() == separator.strip()
def is_newline(s): def is_newline(s):
return (s == "\n") return s.strip() == ""
def remove_new_line(line): def remove_new_line(line):
if (len(line) >= 1) and (is_newline(line[-1])): # Remove trailing newline characters
return line[:-1] return line.rstrip('\n\r')
else:
return line
note_list = [] note_list = []
cur_notes = "" cur_notes = ""
@@ -87,16 +94,25 @@ def decode_notes(note_lines, separator):
for line in note_lines: for line in note_lines:
line = remove_new_line(line) line = remove_new_line(line)
if is_separator(line): if separator == "" and is_newline(line):
if cur_notes: # Using newline as separator
note_list.append(cur_notes) 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 = "" cur_notes = ""
else: else:
if not is_title(line): if not is_title(line):
cur_notes += line + "\n" # newline
if cur_notes: if cur_notes:
note_list.append(cur_notes) cur_notes += "\n"
cur_notes += line
# Add the last notes if any
if cur_notes.strip():
note_list.append(cur_notes.strip())
return note_list return note_list
@@ -104,10 +120,13 @@ def decode_notes(note_lines, separator):
def get_notes(file_path, separator): def get_notes(file_path, separator):
""" """
Takes a path to a file and returns a list with the notes 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. Returns False if file is empty.
""" """
if not file_exists(file_path):
return False
note_lines = get_note_lines(file_path) note_lines = get_note_lines(file_path)
if not note_lines: if not note_lines:
@@ -115,7 +134,7 @@ def get_notes(file_path, separator):
note_list = decode_notes(note_lines, separator) note_list = decode_notes(note_lines, separator)
return note_list return note_list if note_list else False
def select_file(): def select_file():
@@ -124,15 +143,23 @@ def select_file():
Returns False upon no file selection. Returns False upon no file selection.
Otherwise returns absolute path to selected file. Otherwise returns absolute path to selected file.
""" """
try:
file = file_dia.askopenfilename(filetypes=config.TEXT_FILES) file = file_dia.askopenfilename(
title="Select Notes File",
filetypes=config.TEXT_FILES
)
if file: if file:
return file return file
else: else:
return False return False
except Exception:
return False
def file_exists(file): def file_exists(file):
"""Checks if given path leads to an existing file.""" """Checks if given path leads to an existing file."""
try:
return path.isfile(file) return path.isfile(file)
except:
return False
+26
View File
@@ -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
+94
View File
@@ -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()
+227 -98
View File
@@ -6,14 +6,23 @@ import sys
import config 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( settings_path = os.path.join(
str(os.path.dirname(os.path.realpath(sys.argv[0]))), application_path,
config.RESOURCE_FOLDER, config.RESOURCE_FOLDER,
config.SETTINGS_FILE config.SETTINGS_FILE
) )
settings_icon_path = os.path.join( settings_icon_path = os.path.join(
str(os.path.dirname(os.path.realpath(sys.argv[0]))), application_path,
config.RESOURCE_FOLDER, config.RESOURCE_FOLDER,
config.ICONS["SETTINGS"] config.ICONS["SETTINGS"]
) )
@@ -27,18 +36,25 @@ def load_settings():
returns a dictionary with all 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 to open default settings file
try: try:
settings_file = open(settings_path, "r+") with open(settings_path, "r", encoding='utf-8') as settings_file:
settings_content = get_file_lines(settings_file) settings_content = settings_file.readlines()
settings_content = [line.strip() for line in settings_content]
print(f"Settings loaded from: {settings_path}")
except: except:
# File not found # File not found
print("Settings file not found, creating default settings")
settings_content = set_default_settings() settings_content = set_default_settings()
settings = format_settings(settings_content) settings = format_settings(settings_content)
# Check so settings file has all settings # Check so settings file has all settings
if not validate_settings(settings): if not validate_settings(settings):
print("Settings validation failed, creating default settings")
settings = format_settings(set_default_settings()) settings = format_settings(set_default_settings())
return settings return settings
@@ -49,6 +65,7 @@ def set_default_settings():
Creates a config file with default settings. Creates a config file with default settings.
Returns the default config-file content. Returns the default config-file content.
""" """
print(f"Creating default settings at: {settings_path}")
set_settings_file_content(config.DEFAULT_CONFIG) set_settings_file_content(config.DEFAULT_CONFIG)
return config.DEFAULT_CONFIG.split("\n") return config.DEFAULT_CONFIG.split("\n")
@@ -65,81 +82,141 @@ def format_settings(file_rows):
settings = {} settings = {}
for row in file_rows: for row in file_rows:
row = row.strip("\n") row = row.strip()
if '=' in row and not row.startswith('#'): # Skip comments
parts = row.split("=", 1) parts = row.split("=", 1)
if len(parts) == SETTING_PART_LENGTH: if len(parts) == SETTING_PART_LENGTH:
# Strip to remove whitespace at end and beginning # Strip to remove whitespace at end and beginning
settings[parts[0].strip()] = parts[1].strip() key = parts[0].strip()
value = parts[1].strip()
settings[key] = value
return settings 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): def validate_settings(settings):
""" """
Checks a settings dictionary so that all the needed settings are present. Checks a settings dictionary so that all the needed settings are present.
Enhanced with comprehensive bridge settings validation.
""" """
for req_setting in config.REQUIRED_SETTINGS: for req_setting in config.REQUIRED_SETTINGS:
if not (req_setting in settings): if req_setting not in settings:
print(f"Missing required setting: {req_setting}")
return False return False
if not validate_font_size(settings["font_size"]): if not validate_font_size(settings["font_size"]):
print(f"Invalid font size: {settings['font_size']}")
return False return False
if not validate_server_port(settings["server_port"]): if not validate_server_port(settings["server_port"]):
print(f"Invalid server port: {settings['server_port']}")
return False return False
if not validate_color(settings["text_color"]): if not validate_color(settings["text_color"]):
print(f"Invalid text color: {settings['text_color']}")
return False return False
if not validate_color(settings["background_color"]): if not validate_color(settings["background_color"]):
print(f"Invalid background color: {settings['background_color']}")
return False return False
if not (settings["font"] in config.AVAILABLE_FONTS): if settings["font"] not in config.AVAILABLE_FONTS:
print(f"Invalid font: {settings['font']}")
return False return False
if not ((settings["double_layout"] == "True") or if settings["double_layout"].lower() not in ["true", "false"]:
(settings["double_layout"] == "False")): print(f"Invalid double_layout: {settings['double_layout']}")
return False return False
if not validate_pixels(settings["width"]): if not validate_pixels(settings["width"]):
print(f"Invalid width: {settings['width']}")
return False return False
if not validate_pixels(settings["height"]): if not validate_pixels(settings["height"]):
print(f"Invalid height: {settings['height']}")
return False return False
if not validate_separator(settings["separator"]): if not validate_separator(settings["separator"]):
print(f"Invalid separator: {settings['separator']}")
return False return False
# Bridge settings validation
if settings["bridge_enabled"].lower() not in ["true", "false"]:
print(f"Invalid bridge_enabled: {settings['bridge_enabled']}")
return False
if not validate_bridge_port(settings["bridge_port"]):
print(f"Invalid bridge port: {settings['bridge_port']}")
return False
print("All settings validated successfully")
return True return True
def validate_bridge_port(port):
"""Returns whether or not given port is a valid bridge server port."""
try:
port_num = int(port)
return 1024 <= port_num <= 65535
except:
return False
def set_settings_file_content(content): def set_settings_file_content(content):
""" """
Saves given content to the config file, config.cfg, in the resources directory. Saves given content to the config file, config.cfg, in the resources directory.
""" """
settings_file = open(settings_path, "w") # 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) settings_file.write(content)
settings_file.close() print(f"Settings content written to: {settings_path}")
except Exception as e:
print(f"Error saving settings content: {e}")
def save_settings(settings):
"""
Saves given settings to the settings file.
Enhanced to ensure bridge settings are properly formatted.
"""
print("Saving settings...")
# Ensure bridge settings are present and properly formatted
if "bridge_enabled" not in settings:
settings["bridge_enabled"] = "false"
if "bridge_port" not in settings:
settings["bridge_port"] = "16835"
# Convert boolean values to lowercase strings for consistency
if isinstance(settings.get("bridge_enabled"), bool):
settings["bridge_enabled"] = str(settings["bridge_enabled"]).lower()
file_content = ""
# Write settings in a specific order for better readability
setting_order = [
"notes", "font", "font_size", "text_color", "background_color",
"double_layout", "server_port", "width", "height", "separator",
"bridge_enabled", "bridge_port"
]
# Write ordered settings first
for key in setting_order:
if key in settings:
file_content += f"{key}={settings[key]}\n"
# Write any additional settings not in the order list
for key, value in settings.items():
if key not in setting_order:
file_content += f"{key}={value}\n"
set_settings_file_content(file_content)
print(f"Settings saved: bridge_enabled={settings.get('bridge_enabled')}, bridge_port={settings.get('bridge_port')}")
def edit_settings(root_wnd, apply_method): def edit_settings(root_wnd, apply_method):
@@ -152,13 +229,29 @@ def edit_settings(root_wnd, apply_method):
width=config.SETTINGS_WINDOW["WIDTH"], width=config.SETTINGS_WINDOW["WIDTH"],
height=config.SETTINGS_WINDOW["HEIGHT"]) height=config.SETTINGS_WINDOW["HEIGHT"])
settings_wnd.title(config.SETTINGS_WINDOW["TITLE"]) 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 = load_settings()
settings_wnd.resizable(0, 0) 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}")
# Create labels
font_label = tkinter.Label(settings_wnd, font_label = tkinter.Label(settings_wnd,
text=config.SETTINGS_OPTIONS["FONT"], text=config.SETTINGS_OPTIONS["FONT"],
font=config.GUI_FONT) font=config.GUI_FONT)
@@ -197,14 +290,13 @@ def edit_settings(root_wnd, apply_method):
font_dropdown.configure(font=config.GUI_FONT) font_dropdown.configure(font=config.GUI_FONT)
# Font Size Selection # 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"]) font_size_entry.insert(0, settings["font_size"])
# Text Color Selection # Text Color Selection
text_color = tkinter.Button(settings_wnd, text_color = tkinter.Button(settings_wnd,
width=3, width=3,
height=1, height=1)
)
if validate_color(settings["text_color"]): if validate_color(settings["text_color"]):
text_color.configure(background=settings["text_color"]) text_color.configure(background=settings["text_color"])
@@ -212,11 +304,10 @@ def edit_settings(root_wnd, apply_method):
text_color.configure(background="#000000") text_color.configure(background="#000000")
def text_color_selection(): def text_color_selection():
choosen_color = colorchooser.askcolor() chosen_color = colorchooser.askcolor(parent=settings_wnd)
if choosen_color[1]: if chosen_color[1]:
settings["text_color"] = choosen_color[1] settings["text_color"] = chosen_color[1]
text_color.configure(background=settings["text_color"]) text_color.configure(background=settings["text_color"])
settings_wnd.focus_force() settings_wnd.focus_force()
text_color.configure(command=text_color_selection) text_color.configure(command=text_color_selection)
@@ -232,17 +323,16 @@ def edit_settings(root_wnd, apply_method):
bg_color.configure(background="#FFFFFF") bg_color.configure(background="#FFFFFF")
def bg_color_selection(): def bg_color_selection():
choosen_color = colorchooser.askcolor() chosen_color = colorchooser.askcolor(parent=settings_wnd)
if choosen_color[1]: if chosen_color[1]:
settings["background_color"] = choosen_color[1] settings["background_color"] = chosen_color[1]
bg_color.configure(background=settings["background_color"]) bg_color.configure(background=settings["background_color"])
settings_wnd.focus_force() settings_wnd.focus_force()
bg_color.configure(command=bg_color_selection) bg_color.configure(command=bg_color_selection)
# Server port 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"]) port_entry.insert(0, settings["server_port"])
# Double Layout Selection # Double Layout Selection
@@ -264,13 +354,11 @@ def edit_settings(root_wnd, apply_method):
use_newline = tkinter.BooleanVar() use_newline = tkinter.BooleanVar()
newline_btn = tkinter.Checkbutton(settings_wnd, newline_btn = tkinter.Checkbutton(settings_wnd,
variable=use_newline, variable=use_newline,
command= command=lambda: set_separator_active(not use_newline.get()))
(lambda: set_separator_active(not use_newline.get()))
)
if settings["separator"] == config.NEWLINE_CONSTANT: if settings["separator"] == config.NEWLINE_CONSTANT:
newline_btn.select() newline_btn.select()
set_separator_active(False); set_separator_active(False)
else: else:
separator_entry.insert(0, settings["separator"]) separator_entry.insert(0, settings["separator"])
@@ -291,19 +379,19 @@ def edit_settings(root_wnd, apply_method):
settings["double_layout"] = encode_boolean_setting(double_layout.get()) settings["double_layout"] = encode_boolean_setting(double_layout.get())
if not validate_font_size(chosen_font_size): 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 errors_found = True
else: else:
settings["font_size"] = chosen_font_size settings["font_size"] = chosen_font_size
if not validate_server_port(chosen_port): 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 errors_found = True
else: else:
settings["server_port"] = chosen_port settings["server_port"] = chosen_port
if not validate_separator(chosen_separator): 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 errors_found = True
else: else:
settings["separator"] = chosen_separator settings["separator"] = chosen_separator
@@ -324,35 +412,63 @@ def edit_settings(root_wnd, apply_method):
text=config.SETTINGS_WINDOW["CANCEL"], text=config.SETTINGS_WINDOW["CANCEL"],
font=config.GUI_FONT) font=config.GUI_FONT)
# Place all components # Place all components with updated positions
font_label.place(x=15, y=15) y_offset = 15
font_size_label.place(x=15, y=55) font_label.place(x=15, y=y_offset)
text_color_label.place(x=15, y=95) y_offset += 40
bg_color_label.place(x=15, y=135) font_size_label.place(x=15, y=y_offset)
layout_label.place(x=15, y=175) y_offset += 40
newline_label.place(x=15, y=215) text_color_label.place(x=15, y=y_offset)
separator_label.place(x=15, y=240) y_offset += 40
port_label.place(x=15, y=280) bg_color_label.place(x=15, y=y_offset)
default_port_label.place(x=15, y=305) y_offset += 40
layout_label.place(x=15, y=y_offset)
y_offset += 25
newline_label.place(x=15, y=y_offset)
y_offset += 25
separator_label.place(x=15, y=y_offset)
y_offset += 40
port_label.place(x=15, y=y_offset)
y_offset += 25
default_port_label.place(x=15, y=y_offset)
font_dropdown.place(x=208, y=15) # Place input controls
font_size_entry.place(x=210, y=55) y_offset = 15
text_color.place(x=210, y=95) font_dropdown.place(x=208, y=y_offset)
bg_color.place(x=210, y=135) y_offset += 40
double_layout_btn.place(x=210, y=175) font_size_entry.place(x=210, y=y_offset)
newline_btn.place(x=210, y=215) y_offset += 40
separator_entry.place(x=210, y=240) text_color.place(x=210, y=y_offset)
port_entry.place(x=210, y=280) y_offset += 40
bg_color.place(x=210, y=y_offset)
y_offset += 40
double_layout_btn.place(x=210, y=y_offset)
y_offset += 25
newline_btn.place(x=210, y=y_offset)
y_offset += 25
separator_entry.place(x=210, y=y_offset)
y_offset += 40
port_entry.place(x=210, y=y_offset)
save_btn.place(x=110, y=350) # Place buttons at bottom
cancel_btn.place(x=190, y=350) save_btn.place(x=110, y=450)
cancel_btn.place(x=190, y=450)
# 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): def validate_color(color):
""" """
Returns whether or not given color is valid in the hexadecimal format. 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): def validate_font_size(size):
@@ -364,62 +480,75 @@ def validate_font_size(size):
except: except:
return False return False
return 0 < size < 70 return 6 <= size <= 72
def validate_server_port(port): 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: try:
int(port) port_num = int(port)
return True return 1024 <= port_num <= 65535
except: except:
return False return False
def save_settings(settings):
"""
Saves given settings to the settings file.
"""
file_content = ""
for key in settings.keys():
file_content += key + "=" + settings[key] + "\n"
set_settings_file_content(file_content)
def decode_boolean_setting(setting): def decode_boolean_setting(setting):
""" """
Decodes a boolean string of "True" or "False" Decodes a boolean string of "True" or "False"
to the coorect boolean value. to the correct boolean value.
Enhanced to handle multiple formats.
""" """
return setting == "True" return str(setting).lower() in ("true", "1", "yes", "on")
def encode_boolean_setting(value): 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" if value else "false"
return "True"
else:
return "False"
def validate_pixels(pixels): def validate_pixels(pixels):
""" """
Checks if given string can be used as a pixel value for height or width. 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: try:
pixels = int(pixels) pixels = int(pixels)
except: except:
return False return False
return 0 < pixels < 10000 return 200 <= pixels <= 10000
def validate_separator(separator): def validate_separator(separator):
return separator.strip() """
Validates the separator string.
"""
if separator == config.NEWLINE_CONSTANT:
return True
return len(separator.strip()) > 0
def debug_settings():
"""Debug function to print current settings"""
print("\n=== Settings Debug ===")
try:
settings = load_settings()
print("Current settings:")
for key, value in settings.items():
print(f" {key}: {value}")
print(f"\nSettings file location: {settings_path}")
print(f"Settings file exists: {os.path.exists(settings_path)}")
if os.path.exists(settings_path):
with open(settings_path, 'r') as f:
content = f.read()
print(f"\nRaw file content:\n{content}")
except Exception as e:
print(f"Error in debug_settings: {e}")
print("=== End Settings Debug ===\n")
+237
View File
@@ -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
+368
View File
@@ -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
+108
View File
@@ -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
+55
View File
@@ -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',
)
+75
View File
@@ -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'
])
]
)
"""