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.