From 82b5f98c470faa8964466db02b4c36ddc10f2dd2 Mon Sep 17 00:00:00 2001 From: Anton Karpov Date: Mon, 3 Aug 2026 22:40:50 +0300 Subject: [PATCH] Accept the zoom in hotkey when shift is held On most keyboard layouts '+' is typed as shift + '=', so the key press reaches the app with shift among the modifiers. The subscription matched the modifier set by equality, so ctrl + '+' fell through to the catch-all and no zoom happened. ctrl + '-' was unaffected since '-' needs no shift. Accept '=' as a zoom in key too, the way browsers do, and handle the command + shift combination. The hotkey mapping moves into its own function so it can be covered by tests. --- src/gui/sniffer.rs | 146 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 110 insertions(+), 36 deletions(-) diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 6a3d78100..a767866ac 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -203,46 +203,16 @@ impl Sniffer { if self.thumbnail { iced::event::listen_with(|event, _, _| match event { - Keyboard(Event::KeyPressed { - key, - modifiers: Modifiers::COMMAND, - .. - }) => match key.as_ref() { - Key::Character("q") => Some(Message::QuitWrapper), - Key::Character("t") => Some(Message::CtrlTPressed), - Key::Named(Named::Space) => Some(Message::CtrlSpacePressed), - _ => None, - }, + Keyboard(Event::KeyPressed { key, modifiers, .. }) => { + hotkey_message(&key, modifiers, true) + } _ => None, }) } else { iced::event::listen_with(|event, _, _| match event { - Keyboard(Event::KeyPressed { key, modifiers, .. }) => match modifiers { - Modifiers::COMMAND => match key.as_ref() { - Key::Character("q") => Some(Message::QuitWrapper), - Key::Character("t") => Some(Message::CtrlTPressed), - Key::Named(Named::Space) => Some(Message::CtrlSpacePressed), - Key::Character(",") => Some(Message::OpenLastSettings), - Key::Named(Named::Backspace) => Some(Message::ResetButtonPressed), - Key::Character("d") => Some(Message::CtrlDPressed), - Key::Named(Named::ArrowLeft) => Some(Message::ArrowPressed(false)), - Key::Named(Named::ArrowRight) => Some(Message::ArrowPressed(true)), - Key::Character("-") => Some(Message::ScaleFactorShortcut(false)), - Key::Character("+") => Some(Message::ScaleFactorShortcut(true)), - _ => None, - }, - Modifiers::SHIFT => match key { - Key::Named(Named::Tab) => Some(Message::SwitchPage(false)), - _ => None, - }, - Modifiers::NONE => match key { - Key::Named(Named::Enter) => Some(Message::ReturnKeyPressed), - Key::Named(Named::Escape) => Some(Message::EscKeyPressed), - Key::Named(Named::Tab) => Some(Message::SwitchPage(true)), - _ => None, - }, - _ => None, - }, + Keyboard(Event::KeyPressed { key, modifiers, .. }) => { + hotkey_message(&key, modifiers, false) + } _ => None, }) } @@ -1436,6 +1406,56 @@ impl Sniffer { } } +/// Maps a key press to the message it triggers, if any. +fn hotkey_message(key: &Key, modifiers: Modifiers, thumbnail: bool) -> Option { + /// `+` is typed as `shift` + `=` on most layouts, so the zoom in shortcut + /// arrives with `shift` held down. + const COMMAND_SHIFT: Modifiers = Modifiers::COMMAND.union(Modifiers::SHIFT); + + if thumbnail { + return match modifiers { + Modifiers::COMMAND => match key.as_ref() { + Key::Character("q") => Some(Message::QuitWrapper), + Key::Character("t") => Some(Message::CtrlTPressed), + Key::Named(Named::Space) => Some(Message::CtrlSpacePressed), + _ => None, + }, + _ => None, + }; + } + + match modifiers { + Modifiers::COMMAND => match key.as_ref() { + Key::Character("q") => Some(Message::QuitWrapper), + Key::Character("t") => Some(Message::CtrlTPressed), + Key::Named(Named::Space) => Some(Message::CtrlSpacePressed), + Key::Character(",") => Some(Message::OpenLastSettings), + Key::Named(Named::Backspace) => Some(Message::ResetButtonPressed), + Key::Character("d") => Some(Message::CtrlDPressed), + Key::Named(Named::ArrowLeft) => Some(Message::ArrowPressed(false)), + Key::Named(Named::ArrowRight) => Some(Message::ArrowPressed(true)), + Key::Character("-") => Some(Message::ScaleFactorShortcut(false)), + Key::Character("+" | "=") => Some(Message::ScaleFactorShortcut(true)), + _ => None, + }, + COMMAND_SHIFT => match key.as_ref() { + Key::Character("+" | "=") => Some(Message::ScaleFactorShortcut(true)), + _ => None, + }, + Modifiers::SHIFT => match key { + Key::Named(Named::Tab) => Some(Message::SwitchPage(false)), + _ => None, + }, + Modifiers::NONE => match key { + Key::Named(Named::Enter) => Some(Message::ReturnKeyPressed), + Key::Named(Named::Escape) => Some(Message::EscKeyPressed), + Key::Named(Named::Tab) => Some(Message::SwitchPage(true)), + _ => None, + }, + _ => None, + } +} + #[cfg(test)] mod tests { #![allow(unused_must_use)] @@ -1479,6 +1499,9 @@ mod tests { use crate::report::types::sort_type::SortType; use crate::{ByteMultiple, Language, RunningPage, Sniffer, StyleType}; + use super::hotkey_message; + use iced::keyboard::{Key, Modifiers}; + // helpful to clean up files generated from tests impl Drop for Sniffer { fn drop(&mut self) { @@ -2423,4 +2446,55 @@ mod tests { Some(&LatencyStatus::Failed("no reply".to_string())) ); } + + #[test] + #[parallel] + fn test_zoom_hotkeys() { + let plus = Key::Character("+".into()); + let equal = Key::Character("=".into()); + let minus = Key::Character("-".into()); + let command_shift = Modifiers::COMMAND | Modifiers::SHIFT; + + // zoom out needs no shift and has always worked + assert!(matches!( + hotkey_message(&minus, Modifiers::COMMAND, false), + Some(Message::ScaleFactorShortcut(true_if_increase)) if !true_if_increase + )); + + // on layouts where '+' sits on its own key, no shift is reported + assert!(matches!( + hotkey_message(&plus, Modifiers::COMMAND, false), + Some(Message::ScaleFactorShortcut(true)) + )); + + // on layouts where '+' is shift + '=', shift reaches us as well + assert!(matches!( + hotkey_message(&plus, command_shift, false), + Some(Message::ScaleFactorShortcut(true)) + )); + + // '=' without shift is the same shortcut in browsers, accept it too + assert!(matches!( + hotkey_message(&equal, Modifiers::COMMAND, false), + Some(Message::ScaleFactorShortcut(true)) + )); + + // no zoom shortcuts in thumbnail mode + assert!(hotkey_message(&plus, command_shift, true).is_none()); + assert!(hotkey_message(&minus, Modifiers::COMMAND, true).is_none()); + } + + #[test] + #[parallel] + fn test_hotkeys_ignore_unknown_modifier_combinations() { + let quit = Key::Character("q".into()); + + assert!(matches!( + hotkey_message(&quit, Modifiers::COMMAND, false), + Some(Message::QuitWrapper) + )); + // adding shift must not trigger the plain command shortcut + assert!(hotkey_message(&quit, Modifiers::COMMAND | Modifiers::SHIFT, false).is_none()); + assert!(hotkey_message(&quit, Modifiers::NONE, false).is_none()); + } }