Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/input/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ impl State {
&mut self.common.workspace_state.update(),
);
}
SwipeAction::ToggleWorkspaceOverview => {
if let Some(command) = self
.common
.config
.system_actions
.get(&shortcuts::action::System::WorkspaceOverview)
{
self.spawn_command(command.clone());
}
}
SwipeAction::ToggleAppLibrary => {
if let Some(command) = self
.common
.config
.system_actions
.get(&shortcuts::action::System::AppLibrary)
{
self.spawn_command(command.clone());
}
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/input/gestures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct SwipeEvent {
pub enum SwipeAction {
NextWorkspace,
PrevWorkspace,
ToggleWorkspaceOverview,
ToggleAppLibrary,
}

#[derive(Debug, Clone)]
Expand Down
161 changes: 126 additions & 35 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ use crate::{
},
zoom::ZoomState,
},
utils::{float::NextDown, prelude::*, quirks::workspace_overview_is_open},
utils::{
float::NextDown,
prelude::*,
quirks::{app_library_is_open, workspace_overview_is_open},
},
wayland::{
handlers::{screencopy::SessionHolder, xwayland_keyboard_grab::XWaylandGrabSeat},
protocols::screencopy::{BufferConstraints, CursorSessionRef},
Expand Down Expand Up @@ -964,7 +968,9 @@ impl State {
.cloned();
if let Some(seat) = maybe_seat {
self.common.idle_notifier_state.notify_activity(&seat);
if event.fingers() >= 3 && !workspace_overview_is_open(&seat.active_output()) {
// Allow 4-finger gestures even when overview is open (for closing it)
// Allow 3+ finger gestures when overview is not open
if event.fingers() >= 3 {
self.common.gesture_state = Some(GestureState::new(event.fingers()));
} else {
let serial = SERIAL_COUNTER.next_serial();
Expand Down Expand Up @@ -1006,47 +1012,86 @@ impl State {
natural_scroll = natural;
}
}
let overview_is_open =
workspace_overview_is_open(&seat.active_output());
let app_library_is_open = app_library_is_open(&seat.active_output());

// Helper to determine action for overview/app library toggle gestures
let overview_or_library_action =
|is_overview_direction: bool| -> Option<SwipeAction> {
if is_overview_direction {
// Swipe in "overview direction" closes app library or opens overview
if app_library_is_open {
Some(SwipeAction::ToggleAppLibrary)
} else if !overview_is_open {
Some(SwipeAction::ToggleWorkspaceOverview)
} else {
None
}
} else {
// Swipe in "app library direction" closes overview or opens app library
if overview_is_open {
Some(SwipeAction::ToggleWorkspaceOverview)
} else if !app_library_is_open {
Some(SwipeAction::ToggleAppLibrary)
} else {
None
}
}
};

activate_action = match gesture_state.fingers {
3 => None, // TODO: 3 finger gestures
4 => {
if self.common.config.cosmic_conf.workspaces.workspace_layout
== WorkspaceLayout::Horizontal
{
match gesture_state.direction {
Some(Direction::Left) => {
if natural_scroll {
Some(SwipeAction::NextWorkspace)
} else {
Some(SwipeAction::PrevWorkspace)
}
let is_horizontal =
self.common.config.cosmic_conf.workspaces.workspace_layout
== WorkspaceLayout::Horizontal;

match gesture_state.direction {
// Vertical gestures in horizontal layout, or horizontal gestures in vertical layout
Some(Direction::Up) if is_horizontal => {
overview_or_library_action(natural_scroll)
}
Some(Direction::Down) if is_horizontal => {
overview_or_library_action(!natural_scroll)
}
Some(Direction::Left) if !is_horizontal => {
overview_or_library_action(natural_scroll)
}
Some(Direction::Right) if !is_horizontal => {
overview_or_library_action(!natural_scroll)
}
// Horizontal gestures in horizontal layout = workspace switching
Some(Direction::Left) if is_horizontal => {
if natural_scroll {
Some(SwipeAction::NextWorkspace)
} else {
Some(SwipeAction::PrevWorkspace)
}
Some(Direction::Right) => {
if natural_scroll {
Some(SwipeAction::PrevWorkspace)
} else {
Some(SwipeAction::NextWorkspace)
}
}
Some(Direction::Right) if is_horizontal => {
if natural_scroll {
Some(SwipeAction::PrevWorkspace)
} else {
Some(SwipeAction::NextWorkspace)
}
_ => None, // TODO: Other actions
}
} else {
match gesture_state.direction {
Some(Direction::Up) => {
if natural_scroll {
Some(SwipeAction::NextWorkspace)
} else {
Some(SwipeAction::PrevWorkspace)
}
// Vertical gestures in vertical layout = workspace switching
Some(Direction::Up) if !is_horizontal => {
if natural_scroll {
Some(SwipeAction::NextWorkspace)
} else {
Some(SwipeAction::PrevWorkspace)
}
Some(Direction::Down) => {
if natural_scroll {
Some(SwipeAction::PrevWorkspace)
} else {
Some(SwipeAction::NextWorkspace)
}
}
Some(Direction::Down) if !is_horizontal => {
if natural_scroll {
Some(SwipeAction::PrevWorkspace)
} else {
Some(SwipeAction::NextWorkspace)
}
_ => None, // TODO: Other actions
}
_ => None,
}
}
_ => None,
Expand Down Expand Up @@ -1075,8 +1120,15 @@ impl State {
);
}

// Only trigger workspace switching during updates
// Other actions (overview, app library) are triggered on gesture end
if let Some(action) = activate_action {
self.handle_swipe_action(action, &seat);
match action {
SwipeAction::NextWorkspace | SwipeAction::PrevWorkspace => {
self.handle_swipe_action(action, &seat);
}
_ => {}
}
}
}
}
Expand Down Expand Up @@ -1108,6 +1160,45 @@ impl State {
&mut self.common.workspace_state.update(),
);
}
Some(SwipeAction::ToggleWorkspaceOverview)
| Some(SwipeAction::ToggleAppLibrary) => {
// For overview and app library actions, check if gesture was completed
// (not cancelled and sufficient delta/velocity)
if !event.cancelled() {
let velocity = gesture_state.velocity();
let delta = gesture_state.delta.abs();

// Use thresholds similar to workspace switching
let output_size = if self
.common
.config
.cosmic_conf
.workspaces
.workspace_layout
== WorkspaceLayout::Horizontal
{
seat.active_output().geometry().size.h as f64
} else {
seat.active_output().geometry().size.w as f64
};

let norm_velocity = velocity / output_size;
let norm_delta = delta / output_size;

// Trigger action if sufficient movement or velocity
const POSITION_THRESHOLD: f64 = 0.15;
const VELOCITY_THRESHOLD: f64 = 0.0005;

if norm_delta >= POSITION_THRESHOLD
|| norm_velocity.abs() >= VELOCITY_THRESHOLD
{
self.handle_swipe_action(
gesture_state.action.unwrap(),
&seat,
);
}
}
}
_ => {}
}
self.common.gesture_state = None;
Expand Down
10 changes: 10 additions & 0 deletions src/utils/quirks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@ use smithay::{desktop::layer_map_for_output, output::Output};
// TODO: Avoid special case, or add protocol to expose required behavior
pub const WORKSPACE_OVERVIEW_NAMESPACE: &str = "cosmic-workspace-overview";

/// Layer shell namespace used by `cosmic-applibrary`
pub const APP_LIBRARY_NAMESPACE: &str = "app-library";

/// Check if a workspace overview shell surface is open on the output
pub fn workspace_overview_is_open(output: &Output) -> bool {
layer_map_for_output(output)
.layers()
.any(|s| s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE)
}

/// Check if an app library shell surface is open on the output
pub fn app_library_is_open(output: &Output) -> bool {
layer_map_for_output(output)
.layers()
.any(|s| s.namespace() == APP_LIBRARY_NAMESPACE)
}