patch 9.0.0930: cannot debug the Kitty keyboard protocol with TermDebug

Problem:    Cannot debug the Kitty keyboard protocol with TermDebug.
Solution:   Add Kitty keyboard protocol support to the libvterm fork.
            Recognize the escape sequences that the protocol generates.  Add
            the 'keyprotocol' option to allow the user to specify for which
            terminal what protocol is to be used, instead of hard-coding this.
            Add recognizing the kitty keyboard protocol status.
diff --git a/src/edit.c b/src/edit.c
index 2e6a980..01e5cc2 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -330,7 +330,7 @@
 
 	// Disable modifyOtherKeys, keys with modifiers would cause exiting
 	// Insert mode.
-	out_str(T_CTE);
+	out_str_t_TE();
     }
 
     /*
diff --git a/src/globals.h b/src/globals.h
index 9d70da6..a5e3982 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1377,6 +1377,24 @@
 // no longer be used.
 EXTERN int seenModifyOtherKeys INIT(= FALSE);
 
+// The state for the Kitty keyboard protocol.
+typedef enum {
+    // Initially we have no clue if the protocol is on or off.
+    KKPS_INITIAL,
+    // Used when receiving the state and the flags are zero.
+    KKPS_OFF,
+    // Used when receiving the state and the flags are non-zero.
+    KKPS_ENABLED,
+    // Used after outputting t_KE when the state was KKPS_ENABLED.  We do not
+    // really know if t_KE actually disabled the protocol, the following t_KI
+    // is expected to request the state, but the response may come only later.
+    KKPS_DISABLED,
+    // Used after outputting t_KE when the state was not KKPS_ENABLED.
+    KKPS_AFTER_T_KE,
+} kkpstate_T;
+
+EXTERN kkpstate_T kitty_protocol_state INIT(= KKPS_INITIAL);
+
 EXTERN int no_mapping INIT(= FALSE);	// currently no mapping allowed
 EXTERN int no_zero_mapping INIT(= 0);	// mapping zero not allowed
 EXTERN int allow_keys INIT(= FALSE);	// allow key codes when no_mapping
diff --git a/src/libvterm/include/vterm.h b/src/libvterm/include/vterm.h
index e5887c8..6a967db 100644
--- a/src/libvterm/include/vterm.h
+++ b/src/libvterm/include/vterm.h
@@ -368,6 +368,7 @@
 size_t vterm_output_read(VTerm *vt, char *buffer, size_t len);
 
 int vterm_is_modify_other_keys(VTerm *vt);
+int vterm_is_kitty_keyboard(VTerm *vt);
 void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod);
 void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod);
 
diff --git a/src/libvterm/src/keyboard.c b/src/libvterm/src/keyboard.c
index ffd8319..7a44670 100644
--- a/src/libvterm/src/keyboard.c
+++ b/src/libvterm/src/keyboard.c
@@ -10,6 +10,12 @@
   return vt->state->mode.modify_other_keys;
 }
 
+// VIM: added
+int vterm_is_kitty_keyboard(VTerm *vt)
+{
+  return vt->state->mode.kitty_keyboard;
+}
+
 
 void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
 {
@@ -19,6 +25,12 @@
     return;
   }
 
+  // VIM: added kitty keyboard protocol support
+  if (vterm_is_kitty_keyboard(vt) && mod != 0) {
+    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
+    return;
+  }
+
   /* The shift modifier is never important for Unicode characters
    * apart from Space
    */
@@ -166,8 +178,10 @@
     break;
 
   case KEYCODE_TAB:
+    if (vterm_is_kitty_keyboard(vt) && mod != 0)
+      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "9;%du", mod+1);
     /* Shift-Tab is CSI Z but plain Tab is 0x09 */
-    if(mod == VTERM_MOD_SHIFT)
+    else if(mod == VTERM_MOD_SHIFT)
       vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
     else if(mod & VTERM_MOD_SHIFT)
       vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
