diff --git a/build/wire.rs b/build/wire.rs index 4700d9c1..96b9ca8a 100644 --- a/build/wire.rs +++ b/build/wire.rs @@ -663,11 +663,8 @@ fn write_message(f: &mut W, obj: &BStr, message: &Message) -> Result<( writeln!(f, " fn id(&self) -> ObjectId {{")?; writeln!(f, " self.self_id.into()")?; writeln!(f, " }}")?; - writeln!( - f, - " fn interface(&self) -> crate::object::Interface {{" - )?; - writeln!(f, " crate::object::Interface::{}", obj)?; + writeln!(f, " fn interface(&self) -> Interface {{")?; + writeln!(f, " {}", obj)?; writeln!(f, " }}")?; writeln!(f, " }}")?; Ok(()) @@ -681,6 +678,12 @@ fn write_file(f: &mut W, file: &DirEntry) -> Result<()> { let camel_obj_name = to_camel(obj_name); writeln!(f)?; writeln!(f, "id!({}Id);", camel_obj_name)?; + writeln!(f)?; + writeln!( + f, + "pub const {}: Interface = Interface(\"{}\");", + camel_obj_name, obj_name + )?; let contents = std::fs::read(file.path())?; let messages = parse_messages(&contents)?; if messages.is_empty() { @@ -703,7 +706,7 @@ pub fn main() -> Result<()> { writeln!(f, "use bstr::BStr;")?; writeln!(f, "use crate::fixed::Fixed;")?; writeln!(f, "use crate::client::{{EventFormatter, RequestParser}};")?; - writeln!(f, "use crate::object::ObjectId;")?; + writeln!(f, "use crate::object::{{ObjectId, Interface}};")?; writeln!( f, "use crate::utils::buffd::{{MsgFormatter, MsgParser, MsgParserError}};" diff --git a/src/client/objects.rs b/src/client/objects.rs index 99c830ee..83c32234 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -3,6 +3,7 @@ use crate::ifs::ipc::wl_data_source::WlDataSource; use crate::ifs::ipc::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1; use crate::ifs::wl_buffer::WlBuffer; use crate::ifs::wl_display::WlDisplay; +use crate::ifs::wl_output::WlOutput; use crate::ifs::wl_region::WlRegion; use crate::ifs::wl_registry::WlRegistry; use crate::ifs::wl_seat::WlSeat; @@ -16,8 +17,8 @@ use crate::tree::Node; use crate::utils::clonecell::CloneCell; use crate::utils::copyhashmap::CopyHashMap; use crate::wire::{ - WlBufferId, WlDataSourceId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, XdgPositionerId, - XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwpPrimarySelectionSourceV1Id, + WlBufferId, WlDataSourceId, WlOutputId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, + XdgPositionerId, XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwpPrimarySelectionSourceV1Id, }; use ahash::AHashMap; use std::cell::{RefCell, RefMut}; @@ -29,6 +30,7 @@ pub struct Objects { pub display: CloneCell>>, registry: CopyHashMap>, registries: CopyHashMap>, + pub outputs: CopyHashMap>, pub surfaces: CopyHashMap>, pub xdg_surfaces: CopyHashMap>, pub xdg_toplevel: CopyHashMap>, @@ -52,6 +54,7 @@ impl Objects { display: CloneCell::new(None), registry: Default::default(), registries: Default::default(), + outputs: Default::default(), surfaces: Default::default(), xdg_surfaces: Default::default(), xdg_toplevel: Default::default(), @@ -83,6 +86,7 @@ impl Objects { } self.display.set(None); self.registries.clear(); + self.outputs.clear(); self.surfaces.clear(); self.xdg_surfaces.clear(); self.wl_data_source.clear(); diff --git a/src/globals.rs b/src/globals.rs index c08e656d..29b48848 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -6,6 +6,7 @@ use crate::ifs::wl_drm::WlDrmGlobal; use crate::ifs::wl_output::WlOutputGlobal; use crate::ifs::wl_registry::WlRegistry; use crate::ifs::wl_seat::WlSeatGlobal; +use crate::ifs::zwlr_layer_shell_v1::ZwlrLayerShellV1Global; use crate::object::{Interface, ObjectId}; use crate::utils::copyhashmap::CopyHashMap; use crate::{ @@ -18,6 +19,7 @@ use std::error::Error; use std::fmt::{Display, Formatter}; use std::rc::Rc; use thiserror::Error; +use crate::ifs::zxdg_output_manager_v1::ZxdgOutputManagerV1Global; #[derive(Debug, Error)] pub enum GlobalsError { @@ -103,6 +105,8 @@ impl Globals { add_singleton!(ZxdgDecorationManagerV1Global); add_singleton!(OrgKdeKwinServerDecorationManagerGlobal); add_singleton!(ZwpPrimarySelectionDeviceManagerV1Global); + add_singleton!(ZwlrLayerShellV1Global); + add_singleton!(ZxdgOutputManagerV1Global); slf } diff --git a/src/ifs/mod.rs b/src/ifs/mod.rs index fd20959b..80317384 100644 --- a/src/ifs/mod.rs +++ b/src/ifs/mod.rs @@ -16,7 +16,10 @@ pub mod wl_subcompositor; pub mod wl_surface; pub mod xdg_positioner; pub mod xdg_wm_base; +pub mod zwlr_layer_shell_v1; pub mod zwp_linux_buffer_params_v1; pub mod zwp_linux_dmabuf_v1; pub mod zxdg_decoration_manager_v1; pub mod zxdg_toplevel_decoration_v1; +pub mod zxdg_output_manager_v1; +pub mod zxdg_output_v1; diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index e1c1c375..3816b5f3 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -6,12 +6,17 @@ use crate::object::Object; use crate::utils::buffd::MsgParser; use crate::utils::buffd::MsgParserError; use crate::wire::wl_output::*; -use crate::wire::WlOutputId; +use crate::wire::{WlOutputId, ZxdgOutputV1Id}; use ahash::AHashMap; use std::cell::{Cell, RefCell}; use std::collections::hash_map::Entry; use std::rc::Rc; use thiserror::Error; +use crate::CloneCell; +use crate::ifs::zxdg_output_v1::ZxdgOutputV1; +use crate::rect::Rect; +use crate::tree::OutputNode; +use crate::utils::copyhashmap::CopyHashMap; const SP_UNKNOWN: i32 = 0; #[allow(dead_code)] @@ -48,10 +53,8 @@ const MODE_PREFERRED: u32 = 2; pub struct WlOutputGlobal { name: GlobalName, output: Rc, - pub x: Cell, - pub y: Cell, - width: Cell, - height: Cell, + pos: Cell, + pub node: CloneCell>>, pub bindings: RefCell>>>, } @@ -60,23 +63,27 @@ impl WlOutputGlobal { Self { name, output: output.clone(), - x: Cell::new(0), - y: Cell::new(0), - width: Cell::new(output.width()), - height: Cell::new(output.height()), + pos: Cell::new(Rect::new_sized(0, 0, output.width(), output.height()).unwrap()), + node: Default::default(), bindings: Default::default(), } } + pub fn position(&self) -> Rect { + self.pos.get() + } + pub fn update_properties(&self) { let width = self.output.width(); let height = self.output.height(); - let mut changed = false; - changed |= self.width.replace(width) != width; - changed |= self.height.replace(height) != height; + let pos = self.pos.get(); + let old_width = pos.width(); + let old_height = pos.height(); + let changed = old_width != width || old_height != height; if changed { + self.pos.set(Rect::new_sized(pos.x1(), pos.y1(), width, height).unwrap()); let bindings = self.bindings.borrow_mut(); for binding in bindings.values() { for binding in binding.values() { @@ -85,6 +92,10 @@ impl WlOutputGlobal { binding.send_scale(); binding.send_done(); binding.client.flush(); + let xdg = binding.xdg_outputs.lock(); + for xdg in xdg.values() { + xdg.send_updates(); + } } } } @@ -99,6 +110,7 @@ impl WlOutputGlobal { let obj = Rc::new(WlOutput { global: self.clone(), id, + xdg_outputs: Default::default(), client: client.clone(), version, tracker: Default::default(), @@ -141,10 +153,11 @@ impl Global for WlOutputGlobal { dedicated_add_global!(WlOutputGlobal, outputs); pub struct WlOutput { - global: Rc, + pub global: Rc, pub id: WlOutputId, + pub xdg_outputs: CopyHashMap>, client: Rc, - version: u32, + pub version: u32, tracker: Tracker, } @@ -153,12 +166,13 @@ pub const SEND_SCALE_SINCE: u32 = 2; impl WlOutput { fn send_geometry(&self) { + let pos = self.global.pos.get(); let event = Geometry { self_id: self.id, - x: 0, - y: 0, - physical_width: self.global.width.get() as _, - physical_height: self.global.height.get() as _, + x: pos.x1(), + y: pos.y1(), + physical_width: pos.width(), + physical_height: pos.height(), subpixel: SP_UNKNOWN, make: "i4", model: "i4", @@ -168,11 +182,12 @@ impl WlOutput { } fn send_mode(&self) { + let pos = self.global.pos.get(); let event = Mode { self_id: self.id, flags: MODE_CURRENT, - width: self.global.width.get() as _, - height: self.global.height.get() as _, + width: pos.width(), + height: pos.height(), refresh: 60_000_000, }; self.client.event(event); @@ -186,7 +201,7 @@ impl WlOutput { self.client.event(event); } - fn send_done(&self) { + pub fn send_done(&self) { let event = Done { self_id: self.id }; self.client.event(event); } @@ -202,6 +217,7 @@ impl WlOutput { fn release(&self, parser: MsgParser<'_, '_>) -> Result<(), ReleaseError> { let _req: Release = self.client.parse(self, parser)?; + self.xdg_outputs.clear(); self.remove_binding(); self.client.remove_obj(self)?; Ok(()) @@ -224,11 +240,12 @@ impl Object for WlOutput { } fn break_loops(&self) { + self.xdg_outputs.clear(); self.remove_binding(); } } -simple_add_obj!(WlOutput); +dedicated_add_obj!(WlOutput, WlOutputId, outputs); #[derive(Debug, Error)] pub enum WlOutputError { diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 9a20ff40..0ebe9867 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -3,6 +3,7 @@ mod pointer_owner; pub mod wl_keyboard; pub mod wl_pointer; pub mod wl_touch; +mod kb_owner; use crate::async_engine::SpawnedFuture; use crate::client::{Client, ClientError, ClientId}; @@ -43,10 +44,12 @@ use i4config::Direction; use std::cell::{Cell, RefCell}; use std::collections::hash_map::Entry; use std::mem; -use std::ops::DerefMut; +use std::ops::{Deref, DerefMut}; use std::rc::Rc; use thiserror::Error; use uapi::{c, Errno, OwnedFd}; +use crate::ifs::wl_output::{WlOutputGlobal}; +use crate::ifs::wl_seat::kb_owner::KbOwnerHolder; const POINTER: u32 = 1; const KEYBOARD: u32 = 2; @@ -112,6 +115,7 @@ pub struct WlSeatGlobal { selection: CloneCell>>, primary_selection: CloneCell>>, pointer_owner: PointerOwnerHolder, + kb_owner: KbOwnerHolder, dropped_dnd: RefCell>, shortcuts: CopyHashMap<(u32, u32), Modifiers>, queue_link: Cell>>>, @@ -146,6 +150,7 @@ impl WlSeatGlobal { selection: Default::default(), primary_selection: Default::default(), pointer_owner: Default::default(), + kb_owner: Default::default(), dropped_dnd: RefCell::new(None), shortcuts: Default::default(), queue_link: Cell::new(None), @@ -162,6 +167,17 @@ impl WlSeatGlobal { slf } + pub fn get_output(&self) -> Option> { + let ps = self.pointer_stack.borrow_mut(); + for node in ps.deref() { + if node.is_output() { + let on = node.clone().into_output().unwrap(); + return Some(on.global.clone()); + } + } + None + } + pub fn mark_last_active(self: &Rc) { self.queue_link .set(Some(self.state.seat_queue.add_last(self.clone()))); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 9421faeb..8f2d484b 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -75,6 +75,22 @@ impl NodeSeatState { self.kb_foci.len() > 0 } + pub fn release_kb_grab(&self) { + for (_, seat) in &self.kb_foci { + seat.ungrab_kb(); + } + } + + pub fn release_kb_focus(&self) { + while let Some((_, seat)) = self.kb_foci.pop() { + seat.ungrab_kb(); + seat.keyboard_node.set(seat.state.root.clone()); + if let Some(tl) = seat.toplevel_focus_history.last() { + seat.focus_xdg_surface(&tl.xdg); + } + } + } + pub fn destroy_node(&self, node: &dyn Node) { while let Some((_, seat)) = self.grabs.pop() { seat.pointer_owner.revert_to_default(&seat); @@ -94,12 +110,7 @@ impl NodeSeatState { } seat.state.tree_changed(); } - while let Some((_, seat)) = self.kb_foci.pop() { - seat.keyboard_node.set(seat.state.root.clone()); - if let Some(tl) = seat.toplevel_focus_history.last() { - seat.focus_xdg_surface(&tl.xdg); - } - } + self.release_kb_focus(); } } @@ -124,8 +135,9 @@ impl WlSeatGlobal { Some(o) => o, _ => return, }; - x += Fixed::from_int(output.x.get()); - y += Fixed::from_int(output.y.get()); + let pos = output.position(); + x += Fixed::from_int(pos.x1()); + y += Fixed::from_int(pos.y1()); self.set_new_position(x, y); } @@ -216,21 +228,16 @@ impl WlSeatGlobal { self.focus_node(xdg.focus_surface(self)); } - pub fn focus_node(self: &Rc, node: Rc) { - let old = self.keyboard_node.get(); - if old.id() == node.id() { - return; - } - old.unfocus(self); - if old.seat_state().unfocus(self) { - old.active_changed(false); - } + fn ungrab_kb(self: &Rc) { + self.kb_owner.ungrab(self); + } - if node.seat_state().focus(self) { - node.active_changed(true); - } - node.clone().focus(self); - self.keyboard_node.set(node.clone()); + pub fn grab(self: &Rc, node: Rc) { + self.kb_owner.grab(self, node); + } + + pub fn focus_node(self: &Rc, node: Rc) { + self.kb_owner.set_kb_node(self, node); } fn offer_selection( @@ -380,7 +387,7 @@ impl WlSeatGlobal { let serial = self.serial.fetch_add(1); self.surface_pointer_event(0, surface, |p| p.send_button(serial, 0, button, state)); self.surface_pointer_frame(surface); - if pressed && surface.belongs_to_toplevel() { + if pressed && surface.accepts_kb_focus() { self.focus_node(surface.clone()); } } diff --git a/src/ifs/wl_seat/kb_owner.rs b/src/ifs/wl_seat/kb_owner.rs new file mode 100644 index 00000000..547de118 --- /dev/null +++ b/src/ifs/wl_seat/kb_owner.rs @@ -0,0 +1,85 @@ +use std::rc::Rc; +use crate::CloneCell; +use crate::ifs::wl_seat::WlSeatGlobal; +use crate::tree::Node; + +pub struct KbOwnerHolder { + default: Rc, + owner: CloneCell>, +} + +impl Default for KbOwnerHolder { + fn default() -> Self { + Self { + default: Rc::new(DefaultKbOwner), + owner: CloneCell::new(Rc::new(DefaultKbOwner)), + } + } +} + +impl KbOwnerHolder { + pub fn grab(&self, seat: &Rc, node: Rc) -> bool { + self.owner.get().grab(seat, node) + } + + pub fn ungrab(&self, seat: &Rc) { + self.owner.get().ungrab(seat) + } + + pub fn set_kb_node(&self, seat: &Rc, node: Rc) { + self.owner.get().set_kb_node(seat, node); + } +} + +struct DefaultKbOwner; + +struct GrabKbOwner; + +trait KbOwner { + fn grab(&self, seat: &Rc, node: Rc) -> bool; + fn ungrab(&self, seat: &Rc); + fn set_kb_node(&self, seat: &Rc, node: Rc); +} + +impl KbOwner for DefaultKbOwner { + fn grab(&self, seat: &Rc, node: Rc) -> bool { + self.set_kb_node(seat, node); + seat.kb_owner.owner.set(Rc::new(GrabKbOwner)); + true + } + + fn ungrab(&self, _seat: &Rc) { + // nothing + } + + fn set_kb_node(&self, seat: &Rc, node: Rc) { + let old = seat.keyboard_node.get(); + if old.id() == node.id() { + return; + } + old.unfocus(seat); + if old.seat_state().unfocus(seat) { + old.active_changed(false); + } + + if node.seat_state().focus(seat) { + node.active_changed(true); + } + node.clone().focus(seat); + seat.keyboard_node.set(node.clone()); + } +} + +impl KbOwner for GrabKbOwner { + fn grab(&self, _seat: &Rc, _node: Rc) -> bool { + false + } + + fn ungrab(&self, seat: &Rc) { + seat.kb_owner.owner.set(seat.kb_owner.default.clone()); + } + + fn set_kb_node(&self, _seat: &Rc, _node: Rc) { + // nothing + } +} diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index 6e2d62a6..de3354fe 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -147,9 +147,11 @@ impl PointerOwner for DefaultPointerOwner { } if (stack.len(), found_tree.len()) == (divergence, divergence) { if let Some(node) = found_tree.last() { - node.node - .clone() - .pointer_motion(seat, x.apply_fract(node.x), y.apply_fract(node.y)); + node.node.clone().pointer_motion( + seat, + x.apply_fract(node.x), + y.apply_fract(node.y), + ); } } else { if let Some(last) = stack.last() { @@ -161,16 +163,20 @@ impl PointerOwner for DefaultPointerOwner { } if found_tree.len() == divergence { if let Some(node) = found_tree.last() { - node.node - .clone() - .pointer_motion(seat, x.apply_fract(node.x), y.apply_fract(node.y)); + node.node.clone().pointer_motion( + seat, + x.apply_fract(node.x), + y.apply_fract(node.y), + ); } } else { for new in found_tree.drain(divergence..) { new.node.seat_state().enter(seat); - new.node - .clone() - .pointer_enter(seat, x.apply_fract(new.x), y.apply_fract(new.y)); + new.node.clone().pointer_enter( + seat, + x.apply_fract(new.x), + y.apply_fract(new.y), + ); stack.push(new.node); } } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index e094dca4..9b6806b4 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1,6 +1,7 @@ pub mod cursor; pub mod wl_subsurface; pub mod xdg_surface; +pub mod zwlr_layer_surface_v1; use crate::backend::{KeyState, ScrollAxis}; use crate::client::{Client, ClientError, RequestParser}; @@ -34,6 +35,7 @@ use std::mem; use std::ops::{Deref, DerefMut}; use std::rc::Rc; use thiserror::Error; +use crate::ifs::wl_surface::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1Error; #[allow(dead_code)] const INVALID_SCALE: u32 = 0; @@ -49,6 +51,7 @@ pub enum SurfaceRole { XdgSurface, Cursor, DndIcon, + ZwlrLayerSurface, } impl SurfaceRole { @@ -59,6 +62,7 @@ impl SurfaceRole { SurfaceRole::XdgSurface => "xdg_surface", SurfaceRole::Cursor => "cursor", SurfaceRole::DndIcon => "dnd_icon", + SurfaceRole::ZwlrLayerSurface => "zwlr_layer_surface", } } } @@ -111,7 +115,7 @@ trait SurfaceExt { Ok(CommitAction::ContinueCommit) } - fn post_commit(&self) { + fn post_commit(self: Rc) { // nothing } @@ -134,6 +138,10 @@ trait SurfaceExt { fn into_subsurface(self: Rc) -> Option> { None } + + fn accepts_kb_focus(&self) -> bool { + true + } } pub struct NoneSurfaceExt; @@ -222,11 +230,11 @@ impl WlSurface { Ok(cursor) } - pub fn belongs_to_toplevel(&self) -> bool { + pub fn accepts_kb_focus(&self) -> bool { if let Some(xdg) = self.xdg.get() { return xdg.role() == XdgSurfaceRole::XdgToplevel; } - false + self.ext.get().accepts_kb_focus() } fn send_enter(&self, output: WlOutputId) { @@ -747,6 +755,8 @@ pub enum WlSurfaceError { #[error(transparent)] ClientError(Box), #[error(transparent)] + ZwlrLayerSurfaceV1Error(Box), + #[error(transparent)] XdgSurfaceError(Box), #[error("Could not process `destroy` request")] DestroyError(#[source] Box), @@ -784,13 +794,10 @@ efrom!(WlSurfaceError, FrameError); efrom!(WlSurfaceError, SetOpaqueRegionError); efrom!(WlSurfaceError, SetInputRegionError); efrom!(WlSurfaceError, CommitError); -efrom!( - WlSurfaceError, - SetBufferTransformError, - SetBufferTransformError -); +efrom!(WlSurfaceError, SetBufferTransformError); efrom!(WlSurfaceError, SetBufferScaleError); efrom!(WlSurfaceError, DamageBufferError); +efrom!(WlSurfaceError, ZwlrLayerSurfaceV1Error); #[derive(Debug, Error)] pub enum DestroyError { diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index a412537e..e1826076 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -263,9 +263,8 @@ impl SurfaceExt for WlSubsurface { Ok(CommitAction::ContinueCommit) } - fn post_commit(&self) { + fn post_commit(self: Rc) { if let Some(v) = self.pending.node.take() { - log::info!("post commit"); v.pending.set(false); self.node.borrow_mut().replace(v); } diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index eb2ec6ef..a3381abd 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -443,7 +443,7 @@ impl SurfaceExt for XdgSurface { Ok(CommitAction::ContinueCommit) } - fn post_commit(&self) { + fn post_commit(self: Rc) { if let Some(ext) = self.ext.get() { ext.post_commit(); } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index a9c954f5..7c847f5c 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -27,6 +27,7 @@ use std::fmt::{Debug, Formatter}; use std::mem; use std::ops::Deref; use std::rc::Rc; +use backtrace::Backtrace; use thiserror::Error; #[derive(Copy, Clone, Debug, FromPrimitive)] diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs new file mode 100644 index 00000000..25e9dad8 --- /dev/null +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -0,0 +1,533 @@ +use std::cell::Cell; +use std::ops::Deref; +use crate::client::{Client, ClientError, ClientId}; +use crate::ifs::wl_output::{WlOutput, WlOutputGlobal}; +use crate::ifs::wl_surface::{CommitAction, CommitContext, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError}; +use crate::ifs::zwlr_layer_shell_v1::{OVERLAY, ZwlrLayerShellV1}; +use crate::leaks::Tracker; +use crate::object::Object; +use crate::utils::buffd::MsgParser; +use crate::utils::buffd::MsgParserError; +use crate::wire::zwlr_layer_surface_v1::*; +use crate::wire::{WlSurfaceId, ZwlrLayerSurfaceV1Id}; +use std::rc::Rc; +use thiserror::Error; +use i4config::Direction; +use crate::ifs::wl_surface::wl_subsurface::WlSubsurface; +use crate::{CloneCell, NumCell}; +use crate::backend::{KeyState, ScrollAxis}; +use crate::fixed::Fixed; +use crate::ifs::wl_seat::{Dnd, NodeSeatState, WlSeatGlobal}; +use crate::rect::Rect; +use crate::render::Renderer; +use crate::tree::{ContainerNode, ContainerSplit, FindTreeResult, FloatNode, FoundNode, Node, NodeId, OutputNode, WorkspaceNode}; +use crate::tree::walker::NodeVisitor; +use crate::utils::bitflags::BitflagsExt; +use crate::utils::linkedlist::LinkedNode; + +const KI_NONE: u32 = 0; +#[allow(dead_code)] +const KI_EXCLUSIVE: u32 = 1; +const KI_ON_DEMAND: u32 = 2; + +const TOP: u32 = 1; +const BOTTOM: u32 = 2; +const LEFT: u32 = 4; +const RIGHT: u32 = 8; + +tree_id!(ZwlrLayerSurfaceV1NodeId); +pub struct ZwlrLayerSurfaceV1 { + pub id: ZwlrLayerSurfaceV1Id, + node_id: ZwlrLayerSurfaceV1NodeId, + pub shell: Rc, + pub client: Rc, + pub surface: Rc, + pub output: Rc, + pub namespace: String, + pub tracker: Tracker, + pos: Cell, + mapped: Cell, + layer: Cell, + pending: Pending, + requested_serial: NumCell, + acked_serial: Cell>, + size: Cell<(i32, i32)>, + anchor: Cell, + exclusive_zone: Cell, + margin: Cell<(i32, i32, i32, i32)>, + keyboard_interactivity: Cell, + link: Cell>>>, + seat_state: NodeSeatState, +} + +#[derive(Default)] +struct Pending { + size: Cell>, + anchor: Cell>, + exclusive_zone: Cell>, + margin: Cell>, + keyboard_interactivity: Cell>, + layer: Cell>, +} + +impl ZwlrLayerSurfaceV1 { + pub fn new( + id: ZwlrLayerSurfaceV1Id, + shell: &Rc, + surface: &Rc, + output: &Rc, + layer: u32, + namespace: &str, + ) -> Self { + Self { + id, + node_id: shell.client.state.node_ids.next(), + shell: shell.clone(), + client: shell.client.clone(), + surface: surface.clone(), + output: output.clone(), + namespace: namespace.to_string(), + tracker: Default::default(), + pos: Cell::new(Default::default()), + mapped: Cell::new(false), + layer: Cell::new(layer), + pending: Default::default(), + requested_serial: Default::default(), + acked_serial: Cell::new(None), + size: Cell::new((0, 0)), + anchor: Cell::new(0), + exclusive_zone: Cell::new(0), + margin: Cell::new((0, 0, 0, 0)), + keyboard_interactivity: Cell::new(0), + link: Cell::new(None), + seat_state: Default::default() + } + } + + pub fn install(self: &Rc) -> Result<(), ZwlrLayerSurfaceV1Error> { + self.surface.set_role(SurfaceRole::ZwlrLayerSurface)?; + if self.surface.ext.get().is_some() { + return Err(ZwlrLayerSurfaceV1Error::AlreadyAttached(self.surface.id)); + } + self.surface.ext.set(self.clone()); + Ok(()) + } + + fn send_configure(&self, serial: u32, width: u32, height: u32) { + self.client.event(Configure { + self_id: self.id, + serial, + width, + height, + }); + } + + #[allow(dead_code)] + fn send_closed(&self) { + self.client.event(Closed { self_id: self.id }); + } + + fn set_size(&self, parser: MsgParser<'_, '_>) -> Result<(), SetSizeError> { + let req: SetSize = self.client.parse(self, parser)?; + if req.width > u16::MAX as u32 || req.height > u16::MAX as u32 { + return Err(SetSizeError::ExcessiveSize); + } + self.pending.size.set(Some((req.width as _, req.height as _))); + Ok(()) + } + + fn set_anchor(&self, parser: MsgParser<'_, '_>) -> Result<(), SetAnchorError> { + let req: SetAnchor = self.client.parse(self, parser)?; + if req.anchor & !(LEFT | RIGHT | TOP | BOTTOM) != 0 { + return Err(SetAnchorError::UnknownAnchor(req.anchor)); + } + self.pending.anchor.set(Some(req.anchor)); + Ok(()) + } + + fn set_exclusive_zone(&self, parser: MsgParser<'_, '_>) -> Result<(), SetExclusiveZoneError> { + let req: SetExclusiveZone = self.client.parse(self, parser)?; + self.pending.exclusive_zone.set(Some(req.zone)); + Ok(()) + } + + fn set_margin(&self, parser: MsgParser<'_, '_>) -> Result<(), SetMarginError> { + let req: SetMargin = self.client.parse(self, parser)?; + self.pending.margin.set(Some((req.top, req.right, req.bottom, req.left))); + Ok(()) + } + + fn set_keyboard_interactivity(&self, parser: MsgParser<'_, '_>) -> Result<(), SetKeyboardInteractivityError> { + let req: SetKeyboardInteractivity = self.client.parse(self, parser)?; + if req.keyboard_interactivity > KI_ON_DEMAND { + return Err(SetKeyboardInteractivityError::UnknownKi(req.keyboard_interactivity)); + } + self.pending.keyboard_interactivity.set(Some(req.keyboard_interactivity)); + Ok(()) + } + + fn get_popup(&self, parser: MsgParser<'_, '_>) -> Result<(), GetPopupError> { + let _req: GetPopup = self.client.parse(self, parser)?; + Ok(()) + } + + fn ack_configure(&self, parser: MsgParser<'_, '_>) -> Result<(), AckConfigureError> { + let req: AckConfigure = self.client.parse(self, parser)?; + self.acked_serial.set(Some(req.serial)); + Ok(()) + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), DestroyError> { + let _req: Destroy = self.client.parse(self, parser)?; + self.destroy_node(true); + self.client.remove_obj(self)?; + self.surface.unset_ext(); + Ok(()) + } + + fn set_layer(&self, parser: MsgParser<'_, '_>) -> Result<(), SetLayerError> { + let req: SetLayer = self.client.parse(self, parser)?; + if req.layer > OVERLAY { + return Err(SetLayerError::UnknownLayer(req.layer)); + } + self.pending.layer.set(Some(req.layer)); + Ok(()) + } + + fn pre_commit(&self) -> Result<(), ZwlrLayerSurfaceV1Error> { + let mut send_configure = false; + if let Some(size) = self.pending.size.take() { + self.size.set(size); + } + if let Some(anchor) = self.pending.anchor.take() { + self.anchor.set(anchor); + } + if let Some(ez) = self.pending.exclusive_zone.take() { + self.exclusive_zone.set(ez); + } + if let Some(margin) = self.pending.margin.take() { + self.margin.set(margin); + } + if let Some(ki) = self.pending.keyboard_interactivity.take() { + self.keyboard_interactivity.set(ki); + } + if let Some(layer) = self.pending.layer.take() { + self.layer.set(layer); + } + { + let (mut width, mut height) = self.size.get(); + let anchor = self.anchor.get(); + if width == 0 { + if !anchor.contains(LEFT | RIGHT) { + return Err(ZwlrLayerSurfaceV1Error::WidthZero); + } + send_configure = true; + width = self.output.global.position().width(); + } + if height == 0 { + if !anchor.contains(TOP | BOTTOM) { + return Err(ZwlrLayerSurfaceV1Error::HeightZero); + } + send_configure = true; + height = self.output.global.position().height(); + } + self.size.set((width, height)); + } + if self.acked_serial.get().is_none() { + send_configure = true; + } + if send_configure { + let (width, height) = self.size.get(); + let serial = self.requested_serial.fetch_add(1) + 1; + self.send_configure(serial, width as _, height as _); + } + Ok(()) + } + + pub fn position(&self) -> Rect { + self.pos.get() + } + + fn compute_position(&self) { + let (width, height) = self.size.get(); + let mut anchor = self.anchor.get(); + if anchor == 0 { + anchor = LEFT | RIGHT | TOP | BOTTOM; + } + let opos = self.output.position.get(); + let mut x1 = opos.x1(); + let mut y1 = opos.y1(); + if anchor.contains(LEFT) { + if anchor.contains(RIGHT) { + x1 += (opos.width() - width) / 2; + } + } else if anchor.contains(RIGHT) { + x1 += opos.width() - width; + } + if anchor.contains(TOP) { + if anchor.contains(BOTTOM) { + y1 += (opos.height() - height) / 2; + } + } else if anchor.contains(BOTTOM) { + y1 += opos.height() - height; + } + self.pos.set(Rect::new_sized(x1, y1, width, height).unwrap()); + self.client.state.tree_changed(); + } +} + +impl SurfaceExt for ZwlrLayerSurfaceV1 { + fn pre_commit(self: Rc, _ctx: CommitContext) -> Result { + self.deref().pre_commit()?; + Ok(CommitAction::ContinueCommit) + } + + fn post_commit(self: Rc) { + let buffer = self.surface.buffer.get(); + if self.mapped.get() { + if buffer.is_none() { + self.destroy_node(true); + } else { + let pos = self.pos.get(); + let (width, height) = self.size.get(); + if width != pos.width() || height != pos.height() { + self.compute_position(); + } + } + } else if buffer.is_some() { + let layer = &self.output.layers[self.layer.get() as usize]; + self.link.set(Some(layer.add_last(self.clone()))); + self.mapped.set(true); + self.compute_position(); + } + if self.mapped.get() { + match self.keyboard_interactivity.get() { + KI_NONE => { + let was_active = self.surface.seat_state.is_active(); + self.surface.seat_state.release_kb_focus(); + if was_active { + self.surface.active_changed(false); + } + }, + KI_ON_DEMAND => self.surface.seat_state.release_kb_grab(), + KI_EXCLUSIVE => { + let seats = self.client.state.globals.seats.lock(); + for seat in seats.values() { + seat.grab(self.surface.clone()); + } + } + _ => unreachable!(), + } + } + } + + fn accepts_kb_focus(&self) -> bool { + self.keyboard_interactivity.get() != KI_NONE + } +} + +impl Node for ZwlrLayerSurfaceV1 { + fn id(&self) -> NodeId { + self.node_id.into() + } + + fn seat_state(&self) -> &NodeSeatState { + &self.seat_state + } + + fn destroy_node(&self, _detach: bool) { + self.link.set(None); + self.mapped.set(false); + self.surface.destroy_node(false); + self.seat_state.destroy_node(self); + } + + fn visit(self: Rc, visitor: &mut dyn NodeVisitor) { + visitor.visit_layer_surface(&self); + } + + fn visit_children(&self, visitor: &mut dyn NodeVisitor) { + self.surface.clone().visit(visitor); + } + + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { + renderer.render_layer_surface(self, x, y); + } + + fn absolute_position(&self) -> Rect { + self.pos.get() + } + + fn find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + tree.push(FoundNode { + node: self.surface.clone(), + x, + y + }); + self.surface.find_tree_at(x, y, tree) + } + + fn change_extents(self: Rc, _rect: &Rect) { + self.compute_position(); + } +} + +object_base! { + ZwlrLayerSurfaceV1, ZwlrLayerSurfaceV1Error; + + SET_SIZE => set_size, + SET_ANCHOR => set_anchor, + SET_EXCLUSIVE_ZONE => set_exclusive_zone, + SET_MARGIN => set_margin, + SET_KEYBOARD_INTERACTIVITY => set_keyboard_interactivity, + GET_POPUP => get_popup, + ACK_CONFIGURE => ack_configure, + DESTROY => destroy, + SET_LAYER => set_layer, +} + +impl Object for ZwlrLayerSurfaceV1 { + fn num_requests(&self) -> u32 { + let last_req = match self.shell.version { + 0..=1 => DESTROY, + _ => SET_LAYER, + }; + last_req + 1 + } + + fn break_loops(&self) { + self.destroy_node(true); + self.link.set(None); + } +} + +simple_add_obj!(ZwlrLayerSurfaceV1); + +#[derive(Debug, Error)] +pub enum ZwlrLayerSurfaceV1Error { + #[error("Could not process `set_size` request")] + SetSizeError(#[from] SetSizeError), + #[error("Could not process `set_anchor` request")] + SetAnchorError(#[from] SetAnchorError), + #[error("Could not process `set_exclusive_zone` request")] + SetExclusiveZoneError(#[from] SetExclusiveZoneError), + #[error("Could not process `set_margin` request")] + SetMarginError(#[from] SetMarginError), + #[error("Could not process `set_keyboard_interactivity` request")] + SetKeyboardInteractivityError(#[from] SetKeyboardInteractivityError), + #[error("Could not process `get_popup` request")] + GetPopupError(#[from] GetPopupError), + #[error("Could not process `ack_configure` request")] + AckConfigureError(#[from] AckConfigureError), + #[error("Could not process `destroy` request")] + DestroyError(#[from] DestroyError), + #[error("Could not process `set_layer` request")] + SetLayerError(#[from] SetLayerError), + #[error("Surface {0} cannot be turned into a zwlr_layer_surface because it already has an attached zwlr_layer_surface")] + AlreadyAttached(WlSurfaceId), + #[error("Width was set to 0 but anchor did not contain LEFT and RIGHT")] + WidthZero, + #[error("Height was set to 0 but anchor did not contain TOP and BOTTOM")] + HeightZero, + #[error(transparent)] + WlSurfaceError(Box), +} +efrom!(ZwlrLayerSurfaceV1Error, WlSurfaceError); + +#[derive(Debug, Error)] +pub enum SetSizeError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("Surface size must not be larger than 65535x65535")] + ExcessiveSize, +} +efrom!(SetSizeError, MsgParserError); +efrom!(SetSizeError, ClientError); + +#[derive(Debug, Error)] +pub enum SetAnchorError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("Unknown anchor {0}")] + UnknownAnchor(u32), +} +efrom!(SetAnchorError, MsgParserError); +efrom!(SetAnchorError, ClientError); + +#[derive(Debug, Error)] +pub enum SetExclusiveZoneError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(SetExclusiveZoneError, MsgParserError); +efrom!(SetExclusiveZoneError, ClientError); + +#[derive(Debug, Error)] +pub enum SetMarginError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(SetMarginError, MsgParserError); +efrom!(SetMarginError, ClientError); + +#[derive(Debug, Error)] +pub enum SetKeyboardInteractivityError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("Unknown keyboard interactivity {0}")] + UnknownKi(u32), +} +efrom!(SetKeyboardInteractivityError, MsgParserError); +efrom!(SetKeyboardInteractivityError, ClientError); + +#[derive(Debug, Error)] +pub enum GetPopupError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(GetPopupError, MsgParserError); +efrom!(GetPopupError, ClientError); + +#[derive(Debug, Error)] +pub enum AckConfigureError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(AckConfigureError, MsgParserError); +efrom!(AckConfigureError, ClientError); + +#[derive(Debug, Error)] +pub enum DestroyError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(DestroyError, MsgParserError); +efrom!(DestroyError, ClientError); + +#[derive(Debug, Error)] +pub enum SetLayerError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("Unknown layer {0}")] + UnknownLayer(u32), +} +efrom!(SetLayerError, MsgParserError); +efrom!(SetLayerError, ClientError); diff --git a/src/ifs/zwlr_layer_shell_v1.rs b/src/ifs/zwlr_layer_shell_v1.rs new file mode 100644 index 00000000..73410c56 --- /dev/null +++ b/src/ifs/zwlr_layer_shell_v1.rs @@ -0,0 +1,176 @@ +use crate::client::{Client, ClientError}; +use crate::globals::{Global, GlobalName}; +use crate::ifs::wl_surface::zwlr_layer_surface_v1::{ZwlrLayerSurfaceV1, ZwlrLayerSurfaceV1Error}; +use crate::leaks::Tracker; +use crate::object::Object; +use crate::utils::buffd::MsgParser; +use crate::utils::buffd::MsgParserError; +use crate::wire::zwlr_layer_shell_v1::*; +use crate::wire::ZwlrLayerShellV1Id; +use std::rc::Rc; +use thiserror::Error; + +#[allow(dead_code)] +pub const BACKGROUND: u32 = 0; +#[allow(dead_code)] +pub const BOTTOM: u32 = 1; +#[allow(dead_code)] +pub const TOP: u32 = 2; +pub const OVERLAY: u32 = 3; + +pub struct ZwlrLayerShellV1Global { + name: GlobalName, +} + +pub struct ZwlrLayerShellV1 { + pub id: ZwlrLayerShellV1Id, + pub client: Rc, + pub version: u32, + pub tracker: Tracker, +} + +impl ZwlrLayerShellV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ZwlrLayerShellV1Id, + client: &Rc, + version: u32, + ) -> Result<(), ZwlrLayerShellV1Error> { + let obj = Rc::new(ZwlrLayerShellV1 { + id, + client: client.clone(), + version, + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +impl ZwlrLayerShellV1 { + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), DestroyError> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_layer_surface( + self: &Rc, + parser: MsgParser<'_, '_>, + ) -> Result<(), GetLayerSurfaceError> { + let req: GetLayerSurface = self.client.parse(&**self, parser)?; + let surface = self.client.lookup(req.surface)?; + let output = 'get_output: { + if req.output.is_some() { + self.client.lookup(req.output)?.global.node.get().unwrap() + } else { + for seat in self.client.state.seat_queue.rev_iter() { + if let Some(output) = seat.get_output() { + break 'get_output output.node.get().unwrap(); + } + } + let outputs = self.client.state.outputs.lock(); + match outputs.values().next() { + Some(ou) => ou.node.get().unwrap(), + _ => return Err(GetLayerSurfaceError::NoOutputs), + } + } + }; + if req.layer > OVERLAY { + return Err(GetLayerSurfaceError::UnknownLayer(req.layer)); + } + let surface = Rc::new(ZwlrLayerSurfaceV1::new( + req.id, + self, + &surface, + &output, + req.layer, + req.namespace, + )); + track!(self.client, surface); + self.client.add_client_obj(&surface)?; + surface.install()?; + Ok(()) + } +} + +global_base!( + ZwlrLayerShellV1Global, + ZwlrLayerShellV1, + ZwlrLayerShellV1Error +); + +impl Global for ZwlrLayerShellV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 4 + } +} + +simple_add_global!(ZwlrLayerShellV1Global); + +object_base! { + ZwlrLayerShellV1, ZwlrLayerShellV1Error; + + GET_LAYER_SURFACE => get_layer_surface, + DESTROY => destroy, +} + +simple_add_obj!(ZwlrLayerShellV1); + +impl Object for ZwlrLayerShellV1 { + fn num_requests(&self) -> u32 { + let last_request = if self.version >= 3 { + DESTROY + } else { + GET_LAYER_SURFACE + }; + last_request + 1 + } +} + +#[derive(Debug, Error)] +pub enum ZwlrLayerShellV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Could not process a `destroy` request")] + DestroyError(#[from] DestroyError), + #[error("Could not process a `get_layer_surface` request")] + GetLayerSurfaceError(#[from] GetLayerSurfaceError), +} +efrom!(ZwlrLayerShellV1Error, ClientError); + +#[derive(Debug, Error)] +pub enum DestroyError { + #[error("Parsing failed")] + ParseError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(DestroyError, ParseError, MsgParserError); +efrom!(DestroyError, ClientError); + +#[derive(Debug, Error)] +pub enum GetLayerSurfaceError { + #[error("Parsing failed")] + ParseError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("Unknown layer {0}")] + UnknownLayer(u32), + #[error("There are no outputs")] + NoOutputs, + #[error(transparent)] + ZwlrLayerSurfaceV1Error(Box), +} +efrom!(GetLayerSurfaceError, ParseError, MsgParserError); +efrom!(GetLayerSurfaceError, ClientError); +efrom!(GetLayerSurfaceError, ZwlrLayerSurfaceV1Error); diff --git a/src/ifs/zxdg_output_manager_v1.rs b/src/ifs/zxdg_output_manager_v1.rs new file mode 100644 index 00000000..7a3cee9a --- /dev/null +++ b/src/ifs/zxdg_output_manager_v1.rs @@ -0,0 +1,137 @@ +use crate::client::{Client, ClientError}; +use crate::globals::{Global, GlobalName}; +use crate::leaks::Tracker; +use crate::object::Object; +use crate::utils::buffd::MsgParser; +use crate::utils::buffd::MsgParserError; +use crate::wire::zxdg_output_manager_v1::*; +use crate::wire::{ZxdgOutputManagerV1Id}; +use std::rc::Rc; +use thiserror::Error; +use crate::ifs::zxdg_output_v1::ZxdgOutputV1; + +pub struct ZxdgOutputManagerV1Global { + name: GlobalName, +} + +pub struct ZxdgOutputManagerV1 { + pub id: ZxdgOutputManagerV1Id, + pub client: Rc, + pub version: u32, + pub tracker: Tracker, +} + +impl ZxdgOutputManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ZxdgOutputManagerV1Id, + client: &Rc, + version: u32, + ) -> Result<(), ZxdgOutputManagerV1Error> { + let obj = Rc::new(ZxdgOutputManagerV1 { + id, + client: client.clone(), + version, + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +impl ZxdgOutputManagerV1 { + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), DestroyError> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_xdg_output( + self: &Rc, + parser: MsgParser<'_, '_>, + ) -> Result<(), GetXdgOutputError> { + let req: GetXdgOutput = self.client.parse(&**self, parser)?; + let output = self.client.lookup(req.output)?; + let xdg_output = Rc::new(ZxdgOutputV1 { + id: req.id, + version: self.version, + client: self.client.clone(), + output: output.clone(), + tracker: Default::default() + }); + track!(self.client, xdg_output); + self.client.add_client_obj(&xdg_output)?; + xdg_output.send_updates(); + output.xdg_outputs.set(req.id, xdg_output); + Ok(()) + } +} + +global_base!( + ZxdgOutputManagerV1Global, + ZxdgOutputManagerV1, + ZxdgOutputManagerV1Error +); + +impl Global for ZxdgOutputManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 3 + } +} + +simple_add_global!(ZxdgOutputManagerV1Global); + +object_base! { + ZxdgOutputManagerV1, ZxdgOutputManagerV1Error; + + DESTROY => destroy, + GET_XDG_OUTPUT => get_xdg_output, +} + +simple_add_obj!(ZxdgOutputManagerV1); + +impl Object for ZxdgOutputManagerV1 { + fn num_requests(&self) -> u32 { + GET_XDG_OUTPUT + 1 + } +} + +#[derive(Debug, Error)] +pub enum ZxdgOutputManagerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Could not process a `destroy` request")] + DestroyError(#[from] DestroyError), + #[error("Could not process a `get_xdg_output` request")] + GetXdgOutputError(#[from] GetXdgOutputError), +} +efrom!(ZxdgOutputManagerV1Error, ClientError); + +#[derive(Debug, Error)] +pub enum DestroyError { + #[error("Parsing failed")] + ParseError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(DestroyError, ParseError, MsgParserError); +efrom!(DestroyError, ClientError); + +#[derive(Debug, Error)] +pub enum GetXdgOutputError { + #[error("Parsing failed")] + ParseError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(GetXdgOutputError, ParseError, MsgParserError); +efrom!(GetXdgOutputError, ClientError); diff --git a/src/ifs/zxdg_output_v1.rs b/src/ifs/zxdg_output_v1.rs new file mode 100644 index 00000000..6bb2a3ab --- /dev/null +++ b/src/ifs/zxdg_output_v1.rs @@ -0,0 +1,107 @@ +use std::rc::Rc; +use thiserror::Error; +use crate::client::{Client, ClientError}; +use crate::ifs::wl_output::{SEND_DONE_SINCE, WlOutput}; +use crate::leaks::Tracker; +use crate::object::Object; +use crate::utils::buffd::{MsgParser, MsgParserError}; +use crate::wire::ZxdgOutputV1Id; +use crate::wire::zxdg_output_v1::*; + +pub const NAME_SINCE: u32 = 2; +pub const DESCRIPTION_SINCE: u32 = 2; +pub const NO_DONE_SINCE: u32 = 3; + +pub struct ZxdgOutputV1 { + pub id: ZxdgOutputV1Id, + pub version: u32, + pub client: Rc, + pub output: Rc, + pub tracker: Tracker, +} + +impl ZxdgOutputV1 { + pub fn send_logical_position(&self, x: i32, y: i32) { + self.client.event(LogicalPosition { + self_id: self.id, + x, + y, + }); + } + + pub fn send_logical_size(&self, width: i32, height: i32) { + self.client.event(LogicalSize { + self_id: self.id, + width, + height, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { + self_id: self.id, + }); + } + + pub fn send_name(&self, name: &str) { + self.client.event(Name { + self_id: self.id, + name, + }); + } + + pub fn send_description(&self, description: &str) { + self.client.event(Description { + self_id: self.id, + description, + }); + } + + pub fn send_updates(&self) { + let pos = self.output.global.position(); + self.send_logical_position(pos.x1(), pos.y1()); + self.send_logical_size(pos.width(), pos.height()); + if self.version >= NO_DONE_SINCE || self.output.version < SEND_DONE_SINCE { + self.output.send_done(); + } else { + self.send_done(); + } + } + + pub fn destroy(&self, msg: MsgParser) -> Result<(), DestroyError> { + let _req: Destroy = self.client.parse(self, msg)?; + self.output.xdg_outputs.remove(&self.id); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + ZxdgOutputV1, ZxdgOutputV1Error; + + DESTROY => destroy, +} + +impl Object for ZxdgOutputV1 { + fn num_requests(&self) -> u32 { + DESTROY + 1 + } +} + +simple_add_obj!(ZxdgOutputV1); + +#[derive(Debug, Error)] +pub enum ZxdgOutputV1Error { + #[error("Could not process a `destroy` request")] + DestroyError(#[from] DestroyError), +} + +#[derive(Debug, Error)] +pub enum DestroyError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(DestroyError, MsgParserError); +efrom!(DestroyError, ClientError); diff --git a/src/macros.rs b/src/macros.rs index 6c7c9dcd..7d6f6899 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -44,14 +44,14 @@ macro_rules! object_base { } fn interface(&self) -> crate::object::Interface { - crate::object::Interface::$oname + crate::wire::$oname } } impl From<$ename> for crate::client::ObjectError { fn from(v: $ename) -> Self { Self { - interface: crate::object::Interface::$oname, + interface: crate::wire::$oname, error: Box::new(v), } } @@ -79,14 +79,14 @@ macro_rules! global_base { } fn interface(&self) -> crate::object::Interface { - crate::object::Interface::$ifname + crate::wire::$ifname } } impl From<$ename> for crate::globals::GlobalError { fn from(e: $ename) -> Self { Self { - interface: crate::object::Interface::$ifname, + interface: crate::wire::$ifname, error: Box::new(e), } } @@ -281,7 +281,7 @@ macro_rules! dedicated_add_obj { impl crate::client::WaylandObjectLookup for $idname { type Object = $oname; - const INTERFACE: crate::object::Interface = crate::object::Interface::$oname; + const INTERFACE: crate::object::Interface = crate::wire::$oname; fn lookup(client: &crate::client::Client, id: Self) -> Option> { client.objects.$field.get(&id) diff --git a/src/object.rs b/src/object.rs index 86f5a6c3..b7a5da87 100644 --- a/src/object.rs +++ b/src/object.rs @@ -42,91 +42,11 @@ pub trait Object: ObjectBase + 'static { fn break_loops(&self) {} } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Interface { - WlDisplay, - WlCallback, - WlCompositor, - WlOutput, - WlRegistry, - WlShm, - WlShmPool, - WlTouch, - WlPointer, - WlKeyboard, - WlSubcompositor, - WlDataDeviceManager, - WlDataDevice, - WlDataSource, - WlDataOffer, - XdgWmBase, - XdgPositioner, - WlSurface, - WlSubsurface, - XdgSurface, - XdgPopup, - XdgToplevel, - WlRegion, - WlBuffer, - WlSeat, - WlDrm, - ZwpLinuxDmabufV1, - ZwpLinuxDmabufFeedbackV1, - ZwpLinuxBufferParamsV1, - ZxdgDecorationManagerV1, - ZxdgToplevelDecorationV1, - OrgKdeKwinServerDecorationManager, - OrgKdeKwinServerDecoration, - ZwpPrimarySelectionDeviceManagerV1, - ZwpPrimarySelectionDeviceV1, - ZwpPrimarySelectionSourceV1, - ZwpPrimarySelectionOfferV1, -} +#[derive(Copy, Clone, Debug)] +pub struct Interface(pub &'static str); impl Interface { pub fn name(self) -> &'static str { - match self { - Interface::WlDisplay => "wl_display", - Interface::WlCallback => "wl_callback", - Interface::WlCompositor => "wl_compositor", - Interface::WlRegistry => "wl_registry", - Interface::WlShm => "wl_shm", - Interface::WlSubcompositor => "wl_subcompositor", - Interface::XdgWmBase => "xdg_wm_base", - Interface::WlSurface => "wl_surface", - Interface::WlSubsurface => "wl_subsurface", - Interface::WlShmPool => "wl_shm_pool", - Interface::WlRegion => "wl_region", - Interface::XdgSurface => "xdg_surface", - Interface::XdgPositioner => "xdg_positioner", - Interface::XdgPopup => "xdg_popup", - Interface::XdgToplevel => "xdg_toplevel", - Interface::WlBuffer => "wl_buffer", - Interface::WlOutput => "wl_output", - Interface::WlSeat => "wl_seat", - Interface::WlTouch => "wl_touch", - Interface::WlPointer => "wl_pointer", - Interface::WlKeyboard => "wl_keyboard", - Interface::WlDataDeviceManager => "wl_data_device_manager", - Interface::WlDataDevice => "wl_data_device", - Interface::WlDataSource => "wl_data_source", - Interface::WlDataOffer => "wl_data_offer", - Interface::ZwpLinuxDmabufV1 => "zwp_linux_dmabuf_v1", - Interface::ZwpLinuxDmabufFeedbackV1 => "zwp_linux_dmabuf_feedback_v1", - Interface::ZwpLinuxBufferParamsV1 => "zwp_linux_buffer_params_v1", - Interface::WlDrm => "wl_drm", - Interface::ZxdgDecorationManagerV1 => "zxdg_decoration_manager_v1", - Interface::ZxdgToplevelDecorationV1 => "zxdg_toplevel_decoration_v1", - Interface::OrgKdeKwinServerDecorationManager => { - "org_kde_kwin_server_decoration_manager" - } - Interface::OrgKdeKwinServerDecoration => "org_kde_kwin_server_decoration", - Interface::ZwpPrimarySelectionDeviceManagerV1 => { - "zwp_primary_selection_device_manager_v1" - } - Interface::ZwpPrimarySelectionDeviceV1 => "zwp_primary_selection_device_v1", - Interface::ZwpPrimarySelectionSourceV1 => "zwp_primary_selection_source_v1", - Interface::ZwpPrimarySelectionOfferV1 => "zwp_primary_selection_offer_v1", - } + self.0 } } diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index 7e3c0390..2f19a203 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -21,6 +21,7 @@ use crate::State; use std::ops::Deref; use std::rc::Rc; use std::slice; +use crate::ifs::wl_surface::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; const NON_COLOR: Color = Color::from_rgbaf(0.2, 0.2, 0.2, 1.0); const CHILD_COLOR: Color = Color::from_rgbaf(0.8, 0.8, 0.8, 1.0); @@ -42,9 +43,21 @@ pub struct Renderer<'a> { impl Renderer<'_> { pub fn render_output(&mut self, output: &OutputNode, x: i32, y: i32) { + macro_rules! render_layer { + ($layer:expr) => { + for ls in $layer.iter() { + let pos = ls.position(); + self.render_layer_surface(ls.deref(), pos.x1(), pos.y1()); + } + } + } + render_layer!(output.layers[0]); + render_layer!(output.layers[1]); if let Some(ws) = output.workspace.get() { self.render_workspace(&ws, x, y); } + render_layer!(output.layers[2]); + render_layer!(output.layers[3]); } pub fn render_workspace(&mut self, workspace: &WorkspaceNode, x: i32, y: i32) { @@ -392,4 +405,13 @@ impl Renderer<'_> { }); } } + + pub fn render_layer_surface(&mut self, surface: &ZwlrLayerSurfaceV1, x: i32, y: i32) { + unsafe { + let body = surface.position(); + with_scissor(&body, || { + self.render_surface(&surface.surface, x, y); + }); + } + } } diff --git a/src/tasks/output.rs b/src/tasks/output.rs index c35c99dd..0fc7faae 100644 --- a/src/tasks/output.rs +++ b/src/tasks/output.rs @@ -30,7 +30,9 @@ impl OutputHandler { workspace: CloneCell::new(None), seat_state: Default::default(), global: global.clone(), + layers: Default::default(), }); + global.node.set(Some(on.clone())); let workspace = Rc::new(WorkspaceNode { id: self.state.node_ids.next(), output: CloneCell::new(on.clone()), @@ -60,6 +62,7 @@ impl OutputHandler { global.update_properties(); ae.triggered().await; } + global.node.set(None); self.state.outputs.remove(&self.output.id()); let _ = self.state.remove_global(&*global); self.state diff --git a/src/tree/container.rs b/src/tree/container.rs index 143f68b9..c56ce47f 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -345,7 +345,13 @@ impl ContainerNode { } _ => { let height = body.height() + add; - (0, pos + title_height + 1, other_content_size, height, height) + ( + 0, + pos + title_height + 1, + other_content_size, + height, + height, + ) } }; body = Rect::new_sized(x1, y1, width, height).unwrap(); diff --git a/src/tree/mod.rs b/src/tree/mod.rs index f5df15f5..7c5b859f 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -2,13 +2,11 @@ use crate::backend::{KeyState, OutputId, ScrollAxis}; use crate::client::{Client, ClientId}; use crate::cursor::KnownCursor; use crate::fixed::Fixed; -use crate::ifs::wl_output::WlOutputGlobal; use crate::ifs::wl_seat::{Dnd, NodeSeatState, WlSeatGlobal}; use crate::ifs::wl_surface::WlSurface; use crate::rect::Rect; use crate::render::Renderer; use crate::tree::walker::NodeVisitor; -use crate::utils::clonecell::CloneCell; use crate::utils::copyhashmap::CopyHashMap; use crate::utils::linkedlist::LinkedList; use crate::xkbcommon::ModifierState; @@ -16,16 +14,17 @@ use crate::NumCell; pub use container::*; pub use float::*; use i4config::Direction; -use std::cell::{Cell, RefCell}; -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::{Debug, Display}; use std::ops::Deref; use std::rc::Rc; pub use workspace::*; +pub use output::*; mod container; mod float; pub mod walker; mod workspace; +mod output; pub struct NodeIds { next: NumCell, @@ -254,6 +253,14 @@ pub trait Node { false } + fn is_output(&self) -> bool { + false + } + + fn into_output(self: Rc) -> Option> { + None + } + fn accepts_child(&self, node: &dyn Node) -> bool { let _ = node; false @@ -422,87 +429,3 @@ impl Node for DisplayNode { seat.set_known_cursor(KnownCursor::Default); } } - -tree_id!(OutputNodeId); -pub struct OutputNode { - pub display: Rc, - pub id: OutputNodeId, - pub position: Cell, - pub global: Rc, - pub workspaces: RefCell>>, - pub workspace: CloneCell>>, - pub seat_state: NodeSeatState, -} - -impl Debug for OutputNode { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("OutputNode").finish_non_exhaustive() - } -} - -impl Node for OutputNode { - fn id(&self) -> NodeId { - self.id.into() - } - - fn seat_state(&self) -> &NodeSeatState { - &self.seat_state - } - - fn destroy_node(&self, detach: bool) { - if detach { - self.display.clone().remove_child(self); - } - let mut workspaces = self.workspaces.borrow_mut(); - for workspace in workspaces.drain(..) { - workspace.destroy_node(false); - } - self.seat_state.destroy_node(self); - } - - fn visit(self: Rc, visitor: &mut dyn NodeVisitor) { - visitor.visit_output(&self); - } - - fn visit_children(&self, visitor: &mut dyn NodeVisitor) { - let ws = self.workspaces.borrow_mut(); - for ws in ws.deref() { - visitor.visit_workspace(ws); - } - } - - fn absolute_position(&self) -> Rect { - self.position.get() - } - - fn find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { - if let Some(ws) = self.workspace.get() { - tree.push(FoundNode { - node: ws.clone(), - x, - y, - }); - ws.find_tree_at(x, y, tree); - } - FindTreeResult::AcceptsInput - } - - fn remove_child(self: Rc, _child: &dyn Node) { - self.workspace.set(None); - } - - fn pointer_focus(&self, seat: &Rc) { - seat.set_known_cursor(KnownCursor::Default); - } - - fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { - renderer.render_output(self, x, y); - } - - fn change_extents(self: Rc, rect: &Rect) { - self.position.set(*rect); - if let Some(c) = self.workspace.get() { - c.change_extents(rect); - } - } -} diff --git a/src/tree/output.rs b/src/tree/output.rs new file mode 100644 index 00000000..d11dee73 --- /dev/null +++ b/src/tree/output.rs @@ -0,0 +1,117 @@ +use std::cell::{Cell, RefCell}; +use std::fmt::{Debug, Formatter}; +use std::ops::Deref; +use std::rc::Rc; +use crate::{CloneCell, DisplayNode}; +use crate::cursor::KnownCursor; +use crate::ifs::wl_output::WlOutputGlobal; +use crate::ifs::wl_seat::{NodeSeatState, WlSeatGlobal}; +use crate::ifs::wl_surface::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; +use crate::rect::Rect; +use crate::render::Renderer; +use crate::tree::{FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode}; +use crate::tree::walker::NodeVisitor; +use crate::utils::linkedlist::LinkedList; + +tree_id!(OutputNodeId); +pub struct OutputNode { + pub display: Rc, + pub id: OutputNodeId, + pub position: Cell, + pub global: Rc, + pub workspaces: RefCell>>, + pub workspace: CloneCell>>, + pub seat_state: NodeSeatState, + pub layers: [LinkedList>; 4], +} + +impl Debug for OutputNode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OutputNode").finish_non_exhaustive() + } +} + +impl Node for OutputNode { + fn id(&self) -> NodeId { + self.id.into() + } + + fn seat_state(&self) -> &NodeSeatState { + &self.seat_state + } + + fn destroy_node(&self, detach: bool) { + if detach { + self.display.clone().remove_child(self); + } + let mut workspaces = self.workspaces.borrow_mut(); + for workspace in workspaces.drain(..) { + workspace.destroy_node(false); + } + self.seat_state.destroy_node(self); + } + + fn visit(self: Rc, visitor: &mut dyn NodeVisitor) { + visitor.visit_output(&self); + } + + fn visit_children(&self, visitor: &mut dyn NodeVisitor) { + let ws = self.workspaces.borrow_mut(); + for ws in ws.deref() { + visitor.visit_workspace(ws); + } + for layers in &self.layers { + for surface in layers.iter() { + visitor.visit_layer_surface(surface.deref()); + } + } + } + + fn absolute_position(&self) -> Rect { + self.position.get() + } + + fn find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + if let Some(ws) = self.workspace.get() { + tree.push(FoundNode { + node: ws.clone(), + x, + y, + }); + ws.find_tree_at(x, y, tree); + } + FindTreeResult::AcceptsInput + } + + fn remove_child(self: Rc, _child: &dyn Node) { + self.workspace.set(None); + } + + fn pointer_focus(&self, seat: &Rc) { + seat.set_known_cursor(KnownCursor::Default); + } + + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { + renderer.render_output(self, x, y); + } + + fn is_output(&self) -> bool { + true + } + + fn into_output(self: Rc) -> Option> { + Some(self) + } + + fn change_extents(self: Rc, rect: &Rect) { + self.position.set(*rect); + if let Some(c) = self.workspace.get() { + c.change_extents(rect); + } + for layer in &self.layers { + for surface in layer.iter() { + surface.deref().clone().change_extents(rect); + } + } + } +} diff --git a/src/tree/walker.rs b/src/tree/walker.rs index 9c3b381a..52d04923 100644 --- a/src/tree/walker.rs +++ b/src/tree/walker.rs @@ -4,6 +4,7 @@ use crate::ifs::wl_surface::WlSurface; use crate::tree::{ContainerNode, FloatNode, Node, OutputNode, WorkspaceNode}; use crate::DisplayNode; use std::rc::Rc; +use crate::ifs::wl_surface::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; pub trait NodeVisitorBase: Sized { fn visit_surface(&mut self, node: &Rc) { @@ -37,6 +38,10 @@ pub trait NodeVisitorBase: Sized { fn visit_workspace(&mut self, node: &Rc) { node.visit_children(self); } + + fn visit_layer_surface(&mut self, node: &Rc) { + node.visit_children(self); + } } pub trait NodeVisitor { @@ -48,6 +53,7 @@ pub trait NodeVisitor { fn visit_output(&mut self, node: &Rc); fn visit_float(&mut self, node: &Rc); fn visit_workspace(&mut self, node: &Rc); + fn visit_layer_surface(&mut self, node: &Rc); } impl NodeVisitor for T { @@ -82,6 +88,10 @@ impl NodeVisitor for T { fn visit_workspace(&mut self, node: &Rc) { ::visit_workspace(self, node) } + + fn visit_layer_surface(&mut self, node: &Rc) { + ::visit_layer_surface(self, node) + } } // pub fn visit_containers)>(f: F) -> impl NodeVisitor { diff --git a/src/wire.rs b/src/wire.rs index 4b7c9fd3..d79afd43 100644 --- a/src/wire.rs +++ b/src/wire.rs @@ -1 +1,3 @@ +#![allow(non_upper_case_globals)] + include!(concat!(env!("OUT_DIR"), "/wire.rs")); diff --git a/todo.md b/todo.md index 39096a98..4075279f 100644 --- a/todo.md +++ b/todo.md @@ -5,10 +5,11 @@ - presentation time - viewporter - session lock -- layer shell +- xwayland # done +- layer shell - Float moving - Float toggle - Container moving (kb) diff --git a/wire/zwlr_layer_shell_v1.txt b/wire/zwlr_layer_shell_v1.txt new file mode 100644 index 00000000..97ea1404 --- /dev/null +++ b/wire/zwlr_layer_shell_v1.txt @@ -0,0 +1,12 @@ +# requests + +msg get_layer_surface = 0 { + id: id(zwlr_layer_surface_v1), + surface: id(wl_surface), + output: id(wl_output), + layer: u32, + namespace: str, +} + +msg destroy = 1 { +} diff --git a/wire/zwlr_layer_surface_v1.txt b/wire/zwlr_layer_surface_v1.txt new file mode 100644 index 00000000..a043cfaf --- /dev/null +++ b/wire/zwlr_layer_surface_v1.txt @@ -0,0 +1,49 @@ +# requests + +msg set_size = 0 { + width: u32, + height: u32, +} + +msg set_anchor = 1 { + anchor: u32, +} + +msg set_exclusive_zone = 2 { + zone: i32, +} + +msg set_margin = 3 { + top: i32, + right: i32, + bottom: i32, + left: i32, +} + +msg set_keyboard_interactivity = 4 { + keyboard_interactivity: u32, +} + +msg get_popup = 5 { + popup: id(xdg_popup), +} + +msg ack_configure = 6 { + serial: u32, +} + +msg destroy = 7 { } + +msg set_layer = 8 { + layer: u32, +} + +# events + +msg configure = 0 { + serial: u32, + width: u32, + height: u32, +} + +msg closed = 1 { } diff --git a/wire/zxdg_output_manager_v1.txt b/wire/zxdg_output_manager_v1.txt new file mode 100644 index 00000000..724e5da5 --- /dev/null +++ b/wire/zxdg_output_manager_v1.txt @@ -0,0 +1,8 @@ +# requests + +msg destroy = 0 { } + +msg get_xdg_output = 1 { + id: id(zxdg_output_v1), + output: id(wl_output), +} diff --git a/wire/zxdg_output_v1.txt b/wire/zxdg_output_v1.txt new file mode 100644 index 00000000..4a69b334 --- /dev/null +++ b/wire/zxdg_output_v1.txt @@ -0,0 +1,25 @@ +# requests + +msg destroy = 0 { } + +# events + +msg logical_position = 0 { + x: i32, + y: i32, +} + +msg logical_size = 1 { + width: i32, + height: i32, +} + +msg done = 2 { } + +msg name = 3 { + name: str, +} + +msg description = 4 { + description: str, +}