zed: Persist window stack order across restarts (#15419)

This changes the workspace/session serialization to also persist the
order of windows across restarts.

Release Notes:

- Improved restoring of windows across restarts: the order of the
windows is now also restored. That means windows that were in the
foreground when Zed was quit will be in the foreground after restart.
(Right now only supported on Linux/X11, not on Linux/Wayland.)

Demo:



https://github.com/user-attachments/assets/0b8162f8-f06d-43df-88d3-c45d8460fb68
This commit is contained in:
Thorsten Ball 2024-07-29 17:05:56 +02:00 committed by GitHub
parent 6e1f7c6e1d
commit f58ef9b82b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 365 additions and 55 deletions

View file

@ -469,6 +469,15 @@ impl AppContext {
.collect()
}
/// Returns the window handles ordered by their appearance on screen, front to back.
///
/// The first window in the returned list is the active/topmost window of the application.
///
/// This method returns None if the platform doesn't implement the method yet.
pub fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
self.platform.window_stack()
}
/// Returns a handle to the window that is currently focused at the platform level, if one exists.
pub fn active_window(&self) -> Option<AnyWindowHandle> {
self.platform.active_window()

View file

@ -121,6 +121,9 @@ pub(crate) trait Platform: 'static {
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn active_window(&self) -> Option<AnyWindowHandle>;
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
None
}
fn open_window(
&self,

View file

@ -63,6 +63,10 @@ impl LinuxClient for HeadlessClient {
None
}
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
None
}
fn open_window(
&self,
_handle: AnyWindowHandle,

View file

@ -77,6 +77,7 @@ pub trait LinuxClient {
fn read_from_primary(&self) -> Option<ClipboardItem>;
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn active_window(&self) -> Option<AnyWindowHandle>;
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>>;
fn run(&self);
}
@ -144,11 +145,10 @@ impl<P: LinuxClient + 'static> Platform for P {
LinuxClient::run(self);
self.with_common(|common| {
if let Some(mut fun) = common.callbacks.quit.take() {
fun();
}
});
let quit = self.with_common(|common| common.callbacks.quit.take());
if let Some(mut fun) = quit {
fun();
}
}
fn quit(&self) {
@ -240,6 +240,10 @@ impl<P: LinuxClient + 'static> Platform for P {
self.active_window()
}
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
self.window_stack()
}
fn open_window(
&self,
handle: AnyWindowHandle,

View file

@ -750,6 +750,10 @@ impl LinuxClient for WaylandClient {
.map(|window| window.handle())
}
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
None
}
fn compositor_name(&self) -> &'static str {
"Wayland"
}

View file

@ -1345,6 +1345,48 @@ impl LinuxClient for X11Client {
.map(|window| window.handle())
})
}
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
let state = self.0.borrow();
let root = state.xcb_connection.setup().roots[state.x_root_index].root;
let reply = state
.xcb_connection
.get_property(
false,
root,
state.atoms._NET_CLIENT_LIST_STACKING,
xproto::AtomEnum::WINDOW,
0,
u32::MAX,
)
.ok()?
.reply()
.ok()?;
let window_ids = reply
.value
.chunks_exact(4)
.map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap()))
.collect::<Vec<xproto::Window>>();
let mut handles = Vec::new();
// We need to reverse, since _NET_CLIENT_LIST_STACKING has
// a back-to-front order.
// See: https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html
for window_ref in window_ids
.iter()
.rev()
.filter_map(|&win| state.windows.get(&win))
{
if !window_ref.window.state.borrow().destroyed {
handles.push(window_ref.handle());
}
}
Some(handles)
}
}
// Adatpted from:

View file

@ -56,6 +56,7 @@ x11rb::atom_manager! {
_GTK_SHOW_WINDOW_MENU,
_GTK_FRAME_EXTENTS,
_GTK_EDGE_CONSTRAINTS,
_NET_CLIENT_LIST_STACKING,
}
}

View file

@ -522,6 +522,12 @@ impl Platform for MacPlatform {
MacWindow::active_window()
}
// Returns the windows ordered front-to-back, meaning that the active
// window is the first one in the returned vec.
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
Some(MacWindow::ordered_windows())
}
fn open_window(
&self,
handle: AnyWindowHandle,

View file

@ -738,6 +738,25 @@ impl MacWindow {
}
}
}
pub fn ordered_windows() -> Vec<AnyWindowHandle> {
unsafe {
let app = NSApplication::sharedApplication(nil);
let windows: id = msg_send![app, orderedWindows];
let count: NSUInteger = msg_send![windows, count];
let mut window_handles = Vec::new();
for i in 0..count {
let window: id = msg_send![windows, objectAtIndex:i];
if msg_send![window, isKindOfClass: WINDOW_CLASS] {
let handle = get_window_state(&*window).lock().handle;
window_handles.push(handle);
}
}
window_handles
}
}
}
impl Drop for MacWindow {

View file

@ -4576,6 +4576,12 @@ impl WindowId {
}
}
impl From<u64> for WindowId {
fn from(value: u64) -> Self {
WindowId(slotmap::KeyData::from_ffi(value))
}
}
/// A handle to a window with a specific root view type.
/// Note that this does not keep the window alive on its own.
#[derive(Deref, DerefMut)]