diff --git a/AGENTS.md b/AGENTS.md index 6838ba7..e9ae9aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,4 +93,4 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does; - 2026-10-15: on Omarchy 4 (Linux) ELYTerminal follows the system's theme. Omarchy renders the current theme's `colors.toml` through its own resolver into `~/.local/state/omarchy/current/theme/ghostty.conf`, under `$HOME` whatever `XDG_STATE_HOME` says, so ELYTerminal reads that file and wears what Ghostty wears there: its color lines go through the strict parser the bundled themes use, and the rest, such as the glass block, pass by. Omarchy writes no `cursor-text`, so the background stands in, as in Ghostty and Omarchy's kitty template. ELYTerminal reads the file twice a second, since Omarchy signals only Alacritty, kitty and Ghostty, by name, and wears it whenever its text changes and parses; a check that fails is logged once while it lasts and tried again, so a file made readable again is worn though nothing else about it changed. The Omarchy theme is the default unless that file is surely absent, so a folder that cannot be read makes a fresh install wear it and the start name the error; another pick stops following. At start a missing file means no Omarchy unless the settings wear it, a file that cannot be read stops the start, and one that does not parse stops it only when worn, else it is logged and no Omarchy card shows. While running, a missing file is a switch in progress, and any other failure keeps the last theme. Omarchy 3's `~/.config/omarchy/current` gets no shim. - 2026-10-16: on Hyprland 0.56 a window blurs only while Hyprland's own `decoration:blur:enabled` is on: its `shouldBlur` reads that option first, then the window's `no_blur`, RGBX and opaque rules, then the blur region a surface sets through `ext_background_effect_v1`, which Hyprland offers with the blur capability whatever the option says. Omarchy 4.0.4's default `looknfeel.lua` turns the option off, so on Omarchy no window blurs until the user turns it on; ELYTerminal leaves the compositor's settings alone. On the owner's Omarchy the log named `ext_background_effect_v1` and the pane's region, and the wallpaper behind the window blurred with the option on and showed sharp with it off. - 2026-10-16: herdr 0.9 keeps its panes' OSC 7 and writes only a title, notifications, OSC 52 and bells to the terminal it runs in, so no terminal hears a folder from inside it; herdr's server knows each pane's `cwd`, the folder its shell reported, else the shell's own. On macOS and Linux each session asks twice a second which process group leads its pty's foreground (`tcgetpgrp` on a copy of the master) and what executable that group's leader runs: an exec keeps the group and swaps the executable, so `exec herdr` from a shell is a new program, and one that did not read is tried again; on Linux the ` (deleted)` mark an update leaves on a running executable is set aside. When that executable is `herdr` and its arguments start herdr's terminal client (no command, `session attach NAME` or `client`), ELYTerminal reads the client's arguments and variables (`/proc` on Linux; `KERN_PROCARGS2` on macOS, which hides only the variables of Apple's own programs) and finds its socket as herdr 0.9 does: `--session NAME`, then `HERDR_SOCKET_PATH`, then `HERDR_SESSION`, under `$XDG_CONFIG_HOME/herdr`, else `~/.config/herdr`; a relative socket lies in the client's folder, as herdr reads it (`/proc/PID/cwd`; `proc_pidinfo` on macOS). It asks that socket for `session.snapshot` twice a second, a connection each, since herdr closes one after its reply, and reports the focused pane's `cwd` as the session's folder when it changes and again each time herdr takes the foreground. Focus belongs to herdr's server, so clients of one server follow one focus; a client of another machine's server (`--remote`, `--machine`) is logged, not followed. A failure is logged once while it lasts. Windows has none of this: there herdr is a beta on named pipes. -- 2026-10-16: the main window opens at the size it was left at, which the owner asked for on macOS, Windows and Linux: `window_width` and `window_height` in the settings hold the size gpui's `window_bounds` gives 0.5 s after the bounds settle, saved through `SettingsStore::update`, which writes only a change, so a drag saves once and a move saves nothing. A maximized, full-screen or hidden window keeps the size from before; its state and size are read once they settle, since X11 marks a window maximized after its new size, gpui's X11 window then gives a maximized window's own size, and it calls a maximized window unmaximized while minimized. The window is no smaller than 100 by 100, the least the settings keep, or a smaller size the platform allows could not be kept. The window opens centered, its place not kept, and a kept side past the primary display's visible area shrinks to it, logged. A size outside 100 to 16384 stops the start, as any value out of range does. +- 2026-10-16: the main window opens at the size it was left at, which the owner asked for on macOS, Windows and Linux: `window_width` and `window_height` in the settings hold the size gpui's `window_bounds` gives 0.5 s after the bounds settle, saved through `SettingsStore::update`, which writes only a change, so a drag saves once and a move saves nothing. A maximized, full-screen or hidden window keeps the size from before; its state and size are read once they settle, since X11 marks a window maximized after its new size, gpui's X11 window then gives a maximized window's own size, and it calls a maximized window unmaximized while minimized. A window that shows again, uncovered, restored or back on its screen, is read again, so a size it took just before it hid is kept; a size it takes in the half second before it closes, or before it hides until the app quits, is lost. The window is no smaller than 100 by 100, the least the settings keep, or a smaller size the platform allows could not be kept. The window opens centered at whole points, since macOS grows a window by a point on a side whose edge falls on a half point; its place is not kept, and a kept side past the primary display's visible area shrinks to it, logged. A size outside 100 to 16384 stops the start, as any value out of range does. diff --git a/src/app.rs b/src/app.rs index f257363..e255963 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, time::Duration}; +use std::{borrow::Cow, cell::RefCell, rc::Rc, time::Duration}; use anyhow::Context as _; use ely_gpui_component::theme::ActiveTheme; @@ -89,7 +89,9 @@ pub fn start(cx: &mut App) -> anyhow::Result<()> { /// Opens the main window running `launch`. pub fn open(launch: Launch, cx: &mut App) -> anyhow::Result> { let kept = opening_size(cx); - let bounds = Bounds::centered(None, kept, cx); + // macOS grows a window by a point on a side whose edge falls on a half point. + let centered = Bounds::centered(None, kept, cx); + let bounds = Bounds::new(centered.origin.map(|at| at.round()), kept); let background = blur::appearance(SettingsStore::get(cx)); let least = px(*settings::WINDOW.start()); let options = WindowOptions { @@ -149,9 +151,9 @@ fn opening_size(cx: &App) -> Size { /// Keeps the window's size in the settings once it settles; maximized, full screen or hidden, the size before stays. fn keep_size(window: &mut Window, cx: &mut Context) { - let mut _settling: Option> = None; - cx.observe_window_bounds(window, move |_, window, cx| { - _settling = Some(cx.spawn_in(window, async move |_, cx| { + let settling: Rc>>> = Rc::default(); + let settle = move |window: &mut Window, cx: &mut Context| { + let task = cx.spawn_in(window, async move |_, cx| { cx.background_executor().timer(SETTLE).await; // Read once settled: X11 marks a window maximized late, and unmaximized while hidden. let kept = cx.update(|window, cx| { @@ -179,7 +181,17 @@ fn keep_size(window: &mut Window, cx: &mut Context) { Ok(Err(error)) => log::error!("{error:#}"), Err(_) => log::debug!("app: the window closed before its size settled"), } - })); + }); + settling.replace(Some(task)); + }; + let shown = settle.clone(); + cx.observe_window_bounds(window, move |_, window, cx| settle(window, cx)) + .detach(); + // A size skipped while the window hid is read again once it shows. + cx.observe_window_visibility(window, move |_, visibility, window, cx| { + if visibility.is_visible() { + shown(window, cx); + } }) .detach(); } diff --git a/tasks/acceptance.md b/tasks/acceptance.md index 7dba5fc..f1d5220 100644 --- a/tasks/acceptance.md +++ b/tasks/acceptance.md @@ -70,7 +70,7 @@ What no check reached: no one has looked at the window on Windows, which CI buil |---|---|---|---| | `ColumnShell` with three columns; blur when the setting is on | T11, T19 | `shot` names a `BlurredView` under the window | Pass | | Columns toggle and keep their state | T11, T23 | a click on each title bar button hid its column and saved it, and a restart opened the window with both hidden (147 columns against 87); Cmd-B with the app active; Ctrl-Shift-B on a real X11 window | Pass | -| The window opens at the size it was left at; maximized or full screen, the size before stays; a kept size past the screen shrinks to it | T30 | `a_value_out_of_range_names_its_field`; on X11 under xfwm4: the first start opened at 1200 by 760 and wrote nothing; a resize to 900 by 600 was kept with one write, and the next start opened at 900 by 600 and wrote nothing; maximized by xfwm4's Alt+F10 the window spanned the 1400 by 900 screen while the file kept 900 by 600, and restored it was 900 by 600 again; maximized, minimized and restored, the file kept 900 by 600, and the next start opened at 900 by 600; `WM_NORMAL_HINTS` names a 100 by 100 minimum, and a resize to 900 by 50 held at 900 by 100, kept so; a kept 3000 by 2000 opened at 1400 by 900, logged; on macOS under `shot`, a kept 900 by 600 opened at 900 by 600 and a kept 5000 by 4000 at the visible 1920 by 1113, logged; a resize on macOS and any run on Windows not yet shown | Partial | +| The window opens at the size it was left at; maximized or full screen, the size before stays; a kept size past the screen shrinks to it | T30 | `a_value_out_of_range_names_its_field`; on X11 under xfwm4: the first start opened at 1200 by 760 and wrote nothing; a resize to 900 by 600 was kept with one write; maximized by xfwm4's Alt+F10 the window spanned the 1400 by 900 screen while the file kept 900 by 600, and restored it was 900 by 600 again; maximized, minimized and restored, the file kept 900 by 600; resized to 950 by 650 and minimized at once, the file kept 900 by 600 while minimized and 950 by 650 once restored; the next start opened at 950 by 650 and wrote nothing; `WM_NORMAL_HINTS` names a 100 by 100 minimum, and a resize to 900 by 50 held at 900 by 100, kept so; a kept 3000 by 2000 opened at 1400 by 900, logged; on macOS, through gpui's own window: a kept 900 by 600 opened at 900 by 600, where a centered edge on a half point had grown it to 601, and a move wrote nothing; a resize to 923 by 617, covered by an opaque window within 150 ms and uncovered, was kept once shown, and the next start opened at 923 by 617; zoomed, minimized, restored, full screen and back, the file kept 923 by 617; a 100 by 100 window was kept; a kept 5000 by 4000 opened at the visible 1920 by 1113, logged; a drag on macOS and any run on Windows not yet shown | Partial | | The title shows the session's title, nothing else | T11 | log: "the title is", per session shown | Pass | | The pointer over the terminal is the arrow | T26 | the terminal sets no pointer, so gpui's arrow shows; on X11 with the pointer over the grid, gpui asked for no I-beam ("text"), which it had asked for, and failed to load under Xvfb, before | Pass | | The title bar is transparent | T11, T19 | T19 review | Pass |