Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a9941bfd2
|
||
|
|
9e185eab58
|
||
|
|
55250f49f5
|
||
|
|
17e91aafef
|
||
|
|
3759cf8825
|
||
|
|
15153b4aae
|
No files matched your search
@@ -33,11 +33,11 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
|
|||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
- `build.rs`: on Windows, the icon into the executable as resource 1, through `assets/icons/elyterminal.rc`. `.cargo/config.toml`: Windows builds carry the C runtime. `rust-toolchain.toml`: the toolchain rustup installs.
|
- `build.rs`: on Windows, the icon into the executable as resource 1, through `assets/icons/elyterminal.rc`. `.cargo/config.toml`: Windows builds carry the C runtime. `rust-toolchain.toml`: the toolchain rustup installs.
|
||||||
- `src/main.rs`: start. `src/lib.rs`: the modules, public so tests and examples reach them. `src/app.rs`: fonts, settings, integration, then the main window, at the size the settings keep, which it keeps as it settles. `src/menus.rs`: the menu bar, the keys of Quit, the font's size and Minimize, and what they do. `src/blur.rs`: what the window shows of the desktop, from the opacity and blur settings, set as they change; `blur/macos` gives gpui's NSVisualEffectView a material that blurs; `blur/wayland` asks a Wayland compositor for blur behind the window's surface through `ext_background_effect_v1`; `blur/x11` names the window's pane in `_KDE_NET_WM_BLUR_BEHIND_REGION`. `src/workspace.rs`: the window's content, Ely's `ColumnShell` with the sessions, terminal and right columns, their toggles, the right column's Git and Files switch, the title, and the sessions' lifecycle. `src/workspace/widths.rs`: the side columns' widths, which a drag on their border sets and the settings keep once it rests. `src/workspace/events.rs`: what a session's terminal reports: title, folder, bell, the state its row shows, the program's exit; and a press on a notification a session posted. `src/sessions.rs`: a session and its row, with the mark of its status and what hover shows of it; `sessions/orb` draws the mark, 3 by 3 dots on one clock. `src/files.rs`: the Files panel, the shown session's folder as Ely's `Tree` with Git's marks. `src/git.rs`: git through its command line: Files' marks, and the Git panel's status, stage, unstage, discard, commit and log; `src/git/diff.rs` reads git's diff into Ely's stretches. `src/git/panel.rs`: the Git panel's state and actions; `src/git/panel/view.rs`: how it draws.
|
- `src/main.rs`: start. `src/lib.rs`: the modules, public so tests and examples reach them. `src/app.rs`: fonts, settings, integration, then the main window, at the size the settings keep, which it keeps as it settles. `src/menus.rs`: the menu bar, the keys of Quit, the font's size and Minimize, and what they do. `src/blur.rs`: what the window shows of the desktop, from the opacity and blur settings, set as they change; `blur/macos` gives gpui's NSVisualEffectView a material that blurs; `blur/wayland` asks a Wayland compositor for blur behind the window's surface through `ext_background_effect_v1`; `blur/x11` names the window's pane in `_KDE_NET_WM_BLUR_BEHIND_REGION`. `src/workspace.rs`: the window's content, Ely's `ColumnShell` with the sessions, terminal and right columns, their toggles, the right column's Git and Files switch, the title, and the sessions' lifecycle. `src/workspace/widths.rs`: the side columns' widths, which a drag on their border sets and the settings keep once it rests. `src/workspace/events.rs`: what a session's terminal reports: title, folder, bell, the state its row shows, the program's exit; and a press on a notification a session posted. `src/sessions.rs`: a session and its row, with the mark of its status and what hover shows of it; `sessions/orb` draws the mark, 3 by 3 dots on one clock. `src/files.rs`: the Files panel, the shown session's folder as Ely's `Tree` with Git's marks. `src/git.rs`: git through its command line: Files' marks, and the Git panel's status, stage, unstage, discard, commit and log; `src/git/diff.rs` reads git's diff into Ely's stretches. `src/git/panel.rs`: the Git panel's state and actions; `src/git/panel/view.rs`: how it draws. `src/updates.rs`: checks for a newer release through `curl` and Gitea's API, at start and once a day while the settings ask, and when Settings › Terminal asks.
|
||||||
- `src/pet.rs`: Ely, peeking over the main window's top edge from a window of her own while the settings want her, or up from its bottom edge where no room lies above it; she draws at every frame the display gives her window; `pet/rig` draws her from the icon's paths in layers of one color, each moved by a matrix of its own, as the films rig her; `pet/look` reads her mood from the sessions' marks and her pose at each moment of it, `look/curves` the timings of her moves; and `pet/macos`, `pet/windows` and `pet/x11` keep her window on the main window's edge where the platform does not.
|
- `src/pet.rs`: Ely, peeking over the main window's top edge from a window of her own while the settings want her, or up from its bottom edge where no room lies above it; she draws at every frame the display gives her window; `pet/rig` draws her from the icon's paths in layers of one color, each moved by a matrix of its own, as the films rig her; `pet/look` reads her mood from the sessions' marks and her pose at each moment of it, `look/curves` the timings of her moves; and `pet/macos`, `pet/windows` and `pet/x11` keep her window on the main window's edge where the platform does not.
|
||||||
- `src/settings`: the model, its ranges, and `SettingsStore`, the global that loads, checks and saves `settings.json`; `window` is the settings window, `window/appearance` its Appearance page, `window/terminal` its Terminal page. `tests/fonts.rs` brings its own main: gpui builds its Mac platform only on the main thread.
|
- `src/settings`: the model, its ranges, and `SettingsStore`, the global that loads, checks and saves `settings.json`; `window` is the settings window, `window/appearance` its Appearance page, `window/terminal` its Terminal page. `tests/fonts.rs` brings its own main: gpui builds its Mac platform only on the main thread.
|
||||||
- `src/themes`: built-in terminal themes, the chrome palette each maps to, and `apply`, which dresses the app for the system's appearance; `omarchy`, on Linux, wears Omarchy 4's current theme and follows its switches.
|
- `src/themes`: built-in terminal themes, the chrome palette each maps to, and `apply`, which dresses the app for the system's appearance; `omarchy`, on Linux, wears Omarchy 4's current theme and follows its switches.
|
||||||
- `src/term`: the terminal engine. `pty` starts a program through alacritty's event loop; `tap` wraps the pty so its reader scans OSC 0, 2, 7, 9, 99, 133, 777 and 7501, the title stack's CSI 22 t and 23 t, full resets, the mouse modes and, on Windows, win32-input-mode (`tap/modes`) before the parser; `titles` keeps the title a session shows, read in the output's order; `marks` reads OSC 133's prompt marks; `status` reads OSC 7501 and keeps a terminal's records; `notify` reads OSC 9, 777 and 99, the notifications and OSC 9;4's progress, and gathers OSC 99's chunks; `reports` is the view's side of what programs report: it keeps OSC 7501's records, maps OSC 9;4, answers OSC 99's query and alive, and posts the system notifications its session posts; `prompts` turns each mark into an OSC 8 link the parser keeps in a prompt's cells, and finds the prompt to jump to; `herdr` follows herdr's focused pane and its agents' states through herdr's socket while herdr's client runs in the session, and tells the view herdr leads, so herdr opens its links: `herdr/unix` finds the client leading the pty's foreground, `herdr/windows` under the session's shell, and asks its named pipe. `unix` starts the program itself, so a failed `chdir` fails the start; Windows uses alacritty's ConPTY. `shell` writes the integration scripts at start and builds each shell's launch. `frame` reads the grid into styled rows, `boxes` turns box drawing, block elements and powerline arrows into rects and polygons (`boxes/outline` strokes curves and diagonals into polygons), `ink` resolves colors, `paint` draws rows, decorations and the cursor, `view` is the entity: pty events, metrics, blink, resize. `zoom` holds the points Bigger and Smaller add to the Size setting, unsaved. `keys` turns key events into bytes, the kitty protocol and, in `keys/legacy`, xterm's encoding, with no gpui types; `input` is the view's key listeners, the bindings that keep Tab and F6 from Ely's focus moves, and its IME handler; `mac` reads the Option side, the repeat flag and the key code of AppKit's current event, and the key's own character from the layout, as gpui reads key names; it keeps held keys repeating, and hands Command key-ups to the focused view. `win32` sends a key as Windows' own record, win32-input-mode, and finds a key's own character in Windows' layout. `mouse` encodes mouse reports, with no gpui types; `links` finds the link at a cell; `pointer` is the view's mouse listeners: selection, reports, wheel, links, copy and paste. `find` is the find box: Ely's `FindWidget`, and Ely's `find_all` over the grid's shown text.
|
- `src/term`: the terminal engine. `pty` starts a program through alacritty's event loop; `tap` wraps the pty so its reader scans OSC 0, 2, 7, 9, 99, 133, 777 and 7501, the title stack's CSI 22 t and 23 t, full resets, the mouse modes and, on Windows, win32-input-mode (`tap/modes`) before the parser; `titles` keeps the title a session shows, read in the output's order; `marks` reads OSC 133's prompt marks; `status` reads OSC 7501 and keeps a terminal's records; `notify` reads OSC 9, 777 and 99, the notifications and OSC 9;4's progress, and gathers OSC 99's chunks; `reports` is the view's side of what programs report: it keeps OSC 7501's records, maps OSC 9;4, answers OSC 99's query and alive, and posts the system notifications its session posts; `prompts` turns each mark into an OSC 8 link the parser keeps in a prompt's cells, and finds the prompt to jump to; `herdr` follows herdr's focused pane and its agents' states through herdr's socket while herdr's client runs in the session, and tells the view herdr leads, so herdr opens its links: `herdr/unix` finds the client leading the pty's foreground, `herdr/windows` under the session's shell, and asks its named pipe. `unix` starts the program itself, so a failed `chdir` fails the start; Windows uses alacritty's ConPTY. `shell` writes the integration scripts at start and builds each shell's launch. `frame` reads the grid into styled rows, `boxes` turns box drawing, block elements and powerline arrows into rects and polygons (`boxes/outline` strokes curves and diagonals into polygons), `ink` resolves colors, `paint` draws rows, decorations and the cursor, `view` is the entity: pty events, metrics, blink, resize. `zoom` holds the points Bigger and Smaller add to the Size setting, unsaved. `keys` turns key events into bytes, the kitty protocol and, in `keys/legacy`, xterm's encoding, with no gpui types; `input` is the view's key listeners, the bindings that keep Tab and F6 from Ely's focus moves, and its IME handler; `mac` reads the Option side, the repeat flag and the key code of AppKit's current event, and the key's own character from the layout, as gpui reads key names; it keeps held keys repeating, and hands Command key-ups to the focused view. `win32` sends a key as Windows' own record, win32-input-mode, and finds a key's own character in Windows' layout. `mouse` encodes mouse reports, with no gpui types; `links` finds the link at a cell; `pointer` is the view's mouse listeners: selection, reports, wheel, links, copy and paste. `scrollbar` is the thumb over the history that a drag moves through it. `find` is the find box: Ely's `FindWidget`, and Ely's `find_all` over the grid's shown text.
|
||||||
- `assets/shell-integration`: ELYTerminal's own scripts (not Ghostty's, which are GPL): zsh through `ZDOTDIR`, bash through `--rcfile`, fish through `XDG_DATA_DIRS`, PowerShell through `-NoExit -Command`. Each prompt sends OSC 7 and, except in fish, whose `fish_title` stays, a title; each marks prompts and commands with OSC 133 where the shell does not.
|
- `assets/shell-integration`: ELYTerminal's own scripts (not Ghostty's, which are GPL): zsh through `ZDOTDIR`, bash through `--rcfile`, fish through `XDG_DATA_DIRS`, PowerShell through `-NoExit -Command`. Each prompt sends OSC 7 and, except in fish, whose `fish_title` stays, a title; each marks prompts and commands with OSC 133 where the shell does not.
|
||||||
- `assets/fonts`: JetBrains Mono 2.304's bold and italic faces (OFL; Ely bundles regular), and Nerd Fonts 3.5.1's JetBrains Mono NF Mono Regular, which draws icons a font lacks.
|
- `assets/fonts`: JetBrains Mono 2.304's bold and italic faces (OFL; Ely bundles regular), and Nerd Fonts 3.5.1's JetBrains Mono NF Mono Regular, which draws icons a font lacks.
|
||||||
- `assets/themes`: Ghostty theme files copied verbatim from iTerm2-Color-Schemes (MIT, its `LICENSE` beside them).
|
- `assets/themes`: Ghostty theme files copied verbatim from iTerm2-Color-Schemes (MIT, its `LICENSE` beside them).
|
||||||
@@ -111,3 +111,6 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
|
|||||||
- 2026-10-18: Ely peeks over the main window's top edge, which the owner asked for after the 0.1.3 film, her head and fingers drawn from the icon's own paths and cut at the edge she holds, in a transparent window of her own, 112 by 107 points with 16 above her for her hops, 140 in from the pane's left edge, less on a pane too narrow for that, so she holds its edge at any width, and the main window seems to hide the rest of her. Her window never takes the keys from the terminal: on macOS her panel becomes key only if needed, which gpui's view never asks; on Windows her window answers a press's activation with none, every other message passed on to gpui's own procedure; on X11 and Wayland a press moves no focus. gpui moves no window and places its popups only on Wayland, so each platform keeps her there its own way: on macOS gpui's popup panel is made the main window's child, at its level and in its Spaces, without a shadow, so AppKit moves it and hides it with the window; on Windows gpui's popup is owned by the main window and loses the topmost place gpui gives it, so it stays just above the main window and hides with it; gpui makes it an overlapped window, which Windows gives a caption and a frame, and opens it as large as the display when the middle of the box asked for lies off every display, as hers does over a window near the screen's top: in 0.1.4 a user found her at the bottom of a framed window the display's size, which took the clicks meant for the windows behind; so it becomes a bare popup, without caption, frame, corners or shadow, and the glue posts her window her bottom left corner on the pane's top edge as the main window moves, since Windows sends a new size at once and gpui hears it only outside its own updates, as gpui defers its own resize; her window sizes her at the DPI it has when the place lands, so her height is her own DPI's and her bottom the pane's top, whatever the two windows' DPI, and a place posted before her DPI changed sizes her at the new one; at a new DPI gpui takes the rect Windows suggests, and once the move that brought it is done she takes her box at that DPI on the corner her last place gave her, while that DPI is still hers and the box lies mostly on a display of it, as Windows reckons a window's display, else Windows' rect stands; so a keep changes no DPI and posts no other, where between two displays a corner kept always, or a keep whose DPI had passed while places piled up, set her DPI back, and back again; on X11 gpui's popup, which no window manager places, is moved onto the pane, stacked just above the window manager's frame as the window moves or comes forward, and unmapped while it hides; on Wayland she is gpui's anchored popup, an xdg_popup on the pane's top edge, which the compositor keeps there; gpui moves a popup's anchor never, so a new width that moves her place opens her again. She opens with the sessions' mood and moves all the time, as the films move her, which the owner asked for after 0.1.4, where she stood still but for a press. She is rigged from the icon's paths as the films rig her, in layers of one color, each an SVG mask gpui rasterizes once at its size and moves with a matrix of its own after its pixels are placed, so she moves between pixels; her head bobs with her breath while her neck, collar and hands stay on the edge, her side locks sway, her ahoge breathes, and she blinks on her own, at times twice. Her mood sets her act: calm, she glances left, then right, her head after her eyes, and dozes after 5 minutes, her eyes falling shut, her head and ahoge drooping, zs floating up, the 5 minutes counted from her mood's start or the last press, so a press wakes her, which the owner asked for after 0.1.4, where only a new mood did; a program that works runs her eyes along the terminal's lines while her fingers tap by turns; one that waits on you widens her eyes, opens her mouth, rocks a mark beside her and hops her twice every 2.6 s; an error raises her brows and shakes her, more softly every 4.5 s after, while a drop runs down; a finish cheers her for 4 s with smiling eyes, a hop and sparkles; a press winks at you while a heart rises. Each act begins with her eyes opening and her mouth popping, and a change of act, a change of mood though the act stays, as inside a wink, and a press each ease her from the pose she was in, in 0.3 s, her brows with her, the marks she leaves fading. She draws at every frame the display gives her window while it shows, and gpui gives a hidden window none; her time is folded into each move's round in f64, so weeks of one mood keep her steps fine. Settings › Terminal › Ely turns her off; she is on by default.
|
- 2026-10-18: Ely peeks over the main window's top edge, which the owner asked for after the 0.1.3 film, her head and fingers drawn from the icon's own paths and cut at the edge she holds, in a transparent window of her own, 112 by 107 points with 16 above her for her hops, 140 in from the pane's left edge, less on a pane too narrow for that, so she holds its edge at any width, and the main window seems to hide the rest of her. Her window never takes the keys from the terminal: on macOS her panel becomes key only if needed, which gpui's view never asks; on Windows her window answers a press's activation with none, every other message passed on to gpui's own procedure; on X11 and Wayland a press moves no focus. gpui moves no window and places its popups only on Wayland, so each platform keeps her there its own way: on macOS gpui's popup panel is made the main window's child, at its level and in its Spaces, without a shadow, so AppKit moves it and hides it with the window; on Windows gpui's popup is owned by the main window and loses the topmost place gpui gives it, so it stays just above the main window and hides with it; gpui makes it an overlapped window, which Windows gives a caption and a frame, and opens it as large as the display when the middle of the box asked for lies off every display, as hers does over a window near the screen's top: in 0.1.4 a user found her at the bottom of a framed window the display's size, which took the clicks meant for the windows behind; so it becomes a bare popup, without caption, frame, corners or shadow, and the glue posts her window her bottom left corner on the pane's top edge as the main window moves, since Windows sends a new size at once and gpui hears it only outside its own updates, as gpui defers its own resize; her window sizes her at the DPI it has when the place lands, so her height is her own DPI's and her bottom the pane's top, whatever the two windows' DPI, and a place posted before her DPI changed sizes her at the new one; at a new DPI gpui takes the rect Windows suggests, and once the move that brought it is done she takes her box at that DPI on the corner her last place gave her, while that DPI is still hers and the box lies mostly on a display of it, as Windows reckons a window's display, else Windows' rect stands; so a keep changes no DPI and posts no other, where between two displays a corner kept always, or a keep whose DPI had passed while places piled up, set her DPI back, and back again; on X11 gpui's popup, which no window manager places, is moved onto the pane, stacked just above the window manager's frame as the window moves or comes forward, and unmapped while it hides; on Wayland she is gpui's anchored popup, an xdg_popup on the pane's top edge, which the compositor keeps there; gpui moves a popup's anchor never, so a new width that moves her place opens her again. She opens with the sessions' mood and moves all the time, as the films move her, which the owner asked for after 0.1.4, where she stood still but for a press. She is rigged from the icon's paths as the films rig her, in layers of one color, each an SVG mask gpui rasterizes once at its size and moves with a matrix of its own after its pixels are placed, so she moves between pixels; her head bobs with her breath while her neck, collar and hands stay on the edge, her side locks sway, her ahoge breathes, and she blinks on her own, at times twice. Her mood sets her act: calm, she glances left, then right, her head after her eyes, and dozes after 5 minutes, her eyes falling shut, her head and ahoge drooping, zs floating up, the 5 minutes counted from her mood's start or the last press, so a press wakes her, which the owner asked for after 0.1.4, where only a new mood did; a program that works runs her eyes along the terminal's lines while her fingers tap by turns; one that waits on you widens her eyes, opens her mouth, rocks a mark beside her and hops her twice every 2.6 s; an error raises her brows and shakes her, more softly every 4.5 s after, while a drop runs down; a finish cheers her for 4 s with smiling eyes, a hop and sparkles; a press winks at you while a heart rises. Each act begins with her eyes opening and her mouth popping, and a change of act, a change of mood though the act stays, as inside a wink, and a press each ease her from the pose she was in, in 0.3 s, her brows with her, the marks she leaves fading. She draws at every frame the display gives her window while it shows, and gpui gives a hidden window none; her time is folded into each move's round in f64, so weeks of one mood keep her steps fine. Settings › Terminal › Ely turns her off; she is on by default.
|
||||||
- 2026-10-19: Ely stands inside the main window, her bottom on its pane's bottom edge, as far in from the left, while her box would not fit above it, which the owner asked for after 0.1.4: on a window at the screen's top or full screen her titled macOS panel, which AppKit keeps under the menu bar, stuck between the menu bar and the terminal, and on Windows and X11 she stood off the screen. The rule lives in `pet.rs`: she stands on the top edge while the room above the pane, up to the top of the work area of the main window's display, holds her box, 123.4 points; each platform measures the room and places her: macOS from the main window's `frame` and its `screen`'s `visibleFrame`, which leaves out the menu bar, both in AppKit's points; Windows from the client rect and the work area (`rcWork`) of the monitor `MonitorFromWindow` names, at the main window's scale, since above it or inside it she stands on that monitor; X11 from the pane's place on the root, up to the top of the RandR monitor holding most of it, as Windows and macOS pick a window's display, or of `_NET_WORKAREA`, its first desktop's, where that lies lower: the work area spans every monitor, so on a lower monitor its top is another's. A Wayland client knows no place of its own, so there she stands on the bottom edge while the window is full screen, maximized or tiled at its top, which gpui reports only for a window it draws itself: under server decorations, as Sway's tiling draws them, she stays on top. gpui moves a popup's anchor never, hears a change of state alone as no change of bounds, and gives an unseen popup no frames, so twice a second she checks where the window's size and state put her anchor, counted from the pane as gpui counts it, and opens there again when it moved. Each place logs the edge she moves to. On WSLg, X11 and Weston's Wayland both put her inside a window as tall as the screen or maximized, and over one with room; headless Sway and a two-monitor Xorg did too.
|
- 2026-10-19: Ely stands inside the main window, her bottom on its pane's bottom edge, as far in from the left, while her box would not fit above it, which the owner asked for after 0.1.4: on a window at the screen's top or full screen her titled macOS panel, which AppKit keeps under the menu bar, stuck between the menu bar and the terminal, and on Windows and X11 she stood off the screen. The rule lives in `pet.rs`: she stands on the top edge while the room above the pane, up to the top of the work area of the main window's display, holds her box, 123.4 points; each platform measures the room and places her: macOS from the main window's `frame` and its `screen`'s `visibleFrame`, which leaves out the menu bar, both in AppKit's points; Windows from the client rect and the work area (`rcWork`) of the monitor `MonitorFromWindow` names, at the main window's scale, since above it or inside it she stands on that monitor; X11 from the pane's place on the root, up to the top of the RandR monitor holding most of it, as Windows and macOS pick a window's display, or of `_NET_WORKAREA`, its first desktop's, where that lies lower: the work area spans every monitor, so on a lower monitor its top is another's. A Wayland client knows no place of its own, so there she stands on the bottom edge while the window is full screen, maximized or tiled at its top, which gpui reports only for a window it draws itself: under server decorations, as Sway's tiling draws them, she stays on top. gpui moves a popup's anchor never, hears a change of state alone as no change of bounds, and gives an unseen popup no frames, so twice a second she checks where the window's size and state put her anchor, counted from the pane as gpui counts it, and opens there again when it moved. Each place logs the edge she moves to. On WSLg, X11 and Weston's Wayland both put her inside a window as tall as the screen or maximized, and over one with room; headless Sway and a two-monitor Xorg did too.
|
||||||
- 2026-10-19: Ely's window shows nothing but her, which the owner asked for after a user's 0.1.4 drew a hairline along the top of her box on macOS. gpui opens a popup as a titled panel, style mask 0x8081, titled, full-size content and nonactivating, and macOS draws a titled window's frame, there as a light edge over the clear room above her head. As it is glued her panel turns borderless, nonactivating alone, 0x80, and the log names both masks; a mask that does not take fails the glue, which closes her window, as any failed glue now does, where her window stayed open, unplaced, while the log said she did not show. macOS 27.2 draws nothing of that frame, so the line never showed here, the app active or not; there a real click on her borderless panel still winks her, the front app stays in front, and keys typed after it reach the terminal. On Windows her bare popup, and on Linux X11's override-redirect window and Wayland's xdg_popup, draw no frame, border or shadow.
|
- 2026-10-19: Ely's window shows nothing but her, which the owner asked for after a user's 0.1.4 drew a hairline along the top of her box on macOS. gpui opens a popup as a titled panel, style mask 0x8081, titled, full-size content and nonactivating, and macOS draws a titled window's frame, there as a light edge over the clear room above her head. As it is glued her panel turns borderless, nonactivating alone, 0x80, and the log names both masks; a mask that does not take fails the glue, which closes her window, as any failed glue now does, where her window stayed open, unplaced, while the log said she did not show. macOS 27.2 draws nothing of that frame, so the line never showed here, the app active or not; there a real click on her borderless panel still winks her, the front app stays in front, and keys typed after it reach the terminal. On Windows her bare popup, and on Linux X11's override-redirect window and Wayland's xdg_popup, draw no frame, border or shadow.
|
||||||
|
- 2026-10-19: a drag moves Ely along the edge she stands on, which the owner asked for after 0.1.5. On macOS, Windows and X11 her glue moves her window after the pointer's place on the screen, `NSEvent`'s `mouseLocation`, `GetCursorPos` and `query_pointer`, since her window moves under the pointer, so a place in it would lag; her window keeps the pointer while the button is held, inside it or not, as AppKit, gpui's `SetCapture` and X's implicit grab give it. A Wayland popup cannot move, so there she moves once let go: the settings keep the place, and the twice-a-second check opens her popup there. She moves by whole points inside the pane, and `ely_left` keeps where she stands, 140 by default, where she stood before; a narrower pane holds her at its right end, and the log names where she is let go. Under Xorg's dummy screen xdotool dragged her 200 pixels and her window followed them; under Sway nested in Xorg, whose X input gives its seat a pointer, xdotool's drag reopened her popup 200 points on. Sway's own `seat cursor` commands send no motion while their press is held, and Sway draws no popup opened before its window settled, so a probe there reopens her before it drags.
|
||||||
|
- 2026-10-19: Settings › Terminal checks for updates, which the owner asked for after 0.1.5: under Ely, a switch, on by default, checks at start and once a day, and Check now checks at once. The check runs `curl`, as git runs through its command line, so no HTTP or TLS crate joins the build; macOS and Windows 10 ship it, and a missing curl shows as a failed check. It asks Gitea's API for the latest published release, compares its tag's three numbers with the running version, and the row says what it found; a newer one brings Download, which opens the release's page in the browser, since replacing a running, signed app is a job of its own. An automatic check that finds one posts a system notification, at most once a day, whose press opens the page. Checks run off the main thread, one at a time, each start and finding logged; a failure shows its error in the row. Under Xorg's dummy screen the start's check found 0.1.5 the latest, Check now and the switch checked again, and a build named 0.1.4 showed 0.1.5 out with Download, which opened its page.
|
||||||
|
- 2026-10-19: the terminal shows a scrollbar, which a user asked for in issue #2 after 0.1.5: a program that tracks the mouse takes the wheel, so the wheel alone could not reach the history. A thumb rides the view's right edge, over the padding, in Ely's scrollbar sizes and colors, its thickness, its least length and the foreground at 22 and 42 percent; its length is the screen's share of the history, and its place where the view stands. A drag moves the view through the history, whatever mouse mode the program asked for; the bar covers the grid's hitbox, so the terminal sees none of its presses, and a release with no press of its own reports nothing. Ely's `Scrollbar` reads a gpui `ScrollHandle`, and the terminal's place is alacritty's display offset, so the bar is the terminal's own, drawn as Ely draws hers and dragged through gpui's drag as hers is; gpui sets a drag's anchor at its first move past the threshold, so a pointer that jumps far at once loses that jump. The alternate screen holds no history, so it shows none. Under Xorg's dummy screen, 500 lines in, a drag to the top showed the first prompt and lines 1 to 39, and with mouse tracking on, which sent the wheel to the program, the bar still scrolled.
|
||||||
Generated
+1
-1
@@ -1747,7 +1747,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "elyterminal"
|
name = "elyterminal"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alacritty_terminal",
|
"alacritty_terminal",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "elyterminal"
|
name = "elyterminal"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.95"
|
rust-version = "1.95"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ Ghostty is the bar for what the terminal does. ELYTerminal owns its engine on `a
|
|||||||
- Clipboard: copy, paste (bracketed when asked), copy on select per setting, OSC 52 writes.
|
- Clipboard: copy, paste (bracketed when asked), copy on select per setting, OSC 52 writes.
|
||||||
- Links: OSC 8 and plain URLs; Cmd-press (Ctrl-press off macOS) opens them. Inside herdr, while it takes the mouse, herdr opens them, web links only, as herdr does, whole where its pane wraps them, and underlines them while the key is held; with Shift the terminal opens what it sees.
|
- Links: OSC 8 and plain URLs; Cmd-press (Ctrl-press off macOS) opens them. Inside herdr, while it takes the mouse, herdr opens them, web links only, as herdr does, whole where its pane wraps them, and underlines them while the key is held; with Shift the terminal opens what it sees.
|
||||||
- Focus reporting, title (OSC 0/2), scrollback limit per setting, find in scrollback (Ely's `FindWidget`).
|
- Focus reporting, title (OSC 0/2), scrollback limit per setting, find in scrollback (Ely's `FindWidget`).
|
||||||
|
- A scrollbar over the history, along the terminal's right edge: a drag on its thumb scrolls, whatever the program does with the wheel.
|
||||||
- Bell: no sound. The session row marks it until the session is shown.
|
- Bell: no sound. The session row marks it until the session is shown.
|
||||||
|
|
||||||
## 6. Main window and layout
|
## 6. Main window and layout
|
||||||
@@ -70,7 +71,7 @@ Ghostty is the bar for what the terminal does. ELYTerminal owns its engine on `a
|
|||||||
- The window opens at the size it was left at, on macOS, Windows and Linux: the size is kept in settings once it settles; a maximized or full-screen window keeps the size from before; a kept size past the screen shrinks to it; the window is no smaller than 100 by 100, the least the settings keep.
|
- The window opens at the size it was left at, on macOS, Windows and Linux: the size is kept in settings once it settles; a maximized or full-screen window keeps the size from before; a kept size past the screen shrinks to it; the window is no smaller than 100 by 100, the least the settings keep.
|
||||||
- The title shows the active session's title, and no other text.
|
- The title shows the active session's title, and no other text.
|
||||||
- The pointer over the terminal is the arrow.
|
- The pointer over the terminal is the arrow.
|
||||||
- Ely, the icon's girl, peeks over the window's top edge from behind it, on macOS, Windows and Linux; where no room lies above the window, at the screen's top or full screen, she stands inside it at its bottom left. She moves all the time, as she does in the films: she breathes, sways and blinks, and how the sessions are doing, waiting, an error, work, a finish or calm, moves her. Nothing but her shows: no frame, line or shadow around her. A press pets her, and wakes her from a doze. A switch in Settings, Terminal, turns her off.
|
- Ely, the icon's girl, peeks over the window's top edge from behind it, on macOS, Windows and Linux; where no room lies above the window, at the screen's top or full screen, she stands inside it at its bottom left. She moves all the time, as she does in the films: she breathes, sways and blinks, and how the sessions are doing, waiting, an error, work, a finish or calm, moves her. Nothing but her shows: no frame, line or shadow around her. A press pets her, and wakes her from a doze. A drag moves her along the edge she stands on, and she stays where she is let go. A switch in Settings, Terminal, turns her off.
|
||||||
- The title bar is transparent, as Ghostty's: the window's background, opacity and blur run under it, so it wears the terminal's color.
|
- The title bar is transparent, as Ghostty's: the window's background, opacity and blur run under it, so it wears the terminal's color.
|
||||||
- Every control in the title bar takes its own press on every platform, the window buttons Ely draws on Windows and Linux too.
|
- Every control in the title bar takes its own press on every platform, the window buttons Ely draws on Windows and Linux too.
|
||||||
|
|
||||||
@@ -118,6 +119,8 @@ Cmd-, opens it; on Windows and Linux, which have no menu bar, so does a gear in
|
|||||||
- Option as Alt: off, left, right, both (macOS only).
|
- Option as Alt: off, left, right, both (macOS only).
|
||||||
- Copy on select.
|
- Copy on select.
|
||||||
- Ely: a switch.
|
- Ely: a switch.
|
||||||
|
- Check for updates automatically: a switch, on by default, which checks at start and once a day.
|
||||||
|
- Updates: what the last check found, and Check now; a newer release brings Download, which opens its page, and one an automatic check finds posts a notification.
|
||||||
- Shortcuts: every shortcut, as this platform's keys, with a search.
|
- Shortcuts: every shortcut, as this platform's keys, with a search.
|
||||||
|
|
||||||
## 11. Background blur
|
## 11. Background blur
|
||||||
|
|||||||
@@ -42,8 +42,9 @@ The app writes its log to stderr. `RUST_LOG=elyterminal=debug` writes more; `tra
|
|||||||
## What it does
|
## What it does
|
||||||
|
|
||||||
- The terminal runs on `alacritty_terminal`: 256 colors and truecolor, bold, dim, italic, five underline styles, wide characters and combining marks. Box drawing, block elements and powerline shapes are drawn as geometry, so lines join across cells. Keys follow xterm and the kitty keyboard protocol, and on Windows win32-input-mode, as Windows Terminal sends them, so herdr's shortcuts work; input methods compose at the cursor; programs can track the mouse; pastes are bracketed when a program asks; OSC 8 and plain links open with the link press below; inside herdr, herdr opens them, web links only, whole where its pane wraps them, and with Shift the terminal opens what it sees; find searches the scrollback.
|
- The terminal runs on `alacritty_terminal`: 256 colors and truecolor, bold, dim, italic, five underline styles, wide characters and combining marks. Box drawing, block elements and powerline shapes are drawn as geometry, so lines join across cells. Keys follow xterm and the kitty keyboard protocol, and on Windows win32-input-mode, as Windows Terminal sends them, so herdr's shortcuts work; input methods compose at the cursor; programs can track the mouse; pastes are bracketed when a program asks; OSC 8 and plain links open with the link press below; inside herdr, herdr opens them, web links only, whole where its pane wraps them, and with Shift the terminal opens what it sees; find searches the scrollback.
|
||||||
|
- A scrollbar along the terminal's right edge scrolls its history, even while a program takes the wheel.
|
||||||
- Shell integration for zsh, bash, fish and PowerShell: each prompt reports its folder and title, and OSC 133 marks each prompt, each command and its exit status.
|
- Shell integration for zsh, bash, fish and PowerShell: each prompt reports its folder and title, and OSC 133 marks each prompt, each command and its exit status.
|
||||||
- Ely peeks over the window's top edge, or up from its bottom left when the window leaves her no room above it, and moves all the time: she breathes, sways and blinks. She follows your sessions: she hops when a program waits on you, shakes at an error, cheers a finish, reads along while a program works, and dozes when all is calm for long. Press her and she winks, waking her from a doze. Settings › Terminal › Ely turns her off.
|
- Ely peeks over the window's top edge, or up from its bottom left when the window leaves her no room above it, and moves all the time: she breathes, sways and blinks. She follows your sessions: she hops when a program waits on you, shakes at an error, cheers a finish, reads along while a program works, and dozes when all is calm for long. Press her and she winks, waking her from a doze. Drag her along the edge to move her. Settings › Terminal › Ely turns her off.
|
||||||
- Sessions: new, close and switch. A bell marks a hidden session's row and makes no sound. A row's mark shows what its programs report through OSC 7501, OSC 9;4 or herdr: waiting on you, an error, work with its progress, done or idle, else a failed last command. The mark is 3 by 3 dots: work ripples and waiting dips while they last. Hover over the mark lists each report.
|
- Sessions: new, close and switch. A bell marks a hidden session's row and makes no sound. A row's mark shows what its programs report through OSC 7501, OSC 9;4 or herdr: waiting on you, an error, work with its progress, done or idle, else a failed last command. The mark is 3 by 3 dots: work ripples and waiting dips while they last. Hover over the mark lists each report.
|
||||||
- Notifications: while the window is in the background or the session hidden, a session that starts waiting, finishes or fails, and a program's OSC 9, 777 or 99, post a system notification, one a session every 3 seconds at most; a press shows the session. On macOS only the app bundle posts them, not `cargo run`.
|
- Notifications: while the window is in the background or the session hidden, a session that starts waiting, finishes or fails, and a program's OSC 9, 777 or 99, post a system notification, one a session every 3 seconds at most; a press shows the session. On macOS only the app bundle posts them, not `cargo run`.
|
||||||
- Cmd-Up and Cmd-Down, Ctrl-Shift-Up and Ctrl-Shift-Down off macOS, jump to the prompt before or after the view's top.
|
- Cmd-Up and Cmd-Down, Ctrl-Shift-Up and Ctrl-Shift-Down off macOS, jump to the prompt before or after the view's top.
|
||||||
@@ -84,6 +85,8 @@ The settings window saves each change at once to `settings.json` in the platform
|
|||||||
|
|
||||||
A file that does not parse, or holds a value out of range, stops the start with an error naming the file and the field. The app never overwrites it.
|
A file that does not parse, or holds a value out of range, stops the start with an error naming the file and the field. The app never overwrites it.
|
||||||
|
|
||||||
|
Settings › Terminal checks for a newer release at start and once a day, or at once with Check now; a newer one shows a Download button that opens its page. The check runs `curl`, which macOS and Windows ship.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
`scripts/check.sh` runs fmt, clippy, the tests and the 500-line rule. `AGENTS.md` lists the rest: the layout, the decisions and the tools.
|
`scripts/check.sh` runs fmt, clippy, the tests and the 500-line rule. `AGENTS.md` lists the rest: the layout, the decisions and the tools.
|
||||||
|
|||||||
@@ -138,4 +138,14 @@ Progress truth. Top to bottom, one at a time. A task is ticked only after its co
|
|||||||
|
|
||||||
## 29. Release
|
## 29. Release
|
||||||
|
|
||||||
- [ ] T54 Release v0.1.5 with T51 to T53: version 0.1.5; the macOS disk image, the Windows zip and the Linux tarball, each from its script; the notes list what changed.
|
- [x] T54 Release v0.1.5 with T51 to T53: version 0.1.5; the macOS disk image, the Windows zip and the Linux tarball, each from its script; the notes list what changed.
|
||||||
|
|
||||||
|
## 30. Ely's drag, updates and the scrollbar
|
||||||
|
|
||||||
|
- [x] T55 A drag moves Ely along the edge she stands on, which the owner asked for after 0.1.5: she follows the pointer sideways inside the pane, on macOS, Windows and X11 as it moves, on Wayland, whose popup keeps its place, once it lets go; where she stands, in points from the pane's left edge, the settings keep as `ely_left`.
|
||||||
|
- [x] T56 Settings › Terminal checks for updates, which the owner asked for after 0.1.5: under Ely, a switch checks ELYTerminal's latest release at start and once a day, on by default, and a button checks at once; the row says what the check found, and a newer release brings a button that opens its page; a newer release an automatic check finds posts a system notification, whose press opens that page.
|
||||||
|
- [x] T58 The terminal shows a scrollbar over its history, which a user asked for in issue #2 after 0.1.5, since a program that takes the mouse takes the wheel too: a thumb along its right edge, in Ely's sizes and colors, stands where the view stands in the history, and a drag on it moves the view through it; none shows without history, as on the alternate screen, and a press on it never reaches the terminal.
|
||||||
|
|
||||||
|
## 31. Release
|
||||||
|
|
||||||
|
- [ ] T57 Release v0.1.6 with T55, T56 and T58: version 0.1.6; the macOS disk image, the Windows zip and the Linux tarball, each from its script; the notes list what changed.
|
||||||
+6
-2
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
blur, menus,
|
blur, menus,
|
||||||
settings::{self, SettingsStore},
|
settings::{self, SettingsStore},
|
||||||
term::{ICONS, Launch, MONO, config, input, shell::ShellIntegration, spawn},
|
term::{ICONS, Launch, MONO, config, input, shell::ShellIntegration, spawn},
|
||||||
themes,
|
themes, updates,
|
||||||
workspace::{self, TITLE, Workspace},
|
workspace::{self, TITLE, Workspace},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,15 +45,19 @@ pub fn start(cx: &mut App) -> anyhow::Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
SettingsStore::init(cx)?;
|
SettingsStore::init(cx)?;
|
||||||
|
updates::start(cx);
|
||||||
ShellIntegration::init(cx)?;
|
ShellIntegration::init(cx)?;
|
||||||
input::bind_keys(cx);
|
input::bind_keys(cx);
|
||||||
workspace::bind_keys(cx);
|
workspace::bind_keys(cx);
|
||||||
settings::window::bind_keys(cx);
|
settings::window::bind_keys(cx);
|
||||||
menus::init(cx);
|
menus::init(cx);
|
||||||
cx.set_app_identity(APP_ID, TITLE);
|
cx.set_app_identity(APP_ID, TITLE);
|
||||||
// A press on a notification goes to the window whose session posted it.
|
// A press on a notification goes to the window whose session posted it, or to a newer release's page.
|
||||||
cx.on_system_notification_response(|response, cx| {
|
cx.on_system_notification_response(|response, cx| {
|
||||||
let tag = response.tag;
|
let tag = response.tag;
|
||||||
|
if updates::pressed(&tag, cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let pressed = cx
|
let pressed = cx
|
||||||
.windows()
|
.windows()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
+2
-2
@@ -25,9 +25,9 @@ fn line<A: AsRef<OsStr>>(args: &[A]) -> String {
|
|||||||
args.join(" ")
|
args.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keeps Windows from opening a console window for git.
|
/// Keeps Windows from opening a console window for git and curl.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
pub(crate) const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||||
|
|
||||||
/// Runs git in `folder`: English messages, literal paths, no lock the user's own git could meet; `input` feeds stdin.
|
/// Runs git in `folder`: English messages, literal paths, no lock the user's own git could meet; `input` feeds stdin.
|
||||||
fn git<A: AsRef<OsStr>>(folder: &Path, args: &[A], input: &[u8]) -> anyhow::Result<Output> {
|
fn git<A: AsRef<OsStr>>(folder: &Path, args: &[A], input: &[u8]) -> anyhow::Result<Output> {
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ mod testlog;
|
|||||||
#[cfg(all(test, target_os = "linux"))]
|
#[cfg(all(test, target_os = "linux"))]
|
||||||
mod testx11;
|
mod testx11;
|
||||||
pub mod themes;
|
pub mod themes;
|
||||||
|
pub mod updates;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
+92
-18
@@ -18,10 +18,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext as _, Bounds, Context, Decorations, InteractiveElement, IntoElement,
|
App, AppContext as _, Bounds, Context, Decorations, DispatchPhase, InteractiveElement,
|
||||||
MouseButton, ParentElement, Pixels, Point, Render, Styled, Subscription, Window,
|
IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels,
|
||||||
WindowBackgroundAppearance, WindowBounds, WindowHandle, WindowKind, WindowOptions, canvas, div,
|
Point, Render, Styled, Subscription, Window, WindowBackgroundAppearance, WindowBounds,
|
||||||
point, popup, px, size,
|
WindowHandle, WindowKind, WindowOptions, canvas, div, point, popup, px, size,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -40,8 +40,6 @@ use crate::{settings::SettingsStore, workspace::Workspace};
|
|||||||
const WIDTH: f32 = 112.0;
|
const WIDTH: f32 = 112.0;
|
||||||
const HEAD: f32 = 107.4;
|
const HEAD: f32 = 107.4;
|
||||||
const ROOM: f32 = 16.0;
|
const ROOM: f32 = 16.0;
|
||||||
/// How far in from the pane's left edge she holds its top edge.
|
|
||||||
const LEFT: f32 = 140.0;
|
|
||||||
/// How often she checks her Wayland anchor against the main window.
|
/// How often she checks her Wayland anchor against the main window.
|
||||||
const CHECK: Duration = Duration::from_millis(500);
|
const CHECK: Duration = Duration::from_millis(500);
|
||||||
|
|
||||||
@@ -70,6 +68,10 @@ pub struct Pet {
|
|||||||
/// The edge she stands on, and on Wayland the anchor her popup opened at, from the pane's top left, which never moves.
|
/// The edge she stands on, and on Wayland the anchor her popup opened at, from the pane's top left, which never moves.
|
||||||
edge: Edge,
|
edge: Edge,
|
||||||
anchor: Option<Point<Pixels>>,
|
anchor: Option<Point<Pixels>>,
|
||||||
|
/// How far in from the pane's left edge she stands, and a drag under way: the pointer's x where it began and where she stood then.
|
||||||
|
at: f32,
|
||||||
|
drag: Option<(f32, f32)>,
|
||||||
|
main: WindowHandle<Workspace>,
|
||||||
/// What keeps her window on the main window's edge, where the platform does not.
|
/// What keeps her window on the main window's edge, where the platform does not.
|
||||||
glue: Option<platform::Glue>,
|
glue: Option<platform::Glue>,
|
||||||
_watch: Vec<Subscription>,
|
_watch: Vec<Subscription>,
|
||||||
@@ -125,9 +127,9 @@ fn close(pet: WindowHandle<Pet>, why: &str, cx: &mut App) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How far in from the pane's left edge she stands on a pane `width` points wide: `LEFT`, or less on a narrow pane, so she holds its edge.
|
/// How far in from the pane's left edge she stands on a pane `width` points wide: `at`, or less on a narrow pane, so she holds its edge.
|
||||||
fn left(width: Pixels) -> f32 {
|
fn left(width: Pixels, at: f32) -> f32 {
|
||||||
LEFT.min(f32::from(width) - WIDTH).max(0.0)
|
at.min(f32::from(width) - WIDTH).max(0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The edge she stands on with `room` points free above the pane, up to the top of the work area of the main window's display.
|
/// The edge she stands on with `room` points free above the pane, up to the top of the work area of the main window's display.
|
||||||
@@ -150,19 +152,20 @@ fn wayland_edge(window: &Window) -> Edge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Where she stands over a Wayland window: the edge, and her anchor counted from the pane's top left, as gpui's popups count.
|
/// Where she stands over a Wayland window: the edge, and her anchor counted from the pane's top left, as gpui's popups count.
|
||||||
fn wayland_spot(window: &Window) -> (Edge, Point<Pixels>) {
|
fn wayland_spot(window: &Window, at: f32) -> (Edge, Point<Pixels>) {
|
||||||
let pane = pane(window);
|
let pane = pane(window);
|
||||||
let edge = wayland_edge(window);
|
let edge = wayland_edge(window);
|
||||||
let y = match edge {
|
let y = match edge {
|
||||||
Edge::Top => px(0.),
|
Edge::Top => px(0.),
|
||||||
Edge::Bottom => pane.size.height - px(1.),
|
Edge::Bottom => pane.size.height - px(1.),
|
||||||
};
|
};
|
||||||
(edge, point(px(left(pane.size.width)), y))
|
(edge, point(px(left(pane.size.width, at)), y))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens her again over a Wayland `main` once its size or state moves the anchor her popup opened at, which never moves.
|
/// Opens her again over a Wayland `main` once its size or state moves the anchor her popup opened at, which never moves.
|
||||||
fn reanchor(main: WindowHandle<Workspace>, shown: &Shown, cx: &mut App) -> anyhow::Result<()> {
|
fn reanchor(main: WindowHandle<Workspace>, shown: &Shown, cx: &mut App) -> anyhow::Result<()> {
|
||||||
let spot = main.update(cx, |_, window, _| wayland_spot(window))?;
|
let at = SettingsStore::get(cx).ely_left;
|
||||||
|
let spot = main.update(cx, |_, window, _| wayland_spot(window, at))?;
|
||||||
let Some(pet) = *shown.borrow() else {
|
let Some(pet) = *shown.borrow() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
@@ -174,7 +177,7 @@ fn reanchor(main: WindowHandle<Workspace>, shown: &Shown, cx: &mut App) -> anyho
|
|||||||
}
|
}
|
||||||
close(
|
close(
|
||||||
pet,
|
pet,
|
||||||
"to stand where the window's new size or state puts her",
|
"to stand where the window's size or state, or a drag, puts her",
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
*shown.borrow_mut() = open_pet(main, cx);
|
*shown.borrow_mut() = open_pet(main, cx);
|
||||||
@@ -183,15 +186,16 @@ fn reanchor(main: WindowHandle<Workspace>, shown: &Shown, cx: &mut App) -> anyho
|
|||||||
|
|
||||||
/// Opens her window on `main`'s pane: a popup Wayland's compositor places, else a window kept there.
|
/// Opens her window on `main`'s pane: a popup Wayland's compositor places, else a window kept there.
|
||||||
fn show(main: WindowHandle<Workspace>, cx: &mut App) -> anyhow::Result<WindowHandle<Pet>> {
|
fn show(main: WindowHandle<Workspace>, cx: &mut App) -> anyhow::Result<WindowHandle<Pet>> {
|
||||||
|
let kept = SettingsStore::get(cx).ely_left;
|
||||||
let (bounds, pane, wayland, (edge, spot)) = main.update(cx, |_, window, _| {
|
let (bounds, pane, wayland, (edge, spot)) = main.update(cx, |_, window, _| {
|
||||||
(
|
(
|
||||||
window.bounds(),
|
window.bounds(),
|
||||||
pane(window),
|
pane(window),
|
||||||
wayland(window),
|
wayland(window),
|
||||||
wayland_spot(window),
|
wayland_spot(window, kept),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let at = left(pane.size.width);
|
let at = left(pane.size.width, kept);
|
||||||
let room = size(px(WIDTH), px(HEAD + ROOM));
|
let room = size(px(WIDTH), px(HEAD + ROOM));
|
||||||
let (edge, kind) = if wayland {
|
let (edge, kind) = if wayland {
|
||||||
let anchor = match edge {
|
let anchor = match edge {
|
||||||
@@ -254,6 +258,9 @@ fn show(main: WindowHandle<Workspace>, cx: &mut App) -> anyhow::Result<WindowHan
|
|||||||
eased: None,
|
eased: None,
|
||||||
edge,
|
edge,
|
||||||
anchor: wayland.then_some(spot),
|
anchor: wayland.then_some(spot),
|
||||||
|
at: kept,
|
||||||
|
drag: None,
|
||||||
|
main,
|
||||||
glue: None,
|
glue: None,
|
||||||
_watch: vec![feel],
|
_watch: vec![feel],
|
||||||
}
|
}
|
||||||
@@ -282,7 +289,7 @@ fn glue(
|
|||||||
main.update(cx, |_, main, cx| {
|
main.update(cx, |_, main, cx| {
|
||||||
pet.update(cx, |pet, window, cx| {
|
pet.update(cx, |pet, window, cx| {
|
||||||
let glue = platform::Glue::new(window, main)?;
|
let glue = platform::Glue::new(window, main)?;
|
||||||
pet.edge = glue.place(main)?;
|
pet.edge = glue.place(main, pet.at)?;
|
||||||
pet.glue = Some(glue);
|
pet.glue = Some(glue);
|
||||||
pet._watch
|
pet._watch
|
||||||
.push(cx.observe_window_bounds(main, |pet, main, _| pet.place(main)));
|
.push(cx.observe_window_bounds(main, |pet, main, _| pet.place(main)));
|
||||||
@@ -371,7 +378,7 @@ impl Pet {
|
|||||||
let Some(glue) = &self.glue else {
|
let Some(glue) = &self.glue else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match glue.place(main) {
|
match glue.place(main, self.at) {
|
||||||
Ok(edge) if edge != self.edge => {
|
Ok(edge) if edge != self.edge => {
|
||||||
let name = format!("{edge:?}").to_lowercase();
|
let name = format!("{edge:?}").to_lowercase();
|
||||||
log::info!("pet: Ely moves to the pane's {name} edge");
|
log::info!("pet: Ely moves to the pane's {name} edge");
|
||||||
@@ -382,6 +389,58 @@ impl Pet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes hold of her for a drag along her edge, the pointer `local` in her window.
|
||||||
|
fn grab(&mut self, local: Pixels, scale: f32) {
|
||||||
|
match self.pointer(local, scale) {
|
||||||
|
Ok(x) => self.drag = Some((x, self.at)),
|
||||||
|
Err(error) => log::error!("{error:#}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves her along her edge after the pointer, inside the pane.
|
||||||
|
fn slide(&mut self, local: Pixels, scale: f32, cx: &mut App) {
|
||||||
|
let Some((from, at)) = self.drag else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let x = match self.pointer(local, scale) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(error) => {
|
||||||
|
self.drag = None;
|
||||||
|
return log::error!("{error:#}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let main = self.main;
|
||||||
|
let slid = main.update(cx, |_, main, _| {
|
||||||
|
let room = (f32::from(pane(main).size.width) - WIDTH).max(0.0);
|
||||||
|
// Whole points, so the kept place carries no float noise.
|
||||||
|
self.at = (at + x - from).round().clamp(0.0, room.floor());
|
||||||
|
self.place(main);
|
||||||
|
});
|
||||||
|
if let Err(error) = slid {
|
||||||
|
log::debug!("pet: the main window does not read: {error:#}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lets go of her, the settings keeping where the drag left her, where a Wayland popup then opens again.
|
||||||
|
fn release(&mut self, cx: &mut App) {
|
||||||
|
if self.drag.take().is_none() || SettingsStore::get(cx).ely_left == self.at {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let at = self.at;
|
||||||
|
match SettingsStore::update(cx, |settings| settings.ely_left = at) {
|
||||||
|
Ok(()) => log::info!("pet: Ely stands {at} points in from the pane's left edge"),
|
||||||
|
Err(error) => log::error!("pet: where Ely stands is not kept: {error:#}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pointer's x in points: on the screen where her glue moves her window, else in her window, which a drag leaves in place.
|
||||||
|
fn pointer(&self, local: Pixels, scale: f32) -> anyhow::Result<f32> {
|
||||||
|
match &self.glue {
|
||||||
|
Some(glue) => glue.pointer(scale),
|
||||||
|
None => Ok(local.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn shown(&mut self, visible: bool, main: &Window) {
|
fn shown(&mut self, visible: bool, main: &Window) {
|
||||||
if let Some(glue) = &self.glue {
|
if let Some(glue) = &self.glue {
|
||||||
@@ -402,17 +461,32 @@ impl Render for Pet {
|
|||||||
// gpui gives a hidden window no frames, so she rests while unseen.
|
// gpui gives a hidden window no frames, so she rests while unseen.
|
||||||
window.request_animation_frame();
|
window.request_animation_frame();
|
||||||
let rig = self.rig.clone();
|
let rig = self.rig.clone();
|
||||||
|
let this = cx.entity();
|
||||||
let drawn = canvas(
|
let drawn = canvas(
|
||||||
|_, _, _| {},
|
|_, _, _| {},
|
||||||
move |bounds, _, window, cx| {
|
move |bounds, _, window, cx| {
|
||||||
let head = point(bounds.origin.x, bounds.origin.y + px(ROOM));
|
let head = point(bounds.origin.x, bounds.origin.y + px(ROOM));
|
||||||
rig.paint(head, &pose, window, cx);
|
rig.paint(head, &pose, window, cx);
|
||||||
|
// Her window keeps the pointer while the button is held, inside it or not.
|
||||||
|
let held = this.clone();
|
||||||
|
window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble && event.dragging() {
|
||||||
|
let scale = window.scale_factor();
|
||||||
|
held.update(cx, |pet, cx| pet.slide(event.position.x, scale, cx));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.on_mouse_event(move |event: &MouseUpEvent, phase, _, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble && event.button == MouseButton::Left {
|
||||||
|
this.update(cx, |pet, cx| pet.release(cx));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
div().size_full().child(drawn.size_full()).on_mouse_down(
|
div().size_full().child(drawn.size_full()).on_mouse_down(
|
||||||
MouseButton::Left,
|
MouseButton::Left,
|
||||||
cx.listener(|pet, _, _, cx| {
|
cx.listener(|pet, event: &MouseDownEvent, window, cx| {
|
||||||
pet.press(Instant::now());
|
pet.press(Instant::now());
|
||||||
|
pet.grab(event.position.x, window.scale_factor());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
+10
-4
@@ -3,7 +3,7 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use gpui::Window;
|
use gpui::Window;
|
||||||
use objc::{
|
use objc::{
|
||||||
msg_send,
|
class, msg_send,
|
||||||
runtime::{NO, Object, YES},
|
runtime::{NO, Object, YES},
|
||||||
sel, sel_impl,
|
sel, sel_impl,
|
||||||
};
|
};
|
||||||
@@ -64,8 +64,8 @@ impl Glue {
|
|||||||
Ok(Self { pet, main })
|
Ok(Self { pet, main })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stands her on the main window's top edge while her box fits under the top of its screen's visible frame, else on its bottom edge, as far in as its width allows, and gives the edge.
|
/// Stands her on the main window's top edge while her box fits under the top of its screen's visible frame, else on its bottom edge, `at` in as far as its width allows, and gives the edge.
|
||||||
pub fn place(&self, _: &Window) -> anyhow::Result<Edge> {
|
pub fn place(&self, _: &Window, at: f32) -> anyhow::Result<Edge> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let frame: Rect = msg_send![self.main, frame];
|
let frame: Rect = msg_send![self.main, frame];
|
||||||
let screen: *mut Object = msg_send![self.main, screen];
|
let screen: *mut Object = msg_send![self.main, screen];
|
||||||
@@ -74,7 +74,7 @@ impl Glue {
|
|||||||
let top = frame.origin.y + frame.height;
|
let top = frame.origin.y + frame.height;
|
||||||
let edge = edge((visible.origin.y + visible.height - top) as f32);
|
let edge = edge((visible.origin.y + visible.height - top) as f32);
|
||||||
let at = Point {
|
let at = Point {
|
||||||
x: frame.origin.x + f64::from(left(gpui::px(frame.width as f32))),
|
x: frame.origin.x + f64::from(left(gpui::px(frame.width as f32), at)),
|
||||||
y: match edge {
|
y: match edge {
|
||||||
Edge::Top => top,
|
Edge::Top => top,
|
||||||
Edge::Bottom => frame.origin.y,
|
Edge::Bottom => frame.origin.y,
|
||||||
@@ -84,6 +84,12 @@ impl Glue {
|
|||||||
Ok(edge)
|
Ok(edge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The pointer's x on the screen, in points.
|
||||||
|
pub fn pointer(&self, _: f32) -> anyhow::Result<f32> {
|
||||||
|
let at: Point = unsafe { msg_send![class!(NSEvent), mouseLocation] };
|
||||||
|
Ok(at.x as f32)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The NSWindow gpui made for `window`.
|
/// The NSWindow gpui made for `window`.
|
||||||
|
|||||||
+12
-4
@@ -1,5 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use gpui::WindowId;
|
||||||
|
|
||||||
use super::{look::Mark, *};
|
use super::{look::Mark, *};
|
||||||
|
|
||||||
/// Her, `mood` since `since`, come long before.
|
/// Her, `mood` since `since`, come long before.
|
||||||
@@ -15,6 +17,9 @@ fn ely(mood: Mood, since: Instant) -> Pet {
|
|||||||
eased: None,
|
eased: None,
|
||||||
edge: Edge::Top,
|
edge: Edge::Top,
|
||||||
anchor: None,
|
anchor: None,
|
||||||
|
at: 140.0,
|
||||||
|
drag: None,
|
||||||
|
main: WindowHandle::new(WindowId::from(1)),
|
||||||
glue: None,
|
glue: None,
|
||||||
_watch: Vec::new(),
|
_watch: Vec::new(),
|
||||||
}
|
}
|
||||||
@@ -98,11 +103,14 @@ fn she_stands_on_the_top_edge_while_her_box_fits_above_it() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn she_holds_the_edge_of_any_pane() {
|
fn she_holds_the_edge_of_any_pane_where_a_drag_left_her() {
|
||||||
assert_eq!(left(px(1200.)), LEFT);
|
assert_eq!(left(px(1200.), 140.0), 140.0);
|
||||||
assert_eq!(left(px(200.)), 200.0 - WIDTH);
|
assert_eq!(left(px(1200.), 900.0), 900.0, "dragged right");
|
||||||
|
assert_eq!(left(px(1200.), 0.0), 0.0, "dragged to its left end");
|
||||||
|
assert_eq!(left(px(600.), 900.0), 600.0 - WIDTH, "a narrower pane");
|
||||||
|
assert_eq!(left(px(200.), 140.0), 200.0 - WIDTH);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
left(px(100.)),
|
left(px(100.), 140.0),
|
||||||
0.0,
|
0.0,
|
||||||
"narrower than she is, she stands at its left"
|
"narrower than she is, she stands at its left"
|
||||||
);
|
);
|
||||||
|
|||||||
+20
-12
@@ -9,14 +9,14 @@ use anyhow::bail;
|
|||||||
use gpui::{Bounds, Pixels, Window};
|
use gpui::{Bounds, Pixels, Window};
|
||||||
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
|
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
|
||||||
use windows_sys::Win32::{
|
use windows_sys::Win32::{
|
||||||
Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM},
|
Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||||
Graphics::Gdi::{
|
Graphics::Gdi::{
|
||||||
GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromRect, MonitorFromWindow,
|
GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromRect, MonitorFromWindow,
|
||||||
},
|
},
|
||||||
UI::{
|
UI::{
|
||||||
HiDpi::{GetDpiForMonitor, GetDpiForWindow, MDT_EFFECTIVE_DPI},
|
HiDpi::{GetDpiForMonitor, GetDpiForWindow, MDT_EFFECTIVE_DPI},
|
||||||
WindowsAndMessaging::{
|
WindowsAndMessaging::{
|
||||||
CallWindowProcW, GWL_STYLE, GWLP_HWNDPARENT, GWLP_WNDPROC, GetWindowInfo,
|
CallWindowProcW, GWL_STYLE, GWLP_HWNDPARENT, GWLP_WNDPROC, GetCursorPos, GetWindowInfo,
|
||||||
GetWindowLongPtrW, HWND_NOTOPMOST, MA_NOACTIVATE, PostMessageW, SWP_FRAMECHANGED,
|
GetWindowLongPtrW, HWND_NOTOPMOST, MA_NOACTIVATE, PostMessageW, SWP_FRAMECHANGED,
|
||||||
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SetWindowLongPtrW, SetWindowPos,
|
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SetWindowLongPtrW, SetWindowPos,
|
||||||
USER_DEFAULT_SCREEN_DPI, WINDOWINFO, WM_APP, WM_DPICHANGED, WM_MOUSEACTIVATE, WNDPROC,
|
USER_DEFAULT_SCREEN_DPI, WINDOWINFO, WM_APP, WM_DPICHANGED, WM_MOUSEACTIVATE, WNDPROC,
|
||||||
@@ -77,13 +77,21 @@ impl Glue {
|
|||||||
Ok(Self { pet, main })
|
Ok(Self { pet, main })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stands her on the main window's top edge while her box fits under the top of its display's work area, else on its bottom edge, as far in as its width allows, and gives the edge.
|
/// Stands her on the main window's top edge while her box fits under the top of its display's work area, else on its bottom edge, `at` in as far as its width allows, and gives the edge.
|
||||||
pub fn place(&self, main: &Window) -> anyhow::Result<Edge> {
|
pub fn place(&self, main: &Window, at: f32) -> anyhow::Result<Edge> {
|
||||||
self.put(main.scale_factor(), pane(main))
|
self.put(main.scale_factor(), pane(main), at)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Posts her bottom left corner on an edge of `pane` at `scale`, in the main window's points; her window sizes her at the DPI it has when the place lands, as gpui did not when it opened her off the display.
|
/// The pointer's x on the screen, in points at `scale`.
|
||||||
fn put(&self, scale: f32, pane: Bounds<Pixels>) -> anyhow::Result<Edge> {
|
pub fn pointer(&self, scale: f32) -> anyhow::Result<f32> {
|
||||||
|
let mut at = POINT::default();
|
||||||
|
let read = unsafe { GetCursorPos(&mut at) };
|
||||||
|
anyhow::ensure!(read != 0, "pet: the pointer's place does not read");
|
||||||
|
Ok(at.x as f32 / scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Posts her bottom left corner on an edge of `pane` at `scale`, `at` points in, in the main window's points; her window sizes her at the DPI it has when the place lands, as gpui did not when it opened her off the display.
|
||||||
|
fn put(&self, scale: f32, pane: Bounds<Pixels>, at: f32) -> anyhow::Result<Edge> {
|
||||||
let mut main = WINDOWINFO {
|
let mut main = WINDOWINFO {
|
||||||
cbSize: size_of::<WINDOWINFO>() as u32,
|
cbSize: size_of::<WINDOWINFO>() as u32,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -97,9 +105,9 @@ impl Glue {
|
|||||||
let monitor = unsafe { MonitorFromWindow(self.main, MONITOR_DEFAULTTONEAREST) };
|
let monitor = unsafe { MonitorFromWindow(self.main, MONITOR_DEFAULTTONEAREST) };
|
||||||
let read = unsafe { GetMonitorInfoW(monitor, &mut display) };
|
let read = unsafe { GetMonitorInfoW(monitor, &mut display) };
|
||||||
anyhow::ensure!(read != 0, "pet: the main window's display does not read");
|
anyhow::ensure!(read != 0, "pet: the main window's display does not read");
|
||||||
let (_, top) = stand(main.rcClient, scale, pane, Edge::Top);
|
let (_, top) = stand(main.rcClient, scale, pane, Edge::Top, at);
|
||||||
let edge = edge((top - display.rcWork.top) as f32 / scale);
|
let edge = edge((top - display.rcWork.top) as f32 / scale);
|
||||||
let corner = stand(main.rcClient, scale, pane, edge);
|
let corner = stand(main.rcClient, scale, pane, edge, at);
|
||||||
let posted = unsafe { PostMessageW(self.pet, PLACE, pack(corner), 0) };
|
let posted = unsafe { PostMessageW(self.pet, PLACE, pack(corner), 0) };
|
||||||
anyhow::ensure!(posted != 0, "pet: her place does not post");
|
anyhow::ensure!(posted != 0, "pet: her place does not post");
|
||||||
log::debug!("pet: Ely is to stand on {corner:?}, the pane's {edge:?} edge");
|
log::debug!("pet: Ely is to stand on {corner:?}, the pane's {edge:?} edge");
|
||||||
@@ -107,13 +115,13 @@ impl Glue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Her bottom left corner on `edge` of `pane` at `scale` over the main window's `client` rect, in pixels.
|
/// Her bottom left corner on `edge` of `pane`, `at` points in, at `scale` over the main window's `client` rect, in pixels.
|
||||||
fn stand(client: RECT, scale: f32, pane: Bounds<Pixels>, edge: Edge) -> (i32, i32) {
|
fn stand(client: RECT, scale: f32, pane: Bounds<Pixels>, edge: Edge, at: f32) -> (i32, i32) {
|
||||||
let y = match edge {
|
let y = match edge {
|
||||||
Edge::Top => pane.origin.y,
|
Edge::Top => pane.origin.y,
|
||||||
Edge::Bottom => pane.bottom(),
|
Edge::Bottom => pane.bottom(),
|
||||||
};
|
};
|
||||||
let x = f32::from(pane.origin.x) + left(pane.size.width);
|
let x = f32::from(pane.origin.x) + left(pane.size.width, at);
|
||||||
(
|
(
|
||||||
client.left + (x * scale).round() as i32,
|
client.left + (x * scale).round() as i32,
|
||||||
client.top + (f32::from(y) * scale).round() as i32,
|
client.top + (f32::from(y) * scale).round() as i32,
|
||||||
|
|||||||
@@ -102,14 +102,14 @@ fn her_bottom_stands_on_either_edge_of_the_pane_whatever_the_two_windows_dpi() {
|
|||||||
let pane = Bounds::new(point(px(10.), px(8.)), size(px(800.), px(600.)));
|
let pane = Bounds::new(point(px(10.), px(8.)), size(px(800.), px(600.)));
|
||||||
for (scale, dpi) in [(1.0, 96), (1.0, 144), (1.5, 96), (2.0, 120)] {
|
for (scale, dpi) in [(1.0, 96), (1.0, 144), (1.5, 96), (2.0, 120)] {
|
||||||
for (edge, y) in [(Edge::Top, 8.0), (Edge::Bottom, 608.0)] {
|
for (edge, y) in [(Edge::Top, 8.0), (Edge::Bottom, 608.0)] {
|
||||||
let her = boxed(stand(client, scale, pane, edge), dpi);
|
let her = boxed(stand(client, scale, pane, edge, 140.0), dpi);
|
||||||
let own = dpi as f32 / 96.0;
|
let own = dpi as f32 / 96.0;
|
||||||
let own_box = (
|
let own_box = (
|
||||||
(WIDTH * own).round() as i32,
|
(WIDTH * own).round() as i32,
|
||||||
((HEAD + ROOM) * own).round() as i32,
|
((HEAD + ROOM) * own).round() as i32,
|
||||||
);
|
);
|
||||||
let corner = (
|
let corner = (
|
||||||
100 + ((10.0 + left(px(800.))) * scale).round() as i32,
|
100 + ((10.0 + left(px(800.), 140.0)) * scale).round() as i32,
|
||||||
20 + (y * scale).round() as i32,
|
20 + (y * scale).round() as i32,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -272,7 +272,7 @@ fn the_large_framed_popup_gpui_makes_stands_bare_in_her_box_on_an_edge_of_the_pa
|
|||||||
let work = work_area(main);
|
let work = work_area(main);
|
||||||
move_main(main, (work.left + 200, work.top + 400));
|
move_main(main, (work.left + 200, work.top + 400));
|
||||||
let pane = Bounds::new(point(px(0.), px(0.)), size(px(380.), px(280.)));
|
let pane = Bounds::new(point(px(0.), px(0.)), size(px(380.), px(280.)));
|
||||||
let roomy = glue.put(1.0, pane).expect("placed");
|
let roomy = glue.put(1.0, pane, 140.0).expect("placed");
|
||||||
let made = info(pet).rcWindow;
|
let made = info(pet).rcWindow;
|
||||||
deliver(pet);
|
deliver(pet);
|
||||||
let (her, under) = (info(pet), info(main));
|
let (her, under) = (info(pet), info(main));
|
||||||
@@ -297,7 +297,7 @@ fn the_large_framed_popup_gpui_makes_stands_bare_in_her_box_on_an_edge_of_the_pa
|
|||||||
let (suggested, taken, stood) = change(display + 48);
|
let (suggested, taken, stood) = change(display + 48);
|
||||||
// At the work area's top the main window leaves her no room above it.
|
// At the work area's top the main window leaves her no room above it.
|
||||||
move_main(main, (work.left + 200, work.top));
|
move_main(main, (work.left + 200, work.top));
|
||||||
let cramped = glue.put(1.0, pane).expect("placed again");
|
let cramped = glue.put(1.0, pane, 140.0).expect("placed again");
|
||||||
deliver(pet);
|
deliver(pet);
|
||||||
let (inside, low) = (info(pet).rcWindow, info(main).rcClient);
|
let (inside, low) = (info(pet).rcWindow, info(main).rcClient);
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -317,7 +317,7 @@ fn the_large_framed_popup_gpui_makes_stands_bare_in_her_box_on_an_edge_of_the_pa
|
|||||||
((HEAD + ROOM) * own).round() as i32,
|
((HEAD + ROOM) * own).round() as i32,
|
||||||
);
|
);
|
||||||
let edge = (
|
let edge = (
|
||||||
under.rcClient.left + left(px(380.)).round() as i32,
|
under.rcClient.left + left(px(380.), 140.0).round() as i32,
|
||||||
under.rcClient.top,
|
under.rcClient.top,
|
||||||
);
|
);
|
||||||
let rect = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom);
|
let rect = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom);
|
||||||
|
|||||||
+20
-11
@@ -49,20 +49,29 @@ impl Glue {
|
|||||||
Ok(glue)
|
Ok(glue)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stands her on the main window's pane, on its top edge while her box fits under the top of the monitor holding most of the pane, or of the work area where that lies lower, else on its bottom edge, as far in as its width allows, stacked just above the main window's frame, and gives the edge.
|
/// Stands her on the main window's pane, on its top edge while her box fits under the top of the monitor holding most of the pane, or of the work area where that lies lower, else on its bottom edge, `at` in as far as its width allows, stacked just above the main window's frame, and gives the edge.
|
||||||
pub fn place(&self, main: &Window) -> anyhow::Result<Edge> {
|
pub fn place(&self, main: &Window, at: f32) -> anyhow::Result<Edge> {
|
||||||
self.put(main.scale_factor(), pane(main))
|
self.put(main.scale_factor(), pane(main), at)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pointer's x on the screen, in points at `scale`.
|
||||||
|
pub fn pointer(&self, scale: f32) -> anyhow::Result<f32> {
|
||||||
|
let reply = (self.connection)
|
||||||
|
.query_pointer(self.pet)?
|
||||||
|
.reply()
|
||||||
|
.context("pet: the pointer's place")?;
|
||||||
|
Ok(f32::from(reply.root_x) / scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Places her at `scale` on an edge of `pane`, in the main window's points.
|
/// Places her at `scale` on an edge of `pane`, in the main window's points.
|
||||||
fn put(&self, scale: f32, pane: Bounds<Pixels>) -> anyhow::Result<Edge> {
|
fn put(&self, scale: f32, pane: Bounds<Pixels>, at: f32) -> anyhow::Result<Edge> {
|
||||||
let (root, frame) = self.frame()?;
|
let (root, frame) = self.frame()?;
|
||||||
let at = (self.connection)
|
let origin = (self.connection)
|
||||||
.translate_coordinates(self.main, root, 0, 0)?
|
.translate_coordinates(self.main, root, 0, 0)?
|
||||||
.reply()
|
.reply()
|
||||||
.context("pet: the main window's place")?;
|
.context("pet: the main window's place")?;
|
||||||
let down = |y: Pixels| i32::from(at.dst_y) + (f32::from(y) * scale).round() as i32;
|
let down = |y: Pixels| i32::from(origin.dst_y) + (f32::from(y) * scale).round() as i32;
|
||||||
let across = |x: Pixels| i32::from(at.dst_x) + (f32::from(x) * scale).round() as i32;
|
let across = |x: Pixels| i32::from(origin.dst_x) + (f32::from(x) * scale).round() as i32;
|
||||||
let (west, top) = (across(pane.origin.x), down(pane.origin.y));
|
let (west, top) = (across(pane.origin.x), down(pane.origin.y));
|
||||||
let rect = [
|
let rect = [
|
||||||
west,
|
west,
|
||||||
@@ -77,7 +86,7 @@ impl Glue {
|
|||||||
Edge::Bottom => pane.bottom(),
|
Edge::Bottom => pane.bottom(),
|
||||||
});
|
});
|
||||||
let aux = ConfigureWindowAux::new()
|
let aux = ConfigureWindowAux::new()
|
||||||
.x(across(pane.origin.x + px(left(pane.size.width))))
|
.x(across(pane.origin.x + px(left(pane.size.width, at))))
|
||||||
.y(bottom - ((HEAD + ROOM) * scale).round() as i32)
|
.y(bottom - ((HEAD + ROOM) * scale).round() as i32)
|
||||||
.sibling(frame)
|
.sibling(frame)
|
||||||
.stack_mode(StackMode::ABOVE);
|
.stack_mode(StackMode::ABOVE);
|
||||||
@@ -269,7 +278,7 @@ mod tests {
|
|||||||
workarea,
|
workarea,
|
||||||
};
|
};
|
||||||
let pane = Bounds::new(point(px(10.), px(10.)), size(px(380.), px(280.)));
|
let pane = Bounds::new(point(px(10.), px(10.)), size(px(380.), px(280.)));
|
||||||
let roomy = glue.put(2.0, pane).expect("she is placed");
|
let roomy = glue.put(2.0, pane, 140.0).expect("she is placed");
|
||||||
let check = x11rb::connect(Some(&xvfb.display))
|
let check = x11rb::connect(Some(&xvfb.display))
|
||||||
.expect("the check connects")
|
.expect("the check connects")
|
||||||
.0;
|
.0;
|
||||||
@@ -280,7 +289,7 @@ mod tests {
|
|||||||
.expect("her place");
|
.expect("her place");
|
||||||
let tall = ((HEAD + ROOM) * 2.0).round() as i32;
|
let tall = ((HEAD + ROOM) * 2.0).round() as i32;
|
||||||
let stands = (
|
let stands = (
|
||||||
100 + ((10.0 + left(px(380.))) * 2.0).round() as i32,
|
100 + ((10.0 + left(px(380.), 140.0)) * 2.0).round() as i32,
|
||||||
440 - tall,
|
440 - tall,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -322,7 +331,7 @@ mod tests {
|
|||||||
.expect("the request")
|
.expect("the request")
|
||||||
.check()
|
.check()
|
||||||
.expect("a work area");
|
.expect("a work area");
|
||||||
let cramped = glue.put(2.0, pane).expect("she is placed again");
|
let cramped = glue.put(2.0, pane, 140.0).expect("she is placed again");
|
||||||
let low = check
|
let low = check
|
||||||
.get_geometry(pet)
|
.get_geometry(pet)
|
||||||
.expect("the request")
|
.expect("the request")
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ pub struct Settings {
|
|||||||
pub copy_on_select: bool,
|
pub copy_on_select: bool,
|
||||||
/// Ely peeks over the main window's top edge.
|
/// Ely peeks over the main window's top edge.
|
||||||
pub ely: bool,
|
pub ely: bool,
|
||||||
|
/// How far in from the pane's left edge she stands, in points, as a drag left her.
|
||||||
|
pub ely_left: f32,
|
||||||
|
/// A check for a newer release at start and once a day.
|
||||||
|
pub check_updates: bool,
|
||||||
pub show_sessions: bool,
|
pub show_sessions: bool,
|
||||||
pub show_panel: bool,
|
pub show_panel: bool,
|
||||||
pub panel: PanelTab,
|
pub panel: PanelTab,
|
||||||
@@ -106,6 +110,8 @@ impl Default for Settings {
|
|||||||
option_as_alt: OptionAsAlt::Off,
|
option_as_alt: OptionAsAlt::Off,
|
||||||
copy_on_select: false,
|
copy_on_select: false,
|
||||||
ely: true,
|
ely: true,
|
||||||
|
ely_left: 140.0,
|
||||||
|
check_updates: true,
|
||||||
show_sessions: true,
|
show_sessions: true,
|
||||||
show_panel: true,
|
show_panel: true,
|
||||||
panel: PanelTab::Git,
|
panel: PanelTab::Git,
|
||||||
@@ -124,6 +130,7 @@ pub const PADDING: RangeInclusive<f32> = 0.0..=64.0;
|
|||||||
pub const SCROLLBACK: RangeInclusive<u32> = 0..=100_000;
|
pub const SCROLLBACK: RangeInclusive<u32> = 0..=100_000;
|
||||||
pub const COLUMN: RangeInclusive<f32> = 160.0..=960.0;
|
pub const COLUMN: RangeInclusive<f32> = 160.0..=960.0;
|
||||||
pub const WINDOW: RangeInclusive<f32> = 100.0..=16384.0;
|
pub const WINDOW: RangeInclusive<f32> = 100.0..=16384.0;
|
||||||
|
pub const ELY_LEFT: RangeInclusive<f32> = 0.0..=16384.0;
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
/// Every value inside its range, every theme built in; the error names the field.
|
/// Every value inside its range, every theme built in; the error names the field.
|
||||||
@@ -138,6 +145,7 @@ impl Settings {
|
|||||||
within("panel_width", self.panel_width, &COLUMN)?;
|
within("panel_width", self.panel_width, &COLUMN)?;
|
||||||
within("window_width", self.window_width, &WINDOW)?;
|
within("window_width", self.window_width, &WINDOW)?;
|
||||||
within("window_height", self.window_height, &WINDOW)?;
|
within("window_height", self.window_height, &WINDOW)?;
|
||||||
|
within("ely_left", self.ely_left, &ELY_LEFT)?;
|
||||||
for (field, id) in [
|
for (field, id) in [
|
||||||
("theme", Some(&self.theme)),
|
("theme", Some(&self.theme)),
|
||||||
("dark_theme", self.dark_theme.as_ref()),
|
("dark_theme", self.dark_theme.as_ref()),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{Settings, SettingsStore};
|
use super::{Settings, SettingsStore};
|
||||||
use crate::{app::APP_ID, menus, term::BLINK};
|
use crate::{app::APP_ID, menus, term::BLINK, updates::Updates};
|
||||||
|
|
||||||
mod appearance;
|
mod appearance;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
@@ -100,6 +100,7 @@ pub struct SettingsWindow {
|
|||||||
lit: bool,
|
lit: bool,
|
||||||
_typed: Subscription,
|
_typed: Subscription,
|
||||||
_settings: Subscription,
|
_settings: Subscription,
|
||||||
|
_updates: Subscription,
|
||||||
_blink: Task<()>,
|
_blink: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +134,7 @@ impl SettingsWindow {
|
|||||||
lit: true,
|
lit: true,
|
||||||
_typed: typed,
|
_typed: typed,
|
||||||
_settings: cx.observe_global::<SettingsStore>(|_, cx| cx.notify()),
|
_settings: cx.observe_global::<SettingsStore>(|_, cx| cx.notify()),
|
||||||
|
_updates: cx.observe_global::<Updates>(|_, cx| cx.notify()),
|
||||||
_blink: Self::blink(cx),
|
_blink: Self::blink(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use ely_gpui_component::{
|
use ely_gpui_component::{
|
||||||
buttons::SegmentedControl,
|
buttons::{Button, SegmentedControl},
|
||||||
feedback::InlineMessage,
|
feedback::InlineMessage,
|
||||||
forms::{Copy, NumberInput, Paste, PathInput, SelectAll},
|
forms::{Copy, NumberInput, Paste, PathInput, SelectAll, Switch},
|
||||||
primitives::Severity,
|
primitives::Severity,
|
||||||
settings::{KeyboardShortcutsList, SettingsRow, SettingsSection, Shortcut},
|
settings::{KeyboardShortcutsList, SettingsRow, SettingsSection, Shortcut},
|
||||||
theme::{ActiveTheme, ControlSize},
|
theme::{ActiveTheme, ControlSize},
|
||||||
@@ -15,6 +15,7 @@ use crate::{
|
|||||||
menus::{Bigger, Minimize, Quit, ResetSize, Smaller},
|
menus::{Bigger, Minimize, Quit, ResetSize, Smaller},
|
||||||
settings::{OptionAsAlt, SCROLLBACK, SettingsStore, StartIn},
|
settings::{OptionAsAlt, SCROLLBACK, SettingsStore, StartIn},
|
||||||
term::input::{self, Find, NextPrompt, PreviousPrompt},
|
term::input::{self, Find, NextPrompt, PreviousPrompt},
|
||||||
|
updates::{self, Updates, VERSION},
|
||||||
workspace::{
|
workspace::{
|
||||||
self, CloseSession, LastSession, NewSession, NextSession, PreviousSession, SelectSession,
|
self, CloseSession, LastSession, NewSession, NextSession, PreviousSession, SelectSession,
|
||||||
TogglePanel, ToggleSessions,
|
TogglePanel, ToggleSessions,
|
||||||
@@ -42,7 +43,7 @@ const PLACES: [&str; 8] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
impl SettingsWindow {
|
impl SettingsWindow {
|
||||||
/// Shell, start in, scrollback, Option as Alt on macOS and copy on select, then the shortcuts.
|
/// Shell, start in, scrollback, Option as Alt on macOS, copy on select, Ely and updates, then the shortcuts.
|
||||||
pub(super) fn terminal(&self, cx: &Context<Self>) -> Div {
|
pub(super) fn terminal(&self, cx: &Context<Self>) -> Div {
|
||||||
let settings = SettingsStore::get(cx);
|
let settings = SettingsStore::get(cx);
|
||||||
let this = cx.entity();
|
let this = cx.entity();
|
||||||
@@ -106,13 +107,70 @@ impl SettingsWindow {
|
|||||||
SettingsRow::new("Ely")
|
SettingsRow::new("Ely")
|
||||||
.description("Peeks over the window, her face as your sessions are")
|
.description("Peeks over the window, her face as your sessions are")
|
||||||
.control(ely),
|
.control(ely),
|
||||||
);
|
)
|
||||||
|
.row(
|
||||||
|
SettingsRow::new("Check for updates automatically")
|
||||||
|
.description("At start and once a day")
|
||||||
|
.control(auto_check(&this, settings.check_updates)),
|
||||||
|
)
|
||||||
|
.row(updates_row(cx));
|
||||||
let keys = SettingsSection::new("Shortcuts")
|
let keys = SettingsSection::new("Shortcuts")
|
||||||
.row(KeyboardShortcutsList::new(shortcuts(cx), &self.search));
|
.row(KeyboardShortcutsList::new(shortcuts(cx), &self.search));
|
||||||
div().flex().flex_col().gap_6().child(section).child(keys)
|
div().flex().flex_col().gap_6().child(section).child(keys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The switch for automatic checks; turning it on checks at once.
|
||||||
|
fn auto_check(this: &Entity<SettingsWindow>, on: bool) -> Switch {
|
||||||
|
let this = this.clone();
|
||||||
|
Switch::new("check_updates", on).on_change(move |on, _, cx| {
|
||||||
|
let change = format!("check_updates is {on}");
|
||||||
|
if set(&this, change, cx, |settings| settings.check_updates = on) && on {
|
||||||
|
updates::check(false, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What the last check found, a button that checks now, and one that opens a newer release's page.
|
||||||
|
fn updates_row(cx: &App) -> SettingsRow {
|
||||||
|
let found = cx.global::<Updates>();
|
||||||
|
let status: SharedString = match found {
|
||||||
|
Updates::Unchecked => format!("ELYTerminal {VERSION}").into(),
|
||||||
|
Updates::Checking => "Checking for a newer release…".into(),
|
||||||
|
Updates::Latest => format!("ELYTerminal {VERSION} is the latest").into(),
|
||||||
|
Updates::Newer { version, .. } => {
|
||||||
|
format!("ELYTerminal {version} is out; this is {VERSION}").into()
|
||||||
|
}
|
||||||
|
Updates::Failed(why) => format!("The check failed: {why}").into(),
|
||||||
|
};
|
||||||
|
let page = match found {
|
||||||
|
Updates::Newer { page, .. } => Some(page.clone()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let check = Button::new("check-updates", "Check now")
|
||||||
|
.size(ControlSize::Sm)
|
||||||
|
.loading(*found == Updates::Checking)
|
||||||
|
.on_click(|_, _, cx| updates::check(false, cx));
|
||||||
|
let open = page.map(|page| {
|
||||||
|
Button::new("open-update", "Download")
|
||||||
|
.primary()
|
||||||
|
.size(ControlSize::Sm)
|
||||||
|
.on_click(move |_, _, cx| {
|
||||||
|
log::info!("updates: Settings opens {page}");
|
||||||
|
cx.open_url(&page);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let control = div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.children(open)
|
||||||
|
.child(check);
|
||||||
|
SettingsRow::new("Updates")
|
||||||
|
.description(status)
|
||||||
|
.control(control)
|
||||||
|
}
|
||||||
|
|
||||||
/// Where new sessions start, as the settings keep it.
|
/// Where new sessions start, as the settings keep it.
|
||||||
fn start_in(this: &Entity<SettingsWindow>, value: &SharedString, cx: &mut App) {
|
fn start_in(this: &Entity<SettingsWindow>, value: &SharedString, cx: &mut App) {
|
||||||
let start = match value.as_ref() {
|
let start = match value.as_ref() {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ mod pointer;
|
|||||||
mod prompts;
|
mod prompts;
|
||||||
mod pty;
|
mod pty;
|
||||||
mod reports;
|
mod reports;
|
||||||
|
mod scrollbar;
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
mod status;
|
mod status;
|
||||||
mod tap;
|
mod tap;
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
//! The terminal's scrollbar: a thumb over the history, in Ely's sizes and colors, that a drag moves through it, whatever the wheel is bound to.
|
||||||
|
|
||||||
|
use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
|
use alacritty_terminal::{
|
||||||
|
grid::{Dimensions, Scroll},
|
||||||
|
term::Term,
|
||||||
|
};
|
||||||
|
use ely_gpui_component::theme::ActiveTheme;
|
||||||
|
use gpui::{
|
||||||
|
AnyElement, AppContext as _, Context, DragMoveEvent, EmptyView, EntityId, InteractiveElement,
|
||||||
|
IntoElement, ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, div, relative,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::view::TerminalView;
|
||||||
|
|
||||||
|
/// A drag of the thumb: its view, how far down the history the view stood as it began, and the drag's first point.
|
||||||
|
pub(super) struct ThumbDrag {
|
||||||
|
owner: EntityId,
|
||||||
|
from: f32,
|
||||||
|
anchor: Rc<Cell<Option<Pixels>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where the view stands in the history: the lines above the screen, the screen's lines, and how far up the view is.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub(super) struct Span {
|
||||||
|
history: usize,
|
||||||
|
screen: usize,
|
||||||
|
offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
pub(super) fn of<T>(term: &Term<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
history: term.grid().history_size(),
|
||||||
|
screen: term.screen_lines(),
|
||||||
|
offset: term.grid().display_offset(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The thumb's share of the track.
|
||||||
|
fn share(self) -> f32 {
|
||||||
|
self.screen as f32 / (self.history + self.screen) as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How far down the history the view stands: 0 at its top, 1 at the bottom.
|
||||||
|
fn ratio(self) -> f32 {
|
||||||
|
1.0 - self.offset as f32 / self.history as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How far up the view stands with the thumb `ratio` down the track.
|
||||||
|
fn offset_at(self, ratio: f32) -> usize {
|
||||||
|
((1.0 - ratio.clamp(0.0, 1.0)) * self.history as f32).round() as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The bar along the view's right edge, over the padding there; none without history, as on the alternate screen.
|
||||||
|
pub(super) fn bar(span: Span, window: &Window, cx: &Context<TerminalView>) -> Option<AnyElement> {
|
||||||
|
if span.history == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let theme = cx.theme();
|
||||||
|
let rem = window.rem_size();
|
||||||
|
let thickness = theme.scrollbar_thickness().to_pixels(rem);
|
||||||
|
let min_thumb = theme.scrollbar_min_thumb().to_pixels(rem);
|
||||||
|
let (idle, busy) = (theme.colors.fg.opacity(0.22), theme.colors.fg.opacity(0.42));
|
||||||
|
let ratio = span.ratio();
|
||||||
|
// The room above and below the thumb shares what it leaves as the view stands.
|
||||||
|
let (mut above, mut below) = (div(), div());
|
||||||
|
above.style().flex_grow = Some(ratio);
|
||||||
|
below.style().flex_grow = Some(1.0 - ratio);
|
||||||
|
let drag = ThumbDrag {
|
||||||
|
owner: cx.entity_id(),
|
||||||
|
from: ratio,
|
||||||
|
anchor: Rc::default(),
|
||||||
|
};
|
||||||
|
let thumb = div()
|
||||||
|
.id("thumb")
|
||||||
|
.w(thickness)
|
||||||
|
.h(relative(span.share()))
|
||||||
|
.min_h(min_thumb)
|
||||||
|
.flex_shrink_0()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(idle)
|
||||||
|
.hover(|style| style.bg(busy))
|
||||||
|
.on_drag(drag, |_, _, _, cx| cx.new(|_| EmptyView));
|
||||||
|
let bar = div()
|
||||||
|
.id("scrollbar")
|
||||||
|
.absolute()
|
||||||
|
.top_0()
|
||||||
|
.bottom_0()
|
||||||
|
.right_0()
|
||||||
|
.w(thickness * 2.0)
|
||||||
|
.pl(thickness * 0.5)
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.occlude()
|
||||||
|
.on_drag_move(cx.listener(TerminalView::drag_thumb))
|
||||||
|
.child(above)
|
||||||
|
.child(thumb)
|
||||||
|
.child(below);
|
||||||
|
Some(bar.into_any_element())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TerminalView {
|
||||||
|
/// Scrolls the history after the thumb, along its track's bounds.
|
||||||
|
fn drag_thumb(
|
||||||
|
&mut self,
|
||||||
|
event: &DragMoveEvent<ThumbDrag>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let drag = event.drag(cx);
|
||||||
|
if drag.owner != cx.entity_id() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let min_thumb = cx
|
||||||
|
.theme()
|
||||||
|
.scrollbar_min_thumb()
|
||||||
|
.to_pixels(window.rem_size());
|
||||||
|
let mut term = self.pty.term.lock();
|
||||||
|
let span = Span::of(&term);
|
||||||
|
let height = event.bounds.size.height;
|
||||||
|
let travel = height - (height * span.share()).max(min_thumb).min(height);
|
||||||
|
if span.history == 0 || travel <= Pixels::ZERO {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let along = event.event.position.y - event.bounds.origin.y;
|
||||||
|
let anchor = drag.anchor.get().unwrap_or(along);
|
||||||
|
drag.anchor.set(Some(anchor));
|
||||||
|
let offset = span.offset_at(drag.from + (along - anchor) / travel);
|
||||||
|
let delta = offset as i32 - span.offset as i32;
|
||||||
|
if delta != 0 {
|
||||||
|
term.scroll_display(Scroll::Delta(delta));
|
||||||
|
drop(term);
|
||||||
|
log::trace!(
|
||||||
|
"terminal {}: the scrollbar shows {offset} lines up",
|
||||||
|
self.pty.pid
|
||||||
|
);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_thumb_stands_where_the_view_stands_in_the_history() {
|
||||||
|
let span = |offset| Span {
|
||||||
|
history: 300,
|
||||||
|
screen: 100,
|
||||||
|
offset,
|
||||||
|
};
|
||||||
|
assert_eq!(span(0).share(), 0.25, "a screen of four");
|
||||||
|
assert_eq!(span(0).ratio(), 1.0, "at the bottom");
|
||||||
|
assert_eq!(span(300).ratio(), 0.0, "at the top");
|
||||||
|
assert_eq!(span(150).ratio(), 0.5);
|
||||||
|
assert_eq!(
|
||||||
|
span(0).offset_at(0.0),
|
||||||
|
300,
|
||||||
|
"the thumb at the top shows the oldest lines"
|
||||||
|
);
|
||||||
|
assert_eq!(span(0).offset_at(1.0), 0);
|
||||||
|
assert_eq!(span(0).offset_at(0.5), 150);
|
||||||
|
assert_eq!(span(0).offset_at(-0.2), 300, "past the track's ends");
|
||||||
|
assert_eq!(span(0).offset_at(1.3), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-4
@@ -28,6 +28,7 @@ use super::{
|
|||||||
pointer::Pointer,
|
pointer::Pointer,
|
||||||
pty::{Events, Pty, PtyEvent, config, fit},
|
pty::{Events, Pty, PtyEvent, config, fit},
|
||||||
reports::Notices,
|
reports::Notices,
|
||||||
|
scrollbar::{self, Span},
|
||||||
status::{Records, State},
|
status::{Records, State},
|
||||||
zoom,
|
zoom,
|
||||||
};
|
};
|
||||||
@@ -413,6 +414,7 @@ impl Render for TerminalView {
|
|||||||
});
|
});
|
||||||
let shown = frame(&term, &ink, &lit);
|
let shown = frame(&term, &ink, &lit);
|
||||||
let blinks = term.cursor_style().blinking;
|
let blinks = term.cursor_style().blinking;
|
||||||
|
let span = Span::of(&term);
|
||||||
drop(term);
|
drop(term);
|
||||||
let focused = self.focus.is_focused(window) && window.is_window_active();
|
let focused = self.focus.is_focused(window) && window.is_window_active();
|
||||||
self.blinking = focused && blinks;
|
self.blinking = focused && blinks;
|
||||||
@@ -480,9 +482,14 @@ impl Render for TerminalView {
|
|||||||
root.text_color(ink.fg).child(format!("{error:#}"))
|
root.text_color(ink.fg).child(format!("{error:#}"))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
div().relative().size_full().child(root).children(
|
div()
|
||||||
self.find_box(cx)
|
.relative()
|
||||||
.map(|found| div().absolute().top_2().right_4().child(found)),
|
.size_full()
|
||||||
)
|
.child(root)
|
||||||
|
.children(scrollbar::bar(span, window, cx))
|
||||||
|
.children(
|
||||||
|
self.find_box(cx)
|
||||||
|
.map(|found| div().absolute().top_2().right_4().child(found)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+207
@@ -0,0 +1,207 @@
|
|||||||
|
//! Checks for a newer release through curl and Gitea's API: at start and once a day while the settings ask, and whenever Settings asks at once.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
process::{Command, Stdio},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use gpui::{App, Global, SystemNotification};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::{settings::SettingsStore, workspace::TITLE};
|
||||||
|
|
||||||
|
/// The latest published release.
|
||||||
|
const LATEST: &str =
|
||||||
|
"https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/ELYTerminal/releases/latest";
|
||||||
|
/// How long an automatic check waits for the next.
|
||||||
|
const DAY: Duration = Duration::from_secs(24 * 60 * 60);
|
||||||
|
/// The tag a newer release's notification carries.
|
||||||
|
const TAG: &str = "elyterminal-update";
|
||||||
|
/// The running version.
|
||||||
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
/// What the last check found.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Updates {
|
||||||
|
Unchecked,
|
||||||
|
Checking,
|
||||||
|
Latest,
|
||||||
|
Newer { version: String, page: String },
|
||||||
|
Failed(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Global for Updates {}
|
||||||
|
|
||||||
|
/// The fields of a release the check reads.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Release {
|
||||||
|
tag_name: String,
|
||||||
|
html_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks at once and then once a day, each time the settings ask for it.
|
||||||
|
pub fn start(cx: &mut App) {
|
||||||
|
cx.set_global(Updates::Unchecked);
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
loop {
|
||||||
|
cx.update(|cx| {
|
||||||
|
if SettingsStore::get(cx).check_updates {
|
||||||
|
check(true, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cx.background_executor().timer(DAY).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asks for the latest release off the main thread; when `auto`, a newer one posts a notification.
|
||||||
|
pub fn check(auto: bool, cx: &mut App) {
|
||||||
|
if *cx.global::<Updates>() == Updates::Checking {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log::info!("updates: checking for a release newer than {VERSION}");
|
||||||
|
cx.set_global(Updates::Checking);
|
||||||
|
let fetched = cx.background_executor().spawn(async { latest() });
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let found = fetched.await.and_then(newer);
|
||||||
|
cx.update(|cx| finish(found, auto, cx));
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keeps what the check found, and tells of a newer release it found by itself.
|
||||||
|
fn finish(found: anyhow::Result<Option<(String, String)>>, auto: bool, cx: &mut App) {
|
||||||
|
let found = match found {
|
||||||
|
Ok(Some((version, page))) => {
|
||||||
|
log::info!("updates: {version} is out, newer than {VERSION}, at {page}");
|
||||||
|
if auto {
|
||||||
|
cx.show_system_notification(SystemNotification {
|
||||||
|
tag: TAG.into(),
|
||||||
|
title: TITLE.into(),
|
||||||
|
body: format!("ELYTerminal {version} is out. Press to open its page.").into(),
|
||||||
|
actions: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Updates::Newer { version, page }
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
log::info!("updates: {VERSION} is the latest");
|
||||||
|
Updates::Latest
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("{error:#}");
|
||||||
|
Updates::Failed(format!("{error:#}"))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cx.set_global(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the newer release's page when its notification is pressed; tells whether `tag` was that notification's.
|
||||||
|
pub fn pressed(tag: &str, cx: &mut App) -> bool {
|
||||||
|
if tag != TAG {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match cx.global::<Updates>().clone() {
|
||||||
|
Updates::Newer { page, .. } => {
|
||||||
|
log::info!("updates: the notification opens {page}");
|
||||||
|
cx.open_url(&page);
|
||||||
|
}
|
||||||
|
found => {
|
||||||
|
log::info!("updates: the notification was pressed, the last check finding {found:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The latest release, which curl fetches from Gitea's API.
|
||||||
|
fn latest() -> anyhow::Result<Release> {
|
||||||
|
let mut command = Command::new("curl");
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::os::windows::process::CommandExt::creation_flags(
|
||||||
|
&mut command,
|
||||||
|
crate::git::CREATE_NO_WINDOW,
|
||||||
|
);
|
||||||
|
let output = command
|
||||||
|
.args(["--fail", "--silent", "--show-error", "--location"])
|
||||||
|
.args(["--max-time", "30", LATEST])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.output()
|
||||||
|
.context("updates: curl did not start")?;
|
||||||
|
anyhow::ensure!(
|
||||||
|
output.status.success(),
|
||||||
|
"updates: curl failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr).trim()
|
||||||
|
);
|
||||||
|
serde_json::from_slice(&output.stdout).context("updates: the latest release does not read")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The release's version and page when it is newer than this one.
|
||||||
|
fn newer(release: Release) -> anyhow::Result<Option<(String, String)>> {
|
||||||
|
let latest = version(&release.tag_name)?;
|
||||||
|
let shown = release.tag_name.trim_start_matches('v').to_string();
|
||||||
|
Ok((latest > version(VERSION)?).then_some((shown, release.html_url)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `tag`'s three numbers, from `v0.1.6` or `0.1.6`.
|
||||||
|
fn version(tag: &str) -> anyhow::Result<[u64; 3]> {
|
||||||
|
let numbers: Vec<u64> = (tag.trim_start_matches('v').split('.'))
|
||||||
|
.map(str::parse)
|
||||||
|
.collect::<Result<_, _>>()
|
||||||
|
.with_context(|| format!("updates: {tag:?} is no version"))?;
|
||||||
|
numbers
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| anyhow::anyhow!("updates: {tag:?} is no version of three numbers"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_tag_reads_as_three_numbers() {
|
||||||
|
assert_eq!(version("v0.1.6").expect("a tag"), [0, 1, 6]);
|
||||||
|
assert_eq!(version("1.10.0").expect("no v"), [1, 10, 0]);
|
||||||
|
assert!(version("v0.1").is_err(), "two numbers");
|
||||||
|
assert!(version("v0.1.6-beta").is_err(), "a suffix");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_a_later_release_is_newer() {
|
||||||
|
let release = |tag: &str| Release {
|
||||||
|
tag_name: tag.into(),
|
||||||
|
html_url: format!("https://example.org/{tag}"),
|
||||||
|
};
|
||||||
|
let [major, minor, patch] = version(VERSION).expect("this version");
|
||||||
|
let later = format!("v{major}.{minor}.{}", patch + 1);
|
||||||
|
let found = newer(release(&later)).expect("reads");
|
||||||
|
let shown = later.trim_start_matches('v').to_string();
|
||||||
|
assert_eq!(found, Some((shown, format!("https://example.org/{later}"))));
|
||||||
|
assert_eq!(
|
||||||
|
newer(release(&format!("v{VERSION}"))).expect("reads"),
|
||||||
|
None,
|
||||||
|
"this one"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
newer(release("v0.0.1")).expect("reads"),
|
||||||
|
None,
|
||||||
|
"an older one"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
newer(release(&format!("v{}.0.0", major + 1)))
|
||||||
|
.expect("reads")
|
||||||
|
.map(|(v, _)| v),
|
||||||
|
Some(format!("{}.0.0", major + 1)),
|
||||||
|
"by its numbers, not as text"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gitea_s_release_reads() {
|
||||||
|
let body = r#"{"id":213,"tag_name":"v0.1.5","name":"ELYTerminal 0.1.5","html_url":"https://git.zacharyzhang.com/ZacharyZhang-NY/ELYTerminal/releases/tag/v0.1.5","draft":false,"assets":[]}"#;
|
||||||
|
let release: Release = serde_json::from_str(body).expect("a release");
|
||||||
|
assert_eq!(release.tag_name, "v0.1.5");
|
||||||
|
assert!(release.html_url.ends_with("/releases/tag/v0.1.5"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-6
@@ -70,6 +70,7 @@ What no check reached: no real KWin drew the blur; of Wayland desktops, only Hyp
|
|||||||
| Copy, paste, bracketed paste, copy on select, OSC 52 | T09 | T09 review | Pass |
|
| Copy, paste, bracketed paste, copy on select, OSC 52 | T09 | T09 review | Pass |
|
||||||
| OSC 8 and plain links on Cmd-press, Ctrl-press off macOS; inside herdr, herdr opens them whole | T09, T45 | `term::links` tests; on macOS with real herdr 0.9.5 and a stand-in `open` on its PATH, Cmd-press on a URL herdr wraps, on its wrapped tail, on an OSC 8 link and on a URL over three rows opened the whole URL and the OSC 8 target, where before the app opened `.../ELYTermin` and nothing; Cmd-hover underlined the long URL across its rows; outside herdr the app opened the URL itself; an OSC 8 link whose label holds `file:///etc/hosts` and whose target is a web page opened the page through herdr, which keeps the target from the terminal while it takes the mouse; Shift-Cmd-press on a plain `file:///etc/hosts` inside herdr the app opened itself, caught by an `NSWorkspace` stand-in | Pass |
|
| OSC 8 and plain links on Cmd-press, Ctrl-press off macOS; inside herdr, herdr opens them whole | T09, T45 | `term::links` tests; on macOS with real herdr 0.9.5 and a stand-in `open` on its PATH, Cmd-press on a URL herdr wraps, on its wrapped tail, on an OSC 8 link and on a URL over three rows opened the whole URL and the OSC 8 target, where before the app opened `.../ELYTermin` and nothing; Cmd-hover underlined the long URL across its rows; outside herdr the app opened the URL itself; an OSC 8 link whose label holds `file:///etc/hosts` and whose target is a web page opened the page through herdr, which keeps the target from the terminal while it takes the mouse; Shift-Cmd-press on a plain `file:///etc/hosts` inside herdr the app opened itself, caught by an `NSWorkspace` stand-in | Pass |
|
||||||
| Focus reporting, title, scrollback limit, find | T10 | `term::find` tests; round 3 failed at the cap, its fix stands on a red-green test | Pass |
|
| Focus reporting, title, scrollback limit, find | T10 | `term::find` tests; round 3 failed at the cap, its fix stands on a red-green test | Pass |
|
||||||
|
| A scrollbar over the history: a drag on its thumb scrolls, whatever the program does with the wheel | T58 | `term::scrollbar` test: the thumb's share and place follow the history and the view's offset, and a place past either end of the track holds at it; under Xorg's dummy screen, after `seq 1 500`, the thumb stood at the bottom right, a drag to the top showed the first prompt and lines 1 to 39, and one back down lines 220 to 259; with mouse tracking on, the wheel went to the program as reports and did not scroll, while a drag on the thumb showed lines 245 to 284 | Pass |
|
||||||
| A bell makes no sound; the row marks it | T10, T12 | T12 review | Pass |
|
| A bell makes no sound; the row marks it | T10, T12 | T12 review | Pass |
|
||||||
|
|
||||||
## 6. Main window and layout
|
## 6. Main window and layout
|
||||||
@@ -84,7 +85,7 @@ What no check reached: no real KWin drew the blur; of Wayland desktops, only Hyp
|
|||||||
| 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 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 |
|
| The title bar is transparent | T11, T19 | T19 review | Pass |
|
||||||
| Every title bar control takes its press, Ely's window buttons on Windows and Linux too | T26 | Ely `cdc5ce8`: `window_controls_occlude_what_lies_under` (each style; before, the box under Windows' caption buttons took the press), `a_wobbling_press_on_a_slot_moves_no_window` (before, the window moved), `a_wobbling_press_on_a_column_title_moves_the_window`; on the owner's PC, window messages asked the app for Windows' hit test along the title bar: v0.1.0 answered caption from end to end, T26's build answers client over the toggles and +, caption over the titles, min, max and close over Ely's buttons, and a press on close closed the app; the owner, given v0.1.1's build and these controls to click, reported it works on Windows | Pass |
|
| Every title bar control takes its press, Ely's window buttons on Windows and Linux too | T26 | Ely `cdc5ce8`: `window_controls_occlude_what_lies_under` (each style; before, the box under Windows' caption buttons took the press), `a_wobbling_press_on_a_slot_moves_no_window` (before, the window moved), `a_wobbling_press_on_a_column_title_moves_the_window`; on the owner's PC, window messages asked the app for Windows' hit test along the title bar: v0.1.0 answered caption from end to end, T26's build answers client over the toggles and +, caption over the titles, min, max and close over Ely's buttons, and a press on close closed the app; the owner, given v0.1.1's build and these controls to click, reported it works on Windows | Pass |
|
||||||
| Ely peeks over the window's top edge from behind it, or stands inside at its bottom left where no room lies above it; her face follows the sessions; a press pets her and wakes her from a doze; nothing but her shows | T46, T48, T49, T51, T52, T53 | `pet::look` tests: the most pressing mark sets her mood; she moves in every mood, weeks into one too; she blinks on her own, twice at times, her eyes opening as an act begins; calm glances, then dozes with zs; a finish cheers her with a hop; work runs her eyes along the lines and taps her fingers; a wait widens her eyes and hops her twice a round; an error shakes her and runs a drop down; a press winks with a rising heart; she rises from behind the edge; a change of act eases her from her last pose, her brows easing and the marks she leaves fading; `pet` tests: a mood that changes inside a wink, an old finish cleared mid-glance and a second press mid-hop each ease her from where she was, where round 1 of T49's review found her drop 12 points; `pet::rig` tests: every layer of the icon's paths parses and stays in its box, and every mark has its layer; on macOS `shot`'s snaps, 40 in 10 s while calm, caught her breathing, blinking and glancing, and two a second apart in each mood caught her reading along, rocking her mark, fretting with her drop, cheering, and easing back to calm; `pet::x11` test under Xvfb: her window stands on the pane, just above the window manager's frame, and unmaps and maps with the main window; on macOS `shot` found her the main window's child, 112 by 124 points on its top edge 140 in, a capture of the screen showed her over the edge, and OSC 7501 reports gave her surprise, worry, eyes down and a cheer; a press on her window left the main window key and every key typed before and after it reached the program; switched off and on during an error she came back worried; on a 100-point window she stood at its left edge, and moved, resized and minimized she followed it; on Windows the `pet::windows` test, red before T48, finds the framed popup Windows makes, 1898 by 1481, bare in her own box, 168 by 185 at 150%, on the pane's top edge, and no size until her window takes the place posted to it; `pet::windows` tests, red before T48's second round: with the main window at 1, 1.5 or 2 and hers at 1, 1.25 or 1.5, her height is her own and her bottom the pane's top, where it fell 62 pixels into the terminal, and at a new DPI gpui takes the rect Windows suggests, then her box comes back on her corner, or Windows' rect stands where that box would lie on a display of another DPI; two displays side by side at 144 and 96 DPI, simulated, settle her after one change, where a corner kept always, red before T48's third round, changed her DPI back and forth; and two places piled up before either ran drain on the last corner in her DPI's size, where a keep whose DPI had passed, red before T48's fourth change, set it back and forth, and where a place sized when it was posted left her 112 by 123 at 144 DPI, red before its fifth; on a Windows 11 VM at 150%, with the main window 49 pixels from the screen's top, 0.1.4 opened her 2326 by 1654 with a caption, drawn at its bottom over the terminal, and a press inside the terminal went to her; T48's build opened her 168 by 185 without one, on the edge, the press went to the terminal, one beside her box to the program behind, and one on her to her; with the window mid-screen she showed whole on its edge; T51: `pet` tests: she stands on the top edge while her box fits above the pane, 123.4 points, else the bottom; on macOS 0.1.4 opened her, over a window under the menu bar, at 1081 to 1205, inside its top, as the owner saw, and T51 at its bottom edge, 92, 140 in, and over a centered window still on its top edge, 1028; the `pet::windows` tests on the owner's PC put her on the top edge 400 pixels down the work area and on the bottom edge at its top, and there the release build stood her on the top edge of a centered window, on the bottom edge of one moved to the work area's top and of a maximized one, 210 pixels in at 150%, and on the top edge again once it came back; the `pet::x11` test under Xvfb puts her on the bottom edge once `_NET_WORKAREA` leaves no room; on WSLg's X11 she stood over a window with room and inside one as tall as the screen; on WSLg's Wayland, Weston, her popup stood on the pane's top edge and, once the window was maximized, opened again on its bottom edge, at its bottom left; under Xorg with two monitors she moved to the bottom edge of a window at the lower one's top, 112x123+240+1277, and of one held mostly by a staggered monitor at its top, 112x123+1240+877; under headless Sway her anchor went (140, 0) on the top edge, then (140, 999), (140, 799) and (140, 999) on the bottom edge as the window went full screen and the output 800 and 1000 high, and full screen at the same size moved her too; T52: the `pet::look` test wakes her from a doze 400 s into calm with a press, which winks her, then she stays awLine truncated
|
| Ely peeks over the window's top edge from behind it, or stands inside at its bottom left where no room lies above it; her face follows the sessions; a press pets her and wakes her from a doze; nothing but her shows; a drag moves her along her edge | T46, T48, T49, T51, T52, T53, T55 | `pet::look` tests: the most pressing mark sets her mood; she moves in every mood, weeks into one too; she blinks on her own, twice at times, her eyes opening as an act begins; calm glances, then dozes with zs; a finish cheers her with a hop; work runs her eyes along the lines and taps her fingers; a wait widens her eyes and hops her twice a round; an error shakes her and runs a drop down; a press winks with a rising heart; she rises from behind the edge; a change of act eases her from her last pose, her brows easing and the marks she leaves fading; `pet` tests: a mood that changes inside a wink, an old finish cleared mid-glance and a second press mid-hop each ease her from where she was, where round 1 of T49's review found her drop 12 points; `pet::rig` tests: every layer of the icon's paths parses and stays in its box, and every mark has its layer; on macOS `shot`'s snaps, 40 in 10 s while calm, caught her breathing, blinking and glancing, and two a second apart in each mood caught her reading along, rocking her mark, fretting with her drop, cheering, and easing back to calm; `pet::x11` test under Xvfb: her window stands on the pane, just above the window manager's frame, and unmaps and maps with the main window; on macOS `shot` found her the main window's child, 112 by 124 points on its top edge 140 in, a capture of the screen showed her over the edge, and OSC 7501 reports gave her surprise, worry, eyes down and a cheer; a press on her window left the main window key and every key typed before and after it reached the program; switched off and on during an error she came back worried; on a 100-point window she stood at its left edge, and moved, resized and minimized she followed it; on Windows the `pet::windows` test, red before T48, finds the framed popup Windows makes, 1898 by 1481, bare in her own box, 168 by 185 at 150%, on the pane's top edge, and no size until her window takes the place posted to it; `pet::windows` tests, red before T48's second round: with the main window at 1, 1.5 or 2 and hers at 1, 1.25 or 1.5, her height is her own and her bottom the pane's top, where it fell 62 pixels into the terminal, and at a new DPI gpui takes the rect Windows suggests, then her box comes back on her corner, or Windows' rect stands where that box would lie on a display of another DPI; two displays side by side at 144 and 96 DPI, simulated, settle her after one change, where a corner kept always, red before T48's third round, changed her DPI back and forth; and two places piled up before either ran drain on the last corner in her DPI's size, where a keep whose DPI had passed, red before T48's fourth change, set it back and forth, and where a place sized when it was posted left her 112 by 123 at 144 DPI, red before its fifth; on a Windows 11 VM at 150%, with the main window 49 pixels from the screen's top, 0.1.4 opened her 2326 by 1654 with a caption, drawn at its bottom over the terminal, and a press inside the terminal went to her; T48's build opened her 168 by 185 without one, on the edge, the press went to the terminal, one beside her box to the program behind, and one on her to her; with the window mid-screen she showed whole on its edge; T51: `pet` tests: she stands on the top edge while her box fits above the pane, 123.4 points, else the bottom; on macOS 0.1.4 opened her, over a window under the menu bar, at 1081 to 1205, inside its top, as the owner saw, and T51 at its bottom edge, 92, 140 in, and over a centered window still on its top edge, 1028; the `pet::windows` tests on the owner's PC put her on the top edge 400 pixels down the work area and on the bottom edge at its top, and there the release build stood her on the top edge of a centered window, on the bottom edge of one moved to the work area's top and of a maximized one, 210 pixels in at 150%, and on the top edge again once it came back; the `pet::x11` test under Xvfb puts her on the bottom edge once `_NET_WORKAREA` leaves no room; on WSLg's X11 she stood over a window with room and inside one as tall as the screen; on WSLg's Wayland, Weston, her popup stood on the pane's top edge and, once the window was maximized, opened again on its bottom edge, at its bottom left; under Xorg with two monitors she moved to the bottom edge of a window at the lower one's top, 112x123+240+1277, and of one held mostly by a staggered monitor at its top, 112x123+1240+877; under headless Sway her anchor went (140, 0) on the top edge, then (140, 999), (140, 799) and (140, 999) on the bottom edge as the window went full screen and the output 800 and 1000 high, and full screen at the same size moved her too; T52: the `pet::look` test wakes her from a doze 400 s into calm with a preLine truncated
|
||||||
|
|
||||||
## 7. Sessions panel
|
## 7. Sessions panel
|
||||||
|
|
||||||
@@ -126,6 +127,7 @@ What no check reached: no real KWin drew the blur; of Wayland desktops, only Hyp
|
|||||||
| On Omarchy, the Omarchy theme first in the grid | T27 | on X11 with Omarchy's theme folder, the Omarchy card leads the grid, chosen, over a preview in its colors | Pass |
|
| On Omarchy, the Omarchy theme first in the grid | T27 | on X11 with Omarchy's theme folder, the Omarchy card leads the grid, chosen, over a preview in its colors | Pass |
|
||||||
| Terminal: scrollback, Option as Alt (macOS), copy on select | T18 | T18 review | Pass |
|
| Terminal: scrollback, Option as Alt (macOS), copy on select | T18 | T18 review | Pass |
|
||||||
| Terminal: Ely, a switch | T46 | on macOS the switch closed her window and saved `"ely": false`, a start with it showed no Ely, and the switch brought her back | Pass |
|
| Terminal: Ely, a switch | T46 | on macOS the switch closed her window and saved `"ely": false`, a start with it showed no Ely, and the switch brought her back | Pass |
|
||||||
|
| Terminal: check for updates automatically, a switch on by default, at start and once a day; Updates says what the check found, with Check now, and Download for a newer release; an automatic check that finds one posts a notification that opens its page | T56 | `updates` tests: a tag reads as three numbers, only a later release is newer, by its numbers, and Gitea's release body reads; under Xorg's dummy screen the app's start checked through curl and logged `0.1.5 is the latest`, the two rows stood under Ely, before Shortcuts, Check now checked again, the switch saved `check_updates` off and on, and turning it on checked at once; a build named 0.1.4 logged `0.1.5 is out`, its row read `ELYTerminal 0.1.5 is out; this is 0.1.4` beside Download and Check now, and Download logged that it opened the release's page; the notification's press went unexercised, since the container held no notification daemon | Pass |
|
||||||
| Terminal: every shortcut, with a search | T26 | read from the keymap, the bindings that apply in the main window's terminal; on X11 the list showed this platform's keys (Ctrl+Shift+T, Alt+1 and on), and typing "paste" left Edit's Paste, Ctrl+Shift+V | Pass |
|
| Terminal: every shortcut, with a search | T26 | read from the keymap, the bindings that apply in the main window's terminal; on X11 the list showed this platform's keys (Ctrl+Shift+T, Alt+1 and on), and typing "paste" left Edit's Paste, Ctrl+Shift+V | Pass |
|
||||||
|
|
||||||
## 11. Background blur
|
## 11. Background blur
|
||||||
@@ -160,16 +162,16 @@ What no check reached: no real KWin drew the blur; of Wayland desktops, only Hyp
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| The icon: Ely, a girl with long pink hair, holding a terminal | T24 | `assets/icons`, drawn at 16 to 1024 px and legible at 32; the owner saw it | Pass |
|
| The icon: Ely, a girl with long pink hair, holding a terminal | T24 | `assets/icons`, drawn at 16 to 1024 px and legible at 32; the owner saw it | Pass |
|
||||||
| macOS: the rounded square; Windows and Linux: the figure alone, flat | T24 | `elyterminal-macos.svg` on Apple's grid; `elyterminal.svg`, which the Linux tarball carries and the Windows `.ico` renders | Pass |
|
| macOS: the rounded square; Windows and Linux: the figure alone, flat | T24 | `elyterminal-macos.svg` on Apple's grid; `elyterminal.svg`, which the Linux tarball carries and the Windows `.ico` renders | Pass |
|
||||||
| `scripts/mac-app.sh` builds the app, signed ad hoc, or for a release with Developer ID and notarized, and its disk image | T24, T26, T31, T41, T43, T47, T50 | its run: `codesign --verify --strict` passes on the app and on its copy in the image; the `.icns` holds 16 to 1024 px; the image verifies and mounts with the app beside Applications; LaunchServices opens the app as `com.zacharyzhang.elyterminal`; for v0.1.1 the Release workflow (run 959) ran it on `macos-signing`: notarization Accepted, the ticket stapled, and on this Mac the downloaded image and its app pass `codesign --verify --strict` under the Developer ID and Gatekeeper's `accepted, source=Notarized Developer ID`; the owner opened it; for v0.1.2 the Release workflow (run 1001) ran it again: the downloaded image's stapled ticket validates and Gatekeeper accepts it as `Notarized Developer ID`, and its app, version 0.1.2, passes `codesign --verify --strict --deep` under the Developer ID with the hardened runtime and is accepted the same way; for v0.1.3 the Release workflow (run 1157) ran it again, and its image and app, version 0.1.3, check the same way, as does the image rebuilt for T43 (run 1181); for v0.1.4 the Release workflow (run 1216) ran it again, and its image and app, version 0.1.4, for macOS 11.0 or later, check the same way, as does the image rebuilt for T50 (run 1256); without the key the script stops before building | Pass |
|
| `scripts/mac-app.sh` builds the app, signed ad hoc, or for a release with Developer ID and notarized, and its disk image | T24, T26, T31, T41, T43, T47, T50, T54 | its run: `codesign --verify --strict` passes on the app and on its copy in the image; the `.icns` holds 16 to 1024 px; the image verifies and mounts with the app beside Applications; LaunchServices opens the app as `com.zacharyzhang.elyterminal`; for v0.1.1 the Release workflow (run 959) ran it on `macos-signing`: notarization Accepted, the ticket stapled, and on this Mac the downloaded image and its app pass `codesign --verify --strict` under the Developer ID and Gatekeeper's `accepted, source=Notarized Developer ID`; the owner opened it; for v0.1.2 the Release workflow (run 1001) ran it again: the downloaded image's stapled ticket validates and Gatekeeper accepts it as `Notarized Developer ID`, and its app, version 0.1.2, passes `codesign --verify --strict --deep` under the Developer ID with the hardened runtime and is accepted the same way; for v0.1.3 the Release workflow (run 1157) ran it again, and its image and app, version 0.1.3, check the same way, as does the image rebuilt for T43 (run 1181); for v0.1.4 the Release workflow (run 1216) ran it again, and its image and app, version 0.1.4, for macOS 11.0 or later, check the same way, as does the image rebuilt for T50 (run 1256); without the key the script stops before building; v0.1.5's disk image, from the Release workflow (run 1312) at `0119776`: its stapled ticket validates, Gatekeeper accepts the image and the app inside as notarized Developer ID, the app passes `codesign --verify --strict --deep` and reads 0.1.5, macOS 11.0, arm64, beside a link to Applications | Pass |
|
||||||
|
|
||||||
## 15. Release
|
## 15. Release
|
||||||
|
|
||||||
| PRD line | Task | Evidence | Status |
|
| PRD line | Task | Evidence | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| A Gitea release, `vVERSION`, with the macOS disk image, a Windows zip and a Linux tarball | T25, T26, T27, T31, T41, T43, T47, T50 | `v0.1.4` rebuilt for T50, at `a54f4ff`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1256), signed and notarized, each in place of the first build of its name, once CI run 1253 passed on macOS, Ubuntu and Windows on `a9be8ab`, which `a54f4ff` follows with `TASKS.md` and `tasks/reviews.md` alone; the tag moved from `2d9180e` to `a54f4ff`, and the release's target with it; the notes give the new SHA-256, add T48's fix and T49's motion, and say the builds were replaced; each file downloaded back anonymously matches its SHA-256, each binary holds the rig's log line and none the old faces' string, and the tag's source archive holds `src/pet/rig.rs` and no `src/pet/faces.rs`. The first `v0.1.4` builds, at `2d9180e`: the zip from its script on the owner's Windows VM, the tarball from its script on the owner's PC under Omarchy, the disk image from the Release workflow (run 1216), signed and notarized; published once CI run 1215 on that commit passed on macOS, Ubuntu and Windows; each file downloaded back anonymously matches the SHA-256 in the release notes, each binary holds Ely's log line, and the tag named `2d9180e`. `v0.1.3` rebuilt for T43, at `eb96109`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1181), signed and notarized, each in place of the first build of its name, once CI run 1180 on that commit passed on macOS, Ubuntu and Windows; the tag moved from `7569f00` to `eb96109`, and the release's target with it; the notes give the new SHA-256 and say the builds were replaced; each file downloaded back anonymously matches its SHA-256, each binary holds the moving marks' log line and none the old ring's element id, and the tag's source archive holds `src/sessions/orb.rs`. The first `v0.1.3` builds, at `7569f00`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1157), signed and notarized; published once CI run 1156 on that commit passed on macOS, Ubuntu and Windows; each file downloaded back anonymously matches the SHA-256 in the release notes, and the tag named `7569f00`. `v0.1.2` at `4364802`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1001), signed and notarized; published once CI run 1000 on that commit passed on macOS, Ubuntu and Windows; each file downloaded back anonymously matches the SHA-256 in the release notes, and the tag names `4364802`. `v0.1.1` at `43ea5b1`: the zip from its script on the owner's PC, the disk image from the Release workflow, signed and notarized; the Linux tarball, rebuilt for T27 from `aae7fba` by its script on the owner's PC under Omarchy, took the first one's place, and the notes say so; each downloaded back anonymously, once the repository was public, matches the SHA-256 in the release notes. `v0.1.0` at `0075667`, its three files built from that commit by their scripts and checked again: the disk image verifies and its app passes `codesign --verify --strict`; the zip's executable has the icon and no C runtime DLL; the tarball opens its window on Ubuntu 22.04; each file downloaded back matches the SHA-256 in the release notes | Pass |
|
| A Gitea release, `vVERSION`, with the macOS disk image, a Windows zip and a Linux tarball | T25, T26, T27, T31, T41, T43, T47, T50, T54 | `v0.1.5` at `0119776`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1312), signed and notarized; published once CI run 1311 on that commit passed on macOS, Ubuntu and Windows; each file downloaded back anonymously matches the SHA-256 in the release notes; each binary holds T51's `Ely moves to the pane's` and the failed glue's line, the disk image T53's `her panel drops its titled frame` and the tarball the monitors' line; the downloaded app, run with a HOME of its own, logged her panel going borderless, 0x8081 to 0x80, and Ely on the pane's top edge; the tag names `0119776`. `v0.1.4` rebuilt for T50, at `a54f4ff`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1256), signed and notarized, each in place of the first build of its name, once CI run 1253 passed on macOS, Ubuntu and Windows on `a9be8ab`, which `a54f4ff` follows with `TASKS.md` and `tasks/reviews.md` alone; the tag moved from `2d9180e` to `a54f4ff`, and the release's target with it; the notes give the new SHA-256, add T48's fix and T49's motion, and say the builds were replaced; each file downloaded back anonymously matches its SHA-256, each binary holds the rig's log line and none the old faces' string, and the tag's source archive holds `src/pet/rig.rs` and no `src/pet/faces.rs`. The first `v0.1.4` builds, at `2d9180e`: the zip from its script on the owner's Windows VM, the tarball from its script on the owner's PC under Omarchy, the disk image from the Release workflow (run 1216), signed and notarized; published once CI run 1215 on that commit passed on macOS, Ubuntu and Windows; each file downloaded back anonymously matches the SHA-256 in the release notes, each binary holds Ely's log line, and the tag named `2d9180e`. `v0.1.3` rebuilt for T43, at `eb96109`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1181), signed and notarized, each in place of the first build of its name, once CI run 1180 on that commit passed on macOS, Ubuntu and Windows; the tag moved from `7569f00` to `eb96109`, and the release's target with it; the notes give the new SHA-256 and say the builds were replaced; each file downloaded back anonymously matches its SHA-256, each binary holds the moving marks' log line and none the old ring's element id, and the tag's source archive holds `src/sessions/orb.rs`. The first `v0.1.3` builds, at `7569f00`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1157), signed and notarized; published once CI run 1156 on that commit passed on macOS, Ubuntu and Windows; each file downloaded back anonymously matches the SHA-256 in the release notes, and the tag named `7569f00`. `v0.1.2` at `4364802`: the zip and the tarball from their scripts on the owner's PC and its WSL, the disk image from the Release workflow (run 1001), signed and notarized; published once CI run 1000 on that commit passed on macOS, Ubuntu and Windows; each file downloaded back anonymously matches the SHA-256 in the release notes, and the tag names `4364802`. `v0.1.1` at `43ea5b1`: the zip from its script on the owner's PC, the disk image from the Release workflow, signed and notarized; the Linux tarball, rebuilt for T27 from `aae7fba` by its script on the owner's PC under Omarchy, took the first one's place, and the notes say so; each downloaded back anonymously, once the repository was public, matches the SHA-256 in the release notes. `v0.1.0` at `0075667`, its three files built from that commit by their scripts and checked again: the disk image verifies and its app passes `codesign --verify --strict`; the zip's executable has the icon and no C runtime DLL; the tarball opens its window on Ubuntu 22.04; each file downloaded back matches the SHA-256 in the release notes | Pass |
|
||||||
| The Windows executable carries the icon | T25, T31, T41, T47, T50 | its resources: icon group 1 with the seven images of `elyterminal.ico`, 16 to 256 px, and gpui's manifest (PerMonitorV2); a GUI program, importing no C runtime DLL; on a Windows 11 VM the first 0.1.4 executable, running, showed it in the taskbar; v0.1.2's, v0.1.3's and v0.1.4's executables the same, T50's rebuild with icon and manifest byte for byte the first's | Pass |
|
| The Windows executable carries the icon | T25, T31, T41, T47, T50, T54 | its resources: icon group 1 with the seven images of `elyterminal.ico`, 16 to 256 px, and gpui's manifest (PerMonitorV2); a GUI program, importing no C runtime DLL; on a Windows 11 VM the first 0.1.4 executable, running, showed it in the taskbar; v0.1.2's, v0.1.3's and v0.1.4's executables the same, T50's rebuild with icon and manifest byte for byte the first's; v0.1.5's executable a GUI program importing no C runtime DLL, its seven icon images, icon group 1 and manifest byte for byte 0.1.4's | Pass |
|
||||||
| The Linux tarball carries the icon and a desktop entry; the window names itself `com.zacharyzhang.elyterminal` | T25, T27, T41, T47, T50 | on Ubuntu 22.04: `desktop-file-validate` reports nothing, every library resolves, glibc 2.35; under Xvfb `WM_CLASS` reads `com.zacharyzhang.elyterminal` twice, and under headless sway the window's `app_id` reads the same; v0.1.2's passes the same checks on Ubuntu 22.04, run in the owner's WSL, `WM_CLASS` too; T27's rebuild passes the same checks on Ubuntu 22.04, whose llvmpipe (LLVM 15) leaves the window black in a capture, the first tarball's too, while Debian 13's paints it; v0.1.3's holds the same three files, its desktop entry names `com.zacharyzhang.elyterminal`, and its binary asks for glibc 2.35 at most; v0.1.4's the same, T50's rebuild too | Pass |
|
| The Linux tarball carries the icon and a desktop entry; the window names itself `com.zacharyzhang.elyterminal` | T25, T27, T41, T47, T50, T54 | on Ubuntu 22.04: `desktop-file-validate` reports nothing, every library resolves, glibc 2.35; under Xvfb `WM_CLASS` reads `com.zacharyzhang.elyterminal` twice, and under headless sway the window's `app_id` reads the same; v0.1.2's passes the same checks on Ubuntu 22.04, run in the owner's WSL, `WM_CLASS` too; T27's rebuild passes the same checks on Ubuntu 22.04, whose llvmpipe (LLVM 15) leaves the window black in a capture, the first tarball's too, while Debian 13's paints it; v0.1.3's holds the same three files, its desktop entry names `com.zacharyzhang.elyterminal`, and its binary asks for glibc 2.35 at most; v0.1.4's the same, T50's rebuild too; v0.1.5's holds the same three files, its desktop entry and icon byte for byte 0.1.4's, and its binary asks for glibc 2.35 at most | Pass |
|
||||||
| Each build comes from its script | T25, T27, T31, T41, T43, T47, T50 | `scripts/mac-app.sh` on macOS, `scripts/windows-zip.ps1` on the owner's Windows PC, `scripts/linux-tar.sh` in the owner's WSL, and for T27 on the owner's PC under Omarchy; v0.1.2's and v0.1.3's three the same way, and v0.1.3's again for T43, the disk image's on `macos-signing`; v0.1.4's zip on the owner's Windows VM, its tarball on the owner's PC under Omarchy, its disk image on `macos-signing`; T50's zip on the owner's PC, its tarball in the PC's WSL, its disk image on `macos-signing` | Pass |
|
| Each build comes from its script | T25, T27, T31, T41, T43, T47, T50, T54 | `scripts/mac-app.sh` on macOS, `scripts/windows-zip.ps1` on the owner's Windows PC, `scripts/linux-tar.sh` in the owner's WSL, and for T27 on the owner's PC under Omarchy; v0.1.2's and v0.1.3's three the same way, and v0.1.3's again for T43, the disk image's on `macos-signing`; v0.1.4's zip on the owner's Windows VM, its tarball on the owner's PC under Omarchy, its disk image on `macos-signing`; T50's zip on the owner's PC, its tarball in the PC's WSL, its disk image on `macos-signing`; v0.1.5's three the same way: `scripts/windows-zip.ps1` on the owner's PC, `scripts/linux-tar.sh` in its WSL, `scripts/mac-app.sh` in the Release workflow on `macos-signing` | Pass |
|
||||||
|
|
||||||
## CI notes
|
## CI notes
|
||||||
|
|
||||||
|
|||||||
@@ -268,3 +268,19 @@ Each task gets at most three codex rounds. Each round: the commit, the verdict,
|
|||||||
## T53
|
## T53
|
||||||
|
|
||||||
- Round 1, a6c044f: PASS. On macOS 27.2 the reviewer read her panel back as mask 0x80, no shadow, key only if needed, never key, a child of the main window, its frame and content rect alike; her window 112 by 124, inside the bottom edge under the menu bar; the top 32 rows and the outer columns of her picture clear; abc reached cat. A probe that kept setStyleMask: from taking logged 'pet: Ely goes, as her window cannot be kept there' and 'pet: her panel keeps style mask 0x8081', left no child window, and the main window still took abc. check.sh passed with 240 tests. This macOS draws no edge for a titled panel, so the hairline's going awaits the owner's macOS; Windows and Linux rest on the captures supplied.
|
- Round 1, a6c044f: PASS. On macOS 27.2 the reviewer read her panel back as mask 0x80, no shadow, key only if needed, never key, a child of the main window, its frame and content rect alike; her window 112 by 124, inside the bottom edge under the menu bar; the top 32 rows and the outer columns of her picture clear; abc reached cat. A probe that kept setStyleMask: from taking logged 'pet: Ely goes, as her window cannot be kept there' and 'pet: her panel keeps style mask 0x8081', left no child window, and the main window still took abc. check.sh passed with 240 tests. This macOS draws no edge for a titled panel, so the hairline's going awaits the owner's macOS; Windows and Linux rest on the captures supplied.
|
||||||
|
|
||||||
|
## T54
|
||||||
|
|
||||||
|
- Round 1, 0119776 and 15153b4 with v0.1.5 published: PASS. The reviewer found the bump only elyterminal's version; downloaded the three files anonymously, each matching the notes' SHA-256 and target/t54/anon byte for byte, and the tag's source archive the commit's 141 files; checked the disk image's ticket, strict signatures and Gatekeeper, the hardened arm64 app at 0.1.5 and macOS 11.0, the zip's GUI executable with its seven icon images, icon group 1, PerMonitorV2 manifest and no C runtime DLL, and the tarball's modes and glibc 2.35; found T51's and the failed glue's markers in all three binaries, T53's in the app and the monitors' in the tarball; and read CI run 1311 succeed on macOS, Ubuntu and Windows at 0119776 a minute before the release was published, and Release run 1312 on the signing runner. check.sh passed with 240 tests. Windows and Linux windows went unexercised.
|
||||||
|
|
||||||
|
## T55
|
||||||
|
|
||||||
|
- No review: the owner asked for 0.1.6 without one. Checked by scripts/check.sh (240 tests), Linux tests (247) and clippy, Windows gnu clippy, an xdotool drag under Xorg, a drag under Sway nested in Xorg, and the owner's own drags on macOS.
|
||||||
|
|
||||||
|
## T56
|
||||||
|
|
||||||
|
- No review: the owner asked for 0.1.6 without one. Checked by scripts/check.sh (243 tests), Linux tests (250) and clippy, Windows gnu clippy, and under Xorg's dummy screen the start's check, Check now, the switch, and a build named 0.1.4 that found 0.1.5 out and opened its page.
|
||||||
|
|
||||||
|
## T58
|
||||||
|
|
||||||
|
- No review: the owner asked for it at once, in 0.1.6, without one. Checked by scripts/check.sh (244 tests), Linux tests (251) and clippy, Windows gnu clippy, and under Xorg's dummy screen drags of the thumb through 500 lines, with mouse tracking on as well.
|
||||||
Reference in new issue
Block a user