clean up patches

This commit is contained in:
Henry Hiles 2026-10-20 11:06:53 -04:00
commit 81fbae91d0
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
6 changed files with 221 additions and 207 deletions

View file

@ -68,7 +68,7 @@ in
arg = "fht-compositor ipc action reload-config; ${session-programs}"; 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 = { Super_L = {
action = "run-command"; action = "run-command";
arg = "walker"; arg = "walker";
@ -148,9 +148,8 @@ in
(old: { (old: {
patches = (old.patches or [ ]) ++ [ patches = (old.patches or [ ]) ++ [
./resize-tiled-windows.patch ./resize-tiled-windows.patch
./lone-modifier-and-scroll.patch ./lone-modifier.patch
# Fixes screencasting (e.g. OBS window capture) being rejected by xdg-desktop-portal ./scroll-accumulation.patch
./portal-version.patch
]; ];
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.makeWrapper ]; nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.makeWrapper ];

View file

@ -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<Option<Keysym>> = 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<B: InputBackend>(&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 {

View file

@ -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<Keysym>,
// 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<B: InputBackend>(&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<B: InputBackend>(&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

View file

@ -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
}

View file

@ -0,0 +1,132 @@
--- a/src/state.rs
+++ b/src/state.rs
@@ -719,6 +719,9 @@
pub seat: Seat<State>,
pub keyboard: KeyboardHandle<State>,
pub pointer: PointerHandle<State>,
+ // 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<Monotonic>,
pub suppressed_keys: HashMap<u32, (KeyPattern, KeyAction)>,
// 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.

6
flake.lock generated
View file

@ -306,11 +306,11 @@
"rust-overlay": [] "rust-overlay": []
}, },
"locked": { "locked": {
"lastModified": 1792487869, "lastModified": 1792507568,
"narHash": "sha256-4e5XvmR5lvSr/tSbSM1byipvKpblM5SJjSJWNgGAiSo=", "narHash": "sha256-RGXq8SkxpxU0TaMk3oXbJ5SwkRDY90tvN2zIiofAVMQ=",
"owner": "nferhat", "owner": "nferhat",
"repo": "fht-compositor", "repo": "fht-compositor",
"rev": "a39a9a6e4f13307d034c5976ca5514beda7b21ac", "rev": "458dc48d96aa6d8aa0b834a8052f33c09700ffe8",
"type": "github" "type": "github"
}, },
"original": { "original": {