GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: Application::utility() — declare a content-shaped window
A default-false trait method (the layer() idiom — WindowSettings has
52 literal constructions across the workspace, so no new field). When
true, the runner binds zcce_window_manager_v1 at exactly version 5 and
sends set_utility before the initial commit, so the mode is set before
the compositor maps or restores the window; on an older compositor the
bind fails and the app runs as a plain floating window, with a warning
instead of a protocol error.
cce-relief turns it on — the window whose shape IS content_height():
it previously asked for ~896px and got back the 520x480 it was last
left at, collapsing the cutaway onto its 90px floor. Verified in a
shadow session mapping at its own 520x913 with mode=Utility.
Co-Authored-By: Claude <[email protected]>
protocol/cce-window-management-v1.xml | 19 ++++++-
src/backend/window_runner.rs | 99 +++++++++++++++++++++++++++++++++++
src/bin/cce-relief.rs | 7 +++
3 files changed, 123 insertions(+), 2 deletions(-)
diff --git a/protocol/cce-window-management-v1.xml b/protocol/cce-window-management-v1.xml
index ad7d32d..5e998ab 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="4">
+ <interface name="zcce_window_manager_v1" version="5">
<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="2">
+ <interface name="zcce_toplevel_v1" version="3">
<description summary="toplevel window management controls">
An interface to control and listen to CCE-specific window management states
for a client surface.
@@ -1533,6 +1533,21 @@
Request the compositor to return the window to normal tiling mode.
</description>
</request>
+ <request name="set_utility" since="3">
+ <description summary="request utility mode">
+ Declare the window a utility window: a tool whose shape is decided by
+ its contents. The compositor stops dictating a size to it (every
+ configure carries the "you choose" 0x0), offers no resize affordance,
+ and never saves geometry for it. The window still floats and moves
+ like an ordinary window. The declaration is explicit by design — the
+ compositor never infers utility mode from size hints.
+ </description>
+ </request>
+ <request name="unset_utility" since="3">
+ <description summary="unset utility mode">
+ Request the compositor to return the window to normal tiling mode.
+ </description>
+ </request>
<event name="floating_state">
<description summary="floating status event">
Sent by the compositor to inform the client of its current floating status.
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index b320dff..509a502 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2718,6 +2718,18 @@ pub trait Application: Sized + 'static {
fn layer(&self) -> Option<LayerSettings> {
None
}
+ /// Declare the window a UTILITY window: a tool whose shape is decided by
+ /// its contents. The compositor then never dictates a size to it (every
+ /// configure is the "you choose" 0x0 — [`WindowSettings::width`]/`height`
+ /// become the surface's own initial size), offers no resize affordance
+ /// (the whole border band moves the window), and never saves geometry
+ /// for it, so a stale remembered size can't be restored over what the
+ /// app asks for. Declared over the cce window-management protocol at
+ /// window creation; on a compositor too old to know the request this is
+ /// silently a plain floating window. Defaults to `false`.
+ fn utility(&self) -> bool {
+ false
+ }
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
@@ -3023,6 +3035,9 @@ pub struct EngineState<A: Application> {
pub just_configured: bool,
pub pointer_gestures: Option<ZwpPointerGesturesV1>,
pub pinch_gesture: Option<ZwpPointerGesturePinchV1>,
+ /// 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>,
pub last_pinch_scale: f32,
pub cursor_pos: (f32, f32),
/// Serial of the most recent pointer press, kept for
@@ -4280,6 +4295,71 @@ impl<A: Application> wayland_client::Dispatch<crate::protocol::zcce_inspector_v1
) {}
}
+impl<A: Application> wayland_client::Dispatch<crate::protocol::cce_window_management_v1::zcce_window_manager_v1::ZcceWindowManagerV1, ()> for EngineState<A> {
+ fn event(
+ _state: &mut Self,
+ _proxy: &crate::protocol::cce_window_management_v1::zcce_window_manager_v1::ZcceWindowManagerV1,
+ _event: crate::protocol::cce_window_management_v1::zcce_window_manager_v1::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ ) {}
+
+ wayland_client::event_created_child!(
+ EngineState<A>,
+ crate::protocol::cce_window_management_v1::zcce_window_manager_v1::ZcceWindowManagerV1,
+ [
+ 6 => (crate::protocol::cce_window_management_v1::zcce_window_v1::ZcceWindowV1, ()),
+ 7 => (crate::protocol::cce_window_management_v1::zcce_output_v1::ZcceOutputV1, ()),
+ 8 => (crate::protocol::cce_window_management_v1::zcce_seat_v1::ZcceSeatV1, ()),
+ ]
+ );
+}
+
+impl<A: Application> wayland_client::Dispatch<crate::protocol::cce_window_management_v1::zcce_window_v1::ZcceWindowV1, ()> for EngineState<A> {
+ fn event(
+ _state: &mut Self,
+ _proxy: &crate::protocol::cce_window_management_v1::zcce_window_v1::ZcceWindowV1,
+ _event: crate::protocol::cce_window_management_v1::zcce_window_v1::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ ) {}
+}
+
+impl<A: Application> wayland_client::Dispatch<crate::protocol::cce_window_management_v1::zcce_output_v1::ZcceOutputV1, ()> for EngineState<A> {
+ fn event(
+ _state: &mut Self,
+ _proxy: &crate::protocol::cce_window_management_v1::zcce_output_v1::ZcceOutputV1,
+ _event: crate::protocol::cce_window_management_v1::zcce_output_v1::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ ) {}
+}
+
+impl<A: Application> wayland_client::Dispatch<crate::protocol::cce_window_management_v1::zcce_seat_v1::ZcceSeatV1, ()> for EngineState<A> {
+ fn event(
+ _state: &mut Self,
+ _proxy: &crate::protocol::cce_window_management_v1::zcce_seat_v1::ZcceSeatV1,
+ _event: crate::protocol::cce_window_management_v1::zcce_seat_v1::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ ) {}
+}
+
+impl<A: Application> wayland_client::Dispatch<crate::protocol::cce_window_management_v1::zcce_toplevel_v1::ZcceToplevelV1, ()> for EngineState<A> {
+ fn event(
+ _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,
+ _data: &(),
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ ) {}
+}
+
delegate_compositor!(@<A: Application> EngineState<A>);
delegate_xdg_shell!(@<A: Application> EngineState<A>);
delegate_xdg_window!(@<A: Application> EngineState<A>);
@@ -4578,6 +4658,7 @@ fn run_session<'l, A: Application>(
just_configured: false,
pointer_gestures,
pinch_gesture: None,
+ cce_toplevel: None,
last_pinch_scale: 1.0,
cursor_pos: (0.0, 0.0),
last_press_serial: None,
@@ -4652,6 +4733,24 @@ 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() {
+ // 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, ()) {
+ Ok(cce_wm) => {
+ let toplevel = cce_wm.get_cce_toplevel(&surface, &qh, ());
+ toplevel.set_utility();
+ engine_state.cce_toplevel = Some(toplevel);
+ }
+ Err(e) => {
+ log::warn!("[window_runner] utility window declaration unavailable: {e}");
+ }
+ }
+ }
window.commit();
engine_state.window = Some(window);
}
diff --git a/src/bin/cce-relief.rs b/src/bin/cce-relief.rs
index 89e019a..3ea53d3 100644
--- a/src/bin/cce-relief.rs
+++ b/src/bin/cce-relief.rs
@@ -1211,6 +1211,13 @@ impl Application for BevelPopup {
}
}
+ /// The motivating case for the mode: this window's shape IS
+ /// `content_height`, so nothing — not a drag, not a remembered size —
+ /// should ever dictate a different one.
+ fn utility(&self) -> bool {
+ true
+ }
+
fn update(&mut self, msg: Self::Message, _needs_rebuild: &mut bool, exit: &mut bool) {
match msg {
BevelMsg::Exit => *exit = true,