mirror of
https://github.com/ApfelTeeSaft/SplitNotes.git
synced 2026-08-27 11:53:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44ff009021 | ||
|
|
d33dbd523e | ||
|
|
59f075c247 | ||
|
|
fdbbc707fc | ||
|
|
e19c33b166 | ||
|
|
ca2a6d93a6 | ||
|
|
199acd4847 | ||
|
|
3ae68fa291 | ||
|
|
9ba8edc8d9 | ||
|
|
91d71308df | ||
|
|
6e30be4b4a | ||
|
|
a49e0934af | ||
|
|
fe83570cec |
@@ -0,0 +1,191 @@
|
||||
# Makefile for SplitNotes cross-platform builds
|
||||
|
||||
# Variables
|
||||
PYTHON := python3
|
||||
PIP := pip3
|
||||
APP_NAME := SplitNotes
|
||||
VERSION := 1.0.0
|
||||
|
||||
# Default target
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "SplitNotes Build System"
|
||||
@echo "======================"
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@echo " setup - Install build dependencies"
|
||||
@echo " run - Run from source"
|
||||
@echo " clean - Clean build artifacts"
|
||||
@echo " build-windows - Build Windows executable"
|
||||
@echo " build-macos - Build macOS application"
|
||||
@echo " build-linux - Build Linux executable"
|
||||
@echo " build-all - Build for all platforms (if tools available)"
|
||||
@echo " package-linux - Create Linux package structure"
|
||||
@echo " test - Run basic tests"
|
||||
@echo " lint - Run code linting"
|
||||
@echo " format - Format code with black"
|
||||
@echo ""
|
||||
|
||||
# Setup development environment
|
||||
.PHONY: setup
|
||||
setup:
|
||||
@echo "Setting up development environment..."
|
||||
$(PIP) install --upgrade pip
|
||||
@echo "Basic setup complete."
|
||||
@echo ""
|
||||
@echo "For building, install platform-specific tools:"
|
||||
@echo " Windows: pip install cx_Freeze py2exe"
|
||||
@echo " macOS: pip install py2app"
|
||||
@echo " Linux: pip install cx_Freeze"
|
||||
|
||||
# Run from source
|
||||
.PHONY: run
|
||||
run:
|
||||
@echo "Running SplitNotes from source..."
|
||||
$(PYTHON) main_window.py
|
||||
|
||||
# Clean build artifacts
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@echo "Cleaning build artifacts..."
|
||||
rm -rf build/
|
||||
rm -rf dist/
|
||||
rm -rf *.egg-info/
|
||||
rm -rf __pycache__/
|
||||
rm -rf resources/__pycache__/
|
||||
find . -name "*.pyc" -delete
|
||||
find . -name "*.pyo" -delete
|
||||
@echo "Clean complete."
|
||||
|
||||
# Build Windows executable
|
||||
.PHONY: build-windows
|
||||
build-windows:
|
||||
@echo "Building Windows executable..."
|
||||
@if command -v python >/dev/null 2>&1; then \
|
||||
python setup_windows.py build; \
|
||||
else \
|
||||
echo "Error: Python not found. Install Python for Windows."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "Windows build complete. Check build/ directory."
|
||||
|
||||
# Build macOS application
|
||||
.PHONY: build-macos
|
||||
build-macos:
|
||||
@echo "Building macOS application..."
|
||||
@if [ "$$(uname)" = "Darwin" ]; then \
|
||||
$(PYTHON) setup_mac.py py2app; \
|
||||
echo "macOS build complete. Check dist/ directory."; \
|
||||
else \
|
||||
echo "Error: macOS build must be run on macOS."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Build Linux executable
|
||||
.PHONY: build-linux
|
||||
build-linux:
|
||||
@echo "Building Linux executable..."
|
||||
$(PYTHON) setup_linux.py build
|
||||
@echo "Linux build complete. Check build/ directory."
|
||||
|
||||
# Create Linux package structure
|
||||
.PHONY: package-linux
|
||||
package-linux: build-linux
|
||||
@echo "Creating Linux package..."
|
||||
$(PYTHON) setup_linux.py package
|
||||
@echo "Linux package complete."
|
||||
|
||||
# Build for all platforms (if tools are available)
|
||||
.PHONY: build-all
|
||||
build-all:
|
||||
@echo "Building for all available platforms..."
|
||||
@if command -v python >/dev/null 2>&1 && python -c "import cx_Freeze" 2>/dev/null; then \
|
||||
echo "Building Windows..."; \
|
||||
make build-windows; \
|
||||
else \
|
||||
echo "Skipping Windows build (cx_Freeze not available)"; \
|
||||
fi
|
||||
@if [ "$$(uname)" = "Darwin" ] && $(PYTHON) -c "import py2app" 2>/dev/null; then \
|
||||
echo "Building macOS..."; \
|
||||
make build-macos; \
|
||||
else \
|
||||
echo "Skipping macOS build (not on macOS or py2app not available)"; \
|
||||
fi
|
||||
@if $(PYTHON) -c "import cx_Freeze" 2>/dev/null; then \
|
||||
echo "Building Linux..."; \
|
||||
make build-linux; \
|
||||
else \
|
||||
echo "Skipping Linux build (cx_Freeze not available)"; \
|
||||
fi
|
||||
|
||||
# Basic functionality test
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "Running basic tests..."
|
||||
@echo "Testing imports..."
|
||||
$(PYTHON) -c "import config; import ls_connection; import note_reader; import setting_handler; print('All modules imported successfully')"
|
||||
@echo "Testing configuration..."
|
||||
$(PYTHON) -c "import config; print(f'App: {config.APP_NAME} v{config.APP_VERSION}')"
|
||||
@echo "Testing note parsing..."
|
||||
@echo "Test note 1\n\nTest note 2" > test_notes.txt
|
||||
$(PYTHON) -c "import note_reader; notes = note_reader.get_notes('test_notes.txt', 'new_line'); print(f'Parsed {len(notes)} notes')"
|
||||
rm -f test_notes.txt
|
||||
@echo "Basic tests passed."
|
||||
|
||||
# Code linting (if flake8 is available)
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@if $(PYTHON) -c "import flake8" 2>/dev/null; then \
|
||||
echo "Running flake8 linting..."; \
|
||||
$(PYTHON) -m flake8 *.py --max-line-length=100 --ignore=E501,W503; \
|
||||
else \
|
||||
echo "flake8 not available. Install with: pip install flake8"; \
|
||||
fi
|
||||
|
||||
# Code formatting (if black is available)
|
||||
.PHONY: format
|
||||
format:
|
||||
@if $(PYTHON) -c "import black" 2>/dev/null; then \
|
||||
echo "Formatting code with black..."; \
|
||||
$(PYTHON) -m black *.py --line-length=100; \
|
||||
else \
|
||||
echo "black not available. Install with: pip install black"; \
|
||||
fi
|
||||
|
||||
# Create resources directory with placeholder files
|
||||
.PHONY: setup-resources
|
||||
setup-resources:
|
||||
@echo "Setting up resources directory..."
|
||||
@mkdir -p resources
|
||||
@if [ ! -f resources/green.png ]; then \
|
||||
echo "Creating placeholder green.png..."; \
|
||||
touch resources/green.png; \
|
||||
fi
|
||||
@if [ ! -f resources/red.png ]; then \
|
||||
echo "Creating placeholder red.png..."; \
|
||||
touch resources/red.png; \
|
||||
fi
|
||||
@if [ ! -f resources/settings_icon.png ]; then \
|
||||
echo "Creating placeholder settings_icon.png..."; \
|
||||
touch resources/settings_icon.png; \
|
||||
fi
|
||||
@echo "Resources directory ready. Replace placeholder files with actual icons."
|
||||
|
||||
# Development server (just runs the app)
|
||||
.PHONY: dev
|
||||
dev: run
|
||||
|
||||
# Show system information
|
||||
.PHONY: info
|
||||
info:
|
||||
@echo "System Information:"
|
||||
@echo "=================="
|
||||
@echo "OS: $$(uname -s)"
|
||||
@echo "Architecture: $$(uname -m)"
|
||||
@echo "Python: $$($(PYTHON) --version)"
|
||||
@echo "Python Path: $$(which $(PYTHON))"
|
||||
@echo ""
|
||||
@echo "Python Modules:"
|
||||
@$(PYTHON) -c "import sys; print('tkinter:', 'available' if 'tkinter' in sys.modules or __import__('tkinter') else 'missing')" 2>/dev/null || echo "tkinter: missing"
|
||||
@$(PYTHON) -c "import socket; print('socket: available')" 2>/dev/null || echo "socket: missing"
|
||||
@$(PYTHON) -c "import threading; print('threading: available')" 2>/dev/null || echo "threading: missing"
|
||||
@@ -1,57 +1,228 @@
|
||||
# SplitNotes
|
||||
Software for syncing notes with LiveSplit using the LiveSplit server component.
|
||||
|
||||
Splitnotes automatically shows notes for the split you are currently on.
|
||||
|
||||

|
||||
Cross-platform software for syncing notes with LiveSplit using the LiveSplit server component.
|
||||
|
||||
SplitNotes automatically shows notes for the split you are currently on, with support for Windows, macOS, and Linux.
|
||||
|
||||

|
||||