@@ -186,7 +200,10 @@
   case KEYCODE_LITERAL: case_LITERAL:
     if (vterm_is_modify_other_keys(vt) && mod != 0)
       vterm_push_output_sprintf_ctrl(vt, C1_CSI, "27;%d;%d~", mod+1, k.literal);
-    else if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
+    else if (vterm_is_kitty_keyboard(vt) && mod == 0 && k.literal == '\x1b')
+      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%du", k.literal);
+    else if ((vterm_is_kitty_keyboard(vt) && mod != 0)
+	|| (mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)))
       vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1);
     else
       vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c
index 8aae7d1..33395e9 100644
--- a/src/libvterm/src/state.c
+++ b/src/libvterm/src/state.c
@@ -1423,9 +1423,37 @@
     }
     break;
 
-  case LEADER('>', 0x6d): // xterm resource modifyOtherKeys
+  case LEADER('>', 0x6d): // CSI > 4 ; Pv m   xterm resource modifyOtherKeys
     if (argcount == 2 && args[0] == 4)
+    {
+      // can't have both modify_other_keys and kitty_keyboard
+      state->mode.kitty_keyboard = 0;
+
       state->mode.modify_other_keys = args[1] == 2;
+    }
+    break;
+
+  case LEADER('>', 0x75): // CSI > 1 u  enable kitty keyboard protocol
+    if (argcount == 1 && args[0] == 1)
+    {
+      // can't have both modify_other_keys and kitty_keyboard
+      state->mode.modify_other_keys = 0;
+
+      state->mode.kitty_keyboard = 1;
+    }
+    break;
+
+  case LEADER('<', 0x75): // CSI < u  disable kitty keyboard protocol
+    if (argcount <= 1)
+      state->mode.kitty_keyboard = 0;
+    break;
+
+  case LEADER('?', 0x75): // CSI ? u  request kitty keyboard protocol state
+    if (argcount <= 1)
+      // TODO: this only uses the values zero and one.  The protocol specifies
+      // more values, the progressive enhancement flags.
+      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%du",
+						   state->mode.kitty_keyboard);
     break;
 
   case 0x6e: // DSR - ECMA-48 8.3.35
diff --git a/src/libvterm/src/vterm_internal.h b/src/libvterm/src/vterm_internal.h
index 0e389d0..3e95611 100644
--- a/src/libvterm/src/vterm_internal.h
+++ b/src/libvterm/src/vterm_internal.h
@@ -130,6 +130,7 @@
     unsigned int bracketpaste:1;
     unsigned int report_focus:1;
     unsigned int modify_other_keys:1;
+    unsigned int kitty_keyboard:1;
   } mode;
 
   VTermEncodingInstance encoding[4], encoding_utf8;
diff --git a/src/map.c b/src/map.c
index 4dec9e7..ee249c7 100644
--- a/src/map.c
+++ b/src/map.c
@@ -312,8 +312,27 @@
     // Prevent mappings to be cleared while at the more prompt.
     ++map_locked;
 
-    if (p_verbose > 0 && keyround == 1 && seenModifyOtherKeys)
-	msg_puts(_("Seen modifyOtherKeys: true"));
+    if (p_verbose > 0 && keyround == 1)
+    {
+	if (seenModifyOtherKeys)
+	    msg_puts(_("Seen modifyOtherKeys: true"));
+	if (kitty_protocol_state != KKPS_INITIAL)
+	{
+	    char *name = _("Unknown");
+	    switch (kitty_protocol_state)
+	    {
+		case KKPS_INITIAL: break;
+		case KKPS_OFF: name = _("Off"); break;
+		case KKPS_ENABLED: name = _("On"); break;
+		case KKPS_DISABLED: name = _("Disabled"); break;
+		case KKPS_AFTER_T_KE: name = _("Cleared"); break;
+	    }
+
+	    char buf[200];
+	    vim_snprintf(buf, sizeof(buf), _("Kitty keyboard protocol: %s"), name);
+	    msg_puts(buf);
+	}
+    }
 
     // need to loop over all global hash lists
     for (int hash = 0; hash < 256 && !got_int; ++hash)
