mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #79 from mitchellh/flatpak
Flatpak Package for Linux
This commit is contained in:
17
.github/workflows/clean-artifacts.yml
vendored
Normal file
17
.github/workflows/clean-artifacts.yml
vendored
Normal file
@ -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
|
44
.github/workflows/release-tip.yml
vendored
44
.github/workflows/release-tip.yml
vendored
@ -9,6 +9,50 @@ on:
|
|||||||
name: Release Tip
|
name: Release Tip
|
||||||
|
|
||||||
jobs:
|
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:
|
build-macos:
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
|
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
|
||||||
runs-on: macos-12
|
runs-on: macos-12
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.direnv/
|
.direnv/
|
||||||
|
.flatpak-builder/
|
||||||
zig-cache/
|
zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
/result*
|
/result*
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -40,3 +40,6 @@
|
|||||||
[submodule "vendor/libxev"]
|
[submodule "vendor/libxev"]
|
||||||
path = vendor/libxev
|
path = vendor/libxev
|
||||||
url = https://github.com/mitchellh/libxev.git
|
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
|
||||||
|
26
build.zig
26
build.zig
@ -46,6 +46,7 @@ comptime {
|
|||||||
var tracy: bool = false;
|
var tracy: bool = false;
|
||||||
var enable_coretext: bool = false;
|
var enable_coretext: bool = false;
|
||||||
var enable_fontconfig: bool = false;
|
var enable_fontconfig: bool = false;
|
||||||
|
var flatpak: bool = false;
|
||||||
var app_runtime: apprt.Runtime = .none;
|
var app_runtime: apprt.Runtime = .none;
|
||||||
|
|
||||||
pub fn build(b: *std.build.Builder) !void {
|
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)",
|
"Enable Tracy integration (default true in Debug on Linux)",
|
||||||
) orelse (optimize == .Debug and target.isLinux());
|
) 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(
|
enable_coretext = b.option(
|
||||||
bool,
|
bool,
|
||||||
"coretext",
|
"coretext",
|
||||||
@ -123,6 +130,7 @@ pub fn build(b: *std.build.Builder) !void {
|
|||||||
});
|
});
|
||||||
const exe_options = b.addOptions();
|
const exe_options = b.addOptions();
|
||||||
exe_options.addOption(bool, "tracy_enabled", tracy);
|
exe_options.addOption(bool, "tracy_enabled", tracy);
|
||||||
|
exe_options.addOption(bool, "flatpak", flatpak);
|
||||||
exe_options.addOption(bool, "coretext", enable_coretext);
|
exe_options.addOption(bool, "coretext", enable_coretext);
|
||||||
exe_options.addOption(bool, "fontconfig", enable_fontconfig);
|
exe_options.addOption(bool, "fontconfig", enable_fontconfig);
|
||||||
exe_options.addOption(apprt.Runtime, "app_runtime", app_runtime);
|
exe_options.addOption(apprt.Runtime, "app_runtime", app_runtime);
|
||||||
@ -146,7 +154,11 @@ pub fn build(b: *std.build.Builder) !void {
|
|||||||
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html
|
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html
|
||||||
|
|
||||||
// Desktop file so that we have an icon and other metadata
|
// 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
|
// Various icons that our application can use, including the icon
|
||||||
// that will be used for the desktop.
|
// that will be used for the desktop.
|
||||||
@ -159,7 +171,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_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_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_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)
|
// App (Mac)
|
||||||
@ -579,6 +590,17 @@ fn addDeps(
|
|||||||
step.addIncludePath("vendor/glad/include/");
|
step.addIncludePath("vendor/glad/include/");
|
||||||
step.addCSourceFile("vendor/glad/src/gl.c", &.{});
|
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");
|
||||||
|
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) {
|
switch (app_runtime) {
|
||||||
.none => {},
|
.none => {},
|
||||||
|
|
||||||
|
56
com.mitchellh.ghostty.yml
Normal file
56
com.mitchellh.ghostty.yml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
app-id: com.mitchellh.ghostty
|
||||||
|
runtime: org.gnome.Platform
|
||||||
|
runtime-version: '43'
|
||||||
|
sdk: org.gnome.Sdk
|
||||||
|
default-branch: tip
|
||||||
|
command: ghostty
|
||||||
|
build-options:
|
||||||
|
append-path: /app/tmp/zig
|
||||||
|
strip: false
|
||||||
|
no-debuginfo: true
|
||||||
|
# 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
|
||||||
|
# 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 -Doptimize=ReleaseSafe -Dcpu=baseline -Dflatpak=true -Dapp-runtime=gtk --prefix /app
|
||||||
|
sources:
|
||||||
|
- type: dir
|
||||||
|
path: .
|
||||||
|
skip:
|
||||||
|
- .flatpak-builder
|
||||||
|
- zig-cache
|
||||||
|
- zig-out
|
9
dist/linux/app-flatpak.desktop
vendored
Normal file
9
dist/linux/app-flatpak.desktop
vendored
Normal file
@ -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
|
@ -1,5 +1,7 @@
|
|||||||
{ mkShell, lib, stdenv
|
{ mkShell, lib, stdenv
|
||||||
|
|
||||||
|
, debugedit
|
||||||
|
, flatpak-builder
|
||||||
, fpm
|
, fpm
|
||||||
, gdb
|
, gdb
|
||||||
, glxinfo
|
, glxinfo
|
||||||
@ -86,6 +88,10 @@ in mkShell rec {
|
|||||||
wabt
|
wabt
|
||||||
wasmtime
|
wasmtime
|
||||||
] ++ lib.optionals stdenv.isLinux [
|
] ++ lib.optionals stdenv.isLinux [
|
||||||
|
# Flatpak builds
|
||||||
|
debugedit
|
||||||
|
flatpak-builder
|
||||||
|
|
||||||
valgrind
|
valgrind
|
||||||
wraptest
|
wraptest
|
||||||
];
|
];
|
||||||
|
@ -24,6 +24,7 @@ const Command = @This();
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const TempDir = @import("TempDir.zig");
|
const TempDir = @import("TempDir.zig");
|
||||||
|
const internal_os = @import("os/main.zig");
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const os = std.os;
|
const os = std.os;
|
||||||
const debug = std.debug;
|
const debug = std.debug;
|
||||||
|
11
src/Pty.zig
11
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;
|
const TIOCGWINSZ = if (builtin.os.tag == .macos) 1074295912 else c.TIOCGWINSZ;
|
||||||
|
|
||||||
/// Redeclare this winsize struct so we can just use a Zig struct. This
|
/// 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 {
|
const winsize = extern struct {
|
||||||
ws_row: u16,
|
ws_row: u16 = 100,
|
||||||
ws_col: u16,
|
ws_col: u16 = 80,
|
||||||
ws_xpixel: u16,
|
ws_xpixel: u16 = 800,
|
||||||
ws_ypixel: u16,
|
ws_ypixel: u16 = 600,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub extern "c" fn setsid() std.c.pid_t;
|
pub extern "c" fn setsid() std.c.pid_t;
|
||||||
|
@ -22,6 +22,9 @@ pub const app_runtime = std.meta.stringToEnum(
|
|||||||
/// compiled.
|
/// compiled.
|
||||||
pub const devmode_enabled = artifact == .exe and app_runtime == .glfw;
|
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 {
|
pub const Artifact = enum {
|
||||||
/// Standalone executable
|
/// Standalone executable
|
||||||
exe,
|
exe,
|
||||||
|
@ -344,14 +344,18 @@ pub const Config = struct {
|
|||||||
if (self.command == null or wd_home) command: {
|
if (self.command == null or wd_home) command: {
|
||||||
const alloc = self._arena.?.allocator();
|
const alloc = self._arena.?.allocator();
|
||||||
|
|
||||||
// First look up the command using the SHELL env var.
|
// We don't do this in flatpak because SHELL in Flatpak is
|
||||||
if (std.process.getEnvVarOwned(alloc, "SHELL")) |value| {
|
// always set to /bin/sh
|
||||||
log.debug("default shell source=env value={s}", .{value});
|
if (!internal_os.isFlatpak()) {
|
||||||
self.command = value;
|
// 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 we don't need the working directory, then we can exit now.
|
||||||
if (!wd_home) break :command;
|
if (!wd_home) break :command;
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
|
}
|
||||||
|
|
||||||
// We need the passwd entry for the remainder
|
// We need the passwd entry for the remainder
|
||||||
const pw = try passwd.get(alloc);
|
const pw = try passwd.get(alloc);
|
||||||
|
392
src/os/flatpak.zig
Normal file
392
src/os/flatpak.zig
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
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. 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.
|
||||||
|
///
|
||||||
|
/// 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 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. It is up to the
|
||||||
|
/// caller to create the file descriptors and set them up.
|
||||||
|
stdin: fd_t,
|
||||||
|
stdout: fd_t,
|
||||||
|
stderr: fd_t,
|
||||||
|
|
||||||
|
/// 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 = .{},
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
|
||||||
|
/// Process started with the given pid on the host.
|
||||||
|
started: struct {
|
||||||
|
pid: c_int,
|
||||||
|
subscription: c.guint,
|
||||||
|
loop: *c.GMainLoop,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Process exited
|
||||||
|
exited: struct {
|
||||||
|
pid: c_int,
|
||||||
|
status: u8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// 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.FlatpakMustBeStarted,
|
||||||
|
.err => return Error.FlatpakSpawnFail,
|
||||||
|
.started => {},
|
||||||
|
.exited => |v| {
|
||||||
|
self.state = .{ .init = {} };
|
||||||
|
self.state_cv.broadcast();
|
||||||
|
return v.status;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state_cv.wait(&self.state_mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// 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();
|
||||||
|
|
||||||
|
// 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.FlatpakSetupFail;
|
||||||
|
}
|
||||||
|
if (c.g_unix_fd_list_append(fd_list, self.stdout, &err) < 0) {
|
||||||
|
log.warn("error adding fd: {s}", .{err.*.message});
|
||||||
|
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.FlatpakSetupFail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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}"));
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
"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.FlatpakRPCFail;
|
||||||
|
};
|
||||||
|
defer c.g_variant_unref(reply);
|
||||||
|
|
||||||
|
var pid: c_int = 0;
|
||||||
|
c.g_variant_get(reply, "(u)", &pid);
|
||||||
|
log.debug("HostCommand started pid={} subscription={}", .{
|
||||||
|
pid,
|
||||||
|
subscription_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.updateState(.{
|
||||||
|
.started = .{
|
||||||
|
.pid = pid,
|
||||||
|
.subscription = subscription_id,
|
||||||
|
.loop = loop,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
|
||||||
|
// We are also done with our loop so we can exit.
|
||||||
|
c.g_main_loop_quit(state.loop);
|
||||||
|
}
|
||||||
|
};
|
@ -2,6 +2,7 @@
|
|||||||
//! system.
|
//! system.
|
||||||
|
|
||||||
pub usingnamespace @import("file.zig");
|
pub usingnamespace @import("file.zig");
|
||||||
|
pub usingnamespace @import("flatpak.zig");
|
||||||
pub usingnamespace @import("locale.zig");
|
pub usingnamespace @import("locale.zig");
|
||||||
pub usingnamespace @import("macos_version.zig");
|
pub usingnamespace @import("macos_version.zig");
|
||||||
pub usingnamespace @import("mouse.zig");
|
pub usingnamespace @import("mouse.zig");
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const build_config = @import("build_config.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
const internal_os = @import("os/main.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.passwd);
|
const log = std.log.scoped(.passwd);
|
||||||
|
|
||||||
@ -45,6 +47,77 @@ pub fn get(alloc: Allocator) !Entry {
|
|||||||
|
|
||||||
var result: 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.
|
||||||
|
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
|
||||||
|
// utilities properly until we get a login shell.
|
||||||
|
const Pty = @import("Pty.zig");
|
||||||
|
var pty = try Pty.open(.{});
|
||||||
|
defer pty.deinit();
|
||||||
|
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)},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
.stdin = pty.slave,
|
||||||
|
.stdout = pty.slave,
|
||||||
|
.stderr = pty.slave,
|
||||||
|
};
|
||||||
|
_ = try cmd.spawn(alloc);
|
||||||
|
_ = try cmd.wait();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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| {
|
if (pw.pw_shell) |ptr| {
|
||||||
const source = std.mem.sliceTo(ptr, 0);
|
const source = std.mem.sliceTo(ptr, 0);
|
||||||
const sh = try alloc.alloc(u8, source.len);
|
const sh = try alloc.alloc(u8, source.len);
|
||||||
|
@ -136,6 +136,7 @@ pub fn threadMain(self: *Thread) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn threadMain_(self: *Thread) !void {
|
fn threadMain_(self: *Thread) !void {
|
||||||
|
defer log.debug("renderer thread exited", .{});
|
||||||
tracy.setThreadName("renderer");
|
tracy.setThreadName("renderer");
|
||||||
|
|
||||||
// Run our thread start/end callbacks. This is important because some
|
// Run our thread start/end callbacks. This is important because some
|
||||||
@ -185,7 +186,7 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
|
|
||||||
// Run
|
// Run
|
||||||
log.debug("starting renderer thread", .{});
|
log.debug("starting renderer thread", .{});
|
||||||
defer log.debug("exiting renderer thread", .{});
|
defer log.debug("starting renderer thread shutdown", .{});
|
||||||
_ = try self.loop.run(.until_done);
|
_ = try self.loop.run(.until_done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ const builtin = @import("builtin");
|
|||||||
const build_config = @import("../build_config.zig");
|
const build_config = @import("../build_config.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const EnvMap = std.process.EnvMap;
|
||||||
const termio = @import("../termio.zig");
|
const termio = @import("../termio.zig");
|
||||||
const Command = @import("../Command.zig");
|
const Command = @import("../Command.zig");
|
||||||
const Pty = @import("../Pty.zig");
|
const Pty = @import("../Pty.zig");
|
||||||
@ -17,6 +18,7 @@ const tracy = @import("tracy");
|
|||||||
const trace = tracy.trace;
|
const trace = tracy.trace;
|
||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const fastmem = @import("../fastmem.zig");
|
const fastmem = @import("../fastmem.zig");
|
||||||
|
const internal_os = @import("../os/main.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.io_exec);
|
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 {
|
pub fn deinit(self: *Exec) void {
|
||||||
self.subprocess.deinit(self.alloc);
|
self.subprocess.deinit();
|
||||||
|
|
||||||
// Clean up our other members
|
// Clean up our other members
|
||||||
self.terminal.deinit(self.alloc);
|
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
|
// Store our data so our callbacks can access it
|
||||||
self.data = ev_data_ptr;
|
self.data = ev_data_ptr;
|
||||||
|
errdefer self.data = null;
|
||||||
|
|
||||||
// Start our reader thread
|
// Start our reader thread
|
||||||
const read_thread = try std.Thread.spawn(
|
const read_thread = try std.Thread.spawn(
|
||||||
@ -319,22 +322,33 @@ fn ttyWrite(
|
|||||||
|
|
||||||
/// Subprocess manages the lifecycle of the shell subprocess.
|
/// Subprocess manages the lifecycle of the shell subprocess.
|
||||||
const Subprocess = struct {
|
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,
|
cwd: ?[]const u8,
|
||||||
env: std.process.EnvMap,
|
env: EnvMap,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
argv0_override: ?[]const u8,
|
args: [][]const u8,
|
||||||
grid_size: renderer.GridSize,
|
grid_size: renderer.GridSize,
|
||||||
screen_size: renderer.ScreenSize,
|
screen_size: renderer.ScreenSize,
|
||||||
pty: ?Pty = null,
|
pty: ?Pty = null,
|
||||||
command: ?Command = null,
|
command: ?Command = null,
|
||||||
|
flatpak_command: ?FlatpakHostCommand = null,
|
||||||
|
|
||||||
/// Initialize the subprocess. This will NOT start it, this only sets
|
/// Initialize the subprocess. This will NOT start it, this only sets
|
||||||
/// up the internal state necessary to start it later.
|
/// 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
|
// Determine the path to the binary we're executing
|
||||||
const path = (try Command.expandPath(alloc, opts.config.command orelse "sh")) orelse
|
const path = (try Command.expandPath(alloc, opts.config.command orelse "sh")) orelse
|
||||||
return error.CommandNotFound;
|
return error.CommandNotFound;
|
||||||
errdefer alloc.free(path);
|
|
||||||
|
|
||||||
// On macOS, we launch the program as a login shell. This is a Mac-specific
|
// 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
|
// behavior (see other terminals). Terminals in general should NOT be
|
||||||
@ -354,10 +368,19 @@ const Subprocess = struct {
|
|||||||
std.mem.copy(u8, argv0_buf[1..], argv0);
|
std.mem.copy(u8, argv0_buf[1..], argv0);
|
||||||
break :argv0 argv0_buf;
|
break :argv0 argv0_buf;
|
||||||
} else null;
|
} else null;
|
||||||
errdefer if (argv0_override) |buf| alloc.free(buf);
|
|
||||||
|
|
||||||
// Set our env vars
|
// Set our env vars. For Flatpak builds running in Flatpak we don't
|
||||||
var env = try std.process.getEnvMap(alloc);
|
// 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();
|
errdefer env.deinit();
|
||||||
try env.put("TERM", "xterm-256color");
|
try env.put("TERM", "xterm-256color");
|
||||||
try env.put("COLORTERM", "truecolor");
|
try env.put("COLORTERM", "truecolor");
|
||||||
@ -377,22 +400,41 @@ 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();
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
try args.append(path);
|
||||||
|
|
||||||
|
break :args try args.toOwnedSlice();
|
||||||
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
.arena = arena,
|
||||||
.env = env,
|
.env = env,
|
||||||
.cwd = opts.config.@"working-directory",
|
.cwd = opts.config.@"working-directory",
|
||||||
.path = path,
|
.path = if (internal_os.isFlatpak()) args[0] else path,
|
||||||
.argv0_override = argv0_override,
|
.args = args,
|
||||||
.grid_size = opts.grid_size,
|
.grid_size = opts.grid_size,
|
||||||
.screen_size = opts.screen_size,
|
.screen_size = opts.screen_size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clean up the subprocess. This will stop the subprocess if it is started.
|
/// 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.stop();
|
||||||
self.env.deinit();
|
self.arena.deinit();
|
||||||
alloc.free(self.path);
|
|
||||||
if (self.argv0_override) |v| alloc.free(v);
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,12 +456,51 @@ const Subprocess = struct {
|
|||||||
self.pty = null;
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
// Build our subcommand
|
// Build our subcommand
|
||||||
var cmd: Command = .{
|
var cmd: Command = .{
|
||||||
.path = self.path,
|
.path = self.path,
|
||||||
.args = args,
|
.args = self.args,
|
||||||
.env = &self.env,
|
.env = &self.env,
|
||||||
.cwd = self.cwd,
|
.cwd = self.cwd,
|
||||||
.stdin = .{ .handle = pty.slave },
|
.stdin = .{ .handle = pty.slave },
|
||||||
@ -454,6 +535,17 @@ const Subprocess = struct {
|
|||||||
self.command = null;
|
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
|
// Close our PTY. We do this after killing our command because on
|
||||||
// macOS, close will block until all blocking operations read/write
|
// macOS, close will block until all blocking operations read/write
|
||||||
// are done with it and our reader thread is probably still alive.
|
// are done with it and our reader thread is probably still alive.
|
||||||
@ -512,6 +604,12 @@ 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 {
|
||||||
|
try command.signal(c.SIGHUP, true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The read thread sits in a loop doing the following pseudo code:
|
/// The read thread sits in a loop doing the following pseudo code:
|
||||||
@ -535,7 +633,9 @@ const ReadThread = struct {
|
|||||||
switch (err) {
|
switch (err) {
|
||||||
// This means our pty is closed. We're probably
|
// This means our pty is closed. We're probably
|
||||||
// gracefully shutting down.
|
// gracefully shutting down.
|
||||||
error.NotOpenForReading => log.info("io reader exiting", .{}),
|
error.NotOpenForReading,
|
||||||
|
error.InputOutput,
|
||||||
|
=> log.info("io reader exiting", .{}),
|
||||||
|
|
||||||
else => {
|
else => {
|
||||||
log.err("io reader error err={}", .{err});
|
log.err("io reader error err={}", .{err});
|
||||||
|
@ -95,6 +95,7 @@ pub fn threadMain(self: *Thread) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn threadMain_(self: *Thread) !void {
|
fn threadMain_(self: *Thread) !void {
|
||||||
|
defer log.debug("IO thread exited", .{});
|
||||||
tracy.setThreadName("pty io");
|
tracy.setThreadName("pty io");
|
||||||
|
|
||||||
// Run our thread start/end callbacks. This allows the implementation
|
// Run our thread start/end callbacks. This allows the implementation
|
||||||
@ -109,7 +110,7 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
|
|
||||||
// Run
|
// Run
|
||||||
log.debug("starting IO thread", .{});
|
log.debug("starting IO thread", .{});
|
||||||
defer log.debug("exiting IO thread", .{});
|
defer log.debug("starting IO thread shutdown", .{});
|
||||||
try self.loop.run(.until_done);
|
try self.loop.run(.until_done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
vendor/mach-sdk/sdk-linux-aarch64
vendored
2
vendor/mach-sdk/sdk-linux-aarch64
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 8f6ddaf6cc25df02925ef78448d512c3184abc63
|
Subproject commit a279b0a3ef2f103b308defcd7e1a32e20346f70b
|
1
vendor/mach-sdk/sdk-linux-x86_64
vendored
Submodule
1
vendor/mach-sdk/sdk-linux-x86_64
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit ebd1ce12e9abc152c7ed43afbcdb4b6e1c95be07
|
Reference in New Issue
Block a user