diff --git a/src/common/config.rs b/src/common/config.rs index d4df681..9c0f978 100644 --- a/src/common/config.rs +++ b/src/common/config.rs @@ -20,13 +20,31 @@ pub struct Config { } /// Preferences for the `pixelpass --gui` front-end. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuiSettings { /// When true, the window's close button hides the app to the system tray /// (keeping any live stream running) instead of quitting. Defaults to /// false — closing quits, which is what people expect. #[serde(default)] pub close_to_tray: bool, + /// When true, the host screen renders a QR-code panel for the ticket. + /// Defaults to true; the toggle exists for users who prefer the plain + /// text-only host screen. + #[serde(default = "default_true")] + pub show_qr: bool, +} + +impl Default for GuiSettings { + fn default() -> Self { + Self { + close_to_tray: false, + show_qr: true, + } + } +} + +fn default_true() -> bool { + true } /// Result of the first-run upstream measurement. diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 248bc52..4d7718e 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -596,16 +596,17 @@ pub fn run() -> anyhow::Result<()> { }; // The tray runs on its own thread and wakes us via the proxy. let tray = tray::start(proxy.clone()); - let close_to_tray = crate::common::config::load() - .map(|c| c.gui.close_to_tray) - .unwrap_or(false); + let gui_settings = crate::common::config::load() + .map(|c| c.gui) + .unwrap_or_default(); let state = PixelPassApp { screen: Screen::default(), host: HostState::default(), viewer: ViewerState::default(), tray, - close_to_tray, + close_to_tray: gui_settings.close_to_tray, + show_qr: gui_settings.show_qr, waker, }; let mut app = App { @@ -691,6 +692,14 @@ fn persist_close_to_tray(value: bool) { } } +fn persist_show_qr(value: bool) { + let mut cfg = crate::common::config::load().unwrap_or_default(); + cfg.gui.show_qr = value; + if let Err(e) = crate::common::config::save(&cfg) { + tracing::warn!("failed to save settings: {e}"); + } +} + /// Which screen the single window is currently showing. #[derive(Default, PartialEq)] enum Screen { @@ -833,6 +842,9 @@ struct PixelPassApp { /// hides to the tray instead of quitting. Loaded at startup, written on /// toggle in Settings. close_to_tray: bool, + /// Persisted preference: render the QR-code panel on the host screen. + /// Defaults to on; toggled in Settings. + show_qr: bool, /// Wakes the winit loop when a spawned child emits/exits. waker: Waker, } @@ -938,6 +950,21 @@ impl PixelPassApp { .weak(), ); + ui.add_space(12.0); + let qr_resp = ui.checkbox(&mut self.show_qr, "Show QR-code panel on the host screen"); + if qr_resp.changed() { + persist_show_qr(self.show_qr); + } + ui.add_space(4.0); + ui.label( + egui::RichText::new( + "Off: the host screen shows only the ticket text. \ + Useful when both ends are computers and the QR is just extra clutter.", + ) + .small() + .weak(), + ); + // The option does nothing without a tray to hide into; say so plainly. if !self.tray.as_ref().is_some_and(TrayHandle::registered) { ui.add_space(8.0); @@ -1127,8 +1154,10 @@ impl PixelPassApp { // Lazy QR build: first draw after a Ticket event has `qr_texture = // None`, so we encode the ticket and load the texture once. The // 4-module quiet zone (white border) matters — phone scanners reject - // QR codes flush against a non-white edge. - if self.host.qr_texture.is_none() + // QR codes flush against a non-white edge. Skipped when the user + // disabled the QR panel in Settings. + if self.show_qr + && self.host.qr_texture.is_none() && let Ok(code) = qrcode::QrCode::new(ticket.as_bytes()) { let w = code.width(); @@ -1150,7 +1179,9 @@ impl PixelPassApp { egui::TextureOptions::NEAREST, )); } - if let Some(tex) = &self.host.qr_texture { + if self.show_qr + && let Some(tex) = &self.host.qr_texture + { ui.add_space(8.0); ui.add(egui::Image::new(tex).fit_to_exact_size(egui::vec2(200.0, 200.0))); }