diff --git a/src/normal.c b/src/normal.c
index 5000f53..938ae71 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -424,7 +424,7 @@
 	    // Disable bracketed paste and modifyOtherKeys here, we won't
 	    // recognize the escape sequences with 'esckeys' off.
 	    out_str(T_BD);
-	    out_str(T_CTE);
+	    out_str_t_TE();
 	}
 
 	*cp = plain_vgetc();
diff --git a/src/option.h b/src/option.h
index eed12e8..6f27013 100644
--- a/src/option.h
+++ b/src/option.h
@@ -698,6 +698,7 @@
 #endif
 EXTERN char_u	*p_kp;		// 'keywordprg'
 EXTERN char_u	*p_km;		// 'keymodel'
+EXTERN char_u	*p_kpc;		// 'keyprotocol'
 #ifdef FEAT_LANGMAP
 EXTERN char_u	*p_langmap;	// 'langmap'
 EXTERN int	p_lnr;		// 'langnoremap'
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 5b3860b..0278b60 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -1447,6 +1447,10 @@
     {"keymodel",    "km",   P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
 			    (char_u *)&p_km, PV_NONE,
 			    {(char_u *)"", (char_u *)0L} SCTX_INIT},
+    {"keyprotocol", "kpc",  P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
+			    (char_u *)&p_kpc, PV_NONE,
+			    {(char_u *)"kitty:kitty,foot:kitty,wezterm:kitty,xterm:mok2", (char_u *)0L}
+			    SCTX_INIT},
     {"keywordprg",  "kp",   P_STRING|P_EXPAND|P_VI_DEF|P_SECURE,
 			    (char_u *)&p_kp, PV_KP,
 			    {
diff --git a/src/optionstr.c b/src/optionstr.c
index 9e9d18f..084b438 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -1663,6 +1663,13 @@
 	}
     }
 
