From b64a6403cad4bf03fd8a54e3e20efd30b10b95ea Mon Sep 17 00:00:00 2001 From: DOKOS-TAYOS Date: Fri, 25 Sep 2026 06:18:58 +0200 Subject: [PATCH 1/3] fix(install): use project venv for Linux launchers --- .env.example | 2 +- CHANGELOG.md | 4 ++ README.md | 12 +++- bin/linux_desktop.sh | 45 +++++++++++++ bin/run.sh | 13 ++-- bin/setup.sh | 14 ++-- docs/getting-started.md | 12 ++-- install.sh | 65 ++++--------------- src/config/env.py | 2 +- tests/test_linux_installation_ux.py | 99 +++++++++++++++++++++++++++++ 10 files changed, 196 insertions(+), 72 deletions(-) create mode 100644 bin/linux_desktop.sh mode change 100644 => 100755 bin/run.sh mode change 100644 => 100755 bin/setup.sh mode change 100644 => 100755 install.sh create mode 100644 tests/test_linux_installation_ux.py diff --git a/.env.example b/.env.example index a83fa36..63cbc7c 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,7 @@ UI_BUTTON_WIDTH=14 UI_BUTTON_FG="lime green" UI_BUTTON_FG_CANCEL=red2 UI_BUTTON_FG_ACCENT2=yellow -UI_FONT_SIZE=16 +UI_FONT_SIZE=13 UI_FONT_FAMILY=Bahnschrift UI_PADDING=8 diff --git a/CHANGELOG.md b/CHANGELOG.md index a013e21..83274de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Set the default base UI font size to 13 and made Linux setup, run, and desktop + launchers use the project virtual environment without requiring shell activation. +- Added an XDG-aware Linux application-menu launcher and an optional Desktop + launcher when the user's Desktop directory exists. - Synchronized maintained user, developer, API, and in-app reference documentation with the published 1.0.0 implementation. ## [1.0.0] - 2026-09-22 diff --git a/README.md b/README.md index 20d7817..1d9708e 100644 --- a/README.md +++ b/README.md @@ -140,11 +140,18 @@ bin\run.bat Linux/macOS: ```bash -chmod +x bin/setup.sh bin/run.sh ./bin/setup.sh ./bin/run.sh ``` +The setup and run scripts use the project-local `.venv` directly, so you do not +need to activate it first. Manual activation remains useful for development +commands you run yourself. Running `./bin/run.sh` or `.venv/bin/differential-lab` +keeps imports tied to the installed project environment and avoids mismatches +from accidentally using the system Python. On Linux, `install.sh` adds an +application-menu launcher and a Desktop launcher when the XDG Desktop directory +exists. + For development dependencies: ```bat @@ -157,7 +164,8 @@ or on Linux/macOS: ./bin/setup.sh --dev ``` -Direct run from an activated environment: +Manual run from an activated environment (activation is optional when using +the project scripts above): ```bash python src/main_program.py diff --git a/bin/linux_desktop.sh b/bin/linux_desktop.sh new file mode 100644 index 0000000..6a721b0 --- /dev/null +++ b/bin/linux_desktop.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Create Linux application-menu and optional Desktop launchers. + +create_linux_launchers() { + local project_dir="$1" + local data_home="${XDG_DATA_HOME:-$HOME/.local/share}" + local applications_dir="$data_home/applications" + local app_launcher="$applications_dir/DifferentialLab.desktop" + local desktop_dir="" + local entry_point="$project_dir/.venv/bin/differential-lab" + local icon_path="$project_dir/images/DifferentialLab_icon.png" + + mkdir -p "$applications_dir" + cat > "$app_launcher" << EOF +[Desktop Entry] +Version=1.0 +Type=Application +Name=DifferentialLab +Comment=Launch DifferentialLab +Exec="$entry_point" +Path=$project_dir +Icon=$icon_path +Terminal=false +EOF + chmod +x "$app_launcher" + echo "Application-menu launcher created: $app_launcher" + + if [ -n "${XDG_DESKTOP_DIR:-}" ] && [ -d "$XDG_DESKTOP_DIR" ]; then + desktop_dir="$XDG_DESKTOP_DIR" + elif command -v xdg-user-dir >/dev/null 2>&1; then + desktop_dir="$(xdg-user-dir DESKTOP 2>/dev/null || true)" + [ -d "$desktop_dir" ] || desktop_dir="" + fi + if [ -z "$desktop_dir" ] && [ -d "$HOME/Desktop" ]; then + desktop_dir="$HOME/Desktop" + fi + + if [ -n "$desktop_dir" ]; then + cp "$app_launcher" "$desktop_dir/DifferentialLab.desktop" + chmod +x "$desktop_dir/DifferentialLab.desktop" + echo "Desktop launcher created: $desktop_dir/DifferentialLab.desktop" + else + echo "No usable Desktop directory found; application-menu launcher is available." + fi +} diff --git a/bin/run.sh b/bin/run.sh old mode 100644 new mode 100755 index 5904fd0..5ba3bc5 --- a/bin/run.sh +++ b/bin/run.sh @@ -13,17 +13,22 @@ if [ ! -d ".venv" ]; then exit 1 fi +ENTRY_POINT=".venv/bin/differential-lab" +if [ ! -x "$ENTRY_POINT" ]; then + echo "ERROR: DifferentialLab entry point not found or not executable: $ENTRY_POINT" + echo "Please run bin/setup.sh first" + exit 1 +fi + MODE="${1:---prod}" case "$MODE" in --dev|-d) - source .venv/bin/activate - python src/main_program.py + "$ENTRY_POINT" ;; --background|-b|--prod|-p) - source .venv/bin/activate mkdir -p logs - nohup python src/main_program.py >> logs/run.log 2>&1 & + nohup "$ENTRY_POINT" >> logs/run.log 2>&1 & PID="$!" echo "DifferentialLab started in background (PID: $PID)" echo "Logs: logs/run.log" diff --git a/bin/setup.sh b/bin/setup.sh old mode 100644 new mode 100755 index 6c3b45d..0adebea --- a/bin/setup.sh +++ b/bin/setup.sh @@ -130,17 +130,19 @@ else echo " Virtual environment created" fi -echo "" -echo "[4/7] Activating virtual environment..." -source .venv/bin/activate +VENV_PYTHON=".venv/bin/python" +if [ ! -x "$VENV_PYTHON" ]; then + echo "ERROR: Virtual environment Python not found or not executable: $VENV_PYTHON" + exit 1 +fi echo "" -echo "[5/7] Upgrading pip..." -python -m pip install --upgrade pip +echo "[4/7] Upgrading pip..." +"$VENV_PYTHON" -m pip install --upgrade pip echo "" echo "[6/7] Installing dependencies..." -python -m pip install -e "$INSTALL_TARGET" +"$VENV_PYTHON" -m pip install -e "$INSTALL_TARGET" echo "" echo "[7/7] Setting up environment file..." diff --git a/docs/getting-started.md b/docs/getting-started.md index 9810af0..7c3aba6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -15,7 +15,8 @@ create the environment yourself before installing the package. ### First-time clone and setup These scripts are intended for users who do not already have the repository. -They clone the project, run setup, and create a desktop shortcut where possible. +They clone the project and run setup. On Linux, they also add an application-menu +launcher and a Desktop launcher when the XDG Desktop directory exists. Windows: @@ -26,7 +27,6 @@ install.bat Linux/macOS: ```bash -chmod +x install.sh ./install.sh ``` @@ -41,10 +41,14 @@ bin\setup.bat Linux/macOS: ```bash -chmod +x bin/setup.sh ./bin/setup.sh ``` +The setup and run scripts use `.venv` directly, so manual activation is not +required. You can still activate it for manual development commands. Using +`./bin/run.sh` or `.venv/bin/differential-lab` also avoids import and dependency +mismatches caused by running with the system Python. + For development work, install the development extras: ```bat @@ -110,7 +114,7 @@ bin\run.bat --background :: Windows background mode, writes logs/run.log ./bin/run.sh --background # Linux/macOS background mode ``` -Direct run from an activated environment: +Manual run from an activated environment (activation is optional for the scripts): ```bash python src/main_program.py diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 index 31cf550..dcfa3c9 --- a/install.sh +++ b/install.sh @@ -76,32 +76,13 @@ if [ -d "$REPO_NAME" ]; then else echo " Using existing directory..." cd "$REPO_NAME" - chmod +x bin/setup.sh + chmod +x bin/setup.sh bin/run.sh ./bin/setup.sh PROJECT_DIR="$(pwd)" - DESKTOP="${XDG_DESKTOP_DIR:-$HOME/Desktop}" - SHORTCUT="$DESKTOP/DifferentialLab.desktop" - ICON_PATH="$PROJECT_DIR/images/DifferentialLab_icon.ico" - if [ -d "$DESKTOP" ]; then - if [ -f "$ICON_PATH" ]; then - ICON_LINE="Icon=$ICON_PATH" - else - ICON_LINE="Icon=utilities-terminal" - fi - cat > "$SHORTCUT" << EOF -[Desktop Entry] -Version=1.0 -Type=Application -Name=DifferentialLab -Comment=Launch DifferentialLab -Exec=$PROJECT_DIR/bin/run.sh -Path=$PROJECT_DIR -$ICON_LINE -Terminal=false -EOF - chmod +x "$SHORTCUT" - echo "" - echo "Desktop shortcut created: $SHORTCUT" + source "$PROJECT_DIR/bin/linux_desktop.sh" + chmod +x install.sh bin/setup.sh bin/run.sh + if [ "$(uname -s)" = "Linux" ]; then + create_linux_launchers "$PROJECT_DIR" fi exit 0 fi @@ -126,38 +107,14 @@ echo "" echo "[3/4] Running setup..." echo "" -chmod +x bin/setup.sh +chmod +x install.sh bin/setup.sh bin/run.sh ./bin/setup.sh -echo "" -echo "[4/4] Creating desktop shortcut..." - PROJECT_DIR="$(pwd)" -DESKTOP="${XDG_DESKTOP_DIR:-$HOME/Desktop}" -SHORTCUT="$DESKTOP/DifferentialLab.desktop" -ICON_PATH="$PROJECT_DIR/images/DifferentialLab_icon.ico" - -if [ -d "$DESKTOP" ]; then - if [ -f "$ICON_PATH" ]; then - ICON_LINE="Icon=$ICON_PATH" - else - ICON_LINE="Icon=utilities-terminal" - fi - cat > "$SHORTCUT" << EOF -[Desktop Entry] -Version=1.0 -Type=Application -Name=DifferentialLab -Comment=Launch DifferentialLab -Exec=$PROJECT_DIR/bin/run.sh -Path=$PROJECT_DIR -$ICON_LINE -Terminal=false -EOF - chmod +x "$SHORTCUT" - echo " Desktop shortcut created: $SHORTCUT" -else - echo " WARNING: Desktop directory not found, skipping shortcut" +source "$PROJECT_DIR/bin/linux_desktop.sh" +chmod +x install.sh bin/setup.sh bin/run.sh +if [ "$(uname -s)" = "Linux" ]; then + create_linux_launchers "$PROJECT_DIR" fi echo "" @@ -167,5 +124,5 @@ echo "====================================" echo "" echo "DifferentialLab has been cloned and set up." echo "You can now run the application from: $PROJECT_DIR" -echo "Desktop shortcut: $SHORTCUT" +echo "Application-menu launcher is available on Linux." echo "" diff --git a/src/config/env.py b/src/config/env.py index 399285d..0c3faae 100644 --- a/src/config/env.py +++ b/src/config/env.py @@ -79,7 +79,7 @@ # --- ui: text / inputs --- { "key": "UI_FONT_SIZE", - "default": 16, + "default": 13, "cast_type": int, "min": 1, "description": "Base font size in points used across the UI.", diff --git a/tests/test_linux_installation_ux.py b/tests/test_linux_installation_ux.py new file mode 100644 index 0000000..9625304 --- /dev/null +++ b/tests/test_linux_installation_ux.py @@ -0,0 +1,99 @@ +"""Focused coverage for Linux venv launch and desktop integration.""" + +import os +import shutil +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +BASH = shutil.which("bash") + + +def test_run_script_uses_installed_venv_entry_point() -> None: + """The launcher does not depend on shell activation or system Python.""" + script = (ROOT / "bin" / "run.sh").read_text(encoding="utf-8") + + assert 'ENTRY_POINT=".venv/bin/differential-lab"' in script + assert '"$ENTRY_POINT"' in script + assert "source .venv/bin/activate" not in script + assert "python src/main_program.py" not in script + + +@pytest.mark.skipif( + BASH is None or os.name != "posix", + reason="The Linux desktop helper can only run with POSIX paths", +) +def test_desktop_helper_keeps_menu_launcher_without_desktop( + tmp_path: Path, +) -> None: + """A missing Desktop directory does not prevent application-menu setup.""" + project = tmp_path / "Project With Spaces" + home = tmp_path / "home" + data_home = tmp_path / "xdg data" + project.mkdir() + home.mkdir() + environment = os.environ.copy() + environment.update({"HOME": str(home), "XDG_DATA_HOME": str(data_home)}) + environment.pop("XDG_DESKTOP_DIR", None) + + subprocess.run( + [ + BASH or "bash", + "-c", + 'source "$1"; create_linux_launchers "$2"', + "bash", + str(ROOT / "bin" / "linux_desktop.sh"), + str(project), + ], + check=True, + capture_output=True, + text=True, + env=environment, + ) + + launcher = data_home / "applications" / "DifferentialLab.desktop" + contents = launcher.read_text(encoding="utf-8") + assert f'Exec="{project}/.venv/bin/differential-lab"' in contents + assert f"Icon={project}/images/DifferentialLab_icon.png" in contents + assert not (home / "DifferentialLab.desktop").exists() + + +@pytest.mark.skipif( + BASH is None or os.name != "posix", + reason="The Linux desktop helper can only run with POSIX paths", +) +def test_desktop_helper_uses_xdg_desktop_directory(tmp_path: Path) -> None: + """An explicit XDG Desktop path receives a copy of the menu launcher.""" + project = tmp_path / "project" + home = tmp_path / "home" + desktop = tmp_path / "custom desktop" + project.mkdir() + home.mkdir() + desktop.mkdir() + environment = os.environ.copy() + environment.update( + { + "HOME": str(home), + "XDG_DATA_HOME": str(tmp_path / "data"), + "XDG_DESKTOP_DIR": str(desktop), + } + ) + + subprocess.run( + [ + BASH or "bash", + "-c", + 'source "$1"; create_linux_launchers "$2"', + "bash", + str(ROOT / "bin" / "linux_desktop.sh"), + str(project), + ], + check=True, + capture_output=True, + text=True, + env=environment, + ) + + assert (desktop / "DifferentialLab.desktop").is_file() From 1723a4cf8a0059a29ec036851dcf6190d4e60480 Mon Sep 17 00:00:00 2001 From: Alejandro Mata Ali <78550667+DOKOS-TAYOS@users.noreply.github.com> Date: Fri, 25 Sep 2026 06:22:24 +0200 Subject: [PATCH 2/3] fix(install): guard Linux desktop helper --- install.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index dcfa3c9..96ea4cf 100755 --- a/install.sh +++ b/install.sh @@ -79,10 +79,15 @@ if [ -d "$REPO_NAME" ]; then chmod +x bin/setup.sh bin/run.sh ./bin/setup.sh PROJECT_DIR="$(pwd)" - source "$PROJECT_DIR/bin/linux_desktop.sh" chmod +x install.sh bin/setup.sh bin/run.sh if [ "$(uname -s)" = "Linux" ]; then - create_linux_launchers "$PROJECT_DIR" + DESKTOP_HELPER="$PROJECT_DIR/bin/linux_desktop.sh" + if [ -f "$DESKTOP_HELPER" ]; then + source "$DESKTOP_HELPER" + create_linux_launchers "$PROJECT_DIR" + else + echo "WARNING: Linux desktop helper not found, skipping launcher creation" + fi fi exit 0 fi @@ -111,10 +116,15 @@ chmod +x install.sh bin/setup.sh bin/run.sh ./bin/setup.sh PROJECT_DIR="$(pwd)" -source "$PROJECT_DIR/bin/linux_desktop.sh" chmod +x install.sh bin/setup.sh bin/run.sh if [ "$(uname -s)" = "Linux" ]; then - create_linux_launchers "$PROJECT_DIR" + DESKTOP_HELPER="$PROJECT_DIR/bin/linux_desktop.sh" + if [ -f "$DESKTOP_HELPER" ]; then + source "$DESKTOP_HELPER" + create_linux_launchers "$PROJECT_DIR" + else + echo "WARNING: Linux desktop helper not found, skipping launcher creation" + fi fi echo "" From b57a7cea19067e936b8d7c5f911928ca77c96f63 Mon Sep 17 00:00:00 2001 From: Alejandro Mata Ali <78550667+DOKOS-TAYOS@users.noreply.github.com> Date: Fri, 25 Sep 2026 06:22:57 +0200 Subject: [PATCH 3/3] docs(config): align default UI font size --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index e938b89..0a3f233 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -37,7 +37,7 @@ and are logged. | `UI_BUTTON_FG` | `str` | `lime green` | Primary button text color. | | `UI_BUTTON_FG_CANCEL` | `str` | `red2` | Cancel/destructive button text color. | | `UI_BUTTON_FG_ACCENT2` | `str` | `yellow` | Secondary accent button text color. | -| `UI_FONT_SIZE` | `int` | `16` | Base UI font size in points. | +| `UI_FONT_SIZE` | `int` | `13` | Base UI font size in points. | | `UI_FONT_FAMILY` | `str` | `Bahnschrift` | UI font family. | | `UI_PADDING` | `int` | `8` | General spacing between UI elements. |