GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: grid-layer client support — Application::grid + grid_patch events
Protocol copy synced (manager v6, toplevel v4: set_grid, grid_patch,
ack_grid_patch). An app returning true from Application::grid() binds
manager v6 and declares its surface the desktop-grid layer; the runner
handles grid_patch events by resizing the surface to the patch's buffer
size, forwarding the patch to Application::grid_patch, and acking right
before the frame that renders it — so the commit the compositor latches
is the one drawn for that anchor. A newer patch supersedes an
unconsumed older one. On an older compositor the declaration is skipped
gracefully, like utility windows.
Co-Authored-By: Claude Fable 5 <[email protected]>
protocol/cce-window-management-v1.xml | 48 +++++++++++++++++++++++--
src/backend/window_runner.rs | 67 +++++++++++++++++++++++++++++------
2 files changed, 102 insertions(+), 13 deletions(-)
diff --git a/protocol/cce-window-management-v1.xml b/protocol/cce-window-management-v1.xml
index 5e998ab..1bcfd5b 100644
--- a/protocol/cce-window-management-v1.xml
+++ b/protocol/cce-window-management-v1.xml
@@ -28,7 +28,7 @@
"should", "should not", "recommended", "may", and "optional" in this
document are to be interpreted as described in IETF RFC 2119.
</description>
- <interface name="zcce_window_manager_v1" version="5">
+ <interface name="zcce_window_manager_v1" version="6">
<description summary="window manager global interface">
This global interface should only be advertised to the window manager
process. Only one window management client may be active at a time. The
@@ -1490,7 +1490,7 @@
</description>
</event>
</interface>
- <interface name="zcce_toplevel_v1" version="3">
+ <interface name="zcce_toplevel_v1" version="4">
<description summary="toplevel window management controls">
An interface to control and listen to CCE-specific window management states
for a client surface.
@@ -1548,11 +1548,55 @@
Request the compositor to return the window to normal tiling mode.
</description>
</request>
+ <request name="set_grid" since="4">
+ <description summary="declare the desktop-grid surface">
+ Declare this surface the desktop-grid layer. The compositor anchors
+ the surface to the virtual desktop: it is positioned and scaled with
+ the camera every frame, exactly like window content, so the client is
+ never part of the pan/zoom loop. The surface becomes
+ input-transparent, is stacked above the wallpaper and below all
+ windows, is excluded from focus, session save and the overview, and
+ the compositor stops dictating sizes to it.
+
+ The compositor tells the client WHAT to render via grid_patch
+ events; the surface stays unmapped-invisible until the first
+ acknowledged patch is committed.
+ </description>
+ </request>
+ <request name="ack_grid_patch" since="4">
+ <description summary="acknowledge a grid patch">
+ Acknowledge a grid_patch event. The next buffer the client commits
+ after this request is taken to be rendered for the acknowledged
+ patch: the compositor latches that patch's anchor rectangle at that
+ commit, so an in-flight older buffer is never shown at a new
+ anchor.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the grid_patch event"/>
+ </request>
<event name="floating_state">
<description summary="floating status event">
Sent by the compositor to inform the client of its current floating status.
</description>
<arg name="state" type="uint" summary="1 if floating, 0 otherwise"/>
</event>
+ <event name="grid_patch" since="4">
+ <description summary="render this patch of the desktop grid">
+ Instructs the grid client what region of the virtual desktop to
+ render, and at what resolution. x/y/width/height are virtual-surface
+ coordinates; scale is buffer pixels per virtual unit, so the
+ expected buffer size is (width * scale) x (height * scale). The
+ client renders the region, sends ack_grid_patch with the serial, and
+ commits the new buffer.
+
+ A newer grid_patch supersedes an unacknowledged older one; the
+ client should render only the latest.
+ </description>
+ <arg name="serial" type="uint" summary="serial to pass to ack_grid_patch"/>
+ <arg name="x" type="fixed" summary="patch origin x, virtual units"/>
+ <arg name="y" type="fixed" summary="patch origin y, virtual units"/>
+ <arg name="width" type="fixed" summary="patch width, virtual units"/>
+ <arg name="height" type="fixed" summary="patch height, virtual units"/>
+ <arg name="scale" type="fixed" summary="buffer px per virtual unit"/>
+ </event>
</interface>
</protocol>
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 509a502..329a124 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2730,6 +2730,22 @@ pub trait Application: Sized + 'static {
fn utility(&self) -> bool {
false
}
+ /// Declare the window the DESKTOP-GRID layer (zcce set_grid): the
+ /// compositor world-anchors the surface to the virtual desktop and
+ /// pans/zooms it per frame like window content; the app renders only
+ /// when handed a patch (see [`Application::grid_patch`]). The surface
+ /// becomes input-transparent and lives behind all windows. Needs
+ /// manager v6; on an older compositor the declaration is skipped.
+ /// Defaults to `false`.
+ fn grid(&self) -> bool {
+ false
+ }
+ /// A grid patch to render (grid apps only): virtual origin (`x`, `y`),
+ /// virtual size (`w`, `h`), and `scale` surface px per virtual unit.
+ /// Called right before the frame that must show it; the runner has
+ /// already resized the surface to `(w*scale, h*scale)` and acks the
+ /// patch so the coming commit is latched at the new anchor.
+ fn grid_patch(&mut self, _x: f64, _y: f64, _w: f64, _h: f64, _scale: f64) {}
fn update(&mut self, msg: Self::Message, needs_rebuild: &mut bool, exit: &mut bool);
fn tick(&mut self, dt: f32, needs_rebuild: &mut bool);
/// On-top overlay quads drawn after the display list and its text (e.g. the status bar's
@@ -3038,6 +3054,9 @@ pub struct EngineState<A: Application> {
/// The cce window-management toplevel handle, held for the window's
/// lifetime once [`Application::utility`] declared the mode.
pub cce_toplevel: Option<crate::protocol::cce_window_management_v1::zcce_toplevel_v1::ZcceToplevelV1>,
+ /// Latest unrendered grid_patch (serial, x, y, w, h, scale) — a newer
+ /// event supersedes an unconsumed older one, per protocol.
+ pub pending_grid_patch: Option<(u32, f64, f64, f64, f64, f64)>,
pub last_pinch_scale: f32,
pub cursor_pos: (f32, f32),
/// Serial of the most recent pointer press, kept for
@@ -3215,6 +3234,16 @@ impl<A: Application> EngineState<A> {
}
pub fn render(&mut self) {
+ // Grid patch: resize to the patch's buffer size, tell the app what
+ // world region this frame covers, and ack — the commit this render
+ // produces is the one the compositor latches at the new anchor.
+ if let Some((serial, px, py, pw, ph, pscale)) = self.pending_grid_patch.take() {
+ self.resize((pw * pscale) as f32, (ph * pscale) as f32);
+ self.inner.as_mut().unwrap().grid_patch(px, py, pw, ph, pscale);
+ if let Some(tl) = &self.cce_toplevel {
+ tl.ack_grid_patch(serial);
+ }
+ }
let logical_w = self.logical_width;
let logical_h = self.logical_height;
let scale_factor = self.scale_factor;
@@ -4351,13 +4380,20 @@ impl<A: Application> wayland_client::Dispatch<crate::protocol::cce_window_manage
impl<A: Application> wayland_client::Dispatch<crate::protocol::cce_window_management_v1::zcce_toplevel_v1::ZcceToplevelV1, ()> for EngineState<A> {
fn event(
- _state: &mut Self,
+ state: &mut Self,
_proxy: &crate::protocol::cce_window_management_v1::zcce_toplevel_v1::ZcceToplevelV1,
- _event: crate::protocol::cce_window_management_v1::zcce_toplevel_v1::Event,
+ event: crate::protocol::cce_window_management_v1::zcce_toplevel_v1::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Self>,
- ) {}
+ ) {
+ use crate::protocol::cce_window_management_v1::zcce_toplevel_v1::Event;
+ if let Event::GridPatch { serial, x, y, width, height, scale } = event {
+ // A newer patch supersedes an unconsumed older one.
+ state.pending_grid_patch = Some((serial, x, y, width, height, scale));
+ state.redraw = true;
+ }
+ }
}
delegate_compositor!(@<A: Application> EngineState<A>);
@@ -4659,6 +4695,7 @@ fn run_session<'l, A: Application>(
pointer_gestures,
pinch_gesture: None,
cce_toplevel: None,
+ pending_grid_patch: None,
last_pinch_scale: 1.0,
cursor_pos: (0.0, 0.0),
last_press_serial: None,
@@ -4733,21 +4770,29 @@ fn run_session<'l, A: Application>(
if let Some((min_w, min_h)) = settings.min_size {
window.set_min_size(Some((min_w, min_h)));
}
- if engine_state.inner.as_ref().unwrap().utility() {
+ let wants_utility = engine_state.inner.as_ref().unwrap().utility();
+ let wants_grid = engine_state.inner.as_ref().unwrap().grid();
+ if wants_utility || wants_grid {
// Declared BEFORE the initial commit so the mode is set by the
// time the compositor maps (and would otherwise restore) the
- // window. Manager version 5 is where set_utility appeared; on an
- // older compositor the declaration is skipped and the app runs
- // as a plain floating window rather than dying on an unknown
- // opcode.
- match globals.bind::<crate::protocol::cce_window_management_v1::zcce_window_manager_v1::ZcceWindowManagerV1, _, _>(&qh, 5..=5, ()) {
+ // window. Manager version 5 is where set_utility appeared, 6 is
+ // where the grid role did; on an older compositor the
+ // declaration is skipped and the app runs as a plain floating
+ // window rather than dying on an unknown opcode.
+ let version = if wants_grid { 6..=6 } else { 5..=5 };
+ match globals.bind::<crate::protocol::cce_window_management_v1::zcce_window_manager_v1::ZcceWindowManagerV1, _, _>(&qh, version, ()) {
Ok(cce_wm) => {
let toplevel = cce_wm.get_cce_toplevel(&surface, &qh, ());
- toplevel.set_utility();
+ if wants_utility {
+ toplevel.set_utility();
+ }
+ if wants_grid {
+ toplevel.set_grid();
+ }
engine_state.cce_toplevel = Some(toplevel);
}
Err(e) => {
- log::warn!("[window_runner] utility window declaration unavailable: {e}");
+ log::warn!("[window_runner] cce window-management declaration unavailable: {e}");
}
}
}