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 2ae762c2842bcf39471c7c108e4731e2f3bbe0b5
Author: [email protected] <[email protected]>
AuthorDate: Sun Mar 15 21:33:42 2026 -0600
feat: implement tymuxd daemon for session-local socket IPC
The tymuxd daemon runs one-per-session and provides a Unix socket
endpoint for tymux clients to connect to. It:
- Takes a session name argument and validates its length
- Initializes Eina/Ecore and configures SIGPIPE to be ignored
- Detects stale or duplicate sessions via probe connection
- Creates a non-blocking Unix socket at
$XDG_RUNTIME_DIR/terminology/tymux/<session>.sock
- Binds the socket and enters Ecore's main loop to accept clients
- Receives ATTACH messages and adds clients to the session PTY
- Cleans up socket and session state on exit
This enables the IPC recovery pattern where tymux clients reconnect
to an already-running shell session in the same process rather than
spawning new shells each time.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
src/bin/tymuxd.c | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
src/bin/tymuxd.h | 3 +-
2 files changed, 247 insertions(+), 4 deletions(-)
diff --git a/src/bin/tymuxd.c b/src/bin/tymuxd.c
index 6e718a85..2d777152 100644
--- a/src/bin/tymuxd.c
+++ b/src/bin/tymuxd.c
@@ -1,11 +1,253 @@
#include "private.h"
#include "tymuxd.h"
+#include "tymux_session.h"
+#include "termsrv.h"
+
+#include <Ecore.h>
+#include <Eina.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+/* ── log domain ──────────────────────────────────────────────────── */
int _log_domain = -1;
-int main(int argc, char **argv)
+/* ── global state ────────────────────────────────────────────────── */
+
+static TermDSession *_session = NULL;
+static int _listen_fd = -1;
+static char _sock_path[512];
+static Ecore_Fd_Handler *_listen_handler = NULL;
+
+/* ── accept loop ─────────────────────────────────────────────────── */
+
+static Eina_Bool
+_cb_client_connect(void *data EINA_UNUSED,
+ Ecore_Fd_Handler *fd_handler EINA_UNUSED)
{
- (void)argc;
- (void)argv;
+ int cfd;
+ void *payload = NULL;
+ uint32_t size = 0;
+ int type;
+
+ cfd = accept(_listen_fd, NULL, NULL);
+ if (cfd < 0)
+ {
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ ERR("accept: %s", strerror(errno));
+ return ECORE_CALLBACK_RENEW;
+ }
+
+ {
+ int flags = fcntl(cfd, F_GETFL, 0);
+ if (flags >= 0) fcntl(cfd, F_SETFL, flags | O_NONBLOCK);
+ }
+
+ type = termsrv_msg_recv(cfd, &payload, &size);
+ if (type != TSRV_MSG_ATTACH)
+ {
+ ERR("expected ATTACH from new client (got %d), closing fd %d", type, cfd);
+ free(payload);
+ close(cfd);
+ return ECORE_CALLBACK_RENEW;
+ }
+
+ free(payload);
+
+ termd_session_client_add(_session, cfd);
+ return ECORE_CALLBACK_RENEW;
+}
+
+/* ── entry point ─────────────────────────────────────────────────── */
+
+int
+main(int argc, char **argv)
+{
+ const char *name;
+ int probe_fd;
+ struct sockaddr_un sa;
+
+ /* ── argument validation ────────────────────────────────────── */
+
+ if (argc != 2 || argv[1][0] == '-')
+ {
+ fprintf(stderr, "Usage: tymuxd <session-name>\n");
+ return 1;
+ }
+
+ name = argv[1];
+
+ if (strlen(name) > TSRV_MAX_SESSION_NAME)
+ {
+ fprintf(stderr,
+ "tymuxd: session name too long (max %d characters)\n",
+ TSRV_MAX_SESSION_NAME);
+ return 1;
+ }
+
+ /* ── EFL init ───────────────────────────────────────────────── */
+
+ eina_init();
+ ecore_init();
+
+ _log_domain = eina_log_domain_register("tymuxd", NULL);
+ if (_log_domain < 0)
+ EINA_LOG_ERR("Could not create log domain 'tymuxd'");
+
+ /* Prevent SIGPIPE from killing the daemon on broken socket writes */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* ── socket directory ───────────────────────────────────────── */
+
+ if (termsrv_ensure_sessions_dir() < 0)
+ {
+ ERR("could not create sessions directory: %s", strerror(errno));
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ if (termsrv_socket_path(name, _sock_path, sizeof(_sock_path)) < 0)
+ {
+ ERR("socket path too long for session '%s'", name);
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ /* ── duplicate detection ────────────────────────────────────── */
+
+ probe_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (probe_fd < 0)
+ {
+ ERR("socket (probe): %s", strerror(errno));
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strncpy(sa.sun_path, _sock_path, sizeof(sa.sun_path) - 1);
+
+ if (connect(probe_fd, (struct sockaddr *)&sa, sizeof(sa)) == 0)
+ {
+ /* A live daemon is already accepting on this socket */
+ close(probe_fd);
+ fprintf(stderr, "tymuxd: session '%s' already running\n", name);
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ close(probe_fd);
+
+ /* Stale socket file from a previous crash — remove it */
+ unlink(_sock_path);
+
+ /* ── create listen socket ───────────────────────────────────── */
+
+ _listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (_listen_fd < 0)
+ {
+ ERR("socket (listen): %s", strerror(errno));
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strncpy(sa.sun_path, _sock_path, sizeof(sa.sun_path) - 1);
+
+ if (bind(_listen_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
+ {
+ ERR("bind '%s': %s", _sock_path, strerror(errno));
+ close(_listen_fd);
+ _listen_fd = -1;
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ if (listen(_listen_fd, 8) < 0)
+ {
+ ERR("listen: %s", strerror(errno));
+ unlink(_sock_path);
+ close(_listen_fd);
+ _listen_fd = -1;
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ /* ── create session ─────────────────────────────────────────── */
+
+ _session = termd_session_new(name, NULL);
+ if (!_session)
+ {
+ ERR("termd_session_new failed");
+ unlink(_sock_path);
+ close(_listen_fd);
+ _listen_fd = -1;
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ /* ── register fd handler ────────────────────────────────────── */
+
+ _listen_handler = ecore_main_fd_handler_add(_listen_fd, ECORE_FD_READ,
+ _cb_client_connect, NULL,
+ NULL, NULL);
+ if (!_listen_handler)
+ {
+ ERR("ecore_main_fd_handler_add failed");
+ termd_session_free(_session);
+ _session = NULL;
+ unlink(_sock_path);
+ close(_listen_fd);
+ _listen_fd = -1;
+ ecore_shutdown();
+ eina_shutdown();
+ return 1;
+ }
+
+ INF("tymuxd: session '%s' ready at %s", name, _sock_path);
+
+ /* ── main loop ──────────────────────────────────────────────── */
+
+ ecore_main_loop_begin();
+
+ /* ── cleanup ────────────────────────────────────────────────── */
+
+ if (_listen_handler)
+ {
+ ecore_main_fd_handler_del(_listen_handler);
+ _listen_handler = NULL;
+ }
+
+ if (_listen_fd >= 0)
+ {
+ close(_listen_fd);
+ _listen_fd = -1;
+ }
+
+ unlink(_sock_path);
+
+ termd_session_free(_session);
+ _session = NULL;
+
+ ecore_shutdown();
+ eina_shutdown();
+
return 0;
}
diff --git a/src/bin/tymuxd.h b/src/bin/tymuxd.h
index 5f449927..cd8be4c2 100644
--- a/src/bin/tymuxd.h
+++ b/src/bin/tymuxd.h
@@ -1,3 +1,4 @@
#ifndef TERMINOLOGY_TYMUXD_H_
#define TERMINOLOGY_TYMUXD_H_
-#endif
+/* Usage: tymuxd <session-name> */
+#endif /* TERMINOLOGY_TYMUXD_H_ */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.