[PATCH 06/10] terminal: Basic vt100 escape codes

Callum Lowcay callum at callumscode.com
Fri Jan 7 11:46:59 PST 2011


Implements correct behaviour for vt100 cursor movement, erasing, custom
tabs, and reporting. Includes relevant terminal modes.

Signed-off-by: Callum Lowcay <callum at callumscode.com>
---
 clients/terminal.c |  386 ++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 343 insertions(+), 43 deletions(-)

diff --git a/clients/terminal.c b/clients/terminal.c
index e900298..60da93a 100644
--- a/clients/terminal.c
+++ b/clients/terminal.c
@@ -56,6 +56,13 @@ static int option_fullscreen;
 #define MAX_RESPONSE		11
 #define MAX_ESCAPE		64
 
+/* Terminal modes */
+#define MODE_SHOW_CURSOR	0x00000001
+#define MODE_INVERSE		0x00000002
+#define MODE_AUTOWRAP		0x00000004
+#define MODE_AUTOREPEAT		0x00000008
+#define MODE_LF_NEWLINE		0x00000010
+
 union utf8_char {
 	unsigned char byte[4];
 	uint32_t ch;
@@ -185,13 +192,18 @@ struct terminal {
 	struct window *window;
 	struct display *display;
 	union utf8_char *data;
+	char *tab_ruler;
 	struct attr *data_attr;
 	struct attr curr_attr;
+	uint32_t mode;
 	char origin_mode;
+	char saved_origin_mode;
+	struct attr saved_attr;
 	union utf8_char last_char;
 	int margin_top, margin_bottom;
 	int data_pitch, attr_pitch;  /* The width in bytes of a line */
 	int width, height, start, row, column;
+	int saved_row, saved_column;
 	int fd, master;
 	GIOChannel *channel;
 	uint32_t modifiers;
@@ -209,14 +221,39 @@ struct terminal {
 	cairo_font_face_t *font_normal, *font_bold;
 };
 
+/* Create default tab stops, every 8 characters */
+static void
+terminal_init_tabs(struct terminal *terminal)
+{
+	int i = 0;
+	
+	while (i < terminal->width) {
+		if (i % 8 == 0)
+			terminal->tab_ruler[i] = 1;
+		else
+			terminal->tab_ruler[i] = 0;
+		i++;
+	}
+}
+
 static void
 terminal_init(struct terminal *terminal)
 {
 	terminal->curr_attr = terminal->color_scheme->default_attr;
 	terminal->origin_mode = 0;
+	terminal->mode = MODE_SHOW_CURSOR |
+			 MODE_AUTOREPEAT |
+			 MODE_AUTOWRAP;
 
 	terminal->row = 0;
 	terminal->column = 0;
+
+	terminal->saved_attr = terminal->curr_attr;
+	terminal->saved_origin_mode = terminal->origin_mode;
+	terminal->saved_row = terminal->row;
+	terminal->saved_column = terminal->column;
+
+	if (terminal->tab_ruler != NULL) terminal_init_tabs(terminal);
 }
 
 static void
@@ -362,6 +399,7 @@ terminal_resize(struct terminal *terminal, int width, int height)
 	size_t size;
 	union utf8_char *data;
 	struct attr *data_attr;
+	char *tab_ruler;
 	int data_pitch, attr_pitch;
 	int i, l, total_rows, start;
 	struct rectangle rectangle;
@@ -375,7 +413,9 @@ terminal_resize(struct terminal *terminal, int width, int height)
 	data = malloc(size);
 	attr_pitch = width * sizeof(struct attr);
 	data_attr = malloc(attr_pitch * height);
+	tab_ruler = malloc(width);
 	memset(data, 0, size);
+	memset(tab_ruler, 0, width);
 	attr_init(data_attr, terminal->curr_attr, width * height);
 	if (terminal->data && terminal->data_attr) {
 		if (width > terminal->width)
@@ -402,6 +442,7 @@ terminal_resize(struct terminal *terminal, int width, int height)
 
 		free(terminal->data);
 		free(terminal->data_attr);
+		free(terminal->tab_ruler);
 	}
 
 	terminal->data_pitch = data_pitch;
@@ -412,6 +453,8 @@ terminal_resize(struct terminal *terminal, int width, int height)
 		terminal->margin_bottom = terminal->height - 1;
 	terminal->data = data;
 	terminal->data_attr = data_attr;
+	terminal->tab_ruler = tab_ruler;
+	terminal_init_tabs(terminal);
 
 	if (terminal->row >= terminal->height)
 		terminal->row = terminal->height - 1;
@@ -473,7 +516,7 @@ terminal_draw_contents(struct terminal *terminal)
 		union utf8_char c;
 		char null;
 	} toShow;
-	int foreground, background, bold, underline;
+	int foreground, background, bold, underline, tmp;
 	int text_x, text_y;
 	cairo_surface_t *surface;
 	double d;
@@ -508,8 +551,9 @@ terminal_draw_contents(struct terminal *terminal)
 			/* get the attributes for this character cell */
 			attr = terminal_get_attr(terminal, row, col);
 			if ((attr.a & ATTRMASK_INVERSE) ||
-				(terminal->focused &&
-				terminal->row == row && terminal->column == col))
+				((terminal->mode & MODE_SHOW_CURSOR) &&
+				terminal->focused && terminal->row == row &&
+				terminal->column == col))
 			{
 				foreground = attr.bg;
 				background = attr.fg;
@@ -517,6 +561,11 @@ terminal_draw_contents(struct terminal *terminal)
 				foreground = attr.fg;
 				background = attr.bg;
 			}
