Skip to content

Conversation

@Dimi20cen
Copy link
Contributor

@Dimi20cen Dimi20cen commented Aug 18, 2025

Summary

This pull request enhances the UX on Windows by introducing a system tray icon. This work builds upon the previous two-process architecture to create an improved Windows application.

Explanation of core changes (Windows):

  1. System Tray / Taskbar Icon Integration:

    • What: A new system tray icon (KolibriTaskBarIcon) is now the primary interface for managing the application's lifecycle on Windows. It provides status notifications (starting, ready, failed) and a right-click context menu.
    • Why: This provides a persistent control point for the user. It allows the UI to be closed while the server (either local or service-based) continues running in the background.
    • Menu Toggle Options:
      • UI Startup (Per-User): A menu option allows users to toggle whether the Kolibri UI window opens automatically on logon. This is a per-user setting managed via the HKEY_CURRENT_USER Run key.
      • Service Startup (System-Wide): A separate menu option allows users to enable or disable the Kolibri background service. As this is a system-wide change, it triggers a UAC prompt by re-launching the application with elevated privileges to execute a new --configure-service command.
  2. Installer and Startup Logic Overhaul:

    • What: The Inno Setup installer logic has been refactored. It now unconditionally installs the service components but enables the service's start type (Automatic vs. Disabled) based on the user's choice during installation.
    • Tray-Only Mode: When the user opts to run Kolibri automatically on boot, the installer adds a startup entry to HKEY_LOCAL_MACHINE that launches the app in --tray-only mode. This makes only the tray icon to appear on system start, not the full application window.
  3. Refined Window Management:

    • What: On Windows, clicking the close button on the main window no longer quits the application. Instead, it simply hides the window, which can be re-opened from the tray icon. The application is now exited explicitly via the "Exit" option in the tray menu.

Code Pointers

  • src/kolibri_app/taskbar_icon.py: (New File) Contains all the logic for the system tray icon, including menu creation, event handling, notifications, and UAC elevation for service configuration.
  • src/kolibri_app/windows_registry.py: (New File) Centralizes all Windows Registry operations for checking WebView2 installation and managing startup settings.
  • src/kolibri_app/application.py: Updates to implement to create a hidden window for IPC messages, and integrate the new taskbar icon and its notifications.
  • src/kolibri_app/main.py: Updated to parse the new command-line arguments: --tray-only for background startup and --configure-service for the elevated process that modifies the Windows service.
  • kolibri.iss: The Inno Setup script has been revised to implement the new startup logic, conditionally setting the service start type and adding the KolibriTray entry to the system-wide Run key.
  • src/kolibri_app/view.py: The OnClose event handler is modified to hide the window on Windows instead of shutting down.

References

Fixes #177

Reviewer guidance

The most critical area for review is the new Windows-specific logic, focusing on the system tray icon, and the revised installer startup behavior.

How to test on Windows:

  1. Install dependencies again, (make dependencies).
  2. Run
make get-whl whl="https://github.com/learningequality/kolibri/releases/download/v0.18.1/kolibri-0.18.1-py2.py3-none-any.whl"
  1. Run make pyinstaller to build the application.
  2. Run the executable located in dist/Kolibri-0.18.1/Kolibri-0.18.1.exeto test the standalone app.
  3. To test the installer: Run make build-installer-windows (requires Inno Setup installed)
  4. Then run the installer located in dist-installer/kolibri-setup-0.18.1.exe

Primary focus for testing:

Tray Icon and Application Lifecycle

  • Standard Operation:
    1. Launch the app. Verify the tray icon appears.
    2. Left-click the tray icon. The main UI window should appear.
    3. Click the window's close (X) button. The window should hide, but the tray icon should remain. The process should still be running in Task Manager.
    4. Left-click the tray icon again to show the window.
  • Context Menu:
    1. Right-click the tray icon to open the menu.
    2. Select "Open UI". It should show the main window.
    3. Click on "Exit".
      • If running with the background service, only the tray icon and UI process should exit. The "Kolibri" service should continue running.
        • You can check that either from services.msc or from Task Manager.
      • If running a local server, both the UI and server subprocess should terminate.
  • Notifications on start:
    1. Launch the app. Verify the "Kolibri is starting..." notification appears.
    2. Verify the "Kolibri Ready" notification appears once the server is up.

Startup Configuration (from Tray Menu)

  • Toggle "Open Kolibri UI on logon":
    1. Right-click the tray icon and check/uncheck this option.
    2. Verify a notification appears confirming the change.
    3. Verify that a Kolibri_UI value is created or deleted in the current user's Run key: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run.
  • Toggle "Run Kolibri service on start":
    1. Right-click the tray icon and check/uncheck this option.
    2. Verify a UAC prompt appears.
    3. Approve the prompt and then verify the service startup type changes between "Automatic" and "Disabled" in services.msc and a success notification appears.

