806 lines
22 KiB
Rust
806 lines
22 KiB
Rust
use super::{
|
|
seat::{Cursor, SeatData},
|
|
surface::CoreSurface,
|
|
xdg_shell::{PopupData, ToplevelData, XdgSurfaceData},
|
|
SERIAL_COUNTER,
|
|
};
|
|
use crate::{
|
|
core::{
|
|
client::{get_env, startup_settings, Client, INTERNAL_CLIENT},
|
|
registry::Registry,
|
|
},
|
|
nodes::{
|
|
drawable::Drawable,
|
|
items::{self, Item, ItemSpecialization, ItemType, TypeInfo},
|
|
spatial::Spatial,
|
|
Node,
|
|
},
|
|
wayland::seat::{KeyboardEvent, PointerEvent},
|
|
};
|
|
use color_eyre::eyre::{bail, eyre, Result};
|
|
use glam::Mat4;
|
|
use lazy_static::lazy_static;
|
|
use mint::Vector2;
|
|
use nanoid::nanoid;
|
|
use parking_lot::Mutex;
|
|
use rustc_hash::FxHashMap;
|
|
use serde::{
|
|
de::{Deserializer, Error, SeqAccess, Visitor},
|
|
ser::Serializer,
|
|
Deserialize, Serialize,
|
|
};
|
|
use smithay::{
|
|
reexports::{
|
|
wayland_protocols::xdg::shell::server::{
|
|
xdg_popup::XdgPopup,
|
|
xdg_surface::XdgSurface,
|
|
xdg_toplevel::{XdgToplevel, EVT_CONFIGURE_BOUNDS_SINCE, EVT_WM_CAPABILITIES_SINCE},
|
|
},
|
|
wayland_server::{
|
|
backend::Credentials, protocol::wl_surface::WlSurface, Resource, Weak as WlWeak,
|
|
},
|
|
},
|
|
wayland::compositor,
|
|
};
|
|
#[cfg(feature = "xwayland")]
|
|
use smithay::{
|
|
utils::Rectangle,
|
|
xwayland::{xwm::X11SurfaceError, X11Surface},
|
|
};
|
|
use stardust_xr::schemas::flex::{deserialize, serialize};
|
|
use std::sync::{Arc, Weak};
|
|
use tracing::debug;
|
|
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
|
|
|
|
lazy_static! {
|
|
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
|
|
type_name: "panel",
|
|
aliased_local_signals: vec![
|
|
"apply_surface_material",
|
|
"configure_toplevel",
|
|
"set_toplevel_capabilities",
|
|
"pointer_scroll",
|
|
"pointer_button",
|
|
"pointer_motion",
|
|
"keyboard_key",
|
|
"keyboard_set_keymap_names",
|
|
"keyboard_set_keymap_string",
|
|
"close",
|
|
],
|
|
aliased_local_methods: vec![],
|
|
aliased_remote_signals: vec![
|
|
"commit_toplevel",
|
|
"recommend_toplevel_state",
|
|
"set_cursor",
|
|
"new_popup",
|
|
"reposition_popup",
|
|
"drop_popup",
|
|
],
|
|
ui: Default::default(),
|
|
items: Registry::new(),
|
|
acceptors: Registry::new(),
|
|
};
|
|
}
|
|
|
|
/// An ID for a surface inside this panel item
|
|
#[derive(Debug, Clone)]
|
|
#[allow(dead_code)]
|
|
pub enum SurfaceID {
|
|
Cursor,
|
|
Toplevel,
|
|
Popup(String),
|
|
}
|
|
impl Default for SurfaceID {
|
|
fn default() -> Self {
|
|
Self::Toplevel
|
|
}
|
|
}
|
|
|
|
impl<'de> serde::Deserialize<'de> for SurfaceID {
|
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
deserializer.deserialize_seq(SurfaceIDVisitor)
|
|
}
|
|
}
|
|
|
|
struct SurfaceIDVisitor;
|
|
|
|
impl<'de> Visitor<'de> for SurfaceIDVisitor {
|
|
type Value = SurfaceID;
|
|
|
|
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
f.write_str("idk")
|
|
}
|
|
|
|
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
|
|
let Some(discrim) = seq.next_element()? else {
|
|
return Err(A::Error::missing_field("discrim"));
|
|
};
|
|
|
|
// idk if you wanna check for extraneous elements
|
|
// I didn't bother
|
|
|
|
match discrim {
|
|
"Cursor" => Ok(SurfaceID::Cursor),
|
|
"Toplevel" => Ok(SurfaceID::Toplevel),
|
|
"Popup" => {
|
|
let Some(text) = seq.next_element()? else {
|
|
return Err(A::Error::missing_field("popup_text"));
|
|
};
|
|
Ok(SurfaceID::Popup(text))
|
|
}
|
|
_ => Err(A::Error::unknown_variant(
|
|
discrim,
|
|
&["Cursor", "Toplevel", "Popup"],
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl serde::Serialize for SurfaceID {
|
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
match self {
|
|
Self::Cursor => ["Cursor"].serialize(serializer),
|
|
Self::Toplevel => ["Toplevel"].serialize(serializer),
|
|
Self::Popup(text) => ["Popup", text].serialize(serializer),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize)]
|
|
#[serde(tag = "type", content = "content")]
|
|
pub enum RecommendedState {
|
|
Maximize(bool),
|
|
Fullscreen(bool),
|
|
Minimize,
|
|
Move,
|
|
Resize(u32),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct WaylandBackend {
|
|
toplevel: WlWeak<XdgToplevel>,
|
|
toplevel_wl_surface: WlWeak<WlSurface>,
|
|
popups: Mutex<FxHashMap<String, WlWeak<XdgPopup>>>,
|
|
cursor: Mutex<Option<WlWeak<WlSurface>>>,
|
|
}
|
|
impl WaylandBackend {
|
|
pub fn create(toplevel: XdgToplevel) -> Option<Self> {
|
|
let toplevel_wl_surface =
|
|
XdgSurfaceData::get(&ToplevelData::get(&toplevel).lock().xdg_surface()?)?
|
|
.lock()
|
|
.wl_surface()?
|
|
.downgrade();
|
|
Some(WaylandBackend {
|
|
toplevel: toplevel.downgrade(),
|
|
toplevel_wl_surface,
|
|
popups: Mutex::new(FxHashMap::default()),
|
|
cursor: Mutex::new(None),
|
|
})
|
|
}
|
|
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 wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
|
|
match id {
|
|
SurfaceID::Cursor => self.cursor.lock().clone()?.upgrade().ok(),
|
|
SurfaceID::Toplevel => self.toplevel_wl_surface(),
|
|
SurfaceID::Popup(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 input_surfaces(&self) -> Vec<WlSurface> {
|
|
let mut surfaces = self
|
|
.toplevel_wl_surface()
|
|
.map(|s| vec![s])
|
|
.unwrap_or_default();
|
|
surfaces.extend(self.popups.lock().values().filter_map(|p| {
|
|
let popup = p.upgrade().ok()?;
|
|
let popup_data = PopupData::get(&popup)?.lock();
|
|
let xdg_surface = popup_data.xdg_surface()?;
|
|
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface)?.lock();
|
|
xdg_surface_data.wl_surface()
|
|
}));
|
|
surfaces
|
|
}
|
|
|
|
fn configure_toplevel(
|
|
&self,
|
|
size: Option<Vector2<u32>>,
|
|
states: Vec<u32>,
|
|
bounds: Option<Vector2<u32>>,
|
|
) {
|
|
let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return};
|
|
let Some(xdg_surface) = self.toplevel_xdg_surface() else {return};
|
|
debug!(?size, ?states, ?bounds, "Configure toplevel info");
|
|
if let Some(bounds) = bounds {
|
|
if xdg_toplevel.version() > EVT_CONFIGURE_BOUNDS_SINCE {
|
|
xdg_toplevel.configure_bounds(bounds.x as i32, bounds.y as i32);
|
|
}
|
|
}
|
|
let size = size.unwrap_or(Vector2::from([0; 2]));
|
|
xdg_toplevel.configure(
|
|
size.x as i32,
|
|
size.y as i32,
|
|
states
|
|
.into_iter()
|
|
.flat_map(|state| state.to_ne_bytes())
|
|
.collect(),
|
|
);
|
|
xdg_surface.configure(SERIAL_COUNTER.inc());
|
|
}
|
|
|
|
fn serialize_toplevel(&self) -> Result<Vec<u8>> {
|
|
let toplevel = self
|
|
.toplevel()
|
|
.ok_or_else(|| eyre!("Toplevel does not exist"))?;
|
|
let state = ToplevelData::get(&toplevel);
|
|
let data = serialize(&state.lock().clone())?;
|
|
Ok(data)
|
|
}
|
|
|
|
fn set_toplevel_capabilities(&self, capabilities: Vec<u8>) {
|
|
let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return};
|
|
xdg_toplevel.wm_capabilities(capabilities);
|
|
let Some(xdg_surface) = self.toplevel_xdg_surface() else {return};
|
|
xdg_surface.configure(SERIAL_COUNTER.inc());
|
|
}
|
|
|
|
pub fn new_popup(&self, panel_item: &PanelItem, popup: &XdgPopup, data: &PopupData) {
|
|
let uid = data.uid.clone();
|
|
|
|
self.popups.lock().insert(uid.clone(), popup.downgrade());
|
|
|
|
let Some(node) = panel_item.node.upgrade() else { return };
|
|
let _ = node.send_remote_signal("new_popup", &serialize(&(&uid, data)).unwrap());
|
|
}
|
|
pub fn reposition_popup(&self, panel_item: &PanelItem, popup_state: &PopupData) {
|
|
let Some(node) = panel_item.node.upgrade() else { return };
|
|
|
|
let _ = node.send_remote_signal(
|
|
"reposition_popup",
|
|
&serialize(popup_state.positioner_data().unwrap()).unwrap(),
|
|
);
|
|
}
|
|
pub fn drop_popup(&self, panel_item: &PanelItem, uid: &str) {
|
|
'seat_drop: {
|
|
let Some(popup) = self
|
|
.popups
|
|
.lock()
|
|
.remove(uid) else {break 'seat_drop};
|
|
let Some(popup) = popup.upgrade().ok() else {break 'seat_drop};
|
|
let Some(popup) = popup.data::<Arc<PopupData>>().cloned() else {break 'seat_drop};
|
|
let Some(wl_surface) = popup.wl_surface() else {break 'seat_drop};
|
|
panel_item.seat_data.drop_surface(&wl_surface);
|
|
}
|
|
|
|
let Some(node) = panel_item.node.upgrade() else { return };
|
|
let _ = node.send_remote_signal("drop_popup", &serialize(uid).unwrap());
|
|
}
|
|
|
|
fn popups_data(&self) -> Vec<PopupData> {
|
|
self.popups
|
|
.lock()
|
|
.values()
|
|
.filter_map(|v| Some(v.upgrade().ok()?.data::<Mutex<PopupData>>()?.lock().clone()))
|
|
.collect::<Vec<_>>()
|
|
}
|
|
fn cursor_data(&self) -> Option<(Vector2<u32>, Vector2<i32>)> {
|
|
let cursor = self.cursor.lock().as_ref().and_then(|c| c.upgrade().ok());
|
|
let cursor_size = cursor
|
|
.as_ref()
|
|
.and_then(|c| CoreSurface::from_wl_surface(&c))
|
|
.and_then(|c| c.size());
|
|
let cursor_hotspot = cursor
|
|
.and_then(|c| {
|
|
compositor::with_states(&c, |data| data.data_map.get::<Arc<Cursor>>().cloned())
|
|
})
|
|
.map(|cursor| cursor.hotspot);
|
|
|
|
cursor_size.zip(cursor_hotspot)
|
|
}
|
|
|
|
pub fn set_cursor(
|
|
&self,
|
|
panel_item: &PanelItem,
|
|
surface: Option<&WlSurface>,
|
|
hotspot_x: i32,
|
|
hotspot_y: i32,
|
|
) {
|
|
let Some(node) = panel_item.node.upgrade() else { return };
|
|
debug!(?surface, hotspot_x, hotspot_y, "Set cursor size");
|
|
let mut data = serialize(()).unwrap();
|
|
|
|
let cursor_size = surface
|
|
.and_then(|c| CoreSurface::from_wl_surface(c))
|
|
.and_then(|c| c.size());
|
|
|
|
if let Some(size) = cursor_size {
|
|
data = serialize((size, (hotspot_x, hotspot_y))).unwrap();
|
|
}
|
|
|
|
let _ = node.send_remote_signal("set_cursor", &data);
|
|
*self.cursor.lock() = surface.map(|surf| surf.downgrade());
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "xwayland")]
|
|
#[derive(Debug)]
|
|
pub struct X11Backend {
|
|
pub toplevel_parent: Option<X11Surface>,
|
|
pub toplevel: X11Surface,
|
|
}
|
|
#[cfg(feature = "xwayland")]
|
|
impl X11Backend {
|
|
fn configure_toplevel(
|
|
&self,
|
|
size: Option<Vector2<u32>>,
|
|
states: Vec<u32>,
|
|
) -> Result<(), X11SurfaceError> {
|
|
self.toplevel.configure(
|
|
size.map(|s| Rectangle::from_loc_and_size((0, 0), (s.x as i32, s.y as i32))),
|
|
)?;
|
|
self.toplevel.set_maximized(states.contains(&1))?;
|
|
Ok(())
|
|
}
|
|
|
|
fn serialize_toplevel(&self) -> Result<Vec<u8>> {
|
|
let toplevel_state = (
|
|
None::<String>,
|
|
self.toplevel.title(),
|
|
None::<String>,
|
|
(
|
|
self.toplevel.geometry().size.w,
|
|
self.toplevel.geometry().size.h,
|
|
),
|
|
self.toplevel.min_size().map(|s| (s.w, s.h)),
|
|
self.toplevel.max_size().map(|s| (s.w, s.w)),
|
|
);
|
|
let data = serialize(&toplevel_state)?;
|
|
Ok(data)
|
|
}
|
|
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
|
|
match id {
|
|
SurfaceID::Cursor => None,
|
|
SurfaceID::Toplevel => self.toplevel.wl_surface(),
|
|
SurfaceID::Popup(_) => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: abstract this away into a trait so other platforms e.g. arcan/android/windows/xrdesktop can also be 2D backends
|
|
#[derive(Debug)]
|
|
pub enum Backend {
|
|
Wayland(WaylandBackend),
|
|
#[cfg(feature = "xwayland")]
|
|
X11(X11Backend),
|
|
}
|
|
|
|
pub struct PanelItem {
|
|
pub uid: String,
|
|
node: Weak<Node>,
|
|
pub seat_data: Arc<SeatData>,
|
|
pub backend: Backend,
|
|
pointer_grab: Mutex<Option<SurfaceID>>,
|
|
keyboard_grab: Mutex<Option<SurfaceID>>,
|
|
}
|
|
impl PanelItem {
|
|
pub fn create(
|
|
wl_surface: WlSurface,
|
|
backend: Backend,
|
|
client_credentials: Option<Credentials>,
|
|
seat_data: Arc<SeatData>,
|
|
) -> (Arc<Node>, Arc<PanelItem>) {
|
|
debug!(?backend, ?client_credentials, "Create panel item");
|
|
|
|
let startup_settings = client_credentials
|
|
.and_then(|cred| get_env(cred.pid).ok())
|
|
.and_then(|env| startup_settings(&env));
|
|
|
|
let uid = nanoid!();
|
|
let node = Node::create(&INTERNAL_CLIENT, "/item/panel/item", &uid, true)
|
|
.add_to_scenegraph()
|
|
.unwrap();
|
|
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
|
|
let panel_item = Arc::new(PanelItem {
|
|
uid: uid.clone(),
|
|
node: Arc::downgrade(&node),
|
|
seat_data,
|
|
backend,
|
|
pointer_grab: Mutex::new(None),
|
|
keyboard_grab: Mutex::new(None),
|
|
});
|
|
|
|
if let Some(startup_settings) = &startup_settings {
|
|
spatial.set_local_transform(
|
|
spatial.global_transform().inverse() * startup_settings.transform,
|
|
);
|
|
}
|
|
|
|
panel_item
|
|
.seat_data
|
|
.new_surface(&wl_surface, Arc::downgrade(&panel_item));
|
|
|
|
let item = Item::add_to(
|
|
&node,
|
|
uid,
|
|
&ITEM_TYPE_INFO_PANEL,
|
|
ItemType::Panel(panel_item.clone()),
|
|
);
|
|
|
|
if let Some(startup_settings) = &startup_settings {
|
|
if let Some(acceptor) = startup_settings
|
|
.acceptors
|
|
.get(&*ITEM_TYPE_INFO_PANEL)
|
|
.and_then(|acc| acc.upgrade())
|
|
{
|
|
items::capture(&item, &acceptor);
|
|
}
|
|
}
|
|
node.add_local_signal(
|
|
"apply_surface_material",
|
|
PanelItem::apply_surface_material_flex,
|
|
);
|
|
node.add_local_signal("configure_toplevel", PanelItem::configure_toplevel_flex);
|
|
node.add_local_signal(
|
|
"set_toplevel_capabilities",
|
|
PanelItem::set_toplevel_capabilities_flex,
|
|
);
|
|
node.add_local_signal("pointer_scroll", PanelItem::pointer_scroll_flex);
|
|
node.add_local_signal("pointer_button", PanelItem::pointer_button_flex);
|
|
node.add_local_signal("pointer_motion", PanelItem::pointer_motion_flex);
|
|
|
|
node.add_local_signal(
|
|
"keyboard_set_keymap_string",
|
|
PanelItem::keyboard_set_keymap_string_flex,
|
|
);
|
|
node.add_local_signal(
|
|
"keyboard_set_keymap_names",
|
|
PanelItem::keyboard_set_keymap_names_flex,
|
|
);
|
|
node.add_local_signal("keyboard_key", PanelItem::keyboard_key_flex);
|
|
|
|
(node, panel_item)
|
|
}
|
|
|
|
pub fn from_node(node: &Node) -> Option<Arc<PanelItem>> {
|
|
let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None};
|
|
Some(panel_item.clone())
|
|
}
|
|
|
|
fn toplevel_wl_surface(&self) -> Option<WlSurface> {
|
|
match &self.backend {
|
|
Backend::Wayland(w) => w.toplevel_wl_surface(),
|
|
#[cfg(feature = "xwayland")]
|
|
Backend::X11(x) => x.toplevel.wl_surface(),
|
|
}
|
|
}
|
|
fn core_surface(&self) -> Option<Arc<CoreSurface>> {
|
|
compositor::with_states(&self.toplevel_wl_surface()?, |data| {
|
|
data.data_map.get::<Arc<CoreSurface>>().cloned()
|
|
})
|
|
}
|
|
fn flush_clients(&self) {
|
|
if let Some(core_surface) = self.core_surface() {
|
|
core_surface.flush_clients();
|
|
}
|
|
}
|
|
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
|
|
match &self.backend {
|
|
Backend::Wayland(w) => w.wl_surface_from_id(id),
|
|
#[cfg(feature = "xwayland")]
|
|
Backend::X11(x) => x.wl_surface_from_id(id),
|
|
}
|
|
}
|
|
fn wl_surface_from_id_result(&self, id: &SurfaceID) -> Result<WlSurface> {
|
|
self.wl_surface_from_id(id)
|
|
.ok_or(eyre!("Surface with ID not found"))
|
|
}
|
|
|
|
fn apply_surface_material_flex(
|
|
node: &Node,
|
|
calling_client: Arc<Client>,
|
|
data: &[u8],
|
|
) -> Result<()> {
|
|
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct SurfaceMaterialInfo<'a> {
|
|
surface: SurfaceID,
|
|
model_node_path: &'a str,
|
|
}
|
|
|
|
let info: SurfaceMaterialInfo = deserialize(data)?;
|
|
|
|
let Some(wl_surface) = panel_item.wl_surface_from_id(&info.surface) else { return Ok(()) };
|
|
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else { return Ok(()) };
|
|
|
|
let model_node = calling_client
|
|
.scenegraph
|
|
.get_node(info.model_node_path)
|
|
.ok_or_else(|| eyre!("Model node not found"))?;
|
|
let Some(Drawable::ModelPart(model_node)) = model_node.drawable.get() else {bail!("Node is not a model")};
|
|
debug!(?info, "Apply surface material");
|
|
|
|
core_surface.apply_material(model_node);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn pointer_motion_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
|
|
let (surface_id, position): (SurfaceID, Vector2<f64>) = deserialize(data)?;
|
|
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
|
|
debug!(?surface_id, ?position, "Pointer deactivate");
|
|
|
|
panel_item
|
|
.seat_data
|
|
.pointer_event(&wl_surface, PointerEvent::Motion(position));
|
|
panel_item.flush_clients();
|
|
|
|
Ok(())
|
|
}
|
|
fn pointer_button_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
|
|
let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(data)?;
|
|
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
|
|
debug!(?surface_id, button, state, "Pointer button");
|
|
|
|
panel_item
|
|
.seat_data
|
|
.pointer_event(&wl_surface, PointerEvent::Button { button, state });
|
|
panel_item.flush_clients();
|
|
|
|
Ok(())
|
|
}
|
|
fn pointer_scroll_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct PointerScrollInfo {
|
|
surface_id: SurfaceID,
|
|
axis_continuous: Option<Vector2<f32>>,
|
|
axis_discrete: Option<Vector2<f32>>,
|
|
}
|
|
let info: PointerScrollInfo = deserialize(data)?;
|
|
let wl_surface = panel_item.wl_surface_from_id_result(&info.surface_id)?;
|
|
|
|
debug!(?info, "Pointer scroll");
|
|
|
|
panel_item.seat_data.pointer_event(
|
|
&wl_surface,
|
|
PointerEvent::Scroll {
|
|
axis_continuous: info.axis_continuous,
|
|
axis_discrete: info.axis_discrete,
|
|
},
|
|
);
|
|
panel_item.flush_clients();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn keyboard_set_keymap_string_flex(
|
|
node: &Node,
|
|
_calling_client: Arc<Client>,
|
|
data: &[u8],
|
|
) -> Result<()> {
|
|
let context = xkb::Context::new(0);
|
|
let keymap =
|
|
Keymap::new_from_string(&context, deserialize(data)?, XKB_KEYMAP_FORMAT_TEXT_V1, 0)
|
|
.ok_or_else(|| eyre!("Keymap is not valid"))?;
|
|
|
|
PanelItem::keyboard_set_keymap_flex(node, &keymap)
|
|
}
|
|
fn keyboard_set_keymap_names_flex(
|
|
node: &Node,
|
|
_calling_client: Arc<Client>,
|
|
data: &[u8],
|
|
) -> Result<()> {
|
|
#[derive(Debug, Deserialize)]
|
|
struct Names<'a> {
|
|
rules: &'a str,
|
|
model: &'a str,
|
|
layout: &'a str,
|
|
variant: &'a str,
|
|
options: Option<String>,
|
|
}
|
|
let names: Names = deserialize(data)?;
|
|
let context = xkb::Context::new(0);
|
|
let keymap = Keymap::new_from_names(
|
|
&context,
|
|
names.rules,
|
|
names.model,
|
|
names.layout,
|
|
names.variant,
|
|
names.options,
|
|
XKB_KEYMAP_FORMAT_TEXT_V1,
|
|
)
|
|
.ok_or_else(|| eyre!("Keymap is not valid"))?;
|
|
|
|
PanelItem::keyboard_set_keymap_flex(node, &keymap)
|
|
}
|
|
fn keyboard_set_keymap_flex(node: &Node, keymap: &Keymap) -> Result<()> {
|
|
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
debug!("Keyboard set keymap");
|
|
|
|
panel_item.seat_data.set_keymap(
|
|
keymap,
|
|
match &panel_item.backend {
|
|
Backend::Wayland(w) => w.input_surfaces(),
|
|
#[cfg(feature = "xwayland")]
|
|
Backend::X11(_) => panel_item
|
|
.toplevel_wl_surface()
|
|
.map(|s| vec![s])
|
|
.unwrap_or_default(),
|
|
},
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn keyboard_key_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(data)?;
|
|
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
|
|
debug!(key, state, "Set keyboard key state");
|
|
|
|
panel_item
|
|
.seat_data
|
|
.keyboard_event(&wl_surface, KeyboardEvent::Key { key, state });
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn configure_toplevel_flex(
|
|
node: &Node,
|
|
_calling_client: Arc<Client>,
|
|
data: &[u8],
|
|
) -> Result<()> {
|
|
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ConfigureToplevelInfo {
|
|
size: Option<Vector2<u32>>,
|
|
states: Vec<u32>,
|
|
bounds: Option<Vector2<u32>>,
|
|
}
|
|
let info: ConfigureToplevelInfo = deserialize(data)?;
|
|
|
|
match &panel_item.backend {
|
|
Backend::Wayland(w) => w.configure_toplevel(info.size, info.states, info.bounds),
|
|
#[cfg(feature = "xwayland")]
|
|
Backend::X11(x) => x.configure_toplevel(info.size, info.states)?,
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn set_toplevel_capabilities_flex(
|
|
node: &Node,
|
|
_calling_client: Arc<Client>,
|
|
data: &[u8],
|
|
) -> Result<()> {
|
|
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
let Some(core_surface) = panel_item.core_surface() else { return Ok(()) };
|
|
if let Backend::Wayland(w) = &panel_item.backend {
|
|
let Some(toplevel) = w.toplevel() else {return Ok(())};
|
|
if toplevel.version() < EVT_WM_CAPABILITIES_SINCE {
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
let capabilities: Vec<u8> = deserialize(data)?;
|
|
debug!("Set toplevel capabilities");
|
|
match &panel_item.backend {
|
|
Backend::Wayland(w) => w.set_toplevel_capabilities(capabilities),
|
|
_ => (),
|
|
}
|
|
core_surface.flush_clients();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn commit_toplevel(&self) {
|
|
debug!("Commit toplevel");
|
|
let Some(node) = self.node.upgrade() else { return };
|
|
let Ok(data) = (match &self.backend {
|
|
Backend::Wayland(w) => w.serialize_toplevel(),
|
|
#[cfg(feature = "xwayland")]
|
|
Backend::X11(x) => x.serialize_toplevel(),
|
|
}) else {return};
|
|
let _ = node.send_remote_signal("commit_toplevel", &data);
|
|
}
|
|
|
|
pub fn recommend_toplevel_state(&self, state: RecommendedState) {
|
|
let Some(node) = self.node.upgrade() else { return };
|
|
let data = serialize(state).unwrap();
|
|
debug!(?state, "Recommend toplevel state");
|
|
|
|
let _ = node.send_remote_signal("recommend_toplevel_state", &data);
|
|
}
|
|
|
|
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
|
|
let Some(node) = self.node.upgrade() else { return };
|
|
|
|
let _ = node.send_remote_signal("grab_keyboard", &serialize(sid).unwrap());
|
|
}
|
|
|
|
pub fn on_drop(&self) {
|
|
let Some(toplevel) = self.toplevel_wl_surface() else {return};
|
|
self.seat_data.drop_surface(&toplevel);
|
|
|
|
debug!("Dropped panel item gracefully");
|
|
}
|
|
}
|
|
impl ItemSpecialization for PanelItem {
|
|
fn serialize_start_data(&self, id: &str) -> Option<Vec<u8>> {
|
|
match &self.backend {
|
|
Backend::Wayland(w) => {
|
|
let toplevel = w.toplevel()?;
|
|
let toplevel_state = ToplevelData::get(&toplevel);
|
|
let toplevel_state = toplevel_state.lock().clone();
|
|
|
|
let pointer_grab = self.pointer_grab.lock().clone();
|
|
let keyboard_grab = self.keyboard_grab.lock().clone();
|
|
|
|
serialize((
|
|
id,
|
|
(
|
|
w.cursor_data(),
|
|
toplevel_state,
|
|
w.popups_data(),
|
|
pointer_grab,
|
|
keyboard_grab,
|
|
),
|
|
))
|
|
.ok()
|
|
}
|
|
#[cfg(feature = "xwayland")]
|
|
Backend::X11(x) => {
|
|
let size = (
|
|
x.toplevel.geometry().size.w as u32,
|
|
x.toplevel.geometry().size.h as u32,
|
|
);
|
|
let toplevel_state = (
|
|
None::<String>,
|
|
x.toplevel.title(),
|
|
None::<String>,
|
|
(
|
|
x.toplevel.geometry().size.w as u32,
|
|
x.toplevel.geometry().size.h as u32,
|
|
),
|
|
x.toplevel.min_size().map(|s| (s.w as u32, s.h as u32)),
|
|
x.toplevel.max_size().map(|s| (s.w as u32, s.w as u32)),
|
|
((0_i32, 0_i32), size),
|
|
vec![0_u32; 0],
|
|
);
|
|
let info = (
|
|
None::<(Vector2<u32>, Vector2<i32>)>,
|
|
toplevel_state,
|
|
Vec::<PopupData>::new(),
|
|
None::<SurfaceID>,
|
|
None::<SurfaceID>,
|
|
);
|
|
serialize((id, info)).ok()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
impl Drop for PanelItem {
|
|
fn drop(&mut self) {
|
|
// Dropped panel item, basically just a debug breakpoint place
|
|
}
|
|
}
|