From 3345c2bb828f0d95722b127d5807aa099de3cb05 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 16:50:06 -0800 Subject: [PATCH 01/28] nix: add flatpak-builder --- nix/devshell.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/devshell.nix b/nix/devshell.nix index b2dc082fa..02ed75529 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -1,5 +1,6 @@ { mkShell, lib, stdenv +, flatpak-builder , fpm , gdb , glxinfo @@ -65,6 +66,7 @@ in mkShell rec { nativeBuildInputs = [ # For builds + flatpak-builder fpm llvmPackages_latest.llvm pkg-config From bfc4b821ff53437710a62243aad19c3d951c9fcf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 17:31:22 -0800 Subject: [PATCH 02/28] vendor: update linux aarch64 sdk --- vendor/mach-sdk/sdk-linux-aarch64 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mach-sdk/sdk-linux-aarch64 b/vendor/mach-sdk/sdk-linux-aarch64 index 8f6ddaf6c..a279b0a3e 160000 --- a/vendor/mach-sdk/sdk-linux-aarch64 +++ b/vendor/mach-sdk/sdk-linux-aarch64 @@ -1 +1 @@ -Subproject commit 8f6ddaf6cc25df02925ef78448d512c3184abc63 +Subproject commit a279b0a3ef2f103b308defcd7e1a32e20346f70b From f18789da8700547994eb111dbf51d2941748ef85 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 19:39:16 -0800 Subject: [PATCH 03/28] build: omit icon that is too large for freedesktop --- build.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/build.zig b/build.zig index 009ef5521..76e4d9b9c 100644 --- a/build.zig +++ b/build.zig @@ -159,7 +159,6 @@ pub fn build(b: *std.build.Builder) !void { b.installFile("images/icons/icon_32x32@2x@2x.png", "share/icons/hicolor/32x32@2/com.mitchellh.ghostty.png"); b.installFile("images/icons/icon_128x128@2x@2x.png", "share/icons/hicolor/128x128@2/com.mitchellh.ghostty.png"); b.installFile("images/icons/icon_256x256@2x@2x.png", "share/icons/hicolor/256x256@2/com.mitchellh.ghostty.png"); - b.installFile("images/icons/icon_512x512@2x@2x.png", "share/icons/hicolor/512x512@2/com.mitchellh.ghostty.png"); } // App (Mac) From 31540c24e7b7cb8c3195a51dd15b544e8033ca17 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 19:39:33 -0800 Subject: [PATCH 04/28] nix: need debugedit for flatpak-builder --- nix/devshell.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/devshell.nix b/nix/devshell.nix index 02ed75529..5b5db251f 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -1,5 +1,6 @@ { mkShell, lib, stdenv +, debugedit , flatpak-builder , fpm , gdb @@ -66,6 +67,7 @@ in mkShell rec { nativeBuildInputs = [ # For builds + debugedit flatpak-builder fpm llvmPackages_latest.llvm From ec956debb739550c7f73ce104be7c95d430f929b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 21:19:57 -0800 Subject: [PATCH 05/28] Command/Pty work better with Flatpak but not 100% yet --- dist/linux/app.desktop | 2 +- src/Command.zig | 1 + src/Pty.zig | 15 ++++++---- src/os/main.zig | 1 + src/termio/Exec.zig | 67 +++++++++++++++++++++++++++++++++--------- 5 files changed, 65 insertions(+), 21 deletions(-) diff --git a/dist/linux/app.desktop b/dist/linux/app.desktop index a06032d78..99a005750 100644 --- a/dist/linux/app.desktop +++ b/dist/linux/app.desktop @@ -2,7 +2,7 @@ Name=Ghostty Type=Application Comment=A terminal emulator -Exec=/usr/bin/ghostty +Exec=/app/bin/ghostty Icon=com.mitchellh.ghostty Keywords=terminal;tty;pty; StartupNotify=true diff --git a/src/Command.zig b/src/Command.zig index ba11090d5..f5607b200 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -24,6 +24,7 @@ const Command = @This(); const std = @import("std"); const builtin = @import("builtin"); const TempDir = @import("TempDir.zig"); +const internal_os = @import("os/main.zig"); const mem = std.mem; const os = std.os; const debug = std.debug; diff --git a/src/Pty.zig b/src/Pty.zig index c61c9feb9..230aa67ff 100644 --- a/src/Pty.zig +++ b/src/Pty.zig @@ -104,12 +104,15 @@ pub fn childPreExec(self: Pty) !void { if (setsid() < 0) return error.ProcessGroupFailed; // Set controlling terminal - switch (std.os.system.getErrno(c.ioctl(self.slave, TIOCSCTTY, @as(c_ulong, 0)))) { - .SUCCESS => {}, - else => |err| { - log.err("error setting controlling terminal errno={}", .{err}); - return error.SetControllingTerminalFailed; - }, + // TODO: maybe + if (!@import("os/main.zig").isFlatpak()) { + switch (std.os.system.getErrno(c.ioctl(self.slave, TIOCSCTTY, @as(c_ulong, 0)))) { + .SUCCESS => {}, + else => |err| { + log.err("error setting controlling terminal errno={}", .{err}); + return error.SetControllingTerminalFailed; + }, + } } // Can close master/slave pair now diff --git a/src/os/main.zig b/src/os/main.zig index 132764dd7..070e7be1d 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -2,6 +2,7 @@ //! system. pub usingnamespace @import("file.zig"); +pub usingnamespace @import("flatpak.zig"); pub usingnamespace @import("locale.zig"); pub usingnamespace @import("macos_version.zig"); pub usingnamespace @import("mouse.zig"); diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 86dcf71e8..1d3c1c195 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -6,6 +6,7 @@ const builtin = @import("builtin"); const build_config = @import("../build_config.zig"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const EnvMap = std.process.EnvMap; const termio = @import("../termio.zig"); const Command = @import("../Command.zig"); const Pty = @import("../Pty.zig"); @@ -17,6 +18,7 @@ const tracy = @import("tracy"); const trace = tracy.trace; const apprt = @import("../apprt.zig"); const fastmem = @import("../fastmem.zig"); +const internal_os = @import("../os/main.zig"); const log = std.log.scoped(.io_exec); @@ -90,7 +92,7 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec { } pub fn deinit(self: *Exec) void { - self.subprocess.deinit(self.alloc); + self.subprocess.deinit(); // Clean up our other members self.terminal.deinit(self.alloc); @@ -139,6 +141,7 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData { // Store our data so our callbacks can access it self.data = ev_data_ptr; + errdefer self.data = null; // Start our reader thread const read_thread = try std.Thread.spawn( @@ -319,10 +322,11 @@ fn ttyWrite( /// Subprocess manages the lifecycle of the shell subprocess. const Subprocess = struct { + arena: std.heap.ArenaAllocator, cwd: ?[]const u8, - env: std.process.EnvMap, + env: EnvMap, path: []const u8, - argv0_override: ?[]const u8, + args: [][]const u8, grid_size: renderer.GridSize, screen_size: renderer.ScreenSize, pty: ?Pty = null, @@ -330,11 +334,16 @@ const Subprocess = struct { /// Initialize the subprocess. This will NOT start it, this only sets /// up the internal state necessary to start it later. - pub fn init(alloc: Allocator, opts: termio.Options) !Subprocess { + pub fn init(gpa: Allocator, opts: termio.Options) !Subprocess { + // We have a lot of maybe-allocations that all share the same lifetime + // so use an arena so we don't end up in an accounting nightmare. + var arena = std.heap.ArenaAllocator.init(gpa); + errdefer arena.deinit(); + const alloc = arena.allocator(); + // Determine the path to the binary we're executing const path = (try Command.expandPath(alloc, opts.config.command orelse "sh")) orelse return error.CommandNotFound; - errdefer alloc.free(path); // On macOS, we launch the program as a login shell. This is a Mac-specific // behavior (see other terminals). Terminals in general should NOT be @@ -354,10 +363,12 @@ const Subprocess = struct { std.mem.copy(u8, argv0_buf[1..], argv0); break :argv0 argv0_buf; } else null; - errdefer if (argv0_override) |buf| alloc.free(buf); // Set our env vars - var env = try std.process.getEnvMap(alloc); + var env = if (internal_os.isFlatpak()) + EnvMap.init(alloc) + else + try std.process.getEnvMap(alloc); errdefer env.deinit(); try env.put("TERM", "xterm-256color"); try env.put("COLORTERM", "truecolor"); @@ -377,22 +388,47 @@ const Subprocess = struct { } } + // If we're NOT in a flatpak (usually!), then we just exec the + // process directly. If we are in a flatpak, we use flatpak-spawn + // to escape the sandbox. + const args = if (!internal_os.isFlatpak()) &[_][]const u8{ + argv0_override orelse path, + } else args: { + var args = try std.ArrayList([]const u8).initCapacity(alloc, 8); + defer args.deinit(); + + try args.append("/usr/bin/flatpak-spawn"); + try args.append("--host"); + try args.append("--watch-bus"); + var env_it = env.iterator(); + while (env_it.next()) |pair| { + try args.append(try std.fmt.allocPrint( + alloc, + "--env={s}={s}", + .{ pair.key_ptr.*, pair.value_ptr.* }, + )); + } + try args.append(path); + + break :args try args.toOwnedSlice(); + }; + return .{ + .arena = arena, .env = env, .cwd = opts.config.@"working-directory", - .path = path, - .argv0_override = argv0_override, + .path = if (internal_os.isFlatpak()) "/usr/bin/flatpak-spawn" else path, + .args = args, .grid_size = opts.grid_size, .screen_size = opts.screen_size, }; } /// Clean up the subprocess. This will stop the subprocess if it is started. - pub fn deinit(self: *Subprocess, alloc: Allocator) void { + pub fn deinit(self: *Subprocess) void { self.stop(); self.env.deinit(); - alloc.free(self.path); - if (self.argv0_override) |v| alloc.free(v); + self.arena.deinit(); self.* = undefined; } @@ -414,12 +450,15 @@ const Subprocess = struct { self.pty = null; } - const args = &[_][]const u8{self.argv0_override orelse self.path}; + log.debug("starting command path={s} args={s}", .{ + self.path, + self.args, + }); // Build our subcommand var cmd: Command = .{ .path = self.path, - .args = args, + .args = self.args, .env = &self.env, .cwd = self.cwd, .stdin = .{ .handle = pty.slave }, From 64ef2dc05116b72096ec27dc65a58c1ee2d0d45f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 21:20:28 -0800 Subject: [PATCH 06/28] add flatpak builder yml --- com.mitchellh.ghostty.yml | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 com.mitchellh.ghostty.yml diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml new file mode 100644 index 000000000..fc43cbf61 --- /dev/null +++ b/com.mitchellh.ghostty.yml @@ -0,0 +1,54 @@ +app-id: com.mitchellh.ghostty +runtime: org.freedesktop.Platform +runtime-version: '22.08' +sdk: org.freedesktop.Sdk +platform-extensions: + - org.freedesktop.Platform.GL.default +default-branch: tip +command: ghostty +build-options: + append-path: /app/tmp/zig +cleanup: + - /app/tmp/zig +finish-args: + # 3D rendering + - --device=dri + # Windowing + - --share=ipc + - --socket=x11 + - --socket=wayland + # Files (we are a terminal so we need all of them) + - --filesystem=host + # So we can escape the sandbox + - --talk-name=org.freedesktop.Flatpak +modules: + # Note: this should be kept in sync with our flake.nix. Over time this + # should stabilize to being a release version and not a nightly. + - name: zig + buildsystem: simple + build-commands: + - mkdir -p /app/tmp/zig + - cp -r ./* /app/tmp/zig + sources: + - type: archive + url: https://ziglang.org/builds/zig-linux-x86_64-0.11.0-dev.1650+5e7b09ce9.tar.xz + sha256: 8b77a475d3d124f0d4a4c4d4e2756f4a5317838272c08341325f196e8d539747 + only-arches: + - x86_64 + - type: archive + url: https://ziglang.org/builds/zig-linux-aarch64-0.11.0-dev.1650+5e7b09ce9.tar.xz + sha256: 104c2370c6eba25164ede3fefaf133ed650cca951f2e473bd533cfb0660c4e23 + only-arches: + - aarch64 + + - name: ghostty + buildsystem: simple + build-commands: + - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Dcpu=baseline --prefix /app + sources: + - type: dir + path: . + skip: + - .flatpak-builder + - zig-cache + - zig-out From 0a041957f1b8bcafdd3ea783738fc25a576c190f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 21:20:48 -0800 Subject: [PATCH 07/28] os: add flatpak detection --- src/os/flatpak.zig | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/os/flatpak.zig diff --git a/src/os/flatpak.zig b/src/os/flatpak.zig new file mode 100644 index 000000000..afd6c33de --- /dev/null +++ b/src/os/flatpak.zig @@ -0,0 +1,9 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +/// Returns true if we're running in a Flatpak environment. +pub fn isFlatpak() bool { + // If we're not on Linux then we'll make this comptime false. + if (comptime builtin.os.tag != .linux) return false; + return if (std.fs.accessAbsolute("/.flatpak-info", .{})) true else |_| false; +} From 3c81287ffdfe222066b79644c31131043606f489 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 21:56:41 -0800 Subject: [PATCH 08/28] ignore flatpak builder files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2fef0f117..0a3b50879 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .direnv/ +.flatpak-builder/ zig-cache/ zig-out/ /result* From 6b23dbb1696f2b124766487ea38ccfaaae6e2f17 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 21:56:51 -0800 Subject: [PATCH 09/28] flatpak: use host-spawn to find default shell --- com.mitchellh.ghostty.yml | 20 ++++++++++++++++++++ src/config.zig | 18 +++++++++++------- src/passwd.zig | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml index fc43cbf61..ba86b30e4 100644 --- a/com.mitchellh.ghostty.yml +++ b/com.mitchellh.ghostty.yml @@ -41,6 +41,26 @@ modules: only-arches: - aarch64 + # We use this to get a proper PTY on our host spawn. We should eventually + # replace this with using dbus directly. + - name: host-spawn + buildsystem: simple + build-commands: + - mkdir -p /app/bin + - mv ./host-spawn-* /app/bin/host-spawn + - chmod +x /app/bin/host-spawn + sources: + - type: file + url: https://github.com/1player/host-spawn/releases/download/1.4.1/host-spawn-x86_64 + sha256: d81bb6125ec73a9a2e26266c48787367fbcb665d67c2e7fb42217f0981106cf7 + only-arches: + - x86_64 + - type: file + url: https://github.com/1player/host-spawn/releases/download/1.4.1/host-spawn-aarch64 + sha256: 29bff846d72e37093b3fdf7859bae16addd64acc98087f8c059548df3c2273c4 + only-arches: + - aarch64 + - name: ghostty buildsystem: simple build-commands: diff --git a/src/config.zig b/src/config.zig index 324bc713c..b0e13e1e8 100644 --- a/src/config.zig +++ b/src/config.zig @@ -344,14 +344,18 @@ pub const Config = struct { if (self.command == null or wd_home) command: { const alloc = self._arena.?.allocator(); - // First look up the command using the SHELL env var. - if (std.process.getEnvVarOwned(alloc, "SHELL")) |value| { - log.debug("default shell source=env value={s}", .{value}); - self.command = value; + // We don't do this in flatpak because SHELL in Flatpak is + // always set to /bin/sh + if (!internal_os.isFlatpak()) { + // First look up the command using the SHELL env var. + if (std.process.getEnvVarOwned(alloc, "SHELL")) |value| { + log.debug("default shell source=env value={s}", .{value}); + self.command = value; - // If we don't need the working directory, then we can exit now. - if (!wd_home) break :command; - } else |_| {} + // If we don't need the working directory, then we can exit now. + if (!wd_home) break :command; + } else |_| {} + } // We need the passwd entry for the remainder const pw = try passwd.get(alloc); diff --git a/src/passwd.zig b/src/passwd.zig index 0a97676cb..d44ca8900 100644 --- a/src/passwd.zig +++ b/src/passwd.zig @@ -2,6 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; +const internal_os = @import("os/main.zig"); const log = std.log.scoped(.passwd); @@ -45,6 +46,39 @@ pub fn get(alloc: Allocator) !Entry { var result: Entry = .{}; + // If we're in flatpak then our entry is always empty so we grab it + // by shelling out to the host. note that we do HAVE an entry in the + // sandbox but only the username is correct. + // + // Note: we wrap our getent call in a /bin/sh login shell because + // some operating systems (NixOS tested) don't set the PATH for various + // utilities properly until we get a login shell. + if (internal_os.isFlatpak()) { + log.info("flatpak detected, will use host-spawn to get our entry", .{}); + const exec = try std.ChildProcess.exec(.{ + .allocator = alloc, + .argv = &[_][]const u8{ + "/app/bin/host-spawn", + "-pty", + "/bin/sh", + "-l", + "-c", + try std.fmt.allocPrint( + alloc, + "getent passwd {s}", + .{std.mem.sliceTo(pw.pw_name, 0)}, + ), + }, + }); + if (exec.term == .Exited) { + // Shell and home are the last two entries + var it = std.mem.splitBackwards(u8, exec.stdout, ":"); + result.shell = it.next() orelse null; + result.home = it.next() orelse null; + return result; + } + } + if (pw.pw_shell) |ptr| { const source = std.mem.sliceTo(ptr, 0); const sh = try alloc.alloc(u8, source.len); From f012d31ed5b15ca3342ae99987c81e52481da9bd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 22:25:17 -0800 Subject: [PATCH 10/28] passwd entry in flatpak needs to trim newlines --- src/passwd.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passwd.zig b/src/passwd.zig index d44ca8900..33c93e5f0 100644 --- a/src/passwd.zig +++ b/src/passwd.zig @@ -72,7 +72,7 @@ pub fn get(alloc: Allocator) !Entry { }); if (exec.term == .Exited) { // Shell and home are the last two entries - var it = std.mem.splitBackwards(u8, exec.stdout, ":"); + var it = std.mem.splitBackwards(u8, std.mem.trimRight(u8, exec.stdout, " \r\n"), ":"); result.shell = it.next() orelse null; result.home = it.next() orelse null; return result; From fc3802e632425a943bc9dc7ef9fb7eac2a864bd9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 22:36:20 -0800 Subject: [PATCH 11/28] termio: use host-spawn for pty --- src/Pty.zig | 15 ++++++--------- src/renderer/Thread.zig | 3 ++- src/termio/Exec.zig | 28 ++++++++++++---------------- src/termio/Thread.zig | 3 ++- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/Pty.zig b/src/Pty.zig index 230aa67ff..c61c9feb9 100644 --- a/src/Pty.zig +++ b/src/Pty.zig @@ -104,15 +104,12 @@ pub fn childPreExec(self: Pty) !void { if (setsid() < 0) return error.ProcessGroupFailed; // Set controlling terminal - // TODO: maybe - if (!@import("os/main.zig").isFlatpak()) { - switch (std.os.system.getErrno(c.ioctl(self.slave, TIOCSCTTY, @as(c_ulong, 0)))) { - .SUCCESS => {}, - else => |err| { - log.err("error setting controlling terminal errno={}", .{err}); - return error.SetControllingTerminalFailed; - }, - } + switch (std.os.system.getErrno(c.ioctl(self.slave, TIOCSCTTY, @as(c_ulong, 0)))) { + .SUCCESS => {}, + else => |err| { + log.err("error setting controlling terminal errno={}", .{err}); + return error.SetControllingTerminalFailed; + }, } // Can close master/slave pair now diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 12424272d..099c71cdf 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -136,6 +136,7 @@ pub fn threadMain(self: *Thread) void { } fn threadMain_(self: *Thread) !void { + defer log.debug("renderer thread exited", .{}); tracy.setThreadName("renderer"); // Run our thread start/end callbacks. This is important because some @@ -185,7 +186,7 @@ fn threadMain_(self: *Thread) !void { // Run log.debug("starting renderer thread", .{}); - defer log.debug("exiting renderer thread", .{}); + defer log.debug("starting renderer thread shutdown", .{}); _ = try self.loop.run(.until_done); } diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 1d3c1c195..61eb98d63 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -365,10 +365,7 @@ const Subprocess = struct { } else null; // Set our env vars - var env = if (internal_os.isFlatpak()) - EnvMap.init(alloc) - else - try std.process.getEnvMap(alloc); + var env = try std.process.getEnvMap(alloc); errdefer env.deinit(); try env.put("TERM", "xterm-256color"); try env.put("COLORTERM", "truecolor"); @@ -397,17 +394,16 @@ const Subprocess = struct { var args = try std.ArrayList([]const u8).initCapacity(alloc, 8); defer args.deinit(); - try args.append("/usr/bin/flatpak-spawn"); - try args.append("--host"); - try args.append("--watch-bus"); - var env_it = env.iterator(); - while (env_it.next()) |pair| { - try args.append(try std.fmt.allocPrint( - alloc, - "--env={s}={s}", - .{ pair.key_ptr.*, pair.value_ptr.* }, - )); - } + // We use host-spawn so the PTY is setup properly. + // future: rewrite host-spawn into pure Zig using dbus and + // we can run this directly. + try args.append("/app/bin/host-spawn"); + try args.append("-pty"); + try args.append("-env"); + try args.append("TERM,COLORTERM"); + try args.append("/bin/sh"); + try args.append("-l"); + try args.append("-c"); try args.append(path); break :args try args.toOwnedSlice(); @@ -417,7 +413,7 @@ const Subprocess = struct { .arena = arena, .env = env, .cwd = opts.config.@"working-directory", - .path = if (internal_os.isFlatpak()) "/usr/bin/flatpak-spawn" else path, + .path = if (internal_os.isFlatpak()) args[0] else path, .args = args, .grid_size = opts.grid_size, .screen_size = opts.screen_size, diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig index 48dd4a35e..aa2a9c568 100644 --- a/src/termio/Thread.zig +++ b/src/termio/Thread.zig @@ -95,6 +95,7 @@ pub fn threadMain(self: *Thread) void { } fn threadMain_(self: *Thread) !void { + defer log.debug("IO thread exited", .{}); tracy.setThreadName("pty io"); // Run our thread start/end callbacks. This allows the implementation @@ -109,7 +110,7 @@ fn threadMain_(self: *Thread) !void { // Run log.debug("starting IO thread", .{}); - defer log.debug("exiting IO thread", .{}); + defer log.debug("starting IO thread shutdown", .{}); try self.loop.run(.until_done); } From 5b9bc9abdb1acf346f5abf0fc3f6bdc6ae2f9298 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Feb 2023 23:08:04 -0800 Subject: [PATCH 12/28] flatpak: build GTK version --- build.zig | 3 +++ com.mitchellh.ghostty.yml | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 76e4d9b9c..9f0b44766 100644 --- a/build.zig +++ b/build.zig @@ -604,6 +604,9 @@ fn addDeps( try glfw.link(b, step, glfw_opts); step.linkSystemLibrary("gtk4"); + + // This is for Flatpak + step.addLibraryPath("/usr/lib/aarch64-linux-gnu"); }, } } diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml index ba86b30e4..f1a2c1f67 100644 --- a/com.mitchellh.ghostty.yml +++ b/com.mitchellh.ghostty.yml @@ -1,7 +1,7 @@ app-id: com.mitchellh.ghostty -runtime: org.freedesktop.Platform -runtime-version: '22.08' -sdk: org.freedesktop.Sdk +runtime: org.gnome.Platform +runtime-version: '43' +sdk: org.gnome.Sdk platform-extensions: - org.freedesktop.Platform.GL.default default-branch: tip @@ -64,7 +64,7 @@ modules: - name: ghostty buildsystem: simple build-commands: - - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Dcpu=baseline --prefix /app + - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Doptimize=ReleaseFast -Dcpu=baseline -Dapp-runtime=gtk --prefix /app sources: - type: dir path: . From e227f4d5efcaeb8d8ef1ecf42ef01e3ddfdcc339 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 26 Feb 2023 08:45:24 -0800 Subject: [PATCH 13/28] nix: flatpak stuff should go into linux section --- nix/devshell.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nix/devshell.nix b/nix/devshell.nix index 5b5db251f..abea909d6 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -67,8 +67,6 @@ in mkShell rec { nativeBuildInputs = [ # For builds - debugedit - flatpak-builder fpm llvmPackages_latest.llvm pkg-config @@ -90,6 +88,10 @@ in mkShell rec { wabt wasmtime ] ++ lib.optionals stdenv.isLinux [ + # Flatpak builds + debugedit + flatpak-builder + valgrind wraptest ]; From f89d202b0dd4423ab3562a5f98783a45c389d78c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 26 Feb 2023 10:28:54 -0800 Subject: [PATCH 14/28] flatpak.HostCommand wip --- com.mitchellh.ghostty.yml | 2 +- src/os/flatpak.zig | 189 ++++++++++++++++++++++++++++++++++++++ src/passwd.zig | 15 +++ 3 files changed, 205 insertions(+), 1 deletion(-) diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml index f1a2c1f67..b369c0807 100644 --- a/com.mitchellh.ghostty.yml +++ b/com.mitchellh.ghostty.yml @@ -64,7 +64,7 @@ modules: - name: ghostty buildsystem: simple build-commands: - - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Doptimize=ReleaseFast -Dcpu=baseline -Dapp-runtime=gtk --prefix /app + - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Dcpu=baseline -Dapp-runtime=gtk --prefix /app sources: - type: dir path: . diff --git a/src/os/flatpak.zig b/src/os/flatpak.zig index afd6c33de..b75fd1960 100644 --- a/src/os/flatpak.zig +++ b/src/os/flatpak.zig @@ -1,9 +1,198 @@ const std = @import("std"); +const assert = std.debug.assert; const builtin = @import("builtin"); +const log = std.log.scoped(.flatpak); + /// Returns true if we're running in a Flatpak environment. pub fn isFlatpak() bool { // If we're not on Linux then we'll make this comptime false. if (comptime builtin.os.tag != .linux) return false; return if (std.fs.accessAbsolute("/.flatpak-info", .{})) true else |_| false; } + +/// A struct to help execute commands on the host via the +/// org.freedesktop.Flatpak.Development DBus module. +pub const FlatpakHostCommand = struct { + const Allocator = std.mem.Allocator; + const fd_t = std.os.fd_t; + const EnvMap = std.process.EnvMap; + const c = @cImport({ + @cInclude("gio/gio.h"); + @cInclude("gio/gunixfdlist.h"); + }); + + /// Argv are the arguments to call on the host with argv[0] being + /// the command to execute. + argv: []const []const u8, + + /// The cwd for the new process. If this is not set then it will use + /// the current cwd of the calling process. + cwd: ?[:0]const u8 = null, + + /// Environment variables for the child process. If this is null, this + /// does not send any environment variables. + env: ?*const EnvMap = null, + + /// File descriptors to send to the child process. + stdin: StdIo = .{ .devnull = null }, + stdout: StdIo = .{ .devnull = null }, + stderr: StdIo = .{ .devnull = null }, + + /// Process ID is set after spawn is called. + pid: ?c_int = null, + + pub const StdIo = union(enum) { + // Drop the input/output to /dev/null. The value should be NULL + // and the spawn functil will take care of initializing and closing. + devnull: ?fd_t, + + /// Setup the stdio to be a pipe. The value should be set to NULL + /// to start and the spawn function will take care of initializing + /// the pipe. + pipe: ?fd_t, + + fn setup(self: *StdIo) !fd_t { + switch (self.*) { + .devnull => |*v| { + assert(v.* == null); + + // Slight optimization potential: we can open /dev/null + // exactly once but its so rare that we use it that I + // didn't care to optimize this at this time. + const fd = std.os.openZ("/dev/null", std.os.O.RDWR, 0) catch |err| switch (err) { + error.PathAlreadyExists => unreachable, + error.NoSpaceLeft => unreachable, + error.FileTooBig => unreachable, + error.DeviceBusy => unreachable, + error.FileLocksNotSupported => unreachable, + error.BadPathName => unreachable, // Windows-only + error.InvalidHandle => unreachable, // WASI-only + error.WouldBlock => unreachable, + else => |e| return e, + }; + + v.* = fd; + return fd; + }, + + .pipe => unreachable, + } + } + }; + + /// Spawn the command. This will start the host command and set the + /// pid field on success. This will not wait for completion. + pub fn spawn(self: *FlatpakHostCommand, alloc: Allocator) !void { + var arena_allocator = std.heap.ArenaAllocator.init(alloc); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + var err: [*c]c.GError = null; + const bus = c.g_bus_get_sync(c.G_BUS_TYPE_SESSION, null, &err) orelse { + log.warn("spawn error getting bus: {s}", .{err.*.message}); + return error.FlatpakDbusFailed; + }; + defer c.g_object_unref(bus); + + // Our list of file descriptors that we need to send to the process. + const fd_list = c.g_unix_fd_list_new(); + defer c.g_object_unref(fd_list); + + // Build our arguments for the file descriptors. + const fd_builder = c.g_variant_builder_new(c.G_VARIANT_TYPE("a{uh}")); + defer c.g_variant_builder_unref(fd_builder); + try setupFd(&self.stdin, 0, fd_list, fd_builder); + try setupFd(&self.stdout, 1, fd_list, fd_builder); + try setupFd(&self.stderr, 2, fd_list, fd_builder); + + // Build our env vars + const env_builder = c.g_variant_builder_new(c.G_VARIANT_TYPE("a{ss}")); + defer c.g_variant_builder_unref(env_builder); + if (self.env) |env| { + var it = env.iterator(); + while (it.next()) |pair| { + const key = try arena.dupeZ(u8, pair.key_ptr.*); + const value = try arena.dupeZ(u8, pair.value_ptr.*); + c.g_variant_builder_add(env_builder, "{ss}", key.ptr, value.ptr); + } + } + + // Build our args + const args_ptr = c.g_ptr_array_new(); + { + errdefer _ = c.g_ptr_array_free(args_ptr, 1); + for (self.argv) |arg| { + const argZ = try arena.dupeZ(u8, arg); + c.g_ptr_array_add(args_ptr, argZ.ptr); + } + } + const args = c.g_ptr_array_free(args_ptr, 0); + defer c.g_free(@ptrCast(?*anyopaque, args)); + + // Get the cwd in case we don't have ours set. A small optimization + // would be to do this only if we need it but this isn't a + // common code path. + const g_cwd = c.g_get_current_dir(); + defer c.g_free(g_cwd); + + // The params for our RPC call + const params = c.g_variant_new( + "(^ay^aay@a{uh}@a{ss}u)", + if (self.cwd) |cwd| cwd.ptr else g_cwd, + args, + c.g_variant_builder_end(fd_builder), + c.g_variant_builder_end(env_builder), + @as(c_int, 0), + ); + _ = c.g_variant_ref_sink(params); // take ownership + defer c.g_variant_unref(params); + + // Go! + const reply = c.g_dbus_connection_call_with_unix_fd_list_sync( + bus, + "org.freedesktop.Flatpak", + "/org/freedesktop/Flatpak/Development", + "org.freedesktop.Flatpak.Development", + "HostCommand", + params, + c.G_VARIANT_TYPE("(u)"), + c.G_DBUS_CALL_FLAGS_NONE, + c.G_MAXINT, + fd_list, + null, + null, + &err, + ) orelse { + log.warn("Flatpak.HostCommand failed: {s}", .{err.*.message}); + return error.FlatpakHostCommandFailed; + }; + defer c.g_variant_unref(reply); + + var pid: c_int = 0; + c.g_variant_get(reply, "(u)", &pid); + log.debug("HostCommand started pid={}", .{pid}); + + self.pid = pid; + } + + /// Helper to setup our io fd and add it to the necessary fd + /// list for sending to the child and parameter list for calling our + /// API. + fn setupFd( + stdio: *StdIo, + child_fd: fd_t, + list: *c.GUnixFDList, + builder: *c.GVariantBuilder, + ) !void { + const fd = try stdio.setup(); + + var err: [*c]c.GError = null; + if (c.g_unix_fd_list_append(list, fd, &err) < 0) { + log.warn("error adding fd: {s}", .{err.*.message}); + return error.FlatpakFdFailed; + } + + c.g_variant_builder_add(builder, "{uh}", child_fd, fd); + } +}; diff --git a/src/passwd.zig b/src/passwd.zig index 33c93e5f0..542945f69 100644 --- a/src/passwd.zig +++ b/src/passwd.zig @@ -55,6 +55,21 @@ pub fn get(alloc: Allocator) !Entry { // utilities properly until we get a login shell. if (internal_os.isFlatpak()) { log.info("flatpak detected, will use host-spawn to get our entry", .{}); + var cmd: internal_os.FlatpakHostCommand = .{ + .argv = &[_][]const u8{ + "/bin/sh", + "-l", + "-c", + try std.fmt.allocPrint( + alloc, + "getent passwd {s}", + .{std.mem.sliceTo(pw.pw_name, 0)}, + ), + }, + }; + try cmd.spawn(alloc); + if (true) @panic("END"); + const exec = try std.ChildProcess.exec(.{ .allocator = alloc, .argv = &[_][]const u8{ From f64d871847c7f5a2fb23b12aef577c6e15bdcaf3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 10:18:56 -0800 Subject: [PATCH 15/28] os: FlatpakHostCommand uses thread with its own event loop --- src/Pty.zig | 11 +- src/os/flatpak.zig | 289 ++++++++++++++++++++++++++++++++++----------- src/passwd.zig | 10 +- 3 files changed, 233 insertions(+), 77 deletions(-) diff --git a/src/Pty.zig b/src/Pty.zig index c61c9feb9..f9d419ec0 100644 --- a/src/Pty.zig +++ b/src/Pty.zig @@ -28,12 +28,13 @@ const TIOCSWINSZ = if (builtin.os.tag == .macos) 2148037735 else c.TIOCSWINSZ; const TIOCGWINSZ = if (builtin.os.tag == .macos) 1074295912 else c.TIOCGWINSZ; /// Redeclare this winsize struct so we can just use a Zig struct. This -/// layout should be correct on all tested platforms. +/// layout should be correct on all tested platforms. The defaults on this +/// are some reasonable screen size but you should probably not use them. const winsize = extern struct { - ws_row: u16, - ws_col: u16, - ws_xpixel: u16, - ws_ypixel: u16, + ws_row: u16 = 100, + ws_col: u16 = 80, + ws_xpixel: u16 = 800, + ws_ypixel: u16 = 600, }; pub extern "c" fn setsid() std.c.pid_t; diff --git a/src/os/flatpak.zig b/src/os/flatpak.zig index b75fd1960..58141baf3 100644 --- a/src/os/flatpak.zig +++ b/src/os/flatpak.zig @@ -12,7 +12,14 @@ pub fn isFlatpak() bool { } /// A struct to help execute commands on the host via the -/// org.freedesktop.Flatpak.Development DBus module. +/// org.freedesktop.Flatpak.Development DBus module. This uses GIO/GLib +/// under the hood. +/// +/// This always spawns its own thread and maintains its own GLib event loop. +/// This makes it easy for the command to behave synchronously similar to +/// std.process.ChildProcess. +/// +/// Requires GIO, GLib to be available and linked. pub const FlatpakHostCommand = struct { const Allocator = std.mem.Allocator; const fd_t = std.os.fd_t; @@ -34,77 +41,169 @@ pub const FlatpakHostCommand = struct { /// does not send any environment variables. env: ?*const EnvMap = null, - /// File descriptors to send to the child process. - stdin: StdIo = .{ .devnull = null }, - stdout: StdIo = .{ .devnull = null }, - stderr: StdIo = .{ .devnull = null }, + /// File descriptors to send to the child process. It is up to the + /// caller to create the file descriptors and set them up. + stdin: fd_t, + stdout: fd_t, + stderr: fd_t, - /// Process ID is set after spawn is called. - pid: ?c_int = null, + /// State of the process. This is updated by the dedicated thread it + /// runs in and is protected by the given lock and condition variable. + state: State = .{ .init = {} }, + state_mutex: std.Thread.Mutex = .{}, + state_cv: std.Thread.Condition = .{}, - pub const StdIo = union(enum) { - // Drop the input/output to /dev/null. The value should be NULL - // and the spawn functil will take care of initializing and closing. - devnull: ?fd_t, + /// State the process is in. This can't be inspected directly, you + /// must use getters on the struct to get access. + const State = union(enum) { + /// Initial state + init: void, - /// Setup the stdio to be a pipe. The value should be set to NULL - /// to start and the spawn function will take care of initializing - /// the pipe. - pipe: ?fd_t, + /// Error starting. The error message is only available via logs. + /// (This isn't a fundamental limitation, just didn't need the + /// error message yet) + err: void, - fn setup(self: *StdIo) !fd_t { - switch (self.*) { - .devnull => |*v| { - assert(v.* == null); + /// Process started with the given pid on the host. + started: struct { + pid: c_int, + subscription: c.guint, + loop: *c.GMainLoop, + }, - // Slight optimization potential: we can open /dev/null - // exactly once but its so rare that we use it that I - // didn't care to optimize this at this time. - const fd = std.os.openZ("/dev/null", std.os.O.RDWR, 0) catch |err| switch (err) { - error.PathAlreadyExists => unreachable, - error.NoSpaceLeft => unreachable, - error.FileTooBig => unreachable, - error.DeviceBusy => unreachable, - error.FileLocksNotSupported => unreachable, - error.BadPathName => unreachable, // Windows-only - error.InvalidHandle => unreachable, // WASI-only - error.WouldBlock => unreachable, - else => |e| return e, - }; - - v.* = fd; - return fd; - }, - - .pipe => unreachable, - } - } + /// Process exited + exited: struct { + pid: c_int, + status: u8, + }, }; - /// Spawn the command. This will start the host command and set the + /// Execute the command and wait for it to finish. This will automatically + /// read all the data from the provided stdout/stderr fds and return them + /// in the result. + /// + /// This runs the exec in a dedicated thread with a dedicated GLib + /// event loop so that it can run synchronously. + pub fn exec(self: *FlatpakHostCommand, alloc: Allocator) !void { + const thread = try std.Thread.spawn(.{}, threadMain, .{ self, alloc }); + thread.join(); + } + + /// Spawn the command. This will start the host command. On return, + /// the pid will be available. This must only be called with the + /// state in "init". + /// + /// Precondition: The self pointer MUST be stable. + pub fn spawn(self: *FlatpakHostCommand, alloc: Allocator) !c_int { + const thread = try std.Thread.spawn(.{}, threadMain, .{ self, alloc }); + thread.setName("flatpak-host-command") catch {}; + + // Wait for the process to start or error. + self.state_mutex.lock(); + defer self.state_mutex.unlock(); + while (self.state == .init) self.state_cv.wait(&self.state_mutex); + + return switch (self.state) { + .init => unreachable, + .err => error.FlatpakSpawnFail, + .started => |v| v.pid, + .exited => |v| v.pid, + }; + } + + /// Wait for the process to end and return the exit status. This + /// can only be called ONCE. Once this returns, the state is reset. + pub fn wait(self: *FlatpakHostCommand) !u8 { + self.state_mutex.lock(); + defer self.state_mutex.unlock(); + + while (true) { + switch (self.state) { + .init => return error.FlatpakCommandNotStarted, + .err => return error.FlatpakSpawnFail, + .started => {}, + .exited => |v| { + self.state = .{ .init = {} }; + self.state_cv.broadcast(); + return v.status; + }, + } + + self.state_cv.wait(&self.state_mutex); + } + } + + fn threadMain(self: *FlatpakHostCommand, alloc: Allocator) void { + // Create a new thread-local context so that all our sources go + // to this context and we can run our loop correctly. + const ctx = c.g_main_context_new(); + defer c.g_main_context_unref(ctx); + c.g_main_context_push_thread_default(ctx); + defer c.g_main_context_pop_thread_default(ctx); + + // Get our loop for the current thread + const loop = c.g_main_loop_new(ctx, 1).?; + defer c.g_main_loop_unref(loop); + + // Get our bus connection. This has to remain active until we exit + // the thread otherwise our signals won't be called. + var g_err: [*c]c.GError = null; + const bus = c.g_bus_get_sync(c.G_BUS_TYPE_SESSION, null, &g_err) orelse { + log.warn("spawn error getting bus: {s}", .{g_err.*.message}); + self.updateState(.{ .err = {} }); + return; + }; + defer c.g_object_unref(bus); + + // Spawn the command first. This will setup all our IO. + self.start(alloc, bus, loop) catch |err| { + log.warn("error starting host command: {}", .{err}); + self.updateState(.{ .err = {} }); + return; + }; + + // Run the event loop. It quits in the exit callback. + c.g_main_loop_run(loop); + } + + /// Start the command. This will start the host command and set the /// pid field on success. This will not wait for completion. - pub fn spawn(self: *FlatpakHostCommand, alloc: Allocator) !void { + /// + /// Once this is called, the self pointer MUST remain stable. This + /// requirement is due to using GLib under the covers with callbacks. + fn start( + self: *FlatpakHostCommand, + alloc: Allocator, + bus: *c.GDBusConnection, + loop: *c.GMainLoop, + ) !void { + var err: [*c]c.GError = null; var arena_allocator = std.heap.ArenaAllocator.init(alloc); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - var err: [*c]c.GError = null; - const bus = c.g_bus_get_sync(c.G_BUS_TYPE_SESSION, null, &err) orelse { - log.warn("spawn error getting bus: {s}", .{err.*.message}); - return error.FlatpakDbusFailed; - }; - defer c.g_object_unref(bus); - // Our list of file descriptors that we need to send to the process. const fd_list = c.g_unix_fd_list_new(); defer c.g_object_unref(fd_list); + if (c.g_unix_fd_list_append(fd_list, self.stdin, &err) < 0) { + log.warn("error adding fd: {s}", .{err.*.message}); + return error.FlatpakFdFailed; + } + if (c.g_unix_fd_list_append(fd_list, self.stdout, &err) < 0) { + log.warn("error adding fd: {s}", .{err.*.message}); + return error.FlatpakFdFailed; + } + if (c.g_unix_fd_list_append(fd_list, self.stderr, &err) < 0) { + log.warn("error adding fd: {s}", .{err.*.message}); + return error.FlatpakFdFailed; + } // Build our arguments for the file descriptors. const fd_builder = c.g_variant_builder_new(c.G_VARIANT_TYPE("a{uh}")); defer c.g_variant_builder_unref(fd_builder); - try setupFd(&self.stdin, 0, fd_list, fd_builder); - try setupFd(&self.stdout, 1, fd_list, fd_builder); - try setupFd(&self.stderr, 2, fd_list, fd_builder); + c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 0), self.stdin); + c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 1), self.stdout); + c.g_variant_builder_add(fd_builder, "{uh}", @as(c_int, 2), self.stderr); // Build our env vars const env_builder = c.g_variant_builder_new(c.G_VARIANT_TYPE("a{ss}")); @@ -148,6 +247,21 @@ pub const FlatpakHostCommand = struct { _ = c.g_variant_ref_sink(params); // take ownership defer c.g_variant_unref(params); + // Subscribe to exit notifications + const subscription_id = c.g_dbus_connection_signal_subscribe( + bus, + "org.freedesktop.Flatpak", + "org.freedesktop.Flatpak.Development", + "HostCommandExited", + "/org/freedesktop/Flatpak/Development", + null, + 0, + onExit, + self, + null, + ); + errdefer c.g_dbus_connection_signal_unsubscribe(bus, subscription_id); + // Go! const reply = c.g_dbus_connection_call_with_unix_fd_list_sync( bus, @@ -171,28 +285,61 @@ pub const FlatpakHostCommand = struct { var pid: c_int = 0; c.g_variant_get(reply, "(u)", &pid); - log.debug("HostCommand started pid={}", .{pid}); + log.debug("HostCommand started pid={} subscription={}", .{ + pid, + subscription_id, + }); - self.pid = pid; + self.updateState(.{ + .started = .{ + .pid = pid, + .subscription = subscription_id, + .loop = loop, + }, + }); } - /// Helper to setup our io fd and add it to the necessary fd - /// list for sending to the child and parameter list for calling our - /// API. - fn setupFd( - stdio: *StdIo, - child_fd: fd_t, - list: *c.GUnixFDList, - builder: *c.GVariantBuilder, - ) !void { - const fd = try stdio.setup(); + /// Helper to update the state and notify waiters via the cv. + fn updateState(self: *FlatpakHostCommand, state: State) void { + self.state_mutex.lock(); + defer self.state_mutex.unlock(); + defer self.state_cv.broadcast(); + self.state = state; + } - var err: [*c]c.GError = null; - if (c.g_unix_fd_list_append(list, fd, &err) < 0) { - log.warn("error adding fd: {s}", .{err.*.message}); - return error.FlatpakFdFailed; - } + fn onExit( + bus: ?*c.GDBusConnection, + _: [*c]const u8, + _: [*c]const u8, + _: [*c]const u8, + _: [*c]const u8, + params: ?*c.GVariant, + ud: ?*anyopaque, + ) callconv(.C) void { + const self = @ptrCast(*FlatpakHostCommand, @alignCast(@alignOf(FlatpakHostCommand), ud)); + const state = state: { + self.state_mutex.lock(); + defer self.state_mutex.unlock(); + break :state self.state.started; + }; - c.g_variant_builder_add(builder, "{uh}", child_fd, fd); + var pid: c_int = 0; + var exit_status: c_int = 0; + c.g_variant_get(params.?, "(uu)", &pid, &exit_status); + if (state.pid != pid) return; + + // Update our state + self.updateState(.{ + .exited = .{ + .pid = pid, + .status = std.math.cast(u8, exit_status) orelse 255, + }, + }); + + // We're done now, so we can unsubscribe + c.g_dbus_connection_signal_unsubscribe(bus.?, state.subscription); + + // We are also done with our loop so we can exit. + c.g_main_loop_quit(state.loop); } }; diff --git a/src/passwd.zig b/src/passwd.zig index 542945f69..8e834e20c 100644 --- a/src/passwd.zig +++ b/src/passwd.zig @@ -55,6 +55,10 @@ pub fn get(alloc: Allocator) !Entry { // utilities properly until we get a login shell. if (internal_os.isFlatpak()) { log.info("flatpak detected, will use host-spawn to get our entry", .{}); + + const Pty = @import("Pty.zig"); + var pty = try Pty.open(.{}); + defer pty.deinit(); var cmd: internal_os.FlatpakHostCommand = .{ .argv = &[_][]const u8{ "/bin/sh", @@ -66,8 +70,12 @@ pub fn get(alloc: Allocator) !Entry { .{std.mem.sliceTo(pw.pw_name, 0)}, ), }, + .stdin = pty.slave, + .stdout = pty.slave, + .stderr = pty.slave, }; - try cmd.spawn(alloc); + _ = try cmd.spawn(alloc); + _ = try cmd.wait(); if (true) @panic("END"); const exec = try std.ChildProcess.exec(.{ From 630374060d57bee17216e9a50be135485a930dbf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 11:02:59 -0800 Subject: [PATCH 16/28] passwd uses new FlatpakHostCommand --- src/os/flatpak.zig | 36 +++++++++++++------------ src/passwd.zig | 65 +++++++++++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/os/flatpak.zig b/src/os/flatpak.zig index 58141baf3..f41ea22de 100644 --- a/src/os/flatpak.zig +++ b/src/os/flatpak.zig @@ -19,6 +19,10 @@ pub fn isFlatpak() bool { /// This makes it easy for the command to behave synchronously similar to /// std.process.ChildProcess. /// +/// There are lots of chances for low-hanging improvements here (automatic +/// pipes, /dev/null, etc.) but this was purpose built for my needs so +/// it doesn't have all of those. +/// /// Requires GIO, GLib to be available and linked. pub const FlatpakHostCommand = struct { const Allocator = std.mem.Allocator; @@ -78,16 +82,13 @@ pub const FlatpakHostCommand = struct { }, }; - /// Execute the command and wait for it to finish. This will automatically - /// read all the data from the provided stdout/stderr fds and return them - /// in the result. - /// - /// This runs the exec in a dedicated thread with a dedicated GLib - /// event loop so that it can run synchronously. - pub fn exec(self: *FlatpakHostCommand, alloc: Allocator) !void { - const thread = try std.Thread.spawn(.{}, threadMain, .{ self, alloc }); - thread.join(); - } + /// Errors that are possible from us. + pub const Error = error{ + FlatpakMustBeStarted, + FlatpakSpawnFail, + FlatpakSetupFail, + FlatpakRPCFail, + }; /// Spawn the command. This will start the host command. On return, /// the pid will be available. This must only be called with the @@ -105,7 +106,7 @@ pub const FlatpakHostCommand = struct { return switch (self.state) { .init => unreachable, - .err => error.FlatpakSpawnFail, + .err => Error.FlatpakSpawnFail, .started => |v| v.pid, .exited => |v| v.pid, }; @@ -119,8 +120,8 @@ pub const FlatpakHostCommand = struct { while (true) { switch (self.state) { - .init => return error.FlatpakCommandNotStarted, - .err => return error.FlatpakSpawnFail, + .init => return Error.FlatpakMustBeStarted, + .err => return Error.FlatpakSpawnFail, .started => {}, .exited => |v| { self.state = .{ .init = {} }; @@ -187,15 +188,15 @@ pub const FlatpakHostCommand = struct { defer c.g_object_unref(fd_list); if (c.g_unix_fd_list_append(fd_list, self.stdin, &err) < 0) { log.warn("error adding fd: {s}", .{err.*.message}); - return error.FlatpakFdFailed; + return Error.FlatpakSetupFail; } if (c.g_unix_fd_list_append(fd_list, self.stdout, &err) < 0) { log.warn("error adding fd: {s}", .{err.*.message}); - return error.FlatpakFdFailed; + return Error.FlatpakSetupFail; } if (c.g_unix_fd_list_append(fd_list, self.stderr, &err) < 0) { log.warn("error adding fd: {s}", .{err.*.message}); - return error.FlatpakFdFailed; + return Error.FlatpakSetupFail; } // Build our arguments for the file descriptors. @@ -279,7 +280,7 @@ pub const FlatpakHostCommand = struct { &err, ) orelse { log.warn("Flatpak.HostCommand failed: {s}", .{err.*.message}); - return error.FlatpakHostCommandFailed; + return Error.FlatpakRPCFail; }; defer c.g_variant_unref(reply); @@ -335,6 +336,7 @@ pub const FlatpakHostCommand = struct { .status = std.math.cast(u8, exit_status) orelse 255, }, }); + log.debug("HostCommand exited pid={} status={}", .{ pid, exit_status }); // We're done now, so we can unsubscribe c.g_dbus_connection_signal_unsubscribe(bus.?, state.subscription); diff --git a/src/passwd.zig b/src/passwd.zig index 8e834e20c..f334341f5 100644 --- a/src/passwd.zig +++ b/src/passwd.zig @@ -49,13 +49,12 @@ pub fn get(alloc: Allocator) !Entry { // If we're in flatpak then our entry is always empty so we grab it // by shelling out to the host. note that we do HAVE an entry in the // sandbox but only the username is correct. - // - // Note: we wrap our getent call in a /bin/sh login shell because - // some operating systems (NixOS tested) don't set the PATH for various - // utilities properly until we get a login shell. if (internal_os.isFlatpak()) { log.info("flatpak detected, will use host-spawn to get our entry", .{}); + // Note: we wrap our getent call in a /bin/sh login shell because + // some operating systems (NixOS tested) don't set the PATH for various + // utilities properly until we get a login shell. const Pty = @import("Pty.zig"); var pty = try Pty.open(.{}); defer pty.deinit(); @@ -76,30 +75,42 @@ pub fn get(alloc: Allocator) !Entry { }; _ = try cmd.spawn(alloc); _ = try cmd.wait(); - if (true) @panic("END"); - const exec = try std.ChildProcess.exec(.{ - .allocator = alloc, - .argv = &[_][]const u8{ - "/app/bin/host-spawn", - "-pty", - "/bin/sh", - "-l", - "-c", - try std.fmt.allocPrint( - alloc, - "getent passwd {s}", - .{std.mem.sliceTo(pw.pw_name, 0)}, - ), - }, - }); - if (exec.term == .Exited) { - // Shell and home are the last two entries - var it = std.mem.splitBackwards(u8, std.mem.trimRight(u8, exec.stdout, " \r\n"), ":"); - result.shell = it.next() orelse null; - result.home = it.next() orelse null; - return result; - } + // Once started, we can close the child side. We do this after + // wait right now but that is fine too. This lets us read the + // parent and detect EOF. + _ = std.os.close(pty.slave); + + // Read all of our output + const output = output: { + var output: std.ArrayListUnmanaged(u8) = .{}; + while (true) { + const n = std.os.read(pty.master, &buf) catch |err| { + switch (err) { + // EIO is triggered at the end since we closed our + // child side. This is just EOF for this. I'm not sure + // if I'm doing this wrong. + error.InputOutput => break, + else => return err, + } + }; + + try output.appendSlice(alloc, buf[0..n]); + + // Max total size is buf.len. We can do better here by trimming + // the front and continuing reading but we choose to just exit. + if (output.items.len > buf.len) break; + } + + break :output try output.toOwnedSlice(alloc); + }; + log.warn("DONE output={s}", .{output}); + + // Shell and home are the last two entries + var it = std.mem.splitBackwards(u8, std.mem.trimRight(u8, output, " \r\n"), ":"); + result.shell = it.next() orelse null; + result.home = it.next() orelse null; + return result; } if (pw.pw_shell) |ptr| { From 52d22a140cf35e2d6468351bea7548929e4df7f2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 11:26:31 -0800 Subject: [PATCH 17/28] termio: exec uses new flatpak command, no more host-spawn --- build.zig | 18 ++++++++-- com.mitchellh.ghostty.yml | 22 +----------- src/build_config.zig | 3 ++ src/passwd.zig | 11 ++++-- src/termio/Exec.zig | 76 ++++++++++++++++++++++++++++++++++----- 5 files changed, 94 insertions(+), 36 deletions(-) diff --git a/build.zig b/build.zig index 9f0b44766..794d261b5 100644 --- a/build.zig +++ b/build.zig @@ -46,6 +46,7 @@ comptime { var tracy: bool = false; var enable_coretext: bool = false; var enable_fontconfig: bool = false; +var flatpak: bool = false; var app_runtime: apprt.Runtime = .none; pub fn build(b: *std.build.Builder) !void { @@ -67,6 +68,12 @@ pub fn build(b: *std.build.Builder) !void { "Enable Tracy integration (default true in Debug on Linux)", ) orelse (optimize == .Debug and target.isLinux()); + flatpak = b.option( + bool, + "flatpak", + "Build for Flatpak (integrates with Flatpak APIs). Only has an effect targeting Linux.", + ) orelse false; + enable_coretext = b.option( bool, "coretext", @@ -123,6 +130,7 @@ pub fn build(b: *std.build.Builder) !void { }); const exe_options = b.addOptions(); exe_options.addOption(bool, "tracy_enabled", tracy); + exe_options.addOption(bool, "flatpak", flatpak); exe_options.addOption(bool, "coretext", enable_coretext); exe_options.addOption(bool, "fontconfig", enable_fontconfig); exe_options.addOption(apprt.Runtime, "app_runtime", app_runtime); @@ -578,6 +586,13 @@ fn addDeps( step.addIncludePath("vendor/glad/include/"); step.addCSourceFile("vendor/glad/src/gl.c", &.{}); + // When we're targeting flatpak we ALWAYS link GTK so we + // get access to glib for dbus. + if (flatpak) { + step.linkSystemLibrary("gtk4"); + step.addLibraryPath("/usr/lib/aarch64-linux-gnu"); + } + switch (app_runtime) { .none => {}, @@ -604,9 +619,6 @@ fn addDeps( try glfw.link(b, step, glfw_opts); step.linkSystemLibrary("gtk4"); - - // This is for Flatpak - step.addLibraryPath("/usr/lib/aarch64-linux-gnu"); }, } } diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml index b369c0807..d5a33ae7a 100644 --- a/com.mitchellh.ghostty.yml +++ b/com.mitchellh.ghostty.yml @@ -41,30 +41,10 @@ modules: only-arches: - aarch64 - # We use this to get a proper PTY on our host spawn. We should eventually - # replace this with using dbus directly. - - name: host-spawn - buildsystem: simple - build-commands: - - mkdir -p /app/bin - - mv ./host-spawn-* /app/bin/host-spawn - - chmod +x /app/bin/host-spawn - sources: - - type: file - url: https://github.com/1player/host-spawn/releases/download/1.4.1/host-spawn-x86_64 - sha256: d81bb6125ec73a9a2e26266c48787367fbcb665d67c2e7fb42217f0981106cf7 - only-arches: - - x86_64 - - type: file - url: https://github.com/1player/host-spawn/releases/download/1.4.1/host-spawn-aarch64 - sha256: 29bff846d72e37093b3fdf7859bae16addd64acc98087f8c059548df3c2273c4 - only-arches: - - aarch64 - - name: ghostty buildsystem: simple build-commands: - - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Dcpu=baseline -Dapp-runtime=gtk --prefix /app + - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Dcpu=baseline -Dflatpak=true -Dapp-runtime=gtk --prefix /app sources: - type: dir path: . diff --git a/src/build_config.zig b/src/build_config.zig index 05aa38825..27575c79d 100644 --- a/src/build_config.zig +++ b/src/build_config.zig @@ -22,6 +22,9 @@ pub const app_runtime = std.meta.stringToEnum( /// compiled. pub const devmode_enabled = artifact == .exe and app_runtime == .glfw; +/// We want to integrate with Flatpak APIs. +pub const flatpak = options.flatpak; + pub const Artifact = enum { /// Standalone executable exe, diff --git a/src/passwd.zig b/src/passwd.zig index f334341f5..e492d120a 100644 --- a/src/passwd.zig +++ b/src/passwd.zig @@ -1,5 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); +const build_config = @import("build_config.zig"); const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const internal_os = @import("os/main.zig"); @@ -49,8 +50,13 @@ pub fn get(alloc: Allocator) !Entry { // If we're in flatpak then our entry is always empty so we grab it // by shelling out to the host. note that we do HAVE an entry in the // sandbox but only the username is correct. - if (internal_os.isFlatpak()) { - log.info("flatpak detected, will use host-spawn to get our entry", .{}); + if (internal_os.isFlatpak()) flatpak: { + if (comptime !build_config.flatpak) { + log.warn("flatpak detected, but this build doesn't contain flatpak support", .{}); + break :flatpak; + } + + log.info("flatpak detected, will use host command to get our entry", .{}); // Note: we wrap our getent call in a /bin/sh login shell because // some operating systems (NixOS tested) don't set the PATH for various @@ -104,7 +110,6 @@ pub fn get(alloc: Allocator) !Entry { break :output try output.toOwnedSlice(alloc); }; - log.warn("DONE output={s}", .{output}); // Shell and home are the last two entries var it = std.mem.splitBackwards(u8, std.mem.trimRight(u8, output, " \r\n"), ":"); diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 61eb98d63..39cb47e4e 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -322,6 +322,10 @@ fn ttyWrite( /// Subprocess manages the lifecycle of the shell subprocess. const Subprocess = struct { + /// If we build with flatpak support then we have to keep track of + /// a potential execution on the host. + const FlatpakHostCommand = if (build_config.flatpak) internal_os.FlatpakHostCommand else void; + arena: std.heap.ArenaAllocator, cwd: ?[]const u8, env: EnvMap, @@ -331,6 +335,7 @@ const Subprocess = struct { screen_size: renderer.ScreenSize, pty: ?Pty = null, command: ?Command = null, + flatpak_command: ?FlatpakHostCommand = null, /// Initialize the subprocess. This will NOT start it, this only sets /// up the internal state necessary to start it later. @@ -364,8 +369,18 @@ const Subprocess = struct { break :argv0 argv0_buf; } else null; - // Set our env vars - var env = try std.process.getEnvMap(alloc); + // Set our env vars. For Flatpak builds running in Flatpak we don't + // inherit our environment because the login shell on the host side + // will get it. + var env = env: { + if (comptime build_config.flatpak) { + if (internal_os.isFlatpak()) { + break :env std.process.EnvMap.init(alloc); + } + } + + break :env try std.process.getEnvMap(alloc); + }; errdefer env.deinit(); try env.put("TERM", "xterm-256color"); try env.put("COLORTERM", "truecolor"); @@ -394,13 +409,9 @@ const Subprocess = struct { var args = try std.ArrayList([]const u8).initCapacity(alloc, 8); defer args.deinit(); - // We use host-spawn so the PTY is setup properly. - // future: rewrite host-spawn into pure Zig using dbus and - // we can run this directly. - try args.append("/app/bin/host-spawn"); - try args.append("-pty"); - try args.append("-env"); - try args.append("TERM,COLORTERM"); + // We run our shell wrapped in a /bin/sh login shell because + // some systems do not properly initialize the env vars unless + // we start this way (NixOS!) try args.append("/bin/sh"); try args.append("-l"); try args.append("-c"); @@ -451,6 +462,37 @@ const Subprocess = struct { self.args, }); + // In flatpak, we use the HostCommand to execute our shell. + if (internal_os.isFlatpak()) flatpak: { + if (comptime !build_config.flatpak) { + log.warn("flatpak detected, but flatpak support not built-in", .{}); + break :flatpak; + } + + // For flatpak our path and argv[0] must match because that is + // used for execution by the dbus API. + assert(std.mem.eql(u8, self.path, self.args[0])); + + // Flatpak command must have a stable pointer. + self.flatpak_command = .{ + .argv = self.args, + .env = &self.env, + .stdin = pty.slave, + .stdout = pty.slave, + .stderr = pty.slave, + }; + var cmd = &self.flatpak_command.?; + const pid = try cmd.spawn(alloc); + errdefer killCommandFlatpak(cmd); + + log.info("started subcommand on host via flatpak API path={s} pid={?}", .{ + self.path, + pid, + }); + + return pty.master; + } + // Build our subcommand var cmd: Command = .{ .path = self.path, @@ -489,6 +531,17 @@ const Subprocess = struct { self.command = null; } + // Kill our Flatpak command + if (FlatpakHostCommand != void) { + if (self.flatpak_command) |*cmd| { + killCommandFlatpak(cmd) catch |err| + log.err("error sending SIGHUP to command, may hang: {}", .{err}); + _ = cmd.wait() catch |err| + log.err("error waiting for command to exit: {}", .{err}); + self.flatpak_command = null; + } + } + // Close our PTY. We do this after killing our command because on // macOS, close will block until all blocking operations read/write // are done with it and our reader thread is probably still alive. @@ -547,6 +600,11 @@ const Subprocess = struct { } } } + + fn killCommandFlatpak(command: *FlatpakHostCommand) !void { + // TODO + _ = command; + } }; /// The read thread sits in a loop doing the following pseudo code: From 25bde8a35110c4dca00079beb686e8b2bfc56a2d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 11:30:51 -0800 Subject: [PATCH 18/28] flatpak: disable strip -- causes crashes --- com.mitchellh.ghostty.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml index d5a33ae7a..c9c537057 100644 --- a/com.mitchellh.ghostty.yml +++ b/com.mitchellh.ghostty.yml @@ -8,6 +8,8 @@ default-branch: tip command: ghostty build-options: append-path: /app/tmp/zig + strip: false + no-debuginfo: true cleanup: - /app/tmp/zig finish-args: From 83a1d783b1b826daba4583a6d181e3725fa1ff55 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 11:44:18 -0800 Subject: [PATCH 19/28] termio: implement kill command for flatpak --- src/os/flatpak.zig | 47 ++++++++++++++++++++++++++++++++++++++++++++- src/termio/Exec.zig | 14 +++++++++++--- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/os/flatpak.zig b/src/os/flatpak.zig index f41ea22de..bcb8486f5 100644 --- a/src/os/flatpak.zig +++ b/src/os/flatpak.zig @@ -1,5 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; +const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const log = std.log.scoped(.flatpak); @@ -25,7 +26,6 @@ pub fn isFlatpak() bool { /// /// Requires GIO, GLib to be available and linked. pub const FlatpakHostCommand = struct { - const Allocator = std.mem.Allocator; const fd_t = std.os.fd_t; const EnvMap = std.process.EnvMap; const c = @cImport({ @@ -134,6 +134,51 @@ pub const FlatpakHostCommand = struct { } } + /// Send a signal to the started command. This does nothing if the + /// command is not in the started state. + pub fn signal(self: *FlatpakHostCommand, sig: u8, pg: bool) !void { + const pid = pid: { + self.state_mutex.lock(); + defer self.state_mutex.unlock(); + switch (self.state) { + .started => |v| break :pid v.pid, + else => return, + } + }; + + // Get our bus connection. + var g_err: [*c]c.GError = null; + const bus = c.g_bus_get_sync(c.G_BUS_TYPE_SESSION, null, &g_err) orelse { + log.warn("signal error getting bus: {s}", .{g_err.*.message}); + return Error.FlatpakSetupFail; + }; + defer c.g_object_unref(bus); + + const reply = c.g_dbus_connection_call_sync( + bus, + "org.freedesktop.Flatpak", + "/org/freedesktop/Flatpak/Development", + "org.freedesktop.Flatpak.Development", + "HostCommandSignal", + c.g_variant_new( + "(uub)", + pid, + sig, + @intCast(c_int, @boolToInt(pg)), + ), + c.G_VARIANT_TYPE("()"), + c.G_DBUS_CALL_FLAGS_NONE, + c.G_MAXINT, + null, + &g_err, + ); + if (g_err != null) { + log.warn("signal send error: {s}", .{g_err.*.message}); + return; + } + defer c.g_variant_unref(reply); + } + fn threadMain(self: *FlatpakHostCommand, alloc: Allocator) void { // Create a new thread-local context so that all our sources go // to this context and we can run our loop correctly. diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 39cb47e4e..3f5246ab7 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -490,6 +490,11 @@ const Subprocess = struct { pid, }); + // Once started, we can close the pty child side. We do this after + // wait right now but that is fine too. This lets us read the + // parent and detect EOF. + _ = std.os.close(pty.slave); + return pty.master; } @@ -601,9 +606,10 @@ const Subprocess = struct { } } + /// Kill the underlying process started via Flatpak host command. + /// This sends a signal via the Flatpak API. fn killCommandFlatpak(command: *FlatpakHostCommand) !void { - // TODO - _ = command; + try command.signal(c.SIGHUP, true); } }; @@ -628,7 +634,9 @@ const ReadThread = struct { switch (err) { // This means our pty is closed. We're probably // gracefully shutting down. - error.NotOpenForReading => log.info("io reader exiting", .{}), + error.NotOpenForReading, + error.InputOutput, + => log.info("io reader exiting", .{}), else => { log.err("io reader error err={}", .{err}); From a8025f6ec212f622e62930381654a9b7d3c1e922 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 11:55:04 -0800 Subject: [PATCH 20/28] termio: env should not freed because arena gets it --- src/termio/Exec.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 3f5246ab7..b55167dd6 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -434,7 +434,6 @@ const Subprocess = struct { /// Clean up the subprocess. This will stop the subprocess if it is started. pub fn deinit(self: *Subprocess) void { self.stop(); - self.env.deinit(); self.arena.deinit(); self.* = undefined; } From 313519644206ddd3565caae8aca345698d0e787e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 12:01:23 -0800 Subject: [PATCH 21/28] flatpak: enable ReleaseSafe --- com.mitchellh.ghostty.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml index c9c537057..730c36b86 100644 --- a/com.mitchellh.ghostty.yml +++ b/com.mitchellh.ghostty.yml @@ -46,7 +46,7 @@ modules: - name: ghostty buildsystem: simple build-commands: - - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Dcpu=baseline -Dflatpak=true -Dapp-runtime=gtk --prefix /app + - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Doptimize=ReleaseSafe -Dcpu=baseline -Dflatpak=true -Dapp-runtime=gtk --prefix /app sources: - type: dir path: . From e3e1e6c52124369713bb7eb434b4746c19c75413 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 13:58:16 -0800 Subject: [PATCH 22/28] github: trying to add flatpak builds --- .github/workflows/flatpak.yml | 20 ++++++++++++++++++++ .github/workflows/test.yml | 3 +++ 2 files changed, 23 insertions(+) create mode 100644 .github/workflows/flatpak.yml diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml new file mode 100644 index 000000000..07412def3 --- /dev/null +++ b/.github/workflows/flatpak.yml @@ -0,0 +1,20 @@ +on: + workflow_dispatch: {} + +name: Flatpak + +jobs: + flatpak: + name: "Flatpak" + runs-on: ubuntu-latest + container: + image: bilelmoussaoui/flatpak-github-actions:gnome-43 + options: --privileged + steps: + - uses: actions/checkout@v2 + - uses: flatpak/flatpak-github-actions/flatpak-builder@v4 + with: + bundle: ghostty.flatpak + manifest-path: com.mitchellh.ghostty.yml + branch: tip + cache-key: flatpak-builder-${{ github.sha }}-v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 440ee8313..0649b15b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,6 +92,9 @@ jobs: - name: Test Dynamic Build run: nix develop -c zig build -Dstatic=false + - name: Test Flatpak and GTK Build + run: nix develop -c zig build -Dflatpak=true -Dapp-runtime=gtk + - name: Test Wasm Build run: nix develop -c zig build wasm From 533cfbc9b83a4ba0a33e2e699165b6f9f5f8135e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 13:59:42 -0800 Subject: [PATCH 23/28] ci: add flatpak builder --- .github/workflows/flatpak.yml | 8 +++++--- .gitmodules | 3 +++ build.zig | 6 +++++- com.mitchellh.ghostty.yml | 2 -- vendor/mach-sdk/sdk-linux-x86_64 | 1 + 5 files changed, 14 insertions(+), 6 deletions(-) create mode 160000 vendor/mach-sdk/sdk-linux-x86_64 diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 07412def3..68e8652fb 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -1,5 +1,4 @@ -on: - workflow_dispatch: {} +on: [push] name: Flatpak @@ -11,7 +10,10 @@ jobs: image: bilelmoussaoui/flatpak-github-actions:gnome-43 options: --privileged steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 - uses: flatpak/flatpak-github-actions/flatpak-builder@v4 with: bundle: ghostty.flatpak diff --git a/.gitmodules b/.gitmodules index 24bed8d5b..793dcf0a0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -40,3 +40,6 @@ [submodule "vendor/libxev"] path = vendor/libxev url = https://github.com/mitchellh/libxev.git +[submodule "vendor/mach-sdk/sdk-linux-x86_64"] + path = vendor/mach-sdk/sdk-linux-x86_64 + url = https://github.com/hexops/sdk-linux-x86_64.git diff --git a/build.zig b/build.zig index 794d261b5..3032aa8d7 100644 --- a/build.zig +++ b/build.zig @@ -590,7 +590,11 @@ fn addDeps( // get access to glib for dbus. if (flatpak) { step.linkSystemLibrary("gtk4"); - step.addLibraryPath("/usr/lib/aarch64-linux-gnu"); + switch (step.target.getCpuArch()) { + .aarch64 => step.addLibraryPath("/usr/lib/aarch64-linux-gnu"), + .x86_64 => step.addLibraryPath("/usr/lib/x86_64-linux-gnu"), + else => @panic("unsupported flatpak target"), + } } switch (app_runtime) { diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml index 730c36b86..ce360e6fa 100644 --- a/com.mitchellh.ghostty.yml +++ b/com.mitchellh.ghostty.yml @@ -2,8 +2,6 @@ app-id: com.mitchellh.ghostty runtime: org.gnome.Platform runtime-version: '43' sdk: org.gnome.Sdk -platform-extensions: - - org.freedesktop.Platform.GL.default default-branch: tip command: ghostty build-options: diff --git a/vendor/mach-sdk/sdk-linux-x86_64 b/vendor/mach-sdk/sdk-linux-x86_64 new file mode 160000 index 000000000..ebd1ce12e --- /dev/null +++ b/vendor/mach-sdk/sdk-linux-x86_64 @@ -0,0 +1 @@ +Subproject commit ebd1ce12e9abc152c7ed43afbcdb4b6e1c95be07 From dcff7c29982d57071ed41f76bbce61510867b6cc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 14:38:06 -0800 Subject: [PATCH 24/28] ci: try multiarch flatpak builder --- .github/workflows/flatpak.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 68e8652fb..a30639b83 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -9,14 +9,28 @@ jobs: container: image: bilelmoussaoui/flatpak-github-actions:gnome-43 options: --privileged + strategy: + fail-fast: false + matrix: + arch: [x86_64, aarch64] steps: - uses: actions/checkout@v3 with: submodules: recursive fetch-depth: 0 + # Docker is required by the docker/setup-qemu-action which enables emulation + - name: Install deps + run: | + dnf -y install docker + - name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + platforms: arm64 - uses: flatpak/flatpak-github-actions/flatpak-builder@v4 with: bundle: ghostty.flatpak manifest-path: com.mitchellh.ghostty.yml branch: tip - cache-key: flatpak-builder-${{ github.sha }}-v1 + cache-key: flatpak-builder-${{ matrix.arch }}-${{ github.sha }}-v1 + arch: ${{ matrix.arch }} From b25adb3bdf487e93ceea281c61d663dbed29ff02 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 14:54:44 -0800 Subject: [PATCH 25/28] ci: clean artifacts, move flatpak to release tip --- .github/workflows/clean-artifacts.yml | 17 +++++++++++ .github/workflows/flatpak.yml | 36 ---------------------- .github/workflows/release-tip.yml | 44 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/clean-artifacts.yml delete mode 100644 .github/workflows/flatpak.yml diff --git a/.github/workflows/clean-artifacts.yml b/.github/workflows/clean-artifacts.yml new file mode 100644 index 000000000..cb6864b23 --- /dev/null +++ b/.github/workflows/clean-artifacts.yml @@ -0,0 +1,17 @@ +name: Clean Artifacts +on: + schedule: + # Every 6 hours + - cron: '0 */6 * * *' + workflow_dispatch: +jobs: + remove-old-artifacts: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Remove old artifacts + uses: c-hive/gha-remove-artifacts@v1 + with: + age: '1 week' + skip-tags: true + skip-recent: 5 diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml deleted file mode 100644 index a30639b83..000000000 --- a/.github/workflows/flatpak.yml +++ /dev/null @@ -1,36 +0,0 @@ -on: [push] - -name: Flatpak - -jobs: - flatpak: - name: "Flatpak" - runs-on: ubuntu-latest - container: - image: bilelmoussaoui/flatpak-github-actions:gnome-43 - options: --privileged - strategy: - fail-fast: false - matrix: - arch: [x86_64, aarch64] - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - # Docker is required by the docker/setup-qemu-action which enables emulation - - name: Install deps - run: | - dnf -y install docker - - name: Set up QEMU - id: qemu - uses: docker/setup-qemu-action@v1 - with: - platforms: arm64 - - uses: flatpak/flatpak-github-actions/flatpak-builder@v4 - with: - bundle: ghostty.flatpak - manifest-path: com.mitchellh.ghostty.yml - branch: tip - cache-key: flatpak-builder-${{ matrix.arch }}-${{ github.sha }}-v1 - arch: ${{ matrix.arch }} diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index eaeb3f1f5..cbf0a1c7f 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -9,6 +9,50 @@ on: name: Release Tip jobs: + flatpak: + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + container: + image: bilelmoussaoui/flatpak-github-actions:gnome-43 + options: --privileged + strategy: + fail-fast: false + matrix: + arch: [x86_64, aarch64] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + # Docker is required by the docker/setup-qemu-action which enables emulation + - name: Install deps + run: | + dnf -y install docker + - name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + platforms: arm64 + + - uses: flatpak/flatpak-github-actions/flatpak-builder@v4 + with: + bundle: ghostty.flatpak + manifest-path: com.mitchellh.ghostty.yml + branch: tip + cache-key: flatpak-builder-${{ matrix.arch }}-${{ github.sha }}-v1 + arch: ${{ matrix.arch }} + + - name: Rename Bundle + run: mv ghostty.flatpak ghostty-${{ matrix.arch }}.flatpak + - name: Release + uses: softprops/action-gh-release@v1 + with: + name: "Ghostty Tip (\"Nightly\")" + prerelease: true + tag_name: tip + target_commitish: ${{ github.sha }} + files: ghostty-${{ matrix.arch }}.flatpak + build-macos: if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} runs-on: macos-12 From 277fd333ae1a54ba339ccfbe5edf6e6992528ccd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 15:02:03 -0800 Subject: [PATCH 26/28] remove gtk build from test, doesn't work in CI --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0649b15b5..440ee8313 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,9 +92,6 @@ jobs: - name: Test Dynamic Build run: nix develop -c zig build -Dstatic=false - - name: Test Flatpak and GTK Build - run: nix develop -c zig build -Dflatpak=true -Dapp-runtime=gtk - - name: Test Wasm Build run: nix develop -c zig build wasm From 5db9854e63a6fe87dd3ebe9fd4c072188e7afc6f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 15:20:31 -0800 Subject: [PATCH 27/28] build: install the proper linux desktop file depending on flatpak or not --- build.zig | 6 +++++- dist/linux/app-flatpak.desktop | 9 +++++++++ dist/linux/app.desktop | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 dist/linux/app-flatpak.desktop diff --git a/build.zig b/build.zig index 3032aa8d7..9ddc81aa4 100644 --- a/build.zig +++ b/build.zig @@ -154,7 +154,11 @@ pub fn build(b: *std.build.Builder) !void { // https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html // Desktop file so that we have an icon and other metadata - b.installFile("dist/linux/app.desktop", "share/applications/com.mitchellh.ghostty.desktop"); + if (flatpak) { + b.installFile("dist/linux/app-flatpak.desktop", "share/applications/com.mitchellh.ghostty.desktop"); + } else { + b.installFile("dist/linux/app.desktop", "share/applications/com.mitchellh.ghostty.desktop"); + } // Various icons that our application can use, including the icon // that will be used for the desktop. diff --git a/dist/linux/app-flatpak.desktop b/dist/linux/app-flatpak.desktop new file mode 100644 index 000000000..99a005750 --- /dev/null +++ b/dist/linux/app-flatpak.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Ghostty +Type=Application +Comment=A terminal emulator +Exec=/app/bin/ghostty +Icon=com.mitchellh.ghostty +Keywords=terminal;tty;pty; +StartupNotify=true +Terminal=false diff --git a/dist/linux/app.desktop b/dist/linux/app.desktop index 99a005750..a06032d78 100644 --- a/dist/linux/app.desktop +++ b/dist/linux/app.desktop @@ -2,7 +2,7 @@ Name=Ghostty Type=Application Comment=A terminal emulator -Exec=/app/bin/ghostty +Exec=/usr/bin/ghostty Icon=com.mitchellh.ghostty Keywords=terminal;tty;pty; StartupNotify=true From bdfd182802154779cd9105c074f4bc2b65ee8efe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Feb 2023 15:29:32 -0800 Subject: [PATCH 28/28] flatpak: remove zig from final build --- com.mitchellh.ghostty.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml index ce360e6fa..7a330d780 100644 --- a/com.mitchellh.ghostty.yml +++ b/com.mitchellh.ghostty.yml @@ -8,8 +8,10 @@ build-options: append-path: /app/tmp/zig strip: false no-debuginfo: true -cleanup: - - /app/tmp/zig +# Note: we have to use cleanup-commands because flatpak-builder doesn't +# run "cleanup" on its own: https://github.com/flatpak/flatpak-builder/issues/14 +cleanup-commands: + - "rm -rf /app/tmp" finish-args: # 3D rendering - --device=dri