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

git pushed a commit to reference refs/pull/36/head
in repository terminology.

View the commit online.

commit bcfb95d0a50e9feaaa91fafd866b163055994e10
Author: [email protected] <[email protected]>
AuthorDate: Sun Mar 15 21:23:43 2026 -0600

    feat: implement tymux daemon session with memfd-backed shared memory
    
    Introduces TermDSession: the core daemon-side session logic that manages
    one PTY and a memfd-based circular buffer for screen state. Each session
    forks a shell, creates a shared memory buffer, and tracks all connected
    clients via Ecore fd handlers.
    
    Key components:
    
    - TermDSession struct: owns PTY, mmap'd shm header/cells, client list
    - memfd-backed shm buffer with atomic write_seq counter for ABA-safe updates
    - Screen buffer flattening: converts Termpty's circular buffer layout to
      linear shm cells, handling both primary and alternate screen modes
    - Four Termpty callbacks relay screen changes, title, bell, and exit events
      to all connected clients via NOTIFY/TITLE/BELL/EXIT messages
    - Client fd handler dispatches INPUT/RESIZE/DETACH messages back to PTY
    - Synthetic Config object for headless operation (no GUI needed)
    
    Also fixes termsrv.c first include from config.h to private.h (was
    resolving to wrong header, breaking HAVE_TYMUX preprocessor gate).
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 src/bin/termsrv.c       |   2 +-
 src/bin/tymux_session.c | 437 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/bin/tymux_session.h |  22 ++-
 3 files changed, 459 insertions(+), 2 deletions(-)

diff --git a/src/bin/termsrv.c b/src/bin/termsrv.c
index 16ca44f4..c3ac3845 100644
--- a/src/bin/termsrv.c
+++ b/src/bin/termsrv.c
@@ -1,4 +1,4 @@
-#include "config.h"
+#include "private.h"
 
 #ifdef HAVE_TYMUX
 
diff --git a/src/bin/tymux_session.c b/src/bin/tymux_session.c
index a0c719ae..873a59e9 100644
--- a/src/bin/tymux_session.c
+++ b/src/bin/tymux_session.c
@@ -1,2 +1,439 @@
 #include "private.h"
+
+#include <errno.h>
+#include <stdatomic.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <linux/memfd.h>
+
+#include <Eina.h>
+#include <Ecore.h>
+
 #include "tymux_session.h"
