git.lucas.co / cce-window-manager
window management library
git clone https://git.lucas.co/cce-window-manager.git

src/api.rs (16.3K)

  1 // The Policy / Compositor trait boundary — snapshot-style.
  2 //
  3 // `Policy` is implemented policy-side (`actions::DefaultPolicy`): its methods
  4 // take a plain-data snapshot the mechanism captured at dispatch time and
  5 // return `Command`s — the same snapshot → plan convention as the arrange
  6 // pass, with the mechanism owning all state. `Compositor` is implemented by
  7 // the mechanism (`window_manager.rs`): it applies one `Command` at a time
  8 // against the wlroots/scenefx world.
  9 //
 10 // Migration status: `Policy::action` is live — the compositor routes user
 11 // actions through it and falls back to its legacy arms only for actions the
 12 // policy doesn't claim. Further flows (window lifecycle, the animation tick)
 13 // grow new snapshot-taking methods here as they migrate; don't add
 14 // speculative signatures ahead of a real mechanism caller. Effects are
 15 // declarative on purpose: new scenefx capabilities extend `EffectSpec`
 16 // without changing either trait.
 17 
 18 use crate::camera::Camera;
 19 use crate::tiling::TilingMode;
 20 
 21 /// Opaque handle to a window. Wraps the `SlotMap` key that the mechanism side
 22 /// uses internally; policy code never sees a pointer.
 23 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 24 pub struct WindowId(pub crate::slotmap::Key);
 25 
 26 /// What a surface is for. Assigned once at map time, this replaces scattered
 27 /// app_id string-matching (`"cce-wallpaper"`, `"cce-status*"`) in mechanism code.
 28 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 29 pub enum WindowRole {
 30     Normal,
 31     StatusBar,
 32     Background,
 33     Overlay,
 34     /// The desktop-grid layer: a client surface world-anchored to a patch of
 35     /// the virtual desktop (`WindowSnapshot::grid_patch`). The compositor
 36     /// pans/zooms it per frame exactly like window content — the client is
 37     /// never in the frame loop; it re-renders only when handed a new patch.
 38     /// Input-transparent, stacked above the wallpaper and below everything
 39     /// else.
 40     Grid,
 41 }
 42 
 43 impl WindowRole {
 44     /// The single place the special app_id conventions are interpreted.
 45     /// `Overlay` is never derived from an app_id — it comes from tiling mode.
 46     /// `Grid` also has a protocol declaration (`set_grid`); the app_id match
 47     /// makes the fallback swap and placement correct from map time.
 48     pub fn from_app_id(app_id: Option<&str>) -> Self {
 49         match app_id {
 50             Some("cce-wallpaper") => WindowRole::Background,
 51             Some("cce-grid") => WindowRole::Grid,
 52             Some(id) if id.starts_with("cce-status") => WindowRole::StatusBar,
 53             _ => WindowRole::Normal,
 54         }
 55     }
 56 }
 57 
 58 /// User-triggered window-management actions, bound to keys/pointers/gestures
 59 /// by the compositor's config and dispatched into policy code.
 60 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
 61 pub enum Action {
 62     None,
 63     Spawn,
 64     Toggle,
 65     Close,
 66     FocusNext,
 67     FocusPrev,
 68     FocusUp,
 69     FocusDown,
 70     FocusLeft,
 71     FocusRight,
 72     WindowSwitcher,
 73     WindowSwitcherPrev,
 74     Move,
 75     Resize,
 76     MoveWindowLeft,
 77     MoveWindowRight,
 78     MoveWindowUp,
 79     MoveWindowDown,
 80     Exit,
 81     Reload,
 82     Fullscreen,
 83     ModeNext,
 84     ModeNextShared,
 85     Overview,
 86     OverviewEnter,
 87     OverviewExit,
 88     Minimize,
 89     OverlayLeft,
 90     OverlayRight,
 91     ZoomIn,
 92     ZoomOut,
 93     ZoomReset,
 94     PanLeft,
 95     PanRight,
 96     PanUp,
 97     PanDown,
 98     Screenshot,
 99     VolumeUp,
100     VolumeDown,
101     VolumeMute,
102     MicMute,
103     BrightnessUp,
104     BrightnessDown,
105 }
106 
107 impl Action {
108     /// Canonical snake_case name — what users write in the
109     /// `cce-window-manager` domain of `input.kdl`.
110     pub fn name(&self) -> &'static str {
111         match self {
112             Action::None => "none",
113             Action::Spawn => "spawn",
114             Action::Toggle => "toggle",
115             Action::Close => "close_window",
116             Action::FocusNext => "focus_next",
117             Action::FocusPrev => "focus_prev",
118             Action::FocusUp => "focus_up",
119             Action::FocusDown => "focus_down",
120             Action::FocusLeft => "focus_left",
121             Action::FocusRight => "focus_right",
122             Action::WindowSwitcher => "window_switcher",
123             Action::WindowSwitcherPrev => "window_switcher_prev",
124             Action::Move => "move",
125             Action::Resize => "resize",
126             Action::MoveWindowLeft => "move_window_left",
127             Action::MoveWindowRight => "move_window_right",
128             Action::MoveWindowUp => "move_window_up",
129             Action::MoveWindowDown => "move_window_down",
130             Action::Exit => "exit",
131             Action::Reload => "reload",
132             Action::Fullscreen => "toggle_fullscreen",
133             Action::ModeNext => "mode_next",
134             Action::ModeNextShared => "mode_next_shared",
135             Action::Overview => "overview",
136             Action::OverviewEnter => "overview_enter",
137             Action::OverviewExit => "overview_exit",
138             Action::Minimize => "minimize",
139             Action::OverlayLeft => "overlay_left",
140             Action::OverlayRight => "overlay_right",
141             Action::ZoomIn => "zoom_in",
142             Action::ZoomOut => "zoom_out",
143             Action::ZoomReset => "zoom_reset",
144             Action::PanLeft => "pan_left",
145             Action::PanRight => "pan_right",
146             Action::PanUp => "pan_up",
147             Action::PanDown => "pan_down",
148             Action::Screenshot => "screenshot",
149             Action::VolumeUp => "volume_up",
150             Action::VolumeDown => "volume_down",
151             Action::VolumeMute => "volume_mute",
152             Action::MicMute => "mic_mute",
153             Action::BrightnessUp => "brightness_up",
154             Action::BrightnessDown => "brightness_down",
155         }
156     }
157 
158     /// Inverse of `name()`, plus aliases from the old `config.kdl`
159     /// vocabulary (`close`, `fullscreen`, `expose`, `toggle_overview`).
160     pub fn from_name(name: &str) -> Option<Action> {
161         Some(match name.trim() {
162             "none" => Action::None,
163             "spawn" => Action::Spawn,
164             "toggle" => Action::Toggle,
165             "close_window" | "close" => Action::Close,
166             "focus_next" => Action::FocusNext,
167             "focus_prev" => Action::FocusPrev,
168             "focus_up" => Action::FocusUp,
169             "focus_down" => Action::FocusDown,
170             "focus_left" => Action::FocusLeft,
171             "focus_right" => Action::FocusRight,
172             "window_switcher" => Action::WindowSwitcher,
173             "window_switcher_prev" => Action::WindowSwitcherPrev,
174             "move" => Action::Move,
175             "resize" => Action::Resize,
176             "move_window_left" => Action::MoveWindowLeft,
177             "move_window_right" => Action::MoveWindowRight,
178             "move_window_up" => Action::MoveWindowUp,
179             "move_window_down" => Action::MoveWindowDown,
180             "exit" => Action::Exit,
181             "reload" => Action::Reload,
182             "toggle_fullscreen" | "fullscreen" => Action::Fullscreen,
183             "mode_next" => Action::ModeNext,
184             "mode_next_shared" => Action::ModeNextShared,
185             "overview" | "expose" | "toggle_overview" => Action::Overview,
186             "overview_enter" => Action::OverviewEnter,
187             "overview_exit" => Action::OverviewExit,
188             "minimize" => Action::Minimize,
189             "overlay_left" => Action::OverlayLeft,
190             "overlay_right" => Action::OverlayRight,
191             "zoom_in" => Action::ZoomIn,
192             "zoom_out" => Action::ZoomOut,
193             "zoom_reset" => Action::ZoomReset,
194             "pan_left" => Action::PanLeft,
195             "pan_right" => Action::PanRight,
196             "pan_up" => Action::PanUp,
197             "pan_down" => Action::PanDown,
198             "screenshot" => Action::Screenshot,
199             "volume_up" => Action::VolumeUp,
200             "volume_down" => Action::VolumeDown,
201             "volume_mute" | "mute" => Action::VolumeMute,
202             "mic_mute" => Action::MicMute,
203             "brightness_up" => Action::BrightnessUp,
204             "brightness_down" => Action::BrightnessDown,
205             _ => return None,
206         })
207     }
208 }
209 
210 #[derive(Debug, Clone, Copy, PartialEq)]
211 pub struct Rect {
212     pub x: i32,
213     pub y: i32,
214     pub width: i32,
215     pub height: i32,
216 }
217 
218 /// The world-anchored patch a grid client's buffer covers: virtual origin and
219 /// size, plus the buffer resolution. The compositor issues patches
220 /// (grid_patch events) and latches one when the client's rendered buffer
221 /// arrives; arrange places the surface at `(x, y)` with display scale
222 /// `zoom / scale`, so the buffer pans and zooms in lockstep with windows.
223 #[derive(Debug, Clone, Copy, PartialEq)]
224 pub struct GridPatch {
225     pub x: f64,
226     pub y: f64,
227     pub w: f64,
228     pub h: f64,
229     /// Buffer px per virtual unit.
230     pub scale: f64,
231 }
232 
233 /// Premultiplied-alpha RGBA, 0.0–1.0 per channel (scenefx convention).
234 #[derive(Debug, Clone, Copy, PartialEq)]
235 pub struct Rgba(pub [f32; 4]);
236 
237 /// Server-side decoration for one window: borders now, titlebars later.
238 #[derive(Debug, Clone, Copy, PartialEq)]
239 pub struct DecorationSpec {
240     pub border_width: i32,
241     pub border_color: Rgba,
242     pub corner_radius: i32,
243 }
244 
245 /// Declarative per-window scenefx effects.
246 #[derive(Debug, Clone, PartialEq)]
247 pub struct EffectSpec {
248     pub opacity: f32,
249     pub blur: bool,
250     pub shadow: Option<ShadowSpec>,
251 }
252 
253 #[derive(Debug, Clone, Copy, PartialEq)]
254 pub struct ShadowSpec {
255     pub color: Rgba,
256     pub blur_sigma: f32,
257 }
258 
259 /// What the background layer shows. The desktop is normally Grid; Solid is
260 /// the degenerate single-color background.
261 #[derive(Debug, Clone, PartialEq)]
262 pub enum BackgroundSpec {
263     Solid(Rgba),
264     Grid(GridSpec),
265 }
266 
267 /// The desktop grid, as configured (unzoomed virtual units). The per-frame
268 /// geometry — pan/zoom offsets, density fade, cell counts — is derived by
269 /// `background::grid_frame`.
270 #[derive(Debug, Clone, PartialEq)]
271 pub struct GridSpec {
272     /// Backdrop color behind and between the cells (premultiplied).
273     pub gap_color: Rgba,
274     pub cell_color: Rgba,
275     /// Cell width (x axis); the column period is `cell_w + gap_width`.
276     pub cell_w: f64,
277     /// Cell height (y axis); the row period is `cell_h + gap_width`.
278     pub cell_h: f64,
279     pub gap_width: f64,
280     pub cell_corner_radius: i32,
281     /// Cells fade inward by this many virtual px.
282     pub cell_fade_inset: i32,
283     pub fade_mode: GridFadeMode,
284 }
285 
286 /// Which side of the viewport the overlay column docks on.
287 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
288 pub enum OverlaySide {
289     Left,
290     Right,
291 }
292 
293 /// Shape of a cell's inward edge fade.
294 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
295 pub enum GridFadeMode {
296     Linear,
297     Smoothstep,
298     Quadratic,
299     Cosine,
300     Gaussian,
301 }
302 
303 impl GridFadeMode {
304     /// Config-string names; anything unrecognized is Linear.
305     pub fn from_name(name: &str) -> Self {
306         match name {
307             "smoothstep" => GridFadeMode::Smoothstep,
308             "quadratic" => GridFadeMode::Quadratic,
309             "cosine" => GridFadeMode::Cosine,
310             "gaussian" => GridFadeMode::Gaussian,
311             _ => GridFadeMode::Linear,
312         }
313     }
314 }
315 
316 /// Everything `Policy::action` may consult, captured by the mechanism at
317 /// dispatch time. Seat- and scene-dependent answers (cursor output, hovered
318 /// window, focus) are resolved into the snapshot up front — the arrange-pass
319 /// convention; policy never queries back mid-decision.
320 #[derive(Debug, Clone)]
321 pub struct ActionCtx {
322     pub camera: Camera,
323     /// The mechanism is in overview mode. Set by fiat on Overview enter, so
324     /// this is NOT always `camera::is_overview(zoom)` — an overview fit can
325     /// land at zoom 1.
326     pub overview: bool,
327     /// Pending pan-animation targets, if the camera is mid-ease.
328     pub pan_target_x: Option<f64>,
329     pub pan_target_y: Option<f64>,
330     /// First enabled output's extent — the legacy "viewport" for keyed zooms
331     /// and View jumps (the multi-output quirk, preserved by construction).
332     pub viewport_w: f64,
333     pub viewport_h: f64,
334     /// Output box under the cursor, falling back to the first enabled
335     /// output: the viewport Overview enters/exits in.
336     pub cursor_viewport: Rect,
337     /// False when there is no seat; the cursor fields then hold zeros.
338     pub has_cursor: bool,
339     pub cursor_x: f64,
340     pub cursor_y: f64,
341     /// Non-status, non-background window under the cursor.
342     pub hovered: Option<WindowId>,
343     pub focused: Option<WindowId>,
344     /// Desktop grid periods (cell size + gap width, per axis) for
345     /// cell-aligned panning: keyed horizontal pans step `grid_period_x`,
346     /// vertical pans `grid_period_y`.
347     pub grid_period_x: f64,
348     pub grid_period_y: f64,
349     pub windows: Vec<ActionWindow>,
350 }
351 
352 /// A window as `Policy::action` sees it.
353 #[derive(Debug, Clone)]
354 pub struct ActionWindow {
355     pub id: WindowId,
356     pub app_id: Option<String>,
357     pub title: Option<String>,
358     /// state == Mapped (narrower than `visible`, which also spans teardown).
359     pub mapped: bool,
360     /// Virtual-space position. `w`/`h` are the mechanism's working extent in
361     /// output px (box_geom, defaulted to 800x600 while unmapped).
362     pub x: f64,
363     pub y: f64,
364     pub w: f64,
365     pub h: f64,
366     /// Per-window output scale; the virtual footprint is `w * scale`.
367     pub scale: f64,
368     /// The window's own tiling mode (as set, before viewport resolution).
369     pub mode: TilingMode,
370     /// The mode the window resolves to through the viewport-mode rules
371     /// (mechanism's `get_mode_for_window`) — what leaving Fullscreen
372     /// falls back to.
373     pub resolved_mode: TilingMode,
374     /// The mode and lock the window had when a `SetWindowMode` sent it
375     /// Fullscreen — what leaving Fullscreen restores, so a Tiled window
376     /// comes back Tiled. `None` when it is not Fullscreen, or got there by
377     /// another route (a client request, a rule); the exit then falls back
378     /// to `resolved_mode`.
379     pub pre_fullscreen: Option<(TilingMode, bool)>,
380     /// Mapped and not in Closing/Init teardown/startup.
381     pub visible: bool,
382     /// In the focus-cycling set: currently rendered, not minimized, not a
383     /// status bar.
384     pub focus_cyclable: bool,
385     /// Participates in the overview fit: mapped, not minimized, not
386     /// status/background, not popup/overlay.
387     pub overview_eligible: bool,
388 }
389 
390 /// One mechanism write, returned by policy decisions and applied in order —
391 /// the command-stream counterpart of the arrange plan.
392 #[derive(Debug, Clone, PartialEq)]
393 pub enum Command {
394     /// Spawn a command line (the mechanism forks `sh -c`).
395     Spawn(String),
396     /// Write the camera. `overview: None` leaves the mode untouched.
397     /// `animate: true` eases pan and zoom toward the target (the overview
398     /// enter/exit transition); `false` snaps and cancels any easing in
399     /// flight. The mode flip itself always applies immediately.
400     SetCamera { camera: Camera, overview: Option<bool>, animate: bool },
401     /// Set pan-animation targets (a `None` axis is left alone) and start
402     /// easing toward them.
403     PanTo { x: Option<f64>, y: Option<f64> },
404     StopPanAnimation,
405     Focus(WindowId),
406     /// Focus whichever visible window the mechanism's next-visible rule
407     /// picks — the refocus step after closing/minimizing the focused window.
408     FocusNextVisible,
409     /// Raise a window to the top of the stacking order.
410     Raise(WindowId),
411     /// Ask the window to close (and let teardown proceed).
412     CloseWindow(WindowId),
413     SetMinimized { id: WindowId, minimized: bool },
414     /// Set a window's tiling mode; `locked` pins it against viewport-mode
415     /// resolution.
416     SetWindowMode { id: WindowId, mode: TilingMode, locked: bool },
417     /// Dock the overlay column on the given side.
418     SetOverlayPosition(OverlaySide),
419     /// Reposition a window in virtual space.
420     MoveWindow { id: WindowId, x: f64, y: f64 },
421     /// Full re-arrange (the mechanism's `dirty_windowing`).
422     Relayout,
423     /// Camera-only refresh: the fast viewport path when the WM is idle.
424     RefreshCamera,
425 }
426 
427 /// Decisions, policy-side. Implemented by `actions::DefaultPolicy`.
428 pub trait Policy {
429     /// Decide a user action against the snapshot; `arg` is the binding's
430     /// command string (Spawn/Toggle carry one; media-key actions may carry
431     /// an override of their stock command). An empty vec means "not
432     /// mine" — the mechanism falls through to its remaining legacy arms.
433     fn action(&mut self, ctx: &ActionCtx, action: Action, arg: Option<&str>) -> Vec<Command>;
434 }
435 
436 /// Execution, mechanism-side. Implemented by the compositor's WindowManager.
437 pub trait Compositor {
438     fn apply(&mut self, cmd: &Command);
439 }