diff --git a/clients/quadraticpc/fht-compositor/default.nix b/clients/quadraticpc/fht-compositor/default.nix index 80c8124..d084a62 100644 --- a/clients/quadraticpc/fht-compositor/default.nix +++ b/clients/quadraticpc/fht-compositor/default.nix @@ -68,7 +68,7 @@ in arg = "fht-compositor ipc action reload-config; ${session-programs}"; }; - # Runs when Super is tapped on its own (patched in, see lone-modifier-and-scroll.patch) + # Runs when Super is tapped on its own (patched in, see lone-modifier.patch) Super_L = { action = "run-command"; arg = "walker"; @@ -148,9 +148,8 @@ in (old: { patches = (old.patches or [ ]) ++ [ ./resize-tiled-windows.patch - ./lone-modifier-and-scroll.patch - # Fixes screencasting (e.g. OBS window capture) being rejected by xdg-desktop-portal - ./portal-version.patch + ./lone-modifier.patch + ./scroll-accumulation.patch ]; nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.makeWrapper ]; diff --git a/clients/quadraticpc/fht-compositor/lone-modifier-and-scroll.patch b/clients/quadraticpc/fht-compositor/lone-modifier-and-scroll.patch deleted file mode 100644 index c652c08..0000000 --- a/clients/quadraticpc/fht-compositor/lone-modifier-and-scroll.patch +++ /dev/null @@ -1,187 +0,0 @@ ---- a/src/input/mod.rs -+++ b/src/input/mod.rs -@@ -3,6 +3,7 @@ - pub mod resize_tile_grab; - pub mod swap_tile_grab; - -+use std::cell::Cell; - use std::collections::HashMap; - use std::time::Duration; - -@@ -32,6 +33,15 @@ - use crate::output::OutputExt; - use crate::state::State; - -+thread_local! { -+ /// A modifier key that was pressed on its own, with no other key, button or scroll since. -+ /// Keybinds on just a modifier key (e.g. `Super_L`) run when it's released in that state. -+ static LONE_MODIFIER: Cell> = const { Cell::new(None) }; -+ /// Accumulated high-resolution scroll (in 1/120ths of a wheel notch) for scroll bindings, -+ /// so that they trigger once per notch rather than once per event. -+ static SCROLL_ACCUMULATOR: Cell<(f64, f64)> = const { Cell::new((0.0, 0.0)) }; -+} -+ - impl State { - fn handle_focus_follows_mouse(&mut self, under: Option<&PointerFocus>) { - if !self.fht.config.general.focus_follows_mouse { -@@ -244,6 +254,7 @@ - }) - .map(|inhibitor| inhibitor.is_active()) - .unwrap_or(false); -+ let mut lone_modifier_action = None; - let action = keyboard.input( - self, - keycode, -@@ -272,6 +283,20 @@ - return FilterResult::Forward; - } - -+ // Lone modifier taps. The key release is still forwarded to clients, and the -+ // action is run after the input is processed. -+ if let Some(raw) = raw { -+ if key_state == KeyState::Pressed { -+ LONE_MODIFIER.set(raw.is_modifier_key().then_some(raw)); -+ } else if LONE_MODIFIER.take() == Some(raw) { -+ lone_modifier_action = find_keyaction( -+ &state.fht.config.keybinds, -+ &ModifiersState::default(), -+ raw, -+ ); -+ } -+ } -+ - // Handle repeating keybinds - // FIXME: handle this properly, this breaks if there are two repeating keybinds - if key_state == KeyState::Released { -@@ -331,6 +356,9 @@ - if let Some((key_pattern, key_action)) = action { - self.process_key_action(key_action, key_pattern); - } -+ if let Some((key_pattern, key_action)) = lone_modifier_action { -+ self.process_key_action(key_action, key_pattern); -+ } - } - - fn on_pointer_motion(&mut self, event: B::PointerMotionEvent) { -@@ -505,6 +533,11 @@ - let state = wl_pointer::ButtonState::from(event.state()); - let pointer = self.fht.pointer.clone(); - -+ if state == wl_pointer::ButtonState::Pressed { -+ // e.g. Super+click shouldn't count as a lone Super tap -+ LONE_MODIFIER.set(None); -+ } -+ - if state == wl_pointer::ButtonState::Pressed && !pointer.is_grabbed() { - let pointer_loc = pointer.current_location(); - let focus = self.fht.get_pointer_focus(pointer_loc); -@@ -567,62 +600,67 @@ - // Check for mouse axis bindings FIRST - let modifiers = self.fht.keyboard.modifier_state().into(); - let mut handled = false; -+ // e.g. Super+scroll shouldn't count as a lone Super tap -+ LONE_MODIFIER.set(None); -+ -+ // High-resolution wheels send several events per notch, each with part of the 120 units -+ // of a notch, so accumulate them and only trigger the binding once per full notch. -+ let (mut horizontal_accumulated, mut vertical_accumulated) = SCROLL_ACCUMULATOR.get(); -+ let accumulate = |accumulated: &mut f64, discrete: f64| { -+ if accumulated.signum() != discrete.signum() { -+ // Changed direction, start over -+ *accumulated = 0.0; -+ } -+ *accumulated += discrete; -+ if accumulated.abs() >= 120.0 { -+ *accumulated -= 120.0 * accumulated.signum(); -+ true -+ } else { -+ false -+ } -+ }; - - // Check vertical axis bindings using discrete amounts -- if let Some(discrete) = vertical_amount_discrete { -- if discrete > 0.0 { -- let mouse_pattern = fht_compositor_config::MousePattern( -- modifiers, -- fht_compositor_config::MouseInput::Axis( -- fht_compositor_config::MouseAxis::WheelUp, -- ), -- ); -- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -- self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); -- handled = true; -- } -- } else if discrete < 0.0 { -- let mouse_pattern = fht_compositor_config::MousePattern( -- modifiers, -- fht_compositor_config::MouseInput::Axis( -- fht_compositor_config::MouseAxis::WheelDown, -- ), -- ); -- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -+ if let Some(discrete) = vertical_amount_discrete.filter(|discrete| *discrete != 0.0) { -+ let mouse_axis = if discrete > 0.0 { -+ fht_compositor_config::MouseAxis::WheelUp -+ } else { -+ fht_compositor_config::MouseAxis::WheelDown -+ }; -+ let mouse_pattern = fht_compositor_config::MousePattern( -+ modifiers, -+ fht_compositor_config::MouseInput::Axis(mouse_axis), -+ ); -+ if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -+ handled = true; -+ if accumulate(&mut vertical_accumulated, discrete) { - self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); -- handled = true; - } - } - } - - // Check horizontal axis bindings using discrete amounts - if !handled { -- if let Some(discrete) = horizontal_amount_discrete { -- if discrete > 0.0 { -- let mouse_pattern = fht_compositor_config::MousePattern( -- modifiers, -- fht_compositor_config::MouseInput::Axis( -- fht_compositor_config::MouseAxis::WheelRight, -- ), -- ); -- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -- self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); -- handled = true; -- } -- } else if discrete < 0.0 { -- let mouse_pattern = fht_compositor_config::MousePattern( -- modifiers, -- fht_compositor_config::MouseInput::Axis( -- fht_compositor_config::MouseAxis::WheelLeft, -- ), -- ); -- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -+ if let Some(discrete) = horizontal_amount_discrete.filter(|discrete| *discrete != 0.0) -+ { -+ let mouse_axis = if discrete > 0.0 { -+ fht_compositor_config::MouseAxis::WheelRight -+ } else { -+ fht_compositor_config::MouseAxis::WheelLeft -+ }; -+ let mouse_pattern = fht_compositor_config::MousePattern( -+ modifiers, -+ fht_compositor_config::MouseInput::Axis(mouse_axis), -+ ); -+ if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -+ handled = true; -+ if accumulate(&mut horizontal_accumulated, discrete) { - self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); -- handled = true; - } - } - } - } -+ SCROLL_ACCUMULATOR.set((horizontal_accumulated, vertical_accumulated)); - - // Only forward to pointer if we didn't handle it with a binding - if !handled { diff --git a/clients/quadraticpc/fht-compositor/lone-modifier.patch b/clients/quadraticpc/fht-compositor/lone-modifier.patch new file mode 100644 index 0000000..12b0da4 --- /dev/null +++ b/clients/quadraticpc/fht-compositor/lone-modifier.patch @@ -0,0 +1,83 @@ +--- a/src/state.rs ++++ b/src/state.rs +@@ -724,6 +724,9 @@ + // We store both the timer and the keysym used to trigger the key action. + // When we remove the keysym from suppressed keys we stop it. + pub repeated_keyaction_timer: Option<(RegistrationToken, Keysym)>, ++ // A modifier key that was pressed on its own, with no other key, button or scroll since. ++ // Keybinds on just a modifier key (e.g. `Super_L`) run when it's released in that state. ++ pub lone_modifier: Option, + // To focus a layer-surface with an OnDemand keyboard exclusivity mode, the user must click + // on the layer surface. Then, when we update the keyboard focus, we check against the clicked + // layer surface +@@ -962,6 +965,7 @@ + clock, + suppressed_keys: HashMap::new(), + repeated_keyaction_timer: None, ++ lone_modifier: None, + seat, + devices: vec![], + seat_state, +--- a/src/input/mod.rs ++++ b/src/input/mod.rs +@@ -244,6 +244,7 @@ + }) + .map(|inhibitor| inhibitor.is_active()) + .unwrap_or(false); ++ let mut lone_modifier_action = None; + let action = keyboard.input( + self, + keycode, +@@ -272,6 +273,20 @@ + return FilterResult::Forward; + } + ++ // Lone modifier taps. The key release is still forwarded to clients, and the ++ // action is run after the input is processed. ++ if let Some(raw) = raw { ++ if key_state == KeyState::Pressed { ++ state.fht.lone_modifier = raw.is_modifier_key().then_some(raw); ++ } else if state.fht.lone_modifier.take() == Some(raw) { ++ lone_modifier_action = find_keyaction( ++ &state.fht.config.keybinds, ++ &ModifiersState::default(), ++ raw, ++ ); ++ } ++ } ++ + // Handle repeating keybinds + // FIXME: handle this properly, this breaks if there are two repeating keybinds + if key_state == KeyState::Released { +@@ -331,6 +346,9 @@ + if let Some((key_pattern, key_action)) = action { + self.process_key_action(key_action, key_pattern); + } ++ if let Some((key_pattern, key_action)) = lone_modifier_action { ++ self.process_key_action(key_action, key_pattern); ++ } + } + + fn on_pointer_motion(&mut self, event: B::PointerMotionEvent) { +@@ -505,6 +523,11 @@ + let state = wl_pointer::ButtonState::from(event.state()); + let pointer = self.fht.pointer.clone(); + ++ if state == wl_pointer::ButtonState::Pressed { ++ // e.g. Super+click shouldn't count as a lone Super tap ++ self.fht.lone_modifier = None; ++ } ++ + if state == wl_pointer::ButtonState::Pressed && !pointer.is_grabbed() { + let pointer_loc = pointer.current_location(); + let focus = self.fht.get_pointer_focus(pointer_loc); +@@ -555,6 +578,9 @@ + } + + fn on_pointer_axis(&mut self, event: B::PointerAxisEvent) { ++ // e.g. Super+scroll shouldn't count as a lone Super tap ++ self.fht.lone_modifier = None; ++ + let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal); + let vertical_amount_discrete = event.amount_v120(Axis::Vertical); + let horizontal_amount = event diff --git a/clients/quadraticpc/fht-compositor/portal-version.patch b/clients/quadraticpc/fht-compositor/portal-version.patch deleted file mode 100644 index 2ee6430..0000000 --- a/clients/quadraticpc/fht-compositor/portal-version.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- a/src/portals/screencast.rs -+++ b/src/portals/screencast.rs -@@ -165,7 +165,9 @@ - (CursorMode::HIDDEN | CursorMode::EMBEDDED).bits() - } - -- #[zbus(property)] -+ // The spec calls this "version" (zbus would otherwise publish it as "Version"), and -+ // xdg-desktop-portal treats the portal as version 0 without it, rejecting cursor modes. -+ #[zbus(property, name = "version")] - pub fn version(&self) -> u32 { - PORTAL_VERSION - } diff --git a/clients/quadraticpc/fht-compositor/scroll-accumulation.patch b/clients/quadraticpc/fht-compositor/scroll-accumulation.patch new file mode 100644 index 0000000..9c70f45 --- /dev/null +++ b/clients/quadraticpc/fht-compositor/scroll-accumulation.patch @@ -0,0 +1,132 @@ +--- a/src/state.rs ++++ b/src/state.rs +@@ -719,6 +719,9 @@ + pub seat: Seat, + pub keyboard: KeyboardHandle, + pub pointer: PointerHandle, ++ // Accumulated high-resolution scroll (in 1/120ths of a wheel notch), horizontal then vertical, ++ // for scroll bindings, so that they trigger once per notch rather than once per event. ++ pub scroll_binding_accumulator: (f64, f64), + pub clock: Clock, + pub suppressed_keys: HashMap, + // We store both the timer and the keysym used to trigger the key action. +@@ -967,6 +970,7 @@ + seat_state, + keyboard, + pointer, ++ scroll_binding_accumulator: (0.0, 0.0), + mapped_layer_surfaces: HashMap::new(), + lock_state: LockState::Unlocked, + focused_on_demand_layer_shell: None, +--- a/src/input/mod.rs ++++ b/src/input/mod.rs +@@ -569,56 +569,41 @@ + let mut handled = false; + + // Check vertical axis bindings using discrete amounts +- if let Some(discrete) = vertical_amount_discrete { +- if discrete > 0.0 { +- let mouse_pattern = fht_compositor_config::MousePattern( +- modifiers, +- fht_compositor_config::MouseInput::Axis( +- fht_compositor_config::MouseAxis::WheelUp, +- ), +- ); +- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { +- self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); +- handled = true; +- } +- } else if discrete < 0.0 { +- let mouse_pattern = fht_compositor_config::MousePattern( +- modifiers, +- fht_compositor_config::MouseInput::Axis( +- fht_compositor_config::MouseAxis::WheelDown, +- ), +- ); +- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { ++ if let Some(discrete) = vertical_amount_discrete.filter(|discrete| *discrete != 0.0) { ++ let mouse_axis = if discrete > 0.0 { ++ fht_compositor_config::MouseAxis::WheelUp ++ } else { ++ fht_compositor_config::MouseAxis::WheelDown ++ }; ++ let mouse_pattern = fht_compositor_config::MousePattern( ++ modifiers, ++ fht_compositor_config::MouseInput::Axis(mouse_axis), ++ ); ++ if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { ++ handled = true; ++ if accumulate_scroll(&mut self.fht.scroll_binding_accumulator.1, discrete) { + self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); +- handled = true; + } + } + } + + // Check horizontal axis bindings using discrete amounts + if !handled { +- if let Some(discrete) = horizontal_amount_discrete { +- if discrete > 0.0 { +- let mouse_pattern = fht_compositor_config::MousePattern( +- modifiers, +- fht_compositor_config::MouseInput::Axis( +- fht_compositor_config::MouseAxis::WheelRight, +- ), +- ); +- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { +- self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); +- handled = true; +- } +- } else if discrete < 0.0 { +- let mouse_pattern = fht_compositor_config::MousePattern( +- modifiers, +- fht_compositor_config::MouseInput::Axis( +- fht_compositor_config::MouseAxis::WheelLeft, +- ), +- ); +- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { ++ if let Some(discrete) = horizontal_amount_discrete.filter(|discrete| *discrete != 0.0) ++ { ++ let mouse_axis = if discrete > 0.0 { ++ fht_compositor_config::MouseAxis::WheelRight ++ } else { ++ fht_compositor_config::MouseAxis::WheelLeft ++ }; ++ let mouse_pattern = fht_compositor_config::MousePattern( ++ modifiers, ++ fht_compositor_config::MouseInput::Axis(mouse_axis), ++ ); ++ if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { ++ handled = true; ++ if accumulate_scroll(&mut self.fht.scroll_binding_accumulator.0, discrete) { + self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); +- handled = true; + } + } + } +@@ -1080,6 +1065,25 @@ + } + + /// Returns `true` if a VT switch key has been handled. ++/// Accumulate high-resolution scroll for a scroll binding. ++/// ++/// High-resolution wheels send several events per notch, each with part of the 120 units of a ++/// notch, so we accumulate them and only trigger the binding once per full notch. Returns whether ++/// the binding should trigger. ++fn accumulate_scroll(accumulated: &mut f64, v120: f64) -> bool { ++ if accumulated.signum() != v120.signum() { ++ // Changed direction, start over ++ *accumulated = 0.0; ++ } ++ *accumulated += v120; ++ if accumulated.abs() >= 120.0 { ++ *accumulated -= 120.0 * accumulated.signum(); ++ true ++ } else { ++ false ++ } ++} ++ + fn handle_vt_switch(backend: &mut Backend, key_state: KeyState, modified: Keysym) -> bool { + if key_state == KeyState::Released { + // We only do VT switch on presses. diff --git a/flake.lock b/flake.lock index 3e27acf..d048959 100644 --- a/flake.lock +++ b/flake.lock @@ -306,11 +306,11 @@ "rust-overlay": [] }, "locked": { - "lastModified": 1792487869, - "narHash": "sha256-4e5XvmR5lvSr/tSbSM1byipvKpblM5SJjSJWNgGAiSo=", + "lastModified": 1792507568, + "narHash": "sha256-RGXq8SkxpxU0TaMk3oXbJ5SwkRDY90tvN2zIiofAVMQ=", "owner": "nferhat", "repo": "fht-compositor", - "rev": "a39a9a6e4f13307d034c5976ca5514beda7b21ac", + "rev": "458dc48d96aa6d8aa0b834a8052f33c09700ffe8", "type": "github" }, "original": {