From eca39344cd9d2a37fb52ef1c5f4fe3c11f6525cb Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 22:02:52 +0100 Subject: 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. --- gui/src/main.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 108 insertions(+), 17 deletions(-) (limited to 'gui/src/main.rs') 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 @@ +#![cfg_attr(windows, windows_subsystem = "windows")] + //! Desktop frontend for zagan-bet. //! //! Phase 4: the egui shell over the shared `ClientCore`, with the transport on @@ -15,13 +17,15 @@ fn main() -> eframe::Result { } }; if args.help { - println!( - "usage: zagan-bet-gui [-d ] [--unsafe] + let usage = "usage: zagan-bet-gui [-d ] [--unsafe] -d, --data-dir where to keep client.db (default: XDG data dir) --unsafe skip certificate verification - -h, --help show this message" - ); + -h, --help show this message"; + #[cfg(windows)] + show_dialog(usage, false); + #[cfg(not(windows))] + println!("{usage}"); return Ok(()); } @@ -31,23 +35,110 @@ fn main() -> eframe::Result { std::process::exit(1); } - let options = eframe::NativeOptions { - viewport: eframe::egui::ViewportBuilder::default() - .with_title("Zagan-Bet") - .with_app_id("zagan-bet") - .with_inner_size([1440.0, 900.0]) - .with_min_inner_size([1100.0, 720.0]), - renderer: eframe::Renderer::Glow, - ..Default::default() - }; - let startup = Startup { data_dir, unsafe_mode: args.unsafe_mode, }; - eframe::run_native( + let result = eframe::run_native( "Zagan-Bet", - options, + native_options(), Box::new(move |cc| Ok(Box::new(ZaganApp::new(cc, startup)))), - ) + ); + #[cfg(windows)] + if let Err(error) = &result { + // The binary is a GUI-subsystem app, so this is the only way a startup + // failure reaches the player. + show_dialog(&format!("Zagan-Bet could not start.\n\n{error}"), true); + } + result +} + +/// A native Windows message box, for help and for startup failures (there is +/// no console attached). +#[cfg(windows)] +fn show_dialog(message: &str, error: bool) { + use std::ffi::OsStr; + use std::os::windows::ffi::OsStrExt; + + use windows_sys::Win32::UI::WindowsAndMessaging::{ + MB_ICONERROR, MB_ICONINFORMATION, MessageBoxW, + }; + + let wide = |text: &str| -> Vec { + OsStr::new(text) + .encode_wide() + .chain(std::iter::once(0)) + .collect() + }; + let text = wide(message); + let title = wide("Zagan-Bet"); + let icon = if error { + MB_ICONERROR + } else { + MB_ICONINFORMATION + }; + // SAFETY: both strings are NUL-terminated and outlive the call. + unsafe { + MessageBoxW(std::ptr::null_mut(), text.as_ptr(), title.as_ptr(), icon); + } +} + +fn viewport() -> eframe::egui::ViewportBuilder { + eframe::egui::ViewportBuilder::default() + .with_title("Zagan-Bet") + .with_app_id("zagan-bet") + .with_inner_size([1440.0, 900.0]) + .with_min_inner_size([1100.0, 720.0]) +} + +/// Windows renders through Direct3D 12. egui's default adapter choice prefers +/// hardware; this selector also accepts WARP, the software rasterizer, so a +/// Remote Desktop or VM session with no OpenGL driver still runs the app. +#[cfg(windows)] +fn native_options() -> eframe::NativeOptions { + use std::sync::Arc; + + use eframe::egui_wgpu::{WgpuConfiguration, WgpuSetup}; + use eframe::wgpu; + + let mut wgpu_options = WgpuConfiguration::default(); + if let WgpuSetup::CreateNew(setup) = &mut wgpu_options.wgpu_setup { + setup.instance_descriptor.backends = wgpu::Backends::DX12; + setup.native_adapter_selector = Some(Arc::new(|adapters, surface| { + let usable = |adapter: &wgpu::Adapter| { + surface.is_none_or(|surface| adapter.is_surface_supported(surface)) + }; + for kind in [ + wgpu::DeviceType::DiscreteGpu, + wgpu::DeviceType::IntegratedGpu, + wgpu::DeviceType::VirtualGpu, + wgpu::DeviceType::Cpu, + wgpu::DeviceType::Other, + ] { + if let Some(adapter) = adapters + .iter() + .find(|adapter| adapter.get_info().device_type == kind && usable(adapter)) + { + return Ok(adapter.clone()); + } + } + Err("no Direct3D 12 adapter (hardware or WARP) is available".to_string()) + })); + } + + eframe::NativeOptions { + viewport: viewport(), + renderer: eframe::Renderer::Wgpu, + wgpu_options, + ..Default::default() + } +} + +#[cfg(not(windows))] +fn native_options() -> eframe::NativeOptions { + eframe::NativeOptions { + viewport: viewport(), + renderer: eframe::Renderer::Glow, + ..Default::default() + } } -- cgit v1.2.3