patch 9.2.0633: MS-Windows: No support for kitty graphics support in terminal

Commit: 
https://github.com/vim/vim/commit/bf5235bdc97cbb2e9efb9cfc4c2083db30aeae91
Author: Yasuhiro Matsumoto <[email protected]>
Date:   Sat Jun 13 18:12:08 2026 +0000

    patch 9.2.0633: MS-Windows: No support for kitty graphics support in 
terminal
    
    Problem:  MS-Windows: No support for kitty graphics support in terminal
    Solution: Add mch_kitty_probe() to os_win32.c, reading the reply from
              the console input handle in VT input mode, with a short grace
              period after the DA1 answer because ConPTY answers DA1 by
              itself. The response parsing is shared with the UNIX probe via
              the new kitty_probe_parse() in kitty.c. (Yasuhiro Matsumoto).
    
    closes: #20497
    
    Signed-off-by: Yasuhiro Matsumoto <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/kitty.c b/src/kitty.c
index 183d17e58..2b46b1454 100644
--- a/src/kitty.c
+++ b/src/kitty.c
@@ -172,6 +172,74 @@ fail:
     return NULL;
 }
 
+/*
+ * Shared tail of the kitty-graphics-support probe (see popup_kitty_probe()
+ * in popupwin.c for the UNIX side and mch_kitty_probe() in os_win32.c for
+ * the Windows console side).  "buf[n]" holds the raw, NUL-terminated bytes
+ * read back from the terminal after sending the ` _Gi=31...a=q` query
+ * followed by the DA1 (` [c`) sentinel.
+ *
+ * Push everything except the kitty (APC  _G... \) and DA1 ( [?...c)
+ * response chunks back into the input buffer so user keystrokes typed
+ * during the probe are not swallowed.  Scan the buffer linearly, emitting
+ * any byte that is not inside a recognised response range.
+ *
+ * Returns TRUE when the buffer contains the positive "_Gi=31;OK" reply.
+ */
+    int
+kitty_probe_parse(char *buf, int n)
+{
+    int i = 0;
+    int seg_start = 0;
+
+    while (i < n)
+    {
+       int response_end = -1;
+
+       if (buf[i] == ' ' && i + 1 < n)
+       {
+           if (buf[i + 1] == '_')
+           {
+               // Kitty APC reply: scan to terminating ESC '\'.
+               int j;
+               for (j = i + 2; j + 1 < n; ++j)
+                   if (buf[j] == ' ' && buf[j + 1] == '\')
+                   {
+                       response_end = j + 2;
+                       break;
+                   }
+           }
+           else if (buf[i + 1] == '[' && i + 2 < n && buf[i + 2] == '?')
+           {
+               // DA1 reply: ESC [ ? ... c (the '?' distinguishes the
+               // primary device-attributes answer from an arrow key
+               // sequence like ESC [ A that the user might have typed).
+               int j;
+               for (j = i + 3; j < n; ++j)
+                   if (buf[j] == 'c')
+                   {
+                       response_end = j + 1;
+                       break;
+                   }
+           }
+       }
+       if (response_end > 0)
+       {
+           if (i > seg_start)
+               add_to_input_buf((char_u *)(buf + seg_start), i - seg_start);
+           i = response_end;
+           seg_start = i;
+       }
+       else
+           ++i;
+    }
+    if (n > seg_start)
+       add_to_input_buf((char_u *)(buf + seg_start), n - seg_start);
+
+    // A positive kitty reply contains the literal "_Gi=31;OK".
+    return strstr(buf, "_Gi=31;OK") != NULL;
+}
+
 /*
  * Build a kitty "delete image" APC sequence for the placement created
  * by kitty_encode() with the matching `id`.  The caller must
diff --git a/src/os_win32.c b/src/os_win32.c
index c76c2a95f..5e11de231 100644
--- a/src/os_win32.c
+++ b/src/os_win32.c
@@ -4866,6 +4866,128 @@ mch_calc_cell_size(struct cellsize *cs_out)
        cs_out->cs_ypixel = csi14_cell_y;
     }
 }
+
+# if defined(FEAT_IMAGE_KITTY) || defined(PROTO)
+/*
+ * Synchronously probe the host terminal for kitty graphics protocol
+ * support.  Windows console counterpart of popup_kitty_probe() in
+ * popupwin.c: sends the same
+ *      _Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA \ [c
+ * query (kitty answer + DA1 sentinel) and feeds the reply to the shared
+ * kitty_probe_parse().  The reply is read from the console input handle
+ * with ENABLE_VIRTUAL_TERMINAL_INPUT set, the same technique as
+ * query_terminal_pixel_size_w32() above.
+ *
+ * Returns TRUE on a positive `_Gi=31;OK` response.  Missing handles,
+ * a non-VT console and timeouts (~500ms) all yield FALSE.
+ */
+    int
+mch_kitty_probe(void)
+{
+    HANDLE     hOut = g_hConOut;
+    HANDLE     hIn = g_hConIn;
+    DWORD      mode_in_old = 0;
+    int                restored = 0;
+    DWORD      nWritten = 0;
+    char       buf[256];
+    int                n = 0;
+    DWORD      deadline;
+    DWORD      da1_deadline;
+    static const char  query[] =
+                   " _Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA \ [c";
+
+#  ifdef VIMDLL
+    if (gui.in_use)
+       return FALSE;
+#  endif
+    if (hOut == INVALID_HANDLE_VALUE || hIn == INVALID_HANDLE_VALUE)
+       return FALSE;
+    // Without VT output processing the query would be echoed onto the
+    // legacy console as raw text, and the kitty APC image sequences could
+    // not reach a terminal anyway.
+    if (!vtp_working)
+       return FALSE;
+
+    if (GetConsoleMode(hIn, &mode_in_old))
+    {
+       DWORD mode_new = mode_in_old;
+
+       // Read raw bytes; deliver the terminal responses as VT input.
+       mode_new &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT
+                                               | ENABLE_PROCESSED_INPUT);
+       mode_new |= ENABLE_VIRTUAL_TERMINAL_INPUT;
+       if (SetConsoleMode(hIn, mode_new))
+           restored = 1;
+    }
+
+    if (!WriteFile(hOut, query, sizeof(query) - 1, &nWritten, NULL)
+           || nWritten != sizeof(query) - 1)
+    {
+       if (restored)
+           SetConsoleMode(hIn, mode_in_old);
+       return FALSE;
+    }
+
+    // Read until the kitty reply lands or we time out.  Unlike the UNIX
+    // probe, the DA1 reply cannot serve as a hard "no kitty support"
+    // sentinel here: under ConPTY the DA1 answer is generated by conhost
+    // itself, while the kitty APC query has to round-trip through the
+    // attached terminal (possibly over ssh), so the DA1 answer usually
+    // arrives first.  Treat DA1 as "wrap up soon" instead and keep
+    // reading for a short grace period after it.
+    deadline = GetTickCount() + 500;
+    da1_deadline = 0;              // 0: DA1 reply not seen yet
+    while (n < (int)sizeof(buf) - 1)
+    {
+       DWORD           now = GetTickCount();
+       DWORD           until = deadline;
+       INPUT_RECORD    ir;
+       DWORD           count = 0;
+
+       if (da1_deadline != 0 && (int)(da1_deadline - until) < 0)
+           until = da1_deadline;
+       if ((int)(until - now) <= 0)
+           break;
+       if (WaitForSingleObject(hIn, until - now) != WAIT_OBJECT_0)
+           break;
+       if (!ReadConsoleInputW(hIn, &ir, 1, &count) || count != 1)
+           break;
+       if (ir.EventType != KEY_EVENT
+               || !ir.Event.KeyEvent.bKeyDown
+               || ir.Event.KeyEvent.uChar.AsciiChar == 0)
+           continue;
+       buf[n++] = ir.Event.KeyEvent.uChar.AsciiChar;
+       buf[n] = NUL;
+       // Stop as soon as the kitty APC reply is complete (terminated by
+       // ESC \) -- positive or negative, nothing later changes the verdict.
+       if (n >= 2 && buf[n - 1] == '\' && buf[n - 2] == ' '
+               && strstr(buf, "_Gi=31;") != NULL)
+           break;
+       // DA1 reply ends with a primary 'c' that is preceded by '?';
+       // once seen, allow a little more time for a passthrough kitty
+       // reply, then give up.
+       if (da1_deadline == 0
+               && buf[n - 1] == 'c' && n >= 3 && buf[n - 2] != ' ')
+       {
+           int i;
+
+           for (i = n - 2; i > 0; --i)
+               if (buf[i] == '?' && buf[i - 1] == '[')
+                   break;
+           if (i > 0)
+               da1_deadline = GetTickCount() + 250;
+       }
+    }
+    buf[n] = NUL;
+
+    if (restored)
+       SetConsoleMode(hIn, mode_in_old);
+
+    // Filter the probe responses out of the read-back bytes (pushing user
+    // keystrokes back into the input buffer) and check for a positive reply.
+    return kitty_probe_parse(buf, n);
+}
+# endif
 #endif
 
     static BOOL
