Files
server/src/wayland/xdg_shell.rs
2023-09-03 11:02:42 -04:00

1037 lines
30 KiB
Rust

use super::{
seat::{CursorInfo, KeyboardEvent, PointerEvent, SeatData},
state::{ClientState, WaylandState},
surface::CoreSurface,
SERIAL_COUNTER,
};
use crate::{
nodes::{
data::KEYMAPS,
drawable::model::ModelPart,
items::panel::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceID, ToplevelInfo,
},
},
wayland::seat::handle_cursor,
};
use color_eyre::eyre::{bail, eyre, Result};
use mint::Vector2;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use smithay::reexports::{
wayland_protocols::xdg::shell::server::{
xdg_popup::{self, XdgPopup},
xdg_positioner::{self, Anchor, ConstraintAdjustment, Gravity, XdgPositioner},
xdg_surface::{self, XdgSurface},
xdg_toplevel::{self, ResizeEdge, XdgToplevel, EVT_WM_CAPABILITIES_SINCE},
xdg_wm_base::{self, XdgWmBase},
},
wayland_server::{
backend::{ClientId, ObjectId},
protocol::wl_surface::WlSurface,
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
Weak as WlWeak,
},
};
use std::{
fmt::Debug,
sync::{Arc, Weak},
};
use tokio::sync::watch;
use tracing::{debug, warn};
impl GlobalDispatch<XdgWmBase, (), WaylandState> for WaylandState {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<XdgWmBase>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<XdgWmBase, (), WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &XdgWmBase,
request: xdg_wm_base::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_wm_base::Request::CreatePositioner { id } => {
let positioner = data_init.init(id, Mutex::new(PositionerData::default()));
debug!(?positioner, "Create XDG positioner");
}
xdg_wm_base::Request::GetXdgSurface { id, surface } => {
let xdg_surface = data_init.init(id, Mutex::new(XdgSurfaceData::new(&surface)));
debug!(?xdg_surface, "Create XDG surface");
}
xdg_wm_base::Request::Pong { serial } => {
debug!(serial, "Client pong");
}
xdg_wm_base::Request::Destroy => {
debug!("Destroy XDG WM base");
}
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PositionerData {
size: Vector2<u32>,
anchor_rect_pos: Vector2<i32>,
anchor_rect_size: Vector2<u32>,
anchor: Anchor,
gravity: Gravity,
constraint_adjustment: ConstraintAdjustment,
offset: Vector2<i32>,
reactive: bool,
}
impl Default for PositionerData {
fn default() -> Self {
Self {
size: Vector2::from([0; 2]),
anchor_rect_pos: Vector2::from([0; 2]),
anchor_rect_size: Vector2::from([0; 2]),
anchor: Anchor::None,
gravity: Gravity::None,
constraint_adjustment: ConstraintAdjustment::None,
offset: Vector2::from([0; 2]),
reactive: false,
}
}
}
impl PositionerData {
fn anchor_has_edge(&self, edge: Anchor) -> bool {
match edge {
Anchor::Top => {
self.anchor == Anchor::Top
|| self.anchor == Anchor::TopLeft
|| self.anchor == Anchor::TopRight
}
Anchor::Bottom => {
self.anchor == Anchor::Bottom
|| self.anchor == Anchor::BottomLeft
|| self.anchor == Anchor::BottomRight
}
Anchor::Left => {
self.anchor == Anchor::Left
|| self.anchor == Anchor::TopLeft
|| self.anchor == Anchor::BottomLeft
}
Anchor::Right => {
self.anchor == Anchor::Right
|| self.anchor == Anchor::TopRight
|| self.anchor == Anchor::BottomRight
}
_ => unreachable!(),
}
}
fn gravity_has_edge(&self, edge: Gravity) -> bool {
match edge {
Gravity::Top => {
self.gravity == Gravity::Top
|| self.gravity == Gravity::TopLeft
|| self.gravity == Gravity::TopRight
}
Gravity::Bottom => {
self.gravity == Gravity::Bottom
|| self.gravity == Gravity::BottomLeft
|| self.gravity == Gravity::BottomRight
}
Gravity::Left => {
self.gravity == Gravity::Left
|| self.gravity == Gravity::TopLeft
|| self.gravity == Gravity::BottomLeft
}
Gravity::Right => {
self.gravity == Gravity::Right
|| self.gravity == Gravity::TopRight
|| self.gravity == Gravity::BottomRight
}
_ => unreachable!(),
}
}
pub fn get_pos(&self) -> Vector2<i32> {
let mut pos = self.offset;
if self.anchor_has_edge(Anchor::Top) {
pos.y += self.anchor_rect_pos.y;
} else if self.anchor_has_edge(Anchor::Bottom) {
pos.y += self.anchor_rect_pos.y + self.anchor_rect_size.y as i32;
} else {
pos.y += self.anchor_rect_pos.y + self.anchor_rect_size.y as i32 / 2;
}
if self.anchor_has_edge(Anchor::Left) {
pos.x += self.anchor_rect_pos.x;
} else if self.anchor_has_edge(Anchor::Right) {
pos.x += self.anchor_rect_pos.x + self.anchor_rect_size.x as i32;
} else {
pos.x += self.anchor_rect_pos.x + self.anchor_rect_size.x as i32 / 2;
}
if self.gravity_has_edge(Gravity::Top) {
pos.y -= self.size.y as i32;
} else if !self.gravity_has_edge(Gravity::Bottom) {
pos.y -= self.size.y as i32 / 2;
}
if self.gravity_has_edge(Gravity::Left) {
pos.x -= self.size.x as i32;
} else if !self.gravity_has_edge(Gravity::Right) {
pos.x -= self.size.x as i32 / 2;
}
pos
}
}
impl From<PositionerData> for Geometry {
fn from(value: PositionerData) -> Self {
Geometry {
origin: value.get_pos(),
size: value.size,
}
}
}
impl Dispatch<XdgPositioner, Mutex<PositionerData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
positioner: &XdgPositioner,
request: xdg_positioner::Request,
data: &Mutex<PositionerData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_positioner::Request::SetSize { width, height } => {
debug!(?positioner, width, height, "Set positioner size");
data.lock().size = Vector2::from([width as u32, height as u32]);
}
xdg_positioner::Request::SetAnchorRect {
x,
y,
width,
height,
} => {
if width < 1 || height < 1 {
positioner.post_error(
xdg_positioner::Error::InvalidInput,
"Invalid size for positioner's anchor rectangle.",
);
warn!(
?positioner,
width, height, "Invalid size for positioner's anchor rectangle"
);
return;
}
debug!(
?positioner,
x, y, width, height, "Set positioner anchor rectangle"
);
let mut data = data.lock();
data.anchor_rect_pos = [x, y].into();
data.anchor_rect_size = [width as u32, height as u32].into();
}
xdg_positioner::Request::SetAnchor { anchor } => {
if let WEnum::Value(anchor) = anchor {
debug!(?positioner, ?anchor, "Set positioner anchor");
data.lock().anchor = anchor;
}
}
xdg_positioner::Request::SetGravity { gravity } => {
if let WEnum::Value(gravity) = gravity {
debug!(?positioner, ?gravity, "Set positioner gravity");
data.lock().gravity = gravity;
}
}
xdg_positioner::Request::SetConstraintAdjustment {
constraint_adjustment,
} => {
debug!(
?positioner,
constraint_adjustment, "Set positioner constraint adjustment"
);
data.lock().constraint_adjustment =
ConstraintAdjustment::from_bits(constraint_adjustment).unwrap();
}
xdg_positioner::Request::SetOffset { x, y } => {
debug!(?positioner, x, y, "Set positioner offset");
data.lock().offset = [x, y].into();
}
xdg_positioner::Request::SetReactive => {
debug!(?positioner, "Set positioner reactive");
data.lock().reactive = true;
}
xdg_positioner::Request::SetParentSize {
parent_width,
parent_height,
} => {
debug!(
?positioner,
parent_width, parent_height, "Set positioner parent size"
);
}
xdg_positioner::Request::SetParentConfigure { serial } => {
debug!(?positioner, serial, "Set positioner parent size");
}
xdg_positioner::Request::Destroy => (),
_ => unreachable!(),
}
}
}
pub struct XdgSurfaceData {
wl_surface: WlWeak<WlSurface>,
surface_id: SurfaceID,
panel_item: Weak<PanelItem<XDGBackend>>,
geometry: Option<Geometry>,
}
impl XdgSurfaceData {
pub fn new(wl_surface: &WlSurface) -> Self {
XdgSurfaceData {
wl_surface: wl_surface.downgrade(),
surface_id: SurfaceID::Toplevel,
panel_item: Weak::new(),
geometry: None,
}
}
pub fn get(xdg_surface: &XdgSurface) -> Option<&Mutex<Self>> {
xdg_surface.data::<Mutex<Self>>()
}
pub fn wl_surface(&self) -> Option<WlSurface> {
self.wl_surface.upgrade().ok()
}
pub fn panel_item(&self) -> Option<Arc<PanelItem<XDGBackend>>> {
self.panel_item.upgrade()
}
}
// impl Clone for XdgSurfaceData {
// fn clone(&self) -> Self {
// Self {
// wl_surface: self.wl_surface.clone(),
// geometry: self.geometry.clone(),
// surface_type: Mutex::new(self.surface_type.lock().clone()),
// }
// }
// }
impl Dispatch<XdgSurface, Mutex<XdgSurfaceData>, WaylandState> for WaylandState {
fn request(
state: &mut WaylandState,
client: &Client,
xdg_surface: &XdgSurface,
request: xdg_surface::Request,
xdg_surface_data: &Mutex<XdgSurfaceData>,
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_surface::Request::GetToplevel { id } => {
let toplevel_state = Mutex::new(ToplevelData::new(xdg_surface));
let toplevel = data_init.init(id, toplevel_state);
debug!(?toplevel, ?xdg_surface, "Create XDG toplevel");
if toplevel.version() >= EVT_WM_CAPABILITIES_SINCE {
toplevel.wm_capabilities(vec![3]);
}
toplevel.configure(
0,
0,
if toplevel.version() >= 2 {
vec![1, 5, 6, 7, 8]
.into_iter()
.flat_map(u32::to_ne_bytes)
.collect()
} else {
vec![]
},
);
xdg_surface.configure(SERIAL_COUNTER.inc());
let client_credentials = client.get_credentials(&state.display_handle).ok();
let Some(seat_data) = client.get_data::<ClientState>().map(|s| s.seat.clone()) else {return};
let Some(wl_surface) = xdg_surface_data.lock().wl_surface() else {return};
CoreSurface::add_to(
state.display_handle.clone(),
&wl_surface,
{
let toplevel = toplevel.downgrade();
move || {
let toplevel = toplevel.upgrade().unwrap();
let toplevel_data = ToplevelData::get(&toplevel);
let Some(xdg_surface) = toplevel_data.lock().xdg_surface() else {return};
let Some(xdg_surface_data) = XdgSurfaceData::get(&xdg_surface) else {return};
xdg_surface_data.lock().surface_id = SurfaceID::Toplevel;
let Some(backend) = XDGBackend::create(toplevel.clone(), seat_data.clone()) else {return};
let panel_item = PanelItem::create(
Box::new(backend),
client_credentials.map(|c| c.pid),
);
xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item);
let _ = toplevel_data
.lock()
.panel_item
.set(Arc::downgrade(&panel_item));
handle_cursor(&panel_item, panel_item.backend.cursor.clone());
}
},
{
let toplevel = toplevel.downgrade();
move |_| {
let toplevel = toplevel.upgrade().unwrap();
let toplevel_data = ToplevelData::get(&toplevel);
let Some(panel_item) = toplevel_data.lock().panel_item() else {
let Some(xdg_surface) = toplevel_data.lock().xdg_surface() else {return};
// if the wayland toplevel isn't mapped, hammer it again with a configure until it cooperates
toplevel.configure(
0,
0,
if toplevel.version() >= 2 {
vec![5, 6, 7, 8].into_iter().flat_map(u32::to_ne_bytes).collect()
} else {
vec![]
},
);
xdg_surface.configure(SERIAL_COUNTER.inc());
return
};
let Some(xdg_surface) = toplevel_data.lock().xdg_surface() else {return};
let Some(xdg_surface_data) = XdgSurfaceData::get(&xdg_surface) else {return};
let Some(wl_surface) = xdg_surface_data.lock().wl_surface() else {return};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return};
let Some(size) = core_surface.size() else {return};
panel_item.toplevel_size_changed(size);
}
},
);
}
xdg_surface::Request::GetPopup {
id,
parent,
positioner,
} => {
let parent_clone = parent.clone().unwrap();
let parent_data = parent_clone.data::<Mutex<XdgSurfaceData>>().unwrap().lock();
// let positioner_data = positioner
// .data::<Mutex<PositionerData>>()
// .unwrap()
// .lock()
// .clone();
// let parent = match &*parent_data {
// XdgSurfaceType::Toplevel(_) => SurfaceID::Toplevel,
// XdgSurfaceType::Popup(p) => {
// SurfaceID::Popup(p.upgrade().unwrap().uid.clone())
// }
// XdgSurfaceType::Unknown => return,
// };
let uid = nanoid!();
let popup_data = Mutex::new(PopupData::new(
uid.clone(),
xdg_surface,
parent_data.surface_id.clone(),
positioner,
));
let panel_item = parent_data.panel_item().unwrap();
handle_cursor(
&panel_item,
panel_item
.backend
.seat
.new_surface(&popup_data.lock().wl_surface().unwrap()),
);
let xdg_popup = data_init.init(id, popup_data);
xdg_surface_data.lock().surface_id = SurfaceID::Child(uid);
xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item);
debug!(?xdg_popup, ?xdg_surface, "Create XDG popup");
let xdg_surface = xdg_surface.downgrade();
let xdg_popup = xdg_popup.downgrade();
CoreSurface::add_to(
state.display_handle.clone(),
&xdg_surface_data.lock().wl_surface.upgrade().unwrap(),
move || {
let xdg_popup = xdg_popup.upgrade().unwrap();
let Some(popup_data) = PopupData::get(&xdg_popup) else {return};
let popup_data = popup_data.lock();
panel_item
.backend
.new_popup(&panel_item, &xdg_popup, &*popup_data);
},
move |commit_count| {
if commit_count == 0 {
xdg_surface
.upgrade()
.unwrap()
.configure(SERIAL_COUNTER.inc())
}
},
);
}
xdg_surface::Request::SetWindowGeometry {
x,
y,
width,
height,
} => {
debug!(
?xdg_surface,
x, y, width, height, "Set XDG surface geometry"
);
let geometry = Geometry {
origin: [x, y].into(),
size: [width as u32, height as u32].into(),
};
xdg_surface_data.lock().geometry.replace(geometry);
}
xdg_surface::Request::AckConfigure { serial } => {
debug!(?xdg_surface, serial, "Acknowledge XDG surface configure");
}
xdg_surface::Request::Destroy => {
debug!(?xdg_surface, "Destroy XDG surface");
}
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone)]
pub struct ToplevelData {
panel_item: OnceCell<Weak<PanelItem<XDGBackend>>>,
xdg_surface: WlWeak<XdgSurface>,
parent: Option<WlWeak<XdgToplevel>>,
title: Option<String>,
app_id: Option<String>,
max_size: Option<Vector2<u32>>,
min_size: Option<Vector2<u32>>,
}
impl ToplevelData {
fn new(xdg_surface: &XdgSurface) -> Self {
ToplevelData {
panel_item: OnceCell::new(),
xdg_surface: xdg_surface.downgrade(),
parent: None,
title: None,
app_id: None,
max_size: None,
min_size: None,
}
}
pub fn get(toplevel: &XdgToplevel) -> &Mutex<ToplevelData> {
toplevel.data::<Mutex<ToplevelData>>().unwrap()
}
pub fn xdg_surface(&self) -> Option<XdgSurface> {
self.xdg_surface.upgrade().ok()
}
fn panel_item(&self) -> Option<Arc<PanelItem<XDGBackend>>> {
self.panel_item.get()?.upgrade()
}
}
impl Drop for ToplevelData {
fn drop(&mut self) {
let Some(panel_item) = self.panel_item() else {return};
panel_item.drop_toplevel();
}
}
impl Dispatch<XdgToplevel, Mutex<ToplevelData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
xdg_toplevel: &XdgToplevel,
request: xdg_toplevel::Request,
data: &Mutex<ToplevelData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_toplevel::Request::SetParent { parent } => {
debug!(?xdg_toplevel, ?parent, "Set XDG Toplevel parent");
data.lock().parent = parent.clone().map(|toplevel| toplevel.downgrade());
let Some(panel_item) = data.lock().panel_item() else {return};
if let Some(parent) = parent {
panel_item.toplevel_parent_changed(
&ToplevelData::get(&parent).lock().panel_item().unwrap().uid,
);
}
}
xdg_toplevel::Request::SetTitle { title } => {
debug!(?xdg_toplevel, ?title, "Set XDG Toplevel title");
data.lock().title = (!title.is_empty()).then_some(title.clone());
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.toplevel_title_changed(&title);
}
xdg_toplevel::Request::SetAppId { app_id } => {
debug!(?xdg_toplevel, ?app_id, "Set XDG Toplevel app ID");
data.lock().app_id = (!app_id.is_empty()).then_some(app_id.clone());
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.toplevel_app_id_changed(&app_id);
}
xdg_toplevel::Request::Move { seat, serial } => {
debug!(?xdg_toplevel, ?seat, serial, "XDG Toplevel move request");
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.toplevel_move_request();
}
xdg_toplevel::Request::Resize {
seat,
serial,
edges,
} => {
let WEnum::Value(edges) = edges else {return};
debug!(
?xdg_toplevel,
?seat,
serial,
?edges,
"XDG Toplevel resize request"
);
let Some(panel_item) = data.lock().panel_item() else {return};
let (up, down, left, right) = match edges {
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)
}
xdg_toplevel::Request::SetMaxSize { width, height } => {
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel max size");
data.lock().max_size = (width > 1 || height > 1)
.then_some(Vector2::from([width as u32, height as u32]));
}
xdg_toplevel::Request::SetMinSize { width, height } => {
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel min size");
data.lock().min_size = (width > 1 || height > 1)
.then_some(Vector2::from([width as u32, height as u32]));
}
xdg_toplevel::Request::SetFullscreen { output: _ } => {
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.backend.toplevel_state.lock().fullscreen = true;
panel_item.backend.configure(None);
}
xdg_toplevel::Request::UnsetFullscreen => {
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.backend.toplevel_state.lock().fullscreen = false;
panel_item.backend.configure(None);
}
xdg_toplevel::Request::Destroy => {
debug!(?xdg_toplevel, "Destroy XDG Toplevel");
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.drop_toplevel();
}
_ => {}
}
}
}
#[derive(Clone)]
pub struct PopupData {
pub uid: String,
grabbed: bool,
parent_id: SurfaceID,
positioner: XdgPositioner,
xdg_surface: WlWeak<XdgSurface>,
}
impl PopupData {
fn new(
uid: impl ToString,
xdg_surface: &XdgSurface,
parent_id: SurfaceID,
positioner: XdgPositioner,
) -> Self {
PopupData {
uid: uid.to_string(),
grabbed: false,
parent_id,
positioner,
xdg_surface: xdg_surface.downgrade(),
}
}
pub fn get(popup: &XdgPopup) -> Option<&Mutex<Self>> {
popup.data::<Mutex<Self>>()
}
pub fn xdg_surface(&self) -> Option<XdgSurface> {
self.xdg_surface.upgrade().ok()
}
fn panel_item(&self) -> Option<Arc<PanelItem<XDGBackend>>> {
XdgSurfaceData::get(&self.xdg_surface()?)?
.lock()
.panel_item()
}
// fn get_parent(&self) -> Option<XdgSurface> {
// self.parent.as_ref()?.upgrade().ok()
// }
pub fn wl_surface(&self) -> Option<WlSurface> {
XdgSurfaceData::get(&self.xdg_surface()?)?
.lock()
.wl_surface()
}
pub fn positioner_data(&self) -> Option<PositionerData> {
Some(
self.positioner
.data::<Mutex<PositionerData>>()?
.lock()
.clone(),
)
}
}
impl Debug for PopupData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("XdgPopupData")
.field("uid", &self.uid)
.field("positioner", &self.positioner)
.field("xdg_surface", &self.xdg_surface)
.finish()
}
}
impl Dispatch<XdgPopup, Mutex<PopupData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
xdg_popup: &XdgPopup,
request: xdg_popup::Request,
data: &Mutex<PopupData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_popup::Request::Grab { seat, serial } => {
let mut data = data.lock();
data.grabbed = true;
debug!(?xdg_popup, ?seat, serial, "XDG popup grab");
let Some(panel_item) = data.panel_item() else {return};
panel_item.grab_keyboard(Some(SurfaceID::Child(data.uid.clone())));
}
xdg_popup::Request::Reposition { positioner, token } => {
let mut data = data.lock();
debug!(?xdg_popup, ?positioner, token, "XDG popup reposition");
data.positioner = positioner;
let Some(panel_item) = data.panel_item() else {return};
panel_item.backend.reposition_popup(&panel_item, &*data)
}
xdg_popup::Request::Destroy => {
let data = data.lock();
debug!(?xdg_popup, "Destroy XDG popup");
if data.grabbed {
let Some(panel_item) = data.panel_item() else {return};
panel_item.grab_keyboard(None);
}
}
_ => unreachable!(),
}
}
fn destroyed(
_state: &mut WaylandState,
_client: ClientId,
_resource: ObjectId,
data: &Mutex<PopupData>,
) {
let data = data.lock();
let Some(panel_item) = data.panel_item() else {return};
panel_item.backend.drop_popup(&panel_item, &data.uid);
}
}
struct XdgToplevelState {
fullscreen: bool,
activated: bool,
}
pub struct XDGBackend {
toplevel: WlWeak<XdgToplevel>,
toplevel_wl_surface: WlWeak<WlSurface>,
toplevel_state: Mutex<XdgToplevelState>,
popups: Mutex<FxHashMap<String, WlWeak<XdgPopup>>>,
cursor: watch::Receiver<Option<CursorInfo>>,
seat: Arc<SeatData>,
pointer_grab: Mutex<Option<SurfaceID>>,
keyboard_grab: Mutex<Option<SurfaceID>>,
}
impl XDGBackend {
pub fn create(toplevel: XdgToplevel, seat: Arc<SeatData>) -> Option<Self> {
let toplevel_wl_surface =
XdgSurfaceData::get(&ToplevelData::get(&toplevel).lock().xdg_surface()?)?
.lock()
.wl_surface()?
.downgrade();
let cursor = seat.new_surface(&toplevel_wl_surface.upgrade().ok()?);
Some(XDGBackend {
toplevel: toplevel.downgrade(),
toplevel_wl_surface,
toplevel_state: Mutex::new(XdgToplevelState {
fullscreen: false,
activated: false,
}),
popups: Mutex::new(FxHashMap::default()),
cursor,
seat,
pointer_grab: Mutex::new(None),
keyboard_grab: Mutex::new(None),
})
}
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
match id {
SurfaceID::Cursor => self.cursor.borrow().as_ref()?.surface.upgrade().ok(),
SurfaceID::Toplevel => self.toplevel_wl_surface(),
SurfaceID::Child(popup) => {
let popups = self.popups.lock();
let popup = popups.get(popup)?.upgrade().ok()?;
let wl_surface = PopupData::get(&popup)?.lock().wl_surface();
wl_surface
}
}
}
fn toplevel(&self) -> Option<XdgToplevel> {
self.toplevel.upgrade().ok()
}
fn toplevel_xdg_surface(&self) -> Option<XdgSurface> {
let toplevel = self.toplevel()?;
let data = ToplevelData::get(&toplevel).lock();
data.xdg_surface()
}
fn toplevel_wl_surface(&self) -> Option<WlSurface> {
self.toplevel_wl_surface.upgrade().ok()
}
fn configure(&self, size: Option<Vector2<u32>>) {
let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return};
let Some(xdg_surface) = self.toplevel_xdg_surface() else {return};
let Some(wl_surface) = self.toplevel_wl_surface() else {return};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return};
let Some(surface_size) = core_surface.size() else {return};
xdg_toplevel.configure(
size.unwrap_or(surface_size).x as i32,
size.unwrap_or(surface_size).y as i32,
self.states()
.into_iter()
.flat_map(|state| state.to_ne_bytes())
.collect(),
);
xdg_surface.configure(SERIAL_COUNTER.inc());
self.flush_client();
}
fn states(&self) -> Vec<u32> {
let mut states = vec![1, 5, 6, 7, 8]; // maximized always and tiled
let toplevel_state = self.toplevel_state.lock();
if toplevel_state.fullscreen {
states.push(2);
}
if toplevel_state.activated {
states.push(4);
}
states
}
pub fn new_popup(
&self,
panel_item: &PanelItem<XDGBackend>,
popup: &XdgPopup,
data: &PopupData,
) {
self.popups
.lock()
.insert(data.uid.clone(), popup.downgrade());
let Some(positioner_data) = data.positioner_data() else {return};
panel_item.new_child(
&data.uid,
ChildInfo {
parent: data.parent_id.clone(),
geometry: positioner_data.into(),
},
)
}
pub fn reposition_popup(&self, panel_item: &PanelItem<XDGBackend>, popup_state: &PopupData) {
let Some(positioner_data) = popup_state.positioner_data() else {return};
panel_item.reposition_child(&popup_state.uid, positioner_data.into())
}
pub fn drop_popup(&self, panel_item: &PanelItem<XDGBackend>, uid: &str) {
panel_item.drop_child(uid);
let Some(popup) = self
.popups
.lock()
.remove(uid) else {return};
let Some(popup) = popup.upgrade().ok() else {return};
let Some(popup) = popup.data::<Arc<PopupData>>().cloned() else {return};
let Some(wl_surface) = popup.wl_surface() else {return};
self.seat.drop_surface(&wl_surface);
}
fn child_data(&self) -> FxHashMap<String, ChildInfo> {
FxHashMap::from_iter(self.popups.lock().values().filter_map(|v| {
let popup = v.upgrade().ok()?;
let data = PopupData::get(&popup)?;
let data_lock = data.lock();
Some((
data_lock.uid.clone(),
ChildInfo {
parent: data_lock.parent_id.clone(),
geometry: data_lock.positioner_data()?.into(),
},
))
}))
}
fn flush_client(&self) {
let Some(client) = self.toplevel_wl_surface().and_then(|s| s.client()) else {return};
if let Some(client_state) = client.get_data::<ClientState>() {
client_state.flush();
}
}
}
impl Drop for XDGBackend {
fn drop(&mut self) {
let Some(toplevel) = self.toplevel_wl_surface() else {return};
self.seat.drop_surface(&toplevel);
debug!("Dropped panel item gracefully");
}
}
impl Backend for XDGBackend {
fn start_data(&self) -> Result<PanelItemInitData> {
let toplevel_data = self
.toplevel()
.map(|t| ToplevelData::get(&t).lock().clone())
.ok_or_else(|| eyre!("Could not get toplevel"))?;
let pointer_grab = self.pointer_grab.lock().clone();
let keyboard_grab = self.keyboard_grab.lock().clone();
let Some(wl_surface) = self.toplevel_wl_surface() else {bail!("Wayland surface not found")};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {bail!("Core surface not found")};
let Some(size) = core_surface.size() else {bail!("Surface size not found")};
let toplevel = ToplevelInfo {
parent: toplevel_data
.parent
.as_ref()
.and_then(|p| p.upgrade().ok())
.and_then(|p| ToplevelData::get(&p).lock().panel_item())
.map(|p| p.uid.clone()),
title: toplevel_data.title.clone(),
app_id: toplevel_data.app_id.clone(),
size,
min_size: toplevel_data.min_size.clone(),
max_size: toplevel_data.max_size.clone(),
logical_rectangle: XdgSurfaceData::get(&self.toplevel_xdg_surface().unwrap())
.unwrap()
.lock()
.geometry
.clone()
.unwrap_or_else(|| Geometry {
origin: [0, 0].into(),
size,
}),
};
Ok(PanelItemInitData {
cursor: self.cursor.borrow().as_ref().and_then(|c| c.cursor_data()),
toplevel,
children: self.child_data(),
pointer_grab,
keyboard_grab,
})
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
let Some(wl_surface) = self.wl_surface_from_id(&surface) else {return};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return};
core_surface.apply_material(model_part);
}
fn close_toplevel(&self) {
let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return};
xdg_toplevel.close();
}
fn auto_size_toplevel(&self) {
self.configure(Some([0, 0].into()));
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
self.configure(Some(size));
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
self.toplevel_state.lock().activated = focused;
self.configure(None);
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat
.pointer_event(&surface, PointerEvent::Motion(position));
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.pointer_event(
&surface,
PointerEvent::Button {
button,
state: if pressed { 1 } else { 0 },
},
)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.pointer_event(
&surface,
PointerEvent::Scroll {
axis_continuous: scroll_distance,
axis_discrete: scroll_steps,
},
)
}
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(keymap_id).cloned() else {return};
if self.seat.set_keymap(keymap, vec![surface.clone()]).is_err() {
return;
}
for key in keys {
self.seat.keyboard_event(
&surface,
KeyboardEvent::Key {
key: key.abs() as u32,
state: if key < 0 { 1 } else { 0 },
},
);
}
}
}