+#include "termpty.h"
+#include "termsrv.h"
+
+/* ── log domain ─────────────────────────────────────────────────── */
+
+static int _log_dom_session = -1;
+
+#undef  ERR
+#undef  WRN
+#undef  INF
+#undef  DBG
+#define ERR(...)  EINA_LOG_DOM_ERR(_log_dom_session, __VA_ARGS__)
+#define WRN(...)  EINA_LOG_DOM_WARN(_log_dom_session, __VA_ARGS__)
+#define INF(...)  EINA_LOG_DOM_INFO(_log_dom_session, __VA_ARGS__)
+#define DBG(...)  EINA_LOG_DOM_DBG(_log_dom_session, __VA_ARGS__)
+
+/* ── memfd_create wrapper ────────────────────────────────────────── */
+
+#ifndef __NR_memfd_create
+#  error "memfd_create syscall number unknown for this arch"
+#endif
+
+static int
+_memfd_create(const char *name, unsigned int flags)
+{
+   return (int)syscall(__NR_memfd_create, name, flags);
+}
+
+/* ── per-client state ────────────────────────────────────────────── */
+
+typedef struct _TermDClient
+{
+   int                fd;
+   Ecore_Fd_Handler  *handler;
+   TermDSession      *sess;
+} TermDClient;
+
+/* ── session state ───────────────────────────────────────────────── */
+
+struct _TermDSession
+{
+   char          *name;
+   Termpty       *pty;
+   Config        *config;
+   int            shm_fd;
+   TermShmHeader *shm;
+   Eina_List     *clients;
+};
+
+/* ── synthetic Config for headless use ───────────────────────────── */
+
+static Config *
+_default_config_new(void)
+{
+   Config *cfg = calloc(1, sizeof(Config));
+   if (!cfg)
+     return NULL;
+
+   cfg->scrollback      = 10000;
+   cfg->login_shell     = EINA_FALSE;
+   cfg->xterm_256color  = EINA_TRUE;
+   return cfg;
+}
+
+/* ── shm flush ───────────────────────────────────────────────────── */
+
+static void
+_flush_screen_to_shm(TermDSession *sess)
+{
+   Termpty       *ty       = sess->pty;
+   TermShmHeader *hdr      = sess->shm;
+   Termcell      *dst      = TERMSRV_SHM_CELLS(hdr);
+   int            w        = ty->w;
+   int            h        = ty->h;
+   int            cols, rows, y;
+   Termcell      *scr;
+   int            circ_off;
+
+   /* Select primary or alternate screen buffer */
+   if (ty->altbuf)
+     {
+        scr      = ty->screen2;
+        circ_off = ty->circular_offset2;
+     }
+   else
+     {
+        scr      = ty->screen;
+        circ_off = ty->circular_offset;
+     }
+
+   /* Clamp to shm capacity */
+   cols = (w < (int)TERMSRV_MAX_COLS) ? w : (int)TERMSRV_MAX_COLS;
+   rows = (h < (int)TERMSRV_MAX_ROWS) ? h : (int)TERMSRV_MAX_ROWS;
+
+   hdr->cols           = (uint32_t)cols;
+   hdr->rows           = (uint32_t)rows;
+   hdr->cursor_x       = (uint32_t)((ty->cursor_state.cx < cols) ? ty->cursor_state.cx : cols - 1);
+   hdr->cursor_y       = (uint32_t)((ty->cursor_state.cy < rows) ? ty->cursor_state.cy : rows - 1);
+   hdr->cursor_visible = ty->termstate.hide_cursor ? 0 : 1;
+
+   for (y = 0; y < rows; y++)
+     {
+        int       src_row = (y + circ_off) % h;
+        Termcell *src     = "" + (src_row * w);
+        Termcell *d       = dst + (y * cols);
+        memcpy(d, src, (size_t)cols * sizeof(Termcell));
+     }
+
+   uint64_t seq = atomic_load_explicit(&hdr->write_seq, memory_order_relaxed);
+   atomic_store_explicit(&hdr->write_seq, seq + 1, memory_order_release);
+}
+
+/* ── forward declarations ────────────────────────────────────────── */
+
+static void _client_remove(TermDSession *sess, TermDClient *client);
+
+/* ── Termpty callbacks ───────────────────────────────────────────── */
+
+static void
+_pty_change_cb(void *data)
+{
+   TermDSession     *sess = data;
+   TermSrvMsgNotify  pay;
+   Eina_List        *l;
+   TermDClient      *c;
+
+   _flush_screen_to_shm(sess);
+
+   pay.write_seq = atomic_load_explicit(&sess->shm->write_seq,
+                                        memory_order_acquire);
+   EINA_LIST_FOREACH(sess->clients, l, c)
+     termsrv_msg_send(c->fd, TSRV_MSG_NOTIFY, &pay, sizeof(pay));
+}
+
+static void
+_pty_title_cb(void *data)
+{
+   TermDSession *sess  = data;
+   const char   *title = sess->pty->prop.title;
+   Eina_List    *l;
+   TermDClient  *c;
+
+   if (!title) title = "";
+   EINA_LIST_FOREACH(sess->clients, l, c)
+     termsrv_msg_send(c->fd, TSRV_MSG_TITLE, title,
+                      (uint32_t)(strlen(title) + 1));
+}
+
+static void
+_pty_bell_cb(void *data)
+{
+   TermDSession *sess = data;
+   Eina_List    *l;
+   TermDClient  *c;
+
+   EINA_LIST_FOREACH(sess->clients, l, c)
+     termsrv_msg_send(c->fd, TSRV_MSG_BELL, NULL, 0);
+}
+
+static void
+_pty_exit_cb(void *data)
+{
+   TermDSession    *sess = data;
+   TermSrvMsgExit   pay;
+   Eina_List       *l;
+   TermDClient     *c;
+
+   pay.exit_code = (int32_t)sess->pty->exit_code;
+   EINA_LIST_FOREACH(sess->clients, l, c)
+     termsrv_msg_send(c->fd, TSRV_MSG_EXIT, &pay, sizeof(pay));
+
+   ecore_main_loop_quit();
+}
+
+/* ── client fd handler ───────────────────────────────────────────── */
+
+static Eina_Bool
+_cb_client_data(void *data, Ecore_Fd_Handler *fd_handler EINA_UNUSED)
+{
+   TermDClient  *client = data;
+   TermDSession *sess   = client->sess;
+   void         *payload;
+   uint32_t      size;
+   int           type;
+
+   type = termsrv_msg_recv(client->fd, &payload, &size);
+   if (type < 0)
+     {
+        _client_remove(sess, client);
+        return ECORE_CALLBACK_CANCEL;
+     }
+
+   switch ((TermSrvMsgType)type)
+     {
+      case TSRV_MSG_INPUT:
+         if (payload && size > 0)
+           termpty_write(sess->pty, (const char *)payload, (int)size);
+         break;
+
+      case TSRV_MSG_RESIZE:
+         if (payload && size >= sizeof(TermSrvMsgResize))
+           {
+              TermSrvMsgResize *r = payload;
+              uint32_t cols = r->cols;
+              uint32_t rows = r->rows;
+              if (cols > 0 && rows > 0 &&
+                  cols <= TERMSRV_MAX_COLS && rows <= TERMSRV_MAX_ROWS)
+                {
+                   termpty_resize(sess->pty, (int)cols, (int)rows);
+                   _flush_screen_to_shm(sess);
+                }
+           }
+         break;
+
+      case TSRV_MSG_DETACH:
+         free(payload);
+         _client_remove(sess, client);
+         return ECORE_CALLBACK_CANCEL;
+
+      default:
+         break;
+     }
+
+   free(payload);
+   return ECORE_CALLBACK_RENEW;
+}
+
+/* ── client lifecycle ────────────────────────────────────────────── */
+
+static void
+_client_remove(TermDSession *sess, TermDClient *client)
+{
+   sess->clients = eina_list_remove(sess->clients, client);
+   if (client->handler)
+     {
+        ecore_main_fd_handler_del(client->handler);
+        client->handler = NULL;
+     }
+   close(client->fd);
+   free(client);
+}
+
+/* ── public API ──────────────────────────────────────────────────── */
+
+TermDSession *
+termd_session_new(const char *name, const char *cmd)
+{
+   TermDSession *sess;
+   char          memfd_name[128];
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(name, NULL);
+
+   if (_log_dom_session < 0)
+     {
+        _log_dom_session = eina_log_domain_register("tymux_session", NULL);
+        if (_log_dom_session < 0)
+          EINA_LOG_ERR("Could not create log domain 'tymux_session'");
+     }
+
+   sess = calloc(1, sizeof(*sess));
+   if (!sess)
+     return NULL;
+   sess->shm_fd = -1;
+
+   sess->name = strdup(name);
+   if (!sess->name)
+     goto err_name;
+
+   sess->config = _default_config_new();
+   if (!sess->config)
+     goto err_config;
+
+   /* memfd name is advisory; truncate safely */
+   snprintf(memfd_name, sizeof(memfd_name), "ty-session-%s", name);
+   sess->shm_fd = _memfd_create(memfd_name, MFD_CLOEXEC);
+   if (sess->shm_fd < 0)
+     {
+        ERR("memfd_create failed: %s", strerror(errno));
+        goto err_memfd;
+     }
+
+   if (ftruncate(sess->shm_fd, (off_t)TERMSRV_SHM_TOTAL_SIZE) < 0)
+     {
+        ERR("ftruncate failed: %s", strerror(errno));
+        goto err_ftrunc;
+     }
+
+   sess->shm = mmap(NULL, TERMSRV_SHM_TOTAL_SIZE,
+                    PROT_READ | PROT_WRITE, MAP_SHARED,
+                    sess->shm_fd, 0);
+   if (sess->shm == MAP_FAILED)
+     {
+        ERR("mmap failed: %s", strerror(errno));
+        sess->shm = NULL;
+        goto err_mmap;
+     }
+
+   sess->shm->magic   = TERMSRV_SHM_MAGIC;
+   sess->shm->version = TERMSRV_SHM_VERSION;
+   atomic_store_explicit(&sess->shm->write_seq, 0, memory_order_relaxed);
+
+   /* Create PTY: 80×24 initial size, no login shell */
+   sess->pty = termpty_new(cmd, EINA_FALSE, NULL,
+                           80, 24,
+                           sess->config, name, 0);
+   if (!sess->pty)
+     {
+        ERR("termpty_new failed");
+        goto err_pty;
+     }
+
+   sess->pty->cb.change.func    = _pty_change_cb;
+   sess->pty->cb.change.data    = ""
+   sess->pty->cb.set_title.func = _pty_title_cb;
+   sess->pty->cb.set_title.data = ""
+   sess->pty->cb.bell.func      = _pty_bell_cb;
+   sess->pty->cb.bell.data      = ""
+   sess->pty->cb.exited.func    = _pty_exit_cb;
+   sess->pty->cb.exited.data    = ""
+
+   _flush_screen_to_shm(sess);
+
+   return sess;
+
+err_pty:
+   munmap(sess->shm, TERMSRV_SHM_TOTAL_SIZE);
+   sess->shm = NULL;
+err_mmap:
+err_ftrunc:
+   close(sess->shm_fd);
+   sess->shm_fd = -1;
+err_memfd:
+   free(sess->config);
+   sess->config = NULL;
+err_config:
+   free(sess->name);
+   sess->name = NULL;
+err_name:
+   free(sess);
+   return NULL;
+}
+
+void
+termd_session_free(TermDSession *sess)
+{
+   TermDClient *c;
+
+   if (!sess)
+     return;
+
+   EINA_LIST_FREE(sess->clients, c)
+     {
+        if (c->handler)
+          ecore_main_fd_handler_del(c->handler);
+        close(c->fd);
+        free(c);
+     }
+
+   if (sess->pty)
+     {
+        termpty_free(sess->pty);
+        sess->pty = NULL;
+     }
+
+   if (sess->shm)
+     {
+        munmap(sess->shm, TERMSRV_SHM_TOTAL_SIZE);
+        sess->shm = NULL;
+     }
+
+   if (sess->shm_fd >= 0)
+     {
+        close(sess->shm_fd);
+        sess->shm_fd = -1;
+     }
+
+   free(sess->config);
+   free(sess->name);
+   free(sess);
+}
+
+void
+termd_session_client_add(TermDSession *sess, int cfd)
+{
+   TermDClient *client;
+
+   EINA_SAFETY_ON_NULL_RETURN(sess);
+   if (cfd < 0)
+     return;
+
+   if (termsrv_send_attached(cfd, sess->shm_fd,
+                             (uint32_t)sess->pty->w,
+                             (uint32_t)sess->pty->h) < 0)
+     {
+        ERR("termsrv_send_attached failed for fd %d", cfd);
+        close(cfd);
+        return;
+     }
+
+   client = calloc(1, sizeof(*client));
+   if (!client)
+     {
+        ERR("calloc failed for TermDClient");
+        close(cfd);
+        return;
+     }
+
+   client->fd   = cfd;
+   client->sess = sess;
+   client->handler = ecore_main_fd_handler_add(cfd, ECORE_FD_READ,
+                                                _cb_client_data, client,
+                                                NULL, NULL);
+   if (!client->handler)
+     {
+        ERR("ecore_main_fd_handler_add failed for fd %d", cfd);
+        close(cfd);
+        free(client);
+        return;
+     }
+
+   sess->clients = eina_list_append(sess->clients, client);
+}
diff --git a/src/bin/tymux_session.h b/src/bin/tymux_session.h
index c283c658..682a4d7b 100644
--- a/src/bin/tymux_session.h
+++ b/src/bin/tymux_session.h
@@ -1,3 +1,23 @@
 #ifndef TERMINOLOGY_TYMUX_SESSION_H_
 #define TERMINOLOGY_TYMUX_SESSION_H_
-#endif
+
+#include <stdint.h>
+#include <Eina.h>
+#include "termpty.h"
+#include "termsrv.h"
+
+typedef struct _TermDSession TermDSession;
+
+/* Create the session: fork shell, set up memfd.
+ * cmd may be NULL (uses user's default shell).
+ * Returns NULL on failure. */
+TermDSession *termd_session_new(const char *name, const char *cmd);
+
+/* Free the session (kills shell, munmaps, closes memfd). */
+void          termd_session_free(TermDSession *sess);
+
+/* Add a connected client fd; takes ownership, sets up Ecore fd handler.
+ * Sends ATTACHED immediately. */
+void          termd_session_client_add(TermDSession *sess, int cfd);
+
+#endif /* TERMINOLOGY_TYMUX_SESSION_H_ */

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

Reply via email to