summaryrefslogtreecommitdiff
path: root/src/macos.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/macos.zig')
-rw-r--r--src/macos.zig140
1 files changed, 58 insertions, 82 deletions
diff --git a/src/macos.zig b/src/macos.zig
index d6ceb535..b472e6cc 100644
--- a/src/macos.zig
+++ b/src/macos.zig
@@ -12,6 +12,7 @@ const colors = @import("colors.zig");
const look = @import("look.zig");
const message = pardes.Messages.Message;
const layout = @import("layout.zig");
+const animation = @import("animation.zig");
const file_watch = @import("file_watch.zig");
const image = if (pardes.pdf_enabled) @import("image.zig") else struct {};
const host_io = @import("host_io.zig");
@@ -72,8 +73,8 @@ pub const Scene = extern struct {
frame: u32 = 0,
};
-pub const PanelBox = layout.Box;
-pub const PanelTrack = layout.Track;
+pub const PanelBox = animation.Box;
+pub const PanelTrack = animation.Track;
pub const Image = extern struct {
serial: u32,
@@ -111,13 +112,11 @@ const cell_flag_default: u8 = 1;
const cell_flag_tagline: u8 = 2;
const cell_flag_hover: u8 = 4;
const scene_flag_crt: u32 = 1 << 0;
-const scene_flag_ripple: u32 = 1 << 1;
-const scene_flag_glitch: u32 = 1 << 2;
const scene_frame_hz: u32 = 60;
const scene_wrap_ns: u64 = 4096 * std.time.ns_per_s;
-const max_tick_catch_up_ns: u64 = 4 * pardes.layout.Animation.frame_ns;
+const max_tick_catch_up_ns: u64 = 4 * pardes.animation.frame_ns;
const theme_watch_pane: u8 = @intCast(pardes.MAX_PANES);
const watch_slot_count = pardes.MAX_PANES + 1;
@@ -132,31 +131,22 @@ const Pty = struct {
};
/// A watched child exited: wake the host, which takes it (`takeExits`).
+/// Called on a watcher thread holding host_io's exit lock, which
+/// pardes_deinit takes to clear the wake: safe only because the runtime's
+/// wakeup never waits for the main thread (AppDelegate's is a
+/// DispatchQueue.main.async); one that did would deadlock the quit.
fn wakeForExit(ctx: ?*anyopaque) void {
const st: *State = @ptrCast(@alignCast(ctx orelse return));
wake(st);
}
-/// Each watched child that exited: reaped, the core told, and its pty
-/// closed if its end of file came first.
-fn takeExits(st: *State) bool {
- var did = false;
- while (host_io.takeExited()) |pid| for (&st.ptys) |*slot| {
- const pt = if (slot.*) |*pt| pt else continue;
- if (pt.pid != pid or pt.cmd.exited) continue;
- pt.cmd.exit((host_io.reapExited(pid) orelse break).status);
- pt.pid = 0; // reaped: no signal or retire may reach whoever gets it next
- break;
- };
- for (&st.ptys, 0..) |*slot, id| {
- const pt = if (slot.*) |*pt| pt else continue;
- if (!pt.cmd.due()) continue;
- pt.cmd.told = true;
- st.core.update(.{ .exited = .{ .pane = @intCast(id), .status = pt.cmd.status } });
- if (pt.cmd.eof) reap(st, @intCast(id));
- did = true;
- }
- return did;
+/// The command panes' exits, told once their output is in (host_io).
+fn takeExits(st: *State) void {
+ host_io.takeExits(st.core, &st.ptys, st, closeWatched);
+}
+
+fn closeWatched(st: *State, id: usize) void {
+ reap(st, @intCast(id));
}
const WatchedFile = struct {
@@ -882,16 +872,16 @@ export fn pardes_should_quit() bool {
return st.core.quit;
}
-fn encodeSceneEffects(effects: layout.SceneEffect) u32 {
+// The Metal shell keeps its own CRT kernel: the GUI's bundled post pass, as
+// a flag.
+fn encodeSceneEffects(post: *const pardes.config.Runtime.Post) u32 {
var flags: u32 = 0;
- if (effects.crt) flags |= scene_flag_crt;
- if (effects.ripple) flags |= scene_flag_ripple;
- if (effects.glitch) flags |= scene_flag_glitch;
+ if (post.has(.crt)) flags |= scene_flag_crt;
return flags;
}
fn currentSceneFlags(st: *const State) u32 {
- return encodeSceneEffects(st.core.settings.scene_effects);
+ return encodeSceneEffects(&st.core.settings.post);
}
fn advanceSceneClock(st: *State, elapsed_ns: u64) void {
@@ -902,18 +892,18 @@ const TickSpend = struct { elapsed_ns: u64, steps: u32, bank_ns: u64 };
fn spendTickTime(previous_ns: u64, now_ns: u64, bank_ns: u64) TickSpend {
const measured = if (previous_ns == 0 or now_ns <= previous_ns)
- pardes.layout.Animation.frame_ns
+ pardes.animation.frame_ns
else
now_ns - previous_ns;
const elapsed = @min(measured, max_tick_catch_up_ns);
var bank = bank_ns +| elapsed;
var steps: u32 = 0;
- while (bank >= pardes.layout.Animation.frame_ns) : (steps += 1) bank -= pardes.layout.Animation.frame_ns;
+ while (bank >= pardes.animation.frame_ns) : (steps += 1) bank -= pardes.animation.frame_ns;
return .{ .elapsed_ns = elapsed, .steps = steps, .bank_ns = bank };
}
test "the animation clock spends real time, not callbacks" {
- const frame = pardes.layout.Animation.frame_ns;
+ const frame = pardes.animation.frame_ns;
const expectEqual = std.testing.expectEqual;
const first = spendTickTime(0, 999 * std.time.ns_per_s, 0);
@@ -1302,7 +1292,9 @@ export fn pardes_watch_changed(pane: u8, generation: u32) void {
fn drainInbox(st: *State) bool {
var batch = st.inbox.take(st.io);
- var did = takeExits(st) or batch.len > 0;
+ // After this batch's output, which an exit is told behind.
+ defer takeExits(st);
+ var did = batch.len > 0;
for (batch.slice()) |msg| {
defer msg.free(st.gpa);
switch (msg) {
@@ -1318,9 +1310,7 @@ fn drainInbox(st: *State) bool {
// closing it would hang up one that runs on without it.
// Its exit, if it came first, is told now its output is in.
if (pt.cmd.watched) {
- pt.cmd.eof = true;
- st.core.update(.{ .eof = .{ .pane = e.pane } });
- _ = takeExits(st);
+ host_io.commandEof(st.core, &st.ptys, e.pane, st, closeWatched);
continue;
}
// Unwatched, a command's exit is read here, as its end.
@@ -1913,25 +1903,22 @@ fn clearFrame(st: *State) void {
st.panel_diff_len = 0;
}
-fn blankTagCells(cells: []Cell, cols: u16, rows: u16, source: []const pardes.Cell, layers: []const pardes.TagLayer) void {
+fn blankTagCells(cells: []Cell, cols: u16, rows: u16, source: []const pardes.Cell, layers: []const pardes.Layer) void {
for (layers) |layer| {
- if (layer.cols == 0 or layer.viewport.y >= rows or layer.viewport.x >= cols) continue;
+ if (layer.rows == 0 or layer.viewport.y >= rows or layer.viewport.x >= cols) continue;
const start = @as(usize, layer.viewport.y) * cols + layer.viewport.x;
const count = @min(layer.viewport.w, cols - layer.viewport.x);
- for (cells[start..][0..count], source[start..][0..count]) |*cell, was| {
- // The tag text is the layer's to draw, but a look-hover on a tag
- // word is a canonical fact: carry the hover bit through so the
- // glass overlay still finds the affordance rect.
- cell.* = encodeCell(.{});
- cell.flags |= @as(u8, @intFromBool(was.hover)) * cell_flag_hover;
- }
+ // The tag text is the layer's to draw. (The look-hover's glass
+ // rect went with the core's hover bit, stage 8's follow-up.)
+ _ = source;
+ for (cells[start..][0..count]) |*cell| cell.* = encodeCell(.{});
}
}
fn tagCursorCovered(core: *const pardes.Pardes) bool {
const cursor = core.surface.cursor orelse return false;
for (core.surface.tag_layers) |layer| {
- if (layer.cols != 0 and cursor.y == layer.viewport.y and cursor.x >= layer.viewport.x and
+ if (layer.rows != 0 and cursor.y == layer.viewport.y and cursor.x >= layer.viewport.x and
cursor.x - layer.viewport.x < layer.viewport.w) return true;
}
return false;
@@ -2173,7 +2160,7 @@ export fn pardes_tag_layer_value(index: u32, field: u32) u32 {
5 => layer.viewport.y,
6 => layer.viewport.w,
7 => layer.viewport.h,
- 8 => if (layer.cursor) |c| c.col else 65535,
+ 8 => if (layer.cursor) |c| c.x else 65535,
9 => if (layer.cursor) |c| @intFromBool(c.bar) else 0,
10 => @as(u32, layer.bg[0]) << 16 | @as(u32, layer.bg[1]) << 8 | layer.bg[2],
11 => @intFromBool(layer.kind == .pane and st.core.settings.tag_bottom),
@@ -2191,7 +2178,7 @@ export fn pardes_tag_layer_cells(index: u32) ?[*]const Cell {
const st = &(state orelse return null);
if (index >= pardes.MAX_TAG_LAYERS) return null;
const layer = &st.core.surface.tag_layers[index];
- if (layer.cols == 0) return null;
+ if (layer.rows == 0) return null;
const cells = &st.tag_cells[index];
if (cells.len != layer.cells.len) cells.* = st.gpa.realloc(cells.*, layer.cells.len) catch return null;
for (layer.cells, cells.*) |source, *out| out.* = encodeCell(source);
@@ -2460,7 +2447,7 @@ fn spawnShell(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
.pid = child.pid,
.gen = gen,
.reader = .{ .any_future = null, .result = {} },
- .cmd = .{ .watched = (if (core.panes[pane]) |pn| pn.command != null else false) and host_io.watchExit(child.pid) },
+ .cmd = .{ .watched = (if (core.panes[pane]) |pn| pn.command != null else false) and host_io.watchExit(child.pid), .fd = child.file.handle },
};
var lbuf: [1024]u8 = undefined;
if (host_io.shellCwd(child.pid, &lbuf)) |wd| core.setCwd(pane, wd);
@@ -2628,7 +2615,7 @@ fn encodeCell(cell: pardes.Cell) Cell {
.fg = encodeColor(cell.style.fg),
.bg = encodeColor(cell.style.bg),
.attrs = encodeAttrs(cell.style),
- .flags = encodeCellFlags(cell.default, cell.style.font_role, cell.hover),
+ .flags = encodeCellFlags(cell.default, cell.style.font_role, false),
.len = if (cell.default) 1 else cell.len,
};
if (cell.default)
@@ -2897,23 +2884,21 @@ test "pardes.h matches the Zig boundary" {
try expectEqual(@as(u8, c.PARDES_CELL_HOVER), cell_flag_hover);
try expectEqual(@as(u16, c.PARDES_TOPBAR_H), pardes.TOPBAR_H);
try expectEqual(@as(u32, c.PARDES_SCENE_CRT), scene_flag_crt);
- try expectEqual(@as(u32, c.PARDES_SCENE_RIPPLE), scene_flag_ripple);
- try expectEqual(@as(u32, c.PARDES_SCENE_GLITCH), scene_flag_glitch);
- try expectEqual(@as(u8, c.PARDES_PANEL_OPENING), @intFromEnum(layout.Phase.opening));
- try expectEqual(@as(u8, c.PARDES_PANEL_MOVING), @intFromEnum(layout.Phase.moving));
- try expectEqual(@as(u8, c.PARDES_PANEL_CLOSING), @intFromEnum(layout.Phase.closing));
- try expectEqual(@as(u8, c.PARDES_PANEL_OFF), @intFromEnum(layout.Transition.off));
- try expectEqual(@as(u8, c.PARDES_PANEL_SLIDE), @intFromEnum(layout.Transition.slide));
- try expectEqual(@as(u8, c.PARDES_PANEL_ZOOM), @intFromEnum(layout.Transition.zoom));
- try expectEqual(@as(u8, c.PARDES_PANEL_DISSOLVE), @intFromEnum(layout.Transition.dissolve));
- try expectEqual(@as(u8, c.PARDES_PANEL_ASCII), @intFromEnum(layout.Transition.ascii));
- try expectEqual(@as(u8, c.PARDES_PANEL_VERTICAL), @intFromEnum(layout.Transition.vertical));
- try expectEqual(@as(u8, c.PARDES_PANEL_EDGES), @intFromEnum(layout.Transition.edges));
- try expectEqual(@as(u8, c.PARDES_PANEL_FALL), @intFromEnum(layout.Transition.fall));
- try expectEqual(@as(u8, c.PARDES_PANEL_WAVE), @intFromEnum(layout.Transition.wave));
- try expectEqual(@as(u8, c.PARDES_PANEL_CURTAIN), @intFromEnum(layout.Transition.curtain));
- try expectEqual(@as(u8, c.PARDES_PANEL_SCRAMBLE), @intFromEnum(layout.Transition.scramble));
- try expectEqual(@as(u8, c.PARDES_PANEL_TYPEWRITER), @intFromEnum(layout.Transition.typewriter));
+ try expectEqual(@as(u8, c.PARDES_PANEL_OPENING), @intFromEnum(animation.Phase.opening));
+ try expectEqual(@as(u8, c.PARDES_PANEL_MOVING), @intFromEnum(animation.Phase.moving));
+ try expectEqual(@as(u8, c.PARDES_PANEL_CLOSING), @intFromEnum(animation.Phase.closing));
+ try expectEqual(@as(u8, c.PARDES_PANEL_OFF), @intFromEnum(animation.Transition.off));
+ try expectEqual(@as(u8, c.PARDES_PANEL_SLIDE), @intFromEnum(animation.Transition.slide));
+ try expectEqual(@as(u8, c.PARDES_PANEL_ZOOM), @intFromEnum(animation.Transition.zoom));
+ try expectEqual(@as(u8, c.PARDES_PANEL_DISSOLVE), @intFromEnum(animation.Transition.dissolve));
+ try expectEqual(@as(u8, c.PARDES_PANEL_ASCII), @intFromEnum(animation.Transition.ascii));
+ try expectEqual(@as(u8, c.PARDES_PANEL_VERTICAL), @intFromEnum(animation.Transition.vertical));
+ try expectEqual(@as(u8, c.PARDES_PANEL_EDGES), @intFromEnum(animation.Transition.edges));
+ try expectEqual(@as(u8, c.PARDES_PANEL_FALL), @intFromEnum(animation.Transition.fall));
+ try expectEqual(@as(u8, c.PARDES_PANEL_WAVE), @intFromEnum(animation.Transition.wave));
+ try expectEqual(@as(u8, c.PARDES_PANEL_CURTAIN), @intFromEnum(animation.Transition.curtain));
+ try expectEqual(@as(u8, c.PARDES_PANEL_SCRAMBLE), @intFromEnum(animation.Transition.scramble));
+ try expectEqual(@as(u8, c.PARDES_PANEL_TYPEWRITER), @intFromEnum(animation.Transition.typewriter));
try expectEqual(@as(u21, c.PARDES_KEY_ENTER), pardes.Key.enter);
try expectEqual(@as(u21, c.PARDES_KEY_ESCAPE), pardes.Key.escape);
@@ -2969,14 +2954,10 @@ test "pardes.h matches the Zig boundary" {
test "scene effect flags and display clock are compact and independent" {
const expectEqual = std.testing.expectEqual;
- try expectEqual(@as(u32, 0), encodeSceneEffects(.{}));
- try expectEqual(scene_flag_crt, encodeSceneEffects(.{ .crt = true }));
- try expectEqual(scene_flag_ripple, encodeSceneEffects(.{ .ripple = true }));
- try expectEqual(scene_flag_glitch, encodeSceneEffects(.{ .glitch = true }));
- try expectEqual(
- scene_flag_crt | scene_flag_ripple | scene_flag_glitch,
- encodeSceneEffects(.{ .crt = true, .ripple = true, .glitch = true }),
- );
+ var post: pardes.config.Runtime.Post = .{};
+ try expectEqual(@as(u32, 0), encodeSceneEffects(&post));
+ _ = post.set(.crt, "", true);
+ try expectEqual(scene_flag_crt, encodeSceneEffects(&post));
var st: State = undefined;
st.scene_ns = scene_wrap_ns - (std.time.ns_per_ms * 5);
@@ -3164,7 +3145,7 @@ test "mac tag layer ABI preserves logical capacity and physical grip" {
logical[11].text[0] = 'Z';
logical[11].len = 1;
logical[11].default = false;
- core.surface.tag_layers[0] = .{ .kind = .pane, .id = 1, .serial = 42, .viewport = .{ .x = 2, .y = 1, .w = 6, .h = 1 }, .cols = 12, .cells = &logical, .bg = .{ 0x12, 0x34, 0x56 }, .cursor = .{ .col = 11, .bar = true } };
+ core.surface.tag_layers[0] = .{ .kind = .pane, .id = 1, .serial = 42, .viewport = .{ .x = 2, .y = 1, .w = 6, .h = 1 }, .cols = 12, .rows = 1, .cells = &logical, .bg = .{ 0x12, 0x34, 0x56 }, .cursor = .{ .x = 11, .y = 0, .bar = true } };
defer core.surface.tag_layers[0] = .{};
try std.testing.expectEqual(@as(u32, 12), pardes_tag_layer_value(0, 0));
try std.testing.expectEqual(@as(u32, 0x123456), pardes_tag_layer_value(0, 10));
@@ -3185,16 +3166,11 @@ test "mac tag layer ABI preserves logical capacity and physical grip" {
try std.testing.expectEqual(@as(u32, 11), pardes_tag_layer_value(0, 8));
try std.testing.expectEqual(@as(u8, 'Z'), pardes_tag_layer_cells(0).?[11].text[0]);
var canonical: [24]Cell = @splat(encodeCell(.{ .text = .{'G'} ++ .{0} ** 6, .len = 1, .default = false }));
- // A look-hover under the layer is a canonical fact and survives the blank;
- // the glyph it marks does not, because the layer redraws that text itself.
var source: [24]pardes.Cell = @splat(.{});
- source[11].hover = true;
blankTagCells(&canonical, 8, 3, &source, &core.surface.tag_layers);
try std.testing.expectEqual(@as(u8, 'G'), canonical[9].text[0]);
try std.testing.expectEqual(@as(u8, 'G'), canonical[18].text[0]);
try std.testing.expectEqual(encodeCell(.{}).len, canonical[10].len);
- try std.testing.expectEqual(@as(u8, 0), canonical[10].flags & cell_flag_hover);
- try std.testing.expectEqual(cell_flag_hover, canonical[11].flags & cell_flag_hover);
}
test "mac window blur builtin reaches the ABI without changing opacity" {