diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-01 22:02:52 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-01 22:02:52 +0100 |
| commit | eca39344cd9d2a37fb52ef1c5f4fe3c11f6525cb (patch) | |
| tree | 3dcee368dce18a468ac0d629015612cc720eaac3 | |
| parent | docs: record the v4.3.0 packaging verification (diff) | |
| download | zagan-bet-eca39344cd9d2a37fb52ef1c5f4fe3c11f6525cb.tar.gz zagan-bet-eca39344cd9d2a37fb52ef1c5f4fe3c11f6525cb.zip | |
v4.3.1: render Windows through wgpu and keep the installer window open
Windows 10 1809 sessions with only the Microsoft display driver expose OpenGL 1.1, which egui_glow refuses. The Windows build now uses wgpu on Direct3D 12 and its adapter selector accepts WARP, the software rasterizer, after every hardware class, so a VM or Remote Desktop session still starts. Linux keeps the smaller glow renderer.
The GUI is a windows-subsystem binary now, so launching it from the Start menu does not flash a console; startup failures and --help go to a native message box. The PowerShell installer pauses before closing unless -NoPause is passed.
| -rw-r--r-- | AGENTS.md | 6 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | docs/GUI-PLAN.md | 7 | ||||
| -rw-r--r-- | gui/Cargo.toml | 17 | ||||
| -rw-r--r-- | gui/src/main.rs | 125 | ||||
| -rw-r--r-- | scripts/installers/windows.ps1.in | 14 | ||||
| -rw-r--r-- | web/README.md | 5 |
7 files changed, 147 insertions, 29 deletions
| @@ -87,7 +87,11 @@ | |||
| 87 | renders as tofu. | 87 | renders as tofu. |
| 88 | - `scripts/publish.sh` builds the GUI with `gui-release` and renders the | 88 | - `scripts/publish.sh` builds the GUI with `gui-release` and renders the |
| 89 | installers with the published SHA-256. Installers verify the hash and install | 89 | installers with the published SHA-256. Installers verify the hash and install |
| 90 | per-user; never install system-wide or skip the check. | 90 | per-user; never install system-wide or skip the check. The Windows installer |
| 91 | waits for Enter before closing; `-NoPause` is only for automation. | ||
| 92 | - Windows renders through wgpu on Direct3D 12, including the WARP software | ||
| 93 | adapter, so a machine with only OpenGL 1.1 (VM, Remote Desktop) still runs. | ||
| 94 | Linux keeps the smaller glow renderer. Do not assume OpenGL 2.0 on Windows. | ||
| 91 | - The download page links the public git repository | 95 | - The download page links the public git repository |
| 92 | (https://git.2137697.xyz/zagan-bet.git) for the GPL corresponding source. | 96 | (https://git.2137697.xyz/zagan-bet.git) for the GPL corresponding source. |
| 93 | Run publish from a clean tree so the linked history matches the binaries. | 97 | Run publish from a clean tree so the linked history matches the binaries. |
| @@ -3,7 +3,7 @@ members = ["gui"] | |||
| 3 | 3 | ||
| 4 | [package] | 4 | [package] |
| 5 | name = "zagan-bet" | 5 | name = "zagan-bet" |
| 6 | version = "4.3.0" | 6 | version = "4.3.1" |
| 7 | edition = "2024" | 7 | edition = "2024" |
| 8 | license = "GPL-3.0-or-later" | 8 | license = "GPL-3.0-or-later" |
| 9 | 9 | ||
diff --git a/docs/GUI-PLAN.md b/docs/GUI-PLAN.md index 4ddf98f..a322057 100644 --- a/docs/GUI-PLAN.md +++ b/docs/GUI-PLAN.md | |||
| @@ -121,6 +121,9 @@ they show the same layout with the real session and the pinned action bars. | |||
| 121 | "remember this login" choice, lobby with the update notice, history table, | 121 | "remember this login" choice, lobby with the update notice, history table, |
| 122 | and the four games. "Log out" and "forget saved login" are separate. Leaving | 122 | and the four games. "Log out" and "forget saved login" are separate. Leaving |
| 123 | a live hand or round asks first and forfeits the reservation. | 123 | a live hand or round asks first and forfeits the reservation. |
| 124 | - Linux draws with glow (OpenGL); Windows draws with wgpu on Direct3D 12 and | ||
| 125 | accepts the WARP software adapter, so a Windows 10 machine with only OpenGL | ||
| 126 | 1.1 (Remote Desktop, a VM) still starts. | ||
| 124 | - Games are painted from `View`: slots with a 0.9 s reel animation and payline | 127 | - Games are painted from `View`: slots with a 0.9 s reel animation and payline |
| 125 | highlights, roulette with the wheel spin, the hover lattice, chip placement | 128 | highlights, roulette with the wheel spin, the hover lattice, chip placement |
| 126 | and payouts, blackjack with vector-drawn suits and legal actions, crash with | 129 | and payouts, blackjack with vector-drawn suits and legal actions, crash with |
| @@ -153,8 +156,8 @@ Measured on Linux at 1080p with llvmpipe software GL: | |||
| 153 | 156 | ||
| 154 | | Check | Result | | 157 | | Check | Result | |
| 155 | | --- | --- | | 158 | | --- | --- | |
| 156 | | Linux binary | 8.3 MB | | 159 | | Linux binary (glow) | 8.3 MB | |
| 157 | | Windows x86_64 binary | 6.8 MB | | 160 | | Windows x86_64 binary (wgpu/DX12) | 9.8 MB | |
| 158 | | Cold start to window | 72 ms | | 161 | | Cold start to window | 72 ms | |
| 159 | | Idle RSS | 130 MB | | 162 | | Idle RSS | 130 MB | |
| 160 | | Idle CPU | 0% | | 163 | | Idle CPU | 0% | |
diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 8bcbda0..24bf560 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "zagan-bet-gui" | 2 | name = "zagan-bet-gui" |
| 3 | version = "4.3.0" | 3 | version = "4.3.1" |
| 4 | edition = "2024" | 4 | edition = "2024" |
| 5 | license = "GPL-3.0-or-later" | 5 | license = "GPL-3.0-or-later" |
| 6 | description = "Desktop frontend for zagan-bet" | 6 | description = "Desktop frontend for zagan-bet" |
| @@ -12,12 +12,19 @@ path = "src/main.rs" | |||
| 12 | [dependencies] | 12 | [dependencies] |
| 13 | # No default features: the GUI never links the server's native-tls. | 13 | # No default features: the GUI never links the server's native-tls. |
| 14 | zagan-bet = { path = "..", default-features = false } | 14 | zagan-bet = { path = "..", default-features = false } |
| 15 | # glow (OpenGL) rather than wgpu: no Vulkan runtime, and it cross-compiles to | 15 | rusqlite = { version = "0.29", features = ["bundled"] } |
| 16 | # Windows with the mingw toolchain. Fonts are embedded, so no default_fonts. | 16 | sha2 = "0.10" |
| 17 | |||
| 18 | # Linux uses glow (OpenGL): small, and every desktop has a usable driver. | ||
| 19 | # Windows uses wgpu on Direct3D 12, which also has the WARP software adapter on | ||
| 20 | # machines with no OpenGL driver (Windows 10 1809 over Remote Desktop, VMs). | ||
| 21 | [target.'cfg(windows)'.dependencies] | ||
| 22 | eframe = { version = "0.37", default-features = false, features = ["wgpu"] } | ||
| 23 | windows-sys = { version = "0.59", features = ["Win32_UI_WindowsAndMessaging"] } | ||
| 24 | |||
| 25 | [target.'cfg(not(windows))'.dependencies] | ||
| 17 | eframe = { version = "0.37", default-features = false, features = [ | 26 | eframe = { version = "0.37", default-features = false, features = [ |
| 18 | "glow", | 27 | "glow", |
| 19 | "wayland", | 28 | "wayland", |
| 20 | "x11", | 29 | "x11", |
| 21 | ] } | 30 | ] } |
| 22 | rusqlite = { version = "0.29", features = ["bundled"] } | ||
| 23 | sha2 = "0.10" | ||
diff --git a/gui/src/main.rs b/gui/src/main.rs index 82babb6..e0338fa 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs | |||
| @@ -1,3 +1,5 @@ | |||
| 1 | #![cfg_attr(windows, windows_subsystem = "windows")] | ||
| 2 | |||
| 1 | //! Desktop frontend for zagan-bet. | 3 | //! Desktop frontend for zagan-bet. |
| 2 | //! | 4 | //! |
| 3 | //! Phase 4: the egui shell over the shared `ClientCore`, with the transport on | 5 | //! Phase 4: the egui shell over the shared `ClientCore`, with the transport on |
| @@ -15,13 +17,15 @@ fn main() -> eframe::Result { | |||
| 15 | } | 17 | } |
| 16 | }; | 18 | }; |
| 17 | if args.help { | 19 | if args.help { |
| 18 | println!( | 20 | let usage = "usage: zagan-bet-gui [-d <dir>] [--unsafe] |
| 19 | "usage: zagan-bet-gui [-d <dir>] [--unsafe] | ||
| 20 | 21 | ||
| 21 | -d, --data-dir <dir> where to keep client.db (default: XDG data dir) | 22 | -d, --data-dir <dir> where to keep client.db (default: XDG data dir) |
| 22 | --unsafe skip certificate verification | 23 | --unsafe skip certificate verification |
| 23 | -h, --help show this message" | 24 | -h, --help show this message"; |
| 24 | ); | 25 | #[cfg(windows)] |
| 26 | show_dialog(usage, false); | ||
| 27 | #[cfg(not(windows))] | ||
| 28 | println!("{usage}"); | ||
| 25 | return Ok(()); | 29 | return Ok(()); |
| 26 | } | 30 | } |
| 27 | 31 | ||
| @@ -31,23 +35,110 @@ fn main() -> eframe::Result { | |||
| 31 | std::process::exit(1); | 35 | std::process::exit(1); |
| 32 | } | 36 | } |
| 33 | 37 | ||
| 34 | let options = eframe::NativeOptions { | ||
| 35 | viewport: eframe::egui::ViewportBuilder::default() | ||
| 36 | .with_title("Zagan-Bet") | ||
| 37 | .with_app_id("zagan-bet") | ||
| 38 | .with_inner_size([1440.0, 900.0]) | ||
| 39 | .with_min_inner_size([1100.0, 720.0]), | ||
| 40 | renderer: eframe::Renderer::Glow, | ||
| 41 | ..Default::default() | ||
| 42 | }; | ||
| 43 | |||
| 44 | let startup = Startup { | 38 | let startup = Startup { |
| 45 | data_dir, | 39 | data_dir, |
| 46 | unsafe_mode: args.unsafe_mode, | 40 | unsafe_mode: args.unsafe_mode, |
| 47 | }; | 41 | }; |
| 48 | eframe::run_native( | 42 | let result = eframe::run_native( |
| 49 | "Zagan-Bet", | 43 | "Zagan-Bet", |
| 50 | options, | 44 | native_options(), |
| 51 | Box::new(move |cc| Ok(Box::new(ZaganApp::new(cc, startup)))), | 45 | Box::new(move |cc| Ok(Box::new(ZaganApp::new(cc, startup)))), |
| 52 | ) | 46 | ); |
| 47 | #[cfg(windows)] | ||
| 48 | if let Err(error) = &result { | ||
| 49 | // The binary is a GUI-subsystem app, so this is the only way a startup | ||
| 50 | // failure reaches the player. | ||
| 51 | show_dialog(&format!("Zagan-Bet could not start.\n\n{error}"), true); | ||
| 52 | } | ||
| 53 | result | ||
| 54 | } | ||
| 55 | |||
| 56 | /// A native Windows message box, for help and for startup failures (there is | ||
| 57 | /// no console attached). | ||
| 58 | #[cfg(windows)] | ||
| 59 | fn show_dialog(message: &str, error: bool) { | ||
| 60 | use std::ffi::OsStr; | ||
| 61 | use std::os::windows::ffi::OsStrExt; | ||
| 62 | |||
| 63 | use windows_sys::Win32::UI::WindowsAndMessaging::{ | ||
| 64 | MB_ICONERROR, MB_ICONINFORMATION, MessageBoxW, | ||
| 65 | }; | ||
| 66 | |||
| 67 | let wide = |text: &str| -> Vec<u16> { | ||
| 68 | OsStr::new(text) | ||
| 69 | .encode_wide() | ||
| 70 | .chain(std::iter::once(0)) | ||
| 71 | .collect() | ||
| 72 | }; | ||
| 73 | let text = wide(message); | ||
| 74 | let title = wide("Zagan-Bet"); | ||
| 75 | let icon = if error { | ||
| 76 | MB_ICONERROR | ||
| 77 | } else { | ||
| 78 | MB_ICONINFORMATION | ||
| 79 | }; | ||
| 80 | // SAFETY: both strings are NUL-terminated and outlive the call. | ||
| 81 | unsafe { | ||
| 82 | MessageBoxW(std::ptr::null_mut(), text.as_ptr(), title.as_ptr(), icon); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | fn viewport() -> eframe::egui::ViewportBuilder { | ||
| 87 | eframe::egui::ViewportBuilder::default() | ||
| 88 | .with_title("Zagan-Bet") | ||
| 89 | .with_app_id("zagan-bet") | ||
| 90 | .with_inner_size([1440.0, 900.0]) | ||
| 91 | .with_min_inner_size([1100.0, 720.0]) | ||
| 92 | } | ||
| 93 | |||
| 94 | /// Windows renders through Direct3D 12. egui's default adapter choice prefers | ||
| 95 | /// hardware; this selector also accepts WARP, the software rasterizer, so a | ||
| 96 | /// Remote Desktop or VM session with no OpenGL driver still runs the app. | ||
| 97 | #[cfg(windows)] | ||
| 98 | fn native_options() -> eframe::NativeOptions { | ||
| 99 | use std::sync::Arc; | ||
| 100 | |||
| 101 | use eframe::egui_wgpu::{WgpuConfiguration, WgpuSetup}; | ||
| 102 | use eframe::wgpu; | ||
| 103 | |||
| 104 | let mut wgpu_options = WgpuConfiguration::default(); | ||
| 105 | if let WgpuSetup::CreateNew(setup) = &mut wgpu_options.wgpu_setup { | ||
| 106 | setup.instance_descriptor.backends = wgpu::Backends::DX12; | ||
| 107 | setup.native_adapter_selector = Some(Arc::new(|adapters, surface| { | ||
| 108 | let usable = |adapter: &wgpu::Adapter| { | ||
| 109 | surface.is_none_or(|surface| adapter.is_surface_supported(surface)) | ||
| 110 | }; | ||
| 111 | for kind in [ | ||
| 112 | wgpu::DeviceType::DiscreteGpu, | ||
| 113 | wgpu::DeviceType::IntegratedGpu, | ||
| 114 | wgpu::DeviceType::VirtualGpu, | ||
| 115 | wgpu::DeviceType::Cpu, | ||
| 116 | wgpu::DeviceType::Other, | ||
| 117 | ] { | ||
| 118 | if let Some(adapter) = adapters | ||
| 119 | .iter() | ||
| 120 | .find(|adapter| adapter.get_info().device_type == kind && usable(adapter)) | ||
| 121 | { | ||
| 122 | return Ok(adapter.clone()); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | Err("no Direct3D 12 adapter (hardware or WARP) is available".to_string()) | ||
| 126 | })); | ||
| 127 | } | ||
| 128 | |||
| 129 | eframe::NativeOptions { | ||
| 130 | viewport: viewport(), | ||
| 131 | renderer: eframe::Renderer::Wgpu, | ||
| 132 | wgpu_options, | ||
| 133 | ..Default::default() | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | #[cfg(not(windows))] | ||
| 138 | fn native_options() -> eframe::NativeOptions { | ||
| 139 | eframe::NativeOptions { | ||
| 140 | viewport: viewport(), | ||
| 141 | renderer: eframe::Renderer::Glow, | ||
| 142 | ..Default::default() | ||
| 143 | } | ||
| 53 | } | 144 | } |
diff --git a/scripts/installers/windows.ps1.in b/scripts/installers/windows.ps1.in index fd2b8b3..4b8e61d 100644 --- a/scripts/installers/windows.ps1.in +++ b/scripts/installers/windows.ps1.in | |||
| @@ -6,6 +6,9 @@ | |||
| 6 | # | 6 | # |
| 7 | # Run it with: | 7 | # Run it with: |
| 8 | # powershell -ExecutionPolicy Bypass -File .\zagan-bet-gui-*-install.ps1 | 8 | # powershell -ExecutionPolicy Bypass -File .\zagan-bet-gui-*-install.ps1 |
| 9 | # Add -NoPause to run it unattended. | ||
| 10 | param([switch]$NoPause) | ||
| 11 | |||
| 9 | $ErrorActionPreference = "Stop" | 12 | $ErrorActionPreference = "Stop" |
| 10 | 13 | ||
| 11 | $Version = "__VERSION__" | 14 | $Version = "__VERSION__" |
| @@ -13,9 +16,16 @@ $Binary = "__BINARY__" | |||
| 13 | $Sha256 = "__SHA256__" | 16 | $Sha256 = "__SHA256__" |
| 14 | $Url = "__URL__" | 17 | $Url = "__URL__" |
| 15 | 18 | ||
| 19 | function Finish([int]$Code) { | ||
| 20 | if (-not $NoPause) { | ||
| 21 | Read-Host "Press Enter to close" | Out-Null | ||
| 22 | } | ||
| 23 | exit $Code | ||
| 24 | } | ||
| 25 | |||
| 16 | function Stop-Install([string]$Message) { | 26 | function Stop-Install([string]$Message) { |
| 17 | Write-Host "install: $Message" -ForegroundColor Red | 27 | Write-Host "install: $Message" -ForegroundColor Red |
| 18 | exit 1 | 28 | Finish 1 |
| 19 | } | 29 | } |
| 20 | 30 | ||
| 21 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path | 31 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path |
| @@ -73,3 +83,5 @@ try { | |||
| 73 | Remove-Item -LiteralPath $temporary -Force | 83 | Remove-Item -LiteralPath $temporary -Force |
| 74 | } | 84 | } |
| 75 | } | 85 | } |
| 86 | |||
| 87 | Finish 0 | ||
diff --git a/web/README.md b/web/README.md index bd87e11..1ee794f 100644 --- a/web/README.md +++ b/web/README.md | |||
| @@ -35,8 +35,9 @@ bin/server/zagan-bet-server-<version>-<platform>-<arch>[.exe] | |||
| 35 | The installers are generated next to their app. They embed the app's SHA-256, | 35 | The installers are generated next to their app. They embed the app's SHA-256, |
| 36 | check the file (or download it) and install it for the current user: Linux | 36 | check the file (or download it) and install it for the current user: Linux |
| 37 | writes `~/.local/bin/zagan-bet-gui` plus a `.desktop` launcher, Windows writes | 37 | writes `~/.local/bin/zagan-bet-gui` plus a `.desktop` launcher, Windows writes |
| 38 | `%LOCALAPPDATA%\Programs\Zagan-Bet` plus a Start-menu shortcut. The source | 38 | `%LOCALAPPDATA%\Programs\Zagan-Bet` plus a Start-menu shortcut. The Windows |
| 39 | archives satisfy the GPL's corresponding-source requirement. | 39 | installer waits for Enter before closing (`-NoPause` for automation). The page |
| 40 | links the public git repository for the GPL corresponding source. | ||
| 40 | 41 | ||
| 41 | ## Publishing a build | 42 | ## Publishing a build |
| 42 | 43 | ||