diff --git a/src/popupwin.c b/src/popupwin.c
index 8e6090d1a..183dff608 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -1815,62 +1815,9 @@ popup_kitty_probe(void)
     buf[n] = NUL;
     tcsetattr(fd_in, TCSANOW, &old_t);
 
-    // Push everything except the kitty (APC  _G... \) and DA1 ( [?...c)
-    // response chunks back into the input buffer so user keystrokes typed
-    // during the probe are not swallowed.  Scan the buffer linearly,
-    // emitting any byte that is not inside a recognised response range.
-    {
-       int i = 0;
-       int seg_start = 0;
-
-       while (i < n)
-       {
-           int response_end = -1;
-
-           if (buf[i] == ' ' && i + 1 < n)
-           {
-               if (buf[i + 1] == '_')
-               {
-                   // Kitty APC reply: scan to terminating ESC '\'.
-                   int j;
-                   for (j = i + 2; j + 1 < n; ++j)
-                       if (buf[j] == ' ' && buf[j + 1] == '\')
-                       {
-                           response_end = j + 2;
-                           break;
-                       }
-               }
-               else if (buf[i + 1] == '[' && i + 2 < n && buf[i + 2] == '?')
-               {
-                   // DA1 reply: ESC [ ? ... c (the '?' distinguishes the
-                   // primary device-attributes answer from an arrow key
-                   // sequence like ESC [ A that the user might have typed).
-                   int j;
-                   for (j = i + 3; j < n; ++j)
-                       if (buf[j] == 'c')
-                       {
-                           response_end = j + 1;
-                           break;
-                       }
-               }
-           }
-           if (response_end > 0)
-           {
-               if (i > seg_start)
-                   add_to_input_buf((char_u *)(buf + seg_start),
-                                                       i - seg_start);
-               i = response_end;
-               seg_start = i;
-           }
-           else
-               ++i;
-       }
-       if (n > seg_start)
-           add_to_input_buf((char_u *)(buf + seg_start), n - seg_start);
-    }
-
-    // A positive kitty reply contains the literal "_Gi=31;OK".
-    return strstr(buf, "_Gi=31;OK") != NULL;
+    // Filter the probe responses out of the read-back bytes (pushing user
+    // keystrokes back into the input buffer) and check for a positive reply.
+    return kitty_probe_parse(buf, n);
 }
 # endif
 
