1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
#![cfg_attr(windows, windows_subsystem = "windows")]
//! Desktop frontend for zagan-bet.
//!
//! Phase 4: the egui shell over the shared `ClientCore`, with the transport on
//! a worker thread. The same data directory and flags as the terminal client.
use zagan_bet::client::paths::{default_data_dir, parse_args};
use zagan_bet_gui::app::{Startup, ZaganApp};
fn main() -> eframe::Result {
let args = match parse_args(std::env::args().skip(1)) {
Ok(args) => args,
Err(message) => {
eprintln!("{message}");
std::process::exit(2);
}
};
if args.help {
let usage = "usage: zagan-bet-gui [-d <dir>] [--unsafe]
-d, --data-dir <dir> where to keep client.db (default: XDG data dir)
--unsafe skip certificate verification
-h, --help show this message";
#[cfg(windows)]
show_dialog(usage, false);
#[cfg(not(windows))]
println!("{usage}");
return Ok(());
}
let data_dir = args.data_dir.unwrap_or_else(default_data_dir);
if let Err(error) = std::fs::create_dir_all(&data_dir) {
eprintln!("Cannot create {}: {error}", data_dir.display());
std::process::exit(1);
}
let startup = Startup {
data_dir,
unsafe_mode: args.unsafe_mode,
};
let result = eframe::run_native(
"Zagan-Bet",
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<u16> {
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()
}
}
|