xwayland: Add initial support

This commit is contained in:
Victoria Brekenfeld 2023-01-18 20:23:41 +01:00
parent 78ffe3a93d
commit 1d28574088
23 changed files with 781 additions and 185 deletions

View file

@ -4,12 +4,19 @@ use std::{cell::RefCell, collections::HashMap};
use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState;
use smithay::{
desktop::{layer_map_for_output, LayerSurface, PopupManager, WindowSurfaceType},
input::Seat,
input::{
pointer::{Focus, GrabStartData as PointerGrabStartData},
Seat,
},
output::Output,
reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle},
utils::{Logical, Point, Rectangle},
reexports::{
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle},
x11rb::protocol::xproto::Window as X11Window,
},
utils::{Logical, Point, Rectangle, Serial, SERIAL_COUNTER},
wayland::{
compositor::with_states,
seat::WaylandFocus,
shell::{
wlr_layer::{
KeyboardInteractivity, Layer, LayerSurfaceCachedState, WlrLayerShellState,
@ -17,6 +24,7 @@ use smithay::{
xdg::XdgShellState,
},
},
xwayland::X11Surface,
};
use crate::{
@ -42,6 +50,7 @@ pub use self::workspace::*;
use self::{
element::CosmicWindow,
focus::target::KeyboardFocusTarget,
grabs::ResizeEdge,
layout::{floating::FloatingLayout, tiling::TilingLayout},
};
@ -52,6 +61,7 @@ pub struct Shell {
pub floating_default: bool,
pub pending_windows: Vec<(CosmicSurface, Seat<State>)>,
pub pending_layers: Vec<(LayerSurface, Output, Seat<State>)>,
pub override_redirect_windows: Vec<OverrideRedirectWindow>,
// wayland_state
pub layer_shell_state: WlrLayerShellState,
@ -61,6 +71,11 @@ pub struct Shell {
pub workspace_state: WorkspaceState<State>,
}
pub struct OverrideRedirectWindow {
pub surface: X11Surface,
pub above: Option<X11Window>,
}
#[derive(Debug)]
pub struct WorkspaceSet {
active: usize,
@ -431,6 +446,7 @@ impl Shell {
pending_windows: Vec::new(),
pending_layers: Vec::new(),
override_redirect_windows: Vec::new(),
layer_shell_state,
toplevel_info_state,
@ -871,12 +887,18 @@ impl Shell {
.into_iter()
}
pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> {
pub fn element_for_surface(&self, surface: &CosmicSurface) -> Option<&CosmicMapped> {
self.workspaces
.spaces()
.find_map(|w| w.element_for_surface(surface))
}
pub fn element_for_wl_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> {
self.workspaces
.spaces()
.find_map(|w| w.element_for_wl_surface(surface))
}
pub fn space_for(&self, mapped: &CosmicMapped) -> Option<&Workspace> {
self.workspaces
.spaces()
@ -996,6 +1018,24 @@ impl Shell {
.map(mapped.clone(), &seat, focus_stack.iter());
}
if let CosmicSurface::X11(surface) = window {
let geometry = workspace.element_geometry(&mapped);
if let Err(err) = surface.configure(geometry) {
slog_scope::warn!("Failed to configure X11 surface ({:?}): {}", surface, err);
};
if let Some(xwm) = state
.common
.xwayland_state
.values_mut()
.flat_map(|state| state.xwm.as_mut())
.find(|xwm| Some(xwm.id()) == surface.xwm_id())
{
if let Err(err) = xwm.update_stacking_order_downwards(workspace.mapped()) {
slog_scope::warn!("Failed to update Xwayland stacking order: {}", err);
}
}
}
Shell::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None);
let active_space = state.common.shell.active_space(output);
@ -1004,6 +1044,28 @@ impl Shell {
}
}
pub fn map_override_redirect(state: &mut State, window: X11Surface) {
let seat = state.common.last_active_seat().clone();
let mut pos = window.geometry().loc;
let output = state
.common
.shell
.outputs()
.find(|o| o.geometry().contains(pos))
.cloned()
.unwrap_or_else(|| seat.active_output());
pos -= output.geometry().loc;
state
.common
.shell
.override_redirect_windows
.push(OverrideRedirectWindow {
surface: window,
above: None,
});
}
pub fn map_layer(state: &mut State, layer_surface: &LayerSurface) {
let pos = state
.common
@ -1125,6 +1187,71 @@ impl Shell {
}
}
}
pub fn move_request(
state: &mut State,
surface: &WlSurface,
seat: &Seat<State>,
serial: impl Into<Option<Serial>>,
) {
let serial = serial.into();
if let Some(start_data) = check_grab_preconditions(&seat, surface, serial) {
if let Some(mapped) = state.common.shell.element_for_wl_surface(surface).cloned() {
if let Some(workspace) = state.common.shell.space_for_mut(&mapped) {
let output = seat.active_output();
let (window, _) = mapped
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(surface))
.unwrap();
if let Some(grab) = workspace.move_request(&window, &seat, &output, start_data)
{
let handle = workspace.handle;
state
.common
.shell
.toplevel_info_state
.toplevel_leave_workspace(&window, &handle);
state
.common
.shell
.toplevel_info_state
.toplevel_leave_output(&window, &output);
seat.get_pointer().unwrap().set_grab(
state,
grab,
serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()),
Focus::Clear,
);
}
}
}
}
}
pub fn resize_request(
state: &mut State,
surface: &WlSurface,
seat: &Seat<State>,
serial: impl Into<Option<Serial>>,
edges: ResizeEdge,
) {
let serial = serial.into();
if let Some(start_data) = check_grab_preconditions(&seat, surface, serial) {
if let Some(mapped) = state.common.shell.element_for_wl_surface(surface).cloned() {
if let Some(workspace) = state.common.shell.space_for_mut(&mapped) {
if let Some(grab) = workspace.resize_request(&mapped, &seat, start_data, edges)
{
seat.get_pointer().unwrap().set_grab(
state,
grab,
serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()),
Focus::Clear,
);
}
}
}
}
}
}
fn workspace_set_idx<'a>(
@ -1136,3 +1263,38 @@ fn workspace_set_idx<'a>(
state.set_workspace_name(&handle, format!("{}", idx));
state.set_workspace_coordinates(&handle, [Some(idx as u32), Some(output_pos as u32), None]);
}
pub fn check_grab_preconditions(
seat: &Seat<State>,
surface: &WlSurface,
serial: Option<Serial>,
) -> Option<PointerGrabStartData<State>> {
use smithay::reexports::wayland_server::Resource;
// TODO: touch resize.
let pointer = seat.get_pointer().unwrap();
// Check that this surface has a click grab.
if !match serial {
Some(serial) => pointer.has_grab(serial),
None => pointer.is_grabbed(),
} {
return None;
}
let start_data = pointer.grab_start_data().unwrap();
// If the focus was for a different surface, ignore the request.
if start_data.focus.is_none()
|| !start_data
.focus
.as_ref()
.unwrap()
.0
.same_client_as(&surface.id())
{
return None;
}
Some(start_data)
}