31ad592e98
- Production/kiosk mode: exit_on_escape=0, window-close guard, Ctrl+C ignore, Alt+F4/Alt+Tab/Win/Ctrl+Esc keyboard hook (Windows) - Robust video playback: async (non-blocking) ffpyplayer teardown, video progress watchdog (advance at true clip end), EOS re-entrancy guard, stale-advance guard, focus keeper for foreground retention - Resume playback timer after Settings/exit popups close - Windows keep-awake: SetThreadExecutionState + disable screensaver/ lock screen (restored on exit) - Always-on playback_trace.log for diagnosing transitions - exe metadata: app_icon.ico + version_info.txt (publisher identity)
15 KiB
15 KiB
🧪 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.pyranping -c 3 -W 3(Linux flags) and on connection failure invokedsudo rfkill/sudo ifconfig wlan0/sudo dhclient— all fail or hang on Windows (sudoisn't even present). - Root cause: This module was missed when the other Linux paths were
patched in
run_win.py. - Fix: Made
network_monitor.pyself-contained cross-platform:- Added
IS_WINDOWS = platform.system() == 'Windows' _test_server_connection()usesping -n 3 -w 3000on Windows_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)
- Added
- Files:
src/network_monitor.py - Test: Windows
ping -n 3 -w 3000 localhostreturns 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):
- 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. - 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.
- Chrome re-used an existing instance.
- Fix (in
windows/run_win.py):- Launch Chrome/Edge with a dedicated
--user-data-dir(<data>/.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, andtaskkillwithout touching the user's profile. _hide_overlay()now calls_bring_chrome_to_front(proc)(new helper that enumeratesChrome_WidgetWin_1/0windows owned by the launched PID) instead of raising Kivy.
- Launch Chrome/Edge with a dedicated
- Update (2026-07-31 15:42): added
--kioskflag 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 atHWND_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()didimport win32con, butwin32conis a pure-Python module inwin32\lib\that is only importable via thepywin32.pthfile..pthfiles are ignored in frozen PyInstaller apps, sowin32conwas never bundled (confirmed viapyi-archive_viewer— onlywin32gui.pyd/win32api.pyd/win32process.pydpresent). Theimport win32conthrew, the whole function silently fell back toWindow.raise_window(), and the Kivy window was never reliably raised. - Fix (in
windows/run_win.py):- Replaced the
win32condependency with raw ctypes + numeric constants (_SW_SHOWNORMAL,_SWP_*,_HWND_TOPMOST, …). - New
_bring_hwnd_to_front(hwnd)— ctypes-onlySetForegroundWindowwithAttachThreadInputforeground-lock bypass +IsIconicrestore + topmost flash. _bring_kivy_to_front()now uses_find_kivy_hwnd()(win32gui.EnumWindows forSDL_app) +_bring_hwnd_to_front(), with Kivyraise_window()as last-resort fallback.
- Replaced the
- Test: exe rebuilt; no
win32conimport remains inrun_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()andrestart_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—<SettingsPopup@Popup>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
--kioskto--start-maximized --app=URL- explicit
--window-size=WxH.--kioskuses Wayland exclusive-fullscreen protocol which doesn't work on Windows.
- explicit
- Tested rejected solutions:
- ❌
--kioskalone → small window, no fullscreen - ❌
--start-fullscreenalone → 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
_Win32Overlayclass — 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—_Win32Overlayclass
[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 <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:KivyWindow.minimize()made Kivy impossible to bring back reliably_windows_play_current_mediakilled the browser but never restoredcontent_area.opacity = 1, so next widget rendered invisible_bring_kivy_to_front()failed because WindowsSetForegroundWindowrefuses to let a background process steal focus
- Fix applied (2026-07-26):
- Removed
KivyWindow.minimize()in_windows_play_weblink()— Kivy stays visible behind the overlay instead of being hidden - Restored
content_area.opacity = 1in_windows_play_current_mediaand_windows_kill_weblink_after_frame()— ensures next widget is visible _bring_kivy_to_front()— addedAttachThreadInput()to bypass Windows foreground lock so Kivy can steal focus from Chrome- Overlay hide now calls
_bring_kivy_to_front()instead ofWindow.raise_window() - CEF path (
_windows_kill_weblink_after_frame) now also calls_bring_kivy_to_front()after hiding
- Removed
- Note:
cefpython3requires 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 foundfor playlist items. - Root cause: Media download only ran when
server_version > local_version. When versions matched (v16 == v16),download_media_fileswas 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 callednext_media(). - Fix: Added
Clock.unschedule(self.next_media)+Clock.schedule_onceto 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:
- Runtime hook (
pyi_runtime_hook.py) setsKIWY_DATA_DIR = exe_dir run_win.pypatchesSignagePlayer.__init__to useKIWY_DATA_DIR- 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
# 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
Investigate [BUG-006]→ merged into [BUG-007], fixed with CEF + win32guiTest→SetForegroundWindow_bring_kivy_to_front()useswin32gui- Install
cefpython3— embedded Chromium, no more subprocess Verify CEF embedded browser actually works at runtime→ CEF needs Python 3.10, falls back to Chrome/EdgeTest the subprocess fallback path when CEF is unavailable→ Tested and working with_bring_kivy_to_front()Check why→ Runtime hook copies resources to exe dirAsyncImageerror shows for intro1.mp4 (path issue)Ensure media files are downloaded before playback→pyi_runtime_hook.pycopies config/resources on first runAdd→ Already incef_browser.pyto PyInstaller hidden importsbuild.specMake→ [BUG-010] fixed 2026-07-31 (network_monitor.pyWindows-compatibleping -n/netsh wlanon Windows, rfkill path preserved on Linux)- Rebuild the .exe to pick up the
network_monitor.pyfix - Clean
cefpython3fromvenv/(Python 3.12 won't use it anyway) - Verify the .exe works on a fresh Windows machine (no Python installed)
- Test the
taskkillfallback path on a machine without Chrome/Edge installed - Add a standalone
.batlauncher for development mode