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

commitbbd73499d630e247de9953d557253a58bde4a88a
parent462986dd27
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-30 10:18
A mode_rule can centre a prompt on the view

`center=(bool)true` on a mode_rule puts the window on the session
modals' path (`try_center_on_view`): Floating, mode-locked, centred on
the camera as it stands at map, position discarded and size kept,
re-centred when a self-sizer's real size lands. The predicate is now
`wants_view_center`, the hardcoded app_id list OR a matching rule.

1Password's authorization popup is a parentless Electron toplevel under
the vault window's app_id with the bare title "1Password"; it saved as
Tiled and reopened at that one spot on the desk wherever the camera
was. Two rules tell it from the vault window, whose titles all end in
" — 1Password": substring matching, first rule wins.

Verified in a shadow with float-pair --dialog-honours-configure: pan the
camera, respawn, and the dialog maps at the screen's centre at its own
400x370 while the main window stays put.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

 CLAUDE.md                    | 25 +++++++++++++++++++++++++
 src/server/config.rs         | 11 ++++++++++-
 src/server/window.rs         | 21 ++++++++++++++++-----
 src/server/window_manager.rs |  1 +
 4 files changed, 52 insertions(+), 6 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 5b741b33..328f41bb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -819,6 +819,31 @@ with `verify/clients` `float-pair --dialog-honours-configure`: tile the
 main window, and the "Authorize" window maps Tiled at 1404x1076 without a
 rule, Floating at its own 400x370 over the main window with one.
 
+**A prompt opens centred on the view** with a `mode_rule` that says
+`center` (since 2026-09-30):
+
+```kdl
+mode_rule mode="tiled" app_id="com.onepassword.OnePassword" title=" — 1Password"
+mode_rule mode="floating" app_id="com.onepassword.OnePassword" title="1Password" center=(bool)true
+```
+
+It puts the window on the same path as the built-in session modals
+(`Window::try_center_on_view`, gated by `wants_view_center`: the hardcoded
+`cce-authenticator`/`cce-filesystem-chooser` list OR a matching rule):
+forced Floating and `mode_locked`, never minimized, centred on the camera
+as it stands at map with the remembered SIZE kept and the position
+discarded, re-centred on the commit that brings a self-sizer's real size,
+and `hint_placed` so no spawn pan follows it. 1Password's authorization
+popup is a parentless Electron toplevel under the vault window's app_id
+with the bare title `1Password`; it saved as Tiled and reopened at that
+one spot on the desk wherever the camera was. Two rules because title
+matching is substring and the first match wins: the vault window's titles
+all end in ` — 1Password`, so the first rule takes them and only the bare
+title reaches the second. Reproduce with `float-pair --app-id X
+--dialog-honours-configure` under `title="Main window"` / `title="Authorize"
+center` rules: pan the camera, respawn, and `ctl windows` shows the
+dialog at the screen's centre while the main window sits where it was.
+
 **A client reconnecting maps unfocused**: a window that vanishes without
 the compositor asking it to close (`Window::unmap` → `note_vanished`) lets
 the next window of the same app_id AND the same program (`proc_args`
diff --git a/src/server/config.rs b/src/server/config.rs
index bb181e7c..2de35047 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -308,6 +308,12 @@ pub struct ModeRule {
     /// saved state, sizes itself, and maps centred over a mapped sibling of
     /// the same app_id. See `Window::try_center_on_sibling`.
     pub over_sibling: bool,
+    /// The window opens centred on the current view, whatever position is
+    /// remembered for it: a prompt the user has to answer now (1Password's
+    /// authorization popup). Placement only — the remembered size still
+    /// applies, and a self-sizing window is re-centred once its real size
+    /// lands. See `Window::try_center_on_view`.
+    pub center: bool,
 }
 
 pub use cce_window_manager::api::Action;
@@ -1039,6 +1045,7 @@ pub struct ModeRuleConfig {
     pub circular: Option<bool>,
     pub ssd: Option<bool>,
     pub over_sibling: Option<bool>,
+    pub center: Option<bool>,
 }
 
 #[derive(Debug, Deserialize)]
@@ -1794,7 +1801,8 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
                 let circular = get_prop_bool_opt(node, "circular");
                 let ssd = get_prop_bool_opt(node, "ssd");
                 let over_sibling = get_prop_bool_opt(node, "over_sibling");
-                mode_rule.push(ModeRuleConfig { mode, app_id, title, single, tag, circular, ssd, over_sibling });
+                let center = get_prop_bool_opt(node, "center");
+                mode_rule.push(ModeRuleConfig { mode, app_id, title, single, tag, circular, ssd, over_sibling, center });
             }
             "tag_layout" => {
                 let tag = get_prop_i64(node, "tag", 0);
@@ -2981,6 +2989,7 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
             circular: rule.circular.unwrap_or(false),
             ssd: rule.ssd,
             over_sibling: rule.over_sibling.unwrap_or(false),
+            center: rule.center.unwrap_or(false),
         });
     }
 
diff --git a/src/server/window.rs b/src/server/window.rs
index 6130abb7..ae564dbc 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -1892,9 +1892,12 @@ impl Window {
     ///
     /// Hardcoded by app_id like the compositor's other DE-internal window
     /// classes (`cce-status*`/`cce-wallpaper`/`cce-grid` in `try_restore`,
-    /// `cce-notifier`/`cce-cloud` in `get_mode_for_window`). The config's
-    /// per-app window rules assign a tiling MODE, not a placement, so there
-    /// is nothing there to hang this off yet.
+    /// `cce-notifier`/`cce-cloud` in `get_mode_for_window`). A third-party
+    /// prompt asks for the same treatment through a `mode_rule` with
+    /// `center` (`wants_view_center`): 1Password's authorization popup is a
+    /// parentless Electron toplevel under the vault window's app_id, told
+    /// apart by its bare title, and it restored Tiled to wherever it was
+    /// last answered.
     fn is_view_centered_modal(app_id: &str) -> bool {
         // The polkit prompt, and the file chooser cce-files runs in --select/
         // --save mode: both are spawned BY an action in the current view and
@@ -1904,9 +1907,17 @@ impl Window {
         app_id == "cce-authenticator" || app_id == "cce-filesystem-chooser"
     }
 
-    unsafe fn try_center_on_view(&mut self) {
+    /// The built-in modal list, or a matching `mode_rule` that says `center`.
+    unsafe fn wants_view_center(&mut self) -> bool {
         let app_id = self.get_app_id_string().unwrap_or_default();
-        if !Self::is_view_centered_modal(&app_id) {
+        if Self::is_view_centered_modal(&app_id) {
+            return true;
+        }
+        (*self.server).wm.get_rule_for_window(self as *mut Window).map_or(false, |r| r.center)
+    }
+
+    unsafe fn try_center_on_view(&mut self) {
+        if !self.wants_view_center() {
             return;
         }
 
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 8602d35c..500fce6c 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -6195,6 +6195,7 @@ impl WindowManager {
                     circular: false,
                     ssd: None,
                     over_sibling: false,
+                    center: false,
                 });
                 self.dirty_windowing();
                 "ok\n".to_string()