refactor(wayland): use smithay xdg_shell handler

This commit is contained in:
Nova
2024-03-09 02:23:40 -05:00
parent fba4e10611
commit 34aab266a3
14 changed files with 661 additions and 1785 deletions

601
src/wayland/xdg_shell.rs Normal file
View File

@@ -0,0 +1,601 @@
use super::{
seat::{handle_cursor, SeatWrapper},
state::{ClientState, WaylandState},
surface::CoreSurface,
utils,
};
use crate::nodes::{
drawable::model::ModelPart,
items::panel::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceID, ToplevelInfo,
},
};
use color_eyre::eyre::Result;
use mint::Vector2;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use smithay::{
delegate_xdg_shell,
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
shell::server::xdg_toplevel::{ResizeEdge, State},
},
wayland_server::{
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
Resource,
},
},
utils::{Logical, Rectangle, Serial},
wayland::{
compositor,
shell::xdg::{
Configure, PopupSurface, PositionerState, ShellClient, SurfaceCachedState,
ToplevelSurface, XdgShellHandler, XdgShellState, XdgToplevelSurfaceData,
},
},
};
use std::sync::{Arc, Weak};
use tracing::warn;
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(),
}
}
}
fn surface_panel_item(wl_surface: &WlSurface) -> Option<Arc<PanelItem<XdgBackend>>> {
let panel_item = utils::get_data::<Weak<PanelItem<XdgBackend>>>(wl_surface)
.as_deref()
.and_then(Weak::upgrade);
if panel_item.is_none() {
warn!("Couldn't get panel item");
// println!("panel item not found at \n{}\n\n", {
// let backtrace = Backtrace::force_capture().to_string();
// let mut split_backtrace = backtrace
// .split('\n')
// .map(|s| s.to_string())
// .collect::<Vec<_>>();
// split_backtrace.resize(4, "".to_string());
// split_backtrace.join("\n")
// });
}
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.with_pending_state(|s| {
s.decoration_mode = Some(Mode::ClientSide);
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);
});
toplevel.send_configure();
utils::insert_data(toplevel.wl_surface(), SurfaceID::Toplevel);
CoreSurface::add_to(
self.display_handle.clone(),
toplevel.wl_surface(),
{
let toplevel = toplevel.clone();
move || {
let wl_surface = toplevel.wl_surface().client().unwrap();
let client_state = wl_surface.get_data::<ClientState>().unwrap();
let (node, panel_item) = PanelItem::create(
Box::new(XdgBackend::create(
toplevel.clone(),
client_state.seat.clone(),
)),
client_state.pid.clone(),
);
handle_cursor(&panel_item, panel_item.backend.seat.cursor_info_rx.clone());
utils::insert_data(toplevel.wl_surface(), Arc::downgrade(&panel_item));
utils::insert_data_raw(toplevel.wl_surface(), node);
}
},
{
let toplevel = toplevel.clone();
move |_c| {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
// if the wayland toplevel isn't mapped, hammer it again with a configure until it cooperates
toplevel.send_configure();
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(toplevel.wl_surface())
else {
return;
};
let Some(size) = core_surface.size() else {
return;
};
panel_item.toplevel_size_changed(size);
}
},
);
}
fn toplevel_destroyed(&mut self, toplevel: ToplevelSurface) {
if let Some(core_surface) = CoreSurface::from_wl_surface(toplevel.wl_surface()) {
core_surface.decycle();
}
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.popups.lock().clear();
panel_item.drop_toplevel();
// println!(
// "Dropping toplevel resulted in {} references",
// Arc::strong_count(&panel_item)
// );
}
fn app_id_changed(&mut self, toplevel: ToplevelSurface) {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.toplevel_app_id_changed(&compositor::with_states(
toplevel.wl_surface(),
|states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.app_id
.clone()
.unwrap()
},
))
}
fn title_changed(&mut self, toplevel: ToplevelSurface) {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.toplevel_title_changed(&compositor::with_states(
toplevel.wl_surface(),
|states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.title
.clone()
.unwrap()
},
))
}
fn new_popup(&mut self, popup: PopupSurface, positioner: PositionerState) {
let uid = nanoid::nanoid!();
let Some(parent) = popup.get_parent_surface() else {
return;
};
let Some(panel_item) = surface_panel_item(&parent) else {
return;
};
if popup.send_configure().is_err() {
return;
}
utils::insert_data(popup.wl_surface(), SurfaceID::Child(uid.clone()));
utils::insert_data(popup.wl_surface(), uid.clone());
utils::insert_data(popup.wl_surface(), Arc::downgrade(&panel_item));
CoreSurface::add_to(
self.display_handle.clone(),
popup.wl_surface(),
{
let popup = popup.clone();
move || {
panel_item
.backend
.new_popup(&uid, popup.clone(), positioner);
}
},
{
let popup = popup.clone();
move |_c| {
if surface_panel_item(popup.wl_surface()).is_none() {
// if the popup toplevel isn't mapped, hammer it again with a configure until it cooperates
let _ = popup.send_configure();
};
}
},
);
}
fn reposition_request(
&mut self,
popup: PopupSurface,
positioner: PositionerState,
_token: u32,
) {
let Some(panel_item) = surface_panel_item(popup.wl_surface()) else {
return;
};
let Some(uid) = utils::get_data::<String>(popup.wl_surface())
.as_deref()
.cloned()
else {
return;
};
panel_item.backend.reposition_popup(&uid, popup, positioner)
}
fn popup_destroyed(&mut self, popup: PopupSurface) {
if let Some(core_surface) = CoreSurface::from_wl_surface(popup.wl_surface()) {
core_surface.decycle();
}
let Some(panel_item) = surface_panel_item(popup.wl_surface()) else {
return;
};
let Some(uid) = utils::get_data::<String>(popup.wl_surface())
.as_deref()
.cloned()
else {
return;
};
panel_item.backend.seat.unfocus(popup.wl_surface(), self);
panel_item.backend.drop_popup(&uid);
}
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);
}
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
let Some(panel_item) = surface_panel_item(&surface) else {
return;
};
match configure {
Configure::Toplevel(t) => {
if let Some(size) = t.state.size {
panel_item.toplevel_size_changed([size.w as u32, size.h as u32].into())
}
}
Configure::Popup(_p) => (),
}
}
}
delegate_xdg_shell!(WaylandState);
pub struct XdgBackend {
toplevel: Mutex<Option<ToplevelSurface>>,
popups: Mutex<FxHashMap<String, (PopupSurface, PositionerState)>>,
pub seat: Arc<SeatWrapper>,
}
impl XdgBackend {
pub fn create(toplevel: ToplevelSurface, seat: Arc<SeatWrapper>) -> Self {
XdgBackend {
toplevel: Mutex::new(Some(toplevel)),
popups: Mutex::new(FxHashMap::default()),
seat,
}
}
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
match id {
SurfaceID::Cursor => self
.seat
.cursor_info_rx
.borrow()
.surface
.clone()?
.upgrade()
.ok(),
SurfaceID::Toplevel => Some(self.toplevel.lock().clone()?.wl_surface().clone()),
SurfaceID::Child(popup) => {
let popups = self.popups.lock();
Some(popups.get(popup)?.0.wl_surface().clone())
}
}
}
fn panel_item(&self) -> Option<Arc<PanelItem<XdgBackend>>> {
surface_panel_item(self.toplevel.lock().clone()?.wl_surface())
}
pub fn new_popup(&self, uid: &str, popup: PopupSurface, positioner: PositionerState) {
let Some(panel_item) = self.panel_item() else {
return;
};
self.popups
.lock()
.insert(uid.to_string(), (popup, positioner));
panel_item.new_child(uid, self.child_data(uid).unwrap());
}
pub fn reposition_popup(&self, uid: &str, _popup: PopupSurface, positioner: PositionerState) {
self.popups.lock().get_mut(uid).unwrap().1 = positioner;
let Some(panel_item) = self.panel_item() else {
return;
};
let geometry = positioner.get_geometry();
panel_item.reposition_child(uid, geometry.into());
}
pub fn drop_popup(&self, uid: &str) {
let Some(panel_item) = self.panel_item() else {
return;
};
panel_item.drop_child(uid);
}
fn child_data(&self, uid: &str) -> Option<ChildInfo> {
let (popup, positioner) = self.popups.lock().get(uid)?.clone();
Some(ChildInfo {
parent: (*utils::get_data::<SurfaceID>(&popup.get_parent_surface()?)?).clone(),
geometry: positioner.get_geometry().into(),
})
}
}
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(CoreSurface::from_wl_surface)
.and_then(|c| c.size())
.map(|size| Geometry {
origin: [0; 2].into(),
size,
});
let toplevel = self.toplevel.lock().clone().unwrap();
let app_id = compositor::with_states(toplevel.wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.title
.clone()
});
let title = compositor::with_states(toplevel.wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.app_id
.clone()
});
let toplevel_cached_state = compositor::with_states(toplevel.wl_surface(), |states| {
states.cached_state.current::<SurfaceCachedState>().clone()
});
let toplevel_core_surface = CoreSurface::from_wl_surface(toplevel.wl_surface()).unwrap();
let size = toplevel
.current_state()
.size
.clone()
.map(|s| [s.w as u32, s.h as u32].into())
.or_else(|| toplevel_core_surface.size())
.unwrap_or([0; 2].into());
let toplevel = ToplevelInfo {
parent: toplevel
.parent()
.as_ref()
.and_then(surface_panel_item)
.map(|p| p.uid.clone()),
title,
app_id,
size,
min_size: if toplevel_cached_state.min_size.w != 0
&& toplevel_cached_state.min_size.h != 0
{
Some(
[
toplevel_cached_state.min_size.w as u32,
toplevel_cached_state.min_size.h as u32,
]
.into(),
)
} else {
None
},
max_size: if toplevel_cached_state.max_size.w != 0
&& toplevel_cached_state.max_size.h != 0
{
Some(
[
toplevel_cached_state.max_size.w as u32,
toplevel_cached_state.max_size.h as u32,
]
.into(),
)
} else {
None
},
logical_rectangle: toplevel_cached_state
.geometry
.map(Into::into)
.unwrap_or(Geometry {
origin: [0; 2].into(),
size,
}),
};
let children = self
.popups
.lock()
.keys()
.map(|k| (k.clone(), self.child_data(k).unwrap()))
.collect();
Ok(PanelItemInitData {
cursor,
toplevel,
children,
pointer_grab: None,
keyboard_grab: None,
})
}
fn surface_alive(&self, surface: &SurfaceID) -> bool {
let Some(surface) = self.wl_surface_from_id(surface) else {
return false;
};
surface.is_alive()
}
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 as i32, size.y as i32).into()));
toplevel.send_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_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>) {
let Some(surface) = self.wl_surface_from_id(&surface) else {
return;
};
self.seat.keyboard_keys(surface, keymap_id, keys)
}
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_touches(&self) {
self.seat.reset_touches()
}
}