Something went wrong. Try again.
A GPU terminal emulator for Wayland and Windows on libghostty-vt, drawing with Vulkan, running a program on a pty or a serial port.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! A request: one thing asked of the primary — a new window, a new tab, a//! split, or the focus on a pane already open — by the command line, a//! hand-off, a profile, Launch Command, or a split; and what harrier shows//! on its icon.//!//! Apart from the session bus and the Windows pipe that carry them between//! processes, so that the windows and the code that opens them need neither.
const std = @import("std");const Allocator = std.mem.Allocator;
/// The application ID: the bus name, the Wayland app_id, and the name of/// the desktop file, the icon and the systemd unit; on Windows, the window/// class, the name of the pipe, and the AppUserModelID (see/// `windows/app_user_model.zig`).pub const app_id = "dev.jcollie.harrier";
/// What harrier's icon shows of the programs' progress.pub const Launcher = struct { /// From 0 to 1, shown when `visible`. progress: f64 = 0, visible: bool = false, /// Something wants looking at: a program reported an error. urgent: bool = false,};
/// One thing asked of the primary: usually a window or tab to open, or/// with `focus_pane`, a pane to show. Every field is owned.pub const Request = struct { /// The program and its arguments; null for the user's shell. command: ?[]const []const u8 = null, /// Where the program starts; null for harrier's own directory. cwd: ?[]const u8 = null, /// The program's environment, `NAME=value`; null for harrier's own. environ: ?[]const []const u8 = null, /// `NAME=value`s set on top of that environment, whichever it is. environ_set: ?[]const []const u8 = null, /// A serial port to open instead of a program. serial_path: ?[]const u8 = null, baud: ?u32 = null, /// An xdg-activation token, which lets the new window take focus. activation_token: ?[]const u8 = null, /// The window's title until the program sets one; null to name the /// program. title: ?[]const u8 = null, /// Whether the window stays open once there is nothing left in it. hold: Hold = .never, /// The profile it runs, by name: its program or port, /// environment and colors. profile: ?[]const u8 = null, /// Whether the profile's program or port runs, rather than the shell with /// only its colors: a split from a serial console's pane cannot open /// the port again. profile_program: bool = true, /// Not a new window or tab at all: show the pane with this ID — its /// tab, and the focus in it — and raise its window: for a search /// result, a notification clicked, or an item in the jump list. focus_pane: ?u64 = null, /// The primary `focus_pane` is a pane of, by its process ID; null for /// whichever takes the request. Pane IDs start again in each primary, /// so one from a primary since gone names no pane, or another's. focus_primary: ?u32 = null, /// A tab in the window most recently opened or focused, rather than a /// window of its own; a window when there is none. new_tab: bool = false, /// That window brought forward even with no activation token: for a /// profile picked from GNOME Shell's search, whose click went to the /// Shell and left harrier nothing to activate with. raise: bool = false,
pub const Hold = enum { /// Close with the program, and if it cannot be started, open no /// window: the caller says why. never, /// Close with the program, but if it cannot be started, open the /// window anyway to say why: for a launch with no one watching a /// terminal for an error. failure, /// Stay open after the program exits, saying how it did, until a key /// is pressed. always, /// Like `failure`, and stay open as `always` does when the program /// exits with an error or is killed, or the port goes away: an ssh /// that could not reach its host says why, rather than its window /// vanishing. unsuccessful, };
/// `r`, when it asks for a profile by name alone — from the command /// line, or a hand-off — titled and held as the profile is when /// opened from the profile selector. Its title is the profile's name, /// borrowed: the copy is not to be freed. pub fn asProfile(r: Request) Request { var out = r; if (out.profile) |e| if (out.title == null) { out.title = e; out.hold = .unsuccessful; }; return out; }
pub fn deinit(r: *Request, gpa: Allocator) void { if (r.command) |c| freeStrings(gpa, c); if (r.cwd) |c| gpa.free(c); if (r.environ) |e| freeStrings(gpa, e); if (r.environ_set) |e| freeStrings(gpa, e); if (r.serial_path) |s| gpa.free(s); if (r.activation_token) |t| gpa.free(t); if (r.title) |t| gpa.free(t); if (r.profile) |e| gpa.free(e); r.* = undefined; }};
/// A pane as `--focus-pane` names it: `primary:id`, the primary's process/// ID and the pane's.pub const PaneRef = struct { primary: u32, id: u64,
pub fn parse(s: []const u8) ?PaneRef { const colon = std.mem.findScalar(u8, s, ':') orelse return null; return .{ .primary = std.fmt.parseInt(u32, s[0..colon], 10) catch return null, .id = std.fmt.parseInt(u64, s[colon + 1 ..], 10) catch return null, }; }
pub fn format(r: PaneRef, w: *std.Io.Writer) std.Io.Writer.Error!void { try w.print("{d}:{d}", .{ r.primary, r.id }); }};
test "a pane named on the command line" { try std.testing.expectEqual(PaneRef{ .primary = 4242, .id = 7 }, PaneRef.parse("4242:7").?); try std.testing.expect(PaneRef.parse("7") == null); try std.testing.expect(PaneRef.parse("x:7") == null); try std.testing.expect(PaneRef.parse("4242:") == null); var buf: [32]u8 = undefined; try std.testing.expectEqualStrings("4242:7", try std.fmt.bufPrint(&buf, "{f}", .{PaneRef{ .primary = 4242, .id = 7 }}));}
pub fn freeStrings(gpa: Allocator, list: []const []const u8) void { for (list) |s| gpa.free(s); gpa.free(list);}
pub fn dupeStrings(gpa: Allocator, list: []const []const u8) Allocator.Error![]const []const u8 { const out = try gpa.alloc([]const u8, list.len); var n: usize = 0; errdefer { for (out[0..n]) |s| gpa.free(s); gpa.free(out); } for (list, out) |s, *o| { o.* = try gpa.dupe(u8, s); n += 1; } return out;}