cosmic-comp/src/xwayland.rs

690 lines
25 KiB
Rust
Raw Normal View History

2024-04-24 09:34:46 -07:00
use std::{ffi::OsString, os::unix::io::OwnedFd, process::Stdio};
2023-01-24 17:40:33 +01:00
2023-01-18 20:23:41 +01:00
use crate::{
backend::render::cursor::{load_cursor_theme, Cursor, CursorShape},
shell::{
element::surface::SSD_HEIGHT, focus::target::KeyboardFocusTarget, grabs::ReleaseMode,
CosmicSurface, Shell,
},
2023-09-29 21:33:16 +02:00
state::State,
utils::prelude::*,
2024-03-12 19:42:48 +01:00
wayland::handlers::{
2024-04-10 15:49:08 +02:00
toplevel_management::minimize_rectangle, xdg_activation::ActivationContext,
2023-11-07 18:46:25 +01:00
},
2023-01-18 20:23:41 +01:00
};
use smithay::{
backend::drm::DrmNode,
desktop::space::SpaceElement,
2023-01-18 20:23:41 +01:00
reexports::x11rb::protocol::xproto::Window as X11Window,
2024-04-10 15:49:08 +02:00
utils::{Logical, Point, Rectangle, Size, SERIAL_COUNTER},
2023-11-07 18:46:25 +01:00
wayland::{
selection::{
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,
},
SelectionTarget,
},
2023-11-07 18:46:25 +01:00
xdg_activation::XdgActivationToken,
},
2023-01-18 20:23:41 +01:00
xwayland::{
xwm::{Reorder, X11Relatable, XwmId},
2023-01-18 20:23:41 +01:00
X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler,
},
};
use tracing::{error, trace, warn};
2023-01-18 20:23:41 +01:00
2023-07-12 18:54:53 +02:00
#[derive(Debug)]
2023-01-18 20:23:41 +01:00
pub struct XWaylandState {
pub xwm: Option<X11Wm>,
pub display: u32,
2023-01-18 20:23:41 +01:00
}
impl State {
2023-03-07 20:28:41 +01:00
pub fn launch_xwayland(&mut self, render_node: Option<DrmNode>) {
2024-04-10 15:49:08 +02:00
if self.common.xwayland_state.is_some() {
2023-01-18 20:23:41 +01:00
return;
}
2024-04-24 09:34:46 -07:00
let (xwayland, client) = match XWayland::spawn(
&self.common.display_handle,
2023-01-24 17:40:33 +01:00
None,
2023-05-03 19:47:34 +02:00
std::iter::empty::<(OsString, OsString)>(),
true,
2024-04-24 09:34:46 -07:00
Stdio::null(),
Stdio::null(),
2023-03-07 20:28:41 +01:00
|user_data| {
if let Some(node) = render_node {
user_data.insert_if_missing_threadsafe(|| node);
2023-01-24 17:40:33 +01:00
}
},
) {
2024-04-24 09:34:46 -07:00
Ok((xwayland, client)) => (xwayland, client),
2023-01-18 20:23:41 +01:00
Err(err) => {
error!(?err, "Failed to start Xwayland.");
2024-04-24 09:34:46 -07:00
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 {
xwm: None,
display: display_number,
});
let mut wm = match X11Wm::start_wm(
data.common.event_loop_handle.clone(),
x11_socket,
client.clone(),
) {
Ok(wm) => wm,
Err(err) => {
error!(?err, "Failed to start Xwayland WM");
return;
}
};
let (theme, size) = load_cursor_theme();
let cursor = Cursor::load(&theme, CursorShape::Default, size);
let image = cursor.get_image(1, 0);
if let Err(err) = wm.set_cursor(
&image.pixels_rgba,
Size::from((image.width as u16, image.height as u16)),
Point::from((image.xhot as u16, image.yhot as u16)),
) {
warn!(
id = ?wm.id(),
?err,
"Failed to set default cursor for Xwayland WM",
);
}
let xwayland_state = data.common.xwayland_state.as_mut().unwrap();
xwayland_state.xwm = Some(wm);
data.notify_ready();
}
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();
return;
2023-01-18 20:23:41 +01:00
}
}
}
}
impl Common {
fn is_x_focused(&self, xwm: XwmId) -> bool {
2024-04-10 15:49:08 +02:00
if let Some(keyboard) = self
.shell
.read()
.unwrap()
.seats
.last_active()
.get_keyboard()
{
if let Some(KeyboardFocusTarget::Element(mapped)) = keyboard.current_focus() {
if let Some(surface) = mapped.active_window().x11_surface() {
return surface.xwm_id().unwrap() == xwm;
}
}
}
false
}
pub fn update_x11_stacking_order(&mut self) {
2024-04-10 15:49:08 +02:00
let shell = self.shell.read().unwrap();
let active_output = shell.seats.last_active().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
2024-04-10 15:49:08 +02:00
let order = shell
.workspaces
.sets
.iter()
.filter(|(output, _)| *output == &active_output)
.chain(
2024-04-10 15:49:08 +02:00
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().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(),
)
}),
)
}),
)
})
.collect::<Vec<_>>();
// 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.");
}
}
}
}
2023-09-29 21:33:16 +02:00
impl XwmHandler for State {
2023-03-07 20:28:41 +01:00
fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
2023-09-29 21:33:16 +02:00
self.common
2023-01-18 20:23:41 +01:00
.xwayland_state
2023-03-07 20:28:41 +01:00
.as_mut()
.and_then(|state| state.xwm.as_mut())
2023-01-18 20:23:41 +01:00
.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",);
2023-01-18 20:23:41 +01:00
}
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
2023-11-07 18:46:25 +01:00
let startup_id = window.startup_id();
2024-04-10 15:49:08 +02:00
if shell.element_for_surface(&window).is_some() {
2023-01-23 18:25:01 +01:00
return;
}
2024-04-10 15:49:08 +02:00
let seat = shell.seats.last_active().clone();
2023-11-07 18:46:25 +01:00
if let Some(context) = startup_id
.map(XdgActivationToken::from)
2024-04-10 15:49:08 +02:00
.and_then(|token| self.common.xdg_activation_state.data_for_token(&token))
2023-11-07 18:46:25 +01:00
.and_then(|data| data.user_data.get::<ActivationContext>())
{
2024-04-10 15:49:08 +02:00
shell.pending_activations.insert(
2023-11-07 18:46:25 +01:00
crate::shell::ActivationKey::X11(window.window_id()),
context.clone(),
);
}
let surface = CosmicSurface::from(window);
2024-04-10 15:49:08 +02:00
shell.pending_windows.push((surface, seat, None));
}
2023-01-18 20:23:41 +01:00
fn map_window_notify(&mut self, _xwm: XwmId, surface: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
if let Some((window, _, _)) = shell
2023-01-18 20:23:41 +01:00
.pending_windows
.iter()
.find(|(window, _, _)| window.x11_surface() == Some(&surface))
.cloned()
{
2024-04-10 15:49:08 +02:00
if !shell
2023-11-07 18:46:25 +01:00
.pending_activations
.contains_key(&crate::shell::ActivationKey::X11(surface.window_id()))
{
if let Some(startup_id) = window.x11_surface().and_then(|x| x.startup_id()) {
2023-11-07 18:46:25 +01:00
if let Some(context) = self
.common
.xdg_activation_state
.data_for_token(&XdgActivationToken::from(startup_id))
.and_then(|data| data.user_data.get::<ActivationContext>())
{
2024-04-10 15:49:08 +02:00
shell.pending_activations.insert(
2023-11-07 18:46:25 +01:00
crate::shell::ActivationKey::X11(surface.window_id()),
context.clone(),
);
}
}
}
2024-04-10 15:49:08 +02:00
let res = shell.map_window(
2024-04-05 13:53:35 +02:00
&window,
2024-04-10 15:49:08 +02:00
&mut self.common.toplevel_info_state,
&mut self.common.workspace_state,
2024-04-05 13:53:35 +02:00
&self.common.event_loop_handle,
2024-04-10 15:49:08 +02:00
);
if let Some(target) = res {
let seat = shell.seats.last_active().clone();
std::mem::drop(shell);
2024-04-05 13:53:35 +02:00
Shell::set_focus(self, Some(&target), &seat, None);
}
}
2023-01-18 20:23:41 +01:00
}
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
if shell
2023-01-23 18:25:01 +01:00
.override_redirect_windows
.iter()
.any(|or| or == &window)
2023-01-23 18:25:01 +01:00
{
return;
}
2024-04-10 15:49:08 +02:00
shell.map_override_redirect(window)
2023-01-18 20:23:41 +01:00
}
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
2023-01-23 18:25:01 +01:00
if window.is_override_redirect() {
2024-04-10 15:49:08 +02:00
shell.override_redirect_windows.retain(|or| or != &window);
} else {
2024-04-10 15:49:08 +02:00
let seat = shell.seats.last_active().clone();
shell.unmap_surface(&window, &seat, &mut self.common.toplevel_info_state);
2023-01-18 20:23:41 +01:00
}
let outputs = if let Some(wl_surface) = window.wl_surface() {
2024-04-10 15:49:08 +02:00
shell
2023-12-20 19:58:43 +00:00
.visible_output_for_surface(&wl_surface)
.into_iter()
.cloned()
2023-01-18 20:23:41 +01:00
.collect::<Vec<_>>()
} else {
2024-04-10 15:49:08 +02:00
shell.outputs().cloned().collect::<Vec<_>>()
2023-01-18 20:23:41 +01:00
};
for output in outputs.iter() {
2024-04-10 15:49:08 +02:00
shell.refresh_active_space(output, &self.common.xdg_activation_state);
2023-01-18 20:23:41 +01:00
}
for output in outputs.into_iter() {
2024-06-10 20:41:29 +02:00
self.backend.schedule_render(&output);
2023-01-18 20:23:41 +01:00
}
}
fn configure_request(
&mut self,
_xwm: XwmId,
window: X11Surface,
2023-01-23 18:25:01 +01:00
x: Option<i32>,
y: Option<i32>,
2023-01-18 20:23:41 +01:00
w: Option<u32>,
h: Option<u32>,
_reorder: Option<Reorder>,
) {
// We only allow floating X11 windows to resize themselves. Nothing else
2024-04-10 15:49:08 +02:00
let shell = self.common.shell.read().unwrap();
if let Some(mapped) = shell.element_for_surface(&window) {
let current_geo = if let Some(workspace) = shell.space_for(mapped) {
workspace.is_floating(mapped).then_some(
workspace
.element_geometry(mapped)
.unwrap()
.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 = if window.is_decorated() { 0 } else { SSD_HEIGHT };
mapped.set_geometry(Rectangle::from_loc_and_size(
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),
),
))
} else {
let _ = window.configure(None); // ack and force old state
2023-01-23 18:25:01 +01:00
}
} else {
let mut current_geo = window.geometry();
2023-01-23 18:25:01 +01:00
if let Some(x) = x {
current_geo.loc.x = x;
2023-01-18 20:23:41 +01:00
}
2023-01-23 18:25:01 +01:00
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);
2023-01-18 20:23:41 +01:00
}
}
fn configure_notify(
&mut self,
_xwm: XwmId,
window: X11Surface,
_geometry: Rectangle<i32, Logical>,
above: Option<X11Window>,
) {
if window.is_override_redirect() {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
if let Some(id) = above {
2024-04-10 15:49:08 +02:00
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);
}
}
2023-01-18 20:23:41 +01:00
}
let geo = window.geometry().as_global();
2024-04-10 15:49:08 +02:00
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);
}
}
2023-01-18 20:23:41 +01:00
}
}
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() {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
let seat = shell.seats.last_active().clone();
if let Some((grab, focus)) =
shell.resize_request(&wl_surface, &seat, None, resize_edge.into())
{
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,
)
}
}
2023-01-18 20:23:41 +01:00
}
}
fn move_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32) {
if let Some(wl_surface) = window.wl_surface() {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
let seat = shell.seats.last_active().clone();
if let Some((grab, focus)) = shell.move_request(
&wl_surface,
&seat,
None,
ReleaseMode::NoMouseButtons,
false,
2024-04-10 15:49:08 +02:00
&self.common.config,
&self.common.event_loop_handle,
&self.common.xdg_activation_state,
) {
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,
)
}
}
2023-01-18 20:23:41 +01:00
}
}
fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
if let Some(mapped) = shell.element_for_surface(&window).cloned() {
let seat = shell.seats.last_active().clone();
shell.maximize_request(&mapped, &seat);
2023-01-18 20:23:41 +01:00
}
}
fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
if let Some(mapped) = shell.element_for_surface(&window).cloned() {
shell.unmaximize_request(&mapped);
2023-01-18 20:23:41 +01:00
}
}
2024-02-08 20:28:59 +01:00
fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
if let Some(mapped) = shell.element_for_surface(&window).cloned() {
if !mapped.is_stack() || mapped.active_window().is_window(&window) {
2024-04-10 15:49:08 +02:00
shell.minimize_request(&mapped);
}
2024-02-08 20:28:59 +01:00
}
}
fn unminimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
if let Some(mut mapped) = shell.element_for_surface(&window).cloned() {
let seat = shell.seats.last_active().clone();
shell.unminimize_request(&mapped, &seat);
if mapped.is_stack() {
let maybe_surface = mapped.windows().find(|(w, _)| w.is_window(&window));
if let Some((surface, _)) = maybe_surface {
mapped.stack_ref_mut().unwrap().set_active(&surface);
}
}
2024-02-08 20:28:59 +01:00
}
}
2023-01-18 20:23:41 +01:00
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
let seat = shell.seats.last_active().clone();
if let Some(mapped) = shell.element_for_surface(&window).cloned() {
if let Some((output, handle)) = shell
2024-02-23 17:25:40 +01:00
.space_for(&mapped)
.map(|workspace| (workspace.output.clone(), workspace.handle.clone()))
{
if let Some((surface, _)) = mapped
.windows()
.find(|(w, _)| w.x11_surface() == Some(&window))
{
2024-04-10 15:49:08 +02:00
let from = minimize_rectangle(&output, &surface);
shell
2024-02-23 17:25:40 +01:00
.workspaces
.space_for_handle_mut(&handle)
.unwrap()
.fullscreen_request(&surface, None, from, &seat);
}
2023-01-18 20:23:41 +01:00
}
} else {
2024-04-05 13:53:35 +02:00
let output = seat.active_output();
2024-04-10 15:49:08 +02:00
if let Some(o) = shell
.pending_windows
.iter_mut()
.find(|(s, _, _)| s.x11_surface() == Some(&window))
.map(|(_, _, o)| o)
{
*o = Some(output);
}
2023-01-18 20:23:41 +01:00
}
}
fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
2024-04-10 15:49:08 +02:00
let mut shell = self.common.shell.write().unwrap();
if let Some(mapped) = shell.element_for_surface(&window).cloned() {
if let Some(workspace) = shell.space_for_mut(&mapped) {
let (window, _) = mapped
.windows()
.find(|(w, _)| w.x11_surface() == Some(&window))
.unwrap();
let previous = workspace.unfullscreen_request(&window);
assert!(previous.is_none());
2023-01-18 20:23:41 +01:00
}
}
}
fn send_selection(
&mut self,
_xwm: XwmId,
selection: SelectionTarget,
mime_type: String,
fd: OwnedFd,
) {
2024-04-10 15:49:08 +02:00
let seat = self
.common
.shell
.read()
.unwrap()
.seats
.last_active()
.clone();
match selection {
SelectionTarget::Clipboard => {
2024-04-10 15:49:08 +02:00
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 => {
2024-04-10 15:49:08 +02:00
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 {
2023-09-29 21:33:16 +02:00
self.common.is_x_focused(xwm)
}
fn new_selection(&mut self, xwm: XwmId, selection: SelectionTarget, mime_types: Vec<String>) {
trace!(?selection, ?mime_types, "Got Selection from Xwayland",);
2023-09-29 21:33:16 +02:00
if self.common.is_x_focused(xwm) {
2024-04-10 15:49:08 +02:00
let seat = self
.common
.shell
.read()
.unwrap()
.seats
.last_active()
.clone();
match selection {
SelectionTarget::Clipboard => {
2023-09-29 21:33:16 +02:00
set_data_device_selection(&self.common.display_handle, &seat, mime_types, xwm)
}
SelectionTarget::Primary => {
2023-09-29 21:33:16 +02:00
set_primary_selection(&self.common.display_handle, &seat, mime_types, xwm)
}
}
}
}
fn cleared_selection(&mut self, xwm: XwmId, selection: SelectionTarget) {
2024-04-10 15:49:08 +02:00
let shell = self.common.shell.read().unwrap();
for seat in shell.seats.iter() {
match selection {
SelectionTarget::Clipboard => {
if current_data_device_selection_userdata(seat).as_deref() == Some(&xwm) {
2023-09-29 21:33:16 +02:00
clear_data_device_selection(&self.common.display_handle, seat)
}
}
SelectionTarget::Primary => {
if current_primary_selection_userdata(seat).as_deref() == Some(&xwm) {
2023-09-29 21:33:16 +02:00
clear_primary_selection(&self.common.display_handle, seat)
}
}
}
}
}
}