git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

src/server/output.rs (84.5K)

   1 // SPDX-FileCopyrightText: © 2020 The River Developers
   2 // SPDX-License-Identifier: GPL-3.0-only
   3 
   4 use crate::ffi;
   5 use crate::server::{Server, WlListener, WlList, wl_signal_add, wl_listener_remove, wl_list_insert, wl_list_remove};
   6 use crate::layer_shell::LayerShellOutput;
   7 use crate::lock_manager::LockState;
   8 use crate::util;
   9 
  10 #[derive(Debug, Clone, Copy, PartialEq)]
  11 pub enum OutputStateValue {
  12     Enabled,
  13     DisabledSoft,
  14     DisabledHard,
  15     Destroying,
  16 }
  17 
  18 #[derive(Debug, Clone, Copy)]
  19 pub enum OutputMode {
  20     Standard(*mut ffi::wlr_output_mode),
  21     Custom {
  22         width: i32,
  23         height: i32,
  24         refresh: i32,
  25     },
  26     None,
  27 }
  28 
  29 #[derive(Debug, Clone, Copy)]
  30 pub struct OutputState {
  31     pub state: OutputStateValue,
  32     pub x: i32,
  33     pub y: i32,
  34     pub mode: OutputMode,
  35     pub scale: f32,
  36     pub transform: ffi::wl_output_transform,
  37     pub adaptive_sync: bool,
  38     pub auto_layout: bool,
  39 }
  40 
  41 impl OutputState {
  42     pub fn mode_none(&self) -> bool {
  43         matches!(self.mode, OutputMode::None)
  44     }
  45 
  46     pub unsafe fn from_head_state(state: *const ffi::wlr_output_head_v1_state) -> Self {
  47         assert!((*state).enabled);
  48         let mode = if !(*state).mode.is_null() {
  49             OutputMode::Standard((*state).mode)
  50         } else {
  51             OutputMode::Custom {
  52                 width: (*state).custom_mode.width,
  53                 height: (*state).custom_mode.height,
  54                 refresh: (*state).custom_mode.refresh,
  55             }
  56         };
  57 
  58         // Round to nearest 1/120 to ensure the scale is exactly represented
  59         // in the fractional-scale-v1 protocol.
  60         let scale = ((*state).scale * 120.0).round() / 120.0;
  61 
  62         Self {
  63             state: OutputStateValue::Enabled,
  64             mode,
  65             x: (*state).x,
  66             y: (*state).y,
  67             scale,
  68             transform: (*state).transform,
  69             adaptive_sync: (*state).adaptive_sync_enabled,
  70             auto_layout: false,
  71         }
  72     }
  73 
  74     pub unsafe fn dimensions(&self) -> (i32, i32) {
  75         let (mut w, mut h) = match self.mode {
  76             OutputMode::Standard(mode) => ((*mode).width, (*mode).height),
  77             OutputMode::Custom { width, height, .. } => (width, height),
  78             OutputMode::None => (0, 0),
  79         };
  80         if (self.transform as u32) % 2 != 0 {
  81             std::mem::swap(&mut w, &mut h);
  82         }
  83         (
  84             ((w as f32) / self.scale) as i32,
  85             ((h as f32) / self.scale) as i32,
  86         )
  87     }
  88 
  89     pub unsafe fn box_layout(&self) -> ffi::wlr_box {
  90         let (w, h) = self.dimensions();
  91         ffi::wlr_box {
  92             x: self.x,
  93             y: self.y,
  94             width: w,
  95             height: h,
  96         }
  97     }
  98 
  99     pub unsafe fn apply_no_modeset(&self, wlr_state: *mut ffi::wlr_output_state) {
 100         ffi::wlr_output_state_set_scale(wlr_state, self.scale);
 101         ffi::wlr_output_state_set_transform(wlr_state, self.transform);
 102     }
 103 
 104     pub unsafe fn apply_modeset(&self, wlr_state: *mut ffi::wlr_output_state) {
 105         let enabled = self.state == OutputStateValue::Enabled;
 106         ffi::wlr_output_state_set_enabled(wlr_state, enabled);
 107         if !enabled {
 108             return;
 109         }
 110         self.apply_no_modeset(wlr_state);
 111         match self.mode {
 112             OutputMode::Standard(mode) => {
 113                 ffi::wlr_output_state_set_mode(wlr_state, mode);
 114             }
 115             OutputMode::Custom { width, height, refresh } => {
 116                 ffi::wlr_output_state_set_custom_mode(wlr_state, width, height, refresh);
 117             }
 118             OutputMode::None => {}
 119         }
 120         ffi::wlr_output_state_set_adaptive_sync_enabled(wlr_state, self.adaptive_sync);
 121     }
 122 }
 123 
 124 #[derive(Debug, Clone, Copy, PartialEq)]
 125 pub enum LockRenderState {
 126     PendingUnlock,
 127     Unlocked,
 128     PendingBlank,
 129     Blanked,
 130     PendingLockSurface,
 131     LockSurface,
 132 }
 133 
 134 #[derive(Debug, Clone, Copy)]
 135 pub struct RenderingState {
 136     pub tearing: bool,
 137 }
 138 
 139 /// One cached window-content reading — see `Output::status_win_samples`.
 140 pub struct StatusWinSample {
 141     /// The occluding window's slotmap index.
 142     pub win: u32,
 143     /// The region sampled, in layout px. Part of the key: a segment that
 144     /// moves, or a window that slides, is looking at different pixels.
 145     pub region: crate::backdrop::Rect,
 146     /// When the readback actually ran.
 147     pub at: std::time::Instant,
 148     /// Commit sequences of the window's surfaces, summed. A window that has
 149     /// not committed cannot have changed what it is showing, so this is what
 150     /// keeps a still terminal from being re-read four times a second forever.
 151     pub seq: u32,
 152     /// None when the content could not be read at all.
 153     pub sample: Option<crate::backdrop::BackdropSample>,
 154 }
 155 
 156 pub struct Output {
 157     pub server: *mut Server,
 158     pub wlr_output: *mut ffi::wlr_output,
 159     pub scene_output: *mut ffi::wlr_scene_output,
 160     pub background_rect: *mut ffi::wlr_scene_rect,
 161     pub grid_tree: *mut ffi::wlr_scene_tree,
 162     /// The grid's backdrop (gap colour, or the Solid spec's colour), kept in
 163     /// its own tree under `scene.layers.background_clients` so a client
 164     /// background surface paints over it while the cells in `grid_tree` stay
 165     /// on top. Positioned in lockstep with `grid_tree`.
 166     pub grid_backdrop_tree: *mut ffi::wlr_scene_tree,
 167     pub grid_backdrop_rect: *mut ffi::wlr_scene_rect,
 168     pub adjust_tree: *mut ffi::wlr_scene_tree,
 169     pub adjust_rects: Vec<*mut ffi::wlr_scene_rect>,
 170     pub last_adjust_mode: bool,
 171     pub object: *mut ffi::wl_resource, // zcce_output_v1 resource
 172     pub layer_shell: LayerShellOutput,
 173     pub lock_render_state: LockRenderState,
 174     pub link: ffi::wl_list,
 175     pub link_sent: ffi::wl_list,
 176     pub scheduled: OutputState,
 177     pub sent: OutputState,
 178     pub current: OutputState,
 179     pub sent_wl_output: bool,
 180     pub rendering_requested: RenderingState,
 181     pub rendering_current: RenderingState,
 182 
 183     // Cached grid parameters to avoid redrawing when unchanged
 184     /// Camera state at this output's last rendered frame; any difference
 185     /// forces a full-output repaint (see the frame chokepoint).
 186     pub last_rendered_pan_x: f64,
 187     pub last_rendered_pan_y: f64,
 188     pub last_rendered_zoom: f64,
 189     /// Presentation clock, from the `present` event: when the last frame
 190     /// turned into light (CLOCK_MONOTONIC ns, 0 = never), its vblank
 191     /// sequence number (0 = the backend has none), and the refresh period.
 192     /// `predicted_present_ns` derives the camera's frame clock from these.
 193     pub present_when_ns: u64,
 194     pub present_seq: u32,
 195     pub present_refresh_ns: u64,
 196     /// Running count of vblanks skipped between consecutive presents —
 197     /// the dropped-frame counter `CCE_FRAME_DEBUG` reports.
 198     pub present_dropped: u64,
 199     /// Phase of the frame clock's vblank grid (ns within a refresh period),
 200     /// locked to the presentation times; `u64::MAX` until the first frame.
 201     pub frame_phase_ns: u64,
 202     /// What the status-backdrop measurement last ran against: the window
 203     /// manager's layout epoch, the camera, and when. It re-runs only when one
 204     /// of those moved or `BACKDROP_REFRESH` has passed (for window content
 205     /// under a segment), not on every vblank.
 206     pub backdrop_epoch: u64,
 207     pub backdrop_cam: (f64, f64, f64),
 208     pub backdrop_measured_at: Option<std::time::Instant>,
 209     /// A tearing page-flip test failed for the current tearing episode; do
 210     /// not repeat the atomic TEST_ONLY commit every frame. Cleared when the
 211     /// fullscreen client's tearing request goes away.
 212     pub tearing_test_failed: bool,
 213     /// Darkened by the idle timeout (`IdleManager::set_displays`), so the
 214     /// next activity wakes this one and leaves a client-darkened output alone.
 215     pub idle_off: bool,
 216     pub last_grid_viewport_w: i32,
 217     pub last_grid_viewport_h: i32,
 218     pub last_grid_zoom: f64,
 219     /// The spec the pool was last drawn from; a change (or viewport/zoom
 220     /// change) forces a redraw. Pan alone never redraws — it only moves the
 221     /// grid tree.
 222     pub last_grid_spec: Option<crate::policy::api::BackgroundSpec>,
 223     pub grid_rect_pool: Vec<*mut ffi::wlr_scene_rect>,
 224     /// Lit-chamfer rims over the grid cells — the same scenefx bevel node
 225     /// the windows use, so the grid lines read as raised rails descending
 226     /// into each cell through a shaded fillet that wraps the corner arcs.
 227     /// Pooled like `grid_rect_pool`, but in their own subtree kept above
 228     /// the rects: pool reuse must never stack a rim beneath a
 229     /// later-created cell rect.
 230     pub grid_bevel_pool: Vec<*mut ffi::wlr_scene_bevel>,
 231     pub grid_bevel_tree: *mut ffi::wlr_scene_tree,
 232     /// The cell labels' own subtree inside `grid_tree`, raised above the
 233     /// cell rects and bevels on every draw: label nodes used to be direct
 234     /// children of the grid tree, so any cell rect the pool created after
 235     /// them stacked on top and hid them.
 236     pub cell_label_tree: *mut ffi::wlr_scene_tree,
 237     /// Bevel params the rims were last drawn with (enabled, thickness,
 238     /// light x/y/intensity, shade, shoulder as bits) — the spec alone does
 239     /// not cover them, and a live config reload must redraw the rims too.
 240     pub last_grid_bevel: Option<[u32; 7]>,
 241     pub grid_force_redraw_frames: u8,
 242     /// Scene nodes for the per-square chess-style labels (overview only), and
 243     /// the rasterized glyph buffers behind them. Pooled exactly like
 244     /// `grid_rect_pool`: reused across frames, disabled past the live count.
 245     /// Each entry remembers the buffer it currently shows, because
 246     /// `wlr_scene_buffer_set_buffer` damages the node unconditionally — even
 247     /// when handed the buffer already on it — and these are re-walked every
 248     /// frame while the overview camera moves.
 249     pub cell_label_pool: Vec<(*mut ffi::wlr_scene_buffer, *mut ffi::wlr_buffer)>,
 250     pub cell_labels: crate::text::LabelCache,
 251     /// Label point size actually in use, so a zoom change can re-rasterize.
 252     pub last_label_px: u32,
 253     /// Throttle+cache for the window-content half of the backdrop measurement.
 254     /// Unlike the grid half, which is arithmetic, this one costs a texture
 255     /// readback and a GPU sync, so it is re-taken at most every
 256     /// `WIN_SAMPLE_MS` per segment AND only when the window has actually
 257     /// committed something since. A window whose content changes faster than
 258     /// that is not something the text contrast should be chasing anyway.
 259     pub status_win_samples: Vec<StatusWinSample>,
 260 
 261     pub destroy: ffi::wl_listener,
 262     pub request_state: ffi::wl_listener,
 263     pub frame: ffi::wl_listener,
 264     pub present: ffi::wl_listener,
 265 }
 266 
 267 unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
 268     let output = ffi::wl_resource_get_user_data(resource) as *mut Output;
 269     if !output.is_null() {
 270         if (*output).object != resource {
 271             return;
 272         }
 273         (*output).object = std::ptr::null_mut();
 274         (*output).sent_wl_output = false;
 275     }
 276 }
 277 
 278 unsafe extern "C" fn output_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
 279     ffi::wl_resource_destroy(resource);
 280 }
 281 
 282 unsafe extern "C" fn output_set_presentation_mode(
 283     _client: *mut ffi::wl_client,
 284     resource: *mut ffi::wl_resource,
 285     mode: u32,
 286 ) {
 287     let output = ffi::wl_resource_get_user_data(resource) as *mut Output;
 288     if output.is_null() {
 289         return;
 290     }
 291     if !(*(*output).server).wm.ensure_rendering() {
 292         return;
 293     }
 294     match mode {
 295         ffi::zcce_output_v1_presentation_mode_ZCCE_OUTPUT_V1_PRESENTATION_MODE_VSYNC => {
 296             (*output).rendering_requested.tearing = false;
 297         }
 298         ffi::zcce_output_v1_presentation_mode_ZCCE_OUTPUT_V1_PRESENTATION_MODE_ASYNC => {
 299             (*output).rendering_requested.tearing = true;
 300         }
 301         _ => {
 302             ffi::wl_resource_post_error(
 303                 resource,
 304                 ffi::zcce_output_v1_error_ZCCE_OUTPUT_V1_ERROR_INVALID_PRESENTATION_MODE,
 305                 b"invalid presentation mode enum value\0".as_ptr() as *const _,
 306             );
 307         }
 308     }
 309 }
 310 
 311 static OUTPUT_INTERFACE: ffi::zcce_output_v1_interface = ffi::zcce_output_v1_interface {
 312     destroy: Some(output_destroy),
 313     set_presentation_mode: Some(output_set_presentation_mode),
 314 };
 315 
 316 static INERT_OUTPUT_INTERFACE: ffi::zcce_output_v1_interface = ffi::zcce_output_v1_interface {
 317     destroy: Some(output_destroy),
 318     set_presentation_mode: None,
 319 };
 320 
 321 impl Output {
 322     pub unsafe fn make_inert(&mut self) {
 323         if !self.object.is_null() {
 324             ffi::wl_resource_post_event(self.object, 0); // zcce_output.removed
 325             ffi::wl_resource_set_implementation(
 326                 self.object,
 327                 &INERT_OUTPUT_INTERFACE as *const _ as *const _,
 328                 std::ptr::null_mut(),
 329                 None,
 330             );
 331             self.layer_shell.make_inert();
 332             self.object = std::ptr::null_mut();
 333             self.sent_wl_output = false;
 334             self.grid_rect_pool.clear();
 335             self.grid_bevel_pool.clear();
 336             self.grid_bevel_tree = std::ptr::null_mut();
 337             self.cell_label_pool.clear();
 338             self.cell_label_tree = std::ptr::null_mut();
 339         }
 340     }
 341 
 342     pub unsafe fn manage_start(&mut self) {
 343         match self.scheduled.state {
 344             OutputStateValue::Enabled | OutputStateValue::DisabledSoft => {
 345                 assert!(!self.scheduled.mode_none());
 346                 let wlr_output = self.wlr_output;
 347 
 348                 let self_ptr = self as *mut Output;
 349                 let layer_shell_ptr = &mut self.layer_shell as *mut LayerShellOutput;
 350                 (*layer_shell_ptr).manage_start(self_ptr);
 351 
 352                 let wm_v1 = (*self.server).wm.object;
 353                 if !wm_v1.is_null() {
 354                     let new = self.object.is_null();
 355                     let output_v1 = if new {
 356                         let client = ffi::wl_resource_get_client(wm_v1);
 357                         let res = ffi::wl_resource_create(
 358                             client,
 359                             &ffi::zcce_output_v1_interface,
 360                             ffi::wl_resource_get_version(wm_v1),
 361                             0,
 362                         );
 363                         if res.is_null() {
 364                             log::error!("out of memory");
 365                             return;
 366                         }
 367                         self.object = res;
 368                         ffi::wl_resource_set_implementation(
 369                             res,
 370                             &OUTPUT_INTERFACE as *const _ as *const _,
 371                             self as *mut Output as *mut _,
 372                             Some(handle_destroy_resource),
 373                         );
 374                         ffi::wl_resource_post_event(wm_v1, ffi::ZCCE_WINDOW_MANAGER_V1_OUTPUT, res); // zcce_window_manager_v1.output
 375                         res
 376                     } else {
 377                         self.object
 378                     };
 379 
 380                     if !self.sent_wl_output {
 381                         let global = ffi::river_wlr_output_get_global(wlr_output);
 382                         if !global.is_null() {
 383                             let client = ffi::wl_resource_get_client(output_v1);
 384                             let wl_output_name = ffi::wl_global_get_name(global, client);
 385                             zcce_output_send_wl_output(output_v1, wl_output_name);
 386                             self.sent_wl_output = true;
 387                         }
 388                     }
 389 
 390                     let (scheduled_width, scheduled_height) = self.scheduled.dimensions();
 391                     let (sent_width, sent_height) = self.sent.dimensions();
 392 
 393                     if new || scheduled_width != sent_width || scheduled_height != sent_height {
 394                         zcce_output_send_dimensions(output_v1, scheduled_width, scheduled_height);
 395                     }
 396                     if new || self.scheduled.x != self.sent.x || self.scheduled.y != self.sent.y {
 397                         zcce_output_send_position(output_v1, self.scheduled.x, self.scheduled.y);
 398                     }
 399                 }
 400 
 401                 self.sent = self.scheduled;
 402 
 403                 wl_list_remove(&mut self.link_sent as *mut ffi::wl_list as *mut WlList);
 404                 let sent_outputs = &mut (*self.server).wm.sent.outputs as *mut ffi::wl_list as *mut WlList;
 405                 wl_list_insert((*sent_outputs).prev, &mut self.link_sent as *mut ffi::wl_list as *mut WlList);
 406             }
 407             OutputStateValue::DisabledHard | OutputStateValue::Destroying => {
 408                 self.make_inert();
 409 
 410                 self.sent = self.scheduled;
 411 
 412                 if self.scheduled.state == OutputStateValue::Destroying {
 413                     assert!(self.wlr_output.is_null());
 414                     
 415                     if !self.background_rect.is_null() {
 416                         ffi::wlr_scene_node_destroy(self.background_rect as *mut ffi::wlr_scene_node);
 417                         self.background_rect = std::ptr::null_mut();
 418                     }
 419 
 420                     if !self.grid_tree.is_null() {
 421                         ffi::wlr_scene_node_destroy(self.grid_tree as *mut ffi::wlr_scene_node);
 422                         self.grid_tree = std::ptr::null_mut();
 423                     }
 424                     if !self.grid_backdrop_tree.is_null() {
 425                         ffi::wlr_scene_node_destroy(self.grid_backdrop_tree as *mut ffi::wlr_scene_node);
 426                         self.grid_backdrop_tree = std::ptr::null_mut();
 427                         self.grid_backdrop_rect = std::ptr::null_mut();
 428                     }
 429                     self.grid_rect_pool.clear();
 430                     // The bevel and label subtrees died with grid_tree above.
 431                     self.grid_bevel_pool.clear();
 432                     self.grid_bevel_tree = std::ptr::null_mut();
 433                     self.cell_label_pool.clear();
 434                     self.cell_label_tree = std::ptr::null_mut();
 435 
 436                     if !self.adjust_tree.is_null() {
 437                         ffi::wlr_scene_node_destroy(self.adjust_tree as *mut ffi::wlr_scene_node);
 438                         self.adjust_tree = std::ptr::null_mut();
 439                     }
 440                     self.adjust_rects.clear();
 441 
 442                     // remove output from windows fullscreen hint
 443                     for &window in (*self.server).wm.windows.iter() {
 444                         if let crate::window::FullscreenRequest::Fullscreen(out) = (*window).wm_scheduled.fullscreen_requested {
 445                             if out == self as *mut Output {
 446                                 (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Fullscreen(std::ptr::null_mut());
 447                             }
 448                         }
 449                         if (*window).wm_requested.fullscreen == self as *mut Output {
 450                             (*window).wm_requested.fullscreen = std::ptr::null_mut();
 451                         }
 452                     }
 453 
 454                     wl_list_remove(&mut self.link as *mut ffi::wl_list as *mut WlList);
 455                     wl_list_remove(&mut self.link_sent as *mut ffi::wl_list as *mut WlList);
 456 
 457                     let _ = Box::from_raw(self as *mut Output);
 458                 }
 459             }
 460         }
 461     }
 462 
 463     pub unsafe fn create(server: *mut Server, wlr_output: *mut ffi::wlr_output) -> Result<(), &'static str> {
 464         let title = format!("river - {}\0", std::ffi::CStr::from_ptr(ffi::river_wlr_output_get_name(wlr_output)).to_string_lossy());
 465         
 466         // Check if output is Wayland/X11 and set application title/app_id
 467         if ffi::wlr_output_is_wl(wlr_output) {
 468             ffi::wlr_wl_output_set_app_id(wlr_output, "river\0".as_ptr() as *const _);
 469             ffi::wlr_wl_output_set_title(wlr_output, title.as_ptr() as *const _);
 470         } else if ffi::wlr_output_is_x11(wlr_output) {
 471             ffi::wlr_x11_output_set_title(wlr_output, title.as_ptr() as *const _);
 472         }
 473 
 474         if !ffi::wlr_output_init_render(wlr_output, (*server).allocator, (*server).renderer) {
 475             return Err("Failed to initialize renderer for output");
 476         }
 477 
 478         let scene_output = ffi::wlr_scene_output_create((*server).scene.wlr_scene, wlr_output);
 479         if scene_output.is_null() {
 480             return Err("Failed to create wlr_scene_output");
 481         }
 482 
 483         let name_raw = ffi::river_wlr_output_get_name(wlr_output);
 484         let name = std::ffi::CStr::from_ptr(name_raw).to_string_lossy();
 485         let scale_key = format!("scale_{}", name);
 486         let output_scale = (*server).wm.display.get(&scale_key)
 487             .map(|&s| s as f32)
 488             .unwrap_or((*server).wm.output_scale);
 489 
 490         // The physical size every client's wl_output geometry will carry,
 491         // which cce-ui's `units::Metric` measures logical px per mm against.
 492         // A configured `size_mm` replaces the EDID figure before the global
 493         // exists (wlr_output_layout_add creates it), so no client ever sees
 494         // the lie. Logged either way: an output with no size at all leaves
 495         // clients on the assumed 96 ppi, and that is worth knowing.
 496         let (mut edid_w, mut edid_h) = (0i32, 0i32);
 497         ffi::river_wlr_output_get_phys_size(wlr_output, &mut edid_w, &mut edid_h);
 498         let configured = (*server).wm.display.get(&format!("mm_w_{}", name))
 499             .zip((*server).wm.display.get(&format!("mm_h_{}", name)))
 500             .map(|(&w, &h)| (w.round() as i32, h.round() as i32));
 501         match configured {
 502             Some((w, h)) => {
 503                 ffi::river_wlr_output_set_phys_size(wlr_output, w, h);
 504                 log::info!("output {}: physical size {}x{} mm (configured size_mm; EDID said {}x{})", name, w, h, edid_w, edid_h);
 505             }
 506             None if edid_w > 0 && edid_h > 0 => {
 507                 log::info!("output {}: physical size {}x{} mm (EDID)", name, edid_w, edid_h);
 508             }
 509             None => {
 510                 log::info!("output {}: no physical size — clients assume 96 ppi; set `output {{ {} size_mm=\"WxH\" }}` to measure", name, name);
 511             }
 512         }
 513 
 514         let initial = OutputState {
 515             state: OutputStateValue::DisabledHard,
 516             x: 0,
 517             y: 0,
 518             mode: OutputMode::None,
 519             scale: output_scale,
 520             transform: ffi::wl_output_transform_WL_OUTPUT_TRANSFORM_NORMAL,
 521             adaptive_sync: ffi::river_wlr_output_get_adaptive_sync_status(wlr_output) == ffi::wlr_output_adaptive_sync_status_WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED,
 522             auto_layout: true,
 523         };
 524 
 525         let output = Box::new(Output {
 526             server,
 527             wlr_output,
 528             scene_output,
 529             background_rect: std::ptr::null_mut(),
 530             grid_tree: std::ptr::null_mut(),
 531             grid_backdrop_tree: std::ptr::null_mut(),
 532             grid_backdrop_rect: std::ptr::null_mut(),
 533             adjust_tree: std::ptr::null_mut(),
 534             adjust_rects: Vec::new(),
 535             last_adjust_mode: false,
 536             object: std::ptr::null_mut(),
 537             layer_shell: LayerShellOutput::default(),
 538             lock_render_state: LockRenderState::Blanked,
 539             link: std::mem::zeroed(),
 540             link_sent: std::mem::zeroed(),
 541             scheduled: initial,
 542             sent: initial,
 543             current: initial,
 544             sent_wl_output: false,
 545             rendering_requested: RenderingState { tearing: false },
 546             rendering_current: RenderingState { tearing: false },
 547             last_rendered_pan_x: f64::NAN,
 548             last_rendered_pan_y: f64::NAN,
 549             last_rendered_zoom: f64::NAN,
 550             present_when_ns: 0,
 551             present_seq: 0,
 552             present_refresh_ns: 0,
 553             present_dropped: 0,
 554             frame_phase_ns: u64::MAX,
 555             backdrop_epoch: u64::MAX,
 556             backdrop_cam: (f64::NAN, f64::NAN, f64::NAN),
 557             backdrop_measured_at: None,
 558             tearing_test_failed: false,
 559             idle_off: false,
 560             last_grid_viewport_w: 0,
 561             last_grid_viewport_h: 0,
 562             last_grid_zoom: 0.0,
 563             last_grid_spec: None,
 564             grid_rect_pool: Vec::new(),
 565             grid_bevel_pool: Vec::new(),
 566             grid_bevel_tree: std::ptr::null_mut(),
 567             cell_label_tree: std::ptr::null_mut(),
 568             last_grid_bevel: None,
 569             grid_force_redraw_frames: 0,
 570             cell_label_pool: Vec::new(),
 571             cell_labels: Default::default(),
 572             last_label_px: 0,
 573             status_win_samples: Vec::new(),
 574             destroy: std::mem::zeroed(),
 575             request_state: std::mem::zeroed(),
 576             frame: std::mem::zeroed(),
 577             present: std::mem::zeroed(),
 578         });
 579 
 580         let raw = Box::into_raw(output);
 581         ffi::river_wlr_output_set_data(wlr_output, raw as *mut std::ffi::c_void);
 582 
 583         let list_head = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
 584         let link_custom = &mut (*raw).link as *mut ffi::wl_list as *mut WlList;
 585         wl_list_insert((*list_head).prev, link_custom);
 586         
 587         ffi::wl_list_init(&mut (*raw).link_sent);
 588 
 589         // Add event listeners
 590         let d_listener = &mut (*raw).destroy as *mut ffi::wl_listener as *mut WlListener;
 591         (*d_listener).notify = Some(handle_destroy);
 592         wl_signal_add(ffi::river_wlr_output_get_destroy_signal(wlr_output), &mut (*raw).destroy);
 593 
 594         let req_listener = &mut (*raw).request_state as *mut ffi::wl_listener as *mut WlListener;
 595         (*req_listener).notify = Some(handle_request_state);
 596         wl_signal_add(ffi::river_wlr_output_get_request_state_signal(wlr_output), &mut (*raw).request_state);
 597 
 598         let frame_listener = &mut (*raw).frame as *mut ffi::wl_listener as *mut WlListener;
 599         (*frame_listener).notify = Some(handle_frame);
 600         wl_signal_add(ffi::river_wlr_output_get_frame_signal(wlr_output), &mut (*raw).frame);
 601 
 602         let pres_listener = &mut (*raw).present as *mut ffi::wl_listener as *mut WlListener;
 603         (*pres_listener).notify = Some(handle_present);
 604         wl_signal_add(ffi::river_wlr_output_get_present_signal(wlr_output), &mut (*raw).present);
 605 
 606         (*raw).scheduled.state = OutputStateValue::Enabled;
 607         let preferred = ffi::wlr_output_preferred_mode(wlr_output);
 608         if !preferred.is_null() {
 609             (*raw).scheduled.mode = OutputMode::Standard(preferred);
 610         } else {
 611             (*raw).scheduled.mode = OutputMode::Custom { width: 1280, height: 720, refresh: 0 };
 612         }
 613         
 614         (*server).wm.dirty_windowing();
 615         Ok(())
 616     }
 617 
 618     pub unsafe fn render_and_commit(&mut self) -> Result<(), &'static str> {
 619         // Update grid node positions and parameters first, which marks the scene output as damaged if changed
 620         self.draw_grid();
 621         self.draw_adjust_overlay();
 622         // Right after draw_grid, whose geometry this reuses, and BEFORE the
 623         // needs-frame early-out: a camera move slides the lattice under a
 624         // segment that has no damage of its own, and the bar still has to
 625         // hear about it — once the camera has SETTLED. During the motion
 626         // itself the measurement is skipped: the sliding lattice changed the
 627         // quantized reading on most frames (a push and a bar repaint each),
 628         // and a window under a segment moved its sampled region every frame,
 629         // defeating the per-(window, region) readback cache — a GPU texture
 630         // readback inside the render path, per pan frame. The settle's full
 631         // repaint frame runs the measurement with the final camera.
 632         //
 633         // And not on every frame either: the desktop half of the reading is
 634         // pure geometry, which only moves with a transaction (`layout_epoch`)
 635         // or the camera; the window-content half is throttled inside
 636         // `window_backdrop_sample` at `BACKDROP_REFRESH` anyway. Running the
 637         // walk — Vecs, a String per segment, the O(segments × windows) scan,
 638         // then `update_status` rebuilding and comparing the whole status
 639         // snapshot — every vblank was the largest steady CPU cost of an idle
 640         // desktop with anything animating on it.
 641         {
 642             let wm = &(*self.server).wm;
 643             if !wm.viewport_is_active {
 644                 let cam = (wm.desk_pan_x, wm.desk_pan_y, wm.desk_zoom);
 645                 let due = self.backdrop_epoch != wm.layout_epoch
 646                     || self.backdrop_cam != cam
 647                     || self.backdrop_measured_at.map_or(true, |t| t.elapsed() >= BACKDROP_REFRESH);
 648                 if due {
 649                     self.backdrop_epoch = wm.layout_epoch;
 650                     self.backdrop_cam = cam;
 651                     self.backdrop_measured_at = Some(std::time::Instant::now());
 652                     self.measure_status_backdrops();
 653                 }
 654             }
 655         }
 656 
 657         // A parked `ccectl screenshot` targeting this output forces a render
 658         // even without damage so there is a fresh buffer to read back.
 659         let pending_shot = {
 660             let wm = &mut (*self.server).wm;
 661             if wm
 662                 .pending_screenshot
 663                 .as_ref()
 664                 .map(|s| s.output == self as *mut Output)
 665                 .unwrap_or(false)
 666             {
 667                 wm.pending_screenshot.take()
 668             } else {
 669                 None
 670             }
 671         };
 672 
 673         // One chokepoint for every camera-mutation path (wheel zoom, IPC,
 674         // keyed actions, edge-pan, the pan animation — whichever of
 675         // update_viewport_local or the manage transaction carried it): if
 676         // the camera changed since this output last rendered, per-node
 677         // damage under-reports the whole-screen relayout (stale slivers of
 678         // the previous zoom survive wherever idle content used to be), so
 679         // force a full repaint. Must run BEFORE the needs-frame early-out —
 680         // a camera change with no other pending damage would otherwise skip
 681         // the frame entirely.
 682         {
 683             // Quantized to screen pixels: a sub-pixel pan moves no node
 684             // (see `update_viewport_local`), so it is not a reason to paint.
 685             let wm = &(*self.server).wm;
 686             let q = wm.desk_zoom * self.current.scale as f64;
 687             let cam = ((wm.desk_pan_x * q).round(), (wm.desk_pan_y * q).round(), wm.desk_zoom);
 688             if cam != (self.last_rendered_pan_x, self.last_rendered_pan_y, self.last_rendered_zoom) {
 689                 self.last_rendered_pan_x = cam.0;
 690                 self.last_rendered_pan_y = cam.1;
 691                 self.last_rendered_zoom = cam.2;
 692                 if !self.scene_output.is_null() {
 693                     ffi::river_scene_output_damage_whole(self.scene_output);
 694                 }
 695             }
 696         }
 697 
 698         if pending_shot.is_none() && !ffi::wlr_scene_output_needs_frame(self.scene_output) {
 699             return Ok(());
 700         }
 701 
 702         // Re-apply scale to all windows whose scale is not 1.0 right before rendering
 703         let wm = &(*self.server).wm;
 704         for &window in wm.windows.iter() {
 705             if !window.is_null() && ((*window).scale != 1.0 || (*window).x11_buffer_scale() != 1.0) {
 706                 (*window).scale_only_render_finish();
 707             }
 708         }
 709         for &or in wm.override_redirects.iter() {
 710             if !or.is_null() {
 711                 (*or).apply_x11_scale();
 712             }
 713         }
 714 
 715         // Overview-delay debugging: with `CCE_OVDBG=1` in the compositor's
 716         // environment, while /tmp/cce-ovdbg exists (contents = comma-separated
 717         // app_id substrings), dump the scene-side truth for matching windows
 718         // every rendered frame. Toggle live with
 719         // `echo firefox,cce-calendar > /tmp/cce-ovdbg`; `rm` to stop. The env
 720         // gate is what keeps a release build from doing an open()+read() of
 721         // that path on every frame it ever renders.
 722         if ovdbg_enabled() {
 723         if let Ok(filter) = std::fs::read_to_string("/tmp/cce-ovdbg") {
 724             let pats: Vec<&str> = filter.trim().split(',').filter(|p| !p.is_empty()).collect();
 725             for &window in wm.windows.iter() {
 726                 if window.is_null() || (*window).closed {
 727                     continue;
 728                 }
 729                 let app = (*window).get_app_id_string().unwrap_or_default();
 730                 if !pats.iter().any(|p| app.contains(p)) {
 731                     continue;
 732                 }
 733                 let now = std::time::SystemTime::now()
 734                     .duration_since(std::time::UNIX_EPOCH)
 735                     .unwrap_or_default();
 736                 eprintln!(
 737                     "[ovdbg] t={}.{:03} win {} scale={} req=({},{}) box=({},{}) state={:?} hidden={} saved={} tree_en={}",
 738                     now.as_secs(),
 739                     now.subsec_millis(),
 740                     app,
 741                     (*window).scale,
 742                     (*window).rendering_requested.x,
 743                     (*window).rendering_requested.y,
 744                     (*window).box_geom.x,
 745                     (*window).box_geom.y,
 746                     (*window).state,
 747                     (*window).rendering_requested.hidden,
 748                     (*window).surfaces.saved,
 749                     ffi::river_scene_node_get_enabled((*window).tree as *mut ffi::wlr_scene_node),
 750                 );
 751                 if let Ok(tag) = std::ffi::CString::new(app) {
 752                     ffi::river_scene_shadow_dbg((*window).shadow, tag.as_ptr());
 753                     ffi::river_scene_ovdbg_dump(
 754                         (*window).tree as *mut ffi::wlr_scene_node,
 755                         tag.as_ptr(),
 756                     );
 757                 }
 758             }
 759         }
 760         }
 761 
 762         let mut state = std::mem::zeroed();
 763         ffi::wlr_output_state_init(&mut state);
 764         
 765         self.current.apply_no_modeset(&mut state);
 766 
 767         if !ffi::wlr_scene_output_build_state(self.scene_output, &mut state, std::ptr::null()) {
 768             ffi::wlr_output_state_finish(&mut state);
 769             return Err("Failed to build scene state");
 770         }
 771 
 772         if self.rendering_current.tearing {
 773             // The test is an atomic TEST_ONLY commit; once it has said no for
 774             // this episode, asking again every frame just taxes the game.
 775             if !self.tearing_test_failed {
 776                 state.tearing_page_flip = true;
 777                 if !ffi::wlr_output_test_state(self.wlr_output, &state) {
 778                     state.tearing_page_flip = false;
 779                     self.tearing_test_failed = true;
 780                 }
 781             }
 782         } else {
 783             self.tearing_test_failed = false;
 784         }
 785 
 786         if !ffi::wlr_output_commit_state(self.wlr_output, &state) {
 787             ffi::wlr_output_state_finish(&mut state);
 788             return Err("Failed to commit state");
 789         }
 790 
 791         // Read the just-committed frame back while the state's buffer is
 792         // still alive; encode/notify happen on a worker thread.
 793         if let Some(mut shot) = pending_shot {
 794             if state.buffer.is_null() {
 795                 log::warn!("screenshot: output state has no buffer");
 796                 shot.reply_err("screenshot: output state has no buffer");
 797             } else {
 798                 crate::screenshot::capture_state_buffer(
 799                     (*self.server).renderer,
 800                     state.buffer,
 801                     ffi::river_wlr_output_get_width(self.wlr_output),
 802                     ffi::river_wlr_output_get_height(self.wlr_output),
 803                     shot,
 804                 );
 805             }
 806         }
 807 
 808         ffi::wlr_output_state_finish(&mut state);
 809 
 810         match (*self.server).lock_manager.state {
 811             LockState::Unlocked => {
 812                 if self.lock_render_state != LockRenderState::Unlocked {
 813                     self.lock_render_state = LockRenderState::PendingUnlock;
 814                 }
 815             }
 816             LockState::Locked => {
 817                 // Assert normal tree disabled, lock surface rendered
 818             }
 819             LockState::WaitingForBlank => {
 820                 if self.lock_render_state != LockRenderState::Blanked {
 821                     self.lock_render_state = LockRenderState::PendingBlank;
 822                 }
 823             }
 824             LockState::WaitingForLockSurfaces => {
 825                 if let Some(_lock_surf) = (*self.server).lock_manager.lock_surface_from_output(self) {
 826                     if self.lock_render_state != LockRenderState::LockSurface {
 827                         self.lock_render_state = LockRenderState::PendingLockSurface;
 828                     }
 829                 } else {
 830                     if self.lock_render_state != LockRenderState::Unlocked {
 831                         self.lock_render_state = LockRenderState::PendingUnlock;
 832                     }
 833                 }
 834             }
 835         }
 836 
 837         Ok(())
 838     }
 839 
 840     pub unsafe fn update_background_color(&mut self) {
 841         if !self.background_rect.is_null() {
 842             let wm = &(*self.server).wm;
 843             let color: [f32; 4] = [
 844                 (wm.layout.background_r as f64 / u32::MAX as f64) as f32,
 845                 (wm.layout.background_g as f64 / u32::MAX as f64) as f32,
 846                 (wm.layout.background_b as f64 / u32::MAX as f64) as f32,
 847                 (wm.layout.background_a as f64 / u32::MAX as f64) as f32,
 848             ];
 849             ffi::wlr_scene_rect_set_color(self.background_rect, color.as_ptr());
 850         }
 851     }
 852 
 853     /// Measure what each status segment on this output is composited over
 854     /// and push the result to the bar (see [`crate::backdrop`] for why this
 855     /// is geometry rather than a readback).
 856     ///
 857     /// Runs per frame. The cost is a handful of rect intersections per
 858     /// segment; the resend gate is `update_status`'s equality check against
 859     /// the last update, which the whole-percent quantization makes stick —
 860     /// so a still desktop pushes nothing however many frames go by.
 861     pub unsafe fn measure_status_backdrops(&mut self) {
 862         let wm = &(*self.server).wm;
 863         if wm.status_sender.is_none() {
 864             return;
 865         }
 866 
 867         let (viewport_w, viewport_h) = self.current.dimensions();
 868         let out_rect = crate::backdrop::Rect {
 869             x: self.sent.x,
 870             y: self.sent.y,
 871             w: viewport_w,
 872             h: viewport_h,
 873         };
 874 
 875         // The opaque ground the grid is drawn onto, so a gap or cell color
 876         // carrying alpha resolves against what the screen actually shows.
 877         let base = [
 878             (wm.layout.background_r as f64 / u32::MAX as f64) as f32,
 879             (wm.layout.background_g as f64 / u32::MAX as f64) as f32,
 880             (wm.layout.background_b as f64 / u32::MAX as f64) as f32,
 881         ];
 882 
 883         let spec = wm.layout.background_spec();
 884         let grid = match &spec {
 885             crate::policy::api::BackgroundSpec::Grid(g) => g,
 886             // No lattice: every segment sits on the flat background color,
 887             // which `measure` still reports correctly through an empty frame.
 888             _ => {
 889                 let flat = crate::policy::background::GridFrame {
 890                     tree_pos: None,
 891                     period_px_x: 0,
 892                     period_px_y: 0,
 893                     period_px_exact_x: 0.0,
 894                     period_px_exact_y: 0.0,
 895                     backdrop_w: 0,
 896                     backdrop_h: 0,
 897                     cells: None,
 898                     first_col: 0,
 899                     first_row: 0,
 900                 };
 901                 self.store_backdrops(&flat, crate::policy::api::Rgba([base[0], base[1], base[2], 1.0]), base, out_rect);
 902                 return;
 903             }
 904         };
 905 
 906         let frame = crate::policy::background::grid_frame(
 907             grid,
 908             wm.camera(),
 909             viewport_w,
 910             viewport_h,
 911             self.sent.x,
 912             self.sent.y,
 913         );
 914         let gap = grid.gap_color;
 915         self.store_backdrops(&frame, gap, base, out_rect);
 916     }
 917 
 918     /// The half of [`Self::measure_status_backdrops`] that walks the windows:
 919     /// each status segment on this output measured against `frame`, with any
 920     /// window covering part of it sampled for its actual content and folded in.
 921     unsafe fn store_backdrops(
 922         &mut self,
 923         frame: &crate::policy::background::GridFrame,
 924         gap: crate::policy::api::Rgba,
 925         base: [f32; 3],
 926         out_rect: crate::backdrop::Rect,
 927     ) {
 928         let wm = &(*self.server).wm;
 929 
 930         let visible = |w: *mut crate::window::Window| -> bool {
 931             !w.is_null()
 932                 && !(*w).closed
 933                 && !(*w).minimized
 934                 && !matches!((*w).state, crate::window::WindowState::Closing | crate::window::WindowState::Init)
 935         };
 936         let rect_of = |w: *mut crate::window::Window| crate::backdrop::Rect {
 937             x: (*w).box_geom.x,
 938             y: (*w).box_geom.y,
 939             w: (*w).box_geom.width,
 940             h: (*w).box_geom.height,
 941         };
 942 
 943         let mut mine: Vec<(String, u8, u8)> = Vec::new();
 944         let mut live_keys: Vec<(u32, crate::backdrop::Rect)> = Vec::new();
 945         for &seg in wm.windows.iter() {
 946             if !visible(seg) || !(*seg).is_status_bar() {
 947                 continue;
 948             }
 949             let seg_rect = rect_of(seg);
 950             if !seg_rect.intersects(&out_rect) {
 951                 continue;
 952             }
 953             let Some(app_id) = (*seg).get_app_id_string() else {
 954                 continue;
 955             };
 956 
 957             let desktop = crate::backdrop::measure(frame, gap, base, seg_rect, false);
 958 
 959             // The window covering the most of this segment, if any. Stacking
 960             // order is deliberately not consulted: the compositor's own
 961             // hit-test answers with the segment itself (it is on top of
 962             // whatever it is asking about), and where two windows both reach
 963             // under one segment the larger share is the better guess at what
 964             // the text is actually over.
 965             let mut best: Option<(*mut crate::window::Window, i64)> = None;
 966             for &other in wm.windows.iter() {
 967                 if other == seg || !visible(other) {
 968                     continue;
 969                 }
 970                 if (*other).is_status_bar() || (*other).is_wallpaper() || (*other).is_grid() {
 971                     continue;
 972                 }
 973                 let area = rect_of(other).intersect_area(&seg_rect);
 974                 if area > 0 && best.map_or(true, |(_, a)| area > a) {
 975                     best = Some((other, area));
 976                 }
 977             }
 978 
 979             let sample = match best {
 980                 None => desktop,
 981                 Some((win, area)) => {
 982                     let region = rect_of(win).intersection(&seg_rect).unwrap_or(seg_rect);
 983                     let key = ((*win).ref_key.index, region);
 984                     live_keys.push(key);
 985                     match self.window_backdrop_sample(win, region) {
 986                         // Blended, not replaced: a window covering half a
 987                         // segment leaves the other half on the desktop, and
 988                         // the seam between them is its own legibility problem.
 989                         Some(w) => {
 990                             let coverage = area as f32 / (seg_rect.w as f32 * seg_rect.h as f32).max(1.0);
 991                             crate::backdrop::blend(desktop, w, coverage)
 992                         }
 993                         // Unreadable content (no committed buffer yet, an
 994                         // unsupported read format): the honest answer is still
 995                         // "unknown", exactly as before this path existed.
 996                         None => crate::backdrop::UNKNOWN,
 997                     }
 998                 }
 999             };
1000             mine.push((app_id, sample.luma, sample.spread));
1001         }
1002 
1003         // Drop cache entries for segment/window pairs that no longer exist,
1004         // so a closed window or a moved segment cannot pin a stale reading.
1005         self.status_win_samples
1006             .retain(|e| live_keys.iter().any(|(k, r)| *k == e.win && *r == e.region));
1007 
1008         {
1009             let mut store = wm.status_backdrops.borrow_mut();
1010             // Replace only this output's segments; another output's entries
1011             // are its own to maintain. Sorted so a reordering of the window
1012             // list cannot, by itself, look like a change worth resending.
1013             store.retain(|(id, _, _)| !mine.iter().any(|(m, _, _)| m == id));
1014             store.extend(mine);
1015             store.sort_by(|a, b| a.0.cmp(&b.0));
1016         }
1017 
1018         wm.update_status();
1019     }
1020 
1021     /// The window-content half of the backdrop measurement: what `win` is
1022     /// actually showing inside `region` (layout px), or None when it cannot be
1023     /// read.
1024     ///
1025     /// Throttled and cached per (window, region) — this is the one part of the
1026     /// measurement that costs a texture readback and its GPU sync, and it runs
1027     /// inside the render path.
1028     unsafe fn window_backdrop_sample(
1029         &mut self,
1030         win: *mut crate::window::Window,
1031         region: crate::backdrop::Rect,
1032     ) -> Option<crate::backdrop::BackdropSample> {
1033         /// Re-read a window's content at most this often, per segment.
1034         const WIN_SAMPLE_MS: u128 = 250;
1035         /// Refuse to read back more than this many pixels in one sample. A bar
1036         /// strip is naturally short, so this only trips on an implausibly wide
1037         /// segment at a high buffer scale — where reporting "unknown" and
1038         /// wearing the outline beats stalling the render thread.
1039         const MAX_SAMPLE_PX: i64 = 512 * 1024;
1040 
1041         let id = (*win).ref_key.index;
1042         let now = std::time::Instant::now();
1043         let seq = Self::surface_content_seq((*win).root_surface());
1044         if let Some(hit) = self
1045             .status_win_samples
1046             .iter()
1047             .find(|e| e.win == id && e.region == region)
1048         {
1049             // Two gates, and the content one is the load-bearing half: a
1050             // window nobody is typing in never gets read a second time.
1051             if hit.seq == seq || now.duration_since(hit.at).as_millis() < WIN_SAMPLE_MS {
1052                 return hit.sample;
1053             }
1054         }
1055 
1056         let fresh = self.read_window_region(win, region, MAX_SAMPLE_PX);
1057         match self
1058             .status_win_samples
1059             .iter_mut()
1060             .find(|e| e.win == id && e.region == region)
1061         {
1062             Some(slot) => {
1063                 slot.at = now;
1064                 slot.seq = seq;
1065                 slot.sample = fresh;
1066             }
1067             None => self.status_win_samples.push(StatusWinSample {
1068                 win: id,
1069                 region,
1070                 at: now,
1071                 seq,
1072                 sample: fresh,
1073             }),
1074         }
1075         fresh
1076     }
1077 
1078     /// Commit sequences of a surface tree, summed — a cheap "has this window
1079     /// drawn anything new?" key. Walks subsurfaces too, because a toolkit that
1080     /// renders into one can leave the root's own sequence untouched for the
1081     /// life of the window.
1082     unsafe fn surface_content_seq(root: *mut ffi::wlr_surface) -> u32 {
1083         if root.is_null() {
1084             return 0;
1085         }
1086         unsafe extern "C" fn sum_cb(
1087             surface: *mut ffi::wlr_surface,
1088             _sx: std::os::raw::c_int,
1089             _sy: std::os::raw::c_int,
1090             data: *mut std::ffi::c_void,
1091         ) {
1092             let total = &mut *(data as *mut u32);
1093             *total = total.wrapping_add(ffi::river_wlr_surface_current_seq(surface));
1094         }
1095         let mut total: u32 = 0;
1096         ffi::wlr_surface_for_each_surface(
1097             root,
1098             Some(sum_cb),
1099             &mut total as *mut u32 as *mut std::ffi::c_void,
1100         );
1101         total
1102     }
1103 
1104     /// Read `region` (layout px) out of a window's committed surfaces and
1105     /// measure it. Subsurfaces are composited in, because a toolkit that puts
1106     /// its content in one would otherwise be measured as its blank root.
1107     unsafe fn read_window_region(
1108         &self,
1109         win: *mut crate::window::Window,
1110         region: crate::backdrop::Rect,
1111         max_px: i64,
1112     ) -> Option<crate::backdrop::BackdropSample> {
1113         let root = (*win).root_surface();
1114         if root.is_null() {
1115             return None;
1116         }
1117         let (mut bw, mut bh) = (0i32, 0i32);
1118         ffi::river_wlr_surface_get_buffer_size(root, &mut bw, &mut bh);
1119         if bw <= 0 || bh <= 0 {
1120             return None;
1121         }
1122         // Two scales stack here: the window's own render scale maps layout px
1123         // to surface-logical px, and the buffer scale maps those to the
1124         // physical pixels a texture read is addressed in.
1125         let logical_w = ffi::river_wlr_surface_get_width(root).max(1);
1126         let buf_scale = bw as f64 / logical_w as f64;
1127         let win_scale = if (*win).scale > 0.0 { (*win).scale } else { 1.0 };
1128         let to_buf = buf_scale / win_scale;
1129 
1130         let rx = (((region.x - (*win).box_geom.x) as f64) * to_buf).round() as i32;
1131         let ry = (((region.y - (*win).box_geom.y) as f64) * to_buf).round() as i32;
1132         let rw = ((region.w as f64) * to_buf).round() as i32;
1133         let rh = ((region.h as f64) * to_buf).round() as i32;
1134         if rw <= 0 || rh <= 0 || (rw as i64) * (rh as i64) > max_px {
1135             return None;
1136         }
1137 
1138         struct Collect {
1139             list: Vec<(*mut ffi::wlr_surface, i32, i32)>,
1140         }
1141         unsafe extern "C" fn collect_cb(
1142             surface: *mut ffi::wlr_surface,
1143             sx: std::os::raw::c_int,
1144             sy: std::os::raw::c_int,
1145             data: *mut std::ffi::c_void,
1146         ) {
1147             let collect = &mut *(data as *mut Collect);
1148             collect.list.push((surface, sx, sy));
1149         }
1150         let mut collect = Collect { list: Vec::new() };
1151         ffi::wlr_surface_for_each_surface(
1152             root,
1153             Some(collect_cb),
1154             &mut collect as *mut Collect as *mut std::ffi::c_void,
1155         );
1156 
1157         let mut canvas = vec![0u8; (rw as usize) * (rh as usize) * 4];
1158         let mut composited = 0usize;
1159         for (surface, sx, sy) in collect.list {
1160             let texture = ffi::wlr_surface_get_texture(surface);
1161             if texture.is_null() {
1162                 continue;
1163             }
1164             let (mut sw, mut sh) = (0i32, 0i32);
1165             ffi::river_wlr_surface_get_buffer_size(surface, &mut sw, &mut sh);
1166             if sw <= 0 || sh <= 0 {
1167                 continue;
1168             }
1169             // Subsurface offsets are surface-logical; buffers are physical.
1170             let off_x = (sx as f64 * buf_scale).round() as i32;
1171             let off_y = (sy as f64 * buf_scale).round() as i32;
1172             let x0 = off_x.max(rx);
1173             let y0 = off_y.max(ry);
1174             let x1 = (off_x + sw).min(rx + rw);
1175             let y1 = (off_y + sh).min(ry + rh);
1176             if x1 <= x0 || y1 <= y0 {
1177                 continue;
1178             }
1179             let src = ffi::wlr_box {
1180                 x: x0 - off_x,
1181                 y: y0 - off_y,
1182                 width: x1 - x0,
1183                 height: y1 - y0,
1184             };
1185             let Some((pixels, format)) =
1186                 crate::screenshot::read_texture_region(texture, src, x1 - x0, y1 - y0)
1187             else {
1188                 continue;
1189             };
1190             let Some(rgba) = crate::screenshot::to_rgba(pixels, format) else { continue };
1191             crate::screenshot::blit(&mut canvas, rw, rh, &rgba, x1 - x0, y1 - y0, x0 - rx, y0 - ry);
1192             composited += 1;
1193         }
1194         if composited == 0 {
1195             return None;
1196         }
1197         crate::backdrop::measure_pixels(&canvas)
1198     }
1199 
1200     pub unsafe fn draw_adjust_overlay(&mut self) {
1201         if self.adjust_tree.is_null() {
1202             return;
1203         }
1204 
1205         let wm = &(*self.server).wm;
1206         if wm.adjust_position_mode != self.last_adjust_mode {
1207             self.last_adjust_mode = wm.adjust_position_mode;
1208             ffi::wlr_output_schedule_frame(self.wlr_output);
1209         }
1210 
1211         if !wm.adjust_position_mode {
1212             ffi::wlr_scene_node_set_enabled(self.adjust_tree as *mut ffi::wlr_scene_node, false);
1213             return;
1214         }
1215 
1216         // Enable the overlay tree.
1217         ffi::wlr_scene_node_set_enabled(self.adjust_tree as *mut ffi::wlr_scene_node, true);
1218         ffi::wlr_scene_node_lower_to_bottom(self.adjust_tree as *mut ffi::wlr_scene_node);
1219 
1220         let (viewport_w, viewport_h) = self.current.dimensions();
1221         let w = viewport_w;
1222         let h = viewport_h;
1223 
1224         // Semicircles size: 120 x 120 (so radius = 60).
1225         let targets = [
1226             // TopLeft (nw): x = 0, y = -60
1227             (0, -60),
1228             // TopCenter (n): x = w/2 - 60, y = -60
1229             (w / 2 - 60, -60),
1230             // TopRight (ne): x = w - 120, y = -60
1231             (w - 120, -60),
1232             // BottomLeft (sw): x = 0, y = h - 60
1233             (0, h - 60),
1234             // BottomCenter (s): x = w/2 - 60, y = h - 60
1235             (w / 2 - 60, h - 60),
1236             // BottomRight (se): x = w - 120, y = h - 60
1237             (w - 120, h - 60),
1238             // Left (w): x = -60, y = h/2 - 60
1239             (-60, h / 2 - 60),
1240             // Right (e): x = w - 60, y = h/2 - 60
1241             (w - 60, h / 2 - 60),
1242         ];
1243 
1244         let color: [f32; 4] = [0.4, 0.6, 0.9, 0.5];
1245         let color_ptr = color.as_ptr();
1246 
1247         for (idx, &(tx, ty)) in targets.iter().enumerate() {
1248             let rect = if idx < self.adjust_rects.len() {
1249                 let node = self.adjust_rects[idx];
1250                 ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, true);
1251                 ffi::wlr_scene_rect_set_size(node, 120, 120);
1252                 ffi::wlr_scene_rect_set_color(node, color_ptr);
1253                 node
1254             } else {
1255                 let node = ffi::wlr_scene_rect_create(self.adjust_tree, 120, 120, color_ptr);
1256                 if !node.is_null() {
1257                     self.adjust_rects.push(node);
1258                     // Make it circular!
1259                     ffi::river_scene_rect_set_corner_radius(node, 60);
1260                 }
1261                 node
1262             };
1263 
1264             if !rect.is_null() {
1265                 ffi::wlr_scene_node_set_position(rect as *mut ffi::wlr_scene_node, tx, ty);
1266             }
1267         }
1268 
1269         // Disable any extra rects in the pool if we somehow have more
1270         for idx in targets.len()..self.adjust_rects.len() {
1271             let node = self.adjust_rects[idx];
1272             ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, false);
1273         }
1274     }
1275 
1276     /// Draw the desktop background from the policy crate's declarative
1277     /// spec: `Layout::background_spec()` says WHAT to show, and (for the
1278     /// grid) `policy::background::grid_frame` derives this frame's geometry
1279     /// — tree shift, backdrop extent, cell lattice, density fade. This side
1280     /// keeps the scene nodes, the rect reuse pool, and scenefx's fade-inset
1281     /// wire encoding.
1282     pub unsafe fn draw_grid(&mut self) {
1283         if self.grid_tree.is_null() {
1284             return;
1285         }
1286 
1287         let wm = &(*self.server).wm;
1288 
1289         // Enable the grid tree.
1290         ffi::wlr_scene_node_set_enabled(self.grid_tree as *mut ffi::wlr_scene_node, true);
1291 
1292         // Keep the grid tree (cells + rims) at the top of the background layer,
1293         // above the client backgrounds in layers.background_clients.
1294         ffi::wlr_scene_node_raise_to_top(self.grid_tree as *mut ffi::wlr_scene_node);
1295 
1296         // The backdrop goes BELOW the client backgrounds: its own tree, placed
1297         // just above this output's base rect (or at the very bottom), so a
1298         // layer-shell Background surface or a wallpaper window replaces the flat
1299         // colour and keeps the cell lattice.
1300         if self.grid_backdrop_tree.is_null() {
1301             self.grid_backdrop_tree = ffi::wlr_scene_tree_create((*self.server).scene.layers.background);
1302             ffi::river_scene_tree_set_desk_offset(self.grid_backdrop_tree, true);
1303             if self.grid_backdrop_tree.is_null() {
1304                 return;
1305             }
1306             if !self.background_rect.is_null() {
1307                 ffi::wlr_scene_node_lower_to_bottom(self.background_rect as *mut ffi::wlr_scene_node);
1308                 ffi::wlr_scene_node_place_above(
1309                     self.grid_backdrop_tree as *mut ffi::wlr_scene_node,
1310                     self.background_rect as *mut ffi::wlr_scene_node,
1311                 );
1312             } else {
1313                 ffi::wlr_scene_node_lower_to_bottom(self.grid_backdrop_tree as *mut ffi::wlr_scene_node);
1314             }
1315         }
1316         ffi::wlr_scene_node_set_enabled(self.grid_backdrop_tree as *mut ffi::wlr_scene_node, true);
1317         let backdrop_tree = self.grid_backdrop_tree;
1318         let backdrop_rect = &mut self.grid_backdrop_rect;
1319         let mut set_backdrop = |w: i32, h: i32, color_ptr: *const f32| {
1320             if backdrop_rect.is_null() {
1321                 *backdrop_rect = ffi::wlr_scene_rect_create(backdrop_tree, w, h, color_ptr);
1322             } else {
1323                 ffi::wlr_scene_rect_set_size(*backdrop_rect, w, h);
1324                 ffi::wlr_scene_rect_set_color(*backdrop_rect, color_ptr);
1325             }
1326         };
1327 
1328         let (viewport_w, viewport_h) = self.current.dimensions();
1329         let spec = wm.layout.background_spec();
1330         let zoom = crate::policy::background::sanitized_zoom(wm.desk_zoom);
1331 
1332         // Spec/viewport/zoom/bevel changes force a redraw of the pools;
1333         // pan alone only moves the grid tree.
1334         let bevel_key = [
1335             wm.layout.bevel_enabled as u32,
1336             wm.layout.bevel_thickness.to_bits(),
1337             wm.layout.bevel_light_x.to_bits(),
1338             wm.layout.bevel_light_y.to_bits(),
1339             wm.layout.bevel_light_intensity.to_bits(),
1340             wm.layout.bevel_shade_intensity.to_bits(),
1341             wm.layout.bevel_shoulder.to_bits(),
1342         ];
1343         let structure_changed = self.last_grid_viewport_w != viewport_w
1344             || self.last_grid_viewport_h != viewport_h
1345             || self.last_grid_zoom != zoom
1346             || self.last_grid_spec.as_ref() != Some(&spec)
1347             || self.last_grid_bevel != Some(bevel_key);
1348         if structure_changed {
1349             self.grid_force_redraw_frames = 3;
1350             // Fallback-grid structure (spec/zoom/viewport) is backdrop
1351             // content in the optimized-blur capture set — same staleness
1352             // rule as the client-grid latch. Not while a camera gesture
1353             // holds the bakes frozen: the zoom restructures the grid every
1354             // frame, and the settle re-bakes once at the end.
1355             if !wm.viewport_is_active {
1356                 ffi::river_scene_mark_optimized_blur_dirty((*self.server).scene.wlr_scene);
1357             }
1358         }
1359         let force = self.grid_force_redraw_frames > 0;
1360         if force {
1361             self.grid_force_redraw_frames -= 1;
1362             self.last_grid_viewport_w = viewport_w;
1363             self.last_grid_viewport_h = viewport_h;
1364             self.last_grid_zoom = zoom;
1365             self.last_grid_spec = Some(spec.clone());
1366             self.last_grid_bevel = Some(bevel_key);
1367         }
1368 
1369         // The cell rims live in their own subtree kept above every pooled
1370         // rect (incl. the backdrop), so reuse order can never bury one.
1371         if self.grid_bevel_tree.is_null() {
1372             self.grid_bevel_tree = ffi::wlr_scene_tree_create(self.grid_tree);
1373         }
1374         ffi::wlr_scene_node_raise_to_top(self.grid_bevel_tree as *mut ffi::wlr_scene_node);
1375 
1376         let grid_tree = self.grid_tree;
1377         if !grid_tree.is_null() {
1378             // Desk content: rendered with the camera's sub-pixel offset.
1379             ffi::river_scene_tree_set_desk_offset(grid_tree, true);
1380         }
1381         let pool = &mut self.grid_rect_pool;
1382         let mut pool_idx = 0;
1383 
1384         let layout = &wm.layout;
1385         // The relief lives on the LINES, never the cells (mirroring the
1386         // cce-grid client, which must be able to latch without swapping the
1387         // grid's material): each cell's chamfer box is expanded by the
1388         // half-gap, so the lit wall occupies exactly the half-rail around
1389         // the cell — a crest at the rail centerline descending to the cell
1390         // edge — and neighboring rings abut without overlap. Cell floors
1391         // stay flat.
1392         let bevel_on = layout.bevel_enabled;
1393         // Light normalized exactly like the window bevels — the grid is lit
1394         // by the same lamp.
1395         let (bevel_lx, bevel_ly) = {
1396             let (lx, ly) = (layout.bevel_light_x, layout.bevel_light_y);
1397             let len = (lx * lx + ly * ly).sqrt();
1398             if len > 1e-6 { (lx / len, ly / len) } else { (-0.7071, -0.7071) }
1399         };
1400         let bevel_tree = self.grid_bevel_tree;
1401         let bevel_pool = &mut self.grid_bevel_pool;
1402         let mut bevel_idx = 0;
1403 
1404         let mut get_bevel = |w: i32, h: i32, x: i32, y: i32, radius: i32, thickness: f32| {
1405             let bevel = if bevel_idx < bevel_pool.len() {
1406                 let node = bevel_pool[bevel_idx];
1407                 ffi::wlr_scene_node_set_enabled(&mut (*node).node as *mut ffi::wlr_scene_node, true);
1408                 ffi::wlr_scene_bevel_set_size(node, w, h);
1409                 node
1410             } else {
1411                 let node = ffi::wlr_scene_bevel_create(bevel_tree, w, h, 0, 0.0, layout.bevel_color.as_ptr());
1412                 if !node.is_null() {
1413                     bevel_pool.push(node);
1414                 }
1415                 node
1416             };
1417             if !bevel.is_null() {
1418                 ffi::wlr_scene_node_set_position(&mut (*bevel).node as *mut ffi::wlr_scene_node, x, y);
1419                 ffi::wlr_scene_bevel_set_corner_radius(bevel, radius);
1420                 ffi::wlr_scene_bevel_set_thickness(bevel, thickness.max(1.0));
1421                 ffi::wlr_scene_bevel_set_light(
1422                     bevel,
1423                     bevel_lx,
1424                     bevel_ly,
1425                     layout.bevel_light_intensity,
1426                     layout.bevel_shade_intensity,
1427                 );
1428                 ffi::wlr_scene_bevel_set_shoulder(bevel, layout.bevel_shoulder);
1429                 ffi::wlr_scene_bevel_set_color(bevel, layout.bevel_color.as_ptr());
1430             }
1431             bevel_idx += 1;
1432         };
1433 
1434         // Helper closure to manage/reuse the pool of wlr_scene_rect elements.
1435         let mut get_rect = |w: i32, h: i32, color_ptr: *const f32, x: i32, y: i32, corner_r: i32, fade_i: i32| -> *mut ffi::wlr_scene_rect {
1436             let rect = if pool_idx < pool.len() {
1437                 let node = pool[pool_idx];
1438                 ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, true);
1439                 ffi::wlr_scene_rect_set_size(node, w, h);
1440                 ffi::wlr_scene_rect_set_color(node, color_ptr);
1441                 node
1442             } else {
1443                 let node = ffi::wlr_scene_rect_create(grid_tree, w, h, color_ptr);
1444                 if !node.is_null() {
1445                     pool.push(node);
1446                 }
1447                 node
1448             };
1449 
1450             if !rect.is_null() {
1451                 ffi::wlr_scene_node_set_position(rect as *mut ffi::wlr_scene_node, x, y);
1452                 ffi::river_scene_rect_set_corner_radius(rect, corner_r);
1453                 ffi::wlr_scene_rect_set_fade_inset(rect, fade_i);
1454             }
1455             pool_idx += 1;
1456             rect
1457         };
1458 
1459         match &spec {
1460             crate::policy::api::BackgroundSpec::Grid(grid) => {
1461                 let frame = crate::policy::background::grid_frame(
1462                     grid,
1463                     wm.layout_camera().0,
1464                     viewport_w,
1465                     viewport_h,
1466                     self.sent.x,
1467                     self.sent.y,
1468                 );
1469 
1470                 if let Some((x, y)) = frame.tree_pos {
1471                     ffi::river_scene_node_set_position_if_changed(
1472                         grid_tree as *mut ffi::wlr_scene_node,
1473                         x,
1474                         y,
1475                     );
1476                     ffi::river_scene_node_set_position_if_changed(
1477                         backdrop_tree as *mut ffi::wlr_scene_node,
1478                         x,
1479                         y,
1480                     );
1481                 }
1482 
1483                 if force {
1484                     // Backdrop in the gap color, then the cell lattice. The
1485                     // backdrop always draws — while a grid client is live it
1486                     // is the safety net beyond the patch edges during fast
1487                     // pans; the CELLS yield to the client's rendering.
1488                     set_backdrop(frame.backdrop_w, frame.backdrop_h, grid.gap_color.0.as_ptr());
1489 
1490                     if let Some(cells) = frame.cells.as_ref().filter(|_| wm.grid_cells_enabled) {
1491                         // scenefx fade-inset wire encoding: inset px * 1000
1492                         // + fade-mode index; 0 disables the fade.
1493                         use crate::policy::api::GridFadeMode;
1494                         let fade_mode = match grid.fade_mode {
1495                             GridFadeMode::Linear => 0,
1496                             GridFadeMode::Smoothstep => 1,
1497                             GridFadeMode::Quadratic => 2,
1498                             GridFadeMode::Cosine => 3,
1499                             GridFadeMode::Gaussian => 4,
1500                         };
1501                         let inset_scaled = if cells.fade_inset_px > 0 {
1502                             cells.fade_inset_px * 1000 + fade_mode
1503                         } else {
1504                             0
1505                         };
1506                         // Widened exactly like the windows' corner clip:
1507                         // at corner_shape > 2 the superellipse hugs the
1508                         // corner, so the raw radius reads nearly square —
1509                         // and a tiled window's (widened) arc must land on
1510                         // the cell's arc.
1511                         let cell_radius = crate::window::widen_corner_radius(
1512                             cells.corner_radius_px, cells.cell_w_px, cells.cell_h_px,
1513                         );
1514                         // The root plate-edge roll (mirroring cce-grid): the
1515                         // bevel-width knob clamped to a fraction of the
1516                         // rail, pre-scaled by zoom like every cell metric,
1517                         // so the rail reads as a flat face with a narrow
1518                         // lip at each sunken cell — not a full-ramp grout.
1519                         // style.surface.desktop.line_relief overrides the
1520                         // width outright (0 = no lip). The ring expands the
1521                         // cell box by the roll, inner edge concentric with
1522                         // the cell arc.
1523                         let gap_px = (frame.period_px_exact_x - cells.cell_w_px as f64)
1524                             .min(frame.period_px_exact_y - cells.cell_h_px as f64)
1525                             .max(0.0);
1526                         let roll = layout
1527                             .desktop_line_relief
1528                             .map(|v| v * zoom)
1529                             .unwrap_or_else(|| (layout.bevel_thickness as f64 * zoom).min(gap_px * 0.25))
1530                             .max(0.0);
1531                         let hg = roll.round() as i32;
1532                         let ring_w_px = cells.cell_w_px + 2 * hg;
1533                         let ring_h_px = cells.cell_h_px + 2 * hg;
1534                         let ring_radius = cell_radius + hg;
1535                         // Cell positions from the EXACT period, rounded per
1536                         // cell: a rounded-period spacing drifts from the
1537                         // world-anchored windows at fractional zooms (the
1538                         // grid visibly slides against window edges when
1539                         // panning).
1540                         for col in 0..=cells.cols {
1541                             let rel_x = (col as f64 * frame.period_px_exact_x).round() as i32;
1542                             for row in 0..=cells.rows {
1543                                 let rel_y = (row as f64 * frame.period_px_exact_y).round() as i32;
1544                                 get_rect(cells.cell_w_px, cells.cell_h_px, cells.color.0.as_ptr(), rel_x, rel_y, cell_radius, inset_scaled);
1545                                 if bevel_on && hg > 0 {
1546                                     get_bevel(ring_w_px, ring_h_px, rel_x - hg, rel_y - hg, ring_radius, roll as f32);
1547                                 }
1548                             }
1549                         }
1550                     }
1551                 }
1552             }
1553             crate::policy::api::BackgroundSpec::Solid(color) => {
1554                 ffi::river_scene_node_set_position_if_changed(
1555                     grid_tree as *mut ffi::wlr_scene_node,
1556                     self.sent.x,
1557                     self.sent.y,
1558                 );
1559                 ffi::river_scene_node_set_position_if_changed(
1560                     backdrop_tree as *mut ffi::wlr_scene_node,
1561                     self.sent.x,
1562                     self.sent.y,
1563                 );
1564                 if force {
1565                     set_backdrop(viewport_w, viewport_h, color.0.as_ptr());
1566                 }
1567             }
1568         }
1569 
1570         if force {
1571             // Disable unused rects in the pool to release GPU/scene resources.
1572             for i in pool_idx..pool.len() {
1573                 ffi::wlr_scene_node_set_enabled(pool[i] as *mut ffi::wlr_scene_node, false);
1574             }
1575             for i in bevel_idx..bevel_pool.len() {
1576                 ffi::wlr_scene_node_set_enabled(&mut (*bevel_pool[i]).node as *mut ffi::wlr_scene_node, false);
1577             }
1578         }
1579 
1580         self.draw_cell_labels();
1581     }
1582 
1583     /// Name every visible desktop square, chess style, while overview is open.
1584     ///
1585     /// The labels live in the grid tree, so they inherit its modulo shift and
1586     /// ride along with a pan for free; only the world index of the first drawn
1587     /// cell (`GridFrame::first_col/row`, computed in policy) is needed to know
1588     /// what to write. Outside overview every node is disabled — this is a
1589     /// navigation aid, not desktop furniture.
1590     unsafe fn draw_cell_labels(&mut self) {
1591         let wm = &(*self.server).wm;
1592         let overview = wm.mode == crate::window_manager::WindowManagerMode::Overview
1593             && wm.layout.desktop_cell_labels;
1594 
1595         if !overview {
1596             if !self.cell_label_pool.is_empty() {
1597                 for &(node, _) in &self.cell_label_pool {
1598                     ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, false);
1599                 }
1600             }
1601             return;
1602         }
1603         if self.grid_tree.is_null() {
1604             return;
1605         }
1606         if self.cell_label_tree.is_null() {
1607             self.cell_label_tree = ffi::wlr_scene_tree_create(self.grid_tree);
1608             if self.cell_label_tree.is_null() {
1609                 return;
1610             }
1611         }
1612         // Above the cell rects and the bevel subtree, which draw_grid raised
1613         // just before this.
1614         ffi::wlr_scene_node_raise_to_top(self.cell_label_tree as *mut ffi::wlr_scene_node);
1615 
1616         let (viewport_w, viewport_h) = self.current.dimensions();
1617         let spec = wm.layout.background_spec();
1618         let crate::policy::api::BackgroundSpec::Grid(grid) = &spec else {
1619             self.disable_cell_labels();
1620             return;
1621         };
1622         let frame = crate::policy::background::grid_frame(
1623             grid,
1624             wm.camera(),
1625             viewport_w,
1626             viewport_h,
1627             self.sent.x,
1628             self.sent.y,
1629         );
1630         let Some(cells) = &frame.cells else {
1631             self.disable_cell_labels();
1632             return;
1633         };
1634 
1635         // A fixed fraction of the on-screen cell, clamped so labels stay
1636         // readable when zoomed far out and don't swell into billboards when
1637         // near. Below the floor there is no room for glyphs at all.
1638         let min_cell_px = cells.cell_w_px.min(cells.cell_h_px);
1639         let px = ((min_cell_px as f32) * 0.16).clamp(9.0, 40.0);
1640         if px * 3.0 > min_cell_px as f32 {
1641             self.disable_cell_labels();
1642             return;
1643         }
1644         let px_key = px.round() as u32;
1645         if px_key != self.last_label_px || self.cell_labels.len() > 512 {
1646             self.cell_labels.clear();
1647             self.last_label_px = px_key;
1648             // The freed buffers' addresses can be handed straight back to the
1649             // next rasterization, so a stale pointer here would compare equal
1650             // to a different label and skip the update.
1651             for entry in self.cell_label_pool.iter_mut() {
1652                 entry.1 = std::ptr::null_mut();
1653             }
1654         }
1655 
1656         let inset = (min_cell_px as f64 * 0.06).round() as i32;
1657         let mut idx = 0usize;
1658         for col in 0..=cells.cols {
1659             let rel_x = (col as f64 * frame.period_px_exact_x).round() as i32;
1660             for row in 0..=cells.rows {
1661                 let rel_y = (row as f64 * frame.period_px_exact_y).round() as i32;
1662                 let Some(label) = self.cell_labels.get_square(frame.first_col + col, frame.first_row + row, px) else {
1663                     continue;
1664                 };
1665                 let (buf, lw, lh) = (label.buffer, label.width, label.height);
1666 
1667                 let node = if idx < self.cell_label_pool.len() {
1668                     let (node, shown) = self.cell_label_pool[idx];
1669                     if shown != buf {
1670                         ffi::wlr_scene_buffer_set_buffer(node, buf);
1671                         self.cell_label_pool[idx].1 = buf;
1672                     }
1673                     ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, true);
1674                     node
1675                 } else {
1676                     let node = ffi::wlr_scene_buffer_create(self.cell_label_tree, buf);
1677                     if node.is_null() {
1678                         continue;
1679                     }
1680                     self.cell_label_pool.push((node, buf));
1681                     node
1682                 };
1683                 ffi::wlr_scene_buffer_set_dest_size(node, lw, lh);
1684                 // Top-left corner of the cell, inside the fade inset.
1685                 ffi::river_scene_node_set_position_if_changed(
1686                     node as *mut ffi::wlr_scene_node,
1687                     rel_x + inset,
1688                     rel_y + inset,
1689                 );
1690                 let _ = lh;
1691                 idx += 1;
1692             }
1693         }
1694 
1695         for i in idx..self.cell_label_pool.len() {
1696             ffi::wlr_scene_node_set_enabled(
1697                 self.cell_label_pool[i].0 as *mut ffi::wlr_scene_node,
1698                 false,
1699             );
1700         }
1701     }
1702 
1703     unsafe fn disable_cell_labels(&self) {
1704         for &(node, _) in &self.cell_label_pool {
1705             ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, false);
1706         }
1707     }
1708 }
1709 
1710 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
1711     let output = crate::container_of!(listener, Output, destroy);
1712 
1713     log::debug!("Output destroyed");
1714     crate::xwayland_window::note_output_change();
1715 
1716     // Remove listeners
1717     wl_listener_remove(&mut (*output).destroy);
1718     wl_listener_remove(&mut (*output).request_state);
1719     wl_listener_remove(&mut (*output).frame);
1720     wl_listener_remove(&mut (*output).present);
1721 
1722     if !(*output).scene_output.is_null() {
1723         ffi::wlr_scene_output_destroy((*output).scene_output);
1724         (*output).scene_output = std::ptr::null_mut();
1725     }
1726 
1727     if !(*output).background_rect.is_null() {
1728         ffi::wlr_scene_node_destroy((*output).background_rect as *mut ffi::wlr_scene_node);
1729         (*output).background_rect = std::ptr::null_mut();
1730     }
1731 
1732     if !(*output).grid_tree.is_null() {
1733         ffi::wlr_scene_node_destroy((*output).grid_tree as *mut ffi::wlr_scene_node);
1734         (*output).grid_tree = std::ptr::null_mut();
1735     }
1736     if !(*output).grid_backdrop_tree.is_null() {
1737         ffi::wlr_scene_node_destroy((*output).grid_backdrop_tree as *mut ffi::wlr_scene_node);
1738         (*output).grid_backdrop_tree = std::ptr::null_mut();
1739         (*output).grid_backdrop_rect = std::ptr::null_mut();
1740     }
1741 
1742     if !(*output).adjust_tree.is_null() {
1743         ffi::wlr_scene_node_destroy((*output).adjust_tree as *mut ffi::wlr_scene_node);
1744         (*output).adjust_tree = std::ptr::null_mut();
1745     }
1746     (*output).adjust_rects.clear();
1747 
1748     if !(*output).wlr_output.is_null() {
1749         ffi::river_wlr_output_set_data((*output).wlr_output, std::ptr::null_mut());
1750     }
1751 
1752     (*output).wlr_output = std::ptr::null_mut();
1753     (*output).scheduled.mode = OutputMode::None;
1754     (*output).sent.mode = OutputMode::None;
1755     (*output).current.mode = OutputMode::None;
1756     (*output).scheduled.state = OutputStateValue::Destroying;
1757 
1758     (*(*output).server).wm.dirty_windowing();
1759 }
1760 
1761 unsafe extern "C" fn handle_request_state(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
1762     let output = &mut *crate::container_of!(listener, Output, request_state);
1763     let event = data as *mut ffi::wlr_output_event_request_state;
1764 
1765     let committed: u32 = std::mem::transmute((*(*event).state).committed);
1766     // mode field is bit 1 (mode)
1767     if committed & 1 != 0 {
1768         if !(*(*event).state).mode.is_null() {
1769             output.scheduled.mode = OutputMode::Standard((*(*event).state).mode);
1770         } else {
1771             output.scheduled.mode = OutputMode::Custom {
1772                 width: (*(*event).state).custom_mode.width,
1773                 height: (*(*event).state).custom_mode.height,
1774                 refresh: (*(*event).state).custom_mode.refresh,
1775             };
1776         }
1777     }
1778 
1779     (*output.server).wm.dirty_windowing();
1780 }
1781 
1782 /// `CCE_FRAME_DEBUG` (any value) also ticks every output frame, so a client's
1783 /// frame-callback interval can be compared against the rate the output is
1784 /// actually rendering at — the two diverging is the signature of a surface
1785 /// being skipped by the scene's visible gate in `wlr_scene_buffer_send_frame_done`.
1786 pub(crate) fn frame_debug() -> bool {
1787     static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1788     *FLAG.get_or_init(|| std::env::var_os("CCE_FRAME_DEBUG").is_some())
1789 }
1790 
1791 /// `CCE_OVDBG=1` arms the `/tmp/cce-ovdbg` per-frame scene dump (see
1792 /// `render_and_commit`); without it the file is never even looked for.
1793 fn ovdbg_enabled() -> bool {
1794     static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1795     *FLAG.get_or_init(|| std::env::var_os("CCE_OVDBG").is_some())
1796 }
1797 
1798 /// Ceiling on how long a status segment's backdrop reading may go unmeasured
1799 /// while frames are being rendered; matches the readback throttle in
1800 /// `window_backdrop_sample`. A still desktop renders no frames and measures
1801 /// nothing at all.
1802 const BACKDROP_REFRESH: std::time::Duration = std::time::Duration::from_millis(250);
1803 
1804 impl Output {
1805     /// The refresh period to plan frames by: what the last `present`
1806     /// reported, else the current mode's rate, else 60 Hz.
1807     unsafe fn refresh_period_ns(&self) -> u64 {
1808         if self.present_refresh_ns > 0 {
1809             return self.present_refresh_ns;
1810         }
1811         let mhz = if self.wlr_output.is_null() { 0 } else { ffi::river_wlr_output_get_refresh(self.wlr_output) };
1812         if mhz > 0 {
1813             1_000_000_000_000 / mhz as u64
1814         } else {
1815             16_666_667
1816         }
1817     }
1818 
1819     /// When the frame rendered now is expected to reach the screen: the
1820     /// first point of a vblank grid after now (plus a small render lead).
1821     /// The camera animates to this instant, so its step is an exact whole
1822     /// number of refresh periods whether the frame callback ran early or
1823     /// late, a missed vblank is a double step rather than a stumble, and a
1824     /// second frame inside one period gets the same target (a zero step).
1825     ///
1826     /// The grid's phase locks to the hardware presentation timestamps and
1827     /// re-anchors only when they drift by more than a quarter period, so
1828     /// the per-present jitter of the timestamps themselves (and the
1829     /// headless backend's commit-time stamps) never reaches the camera.
1830     pub unsafe fn predicted_present_ns(&mut self) -> u64 {
1831         let now = util::timestamp_ns();
1832         let period = self.refresh_period_ns().max(1);
1833         if self.present_when_ns != 0 {
1834             let phase = self.present_when_ns % period;
1835             let drift = if self.frame_phase_ns == u64::MAX {
1836                 u64::MAX
1837             } else {
1838                 let d = phase.abs_diff(self.frame_phase_ns);
1839                 d.min(period - d)
1840             };
1841             if drift > period / 4 {
1842                 self.frame_phase_ns = phase;
1843             }
1844         } else if self.frame_phase_ns == u64::MAX {
1845             self.frame_phase_ns = now % period;
1846         }
1847         let lead = period / 8;
1848         let base = (now + lead).saturating_sub(self.frame_phase_ns);
1849         self.frame_phase_ns + (base / period + 1) * period
1850     }
1851 }
1852 
1853 unsafe extern "C" fn handle_frame(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
1854     let output = &mut *crate::container_of!(listener, Output, frame);
1855     // The camera steps here, on the vblank, to where it should be at the
1856     // instant THIS frame is presented (see WindowManager::step_camera_frame).
1857     let frame_target_ns = output.predicted_present_ns();
1858     (*output.server).wm.step_camera_frame(frame_target_ns);
1859     // Likewise the interactive move/resize: one configure + relayout per
1860     // vblank, for the pointer's latest position.
1861     (*output.server).wm.step_op_frame();
1862     let render_start = if frame_debug() {
1863         Some(std::time::Instant::now())
1864     } else {
1865         None
1866     };
1867     if let Err(e) = output.render_and_commit() {
1868         log::error!("{}", e);
1869     }
1870     if let Some(start) = render_start {
1871         // Epoch ms mod 100000 — the shared tracer time base (see cce-ui's
1872         // CCE_PRESENT_DEBUG), so compositor and client logs interleave.
1873         let t = std::time::SystemTime::now()
1874             .duration_since(std::time::UNIX_EPOCH)
1875             .unwrap()
1876             .as_millis()
1877             % 100000;
1878         log::info!(
1879             "[cce-frame] t={} output frame (render_and_commit {}us)",
1880             t,
1881             start.elapsed().as_micros()
1882         );
1883     }
1884     let now = util::timestamp();
1885     let mut ffi_now = ffi::timespec {
1886         tv_sec: now.tv_sec,
1887         tv_nsec: now.tv_nsec,
1888     };
1889     ffi::wlr_scene_output_send_frame_done(output.scene_output, &mut ffi_now);
1890     // The scene's frame-done pass is gated on a node being visible, and the
1891     // desktop grid spends most of its life behind opaque windows — so the
1892     // one client that MUST repaint on demand is the one whose callbacks dry
1893     // up. cce-ui's runner waits on a frame callback before it renders, and
1894     // only a 250ms starvation fallback unblocks it: every patch took a
1895     // quarter second to come back, which is longer than the overview ramp
1896     // and is why an exit's replacement patch used to land after the
1897     // animation. While a patch is in the air, drive the client directly.
1898     (*output.server).wm.send_frame_done_to_grid_clients_awaiting_patch();
1899 }
1900 
1901 unsafe extern "C" fn handle_present(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
1902     let output = &mut *crate::container_of!(listener, Output, present);
1903     let event = data as *mut ffi::wlr_output_event_present;
1904     if !(*event).presented {
1905         return;
1906     }
1907     // Presentation clock bookkeeping, and the dropped-frame count: a vblank
1908     // sequence that advanced by more than one since the last present means
1909     // frames were skipped. Backends without a counter (headless) report
1910     // seq 0; there the gap is inferred from time, but only while the camera
1911     // is animating — a still desktop legitimately presents nothing for ages.
1912     {
1913         let when_ns = (*event).when.tv_sec as u64 * 1_000_000_000 + (*event).when.tv_nsec as u64;
1914         if (*event).refresh > 0 {
1915             output.present_refresh_ns = (*event).refresh as u64;
1916         }
1917         let period = output.refresh_period_ns();
1918         let seq = (*event).seq as u32;
1919         let mut dropped = 0u64;
1920         if seq != 0 && output.present_seq != 0 && seq > output.present_seq + 1 {
1921             dropped = (seq - output.present_seq - 1) as u64;
1922         } else if seq == 0 && output.present_when_ns != 0 && (*output.server).wm.camera_anim_active {
1923             let gap = when_ns.saturating_sub(output.present_when_ns);
1924             let periods = (gap + period / 2) / period;
1925             dropped = periods.saturating_sub(1);
1926         }
1927         if dropped > 0 {
1928             output.present_dropped += dropped;
1929             if frame_debug() {
1930                 log::info!(
1931                     "[cce-frame] present seq={} dropped={} (total {}) refresh={}us",
1932                     seq,
1933                     dropped,
1934                     output.present_dropped,
1935                     period / 1000
1936                 );
1937             }
1938         }
1939         output.present_when_ns = when_ns;
1940         output.present_seq = seq;
1941     }
1942     match output.lock_render_state {
1943         LockRenderState::PendingUnlock => {
1944             output.lock_render_state = LockRenderState::Unlocked;
1945         }
1946         LockRenderState::PendingBlank => {
1947             output.lock_render_state = LockRenderState::Blanked;
1948             (*output.server).lock_manager.maybe_lock();
1949         }
1950         LockRenderState::PendingLockSurface => {
1951             output.lock_render_state = LockRenderState::LockSurface;
1952             (*output.server).lock_manager.maybe_lock();
1953         }
1954         _ => {}
1955     }
1956 }
1957 
1958 // Helpers for raw Wayland FFI protocol events
1959 pub unsafe fn zcce_output_send_removed(resource: *mut ffi::wl_resource) {
1960     ffi::wl_resource_post_event(resource, 0);
1961 }
1962 
1963 pub unsafe fn zcce_output_send_wl_output(resource: *mut ffi::wl_resource, name: u32) {
1964     ffi::wl_resource_post_event(resource, 1, name);
1965 }
1966 
1967 pub unsafe fn zcce_output_send_position(resource: *mut ffi::wl_resource, x: i32, y: i32) {
1968     ffi::wl_resource_post_event(resource, 2, x, y);
1969 }
1970 
1971 pub unsafe fn zcce_output_send_dimensions(resource: *mut ffi::wl_resource, width: i32, height: i32) {
1972     ffi::wl_resource_post_event(resource, 3, width, height);
1973 }