Fix pre-existing player issues: edit upload paths, playlist sync, DPI, console
src/edit_popup.py: pass and reproduce the server-side edited-media layout
('edited_media/<media_id>/') when saving an edit, falling back to the flat
folder when no media id is available, so uploads land where the server expects.
src/get_playlists_v2.py: preserve every server field (audio, muted, description,
id, position, ...) when rewriting playlist items, instead of rebuilding fixed
dicts that silently dropped them. Web-link items keep their original http(s) url
and are never downloaded.
windows/pyi_runtime_hook.py: declare per-monitor DPI awareness before SDL/Kivy
initialise, so on a scaled display the window is not virtualised to a smaller
resolution (which left a black strip and mis-scaled media).
windows/build_win.bat: optional code signing step for PCs with Smart App Control
enabled, driven by KIWY_SIGN_PFX / KIWY_SIGN_PFX_PASSWORD or a local
kiwy_signing.pfx.
windows/build.spec, windows/README_WINDOWS_BUILD.md, windows/development-track.md:
build notes and manifest updates.
This commit is contained in:
@@ -10,7 +10,7 @@ The original app was built for **Raspberry Pi (Linux)**, using these technologie
|
||||
|-----------|---------------------|-------------------|
|
||||
| **GUI** | Kivy 2.3+ | Kivy 2.3+ (works cross-platform) |
|
||||
| **Video** | ffpyplayer | ffpyplayer (needs FFmpeg DLLs) |
|
||||
| **Card Reader** | evdev (Linux input) | **Not available** — gracefully disabled |
|
||||
| **Card Reader** | evdev (Linux input) | ✅ Raw Input API + LL-hook fallback |
|
||||
| **Screen Keep-Awake** | xset, xdotool, Wayland | `SetThreadExecutionState` (Win32 API) |
|
||||
| **Weblink** | chromium-browser (kiosk) | Chrome/Edge (--kiosk mode) |
|
||||
| **Audio** | ALSA/PulseAudio | DirectSound |
|
||||
@@ -27,9 +27,9 @@ The original app was built for **Raspberry Pi (Linux)**, using these technologie
|
||||
- ✅ Web links (opens in Chrome/Edge kiosk)
|
||||
- ✅ Network monitoring
|
||||
- ✅ Auto-update playlist
|
||||
- ✅ Card reader authentication (Raw Input API — see below)
|
||||
|
||||
### What is disabled on Windows
|
||||
- ❌ Card reader (evdev is Linux-only; `EVDEV_AVAILABLE = False`)
|
||||
- ❌ HDMI power management (tvservice is RPi-specific)
|
||||
- ❌ WiFi restart (uses Linux `nmcli`)
|
||||
|
||||
@@ -112,6 +112,50 @@ For a **single-file .exe**, edit `build.spec` — uncomment the `exe_onefile` se
|
||||
}
|
||||
```
|
||||
|
||||
## 💳 Card Reader (Windows Edition)
|
||||
|
||||
The card reader now works on Windows via the **Raw Input API** (with a
|
||||
low-level keyboard-hook fallback). It replaces the Linux-only `evdev`
|
||||
implementation automatically when `run_win.py` starts.
|
||||
|
||||
- Detection mirrors the Linux logic:
|
||||
1. A device named with `card` / `reader` / `rfid`
|
||||
2. A USB HID keyboard (non-PS/2) — most card readers enumerate this way
|
||||
3. Any remaining keyboard (excluding touchscreens/mice)
|
||||
- Only keystrokes from the **selected device** are captured, so the
|
||||
operator's real keyboard cannot pollute card data.
|
||||
- Card data ends on **Enter** (same as Linux).
|
||||
|
||||
### Card reader config (optional)
|
||||
|
||||
Add any of these to `config\app_config.json` next to the .exe:
|
||||
|
||||
```json
|
||||
{
|
||||
"card_reader_mode": "auto", // "auto" | "raw" | "hook"
|
||||
"card_reader_device": "", // e.g. "VID_08FF" to force a specific device
|
||||
"card_reader_timeout": 5 // seconds
|
||||
}
|
||||
```
|
||||
|
||||
- `card_reader_mode`: `auto` (default, tries Raw Input then falls back),
|
||||
`raw` (force Raw Input), or `hook` (force the low-level keyboard hook).
|
||||
- `card_reader_device`: optional substring of the device name to pin the
|
||||
reader (e.g. `VID_08FF`, `HID#VID_08FF`). Run the manual test below to see
|
||||
the exact device names on your host.
|
||||
- `card_reader_timeout`: how long the swipe popup waits (default 5 s).
|
||||
|
||||
### Manual card reader test (no GUI)
|
||||
|
||||
```batch
|
||||
cd windows
|
||||
venv\Scripts\activate
|
||||
python win_card_reader.py
|
||||
```
|
||||
|
||||
Swipe a card within 10 seconds — the tool prints the captured data, then
|
||||
exits. The detected devices are listed in the console/log.
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
```batch
|
||||
|
||||
@@ -107,6 +107,38 @@ if %ERRORLEVEL% neq 0 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM ---- Optional code signing ----------------------------------------
|
||||
REM For production PCs with Smart App Control ON, the exe MUST be signed
|
||||
REM by a cert from a reputable public CA. If you have a .pfx, put its path
|
||||
REM in the env var KIWY_SIGN_PFX (and optionally KIWY_SIGN_PFX_PASSWORD),
|
||||
REM or drop a pfx named "kiwy_signing.pfx" in this folder. The build will
|
||||
REM then auto-sign via sign_exe.ps1.
|
||||
echo.
|
||||
echo [STEP] Checking for code-signing certificate...
|
||||
|
||||
set "SIGN_PFX=%KIWY_SIGN_PFX%"
|
||||
if not defined SIGN_PFX if exist "%~dp0kiwy_signing.pfx" set "SIGN_PFX=%~dp0kiwy_signing.pfx"
|
||||
|
||||
if defined SIGN_PFX (
|
||||
echo [INFO ] Code-signing cert found: %SIGN_PFX%
|
||||
if defined KIWY_SIGN_PFX_PASSWORD (
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0sign_exe.ps1" -CertPath "%SIGN_PFX%" -CertPassword "%KIWY_SIGN_PFX_PASSWORD%"
|
||||
) else (
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0sign_exe.ps1" -CertPath "%SIGN_PFX%"
|
||||
)
|
||||
if not "!ERRORLEVEL!"=="0" (
|
||||
echo [WARNING] Signing failed or signtool missing - the exe is NOT signed.
|
||||
echo Smart App Control machines will still block it.
|
||||
) else (
|
||||
echo [OK] Executable signed successfully.
|
||||
)
|
||||
) else (
|
||||
echo [INFO ] No signing cert found - skipping signing.
|
||||
echo [INFO ] To sign automatically, set KIWY_SIGN_PFX to your .pfx path
|
||||
echo or place "kiwy_signing.pfx" in this folder.
|
||||
echo [INFO ] NOTE: Unsigned exe will be BLOCKED on PCs with Smart App Control ON.
|
||||
)
|
||||
|
||||
REM ---- Success ----
|
||||
echo.
|
||||
echo ============================================
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 📅 Current Session — 2026-07-31
|
||||
## 📅 Current Session — 2026-08-07
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
@@ -14,9 +14,37 @@
|
||||
| **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) |
|
||||
| **Last .exe build** | 2026-08-07 08:23 — `windows\dist\KiwySignagePlayer\KiwySignagePlayer.exe` (98.8 MB) |
|
||||
| **Build command** | `.\venv\Scripts\python.exe -m PyInstaller build.spec --clean --noconfirm` |
|
||||
|
||||
### Overnight soak test findings (2026-08-07 morning)
|
||||
|
||||
- **Symptom:** player froze on the last video widget (never advanced past
|
||||
`video_loaded`); heartbeat stopped; 7 leaked `msedge.exe` processes left
|
||||
running in the background.
|
||||
- **Root cause (two compounding bugs in `run_win.py`):**
|
||||
1. **Leaked browser + instant-exit handoff.** A previously leaked Chrome/Edge
|
||||
process held the `.kiosk-profile` lock. The next weblink launch handed
|
||||
the URL to that leaked instance and **exited in ~2s** (`07:04:57` launch →
|
||||
`07:04:59 next_media_called`). The watchdog advanced instantly, so the
|
||||
weblink never showed AND the leaked browser window was never killed →
|
||||
`msedge.exe` processes accumulated overnight.
|
||||
2. **Main-thread freeze.** The focus keeper ran heavy Win32 work
|
||||
(`EnumWindows` + `AttachThreadInput` + `SetForegroundWindow` + `SendInput`)
|
||||
synchronously on the Kivy thread every second. With leaked Edge windows
|
||||
fighting back, this wedged the event loop → video never advanced, heartbeat
|
||||
stopped (`08-07 07:09`).
|
||||
- **Fix (in `run_win.py`, rebuilt 08:23):**
|
||||
1. New `_windows_kill_browsers_on_profile()` — scans `chrome/msedge/chromium`
|
||||
command lines (WMIC, PowerShell fallback), taskkills any browser holding
|
||||
the `.kiosk-profile` lock. Called **before every weblink launch**.
|
||||
2. Watchdog now has `MIN_ALIVE_BEFORE_EARLY_ADVANCE = 8s` — an instant
|
||||
(~2s) handoff exit no longer advances/skips the weblink.
|
||||
3. `_bring_kivy_to_front(async_ok=True)` runs the heavy Win32 bring-to-front
|
||||
on a **background worker thread** guarded by a lock, so the Kivy main
|
||||
thread is never blocked. Synchronous `async_ok=False` still available for
|
||||
explicit transitions.
|
||||
|
||||
### 📋 Cross-platform audit — Linux commands → Windows handling
|
||||
|
||||
Every Linux-only command in `src/` was cross-referenced against the patches
|
||||
|
||||
@@ -10,6 +10,37 @@ import sys
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _set_process_dpi_awareness():
|
||||
"""Declare per-monitor DPI awareness BEFORE SDL/Kivy initialize.
|
||||
|
||||
On a display scaled above 100% (e.g. 1920x1080 @ 125%), Windows
|
||||
virtualizes a non-DPI-aware app to the scaled-down size (1536x864).
|
||||
Kivy then sizes its content area to the virtualized resolution, leaving a
|
||||
black strip on one side and making images/videos render at the wrong size.
|
||||
Must run before any SDL window is created, so this lives in the runtime
|
||||
hook (the first Python code that runs in the frozen app).
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
aware = ctypes.c_int(2) # PROCESS_PER_MONITOR_DPI_AWARE_V2
|
||||
ctypes.windll.shcore.SetProcessDpiAwareness(aware)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
ctypes.windll.user32.SetProcessDPIAware()
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
import ctypes
|
||||
_set_process_dpi_awareness()
|
||||
|
||||
# ── IMPORTANT: Set Windows environment BEFORE any Kivy code runs ──
|
||||
# This must happen before main.py's top-level code executes, because
|
||||
# main.py sets SDL_VIDEODRIVER=wayland,x11,dummy which would crash on Windows.
|
||||
@@ -23,6 +54,8 @@ os.environ['FFPYPLAYER_CODECS'] = 'h264,h265,vp9,vp8'
|
||||
os.environ['SDL_VIDEO_ALLOW_SCREENSAVER'] = '0'
|
||||
os.environ['KIVY_NO_FILELOG'] = '1'
|
||||
os.environ['KIVY_INPUTPROVIDERS'] = '' # Let Kivy auto-detect on Windows
|
||||
# Use native physical pixels (fixes black strip on DPI-scaled displays).
|
||||
os.environ.setdefault('SDL_VIDEO_HIGHDPI', '1')
|
||||
|
||||
# ── Capture ALL early output to a crash log ─────────────────────────
|
||||
# Ensure we catch any exception that happens before Logger is available.
|
||||
|
||||
Reference in New Issue
Block a user