This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch tymux
in repository terminology.

View the commit online.

commit c9cdd24352c16c51b4d25d2b8c9f5a99c758e128
Author: [email protected] <[email protected]>
AuthorDate: Mon Mar 23 22:17:36 2026 -0600

    feat(tymux): CLI flags --new-tab/--shared/--readonly and PAUSE/RESUME handling
    
    - Parse --new-tab (-t), --shared (-s), --readonly (-r) flags before the
      session name in cmd_attach(); reject unknown options early with usage hint.
    - --new-tab inside Terminology emits \033}atn;<name>\0 and exits cleanly;
      outside Terminology it warns and falls back to inline attach.
    - Normal attach continues to emit \033}at;<name>\0.
    - ATTACH payload now carries a mode byte (0=HIJACK, 1=SHARED, 2=READONLY).
    - Add _TSRV_MSG_PAUSE (12) and _TSRV_MSG_RESUME (13) to the local enum and
      their payload structs _TsrvMsgPause / _TsrvMsgResume.
    - PAUSE handler: clears screen, renders centred PAUSED banner, enters a
      paused sub-loop that drains stdin (detach key only), checks SIGINT, and
      waits for RESUME; on RESUME it resets delta state, sends a fresh RESIZE,
      and returns to normal rendering.  EXIT inside the sub-loop is handled too.
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
 src/bin/tymux.c | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 245 insertions(+), 29 deletions(-)

