Commit: c86265503880612d85f924f794061aea978a8d25
Parent: 0940841f6decc5c9eeaca2997a06ad5eb4aa2438
Author: Randy Palamar
Date: Sun, 7 Jul 2024 11:58:56 -0600
osc and window title handling
Diffstat:
6 files changed, 149 insertions(+), 15 deletions(-)
diff --git a/config.def.h b/config.def.h
@@ -1,3 +1,6 @@
+/* See LICENSE for copyright details */
+static s8 g_default_title = s8("vtgl");
+
static u8 g_tabstop = 8;
static Colour base16_colours[16] = {
diff --git a/main.c b/main.c
@@ -85,7 +85,7 @@ error_callback(int code, const char *desc)
}
static void
-init_window(Term *t)
+init_window(Term *t, Arena arena)
{
if (!glfwInit())
die("Failed to init GLFW\n");
@@ -106,7 +106,8 @@ init_window(Term *t)
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
- t->gl.window = glfwCreateWindow(t->gl.window_size.w, t->gl.window_size.h, "vtgl", NULL, NULL);
+ char *title = s8_to_cstr(&arena, g_default_title);
+ t->gl.window = glfwCreateWindow(t->gl.window_size.w, t->gl.window_size.h, title, NULL, NULL);
if (!t->gl.window) {
glfwTerminate();
die("Failed to spawn GLFW window\n");
@@ -284,7 +285,7 @@ main(void)
static char *font_paths[] = {
"/usr/share/fonts/gofont/Go-Mono.ttf",
};
- init_window(&term);
+ init_window(&term, memory);
init_fonts(&term, font_paths, ARRAY_COUNT(font_paths), 48, &memory);
cursor_reset(&term);
diff --git a/terminal.c b/terminal.c
@@ -10,6 +10,12 @@ typedef struct {
u8 priv;
} CSI;
+typedef struct {
+ s8 raw;
+ s8 arg;
+ i32 cmd;
+} OSC;
+
static const u8 utf8overhangmask[32] = {
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
@@ -17,6 +23,12 @@ static const u8 utf8overhangmask[32] = {
0, 0, 0, 0, 0, 0, 0, 0
};
+static void
+set_window_title(GLFWwindow *win, Arena a, s8 title)
+{
+ glfwSetWindowTitle(win, s8_to_cstr(&a, title));
+}
+
static v2
get_cell_size(Term *t)
{
@@ -372,8 +384,8 @@ static void
window_manipulation(Term *t, CSI *csi)
{
switch (csi->argv[0]) {
- case 22: /* TODO: save title */ break;
- case 23: /* TODO: restore title */ break;
+ case 22: t->saved_title = glfwGetWindowTitle(t->gl.window); break;
+ case 23: glfwSetWindowTitle(t->gl.window, t->saved_title); break;
default:
fprintf(stderr, "unhandled xtwinops: %d\n", csi->argv[0]);
dump_csi(csi);
@@ -444,12 +456,98 @@ handle_csi(Term *t, s8 *raw)
}
}
+static OSC
+parse_osc(s8 *raw)
+{
+ OSC osc = {0};
+
+ osc.raw.data = raw->data;
+
+ /* NOTE: parse command then store the rest as a string */
+ u32 cp;
+ while (raw->len) {
+ cp = get_ascii(raw);
+ osc.raw.len++;
+ if (!BETWEEN(cp, '0', '9'))
+ break;
+ osc.cmd *= 10;
+ osc.cmd += cp - '0';
+
+ /* TODO: Performance? */
+ /* NOTE: The maximum OSC in xterm is 119 so if this
+ * exceeds that the whole sequence is malformed */
+ if (osc.cmd > 1000)
+ break;
+ }
+
+ if (cp != ';' || osc.cmd > 1000)
+ die("parse_osc: malformed\n");
+
+ osc.arg.data = raw->data;
+ while (raw->len) {
+ cp = get_ascii(raw);
+ osc.raw.len++;
+ if (cp == '\a')
+ break;
+ if (cp == 0x1B && peek(*raw, 0) == '\\') {
+ get_ascii(raw);
+ osc.raw.len++;
+ break;
+ }
+ osc.arg.len++;
+ }
+
+ /* NOTE: if raw->len is 0 we ran out of characters */
+ if (raw->len == 0)
+ osc.cmd = -1;
+
+ return osc;
+}
+
+static void
+dump_osc(OSC *osc)
+{
+ fputs("ESC]", stderr);
+ for (size i = 0; i < osc->raw.len; i++) {
+ u8 cp = osc->raw.data[i];
+ if (ISPRINT(cp))
+ fputc(cp, stderr);
+ else if (cp == '\n')
+ fputs("\\n", stderr);
+ else if (cp == '\r')
+ fputs("\\r", stderr);
+ else if (cp == '\a')
+ fputs("\\a", stderr);
+ else
+ fprintf(stderr, "\\x%02X", cp);
+ }
+ fprintf(stderr, "\n\t.cmd = %d, .arg = {.len = %zd}\n", osc->cmd, osc->arg.len);
+}
+
+static void
+handle_osc(Term *t, s8 *raw, Arena a)
+{
+ OSC osc = parse_osc(raw);
+
+ switch (osc.cmd) {
+ case 0: set_window_title(t->gl.window, a, osc.arg); break;
+ case 1: /* IGNORED: set icon name */ break;
+ case 2: set_window_title(t->gl.window, a, osc.arg); break;
+ case -1:
+ default:
+ fputs("unhandled osc cmd: ", stderr);
+ dump_osc(&osc);
+ break;
+ }
+}
+
static void
-handle_escape(Term *t, s8 *raw)
+handle_escape(Term *t, s8 *raw, Arena a)
{
u32 cp = get_ascii(raw);
switch(cp) {
- case '[': handle_csi(t, raw); break;
+ case '[': handle_csi(t, raw); break;
+ case ']': handle_osc(t, raw, a); break;
case '(': /* GZD4 -- set primary charset G0 */
case ')': /* G1D4 -- set secondary charset G1 */
case '*': /* G2D4 -- set tertiary charset G2 */
@@ -484,6 +582,16 @@ enum escape_moves_cursor_result {
};
static enum escape_moves_cursor_result
+validate_osc(Term *t, s8 *raw)
+{
+ enum escape_moves_cursor_result result = EMC_NORMAL_RETURN;
+ OSC osc = parse_osc(raw);
+ if (osc.cmd == -1)
+ return EMC_NEEDS_MORE_BYTES;
+ return result;
+}
+
+static enum escape_moves_cursor_result
check_if_csi_moves_cursor(Term *t, s8 *raw)
{
enum escape_moves_cursor_result result = EMC_NORMAL_RETURN;
@@ -511,6 +619,9 @@ check_if_escape_moves_cursor(Term *t, s8 *raw)
case '[':
result = check_if_csi_moves_cursor(t, raw);
break;
+ case ']':
+ result = validate_osc(t, raw);
+ break;
case '(': /* GZD4 -- set primary charset G0 */
case ')': /* G1D4 -- set secondary charset G1 */
case '*': /* G2D4 -- set tertiary charset G2 */
@@ -632,7 +743,7 @@ push_tab(Term *t)
}
static void
-push_line(Term *t, Line *line)
+push_line(Term *t, Line *line, Arena a)
{
s8 l = line_to_s8(line);
@@ -643,10 +754,10 @@ push_line(Term *t, Line *line)
/* TODO: handle unicode case */
u32 cp = get_ascii(&l);
switch (cp) {
- case 0x1B: handle_escape(t, &l); break;
- case '\r': t->cursor.col = 0; break;
- case '\n': push_newline(t); break;
- case '\t': push_tab(t); break;
+ case 0x1B: handle_escape(t, &l, a); break;
+ case '\r': t->cursor.col = 0; break;
+ case '\n': push_newline(t); break;
+ case '\t': push_tab(t); break;
case '\b':
cursor_move_to(t, t->cursor.row, t->cursor.col - 1);
break;
@@ -673,7 +784,7 @@ get_line_idx(LineBuf *lb, size off)
}
static void
-blit_lines(Term *t)
+blit_lines(Term *t, Arena a)
{
size line_count = t->size.h;
if (line_count > t->log_lines.filled)
@@ -687,6 +798,6 @@ blit_lines(Term *t)
t->cursor.col = 0;
for (size i = 0; i <= line_count; i++) {
size line_idx = get_line_idx(&t->log_lines, -line_count + i);
- push_line(t, t->log_lines.buf + line_idx);
+ push_line(t, t->log_lines.buf + line_idx, a);
}
}
diff --git a/util.c b/util.c
@@ -43,3 +43,13 @@ s8alloc(Arena *a, size len)
{
return (s8){.len = len, .data = alloc(a, u8, len)};
}
+
+static char *
+s8_to_cstr(Arena *a, s8 s)
+{
+ char *cstr = alloc(a, char, s.len + 1);
+ for (size i = 0; i < s.len; i++)
+ cstr[i] = s.data[i];
+ cstr[s.len] = 0;
+ return cstr;
+}
diff --git a/util.h b/util.h
@@ -237,6 +237,11 @@ typedef struct {
i32 deltay;
} FontAtlas;
+enum terminal_mode {
+ TM_ALTSCEEN = 1 << 0,
+ TM_REPLACE = 1 << 1,
+};
+
typedef struct {
GLCtx gl;
FontAtlas fa;
@@ -251,6 +256,10 @@ typedef struct {
uv2 size;
Cursor cursor;
+ u32 mode;
+
+ /* NOTE: owned by GLFW; doesn't need to be freed */
+ const char *saved_title;
FT_Library ftlib;
} Term;
diff --git a/vtgl.c b/vtgl.c
@@ -382,7 +382,7 @@ do_terminal(Term *t, Arena a)
clear_colour();
- blit_lines(t);
+ blit_lines(t, a);
render_framebuffer(t, rpb);
v2 cell_size = get_cell_size(t);