Files
server/src/wayland/xdg_shell.rs
2025-04-01 14:24:06 -07:00

549 lines
14 KiB
Rust

use super::{
seat::{SeatWrapper, handle_cursor},
state::{ClientState, WaylandState},
surface::CoreSurface,
utils::*,
};
use crate::{
core::error::Result,
nodes::{
drawable::model::ModelPart,
items::panel::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
},
},
};
use color_eyre::eyre::eyre;
use mint::Vector2;
use parking_lot::Mutex;
use rand::Rng;
use rustc_hash::FxHashMap;
use smithay::{
delegate_xdg_shell,
desktop::PopupKind,
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
shell::server::xdg_toplevel::{ResizeEdge, State},
},
wayland_server::{
Resource,
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
},
},
utils::{Logical, Rectangle, Serial},
wayland::{
compositor::add_post_commit_hook,
shell::xdg::{
PopupSurface, PositionerState, ShellClient, ToplevelSurface, XdgShellHandler,
XdgShellState,
},
},
};
use std::sync::{Arc, Weak};
use tracing::warn;
fn get_unconstrained_popup_geometry(positioner: &PositionerState) -> Geometry {
positioner
.get_unconstrained_geometry(Rectangle {
loc: (-100000, -100000).into(),
size: (200000, 200000).into(),
})
.into()
}
impl From<Rectangle<i32, Logical>> for Geometry {
fn from(value: Rectangle<i32, Logical>) -> Self {
Geometry {
origin: [value.loc.x, value.loc.y].into(),
size: [value.size.w as u32, value.size.h as u32].into(),
}
}
}
pub fn surface_panel_item(wl_surface: &WlSurface) -> Option<Arc<PanelItem<XdgBackend>>> {
let panel_item = wl_surface
.get_data::<Weak<PanelItem<XdgBackend>>>()
.as_ref()
.and_then(Weak::upgrade);
if panel_item.is_none() {
warn!("Couldn't get panel item");
}
panel_item
}
impl XdgShellHandler for WaylandState {
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
&mut self.xdg_shell
}
fn new_client(&mut self, _client: ShellClient) {}
fn client_destroyed(&mut self, _client: ShellClient) {}
fn new_toplevel(&mut self, toplevel: ToplevelSurface) {
toplevel.wl_surface().insert_data(SurfaceId::Toplevel(()));
toplevel.with_pending_state(|s| {
s.decoration_mode = Some(Mode::ServerSide);
s.states.set(State::TiledTop);
s.states.set(State::TiledBottom);
s.states.set(State::TiledLeft);
s.states.set(State::TiledRight);
s.states.set(State::Maximized);
s.states.unset(State::Fullscreen);
});
let initial_size = toplevel
.wl_surface()
.get_size()
.unwrap_or(Vector2::from([0; 2]));
let initial_toplevel_info = ToplevelInfo {
parent: toplevel.wl_surface().get_parent(),
title: toplevel.wl_surface().get_title(),
app_id: toplevel.wl_surface().get_app_id(),
size: initial_size,
min_size: toplevel
.wl_surface()
.min_size()
.map(|s| Vector2::from([s.x as f32, s.y as f32])),
max_size: toplevel
.wl_surface()
.max_size()
.map(|s| Vector2::from([s.x as f32, s.y as f32])),
logical_rectangle: toplevel.wl_surface().get_geometry().unwrap_or(Geometry {
origin: [0; 2].into(),
size: initial_size,
}),
};
toplevel
.wl_surface()
.insert_data(Mutex::new(initial_toplevel_info));
CoreSurface::add_to(toplevel.wl_surface());
add_post_commit_hook(
toplevel.wl_surface(),
|_state: &mut WaylandState, _dh, surf| {
let parent = surf.get_parent();
let new_size = surf.get_size().unwrap_or(Vector2::from([0; 2]));
let min_size = surf
.min_size()
.map(|s| Vector2::from([s.x as f32, s.y as f32]));
let max_size = surf
.max_size()
.map(|s| Vector2::from([s.x as f32, s.y as f32]));
let logical_rectangle = surf.get_geometry().unwrap_or_default();
let mut size_changed = false;
surf.with_toplevel_info(|info| {
info.parent = parent;
if new_size != info.size {
info.size = new_size;
size_changed = true;
}
info.min_size = min_size;
info.max_size = max_size;
info.logical_rectangle = logical_rectangle;
});
if size_changed {
let Some(panel_item) = surface_panel_item(surf) else {
return;
};
if let Some(toplevel_info) = surf.get_toplevel_info() {
panel_item.toplevel_size_changed(toplevel_info.size);
}
}
},
);
add_post_commit_hook(
toplevel.wl_surface(),
|state: &mut WaylandState, _dh, surf| {
if surface_panel_item(surf).is_some() {
return;
}
let client = surf.client().unwrap();
let client_state = client.get_data::<ClientState>().unwrap();
let Some(toplevel) = state
.xdg_shell
.toplevel_surfaces()
.iter()
.find(|s| s.wl_surface() == surf)
else {
return;
};
let (node, panel_item) = PanelItem::create(
Box::new(XdgBackend::create(
toplevel.clone(),
client_state.seat.clone(),
)),
client_state.pid,
);
handle_cursor(&panel_item, panel_item.backend.seat.cursor_info_rx.clone());
surf.insert_data(Arc::downgrade(&panel_item));
surf.insert_data(node);
},
);
}
fn toplevel_destroyed(&mut self, toplevel: ToplevelSurface) {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.backend.seat.unfocus(toplevel.wl_surface(), self);
panel_item.backend.toplevel.lock().take();
panel_item.backend.children.lock().clear();
}
fn app_id_changed(&mut self, toplevel: ToplevelSurface) {
let wl_surface = toplevel.wl_surface();
let Some(app_id) = wl_surface.get_app_id() else {
return;
};
wl_surface.with_toplevel_info(|info| {
info.app_id = Some(app_id.clone());
});
let Some(panel_item) = surface_panel_item(wl_surface) else {
return;
};
panel_item.toplevel_app_id_changed(&app_id)
}
fn title_changed(&mut self, toplevel: ToplevelSurface) {
let wl_surface = toplevel.wl_surface();
let Some(title) = wl_surface.get_title() else {
return;
};
wl_surface.with_toplevel_info(|info| {
info.title = Some(title.clone());
});
let Some(panel_item) = surface_panel_item(wl_surface) else {
return;
};
panel_item.toplevel_title_changed(&title)
}
fn new_popup(&mut self, popup: PopupSurface, positioner: PositionerState) {
self.popup_manager
.track_popup(PopupKind::Xdg(popup.clone()))
.unwrap();
let id = rand::thread_rng().gen_range(0..u64::MAX);
popup.wl_surface().insert_data(SurfaceId::Child(id));
let Some(parent) = popup.get_parent_surface() else {
warn!("No parent surface found for popup");
return;
};
CoreSurface::add_to(popup.wl_surface());
popup.wl_surface().insert_data(Mutex::new(ChildInfo {
id,
parent: parent.get_data::<SurfaceId>().unwrap(),
geometry: get_unconstrained_popup_geometry(&positioner),
z_order: 1,
receives_input: true,
}));
let Some(panel_item) = surface_panel_item(&parent) else {
warn!("No panel item found for popup parent");
return;
};
let panel_item_weak = Arc::downgrade(&panel_item);
add_post_commit_hook(
popup.wl_surface(),
move |_: &mut WaylandState, _dh, surf| {
if surface_panel_item(surf).is_some() {
return;
}
surf.insert_data(panel_item_weak.clone());
let Some(panel) = surface_panel_item(surf) else {
warn!("Failed to get panel item for popup surface");
return;
};
panel.backend.new_child(surf);
},
);
}
fn reposition_request(
&mut self,
popup: PopupSurface,
positioner: PositionerState,
_token: u32,
) {
let Some(panel_item) = surface_panel_item(popup.wl_surface()) else {
return;
};
popup.wl_surface().with_child_info(|ci| {
ci.geometry = get_unconstrained_popup_geometry(&positioner);
});
panel_item.backend.reposition_child(popup.wl_surface());
}
fn popup_destroyed(&mut self, popup: PopupSurface) {
let Some(panel_item) = surface_panel_item(popup.wl_surface()) else {
return;
};
panel_item.backend.seat.unfocus(popup.wl_surface(), self);
panel_item.backend.drop_child(popup.wl_surface());
}
fn grab(&mut self, _popup: PopupSurface, _seat: WlSeat, _serial: Serial) {}
fn move_request(&mut self, toplevel: ToplevelSurface, _seat: WlSeat, _serial: Serial) {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.toplevel_move_request();
}
fn resize_request(
&mut self,
toplevel: ToplevelSurface,
_seat: WlSeat,
_serial: Serial,
edges: ResizeEdge,
) {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
let (up, down, left, right) = match edges {
ResizeEdge::None => (false, false, false, false),
ResizeEdge::Top => (true, false, false, false),
ResizeEdge::Bottom => (false, true, false, false),
ResizeEdge::Left => (false, false, true, false),
ResizeEdge::TopLeft => (true, false, true, false),
ResizeEdge::BottomLeft => (false, true, true, false),
ResizeEdge::Right => (false, false, false, true),
ResizeEdge::TopRight => (true, false, false, true),
ResizeEdge::BottomRight => (false, true, false, true),
_ => (false, false, false, false),
};
panel_item.toplevel_resize_request(up, down, left, right);
}
fn maximize_request(&mut self, toplevel: ToplevelSurface) {
toplevel.with_pending_state(|s| {
s.states.set(State::Maximized);
s.states.unset(State::Fullscreen);
});
toplevel.send_configure();
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.toplevel_fullscreen_active(false);
}
fn fullscreen_request(&mut self, toplevel: ToplevelSurface, _output: Option<WlOutput>) {
toplevel.with_pending_state(|s| {
s.states.set(State::Fullscreen);
s.states.unset(State::Maximized);
});
toplevel.send_configure();
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.toplevel_fullscreen_active(true);
}
}
delegate_xdg_shell!(WaylandState);
pub struct XdgBackend {
toplevel: Mutex<Option<ToplevelSurface>>,
pub children: Mutex<FxHashMap<u64, WlSurface>>,
seat: Arc<SeatWrapper>,
}
impl XdgBackend {
pub fn create(toplevel: ToplevelSurface, seat: Arc<SeatWrapper>) -> Self {
XdgBackend {
toplevel: Mutex::new(Some(toplevel)),
children: Mutex::new(FxHashMap::default()),
seat,
}
}
fn wl_surface_from_id(&self, id: &SurfaceId) -> Option<WlSurface> {
match id {
SurfaceId::Toplevel(_) => Some(self.toplevel.lock().clone()?.wl_surface().clone()),
SurfaceId::Child(id) => self.children.lock().get(id).cloned(),
}
}
fn panel_item(&self) -> Option<Arc<PanelItem<XdgBackend>>> {
surface_panel_item(self.toplevel.lock().clone()?.wl_surface())
}
pub fn new_child(&self, surface: &WlSurface) {
let Some(panel_item) = self.panel_item() else {
return;
};
let Some(child_info) = surface.get_child_info() else {
return;
};
self.children.lock().insert(child_info.id, surface.clone());
panel_item.create_child(child_info.id, &child_info);
}
pub fn reposition_child(&self, surface: &WlSurface) {
let Some(panel_item) = self.panel_item() else {
return;
};
let Some(child_info) = surface.get_child_info() else {
return;
};
panel_item.reposition_child(child_info.id, &child_info.geometry);
}
pub fn drop_child(&self, surface: &WlSurface) {
let Some(panel_item) = self.panel_item() else {
return;
};
let Some(child_info) = surface.get_child_info() else {
return;
};
panel_item.destroy_child(child_info.id);
self.children.lock().remove(&child_info.id);
}
}
impl Backend for XdgBackend {
fn start_data(&self) -> Result<PanelItemInitData> {
let cursor = self
.seat
.cursor_info_rx
.borrow()
.surface
.clone()
.and_then(|s| s.upgrade().ok())
.as_ref()
.and_then(|c| c.get_size())
.map(|size| Geometry {
origin: [0; 2].into(),
size,
});
let toplevel_info = self
.toplevel
.lock()
.as_ref()
.and_then(|toplevel| toplevel.wl_surface().get_toplevel_info())
.ok_or(eyre!("Internal: no toplevel or ToplevelInfo"))?;
let children = self
.children
.lock()
.values()
.filter_map(|v| v.get_child_info())
.collect();
Ok(PanelItemInitData {
cursor,
toplevel: toplevel_info,
children,
pointer_grab: None,
keyboard_grab: None,
})
}
fn apply_cursor_material(&self, model_part: &Arc<ModelPart>) {
let Some(surface) = self
.seat
.cursor_info_rx
.borrow()
.surface
.clone()
.and_then(|s| s.upgrade().ok())
else {
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&surface) else {
return;
};
core_surface.apply_material(model_part);
}
fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>) {
let Some(surface) = self.wl_surface_from_id(&surface) else {
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&surface) else {
return;
};
core_surface.apply_material(model_part);
}
fn close_toplevel(&self) {
if let Some(toplevel) = self.toplevel.lock().clone() {
toplevel.send_close();
}
}
fn auto_size_toplevel(&self) {
let Some(toplevel) = self.toplevel.lock().clone() else {
return;
};
toplevel.with_pending_state(|s| s.size = None);
toplevel.send_configure();
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
let Some(toplevel) = self.toplevel.lock().clone() else {
return;
};
toplevel.with_pending_state(|s| {
s.size = Some((size.x.max(16) as i32, size.y.max(16) as i32).into())
});
toplevel.send_pending_configure();
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
let Some(toplevel) = self.toplevel.lock().clone() else {
return;
};
toplevel.with_pending_state(|s| {
if focused {
s.states.set(State::Activated);
} else {
s.states.unset(State::Activated);
}
})
}
fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.pointer_motion(surface, position)
}
fn pointer_button(&self, _surface: &SurfaceId, button: u32, pressed: bool) {
self.seat.pointer_button(button, pressed)
}
fn pointer_scroll(
&self,
_surface: &SurfaceId,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
self.seat.pointer_scroll(scroll_distance, scroll_steps)
}
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.keyboard_key(surface, keymap_id, key, pressed)
}
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.touch_down(surface, id, position)
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
self.seat.touch_move(id, position)
}
fn touch_up(&self, id: u32) {
self.seat.touch_up(id)
}
fn reset_input(&self) {
self.seat.reset_input()
}
}