diff --git a/src/bin/tymux.c b/src/bin/tymux.c
index 966dbe7a..e24d5575 100644
--- a/src/bin/tymux.c
+++ b/src/bin/tymux.c
@@ -102,6 +102,8 @@ typedef enum {
    _TSRV_MSG_EXIT            = 9,
    _TSRV_MSG_BACKLOG_BUF_REQ = 10,
    _TSRV_MSG_BACKLOG_BUF     = 11,
+   _TSRV_MSG_PAUSE           = 12,
+   _TSRV_MSG_RESUME          = 13,
 } _TsrvMsgType;
 
 typedef struct {
@@ -122,6 +124,16 @@ typedef struct {
    uint32_t rows;
 } _TsrvMsgResize;
 
+typedef struct {
+   uint32_t cols;
+   uint32_t rows;
+} _TsrvMsgPause;
+
+typedef struct {
+   uint32_t cols;
+   uint32_t rows;
+} _TsrvMsgResume;
+
 typedef struct {
    uint64_t write_seq;
 } _TsrvMsgNotify;
@@ -1016,14 +1028,41 @@ cmd_new(int argc, char **argv)
 static int
 cmd_attach(int argc, char **argv)
 {
-   if (argc < 3)
+   int         new_tab = 0;
+   int         mode    = 0; /* 0=HIJACK, 1=SHARED, 2=READONLY */
+   const char *name    = NULL;
+   int         i;
+
+   for (i = 2; i < argc; i++)
      {
-        fprintf(stderr, "tymux attach: name required\n");
-        fprintf(stderr, "Usage: tymux attach <name>\n");
-        return 1;
+        if (strcmp(argv[i], "--new-tab") == 0 || strcmp(argv[i], "-t") == 0)
+          new_tab = 1;
+        else if (strcmp(argv[i], "--shared") == 0 || strcmp(argv[i], "-s") == 0)
+          mode = 1;
+        else if (strcmp(argv[i], "--readonly") == 0 || strcmp(argv[i], "-r") == 0)
+          mode = 2;
+        else if (argv[i][0] == '-')
+          {
+             fprintf(stderr, "tymux attach: unknown option '%s'\n", argv[i]);
+             return 1;
+          }
+        else
+          {
+             if (name)
+               {
+                  fprintf(stderr, "tymux attach: unexpected argument '%s'\n", argv[i]);
+                  return 1;
+               }
+             name = argv[i];
+          }
      }
 
-   const char *name = argv[2];
+   if (!name)
+     {
+        fprintf(stderr, "tymux attach: name required\n");
+        fprintf(stderr, "Usage: tymux attach [--new-tab] [--shared|--readonly] <name>\n");
+        return 1;
+     }
 
    if (strlen(name) > TYMUX_MAX_NAME)
      {
@@ -1053,25 +1092,50 @@ cmd_attach(int argc, char **argv)
      }
 
    /* If running inside Terminology, ask it to take over natively.
-    * Write \033}at;<name>\0 — Terminology's escape parser catches \033}
-    * sequences and the 'at' opcode triggers _termio_tymux_switch().
-    * If Terminology handles it, our PTY is killed and we exit below via
-    * the proxy loop's EOF path.  If we are in a different terminal the
-    * escape is silently consumed and we fall through to the proxy path.
+    *
+    * --new-tab: emit \033}atn;<name>\0 — Terminology opens the session in a
+    * new tab and this process exits cleanly; the proxy loop is not entered.
+    * If TERMINOLOGY is not set we warn and fall through to inline attach.
+    *
+    * Normal attach: emit \033}at;<name>\0 — Terminology's escape parser
+    * catches \033} sequences and the 'at' opcode triggers
+    * _termio_tymux_switch().  If Terminology handles it, our PTY is killed
+    * and we exit below via the proxy loop's EOF path.  If we are in a
+    * different terminal the escape is silently consumed and we fall through
+    * to the proxy path.
     *
     * The liveness check is performed first so we never send the escape
-    * for a session that does not exist — Terminology would then attempt
-    * to attach to a dead socket and log a spurious error. */
-   {
-      char esc[128];
-      int  n = snprintf(esc, sizeof(esc), "\033}at;%s", name);
-      if (n > 0 && (size_t)n < sizeof(esc))
-        {
-           ssize_t written = write(STDOUT_FILENO, esc, (size_t)(n + 1)); /* +1 for NUL */
-           if (written < 0 || written != (ssize_t)(n + 1))
-             fprintf(stderr, "tymux: warning: escape delivery failed\n");
-        }
-   }
+    * for a session that does not exist. */
+   if (new_tab)
+     {
+        if (getenv("TERMINOLOGY"))
+          {
+             char esc[128];
+             int  n = snprintf(esc, sizeof(esc), "\033}atn;%s", name);
+             if (n > 0 && (size_t)n < sizeof(esc))
+               {
+                  ssize_t written = write(STDOUT_FILENO, esc, (size_t)(n + 1));
+                  if (written < 0 || written != (ssize_t)(n + 1))
+                    fprintf(stderr, "tymux: warning: escape delivery failed\n");
+               }
+             return 0;
+          }
+        else
+          fprintf(stderr,
+                  "tymux: --new-tab requires Terminology; "
+                  "falling back to inline attach\n");
+     }
+   else
+     {
+        char esc[128];
+        int  n = snprintf(esc, sizeof(esc), "\033}at;%s", name);
+        if (n > 0 && (size_t)n < sizeof(esc))
+          {
+             ssize_t written = write(STDOUT_FILENO, esc, (size_t)(n + 1)); /* +1 for NUL */
+             if (written < 0 || written != (ssize_t)(n + 1))
+               fprintf(stderr, "tymux: warning: escape delivery failed\n");
+          }
+     }
 
    /* ── Connect ── */
    int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
@@ -1089,13 +1153,16 @@ cmd_attach(int argc, char **argv)
         return 1;
      }
 
-   /* ── Send ATTACH ── */
-   if (_proto_send(sock, _TSRV_MSG_ATTACH, NULL, 0) < 0)
-     {
-        fprintf(stderr, "tymux: failed to send ATTACH\n");
-        close(sock);
-        return 1;
-     }
+   /* ── Send ATTACH (with mode byte: 0=HIJACK, 1=SHARED, 2=READONLY) ── */
+   {
+      uint8_t m = (uint8_t)mode;
+      if (_proto_send(sock, _TSRV_MSG_ATTACH, &m, sizeof(m)) < 0)
+        {
+           fprintf(stderr, "tymux: failed to send ATTACH\n");
+           close(sock);
+           return 1;
+        }
+   }
 
    /* ── Receive ATTACHED + memfd ── */
    int      shm_fd = -1;
@@ -1319,6 +1386,155 @@ cmd_attach(int argc, char **argv)
                    /* Proxy does not handle backlog buffers */
                    break;
 
+                case _TSRV_MSG_PAUSE:
+                   {
+                      /* Clear screen and show a centered PAUSED banner */
+                      write(STDOUT_FILENO, "\033[2J\033[H", 7);
+                      {
+                         /* Center "PAUSED" on the current terminal */
+                         memset(&ws, 0, sizeof(ws));
+                         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0 ||
+                             ws.ws_col == 0)
+                           {
+                              ws.ws_col = 80;
+                              ws.ws_row = 24;
+                           }
+                         int col = ((int)ws.ws_col - 6) / 2 + 1; /* 6 = len("PAUSED") */
+                         int row = (int)ws.ws_row / 2;
+                         if (col < 1) col = 1;
+                         if (row < 1) row = 1;
+                         char banner[64];
+                         int blen = snprintf(banner, sizeof(banner),
+                                             "\033[%d;%dH\033[7m PAUSED \033[0m",
+                                             row, col);
+                         if (blen > 0)
+                           write(STDOUT_FILENO, banner, (size_t)blen);
+                      }
+
+                      /* ── Paused sub-loop ── */
+                      int paused = 1;
+                      while (paused && running)
+                        {
+                           struct pollfd pfds[2];
+                           pfds[0].fd     = STDIN_FILENO;
+                           pfds[0].events = POLLIN;
+                           pfds[1].fd     = sock;
+                           pfds[1].events = POLLIN;
+
+                           int pr = poll(pfds, 2, 200);
+                           if (pr < 0)
+                             {
+                                if (errno == EINTR)
+                                  {
+                                     if (_got_int)
+                                       {
+                                          _got_int = 0;
+                                          write(STDERR_FILENO,
+                                                "\r\ntymux: received signal, detaching\r\n",
+                                                37);
+                                          _proto_send(sock, _TSRV_MSG_DETACH, NULL, 0);
+                                          running = 0;
+                                       }
+                                     continue;
+                                  }
+                                running = 0;
+                                break;
+                             }
+
+                           /* Drain stdin — only check for detach key */
+                           if (pfds[0].revents & POLLIN)
+                             {
+                                char ibuf[256];
+                                ssize_t nr = read(STDIN_FILENO, ibuf, sizeof(ibuf));
+                                if (nr > 0)
+                                  {
+                                     for (ssize_t si = 0; si < nr; si++)
+                                       {
+                                          if ((unsigned char)ibuf[si] == 0x1D)
+                                            {
+                                               _proto_send(sock, _TSRV_MSG_DETACH, NULL, 0);
+                                               running = 0;
+                                               goto detach_exit;
+                                            }
+                                       }
+                                  }
+                                else if (nr == 0)
+                                  {
+                                     running = 0;
+                                     break;
+                                  }
+                             }
+
+                           /* Check for SIGINT */
+                           if (_got_int)
+                             {
+                                _got_int = 0;
+                                write(STDERR_FILENO,
+                                      "\r\ntymux: received signal, detaching\r\n", 37);
+                                _proto_send(sock, _TSRV_MSG_DETACH, NULL, 0);
+                                running = 0;
+                                break;
+                             }
+
+                           /* Drain daemon messages */
+                           if (pfds[1].revents & (POLLIN | POLLHUP | POLLERR))
+                             {
+                                void    *ppayload = NULL;
+                                uint32_t ppsize   = 0;
+                                int      pmtype   = _proto_recv(sock, &ppayload, &ppsize);
+                                if (pmtype < 0)
+                                  {
+                                     free(ppayload);
+                                     running = 0;
+                                     break;
+                                  }
+                                if (pmtype == (_TSRV_MSG_RESUME))
+                                  {
+                                     /* Resume: free delta state (force full repaint),
+                                      * send a fresh RESIZE so daemon knows our size,
+                                      * then break out of the paused loop. */
+                                     free(prev_cells);
+                                     prev_cells = NULL;
+                                     prev_cols  = 0;
+                                     prev_rows  = 0;
+                                     memset(&ws, 0, sizeof(ws));
+                                     if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 &&
+                                         ws.ws_col > 0 && ws.ws_row > 0)
+                                       {
+                                          _TsrvMsgResize rszm = {
+                                             .cols = ws.ws_col,
+                                             .rows = ws.ws_row,
+                                          };
+                                          _proto_send(sock, _TSRV_MSG_RESIZE,
+                                                      &rszm, sizeof(rszm));
+                                       }
+                                     free(ppayload);
+                                     paused = 0;
+                                     break;
+                                  }
+                                else if (pmtype == (_TSRV_MSG_EXIT))
+                                  {
+                                     if (ppayload && ppsize >= sizeof(_TsrvMsgExit))
+                                       {
+                                          _TsrvMsgExit ep2;
+                                          memcpy(&ep2, ppayload, sizeof(ep2));
+                                          exit_code = (int)ep2.exit_code;
+                                       }
+                                     free(ppayload);
+                                     running = 0;
+                                     break;
+                                  }
+                                /* All other messages: drain and discard */
+                                free(ppayload);
+                             }
+                        }
+                   }
+                   break;
+
+                case _TSRV_MSG_RESUME:
+                   /* Should not arrive outside the paused sub-loop — ignore */
+                   break;
+
                 default:
                    /* Unknown or unexpected message — ignore */
                    break;

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to