mirror of
https://github.com/ApfelTeeSaft/SplitNotes.git
synced 2026-08-27 19:22:31 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44ff009021 | ||
|
|
d33dbd523e | ||
|
|
59f075c247 | ||
|
|
fdbbc707fc | ||
|
|
e19c33b166 | ||
|
|
ca2a6d93a6 | ||
|
|
199acd4847 | ||
|
|
3ae68fa291 | ||
|
|
9ba8edc8d9 |
@@ -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,63 +1,228 @@
|
|||||||
# SplitNotes
|
# SplitNotes
|
||||||
Software for syncing notes with LiveSplit using the LiveSplit server component.
|
|
||||||
|
|
||||||
Splitnotes automatically shows notes for the split you are currently on.
|
Cross-platform software for syncing notes with LiveSplit using the LiveSplit server component.
|
||||||
|
|
||||||
|
SplitNotes automatically shows notes for the split you are currently on, with support for Windows, macOS, and Linux.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
## Install
|
|
||||||
1. Download the latest version of LiveSplit Server Component from [this](https://github.com/LiveSplit/LiveSplit.Server/releases) site.
|
|
||||||
2. Unzip and move the files to the component folder in your LiveSplit install (...\LiveSplit\Components)
|
|
||||||
3. Download the latest version of SplitNotes from [this](https://github.com/joelnir/SplitNotes/releases) page.
|
|
||||||
4. Unzip SplitNotes anywhere.
|
|
||||||
|
|
||||||
## How To Use
|
|
||||||
**Connect to LiveSplit**
|
|
||||||
1. Launch Splitnotes.exe
|
|
||||||
2. Launch LiveSplit
|
|
||||||
3. Go to "Edit Layout" -> "+" -> "Control" -> "LiveSplit Server". Hit ok. (Let the server use the default port: 16834)
|
|
||||||
4. In LiveSplit, select "Control" -> "Start Server".
|
|
||||||
5. SplitNotes should now be connected to LiveSplit. If connection is active the Icon for SplitNotes is green.
|
|
||||||
|
|
||||||
**Format your notes**
|
|
||||||
It is recommended to use a .txt file to store your notes.
|
|
||||||
Note files should be formatted as following:
|
|
||||||
|
|
||||||
* Normal text is treated as notes
|
|
||||||
* New Line means notes for a specific split is over.
|
|
||||||
* Text in bracket ([some text]) is ignored from notes, this could be used to write down titles or other comments to keep the note file tidy.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
>[Split1]
|
|
||||||
>these are some notes for split 1.
|
|
||||||
>
|
|
||||||
>These are some notes for split2.
|
|
||||||
>As you can see a title in brackets is not neccesary.
|
|
||||||
>A simple new line is enough to separate notes for different splits.
|
|
||||||
>
|
|
||||||
>Also som notes for split 3.
|
|
||||||
>You don't have to [ worry abut using brackets [ in the middle of a row].
|
|
||||||
|
|
||||||
**Load Notes**
|
|
||||||
1. Right-Click in SplitNotes and select "Load Notes".
|
|
||||||
2. Choose your text file.
|
|
||||||
3. Make sure that notes for the right amount of splits have been loaded.
|
|
||||||
|
|
||||||
**Change Settings**
|
|
||||||
1. Right-Click in SplitNotes and select "Settings"
|
|
||||||
2. The settings menu will show up. Here you can set things like font, font size, text and background color and the server port.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Automatically displayed notes based on the active split in LiveSplit.
|
* **Cross-platform support** - Works on Windows, macOS, and Linux
|
||||||
* Ability to preview notes in the software when no run is going on by using the right and left arrow keys.
|
* **Automatic note display** based on the active split in LiveSplit
|
||||||
* Two different font sizes to make sure that notes are easy to read.
|
* **Preview functionality** - Use arrow keys to preview notes when no run is active
|
||||||
* Double layout to preview notes for both current and next split.
|
* **Dual layout modes** - Single or double layout to show current and next split notes
|
||||||
* Multiple settings to change the look of the text.
|
* **Customizable appearance** - Adjustable fonts, colors, and window size
|
||||||
* Ability to set a custom port for interaction with the LiveSplit Server.
|
* **Flexible note format** - Support for custom split separators
|
||||||
|
* **Live connection status** - Visual indicator of LiveSplit connection
|
||||||
|
|
||||||
#### Development
|
## System Requirements
|
||||||
Written in mainly procedural Python using tkinter GUI library.
|
|
||||||
Made by Joelnir.
|
- **Python 3.6+** (for running from source)
|
||||||
|
- **LiveSplit** with Server Component
|
||||||
|
- **Operating System**: Windows 7+, macOS 10.12+, or Linux with X11
|
||||||
|
|
||||||
|
### Platform-Specific Requirements
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
- No additional requirements for compiled version
|
||||||
|
- For source: Python with tkinter (included in standard installation)
|
||||||
|
|
||||||
|
**macOS:**
|
||||||
|
- No additional requirements for .app bundle
|
||||||
|
- For source: Python 3 with tkinter
|
||||||
|
|
||||||
|
**Linux:**
|
||||||
|
- For compiled version: glibc 2.17+
|
||||||
|
- For source: `python3`, `python3-tk`
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt install python3 python3-tk
|
||||||
|
|
||||||
|
# Fedora/RHEL
|
||||||
|
sudo dnf install python3 python3-tkinter
|
||||||
|
|
||||||
|
# Arch
|
||||||
|
sudo pacman -S python python-tkinter
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Method 1: Download Compiled Version
|
||||||
|
1. Download the latest release for your platform from the [releases page](https://github.com/apfelteesaft/SplitNotes/releases)
|
||||||
|
2. Extract the archive
|
||||||
|
3. Run the executable (`SplitNotes.exe`, `SplitNotes.app`, or `splitnotes`)
|
||||||
|
|
||||||
|
### Method 2: Install LiveSplit Server Component
|
||||||
|
1. Download the latest version of LiveSplit Server Component from [this site](https://github.com/LiveSplit/LiveSplit.Server/releases)
|
||||||
|
2. Unzip and move the files to the component folder in your LiveSplit install (`...\LiveSplit\Components`)
|
||||||
|
|
||||||
|
### Method 3: Run from Source
|
||||||
|
1. Ensure Python 3.6+ is installed
|
||||||
|
2. Download/clone this repository
|
||||||
|
3. Create a `resources/` folder with the icon files
|
||||||
|
4. Run: `python main_window.py` (or `python3 main_window.py` on Linux/macOS)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Connecting to LiveSplit
|
||||||
|
|
||||||
|
1. **Launch SplitNotes**
|
||||||
|
2. **Launch LiveSplit**
|
||||||
|
3. In LiveSplit: **Edit Layout** → **+** → **Control** → **LiveSplit Server** → **OK**
|
||||||
|
4. In LiveSplit: **Control** → **Start Server**
|
||||||
|
5. SplitNotes should connect automatically (green icon = connected, red = disconnected)
|
||||||
|
|
||||||
|
### Loading Notes
|
||||||
|
|
||||||
|
**Right-click** in SplitNotes and select **"Load Notes"** to choose your text file.
|
||||||
|
|
||||||
|
### Note File Format
|
||||||
|
|
||||||
|
Notes should be stored in a text file with the following format:
|
||||||
|
|
||||||
|
#### Using Newline Separator (Default)
|
||||||
|
```
|
||||||
|
[Split 1 Title]
|
||||||
|
These are notes for the first split.
|
||||||
|
You can write multiple lines.
|
||||||
|
|
||||||
|
[Split 2 Title]
|
||||||
|
Notes for split 2 go here.
|
||||||
|
Empty line above separates the splits.
|
||||||
|
|
||||||
|
[Split 3 Title]
|
||||||
|
Final split notes.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Custom Separator
|
||||||
|
```
|
||||||
|
[Split 1 Title]
|
||||||
|
Notes for split 1.
|
||||||
|
Multiple lines are fine.
|
||||||
|
-end-
|
||||||
|
[Split 2 Title]
|
||||||
|
Notes for split 2.
|
||||||
|
-end-
|
||||||
|
[Split 3 Title]
|
||||||
|
Notes for split 3.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format Rules:**
|
||||||
|
- Text in brackets `[like this]` is ignored (use for titles/comments)
|
||||||
|
- Empty lines (or custom separator) divide notes between splits
|
||||||
|
- All other text becomes part of the notes
|
||||||
|
- Encoding: UTF-8 recommended, with fallback support for other encodings
|
||||||
|
|
||||||
|
### Controls
|
||||||
|
|
||||||
|
- **Right-click**: Open context menu
|
||||||
|
- **Left/Right Arrow Keys**: Preview notes when timer is not running
|
||||||
|
- **Context Menu Options**:
|
||||||
|
- Load Notes
|
||||||
|
- Settings (customize appearance, layout, server port, separator)
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
Access via right-click → **Settings**:
|
||||||
|
|
||||||
|
- **Font & Size**: Choose from system fonts
|
||||||
|
- **Colors**: Customize text and background colors
|
||||||
|
- **Layout**: Single or double (shows current + next split)
|
||||||
|
- **Server Port**: Change if using non-default LiveSplit port (default: 16834)
|
||||||
|
- **Split Separator**: Use newlines or custom text to separate splits
|
||||||
|
|
||||||
|
## Platform-Specific Notes
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
- Windows Defender might flag the executable initially - add an exception if needed
|
||||||
|
- Supports all Windows versions from 7 onwards
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
- First launch might require right-click → Open due to Gatekeeper security
|
||||||
|
- Supports both Intel and Apple Silicon Macs
|
||||||
|
- Integrates with system dark/light mode
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
- Requires X11 display server (most desktop environments)
|
||||||
|
- Tested on Ubuntu, Fedora, and Arch Linux
|
||||||
|
- Application follows system theme where possible
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
See `build_instructions.txt` for detailed build instructions for each platform.
|
||||||
|
|
||||||
|
**Quick Build:**
|
||||||
|
```bash
|
||||||
|
# Windows
|
||||||
|
pip install cx_Freeze
|
||||||
|
python setup_windows.py build
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
pip install py2app
|
||||||
|
python setup_mac.py py2app
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
pip install cx_Freeze
|
||||||
|
python setup_linux.py build
|
||||||
|
python setup_linux.py package
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Connection Issues
|
||||||
|
- Ensure LiveSplit Server component is installed and started
|
||||||
|
- Check that port 16834 is not blocked by firewall
|
||||||
|
- Verify SplitNotes and LiveSplit are on the same machine
|
||||||
|
|
||||||
|
### Display Issues
|
||||||
|
- Update graphics drivers if experiencing rendering problems
|
||||||
|
- Try different font settings if text appears corrupted
|
||||||
|
- Ensure display scaling is set appropriately
|
||||||
|
|
||||||
|
### File Loading Issues
|
||||||
|
- Verify the notes file is not corrupted or locked by another program
|
||||||
|
- Try saving the file with UTF-8 encoding
|
||||||
|
- Check file permissions (especially on Linux/macOS)
|
||||||
|
|
||||||
|
### Performance Issues
|
||||||
|
- Close unnecessary applications if SplitNotes becomes slow
|
||||||
|
- Try reducing font size or window size
|
||||||
|
- Ensure adequate system memory is available
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
**Language**: Python 3.6+
|
||||||
|
**GUI Framework**: tkinter (cross-platform)
|
||||||
|
**Architecture**: Event-driven with threaded network communication
|
||||||
|
**License**: MIT
|
||||||
|
|
||||||
|
**Key Files:**
|
||||||
|
- `main_window.py` - Main application and GUI
|
||||||
|
- `ls_connection.py` - LiveSplit server communication
|
||||||
|
- `note_reader.py` - Note file parsing
|
||||||
|
- `setting_handler.py` - Configuration management
|
||||||
|
- `config.py` - Application constants and platform detection
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see `license.txt` for details.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
**Originally Created by**: joeloskarsson
|
||||||
|
|
||||||
|
**Ported to MacOS, Linux and refind Build Setup by**: ApfelTeeSaft
|
||||||
|
|
||||||
|
**Original Version**: Windows-only Python application
|
||||||
|
|
||||||
|
**Cross-Platform Version**: Enhanced with macOS and Linux support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*For more information and updates, visit the [GitHub repository](https://github.com/apfelteesaft/SplitNotes).*
|
||||||
|
|||||||
@@ -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
|
# Livesplit connection
|
||||||
HOST = "localhost"
|
HOST = "localhost"
|
||||||
PORT = 16834
|
PORT = 16834
|
||||||
|
|
||||||
|
# Bridge server settings
|
||||||
|
BRIDGE_HOST = "localhost"
|
||||||
|
BRIDGE_PORT = 16835
|
||||||
|
BRIDGE_ENABLED = False
|
||||||
|
|
||||||
# In network communication, time out after this time. (in seconds)
|
# In network communication, time out after this time. (in seconds)
|
||||||
COM_TIMEOUT = 0.5
|
COM_TIMEOUT = 0.5
|
||||||
|
|
||||||
@@ -31,21 +38,27 @@ SETTINGS_FILE = "config.cfg"
|
|||||||
# Default Scrollbar Width
|
# Default Scrollbar Width
|
||||||
SCROLLBAR_WIDTH = 16
|
SCROLLBAR_WIDTH = 16
|
||||||
|
|
||||||
# Popup menu options
|
# Popup menu options - Enhanced with bridge settings
|
||||||
MENU_OPTIONS = {
|
MENU_OPTIONS = {
|
||||||
"SINGLE": "Set Single Layout",
|
"SINGLE": "Set Single Layout",
|
||||||
"DOUBLE": "Set Double Layout",
|
"DOUBLE": "Set Double Layout",
|
||||||
"LOAD": "Load Notes",
|
"LOAD": "Load Notes",
|
||||||
"BIG": "Big Font",
|
"BIG": "Big Font",
|
||||||
"SMALL": "Small Font",
|
"SMALL": "Small Font",
|
||||||
"SETTINGS": "Settings"
|
"SETTINGS": "Settings",
|
||||||
|
"BRIDGE": "Bridge Settings"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Error messages
|
# Error messages - Enhanced with bridge server errors
|
||||||
ERRORS = {"NOTES_EMPTY": ("Error!", "Notes empty or can't be loaded!"),
|
ERRORS = {
|
||||||
"FONT_SIZE": ("Error!", "Invalid Font Size!"),
|
"NOTES_EMPTY": ("Error!", "Notes empty or can't be loaded!"),
|
||||||
"SERVER_PORT": ("Error!", "Invalid server port!"),
|
"FONT_SIZE": ("Error!", "Invalid Font Size!"),
|
||||||
"SEPARATOR": ("Error!", "Invalid split separator!")}
|
"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 for notes
|
||||||
MAX_FILE_SIZE = 1000000000 # 1 Giga-Byte
|
MAX_FILE_SIZE = 1000000000 # 1 Giga-Byte
|
||||||
@@ -56,29 +69,43 @@ RUNNING_ALERT = "RUNNING"
|
|||||||
# Font for gui widgets
|
# Font for gui widgets
|
||||||
GUI_FONT = ("arial", 12)
|
GUI_FONT = ("arial", 12)
|
||||||
|
|
||||||
# Files tht should be displayed and opened a notes
|
# Files that should be displayed and opened as notes
|
||||||
TEXT_FILES = [
|
TEXT_FILES = [
|
||||||
("Text Files", ("*.txt", "*.log", "*.asc", "*.conf", "*.cfg")),
|
("Text Files", ("*.txt", "*.log", "*.asc", "*.conf", "*.cfg")),
|
||||||
('All', '*')
|
('All', '*')
|
||||||
]
|
]
|
||||||
|
|
||||||
# Default content of config.cfg file
|
# Default content of config.cfg file - COMPLETE with proper bridge settings
|
||||||
DEFAULT_CONFIG = "notes=\nfont_size=12\nfont=arial\ntext_color=#000000\nbackground_color=#FFFFFF\ndouble_layout=False\nserver_port=16834\nwidth=400\nheight=300\nseparator=new_line"
|
DEFAULT_CONFIG = """notes=
|
||||||
|
font_size=12
|
||||||
|
font=arial
|
||||||
|
text_color=#000000
|
||||||
|
background_color=#FFFFFF
|
||||||
|
double_layout=false
|
||||||
|
server_port=16834
|
||||||
|
width=400
|
||||||
|
height=300
|
||||||
|
separator=new_line
|
||||||
|
bridge_enabled=false
|
||||||
|
bridge_port=16835"""
|
||||||
|
|
||||||
NEWLINE_CONSTANT = "new_line"
|
NEWLINE_CONSTANT = "new_line"
|
||||||
|
|
||||||
# Required settings
|
# Required settings - COMPLETE with bridge settings
|
||||||
REQUIRED_SETTINGS = ("notes",
|
REQUIRED_SETTINGS = (
|
||||||
"font",
|
"notes",
|
||||||
"font_size",
|
"font",
|
||||||
"text_color",
|
"font_size",
|
||||||
"background_color",
|
"text_color",
|
||||||
"server_port",
|
"background_color",
|
||||||
"double_layout",
|
"server_port",
|
||||||
"width",
|
"double_layout",
|
||||||
"height",
|
"width",
|
||||||
"separator"
|
"height",
|
||||||
)
|
"separator",
|
||||||
|
"bridge_enabled",
|
||||||
|
"bridge_port"
|
||||||
|
)
|
||||||
|
|
||||||
# Settings window options
|
# Settings window options
|
||||||
SETTINGS_WINDOW = {"TITLE": "Settings",
|
SETTINGS_WINDOW = {"TITLE": "Settings",
|
||||||
@@ -87,6 +114,15 @@ SETTINGS_WINDOW = {"TITLE": "Settings",
|
|||||||
"CANCEL": "Cancel",
|
"CANCEL": "Cancel",
|
||||||
"SAVE": "Save"}
|
"SAVE": "Save"}
|
||||||
|
|
||||||
|
# Bridge settings window options
|
||||||
|
BRIDGE_SETTINGS_WINDOW = {
|
||||||
|
"TITLE": "TCP Bridge Server Settings",
|
||||||
|
"WIDTH": 520,
|
||||||
|
"HEIGHT": 500,
|
||||||
|
"CANCEL": "Cancel",
|
||||||
|
"APPLY": "Apply"
|
||||||
|
}
|
||||||
|
|
||||||
# OPTIONS IN THE SETTINGS WINDOW
|
# OPTIONS IN THE SETTINGS WINDOW
|
||||||
SETTINGS_OPTIONS = {"FONT": "Font",
|
SETTINGS_OPTIONS = {"FONT": "Font",
|
||||||
"FONT_SIZE": "Font Size",
|
"FONT_SIZE": "Font Size",
|
||||||
@@ -98,12 +134,116 @@ SETTINGS_OPTIONS = {"FONT": "Font",
|
|||||||
"NEW_LINE_SEPARATOR": "Newline as split separator",
|
"NEW_LINE_SEPARATOR": "Newline as split separator",
|
||||||
"CUSTOM_SEPARATOR": "Custom split separator"}
|
"CUSTOM_SEPARATOR": "Custom split separator"}
|
||||||
|
|
||||||
# Fonts that can be selected
|
# Bridge server settings options
|
||||||
AVAILABLE_FONTS = ("arial",
|
BRIDGE_OPTIONS = {
|
||||||
"courier new",
|
"ENABLE_BRIDGE": "Enable TCP Bridge Server for Browser Extensions",
|
||||||
"fixedsys",
|
"BRIDGE_PORT": "Bridge Server Port",
|
||||||
"ms sans serif",
|
"DEFAULT_BRIDGE_PORT": "(Default is 16835)",
|
||||||
"ms serif",
|
"BRIDGE_STATUS": "Server Status",
|
||||||
"system",
|
"CONNECTED_BROWSERS": "Connected Browsers",
|
||||||
"times new roman",
|
"LAST_UPDATE": "Last Update",
|
||||||
"verdana")
|
"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.
|
||||||
+21
-10
@@ -11,6 +11,7 @@ import select # used for checking if socket has data pending
|
|||||||
def ls_connect(ls_socket, call_func, window, server_port):
|
def ls_connect(ls_socket, call_func, window, server_port):
|
||||||
"""Connects given socket to the livesplit server."""
|
"""Connects given socket to the livesplit server."""
|
||||||
con_thread = Thread(target=try_connection, args=(ls_socket, call_func, window, server_port))
|
con_thread = Thread(target=try_connection, args=(ls_socket, call_func, window, server_port))
|
||||||
|
con_thread.daemon = True # Make thread daemon so it doesn't prevent app exit
|
||||||
con_thread.start()
|
con_thread.start()
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +37,10 @@ def try_connection(ls_socket, call_func, window, server_port):
|
|||||||
|
|
||||||
def close_socket(com_socket):
|
def close_socket(com_socket):
|
||||||
"""Closes given socket."""
|
"""Closes given socket."""
|
||||||
com_socket.close()
|
try:
|
||||||
|
com_socket.close()
|
||||||
|
except:
|
||||||
|
pass # Socket might already be closed
|
||||||
|
|
||||||
|
|
||||||
def check_connection(ls_socket):
|
def check_connection(ls_socket):
|
||||||
@@ -56,7 +60,7 @@ def send_to_ls(ls_socket, command):
|
|||||||
Sends given command to ls using given socket.
|
Sends given command to ls using given socket.
|
||||||
If connected is False, tries to send without socket being connected to ls.
|
If connected is False, tries to send without socket being connected to ls.
|
||||||
Returns the response, or False if an error occurs.
|
Returns the response, or False if an error occurs.
|
||||||
Check config.LS_COMMANDS for avaiable commands.
|
Check config.LS_COMMANDS for available commands.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -64,13 +68,17 @@ def send_to_ls(ls_socket, command):
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT)
|
# Use select to check if data is available, with timeout
|
||||||
if socket_ready[0]:
|
try:
|
||||||
try:
|
socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT)
|
||||||
return (ls_socket.recv(1000)).decode("utf-8")
|
if socket_ready[0]:
|
||||||
except:
|
try:
|
||||||
|
return (ls_socket.recv(1000)).decode("utf-8")
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
return False
|
return False
|
||||||
else:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +93,10 @@ def get_split_index(ls_socket):
|
|||||||
ls_data = send_to_ls(ls_socket, "cur_split_index")
|
ls_data = send_to_ls(ls_socket, "cur_split_index")
|
||||||
|
|
||||||
if not isinstance(ls_data, bool):
|
if not isinstance(ls_data, bool):
|
||||||
return int(ls_data)
|
try:
|
||||||
|
return int(ls_data.strip())
|
||||||
|
except:
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -98,6 +109,6 @@ def get_split_name(ls_socket):
|
|||||||
ls_data = send_to_ls(ls_socket, "cur_split_name")
|
ls_data = send_to_ls(ls_socket, "cur_split_name")
|
||||||
|
|
||||||
if ls_data:
|
if ls_data:
|
||||||
return ls_data
|
return ls_data.strip()
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
+847
-167
File diff suppressed because it is too large
Load Diff
+67
-40
@@ -25,28 +25,38 @@ def get_note_lines(file_path):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# check so file isn't too big
|
# 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
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
notes_file = open(file_path, "r")
|
# Try different encodings for cross-platform compatibility
|
||||||
except:
|
encodings = ['utf-8', 'utf-8-sig', 'latin-1', 'cp1252']
|
||||||
|
notes_file = None
|
||||||
|
|
||||||
|
for encoding in encodings:
|
||||||
|
try:
|
||||||
|
notes_file = open(file_path, "r", encoding=encoding)
|
||||||
|
break
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if notes_file is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# read file line per line
|
# read file line per line
|
||||||
f_lines = []
|
f_lines = []
|
||||||
keep_reading = True
|
try:
|
||||||
while keep_reading:
|
with notes_file:
|
||||||
|
for line in notes_file:
|
||||||
try:
|
f_lines.append(line)
|
||||||
cur_line = notes_file.readline()
|
except Exception:
|
||||||
except:
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
if cur_line:
|
|
||||||
f_lines.append(cur_line)
|
|
||||||
else:
|
|
||||||
keep_reading = False
|
|
||||||
|
|
||||||
return f_lines
|
return f_lines
|
||||||
|
|
||||||
@@ -58,28 +68,25 @@ def decode_notes(note_lines, separator):
|
|||||||
Returns the list containing the notes for every split.
|
Returns the list containing the notes for every split.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#Check if newline is being used as separator
|
# Check if newline is being used as separator
|
||||||
|
|
||||||
if separator == config.NEWLINE_CONSTANT:
|
if separator == config.NEWLINE_CONSTANT:
|
||||||
separator = "" # left after stripping newline
|
separator = "" # left after stripping newline
|
||||||
|
|
||||||
def is_title(line):
|
def is_title(line):
|
||||||
if not line:
|
if not line:
|
||||||
return False
|
return False
|
||||||
|
stripped = line.strip()
|
||||||
return (line[0] == "[") and (line[-1] == "]")
|
return stripped.startswith("[") and stripped.endswith("]")
|
||||||
|
|
||||||
def is_separator(line):
|
def is_separator(line):
|
||||||
return (line == separator)
|
return line.strip() == separator.strip()
|
||||||
|
|
||||||
def is_newline(s):
|
def is_newline(s):
|
||||||
return (s == "\n")
|
return s.strip() == ""
|
||||||
|
|
||||||
def remove_new_line(line):
|
def remove_new_line(line):
|
||||||
if (len(line) >= 1) and (is_newline(line[-1])):
|
# Remove trailing newline characters
|
||||||
return line[:-1]
|
return line.rstrip('\n\r')
|
||||||
else:
|
|
||||||
return line
|
|
||||||
|
|
||||||
note_list = []
|
note_list = []
|
||||||
cur_notes = ""
|
cur_notes = ""
|
||||||
@@ -87,16 +94,25 @@ def decode_notes(note_lines, separator):
|
|||||||
for line in note_lines:
|
for line in note_lines:
|
||||||
line = remove_new_line(line)
|
line = remove_new_line(line)
|
||||||
|
|
||||||
if is_separator(line):
|
if separator == "" and is_newline(line):
|
||||||
if cur_notes:
|
# Using newline as separator
|
||||||
note_list.append(cur_notes)
|
if cur_notes.strip():
|
||||||
|
note_list.append(cur_notes.strip())
|
||||||
|
cur_notes = ""
|
||||||
|
elif separator != "" and is_separator(line):
|
||||||
|
# Using custom separator
|
||||||
|
if cur_notes.strip():
|
||||||
|
note_list.append(cur_notes.strip())
|
||||||
cur_notes = ""
|
cur_notes = ""
|
||||||
else:
|
else:
|
||||||
if not is_title(line):
|
if not is_title(line):
|
||||||
cur_notes += line + "\n" # newline
|
if cur_notes:
|
||||||
|
cur_notes += "\n"
|
||||||
|
cur_notes += line
|
||||||
|
|
||||||
if cur_notes:
|
# Add the last notes if any
|
||||||
note_list.append(cur_notes)
|
if cur_notes.strip():
|
||||||
|
note_list.append(cur_notes.strip())
|
||||||
|
|
||||||
return note_list
|
return note_list
|
||||||
|
|
||||||
@@ -104,10 +120,13 @@ def decode_notes(note_lines, separator):
|
|||||||
def get_notes(file_path, separator):
|
def get_notes(file_path, separator):
|
||||||
"""
|
"""
|
||||||
Takes a path to a file and returns a list with the notes
|
Takes a path to a file and returns a list with the notes
|
||||||
in the file encoded according to the note fromatting.
|
in the file encoded according to the note formatting.
|
||||||
|
|
||||||
Returns False if file is empty.
|
Returns False if file is empty.
|
||||||
"""
|
"""
|
||||||
|
if not file_exists(file_path):
|
||||||
|
return False
|
||||||
|
|
||||||
note_lines = get_note_lines(file_path)
|
note_lines = get_note_lines(file_path)
|
||||||
|
|
||||||
if not note_lines:
|
if not note_lines:
|
||||||
@@ -115,7 +134,7 @@ def get_notes(file_path, separator):
|
|||||||
|
|
||||||
note_list = decode_notes(note_lines, separator)
|
note_list = decode_notes(note_lines, separator)
|
||||||
|
|
||||||
return note_list
|
return note_list if note_list else False
|
||||||
|
|
||||||
|
|
||||||
def select_file():
|
def select_file():
|
||||||
@@ -124,15 +143,23 @@ def select_file():
|
|||||||
Returns False upon no file selection.
|
Returns False upon no file selection.
|
||||||
Otherwise returns absolute path to selected file.
|
Otherwise returns absolute path to selected file.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
file = file_dia.askopenfilename(
|
||||||
|
title="Select Notes File",
|
||||||
|
filetypes=config.TEXT_FILES
|
||||||
|
)
|
||||||
|
|
||||||
file = file_dia.askopenfilename(filetypes=config.TEXT_FILES)
|
if file:
|
||||||
|
return file
|
||||||
if file:
|
else:
|
||||||
return file
|
return False
|
||||||
else:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def file_exists(file):
|
def file_exists(file):
|
||||||
"""Checks if given path leads to an existing file."""
|
"""Checks if given path leads to an existing file."""
|
||||||
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()
|
||||||
+237
-108
@@ -6,14 +6,23 @@ import sys
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
|
||||||
|
# Cross-platform path handling
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# Running as compiled executable
|
||||||
|
application_path = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# Running as script
|
||||||
|
application_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
settings_path = os.path.join(
|
settings_path = os.path.join(
|
||||||
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
|
application_path,
|
||||||
config.RESOURCE_FOLDER,
|
config.RESOURCE_FOLDER,
|
||||||
config.SETTINGS_FILE
|
config.SETTINGS_FILE
|
||||||
)
|
)
|
||||||
|
|
||||||
settings_icon_path = os.path.join(
|
settings_icon_path = os.path.join(
|
||||||
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
|
application_path,
|
||||||
config.RESOURCE_FOLDER,
|
config.RESOURCE_FOLDER,
|
||||||
config.ICONS["SETTINGS"]
|
config.ICONS["SETTINGS"]
|
||||||
)
|
)
|
||||||
@@ -27,18 +36,25 @@ def load_settings():
|
|||||||
returns a dictionary with all settings.
|
returns a dictionary with all settings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Ensure resources directory exists
|
||||||
|
os.makedirs(os.path.dirname(settings_path), exist_ok=True)
|
||||||
|
|
||||||
# try to open default settings file
|
# try to open default settings file
|
||||||
try:
|
try:
|
||||||
settings_file = open(settings_path, "r+")
|
with open(settings_path, "r", encoding='utf-8') as settings_file:
|
||||||
settings_content = get_file_lines(settings_file)
|
settings_content = settings_file.readlines()
|
||||||
|
settings_content = [line.strip() for line in settings_content]
|
||||||
|
print(f"Settings loaded from: {settings_path}")
|
||||||
except:
|
except:
|
||||||
# File not found
|
# File not found
|
||||||
|
print("Settings file not found, creating default settings")
|
||||||
settings_content = set_default_settings()
|
settings_content = set_default_settings()
|
||||||
|
|
||||||
settings = format_settings(settings_content)
|
settings = format_settings(settings_content)
|
||||||
|
|
||||||
# Check so settings file has all settings
|
# Check so settings file has all settings
|
||||||
if not validate_settings(settings):
|
if not validate_settings(settings):
|
||||||
|
print("Settings validation failed, creating default settings")
|
||||||
settings = format_settings(set_default_settings())
|
settings = format_settings(set_default_settings())
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
@@ -49,6 +65,7 @@ def set_default_settings():
|
|||||||
Creates a config file with default settings.
|
Creates a config file with default settings.
|
||||||
Returns the default config-file content.
|
Returns the default config-file content.
|
||||||
"""
|
"""
|
||||||
|
print(f"Creating default settings at: {settings_path}")
|
||||||
set_settings_file_content(config.DEFAULT_CONFIG)
|
set_settings_file_content(config.DEFAULT_CONFIG)
|
||||||
return config.DEFAULT_CONFIG.split("\n")
|
return config.DEFAULT_CONFIG.split("\n")
|
||||||
|
|
||||||
@@ -65,81 +82,141 @@ def format_settings(file_rows):
|
|||||||
settings = {}
|
settings = {}
|
||||||
|
|
||||||
for row in file_rows:
|
for row in file_rows:
|
||||||
row = row.strip("\n")
|
row = row.strip()
|
||||||
parts = row.split("=", 1)
|
if '=' in row and not row.startswith('#'): # Skip comments
|
||||||
|
parts = row.split("=", 1)
|
||||||
|
|
||||||
if len(parts) == SETTING_PART_LENGTH:
|
if len(parts) == SETTING_PART_LENGTH:
|
||||||
# Strip to remove whitespace at end and beginning
|
# Strip to remove whitespace at end and beginning
|
||||||
settings[parts[0].strip()] = parts[1].strip()
|
key = parts[0].strip()
|
||||||
|
value = parts[1].strip()
|
||||||
|
settings[key] = value
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
def get_file_lines(file):
|
|
||||||
"""
|
|
||||||
Returns a list containing all the lines of the gicen file.
|
|
||||||
"""
|
|
||||||
# read file line per line
|
|
||||||
f_lines = []
|
|
||||||
keep_reading = True
|
|
||||||
while keep_reading:
|
|
||||||
cur_line = file.readline()
|
|
||||||
|
|
||||||
if cur_line:
|
|
||||||
f_lines.append(cur_line)
|
|
||||||
else:
|
|
||||||
keep_reading = False
|
|
||||||
|
|
||||||
return f_lines
|
|
||||||
|
|
||||||
|
|
||||||
def validate_settings(settings):
|
def validate_settings(settings):
|
||||||
"""
|
"""
|
||||||
Checks a settings dictionary so that all the needed settings are present.
|
Checks a settings dictionary so that all the needed settings are present.
|
||||||
|
Enhanced with comprehensive bridge settings validation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for req_setting in config.REQUIRED_SETTINGS:
|
for req_setting in config.REQUIRED_SETTINGS:
|
||||||
if not (req_setting in settings):
|
if req_setting not in settings:
|
||||||
|
print(f"Missing required setting: {req_setting}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not validate_font_size(settings["font_size"]):
|
if not validate_font_size(settings["font_size"]):
|
||||||
|
print(f"Invalid font size: {settings['font_size']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not validate_server_port(settings["server_port"]):
|
if not validate_server_port(settings["server_port"]):
|
||||||
|
print(f"Invalid server port: {settings['server_port']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not validate_color(settings["text_color"]):
|
if not validate_color(settings["text_color"]):
|
||||||
|
print(f"Invalid text color: {settings['text_color']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not validate_color(settings["background_color"]):
|
if not validate_color(settings["background_color"]):
|
||||||
|
print(f"Invalid background color: {settings['background_color']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not (settings["font"] in config.AVAILABLE_FONTS):
|
if settings["font"] not in config.AVAILABLE_FONTS:
|
||||||
|
print(f"Invalid font: {settings['font']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not ((settings["double_layout"] == "True") or
|
if settings["double_layout"].lower() not in ["true", "false"]:
|
||||||
(settings["double_layout"] == "False")):
|
print(f"Invalid double_layout: {settings['double_layout']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not validate_pixels(settings["width"]):
|
if not validate_pixels(settings["width"]):
|
||||||
|
print(f"Invalid width: {settings['width']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not validate_pixels(settings["height"]):
|
if not validate_pixels(settings["height"]):
|
||||||
|
print(f"Invalid height: {settings['height']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not validate_separator(settings["separator"]):
|
if not validate_separator(settings["separator"]):
|
||||||
|
print(f"Invalid separator: {settings['separator']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Bridge settings validation
|
||||||
|
if settings["bridge_enabled"].lower() not in ["true", "false"]:
|
||||||
|
print(f"Invalid bridge_enabled: {settings['bridge_enabled']}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not validate_bridge_port(settings["bridge_port"]):
|
||||||
|
print(f"Invalid bridge port: {settings['bridge_port']}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("All settings validated successfully")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_bridge_port(port):
|
||||||
|
"""Returns whether or not given port is a valid bridge server port."""
|
||||||
|
try:
|
||||||
|
port_num = int(port)
|
||||||
|
return 1024 <= port_num <= 65535
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def set_settings_file_content(content):
|
def set_settings_file_content(content):
|
||||||
"""
|
"""
|
||||||
Saves given content to the config file, config.cfg, in the resources directory.
|
Saves given content to the config file, config.cfg, in the resources directory.
|
||||||
"""
|
"""
|
||||||
settings_file = open(settings_path, "w")
|
# Ensure directory exists
|
||||||
settings_file.write(content)
|
os.makedirs(os.path.dirname(settings_path), exist_ok=True)
|
||||||
settings_file.close()
|
|
||||||
|
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):
|
def edit_settings(root_wnd, apply_method):
|
||||||
@@ -152,13 +229,29 @@ def edit_settings(root_wnd, apply_method):
|
|||||||
width=config.SETTINGS_WINDOW["WIDTH"],
|
width=config.SETTINGS_WINDOW["WIDTH"],
|
||||||
height=config.SETTINGS_WINDOW["HEIGHT"])
|
height=config.SETTINGS_WINDOW["HEIGHT"])
|
||||||
settings_wnd.title(config.SETTINGS_WINDOW["TITLE"])
|
settings_wnd.title(config.SETTINGS_WINDOW["TITLE"])
|
||||||
settings_icon = tkinter.Image("photo", file=settings_icon_path)
|
|
||||||
settings_wnd.tk.call('wm', 'iconphoto', settings_wnd._w, settings_icon)
|
# Try to load settings icon
|
||||||
|
try:
|
||||||
|
if os.path.exists(settings_icon_path):
|
||||||
|
settings_icon = tkinter.PhotoImage(file=settings_icon_path)
|
||||||
|
settings_wnd.iconphoto(False, settings_icon)
|
||||||
|
except:
|
||||||
|
pass # Icon loading failed, continue without icon
|
||||||
|
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
|
|
||||||
settings_wnd.resizable(0, 0)
|
settings_wnd.resizable(0, 0)
|
||||||
|
settings_wnd.transient(root_wnd) # Make it a dialog
|
||||||
|
settings_wnd.grab_set() # Make it modal
|
||||||
|
|
||||||
|
# Center the window
|
||||||
|
settings_wnd.geometry(f"{config.SETTINGS_WINDOW['WIDTH']}x{config.SETTINGS_WINDOW['HEIGHT']}")
|
||||||
|
settings_wnd.update_idletasks()
|
||||||
|
x = (settings_wnd.winfo_screenwidth() // 2) - (config.SETTINGS_WINDOW['WIDTH'] // 2)
|
||||||
|
y = (settings_wnd.winfo_screenheight() // 2) - (config.SETTINGS_WINDOW['HEIGHT'] // 2)
|
||||||
|
settings_wnd.geometry(f"+{x}+{y}")
|
||||||
|
|
||||||
|
# Create labels
|
||||||
font_label = tkinter.Label(settings_wnd,
|
font_label = tkinter.Label(settings_wnd,
|
||||||
text=config.SETTINGS_OPTIONS["FONT"],
|
text=config.SETTINGS_OPTIONS["FONT"],
|
||||||
font=config.GUI_FONT)
|
font=config.GUI_FONT)
|
||||||
@@ -197,26 +290,24 @@ def edit_settings(root_wnd, apply_method):
|
|||||||
font_dropdown.configure(font=config.GUI_FONT)
|
font_dropdown.configure(font=config.GUI_FONT)
|
||||||
|
|
||||||
# Font Size Selection
|
# Font Size Selection
|
||||||
font_size_entry = tkinter.Entry(settings_wnd, width=2, font=config.GUI_FONT)
|
font_size_entry = tkinter.Entry(settings_wnd, width=5, font=config.GUI_FONT)
|
||||||
font_size_entry.insert(0, settings["font_size"])
|
font_size_entry.insert(0, settings["font_size"])
|
||||||
|
|
||||||
# Text Color Selection
|
# Text Color Selection
|
||||||
text_color = tkinter.Button(settings_wnd,
|
text_color = tkinter.Button(settings_wnd,
|
||||||
width=3,
|
width=3,
|
||||||
height=1,
|
height=1)
|
||||||
)
|
|
||||||
|
|
||||||
if validate_color(settings["text_color"]):
|
if validate_color(settings["text_color"]):
|
||||||
text_color.configure(background=settings["text_color"])
|
text_color.configure(background=settings["text_color"])
|
||||||
else:
|
else:
|
||||||
text_color.configure(background="#000000")
|
text_color.configure(background="#000000")
|
||||||
|
|
||||||
def text_color_selection():
|
def text_color_selection():
|
||||||
choosen_color = colorchooser.askcolor()
|
chosen_color = colorchooser.askcolor(parent=settings_wnd)
|
||||||
if choosen_color[1]:
|
if chosen_color[1]:
|
||||||
settings["text_color"] = choosen_color[1]
|
settings["text_color"] = chosen_color[1]
|
||||||
text_color.configure(background=settings["text_color"])
|
text_color.configure(background=settings["text_color"])
|
||||||
|
|
||||||
settings_wnd.focus_force()
|
settings_wnd.focus_force()
|
||||||
|
|
||||||
text_color.configure(command=text_color_selection)
|
text_color.configure(command=text_color_selection)
|
||||||
@@ -227,22 +318,21 @@ def edit_settings(root_wnd, apply_method):
|
|||||||
height=1)
|
height=1)
|
||||||
|
|
||||||
if validate_color(settings["background_color"]):
|
if validate_color(settings["background_color"]):
|
||||||
bg_color.configure(background=settings["background_color"])
|
bg_color.configure(background=settings["background_color"])
|
||||||
else:
|
else:
|
||||||
bg_color.configure(background="#FFFFFF")
|
bg_color.configure(background="#FFFFFF")
|
||||||
|
|
||||||
def bg_color_selection():
|
def bg_color_selection():
|
||||||
choosen_color = colorchooser.askcolor()
|
chosen_color = colorchooser.askcolor(parent=settings_wnd)
|
||||||
if choosen_color[1]:
|
if chosen_color[1]:
|
||||||
settings["background_color"] = choosen_color[1]
|
settings["background_color"] = chosen_color[1]
|
||||||
bg_color.configure(background=settings["background_color"])
|
bg_color.configure(background=settings["background_color"])
|
||||||
|
|
||||||
settings_wnd.focus_force()
|
settings_wnd.focus_force()
|
||||||
|
|
||||||
bg_color.configure(command=bg_color_selection)
|
bg_color.configure(command=bg_color_selection)
|
||||||
|
|
||||||
# Server port Selection
|
# Server port Selection
|
||||||
port_entry = tkinter.Entry(settings_wnd, width=6, font=config.GUI_FONT)
|
port_entry = tkinter.Entry(settings_wnd, width=8, font=config.GUI_FONT)
|
||||||
port_entry.insert(0, settings["server_port"])
|
port_entry.insert(0, settings["server_port"])
|
||||||
|
|
||||||
# Double Layout Selection
|
# Double Layout Selection
|
||||||
@@ -264,13 +354,11 @@ def edit_settings(root_wnd, apply_method):
|
|||||||
use_newline = tkinter.BooleanVar()
|
use_newline = tkinter.BooleanVar()
|
||||||
newline_btn = tkinter.Checkbutton(settings_wnd,
|
newline_btn = tkinter.Checkbutton(settings_wnd,
|
||||||
variable=use_newline,
|
variable=use_newline,
|
||||||
command=
|
command=lambda: set_separator_active(not use_newline.get()))
|
||||||
(lambda: set_separator_active(not use_newline.get()))
|
|
||||||
)
|
|
||||||
|
|
||||||
if settings["separator"] == config.NEWLINE_CONSTANT:
|
if settings["separator"] == config.NEWLINE_CONSTANT:
|
||||||
newline_btn.select()
|
newline_btn.select()
|
||||||
set_separator_active(False);
|
set_separator_active(False)
|
||||||
else:
|
else:
|
||||||
separator_entry.insert(0, settings["separator"])
|
separator_entry.insert(0, settings["separator"])
|
||||||
|
|
||||||
@@ -291,19 +379,19 @@ def edit_settings(root_wnd, apply_method):
|
|||||||
settings["double_layout"] = encode_boolean_setting(double_layout.get())
|
settings["double_layout"] = encode_boolean_setting(double_layout.get())
|
||||||
|
|
||||||
if not validate_font_size(chosen_font_size):
|
if not validate_font_size(chosen_font_size):
|
||||||
msgbox.showerror(config.ERRORS["FONT_SIZE"][0], config.ERRORS["FONT_SIZE"][1])
|
msgbox.showerror(config.ERRORS["FONT_SIZE"][0], config.ERRORS["FONT_SIZE"][1], parent=settings_wnd)
|
||||||
errors_found = True
|
errors_found = True
|
||||||
else:
|
else:
|
||||||
settings["font_size"] = chosen_font_size
|
settings["font_size"] = chosen_font_size
|
||||||
|
|
||||||
if not validate_server_port(chosen_port):
|
if not validate_server_port(chosen_port):
|
||||||
msgbox.showerror(config.ERRORS["SERVER_PORT"][0], config.ERRORS["SERVER_PORT"][1])
|
msgbox.showerror(config.ERRORS["SERVER_PORT"][0], config.ERRORS["SERVER_PORT"][1], parent=settings_wnd)
|
||||||
errors_found = True
|
errors_found = True
|
||||||
else:
|
else:
|
||||||
settings["server_port"] = chosen_port
|
settings["server_port"] = chosen_port
|
||||||
|
|
||||||
if not validate_separator(chosen_separator):
|
if not validate_separator(chosen_separator):
|
||||||
msgbox.showerror(config.ERRORS["SEPARATOR"][0], config.ERRORS["SEPARATOR"][1])
|
msgbox.showerror(config.ERRORS["SEPARATOR"][0], config.ERRORS["SEPARATOR"][1], parent=settings_wnd)
|
||||||
errors_found = True
|
errors_found = True
|
||||||
else:
|
else:
|
||||||
settings["separator"] = chosen_separator
|
settings["separator"] = chosen_separator
|
||||||
@@ -316,43 +404,71 @@ def edit_settings(root_wnd, apply_method):
|
|||||||
settings_wnd.focus_force()
|
settings_wnd.focus_force()
|
||||||
|
|
||||||
save_btn = tkinter.Button(settings_wnd,
|
save_btn = tkinter.Button(settings_wnd,
|
||||||
command=control_and_save,
|
command=control_and_save,
|
||||||
text=config.SETTINGS_WINDOW["SAVE"],
|
text=config.SETTINGS_WINDOW["SAVE"],
|
||||||
font=config.GUI_FONT)
|
font=config.GUI_FONT)
|
||||||
cancel_btn = tkinter.Button(settings_wnd,
|
cancel_btn = tkinter.Button(settings_wnd,
|
||||||
command=settings_wnd.destroy,
|
command=settings_wnd.destroy,
|
||||||
text=config.SETTINGS_WINDOW["CANCEL"],
|
text=config.SETTINGS_WINDOW["CANCEL"],
|
||||||
font=config.GUI_FONT)
|
font=config.GUI_FONT)
|
||||||
|
|
||||||
# Place all components
|
# Place all components with updated positions
|
||||||
font_label.place(x=15, y=15)
|
y_offset = 15
|
||||||
font_size_label.place(x=15, y=55)
|
font_label.place(x=15, y=y_offset)
|
||||||
text_color_label.place(x=15, y=95)
|
y_offset += 40
|
||||||
bg_color_label.place(x=15, y=135)
|
font_size_label.place(x=15, y=y_offset)
|
||||||
layout_label.place(x=15, y=175)
|
y_offset += 40
|
||||||
newline_label.place(x=15, y=215)
|
text_color_label.place(x=15, y=y_offset)
|
||||||
separator_label.place(x=15, y=240)
|
y_offset += 40
|
||||||
port_label.place(x=15, y=280)
|
bg_color_label.place(x=15, y=y_offset)
|
||||||
default_port_label.place(x=15, y=305)
|
y_offset += 40
|
||||||
|
layout_label.place(x=15, y=y_offset)
|
||||||
|
y_offset += 25
|
||||||
|
newline_label.place(x=15, y=y_offset)
|
||||||
|
y_offset += 25
|
||||||
|
separator_label.place(x=15, y=y_offset)
|
||||||
|
y_offset += 40
|
||||||
|
port_label.place(x=15, y=y_offset)
|
||||||
|
y_offset += 25
|
||||||
|
default_port_label.place(x=15, y=y_offset)
|
||||||
|
|
||||||
font_dropdown.place(x=208, y=15)
|
# Place input controls
|
||||||
font_size_entry.place(x=210, y=55)
|
y_offset = 15
|
||||||
text_color.place(x=210, y=95)
|
font_dropdown.place(x=208, y=y_offset)
|
||||||
bg_color.place(x=210, y=135)
|
y_offset += 40
|
||||||
double_layout_btn.place(x=210, y=175)
|
font_size_entry.place(x=210, y=y_offset)
|
||||||
newline_btn.place(x=210, y=215)
|
y_offset += 40
|
||||||
separator_entry.place(x=210, y=240)
|
text_color.place(x=210, y=y_offset)
|
||||||
port_entry.place(x=210, y=280)
|
y_offset += 40
|
||||||
|
bg_color.place(x=210, y=y_offset)
|
||||||
|
y_offset += 40
|
||||||
|
double_layout_btn.place(x=210, y=y_offset)
|
||||||
|
y_offset += 25
|
||||||
|
newline_btn.place(x=210, y=y_offset)
|
||||||
|
y_offset += 25
|
||||||
|
separator_entry.place(x=210, y=y_offset)
|
||||||
|
y_offset += 40
|
||||||
|
port_entry.place(x=210, y=y_offset)
|
||||||
|
|
||||||
save_btn.place(x=110, y=350)
|
# Place buttons at bottom
|
||||||
cancel_btn.place(x=190, y=350)
|
save_btn.place(x=110, y=450)
|
||||||
|
cancel_btn.place(x=190, y=450)
|
||||||
|
|
||||||
|
# Handle window close
|
||||||
|
def on_closing():
|
||||||
|
settings_wnd.grab_release()
|
||||||
|
settings_wnd.destroy()
|
||||||
|
|
||||||
|
settings_wnd.protocol("WM_DELETE_WINDOW", on_closing)
|
||||||
|
|
||||||
|
|
||||||
def validate_color(color):
|
def validate_color(color):
|
||||||
"""
|
"""
|
||||||
Returns whether or not given color is valid in the hexadecimal format.
|
Returns whether or not given color is valid in the hexadecimal format.
|
||||||
"""
|
"""
|
||||||
return isinstance(color, str) and len(color) == 7 and color[0] == "#"
|
if not isinstance(color, str):
|
||||||
|
return False
|
||||||
|
return len(color) == 7 and color[0] == "#" and all(c in '0123456789ABCDEFabcdef' for c in color[1:])
|
||||||
|
|
||||||
|
|
||||||
def validate_font_size(size):
|
def validate_font_size(size):
|
||||||
@@ -364,62 +480,75 @@ def validate_font_size(size):
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return 0 < size < 70
|
return 6 <= size <= 72
|
||||||
|
|
||||||
|
|
||||||
def validate_server_port(port):
|
def validate_server_port(port):
|
||||||
"""
|
"""
|
||||||
Returns Whether or not gicen port is a valid server port.
|
Returns Whether or not given port is a valid server port.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
int(port)
|
port_num = int(port)
|
||||||
return True
|
return 1024 <= port_num <= 65535
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def save_settings(settings):
|
|
||||||
"""
|
|
||||||
Saves given settings to the settings file.
|
|
||||||
"""
|
|
||||||
file_content = ""
|
|
||||||
|
|
||||||
for key in settings.keys():
|
|
||||||
file_content += key + "=" + settings[key] + "\n"
|
|
||||||
|
|
||||||
set_settings_file_content(file_content)
|
|
||||||
|
|
||||||
|
|
||||||
def decode_boolean_setting(setting):
|
def decode_boolean_setting(setting):
|
||||||
"""
|
"""
|
||||||
Decodes a boolean string of "True" or "False"
|
Decodes a boolean string of "True" or "False"
|
||||||
to the coorect boolean value.
|
to the correct boolean value.
|
||||||
|
Enhanced to handle multiple formats.
|
||||||
"""
|
"""
|
||||||
return setting == "True"
|
return str(setting).lower() in ("true", "1", "yes", "on")
|
||||||
|
|
||||||
|
|
||||||
def encode_boolean_setting(value):
|
def encode_boolean_setting(value):
|
||||||
"""
|
"""
|
||||||
Encodes a boolean to the string "True" or "False".
|
Encodes a boolean to the string "true" or "false".
|
||||||
"""
|
"""
|
||||||
if value:
|
return "true" if value else "false"
|
||||||
return "True"
|
|
||||||
else:
|
|
||||||
return "False"
|
|
||||||
|
|
||||||
|
|
||||||
def validate_pixels(pixels):
|
def validate_pixels(pixels):
|
||||||
"""
|
"""
|
||||||
Checks if given string can be used as a pixel value for height or width.
|
Checks if given string can be used as a pixel value for height or width.
|
||||||
Height or Width or assumed to never surpass 10000
|
Height or Width are assumed to never surpass 10000
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
pixels = int(pixels)
|
pixels = int(pixels)
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return 0 < pixels < 10000
|
return 200 <= pixels <= 10000
|
||||||
|
|
||||||
|
|
||||||
def validate_separator(separator):
|
def validate_separator(separator):
|
||||||
return separator.strip()
|
"""
|
||||||
|
Validates the separator string.
|
||||||
|
"""
|
||||||
|
if separator == config.NEWLINE_CONSTANT:
|
||||||
|
return True
|
||||||
|
return len(separator.strip()) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def debug_settings():
|
||||||
|
"""Debug function to print current settings"""
|
||||||
|
print("\n=== Settings Debug ===")
|
||||||
|
try:
|
||||||
|
settings = load_settings()
|
||||||
|
print("Current settings:")
|
||||||
|
for key, value in settings.items():
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
|
||||||
|
print(f"\nSettings file location: {settings_path}")
|
||||||
|
print(f"Settings file exists: {os.path.exists(settings_path)}")
|
||||||
|
|
||||||
|
if os.path.exists(settings_path):
|
||||||
|
with open(settings_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
print(f"\nRaw file content:\n{content}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in debug_settings: {e}")
|
||||||
|
print("=== End Settings Debug ===\n")
|
||||||
@@ -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