summaryrefslogtreecommitdiffstats
path: root/gui
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 /gui
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.
Diffstat (limited to '')
-rw-r--r--gui/Cargo.toml17
-rw-r--r--gui/src/main.rs125
2 files changed, 120 insertions, 22 deletions
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}