// SPDX-License-Identifier: GPL-3.0-only use crate::{ backend::render::ElementFilter, config::{ key_bindings::{ cosmic_keystate_from_smithay, cosmic_modifiers_eq_smithay, cosmic_modifiers_from_smithay, }, Action, Config, PrivateAction, }, input::gestures::{GestureState, SwipeAction}, shell::{ focus::{ render_input_order, target::{KeyboardFocusTarget, PointerFocusTarget}, Stage, }, grabs::{ReleaseMode, ResizeEdge}, layout::{ floating::ResizeGrabMarker, tiling::{NodeDesc, TilingLayout}, }, SeatExt, Trigger, }, utils::{prelude::*, quirks::workspace_overview_is_open}, wayland::{ handlers::screencopy::SessionHolder, protocols::screencopy::{BufferConstraints, CursorSession}, }, }; use calloop::{ timer::{TimeoutAction, Timer}, RegistrationToken, }; use cosmic_comp_config::workspace::WorkspaceLayout; use cosmic_settings_config::shortcuts; use cosmic_settings_config::shortcuts::action::{Direction, ResizeDirection}; use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _, InputBackend, InputEvent, KeyState, KeyboardKeyEvent, PointerAxisEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent, }, desktop::{utils::under_from_surface_tree, WindowSurfaceType}, input::{ keyboard::{FilterResult, KeysymHandle, ModifiersState}, pointer::{ AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent, PointerGrab, RelativeMotionEvent, }, touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent}, Seat, }, output::Output, reexports::{ input::Device as InputDevice, wayland_server::protocol::wl_shm::Format as ShmFormat, }, utils::{Point, Rectangle, Serial, SERIAL_COUNTER}, wayland::{ keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, pointer_constraints::{with_pointer_constraint, PointerConstraint}, seat::WaylandFocus, tablet_manager::{TabletDescriptor, TabletSeatTrait}, }, }; use tracing::{error, trace}; use xkbcommon::xkb::{Keycode, Keysym}; use std::{ any::Any, borrow::Cow, cell::RefCell, collections::HashSet, ops::ControlFlow, time::{Duration, Instant}, }; pub mod actions; pub mod gestures; /// Used for debouncing focus updates due to pointer motion, if after the focus change is /// triggered the event will cancel if the pointer moves to the original target #[derive(Debug)] pub struct PointerFocusState { //the window under the cursor prior to it's movement originally_focused_window: Option, //the window under the cursor after it's movement scheduled_focused_window: Option, token: RegistrationToken, } #[derive(Default)] pub struct SupressedKeys(RefCell)>>); #[derive(Default)] pub struct SupressedButtons(RefCell>); #[derive(Default, Debug)] pub struct ModifiersShortcutQueue(RefCell>); impl SupressedKeys { fn add(&self, keysym: &KeysymHandle, token: impl Into>) { self.0.borrow_mut().push((keysym.raw_code(), token.into())); } fn filter(&self, keysym: &KeysymHandle) -> Option> { let mut keys = self.0.borrow_mut(); let (removed, remaining) = keys .drain(..) .partition(|(key, _)| *key == keysym.raw_code()); *keys = remaining; if removed.is_empty() { return None; } Some( removed .into_iter() .map(|(_, token)| token) .flatten() .collect::>(), ) } } impl SupressedButtons { fn add(&self, button: u32) { self.0.borrow_mut().insert(button); } fn remove(&self, button: u32) -> bool { self.0.borrow_mut().remove(&button) } } impl ModifiersShortcutQueue { pub fn set(&self, binding: shortcuts::Binding) { let mut set = self.0.borrow_mut(); *set = Some(binding); } pub fn take(&self, binding: &shortcuts::Binding) -> bool { let mut set = self.0.borrow_mut(); if set.is_some() && set.as_ref().unwrap() == binding { *set = None; true } else { false } } pub fn clear(&self) { let mut set = self.0.borrow_mut(); *set = None; } } impl State { pub fn process_input_event(&mut self, event: InputEvent) where ::Device: 'static, { crate::wayland::handlers::output_power::set_all_surfaces_dpms_on(self); use smithay::backend::input::Event; match event { InputEvent::DeviceAdded { device } => { let shell = self.common.shell.read().unwrap(); let seat = shell.seats.last_active(); let led_state = seat.get_keyboard().unwrap().led_state(); seat.devices().add_device(&device, led_state); if device.has_capability(DeviceCapability::TabletTool) { seat.tablet_seat().add_tablet::( &self.common.display_handle, &TabletDescriptor::from(&device), ); } } InputEvent::DeviceRemoved { device } => { for seat in &mut self.common.shell.read().unwrap().seats.iter() { let devices = seat.devices(); if devices.has_device(&device) { devices.remove_device(&device); if device.has_capability(DeviceCapability::TabletTool) { seat.tablet_seat() .remove_tablet(&TabletDescriptor::from(&device)); if seat.tablet_seat().count_tablets() == 0 { seat.tablet_seat().clear_tools(); } } break; } } } InputEvent::Keyboard { event, .. } => { use smithay::backend::input::KeyboardKeyEvent; let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); 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); let keyboard = seat.get_keyboard().unwrap(); if let Some((action, pattern)) = keyboard .input( self, keycode, state, serial, time, |data, modifiers, handle| { Self::filter_keyboard_input( data, &event, &seat, modifiers, handle, serial, ) }, ) .flatten() { if pattern.key.is_none() && state == KeyState::Released { // we still want to send release-events and not have apps stuck on some modifiers. keyboard.input(self, keycode, state, serial, time, |_, _, _| { FilterResult::<()>::Forward }); } self.handle_action(action, &seat, serial, time, pattern, None, true) } } } InputEvent::PointerMotion { event, .. } => { use smithay::backend::input::PointerMotionEvent; let mut shell = self.common.shell.write().unwrap(); if let Some(seat) = shell.seats.for_device(&event.device()).cloned() { self.common.idle_notifier_state.notify_activity(&seat); let current_output = seat.active_output(); let mut position = seat.get_pointer().unwrap().current_location().as_global(); let under = State::surface_under(position, ¤t_output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); let ptr = seat.get_pointer().unwrap(); let mut pointer_locked = false; let mut pointer_confined = false; let mut confine_region = None; if let Some((surface, surface_loc)) = under .as_ref() .and_then(|(target, l)| Some((target.wl_surface()?, l))) { with_pointer_constraint(&surface, &ptr, |constraint| match constraint { Some(constraint) if constraint.is_active() => { // Constraint does not apply if not within region if !constraint.region().map_or(true, |x| { x.contains( (ptr.current_location() - *surface_loc).to_i32_round(), ) }) { return; } match &*constraint { PointerConstraint::Locked(_locked) => { pointer_locked = true; } PointerConstraint::Confined(confine) => { pointer_confined = true; confine_region = confine.region().cloned(); } } } _ => {} }); } let original_position = position; position += event.delta().as_global(); let output = shell .outputs() .find(|output| output.geometry().to_f64().contains(position)) .cloned() .unwrap_or(current_output.clone()); let new_under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); std::mem::drop(shell); ptr.relative_motion( self, under.clone(), &RelativeMotionEvent { delta: event.delta(), delta_unaccel: event.delta_unaccel(), utime: event.time(), }, ); if pointer_locked { ptr.frame(self); return; } if ptr.is_grabbed() { if seat .user_data() .get::() .map(|marker| marker.get()) .unwrap_or(false) { if output != current_output { ptr.frame(self); return; } } //If the pointer isn't grabbed, we should check if the focused element should be updated } else if self.common.config.cosmic_conf.focus_follows_cursor { let shell = self.common.shell.read().unwrap(); let old_keyboard_target = State::element_under(original_position, ¤t_output, &*shell); let new_keyboard_target = State::element_under(position, &output, &*shell); if old_keyboard_target != new_keyboard_target && new_keyboard_target.is_some() { let create_source = if self.common.pointer_focus_state.is_none() { true } else { let PointerFocusState { originally_focused_window, scheduled_focused_window, token, } = self.common.pointer_focus_state.as_ref().unwrap(); if &new_keyboard_target == originally_focused_window { //if we moved to the original window, just cancel the event self.common.event_loop_handle.remove(*token); //clear the state self.common.pointer_focus_state = None; false } else if &new_keyboard_target != scheduled_focused_window { //if we moved to a new window, update the scheduled focus self.common.event_loop_handle.remove(*token); true } else { //the state doesn't need to be updated or cleared false } }; if create_source { // prevent popups from being unfocusable if there is a gap between them and their parent let delay = calloop::timer::Timer::from_duration( //default to 250ms std::time::Duration::from_millis( self.common.config.cosmic_conf.focus_follows_cursor_delay, ), ); let seat = seat.clone(); let token = self .common .event_loop_handle .insert_source(delay, move |_, _, state| { let target = state .common .pointer_focus_state .as_ref() .unwrap() .scheduled_focused_window .clone(); //clear it prior in case the user twitches in the microsecond it //takes this function to run state.common.pointer_focus_state = None; Shell::set_focus( state, target.as_ref(), &seat, Some(SERIAL_COUNTER.next_serial()), false, ); TimeoutAction::Drop }) .ok(); if token.is_some() { let originally_focused_window = if self.common.pointer_focus_state.is_none() { old_keyboard_target } else { // In this case, the pointer has moved to a new window (neither original, nor scheduled) // so we should preserve the original window for the focus state self.common .pointer_focus_state .as_ref() .unwrap() .originally_focused_window .clone() }; self.common.pointer_focus_state = Some(PointerFocusState { originally_focused_window, scheduled_focused_window: new_keyboard_target, token: token.unwrap(), }); } } } } let output_geometry = output.geometry(); position.x = position.x.clamp( output_geometry.loc.x as f64, ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable ); position.y = position.y.clamp( output_geometry.loc.y as f64, ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable ); // If confined, don't move pointer if it would go outside surface or region if pointer_confined { if let Some((surface, surface_loc)) = &under { if new_under.as_ref().and_then(|(under, _)| under.wl_surface()) != surface.wl_surface() { ptr.frame(self); return; } if let PointerFocusTarget::WlSurface { surface, .. } = surface { if under_from_surface_tree( surface, position.as_logical() - surface_loc.to_f64(), (0, 0), WindowSurfaceType::ALL, ) .is_none() { ptr.frame(self); return; } } if let Some(region) = confine_region { if !region .contains((position.as_logical() - *surface_loc).to_i32_round()) { ptr.frame(self); return; } } } } let serial = SERIAL_COUNTER.next_serial(); ptr.motion( self, under, &MotionEvent { location: position.as_logical(), serial, time: event.time_msec(), }, ); ptr.frame(self); // If pointer is now in a constraint region, activate it if let Some((under, surface_location)) = new_under .and_then(|(target, loc)| Some((target.wl_surface()?.into_owned(), loc))) { with_pointer_constraint(&under, &ptr, |constraint| match constraint { Some(constraint) if !constraint.is_active() => { let region = match &*constraint { PointerConstraint::Locked(locked) => locked.region(), PointerConstraint::Confined(confined) => confined.region(), }; let point = (ptr.current_location() - surface_location).to_i32_round(); if region.map_or(true, |region| region.contains(point)) { constraint.activate(); } } _ => {} }); } let mut shell = self.common.shell.write().unwrap(); shell.update_pointer_position(position.to_local(&output), &output); if output != current_output { for session in cursor_sessions_for_output(&*shell, ¤t_output) { session.set_cursor_pos(None); } seat.set_active_output(&output); } for session in cursor_sessions_for_output(&shell, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( position.as_logical().to_buffer( output.current_scale().fractional_scale(), output.current_transform(), &output_geometry.size.to_f64().as_logical(), ), self.common.clock.now(), ) { if session .current_constraints() .map(|constraint| constraint.size != geometry.size) .unwrap_or(true) { session.update_constraints(BufferConstraints { size: geometry.size, shm: vec![ShmFormat::Argb8888], dma: None, }); } session.set_cursor_hotspot(offset); session.set_cursor_pos(Some(geometry.loc)); } } } } InputEvent::PointerMotionAbsolute { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let output = seat.active_output(); let geometry = output.geometry(); let position = geometry.loc.to_f64() + smithay::backend::input::AbsolutePositionEvent::position_transformed( &event, geometry.size.as_logical(), ) .as_global(); let serial = SERIAL_COUNTER.next_serial(); let under = State::surface_under( position, &output, &mut *self.common.shell.write().unwrap(), ) .map(|(target, pos)| (target, pos.as_logical())); let ptr = seat.get_pointer().unwrap(); ptr.motion( self, under, &MotionEvent { location: position.as_logical(), serial, time: event.time_msec(), }, ); ptr.frame(self); let shell = self.common.shell.read().unwrap(); for session in cursor_sessions_for_output(&*shell, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( position.as_logical().to_buffer( output.current_scale().fractional_scale(), output.current_transform(), &geometry.size.to_f64().as_logical(), ), self.common.clock.now(), ) { if session .current_constraints() .map(|constraint| constraint.size != geometry.size) .unwrap_or(true) { session.update_constraints(BufferConstraints { size: geometry.size, shm: vec![ShmFormat::Argb8888], dma: None, }); } session.set_cursor_hotspot(offset); session.set_cursor_pos(Some(geometry.loc)); } } } } InputEvent::PointerButton { event, .. } => { use smithay::backend::input::{ButtonState, PointerButtonEvent}; // let Some(seat) = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned() else { return; }; self.common.idle_notifier_state.notify_activity(&seat); let current_focus = seat.get_keyboard().unwrap().current_focus(); let shortcuts_inhibited = current_focus.is_some_and(|f| { f.wl_surface() .and_then(|surface| { seat.keyboard_shortcuts_inhibitor_for_surface(&surface) .map(|inhibitor| inhibitor.is_active()) }) .unwrap_or(false) }); let serial = SERIAL_COUNTER.next_serial(); let button = event.button_code(); let mut pass_event = !seat.supressed_buttons().remove(button); if event.state() == ButtonState::Pressed { // change the keyboard focus unless the pointer 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() { let output = seat.active_output(); let global_position = seat.get_pointer().unwrap().current_location().as_global(); let under = { let shell = self.common.shell.read().unwrap(); State::element_under(global_position, &output, &shell) }; if let Some(target) = under { if let Some(surface) = target.toplevel().map(Cow::into_owned) { if seat.get_keyboard().unwrap().modifier_state().logo && !shortcuts_inhibited { let seat_clone = seat.clone(); let mouse_button = PointerButtonEvent::button(&event); let mut supress_button = || { // If the logo is held then the pointer event is // aimed at the compositor and shouldn't be passed // to the application. pass_event = false; seat.supressed_buttons().add(button); }; fn dispatch_grab + 'static>( grab: Option<(G, smithay::input::pointer::Focus)>, seat: Seat, serial: Serial, state: &mut State, ) { if let Some((target, focus)) = grab { seat.modifiers_shortcut_queue().clear(); seat.get_pointer() .unwrap() .set_grab(state, target, serial, focus); } } if let Some(mouse_button) = mouse_button { match mouse_button { smithay::backend::input::MouseButton::Left => { supress_button(); self.common.event_loop_handle.insert_idle( move |state| { let mut shell = state.common.shell.write().unwrap(); let res = shell.move_request( &surface, &seat_clone, serial, ReleaseMode::NoMouseButtons, false, &state.common.config, &state.common.event_loop_handle, &state.common.xdg_activation_state, false, ); drop(shell); dispatch_grab( res, seat_clone, serial, state, ); }, ); } smithay::backend::input::MouseButton::Right => { supress_button(); self.common.event_loop_handle.insert_idle( move |state| { let mut shell = state.common.shell.write().unwrap(); let Some(target_elem) = shell.element_for_surface(&surface) else { return; }; let Some(geom) = shell.space_for(target_elem).and_then( |f| f.element_geometry(target_elem), ) else { return; }; let geom = geom.to_f64(); let center = geom.loc + geom.size.downscale(2.0); let offset = center.to_global(&output) - global_position; let edge = match ( offset.x > 0.0, offset.y > 0.0, ) { (true, true) => ResizeEdge::TOP_LEFT, (false, true) => ResizeEdge::TOP_RIGHT, (true, false) => { ResizeEdge::BOTTOM_LEFT } (false, false) => { ResizeEdge::BOTTOM_RIGHT } }; let res = shell.resize_request( &surface, &seat_clone, serial, edge, false, ); drop(shell); dispatch_grab( res, seat_clone, serial, state, ); }, ); } _ => {} } } } } Shell::set_focus(self, Some(&target), &seat, Some(serial), false); } } } else { let mut shell = self.common.shell.write().unwrap(); if let Some(Trigger::Pointer(action_button)) = shell.overview_mode().0.active_trigger() { if *action_button == button { shell.set_overview_mode(None, self.common.event_loop_handle.clone()); } } std::mem::drop(shell); }; let ptr = seat.get_pointer().unwrap(); if pass_event { ptr.button( self, &ButtonEvent { button, state: event.state(), serial, time: event.time_msec(), }, ); ptr.frame(self); } else if event.state() == ButtonState::Released { ptr.unset_grab(self, serial, event.time_msec()) } } InputEvent::PointerAxis { event, .. } => { let scroll_factor = if let Some(device) = ::downcast_ref::(&event.device()) { self.common.config.scroll_factor(device) } else { 1.0 }; let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let mut frame = AxisFrame::new(event.time_msec()).source(event.source()); if let Some(horizontal_amount) = event.amount(Axis::Horizontal) { if horizontal_amount != 0.0 { frame = frame.value(Axis::Horizontal, scroll_factor * horizontal_amount); if let Some(discrete) = event.amount_v120(Axis::Horizontal) { frame = frame.v120( Axis::Horizontal, (discrete * scroll_factor).round() as i32, ); } } else if event.source() == AxisSource::Finger { frame = frame.stop(Axis::Horizontal); } } if let Some(vertical_amount) = event.amount(Axis::Vertical) { if vertical_amount != 0.0 { frame = frame.value(Axis::Vertical, scroll_factor * vertical_amount); if let Some(discrete) = event.amount_v120(Axis::Vertical) { frame = frame.v120( Axis::Vertical, (discrete * scroll_factor).round() as i32, ); } } else if event.source() == AxisSource::Finger { frame = frame.stop(Axis::Vertical); } } let ptr = seat.get_pointer().unwrap(); ptr.axis(self, frame); ptr.frame(self); } } InputEvent::GestureSwipeBegin { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .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()) { self.common.gesture_state = Some(GestureState::new(event.fingers())); } else { let serial = SERIAL_COUNTER.next_serial(); let pointer = seat.get_pointer().unwrap(); pointer.gesture_swipe_begin( self, &GestureSwipeBeginEvent { serial, time: event.time_msec(), fingers: event.fingers(), }, ); } } } InputEvent::GestureSwipeUpdate { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let mut activate_action: Option = None; if let Some(ref mut gesture_state) = self.common.gesture_state { let first_update = gesture_state.update( event.delta(), Duration::from_millis(event.time_msec() as u64), ); // Decide on action if first update if first_update { let mut natural_scroll = false; if let Some(scroll_config) = &self.common.config.cosmic_conf.input_touchpad.scroll_config { if let Some(natural) = scroll_config.natural_scroll { natural_scroll = natural; } } 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) } } Some(Direction::Right) => { 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) } } Some(Direction::Down) => { if natural_scroll { Some(SwipeAction::PrevWorkspace) } else { Some(SwipeAction::NextWorkspace) } } _ => None, // TODO: Other actions } } } _ => None, }; gesture_state.action = activate_action; } match gesture_state.action { Some(SwipeAction::NextWorkspace) | Some(SwipeAction::PrevWorkspace) => { self.common.shell.write().unwrap().update_workspace_delta( &seat.active_output(), gesture_state.delta, ) } _ => {} } } else { let pointer = seat.get_pointer().unwrap(); pointer.gesture_swipe_update( self, &GestureSwipeUpdateEvent { time: event.time_msec(), delta: event.delta(), }, ); } if let Some(action) = activate_action { self.handle_swipe_action(action, &seat); } } } InputEvent::GestureSwipeEnd { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); if let Some(ref gesture_state) = self.common.gesture_state { match gesture_state.action { Some(SwipeAction::NextWorkspace) | Some(SwipeAction::PrevWorkspace) => { let velocity = gesture_state.velocity(); let norm_velocity = if self.common.config.cosmic_conf.workspaces.workspace_layout == WorkspaceLayout::Horizontal { velocity / seat.active_output().geometry().size.w as f64 } else { velocity / seat.active_output().geometry().size.h as f64 }; let _ = self.common.shell.write().unwrap().end_workspace_swipe( &seat.active_output(), norm_velocity, &mut self.common.workspace_state.update(), ); } _ => {} } self.common.gesture_state = None; } else { let serial = SERIAL_COUNTER.next_serial(); let pointer = seat.get_pointer().unwrap(); pointer.gesture_swipe_end( self, &GestureSwipeEndEvent { serial, time: event.time_msec(), cancelled: event.cancelled(), }, ); } } } InputEvent::GesturePinchBegin { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let serial = SERIAL_COUNTER.next_serial(); let pointer = seat.get_pointer().unwrap(); pointer.gesture_pinch_begin( self, &GesturePinchBeginEvent { serial, time: event.time_msec(), fingers: event.fingers(), }, ); } } InputEvent::GesturePinchUpdate { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let pointer = seat.get_pointer().unwrap(); pointer.gesture_pinch_update( self, &GesturePinchUpdateEvent { time: event.time_msec(), delta: event.delta(), scale: event.scale(), rotation: event.rotation(), }, ); } } InputEvent::GesturePinchEnd { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let serial = SERIAL_COUNTER.next_serial(); let pointer = seat.get_pointer().unwrap(); pointer.gesture_pinch_end( self, &GesturePinchEndEvent { serial, time: event.time_msec(), cancelled: event.cancelled(), }, ); } } InputEvent::GestureHoldBegin { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let serial = SERIAL_COUNTER.next_serial(); let pointer = seat.get_pointer().unwrap(); pointer.gesture_hold_begin( self, &GestureHoldBeginEvent { serial, time: event.time_msec(), fingers: event.fingers(), }, ); } } InputEvent::GestureHoldEnd { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let serial = SERIAL_COUNTER.next_serial(); let pointer = seat.get_pointer().unwrap(); pointer.gesture_hold_end( self, &GestureHoldEndEvent { serial, time: event.time_msec(), cancelled: event.cancelled(), }, ); } } InputEvent::TouchDown { event, .. } => { let mut shell = self.common.shell.write().unwrap(); if let Some(seat) = shell.seats.for_device(&event.device()).cloned() { self.common.idle_notifier_state.notify_activity(&seat); let Some(output) = mapped_output_for_device(&self.common.config, &*shell, &event.device()) .cloned() else { return; }; let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); std::mem::drop(shell); let serial = SERIAL_COUNTER.next_serial(); let touch = seat.get_touch().unwrap(); touch.down( self, under, &DownEvent { slot: event.slot(), location: position.as_logical(), serial, time: event.time_msec(), }, ); } } InputEvent::TouchMotion { event, .. } => { let mut shell = self.common.shell.write().unwrap(); if let Some(seat) = shell.seats.for_device(&event.device()).cloned() { self.common.idle_notifier_state.notify_activity(&seat); let Some(output) = mapped_output_for_device(&self.common.config, &*shell, &event.device()) .cloned() else { return; }; let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); std::mem::drop(shell); let touch = seat.get_touch().unwrap(); touch.motion( self, under, &TouchMotionEvent { slot: event.slot(), location: position.as_logical(), time: event.time_msec(), }, ); } } InputEvent::TouchUp { event, .. } => { let mut shell = self.common.shell.write().unwrap(); if let Some(Trigger::Touch(slot)) = shell.overview_mode().0.active_trigger() { if *slot == event.slot() { shell.set_overview_mode(None, self.common.event_loop_handle.clone()); } } let maybe_seat = shell.seats.for_device(&event.device()).cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); std::mem::drop(shell); let serial = SERIAL_COUNTER.next_serial(); let touch = seat.get_touch().unwrap(); touch.up( self, &UpEvent { slot: event.slot(), time: event.time_msec(), serial, }, ); } } InputEvent::TouchCancel { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let touch = seat.get_touch().unwrap(); touch.cancel(self); } } InputEvent::TouchFrame { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); let touch = seat.get_touch().unwrap(); touch.frame(self); } } InputEvent::TabletToolAxis { event, .. } => { let mut shell = self.common.shell.write().unwrap(); if let Some(seat) = shell.seats.for_device(&event.device()).cloned() { self.common.idle_notifier_state.notify_activity(&seat); let Some(output) = mapped_output_for_device(&self.common.config, &shell, &event.device()) .cloned() else { return; }; let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); std::mem::drop(shell); let pointer = seat.get_pointer().unwrap(); pointer.motion( self, under.clone(), &MotionEvent { location: position.as_logical(), serial: SERIAL_COUNTER.next_serial(), time: self.common.clock.now().as_millis(), }, ); let tablet_seat = seat.tablet_seat(); let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device())); let tool = tablet_seat.get_tool(&event.tool()); if let (Some(tablet), Some(tool)) = (tablet, tool) { if event.pressure_has_changed() { tool.pressure(event.pressure()); } if event.distance_has_changed() { tool.distance(event.distance()); } if event.tilt_has_changed() { tool.tilt(event.tilt()); } if event.slider_has_changed() { tool.slider_position(event.slider_position()); } if event.rotation_has_changed() { tool.rotation(event.rotation()); } if event.wheel_has_changed() { tool.wheel(event.wheel_delta(), event.wheel_delta_discrete()); } tool.motion( position.as_logical(), under .and_then(|(f, loc)| f.wl_surface().map(|s| (s.into_owned(), loc))), &tablet, SERIAL_COUNTER.next_serial(), event.time_msec(), ); } } } InputEvent::TabletToolProximity { event, .. } => { let mut shell = self.common.shell.write().unwrap(); if let Some(seat) = shell.seats.for_device(&event.device()).cloned() { self.common.idle_notifier_state.notify_activity(&seat); let Some(output) = mapped_output_for_device(&self.common.config, &shell, &event.device()) .cloned() else { return; }; let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); std::mem::drop(shell); let pointer = seat.get_pointer().unwrap(); pointer.motion( self, under.clone(), &MotionEvent { location: position.as_logical(), serial: SERIAL_COUNTER.next_serial(), time: self.common.clock.now().as_millis(), }, ); let tablet_seat = seat.tablet_seat(); let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device())); let dh = self.common.display_handle.clone(); let tool = tablet_seat.add_tool::(self, &dh, &event.tool()); if let Some(tablet) = tablet { match event.state() { ProximityState::In => { if let Some(under) = under.and_then(|(f, loc)| { f.wl_surface().map(|s| (s.into_owned(), loc)) }) { tool.proximity_in( position.as_logical(), under, &tablet, SERIAL_COUNTER.next_serial(), event.time_msec(), ) } } ProximityState::Out => tool.proximity_out(event.time_msec()), } } } } InputEvent::TabletToolTip { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) { match event.tip_state() { TabletToolTipState::Down => { tool.tip_down(SERIAL_COUNTER.next_serial(), event.time_msec()); } TabletToolTipState::Up => { tool.tip_up(event.time_msec()); } } } } } InputEvent::TabletToolButton { event, .. } => { let maybe_seat = self .common .shell .read() .unwrap() .seats .for_device(&event.device()) .cloned(); if let Some(seat) = maybe_seat { self.common.idle_notifier_state.notify_activity(&seat); if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) { tool.button( event.button(), event.button_state(), SERIAL_COUNTER.next_serial(), event.time_msec(), ); } } } InputEvent::Special(_) => {} InputEvent::SwitchToggle { event: _ } => {} } } /// Determine is key event should be intercepted as a key binding, or forwarded to surface pub fn filter_keyboard_input>( &mut self, event: &E, seat: &Seat, modifiers: &ModifiersState, handle: KeysymHandle<'_>, serial: Serial, ) -> FilterResult> { let mut shell = self.common.shell.write().unwrap(); let keyboard = seat.get_keyboard().unwrap(); let pointer = seat.get_pointer().unwrap(); let is_grabbed = keyboard.is_grabbed() || pointer.is_grabbed(); let current_focus = keyboard.current_focus(); //this should fall back to active output since there may not be a focused output let focused_output = seat.focused_or_active_output(); let shortcuts_inhibited = current_focus.as_ref().is_some_and(|f| { f.wl_surface() .and_then(|surface| { seat.keyboard_shortcuts_inhibitor_for_surface(&surface) .map(|inhibitor| inhibitor.is_active()) }) .unwrap_or(false) }); self.common.atspi_ei.input( modifiers, &handle, event.state(), event.time() as u64 * 1000, ); // Leave move overview mode, if any modifier was released if let Some(Trigger::KeyboardMove(action_modifiers)) = shell.overview_mode().0.active_trigger() { if (action_modifiers.ctrl && !modifiers.ctrl) || (action_modifiers.alt && !modifiers.alt) || (action_modifiers.logo && !modifiers.logo) || (action_modifiers.shift && !modifiers.shift) { shell.set_overview_mode(None, self.common.event_loop_handle.clone()); } } // Leave swap overview mode, if any key was released if let Some(Trigger::KeyboardSwap(action_pattern, old_descriptor)) = shell.overview_mode().0.active_trigger() { if (action_pattern.modifiers.ctrl && !modifiers.ctrl) || (action_pattern.modifiers.alt && !modifiers.alt) || (action_pattern.modifiers.logo && !modifiers.logo) || (action_pattern.modifiers.shift && !modifiers.shift) || (action_pattern.key.is_some() && handle.raw_syms().contains(&action_pattern.key.unwrap()) && event.state() == KeyState::Released) { shell.set_overview_mode(None, self.common.event_loop_handle.clone()); self.keyboard_swap( seat, &mut shell, old_descriptor, current_focus, &focused_output, ); } } // Leave or update resize mode, if modifiers changed or initial key was released if let Some(action_pattern) = shell.resize_mode().0.active_binding() { if action_pattern.key.is_some() && event.state() == KeyState::Released && handle.raw_syms().contains(&action_pattern.key.unwrap()) { shell.set_resize_mode( None, &self.common.config, self.common.event_loop_handle.clone(), ); } else if !cosmic_modifiers_eq_smithay(&action_pattern.modifiers, modifiers) { let mut new_pattern = action_pattern.clone(); new_pattern.modifiers = cosmic_modifiers_from_smithay(modifiers.clone()); let enabled = self.common .config .shortcuts .iter() .find_map(move |(binding, action)| { if binding == &new_pattern && matches!(action, shortcuts::Action::Resizing(_)) { let shortcuts::Action::Resizing(direction) = action else { unreachable!() }; Some((new_pattern.clone(), *direction)) } else { None } }); shell.set_resize_mode( enabled, &self.common.config, self.common.event_loop_handle.clone(), ); } } // Special case resizing with regards to arrow keys if let Some(direction) = shell.resize_mode().0.active_direction() { let resize_edge = match handle.modified_sym() { Keysym::Left | Keysym::h | Keysym::H => Some(ResizeEdge::LEFT), Keysym::Down | Keysym::j | Keysym::J => Some(ResizeEdge::BOTTOM), Keysym::Up | Keysym::k | Keysym::K => Some(ResizeEdge::TOP), Keysym::Right | Keysym::l | Keysym::L => Some(ResizeEdge::RIGHT), _ => None, }; if let Some(mut edge) = resize_edge { if direction == ResizeDirection::Inwards { edge.flip_direction(); } let action = Action::Private(PrivateAction::Resizing( direction, edge.into(), cosmic_keystate_from_smithay(event.state()), )); let key_pattern = shortcuts::Binding { modifiers: cosmic_modifiers_from_smithay(modifiers.clone()), key: Some(handle.modified_sym()), description: None, }; if event.state() == KeyState::Released { if let Some(tokens) = seat.supressed_keys().filter(&handle) { for token in tokens { self.common.event_loop_handle.remove(token); } } } else { let seat_clone = seat.clone(); let action_clone = action.clone(); let key_pattern_clone = key_pattern.clone(); let start = Instant::now(); let time = event.time_msec(); let token = self .common .event_loop_handle .insert_source( Timer::from_duration(Duration::from_millis(200)), move |current, _, state| { let duration = current.duration_since(start).as_millis(); state.handle_action( action_clone.clone(), &seat_clone, serial, time.overflowing_add(duration as u32).0, key_pattern_clone.clone(), None, true, ); calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(25)) }, ) .ok(); seat.supressed_keys().add(&handle, token); } return FilterResult::Intercept(Some((action, key_pattern))); } } std::mem::drop(shell); // cancel grabs if is_grabbed && handle.modified_sym() == Keysym::Escape && event.state() == KeyState::Pressed && !modifiers.alt && !modifiers.ctrl && !modifiers.logo && !modifiers.shift { seat.supressed_keys().add(&handle, None); return FilterResult::Intercept(Some(( Action::Private(PrivateAction::Escape), shortcuts::Binding { modifiers: shortcuts::Modifiers::default(), key: Some(Keysym::Escape), description: None, }, ))); } if event.state() == KeyState::Released { let removed = self .common .atspi_ei .active_virtual_mods .remove(&event.key_code()); // If `Caps_Lock` is a virtual modifier, and is in locked state, clear it if removed && handle.modified_sym() == Keysym::Caps_Lock { if (modifiers.serialized.locked & 2) != 0 { let serial = SERIAL_COUNTER.next_serial(); let time = self.common.clock.now().as_millis(); keyboard.input( self, event.key_code(), KeyState::Pressed, serial, time, |_, _, _| FilterResult::<()>::Forward, ); let serial = SERIAL_COUNTER.next_serial(); keyboard.input( self, event.key_code(), KeyState::Released, serial, time, |_, _, _| FilterResult::<()>::Forward, ); } } } else if event.state() == KeyState::Pressed && self .common .atspi_ei .virtual_mods .contains(&event.key_code()) { self.common .atspi_ei .active_virtual_mods .insert(event.key_code()); tracing::debug!( "active virtual mods: {:?}", self.common.atspi_ei.active_virtual_mods ); seat.supressed_keys().add(&handle, None); return FilterResult::Intercept(None); } // Skip released events for initially surpressed keys if event.state() == KeyState::Released { if let Some(tokens) = seat.supressed_keys().filter(&handle) { for token in tokens { self.common.event_loop_handle.remove(token); } return FilterResult::Intercept(None); } } // Handle VT switches if event.state() == KeyState::Pressed && (Keysym::XF86_Switch_VT_1.raw()..=Keysym::XF86_Switch_VT_12.raw()) .contains(&handle.modified_sym().raw()) { if let Err(err) = self.backend.kms().switch_vt( (handle.modified_sym().raw() - Keysym::XF86_Switch_VT_1.raw() + 1) as i32, ) { error!(?err, "Failed switching virtual terminal."); } seat.supressed_keys().add(&handle, None); return FilterResult::Intercept(None); } if self.common.atspi_ei.has_keyboard_grab() || self .common .atspi_ei .has_key_grab(modifiers.serialized.layout_effective, event.key_code()) { return FilterResult::Intercept(None); } // handle the rest of the global shortcuts let mut clear_queue = true; if !shortcuts_inhibited { let modifiers_queue = seat.modifiers_shortcut_queue(); for (binding, action) in self.common.config.shortcuts.iter() { if *action == shortcuts::Action::Disable { continue; } // is this a released (triggered) modifier-only binding? if binding.key.is_none() && event.state() == KeyState::Released && !cosmic_modifiers_eq_smithay(&binding.modifiers, modifiers) && modifiers_queue.take(binding) { modifiers_queue.clear(); return FilterResult::Intercept(Some(( Action::Shortcut(action.clone()), binding.clone(), ))); } // could this potentially become a modifier-only binding? if binding.key.is_none() && event.state() == KeyState::Pressed && cosmic_modifiers_eq_smithay(&binding.modifiers, modifiers) { modifiers_queue.set(binding.clone()); clear_queue = false; } // is this a normal binding? if binding.key.is_some() && event.state() == KeyState::Pressed && handle.raw_syms().contains(&binding.key.unwrap()) && cosmic_modifiers_eq_smithay(&binding.modifiers, modifiers) { modifiers_queue.clear(); seat.supressed_keys().add(&handle, None); return FilterResult::Intercept(Some(( Action::Shortcut(action.clone()), binding.clone(), ))); } } } // no binding if clear_queue { seat.modifiers_shortcut_queue().clear(); } // keys are passed through to apps FilterResult::Forward } fn keyboard_swap( &self, seat: &Seat, shell: &mut Shell, old_descriptor: &NodeDesc, current_focus: Option, focused_output: &Output, ) { if let Some(focus) = current_focus { if let Some(new_descriptor) = shell.workspaces.active(&focused_output).1.node_desc(focus) { let mut spaces = shell.workspaces.spaces_mut(); if old_descriptor.handle != new_descriptor.handle { let (mut old_w, mut other_w) = spaces.partition::, _>(|w| w.handle == old_descriptor.handle); if let Some(old_workspace) = old_w.get_mut(0) { if let Some(new_workspace) = other_w .iter_mut() .find(|w| w.handle == new_descriptor.handle) { { let mut stack = new_workspace.focus_stack.get_mut(&seat); for elem in old_descriptor.focus_stack.iter().flat_map(|node_id| { old_workspace.tiling_layer.element_for_node(node_id) }) { stack.append(elem); } } { let mut stack = old_workspace.focus_stack.get_mut(&seat); for elem in new_descriptor.focus_stack.iter().flat_map(|node_id| { new_workspace.tiling_layer.element_for_node(node_id) }) { stack.append(elem); } } if let Some(focus) = TilingLayout::swap_trees( &mut old_workspace.tiling_layer, Some(&mut new_workspace.tiling_layer), &old_descriptor, &new_descriptor, ) { let seat = seat.clone(); self.common.event_loop_handle.insert_idle(move |state| { Shell::set_focus(state, Some(&focus), &seat, None, true); }); } old_workspace.refresh_focus_stack(); new_workspace.refresh_focus_stack(); } } } else { if let Some(workspace) = spaces.find(|w| w.handle == new_descriptor.handle) { if let Some(focus) = TilingLayout::swap_trees( &mut workspace.tiling_layer, None, &old_descriptor, &new_descriptor, ) { std::mem::drop(spaces); let seat = seat.clone(); self.common.event_loop_handle.insert_idle(move |state| { Shell::set_focus(state, Some(&focus), &seat, None, true); }); } workspace.refresh_focus_stack(); } } } } else { let new_workspace = shell.workspaces.active(&focused_output).1.handle; if new_workspace != old_descriptor.handle { let spaces = shell.workspaces.spaces_mut(); let (mut old_w, mut other_w) = spaces.partition::, _>(|w| w.handle == old_descriptor.handle); if let Some(old_workspace) = old_w.get_mut(0) { if let Some(new_workspace) = other_w.iter_mut().find(|w| w.handle == new_workspace) { if new_workspace.tiling_layer.windows().next().is_none() { { let mut stack = new_workspace.focus_stack.get_mut(&seat); for elem in old_descriptor.focus_stack.iter().flat_map(|node_id| { old_workspace.tiling_layer.element_for_node(node_id) }) { stack.append(elem); } } if let Some(focus) = TilingLayout::move_tree( &mut old_workspace.tiling_layer, &mut new_workspace.tiling_layer, &new_workspace.handle, &seat, new_workspace.focus_stack.get(&seat).iter(), old_descriptor.clone(), None, ) { let seat = seat.clone(); self.common.event_loop_handle.insert_idle(move |state| { Shell::set_focus(state, Some(&focus), &seat, None, true); }); } old_workspace.refresh_focus_stack(); } } } } } } pub fn element_under( global_pos: Point, output: &Output, shell: &Shell, ) -> Option { let (previous_workspace, workspace) = shell.workspaces.active(output); let (previous_idx, idx) = shell.workspaces.active_num(output); let previous_workspace = previous_workspace .zip(previous_idx) .map(|((w, start), idx)| (w.handle, idx, start)); let workspace = (workspace.handle, idx); let element_filter = if workspace_overview_is_open(output) { ElementFilter::LayerShellOnly } else { ElementFilter::All }; render_input_order( shell, output, previous_workspace, workspace, element_filter, |stage| { match stage { Stage::SessionLock(lock_surface) => { return ControlFlow::Break(Ok(lock_surface .cloned() .map(KeyboardFocusTarget::LockSurface))) } Stage::LayerPopup { layer, popup, location, } => { if layer.can_receive_keyboard_focus() { let surface = popup.wl_surface(); if under_from_surface_tree( surface, global_pos.as_logical(), location.as_logical(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, ) .is_some() { return ControlFlow::Break(Ok(Some( KeyboardFocusTarget::LayerSurface(layer), ))); } } } Stage::LayerSurface { layer, location } => { if layer.can_receive_keyboard_focus() { if under_from_surface_tree( layer.wl_surface(), global_pos.as_logical(), location.as_logical(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, ) .is_some() { return ControlFlow::Break(Ok(Some( KeyboardFocusTarget::LayerSurface(layer), ))); } } } Stage::OverrideRedirect { .. } => { // Override redirect windows take a grab on their own via // the Xwayland keyboard grab protocol. Don't focus them via click. } Stage::StickyPopups(layout) => { if let Some(element) = layout.popup_element_under(global_pos.to_local(output)) { return ControlFlow::Break(Ok(Some(element))); } } Stage::Sticky(layout) => { if let Some(element) = layout.toplevel_element_under(global_pos.to_local(output)) { return ControlFlow::Break(Ok(Some(element))); } } Stage::WorkspacePopups { workspace, offset } => { let location = global_pos + offset.as_global().to_f64(); let output = workspace.output(); let output_geo = output.geometry().to_local(output); if Rectangle::from_loc_and_size(offset.as_local(), output_geo.size) .intersection(output_geo) .is_some_and(|geometry| { geometry.contains(global_pos.to_local(output).to_i32_round()) }) { if let Some(element) = workspace.popup_element_under(location) { return ControlFlow::Break(Ok(Some(element))); } } } Stage::Workspace { workspace, offset } => { let location = global_pos + offset.as_global().to_f64(); let output = workspace.output(); let output_geo = output.geometry().to_local(output); if Rectangle::from_loc_and_size(offset.as_local(), output_geo.size) .intersection(output_geo) .is_some_and(|geometry| { geometry.contains(global_pos.to_local(output).to_i32_round()) }) { if let Some(element) = workspace.toplevel_element_under(location) { return ControlFlow::Break(Ok(Some(element))); } } } } ControlFlow::Continue(()) }, ) .ok() .flatten() } pub fn surface_under( global_pos: Point, output: &Output, shell: &Shell, ) -> Option<(PointerFocusTarget, Point)> { let (previous_workspace, workspace) = shell.workspaces.active(output); let (previous_idx, idx) = shell.workspaces.active_num(output); let previous_workspace = previous_workspace .zip(previous_idx) .map(|((w, start), idx)| (w.handle, idx, start)); let workspace = (workspace.handle, idx); let element_filter = if workspace_overview_is_open(output) { ElementFilter::LayerShellOnly } else { ElementFilter::All }; let relative_pos = global_pos.to_local(output); let output_geo = output.geometry(); let overview = shell.overview_mode().0; render_input_order( shell, output, previous_workspace, workspace, element_filter, |stage| { match stage { Stage::SessionLock(lock_surface) => { return ControlFlow::Break(Ok(lock_surface.map(|surface| { ( PointerFocusTarget::WlSurface { surface: surface.wl_surface().clone(), toplevel: None, }, output_geo.loc.to_f64(), ) }))); } Stage::LayerPopup { popup, location, .. } => { let surface = popup.wl_surface(); if let Some((surface, surface_loc)) = under_from_surface_tree( surface, global_pos.as_logical(), location.as_logical(), WindowSurfaceType::ALL, ) { return ControlFlow::Break(Ok(Some(( PointerFocusTarget::WlSurface { surface, toplevel: None, }, surface_loc.as_global().to_f64(), )))); } } Stage::LayerSurface { layer, location } => { let surface = layer.wl_surface(); if let Some((surface, surface_loc)) = under_from_surface_tree( surface, global_pos.as_logical(), location.as_logical(), WindowSurfaceType::ALL, ) { return ControlFlow::Break(Ok(Some(( PointerFocusTarget::WlSurface { surface, toplevel: None, }, surface_loc.as_global().to_f64(), )))); } } Stage::OverrideRedirect { surface, location } => { if let Some(surface) = surface.wl_surface() { if let Some((surface, surface_loc)) = under_from_surface_tree( &surface, global_pos.as_logical(), location.as_logical(), WindowSurfaceType::ALL, ) { return ControlFlow::Break(Ok(Some(( PointerFocusTarget::WlSurface { surface, toplevel: None, }, surface_loc.as_global().to_f64(), )))); } } } Stage::StickyPopups(floating_layer) => { if let Some(under) = floating_layer .popup_surface_under(relative_pos) .map(|(target, point)| (target, point.to_global(output))) { return ControlFlow::Break(Ok(Some(under))); } } Stage::Sticky(floating_layer) => { if let Some(under) = floating_layer .toplevel_surface_under(relative_pos) .map(|(target, point)| (target, point.to_global(output))) { return ControlFlow::Break(Ok(Some(under))); } } Stage::WorkspacePopups { workspace, offset } => { let global_pos = global_pos + offset.to_f64().as_global(); if let Some(under) = workspace.popup_surface_under(global_pos, overview.clone()) { return ControlFlow::Break(Ok(Some(under))); } } Stage::Workspace { workspace, offset } => { let global_pos = global_pos + offset.to_f64().as_global(); if let Some(under) = workspace.toplevel_surface_under(global_pos, overview.clone()) { return ControlFlow::Break(Ok(Some(under))); } } } ControlFlow::Continue(()) }, ) .ok() .flatten() } } fn cursor_sessions_for_output( shell: &Shell, output: &Output, ) -> impl Iterator { let workspace = shell.active_space(&output); let maybe_fullscreen = workspace.get_fullscreen(); workspace .cursor_sessions() .into_iter() .chain( maybe_fullscreen .map(|w| w.cursor_sessions()) .into_iter() .flatten(), ) .chain(output.cursor_sessions().into_iter()) } fn transform_output_mapped_position<'a, B, E>(output: &Output, event: &E) -> Point where B: InputBackend, E: AbsolutePositionEvent, B::Device: 'static, { let geometry = output.geometry(); let transform = output.current_transform(); let size = transform .invert() .transform_size(geometry.size.as_logical()); geometry.loc.to_f64() + transform .transform_point_in(event.position_transformed(size), &size.to_f64()) .as_global() } // TODO Is it possible to determine mapping for external touchscreen? // Support map_to_region like sway? fn mapped_output_for_device<'a, D: Device + 'static>( config: &Config, shell: &'a Shell, device: &D, ) -> Option<&'a Output> { let map_to_output = if let Some(device) = ::downcast_ref::(device) { config .map_to_output(device) .and_then(|name| shell.outputs().find(|output| output.name() == name)) } else { None }; map_to_output.or_else(|| shell.builtin_output()) } // FIXME: When f64::next_down reaches stable rust, use that instead trait NextDown { fn next_lower(self) -> Self; } impl NextDown for f64 { fn next_lower(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. const NEG_TINY_BITS: u64 = 0x8000_0000_0000_0001; // Smallest (in magnitude) negative f64. const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff; let bits = self.to_bits(); if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { return self; } let abs = bits & CLEAR_SIGN_MASK; let next_bits = if abs == 0 { NEG_TINY_BITS } else if bits == abs { bits - 1 } else { bits + 1 }; Self::from_bits(next_bits) } }