# πŸ§ͺ Development Track β€” Kiwy Signage Player (Windows Edition) > This file tracks every change, bug fix, tested solution, build info, and > pending issues for the Windows port. Read this FIRST before starting any > debugging or coding session. --- ## πŸ“… Current Session β€” 2026-07-31 | Field | Value | |-------|-------| | **Branch** | `Windows-Player` | | **Python** | 3.12.9 β€” `windows\venv\` (250 MB, all deps installed) | | **Kivy** | 2.3.1 | | **PyInstaller** | 6.21.0 | | **Last .exe build** | 2026-07-26 16:53 β€” `windows\dist\KiwySignagePlayer\KiwySignagePlayer.exe` (96 MB) | | **Build command** | `.\venv\Scripts\python.exe -m PyInstaller build.spec --clean --noconfirm` | ### πŸ“‹ Cross-platform audit β€” Linux commands β†’ Windows handling Every Linux-only command in `src/` was cross-referenced against the patches in `windows/run_win.py`. All are covered except the one listed below: | # | File / method | Linux commands | Windows handling | |---|---------------|----------------|------------------| | 1 | `main.py` `signal_screen_activity()` | `xset`, `xdotool`, `xrandr`, `tvservice`, `wlopm`, `wlr-randr`, `ydotool` | βœ… patched β†’ `SetThreadExecutionState` (ctypes) in `run_win.py` | | 2 | `main.py` `play_weblink()` | `chromium-browser` / `chromium` | βœ… patched β†’ CEF embedded, then Chrome/Edge subprocess | | 3 | `main.py` `_start_inactivity_watchdog()` | `/dev/input/event*`, `select` | βœ… patched β†’ fixed timer watchdog | | 4 | `main.py` `CardReader` | `evdev`, `/dev/input/event*` | βœ… fake `evdev` injected β†’ falls back | | 5 | `main.py` `SettingsPopup.test_connection` | `/tmp/temp_auth_test.json` | βœ… patched β†’ `tempfile.gettempdir()` | | 6 | `main.py` weblink kill/prewarm wrappers | `proc.terminate()` only | βœ… patched β†’ `taskkill /F /T` + `_Win32Overlay` | | 7 | `network_monitor.py` `_test_server_connection()` | `ping -c 3 -W 3` | ❌ **was unpatched** β†’ βœ… **fixed 2026-07-31** | | 8 | `network_monitor.py` `_restart_wifi()` | `sudo rfkill`, `sudo ifconfig`, `sudo dhclient` | ❌ **was unpatched** β†’ βœ… **fixed 2026-07-31** | | 9 | `get_playlists_v2.py`, `player_auth.py`, `ssl_utils.py`, `edit_popup.py`, `keyboard_widget.py` | none | βœ… no Linux commands | --- ## πŸ› Bug Tracker ### [BUG-010] NetworkMonitor uses Linux-only ping + rfkill commands - **Status:** βœ… **Fixed β€” 2026-07-31** - **Symptom:** `network_monitor.py` ran `ping -c 3 -W 3` (Linux flags) and on connection failure invoked `sudo rfkill` / `sudo ifconfig wlan0` / `sudo dhclient` β€” all fail or hang on Windows (`sudo` isn't even present). - **Root cause:** This module was missed when the other Linux paths were patched in `run_win.py`. - **Fix:** Made `network_monitor.py` self-contained cross-platform: 1. Added `IS_WINDOWS = platform.system() == 'Windows'` 2. `_test_server_connection()` uses `ping -n 3 -w 3000` on Windows 3. `_restart_wifi()` dispatches to `_restart_wifi_windows()` (`netsh wlan disconnect` β†’ wait β†’ `netsh wlan connect`) or `_restart_wifi_linux()` (original rfkill/ifconfig/dhclient path kept intact) - **Files:** `src/network_monitor.py` - **Test:** Windows `ping -n 3 -w 3000 localhost` returns 0; AST parse OK. --- ### [BUG-011] Weblink never displays on Windows (opens behind Kivy / exits instantly) - **Status:** βœ… **Fixed β€” 2026-07-31** - **Symptom:** Web link items don't show. In the console log the weblink item is reached but no browser appears, then playback moves on. - **Root causes (two compounding):** 1. **Chrome re-used an existing instance.** `subprocess.Popen([chrome, '--new-window', url])` delegates the URL to the already-running Chrome process and this launched process **exits immediately** (`poll() != None`) β†’ the watchdog fired instantly and advanced to the next item, so the weblink never displayed. 2. **Overlay-hide raised Kivy over Chrome.** `_hide_overlay()` called `_bring_kivy_to_front()`, so even when Chrome did open it sat *behind* the borderless-fullscreen Kivy window. - **Fix (in `windows/run_win.py`):** 1. Launch Chrome/Edge with a **dedicated `--user-data-dir`** (`/.kiosk-profile`) so a brand-new, trackable browser instance is created instead of delegating to an existing one. Also guarantees a top-level window we can enumerate, raise, and `taskkill` without touching the user's profile. 2. `_hide_overlay()` now calls **`_bring_chrome_to_front(proc)`** (new helper that enumerates `Chrome_WidgetWin_1/0` windows owned by the launched PID) instead of raising Kivy. - **Update (2026-07-31 15:42):** added **`--kiosk`** flag to the weblink launch args so the browser opens in true kiosk mode (no UI/chrome, locks to screen). Safe with the dedicated `--user-data-dir` β€” does not affect the user's normal browser session. - **Update (2026-07-31 16:04):** replaced the fixed 1.0s overlay-hide timer with **adaptive polling** (`_hide_overlay_when_chrome_ready`). The black overlay now stays up until Chrome's window is actually detected on screen (`_find_chrome_hwnd`), so the host desktop is never exposed during cold starts / slow disk / GPU init. Falls back to Kivy after a 6s timeout. - **Update (2026-07-31 16:19):** added a **persistent `_Win32Backdrop`** β€” a fullscreen black window created at player startup (`_Win32Backdrop.show()`) placed at `HWND_BOTTOM` (below Kivy & the kiosk browser, above the desktop), destroyed only on clean exit. Any browser load/unload gap now reveals clean black instead of the host desktop. - **Test:** exe rebuilt 2026-07-31 16:19; DLL set intact (28 DLLs incl. FFmpeg). ### [BUG-012] Next widget never comes to foreground after weblink ends - **Status:** βœ… **Fixed β€” 2026-07-31** - **Symptom:** After a weblink finishes, the next media/widget renders but the Kivy window stays behind (or the window focus is lost) β€” user sees the wrong window / frozen view. - **Root cause:** `_bring_kivy_to_front()` did `import win32con`, but `win32con` is a pure-Python module in `win32\lib\` that is **only importable via the `pywin32.pth` file**. `.pth` files are ignored in frozen PyInstaller apps, so `win32con` was never bundled (confirmed via `pyi-archive_viewer` β€” only `win32gui.pyd` / `win32api.pyd` / `win32process.pyd` present). The `import win32con` threw, the whole function silently fell back to `Window.raise_window()`, and the Kivy window was never reliably raised. - **Fix (in `windows/run_win.py`):** 1. Replaced the `win32con` dependency with **raw ctypes + numeric constants** (`_SW_SHOWNORMAL`, `_SWP_*`, `_HWND_TOPMOST`, …). 2. New `_bring_hwnd_to_front(hwnd)` β€” ctypes-only `SetForegroundWindow` with `AttachThreadInput` foreground-lock bypass + `IsIconic` restore + topmost flash. 3. `_bring_kivy_to_front()` now uses `_find_kivy_hwnd()` (win32gui.EnumWindows for `SDL_app`) + `_bring_hwnd_to_front()`, with Kivy `raise_window()` as last-resort fallback. - **Test:** exe rebuilt; no `win32con` import remains in `run_win.py`. --- ### [BUG-001] RecursionError: play_current_media ↔ restart_playlist - **Status:** βœ… Fixed 2026-07-24 - **Symptom:** Pressing "Restart Player" in settings with empty playlist causes infinite recursion: `play_current_media β†’ restart_playlist β†’ play_current_media β†’ ...` - **Fix:** Added empty-playlist guard in both `play_current_media()` and `restart_playlist()` β†’ they return early instead of calling each other. - **Files:** `src/main.py` β€” lines ~1304 and ~2073 - **Test:** Verified no Python syntax errors via `ast.parse`. ### [BUG-002] Settings fields cut off on small screens - **Status:** βœ… Fixed 2026-07-24 - **Symptom:** "Screen Name", "Quickconnect" and other fields at the top of the settings popup are invisible on smaller resolutions because content overflows the popup. - **Fix:** Wrapped settings content in a `ScrollView`. Moved "Save & Close" / "Cancel" buttons outside the scroll (always visible). Reduced row heights. - **Files:** `src/signage_player.kv` β€” `` block ### [BUG-003] Chromium not fullscreen on Windows - **Status:** βœ… Fixed 2026-07-24 - **Symptom:** Web links open in a small window instead of fullscreen. - **Fix:** Changed launch args from `--kiosk` to `--start-maximized --app=URL` + explicit `--window-size=WxH`. `--kiosk` uses Wayland exclusive-fullscreen protocol which doesn't work on Windows. - **Tested rejected solutions:** - ❌ `--kiosk` alone β†’ small window, no fullscreen - ❌ `--start-fullscreen` alone β†’ not reliable - βœ… `--start-maximized --app=URL --window-size=...` β†’ works - **Files:** `windows/run_win.py` β€” `_windows_play_weblink()` ### [BUG-004] Desktop flash when switching between Chromium and Kivy - **Status:** βœ… Fixed 2026-07-24 - **Symptom:** When Chrome closes, the desktop is briefly visible before Kivy reappears. Also when Chrome opens, there's a flash. - **Fix:** Added `_Win32Overlay` class β€” a fullscreen black Win32 window that covers the screen during transitions. Shown BEFORE closing Chrome / opening Chrome, hidden AFTER Kivy is ready. - **Tested rejected solutions:** - ❌ `Window.raise_window()` alone β†’ still shows flash - βœ… Win32 black overlay β†’ smooth masking - **Files:** `windows/run_win.py` β€” `_Win32Overlay` class ### [BUG-005] Chrome processes linger after closing weblink - **Status:** βœ… Fixed 2026-07-24 - **Symptom:** After a weblink item ends, Chrome child processes (GPU, renderer) remain running β†’ blank windows accumulate. - **Fix:** Use `taskkill /F /T /PID ` to kill the entire process tree. - **Tested rejected solutions:** - ❌ `proc.terminate()` β†’ leaves children running - ❌ `proc.kill()` β†’ same problem - βœ… `taskkill /F /T` β†’ kills everything - **Files:** `windows/run_win.py` β€” `_windows_kill_process_tree()` ### [BUG-007] Video plays behind Chromium on weblinkβ†’media transition - **Status:** βœ… **Fixed β€” 2026-07-26 (final)** - **Symptom:** When a weblink ends and the next media starts, the media plays *behind* Chromium. Audio is heard but user sees Chrome. - **Root cause (Windows):** Linux renders Kivy widget UNDER Chromium β†’ closes Chrome β†’ widget visible. On Windows Chrome stays ON TOP. `Window.raise_window()` is unreliable. Three compounding issues: 1. `KivyWindow.minimize()` made Kivy impossible to bring back reliably 2. `_windows_play_current_media` killed the browser but never restored `content_area.opacity = 1`, so next widget rendered invisible 3. `_bring_kivy_to_front()` failed because Windows `SetForegroundWindow` refuses to let a background process steal focus - **Fix applied (2026-07-26):** 1. **Removed `KivyWindow.minimize()`** in `_windows_play_weblink()` β€” Kivy stays visible behind the overlay instead of being hidden 2. **Restored `content_area.opacity = 1`** in `_windows_play_current_media` and `_windows_kill_weblink_after_frame()` β€” ensures next widget is visible 3. **`_bring_kivy_to_front()`** β€” added `AttachThreadInput()` to bypass Windows foreground lock so Kivy can steal focus from Chrome 4. **Overlay hide** now calls `_bring_kivy_to_front()` instead of `Window.raise_window()` 5. **CEF path** (`_windows_kill_weblink_after_frame`) now also calls `_bring_kivy_to_front()` after hiding - **Note:** `cefpython3` requires Python 3.10 β€” falls back to subprocess Chrome/Edge on 3.12.9. Transition now works reliably with subprocess path. - **Files:** `windows/run_win.py` ### [BUG-008] Intro video and media files not found at runtime - **Status:** βœ… **Fixed** 2026-07-24 - **Symptom:** `[ERROR] [Image] Error loading <...intro1.mp4>` β€” intro broken. Also `❌ Media file not found` for playlist items. - **Root cause:** Media download only ran when `server_version > local_version`. When versions matched (v16 == v16), `download_media_files` was never called β†’ media folder stayed empty. - **Fix:** Added download check in the "up to date" branch β€” now downloads missing media files even when playlist version hasn't changed. ### [BUG-009] Video never advances to next item (EOS handler empty) - **Status:** βœ… **Fixed** 2026-07-24 - **Symptom:** Video plays but never advances to the next playlist item. - **Root cause:** `_on_video_eos()` callback was a stub β€” just logged "Video finished playing (EOS)" but never called `next_media()`. - **Fix:** Added `Clock.unschedule(self.next_media)` + `Clock.schedule_once` to advance after 0.5s when a video reaches end of stream. --- ## πŸ§ͺ Tested & Rejected Solutions Log > Keep a record of approaches that were tried and didn't work, so we don't > waste time re-testing them. | Date | What was tested | Result | Reason it failed | |------|----------------|--------|-----------------| | 2026-07-24 | Python 3.14 with Kivy | ❌ | `kivy_deps.sdl2_dev~=0.8.0` has no cp314 wheel | | 2026-07-24 | `--kiosk` Chrome flag on Windows | ❌ | Not fullscreen, Wayland exclusive-fullscreen not available | | 2026-07-24 | `--start-fullscreen` alone | ❌ | Inconsistent, sometimes not full | | 2026-07-24 | `proc.terminate()` for Chrome | ❌ | Leaves child processes running | | 2026-07-24 | `proc.kill()` for Chrome | ❌ | Same as terminate β€” children survive | | 2026-07-24 | `Window.raise_window()` for transition | ❌ | Brief desktop flash visible | --- ## πŸ“ Data Directory Behaviour When the .exe runs: 1. Runtime hook (`pyi_runtime_hook.py`) sets `KIWY_DATA_DIR = exe_dir` 2. `run_win.py` patches `SignagePlayer.__init__` to use `KIWY_DATA_DIR` 3. Local folders created next to the .exe: ``` KiwySignagePlayer.exe config/ app_config.json resources/ (icons, intro video) certs/ (SSL certificates) media/ edited_media/ playlists/ logs/ .kivy/ (Kivy home) .player_heartbeat ``` --- ## πŸ”§ Build Cheatsheet ```powershell # Build the .exe (from windows/ directory) Set-Location windows & .\venv\Scripts\python.exe -m PyInstaller build.spec --clean --noconfirm # Run in dev mode (no build needed) & .\venv\Scripts\python.exe run_win.py # Test imports only & .\venv\Scripts\python.exe test_import_fix.py ``` --- ## πŸ“ Notes for the Next Session - [x] ~~Investigate [BUG-006]~~ β†’ merged into [BUG-007], fixed with CEF + win32gui - [x] ~~Test `SetForegroundWindow`~~ β†’ `_bring_kivy_to_front()` uses `win32gui` - [x] Install `cefpython3` β€” embedded Chromium, no more subprocess - [x] ~~Verify CEF embedded browser actually works at runtime~~ β†’ CEF needs Python 3.10, falls back to Chrome/Edge - [x] ~~Test the subprocess fallback path when CEF is unavailable~~ β†’ Tested and working with `_bring_kivy_to_front()` - [x] ~~Check why `AsyncImage` error shows for intro1.mp4 (path issue)~~ β†’ Runtime hook copies resources to exe dir - [x] ~~Ensure media files are downloaded before playback~~ β†’ `pyi_runtime_hook.py` copies config/resources on first run - [x] ~~Add `cef_browser.py` to PyInstaller hidden imports~~ β†’ Already in `build.spec` - [x] ~~Make `network_monitor.py` Windows-compatible~~ β†’ [BUG-010] fixed 2026-07-31 (`ping -n` / `netsh wlan` on Windows, rfkill path preserved on Linux) - [ ] Rebuild the .exe to pick up the `network_monitor.py` fix - [ ] Clean `cefpython3` from `venv/` (Python 3.12 won't use it anyway) - [ ] Verify the .exe works on a fresh Windows machine (no Python installed) - [ ] Test the `taskkill` fallback path on a machine without Chrome/Edge installed - [ ] Add a standalone `.bat` launcher for development mode