[PATCH 3/3] vt: add DECRQM support to report mode states
From: Nicolas Pitre
Date: Mon Oct 19 2026 - 17:20:49 EST
From: Nicolas Pitre <npitre@xxxxxxxxxxxx>
Applications currently have no way to find out whether the VT supports
a given mode, nor what its current state is. DECRQM, "CSI Ps $ p" for
ANSI modes and "CSI ? Ps $ p" for DEC private modes, is the standard
query for this. But the '$' intermediate makes the parser ignore the
whole sequence, leaving applications waiting for a reply that never
comes.
This matters especially for mode 2027, whose specification relies on
DECRQM for applications to detect support before enabling it.
Add an ESdollar parser state for "CSI parameters $", and answer a final
'p' with DECRPM, "CSI [?] Ps ; Pm $ y", where Pm is 1 (set), 2 (reset),
0 (not recognized) or 4 (permanently reset). All modes handled by
csi_DEC_hl() and csi_hl() are reported, with 132-column mode reported
as permanently reset since it isn't implemented. Other sequences using
the '$' intermediate are still ignored.
Assisted-by: LLM
Signed-off-by: Nicolas Pitre <npitre@xxxxxxxxxxxx>
---
drivers/tty/vt/vt.c | 90 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 90 insertions(+)
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 4d5c9cabd058..22dfab8de7bc 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -2083,6 +2083,82 @@ static void csi_hl(struct vc_data *vc, bool on_off)
}
}
+/* DECRPM mode states */
+enum {
+ DECRPM_NOT_RECOGNIZED = 0,
+ DECRPM_SET = 1,
+ DECRPM_RESET = 2,
+ DECRPM_PERMANENTLY_RESET = 4,
+};
+
+static int decrpm_state(bool set)
+{
+ return set ? DECRPM_SET : DECRPM_RESET;
+}
+
+/* State of a DEC private mode as handled by csi_DEC_hl() */
+static int csi_DEC_mode(struct vc_data *vc, unsigned int mode)
+{
+ switch (mode) {
+ case CSI_DEC_hl_CURSOR_KEYS:
+ return decrpm_state(is_kbd(vc, decckm));
+ case CSI_DEC_hl_132_COLUMNS:
+ return DECRPM_PERMANENTLY_RESET;
+ case CSI_DEC_hl_REVERSE_VIDEO:
+ return decrpm_state(vc->vc_decscnm);
+ case CSI_DEC_hl_ORIGIN_MODE:
+ return decrpm_state(vc->vc_decom);
+ case CSI_DEC_hl_AUTOWRAP:
+ return decrpm_state(vc->vc_decawm);
+ case CSI_DEC_hl_AUTOREPEAT:
+ return decrpm_state(is_kbd(vc, decarm));
+ case CSI_DEC_hl_MOUSE_X10:
+ return decrpm_state(vc->vc_report_mouse == 1);
+ case CSI_DEC_hl_SHOW_CURSOR:
+ return decrpm_state(vc->vc_deccm);
+ case CSI_DEC_hl_MOUSE_VT200:
+ return decrpm_state(vc->vc_report_mouse == 2);
+ case CSI_DEC_hl_ALT_SCREEN:
+ return decrpm_state(vc->vc_saved_screen != NULL);
+ case CSI_DEC_hl_BRACKETED_PASTE:
+ return decrpm_state(vc->vc_bracketed_paste);
+ case CSI_DEC_hl_GRAPHEME_WIDTH:
+ return decrpm_state(vc->vc_vs16_wide);
+ }
+ return DECRPM_NOT_RECOGNIZED;
+}
+
+/* State of an ANSI mode as handled by csi_hl() */
+static int csi_ANSI_mode(struct vc_data *vc, unsigned int mode)
+{
+ switch (mode) {
+ case CSI_hl_DISPLAY_CTRL:
+ return decrpm_state(vc->vc_disp_ctrl);
+ case CSI_hl_INSERT:
+ return decrpm_state(vc->vc_decim);
+ case CSI_hl_AUTO_NL:
+ return decrpm_state(is_kbd(vc, lnm));
+ }
+ return DECRPM_NOT_RECOGNIZED;
+}
+
+/*
+ * DECRQM: request mode, "CSI Ps $ p" for ANSI modes and "CSI ? Ps $ p" for
+ * DEC private modes. Reply with DECRPM: "CSI [?] Ps ; Pm $ y".
+ */
+/* console_lock is held */
+static void csi_RQM(struct tty_struct *tty, struct vc_data *vc)
+{
+ unsigned int mode = vc->vc_par[0];
+ bool dec = vc->vc_priv == EPdec;
+ char buf[32];
+ int len;
+
+ len = snprintf(buf, sizeof(buf), "\033[%s%u;%d$y", dec ? "?" : "",
+ mode, dec ? csi_DEC_mode(vc, mode) : csi_ANSI_mode(vc, mode));
+ respond_string(buf, len, tty->port);
+}
+
enum CSI_right_square_bracket {
CSI_RSB_COLOR_FOR_UNDERLINE = 1,
CSI_RSB_COLOR_FOR_HALF_BRIGHT = 2,
@@ -2233,6 +2309,7 @@ static void restore_cur(struct vc_data *vc)
* @ESsetG1: ESC ) parsed
* @ESpercent: ESC % parsed
* @EScsiignore: CSI [0x20-0x3f] parsed
+ * @ESdollar: CSI parameters $ parsed -- final char expected
* @ESnonstd: OSC parsed
* @ESpalette: OSC P parsed
* @ESosc: OSC [0-9] parsed
@@ -2254,6 +2331,7 @@ enum vc_ctl_state {
ESsetG1,
ESpercent,
EScsiignore,
+ ESdollar,
ESnonstd,
ESpalette,
ESosc,
@@ -2788,6 +2866,9 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, u8 c)
vc->vc_par[vc->vc_npar] *= 10;
vc->vc_par[vc->vc_npar] += c - '0';
return;
+ case '$':
+ vc->vc_state = ESdollar;
+ return;
}
if (c >= ASCII_CSI_IGNORE_FIRST && c <= ASCII_CSI_IGNORE_LAST) {
vc->vc_state = EScsiignore;
@@ -2846,6 +2927,15 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, u8 c)
return;
vc->vc_state = ESnormal;
return;
+ case ESdollar: /* CSI parameters $, final char expected */
+ if (c >= ASCII_CSI_IGNORE_FIRST && c <= ASCII_CSI_IGNORE_LAST) {
+ vc->vc_state = EScsiignore;
+ return;
+ }
+ vc->vc_state = ESnormal;
+ if (c == 'p' && (vc->vc_priv == EPdec || vc->vc_priv == EPecma))
+ csi_RQM(tty, vc);
+ return;
case ESpercent: /* ESC % */
vc->vc_state = ESnormal;
switch (c) {
--
2.55.0