Fix screenshots and workspace switching; add configurable swipe bindings
This commit is contained in:
1 parent
5d6c610fec
commit
dbdfc5688e
14 files changed
+707
-101
No files matched your search
+168
-3
@@ -440,6 +440,10 @@ pub struct Binding {
|
||||
enum BindingTrigger {
|
||||
Keysym(u32),
|
||||
Physical(Keycode),
|
||||
Swipe {
|
||||
fingers: u32,
|
||||
direction: crate::gestures::SwipeDirection,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
@@ -452,6 +456,7 @@ struct BindingModifiers {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum BindingAction {
|
||||
None,
|
||||
Spawn(Vec<String>),
|
||||
Close,
|
||||
Exit,
|
||||
@@ -459,6 +464,7 @@ pub enum BindingAction {
|
||||
Move(ferese_layout::Direction),
|
||||
Resize(ferese_layout::Direction),
|
||||
SwitchWorkspace(u8),
|
||||
SwitchRelativeWorkspace(bool),
|
||||
MoveToWorkspace(u8),
|
||||
ToggleFullscreen,
|
||||
ToggleMaximized,
|
||||
@@ -491,8 +497,21 @@ impl Binding {
|
||||
&& match self.trigger {
|
||||
BindingTrigger::Keysym(expected) => keysyms.contains(&expected),
|
||||
BindingTrigger::Physical(expected) => expected == keycode,
|
||||
BindingTrigger::Swipe { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn swipe_fingers(&self, count: u32) -> bool {
|
||||
matches!(self.trigger, BindingTrigger::Swipe { fingers, .. } if fingers == count)
|
||||
}
|
||||
|
||||
pub(crate) fn matches_swipe(
|
||||
&self,
|
||||
count: u32,
|
||||
target: crate::gestures::SwipeDirection,
|
||||
) -> bool {
|
||||
matches!(self.trigger, BindingTrigger::Swipe { fingers, direction } if fingers == count && direction == target)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq)]
|
||||
@@ -501,6 +520,7 @@ enum BindingMatch {
|
||||
#[default]
|
||||
Keysym,
|
||||
Physical,
|
||||
Swipe,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
@@ -530,6 +550,11 @@ pub struct TouchpadSettings {
|
||||
pub tap: bool,
|
||||
pub natural_scroll: bool,
|
||||
pub disable_while_typing: bool,
|
||||
pub swipe_threshold: u16,
|
||||
}
|
||||
|
||||
fn default_swipe_threshold() -> u16 {
|
||||
80
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -572,6 +597,8 @@ struct TouchpadConfig {
|
||||
natural_scroll: bool,
|
||||
#[serde(default = "enabled_by_default")]
|
||||
disable_while_typing: bool,
|
||||
#[serde(default = "default_swipe_threshold")]
|
||||
swipe_threshold: u16,
|
||||
}
|
||||
|
||||
impl Default for TouchpadConfig {
|
||||
@@ -580,6 +607,7 @@ impl Default for TouchpadConfig {
|
||||
tap: true,
|
||||
natural_scroll: true,
|
||||
disable_while_typing: true,
|
||||
swipe_threshold: default_swipe_threshold(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -879,6 +907,12 @@ impl Config {
|
||||
});
|
||||
}
|
||||
|
||||
if !(16..=1000).contains(&self.input.touchpad.swipe_threshold) {
|
||||
return Err(ConfigError::InvalidInputValue {
|
||||
field: "touchpad.swipe_threshold",
|
||||
value: self.input.touchpad.swipe_threshold.to_string(),
|
||||
});
|
||||
}
|
||||
Ok(InputSettings {
|
||||
focus_follows_mouse: self.input.focus_follows_mouse,
|
||||
xkb_layout: self.input.xkb_layout.clone(),
|
||||
@@ -890,6 +924,7 @@ impl Config {
|
||||
tap: self.input.touchpad.tap,
|
||||
natural_scroll: self.input.touchpad.natural_scroll,
|
||||
disable_while_typing: self.input.touchpad.disable_while_typing,
|
||||
swipe_threshold: self.input.touchpad.swipe_threshold,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -901,6 +936,10 @@ impl Config {
|
||||
"screenshot".to_owned(),
|
||||
vec!["ferese-screenshot".to_owned()],
|
||||
),
|
||||
(
|
||||
"screenshot-full".to_owned(),
|
||||
vec!["ferese-screenshot".to_owned(), "--full".to_owned()],
|
||||
),
|
||||
]);
|
||||
commands.extend(self.commands.clone());
|
||||
validate_commands(&commands)?;
|
||||
@@ -1184,6 +1223,15 @@ fn parse_binding(
|
||||
let trigger = match identity.match_mode {
|
||||
BindingMatch::Keysym => BindingTrigger::Keysym(identity.key),
|
||||
BindingMatch::Physical => BindingTrigger::Physical(Keycode::new(identity.key)),
|
||||
BindingMatch::Swipe => BindingTrigger::Swipe {
|
||||
fingers: identity.key / 4,
|
||||
direction: [
|
||||
crate::gestures::SwipeDirection::Up,
|
||||
crate::gestures::SwipeDirection::Down,
|
||||
crate::gestures::SwipeDirection::Left,
|
||||
crate::gestures::SwipeDirection::Right,
|
||||
][(identity.key % 4) as usize],
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Binding {
|
||||
@@ -1198,6 +1246,20 @@ fn binding_identity(
|
||||
keymap: &xkb::Keymap,
|
||||
) -> Result<BindingIdentity, ConfigError> {
|
||||
let (modifiers, key) = parse_chord(&configured.keys)?;
|
||||
if let Some((fingers, direction)) = parse_swipe(&key) {
|
||||
if configured.match_mode == BindingMatch::Physical
|
||||
|| modifiers != BindingModifiers::default()
|
||||
{
|
||||
return Err(ConfigError::InvalidBinding(
|
||||
"swipes cannot use keyboard modifiers or physical key matching".into(),
|
||||
));
|
||||
}
|
||||
return Ok(BindingIdentity {
|
||||
modifiers,
|
||||
match_mode: BindingMatch::Swipe,
|
||||
key: fingers * 4 + direction as u32,
|
||||
});
|
||||
}
|
||||
let key = match configured.match_mode {
|
||||
BindingMatch::Keysym => parse_keysym(&key)?,
|
||||
BindingMatch::Physical => keymap
|
||||
@@ -1209,6 +1271,11 @@ fn binding_identity(
|
||||
configured.keys
|
||||
))
|
||||
})?,
|
||||
BindingMatch::Swipe => {
|
||||
return Err(ConfigError::InvalidBinding(
|
||||
"gesture keys must use Swipe3Left/Right/Up/Down (3–5 fingers)".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(BindingIdentity {
|
||||
@@ -1222,6 +1289,9 @@ fn binding_identity_for_runtime(binding: &Binding) -> BindingIdentity {
|
||||
let (match_mode, key) = match binding.trigger {
|
||||
BindingTrigger::Keysym(key) => (BindingMatch::Keysym, key),
|
||||
BindingTrigger::Physical(key) => (BindingMatch::Physical, key.raw()),
|
||||
BindingTrigger::Swipe { fingers, direction } => {
|
||||
(BindingMatch::Swipe, fingers * 4 + direction as u32)
|
||||
}
|
||||
};
|
||||
|
||||
BindingIdentity {
|
||||
@@ -1269,6 +1339,24 @@ fn parse_chord(chord: &str) -> Result<(BindingModifiers, String), ConfigError> {
|
||||
Ok((modifiers, key))
|
||||
}
|
||||
|
||||
fn parse_swipe(key: &str) -> Option<(u32, crate::gestures::SwipeDirection)> {
|
||||
use crate::gestures::SwipeDirection;
|
||||
let lower = key.to_ascii_lowercase();
|
||||
let rest = lower.strip_prefix("swipe")?;
|
||||
let fingers = rest.get(..1)?.parse::<u32>().ok()?;
|
||||
if !(3..=5).contains(&fingers) {
|
||||
return None;
|
||||
}
|
||||
let direction = match rest.get(1..)? {
|
||||
"up" => SwipeDirection::Up,
|
||||
"down" => SwipeDirection::Down,
|
||||
"left" => SwipeDirection::Left,
|
||||
"right" => SwipeDirection::Right,
|
||||
_ => return None,
|
||||
};
|
||||
Some((fingers, direction))
|
||||
}
|
||||
|
||||
fn parse_keysym(name: &str) -> Result<u32, ConfigError> {
|
||||
let normalized = match name {
|
||||
"Enter" | "enter" => "Return".to_owned(),
|
||||
@@ -1312,6 +1400,16 @@ fn parse_action(
|
||||
};
|
||||
|
||||
match action {
|
||||
"none" => {
|
||||
no_argument()?;
|
||||
Ok(BindingAction::None)
|
||||
}
|
||||
"workspace-next" | "workspace-previous" => {
|
||||
no_argument()?;
|
||||
Ok(BindingAction::SwitchRelativeWorkspace(
|
||||
action == "workspace-next",
|
||||
))
|
||||
}
|
||||
"spawn" => {
|
||||
let command = required_argument()?;
|
||||
let argv = commands.get(command).ok_or_else(|| {
|
||||
@@ -1405,6 +1503,11 @@ fn default_bindings() -> Vec<BindingConfig> {
|
||||
let mut bindings = vec![
|
||||
binding("Super+Enter", "spawn", Some("terminal")),
|
||||
binding("Super+Shift+S", "spawn", Some("screenshot")),
|
||||
binding("Print", "spawn", Some("screenshot-full")),
|
||||
binding("Swipe3Up", "workspace-next", None),
|
||||
binding("Swipe3Down", "workspace-previous", None),
|
||||
binding("Swipe3Left", "focus", Some("right")),
|
||||
binding("Swipe3Right", "focus", Some("left")),
|
||||
binding("Super+Q", "close", None),
|
||||
binding("Super+F", "toggle-maximized", None),
|
||||
binding("Super+Shift+F", "toggle-fullscreen", None),
|
||||
@@ -2054,6 +2157,7 @@ mod tests {
|
||||
tap: false,
|
||||
natural_scroll: false,
|
||||
disable_while_typing: true,
|
||||
swipe_threshold: 80,
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -2070,13 +2174,65 @@ mod tests {
|
||||
assert!(delay.input_settings().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swipe_bindings_can_override_actions_disable_defaults_and_set_distance() {
|
||||
use crate::gestures::SwipeDirection;
|
||||
let config = parse(
|
||||
"[input.touchpad]\nswipe_threshold = 120\n[[bindings]]\nkeys = 'Swipe3Up'\naction = 'toggle-overview'\n[[bindings]]\nkeys = 'Swipe3Left'\naction = 'move'\nargument = 'left'\n[[bindings]]\nkeys = 'Swipe3Down'\ndisabled = true",
|
||||
);
|
||||
let input = config.input_settings().unwrap();
|
||||
assert_eq!(input.touchpad.swipe_threshold, 120);
|
||||
let bindings = config.bindings(&input).unwrap();
|
||||
assert!(
|
||||
bindings
|
||||
.iter()
|
||||
.any(|binding| binding.matches_swipe(3, SwipeDirection::Up)
|
||||
&& binding.action == BindingAction::ToggleOverview)
|
||||
);
|
||||
assert!(
|
||||
bindings
|
||||
.iter()
|
||||
.any(|binding| binding.matches_swipe(3, SwipeDirection::Left)
|
||||
&& binding.action == BindingAction::Move(ferese_layout::Direction::Left))
|
||||
);
|
||||
assert!(
|
||||
!bindings
|
||||
.iter()
|
||||
.any(|binding| binding.matches_swipe(3, SwipeDirection::Down))
|
||||
);
|
||||
for threshold in [0, 15, 1001] {
|
||||
assert!(
|
||||
parse(&format!("[input.touchpad]\nswipe_threshold = {threshold}"))
|
||||
.input_settings()
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gesture_bindings_validate_fingers_directions_and_actions() {
|
||||
for keys in ["Swipe2Up", "Swipe3Diagonal", "Super+Swipe3Up"] {
|
||||
let config = parse(&format!(
|
||||
"[[bindings]]\nkeys = '{keys}'\naction = 'toggle-overview'"
|
||||
));
|
||||
assert!(config.bindings(&config.input_settings().unwrap()).is_err());
|
||||
}
|
||||
let config = parse("[[bindings]]\nkeys = 'Swipe4Up'\naction = 'toggle-overview'");
|
||||
let bindings = config.bindings(&config.input_settings().unwrap()).unwrap();
|
||||
assert!(
|
||||
bindings
|
||||
.iter()
|
||||
.any(|binding| binding.matches_swipe(4, crate::gestures::SwipeDirection::Up))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supplies_complete_v0_bindings_and_terminal_command() {
|
||||
let config = parse("");
|
||||
let input = config.input_settings().unwrap();
|
||||
let bindings = config.bindings(&input).unwrap();
|
||||
|
||||
assert_eq!(bindings.len(), 43);
|
||||
assert_eq!(bindings.len(), 48);
|
||||
for (shift, action) in [
|
||||
(false, BindingAction::ToggleMaximized),
|
||||
(true, BindingAction::ToggleFullscreen),
|
||||
@@ -2102,6 +2258,15 @@ mod tests {
|
||||
&& binding.trigger == BindingTrigger::Keysym(keysyms::KEY_s)
|
||||
&& binding.action == BindingAction::Spawn(vec!["ferese-screenshot".to_owned()])
|
||||
}));
|
||||
assert!(bindings.iter().any(|binding| {
|
||||
binding.trigger == BindingTrigger::Keysym(keysyms::KEY_Print)
|
||||
&& binding.modifiers == BindingModifiers::default()
|
||||
&& binding.action
|
||||
== BindingAction::Spawn(vec![
|
||||
"ferese-screenshot".to_owned(),
|
||||
"--full".to_owned(),
|
||||
])
|
||||
}));
|
||||
assert!(bindings.iter().any(|binding| {
|
||||
binding.modifiers.logo
|
||||
&& binding.trigger == BindingTrigger::Keysym(keysyms::KEY_Tab)
|
||||
@@ -2118,7 +2283,7 @@ mod tests {
|
||||
|
||||
let input = replaced.input_settings().unwrap();
|
||||
let bindings = replaced.bindings(&input).unwrap();
|
||||
assert_eq!(bindings.len(), 43);
|
||||
assert_eq!(bindings.len(), 48);
|
||||
assert!(bindings.iter().any(|binding| {
|
||||
binding.action
|
||||
== BindingAction::Spawn(vec![
|
||||
@@ -2130,7 +2295,7 @@ mod tests {
|
||||
|
||||
let input = unbound.input_settings().unwrap();
|
||||
let bindings = unbound.bindings(&input).unwrap();
|
||||
assert_eq!(bindings.len(), 42);
|
||||
assert_eq!(bindings.len(), 47);
|
||||
assert!(
|
||||
!bindings
|
||||
.iter()
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
//! A swipe commits once on release; cancelled and ambiguous gestures do nothing.
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[repr(u32)]
|
||||
pub(crate) enum SwipeDirection {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Swipe {
|
||||
delta: Option<(f64, f64)>,
|
||||
threshold: f64,
|
||||
fingers: u32,
|
||||
}
|
||||
|
||||
impl Swipe {
|
||||
pub fn begin(&mut self, fingers: u32, blocked: bool, threshold: u16) {
|
||||
self.delta = ((3..=5).contains(&fingers) && !blocked).then_some((0.0, 0.0));
|
||||
self.threshold = f64::from(threshold);
|
||||
self.fingers = fingers;
|
||||
}
|
||||
|
||||
pub fn fingers(&self) -> u32 {
|
||||
self.fingers
|
||||
}
|
||||
|
||||
pub fn active(&self) -> bool {
|
||||
self.delta.is_some()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, dx: f64, dy: f64) {
|
||||
if let Some((x, y)) = &mut self.delta {
|
||||
*x += dx;
|
||||
*y += dy;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(&mut self, cancelled: bool) -> Option<SwipeDirection> {
|
||||
let (x, y) = self.delta.take()?;
|
||||
if cancelled || !x.is_finite() || !y.is_finite() {
|
||||
return None;
|
||||
}
|
||||
const DOMINANCE: f64 = 1.25;
|
||||
if x.abs() >= self.threshold && x.abs() > y.abs() * DOMINANCE {
|
||||
Some(if x < 0.0 {
|
||||
SwipeDirection::Left
|
||||
} else {
|
||||
SwipeDirection::Right
|
||||
})
|
||||
} else if y.abs() >= self.threshold && y.abs() > x.abs() * DOMINANCE {
|
||||
Some(if y < 0.0 {
|
||||
SwipeDirection::Up
|
||||
} else {
|
||||
SwipeDirection::Down
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_swipe_accumulates_motion_and_commits_only_once_on_release() {
|
||||
let mut swipe = Swipe::default();
|
||||
swipe.begin(3, false, 80);
|
||||
for _ in 0..10 {
|
||||
swipe.update(-10.0, 1.0);
|
||||
}
|
||||
assert!(swipe.active());
|
||||
assert_eq!(swipe.finish(false), Some(SwipeDirection::Left));
|
||||
assert!(!swipe.active());
|
||||
assert_eq!(swipe.finish(false), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vertical_and_horizontal_swipes_are_distinct() {
|
||||
for (delta, expected) in [
|
||||
((0.0, -100.0), SwipeDirection::Up),
|
||||
((0.0, 100.0), SwipeDirection::Down),
|
||||
((-100.0, 0.0), SwipeDirection::Left),
|
||||
((100.0, 0.0), SwipeDirection::Right),
|
||||
] {
|
||||
let mut swipe = Swipe::default();
|
||||
swipe.begin(3, false, 80);
|
||||
swipe.update(delta.0, delta.1);
|
||||
assert_eq!(swipe.finish(false), Some(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancellation_jitter_diagonals_and_nonfinite_motion_do_not_navigate() {
|
||||
for (x, y, cancelled) in [
|
||||
(200.0, 0.0, true),
|
||||
(79.0, 0.0, false),
|
||||
(100.0, 100.0, false),
|
||||
(f64::NAN, 100.0, false),
|
||||
] {
|
||||
let mut swipe = Swipe::default();
|
||||
swipe.begin(3, false, 80);
|
||||
swipe.update(x, y);
|
||||
assert_eq!(swipe.finish(cancelled), None);
|
||||
assert!(!swipe.active());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_finger_counts_and_blocked_gestures_are_not_intercepted() {
|
||||
for (fingers, blocked) in [(2, false), (6, false), (3, true)] {
|
||||
let mut swipe = Swipe::default();
|
||||
swipe.begin(fingers, blocked, 80);
|
||||
assert!(!swipe.active());
|
||||
swipe.update(200.0, 0.0);
|
||||
assert_eq!(swipe.finish(false), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configured_distance_changes_when_a_swipe_commits() {
|
||||
let mut swipe = Swipe::default();
|
||||
swipe.begin(3, false, 120);
|
||||
swipe.update(100.0, 0.0);
|
||||
assert_eq!(swipe.finish(false), None);
|
||||
swipe.begin(3, false, 40);
|
||||
swipe.update(100.0, 0.0);
|
||||
assert_eq!(swipe.finish(false), Some(SwipeDirection::Right));
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::Fourcc,
|
||||
renderer::{ExportMem, TextureMapping},
|
||||
},
|
||||
backend::{allocator::Fourcc, renderer::ExportMem},
|
||||
output::Output,
|
||||
reexports::{
|
||||
wayland_protocols_wlr::screencopy::v1::server::{
|
||||
@@ -355,8 +352,8 @@ where
|
||||
return false;
|
||||
}
|
||||
|
||||
// Preserve the renderer's row order and report it through the protocol
|
||||
// instead of making another full-frame copy on the compositor thread.
|
||||
// Smithay's output projection already accounts for OpenGL's Y axis.
|
||||
// These rows are in output-buffer order, including its output transform.
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
source.as_ptr(),
|
||||
@@ -377,12 +374,11 @@ where
|
||||
.frame
|
||||
.damage(0, 0, expected.w as u32, expected.h as u32);
|
||||
}
|
||||
let flags = if mapping.flipped() {
|
||||
zwlr_screencopy_frame_v1::Flags::YInvert
|
||||
} else {
|
||||
zwlr_screencopy_frame_v1::Flags::empty()
|
||||
};
|
||||
capture.frame.flags(flags);
|
||||
// TextureMapping::flipped describes importing the mapping as a texture;
|
||||
// using it here would make clients flip the already-correct output rows.
|
||||
capture
|
||||
.frame
|
||||
.flags(zwlr_screencopy_frame_v1::Flags::empty());
|
||||
|
||||
let seconds = timestamp.as_secs();
|
||||
capture.frame.ready(
|
||||
|
||||
+129
-4
@@ -3,13 +3,17 @@ use std::process::Command;
|
||||
|
||||
use smithay::{
|
||||
backend::input::{
|
||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
|
||||
KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,
|
||||
Switch, SwitchState, SwitchToggleEvent, TouchEvent,
|
||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, GestureBeginEvent,
|
||||
GestureEndEvent, GestureSwipeUpdateEvent as _, InputBackend, InputEvent, KeyState,
|
||||
KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent, Switch,
|
||||
SwitchState, SwitchToggleEvent, TouchEvent,
|
||||
},
|
||||
input::{
|
||||
keyboard::{FilterResult, keysyms},
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerHandle, RelativeMotionEvent},
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, MotionEvent, PointerHandle, RelativeMotionEvent,
|
||||
},
|
||||
touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent},
|
||||
},
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
@@ -43,6 +47,71 @@ impl Ferese {
|
||||
}
|
||||
|
||||
match event {
|
||||
InputEvent::GestureSwipeBegin { event } => {
|
||||
let pointer = seat.get_pointer().expect("seat has a pointer");
|
||||
self.swipe.begin(
|
||||
event.fingers(),
|
||||
self.swipe_navigation_blocked()
|
||||
|| !self
|
||||
.bindings
|
||||
.iter()
|
||||
.any(|binding| binding.swipe_fingers(event.fingers())),
|
||||
self.input_settings.touchpad.swipe_threshold,
|
||||
);
|
||||
if self.swipe.active() {
|
||||
self.focus_output_at(pointer.current_location());
|
||||
} else {
|
||||
pointer.gesture_swipe_begin(
|
||||
self,
|
||||
&GestureSwipeBeginEvent {
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: event.time() as u32,
|
||||
fingers: event.fingers(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
InputEvent::GestureSwipeUpdate { event } => {
|
||||
if self.swipe.active() {
|
||||
self.swipe.update(event.delta_x(), event.delta_y());
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
.expect("seat has a pointer")
|
||||
.gesture_swipe_update(
|
||||
self,
|
||||
&GestureSwipeUpdateEvent {
|
||||
time: event.time() as u32,
|
||||
delta: event.delta(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
InputEvent::GestureSwipeEnd { event } => {
|
||||
if self.swipe.active() {
|
||||
let fingers = self.swipe.fingers();
|
||||
let cancelled = event.cancelled() || self.swipe_navigation_blocked();
|
||||
if let Some(direction) = self.swipe.finish(cancelled)
|
||||
&& let Some(action) = self
|
||||
.bindings
|
||||
.iter()
|
||||
.find(|binding| binding.matches_swipe(fingers, direction))
|
||||
.map(|binding| binding.action.clone())
|
||||
{
|
||||
self.execute_binding(action);
|
||||
}
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
.expect("seat has a pointer")
|
||||
.gesture_swipe_end(
|
||||
self,
|
||||
&GestureSwipeEndEvent {
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: event.time() as u32,
|
||||
cancelled: event.cancelled(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
InputEvent::SwitchToggle { event } if event.switch() == Some(Switch::Lid) => {
|
||||
crate::backends::direct::set_lid_closed(self, event.state() == SwitchState::On);
|
||||
}
|
||||
@@ -387,6 +456,60 @@ impl Ferese {
|
||||
Some(event.position_transformed(geometry.size) + geometry.loc.to_f64())
|
||||
}
|
||||
|
||||
fn swipe_navigation_blocked(&self) -> bool {
|
||||
self.session_lock.active
|
||||
|| self.active_shortcuts_inhibitor.is_some()
|
||||
|| self
|
||||
.seat
|
||||
.get_pointer()
|
||||
.is_some_and(|pointer| pointer.is_grabbed())
|
||||
|| self
|
||||
.seat
|
||||
.get_keyboard()
|
||||
.is_some_and(|keyboard| keyboard.is_grabbed())
|
||||
}
|
||||
|
||||
fn switch_relative_workspace(&mut self, next: bool) {
|
||||
let Some(output) = self.output_workspaces.focused_output() else {
|
||||
return;
|
||||
};
|
||||
let Some(current) = self.output_workspaces.active_workspace(output) else {
|
||||
return;
|
||||
};
|
||||
let mut candidates = self
|
||||
.workspaces
|
||||
.iter()
|
||||
.filter(|workspace| {
|
||||
self.output_workspaces
|
||||
.output_for_workspace(workspace.id)
|
||||
.is_none_or(|owner| owner == output)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
candidates.sort_by_key(|workspace| {
|
||||
(
|
||||
workspace.name.parse::<u32>().unwrap_or(u32::MAX),
|
||||
workspace.id.0,
|
||||
)
|
||||
});
|
||||
let Some(index) = candidates
|
||||
.iter()
|
||||
.position(|workspace| workspace.id == current)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let index = if next {
|
||||
index.checked_add(1)
|
||||
} else {
|
||||
index.checked_sub(1)
|
||||
};
|
||||
if let Some(workspace) = index
|
||||
.and_then(|index| candidates.get(index))
|
||||
.map(|workspace| workspace.id)
|
||||
{
|
||||
self.activate_managed_workspace(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_window_at(&mut self, position: Point<f64, Logical>, serial: Serial, raise: bool) {
|
||||
if self.session_lock.active {
|
||||
self.focus_output_at(position);
|
||||
@@ -562,6 +685,7 @@ impl Ferese {
|
||||
|
||||
fn execute_binding(&mut self, action: BindingAction) {
|
||||
match action {
|
||||
BindingAction::None => {}
|
||||
BindingAction::Spawn(mut argv) => {
|
||||
let program = argv.remove(0);
|
||||
|
||||
@@ -584,6 +708,7 @@ impl Ferese {
|
||||
BindingAction::Focus(direction) => self.focus_direction(direction),
|
||||
BindingAction::Move(direction) => self.move_direction(direction),
|
||||
BindingAction::Resize(direction) => self.resize_direction(direction),
|
||||
BindingAction::SwitchRelativeWorkspace(next) => self.switch_relative_workspace(next),
|
||||
BindingAction::SwitchWorkspace(workspace) => {
|
||||
self.switch_workspace(u32::from(workspace));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ mod cursor;
|
||||
mod daemon;
|
||||
mod dimming;
|
||||
mod effects;
|
||||
mod gestures;
|
||||
mod grabs;
|
||||
mod handlers;
|
||||
mod input;
|
||||
|
||||
+15
-14
@@ -167,6 +167,7 @@ pub struct Ferese {
|
||||
pub(crate) cursor_theme: xcursor::CursorTheme,
|
||||
pub(crate) named_cursors: HashMap<CursorIcon, crate::cursor::NamedCursor>,
|
||||
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
|
||||
pub(crate) swipe: crate::gestures::Swipe,
|
||||
pub idle_inhibitors: HashMap<WlSurface, usize>,
|
||||
pub active_shortcuts_inhibitor: Option<KeyboardShortcutsInhibitor>,
|
||||
pub direct_backend: Option<crate::backends::direct::DirectBackendState>,
|
||||
@@ -640,6 +641,7 @@ impl Ferese {
|
||||
cursor_theme,
|
||||
named_cursors,
|
||||
intercepted_keys: HashSet::new(),
|
||||
swipe: crate::gestures::Swipe::default(),
|
||||
idle_inhibitors: HashMap::new(),
|
||||
active_shortcuts_inhibitor: None,
|
||||
direct_backend: None,
|
||||
@@ -682,6 +684,11 @@ impl Ferese {
|
||||
xdg_shell_state,
|
||||
xdg_toplevel_icon_manager,
|
||||
};
|
||||
// Keep the numbered shortcuts and bar selectors available from startup,
|
||||
// including empty workspaces that have not been assigned to an output.
|
||||
for index in 1..=9 {
|
||||
state.workspaces.ensure_numeric(index)?;
|
||||
}
|
||||
state._ipc_socket = Some(crate::ipc::init(event_loop)?);
|
||||
Ok(state)
|
||||
}
|
||||
@@ -2364,25 +2371,19 @@ impl Ferese {
|
||||
|
||||
let output = self
|
||||
.output_workspaces
|
||||
.output_for_workspace(workspace)
|
||||
.or_else(|| self.output_workspaces.focused_output());
|
||||
.focused_output()
|
||||
.or_else(|| self.output_workspaces.output_for_workspace(workspace));
|
||||
let Some(output) = output else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if self
|
||||
.output_workspaces
|
||||
.output_for_workspace(workspace)
|
||||
.is_none()
|
||||
&& self
|
||||
.output_workspaces
|
||||
.assign_workspace(output, workspace)
|
||||
.is_err()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let owner = match self.output_workspaces.switch_workspace(output, workspace) {
|
||||
Ok(ferese_core::WorkspaceSwitch::Activated(output))
|
||||
| Ok(ferese_core::WorkspaceSwitch::FocusedExisting(output)) => output,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
self.activate_output_workspace(output, workspace);
|
||||
self.activate_output_workspace(owner, workspace);
|
||||
self.relayout();
|
||||
self.restore_keyboard_focus();
|
||||
true
|
||||
|
||||
Reference in new issue
Block a user