gui: Settings toggle to hide the host QR-code panel

Adds a `show_qr` preference (default on) to GuiSettings, with a
checkbox in Settings and a corresponding gate on the host-screen render.
Persists to config.toml alongside the existing close-to-tray setting.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 05:38:58 -04:00
parent a7ea1fd9df
commit e1ed89026d
2 changed files with 57 additions and 8 deletions
+19 -1
View File
@@ -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.
+38 -7
View File
@@ -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)));
}