+    // 'keyprotocol'
+    else if (varp == &p_kpc)
+    {
+	if (match_keyprotocol(NULL) == KEYPROTOCOL_FAIL)
+	    errmsg = e_invalid_argument;
+    }
+
     // 'mousemodel'
     else if (varp == &p_mousem)
     {
diff --git a/src/os_unix.c b/src/os_unix.c
index db57902..0540fc9 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -5348,9 +5348,9 @@
 		long delay_msec = 1;
 
 		if (tmode == TMODE_RAW)
-		    // possibly disables modifyOtherKeys, so that the system
-		    // can recognize CTRL-C
-		    out_str(T_CTE);
+		    // Possibly disables modifyOtherKeys, so that the system
+		    // can recognize CTRL-C.
+		    out_str_t_TE();
 
 		/*
 		 * Similar to the loop above, but only handle X events, no
diff --git a/src/proto/term.pro b/src/proto/term.pro
index 7ce14b9..b1c691d 100644
--- a/src/proto/term.pro
+++ b/src/proto/term.pro
@@ -4,6 +4,7 @@
 void init_term_props(int all);
 void f_terminalprops(typval_T *argvars, typval_T *rettv);
 void set_color_count(int nr);
+keyprot_T match_keyprotocol(char_u *term);
 int set_termname(char_u *term);
 void free_cur_term(void);
 void getlinecol(long *cp, long *rp);
@@ -46,6 +47,7 @@
 void shell_resized(void);
 void shell_resized_check(void);
 void set_shellsize(int width, int height, int mustset);
+void out_str_t_TE(void);
 void settmode(tmode_T tmode);
 void starttermcap(void);
 void stoptermcap(void);
diff --git a/src/term.c b/src/term.c
index bb1e283..7f639dd 100644
--- a/src/term.c
+++ b/src/term.c
@@ -587,6 +587,32 @@
 };
 
 /*
+ * Additions for using modifyOtherKeys level 2.  Same as what is used for
+ * xterm.
+ */
+static tcap_entry_T builtin_mok2[] = {
+    {(int)KS_CTI,	"\033[>4;2m"},
+    {(int)KS_CTE,	"\033[>4;m"},
+
+    {(int)KS_NAME,	NULL}  // end marker
+};
+
+/*
+ * Additions for using the Kitty keyboard protocol.
+ */
+static tcap_entry_T builtin_kitty[] = {
+    // t_TI enables the kitty keyboard protocol, requests the kitty keyboard
+    // protocol state and requests the version response.
+    {(int)KS_CTI,	"\033[>1u\033[?u\033[>c"},
+
+    // t_TE also disabled modifyOtherKeys, because t_TI from xterm may already
+    // have been used.
+    {(int)KS_CTE,	"\033[>4;m\033[<u"},
+
+    {(int)KS_NAME,	NULL}  // end marker
+};
+
+/*
  * iris-ansi for Silicon Graphics machines.
  */
 static tcap_entry_T builtin_iris_ansi[] = {
@@ -1494,26 +1520,22 @@
 }
 
 /*
- * Parsing of the builtin termcap entries.
- * Caller should check if "term" is a valid builtin terminal name.
- * The terminal's name is not set, as this is already done in termcapinit().
+ * Apply entries from a builtin termcap.
  */
     static void
-parse_builtin_tcap(char_u *term)
+apply_builtin_tcap(char_u *term, tcap_entry_T *entries, int overwrite)
 {
-    tcap_entry_T *p = find_builtin_term(term);
-    if (p == NULL)  // builtin term not found
-	return;
-
     int term_8bit = term_is_8bit(term);
 
-    for ( ; p->bt_entry != (int)KS_NAME && p->bt_entry != BT_EXTRA_KEYS; ++p)
+    for (tcap_entry_T *p = entries;
+	      p->bt_entry != (int)KS_NAME && p->bt_entry != BT_EXTRA_KEYS; ++p)
     {
 	if ((int)p->bt_entry >= 0)	// KS_xx entry
 	{
-	    // Only set the value if it wasn't set yet.
+	    // Only set the value if it wasn't set yet or "overwrite" is TRUE.
 	    if (term_strings[p->bt_entry] == NULL
-				 || term_strings[p->bt_entry] == empty_option)
+		    || term_strings[p->bt_entry] == empty_option
+		    || overwrite)
 	    {
 #ifdef FEAT_EVAL
 		int opt_idx = -1;
@@ -1557,13 +1579,26 @@
 	    char_u  name[2];
 	    name[0] = KEY2TERMCAP0((int)p->bt_entry);
 	    name[1] = KEY2TERMCAP1((int)p->bt_entry);
-	    if (find_termcode(name) == NULL)
+	    if (find_termcode(name) == NULL || overwrite)
 		add_termcode(name, (char_u *)p->bt_string, term_8bit);
 	}
     }
 }
 
 /*
+ * Parsing of the builtin termcap entries.
+ * Caller should check if "term" is a valid builtin terminal name.
+ * The terminal's name is not set, as this is already done in termcapinit().
+ */
+    static void
+parse_builtin_tcap(char_u *term)
+{
+    tcap_entry_T *entries = find_builtin_term(term);
+    if (entries != NULL)
+	apply_builtin_tcap(term, entries, FALSE);
+}
+
+/*
  * Set number of colors.
  * Store it as a number in t_colors.
  * Store it as a string in T_CCO (using nr_colors[]).
@@ -1799,6 +1834,67 @@
 }
 
 /*
+ * Parse the 'keyprotocol' option, match against "term" and return the protocol
+ * for the first matching entry.
+ * When "term" is NULL then compile all patterns to check for any errors.
+ * Returns KEYPROTOCOL_FAIL if a pattern cannot be compiled.
+ * Returns KEYPROTOCOL_NONE if there is no match.
+ */
+    keyprot_T
+match_keyprotocol(char_u *term)
+{
+    int		len = (int)STRLEN(p_kpc) + 1;
+    char_u	*buf = alloc(len);
+    if (buf == NULL)
+	return KEYPROTOCOL_FAIL;
+
+    keyprot_T	ret = KEYPROTOCOL_FAIL;
+    char_u	*p = p_kpc;
+    while (*p != NUL)
+    {
+	// Isolate one comma separated item.
+	(void)copy_option_part(&p, buf, len, ",");
+	char_u *colon = vim_strchr(buf, ':');
+	if (colon == NULL || colon == buf || colon[1] == NUL)
+	    goto theend;
+	*colon = NUL;
+
+	keyprot_T prot;
+	if (STRCMP(colon + 1, "none") == 0)
+	    prot = KEYPROTOCOL_NONE;
+	else if (STRCMP(colon + 1, "mok2") == 0)
+	    prot = KEYPROTOCOL_MOK2;
+	else if (STRCMP(colon + 1, "kitty") == 0)
+	    prot = KEYPROTOCOL_KITTY;
+	else
+	    goto theend;
+
+	regmatch_T regmatch;
+	CLEAR_FIELD(regmatch);
+	regmatch.rm_ic = TRUE;
+	regmatch.regprog = vim_regcomp(buf, RE_MAGIC);
+	if (regmatch.regprog == NULL)
+	    goto theend;
+
+	int match = term != NULL && vim_regexec(&regmatch, term, (colnr_T)0);
+	vim_regfree(regmatch.regprog);
+	if (match)
+	{
+	    ret = prot;
+	    goto theend;
+	}
+
+    }
+
+    // No match found, use "none".
+    ret = KEYPROTOCOL_NONE;
+
+theend:
+    vim_free(buf);
+    return ret;
+}
+
+/*
  * Set terminal options for terminal "term".
  * Return OK if terminal 'term' was found in a termcap, FAIL otherwise.
  *
@@ -1924,6 +2020,15 @@
 	    }
 #endif
 	    parse_builtin_tcap(term);
+
+	    // Use the 'keyprotocol' option to adjust the t_TE and t_TI
+	    // termcap entries if there is an entry maching "term".
+	    keyprot_T kpc = match_keyprotocol(term);
+	    if (kpc == KEYPROTOCOL_KITTY)
+		apply_builtin_tcap(term, builtin_kitty, TRUE);
+	    else if (kpc == KEYPROTOCOL_MOK2)
+		apply_builtin_tcap(term, builtin_mok2, TRUE);
+
 #ifdef FEAT_GUI
 	    if (term_is_gui(term))
 	    {
@@ -3562,6 +3667,25 @@
 }
 
 /*
+ * Output T_CTE, the t_TE termcap entry, and handle expected effects.
+ * The code possibly disables modifyOtherKeys and the Kitty keyboard protocol.
+ */
+    void
+out_str_t_TE(void)
+{
+    out_str(T_CTE);
+
+    // When the kitty keyboard protocol is enabled we expect t_TE to disable
+    // it.  Remembering that it was detected to be enabled is useful in some
+    // situations.
+    if (kitty_protocol_state == KKPS_ENABLED
+	    || kitty_protocol_state == KKPS_DISABLED)
+	kitty_protocol_state = KKPS_DISABLED;
+    else
+	kitty_protocol_state = KKPS_AFTER_T_KE;
+}
+
+/*
  * Set the terminal to TMODE_RAW (for Normal mode) or TMODE_COOK (for external
  * commands and Ex mode).
  */
@@ -3613,7 +3737,7 @@
 		if (tmode != TMODE_RAW)
 		{
 		    out_str(T_BD);	// disable bracketed paste mode
-		    out_str(T_CTE);	// possibly disables modifyOtherKeys
+		    out_str_t_TE();	// possibly disables modifyOtherKeys
 		}
 		else
 		{
@@ -3714,7 +3838,8 @@
 	out_flush();
 	termcap_active = FALSE;
 	cursor_on();			// just in case it is still off
-	out_str(T_CTE);			// stop "raw" mode
+	out_str_t_TE();			// stop "raw" mode, modifyOtherKeys and
+					// Kitty keyboard protocol
 	out_str(T_TE);			// stop termcap mode
 	screen_start();			// don't know where cursor is now
 	out_flush();
@@ -5107,6 +5232,15 @@
 # endif
     }
 
+    // Kitty keyboard protocol status response: CSI ? flags u
+    else if (first == '?' && argc == 1 && trail == 'u')
+    {
+	// The protocol has various "progressive enhancement flags" values, but
+	// we only check for zero and non-zero here.
+	kitty_protocol_state = arg[0] == '0' ? KKPS_OFF : KKPS_ENABLED;
+	*slen = csi_len;
+    }
+
     // Check for a window position response from the terminal:
     //       {lead}3;{x};{y}t
     else if (did_request_winpos && argc == 3 && arg[0] == 3
diff --git a/src/terminal.c b/src/terminal.c
index 64e6ad4..fe82fa3 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -1587,6 +1587,13 @@
     if (modmask & (MOD_MASK_ALT | MOD_MASK_META))
 	mod |= VTERM_MOD_ALT;
 
+    // Ctrl-Shift-i may have the key "I" instead of "i", but for the kitty
+    // keyboard protocol should use "i".  Applies to all ascii letters.
+    if (ASCII_ISUPPER(c)
+	    && vterm_is_kitty_keyboard(curbuf->b_term->tl_vterm)
+	    && mod == (VTERM_MOD_CTRL | VTERM_MOD_SHIFT))
+	c = TOLOWER_ASC(c);
+
     /*
      * Convert special keys to vterm keys:
      * - Write keys to vterm: vterm_keyboard_key()
@@ -2182,6 +2189,19 @@
 
 static reduce_key_state_T  no_reduce_key_state = NRKS_NONE;
 
+/*
+ * Return TRUE if the term is using modifyOtherKeys level 2 or the kitty
+ * keyboard protocol.
+ */
+    static int
+vterm_using_key_protocol(void)
+{
+    return curbuf->b_term != NULL
+	&& curbuf->b_term->tl_vterm != NULL
+	&& (vterm_is_modify_other_keys(curbuf->b_term->tl_vterm)
+		|| vterm_is_kitty_keyboard(curbuf->b_term->tl_vterm));
+}
+
     void
 check_no_reduce_keys(void)
 {
@@ -2191,9 +2211,10 @@
 	    || curbuf->b_term->tl_vterm == NULL)
 	return;
 
-    if (vterm_is_modify_other_keys(curbuf->b_term->tl_vterm))
+    if (vterm_using_key_protocol())
     {
-	// "modify_other_keys" was enabled while waiting.
+	// "modify_other_keys" or kitty keyboard protocol was enabled while
+	// waiting.
 	no_reduce_key_state = NRKS_SET;
 	++no_reduce_keys;
     }
@@ -2216,8 +2237,7 @@
     ctrl_break_was_pressed = FALSE;
 #endif
 
-    if (curbuf->b_term->tl_vterm != NULL
-		       && vterm_is_modify_other_keys(curbuf->b_term->tl_vterm))
+    if (vterm_using_key_protocol())
     {
 	++no_reduce_keys;
 	no_reduce_key_state = NRKS_SET;
@@ -2641,12 +2661,13 @@
 
 /*
  * When modify_other_keys is set then do the reverse of raw_c_to_ctrl().
+ * Also when the Kitty keyboard protocol is used.
  * May set "mod_mask".
  */
     static int
 ctrl_to_raw_c(int c)
 {
-    if (c < 0x20 && vterm_is_modify_other_keys(curbuf->b_term->tl_vterm))
+    if (c < 0x20 && vterm_using_key_protocol())
     {
 	mod_mask |= MOD_MASK_CTRL;
 	return c + '@';
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index dd5f73e..7337217 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -113,6 +113,8 @@
       \ 'isprint': [['', '@', '@,48-52'], ['xxx', '@48']],
       \ 'keymap': [['', 'accents'], ['xxx']],
       \ 'keymodel': [['', 'startsel', 'startsel,stopsel'], ['xxx']],
+      \ 'keyprotocol': [['', 'xxx:none', 'yyy:mok2', 'zzz:kitty'],
+      \		[':none', 'xxx:', 'x:non', 'y:mok3', 'z:kittty']],
       \ 'langmap': [['', 'xX', 'aA,bB'], ['xxx']],
       \ 'lispoptions': [['', 'expr:0', 'expr:1'], ['xxx']],
       \ 'listchars': [['', 'eol:x', 'eol:x,space:y'], ['xxx']],
diff --git a/src/testdir/keycode_check.json b/src/testdir/keycode_check.json
index 868c2a9..218859f 100644
--- a/src/testdir/keycode_check.json
+++ b/src/testdir/keycode_check.json
@@ -1 +1 @@
-{"12xterm":{"Space":"20","version":"1b5b3e34313b3335363b3063","C-Tab":"1b5b32373b353b397e","A-Esc":"1b5b32373b333b32377e","C-Space":"1b5b32373b353b33327e","status":"","S-C-I":"1b5b32373b363b37337e","C-I":"1b5b32373b353b3130357e","S-Tab":"1b5b5a","Tab":"09","S-Space":"1b5b32373b323b33327e","A-Tab":"1b5b32373b333b397e","C-Esc":"1b5b32373b353b32377e","protocol":"mok2","A-Space":"1b5b32373b333b33327e","S-Esc":"1b","Esc":"1b"},"libvterm":{"Space":"20","version":"1b5b3e303b3130303b3063","C-Tab":"1b5b32373b353b397e","A-Esc":"1b5b32373b333b32377e","C-Space":"1b5b32373b353b33327e","status":"","S-C-I":"1b5b32373b363b37337e","C-I":"1b5b32373b353b3130357e","S-Tab":"1b5b5a","Tab":"09","resource":"","A-Tab":"1b5b32373b333b397e","S-Space":"1b5b32373b323b33327e","C-Esc":"1b5b32373b353b32377e","protocol":"mok2","A-Space":"1b5b32373b333b33327e","S-Esc":"1b","Esc":"1b"},"2kitty":{"Space":"20","version":"1b5b3e313b343030303b323163","C-Tab":"","A-Esc":"1b5b32373b313175","C-Space":"1b5b33323b3575","status":"1b5b3f3175","S-C-I":"1b5b3130353b3675","C-I":"1b5b3130353b3575","S-Tab":"1b5b393b3275","Tab":"09","S-Space":"20","A-Tab":"1b5b393b313175","C-Esc":"1b5b32373b3575","protocol":"kitty","A-Space":"1b5b33323b313175","S-Esc":"1b5b32373b3275","Esc":"1b5b323775"},"11xterm":{"Space":"20","version":"1b5b3e34313b3335363b3063","C-Tab":"09","A-Esc":"9b00","status":"","S-C-I":"09","C-I":"09","S-Tab":"1b5b5a","Tab":"09","S-Space":"20","A-Tab":"8900","C-Esc":"1b","protocol":"none","A-Space":"a000","S-Esc":"1b","Esc":"1b"}}
+{"31kitty":{"Space":"20","version":"1b5b3e313b343030303b323163","C-Tab":"","A-Esc":"1b5b32373b313175","C-Space":"1b5b33323b3575","status":"1b5b3f3175","S-C-I":"1b5b3130353b3675","C-I":"1b5b3130353b3575","S-Tab":"1b5b393b3275","Tab":"09","resource":"","A-Tab":"1b5b393b313175","S-Space":"20","C-Esc":"1b5b32373b3575","protocol":"kitty","A-Space":"1b5b33323b313175","S-Esc":"1b5b32373b3275","Esc":"1b5b323775"},"32libvterm":{"Space":"20","version":"1b5b3e303b3130303b3063","C-Tab":"1b5b393b3575","A-Esc":"1b5b32373b3375","C-Space":"1b5b33323b3575","status":"1b5b3f3175","S-C-I":"1b5b3130353b3675","C-I":"1b5b3130353b3575","S-Tab":"1b5b393b3275","Tab":"09","resource":"","A-Tab":"1b5b393b3375","S-Space":"1b5b33323b3275","C-Esc":"1b5b32373b3575","protocol":"kitty","A-Space":"1b5b33323b3375","S-Esc":"1b5b323775","Esc":"1b5b323775"},"22libvterm":{"Space":"20","version":"1b5b3e303b3130303b3063","C-Tab":"1b5b32373b353b397e","A-Esc":"1b5b32373b333b32377e","C-Space":"1b5b32373b353b33327e","status":"","S-C-I":"1b5b32373b363b37337e","C-I":"1b5b32373b353b3130357e","S-Tab":"1b5b5a","Tab":"09","resource":"","A-Tab":"1b5b32373b333b397e","S-Space":"1b5b32373b323b33327e","C-Esc":"1b5b32373b353b32377e","protocol":"mok2","A-Space":"1b5b32373b333b33327e","S-Esc":"1b","Esc":"1b"},"13kitty":{"Space":"20","version":"1b5b3e313b343030303b323163","C-Tab":"","A-Esc":"1b1b","status":"","S-C-I":"1b5b3130353b3675","C-I":"09","S-Tab":"1b5b5a","Tab":"09","resource":"","A-Tab":"1b09","S-Space":"20","C-Esc":"1b","protocol":"none","A-Space":"1b5b33323b313175","S-Esc":"1b","Esc":"1b"},"21xterm":{"Space":"20","version":"1b5b3e34313b3335363b3063","C-Tab":"1b5b32373b353b397e","A-Esc":"1b5b32373b333b32377e","C-Space":"1b5b32373b353b33327e","status":"","S-C-I":"1b5b32373b363b37337e","C-I":"1b5b32373b353b3130357e","S-Tab":"1b5b5a","Tab":"09","resource":"=30","A-Tab":"1b5b32373b333b397e","S-Space":"1b5b32373b323b33327e","C-Esc":"1b5b32373b353b32377e","protocol":"mok2","A-Space":"1b5b32373b333b33327e","S-Esc":"1b","Esc":"1b"},"12libvterm":{"Space":"20","version":"1b5b3e303b3130303b3063","C-Tab":"1b5b393b3575","A-Esc":"9b00","status":"","S-C-I":"1b5b5a","C-I":"09","S-Tab":"1b5b5a","Tab":"09","S-Space":"1b5b33323b3275","A-Tab":"8900","resource":"","C-Esc":"1b5b32373b3575","protocol":"none","A-Space":"a000","S-Esc":"1b","Esc":"1b"},"11xterm":{"Space":"20","version":"1b5b3e34313b3335363b3063","C-Tab":"09","A-Esc":"9b00","status":"","S-C-I":"09","C-I":"09","S-Tab":"1b5b5a","Tab":"09","S-Space":"20","A-Tab":"8900","C-Esc":"1b","protocol":"none","A-Space":"a000","S-Esc":"1b","Esc":"1b"}}
diff --git a/src/version.c b/src/version.c
index 42e7200..a96b11a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    930,
+/**/
     929,
 /**/
     928,
diff --git a/src/vim.h b/src/vim.h
index 14630e6..67751ff 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2257,6 +2257,14 @@
     ESTACK_SCRIPT,
 } estack_arg_T;
 
+// Return value of match_keyprotocol()
+typedef enum {
+    KEYPROTOCOL_NONE,
+    KEYPROTOCOL_MOK2,
+    KEYPROTOCOL_KITTY,
+    KEYPROTOCOL_FAIL
+} keyprot_T;
+
 // Flags for assignment functions.
 #define ASSIGN_VAR	0     // ":var" (nothing special)
 #define ASSIGN_FINAL	0x01  // ":final"