use std::{ffi::OsString, os::unix::io::OwnedFd, process::Stdio}; use crate::{ backend::render::cursor::{Cursor, load_cursor_env, load_cursor_theme}, shell::{ CosmicSurface, PendingWindow, Shell, focus::target::KeyboardFocusTarget, grabs::ReleaseMode, }, state::State, utils::prelude::*, wayland::handlers::xdg_activation::ActivationContext, }; use cosmic_comp_config::{EavesdroppingKeyboardMode, XwaylandDescaling}; use smithay::{ backend::{ allocator::Fourcc, drm::DrmNode, input::{ButtonState, KeyState, Keycode}, renderer::{ Bind, Frame, Offscreen, Renderer, element::{ Kind, memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement}, }, pixman::{PixmanError, PixmanRenderer}, utils::draw_render_elements, }, }, desktop::space::SpaceElement, input::{keyboard::ModifiersState, pointer::CursorIcon}, reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window}, utils::{ Buffer as BufferCoords, Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size, Transform, }, wayland::{ selection::{ SelectionTarget, data_device::{ clear_data_device_selection, current_data_device_selection_userdata, request_data_device_client_selection, set_data_device_selection, }, primary_selection::{ clear_primary_selection, current_primary_selection_userdata, request_primary_client_selection, set_primary_selection, }, }, xdg_activation::XdgActivationToken, }, xwayland::{ X11Surface, X11Wm, XWayland, XWaylandClientData, XWaylandEvent, XwmHandler, xwm::{Reorder, XwmId}, }, }; use tracing::{error, trace, warn}; use xcursor::parser::Image; use xkbcommon::xkb::Keysym; #[derive(Debug)] pub struct XWaylandState { pub client: Client, pub xwm: Option, pub display: u32, pub pressed_keys: Vec, pub pressed_buttons: Vec, pub last_modifier_state: Option, pub clipboard_selection_dirty: Option>, pub primary_selection_dirty: Option>, } impl State { pub fn launch_xwayland(&mut self, render_node: Option) { if self.common.xwayland_state.is_some() { return; } let (xwayland, client) = match XWayland::spawn( &self.common.display_handle, None, std::iter::empty::<(OsString, OsString)>(), true, Stdio::null(), Stdio::null(), |user_data| { if let Some(node) = render_node { user_data.insert_if_missing_threadsafe(|| node); } }, ) { Ok((xwayland, client)) => (xwayland, client), Err(err) => { error!(?err, "Failed to start Xwayland."); self.notify_ready(); return; } }; match self .common .event_loop_handle .insert_source(xwayland, move |event, _, data| match event { XWaylandEvent::Ready { x11_socket, display_number, } => { data.common.xwayland_state = Some(XWaylandState { client: client.clone(), xwm: None, display: display_number, pressed_keys: Vec::new(), pressed_buttons: Vec::new(), last_modifier_state: None, clipboard_selection_dirty: None, primary_selection_dirty: None, }); let wm = match X11Wm::start_wm( data.common.event_loop_handle.clone(), &data.common.display_handle, x11_socket, client.clone(), ) { Ok(wm) => wm, Err(err) => { error!(?err, "Failed to start Xwayland WM"); return; } }; let xwayland_state = data.common.xwayland_state.as_mut().unwrap(); xwayland_state.xwm = Some(wm); xwayland_state.reload_cursor(1.); data.notify_ready(); data.common.update_xwayland_scale(); data.common.update_xwayland_primary_output(); } XWaylandEvent::Error => { if let Some(mut xwayland_state) = data.common.xwayland_state.take() { xwayland_state.xwm = None; } data.notify_ready(); } }) { Ok(_token) => {} Err(err) => { error!(?err, "Failed to listen for Xwayland"); self.notify_ready(); } } } } fn scale_cursor( scale: f64, cursor_size: u32, image: &Image, ) -> Result<(Vec, Size, Point), PixmanError> { let mut renderer = PixmanRenderer::new()?; let image_scale = (image.size / cursor_size).max(1); let pixel_size = (cursor_size as f64 * scale).round() as i32; let buffer_size = Size::::from((pixel_size, pixel_size)); let output_size = buffer_size.to_logical(1, Transform::Normal).to_physical(1); let image_buffer = MemoryRenderBuffer::from_slice( &image.pixels_rgba, Fourcc::Abgr8888, (image.width as i32, image.height as i32), image_scale as i32, Transform::Normal, None, ); let element = MemoryRenderBufferRenderElement::from_buffer( &mut renderer, (0., 0.), &image_buffer, None, None, None, Kind::Unspecified, )?; let mut buffer = renderer.create_buffer(Fourcc::Abgr8888, buffer_size)?; let mut fb = renderer.bind(&mut buffer)?; let mut frame = renderer.render(&mut fb, output_size, Transform::Normal)?; draw_render_elements( &mut frame, scale, &[element], &[Rectangle::new((0, 0).into(), output_size)], )?; let sync = frame.finish()?; while sync.wait().is_err() {} let len = (buffer_size.w * buffer_size.h * 4) as usize; let mut data = Vec::with_capacity(len); assert_eq!(buffer.stride(), (buffer_size.w * 4) as usize); data.extend_from_slice(unsafe { std::slice::from_raw_parts(buffer.data() as *mut u8, len) }); let hotspot = Point::::from((image.xhot as i32, image.yhot as i32)) .to_f64() .to_logical( image_scale as f64, Transform::Normal, &Size::from((image.width as f64, image.height as f64)), ) .to_buffer( scale, Transform::Normal, &output_size.to_logical(1).to_f64(), ) .to_i32_round::(); Ok(( data, Size::from((buffer_size.w as u16, buffer_size.h as u16)), Point::from((hotspot.x as u16, hotspot.y as u16)), )) } impl XWaylandState { pub fn reload_cursor(&mut self, scale: f64) { if let Some(wm) = self.xwm.as_mut() { let (theme, size) = load_cursor_theme(); let cursor = Cursor::load(&theme, CursorIcon::Default, size); let image = cursor.get_image(scale.ceil() as u32, 0); let (pixels_rgba, size, hotspot) = match scale_cursor(scale, size, &image) { Ok(x) => x, Err(err) => { warn!("Failed to scale Xwayland cursor image: {}", err); ( image.pixels_rgba, Size::from((image.width as u16, image.height as u16)), Point::from((image.xhot as u16, image.yhot as u16)), ) } }; if let Err(err) = wm.set_cursor(&pixels_rgba, size, hotspot) { warn!( id = ?wm.id(), ?err, "Failed to set default cursor for Xwayland WM", ); } } } } impl Common { pub fn has_x_keyboard_focus(&self, xwmid: XwmId) -> bool { let keyboard = self .shell .read() .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().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, serial: Serial, ) { if let Some(xwm_id) = self .xwayland_state .as_ref() .and_then(|xstate| xstate.xwm.as_ref()) .map(|xwm| xwm.id()) { if target .as_ref() .is_some_and(|target| matches!(target, KeyboardFocusTarget::LockSurface(_))) { self.xwayland_reset_eavesdropping(serial); return; } if !self.has_x_keyboard_focus(xwm_id) && target.as_ref().is_some_and(|target| target.is_xwm(xwm_id)) { self.xwayland_reset_eavesdropping(serial); let xstate = self.xwayland_state.as_mut().unwrap(); if let Some(mime_types) = xstate.clipboard_selection_dirty.take() { if let Err(err) = xstate .xwm .as_mut() .unwrap() .new_selection(SelectionTarget::Clipboard, Some(mime_types)) { warn!(?err, "Failed to set Xwayland clipboard selection."); } } if let Some(mime_types) = xstate.primary_selection_dirty.take() { if let Err(err) = xstate .xwm .as_mut() .unwrap() .new_selection(SelectionTarget::Primary, Some(mime_types)) { warn!(?err, "Failed to set Xwayland clipboard selection."); } } } } } pub fn xwayland_reset_eavesdropping(&mut self, serial: Serial) { let seat = self.shell.read().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()); } } } #[profiling::function] 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() .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; } } 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; } } 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, ); } } } #[profiling::function] 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().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()); } } #[profiling::function] pub fn update_x11_stacking_order(&mut self) { let shell = self.shell.read(); let seat = shell.seats.last_active(); let active_output = seat.active_output(); if let Some(xwm) = self .xwayland_state .as_mut() .and_then(|state| state.xwm.as_mut()) { // front to back, given that is how the workspace enumerates let order = shell .workspaces .sets .iter() .filter(|(output, _)| *output == &active_output) .chain( shell .workspaces .sets .iter() .filter(|(output, _)| *output != &active_output), ) .flat_map(|(_, set)| { set.sticky_layer .mapped() .flat_map(|mapped| { let active = mapped.active_window(); std::iter::once(active.clone()).chain( mapped .is_stack() .then(move || { mapped .windows() .map(|(s, _)| s) .filter(move |s| s != &active) }) .into_iter() .flatten(), ) }) .chain( set.workspaces .iter() .enumerate() .filter(|(i, _)| *i == set.active) .chain( set.workspaces .iter() .enumerate() .filter(|(i, _)| *i != set.active), ) .flat_map(|(_, workspace)| { workspace .get_fullscreen() .filter(|f| { workspace .focus_stack .get(seat) .last() .is_some_and(|t| &t == f) }) .cloned() .into_iter() .chain(workspace.mapped().flat_map(|mapped| { let active = mapped.active_window(); std::iter::once(active.clone()).chain( mapped .is_stack() .then(move || { mapped .windows() .map(|(s, _)| s) .filter(move |s| s != &active) }) .into_iter() .flatten(), ) })) .chain( workspace .get_fullscreen() .filter(|f| { workspace .focus_stack .get(seat) .last() .is_none_or(|t| &t != f) }) .cloned() .into_iter(), ) .chain( workspace .minimized_windows .iter() .flat_map(|m| m.windows()), ) }), ) }) .collect::>(); // we don't include the popup elements, which contain the OR windows, because we are not supposed to restack them. // Which is also why we match upwards, to not disturb elements at the top. // // But this also means we need to match across all outputs and workspaces at once, to be sure nothing that shouldn't be on top of us is. if let Err(err) = xwm.update_stacking_order_upwards(order.iter().rev()) { warn!(wm_id = ?xwm.id(), ?err, "Failed to update Xwm stacking order."); } } } pub fn update_xwayland_scale(&mut self) { let new_scale = match self.config.cosmic_conf.descale_xwayland { XwaylandDescaling::Disabled => 1., XwaylandDescaling::Enabled => { let shell = self.shell.read(); shell .outputs() .map(|o| o.current_scale().integer_scale()) .max() .unwrap_or(1) as f64 } XwaylandDescaling::Fractional => { let shell = self.shell.read(); let val = if let Some(output) = shell.outputs().find(|o| o.config().xwayland_primary) { output.current_scale().fractional_scale().max(1f64) } else { shell .outputs() .map(|o| o.current_scale().fractional_scale()) .fold(1f64, |acc, val| acc.max(val)) }; val } }; // compare with current scale if Some(new_scale) != self.xwayland_scale { if let Some(xwayland) = self.xwayland_state.as_mut() { // backup geometries let geometries = self .shell .read() .mapped() .flat_map(|m| m.windows().map(|(s, _)| s)) .filter_map(|s| s.0.x11_surface().map(|x| (x.clone(), x.geometry()))) .collect::>(); let (_, cursor_size) = load_cursor_env(); // update xorg dpi if let Some(xwm) = xwayland.xwm.as_mut() { let base = 96. * 1024.; let dpi = new_scale * base; let fractional = new_scale.fract(); let integer = (new_scale - fractional).max(1.0); let unscaled_dpi = base * (1.0 + fractional / integer); if let Err(err) = xwm.set_xsettings( [ ("Xft/DPI".into(), (dpi.round() as i32).into()), ( "Xcursor/size".into(), ((new_scale * cursor_size as f64).round() as i32).into(), ), ( "Gdk/UnscaledDPI".into(), (unscaled_dpi.round() as i32).into(), ), ( "Gdk/WindowScalingFactor".into(), (new_scale.round() as i32).into(), ), ( "Gtk/CursorThemeSize".into(), ((new_scale * cursor_size as f64).round() as i32).into(), ), ] .into_iter(), ) { warn!(wm_id = ?xwm.id(), ?err, "Failed to update XSETTINGS."); } } // update cursor xwayland.reload_cursor(new_scale); // update client scale xwayland .client .get_data::() .unwrap() .compositor_state .set_client_scale(new_scale); // update wl/xdg_outputs for output in self.shell.read().outputs() { output.change_current_state(None, None, None, None); } // update geometries for (surface, geometry) in geometries.iter() { if let Err(err) = surface.configure(*geometry) { warn!(?err, surface = ?surface.window_id(), "Failed to update geometry after scale change"); } } self.update_x11_stacking_order(); self.xwayland_scale = Some(new_scale); } } } pub fn update_xwayland_primary_output(&mut self) { let mut xwayland_primary_output = None; for output in self.output_configuration_state.outputs() { if output.config().xwayland_primary { xwayland_primary_output = Some(output); break; } } if let Some(xstate) = self.xwayland_state.as_mut() { if let Some(xwm) = xstate.xwm.as_mut() { if let Err(err) = xwm.set_randr_primary_output(xwayland_primary_output.as_ref()) { warn!("Failed to set xwayland primary output: {}", err); return; }; } } self.output_configuration_state.update(); } } impl XwmHandler for State { fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm { self.common .xwayland_state .as_mut() .and_then(|state| state.xwm.as_mut()) .unwrap() } fn new_window(&mut self, _xwm: XwmId, _window: X11Surface) {} fn new_override_redirect_window(&mut self, _xwm: XwmId, _window: X11Surface) {} fn destroyed_window(&mut self, _xwm: XwmId, _window: X11Surface) {} fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) { if let Err(err) = window.set_mapped(true) { warn!(?window, ?err, "Failed to send Xwayland Mapped-Event",); } let mut shell = self.common.shell.write(); let startup_id = window.startup_id(); if shell.is_surface_mapped(&window) { warn!( ?window, "Got map_request for already mapped window? Ignoring" ); return; } let seat = shell.seats.last_active().clone(); if let Some(context) = startup_id .map(XdgActivationToken::from) .and_then(|token| self.common.xdg_activation_state.data_for_token(&token)) .and_then(|data| data.user_data.get::()) { shell.pending_activations.insert( crate::shell::ActivationKey::X11(window.window_id()), *context, ); } let surface = CosmicSurface::from(window); shell.pending_windows.push(PendingWindow { surface, seat, fullscreen: None, maximized: false, }); } fn map_window_notify(&mut self, _xwm: XwmId, surface: X11Surface) { let mut shell = self.common.shell.write(); if let Some(window) = shell .pending_windows .iter() .find(|pending| pending.surface.x11_surface() == Some(&surface)) .map(|pending| pending.surface.clone()) { if let std::collections::hash_map::Entry::Vacant(e) = shell .pending_activations .entry(crate::shell::ActivationKey::X11(surface.window_id())) { if let Some(startup_id) = window.x11_surface().and_then(|x| x.startup_id()) { if let Some(context) = self .common .xdg_activation_state .data_for_token(&XdgActivationToken::from(startup_id)) .and_then(|data| data.user_data.get::()) { e.insert(*context); } } } let res = shell.map_window( &window, &mut self.common.toplevel_info_state, &mut self.common.workspace_state, &self.common.event_loop_handle, ); if let Some(target) = res { let seat = shell.seats.last_active().clone(); std::mem::drop(shell); Shell::set_focus(self, Some(&target), &seat, None, false); } } } fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); if shell .override_redirect_windows .iter() .any(|or| or == &window) { return; } shell.map_override_redirect(window) } fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); if window.is_override_redirect() { shell.override_redirect_windows.retain(|or| or != &window); } else { let seat = shell.seats.last_active().clone(); shell.unmap_surface(&window, &seat, &mut self.common.toplevel_info_state); } let outputs = if let Some(wl_surface) = window.wl_surface() { shell .visible_output_for_surface(&wl_surface) .into_iter() .cloned() .collect::>() } else { shell.outputs().cloned().collect::>() }; for output in outputs.iter() { shell.refresh_active_space(output); } for output in outputs.into_iter() { self.backend.schedule_render(&output); } } fn configure_request( &mut self, _xwm: XwmId, window: X11Surface, x: Option, y: Option, w: Option, h: Option, _reorder: Option, ) { // We only allow floating X11 windows to resize themselves. Nothing else let shell = self.common.shell.read(); // TODO: Fullscreen if let Some(mapped) = shell .element_for_surface(&window) .filter(|mapped| !mapped.is_minimized()) { let current_geo = if let Some(workspace) = shell.space_for(mapped) { workspace .element_geometry(mapped) .filter(|_| workspace.is_floating(&window)) .map(|geo| geo.to_global(workspace.output())) } else if let Some((output, set)) = shell .workspaces .sets .iter() .find(|(_, set)| set.sticky_layer.mapped().any(|m| m == mapped)) { Some( set.sticky_layer .element_geometry(mapped) .unwrap() .to_global(output), ) } else { None }; if let Some(current_geo) = current_geo { let ssd_height = mapped.ssd_height(false).unwrap_or(0); mapped.set_geometry(Rectangle::new( current_geo.loc, ( w.map(|w| w as i32).unwrap_or(current_geo.size.w), h.map(|h| h as i32 + ssd_height) .unwrap_or(current_geo.size.h), ) .into(), )) } else { let _ = window.configure(None); // ack and force old state } } else { let mut current_geo = window.geometry(); if let Some(x) = x { current_geo.loc.x = x; } if let Some(y) = y { current_geo.loc.y = y; } if let Some(w) = w { current_geo.size.w = w as i32; } if let Some(h) = h { current_geo.size.h = h as i32; } // the window is not yet mapped. Lets give it what it wants let _ = window.configure(current_geo); } } fn configure_notify( &mut self, _xwm: XwmId, window: X11Surface, _geometry: Rectangle, above: Option, ) { if window.is_override_redirect() { let mut shell = self.common.shell.write(); if let Some(id) = above { let or_windows = &mut shell.override_redirect_windows; if let Some(own_pos) = or_windows.iter().position(|or| or == &window) { let compare_pos = or_windows .iter() .position(|or| or.window_id() == id) .unwrap_or(0); if compare_pos > own_pos { let this = or_windows.remove(own_pos); or_windows.insert(compare_pos, this); } } } let geo = window.geometry().as_global(); for (output, overlap) in shell.outputs().cloned().map(|o| { let intersection = o.geometry().intersection(geo); (o, intersection) }) { if let Some(overlap) = overlap { window.output_enter(&output, overlap.as_logical()); } else { window.output_leave(&output); } } } } fn resize_request( &mut self, _xwm: XwmId, window: X11Surface, _button: u32, resize_edge: smithay::xwayland::xwm::ResizeEdge, ) { if let Some(wl_surface) = window.wl_surface() { let mut shell = self.common.shell.write(); let seat = shell.seats.last_active().clone(); if let Some((grab, focus)) = shell.resize_request( &wl_surface, &seat, None, resize_edge.into(), self.common.config.cosmic_conf.edge_snap_threshold, true, ) { std::mem::drop(shell); if grab.is_touch_grab() { seat.get_touch() .unwrap() .set_grab(self, grab, SERIAL_COUNTER.next_serial()) } else { seat.get_pointer().unwrap().set_grab( self, grab, SERIAL_COUNTER.next_serial(), focus, ) } } } } fn move_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32) { if let Some(wl_surface) = window.wl_surface() { let mut shell = self.common.shell.write(); let seat = shell.seats.last_active().clone(); if let Some((grab, focus)) = shell.move_request( &wl_surface, &seat, None, ReleaseMode::NoMouseButtons, false, &self.common.config, &self.common.event_loop_handle, true, ) { std::mem::drop(shell); if grab.is_touch_grab() { seat.get_touch() .unwrap() .set_grab(self, grab, SERIAL_COUNTER.next_serial()) } else { seat.get_pointer().unwrap().set_grab( self, grab, SERIAL_COUNTER.next_serial(), focus, ) } } } } fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); if let Some(mapped) = shell.element_for_surface(&window).cloned() { let seat = shell.seats.last_active().clone(); shell.maximize_request(&mapped, &seat, true, &self.common.event_loop_handle); } else if let Some(pending) = shell .pending_windows .iter_mut() .find(|pending| pending.surface.x11_surface() == Some(&window)) { pending.maximized = true; } } fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); if let Some(mapped) = shell.element_for_surface(&window).cloned() { shell.unmaximize_request(&mapped); } else if let Some(pending) = shell .pending_windows .iter_mut() .find(|pending| pending.surface.x11_surface() == Some(&window)) { pending.maximized = false; } } fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); shell.minimize_request(&window); } fn unminimize_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); let seat = shell.seats.last_active().clone(); shell.unminimize_request(&window, &seat, &self.common.event_loop_handle); } fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); let seat = shell.seats.last_active().clone(); let output = window .wl_surface() .and_then(|surface| shell.visible_output_for_surface(&surface).cloned()) .unwrap_or_else(|| seat.focused_or_active_output()); match shell.fullscreen_request(&window, output.clone(), &self.common.event_loop_handle) { Some(target) => { std::mem::drop(shell); Shell::set_focus(self, Some(&target), &seat, None, true); } None => { if let Some(pending) = shell .pending_windows .iter_mut() .find(|pending| pending.surface.x11_surface() == Some(&window)) { pending.fullscreen = Some(output); } } } } fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); let seat = shell.seats.last_active().clone(); let should_focus = seat .get_keyboard() .unwrap() .current_focus() .is_some_and(|target| { if let KeyboardFocusTarget::Fullscreen(s) = target { s == window } else { false } }); if let Some(target) = shell.unfullscreen_request(&window, &self.common.event_loop_handle) { std::mem::drop(shell); if should_focus { Shell::set_focus(self, Some(&target), &seat, None, true); } } else if let Some(pending) = shell .pending_windows .iter_mut() .find(|pending| pending.surface.x11_surface() == Some(&window)) { pending.fullscreen.take(); } } fn active_window_request( &mut self, _xwm: XwmId, window: X11Surface, _timestamp: u32, _currently_active_window: Option, ) { if let Some(surface) = window.wl_surface() { self.activate_surface(&surface, None); } } fn send_selection( &mut self, _xwm: XwmId, selection: SelectionTarget, mime_type: String, fd: OwnedFd, ) { let seat = self.common.shell.read().seats.last_active().clone(); match selection { SelectionTarget::Clipboard => { if let Err(err) = request_data_device_client_selection(&seat, mime_type, fd) { error!( ?err, "Failed to request current wayland clipboard for Xwayland.", ); } } SelectionTarget::Primary => { if let Err(err) = request_primary_client_selection(&seat, mime_type, fd) { error!( ?err, "Failed to request current wayland primary selection for Xwayland.", ); } } } } fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionTarget) -> bool { self.common.has_x_keyboard_focus(xwm) } fn new_selection(&mut self, xwm: XwmId, selection: SelectionTarget, mime_types: Vec) { trace!(?selection, ?mime_types, "Got Selection from Xwayland",); let seat = self.common.shell.read().seats.last_active().clone(); match selection { SelectionTarget::Clipboard => { set_data_device_selection(&self.common.display_handle, &seat, mime_types, xwm) } SelectionTarget::Primary => { set_primary_selection(&self.common.display_handle, &seat, mime_types, xwm) } } } fn cleared_selection(&mut self, xwm: XwmId, selection: SelectionTarget) { let shell = self.common.shell.read(); for seat in shell.seats.iter() { match selection { SelectionTarget::Clipboard => { if current_data_device_selection_userdata(seat).as_deref() == Some(&xwm) { clear_data_device_selection(&self.common.display_handle, seat) } } SelectionTarget::Primary => { if current_primary_selection_userdata(seat).as_deref() == Some(&xwm) { clear_primary_selection(&self.common.display_handle, seat) } } } } } fn randr_primary_output_change(&mut self, _xwm: XwmId, output_name: Option) { for output in self.common.output_configuration_state.outputs() { output.config_mut().xwayland_primary = output_name.as_deref().is_some_and(|o| o == output.name()); } self.common.output_configuration_state.update(); self.common.update_xwayland_scale(); self.common .config .write_outputs(self.common.output_configuration_state.outputs()); } fn disconnected(&mut self, _xwm: XwmId) { let xwayland_state = self.common.xwayland_state.as_mut().unwrap(); xwayland_state.xwm = None; } }