bf17590f86
It seems that this was only used by the popup menu infrastructure, which can handle this all on its own. Implementing e.g. transients in the future can be done with a simple xdg_shell_set_transient_for.
2965 lines
78 KiB
C
2965 lines
78 KiB
C
/*
|
|
* Copyright © 2008 Kristian Høgsberg
|
|
*
|
|
* Permission to use, copy, modify, distribute, and sell this software and its
|
|
* documentation for any purpose is hereby granted without fee, provided that
|
|
* the above copyright notice appear in all copies and that both that copyright
|
|
* notice and this permission notice appear in supporting documentation, and
|
|
* that the name of the copyright holders not be used in advertising or
|
|
* publicity pertaining to distribution of the software without specific,
|
|
* written prior permission. The copyright holders make no representations
|
|
* about the suitability of this software for any purpose. It is provided "as
|
|
* is" without express or implied warranty.
|
|
*
|
|
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
|
|
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
|
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
|
* OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <pty.h>
|
|
#include <ctype.h>
|
|
#include <cairo.h>
|
|
#include <sys/epoll.h>
|
|
#include <wchar.h>
|
|
#include <locale.h>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <wayland-client.h>
|
|
|
|
#include "../shared/config-parser.h"
|
|
#include "window.h"
|
|
|
|
static int option_fullscreen;
|
|
static char *option_font;
|
|
static int option_font_size;
|
|
static char *option_term;
|
|
static char *option_shell;
|
|
|
|
static struct wl_list terminal_list;
|
|
|
|
static struct terminal *
|
|
terminal_create(struct display *display);
|
|
static void
|
|
terminal_destroy(struct terminal *terminal);
|
|
static int
|
|
terminal_run(struct terminal *terminal, const char *path);
|
|
|
|
#define TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS \
|
|
" !\"#$%&'()*+,-./" \
|
|
"0123456789" \
|
|
":;<=>?@" \
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
|
|
"[\\]^_`" \
|
|
"abcdefghijklmnopqrstuvwxyz" \
|
|
"{|}~" \
|
|
""
|
|
|
|
#define MOD_SHIFT 0x01
|
|
#define MOD_ALT 0x02
|
|
#define MOD_CTRL 0x04
|
|
|
|
#define ATTRMASK_BOLD 0x01
|
|
#define ATTRMASK_UNDERLINE 0x02
|
|
#define ATTRMASK_BLINK 0x04
|
|
#define ATTRMASK_INVERSE 0x08
|
|
#define ATTRMASK_CONCEALED 0x10
|
|
|
|
/* Buffer sizes */
|
|
#define MAX_RESPONSE 256
|
|
#define MAX_ESCAPE 255
|
|
|
|
/* Terminal modes */
|
|
#define MODE_SHOW_CURSOR 0x00000001
|
|
#define MODE_INVERSE 0x00000002
|
|
#define MODE_AUTOWRAP 0x00000004
|
|
#define MODE_AUTOREPEAT 0x00000008
|
|
#define MODE_LF_NEWLINE 0x00000010
|
|
#define MODE_IRM 0x00000020
|
|
#define MODE_DELETE_SENDS_DEL 0x00000040
|
|
#define MODE_ALT_SENDS_ESC 0x00000080
|
|
|
|
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;
|
|
uint32_t unicode;
|
|
};
|
|
|
|
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, unsigned 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;
|
|
machine->unicode = c;
|
|
} 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;
|
|
machine->unicode = c & 0x1f;
|
|
} else if((c & 0xF0) == 0xE0) {
|
|
/* start of three byte sequence */
|
|
machine->s.byte[machine->len++] = c;
|
|
machine->state = utf8state_expect2;
|
|
machine->unicode = c & 0x0f;
|
|
} else if((c & 0xF8) == 0xF0) {
|
|
/* start of four byte sequence */
|
|
machine->s.byte[machine->len++] = c;
|
|
machine->state = utf8state_expect3;
|
|
machine->unicode = c & 0x07;
|
|
} else {
|
|
/* overlong encoding, reject */
|
|
machine->state = utf8state_reject;
|
|
}
|
|
break;
|
|
case utf8state_expect3:
|
|
machine->s.byte[machine->len++] = c;
|
|
machine->unicode = (machine->unicode << 6) | (c & 0x3f);
|
|
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;
|
|
machine->unicode = (machine->unicode << 6) | (c & 0x3f);
|
|
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;
|
|
machine->unicode = (machine->unicode << 6) | (c & 0x3f);
|
|
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;
|
|
}
|
|
|
|
static uint32_t
|
|
get_unicode(union utf8_char utf8)
|
|
{
|
|
struct utf8_state_machine machine;
|
|
int i;
|
|
|
|
init_state_machine(&machine);
|
|
for (i = 0; i < 4; i++) {
|
|
utf8_next_char(&machine, utf8.byte[i]);
|
|
if (machine.state == utf8state_accept ||
|
|
machine.state == utf8state_reject)
|
|
break;
|
|
}
|
|
|
|
if (machine.state == utf8state_reject)
|
|
return 0xfffd;
|
|
|
|
return machine.unicode;
|
|
}
|
|
|
|
static bool
|
|
is_wide(union utf8_char utf8)
|
|
{
|
|
uint32_t unichar = get_unicode(utf8);
|
|
return wcwidth(unichar) > 1;
|
|
}
|
|
|
|
struct char_sub {
|
|
union utf8_char match;
|
|
union utf8_char replace;
|
|
};
|
|
/* Set last char_sub match to NULL char */
|
|
typedef struct char_sub *character_set;
|
|
|
|
struct char_sub CS_US[] = {
|
|
{{{0, }}, {{0, }}}
|
|
};
|
|
static struct char_sub CS_UK[] = {
|
|
{{{'#', 0, }}, {{0xC2, 0xA3, 0, }}}, /* POUND: £ */
|
|
{{{0, }}, {{0, }}}
|
|
};
|
|
static struct char_sub CS_SPECIAL[] = {
|
|
{{{'`', 0, }}, {{0xE2, 0x99, 0xA6, 0}}}, /* diamond: ♦ */
|
|
{{{'a', 0, }}, {{0xE2, 0x96, 0x92, 0}}}, /* 50% cell: ▒ */
|
|
{{{'b', 0, }}, {{0xE2, 0x90, 0x89, 0}}}, /* HT: ␉ */
|
|
{{{'c', 0, }}, {{0xE2, 0x90, 0x8C, 0}}}, /* FF: ␌ */
|
|
{{{'d', 0, }}, {{0xE2, 0x90, 0x8D, 0}}}, /* CR: ␍ */
|
|
{{{'e', 0, }}, {{0xE2, 0x90, 0x8A, 0}}}, /* LF: ␊ */
|
|
{{{'f', 0, }}, {{0xC2, 0xB0, 0, }}}, /* Degree: ° */
|
|
{{{'g', 0, }}, {{0xC2, 0xB1, 0, }}}, /* Plus/Minus: ± */
|
|
{{{'h', 0, }}, {{0xE2, 0x90, 0xA4, 0}}}, /* NL:  */
|
|
{{{'i', 0, }}, {{0xE2, 0x90, 0x8B, 0}}}, /* VT: ␋ */
|
|
{{{'j', 0, }}, {{0xE2, 0x94, 0x98, 0}}}, /* CN_RB: ┘ */
|
|
{{{'k', 0, }}, {{0xE2, 0x94, 0x90, 0}}}, /* CN_RT: ┐ */
|
|
{{{'l', 0, }}, {{0xE2, 0x94, 0x8C, 0}}}, /* CN_LT: ┌ */
|
|
{{{'m', 0, }}, {{0xE2, 0x94, 0x94, 0}}}, /* CN_LB: └ */
|
|
{{{'n', 0, }}, {{0xE2, 0x94, 0xBC, 0}}}, /* CROSS: ┼ */
|
|
{{{'o', 0, }}, {{0xE2, 0x8E, 0xBA, 0}}}, /* Horiz. Scan Line 1: ⎺ */
|
|
{{{'p', 0, }}, {{0xE2, 0x8E, 0xBB, 0}}}, /* Horiz. Scan Line 3: ⎻ */
|
|
{{{'q', 0, }}, {{0xE2, 0x94, 0x80, 0}}}, /* Horiz. Scan Line 5: ─ */
|
|
{{{'r', 0, }}, {{0xE2, 0x8E, 0xBC, 0}}}, /* Horiz. Scan Line 7: ⎼ */
|
|
{{{'s', 0, }}, {{0xE2, 0x8E, 0xBD, 0}}}, /* Horiz. Scan Line 9: ⎽ */
|
|
{{{'t', 0, }}, {{0xE2, 0x94, 0x9C, 0}}}, /* TR: ├ */
|
|
{{{'u', 0, }}, {{0xE2, 0x94, 0xA4, 0}}}, /* TL: ┤ */
|
|
{{{'v', 0, }}, {{0xE2, 0x94, 0xB4, 0}}}, /* TU: ┴ */
|
|
{{{'w', 0, }}, {{0xE2, 0x94, 0xAC, 0}}}, /* TD: ┬ */
|
|
{{{'x', 0, }}, {{0xE2, 0x94, 0x82, 0}}}, /* V: │ */
|
|
{{{'y', 0, }}, {{0xE2, 0x89, 0xA4, 0}}}, /* LE: ≤ */
|
|
{{{'z', 0, }}, {{0xE2, 0x89, 0xA5, 0}}}, /* GE: ≥ */
|
|
{{{'{', 0, }}, {{0xCF, 0x80, 0, }}}, /* PI: π */
|
|
{{{'|', 0, }}, {{0xE2, 0x89, 0xA0, 0}}}, /* NEQ: ≠ */
|
|
{{{'}', 0, }}, {{0xC2, 0xA3, 0, }}}, /* POUND: £ */
|
|
{{{'~', 0, }}, {{0xE2, 0x8B, 0x85, 0}}}, /* DOT: ⋅ */
|
|
{{{0, }}, {{0, }}}
|
|
};
|
|
|
|
static void
|
|
apply_char_set(character_set cs, union utf8_char *utf8)
|
|
{
|
|
int i = 0;
|
|
|
|
while (cs[i].match.byte[0]) {
|
|
if ((*utf8).ch == cs[i].match.ch) {
|
|
*utf8 = cs[i].replace;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
struct key_map {
|
|
int sym;
|
|
int num;
|
|
char escape;
|
|
char code;
|
|
};
|
|
/* Set last key_sub sym to NULL */
|
|
typedef struct key_map *keyboard_mode;
|
|
|
|
static struct key_map KM_NORMAL[] = {
|
|
{ XKB_KEY_Left, 1, '[', 'D' },
|
|
{ XKB_KEY_Right, 1, '[', 'C' },
|
|
{ XKB_KEY_Up, 1, '[', 'A' },
|
|
{ XKB_KEY_Down, 1, '[', 'B' },
|
|
{ XKB_KEY_Home, 1, '[', 'H' },
|
|
{ XKB_KEY_End, 1, '[', 'F' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
static struct key_map KM_APPLICATION[] = {
|
|
{ XKB_KEY_Left, 1, 'O', 'D' },
|
|
{ XKB_KEY_Right, 1, 'O', 'C' },
|
|
{ XKB_KEY_Up, 1, 'O', 'A' },
|
|
{ XKB_KEY_Down, 1, 'O', 'B' },
|
|
{ XKB_KEY_Home, 1, 'O', 'H' },
|
|
{ XKB_KEY_End, 1, 'O', 'F' },
|
|
{ XKB_KEY_KP_Enter, 1, 'O', 'M' },
|
|
{ XKB_KEY_KP_Multiply, 1, 'O', 'j' },
|
|
{ XKB_KEY_KP_Add, 1, 'O', 'k' },
|
|
{ XKB_KEY_KP_Separator, 1, 'O', 'l' },
|
|
{ XKB_KEY_KP_Subtract, 1, 'O', 'm' },
|
|
{ XKB_KEY_KP_Divide, 1, 'O', 'o' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
static int
|
|
function_key_response(char escape, int num, uint32_t modifiers,
|
|
char code, char *response)
|
|
{
|
|
int mod_num = 0;
|
|
int len;
|
|
|
|
if (modifiers & MOD_SHIFT_MASK) mod_num |= 1;
|
|
if (modifiers & MOD_ALT_MASK) mod_num |= 2;
|
|
if (modifiers & MOD_CONTROL_MASK) mod_num |= 4;
|
|
|
|
if (mod_num != 0)
|
|
len = snprintf(response, MAX_RESPONSE, "\e[%d;%d%c",
|
|
num, mod_num + 1, code);
|
|
else if (code != '~')
|
|
len = snprintf(response, MAX_RESPONSE, "\e%c%c",
|
|
escape, code);
|
|
else
|
|
len = snprintf(response, MAX_RESPONSE, "\e%c%d%c",
|
|
escape, num, code);
|
|
|
|
if (len >= MAX_RESPONSE) return MAX_RESPONSE - 1;
|
|
else return len;
|
|
}
|
|
|
|
/* returns the number of bytes written into response,
|
|
* which must have room for MAX_RESPONSE bytes */
|
|
static int
|
|
apply_key_map(keyboard_mode mode, int sym, uint32_t modifiers, char *response)
|
|
{
|
|
struct key_map map;
|
|
int len = 0;
|
|
int i = 0;
|
|
|
|
while (mode[i].sym) {
|
|
map = mode[i++];
|
|
if (sym == map.sym) {
|
|
len = function_key_response(map.escape, map.num,
|
|
modifiers, map.code,
|
|
response);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
struct terminal_color { double r, g, b, a; };
|
|
struct attr {
|
|
unsigned char fg, bg;
|
|
char a; /* attributes format:
|
|
* 76543210
|
|
* cilub */
|
|
char s; /* in selection */
|
|
};
|
|
struct color_scheme {
|
|
struct terminal_color palette[16];
|
|
char border;
|
|
struct attr default_attr;
|
|
};
|
|
|
|
static void
|
|
attr_init(struct attr *data_attr, struct attr attr, int n)
|
|
{
|
|
int i;
|
|
for (i = 0; i < n; i++) {
|
|
data_attr[i] = attr;
|
|
}
|
|
}
|
|
|
|
enum escape_state {
|
|
escape_state_normal = 0,
|
|
escape_state_escape,
|
|
escape_state_dcs,
|
|
escape_state_csi,
|
|
escape_state_osc,
|
|
escape_state_inner_escape,
|
|
escape_state_ignore,
|
|
escape_state_special
|
|
};
|
|
|
|
#define ESC_FLAG_WHAT 0x01
|
|
#define ESC_FLAG_GT 0x02
|
|
#define ESC_FLAG_BANG 0x04
|
|
#define ESC_FLAG_CASH 0x08
|
|
#define ESC_FLAG_SQUOTE 0x10
|
|
#define ESC_FLAG_DQUOTE 0x20
|
|
#define ESC_FLAG_SPACE 0x40
|
|
|
|
enum {
|
|
SELECT_NONE,
|
|
SELECT_CHAR,
|
|
SELECT_WORD,
|
|
SELECT_LINE
|
|
};
|
|
|
|
struct terminal {
|
|
struct window *window;
|
|
struct widget *widget;
|
|
struct display *display;
|
|
char *title;
|
|
union utf8_char *data;
|
|
struct task io_task;
|
|
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;
|
|
character_set cs, g0, g1;
|
|
character_set saved_cs, saved_g0, saved_g1;
|
|
keyboard_mode key_mode;
|
|
int data_pitch, attr_pitch; /* The width in bytes of a line */
|
|
int width, height, row, column, max_width;
|
|
uint32_t buffer_height;
|
|
uint32_t start, end, saved_start, log_size;
|
|
int saved_row, saved_column;
|
|
int scrolling;
|
|
int send_cursor_position;
|
|
int fd, master;
|
|
uint32_t modifiers;
|
|
char escape[MAX_ESCAPE+1];
|
|
int escape_length;
|
|
enum escape_state state;
|
|
enum escape_state outer_state;
|
|
int escape_flags;
|
|
struct utf8_state_machine state_machine;
|
|
int margin;
|
|
struct color_scheme *color_scheme;
|
|
struct terminal_color color_table[256];
|
|
cairo_font_extents_t extents;
|
|
double average_width;
|
|
cairo_scaled_font_t *font_normal, *font_bold;
|
|
uint32_t hide_cursor_serial;
|
|
|
|
struct wl_data_source *selection;
|
|
uint32_t button_time;
|
|
int dragging, click_count;
|
|
int selection_start_x, selection_start_y;
|
|
int selection_end_x, selection_end_y;
|
|
int selection_start_row, selection_start_col;
|
|
int selection_end_row, selection_end_col;
|
|
struct wl_list link;
|
|
};
|
|
|
|
/* 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_ALT_SENDS_ESC |
|
|
MODE_AUTOWRAP;
|
|
|
|
terminal->row = 0;
|
|
terminal->column = 0;
|
|
|
|
terminal->g0 = CS_US;
|
|
terminal->g1 = CS_US;
|
|
terminal->cs = terminal->g0;
|
|
terminal->key_mode = KM_NORMAL;
|
|
|
|
terminal->saved_g0 = terminal->g0;
|
|
terminal->saved_g1 = terminal->g1;
|
|
terminal->saved_cs = terminal->cs;
|
|
|
|
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
|
|
init_color_table(struct terminal *terminal)
|
|
{
|
|
int c, r;
|
|
struct terminal_color *color_table = terminal->color_table;
|
|
|
|
for (c = 0; c < 256; c ++) {
|
|
if (c < 16) {
|
|
color_table[c] = terminal->color_scheme->palette[c];
|
|
} else if (c < 232) {
|
|
r = c - 16;
|
|
color_table[c].b = ((double)(r % 6) / 6.0); r /= 6;
|
|
color_table[c].g = ((double)(r % 6) / 6.0); r /= 6;
|
|
color_table[c].r = ((double)(r % 6) / 6.0);
|
|
color_table[c].a = 1.0;
|
|
} else {
|
|
r = (c - 232) * 10 + 8;
|
|
color_table[c].r = ((double) r) / 256.0;
|
|
color_table[c].g = color_table[c].r;
|
|
color_table[c].b = color_table[c].r;
|
|
color_table[c].a = 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static union utf8_char *
|
|
terminal_get_row(struct terminal *terminal, int row)
|
|
{
|
|
int index;
|
|
|
|
index = (row + terminal->start) & (terminal->buffer_height - 1);
|
|
|
|
return (void *) terminal->data + index * terminal->data_pitch;
|
|
}
|
|
|
|
static struct attr*
|
|
terminal_get_attr_row(struct terminal *terminal, int row)
|
|
{
|
|
int index;
|
|
|
|
index = (row + terminal->start) & (terminal->buffer_height - 1);
|
|
|
|
return (void *) terminal->data_attr + index * terminal->attr_pitch;
|
|
}
|
|
|
|
union decoded_attr {
|
|
struct attr attr;
|
|
uint32_t key;
|
|
};
|
|
|
|
static void
|
|
terminal_decode_attr(struct terminal *terminal, int row, int col,
|
|
union decoded_attr *decoded)
|
|
{
|
|
struct attr attr;
|
|
int foreground, background, tmp;
|
|
|
|
decoded->attr.s = 0;
|
|
if (((row == terminal->selection_start_row &&
|
|
col >= terminal->selection_start_col) ||
|
|
row > terminal->selection_start_row) &&
|
|
((row == terminal->selection_end_row &&
|
|
col < terminal->selection_end_col) ||
|
|
row < terminal->selection_end_row))
|
|
decoded->attr.s = 1;
|
|
|
|
/* get the attributes for this character cell */
|
|
attr = terminal_get_attr_row(terminal, row)[col];
|
|
if ((attr.a & ATTRMASK_INVERSE) ||
|
|
decoded->attr.s ||
|
|
((terminal->mode & MODE_SHOW_CURSOR) &&
|
|
window_has_focus(terminal->window) && terminal->row == row &&
|
|
terminal->column == col)) {
|
|
foreground = attr.bg;
|
|
background = attr.fg;
|
|
if (attr.a & ATTRMASK_BOLD) {
|
|
if (foreground <= 16) foreground |= 0x08;
|
|
if (background <= 16) background &= 0x07;
|
|
}
|
|
} else {
|
|
foreground = attr.fg;
|
|
background = attr.bg;
|
|
}
|
|
|
|
if (terminal->mode & MODE_INVERSE) {
|
|
tmp = foreground;
|
|
foreground = background;
|
|
background = tmp;
|
|
if (attr.a & ATTRMASK_BOLD) {
|
|
if (foreground <= 16) foreground |= 0x08;
|
|
if (background <= 16) background &= 0x07;
|
|
}
|
|
}
|
|
|
|
decoded->attr.fg = foreground;
|
|
decoded->attr.bg = background;
|
|
decoded->attr.a = attr.a;
|
|
}
|
|
|
|
|
|
static void
|
|
terminal_scroll_buffer(struct terminal *terminal, int d)
|
|
{
|
|
int i;
|
|
|
|
terminal->start += d;
|
|
if (d < 0) {
|
|
d = 0 - d;
|
|
for (i = 0; i < d; 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 {
|
|
for (i = terminal->height - d; 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);
|
|
}
|
|
}
|
|
|
|
terminal->selection_start_row -= d;
|
|
terminal->selection_end_row -= d;
|
|
}
|
|
|
|
static void
|
|
terminal_scroll_window(struct terminal *terminal, int d)
|
|
{
|
|
int i;
|
|
int window_height;
|
|
int from_row, to_row;
|
|
|
|
// scrolling range is inclusive
|
|
window_height = terminal->margin_bottom - terminal->margin_top + 1;
|
|
d = d % (window_height + 1);
|
|
if(d < 0) {
|
|
d = 0 - d;
|
|
to_row = terminal->margin_bottom;
|
|
from_row = terminal->margin_bottom - d;
|
|
|
|
for (i = 0; i < (window_height - d); i++) {
|
|
memcpy(terminal_get_row(terminal, to_row - i),
|
|
terminal_get_row(terminal, from_row - i),
|
|
terminal->data_pitch);
|
|
memcpy(terminal_get_attr_row(terminal, to_row - i),
|
|
terminal_get_attr_row(terminal, from_row - i),
|
|
terminal->attr_pitch);
|
|
}
|
|
for (i = terminal->margin_top; i < (terminal->margin_top + d); 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 {
|
|
to_row = terminal->margin_top;
|
|
from_row = terminal->margin_top + d;
|
|
|
|
for (i = 0; i < (window_height - d); i++) {
|
|
memcpy(terminal_get_row(terminal, to_row + i),
|
|
terminal_get_row(terminal, from_row + i),
|
|
terminal->data_pitch);
|
|
memcpy(terminal_get_attr_row(terminal, to_row + i),
|
|
terminal_get_attr_row(terminal, from_row + i),
|
|
terminal->attr_pitch);
|
|
}
|
|
for (i = terminal->margin_bottom - d + 1; i <= terminal->margin_bottom; i++) {
|
|
memset(terminal_get_row(terminal, i), 0, terminal->data_pitch);
|
|
attr_init(terminal_get_attr_row(terminal, i),
|
|
terminal->curr_attr, terminal->width);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
terminal_scroll(struct terminal *terminal, int d)
|
|
{
|
|
if(terminal->margin_top == 0 && terminal->margin_bottom == terminal->height - 1)
|
|
terminal_scroll_buffer(terminal, d);
|
|
else
|
|
terminal_scroll_window(terminal, d);
|
|
}
|
|
|
|
static void
|
|
terminal_shift_line(struct terminal *terminal, int d)
|
|
{
|
|
union utf8_char *row;
|
|
struct attr *attr_row;
|
|
|
|
row = terminal_get_row(terminal, terminal->row);
|
|
attr_row = terminal_get_attr_row(terminal, terminal->row);
|
|
|
|
if ((terminal->width + d) <= terminal->column)
|
|
d = terminal->column + 1 - terminal->width;
|
|
if ((terminal->column + d) >= terminal->width)
|
|
d = terminal->width - terminal->column - 1;
|
|
|
|
if (d < 0) {
|
|
d = 0 - d;
|
|
memmove(&row[terminal->column],
|
|
&row[terminal->column + d],
|
|
(terminal->width - terminal->column - d) * sizeof(union utf8_char));
|
|
memmove(&attr_row[terminal->column], &attr_row[terminal->column + d],
|
|
(terminal->width - terminal->column - d) * sizeof(struct attr));
|
|
memset(&row[terminal->width - d], 0, d * sizeof(union utf8_char));
|
|
attr_init(&attr_row[terminal->width - d], terminal->curr_attr, d);
|
|
} else {
|
|
memmove(&row[terminal->column + d], &row[terminal->column],
|
|
(terminal->width - terminal->column - d) * sizeof(union utf8_char));
|
|
memmove(&attr_row[terminal->column + d], &attr_row[terminal->column],
|
|
(terminal->width - terminal->column - d) * sizeof(struct attr));
|
|
memset(&row[terminal->column], 0, d * sizeof(union utf8_char));
|
|
attr_init(&attr_row[terminal->column], terminal->curr_attr, d);
|
|
}
|
|
}
|
|
|
|
static void
|
|
terminal_resize_cells(struct terminal *terminal,
|
|
int width, int height)
|
|
{
|
|
union utf8_char *data;
|
|
struct attr *data_attr;
|
|
char *tab_ruler;
|
|
int data_pitch, attr_pitch;
|
|
int i, l, total_rows;
|
|
uint32_t d, uheight = height;
|
|
struct rectangle allocation;
|
|
struct winsize ws;
|
|
|
|
if (uheight > terminal->buffer_height)
|
|
height = terminal->buffer_height;
|
|
|
|
if (terminal->width == width && terminal->height == height)
|
|
return;
|
|
|
|
if (terminal->data && width <= terminal->max_width) {
|
|
d = 0;
|
|
if (height < terminal->height && height <= terminal->row)
|
|
d = terminal->height - height;
|
|
else if (height > terminal->height &&
|
|
terminal->height - 1 == terminal->row) {
|
|
d = terminal->height - height;
|
|
if (terminal->log_size < uheight)
|
|
d = -terminal->start;
|
|
}
|
|
|
|
terminal->start += d;
|
|
terminal->row -= d;
|
|
} else {
|
|
terminal->max_width = width;
|
|
data_pitch = width * sizeof(union utf8_char);
|
|
data = zalloc(data_pitch * terminal->buffer_height);
|
|
attr_pitch = width * sizeof(struct attr);
|
|
data_attr = malloc(attr_pitch * terminal->buffer_height);
|
|
tab_ruler = zalloc(width);
|
|
attr_init(data_attr, terminal->curr_attr,
|
|
width * terminal->buffer_height);
|
|
|
|
if (terminal->data && terminal->data_attr) {
|
|
if (width > terminal->width)
|
|
l = terminal->width;
|
|
else
|
|
l = width;
|
|
|
|
if (terminal->height > height) {
|
|
total_rows = height;
|
|
i = 1 + terminal->row - height;
|
|
if (i > 0) {
|
|
terminal->start += i;
|
|
terminal->row = terminal->row - i;
|
|
}
|
|
} else {
|
|
total_rows = terminal->height;
|
|
}
|
|
|
|
for (i = 0; i < total_rows; i++) {
|
|
memcpy(&data[width * i],
|
|
terminal_get_row(terminal, i),
|
|
l * sizeof(union utf8_char));
|
|
memcpy(&data_attr[width * i],
|
|
terminal_get_attr_row(terminal, i),
|
|
l * sizeof(struct attr));
|
|
}
|
|
|
|
free(terminal->data);
|
|
free(terminal->data_attr);
|
|
free(terminal->tab_ruler);
|
|
}
|
|
|
|
terminal->data_pitch = data_pitch;
|
|
terminal->attr_pitch = attr_pitch;
|
|
terminal->data = data;
|
|
terminal->data_attr = data_attr;
|
|
terminal->tab_ruler = tab_ruler;
|
|
terminal_init_tabs(terminal);
|
|
terminal->start = 0;
|
|
}
|
|
|
|
terminal->margin_bottom =
|
|
height - (terminal->height - terminal->margin_bottom);
|
|
terminal->width = width;
|
|
terminal->height = height;
|
|
|
|
/* Update the window size */
|
|
ws.ws_row = terminal->height;
|
|
ws.ws_col = terminal->width;
|
|
widget_get_allocation(terminal->widget, &allocation);
|
|
ws.ws_xpixel = allocation.width;
|
|
ws.ws_ypixel = allocation.height;
|
|
ioctl(terminal->master, TIOCSWINSZ, &ws);
|
|
}
|
|
|
|
static void
|
|
resize_handler(struct widget *widget,
|
|
int32_t width, int32_t height, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
int32_t columns, rows, m;
|
|
char *p;
|
|
m = 2 * terminal->margin;
|
|
columns = (width - m) / (int32_t) terminal->average_width;
|
|
rows = (height - m) / (int32_t) terminal->extents.height;
|
|
|
|
if (!window_is_fullscreen(terminal->window) &&
|
|
!window_is_maximized(terminal->window)) {
|
|
width = columns * terminal->average_width + m;
|
|
height = rows * terminal->extents.height + m;
|
|
widget_set_size(terminal->widget, width, height);
|
|
asprintf(&p, "%s — [%dx%d]", terminal->title, columns, rows);
|
|
window_set_title(terminal->window, p);
|
|
free(p);
|
|
}
|
|
|
|
terminal_resize_cells(terminal, columns, rows);
|
|
}
|
|
|
|
static void
|
|
terminal_resize(struct terminal *terminal, int columns, int rows)
|
|
{
|
|
int32_t width, height, m;
|
|
|
|
if (window_is_fullscreen(terminal->window) ||
|
|
window_is_maximized(terminal->window))
|
|
return;
|
|
|
|
m = 2 * terminal->margin;
|
|
width = columns * terminal->average_width + m;
|
|
height = rows * terminal->extents.height + m;
|
|
|
|
window_frame_set_child_size(terminal->widget, width, height);
|
|
}
|
|
|
|
struct color_scheme DEFAULT_COLORS = {
|
|
{
|
|
{0, 0, 0, 1}, /* black */
|
|
{0.66, 0, 0, 1}, /* red */
|
|
{0 , 0.66, 0, 1}, /* green */
|
|
{0.66, 0.33, 0, 1}, /* orange (nicer than muddy yellow) */
|
|
{0 , 0 , 0.66, 1}, /* blue */
|
|
{0.66, 0 , 0.66, 1}, /* magenta */
|
|
{0, 0.66, 0.66, 1}, /* cyan */
|
|
{0.66, 0.66, 0.66, 1}, /* light grey */
|
|
{0.22, 0.33, 0.33, 1}, /* dark grey */
|
|
{1, 0.33, 0.33, 1}, /* high red */
|
|
{0.33, 1, 0.33, 1}, /* high green */
|
|
{1, 1, 0.33, 1}, /* high yellow */
|
|
{0.33, 0.33, 1, 1}, /* high blue */
|
|
{1, 0.33, 1, 1}, /* high magenta */
|
|
{0.33, 1, 1, 1}, /* high cyan */
|
|
{1, 1, 1, 1} /* white */
|
|
},
|
|
0, /* black border */
|
|
{7, 0, 0, } /* bg:black (0), fg:light gray (7) */
|
|
};
|
|
|
|
static void
|
|
terminal_set_color(struct terminal *terminal, cairo_t *cr, int index)
|
|
{
|
|
cairo_set_source_rgba(cr,
|
|
terminal->color_table[index].r,
|
|
terminal->color_table[index].g,
|
|
terminal->color_table[index].b,
|
|
terminal->color_table[index].a);
|
|
}
|
|
|
|
static void
|
|
terminal_send_selection(struct terminal *terminal, int fd)
|
|
{
|
|
int row, col;
|
|
union utf8_char *p_row;
|
|
union decoded_attr attr;
|
|
FILE *fp;
|
|
int len;
|
|
|
|
fp = fdopen(fd, "w");
|
|
if (fp == NULL){
|
|
close(fd);
|
|
return;
|
|
}
|
|
for (row = 0; row < terminal->height; row++) {
|
|
p_row = terminal_get_row(terminal, row);
|
|
for (col = 0; col < terminal->width; col++) {
|
|
if (p_row[col].ch == 0x200B) /* space glyph */
|
|
continue;
|
|
/* get the attributes for this character cell */
|
|
terminal_decode_attr(terminal, row, col, &attr);
|
|
if (!attr.attr.s)
|
|
continue;
|
|
len = strnlen((char *) p_row[col].byte, 4);
|
|
if (len > 0)
|
|
fwrite(p_row[col].byte, 1, len, fp);
|
|
if (len == 0 || col == terminal->width - 1) {
|
|
fwrite("\n", 1, 1, fp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
struct glyph_run {
|
|
struct terminal *terminal;
|
|
cairo_t *cr;
|
|
unsigned int count;
|
|
union decoded_attr attr;
|
|
cairo_glyph_t glyphs[256], *g;
|
|
};
|
|
|
|
static void
|
|
glyph_run_init(struct glyph_run *run, struct terminal *terminal, cairo_t *cr)
|
|
{
|
|
run->terminal = terminal;
|
|
run->cr = cr;
|
|
run->g = run->glyphs;
|
|
run->count = 0;
|
|
run->attr.key = 0;
|
|
}
|
|
|
|
static void
|
|
glyph_run_flush(struct glyph_run *run, union decoded_attr attr)
|
|
{
|
|
cairo_scaled_font_t *font;
|
|
|
|
if (run->count > ARRAY_LENGTH(run->glyphs) - 10 ||
|
|
(attr.key != run->attr.key)) {
|
|
if (run->attr.attr.a & (ATTRMASK_BOLD | ATTRMASK_BLINK))
|
|
font = run->terminal->font_bold;
|
|
else
|
|
font = run->terminal->font_normal;
|
|
cairo_set_scaled_font(run->cr, font);
|
|
terminal_set_color(run->terminal, run->cr,
|
|
run->attr.attr.fg);
|
|
|
|
if (!(run->attr.attr.a & ATTRMASK_CONCEALED))
|
|
cairo_show_glyphs (run->cr, run->glyphs, run->count);
|
|
run->g = run->glyphs;
|
|
run->count = 0;
|
|
}
|
|
run->attr = attr;
|
|
}
|
|
|
|
static void
|
|
glyph_run_add(struct glyph_run *run, int x, int y, union utf8_char *c)
|
|
{
|
|
int num_glyphs;
|
|
cairo_scaled_font_t *font;
|
|
|
|
num_glyphs = ARRAY_LENGTH(run->glyphs) - run->count;
|
|
|
|
if (run->attr.attr.a & (ATTRMASK_BOLD | ATTRMASK_BLINK))
|
|
font = run->terminal->font_bold;
|
|
else
|
|
font = run->terminal->font_normal;
|
|
|
|
cairo_move_to(run->cr, x, y);
|
|
cairo_scaled_font_text_to_glyphs (font, x, y,
|
|
(char *) c->byte, 4,
|
|
&run->g, &num_glyphs,
|
|
NULL, NULL, NULL);
|
|
run->g += num_glyphs;
|
|
run->count += num_glyphs;
|
|
}
|
|
|
|
|
|
static void
|
|
redraw_handler(struct widget *widget, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
struct rectangle allocation;
|
|
cairo_t *cr;
|
|
int top_margin, side_margin;
|
|
int row, col, cursor_x, cursor_y;
|
|
union utf8_char *p_row;
|
|
union decoded_attr attr;
|
|
int text_x, text_y;
|
|
cairo_surface_t *surface;
|
|
double d;
|
|
struct glyph_run run;
|
|
cairo_font_extents_t extents;
|
|
double average_width;
|
|
double unichar_width;
|
|
|
|
surface = window_get_surface(terminal->window);
|
|
widget_get_allocation(terminal->widget, &allocation);
|
|
cr = widget_cairo_create(terminal->widget);
|
|
cairo_rectangle(cr, allocation.x, allocation.y,
|
|
allocation.width, allocation.height);
|
|
cairo_clip(cr);
|
|
cairo_push_group(cr);
|
|
|
|
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
|
terminal_set_color(terminal, cr, terminal->color_scheme->border);
|
|
cairo_paint(cr);
|
|
|
|
cairo_set_scaled_font(cr, terminal->font_normal);
|
|
|
|
extents = terminal->extents;
|
|
average_width = terminal->average_width;
|
|
side_margin = (allocation.width - terminal->width * average_width) / 2;
|
|
top_margin = (allocation.height - terminal->height * extents.height) / 2;
|
|
|
|
cairo_set_line_width(cr, 1.0);
|
|
cairo_translate(cr, allocation.x + side_margin,
|
|
allocation.y + top_margin);
|
|
/* paint the background */
|
|
for (row = 0; row < terminal->height; row++) {
|
|
p_row = terminal_get_row(terminal, row);
|
|
for (col = 0; col < terminal->width; col++) {
|
|
/* get the attributes for this character cell */
|
|
terminal_decode_attr(terminal, row, col, &attr);
|
|
|
|
if (attr.attr.bg == terminal->color_scheme->border)
|
|
continue;
|
|
|
|
if (is_wide(p_row[col]))
|
|
unichar_width = 2 * average_width;
|
|
else
|
|
unichar_width = average_width;
|
|
|
|
terminal_set_color(terminal, cr, attr.attr.bg);
|
|
cairo_move_to(cr, col * average_width,
|
|
row * extents.height);
|
|
cairo_rel_line_to(cr, unichar_width, 0);
|
|
cairo_rel_line_to(cr, 0, extents.height);
|
|
cairo_rel_line_to(cr, -unichar_width, 0);
|
|
cairo_close_path(cr);
|
|
cairo_fill(cr);
|
|
}
|
|
}
|
|
|
|
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
|
|
|
|
/* paint the foreground */
|
|
glyph_run_init(&run, terminal, cr);
|
|
for (row = 0; row < terminal->height; row++) {
|
|
p_row = terminal_get_row(terminal, row);
|
|
for (col = 0; col < terminal->width; col++) {
|
|
/* get the attributes for this character cell */
|
|
terminal_decode_attr(terminal, row, col, &attr);
|
|
|
|
glyph_run_flush(&run, attr);
|
|
|
|
text_x = col * average_width;
|
|
text_y = extents.ascent + row * extents.height;
|
|
if (attr.attr.a & ATTRMASK_UNDERLINE) {
|
|
terminal_set_color(terminal, cr, attr.attr.fg);
|
|
cairo_move_to(cr, text_x, (double)text_y + 1.5);
|
|
cairo_line_to(cr, text_x + average_width, (double) text_y + 1.5);
|
|
cairo_stroke(cr);
|
|
}
|
|
|
|
glyph_run_add(&run, text_x, text_y, &p_row[col]);
|
|
}
|
|
}
|
|
|
|
attr.key = ~0;
|
|
glyph_run_flush(&run, attr);
|
|
|
|
if ((terminal->mode & MODE_SHOW_CURSOR) &&
|
|
!window_has_focus(terminal->window)) {
|
|
d = 0.5;
|
|
|
|
cairo_set_line_width(cr, 1);
|
|
cairo_move_to(cr, terminal->column * average_width + d,
|
|
terminal->row * extents.height + d);
|
|
cairo_rel_line_to(cr, average_width - 2 * d, 0);
|
|
cairo_rel_line_to(cr, 0, extents.height - 2 * d);
|
|
cairo_rel_line_to(cr, -average_width + 2 * d, 0);
|
|
cairo_close_path(cr);
|
|
|
|
cairo_stroke(cr);
|
|
}
|
|
|
|
cairo_pop_group_to_source(cr);
|
|
cairo_paint(cr);
|
|
cairo_destroy(cr);
|
|
cairo_surface_destroy(surface);
|
|
|
|
if (terminal->send_cursor_position) {
|
|
cursor_x = side_margin + allocation.x +
|
|
terminal->column * average_width;
|
|
cursor_y = top_margin + allocation.y +
|
|
terminal->row * extents.height;
|
|
window_set_text_cursor_position(terminal->window,
|
|
cursor_x, cursor_y);
|
|
terminal->send_cursor_position = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
terminal_write(struct terminal *terminal, const char *data, size_t length)
|
|
{
|
|
if (write(terminal->master, data, length) < 0)
|
|
abort();
|
|
terminal->send_cursor_position = 1;
|
|
}
|
|
|
|
static void
|
|
terminal_data(struct terminal *terminal, const char *data, size_t length);
|
|
|
|
static void
|
|
handle_char(struct terminal *terminal, union utf8_char utf8);
|
|
|
|
static void
|
|
handle_sgr(struct terminal *terminal, int code);
|
|
|
|
static void
|
|
handle_term_parameter(struct terminal *terminal, int code, int sr)
|
|
{
|
|
int i;
|
|
|
|
if (terminal->escape_flags & ESC_FLAG_WHAT) {
|
|
switch(code) {
|
|
case 1: /* DECCKM */
|
|
if (sr) terminal->key_mode = KM_APPLICATION;
|
|
else terminal->key_mode = KM_NORMAL;
|
|
break;
|
|
case 2: /* DECANM */
|
|
/* No VT52 support yet */
|
|
terminal->g0 = CS_US;
|
|
terminal->g1 = CS_US;
|
|
terminal->cs = terminal->g0;
|
|
break;
|
|
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)
|
|
terminal->row = terminal->margin_top;
|
|
else
|
|
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 12: /* Very visible cursor (CVVIS) */
|
|
/* FIXME: What do we do here. */
|
|
break;
|
|
case 25:
|
|
if (sr) terminal->mode |= MODE_SHOW_CURSOR;
|
|
else terminal->mode &= ~MODE_SHOW_CURSOR;
|
|
break;
|
|
case 1034: /* smm/rmm, meta mode on/off */
|
|
/* ignore */
|
|
break;
|
|
case 1037: /* deleteSendsDel */
|
|
if (sr) terminal->mode |= MODE_DELETE_SENDS_DEL;
|
|
else terminal->mode &= ~MODE_DELETE_SENDS_DEL;
|
|
break;
|
|
case 1039: /* altSendsEscape */
|
|
if (sr) terminal->mode |= MODE_ALT_SENDS_ESC;
|
|
else terminal->mode &= ~MODE_ALT_SENDS_ESC;
|
|
break;
|
|
case 1049: /* rmcup/smcup, alternate screen */
|
|
/* Ignore. Should be possible to implement,
|
|
* but it's kind of annoying. */
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown parameter: ?%d\n", code);
|
|
break;
|
|
}
|
|
} else {
|
|
switch(code) {
|
|
case 4: /* IRM */
|
|
if (sr) terminal->mode |= MODE_IRM;
|
|
else terminal->mode &= ~MODE_IRM;
|
|
break;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_dcs(struct terminal *terminal)
|
|
{
|
|
}
|
|
|
|
static void
|
|
handle_osc(struct terminal *terminal)
|
|
{
|
|
char *p;
|
|
int code;
|
|
|
|
terminal->escape[terminal->escape_length++] = '\0';
|
|
p = &terminal->escape[2];
|
|
code = strtol(p, &p, 10);
|
|
if (*p == ';') p++;
|
|
|
|
switch (code) {
|
|
case 0: /* Icon name and window title */
|
|
case 1: /* Icon label */
|
|
case 2: /* Window title*/
|
|
free(terminal->title);
|
|
terminal->title = strdup(p);
|
|
window_set_title(terminal->window, p);
|
|
break;
|
|
case 7: /* shell cwd as uri */
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown OSC escape code %d, text %s\n",
|
|
code, p);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_escape(struct terminal *terminal)
|
|
{
|
|
union utf8_char *row;
|
|
struct attr *attr_row;
|
|
char *p;
|
|
int i, count, x, y, top, bottom;
|
|
int args[10], set[10] = { 0, };
|
|
char response[MAX_RESPONSE] = {0, };
|
|
struct rectangle allocation;
|
|
|
|
terminal->escape[terminal->escape_length++] = '\0';
|
|
i = 0;
|
|
p = &terminal->escape[2];
|
|
while ((isdigit(*p) || *p == ';') && i < 10) {
|
|
if (*p == ';') {
|
|
if (!set[i]) {
|
|
args[i] = 0;
|
|
set[i] = 1;
|
|
}
|
|
p++;
|
|
i++;
|
|
} else {
|
|
args[i] = strtol(p, &p, 10);
|
|
set[i] = 1;
|
|
}
|
|
}
|
|
|
|
switch (*p) {
|
|
case '@': /* ICH */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
terminal_shift_line(terminal, count);
|
|
break;
|
|
case 'A': /* CUU */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
if (terminal->row - count >= terminal->margin_top)
|
|
terminal->row -= count;
|
|
else
|
|
terminal->row = terminal->margin_top;
|
|
break;
|
|
case 'B': /* CUD */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
if (terminal->row + count <= terminal->margin_bottom)
|
|
terminal->row += count;
|
|
else
|
|
terminal->row = terminal->margin_bottom;
|
|
break;
|
|
case 'C': /* CUF */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
if ((terminal->column + count) < terminal->width)
|
|
terminal->column += count;
|
|
else
|
|
terminal->column = terminal->width - 1;
|
|
break;
|
|
case 'D': /* CUB */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
if ((terminal->column - count) >= 0)
|
|
terminal->column -= count;
|
|
else
|
|
terminal->column = 0;
|
|
break;
|
|
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 '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 '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) {
|
|
/* Clear screen by scrolling contents out */
|
|
terminal_scroll_buffer(terminal,
|
|
terminal->end - terminal->start);
|
|
}
|
|
break;
|
|
case 'K': /* EL */
|
|
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);
|
|
} 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 'L': /* IL */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
if (terminal->row >= terminal->margin_top &&
|
|
terminal->row < terminal->margin_bottom)
|
|
{
|
|
top = terminal->margin_top;
|
|
terminal->margin_top = terminal->row;
|
|
terminal_scroll(terminal, 0 - count);
|
|
terminal->margin_top = top;
|
|
} else if (terminal->row == terminal->margin_bottom) {
|
|
memset(terminal_get_row(terminal, terminal->row),
|
|
0, terminal->data_pitch);
|
|
attr_init(terminal_get_attr_row(terminal, terminal->row),
|
|
terminal->curr_attr, terminal->width);
|
|
}
|
|
break;
|
|
case 'M': /* DL */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
if (terminal->row >= terminal->margin_top &&
|
|
terminal->row < terminal->margin_bottom)
|
|
{
|
|
top = terminal->margin_top;
|
|
terminal->margin_top = terminal->row;
|
|
terminal_scroll(terminal, count);
|
|
terminal->margin_top = top;
|
|
} else if (terminal->row == terminal->margin_bottom) {
|
|
memset(terminal_get_row(terminal, terminal->row),
|
|
0, terminal->data_pitch);
|
|
}
|
|
break;
|
|
case 'P': /* DCH */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
terminal_shift_line(terminal, 0 - count);
|
|
break;
|
|
case 'S': /* SU */
|
|
terminal_scroll(terminal, set[0] ? args[0] : 1);
|
|
break;
|
|
case 'T': /* SD */
|
|
terminal_scroll(terminal, 0 - (set[0] ? args[0] : 1));
|
|
break;
|
|
case 'X': /* ECH */
|
|
count = set[0] ? args[0] : 1;
|
|
if (count == 0) count = 1;
|
|
if ((terminal->column + count) > terminal->width)
|
|
count = terminal->width - terminal->column;
|
|
row = terminal_get_row(terminal, terminal->row);
|
|
attr_row = terminal_get_attr_row(terminal, terminal->row);
|
|
memset(&row[terminal->column], 0, count * sizeof(union utf8_char));
|
|
attr_init(&attr_row[terminal->column], terminal->curr_attr, count);
|
|
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 */
|
|
terminal_write(terminal, "\e[?6c", 5);
|
|
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);
|
|
}
|
|
break;
|
|
case 'l': /* RM */
|
|
for(i = 0; i < 10 && set[i]; i++) {
|
|
handle_term_parameter(terminal, args[i], 0);
|
|
}
|
|
break;
|
|
case 'm': /* SGR */
|
|
for(i = 0; i < 10; i++) {
|
|
if (i <= 7 && set[i] && set[i + 1] &&
|
|
set[i + 2] && args[i + 1] == 5)
|
|
{
|
|
if (args[i] == 38) {
|
|
handle_sgr(terminal, args[i + 2] + 256);
|
|
break;
|
|
} else if (args[i] == 48) {
|
|
handle_sgr(terminal, args[i + 2] + 512);
|
|
break;
|
|
}
|
|
}
|
|
if(set[i]) {
|
|
handle_sgr(terminal, args[i]);
|
|
} else if(i == 0) {
|
|
handle_sgr(terminal, 0);
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'n': /* DSR */
|
|
i = set[0] ? args[0] : 0;
|
|
if (i == 0 || i == 5) {
|
|
terminal_write(terminal, "\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);
|
|
terminal_write(terminal, response, strlen(response));
|
|
}
|
|
break;
|
|
case 'r':
|
|
if(!set[0]) {
|
|
terminal->margin_top = 0;
|
|
terminal->margin_bottom = terminal->height-1;
|
|
terminal->row = 0;
|
|
terminal->column = 0;
|
|
} else {
|
|
top = (set[0] ? args[0] : 1) - 1;
|
|
top = top < 0 ? 0 :
|
|
(top >= terminal->height ? terminal->height - 1 : top);
|
|
bottom = (set[1] ? args[1] : 1) - 1;
|
|
bottom = bottom < 0 ? 0 :
|
|
(bottom >= terminal->height ? terminal->height - 1 : bottom);
|
|
if(bottom > top) {
|
|
terminal->margin_top = top;
|
|
terminal->margin_bottom = bottom;
|
|
} else {
|
|
terminal->margin_top = 0;
|
|
terminal->margin_bottom = terminal->height-1;
|
|
}
|
|
if(terminal->origin_mode)
|
|
terminal->row = terminal->margin_top;
|
|
else
|
|
terminal->row = 0;
|
|
terminal->column = 0;
|
|
}
|
|
break;
|
|
case 's':
|
|
terminal->saved_row = terminal->row;
|
|
terminal->saved_column = terminal->column;
|
|
break;
|
|
case 't': /* windowOps */
|
|
if (!set[0]) break;
|
|
switch (args[0]) {
|
|
case 4: /* resize px */
|
|
if (set[1] && set[2]) {
|
|
widget_schedule_resize(terminal->widget,
|
|
args[2], args[1]);
|
|
}
|
|
break;
|
|
case 8: /* resize ch */
|
|
if (set[1] && set[2]) {
|
|
terminal_resize(terminal, args[2], args[1]);
|
|
}
|
|
break;
|
|
case 13: /* report position */
|
|
widget_get_allocation(terminal->widget, &allocation);
|
|
snprintf(response, MAX_RESPONSE, "\e[3;%d;%dt",
|
|
allocation.x, allocation.y);
|
|
terminal_write(terminal, response, strlen(response));
|
|
break;
|
|
case 14: /* report px */
|
|
widget_get_allocation(terminal->widget, &allocation);
|
|
snprintf(response, MAX_RESPONSE, "\e[4;%d;%dt",
|
|
allocation.height, allocation.width);
|
|
terminal_write(terminal, response, strlen(response));
|
|
break;
|
|
case 18: /* report ch */
|
|
snprintf(response, MAX_RESPONSE, "\e[9;%d;%dt",
|
|
terminal->height, terminal->width);
|
|
terminal_write(terminal, response, strlen(response));
|
|
break;
|
|
case 21: /* report title */
|
|
snprintf(response, MAX_RESPONSE, "\e]l%s\e\\",
|
|
window_get_title(terminal->window));
|
|
terminal_write(terminal, response, strlen(response));
|
|
break;
|
|
default:
|
|
if (args[0] >= 24)
|
|
terminal_resize(terminal, terminal->width, args[0]);
|
|
else
|
|
fprintf(stderr, "Unimplemented windowOp %d\n", args[0]);
|
|
break;
|
|
}
|
|
case 'u':
|
|
terminal->row = terminal->saved_row;
|
|
terminal->column = terminal->saved_column;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown CSI escape: %c\n", *p);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_non_csi_escape(struct terminal *terminal, char code)
|
|
{
|
|
switch(code) {
|
|
case 'M': /* RI */
|
|
terminal->row -= 1;
|
|
if(terminal->row < terminal->margin_top) {
|
|
terminal->row = terminal->margin_top;
|
|
terminal_scroll(terminal, -1);
|
|
}
|
|
break;
|
|
case 'E': /* NEL */
|
|
terminal->column = 0;
|
|
// fallthrough
|
|
case 'D': /* IND */
|
|
terminal->row += 1;
|
|
if(terminal->row > terminal->margin_bottom) {
|
|
terminal->row = terminal->margin_bottom;
|
|
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;
|
|
terminal->saved_cs = terminal->cs;
|
|
terminal->saved_g0 = terminal->g0;
|
|
terminal->saved_g1 = terminal->g1;
|
|
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;
|
|
terminal->cs = terminal->saved_cs;
|
|
terminal->g0 = terminal->saved_g0;
|
|
terminal->g1 = terminal->saved_g1;
|
|
break;
|
|
case '=': /* DECPAM */
|
|
terminal->key_mode = KM_APPLICATION;
|
|
break;
|
|
case '>': /* DECPNM */
|
|
terminal->key_mode = KM_NORMAL;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown escape code: %c\n", code);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 if (special == '(' || special == ')') {
|
|
switch(code) {
|
|
case '0':
|
|
if (special == '(')
|
|
terminal->g0 = CS_SPECIAL;
|
|
else
|
|
terminal->g1 = CS_SPECIAL;
|
|
break;
|
|
case 'A':
|
|
if (special == '(')
|
|
terminal->g0 = CS_UK;
|
|
else
|
|
terminal->g1 = CS_UK;
|
|
break;
|
|
case 'B':
|
|
if (special == '(')
|
|
terminal->g0 = CS_US;
|
|
else
|
|
terminal->g1 = CS_US;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown character set %c\n", code);
|
|
break;
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Unknown special escape %c%c\n", special, code);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_sgr(struct terminal *terminal, int code)
|
|
{
|
|
switch(code) {
|
|
case 0:
|
|
terminal->curr_attr = terminal->color_scheme->default_attr;
|
|
break;
|
|
case 1:
|
|
terminal->curr_attr.a |= ATTRMASK_BOLD;
|
|
if (terminal->curr_attr.fg < 8)
|
|
terminal->curr_attr.fg += 8;
|
|
break;
|
|
case 4:
|
|
terminal->curr_attr.a |= ATTRMASK_UNDERLINE;
|
|
break;
|
|
case 5:
|
|
terminal->curr_attr.a |= ATTRMASK_BLINK;
|
|
break;
|
|
case 8:
|
|
terminal->curr_attr.a |= ATTRMASK_CONCEALED;
|
|
break;
|
|
case 2:
|
|
case 21:
|
|
case 22:
|
|
terminal->curr_attr.a &= ~ATTRMASK_BOLD;
|
|
if (terminal->curr_attr.fg < 16 && terminal->curr_attr.fg >= 8)
|
|
terminal->curr_attr.fg -= 8;
|
|
break;
|
|
case 24:
|
|
terminal->curr_attr.a &= ~ATTRMASK_UNDERLINE;
|
|
break;
|
|
case 25:
|
|
terminal->curr_attr.a &= ~ATTRMASK_BLINK;
|
|
break;
|
|
case 7:
|
|
case 26:
|
|
terminal->curr_attr.a |= ATTRMASK_INVERSE;
|
|
break;
|
|
case 27:
|
|
terminal->curr_attr.a &= ~ATTRMASK_INVERSE;
|
|
break;
|
|
case 28:
|
|
terminal->curr_attr.a &= ~ATTRMASK_CONCEALED;
|
|
break;
|
|
case 39:
|
|
terminal->curr_attr.fg = terminal->color_scheme->default_attr.fg;
|
|
break;
|
|
case 49:
|
|
terminal->curr_attr.bg = terminal->color_scheme->default_attr.bg;
|
|
break;
|
|
default:
|
|
if(code >= 30 && code <= 37) {
|
|
terminal->curr_attr.fg = code - 30;
|
|
if (terminal->curr_attr.a & ATTRMASK_BOLD)
|
|
terminal->curr_attr.fg += 8;
|
|
} else if(code >= 40 && code <= 47) {
|
|
terminal->curr_attr.bg = code - 40;
|
|
} else if (code >= 90 && code <= 97) {
|
|
terminal->curr_attr.fg = code - 90 + 8;
|
|
} else if (code >= 100 && code <= 107) {
|
|
terminal->curr_attr.bg = code - 100 + 8;
|
|
} else if(code >= 256 && code < 512) {
|
|
terminal->curr_attr.fg = code - 256;
|
|
} else if(code >= 512 && code < 768) {
|
|
terminal->curr_attr.bg = code - 512;
|
|
} else {
|
|
fprintf(stderr, "Unknown SGR code: %d\n", code);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Returns 1 if c was special, otherwise 0 */
|
|
static int
|
|
handle_special_char(struct terminal *terminal, char c)
|
|
{
|
|
union utf8_char *row;
|
|
struct attr *attr_row;
|
|
|
|
row = terminal_get_row(terminal, terminal->row);
|
|
attr_row = terminal_get_attr_row(terminal, terminal->row);
|
|
|
|
switch(c) {
|
|
case '\r':
|
|
terminal->column = 0;
|
|
break;
|
|
case '\n':
|
|
if (terminal->mode & MODE_LF_NEWLINE) {
|
|
terminal->column = 0;
|
|
}
|
|
/* fallthrough */
|
|
case '\v':
|
|
case '\f':
|
|
terminal->row++;
|
|
if (terminal->row + terminal->start > terminal->end)
|
|
terminal->end = terminal->row + terminal->start;
|
|
if (terminal->end == terminal->buffer_height)
|
|
terminal->log_size = terminal->buffer_height;
|
|
else if (terminal->log_size < terminal->buffer_height)
|
|
terminal->log_size = terminal->end;
|
|
|
|
if (terminal->row > terminal->margin_bottom) {
|
|
terminal->row = terminal->margin_bottom;
|
|
terminal_scroll(terminal, +1);
|
|
}
|
|
|
|
break;
|
|
case '\t':
|
|
while (terminal->column < terminal->width) {
|
|
if (terminal->mode & MODE_IRM)
|
|
terminal_shift_line(terminal, +1);
|
|
|
|
if (row[terminal->column].byte[0] == '\0') {
|
|
row[terminal->column].byte[0] = ' ';
|
|
row[terminal->column].byte[1] = '\0';
|
|
attr_row[terminal->column] = terminal->curr_attr;
|
|
}
|
|
|
|
terminal->column++;
|
|
if (terminal->tab_ruler[terminal->column]) break;
|
|
}
|
|
if (terminal->column >= terminal->width) {
|
|
terminal->column = terminal->width - 1;
|
|
}
|
|
|
|
break;
|
|
case '\b':
|
|
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 */
|
|
break;
|
|
case '\x0E': /* SO */
|
|
terminal->cs = terminal->g1;
|
|
break;
|
|
case '\x0F': /* SI */
|
|
terminal->cs = terminal->g0;
|
|
break;
|
|
case '\0':
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
handle_char(struct terminal *terminal, union utf8_char utf8)
|
|
{
|
|
union utf8_char *row;
|
|
struct attr *attr_row;
|
|
|
|
if (handle_special_char(terminal, utf8.byte[0])) return;
|
|
|
|
apply_char_set(terminal->cs, &utf8);
|
|
|
|
/* There are a whole lot of non-characters, control codes,
|
|
* and formatting codes that should probably be ignored,
|
|
* for example: */
|
|
if (strncmp((char*) utf8.byte, "\xEF\xBB\xBF", 3) == 0) {
|
|
/* BOM, ignore */
|
|
return;
|
|
}
|
|
|
|
/* Some of these non-characters should be translated, e.g.: */
|
|
if (utf8.byte[0] < 32) {
|
|
utf8.byte[0] = utf8.byte[0] + 64;
|
|
}
|
|
|
|
/* handle right margin effects */
|
|
if (terminal->column >= terminal->width) {
|
|
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);
|
|
|
|
if (terminal->mode & MODE_IRM)
|
|
terminal_shift_line(terminal, +1);
|
|
row[terminal->column] = utf8;
|
|
attr_row[terminal->column++] = terminal->curr_attr;
|
|
|
|
/* cursor jump for wide character. */
|
|
if (is_wide(utf8))
|
|
row[terminal->column++].ch = 0x200B; /* space glyph */
|
|
|
|
if (utf8.ch != terminal->last_char.ch)
|
|
terminal->last_char = utf8;
|
|
}
|
|
|
|
static void
|
|
escape_append_utf8(struct terminal *terminal, union utf8_char utf8)
|
|
{
|
|
int len, i;
|
|
|
|
if ((utf8.byte[0] & 0x80) == 0x00) len = 1;
|
|
else if ((utf8.byte[0] & 0xE0) == 0xC0) len = 2;
|
|
else if ((utf8.byte[0] & 0xF0) == 0xE0) len = 3;
|
|
else if ((utf8.byte[0] & 0xF8) == 0xF0) len = 4;
|
|
else len = 1; /* Invalid, cannot happen */
|
|
|
|
if (terminal->escape_length + len <= MAX_ESCAPE) {
|
|
for (i = 0; i < len; i++)
|
|
terminal->escape[terminal->escape_length + i] = utf8.byte[i];
|
|
terminal->escape_length += len;
|
|
} else if (terminal->escape_length < MAX_ESCAPE) {
|
|
terminal->escape[terminal->escape_length++] = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
terminal_data(struct terminal *terminal, const char *data, size_t length)
|
|
{
|
|
unsigned int i;
|
|
union utf8_char utf8;
|
|
enum utf8_state parser_state;
|
|
|
|
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;
|
|
}
|
|
|
|
/* assume escape codes never use non-ASCII characters */
|
|
switch (terminal->state) {
|
|
case escape_state_escape:
|
|
escape_append_utf8(terminal, utf8);
|
|
switch (utf8.byte[0]) {
|
|
case 'P': /* DCS */
|
|
terminal->state = escape_state_dcs;
|
|
break;
|
|
case '[': /* CSI */
|
|
terminal->state = escape_state_csi;
|
|
break;
|
|
case ']': /* OSC */
|
|
terminal->state = escape_state_osc;
|
|
break;
|
|
case '#':
|
|
case '(':
|
|
case ')': /* special */
|
|
terminal->state = escape_state_special;
|
|
break;
|
|
case '^': /* PM (not implemented) */
|
|
case '_': /* APC (not implemented) */
|
|
terminal->state = escape_state_ignore;
|
|
break;
|
|
default:
|
|
terminal->state = escape_state_normal;
|
|
handle_non_csi_escape(terminal, utf8.byte[0]);
|
|
break;
|
|
}
|
|
continue;
|
|
case escape_state_csi:
|
|
if (handle_special_char(terminal, utf8.byte[0]) != 0) {
|
|
/* do nothing */
|
|
} else if (utf8.byte[0] == '?') {
|
|
terminal->escape_flags |= ESC_FLAG_WHAT;
|
|
} else if (utf8.byte[0] == '>') {
|
|
terminal->escape_flags |= ESC_FLAG_GT;
|
|
} else if (utf8.byte[0] == '!') {
|
|
terminal->escape_flags |= ESC_FLAG_BANG;
|
|
} else if (utf8.byte[0] == '$') {
|
|
terminal->escape_flags |= ESC_FLAG_CASH;
|
|
} else if (utf8.byte[0] == '\'') {
|
|
terminal->escape_flags |= ESC_FLAG_SQUOTE;
|
|
} else if (utf8.byte[0] == '"') {
|
|
terminal->escape_flags |= ESC_FLAG_DQUOTE;
|
|
} else if (utf8.byte[0] == ' ') {
|
|
terminal->escape_flags |= ESC_FLAG_SPACE;
|
|
} else {
|
|
escape_append_utf8(terminal, utf8);
|
|
if (terminal->escape_length >= MAX_ESCAPE)
|
|
terminal->state = escape_state_normal;
|
|
}
|
|
|
|
if (isalpha(utf8.byte[0]) || utf8.byte[0] == '@' ||
|
|
utf8.byte[0] == '`')
|
|
{
|
|
terminal->state = escape_state_normal;
|
|
handle_escape(terminal);
|
|
} else {
|
|
}
|
|
continue;
|
|
case escape_state_inner_escape:
|
|
if (utf8.byte[0] == '\\') {
|
|
terminal->state = escape_state_normal;
|
|
if (terminal->outer_state == escape_state_dcs) {
|
|
handle_dcs(terminal);
|
|
} else if (terminal->outer_state == escape_state_osc) {
|
|
handle_osc(terminal);
|
|
}
|
|
} else if (utf8.byte[0] == '\e') {
|
|
terminal->state = terminal->outer_state;
|
|
escape_append_utf8(terminal, utf8);
|
|
if (terminal->escape_length >= MAX_ESCAPE)
|
|
terminal->state = escape_state_normal;
|
|
} else {
|
|
terminal->state = terminal->outer_state;
|
|
if (terminal->escape_length < MAX_ESCAPE)
|
|
terminal->escape[terminal->escape_length++] = '\e';
|
|
escape_append_utf8(terminal, utf8);
|
|
if (terminal->escape_length >= MAX_ESCAPE)
|
|
terminal->state = escape_state_normal;
|
|
}
|
|
continue;
|
|
case escape_state_dcs:
|
|
case escape_state_osc:
|
|
case escape_state_ignore:
|
|
if (utf8.byte[0] == '\e') {
|
|
terminal->outer_state = terminal->state;
|
|
terminal->state = escape_state_inner_escape;
|
|
} else if (utf8.byte[0] == '\a' && terminal->state == escape_state_osc) {
|
|
terminal->state = escape_state_normal;
|
|
handle_osc(terminal);
|
|
} else {
|
|
escape_append_utf8(terminal, utf8);
|
|
if (terminal->escape_length >= MAX_ESCAPE)
|
|
terminal->state = escape_state_normal;
|
|
}
|
|
continue;
|
|
case escape_state_special:
|
|
escape_append_utf8(terminal, utf8);
|
|
terminal->state = escape_state_normal;
|
|
if (isdigit(utf8.byte[0]) || isalpha(utf8.byte[0])) {
|
|
handle_special_escape(terminal, terminal->escape[1],
|
|
utf8.byte[0]);
|
|
}
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* this is valid, because ASCII characters are never used to
|
|
* introduce a multibyte sequence in UTF-8 */
|
|
if (utf8.byte[0] == '\e') {
|
|
terminal->state = escape_state_escape;
|
|
terminal->outer_state = escape_state_normal;
|
|
terminal->escape[0] = '\e';
|
|
terminal->escape_length = 1;
|
|
terminal->escape_flags = 0;
|
|
} else {
|
|
handle_char(terminal, utf8);
|
|
} /* if */
|
|
} /* for */
|
|
|
|
window_schedule_redraw(terminal->window);
|
|
}
|
|
|
|
static void
|
|
data_source_target(void *data,
|
|
struct wl_data_source *source, const char *mime_type)
|
|
{
|
|
fprintf(stderr, "data_source_target, %s\n", mime_type);
|
|
}
|
|
|
|
static void
|
|
data_source_send(void *data,
|
|
struct wl_data_source *source,
|
|
const char *mime_type, int32_t fd)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
terminal_send_selection(terminal, fd);
|
|
}
|
|
|
|
static void
|
|
data_source_cancelled(void *data, struct wl_data_source *source)
|
|
{
|
|
wl_data_source_destroy(source);
|
|
}
|
|
|
|
static const struct wl_data_source_listener data_source_listener = {
|
|
data_source_target,
|
|
data_source_send,
|
|
data_source_cancelled
|
|
};
|
|
|
|
static const char text_mime_type[] = "text/plain;charset=utf-8";
|
|
|
|
static void
|
|
data_handler(struct window *window,
|
|
struct input *input,
|
|
float x, float y, const char **types, void *data)
|
|
{
|
|
int i, has_text = 0;
|
|
|
|
if (!types)
|
|
return;
|
|
for (i = 0; types[i]; i++)
|
|
if (strcmp(types[i], text_mime_type) == 0)
|
|
has_text = 1;
|
|
|
|
if (!has_text) {
|
|
input_accept(input, NULL);
|
|
} else {
|
|
input_accept(input, text_mime_type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
drop_handler(struct window *window, struct input *input,
|
|
int32_t x, int32_t y, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
input_receive_drag_data_to_fd(input, text_mime_type, terminal->master);
|
|
}
|
|
|
|
static void
|
|
fullscreen_handler(struct window *window, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
window_set_fullscreen(window, !window_is_fullscreen(terminal->window));
|
|
}
|
|
|
|
static void
|
|
close_handler(void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
terminal_destroy(terminal);
|
|
}
|
|
|
|
static void
|
|
terminal_copy(struct terminal *terminal, struct input *input)
|
|
{
|
|
terminal->selection =
|
|
display_create_data_source(terminal->display);
|
|
wl_data_source_offer(terminal->selection,
|
|
"text/plain;charset=utf-8");
|
|
wl_data_source_add_listener(terminal->selection,
|
|
&data_source_listener, terminal);
|
|
input_set_selection(input, terminal->selection,
|
|
display_get_serial(terminal->display));
|
|
}
|
|
|
|
static void
|
|
terminal_paste(struct terminal *terminal, struct input *input)
|
|
{
|
|
input_receive_selection_data_to_fd(input,
|
|
"text/plain;charset=utf-8",
|
|
terminal->master);
|
|
|
|
}
|
|
|
|
static void
|
|
terminal_new_instance(struct terminal *terminal)
|
|
{
|
|
struct terminal *new_terminal;
|
|
|
|
new_terminal = terminal_create(terminal->display);
|
|
if (terminal_run(new_terminal, option_shell))
|
|
terminal_destroy(new_terminal);
|
|
}
|
|
|
|
static int
|
|
handle_bound_key(struct terminal *terminal,
|
|
struct input *input, uint32_t sym, uint32_t time)
|
|
{
|
|
switch (sym) {
|
|
case XKB_KEY_X:
|
|
/* Cut selection; terminal doesn't do cut, fall
|
|
* through to copy. */
|
|
case XKB_KEY_C:
|
|
terminal_copy(terminal, input);
|
|
return 1;
|
|
case XKB_KEY_V:
|
|
terminal_paste(terminal, input);
|
|
return 1;
|
|
case XKB_KEY_N:
|
|
terminal_new_instance(terminal);
|
|
return 1;
|
|
|
|
case XKB_KEY_Up:
|
|
if (!terminal->scrolling)
|
|
terminal->saved_start = terminal->start;
|
|
if (terminal->start == terminal->end - terminal->log_size)
|
|
return 1;
|
|
|
|
terminal->scrolling = 1;
|
|
terminal->start--;
|
|
terminal->row++;
|
|
terminal->selection_start_row++;
|
|
terminal->selection_end_row++;
|
|
widget_schedule_redraw(terminal->widget);
|
|
return 1;
|
|
|
|
case XKB_KEY_Down:
|
|
if (!terminal->scrolling)
|
|
terminal->saved_start = terminal->start;
|
|
|
|
if (terminal->start == terminal->saved_start)
|
|
return 1;
|
|
|
|
terminal->scrolling = 1;
|
|
terminal->start++;
|
|
terminal->row--;
|
|
terminal->selection_start_row--;
|
|
terminal->selection_end_row--;
|
|
widget_schedule_redraw(terminal->widget);
|
|
return 1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
key_handler(struct window *window, struct input *input, uint32_t time,
|
|
uint32_t key, uint32_t sym, enum wl_keyboard_key_state state,
|
|
void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
char ch[MAX_RESPONSE];
|
|
uint32_t modifiers, serial;
|
|
int ret, len = 0, d;
|
|
bool convert_utf8 = true;
|
|
|
|
modifiers = input_get_modifiers(input);
|
|
if ((modifiers & MOD_CONTROL_MASK) &&
|
|
(modifiers & MOD_SHIFT_MASK) &&
|
|
state == WL_KEYBOARD_KEY_STATE_PRESSED &&
|
|
handle_bound_key(terminal, input, sym, time))
|
|
return;
|
|
|
|
/* Map keypad symbols to 'normal' equivalents before processing */
|
|
switch (sym) {
|
|
case XKB_KEY_KP_Space:
|
|
sym = XKB_KEY_space;
|
|
break;
|
|
case XKB_KEY_KP_Tab:
|
|
sym = XKB_KEY_Tab;
|
|
break;
|
|
case XKB_KEY_KP_Enter:
|
|
sym = XKB_KEY_Return;
|
|
break;
|
|
case XKB_KEY_KP_Left:
|
|
sym = XKB_KEY_Left;
|
|
break;
|
|
case XKB_KEY_KP_Up:
|
|
sym = XKB_KEY_Up;
|
|
break;
|
|
case XKB_KEY_KP_Right:
|
|
sym = XKB_KEY_Right;
|
|
break;
|
|
case XKB_KEY_KP_Down:
|
|
sym = XKB_KEY_Down;
|
|
break;
|
|
case XKB_KEY_KP_Equal:
|
|
sym = XKB_KEY_equal;
|
|
break;
|
|
case XKB_KEY_KP_Multiply:
|
|
sym = XKB_KEY_asterisk;
|
|
break;
|
|
case XKB_KEY_KP_Add:
|
|
sym = XKB_KEY_plus;
|
|
break;
|
|
case XKB_KEY_KP_Separator:
|
|
/* Note this is actually locale-dependent and should mostly be
|
|
* a comma. But leave it as period until we one day start
|
|
* doing the right thing. */
|
|
sym = XKB_KEY_period;
|
|
break;
|
|
case XKB_KEY_KP_Subtract:
|
|
sym = XKB_KEY_minus;
|
|
break;
|
|
case XKB_KEY_KP_Decimal:
|
|
sym = XKB_KEY_period;
|
|
break;
|
|
case XKB_KEY_KP_Divide:
|
|
sym = XKB_KEY_slash;
|
|
break;
|
|
case XKB_KEY_KP_0:
|
|
case XKB_KEY_KP_1:
|
|
case XKB_KEY_KP_2:
|
|
case XKB_KEY_KP_3:
|
|
case XKB_KEY_KP_4:
|
|
case XKB_KEY_KP_5:
|
|
case XKB_KEY_KP_6:
|
|
case XKB_KEY_KP_7:
|
|
case XKB_KEY_KP_8:
|
|
case XKB_KEY_KP_9:
|
|
sym = (sym - XKB_KEY_KP_0) + XKB_KEY_0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (sym) {
|
|
case XKB_KEY_BackSpace:
|
|
if (modifiers & MOD_ALT_MASK)
|
|
ch[len++] = 0x1b;
|
|
ch[len++] = 0x7f;
|
|
break;
|
|
case XKB_KEY_Tab:
|
|
case XKB_KEY_Linefeed:
|
|
case XKB_KEY_Clear:
|
|
case XKB_KEY_Pause:
|
|
case XKB_KEY_Scroll_Lock:
|
|
case XKB_KEY_Sys_Req:
|
|
case XKB_KEY_Escape:
|
|
ch[len++] = sym & 0x7f;
|
|
break;
|
|
|
|
case XKB_KEY_Return:
|
|
if (terminal->mode & MODE_LF_NEWLINE) {
|
|
ch[len++] = 0x0D;
|
|
ch[len++] = 0x0A;
|
|
} else {
|
|
ch[len++] = 0x0D;
|
|
}
|
|
break;
|
|
|
|
case XKB_KEY_Shift_L:
|
|
case XKB_KEY_Shift_R:
|
|
case XKB_KEY_Control_L:
|
|
case XKB_KEY_Control_R:
|
|
case XKB_KEY_Alt_L:
|
|
case XKB_KEY_Alt_R:
|
|
case XKB_KEY_Meta_L:
|
|
case XKB_KEY_Meta_R:
|
|
case XKB_KEY_Super_L:
|
|
case XKB_KEY_Super_R:
|
|
case XKB_KEY_Hyper_L:
|
|
case XKB_KEY_Hyper_R:
|
|
break;
|
|
|
|
case XKB_KEY_Insert:
|
|
len = function_key_response('[', 2, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_Delete:
|
|
if (terminal->mode & MODE_DELETE_SENDS_DEL) {
|
|
ch[len++] = '\x04';
|
|
} else {
|
|
len = function_key_response('[', 3, modifiers, '~', ch);
|
|
}
|
|
break;
|
|
case XKB_KEY_Page_Up:
|
|
len = function_key_response('[', 5, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_Page_Down:
|
|
len = function_key_response('[', 6, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_F1:
|
|
len = function_key_response('O', 1, modifiers, 'P', ch);
|
|
break;
|
|
case XKB_KEY_F2:
|
|
len = function_key_response('O', 1, modifiers, 'Q', ch);
|
|
break;
|
|
case XKB_KEY_F3:
|
|
len = function_key_response('O', 1, modifiers, 'R', ch);
|
|
break;
|
|
case XKB_KEY_F4:
|
|
len = function_key_response('O', 1, modifiers, 'S', ch);
|
|
break;
|
|
case XKB_KEY_F5:
|
|
len = function_key_response('[', 15, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_F6:
|
|
len = function_key_response('[', 17, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_F7:
|
|
len = function_key_response('[', 18, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_F8:
|
|
len = function_key_response('[', 19, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_F9:
|
|
len = function_key_response('[', 20, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_F10:
|
|
len = function_key_response('[', 21, modifiers, '~', ch);
|
|
break;
|
|
case XKB_KEY_F12:
|
|
len = function_key_response('[', 24, modifiers, '~', ch);
|
|
break;
|
|
default:
|
|
/* Handle special keys with alternate mappings */
|
|
len = apply_key_map(terminal->key_mode, sym, modifiers, ch);
|
|
if (len != 0) break;
|
|
|
|
if (modifiers & MOD_CONTROL_MASK) {
|
|
if (sym >= '3' && sym <= '7')
|
|
sym = (sym & 0x1f) + 8;
|
|
|
|
if (!((sym >= '!' && sym <= '/') ||
|
|
(sym >= '8' && sym <= '?') ||
|
|
(sym >= '0' && sym <= '2'))) sym = sym & 0x1f;
|
|
else if (sym == '2') sym = 0x00;
|
|
else if (sym == '/') sym = 0x1F;
|
|
else if (sym == '8' || sym == '?') sym = 0x7F;
|
|
}
|
|
if (modifiers & MOD_ALT_MASK) {
|
|
if (terminal->mode & MODE_ALT_SENDS_ESC) {
|
|
ch[len++] = 0x1b;
|
|
} else {
|
|
sym = sym | 0x80;
|
|
convert_utf8 = false;
|
|
}
|
|
}
|
|
|
|
if ((sym < 128) ||
|
|
(!convert_utf8 && sym < 256)) {
|
|
ch[len++] = sym;
|
|
} else {
|
|
ret = xkb_keysym_to_utf8(sym, ch + len,
|
|
MAX_RESPONSE - len);
|
|
if (ret < 0)
|
|
fprintf(stderr,
|
|
"Warning: buffer too small to encode "
|
|
"UTF8 character\n");
|
|
else
|
|
len += ret;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (state == WL_KEYBOARD_KEY_STATE_PRESSED && len > 0) {
|
|
if (terminal->scrolling) {
|
|
d = terminal->saved_start - terminal->start;
|
|
terminal->row -= d;
|
|
terminal->selection_start_row -= d;
|
|
terminal->selection_end_row -= d;
|
|
terminal->start = terminal->saved_start;
|
|
terminal->scrolling = 0;
|
|
widget_schedule_redraw(terminal->widget);
|
|
}
|
|
|
|
terminal_write(terminal, ch, len);
|
|
|
|
/* Hide cursor, except if this was coming from a
|
|
* repeating key press. */
|
|
serial = display_get_serial(terminal->display);
|
|
if (terminal->hide_cursor_serial != serial) {
|
|
input_set_pointer_image(input, CURSOR_BLANK);
|
|
terminal->hide_cursor_serial = serial;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
keyboard_focus_handler(struct window *window,
|
|
struct input *device, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
window_schedule_redraw(terminal->window);
|
|
}
|
|
|
|
static int wordsep(int ch)
|
|
{
|
|
const char extra[] = "-,./?%&#:_=+@~";
|
|
|
|
if (ch > 127)
|
|
return 1;
|
|
|
|
return ch == 0 || !(isalpha(ch) || isdigit(ch) || strchr(extra, ch));
|
|
}
|
|
|
|
static int
|
|
recompute_selection(struct terminal *terminal)
|
|
{
|
|
struct rectangle allocation;
|
|
int col, x, width, height;
|
|
int start_row, end_row;
|
|
int word_start, eol;
|
|
int side_margin, top_margin;
|
|
int start_x, end_x;
|
|
int cw, ch;
|
|
union utf8_char *data;
|
|
|
|
cw = terminal->average_width;
|
|
ch = terminal->extents.height;
|
|
widget_get_allocation(terminal->widget, &allocation);
|
|
width = terminal->width * cw;
|
|
height = terminal->height * ch;
|
|
side_margin = allocation.x + (allocation.width - width) / 2;
|
|
top_margin = allocation.y + (allocation.height - height) / 2;
|
|
|
|
start_row = (terminal->selection_start_y - top_margin + ch) / ch - 1;
|
|
end_row = (terminal->selection_end_y - top_margin + ch) / ch - 1;
|
|
|
|
if (start_row < end_row ||
|
|
(start_row == end_row &&
|
|
terminal->selection_start_x < terminal->selection_end_x)) {
|
|
terminal->selection_start_row = start_row;
|
|
terminal->selection_end_row = end_row;
|
|
start_x = terminal->selection_start_x;
|
|
end_x = terminal->selection_end_x;
|
|
} else {
|
|
terminal->selection_start_row = end_row;
|
|
terminal->selection_end_row = start_row;
|
|
start_x = terminal->selection_end_x;
|
|
end_x = terminal->selection_start_x;
|
|
}
|
|
|
|
eol = 0;
|
|
if (terminal->selection_start_row < 0) {
|
|
terminal->selection_start_row = 0;
|
|
terminal->selection_start_col = 0;
|
|
} else {
|
|
x = side_margin + cw / 2;
|
|
data = terminal_get_row(terminal,
|
|
terminal->selection_start_row);
|
|
word_start = 0;
|
|
for (col = 0; col < terminal->width; col++, x += cw) {
|
|
if (col == 0 || wordsep(data[col - 1].ch))
|
|
word_start = col;
|
|
if (data[col].ch != 0)
|
|
eol = col + 1;
|
|
if (start_x < x)
|
|
break;
|
|
}
|
|
|
|
switch (terminal->dragging) {
|
|
case SELECT_LINE:
|
|
terminal->selection_start_col = 0;
|
|
break;
|
|
case SELECT_WORD:
|
|
terminal->selection_start_col = word_start;
|
|
break;
|
|
case SELECT_CHAR:
|
|
terminal->selection_start_col = col;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (terminal->selection_end_row >= terminal->height) {
|
|
terminal->selection_end_row = terminal->height;
|
|
terminal->selection_end_col = 0;
|
|
} else {
|
|
x = side_margin + cw / 2;
|
|
data = terminal_get_row(terminal, terminal->selection_end_row);
|
|
for (col = 0; col < terminal->width; col++, x += cw) {
|
|
if (terminal->dragging == SELECT_CHAR && end_x < x)
|
|
break;
|
|
if (terminal->dragging == SELECT_WORD &&
|
|
end_x < x && wordsep(data[col].ch))
|
|
break;
|
|
}
|
|
terminal->selection_end_col = col;
|
|
}
|
|
|
|
if (terminal->selection_end_col != terminal->selection_start_col ||
|
|
terminal->selection_start_row != terminal->selection_end_row) {
|
|
col = terminal->selection_end_col;
|
|
if (col > 0 && data[col - 1].ch == 0)
|
|
terminal->selection_end_col = terminal->width;
|
|
data = terminal_get_row(terminal, terminal->selection_start_row);
|
|
if (data[terminal->selection_start_col].ch == 0)
|
|
terminal->selection_start_col = eol;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
menu_func(struct window *window, struct input *input, int index, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
fprintf(stderr, "picked entry %d\n", index);
|
|
|
|
switch (index) {
|
|
case 0:
|
|
terminal_new_instance(terminal);
|
|
break;
|
|
case 1:
|
|
terminal_copy(terminal, input);
|
|
break;
|
|
case 2:
|
|
terminal_paste(terminal, input);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
show_menu(struct terminal *terminal, struct input *input, uint32_t time)
|
|
{
|
|
int32_t x, y;
|
|
static const char *entries[] = {
|
|
"Open Terminal", "Copy", "Paste"
|
|
};
|
|
|
|
input_get_position(input, &x, &y);
|
|
window_show_menu(terminal->display, input, time, terminal->window,
|
|
x - 10, y - 10, menu_func,
|
|
entries, ARRAY_LENGTH(entries));
|
|
}
|
|
|
|
static void
|
|
button_handler(struct widget *widget,
|
|
struct input *input, uint32_t time,
|
|
uint32_t button,
|
|
enum wl_pointer_button_state state, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
switch (button) {
|
|
case BTN_LEFT:
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
|
|
|
if (time - terminal->button_time < 500)
|
|
terminal->click_count++;
|
|
else
|
|
terminal->click_count = 1;
|
|
|
|
terminal->button_time = time;
|
|
terminal->dragging =
|
|
(terminal->click_count - 1) % 3 + SELECT_CHAR;
|
|
|
|
input_get_position(input,
|
|
&terminal->selection_start_x,
|
|
&terminal->selection_start_y);
|
|
terminal->selection_end_x = terminal->selection_start_x;
|
|
terminal->selection_end_y = terminal->selection_start_y;
|
|
if (recompute_selection(terminal))
|
|
widget_schedule_redraw(widget);
|
|
} else {
|
|
terminal->dragging = SELECT_NONE;
|
|
}
|
|
break;
|
|
|
|
case BTN_RIGHT:
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
|
|
show_menu(terminal, input, time);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
enter_handler(struct widget *widget,
|
|
struct input *input, float x, float y, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
/* Reset title to get rid of resizing '[WxH]' in titlebar */
|
|
window_set_title(terminal->window, terminal->title);
|
|
|
|
return CURSOR_IBEAM;
|
|
}
|
|
|
|
static int
|
|
motion_handler(struct widget *widget,
|
|
struct input *input, uint32_t time,
|
|
float x, float y, void *data)
|
|
{
|
|
struct terminal *terminal = data;
|
|
|
|
if (terminal->dragging) {
|
|
input_get_position(input,
|
|
&terminal->selection_end_x,
|
|
&terminal->selection_end_y);
|
|
|
|
if (recompute_selection(terminal))
|
|
widget_schedule_redraw(widget);
|
|
}
|
|
|
|
return CURSOR_IBEAM;
|
|
}
|
|
|
|
static void
|
|
output_handler(struct window *window, struct output *output, int enter,
|
|
void *data)
|
|
{
|
|
if (enter)
|
|
window_set_buffer_transform(window, output_get_transform(output));
|
|
window_set_buffer_scale(window, window_get_output_scale(window));
|
|
window_schedule_redraw(window);
|
|
}
|
|
|
|
#ifndef howmany
|
|
#define howmany(x, y) (((x) + ((y) - 1)) / (y))
|
|
#endif
|
|
|
|
static struct terminal *
|
|
terminal_create(struct display *display)
|
|
{
|
|
struct terminal *terminal;
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
cairo_text_extents_t text_extents;
|
|
|
|
terminal = xzalloc(sizeof *terminal);
|
|
terminal->color_scheme = &DEFAULT_COLORS;
|
|
terminal_init(terminal);
|
|
terminal->margin_top = 0;
|
|
terminal->margin_bottom = -1;
|
|
terminal->window = window_create(display);
|
|
terminal->widget = window_frame_create(terminal->window, terminal);
|
|
terminal->title = strdup("Wayland Terminal");
|
|
window_set_title(terminal->window, terminal->title);
|
|
widget_set_transparent(terminal->widget, 0);
|
|
|
|
init_state_machine(&terminal->state_machine);
|
|
init_color_table(terminal);
|
|
|
|
terminal->display = display;
|
|
terminal->margin = 5;
|
|
terminal->buffer_height = 1024;
|
|
terminal->end = 1;
|
|
|
|
window_set_user_data(terminal->window, terminal);
|
|
window_set_key_handler(terminal->window, key_handler);
|
|
window_set_keyboard_focus_handler(terminal->window,
|
|
keyboard_focus_handler);
|
|
window_set_fullscreen_handler(terminal->window, fullscreen_handler);
|
|
window_set_output_handler(terminal->window, output_handler);
|
|
window_set_close_handler(terminal->window, close_handler);
|
|
|
|
window_set_data_handler(terminal->window, data_handler);
|
|
window_set_drop_handler(terminal->window, drop_handler);
|
|
|
|
widget_set_redraw_handler(terminal->widget, redraw_handler);
|
|
widget_set_resize_handler(terminal->widget, resize_handler);
|
|
widget_set_button_handler(terminal->widget, button_handler);
|
|
widget_set_enter_handler(terminal->widget, enter_handler);
|
|
widget_set_motion_handler(terminal->widget, motion_handler);
|
|
|
|
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
|
|
cr = cairo_create(surface);
|
|
cairo_set_font_size(cr, option_font_size);
|
|
cairo_select_font_face (cr, option_font,
|
|
CAIRO_FONT_SLANT_NORMAL,
|
|
CAIRO_FONT_WEIGHT_BOLD);
|
|
terminal->font_bold = cairo_get_scaled_font (cr);
|
|
cairo_scaled_font_reference(terminal->font_bold);
|
|
|
|
cairo_select_font_face (cr, option_font,
|
|
CAIRO_FONT_SLANT_NORMAL,
|
|
CAIRO_FONT_WEIGHT_NORMAL);
|
|
terminal->font_normal = cairo_get_scaled_font (cr);
|
|
cairo_scaled_font_reference(terminal->font_normal);
|
|
|
|
cairo_font_extents(cr, &terminal->extents);
|
|
|
|
/* Compute the average ascii glyph width */
|
|
cairo_text_extents(cr, TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS,
|
|
&text_extents);
|
|
terminal->average_width = howmany
|
|
(text_extents.width,
|
|
strlen(TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS));
|
|
terminal->average_width = ceil(terminal->average_width);
|
|
|
|
cairo_destroy(cr);
|
|
cairo_surface_destroy(surface);
|
|
|
|
terminal_resize(terminal, 20, 5); /* Set minimum size first */
|
|
terminal_resize(terminal, 80, 25);
|
|
|
|
wl_list_insert(terminal_list.prev, &terminal->link);
|
|
|
|
return terminal;
|
|
}
|
|
|
|
static void
|
|
terminal_destroy(struct terminal *terminal)
|
|
{
|
|
display_unwatch_fd(terminal->display, terminal->master);
|
|
window_destroy(terminal->window);
|
|
close(terminal->master);
|
|
wl_list_remove(&terminal->link);
|
|
|
|
if (wl_list_empty(&terminal_list))
|
|
display_exit(terminal->display);
|
|
|
|
free(terminal->title);
|
|
free(terminal);
|
|
}
|
|
|
|
static void
|
|
io_handler(struct task *task, uint32_t events)
|
|
{
|
|
struct terminal *terminal =
|
|
container_of(task, struct terminal, io_task);
|
|
char buffer[256];
|
|
int len;
|
|
|
|
if (events & EPOLLHUP) {
|
|
terminal_destroy(terminal);
|
|
return;
|
|
}
|
|
|
|
len = read(terminal->master, buffer, sizeof buffer);
|
|
if (len < 0)
|
|
terminal_destroy(terminal);
|
|
else
|
|
terminal_data(terminal, buffer, len);
|
|
}
|
|
|
|
static int
|
|
terminal_run(struct terminal *terminal, const char *path)
|
|
{
|
|
int master;
|
|
pid_t pid;
|
|
|
|
pid = forkpty(&master, NULL, NULL, NULL);
|
|
if (pid == 0) {
|
|
setenv("TERM", option_term, 1);
|
|
setenv("COLORTERM", option_term, 1);
|
|
if (execl(path, path, NULL)) {
|
|
printf("exec failed: %m\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else if (pid < 0) {
|
|
fprintf(stderr, "failed to fork and create pty (%m).\n");
|
|
return -1;
|
|
}
|
|
|
|
terminal->master = master;
|
|
fcntl(master, F_SETFL, O_NONBLOCK);
|
|
terminal->io_task.run = io_handler;
|
|
display_watch_fd(terminal->display, terminal->master,
|
|
EPOLLIN | EPOLLHUP, &terminal->io_task);
|
|
|
|
window_set_fullscreen(terminal->window, option_fullscreen);
|
|
if (!window_is_fullscreen(terminal->window))
|
|
terminal_resize(terminal, 80, 24);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct weston_option terminal_options[] = {
|
|
{ WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &option_fullscreen },
|
|
{ WESTON_OPTION_STRING, "font", 0, &option_font },
|
|
{ WESTON_OPTION_STRING, "shell", 0, &option_shell },
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct display *d;
|
|
struct terminal *terminal;
|
|
struct weston_config *config;
|
|
struct weston_config_section *s;
|
|
|
|
/* as wcwidth is locale-dependent,
|
|
wcwidth needs setlocale call to function properly. */
|
|
setlocale(LC_ALL, "");
|
|
|
|
option_shell = getenv("SHELL");
|
|
if (!option_shell)
|
|
option_shell = "/bin/bash";
|
|
|
|
config = weston_config_parse("weston.ini");
|
|
s = weston_config_get_section(config, "terminal", NULL, NULL);
|
|
weston_config_section_get_string(s, "font", &option_font, "mono");
|
|
weston_config_section_get_int(s, "font-size", &option_font_size, 14);
|
|
weston_config_section_get_string(s, "term", &option_term, "xterm");
|
|
weston_config_destroy(config);
|
|
|
|
d = display_create(&argc, argv);
|
|
if (d == NULL) {
|
|
fprintf(stderr, "failed to create display: %m\n");
|
|
return -1;
|
|
}
|
|
|
|
wl_list_init(&terminal_list);
|
|
terminal = terminal_create(d);
|
|
if (terminal_run(terminal, option_shell))
|
|
exit(EXIT_FAILURE);
|
|
|
|
display_run(d);
|
|
|
|
return 0;
|
|
}
|