summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 22:02:52 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 22:02:52 +0100
commiteca39344cd9d2a37fb52ef1c5f4fe3c11f6525cb (patch)
tree3dcee368dce18a468ac0d629015612cc720eaac3
parentdocs: record the v4.3.0 packaging verification (diff)
downloadzagan-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.md6
-rw-r--r--Cargo.toml2
-rw-r--r--docs/GUI-PLAN.md7
-rw-r--r--gui/Cargo.toml17
-rw-r--r--gui/src/main.rs125
-rw-r--r--scripts/installers/windows.ps1.in14
-rw-r--r--web/README.md5
7 files changed, 147 insertions, 29 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 7258710..cd5c71b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -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.
diff --git a/Cargo.toml b/Cargo.toml
index 22a6ec7..8897f9d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ members = ["gui"]
3 3
4[package] 4[package]
5name = "zagan-bet" 5name = "zagan-bet"
6version = "4.3.0" 6version = "4.3.1"
7edition = "2024" 7edition = "2024"
8license = "GPL-3.0-or-later" 8license = "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]
2name = "zagan-bet-gui" 2name = "zagan-bet-gui"
3version = "4.3.0" 3version = "4.3.1"
4edition = "2024" 4edition = "2024"
5license = "GPL-3.0-or-later" 5license = "GPL-3.0-or-later"
6description = "Desktop frontend for zagan-bet" 6description = "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.
14zagan-bet = { path = "..", default-features = false } 14zagan-bet = { path = "..", default-features = false }
15# glow (OpenGL) rather than wgpu: no Vulkan runtime, and it cross-compiles to 15rusqlite = { version = "0.29", features = ["bundled"] }
16# Windows with the mingw toolchain. Fonts are embedded, so no default_fonts. 16sha2 = "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]
22eframe = { version = "0.37", default-features = false, features = ["wgpu"] }
23windows-sys = { version = "0.59", features = ["Win32_UI_WindowsAndMessaging"] }
24
25[target.'cfg(not(windows))'.dependencies]
17eframe = { version = "0.37", default-features = false, features = [ 26eframe = { version = "0.37", default-features = false, features = [
18 "glow", 27 "glow",
19 "wayland", 28 "wayland",
20 "x11", 29 "x11",
21] } 30] }
22rusqlite = { version = "0.29", features = ["bundled"] }
23sha2 = "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)]
59fn 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
86fn 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)]
98fn 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))]
138fn 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.
10param([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
19function Finish([int]$Code) {
20 if (-not $NoPause) {
21 Read-Host "Press Enter to close" | Out-Null
22 }
23 exit $Code
24}
25
16function Stop-Install([string]$Message) { 26function 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
87Finish 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]
35The installers are generated next to their app. They embed the app's SHA-256, 35The installers are generated next to their app. They embed the app's SHA-256,
36check the file (or download it) and install it for the current user: Linux 36check the file (or download it) and install it for the current user: Linux
37writes `~/.local/bin/zagan-bet-gui` plus a `.desktop` launcher, Windows writes 37writes `~/.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
39archives satisfy the GPL's corresponding-source requirement. 39installer waits for Enter before closing (`-NoPause` for automation). The page
40links the public git repository for the GPL corresponding source.
40 41
41## Publishing a build 42## Publishing a build
42 43