@@ -1878,8 +1825,9 @@ popup_kitty_probe(void)
  * Pick a terminal image backend.  Cached on first call.  Returns
  * IMAGE_BACKEND_KITTY when the host terminal advertises kitty graphics
  * support via an active ` _Gi=31...a=q` probe followed by ` [c`
- * (see popup_kitty_probe), otherwise IMAGE_BACKEND_SIXEL.  The probe
- * works regardless of $TERM, vendor env vars, or terminal name, so it
+ * (see popup_kitty_probe for UNIX, mch_kitty_probe in os_win32.c for the
+ * Windows console), otherwise IMAGE_BACKEND_SIXEL.  The probe works
+ * regardless of $TERM, vendor env vars, or terminal name, so it
  * handles Konsole, WSL-from-Ghostty, ssh, and any future terminal that
  * adopts the protocol.
  */
@@ -1898,6 +1846,11 @@ popup_image_backend(void)
     // (~300ms worst case) on first call.
     if (popup_kitty_probe())
        detected = IMAGE_BACKEND_KITTY;
+# elif defined(FEAT_IMAGE_KITTY) && defined(MSWIN)
+    // Same probe, but reading the reply needs Windows console plumbing
+    // (console handles, VT input mode) that lives in os_win32.c.
+    if (mch_kitty_probe())
+       detected = IMAGE_BACKEND_KITTY;
 # endif
     return detected;
 }
diff --git a/src/proto/kitty.pro b/src/proto/kitty.pro
index 11e2ab2f4..8c2970c7d 100644
--- a/src/proto/kitty.pro
+++ b/src/proto/kitty.pro
@@ -1,4 +1,5 @@
 /* kitty.c */
 char_u *kitty_encode(image_rgb_T *img, int id, int zindex);
+int kitty_probe_parse(char *buf, int n);
 char_u *kitty_delete(int id);
 /* vim: set ft=c : */
diff --git a/src/proto/os_win32.pro b/src/proto/os_win32.pro
index 344161355..4850ea02f 100644
--- a/src/proto/os_win32.pro
+++ b/src/proto/os_win32.pro
@@ -49,6 +49,7 @@ void mch_set_shellsize(void);
 void mch_new_shellsize(void);
 void mch_set_winsize_now(void);
 void mch_calc_cell_size(struct cellsize *cs_out);
+int mch_kitty_probe(void);
 int mch_call_shell(char_u *cmd, int options);
 void win32_build_env(dict_T *env, garray_T *gap, int is_terminal);
 char_u *mch_get_cmd_output_direct(char **argv, char_u *infile, int flags, int 
*ret_len);
diff --git a/src/version.c b/src/version.c
index 5e1648127..a32296a6d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    633,
 /**/
     632,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wYT7M-00DPP3-7U%40256bit.org.

Raspunde prin e-mail lui