Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ https://github.com/user-attachments/assets/a4e28a63-557a-44f3-9bae-47b2fd0a5dc6

### Terminal Essentials
- Smooth animated transitions for grid expansion, contraction, and reflow (cells and borders move/resize together)
- Keyboard navigation: ⌘+Return to expand, ⌘1–⌘0 to switch grid slots, ⌘N to add, ⌘W to close (restarts if it's the only terminal), ⌘/ for shortcuts
- Keyboard navigation: ⌘+Return to expand, ⌘1–⌘0 to switch grid slots, ⌘N to add, ⌘W to close a terminal (restarts if it's the only terminal), ⌘/ for shortcuts; quit with ⌘Q or the window close button
- Per-cell cwd bar in grid view with reserved space so terminal content stays visible
- Scrollback with trackpad/wheel support and grid indicator when scrolled
- OSC 8 hyperlink support (Cmd+Click to open)
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ font_scale = 1.0 # Font scale in grid view (0.5-3.0, default: 1.0)

The grid size is dynamic and adjusts automatically based on the number of terminals:
- Press `Cmd+N` to add a new terminal after the currently focused one — the grid expands to accommodate it
- Press `Cmd+W` to close a terminal — remaining terminals compact forward to fill gaps and the grid shrinks when possible; if it's the only terminal, it restarts in place
- Press `Cmd+W` to close a terminal — remaining terminals compact forward to fill gaps and the grid shrinks when possible; if it's the only terminal, it restarts in place (use `Cmd+Q` or the window close button to quit)
- When only one terminal is spawned, the view stays in full-screen mode
- Grid layout maintains `columns >= rows` (e.g., 1x1 → 2x1 → 2x2 → 3x2 → 3x3 → ...)
- Maximum grid size is 12×12 (144 terminals)
Expand Down
31 changes: 31 additions & 0 deletions src/app/runtime.zig
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ pub fn run() !void {
var ime_composition = input_text.ImeComposition{};
var last_focused_session: usize = anim_state.focused_session;
var relaunch_trace_frames: u8 = 0;
var window_close_suppress_countdown: u8 = 0;

const session_interaction_component = try ui_mod.SessionInteractionComponent.init(allocator, sessions, &font);
try ui.register(session_interaction_component.asComponent());
Expand Down Expand Up @@ -623,6 +624,17 @@ pub fn run() !void {
}
processed_event = true;
var scaled_event = layout.scaleEventToRender(&event, scale_x, scale_y);
if (builtin.os.tag == .macos and scaled_event.type == c.SDL_EVENT_KEY_DOWN) {
const key = scaled_event.key.key;
const mod = scaled_event.key.mod;
const has_gui = (mod & c.SDL_KMOD_GUI) != 0;
const has_blocking_mod = (mod & (c.SDL_KMOD_CTRL | c.SDL_KMOD_ALT)) != 0;
if (has_gui and !has_blocking_mod and key == c.SDLK_W) {
Comment thread
forketyfork marked this conversation as resolved.
// Use 2 frames to cover the delay between Cmd+W and SDL delivering a close request.
// A single frame is not always enough to suppress the close in the next loop.
window_close_suppress_countdown = 2;
}
}
const focused_has_foreground_process = foreground_cache.get(now, anim_state.focused_session, sessions);
const host_snapshot = ui_host.makeUiHost(
now,
Expand Down Expand Up @@ -653,6 +665,19 @@ pub fn run() !void {
running = false;
}
},
c.SDL_EVENT_WINDOW_CLOSE_REQUESTED => {
if (builtin.os.tag == .macos and window_close_suppress_countdown > 0) {
// Reset immediately so we only suppress this close request.
window_close_suppress_countdown = 0;
continue;
}
if (handleQuitRequest(sessions[0..], quit_confirm_component)) {
running = false;
}
},
c.SDL_EVENT_WINDOW_DESTROYED => {
running = false;
},
c.SDL_EVENT_WINDOW_MOVED => {
window_x = scaled_event.window.data1;
window_y = scaled_event.window.data2;
Expand Down Expand Up @@ -1290,6 +1315,8 @@ pub fn run() !void {
}
}

if (!running) break;

loop.run(.no_wait) catch |err| {
log.err("xev loop run failed: {}", .{err});
return err;
Expand Down Expand Up @@ -1837,6 +1864,10 @@ pub fn run() !void {
std.Thread.sleep(sleep_ns);
}
}

if (window_close_suppress_countdown > 0) {
window_close_suppress_countdown -= 1;
}
}

if (builtin.os.tag == .macos) {
Expand Down
2 changes: 2 additions & 0 deletions src/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub const SDL_EVENT_WINDOW_RESIZED = c_import.SDL_EVENT_WINDOW_RESIZED;
pub const SDL_EVENT_WINDOW_MOVED = c_import.SDL_EVENT_WINDOW_MOVED;
pub const SDL_EVENT_WINDOW_FOCUS_GAINED = c_import.SDL_EVENT_WINDOW_FOCUS_GAINED;
pub const SDL_EVENT_WINDOW_FOCUS_LOST = c_import.SDL_EVENT_WINDOW_FOCUS_LOST;
pub const SDL_EVENT_WINDOW_CLOSE_REQUESTED = c_import.SDL_EVENT_WINDOW_CLOSE_REQUESTED;
pub const SDL_EVENT_WINDOW_DESTROYED = c_import.SDL_EVENT_WINDOW_DESTROYED;
pub const SDL_EVENT_DROP_BEGIN = c_import.SDL_EVENT_DROP_BEGIN;
pub const SDL_EVENT_DROP_FILE = c_import.SDL_EVENT_DROP_FILE;
pub const SDL_EVENT_DROP_TEXT = c_import.SDL_EVENT_DROP_TEXT;
Expand Down