Add Windows Player support and related files

- New windows/ directory with build scripts, specs, and configuration
- Windows-specific requirements (requirements_win.txt)
- Launch and runtime scripts for Windows (run_win.py, launch_player.bat)
- PyInstaller build configuration (build.spec)
- Updated .gitignore to exclude windows/venv312/
- Updated config and source files for Windows compatibility
- Moved working_files to proper directory
This commit is contained in:
ske087
2026-07-24 08:34:00 +03:00
parent f7b889be11
commit 3845830a86
16 changed files with 1379 additions and 22 deletions
+34
View File
@@ -0,0 +1,34 @@
Requirement already satisfied: ffpyplayer in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (4.5.3)
Requirement already satisfied: requests in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (2.34.2)
Requirement already satisfied: aiohttp in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (3.14.3)
Requirement already satisfied: bcrypt in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (5.0.0)
Requirement already satisfied: certifi in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (2026.7.22)
Requirement already satisfied: pyinstaller in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (6.21.0)
Requirement already satisfied: kivy[base] in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (2.3.1)
Requirement already satisfied: Kivy-Garden>=0.1.4 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (0.1.5)
Requirement already satisfied: docutils in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (0.23)
Requirement already satisfied: pygments in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (2.20.0)
Requirement already satisfied: filetype in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (1.2.0)
Requirement already satisfied: kivy-deps.angle~=0.4.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (0.4.0)
Requirement already satisfied: kivy-deps.sdl2~=0.8.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (0.8.0)
Requirement already satisfied: kivy-deps.glew~=0.3.1 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (0.3.1)
Requirement already satisfied: pypiwin32 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (223)
Requirement already satisfied: pillow<11,>=9.5.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from kivy[base]) (10.4.0)
Requirement already satisfied: charset_normalizer<4,>=2 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from requests) (3.4.9)
Requirement already satisfied: idna<4,>=2.5 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from requests) (3.18)
Requirement already satisfied: urllib3<3,>=1.26 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from requests) (2.7.0)
Requirement already satisfied: aiohappyeyeballs>=2.5.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from aiohttp) (2.7.1)
Requirement already satisfied: aiosignal>=1.4.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from aiohttp) (1.4.0)
Requirement already satisfied: attrs>=17.3.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from aiohttp) (26.1.0)
Requirement already satisfied: frozenlist>=1.1.1 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from aiohttp) (1.8.0)
Requirement already satisfied: multidict<7.0,>=4.5 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from aiohttp) (6.7.1)
Requirement already satisfied: propcache>=0.2.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from aiohttp) (0.5.2)
Requirement already satisfied: typing_extensions>=4.4 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from aiohttp) (4.16.0)
Requirement already satisfied: yarl<2.0,>=1.17.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from aiohttp) (1.24.5)
Requirement already satisfied: altgraph in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from pyinstaller) (0.17.5)
Requirement already satisfied: packaging>=22.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from pyinstaller) (26.2)
Requirement already satisfied: pefile>=2022.5.30 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from pyinstaller) (2024.8.26)
Requirement already satisfied: pyinstaller-hooks-contrib>=2026.6 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from pyinstaller) (2026.6)
Requirement already satisfied: pywin32-ctypes>=0.2.1 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from pyinstaller) (0.2.3)
Requirement already satisfied: setuptools>=42.0.0 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from pyinstaller) (83.0.0)
Requirement already satisfied: pywin32>=223 in c:\users\dell-pc\desktop\kiwy-signage\windows\venv\lib\site-packages (from pypiwin32->kivy[base]) (312)
+152
View File
@@ -0,0 +1,152 @@
# Kiwy Signage Player - Windows Edition
Build and run the Kiwy digital signage player on Windows as a standalone `.exe`.
## 📋 Requirements Analysis
The original app was built for **Raspberry Pi (Linux)**, using these technologies:
| Component | Original (RPi/Linux) | Windows Equivalent |
|-----------|---------------------|-------------------|
| **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 |
| **Screen Keep-Awake** | xset, xdotool, Wayland | `SetThreadExecutionState` (Win32 API) |
| **Weblink** | chromium-browser (kiosk) | Chrome/Edge (--kiosk mode) |
| **Audio** | ALSA/PulseAudio | DirectSound |
| **Window Backend** | SDL2 (Wayland/X11) | SDL2 (Windows native) |
| **OpenGL** | Desktop GL | ANGLE (DirectX wrapper) |
### What works on Windows
- ✅ Media playback (images, videos via ffpyplayer)
- ✅ Playlist sync from DigiServer (HTTP/HTTPS)
- ✅ Touch & mouse controls
- ✅ Settings popup
- ✅ Image editing/annotation
- ✅ Password-protected exit
- ✅ Web links (opens in Chrome/Edge kiosk)
- ✅ Network monitoring
- ✅ Auto-update playlist
### 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`)
## 🚀 Quick Start (Development)
### Prerequisites
1. **Python 3.10+** (64-bit) — [python.org](https://python.org)
2. **FFmpeg** — for video codec support
- Download from [ffmpeg.org](https://ffmpeg.org/download.html)
- Add `bin\` folder to your PATH
3. **Visual C++ Redistributable** — [latest](https://aka.ms/vs/17/release/vc_redist.x64.exe)
### Install & Run
```batch
cd windows
REM Create virtual environment
python -m venv venv
venv\Scripts\activate
REM Install dependencies
pip install -r requirements_win.txt
REM Run in development mode
python run_win.py
```
## 📦 Building the .exe
### One-Command Build
```batch
cd windows
build_win.bat
```
### Manual Build
```batch
cd windows
venv\Scripts\activate
pip install -r requirements_win.txt
pyinstaller build.spec --clean --noconfirm
```
### Output
```
windows\dist\KiwySignagePlayer\
├── KiwySignagePlayer.exe # Main executable
├── config/ # Config files (auto-copied)
├── resources/ # Icons, intro video
└── ... (supporting DLLs)
```
For a **single-file .exe**, edit `build.spec` — uncomment the `exe_onefile` section and comment out the `coll = COLLECT(...)` section.
## ⚙️ Configuration
1. On first run, config files are created in `%APPDATA%\KiwySignage\`
2. Edit `%APPDATA%\KiwySignage\config\app_config.json` to set your server:
```json
{
"server_ip": "192.168.0.109",
"port": "8080",
"screen_name": "Birou_IT",
"quickconnect_key": "8887779",
"orientation": "Landscape",
"touch": "True",
"max_resolution": "1920x1080",
"edit_feature_enabled": true,
"use_https": false,
"verify_ssl": false
}
```
## 🧪 Testing
```batch
cd windows
venv\Scripts\activate
python run_win.py
```
## 🔧 Troubleshooting
| Problem | Solution |
|---------|----------|
| **"ffpyplayer not found"** | Install: `pip install ffpyplayer` |
| **"No video" / black screen** | Install FFmpeg and add to PATH. Try `KIVY_GL_BACKEND=angle_sdl2` or `KIVY_GL_BACKEND=gl` |
| **Kivy window doesn't open** | Run from command prompt to see error messages. Ensure GPU drivers are up to date. |
| **Weblinks not opening** | Install Google Chrome or Microsoft Edge |
| **Can't connect to server** | Check firewall. Try `use_https: false` and `verify_ssl: false` for testing |
| **Antivirus flags .exe** | Add the output folder to antivirus exclusions. This is a false positive common with PyInstaller. |
## 📁 Project Structure (Build)
```
Kiwy-Signage/
├── windows/
│ ├── run_win.py # Windows entry point (patches platform differences)
│ ├── build.spec # PyInstaller configuration
│ ├── build_win.bat # One-click build script
│ ├── pyi_runtime_hook.py # PyInstaller runtime hook
│ ├── requirements_win.txt # Windows Python dependencies
│ └── README_WINDOWS_BUILD.md # This file
├── src/
│ ├── main.py # Main application (original)
│ ├── get_playlists_v2.py # Playlist sync
│ ├── player_auth.py # Authentication
│ ├── ssl_utils.py # SSL/HTTPS
│ ├── keyboard_widget.py # On-screen keyboard
│ ├── network_monitor.py # Network monitoring
│ ├── edit_popup.py # Image editing
│ └── signage_player.kv # Kivy UI layout
├── config/
│ ├── app_config.json # Player configuration
│ └── resources/ # Icons, images, intro video
├── media/ # Downloaded media (created at runtime)
├── playlists/ # Playlist files (created at runtime)
└── logs/ # Log files (created at runtime)
```
+240
View File
@@ -0,0 +1,240 @@
# -*- mode: python ; coding: utf-8 -*-
"""
PyInstaller spec file for Kiwy Signage Player (Windows .exe)
Build command (from windows/ directory):
pyinstaller build.spec --clean --noconfirm
OR use the build script:
build_win.bat
"""
import os
import sys
from pathlib import Path
# --- Paths -----------------------------------------------------------
# This spec file is in windows/build.spec, so the project root is
# always two levels up from this file's real location.
# __file__ may not be available in PyInstaller spec context fallback to cwd.
try:
_spec_dir = Path(__file__).resolve().parent
except NameError:
_spec_dir = Path(os.getcwd()).resolve()
# _spec_dir is now the absolute path to the windows/ directory
BUILD_DIR = _spec_dir
ROOT_DIR = BUILD_DIR.parent
SRC_DIR = ROOT_DIR / 'src'
CONFIG_DIR = ROOT_DIR / 'config'
RESOURCES_DIR = CONFIG_DIR / 'resources'
# --- Determine hidden imports that PyInstaller might miss -------------
hidden_imports = [
# Kivy core modules
'kivy.core.window',
'kivy.core.video',
'kivy.core.audio',
'kivy.core.text',
'kivy.core.image',
'kivy.core.gl',
'kivy.core.camera',
'kivy.core.clipboard',
'kivy.core.spelling',
'kivy.core.text.markup',
'kivy.core.window.window_sdl2',
'kivy.core.image.img_sdl2',
'kivy.core.video.video_ffpyplayer',
'kivy.core.audio.audio_ffpyplayer',
# Kivy modules
'kivy.uix.video',
'kivy.uix.vkeyboard',
'kivy.uix.popup',
'kivy.uix.image',
'kivy.uix.button',
'kivy.uix.label',
'kivy.uix.textinput',
'kivy.uix.boxlayout',
'kivy.uix.floatlayout',
'kivy.uix.slider',
'kivy.uix.widget',
'kivy.uix.checkbox',
'kivy.graphics',
'kivy.graphics.texture',
'kivy.graphics.vertex_instructions',
'kivy.graphics.context_instructions',
'kivy.clock',
'kivy.loader',
'kivy.animation',
'kivy.lang',
'kivy.logger',
'kivy.config',
'kivy.properties',
'kivy.metrics',
'kivy.factory',
# Graphics providers
'kivy.graphics.opengl',
'kivy.graphics.opengl_utils',
'kivy.graphics.fbo',
'kivy.graphics.gl_instructions',
'kivy.graphics.stencil_instructions',
'kivy.graphics.scissor_instructions',
'kivy.graphics.buffer',
'kivy.graphics.vbo',
'kivy.graphics.shader',
'kivy.graphics.compiler',
# ffpyplayer
'ffpyplayer',
'ffpyplayer.player',
'ffpyplayer.pic',
'ffpyplayer.writer',
# Networking
'requests',
'aiohttp',
'urllib3',
'certifi',
'bcrypt',
# Platform
'ctypes',
'ctypes.wintypes',
'subprocess',
'shutil',
'glob',
'selectors',
'tempfile',
]
# Exclude Linux-only modules
excluded_imports = [
'gi', # GTK introspection (Linux)
'gi.repository',
'evdev', # We inject a fake evdev module in run_win.py
]
# --- Application data files to bundle --------------------------------
# Resources (icons, intro video, etc.)
resources_data = []
for item in RESOURCES_DIR.iterdir():
if item.is_file():
target_dir = 'config/resources'
resources_data.append((str(item), target_dir))
# Config directory (app_config.json)
config_data = []
config_file = CONFIG_DIR / 'app_config.json'
if config_file.exists():
config_data.append((str(config_file), 'config'))
# Source files - .kv file
kv_file = SRC_DIR / 'signage_player.kv'
kv_data = []
if kv_file.exists():
kv_data.append((str(kv_file), '.'))
# Bundle the entire src directory as a tree
source_tree = Tree(str(SRC_DIR), prefix='', excludes=['*.pyc', '__pycache__', '*.ini'])
# --- Collect binary DLLs from kivy_deps and ffpyplayer ----------------
import importlib.util
from pathlib import Path as _Path
def _find_share_dlls(package_path, subdir='bin'):
"""Find .dll files under a package's share/ directory."""
if not package_path:
return []
base = _Path(package_path).parent
# Check: share/<pkg>/bin/ relative to parent
share = base / 'share'
if share.is_dir():
results = []
for root, dirs, files in os.walk(share):
for f in files:
if f.endswith('.dll'):
results.append((os.path.join(root, f), '.'))
return results
return []
# SDL2 DLLs
_sdl2_spec = importlib.util.find_spec('kivy_deps.sdl2')
_sdl2_dlls = _find_share_dlls(_sdl2_spec.origin if _sdl2_spec else None)
# ANGLE DLLs
_angle_spec = importlib.util.find_spec('kivy_deps.angle')
_angle_dlls = _find_share_dlls(_angle_spec.origin if _angle_spec else None)
# GLEW DLLs
_glew_spec = importlib.util.find_spec('kivy_deps.glew')
_glew_dlls = _find_share_dlls(_glew_spec.origin if _glew_spec else None)
# ffpyplayer FFmpeg DLLs
_ffpy_spec = importlib.util.find_spec('ffpyplayer')
_ffpy_dlls = _find_share_dlls(_ffpy_spec.origin if _ffpy_spec else None)
_all_binaries = _sdl2_dlls + _angle_dlls + _glew_dlls + _ffpy_dlls
if not _all_binaries:
print("=" * 70)
print("WARNING: No Kivy/ffpyplayer DLLs found via share/ directories.")
print("PyInstaller may still auto-detect them, but if the .exe")
print("fails with 'SDL2.dll not found' or similar, you will need")
print("to manually add the DLL paths to the spec file.")
print("=" * 70)
# --- Build the .exe --------------------------------------------------
a = Analysis(
['run_win.py'], # Entry point (relative to this spec)
pathex=[str(BUILD_DIR), str(SRC_DIR)], # Where to find modules
binaries=_all_binaries,
datas=resources_data + config_data + kv_data,
hiddenimports=hidden_imports,
hookspath=[],
hooksconfig={},
runtime_hooks=[str(BUILD_DIR / 'pyi_runtime_hook.py')],
excludes=excluded_imports,
noarchive=False,
module_collection_mode={
'kivy': 'pyz',
'kivy.core': 'pyz',
'kivy.uix': 'pyz',
'kivy.graphics': 'pyz',
},
)
# Add the source tree (main.py, etc.)
a.datas += source_tree
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='KiwySignagePlayer',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True, # Show console for debugging startup errors
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=str(RESOURCES_DIR / 'app_icon.ico') if (RESOURCES_DIR / 'app_icon.ico').exists() else None,
)
# --- COLLECT everything into a single folder -------------------------
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='KiwySignagePlayer',
)
+126
View File
@@ -0,0 +1,126 @@
@echo off
REM =====================================================================
REM Kiwy Signage Player - Windows Build Script
REM =====================================================================
REM This script builds a standalone Windows .exe using PyInstaller.
REM
REM Prerequisites:
REM 1. Python 3.10+ installed (with "Add to PATH" checked)
REM 2. Visual C++ Redistributable (for ffpyplayer)
REM 3. FFmpeg binaries in PATH (optional, for video codec support)
REM
REM Steps:
REM 1. Run this script from the project root or the windows\ folder
REM 2. The .exe will be created in windows\dist\KiwySignagePlayer\
REM =====================================================================
setlocal enabledelayedexpansion
cd /d "%~dp0"
echo ============================================
echo Kiwy Signage Player - Windows Build
echo ============================================
echo.
REM ---- Check Python ----
where python >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo [ERROR] Python not found! Please install Python 3.10+ and add it to PATH.
pause
exit /b 1
)
echo [INFO] Using Python:
python --version
REM ---- Create virtual environment (if not exists) ----
if not exist "venv\Scripts\python.exe" (
echo.
echo [STEP] Creating virtual environment...
python -m venv venv
if %ERRORLEVEL% neq 0 (
echo [ERROR] Failed to create virtual environment.
pause
exit /b 1
)
) else (
echo [INFO] Virtual environment already exists.
)
REM ---- Activate virtual environment ----
call venv\Scripts\activate.bat
REM ---- Install/upgrade pip ----
echo.
echo [STEP] Upgrading pip...
python -m pip install --upgrade pip
REM ---- Install dependencies ----
echo.
echo [STEP] Installing Windows dependencies...
pip install -r requirements_win.txt
if %ERRORLEVEL% neq 0 (
echo [ERROR] Failed to install dependencies.
pause
exit /b 1
)
REM ---- Verify Kivy installation ----
echo.
echo [STEP] Verifying Kivy installation...
python -c "import kivy; print(f'Kivy {kivy.__version__}')" 2>&1
if %ERRORLEVEL% neq 0 (
echo [WARNING] Kivy check failed. Build may still work but test carefully.
)
REM ---- Check PyInstaller ----
echo.
echo [STEP] Verifying PyInstaller...
python -c "import PyInstaller; print(f'PyInstaller {PyInstaller.__version__}')" 2>&1
if %ERRORLEVEL% neq 0 (
echo [ERROR] PyInstaller not found.
pause
exit /b 1
)
REM ---- Create app icon (from PNG if possible) ----
echo.
echo [STEP] Checking for app icon...
if not exist "..\config\resources\app_icon.ico" (
echo [INFO] No .ico icon found. Will use default PyInstaller icon.
echo [INFO] To add a custom icon, place app_icon.ico in config\resources\
)
REM ---- Run PyInstaller ----
echo.
echo [STEP] Building executable with PyInstaller...
echo This may take several minutes. Please wait...
echo.
pyinstaller build.spec --clean --noconfirm
if %ERRORLEVEL% neq 0 (
echo.
echo [ERROR] PyInstaller build failed!
echo Check the output above for error details.
pause
exit /b 1
)
REM ---- Success ----
echo.
echo ============================================
echo BUILD COMPLETE!
echo ============================================
echo.
echo Output: %~dp0dist\KiwySignagePlayer\
echo.
echo The executable is:
echo %~dp0dist\KiwySignagePlayer\KiwySignagePlayer.exe
echo.
echo To run: Double-click KiwySignagePlayer.exe
echo.
echo Note: The first run may take a while as Windows Defender
echo scans the executable. This is normal.
echo.
pause
+27
View File
@@ -0,0 +1,27 @@
@echo off
REM ============================================================
REM Kiwy Signage Player - Windows Launcher
REM ============================================================
REM This batch file launches the Kiwy Signage Player executable.
REM It creates local folders for playlist, media, config, and logs
REM next to the executable.
REM ============================================================
cd /d "%~dp0dist\KiwySignagePlayer"
echo ============================================
echo Kiwy Signage Player - Windows Edition
echo ============================================
echo.
echo Launching player...
echo.
start "" "KiwySignagePlayer.exe"
echo Player started.
echo.
echo If the player window does not appear, check:
echo dist\KiwySignagePlayer\logs\crash.log
echo dist\KiwySignagePlayer\logs\fatal_crash.log
echo.
pause
+131
View File
@@ -0,0 +1,131 @@
"""
PyInstaller Runtime Hook for Kiwy Signage Player
------------------------------------------------
Runs at startup of the packaged .exe to fix paths and environment.
Creates all necessary folders LOCAL to the executable's directory.
"""
import os
import sys
import platform
from pathlib import Path
# ── 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.
os.environ['SDL_VIDEODRIVER'] = 'windows'
os.environ['SDL_AUDIODRIVER'] = 'directsound'
os.environ['KIVY_WINDOW'] = 'sdl2'
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'
os.environ['KIVY_VIDEO'] = 'ffpyplayer'
os.environ['KIVY_AUDIO'] = 'ffpyplayer'
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
# ── Capture ALL early output to a crash log ─────────────────────────
# Ensure we catch any exception that happens before Logger is available.
_startup_log_path = None
try:
_exe_dir = Path(sys.executable).parent
_startup_log_path = _exe_dir / 'logs' / 'startup_crash.log'
(_startup_log_path.parent).mkdir(parents=True, exist_ok=True)
with open(_startup_log_path, 'w') as _f:
_f.write("pyi_runtime_hook.py started\n")
except Exception:
pass
def _setup_paths():
"""Ensure the app can find its bundled files at runtime.
All data folders (config, media, playlists, logs) are created
LOCAL to the executable's directory — NOT in %%APPDATA%%.
"""
# In PyInstaller, sys.executable is the .exe path.
# sys._MEIPASS is the extraction directory (i.e. _internal/ folder).
exe_dir = Path(sys.executable).parent
internal_dir = Path(getattr(sys, '_MEIPASS', exe_dir))
# ── Change cwd to _internal so Builder.load_file('signage_player.kv')
# and other relative file references from main.py resolve ─────
os.chdir(str(internal_dir))
# Add bundled src directory to Python path
src_dir = str(internal_dir / 'src')
if os.path.isdir(src_dir) and src_dir not in sys.path:
sys.path.insert(0, src_dir)
# Add internal directory for config/media/playlists access
if str(internal_dir) not in sys.path:
sys.path.insert(0, str(internal_dir))
# ── Local folders next to the .exe ──────────────────────────────
# All data lives in the SAME folder as the executable so the user
# can copy/move the whole directory and everything still works.
os.environ['KIWY_DATA_DIR'] = str(exe_dir)
# Set KIVY_HOME to a local .kivy folder next to the .exe
kivy_home = exe_dir / '.kivy'
os.environ.setdefault('KIVY_HOME', str(kivy_home))
kivy_home.mkdir(parents=True, exist_ok=True)
# Create local data folders next to the .exe
for sub in ['config', 'config/resources', 'media', 'playlists', 'logs']:
(exe_dir / sub).mkdir(parents=True, exist_ok=True)
def _copy_bundled_resources():
"""Copy bundled resource/config files to the local folders on first run."""
exe_dir = Path(sys.executable).parent
internal_dir = Path(getattr(sys, '_MEIPASS', exe_dir))
# Files to copy (source in bundle -> destination next to .exe)
files_to_copy = [
('config/app_config.json', 'config/app_config.json'),
('config/resources/access-card.png', 'config/resources/access-card.png'),
('config/resources/arrow.png', 'config/resources/arrow.png'),
('config/resources/backward.png', 'config/resources/backward.png'),
('config/resources/card-checked.png', 'config/resources/card-checked.png'),
('config/resources/edit-pen.png', 'config/resources/edit-pen.png'),
('config/resources/exit.png', 'config/resources/exit.png'),
('config/resources/forward.png', 'config/resources/forward.png'),
('config/resources/intro1.mp4', 'config/resources/intro1.mp4'),
('config/resources/pause.png', 'config/resources/pause.png'),
('config/resources/pencil.png', 'config/resources/pencil.png'),
('config/resources/play.png', 'config/resources/play.png'),
('config/resources/settings.png', 'config/resources/settings.png'),
]
for src_rel, dest_rel in files_to_copy:
src_path = internal_dir / src_rel
dest_path = exe_dir / dest_rel
if src_path.is_file() and not dest_path.exists():
try:
dest_path.parent.mkdir(parents=True, exist_ok=True)
import shutil
shutil.copy2(str(src_path), str(dest_path))
except Exception:
pass # Non-critical; app can still run
# ── Wrap everything in try/except to capture early crashes ──────────
try:
_setup_paths()
_copy_bundled_resources()
# If we reach here, the hook finished successfully
try:
with open(_startup_log_path, 'a') as _f:
_f.write("pyi_runtime_hook.py completed successfully\n")
except Exception:
pass
except Exception as _hook_exc:
import traceback as _tb
try:
with open(_startup_log_path, 'a') as _f:
_f.write(f"pyi_runtime_hook.py CRASHED: {_hook_exc}\n")
_tb.print_exc(file=_f)
except Exception:
pass
raise # Re-raise so the .exe still fails visibly
+28
View File
@@ -0,0 +1,28 @@
# =====================================================================
# Kiwy Signage Player - Windows Dependencies
# =====================================================================
# Install with: pip install -r requirements_win.txt
# --- Core GUI Framework ---
# Kivy 2.3+ with SDL2 backend (best for Windows)
kivy[base]>=2.3.0
# --- Video Playback ---
# ffpyplayer for video decoding
ffpyplayer>=4.5
# --- HTTP / Networking ---
requests>=2.32.0,<3.0.0
aiohttp>=3.9.0,<4.0.0
certifi>=2024.0.0
# --- Password / Auth ---
bcrypt>=4.2.0,<5.0.0
# --- Packaging ---
# PyInstaller for building the .exe
pyinstaller>=6.0
# --- Optional: DirectShow filters for better video on Windows ---
# ffmpeg (install via chocolatey or manual download)
# https://ffmpeg.org/download.html
+574
View File
@@ -0,0 +1,574 @@
"""
Kiwy Signage Player - Windows Entry Point
------------------------------------------
Patches platform-specific code and environment for Windows before launching
the original Kivy-based signage player application.
Usage:
python run_win.py (for development/testing)
run_win.exe (after PyInstaller build)
"""
import ctypes
import os
import sys
import platform
import tempfile
import subprocess
import shutil
import logging
from pathlib import Path
def _show_error_box(title, message):
"""Show a Windows message box with the error (visible even without console)."""
try:
ctypes.windll.user32.MessageBoxW(0, message, title, 0x10) # MB_ICONERROR
except Exception:
pass
# --- Ensure we are on Windows; warn if not ---
if platform.system() != 'Windows':
print(f"WARNING: This entry point is designed for Windows. Detected: {platform.system()}")
# =====================================================================
# 1. Set Windows-compatible environment variables BEFORE Kivy imports
# =====================================================================
# Video driver: Use 'windib' or 'angle' (DirectX via ANGLE) for Windows
os.environ.setdefault('SDL_VIDEODRIVER', 'windows')
# Audio driver: DirectSound for Windows
os.environ.setdefault('SDL_AUDIODRIVER', 'directsound')
# Prevent screensaver
os.environ.setdefault('SDL_VIDEO_ALLOW_SCREENSAVER', '0')
# Video backend via ffpyplayer
os.environ.setdefault('KIVY_VIDEO', 'ffpyplayer')
os.environ.setdefault('KIVY_AUDIO', 'ffpyplayer')
os.environ.setdefault('FFPYPLAYER_CODECS', 'h264,h265,vp9,vp8')
# Kivy window backend: prefer SDL2 on Windows
os.environ.setdefault('KIVY_WINDOW', 'sdl2')
# OpenGL
os.environ.setdefault('KIVY_GL_BACKEND', 'angle_sdl2')
# =====================================================================
# 2. Patch the evdev import — it is Linux-only. We provide a dummy
# module so that `from evdev import ...` will not crash on Windows.
# =====================================================================
class _DummyEvdev:
"""Fake evdev module that raises ImportError for all meaningful uses."""
class InputDevice:
def __init__(self, *a, **kw):
raise ImportError("evdev is not available on Windows")
class ecodes:
EV_KEY = 1
EV_ABS = 3
def categorize(self, *a, **kw):
raise ImportError("evdev is not available on Windows")
def list_devices(self):
return []
class _FakeEvdevInputDevice:
pass
# Inject the fake evdev module into sys.modules so that main.py's
# `try: import evdev` succeeds but EVDEV_AVAILABLE stays False.
_evdev_dummy = _DummyEvdev()
sys.modules['evdev'] = _evdev_dummy
sys.modules['evdev.InputDevice'] = _FakeEvdevInputDevice
# =====================================================================
# 3. Provide a Windows implementation of screen activity signaling
# We monkey-patch the SignagePlayer.signal_screen_activity method
# after the class is defined but before it's used, by hooking into
# the import machinery.
# =====================================================================
# We'll store a reference to the original module's signal_screen_activity
# so we can replace it after import. This is done inside _patch_main().
def _windows_screen_activity(self, dt):
"""Windows alternative to Linux screen-keep-awake commands.
Uses SetThreadExecutionState via ctypes to tell Windows to keep
the display and system awake.
"""
try:
# ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED | ES_CONTINUOUS
ES_CONTINUOUS = 0x80000000
ES_SYSTEM_REQUIRED = 0x00000001
ES_DISPLAY_REQUIRED = 0x00000002
ctypes.windll.kernel32.SetThreadExecutionState(
ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
)
except Exception:
pass # non-critical
def _windows_find_browser():
"""Find Chrome or Edge executable on Windows for weblink support.
Returns the path to the browser or None.
"""
# Common install locations
candidates = [
# Chrome
os.path.expandvars(r'%PROGRAMFILES%\Google\Chrome\Application\chrome.exe'),
os.path.expandvars(r'%PROGRAMFILES(X86)%\Google\Chrome\Application\chrome.exe'),
os.path.expandvars(r'%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe'),
# Edge
os.path.expandvars(r'%PROGRAMFILES%\Microsoft\Edge\Application\msedge.exe'),
os.path.expandvars(r'%PROGRAMFILES(X86)%\Microsoft\Edge\Application\msedge.exe'),
os.path.expandvars(r'%LOCALAPPDATA%\Microsoft\Edge\Application\msedge.exe'),
]
for path in candidates:
if os.path.isfile(path):
return path
# Fallback: try PATH
which = shutil.which('chrome') or shutil.which('msedge') or shutil.which('google-chrome')
if which:
return which
return None
def _patch_main():
"""Patch the main module after import for Windows compatibility."""
# ── CRITICAL: Override Linux env vars BEFORE importing main ─────
# main.py's top-level code sets SDL_VIDEODRIVER=wayland,x11,dummy
# and other Linux values. We MUST override these before main.py
# gets imported, otherwise Kivy will initialize with the wrong
# window provider and crash with SystemExit(1).
os.environ['SDL_VIDEODRIVER'] = 'windows'
os.environ['SDL_AUDIODRIVER'] = 'directsound'
os.environ['KIVY_WINDOW'] = 'sdl2'
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'
os.environ['KIVY_INPUTPROVIDERS'] = '' # Let Kivy auto-detect
os.environ['KIVY_VIDEO'] = 'ffpyplayer'
os.environ['KIVY_AUDIO'] = 'ffpyplayer'
# Now safe to import main.py — env vars are already Windows-correct
import main as signage_main
# Replace signal_screen_activity
# IMPORTANT: Assign under BOTH the attribute name AND the function's own name.
# Kivy's WeakMethod stores self.__func__.__name__ (= '_windows_screen_activity')
# and later does getattr(instance, '_windows_screen_activity'). If we only
# assign under 'signal_screen_activity', the weakref lookup fails with
# AttributeError: 'SignagePlayer' object has no attribute '_windows_screen_activity'
signage_main.SignagePlayer.signal_screen_activity = _windows_screen_activity
signage_main.SignagePlayer._windows_screen_activity = _windows_screen_activity
# Store a reference to the original play_weblink so we can wrap it
_original_play_weblink = signage_main.SignagePlayer.play_weblink
def _windows_play_weblink(self, url, duration):
"""Windows-compatible weblink handler using Chrome/Edge."""
browser = _windows_find_browser()
if not browser:
from kivy.logger import Logger
Logger.error(
"SignagePlayer: Chrome/Edge not found. "
"Cannot display weblink on Windows."
)
self.consecutive_errors += 1
self._skip_to_next_media()
return False
from urllib.parse import urlparse
scheme = urlparse(url).scheme.lower()
if scheme not in ('http', 'https'):
from kivy.logger import Logger
Logger.warning(f"SignagePlayer: Refusing non-http(s) weblink: {url}")
self.consecutive_errors += 1
self._skip_to_next_media()
return False
import subprocess
from kivy.logger import Logger
from kivy.clock import Clock
try:
Logger.info(f"SignagePlayer: Opening weblink: {url} (browser: {browser})")
self._kill_weblink_preload()
# Launch in fullscreen / kiosk-like mode
self._weblink_proc = subprocess.Popen([
browser,
'--kiosk',
'--no-first-run',
'--noerrdialogs',
'--disable-infobars',
'--incognito',
'--no-default-browser-check',
'--disable-session-crashed-bubble',
'--disable-features=TranslateUI',
url,
], shell=False)
Clock.unschedule(self.next_media)
self._start_inactivity_watchdog(duration)
self.preload_next_media()
return True
except Exception as e:
Logger.error(f"SignagePlayer: Error opening weblink: {e}")
self.consecutive_errors += 1
self._skip_to_next_media()
return False
# Replace weblink handling
signage_main.SignagePlayer.play_weblink = _windows_play_weblink
# Patch the _get_browser_target_size to always return a reasonable size on Windows
def _windows_get_browser_target_size(self):
try:
from kivy.core.window import Window
width, height = Window.size
if width >= 1280 and height >= 720:
return int(width), int(height)
except Exception:
pass
return 1920, 1080
signage_main.SignagePlayer._get_browser_target_size = _windows_get_browser_target_size
# Patch _start_inactivity_watchdog for Windows — /dev/input does not exist
_original_watchdog = signage_main.SignagePlayer._start_inactivity_watchdog
def _windows_watchdog(self, duration):
"""Windows watchdog: uses a simple timer since /dev/input is not available.
Falls back to a fixed timer that advances after 'duration' seconds.
"""
from kivy.clock import Clock
import threading
self._stop_inactivity_watchdog()
stop_event = threading.Event()
self._watchdog_stop = stop_event
weblink_proc = self._weblink_proc
def watchdog():
import time
# Simply wait for the duration, checking if Chromium exited early
elapsed = 0.0
step = 0.5
while elapsed < duration and not stop_event.is_set():
if weblink_proc is not None and weblink_proc.poll() is not None:
Clock.schedule_once(self.next_media, 0)
return
time.sleep(step)
elapsed += step
if not stop_event.is_set():
Clock.schedule_once(self.next_media, 0)
self._weblink_watchdog_thread = threading.Thread(
target=watchdog, daemon=True, name='weblink-watchdog-win'
)
self._weblink_watchdog_thread.start()
signage_main.SignagePlayer._start_inactivity_watchdog = _windows_watchdog
# Patch the _kill_weblink_after_frame to work without Linux-specific code
def _windows_kill_weblink_after_frame(self):
from kivy.clock import Clock
self._stop_inactivity_watchdog()
self._kill_weblink_preload()
proc = self._weblink_proc
self._weblink_proc = None
if proc is not None and proc.poll() is None:
def _do_kill(dt):
if proc.poll() is None:
try:
proc.terminate()
try:
proc.wait(timeout=3)
except Exception:
proc.kill()
except Exception:
pass
Clock.schedule_once(_do_kill, 0)
signage_main.SignagePlayer._kill_weblink_after_frame = _windows_kill_weblink_after_frame
# Patch _prewarm_weblink for Windows
def _windows_prewarm_weblink(self, url):
from urllib.parse import urlparse
if not url:
return
scheme = urlparse(url).scheme.lower()
if scheme not in ('http', 'https'):
return
browser = _windows_find_browser()
if not browser:
return
import subprocess
from kivy.logger import Logger
self._kill_weblink_preload()
try:
Logger.debug(f"SignagePlayer: Pre-warming weblink off-screen: {url}")
self._weblink_preload_proc = subprocess.Popen([
browser,
'--incognito',
'--no-first-run',
'--window-position=-9999,-9999',
url,
], shell=False)
except Exception as exc:
Logger.debug(f"SignagePlayer: Pre-warm failed (non-fatal): {exc}")
self._weblink_preload_proc = None
signage_main.SignagePlayer._prewarm_weblink = _windows_prewarm_weblink
# Patch cleanup of temp auth file (was using /tmp/)
_original_connection_test = signage_main.SettingsPopup.test_connection
def _windows_test_connection(self):
"""Test connection with Windows-safe temp file path."""
import tempfile as _tf
import os as _os
from player_auth import PlayerAuth
import re
import threading
from kivy.clock import Clock
self.ids.connection_status.text = 'Testing connection...'
self.ids.connection_status.color = (1, 0.7, 0, 1)
def run_test():
try:
server_ip = self.ids.server_input.text.strip()
screen_name = self.ids.screen_input.text.strip()
quickconnect = self.ids.quickconnect_input.text.strip()
port = self.ids.port_input.text.strip() or self.player.config.get('port', '')
use_https = self.player.config.get('use_https', True)
verify_ssl = self.player.config.get('verify_ssl', True)
if not all([server_ip, screen_name, quickconnect]):
Clock.schedule_once(lambda dt: self.update_connection_status('Error: Fill all fields', False))
return
if server_ip.startswith('http://') or server_ip.startswith('https://'):
server_url = server_ip
if ':' not in server_ip.replace('https://', '').replace('http://', ''):
if port and port not in ('443', '80'):
server_url = f"{server_ip}:{port}"
else:
protocol = "https" if use_https else "http"
if ':' in server_ip:
server_url = f"{protocol}://{server_ip}"
else:
server_url = f"{protocol}://{server_ip}:{port}" if port else f"{protocol}://{server_ip}"
# Use Windows temp path
temp_file = _os.path.join(_tf.gettempdir(), 'temp_auth_test.json')
auth = PlayerAuth(temp_file, use_https=use_https, verify_ssl=verify_ssl)
success, error = auth.authenticate(server_url=server_url, hostname=screen_name, quickconnect_code=quickconnect)
try:
if _os.path.exists(temp_file):
_os.remove(temp_file)
except Exception:
pass
if success:
player_name = auth.get_player_name()
Clock.schedule_once(lambda dt: self.update_connection_status(f'✓ Connected: {player_name}', True))
else:
Clock.schedule_once(lambda dt: self.update_connection_status(f'✗ Failed: {error}', False))
except Exception as e:
Clock.schedule_once(lambda dt: self.update_connection_status(f'✗ Error: {str(e)}', False))
threading.Thread(target=run_test, daemon=True).start()
signage_main.SettingsPopup.test_connection = _windows_test_connection
return signage_main
# =====================================================================
# 4. Adjust path so we can import the src modules
# =====================================================================
# When run from PyInstaller .exe: the runtime hook inserts paths.
# When run as plain python, we add src/ relative to this file.
_script_dir = Path(__file__).resolve().parent
_project_root = _script_dir.parent
_src_dir = _project_root / 'src'
for p in [str(_src_dir), str(_project_root)]:
if p not in sys.path:
sys.path.insert(0, p)
# =====================================================================
# 5. Determine the local data directory (next to the executable)
# =====================================================================
# The pyi_runtime_hook.py (when packaged) sets KIWY_DATA_DIR.
# When running in dev mode from python, use the project root.
# The executable will create its own local folders for playlist,
# media, config, and logs where the executable is launched.
DATA_DIR = os.environ.get('KIWY_DATA_DIR', str(_project_root))
# Create local data folders NEXT TO the executable
os.makedirs(os.path.join(DATA_DIR, 'config', 'resources'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'media'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'media', 'edited_media'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'playlists'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'logs'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'config', 'certs'), exist_ok=True)
# =====================================================================
# 6. Set Kivy config BEFORE importing Kivy
# =====================================================================
os.environ['KIVY_NO_FILELOG'] = '1' # Avoid file logging issues on Windows
os.environ['KIVY_HOME'] = os.path.join(DATA_DIR, '.kivy')
from kivy.config import Config
Config.set('kivy', 'keyboard_mode', '') # Disable default virtual keyboard
Config.set('graphics', 'fullscreen', '0')
Config.set('graphics', 'window_state', 'maximized')
Config.set('graphics', 'multisampling', '0')
Config.set('graphics', 'fast_rgba', '1')
Config.set('kivy', 'log_level', 'warning')
# =====================================================================
# 7. Patch the main module, then run the app
# =====================================================================
if __name__ == '__main__':
try:
# Write a startup marker so we know the .exe at least launched
try:
os.makedirs(os.path.join(DATA_DIR, 'logs'), exist_ok=True)
marker = os.path.join(DATA_DIR, 'logs', 'startup_marker.txt')
with open(marker, 'w') as f:
f.write(f"run_win.py started at {__import__('time').time()}\n")
except Exception:
pass
# Apply all Windows patches before launching
try:
patched_main = _patch_main()
except Exception as e:
# Catch early import errors (pre-Logger) to a file
import traceback
try:
err_log = os.path.join(DATA_DIR, 'logs', 'startup_error.log')
with open(err_log, 'w') as f:
f.write(f"Error in _patch_main(): {e}\n")
traceback.print_exc(file=f)
except Exception:
pass
raise # Re-raise so console shows it too
from kivy.logger import Logger
Logger.info("=" * 80)
Logger.info("Kiwy Signage Player - Windows Edition")
Logger.info(f"Python: {sys.version}")
Logger.info(f"Platform: {platform.platform()}")
Logger.info(f"Data directory: {DATA_DIR}")
Logger.info("=" * 80)
# Patch base_dir in SignagePlayer instances to point to local data folder
_original_init = patched_main.SignagePlayer.__init__
def _patched_init(self, **kwargs):
"""Override SignagePlayer.__init__ to use local data folders.
Creates all necessary folders (config, media, playlists, logs)
in the same directory where the executable is launched.
"""
# Call parent Widget.__init__
_original_init(self, **kwargs)
# Now override ALL paths to point to local data directory
# (where the .exe is located)
self.base_dir = DATA_DIR
self.config_dir = os.path.join(DATA_DIR, 'config')
self.media_dir = os.path.join(DATA_DIR, 'media')
self.playlists_dir = os.path.join(DATA_DIR, 'playlists')
self.config_file = os.path.join(self.config_dir, 'app_config.json')
self.resources_path = os.path.join(self.config_dir, 'resources')
self.heartbeat_file = os.path.join(DATA_DIR, '.player_heartbeat')
# Ensure all required folders exist locally
for directory in [
self.config_dir,
self.resources_path,
os.path.join(self.media_dir, 'edited_media'),
self.playlists_dir,
os.path.join(DATA_DIR, 'logs'),
os.path.join(DATA_DIR, 'config', 'certs'),
]:
os.makedirs(directory, exist_ok=True)
patched_main.SignagePlayer.__init__ = _patched_init
# Patch SSLManager cert directory to use local data folder
# ssl_utils is imported by player_auth.py and get_playlists_v2.py, not main.py
import ssl_utils
ssl_utils.SSLManager.CERT_DIR = os.path.join(DATA_DIR, 'config', 'certs')
ssl_utils.SSLManager.CERT_FILE = os.path.join(
ssl_utils.SSLManager.CERT_DIR, 'server_cert.pem'
)
ssl_utils.SSLManager.CERT_INFO_FILE = os.path.join(
ssl_utils.SSLManager.CERT_DIR, 'cert_info.json'
)
# Run the app
try:
app = patched_main.SignagePlayerApp()
app.run()
except KeyboardInterrupt:
Logger.info("Application stopped by user (Ctrl+C)")
except SystemExit as _se:
Logger.critical(f"Kivy SystemExit (likely window provider missing): {_se}")
try:
crash_log = os.path.join(DATA_DIR, 'logs', 'crash.log')
with open(crash_log, 'w') as f:
f.write(f"Kivy SystemExit: {_se}\n")
f.write("This usually means Kivy could not find a window provider on this system.\n")
except Exception:
pass
_show_error_box(
"Kiwy Signage Player - Kivy Error",
f"Kivy exited: {_se}\n\n"
"This usually means Kivy could not create a window.\n"
"Check your GPU drivers and DirectX installation.\n\n"
"See logs/crash.log for details."
)
sys.exit(1)
except Exception as e:
Logger.critical(f"Fatal error: {e}")
Logger.exception("Full traceback:")
# Also write to a crash log next to the executable
try:
import traceback
crash_log = os.path.join(DATA_DIR, 'logs', 'crash.log')
with open(crash_log, 'w') as f:
f.write(f"Fatal error: {e}\n")
traceback.print_exc(file=f)
except Exception:
pass
sys.exit(1)
finally:
Logger.info("Application shutdown complete")
except BaseException as _top_e:
# Catch any error BEFORE Logger is available (including SystemExit)
import traceback as _tb
_trace = _tb.format_exc()
try:
_crash_log = os.path.join(DATA_DIR, 'logs', 'fatal_crash.log')
with open(_crash_log, 'w') as _f:
_f.write(f"FATAL (pre-Logger): {_top_e}\n")
_f.write(_trace)
except Exception:
pass
_show_error_box(
"Kiwy Signage Player - Startup Error",
f"{_top_e}\n\nSee logs/fatal_crash.log for details."
)
raise # Re-raise so .exe still shows the error
+39
View File
@@ -0,0 +1,39 @@
"""Test that setting env vars before importing main.py fixes the crash."""
import os
import sys
# This is the KEY fix: set Windows env vars BEFORE main.py is imported
os.environ['SDL_VIDEODRIVER'] = 'windows'
os.environ['SDL_AUDIODRIVER'] = 'directsound'
os.environ['KIVY_WINDOW'] = 'sdl2'
# Use 'angle_sdl2' on Windows for better DirectX compatibility
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'
# Let Kivy auto-detect input providers on Windows
os.environ['KIVY_INPUTPROVIDERS'] = ''
os.environ['KIVY_VIDEO'] = 'ffpyplayer'
os.environ['KIVY_AUDIO'] = 'ffpyplayer'
os.environ['FFPYPLAYER_CODECS'] = 'h264,h265,vp9,vp8'
os.environ['SDL_VIDEO_ALLOW_SCREENSAVER'] = '0'
# Add src to path
sys.path.insert(0, r'C:\Users\Dell-PC\Desktop\Kiwy-Signage\src')
print("=" * 60)
print("Testing main.py import with Windows env vars...")
print("=" * 60)
try:
import main
print("SUCCESS: main.py imported without crashing!")
print(f" SDL_VIDEODRIVER = {os.environ.get('SDL_VIDEODRIVER')}")
print(f" KIVY_WINDOW = {os.environ.get('KIVY_WINDOW')}")
print(f" KIVY_GL_BACKEND = {os.environ.get('KIVY_GL_BACKEND')}")
print(f" KIVY_INPUTPROVIDERS = {os.environ.get('KIVY_INPUTPROVIDERS')}")
except SystemExit as e:
print(f"FAILED: SystemExit({e}) - Kivy window provider still not loading")
sys.exit(1)
except Exception as e:
print(f"FAILED with exception: {e}")
import traceback
traceback.print_exc()
sys.exit(1)