Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Restore focused window state on startup and prevent focus stealing race condition
src/server/window.rs | 32 +++++++++++++++++++++++++-------
src/server/window_manager.rs | 18 +++++++++++++++++-
2 files changed, 42 insertions(+), 8 deletions(-)
diff --git a/src/server/window.rs b/src/server/window.rs
index 7368a58..421a796 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -210,6 +210,7 @@ pub struct Window {
pub mode_locked: bool,
pub is_new: bool,
pub restored: bool,
+ pub restored_focused: bool,
pub closed: bool,
pub has_parent: bool,
pub minimized: bool,
@@ -366,6 +367,7 @@ impl Window {
mode_locked: false,
is_new: true,
restored: false,
+ restored_focused: false,
closed: false,
has_parent: false,
minimized: false,
@@ -664,6 +666,7 @@ impl Window {
}
self.restored = true;
+ self.restored_focused = saved.focused;
}
}
@@ -693,13 +696,28 @@ impl Window {
if is_status_bar {
self.tiling_mode = crate::tiling::TilingMode::Status;
} else {
- let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
- let mut curr = (*seats).next;
- while curr != seats {
- let next = (*curr).next;
- let seat = crate::container_of!(curr, crate::seat::Seat, link);
- (*seat).focus(crate::seat::Focus::Window(self as *mut Window));
- curr = next;
+ let mut should_focus = true;
+ if self.restored {
+ if self.restored_focused {
+ (*self.server).wm.restored_focused_window_mapped = true;
+ log::info!("[FocusRestore] Restored focused window mapped: {:?}", self.get_title());
+ } else {
+ if (*self.server).wm.has_restored_focused_window && (*self.server).wm.restored_focused_window_mapped {
+ log::info!("[FocusRestore] Blocking focus to non-focused restored window {:?} because restored focused window is already mapped", self.get_title());
+ should_focus = false;
+ }
+ }
+ }
+
+ if should_focus {
+ let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ (*seat).focus(crate::seat::Focus::Window(self as *mut Window));
+ curr = next;
+ }
}
}
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 2117bdb..3ff04b8 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -53,6 +53,8 @@ pub struct SavedWindowState {
pub width: u32,
pub height: u32,
pub cmdline: String,
+ #[serde(default)]
+ pub focused: bool,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
@@ -101,6 +103,8 @@ pub struct WindowManager {
pub target_desk_pan_x: Option<f64>,
pub target_desk_pan_y: Option<f64>,
pub animation_timer: *mut ffi::wl_event_source,
+ pub has_restored_focused_window: bool,
+ pub restored_focused_window_mapped: bool,
}
impl WindowManager {
@@ -153,6 +157,8 @@ impl WindowManager {
self.shutting_down = false;
self.layout = crate::config::Layout::default();
self.output_scale = 1.0;
+ self.has_restored_focused_window = false;
+ self.restored_focused_window_mapped = false;
self.mode_rules = Vec::new();
self.keybinds = Vec::new();
self.pointer_binds = Vec::new();
@@ -211,7 +217,13 @@ impl WindowManager {
self.desk_zoom = state.desk_zoom;
self.global_layout = state.global_layout;
self.restore_queue = state.windows;
- log::info!("State loaded successfully. {} windows in restore queue.", self.restore_queue.len());
+ self.has_restored_focused_window = self.restore_queue.iter().any(|w| w.focused);
+ self.restored_focused_window_mapped = false;
+ log::info!(
+ "State loaded successfully. {} windows in restore queue, has_restored_focused_window={}.",
+ self.restore_queue.len(),
+ self.has_restored_focused_window
+ );
} else {
log::error!("Failed to parse state JSON from {}", path);
}
@@ -230,6 +242,7 @@ impl WindowManager {
};
log::debug!("Saving state to {}", path_str);
+ let focused_win = self.focused_window();
let mut saved_wins = Vec::new();
for &w in self.windows.iter() {
if w.is_null() || (*w).closed || matches!((*w).state, crate::window::WindowState::Closing | crate::window::WindowState::Init) {
@@ -256,6 +269,8 @@ impl WindowManager {
cmdline = app_id.clone();
}
+ let is_focused = w == focused_win;
+
saved_wins.push(SavedWindowState {
app_id,
title,
@@ -267,6 +282,7 @@ impl WindowManager {
width: (*w).box_geom.width as u32,
height: (*w).box_geom.height as u32,
cmdline,
+ focused: is_focused,
});
}