+			if (terminal->mode & MODE_INVERSE) {
+				tmp = foreground;
+				foreground = background;
+				background = tmp;
+			}
 			bold = attr.a & (ATTRMASK_BOLD | ATTRMASK_BLINK);
 			underline = attr.a & ATTRMASK_UNDERLINE;
 
@@ -559,7 +608,7 @@ terminal_draw_contents(struct terminal *terminal)
 		}
 	}
 
-	if (!terminal->focused) {
+	if ((terminal->mode & MODE_SHOW_CURSOR) && !terminal->focused) {
 		d = 0.5;
 
 		cairo_set_line_width(cr, 1);
@@ -626,8 +675,29 @@ handle_sgr(struct terminal *terminal, int code);
 static void
 handle_term_parameter(struct terminal *terminal, int code, int sr)
 {
+	int i;
+
 	if (terminal->qmark_flag) {
 		switch(code) {
+		case 3:  /* DECCOLM */
+			if (sr)
+				terminal_resize(terminal, 132, 24);
+			else
+				terminal_resize(terminal, 80, 24);
+			
+			/* set columns, but also home cursor and clear screen */
+			terminal->row = 0; terminal->column = 0;
+			for (i = 0; i < terminal->height; i++) {
+				memset(terminal_get_row(terminal, i),
+				    0, terminal->data_pitch);
+				attr_init(terminal_get_attr_row(terminal, i),
+				    terminal->curr_attr, terminal->width);
+			}
+			break;
+		case 5:  /* DECSCNM */
+			if (sr)	terminal->mode |=  MODE_INVERSE;
+			else	terminal->mode &= ~MODE_INVERSE;
+			break;
 		case 6:  /* DECOM */
 			terminal->origin_mode = sr;
 			if (terminal->origin_mode)
@@ -636,12 +706,28 @@ handle_term_parameter(struct terminal *terminal, int code, int sr)
 				terminal->row = 0;
 			terminal->column = 0;
 			break;
+		case 7:  /* DECAWM */
+			if (sr)	terminal->mode |=  MODE_AUTOWRAP;
+			else	terminal->mode &= ~MODE_AUTOWRAP;
+			break;
+		case 8:  /* DECARM */
+			if (sr)	terminal->mode |=  MODE_AUTOREPEAT;
+			else	terminal->mode &= ~MODE_AUTOREPEAT;
+			break;
+		case 25:
+			if (sr)	terminal->mode |=  MODE_SHOW_CURSOR;
+			else	terminal->mode &= ~MODE_SHOW_CURSOR;
+			break;
 		default:
 			fprintf(stderr, "Unknown parameter: ?%d\n", code);
 			break;
 		}
 	} else {
 		switch(code) {
+		case 20: /* LNM */
+			if (sr)	terminal->mode |=  MODE_LF_NEWLINE;
+			else	terminal->mode &= ~MODE_LF_NEWLINE;
+			break;
 		default:
 			fprintf(stderr, "Unknown parameter: %d\n", code);
 			break;
@@ -655,8 +741,9 @@ handle_escape(struct terminal *terminal)
 	union utf8_char *row;
 	struct attr *attr_row;
 	char *p;
-	int i, count, top, bottom;
+	int i, count, x, y, top, bottom;
 	int args[10], set[10] = { 0, };
+	char response[MAX_RESPONSE] = {0, };
 
 	terminal->escape[terminal->escape_length++] = '\0';
 	i = 0;
@@ -676,58 +763,135 @@ handle_escape(struct terminal *terminal)
 	}
 	
 	switch (*p) {
-	case 'A':
+	case 'A':    /* CUU */
 		count = set[0] ? args[0] : 1;
-		if (terminal->row - count >= 0)
+		if (count == 0) count = 1;
+		if (terminal->row - count >= terminal->margin_top)
 			terminal->row -= count;
 		else
-			terminal->row = 0;
+			terminal->row = terminal->margin_top;
 		break;
-	case 'B':
+	case 'B':    /* CUD */
 		count = set[0] ? args[0] : 1;
-		if (terminal->row + count < terminal->height)
+		if (count == 0) count = 1;
+		if (terminal->row + count <= terminal->margin_bottom)
 			terminal->row += count;
 		else
-			terminal->row = terminal->height;
+			terminal->row = terminal->margin_bottom;
 		break;
-	case 'C':
+	case 'C':    /* CUF */
 		count = set[0] ? args[0] : 1;
-		if (terminal->column + count < terminal->width)
+		if (count == 0) count = 1;
+		if ((terminal->column + count) < terminal->width)
 			terminal->column += count;
 		else
-			terminal->column = terminal->width;
+			terminal->column = terminal->width - 1;
 		break;
-	case 'D':
+	case 'D':    /* CUB */
 		count = set[0] ? args[0] : 1;
-		if (terminal->column - count >= 0)
+		if (count == 0) count = 1;
+		if ((terminal->column - count) >= 0)
 			terminal->column -= count;
 		else
 			terminal->column = 0;
 		break;
-	case 'J':
-		row = terminal_get_row(terminal, terminal->row);
-		attr_row = terminal_get_attr_row(terminal, terminal->row);
-		memset(&row[terminal->column], 0, (terminal->width - terminal->column) * sizeof(union utf8_char));
-		attr_init(&attr_row[terminal->column], terminal->curr_attr, (terminal->width - terminal->column));
-		for (i = terminal->row + 1; i < terminal->height; i++) {
-			memset(terminal_get_row(terminal, i), 0, terminal->width * sizeof(union utf8_char));
-			attr_init(terminal_get_attr_row(terminal, i), terminal->curr_attr, terminal->width);
+	case 'E':    /* CNL */
+		count = set[0] ? args[0] : 1;
+		if (terminal->row + count <= terminal->margin_bottom)
+			terminal->row += count;
+		else
+			terminal->row = terminal->margin_bottom;
+		terminal->column = 0;
+		break;
+	case 'F':    /* CPL */
+		count = set[0] ? args[0] : 1;
+		if (terminal->row - count >= terminal->margin_top)
+			terminal->row -= count;
+		else
+			terminal->row = terminal->margin_top;
+		terminal->column = 0;
+		break;
+	case 'G':    /* CHA */
+		y = set[0] ? args[0] : 1;
+		y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y;
+		
+		terminal->column = y - 1;
+		break;
+	case 'f':    /* HVP */
+	case 'H':    /* CUP */
+		x = (set[1] ? args[1] : 1) - 1;
+		x = x < 0 ? 0 :
+		    (x >= terminal->width ? terminal->width - 1 : x);
+		
+		y = (set[0] ? args[0] : 1) - 1;
+		if (terminal->origin_mode) {
+			y += terminal->margin_top;
+			y = y < terminal->margin_top ? terminal->margin_top :
+			    (y > terminal->margin_bottom ? terminal->margin_bottom : y);
+		} else {
+			y = y < 0 ? 0 :
+			    (y >= terminal->height ? terminal->height - 1 : y);
 		}
+		
+		terminal->row = y;
+		terminal->column = x;
 		break;
-	case 'G':
-		if (set[0])
-			terminal->column = args[0] - 1;
+	case 'I':    /* CHT */
+		count = set[0] ? args[0] : 1;
+		if (count == 0) count = 1;
+		while (count > 0 && terminal->column < terminal->width) {
+			if (terminal->tab_ruler[terminal->column]) count--;
+			terminal->column++;
+		}
+		terminal->column--;
 		break;
-	case 'H':
-	case 'f':
-		terminal->row = set[0] ? args[0] - 1 : 0;
-		terminal->column = set[1] ? args[1] - 1 : 0;
+	case 'J':    /* ED */
+		row = terminal_get_row(terminal, terminal->row);
+		attr_row = terminal_get_attr_row(terminal, terminal->row);
+		if (!set[0] || args[0] == 0 || args[0] > 2) {
+			memset(&row[terminal->column],
+			       0, (terminal->width - terminal->column) * sizeof(union utf8_char));
+			attr_init(&attr_row[terminal->column],
+			       terminal->curr_attr, terminal->width - terminal->column);
+			for (i = terminal->row + 1; i < terminal->height; i++) {
+				memset(terminal_get_row(terminal, i),
+				    0, terminal->data_pitch);
+				attr_init(terminal_get_attr_row(terminal, i),
+				    terminal->curr_attr, terminal->width);
+			}
+		} else if (args[0] == 1) {
+			memset(row, 0, (terminal->column+1) * sizeof(union utf8_char));
+			attr_init(attr_row, terminal->curr_attr, terminal->column+1);
+			for (i = 0; i < terminal->row; i++) {
+				memset(terminal_get_row(terminal, i),
+				    0, terminal->data_pitch);
+				attr_init(terminal_get_attr_row(terminal, i),
+				    terminal->curr_attr, terminal->width);
+			}
+		} else if (args[0] == 2) {
+			for (i = 0; i < terminal->height; i++) {
+				memset(terminal_get_row(terminal, i),
+				    0, terminal->data_pitch);
+				attr_init(terminal_get_attr_row(terminal, i),
+				    terminal->curr_attr, terminal->width);
+			}
+		}
 		break;
-	case 'K':
+	case 'K':    /* EL */
 		row = terminal_get_row(terminal, terminal->row);
 		attr_row = terminal_get_attr_row(terminal, terminal->row);
-		memset(&row[terminal->column], 0, (terminal->width - terminal->column) * sizeof(union utf8_char));
-		attr_init(&attr_row[terminal->column], terminal->curr_attr, (terminal->width - terminal->column));
+		if (!set[0] || args[0] == 0 || args[0] > 2) {
+			memset(&row[terminal->column], 0,
+			    (terminal->width - terminal->column) * sizeof(union utf8_char));
+			attr_init(&attr_row[terminal->column], terminal->curr_attr,
+			    terminal->width - terminal->column);
+		} else if (args[0] == 1) {
+			memset(row, 0, (terminal->column+1) * sizeof(union utf8_char));
+			attr_init(attr_row, terminal->curr_attr, terminal->column+1);
+		} else if (args[0] == 2) {
+			memset(row, 0, terminal->data_pitch);
+			attr_init(attr_row, terminal->curr_attr, terminal->width);
+		}
 		break;
 	case 'S':    /* SU */
 		terminal_scroll(terminal, set[0] ? args[0] : 1);
@@ -735,6 +899,46 @@ handle_escape(struct terminal *terminal)
 	case 'T':    /* SD */
 		terminal_scroll(terminal, 0 - (set[0] ? args[0] : 1));
 		break;
+	case 'Z':    /* CBT */
+		count = set[0] ? args[0] : 1;
+		if (count == 0) count = 1;
+		while (count > 0 && terminal->column >= 0) {
+			if (terminal->tab_ruler[terminal->column]) count--;
+			terminal->column--;
+		}
+		terminal->column++;
+		break;
+	case '`':    /* HPA */
+		y = set[0] ? args[0] : 1;
+		y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y;
+		
+		terminal->column = y - 1;
+		break;
+	case 'b':    /* REP */
+		count = set[0] ? args[0] : 1;
+		if (count == 0) count = 1;
+		if (terminal->last_char.byte[0])
+			for (i = 0; i < count; i++)
+				handle_char(terminal, terminal->last_char);
+		terminal->last_char.byte[0] = 0;
+		break;
+	case 'c':    /* Primary DA */
+		write(terminal->master, "\e[?1;2c", 7);
+		sleep(1);
+		break;
+	case 'd':    /* VPA */
+		x = set[0] ? args[0] : 1;
+		x = x <= 0 ? 1 : x > terminal->height ? terminal->height : x;
+		
+		terminal->row = x - 1;
+		break;
+	case 'g':    /* TBC */
+		if (!set[0] || args[0] == 0) {
+			terminal->tab_ruler[terminal->column] = 0;
+		} else if (args[0] == 3) {
+			memset(terminal->tab_ruler, 0, terminal->width);
+		}
+		break;
 	case 'h':    /* SM */
 		for(i = 0; i < 10 && set[i]; i++) {
 			handle_term_parameter(terminal, args[i], 1);
@@ -766,6 +970,19 @@ handle_escape(struct terminal *terminal)
 			}
 		}
 		break;
+	case 'n':    /* DSR */
+		i = set[0] ? args[0] : 0;
+		if (i == 0 || i == 5) {
+			write(terminal->master, "\e[0n", 4);
+		} else if (i == 6) {
+			snprintf(response, MAX_RESPONSE, "\e[%d;%dR",
+			         terminal->origin_mode ?
+				     terminal->row+terminal->margin_top : terminal->row+1,
+				 terminal->column+1);
+			write(terminal->master, response, strlen(response));
+		}
+		sleep(1);  /* is this required? why? */
+ 		break;
 	case 'r':
 		if(!set[0]) {
 			terminal->margin_top = 0;
@@ -793,6 +1010,14 @@ handle_escape(struct terminal *terminal)
 			terminal->column = 0;
 		}
 		break;
+	case 's':
+		terminal->saved_row = terminal->row;
+		terminal->saved_column = terminal->column;
+		break;
+	case 'u':
+		terminal->row = terminal->saved_row;
+		terminal->column = terminal->saved_column;
+		break;
 	default:
 		fprintf(stderr, "Unknown CSI escape: %c\n", *p);
 		break;
@@ -820,6 +1045,24 @@ handle_non_csi_escape(struct terminal *terminal, char code)
 			terminal_scroll(terminal, +1);
 		}
 		break;
+	case 'c':    /* RIS */
+		terminal_init(terminal);
+		break;
+	case 'H':    /* HTS */
+		terminal->tab_ruler[terminal->column] = 1;
+		break;
+	case '7':    /* DECSC */
+		terminal->saved_row = terminal->row;
+		terminal->saved_column = terminal->column;
+		terminal->saved_attr = terminal->curr_attr;
+		terminal->saved_origin_mode = terminal->origin_mode;
+		break;
+	case '8':    /* DECRC */
+		terminal->row = terminal->saved_row;
+		terminal->column = terminal->saved_column;
+		terminal->curr_attr = terminal->saved_attr;
+		terminal->origin_mode = terminal->saved_origin_mode;
+		break;
 	default:
 		fprintf(stderr, "Unknown escape code: %c\n", code);
 		break;
@@ -829,6 +1072,25 @@ handle_non_csi_escape(struct terminal *terminal, char code)
 static void
 handle_special_escape(struct terminal *terminal, char special, char code)
 {
+	int i, numChars;
+
+	if (special == '#') {
+		switch(code) {
+		case '8':
+			/* fill with 'E', no cheap way to do this */
+			memset(terminal->data, 0, terminal->data_pitch * terminal->height);
+			numChars = terminal->width * terminal->height;
+			for(i = 0; i < numChars; i++) {
+				terminal->data[i].byte[0] = 'E';
+			}
+			break;
+		default:
+			fprintf(stderr, "Unknown HASH escape #%c\n", code);
+			break;
+		}
+	} else {
+		fprintf(stderr, "Unknown special escape %c%c\n", special, code);
+	}
 }
 
 static void
@@ -908,7 +1170,9 @@ handle_special_char(struct terminal *terminal, char c)
 		terminal->column = 0;
 		break;
 	case '\n':
-		terminal->column = 0;
+		if (terminal->mode & MODE_LF_NEWLINE) {
+			terminal->column = 0;
+		}
 		/* fallthrough */
 	case '\v':
 	case '\f':
@@ -920,13 +1184,32 @@ handle_special_char(struct terminal *terminal, char c)
 
 		break;
 	case '\t':
-		memset(&row[terminal->column], ' ', (-terminal->column & 7) * sizeof(union utf8_char));
-		attr_init(&attr_row[terminal->column], terminal->curr_attr, -terminal->column & 7);
-		terminal->column = (terminal->column + 7) & ~7;
+		while (terminal->column < terminal->width) {
+			if (terminal->tab_ruler[terminal->column]) break;
+			row[terminal->column].byte[0] = ' ';
+			row[terminal->column].byte[1] = '\0';
+			attr_row[terminal->column] = terminal->curr_attr;
+			terminal->column++;
+		}
+		if (terminal->column >= terminal->width) {
+			terminal->column = terminal->width - 1;
+		}
+
 		break;
 	case '\b':
-		if (terminal->column > 0)
+		if (terminal->column >= terminal->width) {
+			terminal->column = terminal->width - 2;
+		} else if (terminal->column > 0) {
 			terminal->column--;
+		} else if (terminal->mode & MODE_AUTOWRAP) {
+			terminal->column = terminal->width - 1;
+			terminal->row -= 1;
+			if (terminal->row < terminal->margin_top) {
+				terminal->row = terminal->margin_top;
+				terminal_scroll(terminal, -1);
+			}
+		}
+
 		break;
 	case '\a':
 		/* Bell */
@@ -961,8 +1244,17 @@ handle_char(struct terminal *terminal, union utf8_char utf8)
 	
 	/* handle right margin effects */
 	if (terminal->column >= terminal->width) {
-		terminal->column--;
-	}
+		if (terminal->mode & MODE_AUTOWRAP) {
+			terminal->column = 0;
+			terminal->row += 1;
+			if (terminal->row > terminal->margin_bottom) {
+				terminal->row = terminal->margin_bottom;
+				terminal_scroll(terminal, +1);
+			}
+		} else {
+			terminal->column--;
+ 		}
+ 	}
 	
 	row = terminal_get_row(terminal, terminal->row);
 	attr_row = terminal_get_attr_row(terminal, terminal->row);
@@ -1067,7 +1359,7 @@ key_handler(struct window *window, uint32_t key, uint32_t sym,
 	    uint32_t state, uint32_t modifiers, void *data)
 {
 	struct terminal *terminal = data;
-	char ch[2];
+	char ch[MAX_RESPONSE];
 	int len = 0;
 
 	switch (sym) {
@@ -1085,7 +1377,6 @@ key_handler(struct window *window, uint32_t key, uint32_t sym,
 	case XK_Tab:
 	case XK_Linefeed:
 	case XK_Clear:
-	case XK_Return:
 	case XK_Pause:
 	case XK_Scroll_Lock:
 	case XK_Sys_Req:
@@ -1093,6 +1384,15 @@ key_handler(struct window *window, uint32_t key, uint32_t sym,
 		ch[len++] = sym & 0x7f;
 		break;
 
+	case XK_Return:
+		if (terminal->mode & MODE_LF_NEWLINE) {
+			ch[len++] = 0x0D;
+			ch[len++] = 0x0A;
+		} else {
+			ch[len++] = 0x0D;
+		}
+		break;
+
 	case XK_Shift_L:
 	case XK_Shift_R:
 	case XK_Control_L:
-- 
1.7.3.3



More information about the wayland-devel mailing list