xwayland: Allow eavesdropping on certain keyboard/pointer events
This commit is contained in:
parent
23f51eb150
commit
cbc4ad6fc2
6 changed files with 330 additions and 24 deletions
|
|
@ -46,6 +46,8 @@ pub struct CosmicCompConfig {
|
|||
pub focus_follows_cursor_delay: u64,
|
||||
/// Let X11 applications scale themselves
|
||||
pub descale_xwayland: bool,
|
||||
/// Let X11 applications snoop on certain key-presses to allow for global shortcuts
|
||||
pub xwayland_eavesdropping: XwaylandEavesdropping,
|
||||
/// The threshold before windows snap themselves to output edges
|
||||
pub edge_snap_threshold: u32,
|
||||
pub accessibility_zoom: ZoomConfig,
|
||||
|
|
@ -79,6 +81,7 @@ impl Default for CosmicCompConfig {
|
|||
cursor_follows_focus: false,
|
||||
focus_follows_cursor_delay: 250,
|
||||
descale_xwayland: false,
|
||||
xwayland_eavesdropping: XwaylandEavesdropping::default(),
|
||||
edge_snap_threshold: 0,
|
||||
accessibility_zoom: ZoomConfig::default(),
|
||||
}
|
||||
|
|
@ -154,3 +157,18 @@ pub enum ZoomMovement {
|
|||
Centered,
|
||||
Continuously,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct XwaylandEavesdropping {
|
||||
pub keyboard: EavesdroppingKeyboardMode,
|
||||
pub pointer: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum EavesdroppingKeyboardMode {
|
||||
None,
|
||||
Modifiers,
|
||||
#[default]
|
||||
Combinations,
|
||||
All,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ pub use self::types::*;
|
|||
use cosmic::config::CosmicTk;
|
||||
use cosmic_comp_config::{
|
||||
input::InputConfig, workspace::WorkspaceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior,
|
||||
XkbConfig, ZoomConfig,
|
||||
XkbConfig, XwaylandEavesdropping, ZoomConfig,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -923,6 +923,15 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
|||
state.common.update_xwayland_scale();
|
||||
}
|
||||
}
|
||||
"xwayland_eavesdropping" => {
|
||||
let new = get_config::<XwaylandEavesdropping>(&config, "xwayland_eavesdropping");
|
||||
if new != state.common.config.cosmic_conf.xwayland_eavesdropping {
|
||||
state.common.config.cosmic_conf.xwayland_eavesdropping = new;
|
||||
state
|
||||
.common
|
||||
.xwayland_reset_eavesdropping(SERIAL_COUNTER.next_serial());
|
||||
}
|
||||
}
|
||||
"focus_follows_cursor" => {
|
||||
let new = get_config::<bool>(&config, "focus_follows_cursor");
|
||||
if new != state.common.config.cosmic_conf.focus_follows_cursor {
|
||||
|
|
|
|||
|
|
@ -237,9 +237,40 @@ impl State {
|
|||
.lock()
|
||||
.unwrap() = Some(serial);
|
||||
}
|
||||
Self::filter_keyboard_input(
|
||||
|
||||
let current_focus = seat.get_keyboard().unwrap().current_focus();
|
||||
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)
|
||||
});
|
||||
let sym = handle.modified_sym();
|
||||
|
||||
let result = Self::filter_keyboard_input(
|
||||
data, &event, &seat, modifiers, handle, serial,
|
||||
)
|
||||
);
|
||||
|
||||
if (matches!(result, FilterResult::Forward)
|
||||
&& !seat.get_keyboard().unwrap().is_grabbed()
|
||||
&& !shortcuts_inhibited
|
||||
&& !matches!(
|
||||
current_focus,
|
||||
Some(KeyboardFocusTarget::LockSurface(_))
|
||||
))
|
||||
// we don't want to accidentally leave any keys pressed
|
||||
// and do more filtering in `xwayland_notify_key_event`
|
||||
// for released keys
|
||||
|| state == KeyState::Released
|
||||
{
|
||||
data.common.xwayland_notify_key_event(
|
||||
sym, keycode, state, serial, time,
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
},
|
||||
)
|
||||
.flatten()
|
||||
|
|
@ -652,7 +683,7 @@ impl State {
|
|||
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| {
|
||||
let shortcuts_inhibited = current_focus.as_ref().is_some_and(|f| {
|
||||
f.wl_surface()
|
||||
.and_then(|surface| {
|
||||
seat.keyboard_shortcuts_inhibitor_for_surface(&surface)
|
||||
|
|
@ -663,6 +694,7 @@ impl State {
|
|||
|
||||
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
|
||||
|
|
@ -810,6 +842,18 @@ impl State {
|
|||
std::mem::drop(shell);
|
||||
};
|
||||
|
||||
if pass_event
|
||||
&& !matches!(current_focus, Some(KeyboardFocusTarget::LockSurface(_)))
|
||||
&& !shortcuts_inhibited
|
||||
{
|
||||
self.common.xwayland_notify_pointer_button_event(
|
||||
button,
|
||||
event.state(),
|
||||
serial,
|
||||
event.time_msec(),
|
||||
);
|
||||
}
|
||||
|
||||
let ptr = seat.get_pointer().unwrap();
|
||||
if pass_event {
|
||||
ptr.button(
|
||||
|
|
|
|||
|
|
@ -274,12 +274,12 @@ fn update_focus_state(
|
|||
}
|
||||
}
|
||||
|
||||
let serial = serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial());
|
||||
state
|
||||
.common
|
||||
.xwayland_notify_focus_change(target.cloned(), serial);
|
||||
ActiveFocus::set(seat, target.cloned());
|
||||
keyboard.set_focus(
|
||||
state,
|
||||
target.cloned(),
|
||||
serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()),
|
||||
);
|
||||
keyboard.set_focus(state, target.cloned(), serial);
|
||||
std::mem::drop(keyboard);
|
||||
|
||||
//update the focused output or set it to the active output
|
||||
|
|
|
|||
|
|
@ -28,9 +28,12 @@ use smithay::{
|
|||
},
|
||||
Seat,
|
||||
},
|
||||
reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource},
|
||||
reexports::wayland_server::{
|
||||
backend::ObjectId, protocol::wl_surface::WlSurface, Client, Resource,
|
||||
},
|
||||
utils::{IsAlive, Logical, Point, Serial, Transform},
|
||||
wayland::{seat::WaylandFocus, session_lock::LockSurface},
|
||||
xwayland::xwm::XwmId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -155,6 +158,15 @@ impl PointerFocusTarget {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_client(&self, client: &Client) -> bool {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface { surface, .. } => {
|
||||
surface.client().is_some_and(|c| c == *client)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardFocusTarget {
|
||||
|
|
@ -167,6 +179,24 @@ impl KeyboardFocusTarget {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_xwm(&self, xwm: XwmId) -> bool {
|
||||
match self {
|
||||
KeyboardFocusTarget::Element(mapped) => {
|
||||
if let Some(surface) = mapped.active_window().x11_surface() {
|
||||
return surface.xwm_id().unwrap() == xwm;
|
||||
}
|
||||
}
|
||||
KeyboardFocusTarget::Fullscreen(surface) => {
|
||||
if let Some(surface) = surface.x11_surface() {
|
||||
return surface.xwm_id().unwrap() == xwm;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
233
src/xwayland.rs
233
src/xwayland.rs
|
|
@ -11,12 +11,16 @@ use crate::{
|
|||
toplevel_management::minimize_rectangle, xdg_activation::ActivationContext,
|
||||
},
|
||||
};
|
||||
use cosmic_comp_config::EavesdroppingKeyboardMode;
|
||||
use smithay::{
|
||||
backend::drm::DrmNode,
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
input::{ButtonState, KeyState, Keycode},
|
||||
},
|
||||
desktop::space::SpaceElement,
|
||||
input::pointer::CursorIcon,
|
||||
input::{keyboard::ModifiersState, pointer::CursorIcon},
|
||||
reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window},
|
||||
utils::{Logical, Point, Rectangle, Size, SERIAL_COUNTER},
|
||||
utils::{Logical, Point, Rectangle, Serial, Size, SERIAL_COUNTER},
|
||||
wayland::{
|
||||
selection::{
|
||||
data_device::{
|
||||
|
|
@ -37,12 +41,16 @@ use smithay::{
|
|||
},
|
||||
};
|
||||
use tracing::{error, trace, warn};
|
||||
use xkbcommon::xkb::Keysym;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XWaylandState {
|
||||
pub client: Client,
|
||||
pub xwm: Option<X11Wm>,
|
||||
pub display: u32,
|
||||
pub pressed_keys: Vec<Keycode>,
|
||||
pub pressed_buttons: Vec<u32>,
|
||||
pub last_modifier_state: Option<ModifiersState>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
|
@ -84,6 +92,9 @@ impl State {
|
|||
client: client.clone(),
|
||||
xwm: None,
|
||||
display: display_number,
|
||||
pressed_keys: Vec::new(),
|
||||
pressed_buttons: Vec::new(),
|
||||
last_modifier_state: None,
|
||||
});
|
||||
|
||||
let mut wm = match X11Wm::start_wm(
|
||||
|
|
@ -137,31 +148,225 @@ impl State {
|
|||
}
|
||||
|
||||
impl Common {
|
||||
fn is_x_focused(&self, xwm: XwmId) -> bool {
|
||||
if let Some(keyboard) = self
|
||||
fn has_x_keyboard_focus(&self, xwmid: XwmId) -> bool {
|
||||
let keyboard = self
|
||||
.shell
|
||||
.read()
|
||||
.unwrap()
|
||||
.seats
|
||||
.last_active()
|
||||
.get_keyboard()
|
||||
.unwrap();
|
||||
|
||||
keyboard
|
||||
.current_focus()
|
||||
.is_some_and(|target| target.is_xwm(xwmid))
|
||||
}
|
||||
|
||||
fn has_x_pointer_focus(&self, xwmid: XwmId) -> bool {
|
||||
let pointer = self
|
||||
.shell
|
||||
.read()
|
||||
.unwrap()
|
||||
.seats
|
||||
.last_active()
|
||||
.get_pointer()
|
||||
.unwrap();
|
||||
|
||||
if let Some(x_client) = self.xwayland_state.as_ref().and_then(|xstate| {
|
||||
xstate
|
||||
.xwm
|
||||
.as_ref()
|
||||
.is_some_and(|xwm| xwm.id() == xwmid)
|
||||
.then_some(&xstate.client)
|
||||
}) {
|
||||
pointer
|
||||
.current_focus()
|
||||
.is_some_and(|target| target.is_client(x_client))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xwayland_notify_focus_change(
|
||||
&mut self,
|
||||
target: Option<KeyboardFocusTarget>,
|
||||
serial: Serial,
|
||||
) {
|
||||
if let Some(xwm_id) = self
|
||||
.xwayland_state
|
||||
.as_ref()
|
||||
.and_then(|xstate| xstate.xwm.as_ref())
|
||||
.map(|xwm| xwm.id())
|
||||
{
|
||||
match keyboard.current_focus() {
|
||||
Some(KeyboardFocusTarget::Element(mapped)) => {
|
||||
if let Some(surface) = mapped.active_window().x11_surface() {
|
||||
return surface.xwm_id().unwrap() == xwm;
|
||||
if target
|
||||
.as_ref()
|
||||
.is_some_and(|target| matches!(target, KeyboardFocusTarget::LockSurface(_)))
|
||||
|| (!self.has_x_keyboard_focus(xwm_id)
|
||||
&& target.as_ref().is_some_and(|target| target.is_xwm(xwm_id)))
|
||||
{
|
||||
self.xwayland_reset_eavesdropping(serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xwayland_reset_eavesdropping(&mut self, serial: Serial) {
|
||||
let seat = self.shell.read().unwrap().seats.last_active().clone();
|
||||
let keyboard = seat.get_keyboard().unwrap();
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
|
||||
let xstate = self.xwayland_state.as_mut().unwrap();
|
||||
xstate.last_modifier_state.take();
|
||||
for key in xstate.pressed_keys.drain(..).rev() {
|
||||
for wl_keyboard in keyboard.client_keyboards(&xstate.client) {
|
||||
wl_keyboard.key(serial.into(), 0, key.raw() - 8, KeyState::Released.into());
|
||||
}
|
||||
}
|
||||
for button in xstate.pressed_buttons.drain(..).rev() {
|
||||
for wl_pointer in pointer.client_pointers(&xstate.client) {
|
||||
wl_pointer.button(serial.into(), 0, button, ButtonState::Released.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xwayland_notify_key_event(
|
||||
&mut self,
|
||||
sym: Keysym,
|
||||
code: Keycode,
|
||||
state: KeyState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
) {
|
||||
let config = self.config.cosmic_conf.xwayland_eavesdropping.keyboard;
|
||||
if config == EavesdroppingKeyboardMode::None {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.xwayland_state.as_ref().is_none_or(|xstate| {
|
||||
xstate
|
||||
.xwm
|
||||
.as_ref()
|
||||
.is_none_or(|xwm| self.has_x_keyboard_focus(xwm.id()))
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keyboard = self
|
||||
.shell
|
||||
.read()
|
||||
.unwrap()
|
||||
.seats
|
||||
.last_active()
|
||||
.get_keyboard()
|
||||
.unwrap();
|
||||
let modifiers = keyboard.modifier_state();
|
||||
let is_modifier = sym.is_modifier_key();
|
||||
|
||||
let xstate = self.xwayland_state.as_mut().unwrap();
|
||||
if state == KeyState::Pressed {
|
||||
match config {
|
||||
EavesdroppingKeyboardMode::Modifiers => {
|
||||
if !is_modifier {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some(KeyboardFocusTarget::Fullscreen(surface)) => {
|
||||
if let Some(surface) = surface.x11_surface() {
|
||||
return surface.xwm_id().unwrap() == xwm;
|
||||
EavesdroppingKeyboardMode::Combinations => {
|
||||
// don't forward alpha-numeric keys, just because shift is held, but forward shift itself
|
||||
if !is_modifier && !(modifiers.alt || modifiers.ctrl || modifiers.logo) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
xstate.pressed_keys.push(code);
|
||||
} else {
|
||||
let mut removed = false;
|
||||
xstate.pressed_keys.retain(|c| {
|
||||
if *c == code {
|
||||
removed = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if !removed {
|
||||
// Don't forward released events, we don't have a record off.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
tracing::trace!("Forwaring key {} {:?} to xwayland", code.raw() - 8, state);
|
||||
for wl_keyboard in keyboard.client_keyboards(&xstate.client) {
|
||||
wl_keyboard.key(serial.into(), time, code.raw() - 8, state.into());
|
||||
if xstate.last_modifier_state != Some(modifiers) {
|
||||
xstate.last_modifier_state = Some(modifiers);
|
||||
wl_keyboard.modifiers(
|
||||
serial.into(),
|
||||
modifiers.serialized.depressed,
|
||||
modifiers.serialized.latched,
|
||||
modifiers.serialized.locked,
|
||||
modifiers.serialized.layout_effective,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xwayland_notify_pointer_button_event(
|
||||
&mut self,
|
||||
button: u32,
|
||||
state: ButtonState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
) {
|
||||
if !self.config.cosmic_conf.xwayland_eavesdropping.pointer {
|
||||
return;
|
||||
}
|
||||
|
||||
let pointer = self
|
||||
.shell
|
||||
.read()
|
||||
.unwrap()
|
||||
.seats
|
||||
.last_active()
|
||||
.get_pointer()
|
||||
.unwrap();
|
||||
|
||||
if self.xwayland_state.as_ref().is_none_or(|xstate| {
|
||||
xstate
|
||||
.xwm
|
||||
.as_ref()
|
||||
.is_none_or(|xwm| self.has_x_pointer_focus(xwm.id()))
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
let xstate = self.xwayland_state.as_mut().unwrap();
|
||||
if state == ButtonState::Pressed {
|
||||
xstate.pressed_buttons.push(button);
|
||||
} else {
|
||||
let mut removed = false;
|
||||
xstate.pressed_buttons.retain(|b| {
|
||||
if *b == button {
|
||||
removed = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if !removed {
|
||||
// Don't forward released events, we don't have a record off.
|
||||
// This can happen if `xwayland_reset_eavesdropping` was called in between
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::trace!("Forwaring ptr button {} {:?} to Xwayland", button, state);
|
||||
for wl_pointer in pointer.client_pointers(&xstate.client) {
|
||||
wl_pointer.button(serial.into(), time, button, state.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_x11_stacking_order(&mut self) {
|
||||
|
|
@ -756,7 +961,7 @@ impl XwmHandler for State {
|
|||
}
|
||||
|
||||
fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionTarget) -> bool {
|
||||
self.common.is_x_focused(xwm)
|
||||
self.common.has_x_keyboard_focus(xwm)
|
||||
}
|
||||
|
||||
fn new_selection(&mut self, xwm: XwmId, selection: SelectionTarget, mime_types: Vec<String>) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue