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

git pushed a commit to branch master
in repository terminology.

View the commit online.

commit 2088547268a593d229e8b2a5b95691d319388519
Author: Boris Faure <[email protected]>
AuthorDate: Sat Sep 19 16:17:15 2026 +0000

    termptyesc: handle OSC 7
    
    Store the working directory the shell reports through
    OSC 7 ; file://<host>/<path> ST in prop.cwd, beside title and icon.
---
 src/bin/meson.build  |  2 ++
 src/bin/termpty.c    |  1 +
 src/bin/termpty.h    |  2 ++
 src/bin/termptyesc.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 102 insertions(+)

diff --git a/src/bin/meson.build b/src/bin/meson.build
index 0962a6a3..a68db65c 100644
--- a/src/bin/meson.build
+++ b/src/bin/meson.build
@@ -71,6 +71,7 @@ tyfuzz_sources = ['termptyesc.c', 'termptyesc.h',
                   'utils.c', 'utils.h',
                   'utf8.c', 'utf8.h',
                   simd_sources,
+                  'uri_decode.c', 'uri_decode.h',
                   'tytest_common.c', 'tytest_common.h',
                   'tyfuzz.c']
 tytest_sources = ['termptyesc.c', 'termptyesc.h',
@@ -112,6 +113,7 @@ tybench_sources = ['termptyesc.c', 'termptyesc.h',
                   'utils.c', 'utils.h',
                   'utf8.c', 'utf8.h',
                   simd_sources,
+                  'uri_decode.c', 'uri_decode.h',
                   'tytest_common.c', 'tytest_common.h',
                   'tybench.c']
 
diff --git a/src/bin/termpty.c b/src/bin/termpty.c
index 09d501c8..a2b2c4e3 100644
--- a/src/bin/termpty.c
+++ b/src/bin/termpty.c
@@ -893,6 +893,7 @@ termpty_free(Termpty *ty)
    eina_stringshare_del(ty->prop.title);
    eina_stringshare_del(ty->prop.user_title);
    eina_stringshare_del(ty->prop.icon);
+   eina_stringshare_del(ty->prop.cwd);
    termpty_backlog_free(ty);
    free(ty->screen);
    free(ty->screen2);
diff --git a/src/bin/termpty.h b/src/bin/termpty.h
index bee5cfdd..b9ccee35 100644
--- a/src/bin/termpty.h
+++ b/src/bin/termpty.h
@@ -175,6 +175,8 @@ struct tag_Termpty
       const char *title;
       /* set by user */
       const char *user_title;
+      /* working directory reported by the shell through OSC 7 */
+      const char *cwd;
    } prop;
    const char *cur_cmd;
    Termcell *screen, *screen2;
diff --git a/src/bin/termptyesc.c b/src/bin/termptyesc.c
index e33ca621..0457dba3 100644
--- a/src/bin/termptyesc.c
+++ b/src/bin/termptyesc.c
@@ -14,6 +14,9 @@
 #include "tytest.h"
 #endif
 #include "utils.h"
+#include "uri_decode.h"
+#include <limits.h>
+#include <unistd.h>
 
 #undef CRITICAL
 #undef ERR
@@ -4628,6 +4631,82 @@ err:
    ty->decoding_error = EINA_TRUE;
 }
 
+/* Only an empty host, "localhost" or our own name names a directory we can
+ * actually reach. */
+static Eina_Bool
+_osc_7_host_is_local(const char *host, size_t len)
+{
+   static char hostname[256];
+   static Eina_Bool hostname_known = EINA_FALSE;
+
+   if (len == 0)
+     return EINA_TRUE;
+   if ((len == 9) && (strncasecmp(host, "localhost", 9) == 0))
+     return EINA_TRUE;
+
+   if (!hostname_known)
+     {
+        if (gethostname(hostname, sizeof(hostname)) != 0)
+          hostname[0] = '\0';
+        hostname[sizeof(hostname) - 1] = '\0';
+        hostname_known = EINA_TRUE;
+     }
+   return (hostname[0] != '\0') && (strlen(hostname) == len) &&
+          (strncasecmp(host, hostname, len) == 0);
+}
+
+/* OSC 7 ; file://<host>/<percent-encoded path> ST
+ *
+ * Validated here, at the only ingress, because the consumers are spread out
+ * and the next one written will not necessarily be careful.
+ */
+static void
+_handle_osc_7_cwd(Termpty *ty, char *uri)
+{
+   char *path;
+   size_t len, i;
+
+   if (strncmp(uri, "file://", 7) != 0)
+     {
+        WRN("OSC 7: not a file:// URI");
+        goto err;
+     }
+   uri += 7;
+   path = strchr(uri, '/');
+   if (!path)
+     {
+        WRN("OSC 7: URI has no path");
+        goto err;
+     }
+
+   /* A remote shell announcing its own host is behaving correctly. We simply
+    * have nothing to do with its paths, so drop it without an error. */
+   if (!_osc_7_host_is_local(uri, path - uri))
+     return;
+
+   uri_percent_decode_inplace(path);
+   len = strlen(path);
+   if (len >= PATH_MAX)
+     {
+        WRN("OSC 7: path too long (%zu bytes)", len);
+        goto err;
+     }
+   for (i = 0; i < len; i++)
+     {
+        if (((unsigned char)path[i] < 0x20) || (path[i] == 0x7f))
+          {
+             WRN("OSC 7: control character in path");
+             goto err;
+          }
+     }
+
+   eina_stringshare_replace(&ty->prop.cwd, path);
+   return;
+
+err:
+   ty->decoding_error = EINA_TRUE;
+}
+
 static int
 _handle_esc_osc(Termpty *ty, const Eina_Unicode *c, const Eina_Unicode *ce)
 {
@@ -4738,6 +4817,24 @@ _handle_esc_osc(Termpty *ty, const Eina_Unicode *c, const Eina_Unicode *ce)
         if ((cc - c) < 3)
           return 0;
         break;
+      case 7:
+        DBG("set working directory");
+        if (!p)
+          goto err;
+        /* tmux re-emits the active pane's path, and sends it empty when the
+         * pane has none. That means "forget it", not a malformed sequence. */
+        if (!*p)
+          {
+             eina_stringshare_replace(&ty->prop.cwd, NULL);
+             break;
+          }
+        s = eina_unicode_unicode_to_utf8(p, &len);
+        if (s)
+          {
+             _handle_osc_7_cwd(ty, s);
+             free(s);
+          }
+        break;
       case 8:
         DBG("hyperlink");
         if (!p || !*p)

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

Reply via email to