summary refs log tree commit diff
path: root/pkgs/applications/terminal-emulators/foot
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/applications/terminal-emulators/foot')
-rw-r--r--pkgs/applications/terminal-emulators/foot/Add-support-for-opening-an-existing-PTY.patch211
-rw-r--r--pkgs/applications/terminal-emulators/foot/default.nix228
2 files changed, 439 insertions, 0 deletions
diff --git a/pkgs/applications/terminal-emulators/foot/Add-support-for-opening-an-existing-PTY.patch b/pkgs/applications/terminal-emulators/foot/Add-support-for-opening-an-existing-PTY.patch
new file mode 100644
index 00000000000..f07a26bad6f
--- /dev/null
+++ b/pkgs/applications/terminal-emulators/foot/Add-support-for-opening-an-existing-PTY.patch
@@ -0,0 +1,211 @@
+From 6cb6fe265eb8c066fce8ce12473d6b8135f3b3cb Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Fri, 10 Dec 2021 17:40:59 +0000
+Subject: [PATCH] Add support for opening an existing PTY
+
+---
+ main.c     | 12 +++++++++-
+ server.c   |  2 +-
+ terminal.c | 65 +++++++++++++++++++++++++++++++-----------------------
+ terminal.h |  2 +-
+ 4 files changed, 51 insertions(+), 30 deletions(-)
+
+diff --git a/main.c b/main.c
+index 4f2dc7b6..d1307a84 100644
+--- a/main.c
++++ b/main.c
+@@ -152,6 +152,10 @@ print_pid(const char *pid_file, bool *unlink_at_exit)
+         return false;
+ }
+ 
++enum {
++    PTY_OPTION = CHAR_MAX + 1,
++};
++
+ int
+ main(int argc, char *const *argv)
+ {
+@@ -187,6 +191,7 @@ main(int argc, char *const *argv)
+         {"maximized",              no_argument,       NULL, 'm'},
+         {"fullscreen",             no_argument,       NULL, 'F'},
+         {"presentation-timings",   no_argument,       NULL, 'P'}, /* Undocumented */
++        {"pty",                    required_argument, NULL, PTY_OPTION},
+         {"print-pid",              required_argument, NULL, 'p'},
+         {"log-level",              required_argument, NULL, 'd'},
+         {"log-colorize",           optional_argument, NULL, 'l'},
+@@ -202,6 +207,7 @@ main(int argc, char *const *argv)
+     const char *conf_title = NULL;
+     const char *conf_app_id = NULL;
+     const char *custom_cwd = NULL;
++    const char *pty_path = NULL;
+     bool login_shell = false;
+     tll(char *) conf_fonts = tll_init();
+     enum conf_size_type conf_size_type = CONF_SIZE_PX;
+@@ -320,6 +326,10 @@ main(int argc, char *const *argv)
+                 conf_server_socket_path = optarg;
+             break;
+ 
++        case PTY_OPTION:
++            pty_path = optarg;
++            break;
++
+         case 'P':
+             presentation_timings = true;
+             break;
+@@ -538,7 +548,7 @@ main(int argc, char *const *argv)
+         goto out;
+ 
+     if (!as_server && (term = term_init(
+-                           &conf, fdm, reaper, wayl, "foot", cwd, token,
++                           &conf, fdm, reaper, wayl, "foot", cwd, token, pty_path,
+                            argc, argv,
+                            &term_shutdown_cb, &shutdown_ctx)) == NULL) {
+         goto out;
+diff --git a/server.c b/server.c
+index 1d31abc6..190d224d 100644
+--- a/server.c
++++ b/server.c
+@@ -315,7 +315,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
+     instance->terminal = term_init(
+         conf != NULL ? conf : server->conf,
+         server->fdm, server->reaper, server->wayl, "footclient", cwd, token,
+-        cdata.argc, argv, &term_shutdown_handler, instance);
++        NULL, cdata.argc, argv, &term_shutdown_handler, instance);
+ 
+     if (instance->terminal == NULL) {
+         LOG_ERR("failed to instantiate new terminal");
+diff --git a/terminal.c b/terminal.c
+index ac887412..e11ef0d6 100644
+--- a/terminal.c
++++ b/terminal.c
+@@ -327,6 +327,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
+     if (hup) {
+         fdm_del(fdm, fd);
+         term->ptmx = -1;
++        term_shutdown(term);
+     }
+ 
+     return true;
+@@ -1035,10 +1036,12 @@ load_fonts_from_conf(struct terminal *term)
+ static void fdm_client_terminated(
+     struct reaper *reaper, pid_t pid, int status, void *data);
+ 
++static const int PTY_OPEN_FLAGS = O_RDWR | O_NOCTTY;
++
+ struct terminal *
+ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
+           struct wayland *wayl, const char *foot_exe, const char *cwd,
+-          const char *token, int argc, char *const *argv,
++          const char *token, const char *pty_path, int argc, char *const *argv,
+           void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data)
+ {
+     int ptmx = -1;
+@@ -1054,7 +1057,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
+         return NULL;
+     }
+ 
+-    if ((ptmx = posix_openpt(O_RDWR | O_NOCTTY)) < 0) {
++    ptmx = pty_path ? open(pty_path, PTY_OPEN_FLAGS) : posix_openpt(PTY_OPEN_FLAGS);
++    if (ptmx < 0) {
+         LOG_ERRNO("failed to open PTY");
+         goto close_fds;
+     }
+@@ -1116,6 +1120,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
+         .fdm = fdm,
+         .reaper = reaper,
+         .conf = conf,
++        .slave = -1,
+         .ptmx = ptmx,
+         .ptmx_buffers = tll_init(),
+         .ptmx_paste_buffers = tll_init(),
+@@ -1237,16 +1242,18 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
+     }
+     term->font_line_height = conf->line_height;
+ 
+-    /* Start the slave/client */
+-    if ((term->slave = slave_spawn(
+-             term->ptmx, argc, term->cwd, argv,
+-             conf->term, conf->shell, conf->login_shell,
+-             &conf->notifications)) == -1)
+-    {
+-        goto err;
+-    }
++    if (!pty_path) {
++        /* Start the slave/client */
++        if ((term->slave = slave_spawn(
++                 term->ptmx, argc, term->cwd, argv,
++                 conf->term, conf->shell, conf->login_shell,
++                 &conf->notifications)) == -1)
++        {
++            goto err;
++        }
+ 
+-    reaper_add(term->reaper, term->slave, &fdm_client_terminated, term);
++        reaper_add(term->reaper, term->slave, &fdm_client_terminated, term);
++    }
+ 
+     /* Guess scale; we're not mapped yet, so we don't know on which
+      * output we'll be. Pick highest scale we find for now */
+@@ -1506,26 +1513,30 @@ term_shutdown(struct terminal *term)
+         close(term->ptmx);
+ 
+     if (!term->shutdown.client_has_terminated) {
+-        LOG_DBG("initiating asynchronous terminate of slave; "
+-                "sending SIGTERM to PID=%u", term->slave);
++        if (term->slave <= 0) {
++            term->shutdown.client_has_terminated = true;
++        } else {
++            LOG_DBG("initiating asynchronous terminate of slave; "
++                    "sending SIGTERM to PID=%u", term->slave);
+ 
+-        kill(-term->slave, SIGTERM);
++            kill(-term->slave, SIGTERM);
+ 
+-        const struct itimerspec timeout = {.it_value = {.tv_sec = 60}};
++            const struct itimerspec timeout = {.it_value = {.tv_sec = 60}};
+ 
+-        int timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
+-        if (timeout_fd < 0 ||
+-            timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0 ||
+-            !fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_terminate_timeout, term))
+-        {
+-            if (timeout_fd >= 0)
+-                close(timeout_fd);
+-            LOG_ERRNO("failed to create slave terminate timeout FD");
+-            return false;
++            int timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
++            if (timeout_fd < 0 ||
++                timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0 ||
++                !fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_terminate_timeout, term))
++            {
++                if (timeout_fd >= 0)
++                    close(timeout_fd);
++                LOG_ERRNO("failed to create slave terminate timeout FD");
++                return false;
++            }
++
++            xassert(term->shutdown.terminate_timeout_fd < 0);
++            term->shutdown.terminate_timeout_fd = timeout_fd;
+         }
+-
+-        xassert(term->shutdown.terminate_timeout_fd < 0);
+-        term->shutdown.terminate_timeout_fd = timeout_fd;
+     }
+ 
+     term->selection.auto_scroll.fd = -1;
+diff --git a/terminal.h b/terminal.h
+index 09b04614..3beb3e80 100644
+--- a/terminal.h
++++ b/terminal.h
+@@ -671,7 +671,7 @@ struct config;
+ struct terminal *term_init(
+     const struct config *conf, struct fdm *fdm, struct reaper *reaper,
+     struct wayland *wayl, const char *foot_exe, const char *cwd,
+-    const char *token, int argc, char *const *argv,
++    const char *token, const char *pty_path, int argc, char *const *argv,
+     void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data);
+ 
+ bool term_shutdown(struct terminal *term);
+-- 
+2.33.0
+
diff --git a/pkgs/applications/terminal-emulators/foot/default.nix b/pkgs/applications/terminal-emulators/foot/default.nix
new file mode 100644
index 00000000000..a58e45e50dc
--- /dev/null
+++ b/pkgs/applications/terminal-emulators/foot/default.nix
@@ -0,0 +1,228 @@
+{ stdenv
+, lib
+, fetchFromGitea
+, fetchurl
+, runCommand
+, fcft
+, freetype
+, pixman
+, libxkbcommon
+, fontconfig
+, wayland
+, meson
+, ninja
+, ncurses
+, scdoc
+, tllist
+, wayland-protocols
+, wayland-scanner
+, pkg-config
+, utf8proc
+, allowPgo ? true
+, python3  # for PGO
+# for clang stdenv check
+, foot
+, llvmPackages
+, llvmPackages_latest
+}:
+
+let
+  version = "1.11.0";
+
+  # build stimuli file for PGO build and the script to generate it
+  # independently of the foot's build, so we can cache the result
+  # and avoid unnecessary rebuilds as it can take relatively long
+  # to generate
+  #
+  # For every bump, make sure that the hash is still accurate.
+  stimulusGenerator = stdenv.mkDerivation {
+    name = "foot-generate-alt-random-writes";
+
+    src = fetchurl {
+      url = "https://codeberg.org/dnkl/foot/raw/tag/${version}/scripts/generate-alt-random-writes.py";
+      sha256 = "0w4d0rxi54p8lvbynypcywqqwbbzmyyzc0svjab27ngmdj1034ii";
+    };
+
+    dontUnpack = true;
+
+    buildInputs = [ python3 ];
+
+    installPhase = ''
+      install -Dm755 $src $out
+    '';
+  };
+
+  stimuliFile = runCommand "pgo-stimulus-file" { } ''
+    ${stimulusGenerator} \
+      --rows=67 --cols=135 \
+      --scroll --scroll-region \
+      --colors-regular --colors-bright --colors-256 --colors-rgb \
+      --attr-bold --attr-italic --attr-underline \
+      --sixel \
+      --seed=2305843009213693951 \
+      $out
+  '';
+
+  compilerName =
+    if stdenv.cc.isClang
+    then "clang"
+    else if stdenv.cc.isGNU
+    then "gcc"
+    else "unknown";
+
+  # https://codeberg.org/dnkl/foot/src/branch/master/INSTALL.md#performance-optimized-pgo
+  pgoCflags = {
+    "clang" = "-O3 -Wno-ignored-optimization-argument";
+    "gcc" = "-O3";
+  }."${compilerName}";
+
+  # ar with lto support
+  ar = stdenv.cc.bintools.targetPrefix + {
+    "clang" = "llvm-ar";
+    "gcc" = "gcc-ar";
+    "unknown" = "ar";
+  }."${compilerName}";
+
+  # PGO only makes sense if we are not cross compiling and
+  # using a compiler which foot's PGO build supports (clang or gcc)
+  doPgo = allowPgo && (stdenv.hostPlatform == stdenv.buildPlatform)
+    && compilerName != "unknown";
+
+  terminfoDir = "${placeholder "terminfo"}/share/terminfo";
+in
+stdenv.mkDerivation rec {
+  pname = "foot";
+  inherit version;
+
+  src = fetchFromGitea {
+    domain = "codeberg.org";
+    owner = "dnkl";
+    repo = pname;
+    rev = version;
+    sha256 = "1d9bk8lhmw5lc8k0mw80g0vbwgxyh3gw5c7ppy3sir07s9y0y0fn";
+  };
+
+  patches = [
+    ./Add-support-for-opening-an-existing-PTY.patch
+  ];
+
+  depsBuildBuild = [
+    pkg-config
+  ];
+
+  nativeBuildInputs = [
+    wayland-scanner
+    meson
+    ninja
+    ncurses
+    scdoc
+    pkg-config
+  ] ++ lib.optionals (compilerName == "clang") [
+    stdenv.cc.cc.libllvm.out
+  ];
+
+  buildInputs = [
+    tllist
+    wayland-protocols
+    fontconfig
+    freetype
+    pixman
+    wayland
+    libxkbcommon
+    fcft
+    utf8proc
+  ];
+
+  # recommended build flags for performance optimized foot builds
+  # https://codeberg.org/dnkl/foot/src/branch/master/INSTALL.md#release-build
+  CFLAGS =
+    if !doPgo
+    then "-O3 -fno-plt"
+    else pgoCflags;
+
+  # ar with gcc plugins for lto objects
+  preConfigure = ''
+    export AR="${ar}"
+  '';
+
+  mesonBuildType = "release";
+
+  # See https://codeberg.org/dnkl/foot/src/tag/1.9.2/INSTALL.md#options
+  mesonFlags = [
+    # Use lto
+    "-Db_lto=true"
+    # “Build” and install terminfo db
+    "-Dterminfo=enabled"
+    # Ensure TERM=foot is used
+    "-Ddefault-terminfo=foot"
+    # Tell foot to set TERMINFO and where to install the terminfo files
+    "-Dcustom-terminfo-install-location=${terminfoDir}"
+  ];
+
+  # build and run binary generating PGO profiles,
+  # then reconfigure to build the normal foot binary utilizing PGO
+  preBuild = lib.optionalString doPgo ''
+    meson configure -Db_pgo=generate
+    ninja
+    # make sure there is _some_ profiling data on all binaries
+    ./footclient --version
+    ./foot --version
+    ./tests/test-config
+    # generate pgo data of wayland independent code
+    ./pgo ${stimuliFile} ${stimuliFile} ${stimuliFile}
+    meson configure -Db_pgo=use
+  '' + lib.optionalString (doPgo && compilerName == "clang") ''
+    llvm-profdata merge default_*profraw --output=default.profdata
+  '';
+
+  # Install example themes which can be added to foot.ini via the include
+  # directive to a separate output to save a bit of space
+  postInstall = ''
+    moveToOutput share/foot/themes "$themes"
+  '';
+
+  outputs = [ "out" "terminfo" "themes" ];
+
+  passthru.tests = {
+    clang-default-compilation = foot.override {
+      inherit (llvmPackages) stdenv;
+    };
+
+    clang-latest-compilation = foot.override {
+      inherit (llvmPackages_latest) stdenv;
+    };
+
+    noPgo = foot.override {
+      allowPgo = false;
+    };
+
+    # By changing name, this will get rebuilt everytime we change version,
+    # even if the hash stays the same. Consequently it'll fail if we introduce
+    # a hash mismatch when updating.
+    stimulus-script-is-current = stimulusGenerator.src.overrideAttrs (_: {
+      name = "generate-alt-random-writes-${version}.py";
+    });
+  };
+
+  meta = with lib; {
+    homepage = "https://codeberg.org/dnkl/foot/";
+    changelog = "https://codeberg.org/dnkl/foot/releases/tag/${version}";
+    description = "A fast, lightweight and minimalistic Wayland terminal emulator";
+    license = licenses.mit;
+    maintainers = [ maintainers.sternenseemann ];
+    platforms = platforms.linux;
+    # From (presumably) ncurses version 6.3, it will ship a foot
+    # terminfo file. This however won't include some non-standard
+    # capabilities foot's bundled terminfo file contains. Unless we
+    # want to have some features in e. g. vim or tmux stop working,
+    # we need to make sure that the foot terminfo overwrites ncurses'
+    # one. Due to <nixpkgs/nixos/modules/config/system-path.nix>
+    # ncurses is always added to environment.systemPackages on
+    # NixOS with its priority increased by 3, so we need to go
+    # one bigger.
+    # This doesn't matter a lot for local use since foot sets
+    # TERMINFO to a store path, but allows installing foot.terminfo
+    # on remote systems for proper foot terminfo support.
+    priority = (ncurses.meta.priority or 5) + 3 + 1;
+  };
+}