terminal: UTF-8 support

Signed-off-by: Callum Lowcay <callum@callumscode.com>
dev
Callum Lowcay 14 years ago committed by Kristian Høgsberg
parent 052d17d4d9
commit 15bdc5d1d3
  1. 200
      clients/terminal.c

@ -47,10 +47,114 @@ static int option_fullscreen;
#define MOD_ALT 0x02 #define MOD_ALT 0x02
#define MOD_CTRL 0x04 #define MOD_CTRL 0x04
union utf8_char {
unsigned char byte[4];
uint32_t ch;
};
enum utf8_state {
utf8state_start,
utf8state_accept,
utf8state_reject,
utf8state_expect3,
utf8state_expect2,
utf8state_expect1
};
struct utf8_state_machine {
enum utf8_state state;
int len;
union utf8_char s;
};
static void
init_state_machine(struct utf8_state_machine *machine)
{
machine->state = utf8state_start;
machine->len = 0;
machine->s.ch = 0;
}
static enum utf8_state
utf8_next_char(struct utf8_state_machine *machine, char c)
{
switch(machine->state) {
case utf8state_start:
case utf8state_accept:
case utf8state_reject:
machine->s.ch = 0;
machine->len = 0;
if(c == 0xC0 || c == 0xC1) {
/* overlong encoding, reject */
machine->state = utf8state_reject;
} else if((c & 0x80) == 0) {
/* single byte, accept */
machine->s.byte[machine->len++] = c;
machine->state = utf8state_accept;
} else if((c & 0xC0) == 0x80) {
/* parser out of sync, ignore byte */
machine->state = utf8state_start;
} else if((c & 0xE0) == 0xC0) {
/* start of two byte sequence */
machine->s.byte[machine->len++] = c;
machine->state = utf8state_expect1;
} else if((c & 0xF0) == 0xE0) {
/* start of three byte sequence */
machine->s.byte[machine->len++] = c;
machine->state = utf8state_expect2;
} else if((c & 0xF8) == 0xF0) {
/* start of four byte sequence */
machine->s.byte[machine->len++] = c;
machine->state = utf8state_expect3;
} else {
/* overlong encoding, reject */
machine->state = utf8state_reject;
}
break;
case utf8state_expect3:
machine->s.byte[machine->len++] = c;
if((c & 0xC0) == 0x80) {
/* all good, continue */
machine->state = utf8state_expect2;
} else {
/* missing extra byte, reject */
machine->state = utf8state_reject;
}
break;
case utf8state_expect2:
machine->s.byte[machine->len++] = c;
if((c & 0xC0) == 0x80) {
/* all good, continue */
machine->state = utf8state_expect1;
} else {
/* missing extra byte, reject */
machine->state = utf8state_reject;
}
break;
case utf8state_expect1:
machine->s.byte[machine->len++] = c;
if((c & 0xC0) == 0x80) {
/* all good, accept */
machine->state = utf8state_accept;
} else {
/* missing extra byte, reject */
machine->state = utf8state_reject;
}
break;
default:
machine->state = utf8state_reject;
break;
}
return machine->state;
}
struct terminal { struct terminal {
struct window *window; struct window *window;
struct display *display; struct display *display;
char *data; union utf8_char *data;
union utf8_char last_char;
int data_pitch; /* The width in bytes of a line */
int width, height, start, row, column; int width, height, start, row, column;
int fd, master; int fd, master;
GIOChannel *channel; GIOChannel *channel;
@ -58,6 +162,7 @@ struct terminal {
char escape[64]; char escape[64];
int escape_length; int escape_length;
int state; int state;
struct utf8_state_machine state_machine;
int margin; int margin;
int fullscreen; int fullscreen;
int focused; int focused;
@ -65,27 +170,29 @@ struct terminal {
cairo_font_extents_t extents; cairo_font_extents_t extents;
}; };
static char * static union utf8_char *
terminal_get_row(struct terminal *terminal, int row) terminal_get_row(struct terminal *terminal, int row)
{ {
int index; int index;
index = (row + terminal->start) % terminal->height; index = (row + terminal->start) % terminal->height;
return &terminal->data[index * (terminal->width + 1)]; return &terminal->data[index * terminal->width];
} }
static void static void
terminal_resize(struct terminal *terminal, int width, int height) terminal_resize(struct terminal *terminal, int width, int height)
{ {
size_t size; size_t size;
char *data; union utf8_char *data;
int data_pitch;
int i, l, total_rows, start; int i, l, total_rows, start;
if (terminal->width == width && terminal->height == height) if (terminal->width == width && terminal->height == height)
return; return;
size = (width + 1) * height; data_pitch = width * sizeof(union utf8_char);
size = data_pitch * height;
data = malloc(size); data = malloc(size);
memset(data, 0, size); memset(data, 0, size);
if (terminal->data) { if (terminal->data) {
@ -102,13 +209,16 @@ terminal_resize(struct terminal *terminal, int width, int height)
start = 0; start = 0;
} }
for (i = 0; i < total_rows; i++) for (i = 0; i < total_rows; i++) {
memcpy(data + (width + 1) * i, memcpy(&data[width * i],
terminal_get_row(terminal, i), l); terminal_get_row(terminal, i),
l * sizeof(union utf8_char));
}
free(terminal->data); free(terminal->data);
} }
terminal->data_pitch = data_pitch;
terminal->width = width; terminal->width = width;
terminal->height = height; terminal->height = height;
terminal->data = data; terminal->data = data;
@ -130,10 +240,19 @@ terminal_draw_contents(struct terminal *terminal)
struct rectangle rectangle; struct rectangle rectangle;
cairo_t *cr; cairo_t *cr;
cairo_font_extents_t extents; cairo_font_extents_t extents;
int i, top_margin, side_margin; int top_margin, side_margin;
int row, col;
union utf8_char *p_row;
struct utf8_chars {
union utf8_char c;
char null;
} toShow;
int text_x, text_y;
cairo_surface_t *surface; cairo_surface_t *surface;
double d; double d;
toShow.null = 0;
window_get_child_rectangle(terminal->window, &rectangle); window_get_child_rectangle(terminal->window, &rectangle);
surface = display_create_surface(terminal->display, &rectangle); surface = display_create_surface(terminal->display, &rectangle);
@ -161,10 +280,17 @@ terminal_draw_contents(struct terminal *terminal)
side_margin = (rectangle.width - terminal->width * extents.max_x_advance) / 2; side_margin = (rectangle.width - terminal->width * extents.max_x_advance) / 2;
top_margin = (rectangle.height - terminal->height * extents.height) / 2; top_margin = (rectangle.height - terminal->height * extents.height) / 2;
for (i = 0; i < terminal->height; i++) { for (row = 0; row < terminal->height; row++) {
cairo_move_to(cr, side_margin, p_row = terminal_get_row(terminal, row);
top_margin + extents.ascent + extents.height * i); for (col = 0; col < terminal->width; col++) {
cairo_show_text(cr, terminal_get_row(terminal, i)); /* paint the foreground */
text_x = side_margin + col * extents.max_x_advance;
text_y = top_margin + extents.ascent + row * extents.height;
cairo_move_to(cr, text_x, text_y);
toShow.c = p_row[col];
cairo_show_text(cr, (char *) toShow.c.byte);
}
} }
d = terminal->focused ? 0 : 0.5; d = terminal->focused ? 0 : 0.5;
@ -235,7 +361,8 @@ terminal_data(struct terminal *terminal, const char *data, size_t length);
static void static void
handle_escape(struct terminal *terminal) handle_escape(struct terminal *terminal)
{ {
char *row, *p; union utf8_char *row;
char *p;
int i, count; int i, count;
int args[10], set[10] = { 0, }; int args[10], set[10] = { 0, };
@ -283,9 +410,9 @@ handle_escape(struct terminal *terminal)
break; break;
case 'J': case 'J':
row = terminal_get_row(terminal, terminal->row); row = terminal_get_row(terminal, terminal->row);
memset(&row[terminal->column], 0, terminal->width - terminal->column); memset(&row[terminal->column], 0, (terminal->width - terminal->column) * sizeof(union utf8_char));
for (i = terminal->row + 1; i < terminal->height; i++) for (i = terminal->row + 1; i < terminal->height; i++)
memset(terminal_get_row(terminal, i), 0, terminal->width); memset(terminal_get_row(terminal, i), 0, terminal->width * sizeof(union utf8_char));
break; break;
case 'G': case 'G':
if (set[0]) if (set[0])
@ -298,7 +425,7 @@ handle_escape(struct terminal *terminal)
break; break;
case 'K': case 'K':
row = terminal_get_row(terminal, terminal->row); row = terminal_get_row(terminal, terminal->row);
memset(&row[terminal->column], 0, terminal->width - terminal->column); memset(&row[terminal->column], 0, (terminal->width - terminal->column) * sizeof(union utf8_char));
break; break;
case 'm': case 'm':
/* color, blink, bold etc*/ /* color, blink, bold etc*/
@ -322,20 +449,39 @@ static void
terminal_data(struct terminal *terminal, const char *data, size_t length) terminal_data(struct terminal *terminal, const char *data, size_t length)
{ {
int i; int i;
char *row; union utf8_char utf8;
enum utf8_state parser_state;
union utf8_char *row;
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
parser_state =
utf8_next_char(&terminal->state_machine, data[i]);
switch(parser_state) {
case utf8state_accept:
utf8.ch = terminal->state_machine.s.ch;
break;
case utf8state_reject:
/* the unicode replacement character */
utf8.byte[0] = 0xEF;
utf8.byte[1] = 0xBF;
utf8.byte[2] = 0xBD;
utf8.byte[3] = 0x00;
break;
default:
continue;
}
row = terminal_get_row(terminal, terminal->row); row = terminal_get_row(terminal, terminal->row);
if (terminal->state == STATE_ESCAPE) { if (terminal->state == STATE_ESCAPE) {
terminal->escape[terminal->escape_length++] = data[i]; terminal->escape[terminal->escape_length++] = utf8.byte[0];
if (terminal->escape_length == 2 && data[i] != '[') { if (terminal->escape_length == 2 && utf8.byte[0] != '[') {
/* Bad escape sequence. */ /* Bad escape sequence. */
terminal->state = STATE_NORMAL; terminal->state = STATE_NORMAL;
goto cancel_escape; goto cancel_escape;
} }
if (isalpha(data[i])) { if (isalpha(utf8.byte[0])) {
terminal->state = STATE_NORMAL; terminal->state = STATE_NORMAL;
handle_escape(terminal); handle_escape(terminal);
} }
@ -343,7 +489,7 @@ terminal_data(struct terminal *terminal, const char *data, size_t length)
} }
cancel_escape: cancel_escape:
switch (data[i]) { switch (utf8.byte[0]) {
case '\r': case '\r':
terminal->column = 0; terminal->column = 0;
break; break;
@ -356,12 +502,12 @@ terminal_data(struct terminal *terminal, const char *data, size_t length)
if (terminal->start == terminal->height) if (terminal->start == terminal->height)
terminal->start = 0; terminal->start = 0;
memset(terminal_get_row(terminal, terminal->row), memset(terminal_get_row(terminal, terminal->row),
0, terminal->width); 0, terminal->width * sizeof(union utf8_char));
} }
break; break;
case '\t': case '\t':
memset(&row[terminal->column], ' ', -terminal->column & 7); memset(&row[terminal->column], ' ', (-terminal->column & 7) * sizeof(union utf8_char));
terminal->column = (terminal->column + 7) & ~7; terminal->column = (terminal->column + 7) & ~7;
break; break;
case '\e': case '\e':
@ -378,7 +524,8 @@ terminal_data(struct terminal *terminal, const char *data, size_t length)
break; break;
default: default:
if (terminal->column < terminal->width) if (terminal->column < terminal->width)
row[terminal->column++] = data[i] < 32 ? data[i] + 64 : data[i]; if (utf8.byte[0] < 32) utf8.byte[0] += 64;
row[terminal->column++] = utf8;
break; break;
} }
} }
@ -465,6 +612,9 @@ terminal_create(struct display *display, int fullscreen)
terminal->color_scheme = &jbarnes_colors; terminal->color_scheme = &jbarnes_colors;
terminal->window = window_create(display, "Wayland Terminal", terminal->window = window_create(display, "Wayland Terminal",
500, 400); 500, 400);
init_state_machine(&terminal->state_machine);
terminal->display = display; terminal->display = display;
terminal->margin = 5; terminal->margin = 5;

Loading…
Cancel
Save