1096 lines
44 KiB
Rust
1096 lines
44 KiB
Rust
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
use crate::{
|
|
config::{Action, PrivateAction},
|
|
shell::{
|
|
focus::target::KeyboardFocusTarget, layout::tiling::SwapWindowGrab, FocusResult,
|
|
InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
|
|
},
|
|
utils::prelude::*,
|
|
wayland::{
|
|
handlers::xdg_activation::ActivationContext, protocols::workspace::WorkspaceUpdateGuard,
|
|
},
|
|
};
|
|
use cosmic_comp_config::{workspace::WorkspaceLayout, TileBehavior};
|
|
use cosmic_config::ConfigSet;
|
|
use cosmic_settings_config::shortcuts;
|
|
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection};
|
|
use smithay::{
|
|
input::{pointer::MotionEvent, Seat},
|
|
utils::{Point, Serial},
|
|
};
|
|
#[cfg(not(feature = "debug"))]
|
|
use tracing::info;
|
|
use tracing::{error, warn};
|
|
|
|
use std::{os::unix::process::CommandExt, thread};
|
|
|
|
use super::gestures;
|
|
|
|
fn propagate_by_default(action: &shortcuts::Action) -> bool {
|
|
match action {
|
|
shortcuts::Action::Focus(_) | shortcuts::Action::Move(_) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
impl State {
|
|
pub fn handle_action(
|
|
&mut self,
|
|
action: Action,
|
|
seat: &Seat<State>,
|
|
serial: Serial,
|
|
time: u32,
|
|
pattern: shortcuts::Binding,
|
|
direction: Option<Direction>,
|
|
) {
|
|
// TODO: Detect if started from login manager or tty, and only allow
|
|
// `Terminate` if it will return to login manager.
|
|
if self.common.shell.read().session_lock.is_some()
|
|
&& !matches!(
|
|
action,
|
|
Action::Shortcut(shortcuts::Action::Terminate)
|
|
| Action::Shortcut(shortcuts::Action::Debug)
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
match action {
|
|
Action::Shortcut(action) => {
|
|
let propagate = propagate_by_default(&action);
|
|
self.handle_shortcut_action(
|
|
action, seat, serial, time, pattern, direction, propagate,
|
|
)
|
|
}
|
|
Action::Private(PrivateAction::Escape) => {
|
|
{
|
|
let mut shell = self.common.shell.write();
|
|
shell.set_overview_mode(None, self.common.event_loop_handle.clone());
|
|
shell.set_resize_mode(
|
|
None,
|
|
&self.common.config,
|
|
self.common.event_loop_handle.clone(),
|
|
);
|
|
}
|
|
let pointer = seat.get_pointer().unwrap();
|
|
let keyboard = seat.get_keyboard().unwrap();
|
|
if pointer.is_grabbed() {
|
|
pointer.unset_grab(self, serial, time);
|
|
}
|
|
if keyboard.is_grabbed() {
|
|
keyboard.unset_grab(self);
|
|
}
|
|
}
|
|
|
|
Action::Private(PrivateAction::Resizing(direction, edge, state)) => {
|
|
if state == shortcuts::State::Pressed {
|
|
self.common
|
|
.shell
|
|
.write()
|
|
.resize(seat, direction, edge.into());
|
|
} else {
|
|
self.common
|
|
.shell
|
|
.write()
|
|
.finish_resize(direction, edge.into());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn handle_swipe_action(&mut self, action: gestures::SwipeAction, seat: &Seat<State>) {
|
|
use gestures::SwipeAction;
|
|
|
|
match action {
|
|
SwipeAction::NextWorkspace => {
|
|
let _ = to_next_workspace(
|
|
&mut *self.common.shell.write(),
|
|
&seat,
|
|
true,
|
|
&mut self.common.workspace_state.update(),
|
|
);
|
|
}
|
|
SwipeAction::PrevWorkspace => {
|
|
let _ = to_previous_workspace(
|
|
&mut *self.common.shell.write(),
|
|
&seat,
|
|
true,
|
|
&mut self.common.workspace_state.update(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[profiling::function]
|
|
pub fn handle_shortcut_action(
|
|
&mut self,
|
|
action: shortcuts::Action,
|
|
seat: &Seat<State>,
|
|
serial: Serial,
|
|
time: u32,
|
|
pattern: shortcuts::Binding,
|
|
direction: Option<Direction>,
|
|
propagate: bool,
|
|
) {
|
|
use shortcuts::Action;
|
|
|
|
match action {
|
|
Action::Terminate => {
|
|
self.common.should_stop = true;
|
|
}
|
|
|
|
#[cfg(feature = "debug")]
|
|
Action::Debug => {
|
|
let mut shell = self.common.shell.write();
|
|
shell.debug_active = !shell.debug_active;
|
|
for mapped in shell.workspaces.spaces().flat_map(|w| w.mapped()) {
|
|
mapped.set_debug(shell.debug_active);
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "debug"))]
|
|
Action::Debug => {
|
|
info!("Debug overlay not included in this build.")
|
|
}
|
|
|
|
Action::Close => {
|
|
if let Some(focus_target) = seat.get_keyboard().unwrap().current_focus() {
|
|
self.common.shell.read().close_focused(&focus_target);
|
|
}
|
|
}
|
|
|
|
Action::Workspace(key_num) => {
|
|
let current_output = seat.active_output();
|
|
let workspace = match key_num {
|
|
0 => 9,
|
|
x => x - 1,
|
|
};
|
|
let _ = self.common.shell.write().activate(
|
|
¤t_output,
|
|
workspace as usize,
|
|
WorkspaceDelta::new_shortcut(),
|
|
&mut self.common.workspace_state.update(),
|
|
);
|
|
}
|
|
|
|
Action::LastWorkspace => {
|
|
let current_output = seat.active_output();
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.workspaces.len(¤t_output).saturating_sub(1);
|
|
let _ = shell.activate(
|
|
¤t_output,
|
|
workspace,
|
|
WorkspaceDelta::new_shortcut(),
|
|
&mut self.common.workspace_state.update(),
|
|
);
|
|
}
|
|
|
|
Action::NextWorkspace => {
|
|
if let Some(direction) = pattern.inferred_direction() {
|
|
if ((direction == Direction::Left || direction == Direction::Right)
|
|
&& self.common.config.cosmic_conf.workspaces.workspace_layout
|
|
== WorkspaceLayout::Vertical)
|
|
|| ((direction == Direction::Up || direction == Direction::Down)
|
|
&& self.common.config.cosmic_conf.workspaces.workspace_layout
|
|
== WorkspaceLayout::Horizontal)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
let next = to_next_workspace(
|
|
&mut *self.common.shell.write(),
|
|
seat,
|
|
false,
|
|
&mut self.common.workspace_state.update(),
|
|
);
|
|
if next.is_err() {
|
|
if propagate {
|
|
if let Some(inferred) = pattern.inferred_direction() {
|
|
self.handle_shortcut_action(
|
|
Action::SwitchOutput(inferred),
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
direction,
|
|
true,
|
|
)
|
|
};
|
|
} else {
|
|
self.handle_shortcut_action(
|
|
Action::Workspace(1),
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
direction,
|
|
false,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
Action::PreviousWorkspace => {
|
|
if let Some(direction) = pattern.inferred_direction() {
|
|
if ((direction == Direction::Left || direction == Direction::Right)
|
|
&& self.common.config.cosmic_conf.workspaces.workspace_layout
|
|
== WorkspaceLayout::Vertical)
|
|
|| ((direction == Direction::Up || direction == Direction::Down)
|
|
&& self.common.config.cosmic_conf.workspaces.workspace_layout
|
|
== WorkspaceLayout::Horizontal)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
let previous = to_previous_workspace(
|
|
&mut *self.common.shell.write(),
|
|
seat,
|
|
false,
|
|
&mut self.common.workspace_state.update(),
|
|
);
|
|
if previous.is_err() {
|
|
if propagate {
|
|
if let Some(inferred) = pattern.inferred_direction() {
|
|
self.handle_shortcut_action(
|
|
Action::SwitchOutput(inferred),
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
direction,
|
|
true,
|
|
)
|
|
};
|
|
} else {
|
|
self.handle_shortcut_action(
|
|
Action::LastWorkspace,
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
direction,
|
|
false,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
x @ Action::MoveToWorkspace(_) | x @ Action::SendToWorkspace(_) => {
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
let follow = matches!(x, Action::MoveToWorkspace(_));
|
|
let workspace = match x {
|
|
Action::MoveToWorkspace(0) | Action::SendToWorkspace(0) => 9,
|
|
Action::MoveToWorkspace(x) | Action::SendToWorkspace(x) => x - 1,
|
|
_ => unreachable!(),
|
|
};
|
|
let res = self.common.shell.write().move_current_window(
|
|
seat,
|
|
&focused_output,
|
|
(&focused_output, Some(workspace as usize)),
|
|
follow,
|
|
None,
|
|
&mut self.common.workspace_state.update(),
|
|
);
|
|
if let Ok(Some((target, _point))) = res {
|
|
Shell::set_focus(
|
|
self,
|
|
Some(&target),
|
|
seat,
|
|
None,
|
|
matches!(x, Action::MoveToWorkspace(_)),
|
|
);
|
|
}
|
|
}
|
|
|
|
x @ Action::MoveToLastWorkspace | x @ Action::SendToLastWorkspace => {
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.workspaces.len(&focused_output).saturating_sub(1);
|
|
let res = shell.move_current_window(
|
|
seat,
|
|
&focused_output,
|
|
(&focused_output, Some(workspace)),
|
|
matches!(x, Action::MoveToLastWorkspace),
|
|
None,
|
|
&mut self.common.workspace_state.update(),
|
|
);
|
|
// If the active workspace changed, the cursor_follows_focus should probably be checked
|
|
if let Ok(Some((target, _point))) = res {
|
|
std::mem::drop(shell);
|
|
Shell::set_focus(
|
|
self,
|
|
Some(&target),
|
|
seat,
|
|
None,
|
|
matches!(x, Action::MoveToLastWorkspace),
|
|
);
|
|
}
|
|
}
|
|
|
|
x @ Action::MoveToNextWorkspace | x @ Action::SendToNextWorkspace => {
|
|
if let Some(direction) = pattern.inferred_direction() {
|
|
if ((direction == Direction::Left || direction == Direction::Right)
|
|
&& self.common.config.cosmic_conf.workspaces.workspace_layout
|
|
== WorkspaceLayout::Vertical)
|
|
|| ((direction == Direction::Up || direction == Direction::Down)
|
|
&& self.common.config.cosmic_conf.workspaces.workspace_layout
|
|
== WorkspaceLayout::Horizontal)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
|
|
let res = {
|
|
let mut shell = self.common.shell.write();
|
|
shell
|
|
.workspaces
|
|
.active_num(&focused_output)
|
|
.1
|
|
.checked_add(1)
|
|
.ok_or(InvalidWorkspaceIndex)
|
|
.and_then(|workspace| {
|
|
shell.move_current_window(
|
|
seat,
|
|
&focused_output,
|
|
(&focused_output, Some(workspace)),
|
|
matches!(x, Action::MoveToNextWorkspace),
|
|
direction,
|
|
&mut self.common.workspace_state.update(),
|
|
)
|
|
})
|
|
};
|
|
|
|
match res {
|
|
Ok(Some((target, _point))) => {
|
|
// If the active workspace changed, the cursor_follows_focus should probably be checked
|
|
Shell::set_focus(
|
|
self,
|
|
Some(&target),
|
|
seat,
|
|
None,
|
|
matches!(x, Action::MoveToNextWorkspace),
|
|
);
|
|
}
|
|
Ok(None) => {}
|
|
Err(_) if propagate => {
|
|
if let Some(inferred) = pattern.inferred_direction() {
|
|
self.handle_shortcut_action(
|
|
if matches!(x, Action::MoveToNextWorkspace) {
|
|
Action::MoveToOutput(inferred)
|
|
} else {
|
|
Action::SendToOutput(inferred)
|
|
},
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
direction,
|
|
true,
|
|
)
|
|
}
|
|
}
|
|
Err(_) => {
|
|
// cycle through
|
|
self.handle_shortcut_action(
|
|
if matches!(x, Action::MoveToNextWorkspace) {
|
|
Action::MoveToWorkspace(1)
|
|
} else {
|
|
Action::SendToWorkspace(1)
|
|
},
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
direction,
|
|
false,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
x @ Action::MoveToPreviousWorkspace | x @ Action::SendToPreviousWorkspace => {
|
|
if let Some(direction) = pattern.inferred_direction() {
|
|
if ((direction == Direction::Left || direction == Direction::Right)
|
|
&& self.common.config.cosmic_conf.workspaces.workspace_layout
|
|
== WorkspaceLayout::Vertical)
|
|
|| ((direction == Direction::Up || direction == Direction::Down)
|
|
&& self.common.config.cosmic_conf.workspaces.workspace_layout
|
|
== WorkspaceLayout::Horizontal)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
|
|
let res = {
|
|
let mut shell = self.common.shell.write();
|
|
shell
|
|
.workspaces
|
|
.active_num(&focused_output)
|
|
.1
|
|
.checked_sub(1)
|
|
.ok_or(InvalidWorkspaceIndex)
|
|
.and_then(|workspace| {
|
|
shell.move_current_window(
|
|
seat,
|
|
&focused_output,
|
|
(&focused_output, Some(workspace)),
|
|
matches!(x, Action::MoveToPreviousWorkspace),
|
|
direction,
|
|
&mut self.common.workspace_state.update(),
|
|
)
|
|
})
|
|
};
|
|
|
|
match res {
|
|
Ok(Some((target, _point))) => {
|
|
Shell::set_focus(
|
|
self,
|
|
Some(&target),
|
|
seat,
|
|
None,
|
|
matches!(x, Action::MoveToPreviousWorkspace),
|
|
);
|
|
}
|
|
Ok(None) => {}
|
|
Err(_) if propagate => {
|
|
if let Some(inferred) = pattern.inferred_direction() {
|
|
self.handle_shortcut_action(
|
|
if matches!(x, Action::MoveToPreviousWorkspace) {
|
|
Action::MoveToOutput(inferred)
|
|
} else {
|
|
Action::SendToOutput(inferred)
|
|
},
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
direction,
|
|
true,
|
|
)
|
|
}
|
|
}
|
|
Err(_) => {
|
|
// cycle through
|
|
self.handle_shortcut_action(
|
|
if matches!(x, Action::MoveToPreviousWorkspace) {
|
|
Action::MoveToLastWorkspace
|
|
} else {
|
|
Action::SendToLastWorkspace
|
|
},
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
direction,
|
|
false,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
Action::SwitchOutput(direction) => {
|
|
let current_output = seat.active_output();
|
|
let mut shell = self.common.shell.write();
|
|
|
|
let next_output = shell.next_output(¤t_output, direction).cloned();
|
|
|
|
if let Some(next_output) = next_output {
|
|
let res = {
|
|
let mut workspace_guard = self.common.workspace_state.update();
|
|
if propagate {
|
|
if let Some((serial, prev_output, prev_idx)) =
|
|
shell.previous_workspace_idx.take()
|
|
{
|
|
if seat.last_modifier_change().is_some_and(|s| s == serial)
|
|
&& prev_output == current_output
|
|
{
|
|
let _ = shell.activate(
|
|
¤t_output,
|
|
prev_idx,
|
|
WorkspaceDelta::new_shortcut(),
|
|
&mut workspace_guard,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
let idx = shell.workspaces.active_num(&next_output).1;
|
|
let res = shell.activate(
|
|
&next_output,
|
|
idx,
|
|
WorkspaceDelta::new_shortcut(),
|
|
&mut workspace_guard,
|
|
);
|
|
seat.set_active_output(&next_output);
|
|
res
|
|
};
|
|
|
|
if let Ok(Some(new_pos)) = res {
|
|
let new_target = shell
|
|
.workspaces
|
|
.active(&next_output)
|
|
.unwrap()
|
|
.1
|
|
.focus_stack
|
|
.get(&seat)
|
|
.last()
|
|
.cloned()
|
|
.map(KeyboardFocusTarget::from);
|
|
std::mem::drop(shell);
|
|
|
|
let move_cursor = if let Some(under) = new_target {
|
|
let update_cursor = self.common.config.cosmic_conf.focus_follows_cursor;
|
|
Shell::set_focus(self, Some(&under), seat, None, update_cursor);
|
|
!update_cursor
|
|
} else {
|
|
true
|
|
};
|
|
|
|
if let Some(ptr) = seat.get_pointer() {
|
|
if move_cursor {
|
|
ptr.motion(
|
|
self,
|
|
None,
|
|
&MotionEvent {
|
|
location: new_pos.to_f64().as_logical(),
|
|
serial,
|
|
time,
|
|
},
|
|
);
|
|
}
|
|
ptr.frame(self);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(deprecated)]
|
|
Action::NextOutput => {
|
|
warn!("Skipping deprecated shortcut NextOutput");
|
|
}
|
|
|
|
#[allow(deprecated)]
|
|
Action::PreviousOutput => {
|
|
warn!("Skipping deprecated shortcut PreviousOutput");
|
|
}
|
|
|
|
action @ Action::MoveToOutput(_) | action @ Action::SendToOutput(_) => {
|
|
let is_move_action = matches!(action, Action::MoveToOutput(_));
|
|
let direction = match action {
|
|
Action::MoveToOutput(dir) => dir,
|
|
Action::SendToOutput(dir) => dir,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
let mut shell = self.common.shell.write();
|
|
let next_output = shell.next_output(&focused_output, direction).cloned();
|
|
|
|
if let Some(next_output) = next_output {
|
|
let res = {
|
|
let mut workspace_guard = self.common.workspace_state.update();
|
|
let res = shell.move_current_window(
|
|
seat,
|
|
&focused_output,
|
|
(&next_output, None),
|
|
is_move_action,
|
|
Some(direction),
|
|
&mut workspace_guard,
|
|
);
|
|
|
|
if is_move_action && propagate {
|
|
if let Some((_, prev_output, prev_idx)) =
|
|
shell.previous_workspace_idx.take()
|
|
{
|
|
if prev_output == focused_output {
|
|
let _ = shell.activate(
|
|
&focused_output,
|
|
prev_idx,
|
|
WorkspaceDelta::new_shortcut(),
|
|
&mut workspace_guard,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
res
|
|
};
|
|
|
|
if let Ok(Some((target, new_pos))) = res {
|
|
std::mem::drop(shell);
|
|
Shell::set_focus(self, Some(&target), seat, None, is_move_action);
|
|
if let Some(ptr) = seat.get_pointer() {
|
|
ptr.motion(
|
|
self,
|
|
None,
|
|
&MotionEvent {
|
|
location: new_pos.to_f64().as_logical(),
|
|
serial,
|
|
time,
|
|
},
|
|
);
|
|
ptr.frame(self);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(deprecated)]
|
|
Action::MoveToNextOutput | Action::SendToNextOutput => {
|
|
warn!("Ignoring deprecated action Move/SendToNextOutput");
|
|
}
|
|
|
|
#[allow(deprecated)]
|
|
Action::MoveToPreviousOutput | Action::SendToPreviousOutput => {
|
|
warn!("Ignoring deprecated action Move/SendToPreviousOutput");
|
|
}
|
|
|
|
Action::MigrateWorkspaceToOutput(direction) => {
|
|
let active_output = seat.active_output();
|
|
let (active, next_output) = {
|
|
let shell = self.common.shell.read();
|
|
|
|
(
|
|
shell.active_space(&active_output).unwrap().handle,
|
|
shell.next_output(&active_output, direction).cloned(),
|
|
)
|
|
};
|
|
|
|
if let Some(next_output) = next_output {
|
|
self.common
|
|
.migrate_workspace(&active_output, &next_output, &active);
|
|
}
|
|
}
|
|
|
|
#[allow(deprecated)]
|
|
Action::MigrateWorkspaceToNextOutput => {
|
|
warn!("Ignoring deprecated action MigrateWorkspaceToNextOutput");
|
|
}
|
|
|
|
#[allow(deprecated)]
|
|
Action::MigrateWorkspaceToPreviousOutput => {
|
|
warn!("Ignoring deprecated action MigrateWorkspaceToPreviousOutput");
|
|
}
|
|
|
|
Action::Focus(focus) => {
|
|
let result = self.common.shell.read().next_focus(focus, seat);
|
|
|
|
match result {
|
|
FocusResult::None => {
|
|
let dir = match focus {
|
|
FocusDirection::Down => Some(Direction::Down),
|
|
FocusDirection::Up => Some(Direction::Up),
|
|
FocusDirection::Left => Some(Direction::Left),
|
|
FocusDirection::Right => Some(Direction::Right),
|
|
_ => None,
|
|
};
|
|
|
|
if let Some(direction) = dir {
|
|
if let Some(last_mod_serial) = seat.last_modifier_change() {
|
|
let mut shell = self.common.shell.write();
|
|
if !shell
|
|
.previous_workspace_idx
|
|
.as_ref()
|
|
.is_some_and(|(serial, _, _)| *serial == last_mod_serial)
|
|
{
|
|
let current_output = seat.active_output();
|
|
let workspace_idx =
|
|
shell.workspaces.active_num(¤t_output).1;
|
|
shell.previous_workspace_idx = Some((
|
|
last_mod_serial,
|
|
current_output.downgrade(),
|
|
workspace_idx,
|
|
));
|
|
}
|
|
}
|
|
|
|
let action = match (
|
|
direction,
|
|
self.common.config.cosmic_conf.workspaces.workspace_layout,
|
|
) {
|
|
(Direction::Left, WorkspaceLayout::Horizontal)
|
|
| (Direction::Up, WorkspaceLayout::Vertical) => {
|
|
Action::PreviousWorkspace
|
|
}
|
|
(Direction::Right, WorkspaceLayout::Horizontal)
|
|
| (Direction::Down, WorkspaceLayout::Vertical) => {
|
|
Action::NextWorkspace
|
|
}
|
|
_ => Action::SwitchOutput(direction),
|
|
};
|
|
|
|
self.handle_shortcut_action(
|
|
action,
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
Some(direction),
|
|
true,
|
|
)
|
|
}
|
|
}
|
|
FocusResult::Handled => {}
|
|
FocusResult::Some(target) => {
|
|
Shell::set_focus(self, Some(&target), seat, None, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
Action::Move(direction) => {
|
|
let res = self
|
|
.common
|
|
.shell
|
|
.write()
|
|
.move_current_element(direction, seat);
|
|
match res {
|
|
MoveResult::MoveFurther(_move_further) => {
|
|
if let Some(last_mod_serial) = seat.last_modifier_change() {
|
|
let mut shell = self.common.shell.write();
|
|
if !shell
|
|
.previous_workspace_idx
|
|
.as_ref()
|
|
.is_some_and(|(serial, _, _)| *serial == last_mod_serial)
|
|
{
|
|
let current_output = seat.active_output();
|
|
let workspace_idx = shell.workspaces.active_num(¤t_output).1;
|
|
shell.previous_workspace_idx = Some((
|
|
last_mod_serial,
|
|
current_output.downgrade(),
|
|
workspace_idx,
|
|
));
|
|
}
|
|
}
|
|
|
|
let action = match (
|
|
direction,
|
|
self.common.config.cosmic_conf.workspaces.workspace_layout,
|
|
) {
|
|
(Direction::Left, WorkspaceLayout::Horizontal)
|
|
| (Direction::Up, WorkspaceLayout::Vertical) => {
|
|
Action::MoveToPreviousWorkspace
|
|
}
|
|
(Direction::Right, WorkspaceLayout::Horizontal)
|
|
| (Direction::Down, WorkspaceLayout::Vertical) => {
|
|
Action::MoveToNextWorkspace
|
|
}
|
|
_ => Action::MoveToOutput(direction),
|
|
};
|
|
|
|
self.handle_shortcut_action(
|
|
action,
|
|
seat,
|
|
serial,
|
|
time,
|
|
pattern,
|
|
Some(direction),
|
|
true,
|
|
)
|
|
}
|
|
MoveResult::ShiftFocus(shift) => {
|
|
Shell::set_focus(self, Some(&shift), seat, None, true);
|
|
}
|
|
_ => {
|
|
let current_output = seat.active_output();
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.active_space(¤t_output).unwrap();
|
|
if let Some(focused_window) = workspace.focus_stack.get(seat).last() {
|
|
if workspace.is_tiled(focused_window) {
|
|
shell.set_overview_mode(
|
|
Some(Trigger::KeyboardMove(pattern.modifiers)),
|
|
self.common.event_loop_handle.clone(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Action::SwapWindow => {
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
let mut shell = self.common.shell.write();
|
|
|
|
let workspace = shell.active_space_mut(&focused_output).unwrap();
|
|
if workspace.get_fullscreen().is_some() {
|
|
return; // TODO, is this what we want? Maybe disengage fullscreen instead?
|
|
}
|
|
|
|
let keyboard_handle = seat.get_keyboard().unwrap();
|
|
if let Some(focus) = keyboard_handle.current_focus() {
|
|
if let Some(descriptor) = workspace.node_desc(focus) {
|
|
let grab = SwapWindowGrab::new(seat.clone(), descriptor.clone());
|
|
drop(shell);
|
|
keyboard_handle.set_grab(self, grab, serial);
|
|
let mut shell = self.common.shell.write();
|
|
shell.set_overview_mode(
|
|
Some(Trigger::KeyboardSwap(pattern, descriptor)),
|
|
self.common.event_loop_handle.clone(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Action::Minimize => {
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.active_space_mut(&focused_output).unwrap();
|
|
let focus_stack = workspace.focus_stack.get(seat);
|
|
let focused_window = focus_stack.last().cloned();
|
|
if let Some(window) = focused_window {
|
|
shell.minimize_request(&window);
|
|
}
|
|
}
|
|
|
|
Action::Maximize => {
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.active_space(&focused_output).unwrap();
|
|
let focus_stack = workspace.focus_stack.get(seat);
|
|
let focused_window = focus_stack.last().cloned();
|
|
if let Some(window) = focused_window {
|
|
shell.maximize_toggle(&window, seat);
|
|
}
|
|
}
|
|
|
|
Action::Resizing(direction) => self.common.shell.write().set_resize_mode(
|
|
Some((pattern, direction)),
|
|
&self.common.config,
|
|
self.common.event_loop_handle.clone(),
|
|
),
|
|
// NOTE: implementation currently assumes actions that apply to outputs should apply to the active output
|
|
// rather than the output that has keyboard focus
|
|
Action::ToggleOrientation => {
|
|
let output = seat.active_output();
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.active_space_mut(&output).unwrap();
|
|
workspace.tiling_layer.update_orientation(None, &seat);
|
|
}
|
|
|
|
Action::Orientation(orientation) => {
|
|
let output = seat.active_output();
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.active_space_mut(&output).unwrap();
|
|
workspace
|
|
.tiling_layer
|
|
.update_orientation(Some(orientation), &seat);
|
|
}
|
|
|
|
Action::ToggleStacking => {
|
|
let res = self.common.shell.write().toggle_stacking_focused(seat);
|
|
if let Some(new_focus) = res {
|
|
Shell::set_focus(self, Some(&new_focus), seat, Some(serial), false);
|
|
}
|
|
}
|
|
|
|
Action::ToggleTiling => {
|
|
if matches!(
|
|
self.common.config.cosmic_conf.autotile_behavior,
|
|
TileBehavior::Global
|
|
) {
|
|
let autotile = !self.common.config.cosmic_conf.autotile;
|
|
self.common.config.cosmic_conf.autotile = autotile;
|
|
|
|
{
|
|
let mut shell = self.common.shell.write();
|
|
let shell_ref = &mut *shell;
|
|
shell_ref.workspaces.update_autotile(
|
|
self.common.config.cosmic_conf.autotile,
|
|
&mut self.common.workspace_state.update(),
|
|
shell_ref.seats.iter(),
|
|
);
|
|
}
|
|
let config = self.common.config.cosmic_helper.clone();
|
|
thread::spawn(move || {
|
|
if let Err(err) = config.set("autotile", autotile) {
|
|
error!(?err, "Failed to update autotile key");
|
|
}
|
|
});
|
|
} else {
|
|
let output = seat.active_output();
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.workspaces.active_mut(&output).unwrap();
|
|
let mut guard = self.common.workspace_state.update();
|
|
workspace.toggle_tiling(seat, &mut guard);
|
|
}
|
|
}
|
|
|
|
Action::ToggleWindowFloating => {
|
|
let Some(output) = seat.focused_output() else {
|
|
return;
|
|
};
|
|
let mut shell = self.common.shell.write();
|
|
let workspace = shell.active_space_mut(&output).unwrap();
|
|
workspace.toggle_floating_window_focused(seat);
|
|
}
|
|
|
|
Action::ToggleSticky => {
|
|
self.common.shell.write().toggle_sticky_current(seat);
|
|
}
|
|
|
|
// Gets the configured command for a given system action.
|
|
Action::System(system) => {
|
|
if let Some(command) = self.common.config.system_actions.get(&system) {
|
|
self.spawn_command(command.clone());
|
|
}
|
|
}
|
|
|
|
Action::Spawn(command) => self.spawn_command(command),
|
|
|
|
x @ Action::ZoomIn | x @ Action::ZoomOut => {
|
|
let change = {
|
|
let increment =
|
|
self.common.config.cosmic_conf.accessibility_zoom.increment as f64 / 100.0;
|
|
match x {
|
|
Action::ZoomIn => increment,
|
|
Action::ZoomOut => -increment,
|
|
_ => unreachable!(),
|
|
}
|
|
};
|
|
|
|
self.update_zoom(seat, change, true);
|
|
}
|
|
|
|
// Do nothing
|
|
Action::Disable => (),
|
|
}
|
|
}
|
|
|
|
pub fn spawn_command(&mut self, command: String) {
|
|
let mut shell = self.common.shell.write();
|
|
|
|
let (token, data) = self.common.xdg_activation_state.create_external_token(None);
|
|
let (token, data) = (token.clone(), data.clone());
|
|
|
|
let output = shell.seats.last_active().active_output();
|
|
let workspace = shell.active_space_mut(&output).unwrap();
|
|
let handle = workspace.handle;
|
|
std::mem::drop(shell);
|
|
data.user_data
|
|
.insert_if_missing(move || ActivationContext::Workspace(handle));
|
|
|
|
let wayland_display = self.common.socket.clone();
|
|
let display = self
|
|
.common
|
|
.xwayland_state
|
|
.as_ref()
|
|
.map(|s| format!(":{}", s.display))
|
|
.unwrap_or_default();
|
|
|
|
let mut cmd = std::process::Command::new("/bin/sh");
|
|
|
|
cmd.arg("-c")
|
|
.arg(&command)
|
|
.env("WAYLAND_DISPLAY", &wayland_display)
|
|
.env("DISPLAY", &display)
|
|
.env("XDG_ACTIVATION_TOKEN", &*token)
|
|
.env("DESKTOP_STARTUP_ID", &*token)
|
|
.env_remove("COSMIC_SESSION_SOCK");
|
|
unsafe { cmd.pre_exec(|| Ok(crate::utils::rlimit::restore_nofile_limit())) };
|
|
|
|
std::thread::spawn(move || match cmd.spawn() {
|
|
Ok(mut child) => {
|
|
let _res = child.wait();
|
|
}
|
|
Err(err) => {
|
|
tracing::warn!(?err, "Failed to spawn \"{}\"", command);
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn update_zoom(&mut self, seat: &Seat<State>, change: f64, animate: bool) {
|
|
let output = seat.active_output();
|
|
let mut shell = self.common.shell.write();
|
|
let (zoom_seat, current_level) = shell
|
|
.zoom_state()
|
|
.map(|state| (state.current_seat(), state.current_level(&output)))
|
|
.unwrap_or_else(|| (seat.clone(), 1.0));
|
|
|
|
if current_level == 1. && change <= 0. {
|
|
return;
|
|
}
|
|
|
|
if zoom_seat == *seat {
|
|
let new_level = (current_level + change).max(1.0);
|
|
shell.trigger_zoom(
|
|
&seat,
|
|
Some(&output),
|
|
new_level,
|
|
&self.common.config.cosmic_conf.accessibility_zoom,
|
|
animate,
|
|
&self.common.event_loop_handle,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn to_next_workspace(
|
|
shell: &mut Shell,
|
|
seat: &Seat<State>,
|
|
gesture: bool,
|
|
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
|
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
|
|
let current_output = seat.active_output();
|
|
let workspace = shell
|
|
.workspaces
|
|
.active_num(¤t_output)
|
|
.1
|
|
.checked_add(1)
|
|
.ok_or(InvalidWorkspaceIndex)?;
|
|
|
|
shell.activate(
|
|
¤t_output,
|
|
workspace,
|
|
if gesture {
|
|
WorkspaceDelta::new_gesture()
|
|
} else {
|
|
WorkspaceDelta::new_shortcut()
|
|
},
|
|
workspace_state,
|
|
)
|
|
}
|
|
|
|
fn to_previous_workspace(
|
|
shell: &mut Shell,
|
|
seat: &Seat<State>,
|
|
gesture: bool,
|
|
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
|
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
|
|
let current_output = seat.active_output();
|
|
let workspace = shell
|
|
.workspaces
|
|
.active_num(¤t_output)
|
|
.1
|
|
.checked_sub(1)
|
|
.ok_or(InvalidWorkspaceIndex)?;
|
|
|
|
shell.activate(
|
|
¤t_output,
|
|
workspace,
|
|
if gesture {
|
|
WorkspaceDelta::new_gesture()
|
|
} else {
|
|
WorkspaceDelta::new_shortcut()
|
|
},
|
|
workspace_state,
|
|
)
|
|
}
|