Fix top: use FFI for terminal control instead of shelling out

ober

92001ceae28b5156f7cfe03c479f8b05ab966f1a

diff --git a/lib/jerboa-coreutils/top.sls b/lib/jerboa-coreutils/top.sls
index e2a60c9..779b7f2 100644
--- a/lib/jerboa-coreutils/top.sls
+++ b/lib/jerboa-coreutils/top.sls
@@ -43,17 +43,45 @@
           (only (std sugar) with-catch)
           (only (std format) eprintf format)
           (std cli getopt)
-          (std misc terminal)
-          (only (std os tty) tty? tty-size)
+          (except (std misc terminal)
+            terminal-width terminal-height
+            with-raw-mode with-alternate-screen)
           (jerboa-coreutils common)
           (jerboa-coreutils common version)
           (jerboa-coreutils common security))
 
-  ;; ========== FFI for kill/renice ==========
+  ;; ========== FFI for kill/renice/terminal ==========
 
   (define _load-ffi (begin (load-shared-object #f) (void)))
   (define ffi-kill (foreign-procedure "kill" (int int) int))
   (define ffi-setpriority (foreign-procedure "setpriority" (int int int) int))
+  (define ffi-terminal-width (foreign-procedure "coreutils_terminal_width" (int) int))
+  (define ffi-terminal-height (foreign-procedure "coreutils_terminal_height" (int) int))
+  (define ffi-raw-mode-enter (foreign-procedure "coreutils_raw_mode_enter" (int) int))
+  (define ffi-raw-mode-exit (foreign-procedure "coreutils_raw_mode_exit" (int) int))
+
+  ;; ========== Terminal helpers (FFI-based, no fork/exec) ==========
+
+  (define (terminal-width) (ffi-terminal-width 1))
+  (define (terminal-height) (ffi-terminal-height 1))
+
+  (define esc "\x1b;")
+
+  (define (with-raw-mode thunk)
+    (dynamic-wind
+      (lambda () (ffi-raw-mode-enter 0))
+      thunk
+      (lambda () (ffi-raw-mode-exit 0))))
+
+  (define (with-alternate-screen thunk)
+    (dynamic-wind
+      (lambda ()
+        (display (string-append esc "[?1049h"))
+        (flush-output-port (current-output-port)))
+      thunk
+      (lambda ()
+        (display (string-append esc "[?1049l"))
+        (flush-output-port (current-output-port)))))
 
   ;; ========== /proc Readers ==========
 
diff --git a/support/libcoreutils.c b/support/libcoreutils.c
index 4ec0a44..8b09722 100644
--- a/support/libcoreutils.c
+++ b/support/libcoreutils.c
@@ -215,6 +215,37 @@ int coreutils_terminal_width(int fd) {
     return (int)ws.ws_col;
 }
 
+int coreutils_terminal_height(int fd) {
+    struct winsize ws;
+    if (ioctl(fd, TIOCGWINSZ, &ws) < 0 || ws.ws_row == 0)
+        return 24;
+    return (int)ws.ws_row;
+}
+
+/* Raw mode via tcgetattr/tcsetattr — no fork/exec needed */
+#include <termios.h>
+static struct termios coreutils_saved_termios;
+static int coreutils_termios_saved = 0;
+
+int coreutils_raw_mode_enter(int fd) {
+    struct termios raw;
+    if (tcgetattr(fd, &coreutils_saved_termios) < 0) return -1;
+    coreutils_termios_saved = 1;
+    raw = coreutils_saved_termios;
+    raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
+    raw.c_iflag &= ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP);
+    raw.c_oflag &= ~(OPOST);
+    raw.c_cc[VMIN] = 0;
+    raw.c_cc[VTIME] = 0;
+    return tcsetattr(fd, TCSAFLUSH, &raw);
+}
+
+int coreutils_raw_mode_exit(int fd) {
+    if (!coreutils_termios_saved) return -1;
+    coreutils_termios_saved = 0;
+    return tcsetattr(fd, TCSAFLUSH, &coreutils_saved_termios);
+}
+
 static char ls_time_buf[64];
 const char* coreutils_time_format(long t) {
     time_t tt = (time_t)t;
diff --git a/support/libcoreutils.so b/support/libcoreutils.so
index 251a580..11ef13a 100755
Binary files a/support/libcoreutils.so and b/support/libcoreutils.so differ