Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
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()