Single-Instance Behavior

  1. Launch the application normally.
  2. While it's running, try to launch it again from the desktop shortcut or executable.
  3. Verify the second instance exits immediately and the main window of the first instance is brought to the foreground.
  4. Hide the main window by clicking the close button. Launch the application again. Verify the hidden window is shown and brought to the foreground.

Installer and System Startup

  • Clean Install with "Run automatically":
    1. On a clean system, run the installer and check the "Run Kolibri automatically when the computer starts" option.
    2. After installation, check services.msc and verify the "Kolibri" service is installed and its "Startup Type" is "Automatic".
    3. Check the registry (regedit) and verify a KolibriTray entry exists in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run.
  • Clean Install WITHOUT "Run automatically":
    1. Run the installer and uncheck the "Run automatically" option.
    2. Verify the "Kolibri" service is installed but its "Startup Type" is "Disabled".
    3. Verify the KolibriTray registry key does not exist in the HKLM Run key.

Uninstallation Test

  1. Install the application with the "Run automatically" option enabled.
  2. Run the uninstaller from Windows Settings or by running unins000.exe.
  3. Verify the uninstaller correctly stops the service and tray icon process.
  4. After uninstallation, verify the "Kolibri" service is completely removed from services.msc.
  5. Verify the KolibriTray and Kolibri_UI registry keys are removed from both HKLM and HKCU Run keys.
  6. Verify the installation directory (C:\Program Files\Kolibri) is removed.
  7. When prompted, choose to remove user data and verify C:\ProgramData\kolibri is also deleted.

Testing on macOS/Linux (Regression Check):

  • Build and run the application on a non-Windows machine to confirm that there are no regressions. The behavior should be identical to the main branch.
  • Expected Behavior: The application should start, load Kolibri, and shut down exactly as it does on the main branch. This refactoring should have introduced no functional changes on non-Windows platforms.

Known issues:

  • The kolibri icon is blue-yellow instead of yellow-blue in windows notifications, if the windows theme is "light".
  • The user messages for the tray icon and its notifications are inside _() and ready to be translated, but they are not translated yet.

This commit introduces an enhancement to the Windows UX by implementing a tray icon. This allows Kolibri to run as a background application, providing users with windows service controls and status notifications.

The implementation includes:
- System Tray Icon & Notifications: An icon in the taskbar displays server status with a right-click context menu for key actions (Open UI, Open Kolibri UI on logon, Run Kolibri Service on start, Exit). It uses native Windows notifications for events like "server starting" and "server ready."
- Elevated Service Configuration: A `--configure-service `command allows the tray icon to modify the service start type (Automatic/Disabled) with a UAC prompt for elevation.
- Background Mode: The app now supports a `--tray-only` startup flag. On Windows, closing the main window hides it to the tray instead of exiting.
- Installer Integration: The Inno Setup script is updated to always install the service and adds the tray application to system startup when the service is enabled.
@Dimi20cen Dimi20cen force-pushed the feat/windows/system_tray_icon branch from 5e7fe9f to 7f8ee06 Compare August 18, 2025 08:54
@Dimi20cen Dimi20cen marked this pull request as ready for review August 19, 2025 14:14
@Dimi20cen
Copy link
Contributor Author

Should I change the name of taskbar_icon.py to something windows related (e.g., windows_taskbar_icon.py)?

Changes include:
- The app now checks for the WebView2 runtime at startup. If it is not found, it will open the Kolibri URL in the user's default web browser once the server is ready.
- The Inno Setup script now detects legacy Windows versions and skips the WebView2 runtime installation to prevent installer errors.
@Dimi20cen
Copy link
Contributor Author

@ozer550 noticed a scenario which I had overlooked, that results in two Kolibri server processes running simultaneously.

Steps to Reproduce:

  1. Install the app without enabling the "Run Kolibri automatically" option during setup. The Kolibri Windows service is therefore Disabled.
  2. Open the Kolibri App. Since the service isn't running, a local (non-service) Kolibri server process is started by the app.
  3. From the system tray icon's menu, the user toggles on "Run Kolibri service on start".
  4. This action correctly reconfigures the service to Automatic but also immediately starts the service.

At this point, both the original local server and the newly started Windows service are running at the same time.

I see two potential paths to fix this:

1. Don't Start the Service Immediately
When the user toggles the option on, we only change the service's startup type to Automatic but do not start it.

  • The service will then start on the next reboot as expected.