|
||||
|
||||
## Install
|
||||
1. Download the latest version of LiveSplit Server Component from [this](https://github.com/LiveSplit/LiveSplit.Server/releases) site.
|
||||
2. Unzip and move the files to the component folder in your LiveSplit install (...\LiveSplit\Components)
|
||||
3. Download the latest version of SplitNotes from [this](https://github.com/joelnir/SplitNotes/releases) page.
|
||||
4. Unzip SplitNotes anywhere.
|
||||
## Features
|
||||
|
||||
## How To Use
|
||||
**Connect to LiveSplit**
|
||||
1. Launch Splitnotes.exe
|
||||
2. Launch LiveSplit
|
||||
3. Go to "Edit Layout" -> "+" -> "Control" -> "LiveSplit Server". Hit ok. (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.
|
||||
* **Cross-platform support** - Works on Windows, macOS, and Linux
|
||||
* **Automatic note display** based on the active split in LiveSplit
|
||||
* **Preview functionality** - Use arrow keys to preview notes when no run is active
|
||||
* **Dual layout modes** - Single or double layout to show current and next split notes
|
||||
* **Customizable appearance** - Adjustable fonts, colors, and window size
|
||||
* **Flexible note format** - Support for custom split separators
|
||||
* **Live connection status** - Visual indicator of LiveSplit connection
|
||||
|
||||
**Format your notes**
|
||||
It is recommended to use a .txt file to store your notes.
|
||||
Note files should be formatted as following:
|
||||
|
||||
* Normal text is treated as notes
|
||||
* 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.
|
||||
|
||||
## Features
|
||||
|
||||
* Automatically displayed notes based on the active split in LiveSplit.
|
||||
* Ability to preview notes in the software when no run is going on by using the right and left arrow keys.
|
||||
* Two different font sizes to make sure that notes are easy to read.
|
||||
* Double layout to preview notes for both current and next split.
|
||||
## System Requirements
|
||||
|
||||
#### Development
|
||||
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).*
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -1,9 +1,16 @@
|
||||
# CONFIG FILE WITH CONSTANTS
|
||||
# CONFIG FILE WITH CONSTANTS - Complete Bridge Server Support
|
||||
import platform
|
||||
import os
|
||||
|
||||
# Livesplit connection
|
||||
HOST = "localhost"
|
||||
PORT = 16834
|
||||
|
||||
# Bridge server settings
|
||||
BRIDGE_HOST = "localhost"
|
||||
BRIDGE_PORT = 16835
|
||||
BRIDGE_ENABLED = False
|
||||
|
||||
# In network communication, time out after this time. (in seconds)
|
||||
COM_TIMEOUT = 0.5
|
||||
|
||||
@@ -31,20 +38,27 @@ SETTINGS_FILE = "config.cfg"
|
||||
# Default Scrollbar Width
|
||||
SCROLLBAR_WIDTH = 16
|
||||
|
||||
# Popup menu options
|
||||
# Popup menu options - Enhanced with bridge settings
|
||||
MENU_OPTIONS = {
|
||||
"SINGLE": "Set Single Layout",
|
||||
"DOUBLE": "Set Double Layout",
|
||||
"LOAD": "Load Notes",
|
||||
"BIG": "Big Font",
|
||||
"SMALL": "Small Font",
|
||||
"SETTINGS": "Settings"
|
||||
"SETTINGS": "Settings",
|
||||
"BRIDGE": "Bridge Settings"
|
||||
}
|
||||
|
||||
# Error messages
|
||||
ERRORS = {"NOTES_EMPTY": ("Error!", "Notes empty or can't be loaded!"),
|
||||
"FONT_SIZE": ("Error!", "Invalid Font Size!"),
|
||||
"SERVER_PORT": ("Error!", "Invalid server port!")}
|
||||
# Error messages - Enhanced with bridge server errors
|
||||
ERRORS = {
|
||||
"NOTES_EMPTY": ("Error!", "Notes empty or can't be loaded!"),
|
||||
"FONT_SIZE": ("Error!", "Invalid Font Size!"),
|
||||
"SERVER_PORT": ("Error!", "Invalid server port!"),
|
||||
"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 = 1000000000 # 1 Giga-Byte
|
||||
@@ -55,34 +69,60 @@ RUNNING_ALERT = "RUNNING"
|
||||
# Font for gui widgets
|
||||
GUI_FONT = ("arial", 12)
|
||||
|
||||
# Files tht should be displayed and opened a notes
|
||||
# Files that should be displayed and opened as notes
|
||||
TEXT_FILES = [
|
||||
("Text Files", ("*.txt", "*.log", "*.asc", "*.conf", "*.cfg")),
|
||||
('All', '*')
|
||||
]
|
||||
|
||||
# Default content of config.cfg file
|
||||
DEFAULT_CONFIG = "notes=\nfont_size=12\nfont=arial\ntext_color=#000000\nbackground_color=#FFFFFF\ndouble_layout=False\nserver_port=16834\nwidth=400\nheight=300"
|
||||
# Default content of config.cfg file - COMPLETE with proper bridge settings
|
||||
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"""
|
||||
|
||||
# Required settings
|
||||
REQUIRED_SETTINGS = ("notes",
|
||||
"font",
|
||||
"font_size",
|
||||
"text_color",
|
||||
"background_color",
|
||||
"server_port",
|
||||
"double_layout",
|
||||
"width",
|
||||
"height"
|
||||
)
|
||||
NEWLINE_CONSTANT = "new_line"
|
||||
|
||||
# Required settings - COMPLETE with bridge settings
|
||||
REQUIRED_SETTINGS = (
|
||||
"notes",
|
||||
"font",
|
||||
"font_size",
|
||||
"text_color",
|
||||
"background_color",
|
||||
"server_port",
|
||||
"double_layout",
|
||||
"width",
|
||||
"height",
|
||||
"separator",
|
||||
"bridge_enabled",
|
||||
"bridge_port"
|
||||
)
|
||||
|
||||
# Settings window options
|
||||
SETTINGS_WINDOW = {"TITLE": "Settings",
|
||||
"WIDTH": 360,
|
||||
"HEIGHT": 330,
|
||||
"HEIGHT": 410,
|
||||
"CANCEL": "Cancel",
|
||||
"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
|
||||
SETTINGS_OPTIONS = {"FONT": "Font",
|
||||
"FONT_SIZE": "Font Size",
|
||||
@@ -90,14 +130,120 @@ SETTINGS_OPTIONS = {"FONT": "Font",
|
||||
"BG_COLOR": "Background Color",
|
||||
"SERVER_PORT": "LiveSplit Server port",
|
||||
"DEFAULT_SERVER_PORT": "(Default is 16834)",
|
||||
"DOUBLE_LAYOUT": "Use double layout"}
|
||||
"DOUBLE_LAYOUT": "Use double layout",
|
||||
"NEW_LINE_SEPARATOR": "Newline as split separator",
|
||||
"CUSTOM_SEPARATOR": "Custom split separator"}
|
||||
|
||||
# Fonts that can be selected
|
||||
AVAILABLE_FONTS = ("arial",
|
||||
"courier new",
|
||||
"fixedsys",
|
||||
"ms sans serif",
|
||||
"ms serif",
|
||||
"system",
|
||||
"times new roman",
|
||||
"verdana")
|
||||
# Bridge server settings options
|
||||
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",
|
||||
"fixedsys",
|
||||
"ms sans serif",
|
||||
"ms serif",
|
||||
"system",
|
||||
"times new roman",
|
||||
"verdana")
|
||||
|
||||
AVAILABLE_FONTS = get_available_fonts()
|
||||
|
||||
# Platform-specific configurations
|
||||
PLATFORM = platform.system()
|
||||
IS_WINDOWS = PLATFORM == "Windows"
|
||||
IS_MACOS = PLATFORM == "Darwin"
|
||||
IS_LINUX = PLATFORM == "Linux"
|
||||
|
||||
# Default font adjustments for different platforms
|
||||
if IS_MACOS:
|
||||
GUI_FONT = ("SF Pro Display", 12)
|
||||
elif IS_LINUX:
|
||||
GUI_FONT = ("Ubuntu", 12)
|
||||
|
||||
# Application info for packaging
|
||||
APP_NAME = "SplitNotes"
|
||||
APP_VERSION = "1.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
@@ -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.
|
||||
+22
-11
@@ -11,6 +11,7 @@ import select # used for checking if socket has data pending
|
||||
def ls_connect(ls_socket, call_func, window, server_port):
|
||||
"""Connects given socket to the livesplit server."""
|
||||
con_thread = Thread(target=try_connection, args=(ls_socket, call_func, window, server_port))
|
||||
con_thread.daemon = True # Make thread daemon so it doesn't prevent app exit
|
||||
con_thread.start()
|
||||
|
||||
|
||||
@@ -36,7 +37,10 @@ def try_connection(ls_socket, call_func, window, server_port):
|
||||
|
||||
def close_socket(com_socket):
|
||||
"""Closes given socket."""
|
||||
com_socket.close()
|
||||
try:
|
||||
com_socket.close()
|
||||
except:
|
||||
pass # Socket might already be closed
|
||||
|
||||
|
||||
def check_connection(ls_socket):
|
||||
@@ -56,7 +60,7 @@ def send_to_ls(ls_socket, command):
|
||||
Sends given command to ls using given socket.
|
||||
If connected is False, tries to send without socket being connected to ls.
|
||||
Returns the response, or False if an error occurs.
|
||||
Check config.LS_COMMANDS for avaiable commands.
|
||||
Check config.LS_COMMANDS for available commands.
|
||||
"""
|
||||
|
||||
try:
|
||||
@@ -64,13 +68,17 @@ def send_to_ls(ls_socket, command):
|
||||
except:
|
||||
return False
|
||||
|
||||
socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT)
|
||||
if socket_ready[0]:
|
||||
try:
|
||||
return (ls_socket.recv(1000)).decode("utf-8")
|
||||
except:
|
||||
# Use select to check if data is available, with timeout
|
||||
try:
|
||||
socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT)
|
||||
if socket_ready[0]:
|
||||
try:
|
||||
return (ls_socket.recv(1000)).decode("utf-8")
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
@@ -85,7 +93,10 @@ def get_split_index(ls_socket):
|
||||
ls_data = send_to_ls(ls_socket, "cur_split_index")
|
||||
|
||||
if not isinstance(ls_data, bool):
|
||||
return int(ls_data)
|
||||
try:
|
||||
return int(ls_data.strip())
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -98,6 +109,6 @@ def get_split_name(ls_socket):
|
||||
ls_data = send_to_ls(ls_socket, "cur_split_name")
|
||||
|
||||
if ls_data:
|
||||
return ls_data
|
||||
return ls_data.strip()
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
+868
-162
File diff suppressed because it is too large
Load Diff
+77
-43
@@ -5,9 +5,10 @@ import config
|
||||
|
||||
"""
|
||||
NOTE STANDARD FORMATTING
|
||||
|
||||
empty newlines separate notes for different splits
|
||||
|
||||
It is also possible to set your own split separator in the settings menu
|
||||
|
||||
lines that start and end with [ ] are ignored for notes.
|
||||
these can be used for titles.
|
||||
(ex. [Split1] is not included in notes)
|
||||
@@ -24,91 +25,116 @@ def get_note_lines(file_path):
|
||||
"""
|
||||
|
||||
# check so file isn't too big
|
||||
if path.getsize(file_path) > config.MAX_FILE_SIZE:
|
||||
try:
|
||||
if path.getsize(file_path) > config.MAX_FILE_SIZE:
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
try:
|
||||
notes_file = open(file_path, "r")
|
||||
except:
|
||||
# Try different encodings for cross-platform compatibility
|
||||
encodings = ['utf-8', 'utf-8-sig', 'latin-1', 'cp1252']
|
||||
notes_file = None
|
||||
|
||||
for encoding in encodings:
|
||||
try:
|
||||
notes_file = open(file_path, "r", encoding=encoding)
|
||||
break
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
if notes_file is None:
|
||||
return False
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# read file line per line
|
||||
f_lines = []
|
||||
keep_reading = True
|
||||
while keep_reading:
|
||||
|
||||
try:
|
||||
cur_line = notes_file.readline()
|
||||
except:
|
||||
return False
|
||||
|
||||
if cur_line:
|
||||
f_lines.append(cur_line)
|
||||
else:
|
||||
keep_reading = False
|
||||
try:
|
||||
with notes_file:
|
||||
for line in notes_file:
|
||||
f_lines.append(line)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return f_lines
|
||||
|
||||
|
||||
def encode_notes(note_lines):
|
||||
def decode_notes(note_lines, separator):
|
||||
"""
|
||||
Takes a list containing strings.
|
||||
Encodes given strings according to the note formatting.
|
||||
Returns the list containing the notes for every split.
|
||||
"""
|
||||
|
||||
# Check if newline is being used as separator
|
||||
if separator == config.NEWLINE_CONSTANT:
|
||||
separator = "" # left after stripping newline
|
||||
|
||||
def is_title(line):
|
||||
if not line:
|
||||
return False
|
||||
stripped = line.strip()
|
||||
return stripped.startswith("[") and stripped.endswith("]")
|
||||
|
||||
return (line[0] == "[") and (line[-1] == "]")
|
||||
def is_separator(line):
|
||||
return line.strip() == separator.strip()
|
||||
|
||||
def is_newline(line):
|
||||
return (line == "\n") or (line == "\r")
|
||||
def is_newline(s):
|
||||
return s.strip() == ""
|
||||
|
||||
def remove_new_line(line):
|
||||
if (len(line) >= 1) and (is_newline(line[-1])):
|
||||
return line[:-1]
|
||||
else:
|
||||
return line
|
||||
# Remove trailing newline characters
|
||||
return line.rstrip('\n\r')
|
||||
|
||||
note_list = []
|
||||
cur_notes = ""
|
||||
|
||||
for line in note_lines:
|
||||
# remove whitespace at beginning and end
|
||||
line = line.strip(" ")
|
||||
line = remove_new_line(line)
|
||||
|
||||
if is_newline(line):
|
||||
if cur_notes:
|
||||
note_list.append(cur_notes)
|
||||
if separator == "" and is_newline(line):
|
||||
# Using newline as separator
|
||||
if cur_notes.strip():
|
||||
note_list.append(cur_notes.strip())
|
||||
cur_notes = ""
|
||||
elif separator != "" and is_separator(line):
|
||||
# Using custom separator
|
||||
if cur_notes.strip():
|
||||
note_list.append(cur_notes.strip())
|
||||
cur_notes = ""
|
||||
else:
|
||||
line = remove_new_line(line)
|
||||
if not is_title(line):
|
||||
cur_notes += line + "\n" # newline
|
||||
if cur_notes:
|
||||
cur_notes += "\n"
|
||||
cur_notes += line
|
||||
|
||||
if cur_notes:
|
||||
note_list.append(cur_notes)
|
||||
# Add the last notes if any
|
||||
if cur_notes.strip():
|
||||
note_list.append(cur_notes.strip())
|
||||
|
||||
return note_list
|
||||
|
||||
|
||||
def get_notes(file_path):
|
||||
def get_notes(file_path, separator):
|
||||
"""
|
||||
Takes a path to a file and returns a list with the notes
|
||||
in the file encoded according to the note fromatting.
|
||||
in the file encoded according to the note formatting.
|
||||
|
||||
Returns False if file is empty.
|
||||
"""
|
||||
if not file_exists(file_path):
|
||||
return False
|
||||
|
||||
note_lines = get_note_lines(file_path)
|
||||
|
||||
if not note_lines:
|
||||
return False
|
||||
|
||||
note_list = encode_notes(note_lines)
|
||||
note_list = decode_notes(note_lines, separator)
|
||||
|
||||
return note_list
|
||||
return note_list if note_list else False
|
||||
|
||||
|
||||
def select_file():
|
||||
@@ -117,15 +143,23 @@ def select_file():
|
||||
Returns False upon no file selection.
|
||||
Otherwise returns absolute path to selected file.
|
||||
"""
|
||||
try:
|
||||
file = file_dia.askopenfilename(
|
||||
title="Select Notes File",
|
||||
filetypes=config.TEXT_FILES
|
||||
)
|
||||
|
||||
file = file_dia.askopenfilename(filetypes=config.TEXT_FILES)
|
||||
|
||||
if file:
|
||||
return file
|
||||
else:
|
||||
if file:
|
||||
return file
|
||||
else:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def file_exists(file):
|
||||
"""Checks if given path leads to an existing file."""
|
||||
return path.isfile(file)
|
||||
try:
|
||||
return path.isfile(file)
|
||||
except:
|
||||
return False
|
||||
@@ -0,0 +1,26 @@
|
||||
# Core requirements for SplitNotes
|
||||
# No external dependencies required for basic functionality
|
||||
# All required modules are part of Python standard library:
|
||||
# - tkinter (GUI)
|
||||
# - socket (network communication)
|
||||
# - threading (background connections)
|
||||
# - select (socket polling)
|
||||
# - os, sys, platform (system integration)
|
||||
|
||||
# Build requirements (install only when building executables)
|
||||
|
||||
# For Windows builds:
|
||||
# cx-Freeze>=6.14.0
|
||||
# py2exe>=0.12.0.1 # Alternative for Windows
|
||||
|
||||
# For macOS builds:
|
||||
# py2app>=0.28.0
|
||||
|
||||
# For Linux builds:
|
||||
# cx-Freeze>=6.14.0
|
||||
|
||||
# Development requirements:
|
||||
# flake8>=4.0.0 # Code linting
|
||||
# black>=22.0.0 # Code formatting
|
||||
|
||||
# Note: Uncomment the build requirements you need based on your target platform
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cross-platform launcher for SplitNotes
|
||||
This file can be used as an alternative entry point
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the current directory to Python path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, current_dir)
|
||||
|
||||
def check_requirements():
|
||||
"""Check if all required modules are available"""
|
||||
required_modules = ['tkinter', 'socket', 'threading', 'select']
|
||||
missing_modules = []
|
||||
|
||||
for module in required_modules:
|
||||
try:
|
||||
__import__(module)
|
||||
except ImportError:
|
||||
missing_modules.append(module)
|
||||
|
||||
if missing_modules:
|
||||
print("Error: Missing required modules:")
|
||||
for module in missing_modules:
|
||||
print(f" - {module}")
|
||||
|
||||
print("\nInstallation help:")
|
||||
if 'tkinter' in missing_modules:
|
||||
print(" Ubuntu/Debian: sudo apt install python3-tk")
|
||||
print(" Fedora/RHEL: sudo dnf install python3-tkinter")
|
||||
print(" Arch: sudo pacman -S python-tkinter")
|
||||
print(" macOS: tkinter should be included with Python")
|
||||
print(" Windows: tkinter should be included with Python")
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
print("SplitNotes - Cross-platform LiveSplit notes viewer")
|
||||
print("=" * 50)
|
||||
|
||||
# Check Python version
|
||||
if sys.version_info < (3, 6):
|
||||
print("Error: Python 3.6 or higher is required")
|
||||
print(f"Current version: {sys.version}")
|
||||
sys.exit(1)
|
||||
|
||||
# Check requirements
|
||||
if not check_requirements():
|
||||
sys.exit(1)
|
||||
|
||||
# Check if main_window.py exists
|
||||
main_window_path = os.path.join(current_dir, 'main_window.py')
|
||||
if not os.path.exists(main_window_path):
|
||||
print("Error: main_window.py not found in current directory")
|
||||
sys.exit(1)
|
||||
|
||||
# Check if resources directory exists
|
||||
resources_path = os.path.join(current_dir, 'resources')
|
||||
if not os.path.exists(resources_path):
|
||||
print("Warning: resources/ directory not found")
|
||||
print("Creating resources directory...")
|
||||
try:
|
||||
os.makedirs(resources_path)
|
||||
print("Please add icon files to resources/ directory:")
|
||||
print(" - green.png (connection active)")
|
||||
print(" - red.png (connection inactive)")
|
||||
print(" - settings_icon.png (settings window)")
|
||||
except OSError as e:
|
||||
print(f"Could not create resources directory: {e}")
|
||||
|
||||
print("Starting SplitNotes...")
|
||||
print("Platform:", sys.platform)
|
||||
|
||||
try:
|
||||
# Import and run the main application
|
||||
import main_window
|
||||
# main_window.main() is called automatically when imported
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nApplication interrupted by user")
|
||||
except Exception as e:
|
||||
print(f"Error starting application: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+276
-97
@@ -6,14 +6,23 @@ import sys
|
||||
|
||||
import config
|
||||
|
||||
|
||||
# Cross-platform path handling
|
||||
if getattr(sys, 'frozen', False):
|
||||
# Running as compiled executable
|
||||
application_path = os.path.dirname(sys.executable)
|
||||
else:
|
||||
# Running as script
|
||||
application_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
settings_path = os.path.join(
|
||||
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
|
||||
application_path,
|
||||
config.RESOURCE_FOLDER,
|
||||
config.SETTINGS_FILE
|
||||
)
|
||||
|
||||
settings_icon_path = os.path.join(
|
||||
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
|
||||
application_path,
|
||||
config.RESOURCE_FOLDER,
|
||||
config.ICONS["SETTINGS"]
|
||||
)
|
||||
@@ -27,18 +36,25 @@ def load_settings():
|
||||
returns a dictionary with all settings.
|
||||
"""
|
||||
|
||||
# Ensure resources directory exists
|
||||
os.makedirs(os.path.dirname(settings_path), exist_ok=True)
|
||||
|
||||
# try to open default settings file
|
||||
try:
|
||||
settings_file = open(settings_path, "r+")
|
||||
settings_content = get_file_lines(settings_file)
|
||||
with open(settings_path, "r", encoding='utf-8') as settings_file:
|
||||
settings_content = settings_file.readlines()
|
||||
settings_content = [line.strip() for line in settings_content]
|
||||
print(f"Settings loaded from: {settings_path}")
|
||||
except:
|
||||
# File not found
|
||||
print("Settings file not found, creating default settings")
|
||||
settings_content = set_default_settings()
|
||||
|
||||
settings = format_settings(settings_content)
|
||||
|
||||
# Check so settings file has all settings
|
||||
if not validate_settings(settings):
|
||||
print("Settings validation failed, creating default settings")
|
||||
settings = format_settings(set_default_settings())
|
||||
|
||||
return settings
|
||||
@@ -49,6 +65,7 @@ def set_default_settings():
|
||||
Creates a config file with default settings.
|
||||
Returns the default config-file content.
|
||||
"""
|
||||
print(f"Creating default settings at: {settings_path}")
|
||||
set_settings_file_content(config.DEFAULT_CONFIG)
|
||||
return config.DEFAULT_CONFIG.split("\n")
|
||||
|
||||
@@ -65,78 +82,141 @@ def format_settings(file_rows):
|
||||
settings = {}
|
||||
|
||||
for row in file_rows:
|
||||
row = row.strip("\n")
|
||||
parts = row.split("=", 1)
|
||||
row = row.strip()
|
||||
if '=' in row and not row.startswith('#'): # Skip comments
|
||||
parts = row.split("=", 1)
|
||||
|
||||
if len(parts) == SETTING_PART_LENGTH:
|
||||
# Strip to remove whitespace at end and beginning
|
||||
settings[parts[0].strip()] = parts[1].strip()
|
||||
if len(parts) == SETTING_PART_LENGTH:
|
||||
# Strip to remove whitespace at end and beginning
|
||||
key = parts[0].strip()
|
||||
value = parts[1].strip()
|
||||
settings[key] = value
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def get_file_lines(file):
|
||||
"""
|
||||
Returns a list containing all the lines of the gicen file.
|
||||
"""
|
||||
# read file line per line
|
||||
f_lines = []
|
||||
keep_reading = True
|
||||
while keep_reading:
|
||||
cur_line = file.readline()
|
||||
|
||||
if cur_line:
|
||||
f_lines.append(cur_line)
|
||||
else:
|
||||
keep_reading = False
|
||||
|
||||
return f_lines
|
||||
|
||||
|
||||
def validate_settings(settings):
|
||||
"""
|
||||
Checks a settings dictionary so that all the needed settings are present.
|
||||
Enhanced with comprehensive bridge settings validation.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
if not validate_font_size(settings["font_size"]):
|
||||
print(f"Invalid font size: {settings['font_size']}")
|
||||
return False
|
||||
|
||||
if not validate_server_port(settings["server_port"]):
|
||||
print(f"Invalid server port: {settings['server_port']}")
|
||||
return False
|
||||
|
||||
if not validate_color(settings["text_color"]):
|
||||
print(f"Invalid text color: {settings['text_color']}")
|
||||
return False
|
||||
|
||||
if not validate_color(settings["background_color"]):
|
||||
print(f"Invalid background color: {settings['background_color']}")
|
||||
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
|
||||
|
||||
if not ((settings["double_layout"] == "True") or
|
||||
(settings["double_layout"] == "False")):
|
||||
if settings["double_layout"].lower() not in ["true", "false"]:
|
||||
print(f"Invalid double_layout: {settings['double_layout']}")
|
||||
return False
|
||||
|
||||
if not validate_pixels(settings["width"]):
|
||||
print(f"Invalid width: {settings['width']}")
|
||||
return False
|
||||
|
||||
if not validate_pixels(settings["height"]):
|
||||
print(f"Invalid height: {settings['height']}")
|
||||
return False
|
||||
|
||||
if not validate_separator(settings["separator"]):
|
||||
print(f"Invalid separator: {settings['separator']}")
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Saves given content to the config file, config.cfg, in the resources directory.
|
||||
"""
|
||||
settings_file = open(settings_path, "w")
|
||||
settings_file.write(content)
|
||||
settings_file.close()
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(settings_path), exist_ok=True)
|
||||
|
||||
try:
|
||||
with open(settings_path, "w", encoding='utf-8') as settings_file:
|
||||
settings_file.write(content)
|
||||
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):
|
||||
@@ -149,13 +229,29 @@ def edit_settings(root_wnd, apply_method):
|
||||
width=config.SETTINGS_WINDOW["WIDTH"],
|
||||
height=config.SETTINGS_WINDOW["HEIGHT"])
|
||||
settings_wnd.title(config.SETTINGS_WINDOW["TITLE"])
|
||||
settings_icon = tkinter.Image("photo", file=settings_icon_path)
|
||||
settings_wnd.tk.call('wm', 'iconphoto', settings_wnd._w, settings_icon)
|
||||
|
||||
# Try to load settings icon
|
||||
try:
|
||||
if os.path.exists(settings_icon_path):
|
||||
settings_icon = tkinter.PhotoImage(file=settings_icon_path)
|
||||
settings_wnd.iconphoto(False, settings_icon)
|
||||
except:
|
||||
pass # Icon loading failed, continue without icon
|
||||
|
||||
settings = load_settings()
|
||||
|
||||
settings_wnd.resizable(0, 0)
|
||||
settings_wnd.transient(root_wnd) # Make it a dialog
|
||||
settings_wnd.grab_set() # Make it modal
|
||||
|
||||
# Center the window
|
||||
settings_wnd.geometry(f"{config.SETTINGS_WINDOW['WIDTH']}x{config.SETTINGS_WINDOW['HEIGHT']}")
|
||||
settings_wnd.update_idletasks()
|
||||
x = (settings_wnd.winfo_screenwidth() // 2) - (config.SETTINGS_WINDOW['WIDTH'] // 2)
|
||||
y = (settings_wnd.winfo_screenheight() // 2) - (config.SETTINGS_WINDOW['HEIGHT'] // 2)
|
||||
settings_wnd.geometry(f"+{x}+{y}")
|
||||
|
||||
# Create labels
|
||||
font_label = tkinter.Label(settings_wnd,
|
||||
text=config.SETTINGS_OPTIONS["FONT"],
|
||||
font=config.GUI_FONT)
|
||||
@@ -171,6 +267,12 @@ def edit_settings(root_wnd, apply_method):
|
||||
layout_label = tkinter.Label(settings_wnd,
|
||||
text=config.SETTINGS_OPTIONS["DOUBLE_LAYOUT"],
|
||||
font=config.GUI_FONT)
|
||||
newline_label = tkinter.Label(settings_wnd,
|
||||
text=config.SETTINGS_OPTIONS["NEW_LINE_SEPARATOR"],
|
||||
font=config.GUI_FONT)
|
||||
separator_label = tkinter.Label(settings_wnd,
|
||||
text=config.SETTINGS_OPTIONS["CUSTOM_SEPARATOR"],
|
||||
font=config.GUI_FONT)
|
||||
port_label = tkinter.Label(settings_wnd,
|
||||
text=config.SETTINGS_OPTIONS["SERVER_PORT"],
|
||||
font=config.GUI_FONT)
|
||||
@@ -188,26 +290,24 @@ def edit_settings(root_wnd, apply_method):
|
||||
font_dropdown.configure(font=config.GUI_FONT)
|
||||
|
||||
# Font Size Selection
|
||||
font_size_entry = tkinter.Entry(settings_wnd, width=2, font=config.GUI_FONT)
|
||||
font_size_entry = tkinter.Entry(settings_wnd, width=5, font=config.GUI_FONT)
|
||||
font_size_entry.insert(0, settings["font_size"])
|
||||
|
||||
# Text Color Selection
|
||||
text_color = tkinter.Button(settings_wnd,
|
||||
width=3,
|
||||
height=1,
|
||||
)
|
||||
height=1)
|
||||
|
||||
if validate_color(settings["text_color"]):
|
||||
text_color.configure(background=settings["text_color"])
|
||||
text_color.configure(background=settings["text_color"])
|
||||
else:
|
||||
text_color.configure(background="#000000")
|
||||
|
||||
def text_color_selection():
|
||||
choosen_color = colorchooser.askcolor()
|
||||
if choosen_color[1]:
|
||||
settings["text_color"] = choosen_color[1]
|
||||
chosen_color = colorchooser.askcolor(parent=settings_wnd)
|
||||
if chosen_color[1]:
|
||||
settings["text_color"] = chosen_color[1]
|
||||
text_color.configure(background=settings["text_color"])
|
||||
|
||||
settings_wnd.focus_force()
|
||||
|
||||
text_color.configure(command=text_color_selection)
|
||||
@@ -218,22 +318,21 @@ def edit_settings(root_wnd, apply_method):
|
||||
height=1)
|
||||
|
||||
if validate_color(settings["background_color"]):
|
||||
bg_color.configure(background=settings["background_color"])
|
||||
bg_color.configure(background=settings["background_color"])
|
||||
else:
|
||||
bg_color.configure(background="#FFFFFF")
|
||||
|
||||
def bg_color_selection():
|
||||
choosen_color = colorchooser.askcolor()
|
||||
if choosen_color[1]:
|
||||
settings["background_color"] = choosen_color[1]
|
||||
chosen_color = colorchooser.askcolor(parent=settings_wnd)
|
||||
if chosen_color[1]:
|
||||
settings["background_color"] = chosen_color[1]
|
||||
bg_color.configure(background=settings["background_color"])
|
||||
|
||||
settings_wnd.focus_force()
|
||||
|
||||
bg_color.configure(command=bg_color_selection)
|
||||
|
||||
# Server port Selection
|
||||
port_entry = tkinter.Entry(settings_wnd, width=6, font=config.GUI_FONT)
|
||||
port_entry = tkinter.Entry(settings_wnd, width=8, font=config.GUI_FONT)
|
||||
port_entry.insert(0, settings["server_port"])
|
||||
|
||||
# Double Layout Selection
|
||||
@@ -243,6 +342,26 @@ def edit_settings(root_wnd, apply_method):
|
||||
if decode_boolean_setting(settings["double_layout"]):
|
||||
double_layout_btn.select()
|
||||
|
||||
# Separator selection
|
||||
separator_entry = tkinter.Entry(settings_wnd, width=14, font=config.GUI_FONT)
|
||||
|
||||
def set_separator_active(active):
|
||||
if active:
|
||||
separator_entry.configure(state="normal")
|
||||
else:
|
||||
separator_entry.configure(state="disabled")
|
||||
|
||||
use_newline = tkinter.BooleanVar()
|
||||
newline_btn = tkinter.Checkbutton(settings_wnd,
|
||||
variable=use_newline,
|
||||
command=lambda: set_separator_active(not use_newline.get()))
|
||||
|
||||
if settings["separator"] == config.NEWLINE_CONSTANT:
|
||||
newline_btn.select()
|
||||
set_separator_active(False)
|
||||
else:
|
||||
separator_entry.insert(0, settings["separator"])
|
||||
|
||||
# Save and cancel buttons
|
||||
def control_and_save():
|
||||
errors_found = False
|
||||
@@ -252,20 +371,31 @@ def edit_settings(root_wnd, apply_method):
|
||||
chosen_font_size = font_size_entry.get()
|
||||
chosen_port = port_entry.get()
|
||||
|
||||
if use_newline.get():
|
||||
chosen_separator = config.NEWLINE_CONSTANT
|
||||
else:
|
||||
chosen_separator = separator_entry.get()
|
||||
|
||||
settings["double_layout"] = encode_boolean_setting(double_layout.get())
|
||||
|
||||
if not validate_font_size(chosen_font_size):
|
||||
msgbox.showerror(config.ERRORS["FONT_SIZE"][0], config.ERRORS["FONT_SIZE"][1])
|
||||
msgbox.showerror(config.ERRORS["FONT_SIZE"][0], config.ERRORS["FONT_SIZE"][1], parent=settings_wnd)
|
||||
errors_found = True
|
||||
else:
|
||||
settings["font_size"] = chosen_font_size
|
||||
|
||||
if not validate_server_port(chosen_port):
|
||||
msgbox.showerror(config.ERRORS["SERVER_PORT"][0], config.ERRORS["SERVER_PORT"][1])
|
||||
msgbox.showerror(config.ERRORS["SERVER_PORT"][0], config.ERRORS["SERVER_PORT"][1], parent=settings_wnd)
|
||||
errors_found = True
|
||||
else:
|
||||
settings["server_port"] = chosen_port
|
||||
|
||||
if not validate_separator(chosen_separator):
|
||||
msgbox.showerror(config.ERRORS["SEPARATOR"][0], config.ERRORS["SEPARATOR"][1], parent=settings_wnd)
|
||||
errors_found = True
|
||||
else:
|
||||
settings["separator"] = chosen_separator
|
||||
|
||||
if not errors_found:
|
||||
save_settings(settings)
|
||||
apply_method(settings)
|
||||
@@ -274,39 +404,71 @@ def edit_settings(root_wnd, apply_method):
|
||||
settings_wnd.focus_force()
|
||||
|
||||
save_btn = tkinter.Button(settings_wnd,
|
||||
command=control_and_save,
|
||||
text=config.SETTINGS_WINDOW["SAVE"],
|
||||
font=config.GUI_FONT)
|
||||
command=control_and_save,
|
||||
text=config.SETTINGS_WINDOW["SAVE"],
|
||||
font=config.GUI_FONT)
|
||||
cancel_btn = tkinter.Button(settings_wnd,
|
||||
command=settings_wnd.destroy,
|
||||
text=config.SETTINGS_WINDOW["CANCEL"],
|
||||
font=config.GUI_FONT)
|
||||
|
||||
# Place all components
|
||||
font_label.place(x=15, y=15)
|
||||
font_size_label.place(x=15, y=55)
|
||||
text_color_label.place(x=15, y=95)
|
||||
bg_color_label.place(x=15, y=135)
|
||||
layout_label.place(x=15, y=175)
|
||||
port_label.place(x=15, y=215)
|
||||
default_port_label.place(x=15, y=240)
|
||||
# Place all components with updated positions
|
||||
y_offset = 15
|
||||
font_label.place(x=15, y=y_offset)
|
||||
y_offset += 40
|
||||
font_size_label.place(x=15, y=y_offset)
|
||||
y_offset += 40
|
||||
text_color_label.place(x=15, y=y_offset)
|
||||
y_offset += 40
|
||||
bg_color_label.place(x=15, y=y_offset)
|
||||
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=178, y=15)
|
||||
font_size_entry.place(x=180, y=55)
|
||||
text_color.place(x=180, y=95)
|
||||
bg_color.place(x=180, y=135)
|
||||
double_layout_btn.place(x=180, y=175)
|
||||
port_entry.place(x=180, y=215)
|
||||
# Place input controls
|
||||
y_offset = 15
|
||||
font_dropdown.place(x=208, y=y_offset)
|
||||
y_offset += 40
|
||||
font_size_entry.place(x=210, y=y_offset)
|
||||
y_offset += 40
|
||||
text_color.place(x=210, y=y_offset)
|
||||
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=280)
|
||||
cancel_btn.place(x=190, y=280)
|
||||
# Place buttons at bottom
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
@@ -318,58 +480,75 @@ def validate_font_size(size):
|
||||
except:
|
||||
return False
|
||||
|
||||
return 0 < size < 70
|
||||
return 6 <= size <= 72
|
||||
|
||||
|
||||
def validate_server_port(port):
|
||||
"""
|
||||
Returns Whether or not gicen port is a valid server port.
|
||||
Returns Whether or not given port is a valid server port.
|
||||
"""
|
||||
try:
|
||||
int(port)
|
||||
return True
|
||||
port_num = int(port)
|
||||
return 1024 <= port_num <= 65535
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Encodes a boolean to the string "True" or "False".
|
||||
Encodes a boolean to the string "true" or "false".
|
||||
"""
|
||||
if value:
|
||||
return "True"
|
||||
else:
|
||||
return "False"
|
||||
return "true" if value else "false"
|
||||
|
||||
|
||||
def validate_pixels(pixels):
|
||||
"""
|
||||
Checks if given string can be used as a pixel value for height or width.
|
||||
Height or Width ar assumed to never surpass 10000
|
||||
Height or Width are assumed to never surpass 10000
|
||||
"""
|
||||
try:
|
||||
pixels = int(pixels)
|
||||
except:
|
||||
return False
|
||||
|
||||
return 0 < pixels < 10000
|
||||
return 200 <= pixels <= 10000
|
||||
|
||||
|
||||
def validate_separator(separator):
|
||||
"""
|
||||
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")
|
||||
@@ -0,0 +1,237 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM SplitNotes Windows Setup Script
|
||||
REM ================================
|
||||
|
||||
echo.
|
||||
echo SplitNotes Windows Setup
|
||||
echo ========================
|
||||
echo.
|
||||
|
||||
REM Parse command line argument
|
||||
set "COMMAND=%~1"
|
||||
if "%COMMAND%"=="" set "COMMAND=setup"
|
||||
|
||||
REM Jump to appropriate section
|
||||
if /i "%COMMAND%"=="setup" goto :main_setup
|
||||
if /i "%COMMAND%"=="check" goto :check_system
|
||||
if /i "%COMMAND%"=="build" goto :build_app
|
||||
if /i "%COMMAND%"=="run" goto :run_app
|
||||
if /i "%COMMAND%"=="test" goto :test_app
|
||||
if /i "%COMMAND%"=="clean" goto :clean_build
|
||||
if /i "%COMMAND%"=="help" goto :show_help
|
||||
if /i "%COMMAND%"=="-h" goto :show_help
|
||||
if /i "%COMMAND%"=="--help" goto :show_help
|
||||
|
||||
echo Error: Unknown command "%COMMAND%"
|
||||
goto :show_help
|
||||
|
||||
:main_setup
|
||||
echo [INFO] Starting full setup...
|
||||
call :check_system
|
||||
if errorlevel 1 exit /b 1
|
||||
call :setup_resources
|
||||
call :test_app
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo [SUCCESS] Setup complete!
|
||||
echo.
|
||||
echo Next steps:
|
||||
echo 1. Replace placeholder icons in resources\ with actual PNG files
|
||||
echo 2. Install LiveSplit Server component
|
||||
echo 3. Run: setup.bat run
|
||||
echo.
|
||||
echo To build executable: setup.bat build
|
||||
goto :end
|
||||
|
||||
:check_system
|
||||
echo [INFO] Checking system requirements...
|
||||
|
||||
REM Check for Python
|
||||
set "PYTHON_CMD="
|
||||
python --version >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
for /f "tokens=2" %%i in ('python --version 2^>^&1') do set "PYTHON_VERSION=%%i"
|
||||
set "PYTHON_CMD=python"
|
||||
) else (
|
||||
python3 --version >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
for /f "tokens=2" %%i in ('python3 --version 2^>^&1') do set "PYTHON_VERSION=%%i"
|
||||
set "PYTHON_CMD=python3"
|
||||
)
|
||||
)
|
||||
|
||||
if "%PYTHON_CMD%"=="" (
|
||||
echo [ERROR] Python not found!
|
||||
echo Please install Python 3.6+ from python.org
|
||||
echo Make sure to check "Add Python to PATH" during installation
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [SUCCESS] Python found: %PYTHON_CMD% ^(%PYTHON_VERSION%^)
|
||||
|
||||
REM Check Python version (basic check for 3.x)
|
||||
echo %PYTHON_VERSION% | findstr /r "^3\." >nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Python 3.6+ required, found %PYTHON_VERSION%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check required modules
|
||||
echo [INFO] Checking required modules...
|
||||
|
||||
%PYTHON_CMD% -c "import tkinter" >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] tkinter not available
|
||||
echo tkinter should be included with Python. Try reinstalling Python.
|
||||
exit /b 1
|
||||
) else (
|
||||
echo [SUCCESS] tkinter available
|
||||
)
|
||||
|
||||
for %%m in (socket threading select os sys platform) do (
|
||||
%PYTHON_CMD% -c "import %%m" >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] %%m not available
|
||||
exit /b 1
|
||||
) else (
|
||||
echo [SUCCESS] %%m available
|
||||
)
|
||||
)
|
||||
|
||||
goto :eof
|
||||
|
||||
:setup_resources
|
||||
echo [INFO] Setting up resources directory...
|
||||
|
||||
if not exist "resources" (
|
||||
mkdir resources
|
||||
echo [SUCCESS] Created resources directory
|
||||
)
|
||||
|
||||
REM Create placeholder files if they don't exist
|
||||
for %%f in (green.png red.png settings_icon.png) do (
|
||||
if not exist "resources\%%f" (
|
||||
echo. > "resources\%%f"
|
||||
echo [WARNING] Created placeholder resources\%%f
|
||||
) else (
|
||||
echo [SUCCESS] resources\%%f exists
|
||||
)
|
||||
)
|
||||
|
||||
echo [WARNING] Icon files are placeholders
|
||||
echo [INFO] Replace with actual PNG icons for proper functionality
|
||||
|
||||
goto :eof
|
||||
|
||||
:test_app
|
||||
echo [INFO] Testing application...
|
||||
|
||||
echo [INFO] Testing module imports...
|
||||
%PYTHON_CMD% -c "import config; import ls_connection; import note_reader; import setting_handler; print('All modules imported successfully')" >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Module import failed
|
||||
exit /b 1
|
||||
) else (
|
||||
echo [SUCCESS] Module imports working
|
||||
)
|
||||
|
||||
echo [INFO] Testing note parsing...
|
||||
echo Test note 1> test_notes.txt
|
||||
echo.>> test_notes.txt
|
||||
echo Test note 2>> test_notes.txt
|
||||
|
||||
%PYTHON_CMD% -c "import note_reader; notes = note_reader.get_notes('test_notes.txt', 'new_line'); print(f'Parsed {len(notes)} notes successfully')" >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Note parsing failed
|
||||
del test_notes.txt >nul 2>&1
|
||||
exit /b 1
|
||||
) else (
|
||||
echo [SUCCESS] Note parsing working
|
||||
)
|
||||
|
||||
del test_notes.txt >nul 2>&1
|
||||
echo [SUCCESS] Application tests passed
|
||||
|
||||
goto :eof
|
||||
|
||||
:build_app
|
||||
echo [INFO] Building Windows executable...
|
||||
|
||||
call :check_system
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [INFO] Installing build dependencies...
|
||||
%PYTHON_CMD% -m pip install cx_Freeze
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Failed to install cx_Freeze
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [INFO] Building executable...
|
||||
%PYTHON_CMD% setup_windows.py build
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Build failed
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [SUCCESS] Windows build complete!
|
||||
echo Check the build\ directory for your executable
|
||||
|
||||
goto :end
|
||||
|
||||
:run_app
|
||||
echo [INFO] Running SplitNotes from source...
|
||||
call :check_system
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [INFO] Starting SplitNotes... (Press Ctrl+C to stop)
|
||||
%PYTHON_CMD% main_window.py
|
||||
|
||||
goto :end
|
||||
|
||||
:clean_build
|
||||
echo [INFO] Cleaning build artifacts...
|
||||
|
||||
if exist "build" rmdir /s /q "build"
|
||||
if exist "dist" rmdir /s /q "dist"
|
||||
if exist "*.egg-info" rmdir /s /q "*.egg-info"
|
||||
|
||||
for /r . %%d in (__pycache__) do (
|
||||
if exist "%%d" rmdir /s /q "%%d"
|
||||
)
|
||||
|
||||
del /s /q "*.pyc" >nul 2>&1
|
||||
del /s /q "*.pyo" >nul 2>&1
|
||||
|
||||
echo [SUCCESS] Build artifacts cleaned
|
||||
|
||||
goto :end
|
||||
|
||||
:show_help
|
||||
echo SplitNotes Windows Setup Script
|
||||
echo ================================
|
||||
echo.
|
||||
echo Usage: setup.bat [command]
|
||||
echo.
|
||||
echo Commands:
|
||||
echo setup - Full setup ^(check deps, create resources, test^)
|
||||
echo check - Check system requirements only
|
||||
echo build - Install build dependencies and build
|
||||
echo run - Run the application
|
||||
echo test - Test the application
|
||||
echo clean - Clean build artifacts
|
||||
echo help - Show this help message
|
||||
echo.
|
||||
echo Examples:
|
||||
echo setup.bat setup # First time setup
|
||||
echo setup.bat run # Run the application
|
||||
echo setup.bat build # Build executable
|
||||
echo.
|
||||
|
||||
goto :end
|
||||
|
||||
:end
|
||||
echo.
|
||||
pause
|
||||
@@ -0,0 +1,368 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SplitNotes Cross-Platform Setup Script
|
||||
# This script helps set up SplitNotes on different platforms
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Utility functions
|
||||
print_header() {
|
||||
echo -e "${BLUE}================================${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}================================${NC}"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✓ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}✗ $1${NC}"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}ℹ $1${NC}"
|
||||
}
|
||||
|
||||
# Detect operating system
|
||||
detect_os() {
|
||||
case "$OSTYPE" in
|
||||
darwin*) OS="macos" ;;
|
||||
linux*) OS="linux" ;;
|
||||
msys*|cygwin*|mingw*) OS="windows" ;;
|
||||
*) OS="unknown" ;;
|
||||
esac
|
||||
|
||||
print_info "Detected OS: $OS"
|
||||
}
|
||||
|
||||
# Check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check Python installation
|
||||
check_python() {
|
||||
print_header "Checking Python Installation"
|
||||
|
||||
PYTHON_CMD=""
|
||||
|
||||
if command_exists python3; then
|
||||
PYTHON_CMD="python3"
|
||||
elif command_exists python; then
|
||||
PYTHON_VERSION=$(python --version 2>&1 | cut -d' ' -f2 | cut -d'.' -f1)
|
||||
if [ "$PYTHON_VERSION" = "3" ]; then
|
||||
PYTHON_CMD="python"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$PYTHON_CMD" ]; then
|
||||
print_error "Python 3 not found!"
|
||||
echo "Please install Python 3.6 or higher:"
|
||||
case $OS in
|
||||
"linux")
|
||||
echo " Ubuntu/Debian: sudo apt install python3 python3-pip python3-tk"
|
||||
echo " Fedora/RHEL: sudo dnf install python3 python3-pip python3-tkinter"
|
||||
echo " Arch: sudo pacman -S python python-pip python-tkinter"
|
||||
;;
|
||||
"macos")
|
||||
echo " Install from python.org or use Homebrew: brew install python-tk"
|
||||
;;
|
||||
"windows")
|
||||
echo " Download from python.org (make sure to check 'Add to PATH')"
|
||||
;;
|
||||
esac
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PYTHON_VERSION=$($PYTHON_CMD --version 2>&1 | cut -d' ' -f2)
|
||||
print_success "Python found: $PYTHON_CMD ($PYTHON_VERSION)"
|
||||
|
||||
# Check if version is 3.6+
|
||||
MAJOR=$(echo $PYTHON_VERSION | cut -d'.' -f1)
|
||||
MINOR=$(echo $PYTHON_VERSION | cut -d'.' -f2)
|
||||
|
||||
if [ "$MAJOR" -eq 3 ] && [ "$MINOR" -ge 6 ]; then
|
||||
print_success "Python version is compatible"
|
||||
else
|
||||
print_error "Python 3.6+ required, found $PYTHON_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check required modules
|
||||
check_modules() {
|
||||
print_header "Checking Required Modules"
|
||||
|
||||
MISSING_MODULES=""
|
||||
|
||||
# Check tkinter
|
||||
if ! $PYTHON_CMD -c "import tkinter" 2>/dev/null; then
|
||||
MISSING_MODULES="$MISSING_MODULES tkinter"
|
||||
print_error "tkinter not available"
|
||||
else
|
||||
print_success "tkinter available"
|
||||
fi
|
||||
|
||||
# Check other standard library modules
|
||||
for module in socket threading select os sys platform; do
|
||||
if ! $PYTHON_CMD -c "import $module" 2>/dev/null; then
|
||||
MISSING_MODULES="$MISSING_MODULES $module"
|
||||
print_error "$module not available"
|
||||
else
|
||||
print_success "$module available"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$MISSING_MODULES" ]; then
|
||||
print_error "Missing required modules:$MISSING_MODULES"
|
||||
|
||||
case $OS in
|
||||
"linux")
|
||||
if [[ $MISSING_MODULES == *"tkinter"* ]]; then
|
||||
echo "Install tkinter with:"
|
||||
echo " Ubuntu/Debian: sudo apt install python3-tk"
|
||||
echo " Fedora/RHEL: sudo dnf install python3-tkinter"
|
||||
echo " Arch: sudo pacman -S python-tkinter"
|
||||
fi
|
||||
;;
|
||||
"macos"|"windows")
|
||||
echo "tkinter should be included with Python. Try reinstalling Python."
|
||||
;;
|
||||
esac
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Create resources directory
|
||||
setup_resources() {
|
||||
print_header "Setting Up Resources"
|
||||
|
||||
if [ ! -d "resources" ]; then
|
||||
mkdir -p resources
|
||||
print_success "Created resources directory"
|
||||
fi
|
||||
|
||||
# Create placeholder icon files if they don't exist
|
||||
for icon in green.png red.png settings_icon.png; do
|
||||
if [ ! -f "resources/$icon" ]; then
|
||||
touch "resources/$icon"
|
||||
print_warning "Created placeholder resources/$icon"
|
||||
else
|
||||
print_success "resources/$icon exists"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -f "resources/green.png" ] && [ ! -s "resources/green.png" ]; then
|
||||
print_warning "Icon files are empty placeholders"
|
||||
print_info "Replace with actual PNG icons for proper functionality"
|
||||
fi
|
||||
}
|
||||
|
||||
# Install build dependencies
|
||||
install_build_deps() {
|
||||
print_header "Installing Build Dependencies"
|
||||
|
||||
PIP_CMD=""
|
||||
if command_exists pip3; then
|
||||
PIP_CMD="pip3"
|
||||
elif command_exists pip; then
|
||||
PIP_CMD="pip"
|
||||
else
|
||||
print_error "pip not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case $OS in
|
||||
"windows")
|
||||
print_info "Installing Windows build tools..."
|
||||
$PIP_CMD install cx_Freeze
|
||||
print_success "Windows build tools installed"
|
||||
;;
|
||||
"macos")
|
||||
print_info "Installing macOS build tools..."
|
||||
$PIP_CMD install py2app
|
||||
print_success "macOS build tools installed"
|
||||
;;
|
||||
"linux")
|
||||
print_info "Installing Linux build tools..."
|
||||
$PIP_CMD install cx_Freeze
|
||||
print_success "Linux build tools installed"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Test the application
|
||||
test_app() {
|
||||
print_header "Testing Application"
|
||||
|
||||
print_info "Testing module imports..."
|
||||
if $PYTHON_CMD -c "
|
||||
import config
|
||||
import ls_connection
|
||||
import note_reader
|
||||
import setting_handler
|
||||
print('All modules imported successfully')
|
||||
" 2>/dev/null; then
|
||||
print_success "Module imports working"
|
||||
else
|
||||
print_error "Module import failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "Testing note parsing..."
|
||||
echo -e "Test note 1\n\nTest note 2" > test_notes.txt
|
||||
if $PYTHON_CMD -c "
|
||||
import note_reader
|
||||
notes = note_reader.get_notes('test_notes.txt', 'new_line')
|
||||
print(f'Parsed {len(notes)} notes successfully')
|
||||
" 2>/dev/null; then
|
||||
print_success "Note parsing working"
|
||||
else
|
||||
print_error "Note parsing failed"
|
||||
fi
|
||||
rm -f test_notes.txt
|
||||
|
||||
print_success "Application tests passed"
|
||||
}
|
||||
|
||||
# Build application
|
||||
build_app() {
|
||||
print_header "Building Application"
|
||||
|
||||
case $OS in
|
||||
"windows")
|
||||
print_info "Building Windows executable..."
|
||||
$PYTHON_CMD setup_windows.py build
|
||||
print_success "Windows build complete (check build/ directory)"
|
||||
;;
|
||||
"macos")
|
||||
print_info "Building macOS application..."
|
||||
$PYTHON_CMD setup_mac.py py2app
|
||||
print_success "macOS build complete (check dist/ directory)"
|
||||
;;
|
||||
"linux")
|
||||
print_info "Building Linux executable..."
|
||||
$PYTHON_CMD setup_linux.py build
|
||||
$PYTHON_CMD setup_linux.py package
|
||||
print_success "Linux build complete (check build/ directory)"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Run the application
|
||||
run_app() {
|
||||
print_header "Running SplitNotes"
|
||||
|
||||
print_info "Starting SplitNotes from source..."
|
||||
print_info "Press Ctrl+C to stop"
|
||||
$PYTHON_CMD main_window.py
|
||||
}
|
||||
|
||||
# Show usage information
|
||||
show_usage() {
|
||||
echo "SplitNotes Setup Script"
|
||||
echo "======================"
|
||||
echo ""
|
||||
echo "Usage: $0 [command]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " setup - Full setup (check deps, create resources, test)"
|
||||
echo " check - Check system requirements only"
|
||||
echo " build - Install build dependencies and build"
|
||||
echo " run - Run the application"
|
||||
echo " test - Test the application"
|
||||
echo " clean - Clean build artifacts"
|
||||
echo " help - Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 setup # First time setup"
|
||||
echo " $0 run # Run the application"
|
||||
echo " $0 build # Build executable"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Clean build artifacts
|
||||
clean_build() {
|
||||
print_header "Cleaning Build Artifacts"
|
||||
|
||||
rm -rf build/
|
||||
rm -rf dist/
|
||||
rm -rf *.egg-info/
|
||||
find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
find . -name "*.pyc" -delete 2>/dev/null || true
|
||||
find . -name "*.pyo" -delete 2>/dev/null || true
|
||||
|
||||
print_success "Build artifacts cleaned"
|
||||
}
|
||||
|
||||
# Main setup function
|
||||
main_setup() {
|
||||
print_header "SplitNotes Setup"
|
||||
|
||||
detect_os
|
||||
check_python
|
||||
check_modules
|
||||
setup_resources
|
||||
test_app
|
||||
|
||||
print_header "Setup Complete!"
|
||||
print_success "SplitNotes is ready to use"
|
||||
echo ""
|
||||
print_info "Next steps:"
|
||||
echo " 1. Replace placeholder icons in resources/ with actual PNG files"
|
||||
echo " 2. Install LiveSplit Server component"
|
||||
echo " 3. Run: $0 run"
|
||||
echo ""
|
||||
print_info "To build executable: $0 build"
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
case "${1:-setup}" in
|
||||
"setup")
|
||||
main_setup
|
||||
;;
|
||||
"check")
|
||||
detect_os
|
||||
check_python
|
||||
check_modules
|
||||
;;
|
||||
"build")
|
||||
detect_os
|
||||
check_python
|
||||
install_build_deps
|
||||
build_app
|
||||
;;
|
||||
"run")
|
||||
detect_os
|
||||
check_python
|
||||
run_app
|
||||
;;
|
||||
"test")
|
||||
detect_os
|
||||
check_python
|
||||
test_app
|
||||
;;
|
||||
"clean")
|
||||
clean_build
|
||||
;;
|
||||
"help"|"-h"|"--help")
|
||||
show_usage
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown command: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Linux build setup using cx_Freeze
|
||||
Run: python setup_linux.py build
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
# Add the current directory to the path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
import config
|
||||
|
||||
# Dependencies are automatically detected, but some modules need help
|
||||
build_options = {
|
||||
'packages': ['tkinter', 'socket', 'threading', 'select', 'os', 'sys', 'platform'],
|
||||
'excludes': ['matplotlib', 'numpy', 'scipy', 'PIL', 'pygame', 'PyQt5', 'PyQt6'],
|
||||
'include_files': [
|
||||
('resources/', 'resources/'),
|
||||
],
|
||||
'optimize': 2,
|
||||
'build_exe': 'build/linux',
|
||||
}
|
||||
|
||||
executables = [
|
||||
Executable(
|
||||
'main_window.py',
|
||||
base=None, # Console application base for Linux
|
||||
target_name='splitnotes',
|
||||
copyright=f'Copyright (c) 2024 {config.APP_AUTHOR}',
|
||||
)
|
||||
]
|
||||
|
||||
setup(
|
||||
name=config.APP_NAME.lower(),
|
||||
version=config.APP_VERSION,
|
||||
description=config.APP_DESCRIPTION,
|
||||
author=config.APP_AUTHOR,
|
||||
options={'build_exe': build_options},
|
||||
executables=executables
|
||||
)
|
||||
|
||||
# Alternative setup for creating a proper Linux package structure
|
||||
def create_linux_package():
|
||||
"""Create a proper Linux application structure"""
|
||||
import shutil
|
||||
import stat
|
||||
|
||||
app_dir = f"build/{config.APP_NAME.lower()}"
|
||||
|
||||
# Create directory structure
|
||||
os.makedirs(f"{app_dir}/bin", exist_ok=True)
|
||||
os.makedirs(f"{app_dir}/share/{config.APP_NAME.lower()}", exist_ok=True)
|
||||
os.makedirs(f"{app_dir}/share/applications", exist_ok=True)
|
||||
os.makedirs(f"{app_dir}/share/pixmaps", exist_ok=True)
|
||||
|
||||
# Copy executable
|
||||
if os.path.exists("build/linux/splitnotes"):
|
||||
shutil.copy2("build/linux/splitnotes", f"{app_dir}/bin/")
|
||||
|
||||
# Make executable
|
||||
st = os.stat(f"{app_dir}/bin/splitnotes")
|
||||
os.chmod(f"{app_dir}/bin/splitnotes", st.st_mode | stat.S_IEXEC)
|
||||
|
||||
# Copy resources
|
||||
if os.path.exists("resources"):
|
||||
shutil.copytree("resources", f"{app_dir}/share/{config.APP_NAME.lower()}/resources", dirs_exist_ok=True)
|
||||
|
||||
# Create .desktop file for Linux desktop integration
|
||||
desktop_content = f"""[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name={config.APP_NAME}
|
||||
Comment={config.APP_DESCRIPTION}
|
||||
Exec={app_dir}/bin/splitnotes
|
||||
Icon={config.APP_NAME.lower()}
|
||||
Categories=Utility;
|
||||
Terminal=false
|
||||
StartupNotify=true
|
||||
"""
|
||||
|
||||
with open(f"{app_dir}/share/applications/{config.APP_NAME.lower()}.desktop", 'w') as f:
|
||||
f.write(desktop_content)
|
||||
|
||||
# Create launch script
|
||||
launch_script = f"""#!/bin/bash
|
||||
DIR="$( cd "$( dirname "${{BASH_SOURCE[0]}}" )" &> /dev/null && pwd )"
|
||||
cd "$DIR"
|
||||
./bin/splitnotes "$@"
|
||||
"""
|
||||
|
||||
with open(f"{app_dir}/{config.APP_NAME.lower()}", 'w') as f:
|
||||
f.write(launch_script)
|
||||
|
||||
# Make launch script executable
|
||||
st = os.stat(f"{app_dir}/{config.APP_NAME.lower()}")
|
||||
os.chmod(f"{app_dir}/{config.APP_NAME.lower()}", st.st_mode | stat.S_IEXEC)
|
||||
|
||||
print(f"Linux package created in {app_dir}/")
|
||||
print(f"Run with: ./{app_dir}/{config.APP_NAME.lower()}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "package":
|
||||
create_linux_package()
|
||||
else:
|
||||
# Standard build process
|
||||
pass
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
macOS build setup using py2app
|
||||
Run: python setup_mac.py py2app
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from setuptools import setup
|
||||
|
||||
# Add the current directory to the path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
import config
|
||||
|
||||
APP = ['main_window.py']
|
||||
DATA_FILES = [
|
||||
('resources', [
|
||||
'resources/green.png',
|
||||
'resources/red.png',
|
||||
'resources/settings_icon.png'
|
||||
])
|
||||
]
|
||||
|
||||
OPTIONS = {
|
||||
'argv_emulation': True,
|
||||
'plist': {
|
||||
'CFBundleName': config.APP_NAME,
|
||||
'CFBundleDisplayName': config.APP_NAME,
|
||||
'CFBundleGetInfoString': config.APP_DESCRIPTION,
|
||||
'CFBundleIdentifier': 'com.ApfelTeeSaft.splitnotes',
|
||||
'CFBundleVersion': config.APP_VERSION,
|
||||
'CFBundleShortVersionString': config.APP_VERSION,
|
||||
'NSHumanReadableCopyright': f'Copyright (c) 2024 {config.APP_AUTHOR}',
|
||||
'NSHighResolutionCapable': True,
|
||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||
'NSRequiresAquaSystemAppearance': False, # Support dark mode
|
||||
},
|
||||
'packages': ['tkinter'],
|
||||
'excludes': ['matplotlib', 'numpy', 'scipy', 'PIL', 'pygame'],
|
||||
'resources': DATA_FILES,
|
||||
'optimize': 2,
|
||||
}
|
||||
|
||||
setup(
|
||||
name=config.APP_NAME,
|
||||
app=APP,
|
||||
data_files=DATA_FILES,
|
||||
options={'py2app': OPTIONS},
|
||||
setup_requires=['py2app'],
|
||||
author=config.APP_AUTHOR,
|
||||
version=config.APP_VERSION,
|
||||
description=config.APP_DESCRIPTION,
|
||||
license='MIT',
|
||||
url='https://github.com/ApfelTeeSaft/SplitNotes',
|
||||
)
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Windows build setup using cx_Freeze (preferred) or py2exe
|
||||
Run: python setup_windows.py build
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
# Add the current directory to the path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
import config
|
||||
|
||||
# Dependencies are automatically detected, but some modules need help
|
||||
build_options = {
|
||||
'packages': ['tkinter', 'socket', 'threading', 'select', 'os', 'sys', 'platform'],
|
||||
'excludes': ['matplotlib', 'numpy', 'scipy', 'PIL', 'pygame'],
|
||||
'include_files': [
|
||||
('resources/', 'resources/'),
|
||||
],
|
||||
'optimize': 2,
|
||||
}
|
||||
|
||||
# Base for Windows GUI application (no console window)
|
||||
base = 'Win32GUI' if sys.platform == 'win32' else None
|
||||
|
||||
executables = [
|
||||
Executable(
|
||||
'main_window.py',
|
||||
base=base,
|
||||
target_name='SplitNotes.exe',
|
||||
icon=None, # Add icon path here if you have one
|
||||
copyright=f'Copyright (c) 2024 {config.APP_AUTHOR}',
|
||||
)
|
||||
]
|
||||
|
||||
setup(
|
||||
name=config.APP_NAME,
|
||||
version=config.APP_VERSION,
|
||||
description=config.APP_DESCRIPTION,
|
||||
author=config.APP_AUTHOR,
|
||||
options={'build_exe': build_options},
|
||||
executables=executables
|
||||
)
|
||||
|
||||
# Alternative py2exe setup (uncomment if cx_Freeze is not available)
|
||||
"""
|
||||
from distutils.core import setup
|
||||
import py2exe
|
||||
|
||||
setup(
|
||||
windows=[{
|
||||
'script': 'main_window.py',
|
||||
'dest_base': 'SplitNotes',
|
||||
'copyright': f'Copyright (c) 2024 {config.APP_AUTHOR}',
|
||||
}],
|
||||
options={
|
||||
'py2exe': {
|
||||
'bundle_files': 2,
|
||||
'compressed': True,
|
||||
'optimize': 2,
|
||||
'excludes': ['matplotlib', 'numpy', 'scipy', 'PIL', 'pygame'],
|
||||
}
|
||||
},
|
||||
zipfile=None,
|
||||
data_files=[
|
||||
('resources', [
|
||||
'resources/green.png',
|
||||
'resources/red.png',
|
||||
'resources/settings_icon.png'
|
||||
])
|
||||
]
|
||||
)
|
||||
"""
|
||||
Reference in New Issue
Block a user