summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--include/term/term.h7
-rw-r--r--src/escape.c4
-rw-r--r--src/internal.h2
-rw-r--r--src/osc/misc.c2
-rw-r--r--src/screen.c17
-rw-r--r--tests/dcs_test.c53
-rw-r--r--tests/model.c6
-rw-r--r--tests/model.h5
-rw-r--r--tests/model_row_test.c2
-rw-r--r--tests/model_test.c2
-rw-r--r--tests/osc_test.c32
-rw-r--r--tests/term_test.c27
13 files changed, 147 insertions, 15 deletions
diff --git a/README.md b/README.md
index e877a01..c3c5d89 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,8 @@ no flags to get them:
A host wires the terminal in four places:
1. Create it with `term_new()`. Set `term_options.callbacks.reply` to
- send replies to the program.
+ send replies to the program. Set `term_options.name` to the name
+ programs read with XTVERSION and XTGETTCAP. It defaults to `term`.
2. Feed program output with `term_feed()`.
3. Translate key and mouse events into `struct term_key_event` and
`struct term_mouse_event`. Encode them with `term_input_encode_key()`
diff --git a/include/term/term.h b/include/term/term.h
index dc41114..aa1a3d7 100644
--- a/include/term/term.h
+++ b/include/term/term.h
@@ -146,6 +146,13 @@ struct term_options {
struct term_theme theme;
enum term_cursor_style cursor_style;
bool cursor_steady; /* false: the cursor blinks */
+ /*
+ * The terminal's name. Programs read it with XTVERSION and XTGETTCAP.
+ * It is also the title of a notification that has none. NULL or ""
+ * selects "term". Otherwise 1 to 63 printable ASCII bytes, or term_new()
+ * returns NULL. term_new() copies the name.
+ */
+ const char *name;
struct term_callbacks callbacks;
};
diff --git a/src/escape.c b/src/escape.c
index da0449a..dceb7a6 100644
--- a/src/escape.c
+++ b/src/escape.c
@@ -834,7 +834,9 @@ static inline TERM_ALWAYS_INLINE void dispatch_csi(struct term *t, const struct
case 'q': {
if (csi->prefix == '>' && !csi->intermediate &&
(!csi->nparams || !csi->params[0])) {
- send_response(t, "\033P>|wsap_term\033\\");
+ char resp[96];
+ snprintf(resp, sizeof(resp), "\033P>|%s\033\\", t->name);
+ send_response(t, resp);
} else if (!csi->prefix && csi->intermediate == ' ') {
term_sc_set_cursor_style(t, csi->nparams > 0 ? csi->params[0] : 0);
}
diff --git a/src/internal.h b/src/internal.h
index 8c02c91..5da114d 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -208,6 +208,8 @@ struct term {
bool custom_tabs;
char osc_buf[256];
+ /* The host's name. A DCS sequence can change tcap_name, not this. */
+ char name[64];
char tcap_name[64];
char *osc_heap;
diff --git a/src/osc/misc.c b/src/osc/misc.c
index 5d6b506..74b35cf 100644
--- a/src/osc/misc.c
+++ b/src/osc/misc.c
@@ -53,7 +53,7 @@ void term_osc_misc_dispatch(struct term *t, long code, const char *params)
break;
case 9:
if (t->cb.notify && strncmp(params, "4;", 2))
- t->cb.notify(t->cb.ud, "wsap_term", params);
+ t->cb.notify(t->cb.ud, t->name, params);
break;
case 22:
free(t->pointer_shape);
diff --git a/src/screen.c b/src/screen.c
index 87d6f2c..e728cec 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -841,12 +841,26 @@ bool term_tables_ready(void)
return atomic_load_explicit(&tables_state, memory_order_relaxed) == 2;
}
+/* A name is 1 to 63 printable ASCII bytes. DCS replies echo it. */
+static bool name_valid(const char *s)
+{
+ for (size_t i = 0; s[i]; i++) {
+ unsigned char c = (unsigned char)s[i];
+ if (i >= 63 || c < 0x20 || c > 0x7e)
+ return false;
+ }
+ return true;
+}
+
struct term *term_new(const struct term_options *opt)
{
tables_init();
static const struct term_options defaults;
if (!opt)
opt = &defaults;
+ const char *name = opt->name && *opt->name ? opt->name : "term";
+ if (!name_valid(name))
+ return NULL;
int cols = opt->size.x < 1 ? 80 : opt->size.x;
int rows = opt->size.y < 1 ? 24 : opt->size.y;
if (cols > TERM_MAX_COLS)
@@ -856,6 +870,7 @@ struct term *term_new(const struct term_options *opt)
struct term *t = calloc(1, sizeof(*t));
if (!t)
return NULL;
+ memcpy(t->name, name, strlen(name) + 1);
atomic_init(&t->seq, 0);
atomic_init(&t->writer_tid, 0);
t->write_depth = 0;
@@ -902,7 +917,7 @@ struct term *term_new(const struct term_options *opt)
t->autowrap = true;
t->charsets[0] = t->charsets[1] = 'B';
t->saved = term_saved_cursor_initial();
- memcpy(t->tcap_name, "wsap_term", sizeof("wsap_term"));
+ memcpy(t->tcap_name, t->name, sizeof(t->name));
term_tabs_default(t);
t->full_gen = 1;
memset(t->text_width.lo, 0xFF, sizeof(t->text_width.lo));
diff --git a/tests/dcs_test.c b/tests/dcs_test.c
index 0e67f78..d9455be 100644
--- a/tests/dcs_test.c
+++ b/tests/dcs_test.c
@@ -176,8 +176,8 @@ static void test_xtgettcap(void)
{
struct term *t = new_term(80, 24);
assert_tcap(t, "kind", "\033[1;2B");
- assert_tcap(t, "TN", "wsap_term");
- assert_tcap(t, "name", "wsap_term");
+ assert_tcap(t, "TN", "term");
+ assert_tcap(t, "name", "term");
assert_tcap(t, "Co", "16777216");
assert_tcap(t, "colors", "16777216");
assert_tcap(t, "RGB", "8/8/8");
@@ -230,7 +230,7 @@ static void test_xtgettcap_multi(void)
snprintf(req, sizeof(req), "\033P+q%s;%s\033\\", hk, htn);
snprintf(want, sizeof(want), "\033P1+r%s=%s;%s=%s\033\\",
- hk, hv, htn, "777361705f7465726d");
+ hk, hv, htn, "7465726d");
response_len = 0;
write_seq(t, req);
expect_response(want);
@@ -383,7 +383,51 @@ static void test_xtversion(void)
struct term *t = new_term(80, 24);
response_len = 0;
write_seq(t, "\033[>0q");
- expect_response("\033P>|wsap_term\033\\");
+ expect_response("\033P>|term\033\\");
+ term_free(t);
+}
+
+/* term_options.name feeds XTVERSION and the TN and name capabilities. */
+static void test_host_name(void)
+{
+ struct term_options opt = {
+ .size = { .x = 80, .y = 24 },
+ .scrollback = TEST_SCROLLBACK,
+ .name = "myterm",
+ .callbacks = { .reply = respond },
+ };
+ struct term *t = term_new(&opt);
+ assert(t);
+ response_len = 0;
+ write_seq(t, "\033[>0q");
+ expect_response("\033P>|myterm\033\\");
+ assert_tcap(t, "TN", "myterm");
+ assert_tcap(t, "name", "myterm");
+
+ /* A DCS +p sets the capability value. The XTVERSION name does not change. */
+ response_len = 0;
+ write_seq(t, "\033P+p6f74686572\033\\");
+ expect_no_response();
+ assert_tcap(t, "TN", "other");
+ response_len = 0;
+ write_seq(t, "\033[>0q");
+ expect_response("\033P>|myterm\033\\");
+ term_free(t);
+
+ /* NULL and "" both select the default. */
+ opt.name = "";
+ t = term_new(&opt);
+ assert(t);
+ response_len = 0;
+ write_seq(t, "\033[>0q");
+ expect_response("\033P>|term\033\\");
+ term_free(t);
+ opt.name = NULL;
+ t = term_new(&opt);
+ assert(t);
+ response_len = 0;
+ write_seq(t, "\033[>0q");
+ expect_response("\033P>|term\033\\");
term_free(t);
}
@@ -467,6 +511,7 @@ int main(void)
test_synchronized_output();
test_ignored_dcs();
test_xtversion();
+ test_host_name();
test_bel_terminator();
test_dcs_long_query();
test_dcs_abort_c0();
diff --git a/tests/model.c b/tests/model.c
index 8dead6c..b538ba1 100644
--- a/tests/model.c
+++ b/tests/model.c
@@ -72,6 +72,7 @@ enum { GROUND, ESC, CSI, STR, STR_ESC, CHARSET, HASH, DOCS };
enum { OSC, DCS, IGNORE };
struct model {
+ char name[64]; /* XTVERSION reply */
int cols, rows, limit;
struct row **scr;
struct row **hist;
@@ -1625,7 +1626,7 @@ static void csi_dispatch(struct model *m, unsigned char final)
break;
case 'q':
if (m->prefix == '>' && !m->inter && (!m->nparam || !m->param[0])) {
- reply(m, "\033P>|wsap_term\033\\");
+ reply(m, "\033P>|%s\033\\", m->name);
} else if (!m->prefix && m->inter == ' ') {
int ps = m->nparam ? m->param[0] : 0;
if (ps == 0) {
@@ -1971,10 +1972,11 @@ static void feed_byte(struct model *m, unsigned char c)
/* ---- public ---- */
-struct model *model_new(struct term_vector2i size, int history)
+struct model *model_new(struct term_vector2i size, int history, const char *name)
{
struct model *m = calloc(1, sizeof(*m));
if (!m) abort();
+ snprintf(m->name, sizeof(m->name), "%s", name && *name ? name : "term");
m->cols = size.x < 1 ? 80 : size.x;
m->rows = size.y < 1 ? 24 : size.y;
m->limit = history_limit(history);
diff --git a/tests/model.h b/tests/model.h
index 9a4316f..1707ef7 100644
--- a/tests/model.h
+++ b/tests/model.h
@@ -28,8 +28,9 @@ struct model_cell {
struct model;
-/* history is the history limit, as in term_options.scrollback. */
-struct model *model_new(struct term_vector2i size, int history);
+/* history is the history limit, as in term_options.scrollback. name is
+ * as in term_options.name. */
+struct model *model_new(struct term_vector2i size, int history, const char *name);
void model_free(struct model *m);
void model_feed(struct model *m, const char *s, size_t len, unsigned flags);
bool model_resize(struct model *m, struct term_vector2i size);
diff --git a/tests/model_row_test.c b/tests/model_row_test.c
index 7afca05..ad4ac2c 100644
--- a/tests/model_row_test.c
+++ b/tests/model_row_test.c
@@ -5,7 +5,7 @@
int main(void)
{
- struct model *m = model_new((struct term_vector2i){ .x = 10, .y = 3 }, 10);
+ struct model *m = model_new((struct term_vector2i){ .x = 10, .y = 3 }, 10, NULL);
model_feed(m, "A\r\nB\r\nC\r\nD", strlen("A\r\nB\r\nC\r\nD"), 0);
assert(model_history(m) == 1);
assert(model_row(m, -1) == NULL);
diff --git a/tests/model_test.c b/tests/model_test.c
index 395097e..e94307c 100644
--- a/tests/model_test.c
+++ b/tests/model_test.c
@@ -256,7 +256,7 @@ static int run(const struct script *s, enum chunking chunks, uint64_t chunk_seed
.callbacks = { .ud = &got, .reply = on_reply },
};
struct term *t = term_new(&opt);
- struct model *m = model_new(s->size, s->history);
+ struct model *m = model_new(s->size, s->history, NULL);
struct term_snapshot *snap = term_snapshot_new();
if (!t || !snap) abort();
uint64_t saved_rng = rng;
diff --git a/tests/osc_test.c b/tests/osc_test.c
index 41a9247..9d08157 100644
--- a/tests/osc_test.c
+++ b/tests/osc_test.c
@@ -104,6 +104,35 @@ static void reset_captures(void)
notify_count = 0;
}
+/* The notification default is term_options.name. A DCS +p does not change it. */
+static void test_host_name_notify(void)
+{
+ struct term_options opt = {
+ .size = { .x = 80, .y = 24 },
+ .scrollback = TEST_SCROLLBACK,
+ .name = "myterm",
+ .callbacks = { .notify = capture_notify },
+ };
+ struct term *t = term_new(&opt);
+ assert(t);
+ reset_captures();
+ write_seq(t, "\033]9;hello\007");
+ assert(notify_count == 1);
+ assert(!strcmp(notify_title, "myterm"));
+ assert(!strcmp(notify_body, "hello"));
+
+ write_seq(t, "\033P+p6f74686572\033\\");
+ reset_captures();
+ write_seq(t, "\033]9;again\007");
+ assert(notify_count == 1);
+ assert(!strcmp(notify_title, "myterm"));
+
+ write_seq(t, "\033]777;notify;Title;Body\007");
+ assert(notify_count == 2);
+ assert(!strcmp(notify_title, "Title"));
+ term_free(t);
+}
+
static void test_osc52(void)
{
struct term *t = osc_test_term();
@@ -289,7 +318,7 @@ static void test_osc_misc(void)
write_seq(t, "\033]9;hello there\033\\");
assert(notify_count == 1);
- assert(!strcmp(notify_title, "wsap_term"));
+ assert(!strcmp(notify_title, "term"));
assert(!strcmp(notify_body, "hello there"));
write_seq(t, "\033]9;4;3;50\007");
assert(notify_count == 1);
@@ -450,6 +479,7 @@ int main(void)
test_osc8();
test_sgr_reset_keeps_link();
test_osc_misc();
+ test_host_name_notify();
test_osc_colors();
test_osc_legacy_titles();
test_palette_and_theme();
diff --git a/tests/term_test.c b/tests/term_test.c
index 66dbd96..e4ffb27 100644
--- a/tests/term_test.c
+++ b/tests/term_test.c
@@ -1485,6 +1485,32 @@ static void test_string_payloads(void)
}
}
+/* term_new() refuses a name that a DCS reply could echo unsafely. */
+static void test_host_name_rejected(void)
+{
+ static const char *const bad[] = {
+ "\033x", "a\tb", "\177", "\300", "a\nb",
+ };
+ char longest[65];
+ memset(longest, 'a', 64);
+ longest[64] = 0;
+ char fits[64];
+ memset(fits, 'b', 63);
+ fits[63] = 0;
+
+ for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
+ struct term_options opt = { .size = { .x = 10, .y = 3 }, .name = bad[i] };
+ assert(!term_new(&opt));
+ }
+ struct term_options opt = { .size = { .x = 10, .y = 3 }, .name = longest };
+ assert(!term_new(&opt));
+
+ opt.name = fits;
+ struct term *t = term_new(&opt);
+ assert(t);
+ term_free(t);
+}
+
static void test_newline_policy_survives_reset(void)
{
static const char *const resets[] = { "\033c", "\033[!p", "\033[20l" };
@@ -1860,6 +1886,7 @@ int main(void)
test_alt_erase_scrollback();
test_snapshot_follows_new_terminal();
test_bulk_rows_survive_edits();
+ test_host_name_rejected();
assert(!strcmp(UNICODE_DATA_VERSION, "18.0.0"));
assert(!strcmp(term_unicode_version(), UNICODE_DATA_VERSION));