"\033?12l" /* reset cursor blinking */ \
"\033 1q" /* reset cursor style */
+/* How much to wait for a reply to a terminal sequence */
+#define CONSOLE_REPLY_WAIT_USEC (333 * USEC_PER_MSEC)
+
static volatile unsigned cached_columns = 0;
static volatile unsigned cached_lines = 0;
if (r < 0)
goto finish;
- usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC);
+ usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
size_t buf_full = 0;
BackgroundColorContext context = {};
if (r < 0)
goto finish;
- usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC);
+ usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
size_t buf_full = 0;
CursorPositionContext context = {};
return 1;
}
+#define MAX_TERMINFO_LENGTH 64
+/* python -c 'print("".join(hex(ord(i))[2:] for i in "name").upper())' */
+#define DCS_TERMINFO_Q ANSI_DCS "+q" "6E616D65" ANSI_ST
+/* The answer is either 0+r… (invalid) or 1+r… (OK). */
+#define DCS_TERMINFO_R0 ANSI_DCS "0+r" ANSI_ST
+#define DCS_TERMINFO_R1 ANSI_DCS "1+r" "6E616D65" "=" /* This is followed by Pt ST. */
+assert_cc(STRLEN(DCS_TERMINFO_R0) <= STRLEN(DCS_TERMINFO_R1 ANSI_ST));
+
+static int scan_terminfo_response(
+ const char *buf,
+ size_t size,
+ char **ret_name) {
+ int r;
+
+ assert(buf);
+ assert(ret_name);
+
+ /* Check if we have enough space for the shortest possible answer. */
+ if (size < STRLEN(DCS_TERMINFO_R0))
+ return -EAGAIN;
+
+ /* Check if the terminating sequence is present */
+ if (memcmp(buf + size - STRLEN(ANSI_ST), ANSI_ST, STRLEN(ANSI_ST)) != 0)
+ return -EAGAIN;
+
+ if (size <= STRLEN(DCS_TERMINFO_R1 ANSI_ST))
+ return -EINVAL; /* The answer is invalid or empty */
+
+ if (memcmp(buf, DCS_TERMINFO_R1, STRLEN(DCS_TERMINFO_R1)) != 0)
+ return -EINVAL; /* The answer is not valid */
+
+ _cleanup_free_ void *dec = NULL;
+ size_t dec_size;
+ r = unhexmem_full(buf + STRLEN(DCS_TERMINFO_R1), size - STRLEN(DCS_TERMINFO_R1 ANSI_ST),
+ /* secure= */ false,
+ &dec, &dec_size);
+ if (r < 0)
+ return r;
+
+ assert(((const char *) dec)[dec_size] == '\0'); /* unhexmem appends NUL for our convenience */
+ if (memchr(dec, '\0', dec_size) || string_has_cc(dec, NULL) || !filename_is_valid(dec))
+ return -EUCLEAN;
+
+ *ret_name = TAKE_PTR(dec);
+ return 0;
+}
+
+int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
+ int r;
+
+ assert(fd >= 0);
+ assert(ret_name);
+
+ /* Note: fd must be in non-blocking read-write mode! */
+
+ struct termios old_termios;
+ if (tcgetattr(fd, &old_termios) < 0)
+ return -errno;
+
+ struct termios new_termios = old_termios;
+ termios_disable_echo(&new_termios);
+
+ if (tcsetattr(fd, TCSADRAIN, &new_termios) < 0)
+ return -errno;
+
+ r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX);
+ if (r < 0)
+ goto finish;
+
+ usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
+ char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
+ size_t bytes = 0;
+
+ for (;;) {
+ usec_t n = now(CLOCK_MONOTONIC);
+ if (n >= end) {
+ r = -EOPNOTSUPP;
+ break;
+ }
+
+ r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n));
+ if (r < 0)
+ break;
+ if (r == 0) {
+ r = -EOPNOTSUPP;
+ break;
+ }
+
+ /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
+ * read byte by byte, since we don't want to read too much and drop characters from the input
+ * queue. */
+ ssize_t l = read(fd, buf + bytes, bytes == 0 ? STRLEN(DCS_TERMINFO_R0) : 1);
+ if (l < 0) {
+ if (errno == EAGAIN)
+ continue;
+ r = -errno;
+ break;
+ }
+
+ assert((size_t) l <= sizeof(buf) - bytes);
+ bytes += l;
+
+ r = scan_terminfo_response(buf, bytes, ret_name);
+ if (r != -EAGAIN)
+ break;
+
+ if (bytes == sizeof(buf)) {
+ r = -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
+ * answer with a terminator in the alloted space. Something is
+ * wrong, possibly some unrelated bytes got injected into the
+ * answer. */
+ break;
+ }
+ }
+
+finish:
+ /* We ignore failure here. We already got a reply and if cleanup fails, we can't help that. */
+ (void) tcsetattr(fd, TCSADRAIN, &old_termios);
+ return r;
+}
+
int terminal_is_pty_fd(int fd) {
int r;
#define ANSI_WINDOW_TITLE_PUSH "\x1b[22;2t"
#define ANSI_WINDOW_TITLE_POP "\x1b[23;2t"
-/* ANSI "string terminator" character ("ST"). Terminal emulators typically allow three different ones: 0x07,
- * 0x9c, and 0x1B 0x5C. We'll avoid 0x07 (BEL, aka ^G) since it might trigger unexpected TTY signal
- * handling. And we'll avoid 0x9c since that's also valid regular codepoint in UTF-8 and elsewhere, and
- * creates ambiguities. Because of that some terminal emulators explicitly choose not to support it. Hence we
- * use 0x1B 0x5c */
-#define ANSI_ST "\e\\"
+/* The "device control string" ("DCS") start sequence */
+#define ANSI_DCS "\eP"
/* The "operating system command" ("OSC") start sequence */
#define ANSI_OSC "\e]"
+/* ANSI "string terminator" character ("ST"). Terminal emulators typically allow three different ones: 0x07,
+ * 0x9c, and 0x1B 0x5C. We'll avoid 0x07 (BEL, aka ^G) since it might trigger unexpected TTY signal handling.
+ * And we'll avoid 0x9c since that's also valid regular codepoint in UTF-8 and elsewhere, and creates
+ * ambiguities. Because of that some terminal emulators explicitly choose not to support it. Hence we use
+ * 0x1B 0x5c. */
+#define ANSI_ST "\e\\"
+
bool isatty_safe(int fd);
typedef enum TerminalResetFlags {
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue);
int terminal_get_size_by_dsr(int input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns);
-
int terminal_fix_size(int input_fd, int output_fd);
+int terminal_get_terminfo_by_dcs(int fd, char **ret_name);
+
int terminal_is_pty_fd(int fd);
int pty_open_peer(int fd, int mode);