2. Handover
When the user toggles the option on, the app performs a "handover" from the local server to the Windows service. This would involve:

  • Starting the Windows service.
  • The UI process detecting the service is ready.
  • Shutting down the local server process.
  • Connecting the UI to the running service.

Thoughts?

@ozer550
Copy link
Member

ozer550 commented Aug 25, 2025

@ozer550 noticed a scenario which I had overlooked, that results in two Kolibri server processes running simultaneously.

Steps to Reproduce:

  1. Install the app without enabling the "Run Kolibri automatically" option during setup. The Kolibri Windows service is therefore Disabled.
  2. Open the Kolibri App. Since the service isn't running, a local (non-service) Kolibri server process is started by the app.
  3. From the system tray icon's menu, the user toggles on "Run Kolibri service on start".
  4. This action correctly reconfigures the service to Automatic but also immediately starts the service.

At this point, both the original local server and the newly started Windows service are running at the same time.

I see two potential paths to fix this:

1. Don't Start the Service Immediately When the user toggles the option on, we only change the service's startup type to Automatic but do not start it.

  • The service will then start on the next reboot as expected.

2. Handover When the user toggles the option on, the app performs a "handover" from the local server to the Windows service. This would involve:

  • Starting the Windows service.
  • The UI process detecting the service is ready.
  • Shutting down the local server process.
  • Connecting the UI to the running service.

Thoughts?

Option 1 appears to avoid complex race conditions, provides predictable and testable behavior, and requires only minimal code changes.

Correctly restores the main window if it is minimized when the user:
- Left-clicks the tray icon
- Uses the "Open UI" menu option
- Launches the app from an application shortcut
Copy link
Member

@ozer550 ozer550 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, Everything makes sense! Just some small changes for better code readability and future maintenance.

@ozer550 ozer550 self-assigned this Aug 26, 2025
Copy link
Member

@rtibbles rtibbles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some questions but nothing totally blocking.

Previously, when the "Run Kolibri service on start" option was toggled on in the Windows taskbar icon menu, Kolibri would configure the service for automatic startup and attempt to start it.

With this change, the "Run Kolibri service on start" option only sets the Kolibri Windows service's startup type to "Automatic". The service will then start on the next system reboot.
This commit refactors all Windows service interactions to use the `pywin32` library instead of calling `sc.exe`.

The new implementation uses Windows API calls for:
- Checking service status (`is_service_running`)
- Getting service startup type (`get_service_start_type`)
- Configuring service startup type (`_configure_service_start_type`)
Updates taskbar icon loading to use the standard `importlib.resources` library and adjusts the PyInstaller spec accordingly

To support this, the `icons` directory has been moved into `src/kolibri_app`  to be treated as a proper package resource. The PyInstaller spec file has been updated to reflect this new location.
@Dimi20cen
Copy link
Contributor Author

Hi @rtibbles, I changed the location of the icons dir.

If you're okay with this change, could you please test that it didn't break the icons on Mac?

The `dmgbuild_settings.py` file was still pointing to the old location of `Layout.png` and `kolibri.icns`, causing the macOS installer build to fail. This updates the paths to the new location.
@Dimi20cen Dimi20cen force-pushed the feat/windows/system_tray_icon branch from 91bb7e9 to 8ecd582 Compare August 27, 2025 18:46
The previous implementation used a single fixed delay to verify if the service startup type was changed, which could lead to a race condition if the system took longer than expected to apply the change.

This commit implements a polling mechanism that retries checking the service status until it is updated or a timeout is reached. This makes the UI feedback more reliable.

Magic numbers for verification intervals and notification timeouts have also been replaced with named constants.
Create a new windows_registry.py module to contain all Windows Registry interactions. This refactoring moves functions for checking WebView2 installation and managing UI/tray startup settings from taskbar_icon.py and __main__.py into the new centralized module.
@Dimi20cen Dimi20cen requested a review from ozer550 August 28, 2025 05:24
When the application is already running but is not the active window, launching it again from a shortcut did not bring the existing window to the foreground.

This was caused by Windows' focus stealing prevention, which ignores the `view.Raise()` call from a background process. The fix temporarily sets the `wx.STAY_ON_TOP` style before raising the window, which forces it to the front, and then immediately reverts the style to restore normal behavior.
Copy link
Member

@ozer550 ozer550 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the suggestions have been implemented, and code changes make sense to me! Nice work @Dimi20cen!

@rtibbles rtibbles merged commit 7085abb into learningequality:main Aug 29, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add System Tray Icon for Windows

3 participants