// SPDX-License-Identifier: GPL-3.0-only use crate::{ config::{Action, Config, KeyModifiers}, shell::{ focus::{target::PointerFocusTarget, FocusDirection}, layout::{ floating::SeatMoveGrabState, tiling::{Direction, FocusResult}, }, OverviewMode, Workspace, }, // shell::grabs::SeatMoveGrabState state::Common, utils::prelude::*, wayland::{handlers::screencopy::ScreencopySessions, protocols::screencopy::Session}, }; use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use smithay::{ backend::input::{ Axis, AxisSource, Device, DeviceCapability, InputBackend, InputEvent, KeyState, PointerAxisEvent, }, desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType}, input::{ keyboard::{keysyms, FilterResult, KeysymHandle, XkbConfig}, pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent, RelativeMotionEvent}, Seat, SeatState, }, output::Output, reexports::wayland_server::DisplayHandle, utils::{Logical, Point, Rectangle, Serial, SERIAL_COUNTER}, wayland::{ keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, seat::WaylandFocus, shell::wlr_layer::Layer as WlrLayer, }, xwayland::X11Surface, }; #[cfg(not(feature = "debug"))] use tracing::info; use tracing::{error, trace, warn}; use std::{cell::RefCell, collections::HashMap}; use xkbcommon::xkb::KEY_XF86Switch_VT_12; crate::utils::id_gen!(next_seat_id, SEAT_ID, SEAT_IDS); #[repr(transparent)] pub struct SeatId(pub usize); pub struct ActiveOutput(pub RefCell); #[derive(Default)] pub struct SupressedKeys(RefCell>); #[derive(Default)] pub struct Devices(RefCell>>); impl Default for SeatId { fn default() -> SeatId { SeatId(next_seat_id()) } } impl Drop for SeatId { fn drop(&mut self) { SEAT_IDS.lock().unwrap().remove(&self.0); } } impl SupressedKeys { fn add(&self, keysym: &KeysymHandle) { self.0.borrow_mut().push(keysym.raw_code()); } fn filter(&self, keysym: &KeysymHandle) -> bool { let mut keys = self.0.borrow_mut(); if let Some(i) = keys.iter().position(|x| *x == keysym.raw_code()) { keys.remove(i); true } else { false } } } impl Devices { fn add_device(&self, device: &D) -> Vec { let id = device.id(); let mut map = self.0.borrow_mut(); let caps = [DeviceCapability::Keyboard, DeviceCapability::Pointer] .iter() .cloned() .filter(|c| device.has_capability(*c)) .collect::>(); let new_caps = caps .iter() .cloned() .filter(|c| map.values().flatten().all(|has| *c != *has)) .collect::>(); map.insert(id, caps); new_caps } pub fn has_device(&self, device: &D) -> bool { self.0.borrow().contains_key(&device.id()) } fn remove_device(&self, device: &D) -> Vec { let id = device.id(); let mut map = self.0.borrow_mut(); map.remove(&id) .unwrap_or(Vec::new()) .into_iter() .filter(|c| map.values().flatten().all(|has| *c != *has)) .collect() } } pub fn add_seat( dh: &DisplayHandle, seat_state: &mut SeatState, output: &Output, config: &Config, name: String, ) -> Seat { let mut seat = seat_state.new_wl_seat(dh, name); let userdata = seat.user_data(); userdata.insert_if_missing(SeatId::default); userdata.insert_if_missing(Devices::default); userdata.insert_if_missing(SupressedKeys::default); userdata.insert_if_missing(SeatMoveGrabState::default); userdata.insert_if_missing(|| ActiveOutput(RefCell::new(output.clone()))); userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Default)); // A lot of clients bind keyboard and pointer unconditionally once on launch.. // Initial clients might race the compositor on adding periheral and // end up in a state, where they are not able to receive input. // Additionally a lot of clients don't handle keyboards/pointer objects being // removed very well either and we don't want to crash applications, because the // user is replugging their keyboard or mouse. // // So instead of doing the right thing (and initialize these capabilities as matching // devices appear), we have to surrender to reality and just always expose a keyboard and pointer. let conf = config.xkb_config(); if let Err(err) = seat.add_keyboard((&conf).into(), 200, 25) { warn!( ?err, "Failed to load provided xkb config. Trying default...", ); seat.add_keyboard(XkbConfig::default(), 200, 25) .expect("Failed to load xkb configuration files"); } seat.add_pointer(); seat } impl State { pub fn process_input_event(&mut self, event: InputEvent) { use smithay::backend::input::Event; match event { InputEvent::DeviceAdded { device } => { let seat = &mut self.common.last_active_seat(); let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); for cap in devices.add_device(&device) { match cap { // TODO: Handle touch, tablet _ => {} } } #[cfg(feature = "debug")] { self.common.egui.state.handle_device_added(&device); } } InputEvent::DeviceRemoved { device } => { for seat in &mut self.common.seats() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { for cap in devices.remove_device(&device) { match cap { // TODO: Handle touch, tablet _ => {} } } break; } } #[cfg(feature = "debug")] { self.common.egui.state.handle_device_removed(&device); } } InputEvent::Keyboard { event, .. } => { use smithay::backend::input::KeyboardKeyEvent; let device = event.device(); for seat in self.common.seats().cloned().collect::>().iter() { let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); let shortcuts_inhibited = workspace .focus_stack .get(seat) .last() .and_then(|window| { window.wl_surface().and_then(|surface| { seat.keyboard_shortcuts_inhibitor_for_surface(&surface) }) }) .map(|inhibitor| inhibitor.is_active()) .unwrap_or(false); let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { let keycode = event.key_code(); let state = event.state(); trace!(?keycode, ?state, "key"); let serial = SERIAL_COUNTER.next_serial(); let time = Event::time_msec(&event); if let Some((action, mods)) = seat .get_keyboard() .unwrap() .input( self, keycode, state, serial, time, |data, modifiers, handle| { if let OverviewMode::Started(action_modifiers, _) = data.common.shell.overview_mode() { if !(action_modifiers.ctrl && modifiers.ctrl) && !(action_modifiers.alt && modifiers.alt) && !(action_modifiers.logo && modifiers.logo) && !(action_modifiers.shift && modifiers.shift) { data.common.shell.set_overview_mode(None); } } if state == KeyState::Released && userdata.get::().unwrap().filter(&handle) { return FilterResult::Intercept(None); } #[cfg(feature = "debug")] { if data.common.seats().position(|x| x == seat).unwrap() == 0 && data.common.egui.active { if data.common.egui.state.wants_keyboard() { data.common.egui.state.handle_keyboard( &handle, state == KeyState::Pressed, modifiers.clone(), ); userdata .get::() .unwrap() .add(&handle); return FilterResult::Intercept(None); } } } if state == KeyState::Pressed && (keysyms::KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12) .contains(&handle.modified_sym()) { if let Err(err) = data.backend.kms().switch_vt( (handle.modified_sym() - keysyms::KEY_XF86Switch_VT_1 + 1) as i32, ) { error!(?err, "Failed switching virtual terminal."); } userdata.get::().unwrap().add(&handle); return FilterResult::Intercept(None); } // here we can handle global shortcuts and the like if !shortcuts_inhibited { for (binding, action) in data.common.config.static_conf.key_bindings.iter() { if state == KeyState::Pressed && binding.modifiers == *modifiers && handle.raw_syms().contains(&binding.key) { userdata .get::() .unwrap() .add(&handle); return FilterResult::Intercept(Some(( action.clone(), binding.modifiers.clone(), ))); } } } FilterResult::Forward }, ) .flatten() { self.handle_action(action, seat, serial, time, mods) } break; } } } InputEvent::PointerMotion { event, .. } => { use smithay::backend::input::PointerMotionEvent; let device = event.device(); for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { let current_output = seat.active_output(); let mut position = seat.get_pointer().unwrap().current_location(); position += event.delta(); let output = self .common .shell .outputs() .find(|output| output.geometry().to_f64().contains(position)) .cloned() .unwrap_or(current_output.clone()); if output != current_output { for session in sessions_for_output(&self.common, ¤t_output) { session.cursor_leave(seat, InputType::Pointer); } for session in sessions_for_output(&self.common, &output) { session.cursor_enter(seat, InputType::Pointer); } seat.set_active_output(&output); } let output_geometry = output.geometry(); position.x = (output_geometry.loc.x as f64) .max(position.x) .min((output_geometry.loc.x + output_geometry.size.w) as f64); position.y = (output_geometry.loc.y as f64) .max(position.y) .min((output_geometry.loc.y + output_geometry.size.h) as f64); let serial = SERIAL_COUNTER.next_serial(); let relative_pos = self.common.shell.map_global_to_space(position, &output); let workspace = self.common.shell.active_space(&output); let under = State::surface_under( position, relative_pos, &output, output_geometry, &self.common.shell.override_redirect_windows, &workspace, ); for session in sessions_for_output(&self.common, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( position.to_buffer( output.current_scale().fractional_scale(), output.current_transform(), &output.geometry().size.to_f64(), ), self.common.clock.now(), ) { session.cursor_info(seat, InputType::Pointer, geometry, offset); } } let ptr = seat.get_pointer().unwrap(); ptr.motion( self, under.clone(), &MotionEvent { location: position, serial, time: event.time_msec(), }, ); ptr.relative_motion( self, under, &RelativeMotionEvent { delta: event.delta(), delta_unaccel: event.delta_unaccel(), utime: event.time(), }, ); #[cfg(feature = "debug")] if self.common.seats().position(|x| x == seat).unwrap() == 0 { let location = if let Some(output) = self.common.shell.outputs.first() { self.common .shell .map_global_to_space(position, output) .to_i32_round() } else { position.to_i32_round() }; self.common.egui.state.handle_pointer_motion(location); } break; } } } InputEvent::PointerMotionAbsolute { event, .. } => { let device = event.device(); for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { let output = seat.active_output(); let geometry = output.geometry(); let position = geometry.loc.to_f64() + smithay::backend::input::AbsolutePositionEvent::position_transformed( &event, geometry.size, ); let relative_pos = self.common.shell.map_global_to_space(position, &output); let workspace = self.common.shell.active_space(&output); let serial = SERIAL_COUNTER.next_serial(); let under = State::surface_under( position, relative_pos, &output, geometry, &self.common.shell.override_redirect_windows, &workspace, ); for session in sessions_for_output(&self.common, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( position.to_buffer( output.current_scale().fractional_scale(), output.current_transform(), &output.geometry().size.to_f64(), ), self.common.clock.now(), ) { session.cursor_info(seat, InputType::Pointer, geometry, offset); } } seat.get_pointer().unwrap().motion( self, under, &MotionEvent { location: position, serial, time: event.time_msec(), }, ); #[cfg(feature = "debug")] if self.common.seats().position(|x| x == seat).unwrap() == 0 { let location = if let Some(output) = self.common.shell.outputs.first() { self.common .shell .map_global_to_space(position, output) .to_i32_round() } else { position.to_i32_round() }; self.common.egui.state.handle_pointer_motion(location); } break; } } } InputEvent::PointerButton { event, .. } => { use smithay::backend::input::{ButtonState, PointerButtonEvent}; let device = event.device(); for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { #[cfg(feature = "debug")] if self.common.seats().position(|x| x == seat).unwrap() == 0 && self.common.egui.active { if self.common.egui.state.wants_pointer() { if let Some(button) = event.button() { self.common.egui.state.handle_pointer_button( button, event.state() == ButtonState::Pressed, ); } break; } } let serial = SERIAL_COUNTER.next_serial(); let button = event.button_code(); if event.state() == ButtonState::Pressed { // change the keyboard focus unless the pointer or keyboard is grabbed // We test for any matching surface type here but always use the root // (in case of a window the toplevel) surface for the focus. // see: https://gitlab.freedesktop.org/wayland/wayland/-/issues/294 if !seat.get_pointer().unwrap().is_grabbed() && !seat.get_keyboard().map(|k| k.is_grabbed()).unwrap_or(false) { let output = seat.active_output(); let pos = seat.get_pointer().unwrap().current_location(); let relative_pos = self.common.shell.map_global_to_space(pos, &output); let workspace = self.common.shell.active_space_mut(&output); let layers = layer_map_for_output(&output); let mut under = None; if let Some(window) = workspace.get_fullscreen(&output) { if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; if layer.can_receive_keyboard_focus() && layer .surface_under( relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL, ) .is_some() { under = Some(layer.clone().into()); } } else { under = Some(window.clone().into()); } } else { if let Some(layer) = layers .layer_under(WlrLayer::Overlay, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; if layer.can_receive_keyboard_focus() && layer .surface_under( relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL, ) .is_some() { under = Some(layer.clone().into()); } } else if let Some((window, _)) = workspace.element_under(relative_pos) { under = Some(window.clone().into()); } else if let Some(layer) = layers .layer_under(WlrLayer::Bottom, pos) .or_else(|| layers.layer_under(WlrLayer::Background, pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; if layer.can_receive_keyboard_focus() && layer .surface_under( relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL, ) .is_some() { under = Some(layer.clone().into()); } }; } Common::set_focus(self, under.as_ref(), seat, Some(serial)); } }; seat.get_pointer().unwrap().button( self, &ButtonEvent { button, state: event.state(), serial, time: event.time_msec(), }, ); break; } } } InputEvent::PointerAxis { event, .. } => { let device = event.device(); for seat in self.common.seats().cloned().collect::>().iter() { #[cfg(feature = "debug")] if self.common.seats().position(|x| x == seat).unwrap() == 0 && self.common.egui.active { if self.common.egui.state.wants_pointer() { self.common.egui.state.handle_pointer_axis( event .amount_discrete(Axis::Horizontal) .or_else(|| event.amount(Axis::Horizontal).map(|x| x * 3.0)) .unwrap_or(0.0), event .amount_discrete(Axis::Vertical) .or_else(|| event.amount(Axis::Vertical).map(|x| x * 3.0)) .unwrap_or(0.0), ); break; } } let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { let horizontal_amount = event.amount(Axis::Horizontal).unwrap_or_else(|| { event.amount_discrete(Axis::Horizontal).unwrap_or(0.0) * 3.0 }); let vertical_amount = event.amount(Axis::Vertical).unwrap_or_else(|| { event.amount_discrete(Axis::Vertical).unwrap_or(0.0) * 3.0 }); let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal); let vertical_amount_discrete = event.amount_discrete(Axis::Vertical); { let mut frame = AxisFrame::new(event.time_msec()).source(event.source()); if horizontal_amount != 0.0 { frame = frame.value(Axis::Horizontal, horizontal_amount); if let Some(discrete) = horizontal_amount_discrete { frame = frame.discrete(Axis::Horizontal, discrete as i32); } } else if event.source() == AxisSource::Finger { frame = frame.stop(Axis::Horizontal); } if vertical_amount != 0.0 { frame = frame.value(Axis::Vertical, vertical_amount); if let Some(discrete) = vertical_amount_discrete { frame = frame.discrete(Axis::Vertical, discrete as i32); } } else if event.source() == AxisSource::Finger { frame = frame.stop(Axis::Vertical); } seat.get_pointer().unwrap().axis(self, frame); } break; } } } _ => { /* TODO e.g. tablet or touch events */ } } } fn handle_action( &mut self, action: Action, seat: &Seat, serial: Serial, time: u32, mods: KeyModifiers, ) { match action { Action::Terminate => { self.common.should_stop = true; } #[cfg(feature = "debug")] Action::Debug => { self.common.egui.active = !self.common.egui.active; puffin::set_scopes_on(self.common.egui.active); for mapped in self .common .shell .workspaces .spaces() .flat_map(|w| w.mapped()) { mapped.set_debug(self.common.egui.active); } } #[cfg(not(feature = "debug"))] Action::Debug => { info!("Debug overlay not included in this build.") } Action::Close => { let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); if let Some(window) = workspace.focus_stack.get(seat).last() { window.send_close(); } } Action::Workspace(key_num) => { let current_output = seat.active_output(); let workspace = match key_num { 0 => 9, x => x - 1, }; let _ = self .common .shell .activate(¤t_output, workspace as usize); } Action::NextWorkspace => { let current_output = seat.active_output(); let workspace = self .common .shell .workspaces .active_num(¤t_output) .saturating_add(1); // TODO: Possibly move to next output, if idx to large let _ = self.common.shell.activate(¤t_output, workspace); } Action::PreviousWorkspace => { let current_output = seat.active_output(); let workspace = self .common .shell .workspaces .active_num(¤t_output) .saturating_sub(1); // TODO: Possibly move to prev output, if idx < 0 let _ = self.common.shell.activate(¤t_output, workspace); } Action::LastWorkspace => { let current_output = seat.active_output(); let workspace = self .common .shell .workspaces .len(¤t_output) .saturating_sub(1); let _ = self.common.shell.activate(¤t_output, workspace); } x @ Action::MoveToWorkspace(_) | x @ Action::SendToWorkspace(_) => { let current_output = seat.active_output(); 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!(), }; Shell::move_current_window( self, seat, ¤t_output, (¤t_output, Some(workspace as usize)), follow, ); } x @ Action::MoveToNextWorkspace | x @ Action::SendToNextWorkspace => { let current_output = seat.active_output(); let workspace = self .common .shell .workspaces .active_num(¤t_output) .saturating_add(1); // TODO: Possibly move to next output, if idx too large Shell::move_current_window( self, seat, ¤t_output, (¤t_output, Some(workspace as usize)), matches!(x, Action::MoveToNextWorkspace), ); } x @ Action::MoveToPreviousWorkspace | x @ Action::SendToPreviousWorkspace => { let current_output = seat.active_output(); let workspace = self .common .shell .workspaces .active_num(¤t_output) .saturating_sub(1); // TODO: Possibly move to prev output, if idx < 0 Shell::move_current_window( self, seat, ¤t_output, (¤t_output, Some(workspace as usize)), matches!(x, Action::MoveToPreviousWorkspace), ); } x @ Action::MoveToLastWorkspace | x @ Action::SendToLastWorkspace => { let current_output = seat.active_output(); let workspace = self .common .shell .workspaces .len(¤t_output) .saturating_sub(1); Shell::move_current_window( self, seat, ¤t_output, (¤t_output, Some(workspace as usize)), matches!(x, Action::MoveToLastWorkspace), ); } Action::NextOutput => { let current_output = seat.active_output(); if let Some(next_output) = self .common .shell .outputs .iter() .skip_while(|o| *o != ¤t_output) .skip(1) .next() .cloned() { let idx = self.common.shell.workspaces.active_num(&next_output); if let Some(new_pos) = self.common.shell.activate(&next_output, idx) { seat.set_active_output(&next_output); if let Some(ptr) = seat.get_pointer() { ptr.motion( self, None, &MotionEvent { location: new_pos.to_f64(), serial, time, }, ); } } } } Action::PreviousOutput => { let current_output = seat.active_output(); if let Some(prev_output) = self .common .shell .outputs .iter() .rev() .skip_while(|o| *o != ¤t_output) .skip(1) .next() .cloned() { let idx = self.common.shell.workspaces.active_num(&prev_output); if let Some(new_pos) = self.common.shell.activate(&prev_output, idx) { seat.set_active_output(&prev_output); if let Some(ptr) = seat.get_pointer() { ptr.motion( self, None, &MotionEvent { location: new_pos.to_f64(), serial, time, }, ); } } } } Action::MoveToNextOutput => { let current_output = seat.active_output(); if let Some(next_output) = self .common .shell .outputs .iter() .skip_while(|o| *o != ¤t_output) .skip(1) .next() .cloned() { if let Some(new_pos) = Shell::move_current_window( self, seat, ¤t_output, (&next_output, None), true, ) { if let Some(ptr) = seat.get_pointer() { ptr.motion( self, None, &MotionEvent { location: new_pos.to_f64(), serial, time, }, ); } } } } Action::MoveToPreviousOutput => { let current_output = seat.active_output(); if let Some(prev_output) = self .common .shell .outputs .iter() .rev() .skip_while(|o| *o != ¤t_output) .skip(1) .next() .cloned() { if let Some(new_pos) = Shell::move_current_window( self, seat, ¤t_output, (&prev_output, None), true, ) { if let Some(ptr) = seat.get_pointer() { ptr.motion( self, None, &MotionEvent { location: new_pos.to_f64(), serial, time, }, ); } } } } Action::Focus(focus) => { let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); let focus_stack = workspace.focus_stack.get(seat); let mut result = workspace .tiling_layer .next_focus(focus, seat, focus_stack.iter()); if workspace.get_fullscreen(¤t_output).is_some() { result = FocusResult::None; } match result { FocusResult::None => { // TODO: Handle Workspace orientation match focus { FocusDirection::Left => self.handle_action( Action::PreviousWorkspace, seat, serial, time, mods, ), FocusDirection::Right => { self.handle_action(Action::NextWorkspace, seat, serial, time, mods) } FocusDirection::Up => { self.handle_action(Action::PreviousOutput, seat, serial, time, mods) } FocusDirection::Down => { self.handle_action(Action::NextOutput, seat, serial, time, mods) } _ => {} } } FocusResult::Handled => {} FocusResult::Some(target) => { std::mem::drop(focus_stack); Common::set_focus(self, Some(&target), seat, None); } } } Action::Move(direction) => { let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); if workspace.get_fullscreen(¤t_output).is_some() { return; // TODO, is this what we want? How do we indicate the switch? } if let Some(_move_further) = workspace.tiling_layer.move_current_window(direction, seat) { // TODO: Handle Workspace orientation // TODO: Being able to move Groups (move_further should be KeyboardFocusTarget instead) match direction { Direction::Left => self.handle_action( Action::MoveToPreviousWorkspace, seat, serial, time, mods, ), Direction::Right => self.handle_action( Action::MoveToNextWorkspace, seat, serial, time, mods, ), Direction::Up => self.handle_action( Action::MoveToPreviousOutput, seat, serial, time, mods, ), Direction::Down => { self.handle_action(Action::MoveToNextOutput, seat, serial, time, mods) } } } else { let focus_stack = workspace.focus_stack.get(seat); if let Some(focused_window) = focus_stack.last() { if workspace.is_tiled(focused_window) { self.common.shell.set_overview_mode(Some(mods)); } } } } Action::Maximize => { let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); let focus_stack = workspace.focus_stack.get(seat); let focused_window = focus_stack.last(); if let Some(window) = focused_window.map(|f| f.active_window()) { workspace.maximize_toggle(&window, ¤t_output); } } Action::ToggleOrientation => { let output = seat.active_output(); let workspace = self.common.shell.active_space_mut(&output); let focus_stack = workspace.focus_stack.get(seat); workspace .tiling_layer .update_orientation(None, &seat, focus_stack.iter()); } Action::Orientation(orientation) => { let output = seat.active_output(); let workspace = self.common.shell.active_space_mut(&output); let focus_stack = workspace.focus_stack.get(seat); workspace.tiling_layer.update_orientation( Some(orientation), &seat, focus_stack.iter(), ); } Action::ToggleTiling => { let output = seat.active_output(); let workspace = self.common.shell.active_space_mut(&output); workspace.toggle_tiling(seat); } Action::ToggleWindowFloating => { let output = seat.active_output(); let workspace = self.common.shell.active_space_mut(&output); workspace.toggle_floating_window(seat); } Action::Spawn(command) => { if let Err(err) = std::process::Command::new("/bin/sh") .arg("-c") .arg(command.clone()) .env("WAYLAND_DISPLAY", &self.common.socket) .env( "DISPLAY", &self .common .xwayland_state .as_ref() .map(|s| format!(":{}", s.display)) .unwrap_or(String::new()), ) .env_remove("COSMIC_SESSION_SOCK") .spawn() { warn!(?err, "Failed to spawn \"{}\"", command); } } } } pub fn surface_under( global_pos: Point, relative_pos: Point, output: &Output, output_geo: Rectangle, override_redirect_windows: &[X11Surface], workspace: &Workspace, ) -> Option<(PointerFocusTarget, Point)> { let layers = layer_map_for_output(output); if let Some(window) = workspace.get_fullscreen(output) { if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; if layer .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) .is_some() { return Some((layer.clone().into(), output_geo.loc + layer_loc)); } } if let Some(or) = override_redirect_windows .iter() .find(|or| or.is_in_input_region(&(global_pos - or.geometry().loc.to_f64()))) { return Some((or.clone().into(), or.geometry().loc)); } Some((window.clone().into(), output_geo.loc)) } else { if let Some(layer) = layers .layer_under(WlrLayer::Overlay, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; if layer .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) .is_some() { return Some((layer.clone().into(), output_geo.loc + layer_loc)); } } if let Some(or) = override_redirect_windows .iter() .find(|or| or.is_in_input_region(&(global_pos - or.geometry().loc.to_f64()))) { return Some((or.clone().into(), or.geometry().loc)); } if let Some((mapped, loc)) = workspace.element_under(relative_pos) { return Some(( mapped.clone().into(), loc + (global_pos - relative_pos).to_i32_round(), )); } if let Some(layer) = layers .layer_under(WlrLayer::Bottom, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Background, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; if layer .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) .is_some() { return Some((layer.clone().into(), output_geo.loc + layer_loc)); } } None } } } fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator { let workspace = state.shell.active_space(&output); let maybe_fullscreen = workspace.get_fullscreen(&output); workspace .screencopy_sessions .iter() .map(|s| (&**s).clone()) .chain( maybe_fullscreen .and_then(|w| w.user_data().get::()) .map(|sessions| { sessions .0 .borrow() .iter() .map(|s| (&**s).clone()) .collect::>() }) .into_iter() .flatten(), ) .chain( output .user_data() .get::() .map(|sessions| { sessions .0 .borrow() .iter() .map(|s| (&**s).clone()) .collect::>() }) .into_iter() .into_iter() .flatten(), ) .collect::>() .into_iter() }