Created
September 19, 2012 11:28
-
-
Save dvdhrm/3749173 to your computer and use it in GitHub Desktop.
TSM library as one file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* TSM - Unicode Handling | |
* | |
* Copyright (c) 2011-2012 David Herrmann <[email protected]> | |
* Copyright (c) 2011 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* Unicode Helpers | |
* This file provides small helpers to make working with Unicode/UTF8/UCS4 input | |
* and output much easier. | |
*/ | |
#define LLOG_SUBSYSTEM "tsm" | |
#define LLOG_ENABLE_DEBUG 1 | |
#ifndef KMSCON_UNICODE_H | |
#define KMSCON_UNICODE_H | |
#include <inttypes.h> | |
#include <stdlib.h> | |
/* UCS4 helpers */ | |
#define TSM_UCS4_MAX (0x7fffffffUL) | |
#define TSM_UCS4_INVALID (TSM_UCS4_MAX + 1) | |
#define TSM_UCS4_REPLACEMENT (0xfffdUL) | |
#define TSM_UCS4_MAXLEN 10 | |
/* symbols */ | |
struct tsm_symbol_table; | |
typedef uint32_t tsm_symbol_t; | |
extern const tsm_symbol_t tsm_symbol_default; | |
int tsm_symbol_table_new(struct tsm_symbol_table **out); | |
void tsm_symbol_table_ref(struct tsm_symbol_table *tbl); | |
void tsm_symbol_table_unref(struct tsm_symbol_table *tbl); | |
tsm_symbol_t tsm_symbol_make(uint32_t ucs4); | |
tsm_symbol_t tsm_symbol_append(struct tsm_symbol_table *tbl, | |
tsm_symbol_t sym, uint32_t ucs4); | |
const uint32_t *tsm_symbol_get(struct tsm_symbol_table *tbl, | |
tsm_symbol_t *sym, size_t *size); | |
/* ucs4 to utf8 converter */ | |
size_t tsm_ucs4_to_utf8(uint32_t ucs4, char *out); | |
char *tsm_ucs4_to_utf8_alloc(const uint32_t *ucs4, size_t len, size_t *len_out); | |
/* utf8 state machine */ | |
struct tsm_utf8_mach; | |
enum tsm_utf8_mach_state { | |
TSM_UTF8_START, | |
TSM_UTF8_ACCEPT, | |
TSM_UTF8_REJECT, | |
TSM_UTF8_EXPECT1, | |
TSM_UTF8_EXPECT2, | |
TSM_UTF8_EXPECT3, | |
}; | |
int tsm_utf8_mach_new(struct tsm_utf8_mach **out); | |
void tsm_utf8_mach_free(struct tsm_utf8_mach *mach); | |
int tsm_utf8_mach_feed(struct tsm_utf8_mach *mach, char c); | |
uint32_t tsm_utf8_mach_get(struct tsm_utf8_mach *mach); | |
void tsm_utf8_mach_reset(struct tsm_utf8_mach *mach); | |
#endif /* KMSCON_UNICODE_H */ | |
/* | |
* TSM - Screen Management | |
* | |
* Copyright (c) 2011-2012 David Herrmann <[email protected]> | |
* Copyright (c) 2011 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* Screen Management | |
* This screen does not emulate any terminal at all. This subsystem just | |
* provides functions to draw a screen to a framebuffer and modifying the state | |
* of it. | |
*/ | |
#ifndef TSM_SCREEN_H | |
#define TSM_SCREEN_H | |
#include <inttypes.h> | |
#include <stdarg.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
/* screen objects */ | |
struct tsm_screen; | |
/** | |
* tsm_log_t: | |
* @file: Source code file where the log message originated or NULL | |
* @line: Line number in source code or 0 | |
* @func: C function name or NULL | |
* @subs: Subsystem where the message came from or NULL | |
* @sev: Kernel-style severity between 0=FATAL and 7=DEBUG | |
* @format: printf-formatted message | |
* @args: arguments for printf-style @format | |
* | |
* This is the type of a logging callback function. You can always pass NULL | |
* instead of such a function to disable logging. | |
*/ | |
typedef void (*tsm_log_t) (const char *file, | |
int line, | |
const char *func, | |
const char *subs, | |
unsigned int sev, | |
const char *format, | |
va_list args); | |
#define TSM_SCREEN_INSERT_MODE 0x01 | |
#define TSM_SCREEN_AUTO_WRAP 0x02 | |
#define TSM_SCREEN_REL_ORIGIN 0x04 | |
#define TSM_SCREEN_INVERSE 0x08 | |
#define TSM_SCREEN_HIDE_CURSOR 0x10 | |
#define TSM_SCREEN_FIXED_POS 0x20 | |
#define TSM_SCREEN_OPT_RENDER_TIMING 0x01 | |
struct tsm_screen_attr { | |
int8_t fccode; /* foreground color code or <0 for rgb */ | |
int8_t bccode; /* background color code or <0 for rgb */ | |
uint8_t fr; /* foreground red */ | |
uint8_t fg; /* foreground green */ | |
uint8_t fb; /* foreground blue */ | |
uint8_t br; /* background red */ | |
uint8_t bg; /* background green */ | |
uint8_t bb; /* background blue */ | |
unsigned int bold : 1; /* bold character */ | |
unsigned int underline : 1; /* underlined character */ | |
unsigned int inverse : 1; /* inverse colors */ | |
unsigned int protect : 1; /* cannot be erased */ | |
}; | |
typedef int (*tsm_screen_prepare_cb) (struct tsm_screen *con, | |
void *data); | |
typedef int (*tsm_screen_draw_cb) (struct tsm_screen *con, | |
uint32_t id, | |
const uint32_t *ch, | |
size_t len, | |
unsigned int posx, | |
unsigned int posy, | |
const struct tsm_screen_attr *attr, | |
void *data); | |
typedef int (*tsm_screen_render_cb) (struct tsm_screen *con, | |
void *data); | |
int tsm_screen_new(struct tsm_screen **out, tsm_log_t log); | |
void tsm_screen_ref(struct tsm_screen *con); | |
void tsm_screen_unref(struct tsm_screen *con); | |
void tsm_screen_set_opts(struct tsm_screen *scr, unsigned int opts); | |
void tsm_screen_reset_opts(struct tsm_screen *scr, unsigned int opts); | |
unsigned int tsm_screen_get_opts(struct tsm_screen *scr); | |
unsigned int tsm_screen_get_width(struct tsm_screen *con); | |
unsigned int tsm_screen_get_height(struct tsm_screen *con); | |
int tsm_screen_resize(struct tsm_screen *con, unsigned int x, | |
unsigned int y); | |
int tsm_screen_set_margins(struct tsm_screen *con, | |
unsigned int top, unsigned int bottom); | |
void tsm_screen_set_max_sb(struct tsm_screen *con, unsigned int max); | |
void tsm_screen_clear_sb(struct tsm_screen *con); | |
void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_sb_down(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_sb_page_up(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_sb_page_down(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_sb_reset(struct tsm_screen *con); | |
void tsm_screen_set_def_attr(struct tsm_screen *con, | |
const struct tsm_screen_attr *attr); | |
void tsm_screen_reset(struct tsm_screen *con); | |
void tsm_screen_set_flags(struct tsm_screen *con, unsigned int flags); | |
void tsm_screen_reset_flags(struct tsm_screen *con, unsigned int flags); | |
unsigned int tsm_screen_get_flags(struct tsm_screen *con); | |
unsigned int tsm_screen_get_cursor_x(struct tsm_screen *con); | |
unsigned int tsm_screen_get_cursor_y(struct tsm_screen *con); | |
void tsm_screen_set_tabstop(struct tsm_screen *con); | |
void tsm_screen_reset_tabstop(struct tsm_screen *con); | |
void tsm_screen_reset_all_tabstops(struct tsm_screen *con); | |
void tsm_screen_write(struct tsm_screen *con, tsm_symbol_t ch, | |
const struct tsm_screen_attr *attr); | |
void tsm_screen_newline(struct tsm_screen *con); | |
void tsm_screen_scroll_up(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_scroll_down(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_move_to(struct tsm_screen *con, unsigned int x, | |
unsigned int y); | |
void tsm_screen_move_up(struct tsm_screen *con, unsigned int num, | |
bool scroll); | |
void tsm_screen_move_down(struct tsm_screen *con, unsigned int num, | |
bool scroll); | |
void tsm_screen_move_left(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_move_right(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_move_line_end(struct tsm_screen *con); | |
void tsm_screen_move_line_home(struct tsm_screen *con); | |
void tsm_screen_tab_right(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_tab_left(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_insert_lines(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_delete_lines(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_insert_chars(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_delete_chars(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_erase_cursor(struct tsm_screen *con); | |
void tsm_screen_erase_chars(struct tsm_screen *con, unsigned int num); | |
void tsm_screen_erase_cursor_to_end(struct tsm_screen *con, | |
bool protect); | |
void tsm_screen_erase_home_to_cursor(struct tsm_screen *con, | |
bool protect); | |
void tsm_screen_erase_current_line(struct tsm_screen *con, | |
bool protect); | |
void tsm_screen_erase_screen_to_cursor(struct tsm_screen *con, | |
bool protect); | |
void tsm_screen_erase_cursor_to_screen(struct tsm_screen *con, | |
bool protect); | |
void tsm_screen_erase_screen(struct tsm_screen *con, bool protect); | |
void tsm_screen_draw(struct tsm_screen *con, | |
tsm_screen_prepare_cb prepare_cb, | |
tsm_screen_draw_cb draw_cb, | |
tsm_screen_render_cb render_cb, | |
void *data); | |
#endif /* TSM_SCREEN_H */ | |
/* | |
* TSM - VT Emulator | |
* | |
* Copyright (c) 2011 David Herrmann <[email protected]> | |
* Copyright (c) 2011 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* Virtual Terminal Emulator | |
* This is a vt100 implementation. It is written from scratch. It uses the | |
* screen state-machine as output and is tightly bound to it. | |
*/ | |
#ifndef TSM_VTE_H | |
#define TSM_VTE_H | |
#include <stdlib.h> | |
/* available character sets */ | |
typedef tsm_symbol_t tsm_vte_charset[96]; | |
extern tsm_vte_charset tsm_vte_unicode_lower; | |
extern tsm_vte_charset tsm_vte_unicode_upper; | |
extern tsm_vte_charset tsm_vte_dec_supplemental_graphics; | |
extern tsm_vte_charset tsm_vte_dec_special_graphics; | |
/* virtual terminal emulator */ | |
struct tsm_vte; | |
/* keep in sync with uterm_input_modifier */ | |
enum tsm_vte_modifier { | |
TSM_SHIFT_MASK = (1 << 0), | |
TSM_LOCK_MASK = (1 << 1), | |
TSM_CONTROL_MASK = (1 << 2), | |
TSM_MOD1_MASK = (1 << 3), | |
TSM_MOD2_MASK = (1 << 4), | |
TSM_MOD3_MASK = (1 << 5), | |
TSM_MOD4_MASK = (1 << 6), | |
TSM_MOD5_MASK = (1 << 7), | |
}; | |
#define TSM_VTE_INVALID 0xffffffff | |
typedef void (*tsm_vte_write_cb) (struct tsm_vte *vte, | |
const char *u8, | |
size_t len, | |
void *data); | |
int tsm_vte_new(struct tsm_vte **out, struct tsm_screen *con, | |
tsm_vte_write_cb write_cb, void *data, | |
tsm_log_t log); | |
void tsm_vte_ref(struct tsm_vte *vte); | |
void tsm_vte_unref(struct tsm_vte *vte); | |
int tsm_vte_set_palette(struct tsm_vte *vte, const char *palette); | |
void tsm_vte_reset(struct tsm_vte *vte); | |
void tsm_vte_input(struct tsm_vte *vte, const char *u8, size_t len); | |
bool tsm_vte_handle_keyboard(struct tsm_vte *vte, uint32_t keysym, | |
unsigned int mods, uint32_t unicode); | |
#endif /* TSM_VTE_H */ | |
/* | |
* Library Log/Debug Interface | |
* Copyright (c) 2012 David Herrmann <[email protected]> | |
* Dedicated to the Public Domain | |
*/ | |
/* | |
* Library Log/Debug Interface | |
* Libraries should always avoid producing side-effects. This includes writing | |
* log-messages of any kind. However, you often don't want to disable debugging | |
* entirely, therefore, the core objects often contain a pointer to a function | |
* which performs logging. If that pointer is NULL (default), logging is | |
* disabled. | |
* | |
* This header should never be installed into the system! This is _no_ public | |
* header. Instead, copy it into your application if you want and use it there. | |
* Your public library API should include something like this: | |
* | |
* typedef void (*MYPREFIX_log_t) (const char *file, | |
* int line, | |
* const char *func, | |
* const char *subs, | |
* unsigned int sev, | |
* const char *format, | |
* va_list args); | |
* | |
* And then the user can supply such a function when creating a new context | |
* object of your library or simply supply NULL. Internally, you have a field of | |
* type "MYPREFIX_log_t llog" in your main structure. If you pass this to the | |
* convenience helpers like llog_dbg(), llog_warn() etc. it will automatically | |
* use the "llog" field to print the message. If it is NULL, nothing is done. | |
* | |
* The arguments of the log-function are defined as: | |
* file: Zero terminated string of the file-name where the log-message | |
* occurred. Can be NULL. | |
* line: Line number of @file where the message occurred. Set to 0 or smaller | |
* if not available. | |
* func: Function name where the log-message occurred. Can be NULL. | |
* subs: Subsystem where the message occurred (zero terminated). Can be NULL. | |
* sev: Severity of log-message. An integer between 0 and 7 as defined below. | |
* These are identical to the linux-kernel severities so there is no need | |
* to include these in your public API. Every app can define them | |
* themself, if they need it. | |
* format: Format string. Must not be NULL. | |
* args: Argument array | |
*/ | |
#ifndef SHL_LLOG_H_INCLUDED | |
#define SHL_LLOG_H_INCLUDED | |
#include <stdarg.h> | |
#include <stdlib.h> | |
enum llog_severity { | |
LLOG_FATAL = 0, | |
LLOG_ALERT = 1, | |
LLOG_CRITICAL = 2, | |
LLOG_ERROR = 3, | |
LLOG_WARNING = 4, | |
LLOG_NOTICE = 5, | |
LLOG_INFO = 6, | |
LLOG_DEBUG = 7, | |
LLOG_SEV_NUM, | |
}; | |
typedef void (*llog_submit_t) (const char *file, | |
int line, | |
const char *func, | |
const char *subs, | |
unsigned int sev, | |
const char *format, | |
va_list args); | |
__attribute__((__unused__)) | |
static void llog_format(llog_submit_t llog, | |
const char *file, | |
int line, | |
const char *func, | |
const char *subs, | |
unsigned int sev, | |
const char *format, | |
...) | |
{ | |
va_list list; | |
if (llog) { | |
va_start(list, format); | |
llog(file, line, func, subs, sev, format, list); | |
va_end(list); | |
} | |
} | |
#define LLOG_DEFAULT __FILE__, __LINE__, __func__, LLOG_SUBSYSTEM | |
#define llog_printf(obj, sev, format, ...) \ | |
llog_format((obj)->llog, LLOG_DEFAULT, (sev), (format), ##__VA_ARGS__) | |
#define llog_dprintf(obj, sev, format, ...) \ | |
llog_format((obj), LLOG_DEFAULT, (sev), (format), ##__VA_ARGS__) | |
/* | |
* Helpers | |
* They pick-up all the default values and submit the message to the | |
* llog-subsystem. The llog_debug() function produces zero-code if | |
* LLOG_ENABLE_DEBUG is not defined. Therefore, it can be heavily used for | |
* debugging and will not have any side-effects. | |
*/ | |
#ifdef LLOG_ENABLE_DEBUG | |
#define llog_ddebug(obj, format, ...) \ | |
llog_dprintf((obj), LLOG_DEBUG, (format), ##__VA_ARGS__) | |
#define llog_debug(obj, format, ...) \ | |
llog_ddebug((obj)->llog, (format), ##__VA_ARGS__) | |
#else | |
#define llog_ddebug(obj, format, ...) ((void)0) | |
#define llog_debug(obj, format, ...) ((void)0) | |
#endif | |
#define llog_info(obj, format, ...) \ | |
llog_printf((obj), LLOG_INFO, (format), ##__VA_ARGS__) | |
#define llog_dinfo(obj, format, ...) \ | |
llog_dprintf((obj), LLOG_INFO, (format), ##__VA_ARGS__) | |
#define llog_notice(obj, format, ...) \ | |
llog_printf((obj), LLOG_NOTICE, (format), ##__VA_ARGS__) | |
#define llog_dnotice(obj, format, ...) \ | |
llog_dprintf((obj), LLOG_NOTICE, (format), ##__VA_ARGS__) | |
#define llog_warning(obj, format, ...) \ | |
llog_printf((obj), LLOG_WARNING, (format), ##__VA_ARGS__) | |
#define llog_dwarning(obj, format, ...) \ | |
llog_dprintf((obj), LLOG_WARNING, (format), ##__VA_ARGS__) | |
#define llog_error(obj, format, ...) \ | |
llog_printf((obj), LLOG_ERROR, (format), ##__VA_ARGS__) | |
#define llog_derror(obj, format, ...) \ | |
llog_dprintf((obj), LLOG_ERROR, (format), ##__VA_ARGS__) | |
#define llog_critical(obj, format, ...) \ | |
llog_printf((obj), LLOG_CRITICAL, (format), ##__VA_ARGS__) | |
#define llog_dcritical(obj, format, ...) \ | |
llog_dprintf((obj), LLOG_CRITICAL, (format), ##__VA_ARGS__) | |
#define llog_alert(obj, format, ...) \ | |
llog_printf((obj), LLOG_ALERT, (format), ##__VA_ARGS__) | |
#define llog_dalert(obj, format, ...) \ | |
llog_dprintf((obj), LLOG_ALERT, (format), ##__VA_ARGS__) | |
#define llog_fatal(obj, format, ...) \ | |
llog_printf((obj), LLOG_FATAL, (format), ##__VA_ARGS__) | |
#define llog_dfatal(obj, format, ...) \ | |
llog_dprintf((obj), LLOG_FATAL, (format), ##__VA_ARGS__) | |
#define llog_dbg llog_debug | |
#define llog_warn llog_warning | |
#define llog_err llog_error | |
#define llog_crit llog_critical | |
/* | |
* Default log messages | |
* These macros can be used to produce default log messages. You can use them | |
* directly in an "return" statement. The "v" variants automatically cast the | |
* result to void so it can be used in return statements inside of void | |
* functions. The "d" variants use the logging object directly as the parent | |
* might not exist, yet. | |
* | |
* Most of the messages work only if debugging is enabled. This is, because they | |
* are used in debug paths and would slow down normal applications. | |
*/ | |
#define llog_dEINVAL(obj) \ | |
(llog_ddebug((obj), "invalid arguments"), -EINVAL) | |
#define llog_EINVAL(obj) \ | |
(llog_dEINVAL((obj)->llog)) | |
#define llog_vEINVAL(obj) \ | |
((void)llog_EINVAL(obj)) | |
#define llog_vdEINVAL(obj) \ | |
((void)llog_dEINVAL(obj)) | |
#define llog_dEFAULT(obj) \ | |
(llog_ddebug((obj), "operation failed"), -EFAULT) | |
#define llog_EFAULT(obj) \ | |
(llog_dEFAULT((obj)->llog)) | |
#define llog_vEFAULT(obj) \ | |
((void)llog_EFAULT(obj)) | |
#define llog_vdEFAULT(obj) \ | |
((void)llog_dEFAULT(obj)) | |
#define llog_dENOMEM(obj) \ | |
(llog_ddebug((obj), "memory allocation failed"), -ENOMEM) | |
#define llog_ENOMEM(obj) \ | |
(llog_dENOMEM((obj)->llog)) | |
#define llog_vENOMEM(obj) \ | |
((void)llog_ENOMEM(obj)) | |
#define llog_vdENOMEM(obj) \ | |
((void)llog_dENOMEM(obj)) | |
#endif /* SHL_LLOG_H_INCLUDED */ | |
/* | |
* shl - Timers | |
* | |
* Copyright (c) 2011-2012 David Herrmann <[email protected]> | |
* Copyright (c) 2011 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* Timers | |
*/ | |
#ifndef SHL_TIMER_H | |
#define SHL_TIMER_H | |
#include <errno.h> | |
#include <stdbool.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <time.h> | |
struct shl_timer { | |
struct timespec start; | |
uint64_t elapsed; | |
}; | |
static inline void shl_timer_reset(struct shl_timer *timer) | |
{ | |
if (!timer) | |
return; | |
clock_gettime(CLOCK_MONOTONIC, &timer->start); | |
timer->elapsed = 0; | |
} | |
static inline int shl_timer_new(struct shl_timer **out) | |
{ | |
struct shl_timer *timer; | |
if (!out) | |
return -EINVAL; | |
timer = malloc(sizeof(*timer)); | |
if (!timer) | |
return -ENOMEM; | |
memset(timer, 0, sizeof(*timer)); | |
shl_timer_reset(timer); | |
*out = timer; | |
return 0; | |
} | |
static inline void shl_timer_free(struct shl_timer *timer) | |
{ | |
if (!timer) | |
return; | |
free(timer); | |
} | |
static inline void shl_timer_start(struct shl_timer *timer) | |
{ | |
if (!timer) | |
return; | |
clock_gettime(CLOCK_MONOTONIC, &timer->start); | |
} | |
static inline uint64_t shl_timer_stop(struct shl_timer *timer) | |
{ | |
struct timespec spec; | |
int64_t off, nsec; | |
if (!timer) | |
return 0; | |
clock_gettime(CLOCK_MONOTONIC, &spec); | |
off = spec.tv_sec - timer->start.tv_sec; | |
nsec = spec.tv_nsec - timer->start.tv_nsec; | |
if (nsec < 0) { | |
--off; | |
nsec += 1000000000ULL; | |
} | |
off *= 1000000; | |
off += nsec / 1000; | |
memcpy(&timer->start, &spec, sizeof(spec)); | |
timer->elapsed += off; | |
return timer->elapsed; | |
} | |
static inline uint64_t shl_timer_elapsed(struct shl_timer *timer) | |
{ | |
struct timespec spec; | |
int64_t off, nsec; | |
if (!timer) | |
return 0; | |
clock_gettime(CLOCK_MONOTONIC, &spec); | |
off = spec.tv_sec - timer->start.tv_sec; | |
nsec = spec.tv_nsec - timer->start.tv_nsec; | |
if (nsec < 0) { | |
--off; | |
nsec += 1000000000ULL; | |
} | |
off *= 1000000; | |
off += nsec / 1000; | |
return timer->elapsed + off; | |
} | |
#endif /* SHL_TIMER_H */ | |
/* | |
* shl - Dynamic Array | |
* | |
* Copyright (c) 2011-2012 David Herrmann <[email protected]> | |
* Copyright (c) 2011 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* A dynamic array implementation | |
*/ | |
#ifndef SHL_ARRAY_H | |
#define SHL_ARRAY_H | |
#include <stdbool.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
struct shl_array { | |
size_t element_size; | |
size_t length; | |
size_t size; | |
void *data; | |
}; | |
#define SHL_ARRAY_AT(_arr, _type, _pos) \ | |
(&((_type*)shl_array_get_array(_arr))[(_pos)]) | |
static inline int shl_array_new(struct shl_array **out, size_t element_size, | |
size_t initial_size) | |
{ | |
struct shl_array *arr; | |
if (!out || !element_size) | |
return -EINVAL; | |
if (!initial_size) | |
initial_size = 4; | |
arr = malloc(sizeof(*arr)); | |
if (!arr) | |
return -ENOMEM; | |
memset(arr, 0, sizeof(*arr)); | |
arr->element_size = element_size; | |
arr->length = 0; | |
arr->size = initial_size; | |
arr->data = malloc(arr->element_size * arr->size); | |
if (!arr->data) { | |
free(arr); | |
return -ENOMEM; | |
} | |
*out = arr; | |
return 0; | |
} | |
static inline void shl_array_free(struct shl_array *arr) | |
{ | |
if (!arr) | |
return; | |
free(arr->data); | |
free(arr); | |
} | |
static inline int shl_array_push(struct shl_array *arr, void *data) | |
{ | |
void *tmp; | |
size_t newsize; | |
if (!arr || !data) | |
return -EINVAL; | |
if (arr->length >= arr->size) { | |
newsize = arr->size * 2; | |
tmp = realloc(arr->data, arr->element_size * newsize); | |
if (!tmp) | |
return -ENOMEM; | |
arr->data = tmp; | |
arr->size = newsize; | |
} | |
memcpy(((uint8_t*)arr->data) + arr->element_size * arr->length, | |
data, arr->element_size); | |
++arr->length; | |
return 0; | |
} | |
static inline void shl_array_pop(struct shl_array *arr) | |
{ | |
if (!arr || !arr->length) | |
return; | |
--arr->length; | |
} | |
static inline void *shl_array_get_array(struct shl_array *arr) | |
{ | |
if (!arr) | |
return NULL; | |
return arr->data; | |
} | |
static inline size_t shl_array_get_length(struct shl_array *arr) | |
{ | |
if (!arr) | |
return 0; | |
return arr->length; | |
} | |
static inline size_t shl_array_get_bsize(struct shl_array *arr) | |
{ | |
if (!arr) | |
return 0; | |
return arr->length * arr->element_size; | |
} | |
static inline size_t shl_array_get_element_size(struct shl_array *arr) | |
{ | |
if (!arr) | |
return 0; | |
return arr->element_size; | |
} | |
#endif /* SHL_ARRAY_H */ | |
/* Licensed under LGPLv2+ - see LICENSE file for details */ | |
#ifndef CCAN_HTABLE_H | |
#define CCAN_HTABLE_H | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
/** | |
* struct htable - private definition of a htable. | |
* | |
* It's exposed here so you can put it in your structures and so we can | |
* supply inline functions. | |
*/ | |
struct htable { | |
size_t (*rehash)(const void *elem, void *priv); | |
void *priv; | |
unsigned int bits; | |
size_t elems, deleted, max, max_with_deleted; | |
/* These are the bits which are the same in all pointers. */ | |
uintptr_t common_mask, common_bits; | |
uintptr_t perfect_bit; | |
uintptr_t *table; | |
}; | |
/** | |
* HTABLE_INITIALIZER - static initialization for a hash table. | |
* @name: name of this htable. | |
* @rehash: hash function to use for rehashing. | |
* @priv: private argument to @rehash function. | |
* | |
* This is useful for setting up static and global hash tables. | |
* | |
* Example: | |
* // For simplicity's sake, say hash value is contents of elem. | |
* static size_t rehash(const void *elem, void *unused) | |
* { | |
* return *(size_t *)elem; | |
* } | |
* static struct htable ht = HTABLE_INITIALIZER(ht, rehash, NULL); | |
*/ | |
#define HTABLE_INITIALIZER(name, rehash, priv) \ | |
{ rehash, priv, 0, 0, 0, 0, 0, -1, 0, 0, &name.perfect_bit } | |
/** | |
* htable_init - initialize an empty hash table. | |
* @ht: the hash table to initialize | |
* @rehash: hash function to use for rehashing. | |
* @priv: private argument to @rehash function. | |
*/ | |
void htable_init(struct htable *ht, | |
size_t (*rehash)(const void *elem, void *priv), void *priv); | |
/** | |
* htable_clear - empty a hash table. | |
* @ht: the hash table to clear | |
* | |
* This doesn't do anything to any pointers left in it. | |
*/ | |
void htable_clear(struct htable *ht); | |
/** | |
* htable_rehash - use a hashtree's rehash function | |
* @elem: the argument to rehash() | |
* | |
*/ | |
size_t htable_rehash(const void *elem); | |
/** | |
* htable_add - add a pointer into a hash table. | |
* @ht: the htable | |
* @hash: the hash value of the object | |
* @p: the non-NULL pointer | |
* | |
* Also note that this can only fail due to allocation failure. Otherwise, it | |
* returns true. | |
*/ | |
bool htable_add(struct htable *ht, size_t hash, const void *p); | |
/** | |
* htable_del - remove a pointer from a hash table | |
* @ht: the htable | |
* @hash: the hash value of the object | |
* @p: the pointer | |
* | |
* Returns true if the pointer was found (and deleted). | |
*/ | |
bool htable_del(struct htable *ht, size_t hash, const void *p); | |
/** | |
* struct htable_iter - iterator or htable_first or htable_firstval etc. | |
* | |
* This refers to a location inside the hashtable. | |
*/ | |
struct htable_iter { | |
size_t off; | |
}; | |
/** | |
* htable_firstval - find a candidate for a given hash value | |
* @htable: the hashtable | |
* @i: the struct htable_iter to initialize | |
* @hash: the hash value | |
* | |
* You'll need to check the value is what you want; returns NULL if none. | |
* See Also: | |
* htable_delval() | |
*/ | |
void *htable_firstval(const struct htable *htable, | |
struct htable_iter *i, size_t hash); | |
/** | |
* htable_nextval - find another candidate for a given hash value | |
* @htable: the hashtable | |
* @i: the struct htable_iter to initialize | |
* @hash: the hash value | |
* | |
* You'll need to check the value is what you want; returns NULL if no more. | |
*/ | |
void *htable_nextval(const struct htable *htable, | |
struct htable_iter *i, size_t hash); | |
/** | |
* htable_get - find an entry in the hash table | |
* @ht: the hashtable | |
* @h: the hash value of the entry | |
* @cmp: the comparison function | |
* @ptr: the pointer to hand to the comparison function. | |
* | |
* Convenient inline wrapper for htable_firstval/htable_nextval loop. | |
*/ | |
static inline void *htable_get(const struct htable *ht, | |
size_t h, | |
bool (*cmp)(const void *candidate, void *ptr), | |
const void *ptr) | |
{ | |
struct htable_iter i; | |
void *c; | |
for (c = htable_firstval(ht,&i,h); c; c = htable_nextval(ht,&i,h)) { | |
if (cmp(c, (void *)ptr)) | |
return c; | |
} | |
return NULL; | |
} | |
/** | |
* htable_first - find an entry in the hash table | |
* @ht: the hashtable | |
* @i: the struct htable_iter to initialize | |
* | |
* Get an entry in the hashtable; NULL if empty. | |
*/ | |
void *htable_first(const struct htable *htable, struct htable_iter *i); | |
/** | |
* htable_next - find another entry in the hash table | |
* @ht: the hashtable | |
* @i: the struct htable_iter to use | |
* | |
* Get another entry in the hashtable; NULL if all done. | |
* This is usually used after htable_first or prior non-NULL htable_next. | |
*/ | |
void *htable_next(const struct htable *htable, struct htable_iter *i); | |
/** | |
* htable_delval - remove an iterated pointer from a hash table | |
* @ht: the htable | |
* @i: the htable_iter | |
* | |
* Usually used to delete a hash entry after it has been found with | |
* htable_firstval etc. | |
*/ | |
void htable_delval(struct htable *ht, struct htable_iter *i); | |
#endif /* CCAN_HTABLE_H */ | |
/* Licensed under LGPLv2+ - see LICENSE file for details */ | |
#define COLD __attribute__((cold)) | |
#include <stdlib.h> | |
#include <limits.h> | |
#include <stdbool.h> | |
#include <assert.h> | |
/* We use 0x1 as deleted marker. */ | |
#define HTABLE_DELETED (0x1) | |
/* We clear out the bits which are always the same, and put metadata there. */ | |
static inline uintptr_t get_extra_ptr_bits(const struct htable *ht, | |
uintptr_t e) | |
{ | |
return e & ht->common_mask; | |
} | |
static inline void *get_raw_ptr(const struct htable *ht, uintptr_t e) | |
{ | |
return (void *)((e & ~ht->common_mask) | ht->common_bits); | |
} | |
static inline uintptr_t make_hval(const struct htable *ht, | |
const void *p, uintptr_t bits) | |
{ | |
return ((uintptr_t)p & ~ht->common_mask) | bits; | |
} | |
static inline bool entry_is_valid(uintptr_t e) | |
{ | |
return e > HTABLE_DELETED; | |
} | |
static inline uintptr_t get_hash_ptr_bits(const struct htable *ht, | |
size_t hash) | |
{ | |
/* Shuffling the extra bits (as specified in mask) down the | |
* end is quite expensive. But the lower bits are redundant, so | |
* we fold the value first. */ | |
return (hash ^ (hash >> ht->bits)) | |
& ht->common_mask & ~ht->perfect_bit; | |
} | |
void htable_init(struct htable *ht, | |
size_t (*rehash)(const void *elem, void *priv), void *priv) | |
{ | |
struct htable empty = HTABLE_INITIALIZER(empty, NULL, NULL); | |
*ht = empty; | |
ht->rehash = rehash; | |
ht->priv = priv; | |
ht->table = &ht->perfect_bit; | |
} | |
void htable_clear(struct htable *ht) | |
{ | |
if (ht->table != &ht->perfect_bit) | |
free((void *)ht->table); | |
htable_init(ht, ht->rehash, ht->priv); | |
} | |
static size_t hash_bucket(const struct htable *ht, size_t h) | |
{ | |
return h & ((1 << ht->bits)-1); | |
} | |
static void *htable_val(const struct htable *ht, | |
struct htable_iter *i, size_t hash, uintptr_t perfect) | |
{ | |
uintptr_t h2 = get_hash_ptr_bits(ht, hash) | perfect; | |
while (ht->table[i->off]) { | |
if (ht->table[i->off] != HTABLE_DELETED) { | |
if (get_extra_ptr_bits(ht, ht->table[i->off]) == h2) | |
return get_raw_ptr(ht, ht->table[i->off]); | |
} | |
i->off = (i->off + 1) & ((1 << ht->bits)-1); | |
h2 &= ~perfect; | |
} | |
return NULL; | |
} | |
void *htable_firstval(const struct htable *ht, | |
struct htable_iter *i, size_t hash) | |
{ | |
i->off = hash_bucket(ht, hash); | |
return htable_val(ht, i, hash, ht->perfect_bit); | |
} | |
void *htable_nextval(const struct htable *ht, | |
struct htable_iter *i, size_t hash) | |
{ | |
i->off = (i->off + 1) & ((1 << ht->bits)-1); | |
return htable_val(ht, i, hash, 0); | |
} | |
void *htable_first(const struct htable *ht, struct htable_iter *i) | |
{ | |
for (i->off = 0; i->off < (size_t)1 << ht->bits; i->off++) { | |
if (entry_is_valid(ht->table[i->off])) | |
return get_raw_ptr(ht, ht->table[i->off]); | |
} | |
return NULL; | |
} | |
void *htable_next(const struct htable *ht, struct htable_iter *i) | |
{ | |
for (i->off++; i->off < (size_t)1 << ht->bits; i->off++) { | |
if (entry_is_valid(ht->table[i->off])) | |
return get_raw_ptr(ht, ht->table[i->off]); | |
} | |
return NULL; | |
} | |
/* This does not expand the hash table, that's up to caller. */ | |
static void ht_add(struct htable *ht, const void *new, size_t h) | |
{ | |
size_t i; | |
uintptr_t perfect = ht->perfect_bit; | |
i = hash_bucket(ht, h); | |
while (entry_is_valid(ht->table[i])) { | |
perfect = 0; | |
i = (i + 1) & ((1 << ht->bits)-1); | |
} | |
ht->table[i] = make_hval(ht, new, get_hash_ptr_bits(ht, h)|perfect); | |
} | |
static COLD bool double_table(struct htable *ht) | |
{ | |
unsigned int i; | |
size_t oldnum = (size_t)1 << ht->bits; | |
uintptr_t *oldtable, e; | |
oldtable = ht->table; | |
ht->table = calloc(1 << (ht->bits+1), sizeof(size_t)); | |
if (!ht->table) { | |
ht->table = oldtable; | |
return false; | |
} | |
ht->bits++; | |
ht->max = ((size_t)3 << ht->bits) / 4; | |
ht->max_with_deleted = ((size_t)9 << ht->bits) / 10; | |
/* If we lost our "perfect bit", get it back now. */ | |
if (!ht->perfect_bit && ht->common_mask) { | |
for (i = 0; i < sizeof(ht->common_mask) * CHAR_BIT; i++) { | |
if (ht->common_mask & ((size_t)1 << i)) { | |
ht->perfect_bit = (size_t)1 << i; | |
break; | |
} | |
} | |
} | |
if (oldtable != &ht->perfect_bit) { | |
for (i = 0; i < oldnum; i++) { | |
if (entry_is_valid(e = oldtable[i])) { | |
void *p = get_raw_ptr(ht, e); | |
ht_add(ht, p, ht->rehash(p, ht->priv)); | |
} | |
} | |
free(oldtable); | |
} | |
ht->deleted = 0; | |
return true; | |
} | |
static COLD void rehash_table(struct htable *ht) | |
{ | |
size_t start, i; | |
uintptr_t e; | |
/* Beware wrap cases: we need to start from first empty bucket. */ | |
for (start = 0; ht->table[start]; start++); | |
for (i = 0; i < (size_t)1 << ht->bits; i++) { | |
size_t h = (i + start) & ((1 << ht->bits)-1); | |
e = ht->table[h]; | |
if (!e) | |
continue; | |
if (e == HTABLE_DELETED) | |
ht->table[h] = 0; | |
else if (!(e & ht->perfect_bit)) { | |
void *p = get_raw_ptr(ht, e); | |
ht->table[h] = 0; | |
ht_add(ht, p, ht->rehash(p, ht->priv)); | |
} | |
} | |
ht->deleted = 0; | |
} | |
/* We stole some bits, now we need to put them back... */ | |
static COLD void update_common(struct htable *ht, const void *p) | |
{ | |
unsigned int i; | |
uintptr_t maskdiff, bitsdiff; | |
if (ht->elems == 0) { | |
/* Always reveal one bit of the pointer in the bucket, | |
* so it's not zero or HTABLE_DELETED (1), even if | |
* hash happens to be 0. Assumes (void *)1 is not a | |
* valid pointer. */ | |
for (i = sizeof(uintptr_t)*CHAR_BIT - 1; i > 0; i--) { | |
if ((uintptr_t)p & ((uintptr_t)1 << i)) | |
break; | |
} | |
ht->common_mask = ~((uintptr_t)1 << i); | |
ht->common_bits = ((uintptr_t)p & ht->common_mask); | |
ht->perfect_bit = 1; | |
return; | |
} | |
/* Find bits which are unequal to old common set. */ | |
maskdiff = ht->common_bits ^ ((uintptr_t)p & ht->common_mask); | |
/* These are the bits which go there in existing entries. */ | |
bitsdiff = ht->common_bits & maskdiff; | |
for (i = 0; i < (size_t)1 << ht->bits; i++) { | |
if (!entry_is_valid(ht->table[i])) | |
continue; | |
/* Clear the bits no longer in the mask, set them as | |
* expected. */ | |
ht->table[i] &= ~maskdiff; | |
ht->table[i] |= bitsdiff; | |
} | |
/* Take away those bits from our mask, bits and perfect bit. */ | |
ht->common_mask &= ~maskdiff; | |
ht->common_bits &= ~maskdiff; | |
ht->perfect_bit &= ~maskdiff; | |
} | |
bool htable_add(struct htable *ht, size_t hash, const void *p) | |
{ | |
if (ht->elems+1 > ht->max && !double_table(ht)) | |
return false; | |
if (ht->elems+1 + ht->deleted > ht->max_with_deleted) | |
rehash_table(ht); | |
assert(p); | |
if (((uintptr_t)p & ht->common_mask) != ht->common_bits) | |
update_common(ht, p); | |
ht_add(ht, p, hash); | |
ht->elems++; | |
return true; | |
} | |
bool htable_del(struct htable *ht, size_t h, const void *p) | |
{ | |
struct htable_iter i; | |
void *c; | |
for (c = htable_firstval(ht,&i,h); c; c = htable_nextval(ht,&i,h)) { | |
if (c == p) { | |
htable_delval(ht, &i); | |
return true; | |
} | |
} | |
return false; | |
} | |
void htable_delval(struct htable *ht, struct htable_iter *i) | |
{ | |
assert(i->off < (size_t)1 << ht->bits); | |
assert(entry_is_valid(ht->table[i->off])); | |
ht->elems--; | |
ht->table[i->off] = HTABLE_DELETED; | |
ht->deleted++; | |
} | |
/* | |
* shl - Dynamic Array | |
* | |
* Copyright (c) 2011-2012 David Herrmann <[email protected]> | |
* Copyright (c) 2011 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* A dynamic hash table implementation | |
*/ | |
#ifndef SHL_HASHTABLE_H | |
#define SHL_HASHTABLE_H | |
#include <errno.h> | |
#include <stdbool.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
struct shl_hashtable; | |
typedef unsigned int (*shl_hash_cb) (const void *data); | |
typedef bool (*shl_equal_cb) (const void *data1, const void *data2); | |
typedef void (*shl_free_cb) (void *data); | |
struct shl_hashentry { | |
void *key; | |
void *value; | |
}; | |
struct shl_hashtable { | |
struct htable tbl; | |
shl_hash_cb hash_cb; | |
shl_equal_cb equal_cb; | |
shl_free_cb free_key; | |
shl_free_cb free_value; | |
}; | |
static inline unsigned int shl_direct_hash(const void *data) | |
{ | |
return (unsigned int)(unsigned long)data; | |
} | |
static inline bool shl_direct_equal(const void *data1, const void *data2) | |
{ | |
return data1 == data2; | |
} | |
static size_t shl_rehash(const void *ele, void *priv) | |
{ | |
struct shl_hashtable *tbl = priv; | |
const struct shl_hashentry *ent = ele; | |
return tbl->hash_cb(ent->key); | |
} | |
static inline int shl_hashtable_new(struct shl_hashtable **out, | |
shl_hash_cb hash_cb, | |
shl_equal_cb equal_cb, | |
shl_free_cb free_key, | |
shl_free_cb free_value) | |
{ | |
struct shl_hashtable *tbl; | |
if (!out || !hash_cb || !equal_cb) | |
return -EINVAL; | |
tbl = malloc(sizeof(*tbl)); | |
if (!tbl) | |
return -ENOMEM; | |
memset(tbl, 0, sizeof(*tbl)); | |
tbl->hash_cb = hash_cb; | |
tbl->equal_cb = equal_cb; | |
tbl->free_key = free_key; | |
tbl->free_value = free_value; | |
htable_init(&tbl->tbl, shl_rehash, tbl); | |
*out = tbl; | |
return 0; | |
} | |
static inline void shl_hashtable_free(struct shl_hashtable *tbl) | |
{ | |
struct htable_iter i; | |
struct shl_hashentry *entry; | |
if (!tbl) | |
return; | |
for (entry = htable_first(&tbl->tbl, &i); | |
entry; | |
entry = htable_next(&tbl->tbl, &i)) { | |
htable_delval(&tbl->tbl, &i); | |
if (tbl->free_key) | |
tbl->free_key(entry->key); | |
if (tbl->free_value) | |
tbl->free_value(entry->value); | |
free(entry); | |
} | |
htable_clear(&tbl->tbl); | |
free(tbl); | |
} | |
static inline int shl_hashtable_insert(struct shl_hashtable *tbl, void *key, | |
void *value) | |
{ | |
struct shl_hashentry *entry; | |
size_t hash; | |
if (!tbl) | |
return -EINVAL; | |
entry = malloc(sizeof(*entry)); | |
if (!entry) | |
return -ENOMEM; | |
entry->key = key; | |
entry->value = value; | |
hash = tbl->hash_cb(key); | |
if (!htable_add(&tbl->tbl, hash, entry)) { | |
free(entry); | |
return -ENOMEM; | |
} | |
return 0; | |
} | |
static inline void shl_hashtable_remove(struct shl_hashtable *tbl, void *key) | |
{ | |
struct htable_iter i; | |
struct shl_hashentry *entry; | |
size_t hash; | |
if (!tbl) | |
return; | |
hash = tbl->hash_cb(key); | |
for (entry = htable_firstval(&tbl->tbl, &i, hash); | |
entry; | |
entry = htable_nextval(&tbl->tbl, &i, hash)) { | |
if (tbl->equal_cb(key, entry->key)) { | |
htable_delval(&tbl->tbl, &i); | |
return; | |
} | |
} | |
} | |
static inline bool shl_hashtable_find(struct shl_hashtable *tbl, void **out, | |
void *key) | |
{ | |
struct htable_iter i; | |
struct shl_hashentry *entry; | |
size_t hash; | |
if (!tbl) | |
return false; | |
hash = tbl->hash_cb(key); | |
for (entry = htable_firstval(&tbl->tbl, &i, hash); | |
entry; | |
entry = htable_nextval(&tbl->tbl, &i, hash)) { | |
if (tbl->equal_cb(key, entry->key)) { | |
if (out) | |
*out = entry->value; | |
return true; | |
} | |
} | |
return false; | |
} | |
#endif /* SHL_HASHTABLE_H */ | |
/* | |
* TSM - Unicode Handling | |
* | |
* Copyright (c) 2011 David Herrmann <[email protected]> | |
* Copyright (c) 2011-2012 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* The tsm-utf8-state-machine is based on the wayland-compositor demos: | |
* | |
* 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. | |
*/ | |
/* | |
* Unicode Helpers | |
* This implements several helpers for Unicode/UTF8/UCS4 input and output. See | |
* below for comments on each helper. | |
*/ | |
#include <errno.h> | |
#include <inttypes.h> | |
#include <stdlib.h> | |
#include <string.h> | |
/* | |
* Unicode Symbol Handling | |
* The main goal of the tsm_symbol_* functions is to provide a datatype which | |
* can contain the representation of any printable character. This includes all | |
* basic Unicode characters but also combined characters. | |
* To avoid all the memory management we still represent a character as a single | |
* integer value (tsm_symbol_t) but internally we allocate a string which is | |
* represented by this value. | |
* | |
* A tsm_symbol_t is an integer which represents a single character point. | |
* For most Unicode characters this is simply the UCS4 representation. In fact, | |
* every UCS4 characters is a valid tsm_symbol_t object. | |
* However, Unicode standard allows combining marks. Therefore, some characters | |
* consists of more than one Unicode character. | |
* A global symbol-table provides all those combined characters as single | |
* integers. You simply create a valid base character and append your combining | |
* marks and the table will return a new valid tsm_symbol_t. It is no longer | |
* a valid UCS4 value, though. But no memory management is needed as all | |
* tsm_symbol_t objects are simple integers. | |
* | |
* The symbol table contains two-way | |
* references. The Hash Table contains all the symbols with the symbol ucs4 | |
* string as key and the symbol ID as value. | |
* The index array contains the symbol ID as key and a pointer to the ucs4 | |
* string as value. But the hash table owns the ucs4 string. | |
* This allows fast implementations of *_get() and *_append() without long | |
* search intervals. | |
* | |
* When creating a new symbol, we simply return the UCS4 value as new symbol. We | |
* do not add it to our symbol table as it is only one character. However, if a | |
* character is appended to an existing symbol, we create a new ucs4 string and | |
* push the new symbol into the symbol table. | |
*/ | |
const tsm_symbol_t tsm_symbol_default = 0; | |
struct tsm_symbol_table { | |
unsigned long ref; | |
uint32_t next_id; | |
struct shl_array *index; | |
struct shl_hashtable *symbols; | |
}; | |
/* TODO: remove the default context */ | |
static struct tsm_symbol_table *tsm_symbol_table_default; | |
static unsigned int hash_ucs4(const void *key) | |
{ | |
unsigned int val = 5381; | |
size_t i; | |
const uint32_t *ucs4 = key; | |
i = 0; | |
while (ucs4[i] <= TSM_UCS4_MAX) { | |
val = val * 33 + ucs4[i]; | |
++i; | |
} | |
return val; | |
} | |
static bool cmp_ucs4(const void *a, const void *b) | |
{ | |
size_t i; | |
const uint32_t *v1, *v2; | |
v1 = a; | |
v2 = b; | |
i = 0; | |
while (1) { | |
if (v1[i] > TSM_UCS4_MAX && v2[i] > TSM_UCS4_MAX) | |
return true; | |
if (v1[i] > TSM_UCS4_MAX && v2[i] <= TSM_UCS4_MAX) | |
return false; | |
if (v1[i] <= TSM_UCS4_MAX && v2[i] > TSM_UCS4_MAX) | |
return false; | |
if (v1[i] != v2[i]) | |
return false; | |
++i; | |
} | |
} | |
int tsm_symbol_table_new(struct tsm_symbol_table **out) | |
{ | |
struct tsm_symbol_table *tbl; | |
int ret; | |
static const uint32_t *val = NULL; /* we need a valid lvalue */ | |
if (!out) | |
return -EINVAL; | |
tbl = malloc(sizeof(*tbl)); | |
if (!tbl) | |
return -ENOMEM; | |
memset(tbl, 0, sizeof(*tbl)); | |
tbl->ref = 1; | |
tbl->next_id = TSM_UCS4_MAX + 2; | |
ret = shl_array_new(&tbl->index, sizeof(uint32_t*), 4); | |
if (ret) | |
goto err_free; | |
/* first entry is not used so add dummy */ | |
shl_array_push(tbl->index, &val); | |
ret = shl_hashtable_new(&tbl->symbols, hash_ucs4, cmp_ucs4, | |
free, NULL); | |
if (ret) | |
goto err_array; | |
*out = tbl; | |
return 0; | |
err_array: | |
shl_array_free(tbl->index); | |
err_free: | |
free(tbl); | |
return ret; | |
} | |
void tsm_symbol_table_ref(struct tsm_symbol_table *tbl) | |
{ | |
if (!tbl || !tbl->ref) | |
return; | |
++tbl->ref; | |
} | |
void tsm_symbol_table_unref(struct tsm_symbol_table *tbl) | |
{ | |
if (!tbl || !tbl->ref || --tbl->ref) | |
return; | |
shl_hashtable_free(tbl->symbols); | |
shl_array_free(tbl->index); | |
free(tbl); | |
} | |
tsm_symbol_t tsm_symbol_make(uint32_t ucs4) | |
{ | |
if (ucs4 > TSM_UCS4_MAX) | |
return 0; | |
else | |
return ucs4; | |
} | |
/* | |
* This decomposes a symbol into a ucs4 string and a size value. If \sym is a | |
* valid UCS4 character, this returns a pointer to \sym and writes 1 into \size. | |
* Therefore, the returned value may get destroyed if your \sym argument gets | |
* destroyed. | |
* If \sym is a composed ucs4 string, then the returned value points into the | |
* hash table of the symbol table and lives as long as the symbol table does. | |
* | |
* This always returns a valid value. If an error happens, the default character | |
* is returned. If \size is NULL, then the size value is omitted. | |
*/ | |
const uint32_t *tsm_symbol_get(struct tsm_symbol_table *tbl, | |
tsm_symbol_t *sym, size_t *size) | |
{ | |
uint32_t *ucs4; | |
int ret; | |
if (*sym <= TSM_UCS4_MAX) { | |
if (size) | |
*size = 1; | |
return sym; | |
} | |
if (!tbl) { | |
ret = tsm_symbol_table_new(&tbl); | |
if (ret) { | |
if (size) | |
*size = 1; | |
return &tsm_symbol_default; | |
} | |
tsm_symbol_table_default = tbl; | |
} | |
ucs4 = *SHL_ARRAY_AT(tbl->index, uint32_t*, | |
*sym - (TSM_UCS4_MAX + 1)); | |
if (!ucs4) { | |
if (size) | |
*size = 1; | |
return &tsm_symbol_default; | |
} | |
if (size) { | |
*size = 0; | |
while (ucs4[*size] <= TSM_UCS4_MAX) | |
++*size; | |
} | |
return ucs4; | |
} | |
tsm_symbol_t tsm_symbol_append(struct tsm_symbol_table *tbl, | |
tsm_symbol_t sym, uint32_t ucs4) | |
{ | |
uint32_t buf[TSM_UCS4_MAXLEN + 1], nsym, *nval; | |
const uint32_t *ptr; | |
size_t s; | |
void *tmp; | |
bool res; | |
int ret; | |
if (!tbl) { | |
ret = tsm_symbol_table_new(&tbl); | |
if (ret) | |
return sym; | |
tsm_symbol_table_default = tbl; | |
} | |
if (ucs4 > TSM_UCS4_MAX) | |
return sym; | |
ptr = tsm_symbol_get(tbl, &sym, &s); | |
if (s >= TSM_UCS4_MAXLEN) | |
return sym; | |
memcpy(buf, ptr, s * sizeof(uint32_t)); | |
buf[s++] = ucs4; | |
buf[s++] = TSM_UCS4_MAX + 1; | |
res = shl_hashtable_find(tbl->symbols, &tmp, buf); | |
if (res) | |
return (uint32_t)(long)tmp; | |
nval = malloc(sizeof(uint32_t) * s); | |
if (!nval) | |
return sym; | |
memcpy(nval, buf, s * sizeof(uint32_t)); | |
nsym = tbl->next_id + 1; | |
/* Out of IDs; we actually have 2 Billion IDs so this seems | |
* very unlikely but lets be safe here */ | |
if (nsym <= tbl->next_id++) | |
goto err_id; | |
ret = shl_hashtable_insert(tbl->symbols, nval, (void*)(long)nsym); | |
if (ret) | |
goto err_id; | |
ret = shl_array_push(tbl->index, &nval); | |
if (ret) | |
goto err_symbol; | |
return nsym; | |
err_symbol: | |
shl_hashtable_remove(tbl->symbols, nval); | |
err_id: | |
--tbl->next_id; | |
free(nval); | |
return sym; | |
} | |
/* | |
* Convert UCS4 character to UTF-8. This creates one of: | |
* 0xxxxxxx | |
* 110xxxxx 10xxxxxx | |
* 1110xxxx 10xxxxxx 10xxxxxx | |
* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | |
* This is based on the same function from "terminology" from the Enlightenment | |
* project. See COPYING for more information. | |
* | |
* @txt must point to a 4 byte-buffer. A number between 0 and 4 is returned and | |
* indicates how long the written UTF8 string is. | |
* | |
* Please note @g is a real UCS4 code and not a tsm_symbol_t object! | |
*/ | |
size_t tsm_ucs4_to_utf8(uint32_t g, char *txt) | |
{ | |
if (g < (1 << 7)) { | |
txt[0] = g & 0x7f; | |
return 1; | |
} else if (g < (1 << (5 + 6))) { | |
txt[0] = 0xc0 | ((g >> 6) & 0x1f); | |
txt[1] = 0x80 | ((g ) & 0x3f); | |
return 2; | |
} else if (g < (1 << (4 + 6 + 6))) { | |
txt[0] = 0xe0 | ((g >> 12) & 0x0f); | |
txt[1] = 0x80 | ((g >> 6) & 0x3f); | |
txt[2] = 0x80 | ((g ) & 0x3f); | |
return 3; | |
} else if (g < (1 << (3 + 6 + 6 + 6))) { | |
txt[0] = 0xf0 | ((g >> 18) & 0x07); | |
txt[1] = 0x80 | ((g >> 12) & 0x3f); | |
txt[2] = 0x80 | ((g >> 6) & 0x3f); | |
txt[3] = 0x80 | ((g ) & 0x3f); | |
return 4; | |
} else { | |
return 0; | |
} | |
} | |
char *tsm_ucs4_to_utf8_alloc(const uint32_t *ucs4, size_t len, size_t *len_out) | |
{ | |
char *val; | |
size_t i, pos; | |
val = malloc(4 * len); | |
if (!val) | |
return NULL; | |
pos = 0; | |
for (i = 0; i < len; ++i) | |
pos += tsm_ucs4_to_utf8(ucs4[i], &val[pos]); | |
if (!pos) { | |
free(val); | |
return NULL; | |
} | |
if (len_out) | |
*len_out = pos; | |
return val; | |
} | |
/* | |
* UTF8 State Machine | |
* This state machine parses UTF8 and converts it into a stream of Unicode | |
* characters (UCS4 values). A state-machine is represented by a | |
* "struct tsm_utf8_mach" object. It has no global state and all functions are | |
* re-entrant if called with different state-machine objects. | |
* | |
* tsm_utf8_mach_new(): This creates a new state-machine and resets it to its | |
* initial state. Returns 0 on success. | |
* | |
* tsm_uft8_mach_free(): This destroys a state-machine and frees all internally | |
* allocated memory. | |
* | |
* tsm_utf8_mach_reset(): Reset a given state-machine to its initial state. This | |
* is the same state the machine is in after it got created. | |
* | |
* tsm_uft8_mach_feed(): Feed one byte of the UTF8 input stream into the | |
* state-machine. This function returns the new state of the state-machine after | |
* this character has been parsed. If it is TSM_UTF8_ACCEPT or TSM_UTF8_REJECT, | |
* then there is a pending UCS4 character that you should retrieve via | |
* tsm_utf8_mach_get(). If it is TSM_UTF8_ACCEPT, then a character was | |
* successfully parsed. If it is TSM_UTF8_REJECT, the input was invalid UTF8 and | |
* some error recovery was tried or a replacement character was choosen. All | |
* other states mean that the machine needs more input to parse the stream. | |
* | |
* tsm_utf8_mach_get(): Returns the last parsed character. It has no effect on | |
* the state machine so you can call it multiple times. | |
* | |
* Internally, we use TSM_UTF8_START whenever the state-machine is reset. This | |
* can be used to ignore the last read input or to simply reset the machine. | |
* TSM_UTF8_EXPECT* is used to remember how many bytes are still to be read to | |
* get a full UTF8 sequence. | |
* If an error occurs during reading, we go to state TSM_UTF8_REJECT and the | |
* user will read a replacement character. If further errors occur, we go to | |
* state TSM_UTF8_START to avoid printing multiple replacement characters for a | |
* single misinterpreted UTF8 sequence. However, under some circumstances it may | |
* happen that we stay in TSM_UTF8_REJECT and a next replacement character is | |
* returned. | |
* It is difficult to decide how to interpret wrong input but this machine seems | |
* to be quite good at deciding what to do. Generally, we prefer discarding or | |
* replacing input instead of trying to decipher ASCII values from the invalid | |
* data. This guarantees that we do not send wrong values to the terminal | |
* emulator. Some might argue that an ASCII fallback would be better. However, | |
* this means that we might send very weird escape-sequences to the VTE layer. | |
* Especially with C1 codes applications can really break many terminal features | |
* so we avoid any non-ASCII+non-UTF8 input to prevent this. | |
*/ | |
struct tsm_utf8_mach { | |
int state; | |
uint32_t ch; | |
}; | |
int tsm_utf8_mach_new(struct tsm_utf8_mach **out) | |
{ | |
struct tsm_utf8_mach *mach; | |
if (!out) | |
return -EINVAL; | |
mach = malloc(sizeof(*mach)); | |
if (!mach) | |
return -ENOMEM; | |
memset(mach, 0, sizeof(*mach)); | |
mach->state = TSM_UTF8_START; | |
*out = mach; | |
return 0; | |
} | |
void tsm_utf8_mach_free(struct tsm_utf8_mach *mach) | |
{ | |
if (!mach) | |
return; | |
free(mach); | |
} | |
int tsm_utf8_mach_feed(struct tsm_utf8_mach *mach, char ci) | |
{ | |
uint32_t c; | |
if (!mach) | |
return TSM_UTF8_START; | |
c = ci; | |
switch (mach->state) { | |
case TSM_UTF8_START: | |
case TSM_UTF8_ACCEPT: | |
case TSM_UTF8_REJECT: | |
if (c == 0xC0 || c == 0xC1) { | |
/* overlong encoding for ASCII, reject */ | |
mach->state = TSM_UTF8_REJECT; | |
} else if ((c & 0x80) == 0) { | |
/* single byte, accept */ | |
mach->ch = c; | |
mach->state = TSM_UTF8_ACCEPT; | |
} else if ((c & 0xC0) == 0x80) { | |
/* parser out of sync, ignore byte */ | |
mach->state = TSM_UTF8_START; | |
} else if ((c & 0xE0) == 0xC0) { | |
/* start of two byte sequence */ | |
mach->ch = (c & 0x1F) << 6; | |
mach->state = TSM_UTF8_EXPECT1; | |
} else if ((c & 0xF0) == 0xE0) { | |
/* start of three byte sequence */ | |
mach->ch = (c & 0x0F) << 12; | |
mach->state = TSM_UTF8_EXPECT2; | |
} else if ((c & 0xF8) == 0xF0) { | |
/* start of four byte sequence */ | |
mach->ch = (c & 0x07) << 18; | |
mach->state = TSM_UTF8_EXPECT3; | |
} else { | |
/* overlong encoding, reject */ | |
mach->state = TSM_UTF8_REJECT; | |
} | |
break; | |
case TSM_UTF8_EXPECT3: | |
mach->ch |= (c & 0x3F) << 12; | |
if ((c & 0xC0) == 0x80) | |
mach->state = TSM_UTF8_EXPECT2; | |
else | |
mach->state = TSM_UTF8_REJECT; | |
break; | |
case TSM_UTF8_EXPECT2: | |
mach->ch |= (c & 0x3F) << 6; | |
if ((c & 0xC0) == 0x80) | |
mach->state = TSM_UTF8_EXPECT1; | |
else | |
mach->state = TSM_UTF8_REJECT; | |
break; | |
case TSM_UTF8_EXPECT1: | |
mach->ch |= c & 0x3F; | |
if ((c & 0xC0) == 0x80) | |
mach->state = TSM_UTF8_ACCEPT; | |
else | |
mach->state = TSM_UTF8_REJECT; | |
break; | |
default: | |
mach->state = TSM_UTF8_REJECT; | |
break; | |
} | |
return mach->state; | |
} | |
uint32_t tsm_utf8_mach_get(struct tsm_utf8_mach *mach) | |
{ | |
if (!mach || mach->state != TSM_UTF8_ACCEPT) | |
return TSM_UCS4_REPLACEMENT; | |
return mach->ch; | |
} | |
void tsm_utf8_mach_reset(struct tsm_utf8_mach *mach) | |
{ | |
if (!mach) | |
return; | |
mach->state = TSM_UTF8_START; | |
} | |
/* | |
* TSM - Screen Management | |
* | |
* Copyright (c) 2011-2012 David Herrmann <[email protected]> | |
* Copyright (c) 2011 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* Screen Management | |
* This provides the screen drawing and manipulation functions. It does not | |
* provide the terminal emulation. It is just an abstraction layer to draw text | |
* to a framebuffer as used by terminals and consoles. | |
*/ | |
#include <errno.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <string.h> | |
struct cell { | |
tsm_symbol_t ch; | |
struct tsm_screen_attr attr; | |
}; | |
struct line { | |
struct line *next; | |
struct line *prev; | |
unsigned int size; | |
struct cell *cells; | |
}; | |
struct tsm_screen { | |
size_t ref; | |
llog_submit_t llog; | |
unsigned int opts; | |
unsigned int flags; | |
struct shl_timer *timer; | |
/* default attributes for new cells */ | |
struct tsm_screen_attr def_attr; | |
/* current buffer */ | |
unsigned int size_x; | |
unsigned int size_y; | |
unsigned int margin_top; | |
unsigned int margin_bottom; | |
unsigned int line_num; | |
struct line **lines; | |
/* scroll-back buffer */ | |
unsigned int sb_count; /* number of lines in sb */ | |
struct line *sb_first; /* first line; was moved first */ | |
struct line *sb_last; /* last line; was moved last*/ | |
unsigned int sb_max; /* max-limit of lines in sb */ | |
struct line *sb_pos; /* current position in sb or NULL */ | |
/* cursor */ | |
unsigned int cursor_x; | |
unsigned int cursor_y; | |
/* tab ruler */ | |
bool *tab_ruler; | |
}; | |
static void cell_init(struct tsm_screen *con, struct cell *cell) | |
{ | |
cell->ch = 0; | |
memcpy(&cell->attr, &con->def_attr, sizeof(cell->attr)); | |
} | |
static int line_new(struct tsm_screen *con, struct line **out, | |
unsigned int width) | |
{ | |
struct line *line; | |
unsigned int i; | |
if (!width) | |
return -EINVAL; | |
line = malloc(sizeof(*line)); | |
if (!line) | |
return -ENOMEM; | |
line->next = NULL; | |
line->prev = NULL; | |
line->size = width; | |
line->cells = malloc(sizeof(struct cell) * width); | |
if (!line->cells) { | |
free(line); | |
return -ENOMEM; | |
} | |
for (i = 0; i < width; ++i) | |
cell_init(con, &line->cells[i]); | |
*out = line; | |
return 0; | |
} | |
static void line_free(struct line *line) | |
{ | |
free(line->cells); | |
free(line); | |
} | |
static int line_resize(struct tsm_screen *con, struct line *line, | |
unsigned int width) | |
{ | |
struct cell *tmp; | |
if (!line || !width) | |
return -EINVAL; | |
if (line->size < width) { | |
tmp = realloc(line->cells, width * sizeof(struct cell)); | |
if (!tmp) | |
return -ENOMEM; | |
line->cells = tmp; | |
line->size = width; | |
while (line->size < width) { | |
cell_init(con, &line->cells[line->size]); | |
++line->size; | |
} | |
} | |
return 0; | |
} | |
/* This links the given line into the scrollback-buffer */ | |
static void link_to_scrollback(struct tsm_screen *con, struct line *line) | |
{ | |
struct line *tmp; | |
if (con->sb_max == 0) { | |
line_free(line); | |
return; | |
} | |
/* Remove a line from the scrollback buffer if it reaches its maximum. | |
* We must take care to correctly keep the current position as the new | |
* line is linked in after we remove the top-most line here. | |
* sb_max == 0 is tested earlier so we can assume sb_max > 0 here. In | |
* other words, buf->sb_first is a valid line if sb_count >= sb_max. */ | |
if (con->sb_count >= con->sb_max) { | |
tmp = con->sb_first; | |
con->sb_first = tmp->next; | |
if (tmp->next) | |
tmp->next->prev = NULL; | |
else | |
con->sb_last = NULL; | |
--con->sb_count; | |
/* (position == tmp && !next) means we have sb_max=1 so set | |
* position to the new line. Otherwise, set to new first line. | |
* If position!=tmp and we have a fixed-position then nothing | |
* needs to be done because we can stay at the same line. If we | |
* have no fixed-position, we need to set the position to the | |
* next inserted line, which can be "line", too. */ | |
if (con->sb_pos) { | |
if (con->sb_pos == tmp || | |
!(con->flags & TSM_SCREEN_FIXED_POS)) { | |
if (con->sb_pos->next) | |
con->sb_pos = con->sb_pos->next; | |
else | |
con->sb_pos = line; | |
} | |
} | |
line_free(tmp); | |
} | |
line->next = NULL; | |
line->prev = con->sb_last; | |
if (con->sb_last) | |
con->sb_last->next = line; | |
else | |
con->sb_first = line; | |
con->sb_last = line; | |
++con->sb_count; | |
} | |
static void screen_scroll_up(struct tsm_screen *con, unsigned int num) | |
{ | |
unsigned int i, j, max; | |
int ret; | |
if (!num) | |
return; | |
max = con->margin_bottom + 1 - con->margin_top; | |
if (num > max) | |
num = max; | |
/* We cache lines on the stack to speed up the scrolling. However, if | |
* num is too big we might get overflows here so use recursion if num | |
* exceeds a hard-coded limit. | |
* 128 seems to be a sane limit that should never be reached but should | |
* also be small enough so we do not get stack overflows. */ | |
if (num > 128) { | |
screen_scroll_up(con, 128); | |
return screen_scroll_up(con, num - 128); | |
} | |
struct line *cache[num]; | |
for (i = 0; i < num; ++i) { | |
ret = line_new(con, &cache[i], con->size_x); | |
if (!ret) { | |
link_to_scrollback(con, | |
con->lines[con->margin_top + i]); | |
} else { | |
cache[i] = con->lines[con->margin_top + i]; | |
for (j = 0; j < con->size_x; ++j) | |
cell_init(con, &cache[i]->cells[j]); | |
} | |
} | |
if (num < max) { | |
memmove(&con->lines[con->margin_top], | |
&con->lines[con->margin_top + num], | |
(max - num) * sizeof(struct line*)); | |
} | |
memcpy(&con->lines[con->margin_top + (max - num)], | |
cache, num * sizeof(struct line*)); | |
} | |
static void screen_scroll_down(struct tsm_screen *con, unsigned int num) | |
{ | |
unsigned int i, j, max; | |
if (!num) | |
return; | |
max = con->margin_bottom + 1 - con->margin_top; | |
if (num > max) | |
num = max; | |
/* see screen_scroll_up() for an explanation */ | |
if (num > 128) { | |
screen_scroll_down(con, 128); | |
return screen_scroll_down(con, num - 128); | |
} | |
struct line *cache[num]; | |
for (i = 0; i < num; ++i) { | |
cache[i] = con->lines[con->margin_bottom - i]; | |
for (j = 0; j < con->size_x; ++j) | |
cell_init(con, &cache[i]->cells[j]); | |
} | |
if (num < max) { | |
memmove(&con->lines[con->margin_top + num], | |
&con->lines[con->margin_top], | |
(max - num) * sizeof(struct line*)); | |
} | |
memcpy(&con->lines[con->margin_top], | |
cache, num * sizeof(struct line*)); | |
} | |
static void screen_write(struct tsm_screen *con, unsigned int x, | |
unsigned int y, tsm_symbol_t ch, | |
const struct tsm_screen_attr *attr) | |
{ | |
struct line *line; | |
if (x >= con->size_x || y >= con->size_y) { | |
llog_warn(con, "writing beyond buffer boundary"); | |
return; | |
} | |
line = con->lines[y]; | |
if ((con->flags & TSM_SCREEN_INSERT_MODE) && x < (con->size_x - 1)) | |
memmove(&line->cells[x + 1], &line->cells[x], | |
sizeof(struct cell) * (con->size_x - 1 - x)); | |
line->cells[x].ch = ch; | |
memcpy(&line->cells[x].attr, attr, sizeof(*attr)); | |
} | |
static void screen_erase_region(struct tsm_screen *con, | |
unsigned int x_from, | |
unsigned int y_from, | |
unsigned int x_to, | |
unsigned int y_to, | |
bool protect) | |
{ | |
unsigned int to; | |
struct line *line; | |
if (y_to >= con->size_y) | |
y_to = con->size_y - 1; | |
if (x_to >= con->size_x) | |
x_to = con->size_x - 1; | |
for ( ; y_from <= y_to; ++y_from) { | |
line = con->lines[y_from]; | |
if (!line) { | |
x_from = 0; | |
continue; | |
} | |
if (y_from == y_to) | |
to = x_to; | |
else | |
to = con->size_x - 1; | |
for ( ; x_from <= to; ++x_from) { | |
if (protect && line->cells[x_from].attr.protect) | |
continue; | |
cell_init(con, &line->cells[x_from]); | |
} | |
x_from = 0; | |
} | |
} | |
static inline unsigned int to_abs_x(struct tsm_screen *con, unsigned int x) | |
{ | |
return x; | |
} | |
static inline unsigned int to_abs_y(struct tsm_screen *con, unsigned int y) | |
{ | |
if (!(con->flags & TSM_SCREEN_REL_ORIGIN)) | |
return y; | |
return con->margin_top + y; | |
} | |
int tsm_screen_new(struct tsm_screen **out, tsm_log_t log) | |
{ | |
struct tsm_screen *con; | |
int ret; | |
unsigned int i; | |
if (!out) | |
return -EINVAL; | |
con = malloc(sizeof(*con)); | |
if (!con) | |
return -ENOMEM; | |
memset(con, 0, sizeof(*con)); | |
con->ref = 1; | |
con->llog = log; | |
con->def_attr.fr = 255; | |
con->def_attr.fg = 255; | |
con->def_attr.fb = 255; | |
ret = shl_timer_new(&con->timer); | |
if (ret) | |
goto err_free; | |
ret = tsm_screen_resize(con, 80, 24); | |
if (ret) | |
goto err_timer; | |
llog_debug(con, "new screen"); | |
*out = con; | |
return 0; | |
err_timer: | |
shl_timer_free(con->timer); | |
for (i = 0; i < con->line_num; ++i) | |
line_free(con->lines[i]); | |
free(con->lines); | |
free(con->tab_ruler); | |
err_free: | |
free(con); | |
return ret; | |
} | |
void tsm_screen_ref(struct tsm_screen *con) | |
{ | |
if (!con) | |
return; | |
++con->ref; | |
} | |
void tsm_screen_unref(struct tsm_screen *con) | |
{ | |
unsigned int i; | |
if (!con || !con->ref || --con->ref) | |
return; | |
llog_debug(con, "destroying screen"); | |
for (i = 0; i < con->line_num; ++i) | |
line_free(con->lines[i]); | |
free(con->lines); | |
free(con->tab_ruler); | |
shl_timer_free(con->timer); | |
free(con); | |
} | |
void tsm_screen_set_opts(struct tsm_screen *scr, unsigned int opts) | |
{ | |
if (!scr || !opts) | |
return; | |
scr->opts |= opts; | |
} | |
void tsm_screen_reset_opts(struct tsm_screen *scr, unsigned int opts) | |
{ | |
if (!scr || !opts) | |
return; | |
scr->opts &= ~opts; | |
} | |
unsigned int tsm_screen_get_opts(struct tsm_screen *scr) | |
{ | |
if (!scr) | |
return 0; | |
return scr->opts; | |
} | |
unsigned int tsm_screen_get_width(struct tsm_screen *con) | |
{ | |
if (!con) | |
return 0; | |
return con->size_x; | |
} | |
unsigned int tsm_screen_get_height(struct tsm_screen *con) | |
{ | |
if (!con) | |
return 0; | |
return con->size_y; | |
} | |
int tsm_screen_resize(struct tsm_screen *con, unsigned int x, | |
unsigned int y) | |
{ | |
struct line **cache; | |
unsigned int i, j, width; | |
int ret; | |
bool *tab_ruler; | |
if (!con || !x || !y) | |
return -EINVAL; | |
if (con->size_x == x && con->size_y == y) | |
return 0; | |
/* First make sure the line buffer is big enough for our new screen. | |
* That is, allocate all new lines and make sure each line has enough | |
* cells to hold the new screen or the current screen. If we fail, we | |
* can safely return -ENOMEM and the buffer is still valid. We must | |
* allocate the new lines to at least the same size as the current | |
* lines. Otherwise, if this function fails in later turns, we will have | |
* invalid lines in the buffer. */ | |
if (y > con->line_num) { | |
cache = realloc(con->lines, sizeof(struct line*) * y); | |
if (!cache) | |
return -ENOMEM; | |
con->lines = cache; | |
if (x > con->size_x) | |
width = x; | |
else | |
width = con->size_x; | |
while (con->line_num < y) { | |
ret = line_new(con, &cache[con->line_num], width); | |
if (ret) | |
return ret; | |
++con->line_num; | |
} | |
} | |
/* Resize all lines in the buffer if we increase screen width. This | |
* will guarantee that all lines are big enough so we can resize the | |
* buffer without reallocating them later. */ | |
if (x > con->size_x) { | |
tab_ruler = realloc(con->tab_ruler, sizeof(bool) * x); | |
if (!tab_ruler) | |
return -ENOMEM; | |
con->tab_ruler = tab_ruler; | |
for (i = 0; i < con->line_num; ++i) { | |
ret = line_resize(con, con->lines[i], x); | |
if (ret) | |
return ret; | |
} | |
} | |
/* When resizing, we need to reset all the new cells, otherwise, the old | |
* data that was written there will reoccur on the screen. */ | |
for (j = 0; j < y; ++j) { | |
for (i = con->size_x; i < x; ++i) | |
cell_init(con, &con->lines[j]->cells[i]); | |
} | |
/* xterm destroys margins on resize, so do we */ | |
con->margin_top = 0; | |
con->margin_bottom = con->size_y - 1; | |
/* scroll buffer if screen height shrinks */ | |
if (con->size_y != 0 && y < con->size_y) | |
screen_scroll_up(con, con->size_y - y); | |
/* reset tabs */ | |
for (i = 0; i < x; ++i) { | |
if (i % 8 == 0) | |
con->tab_ruler[i] = true; | |
else | |
con->tab_ruler[i] = false; | |
} | |
con->size_x = x; | |
con->size_y = y; | |
con->margin_top = 0; | |
con->margin_bottom = con->size_y - 1; | |
if (con->cursor_x >= con->size_x) | |
con->cursor_x = con->size_x - 1; | |
if (con->cursor_y >= con->size_y) | |
con->cursor_y = con->size_y - 1; | |
return 0; | |
} | |
int tsm_screen_set_margins(struct tsm_screen *con, | |
unsigned int top, unsigned int bottom) | |
{ | |
unsigned int upper, lower; | |
if (!con) | |
return -EINVAL; | |
if (!top) | |
top = 1; | |
if (bottom <= top) { | |
upper = 0; | |
lower = con->size_y - 1; | |
} else if (bottom > con->size_y) { | |
upper = 0; | |
lower = con->size_y - 1; | |
} else { | |
upper = top - 1; | |
lower = bottom - 1; | |
} | |
con->margin_top = upper; | |
con->margin_bottom = lower; | |
return 0; | |
} | |
/* set maximum scrollback buffer size */ | |
void tsm_screen_set_max_sb(struct tsm_screen *con, | |
unsigned int max) | |
{ | |
struct line *line; | |
if (!con) | |
return; | |
while (con->sb_count > max) { | |
line = con->sb_first; | |
con->sb_first = line->next; | |
if (line->next) | |
line->next->prev = NULL; | |
else | |
con->sb_last = NULL; | |
con->sb_count--; | |
/* We treat fixed/unfixed position the same here because we | |
* remove lines from the TOP of the scrollback buffer. */ | |
if (con->sb_pos == line) | |
con->sb_pos = con->sb_first; | |
line_free(line); | |
} | |
con->sb_max = max; | |
} | |
/* clear scrollback buffer */ | |
void tsm_screen_clear_sb(struct tsm_screen *con) | |
{ | |
struct line *iter, *tmp; | |
if (!con) | |
return; | |
for (iter = con->sb_first; iter; ) { | |
tmp = iter; | |
iter = iter->next; | |
line_free(tmp); | |
} | |
con->sb_first = NULL; | |
con->sb_last = NULL; | |
con->sb_count = 0; | |
con->sb_pos = NULL; | |
} | |
void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num) | |
{ | |
if (!con || !num) | |
return; | |
while (num--) { | |
if (con->sb_pos) { | |
if (!con->sb_pos->prev) | |
return; | |
con->sb_pos = con->sb_pos->prev; | |
} else if (!con->sb_last) { | |
return; | |
} else { | |
con->sb_pos = con->sb_last; | |
} | |
} | |
} | |
void tsm_screen_sb_down(struct tsm_screen *con, unsigned int num) | |
{ | |
if (!con || !num) | |
return; | |
while (num--) { | |
if (con->sb_pos) { | |
con->sb_pos = con->sb_pos->next; | |
if (!con->sb_pos) | |
return; | |
} else { | |
return; | |
} | |
} | |
} | |
void tsm_screen_sb_page_up(struct tsm_screen *con, unsigned int num) | |
{ | |
if (!con || !num) | |
return; | |
tsm_screen_sb_up(con, num * con->size_y); | |
} | |
void tsm_screen_sb_page_down(struct tsm_screen *con, unsigned int num) | |
{ | |
if (!con || !num) | |
return; | |
tsm_screen_sb_down(con, num * con->size_y); | |
} | |
void tsm_screen_sb_reset(struct tsm_screen *con) | |
{ | |
if (!con) | |
return; | |
con->sb_pos = NULL; | |
} | |
void tsm_screen_set_def_attr(struct tsm_screen *con, | |
const struct tsm_screen_attr *attr) | |
{ | |
if (!con || !attr) | |
return; | |
memcpy(&con->def_attr, attr, sizeof(*attr)); | |
} | |
void tsm_screen_reset(struct tsm_screen *con) | |
{ | |
unsigned int i; | |
if (!con) | |
return; | |
con->flags = 0; | |
con->margin_top = 0; | |
con->margin_bottom = con->size_y - 1; | |
for (i = 0; i < con->size_x; ++i) { | |
if (i % 8 == 0) | |
con->tab_ruler[i] = true; | |
else | |
con->tab_ruler[i] = false; | |
} | |
} | |
void tsm_screen_set_flags(struct tsm_screen *con, unsigned int flags) | |
{ | |
if (!con || !flags) | |
return; | |
con->flags |= flags; | |
} | |
void tsm_screen_reset_flags(struct tsm_screen *con, unsigned int flags) | |
{ | |
if (!con || !flags) | |
return; | |
con->flags &= ~flags; | |
} | |
unsigned int tsm_screen_get_flags(struct tsm_screen *con) | |
{ | |
if (!con) | |
return 0; | |
return con->flags; | |
} | |
unsigned int tsm_screen_get_cursor_x(struct tsm_screen *con) | |
{ | |
if (!con) | |
return 0; | |
return con->cursor_x; | |
} | |
unsigned int tsm_screen_get_cursor_y(struct tsm_screen *con) | |
{ | |
if (!con) | |
return 0; | |
return con->cursor_y; | |
} | |
void tsm_screen_set_tabstop(struct tsm_screen *con) | |
{ | |
if (!con || con->cursor_x >= con->size_x) | |
return; | |
con->tab_ruler[con->cursor_x] = true; | |
} | |
void tsm_screen_reset_tabstop(struct tsm_screen *con) | |
{ | |
if (!con || con->cursor_x >= con->size_x) | |
return; | |
con->tab_ruler[con->cursor_x] = false; | |
} | |
void tsm_screen_reset_all_tabstops(struct tsm_screen *con) | |
{ | |
unsigned int i; | |
if (!con) | |
return; | |
for (i = 0; i < con->size_x; ++i) | |
con->tab_ruler[i] = false; | |
} | |
void tsm_screen_write(struct tsm_screen *con, tsm_symbol_t ch, | |
const struct tsm_screen_attr *attr) | |
{ | |
unsigned int last; | |
if (!con) | |
return; | |
if (con->cursor_y <= con->margin_bottom || | |
con->cursor_y >= con->size_y) | |
last = con->margin_bottom; | |
else | |
last = con->size_y - 1; | |
if (con->cursor_x >= con->size_x) { | |
if (con->flags & TSM_SCREEN_AUTO_WRAP) { | |
con->cursor_x = 0; | |
++con->cursor_y; | |
} else { | |
con->cursor_x = con->size_x - 1; | |
} | |
} | |
if (con->cursor_y > last) { | |
con->cursor_y = last; | |
screen_scroll_up(con, 1); | |
} | |
screen_write(con, con->cursor_x, con->cursor_y, ch, attr); | |
++con->cursor_x; | |
} | |
void tsm_screen_newline(struct tsm_screen *con) | |
{ | |
if (!con) | |
return; | |
tsm_screen_move_down(con, 1, true); | |
tsm_screen_move_line_home(con); | |
} | |
void tsm_screen_scroll_up(struct tsm_screen *con, unsigned int num) | |
{ | |
if (!con || !num) | |
return; | |
screen_scroll_up(con, num); | |
} | |
void tsm_screen_scroll_down(struct tsm_screen *con, unsigned int num) | |
{ | |
if (!con || !num) | |
return; | |
screen_scroll_down(con, num); | |
} | |
void tsm_screen_move_to(struct tsm_screen *con, unsigned int x, | |
unsigned int y) | |
{ | |
unsigned int last; | |
if (!con) | |
return; | |
if (con->flags & TSM_SCREEN_REL_ORIGIN) | |
last = con->margin_bottom; | |
else | |
last = con->size_y - 1; | |
con->cursor_x = to_abs_x(con, x); | |
if (con->cursor_x >= con->size_x) | |
con->cursor_x = con->size_x - 1; | |
con->cursor_y = to_abs_y(con, y); | |
if (con->cursor_y > last) | |
con->cursor_y = last; | |
} | |
void tsm_screen_move_up(struct tsm_screen *con, unsigned int num, | |
bool scroll) | |
{ | |
unsigned int diff, size; | |
if (!con || !num) | |
return; | |
if (con->cursor_y >= con->margin_top) | |
size = con->margin_top; | |
else | |
size = 0; | |
diff = con->cursor_y - size; | |
if (num > diff) { | |
num -= diff; | |
if (scroll) | |
screen_scroll_down(con, num); | |
con->cursor_y = size; | |
} else { | |
con->cursor_y -= num; | |
} | |
} | |
void tsm_screen_move_down(struct tsm_screen *con, unsigned int num, | |
bool scroll) | |
{ | |
unsigned int diff, size; | |
if (!con || !num) | |
return; | |
if (con->cursor_y <= con->margin_bottom) | |
size = con->margin_bottom + 1; | |
else | |
size = con->size_y; | |
diff = size - con->cursor_y - 1; | |
if (num > diff) { | |
num -= diff; | |
if (scroll) | |
screen_scroll_up(con, num); | |
con->cursor_y = size - 1; | |
} else { | |
con->cursor_y += num; | |
} | |
} | |
void tsm_screen_move_left(struct tsm_screen *con, unsigned int num) | |
{ | |
if (!con || !num) | |
return; | |
if (num > con->size_x) | |
num = con->size_x; | |
if (con->cursor_x >= con->size_x) | |
con->cursor_x = con->size_x - 1; | |
if (num > con->cursor_x) | |
con->cursor_x = 0; | |
else | |
con->cursor_x -= num; | |
} | |
void tsm_screen_move_right(struct tsm_screen *con, unsigned int num) | |
{ | |
if (!con || !num) | |
return; | |
if (num > con->size_x) | |
num = con->size_x; | |
if (num + con->cursor_x >= con->size_x) | |
con->cursor_x = con->size_x - 1; | |
else | |
con->cursor_x += num; | |
} | |
void tsm_screen_move_line_end(struct tsm_screen *con) | |
{ | |
if (!con) | |
return; | |
con->cursor_x = con->size_x - 1; | |
} | |
void tsm_screen_move_line_home(struct tsm_screen *con) | |
{ | |
if (!con) | |
return; | |
con->cursor_x = 0; | |
} | |
void tsm_screen_tab_right(struct tsm_screen *con, unsigned int num) | |
{ | |
unsigned int i, j; | |
if (!con || !num) | |
return; | |
for (i = 0; i < num; ++i) { | |
for (j = con->cursor_x + 1; j < con->size_x; ++j) { | |
if (con->tab_ruler[j]) | |
break; | |
} | |
con->cursor_x = j; | |
if (con->cursor_x + 1 >= con->size_x) | |
break; | |
} | |
/* tabs never cause pending new-lines */ | |
if (con->cursor_x >= con->size_x) | |
con->cursor_x = con->size_x - 1; | |
} | |
void tsm_screen_tab_left(struct tsm_screen *con, unsigned int num) | |
{ | |
unsigned int i; | |
int j; | |
if (!con || !num) | |
return; | |
for (i = 0; i < num; ++i) { | |
for (j = con->cursor_x - 1; j > 0; --j) { | |
if (con->tab_ruler[j]) | |
break; | |
} | |
if (j <= 0) { | |
con->cursor_x = 0; | |
break; | |
} | |
con->cursor_x = j; | |
} | |
} | |
void tsm_screen_insert_lines(struct tsm_screen *con, unsigned int num) | |
{ | |
unsigned int i, j, max; | |
if (!con || !num) | |
return; | |
if (con->cursor_y < con->margin_top || | |
con->cursor_y > con->margin_bottom) | |
return; | |
max = con->margin_bottom - con->cursor_y + 1; | |
if (num > max) | |
num = max; | |
struct line *cache[num]; | |
for (i = 0; i < num; ++i) { | |
cache[i] = con->lines[con->margin_bottom - i]; | |
for (j = 0; j < con->size_x; ++j) | |
cell_init(con, &cache[i]->cells[j]); | |
} | |
if (num < max) { | |
memmove(&con->lines[con->cursor_y + num], | |
&con->lines[con->cursor_y], | |
(max - num) * sizeof(struct line*)); | |
memcpy(&con->lines[con->cursor_y], | |
cache, num * sizeof(struct line*)); | |
} | |
con->cursor_x = 0; | |
} | |
void tsm_screen_delete_lines(struct tsm_screen *con, unsigned int num) | |
{ | |
unsigned int i, j, max; | |
if (!con || !num) | |
return; | |
if (con->cursor_y < con->margin_top || | |
con->cursor_y > con->margin_bottom) | |
return; | |
max = con->margin_bottom - con->cursor_y + 1; | |
if (num > max) | |
num = max; | |
struct line *cache[num]; | |
for (i = 0; i < num; ++i) { | |
cache[i] = con->lines[con->cursor_y + i]; | |
for (j = 0; j < con->size_x; ++j) | |
cell_init(con, &cache[i]->cells[j]); | |
} | |
if (num < max) { | |
memmove(&con->lines[con->cursor_y], | |
&con->lines[con->cursor_y + num], | |
(max - num) * sizeof(struct line*)); | |
memcpy(&con->lines[con->cursor_y + (max - num)], | |
cache, num * sizeof(struct line*)); | |
} | |
con->cursor_x = 0; | |
} | |
void tsm_screen_insert_chars(struct tsm_screen *con, unsigned int num) | |
{ | |
struct cell *cells; | |
unsigned int max, mv, i; | |
if (!con || !num || !con->size_y || !con->size_x) | |
return; | |
if (con->cursor_x >= con->size_x) | |
con->cursor_x = con->size_x - 1; | |
if (con->cursor_y >= con->size_y) | |
con->cursor_y = con->size_y - 1; | |
max = con->size_x - con->cursor_x; | |
if (num > max) | |
num = max; | |
mv = max - num; | |
cells = con->lines[con->cursor_y]->cells; | |
if (mv) | |
memmove(&cells[con->cursor_x + num], | |
&cells[con->cursor_x], | |
mv * sizeof(*cells)); | |
for (i = 0; i < num; ++i) { | |
cell_init(con, &cells[con->cursor_x + i]); | |
} | |
} | |
void tsm_screen_delete_chars(struct tsm_screen *con, unsigned int num) | |
{ | |
struct cell *cells; | |
unsigned int max, mv, i; | |
if (!con || !num || !con->size_y || !con->size_x) | |
return; | |
if (con->cursor_x >= con->size_x) | |
con->cursor_x = con->size_x - 1; | |
if (con->cursor_y >= con->size_y) | |
con->cursor_y = con->size_y - 1; | |
max = con->size_x - con->cursor_x; | |
if (num > max) | |
num = max; | |
mv = max - num; | |
cells = con->lines[con->cursor_y]->cells; | |
if (mv) | |
memmove(&cells[con->cursor_x], | |
&cells[con->cursor_x + num], | |
mv * sizeof(*cells)); | |
for (i = 0; i < num; ++i) { | |
cell_init(con, &cells[con->cursor_x + mv + i]); | |
} | |
} | |
void tsm_screen_erase_cursor(struct tsm_screen *con) | |
{ | |
unsigned int x; | |
if (!con) | |
return; | |
if (con->cursor_x >= con->size_x) | |
x = con->size_x - 1; | |
else | |
x = con->cursor_x; | |
screen_erase_region(con, x, con->cursor_y, x, con->cursor_y, false); | |
} | |
void tsm_screen_erase_chars(struct tsm_screen *con, unsigned int num) | |
{ | |
unsigned int x; | |
if (!con || !num) | |
return; | |
if (con->cursor_x >= con->size_x) | |
x = con->size_x - 1; | |
else | |
x = con->cursor_x; | |
screen_erase_region(con, x, con->cursor_y, x + num - 1, con->cursor_y, | |
false); | |
} | |
void tsm_screen_erase_cursor_to_end(struct tsm_screen *con, | |
bool protect) | |
{ | |
unsigned int x; | |
if (!con) | |
return; | |
if (con->cursor_x >= con->size_x) | |
x = con->size_x - 1; | |
else | |
x = con->cursor_x; | |
screen_erase_region(con, x, con->cursor_y, con->size_x - 1, | |
con->cursor_y, protect); | |
} | |
void tsm_screen_erase_home_to_cursor(struct tsm_screen *con, | |
bool protect) | |
{ | |
if (!con) | |
return; | |
screen_erase_region(con, 0, con->cursor_y, con->cursor_x, | |
con->cursor_y, protect); | |
} | |
void tsm_screen_erase_current_line(struct tsm_screen *con, | |
bool protect) | |
{ | |
if (!con) | |
return; | |
screen_erase_region(con, 0, con->cursor_y, con->size_x - 1, | |
con->cursor_y, protect); | |
} | |
void tsm_screen_erase_screen_to_cursor(struct tsm_screen *con, | |
bool protect) | |
{ | |
if (!con) | |
return; | |
screen_erase_region(con, 0, 0, con->cursor_x, con->cursor_y, protect); | |
} | |
void tsm_screen_erase_cursor_to_screen(struct tsm_screen *con, | |
bool protect) | |
{ | |
unsigned int x; | |
if (!con) | |
return; | |
if (con->cursor_x >= con->size_x) | |
x = con->size_x - 1; | |
else | |
x = con->cursor_x; | |
screen_erase_region(con, x, con->cursor_y, con->size_x - 1, | |
con->size_y - 1, protect); | |
} | |
void tsm_screen_erase_screen(struct tsm_screen *con, bool protect) | |
{ | |
if (!con) | |
return; | |
screen_erase_region(con, 0, 0, con->size_x - 1, con->size_y - 1, | |
protect); | |
} | |
void tsm_screen_draw(struct tsm_screen *con, | |
tsm_screen_prepare_cb prepare_cb, | |
tsm_screen_draw_cb draw_cb, | |
tsm_screen_render_cb render_cb, | |
void *data) | |
{ | |
unsigned int cur_x, cur_y; | |
unsigned int i, j, k; | |
struct line *iter, *line = NULL; | |
struct cell *cell; | |
struct tsm_screen_attr attr; | |
bool cursor_done = false; | |
int ret, warned = 0; | |
uint64_t time_prep = 0, time_draw = 0, time_rend = 0; | |
const uint32_t *ch; | |
size_t len; | |
if (!con || !draw_cb) | |
return; | |
cur_x = con->cursor_x; | |
if (con->cursor_x >= con->size_x) | |
cur_x = con->size_x - 1; | |
cur_y = con->cursor_y; | |
if (con->cursor_y >= con->size_y) | |
cur_y = con->size_y - 1; | |
/* render preparation */ | |
if (prepare_cb) { | |
if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING) | |
shl_timer_reset(con->timer); | |
ret = prepare_cb(con, data); | |
if (ret) { | |
llog_warning(con, | |
"cannot prepare text-renderer for rendering"); | |
return; | |
} | |
if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING) | |
time_prep = shl_timer_elapsed(con->timer); | |
} else { | |
time_prep = 0; | |
} | |
/* push each character into rendering pipeline */ | |
if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING) | |
shl_timer_reset(con->timer); | |
iter = con->sb_pos; | |
k = 0; | |
for (i = 0; i < con->size_y; ++i) { | |
if (iter) { | |
line = iter; | |
iter = iter->next; | |
} else { | |
line = con->lines[k]; | |
k++; | |
} | |
for (j = 0; j < con->size_x; ++j) { | |
cell = &line->cells[j]; | |
memcpy(&attr, &cell->attr, sizeof(attr)); | |
if (k == cur_y + 1 && | |
j == cur_x) { | |
cursor_done = true; | |
if (!(con->flags & TSM_SCREEN_HIDE_CURSOR)) | |
attr.inverse = !attr.inverse; | |
} | |
/* TODO: do some more sophisticated inverse here. When | |
* INVERSE mode is set, we should instead just select | |
* inverse colors instead of switching background and | |
* foreground */ | |
if (con->flags & TSM_SCREEN_INVERSE) | |
attr.inverse = !attr.inverse; | |
ch = tsm_symbol_get(NULL, &cell->ch, &len); | |
if (cell->ch == ' ' || cell->ch == 0) | |
len = 0; | |
ret = draw_cb(con, cell->ch, ch, len, j, i, &attr, | |
data); | |
if (ret && warned++ < 3) { | |
llog_debug(con, | |
"cannot draw glyph at %ux%u via text-renderer", | |
j, i); | |
if (warned == 3) | |
llog_debug(con, | |
"suppressing further warnings during this rendering round"); | |
} | |
} | |
if (k == cur_y + 1 && !cursor_done) { | |
cursor_done = true; | |
if (!(con->flags & TSM_SCREEN_HIDE_CURSOR)) { | |
if (!(con->flags & TSM_SCREEN_INVERSE)) | |
attr.inverse = !attr.inverse; | |
draw_cb(con, 0, NULL, 0, cur_x, i, &attr, data); | |
} | |
} | |
} | |
if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING) | |
time_draw = shl_timer_elapsed(con->timer); | |
/* perform final rendering steps */ | |
if (render_cb) { | |
if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING) | |
shl_timer_reset(con->timer); | |
ret = render_cb(con, data); | |
if (ret) | |
llog_warning(con, | |
"cannot render via text-renderer"); | |
if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING) | |
time_rend = shl_timer_elapsed(con->timer); | |
} else { | |
time_rend = 0; | |
} | |
if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING) | |
llog_debug(con, | |
"timing: sum: %llu prepare: %llu draw: %llu render: %llu", | |
time_prep + time_draw + time_rend, | |
time_prep, time_draw, time_rend); | |
} | |
/* | |
* TSM - VT Emulator | |
* | |
* Copyright (c) 2011 David Herrmann <[email protected]> | |
* Copyright (c) 2011 University of Tuebingen | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* Virtual Terminal Emulator | |
* This is the VT implementation. It is written from scratch. It uses the | |
* screen state-machine as output and is tightly bound to it. It supports | |
* functionality from vt100 up to vt500 series. It doesn't implement an | |
* explicitly selected terminal but tries to support the most important commands | |
* to be compatible with existing implementations. However, full vt102 | |
* compatibility is the least that is provided. | |
* | |
* The main parser in this file controls the parser-state and dispatches the | |
* actions to the related handlers. The parser is based on the state-diagram | |
* from Paul Williams: http://vt100.net/emu/ | |
* It is written from scratch, though. | |
* This parser is fully compatible up to the vt500 series. It requires UTF-8 and | |
* does not support any other input encoding. The G0 and G1 sets are therefore | |
* defined as subsets of UTF-8. You may still map G0-G3 into GL, though. | |
* | |
* However, the CSI/DCS/etc handlers are not designed after a specific VT | |
* series. We try to support all vt102 commands but implement several other | |
* often used sequences, too. Feel free to add further. | |
* | |
* See ./doc/vte.txt for more information on this VT-emulator. | |
*/ | |
#include <errno.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <xkbcommon/xkbcommon-keysyms.h> | |
/* Input parser states */ | |
enum parser_state { | |
STATE_NONE, /* placeholder */ | |
STATE_GROUND, /* initial state and ground */ | |
STATE_ESC, /* ESC sequence was started */ | |
STATE_ESC_INT, /* intermediate escape characters */ | |
STATE_CSI_ENTRY, /* starting CSI sequence */ | |
STATE_CSI_PARAM, /* CSI parameters */ | |
STATE_CSI_INT, /* intermediate CSI characters */ | |
STATE_CSI_IGNORE, /* CSI error; ignore this CSI sequence */ | |
STATE_DCS_ENTRY, /* starting DCS sequence */ | |
STATE_DCS_PARAM, /* DCS parameters */ | |
STATE_DCS_INT, /* intermediate DCS characters */ | |
STATE_DCS_PASS, /* DCS data passthrough */ | |
STATE_DCS_IGNORE, /* DCS error; ignore this DCS sequence */ | |
STATE_OSC_STRING, /* parsing OCS sequence */ | |
STATE_ST_IGNORE, /* unimplemented seq; ignore until ST */ | |
STATE_NUM | |
}; | |
/* Input parser actions */ | |
enum parser_action { | |
ACTION_NONE, /* placeholder */ | |
ACTION_IGNORE, /* ignore the character entirely */ | |
ACTION_PRINT, /* print the character on the console */ | |
ACTION_EXECUTE, /* execute single control character (C0/C1) */ | |
ACTION_CLEAR, /* clear current parameter state */ | |
ACTION_COLLECT, /* collect intermediate character */ | |
ACTION_PARAM, /* collect parameter character */ | |
ACTION_ESC_DISPATCH, /* dispatch escape sequence */ | |
ACTION_CSI_DISPATCH, /* dispatch csi sequence */ | |
ACTION_DCS_START, /* start of DCS data */ | |
ACTION_DCS_COLLECT, /* collect DCS data */ | |
ACTION_DCS_END, /* end of DCS data */ | |
ACTION_OSC_START, /* start of OSC data */ | |
ACTION_OSC_COLLECT, /* collect OSC data */ | |
ACTION_OSC_END, /* end of OSC data */ | |
ACTION_NUM | |
}; | |
/* CSI flags */ | |
#define CSI_BANG 0x0001 /* CSI: ! */ | |
#define CSI_CASH 0x0002 /* CSI: $ */ | |
#define CSI_WHAT 0x0004 /* CSI: ? */ | |
#define CSI_GT 0x0008 /* CSI: > */ | |
#define CSI_SPACE 0x0010 /* CSI: */ | |
#define CSI_SQUOTE 0x0020 /* CSI: ' */ | |
#define CSI_DQUOTE 0x0040 /* CSI: " */ | |
#define CSI_MULT 0x0080 /* CSI: * */ | |
#define CSI_PLUS 0x0100 /* CSI: + */ | |
#define CSI_POPEN 0x0200 /* CSI: ( */ | |
#define CSI_PCLOSE 0x0400 /* CSI: ) */ | |
/* max CSI arguments */ | |
#define CSI_ARG_MAX 16 | |
/* terminal flags */ | |
#define FLAG_CURSOR_KEY_MODE 0x00000001 /* DEC cursor key mode */ | |
#define FLAG_KEYPAD_APPLICATION_MODE 0x00000002 /* DEC keypad application mode; TODO: toggle on numlock? */ | |
#define FLAG_LINE_FEED_NEW_LINE_MODE 0x00000004 /* DEC line-feed/new-line mode */ | |
#define FLAG_8BIT_MODE 0x00000008 /* Disable UTF-8 mode and enable 8bit compatible mode */ | |
#define FLAG_7BIT_MODE 0x00000010 /* Disable 8bit mode and use 7bit compatible mode */ | |
#define FLAG_USE_C1 0x00000020 /* Explicitely use 8bit C1 codes; TODO: implement */ | |
#define FLAG_KEYBOARD_ACTION_MODE 0x00000040 /* Disable keyboard; TODO: implement? */ | |
#define FLAG_INSERT_REPLACE_MODE 0x00000080 /* Enable insert mode */ | |
#define FLAG_SEND_RECEIVE_MODE 0x00000100 /* Disable local echo */ | |
#define FLAG_TEXT_CURSOR_MODE 0x00000200 /* Show cursor */ | |
#define FLAG_INVERSE_SCREEN_MODE 0x00000400 /* Inverse colors */ | |
#define FLAG_ORIGIN_MODE 0x00000800 /* Relative origin for cursor */ | |
#define FLAG_AUTO_WRAP_MODE 0x00001000 /* Auto line wrap mode */ | |
#define FLAG_AUTO_REPEAT_MODE 0x00002000 /* Auto repeat key press; TODO: implement */ | |
#define FLAG_NATIONAL_CHARSET_MODE 0x00004000 /* Send keys from nation charsets; TODO: implement */ | |
#define FLAG_BACKGROUND_COLOR_ERASE_MODE 0x00008000 /* Set background color on erase (bce) */ | |
#define FLAG_PREPEND_ESCAPE 0x00010000 /* Prepend escape character to next output */ | |
struct vte_saved_state { | |
unsigned int cursor_x; | |
unsigned int cursor_y; | |
struct tsm_screen_attr cattr; | |
tsm_vte_charset *gl; | |
tsm_vte_charset *gr; | |
bool wrap_mode; | |
bool origin_mode; | |
}; | |
struct tsm_vte { | |
unsigned long ref; | |
tsm_log_t llog; | |
struct tsm_screen *con; | |
tsm_vte_write_cb write_cb; | |
void *data; | |
char *palette_name; | |
struct tsm_utf8_mach *mach; | |
unsigned long parse_cnt; | |
unsigned int state; | |
unsigned int csi_argc; | |
int csi_argv[CSI_ARG_MAX]; | |
unsigned int csi_flags; | |
uint8_t (*palette)[3]; | |
struct tsm_screen_attr def_attr; | |
struct tsm_screen_attr cattr; | |
unsigned int flags; | |
tsm_vte_charset *gl; | |
tsm_vte_charset *gr; | |
tsm_vte_charset *glt; | |
tsm_vte_charset *grt; | |
tsm_vte_charset *g0; | |
tsm_vte_charset *g1; | |
tsm_vte_charset *g2; | |
tsm_vte_charset *g3; | |
struct vte_saved_state saved_state; | |
}; | |
enum vte_color { | |
COLOR_BLACK, | |
COLOR_RED, | |
COLOR_GREEN, | |
COLOR_YELLOW, | |
COLOR_BLUE, | |
COLOR_MAGENTA, | |
COLOR_CYAN, | |
COLOR_LIGHT_GREY, | |
COLOR_DARK_GREY, | |
COLOR_LIGHT_RED, | |
COLOR_LIGHT_GREEN, | |
COLOR_LIGHT_YELLOW, | |
COLOR_LIGHT_BLUE, | |
COLOR_LIGHT_MAGENTA, | |
COLOR_LIGHT_CYAN, | |
COLOR_WHITE, | |
COLOR_FOREGROUND, | |
COLOR_BACKGROUND, | |
COLOR_NUM | |
}; | |
static uint8_t color_palette[COLOR_NUM][3] = { | |
[COLOR_BLACK] = { 0, 0, 0 }, /* black */ | |
[COLOR_RED] = { 205, 0, 0 }, /* red */ | |
[COLOR_GREEN] = { 0, 205, 0 }, /* green */ | |
[COLOR_YELLOW] = { 205, 205, 0 }, /* yellow */ | |
[COLOR_BLUE] = { 0, 0, 238 }, /* blue */ | |
[COLOR_MAGENTA] = { 205, 0, 205 }, /* magenta */ | |
[COLOR_CYAN] = { 0, 205, 205 }, /* cyan */ | |
[COLOR_LIGHT_GREY] = { 229, 229, 229 }, /* light grey */ | |
[COLOR_DARK_GREY] = { 127, 127, 127 }, /* dark grey */ | |
[COLOR_LIGHT_RED] = { 255, 0, 0 }, /* light red */ | |
[COLOR_LIGHT_GREEN] = { 0, 255, 0 }, /* light green */ | |
[COLOR_LIGHT_YELLOW] = { 255, 255, 0 }, /* light yellow */ | |
[COLOR_LIGHT_BLUE] = { 92, 92, 255 }, /* light blue */ | |
[COLOR_LIGHT_MAGENTA] = { 255, 0, 255 }, /* light magenta */ | |
[COLOR_LIGHT_CYAN] = { 0, 255, 255 }, /* light cyan */ | |
[COLOR_WHITE] = { 255, 255, 255 }, /* white */ | |
[COLOR_FOREGROUND] = { 229, 229, 229 }, /* light grey */ | |
[COLOR_BACKGROUND] = { 0, 0, 0 }, /* black */ | |
}; | |
static uint8_t color_palette_solarized[COLOR_NUM][3] = { | |
[COLOR_BLACK] = { 7, 54, 66 }, /* black */ | |
[COLOR_RED] = { 220, 50, 47 }, /* red */ | |
[COLOR_GREEN] = { 133, 153, 0 }, /* green */ | |
[COLOR_YELLOW] = { 181, 137, 0 }, /* yellow */ | |
[COLOR_BLUE] = { 38, 139, 210 }, /* blue */ | |
[COLOR_MAGENTA] = { 211, 54, 130 }, /* magenta */ | |
[COLOR_CYAN] = { 42, 161, 152 }, /* cyan */ | |
[COLOR_LIGHT_GREY] = { 238, 232, 213 }, /* light grey */ | |
[COLOR_DARK_GREY] = { 0, 43, 54 }, /* dark grey */ | |
[COLOR_LIGHT_RED] = { 203, 75, 22 }, /* light red */ | |
[COLOR_LIGHT_GREEN] = { 88, 110, 117 }, /* light green */ | |
[COLOR_LIGHT_YELLOW] = { 101, 123, 131 }, /* light yellow */ | |
[COLOR_LIGHT_BLUE] = { 131, 148, 150 }, /* light blue */ | |
[COLOR_LIGHT_MAGENTA] = { 108, 113, 196 }, /* light magenta */ | |
[COLOR_LIGHT_CYAN] = { 147, 161, 161 }, /* light cyan */ | |
[COLOR_WHITE] = { 253, 246, 227 }, /* white */ | |
[COLOR_FOREGROUND] = { 238, 232, 213 }, /* light grey */ | |
[COLOR_BACKGROUND] = { 7, 54, 66 }, /* black */ | |
}; | |
static uint8_t color_palette_solarized_black[COLOR_NUM][3] = { | |
[COLOR_BLACK] = { 0, 0, 0 }, /* black */ | |
[COLOR_RED] = { 220, 50, 47 }, /* red */ | |
[COLOR_GREEN] = { 133, 153, 0 }, /* green */ | |
[COLOR_YELLOW] = { 181, 137, 0 }, /* yellow */ | |
[COLOR_BLUE] = { 38, 139, 210 }, /* blue */ | |
[COLOR_MAGENTA] = { 211, 54, 130 }, /* magenta */ | |
[COLOR_CYAN] = { 42, 161, 152 }, /* cyan */ | |
[COLOR_LIGHT_GREY] = { 238, 232, 213 }, /* light grey */ | |
[COLOR_DARK_GREY] = { 0, 43, 54 }, /* dark grey */ | |
[COLOR_LIGHT_RED] = { 203, 75, 22 }, /* light red */ | |
[COLOR_LIGHT_GREEN] = { 88, 110, 117 }, /* light green */ | |
[COLOR_LIGHT_YELLOW] = { 101, 123, 131 }, /* light yellow */ | |
[COLOR_LIGHT_BLUE] = { 131, 148, 150 }, /* light blue */ | |
[COLOR_LIGHT_MAGENTA] = { 108, 113, 196 }, /* light magenta */ | |
[COLOR_LIGHT_CYAN] = { 147, 161, 161 }, /* light cyan */ | |
[COLOR_WHITE] = { 253, 246, 227 }, /* white */ | |
[COLOR_FOREGROUND] = { 238, 232, 213 }, /* light grey */ | |
[COLOR_BACKGROUND] = { 0, 0, 0 }, /* black */ | |
}; | |
static uint8_t color_palette_solarized_white[COLOR_NUM][3] = { | |
[COLOR_BLACK] = { 7, 54, 66 }, /* black */ | |
[COLOR_RED] = { 220, 50, 47 }, /* red */ | |
[COLOR_GREEN] = { 133, 153, 0 }, /* green */ | |
[COLOR_YELLOW] = { 181, 137, 0 }, /* yellow */ | |
[COLOR_BLUE] = { 38, 139, 210 }, /* blue */ | |
[COLOR_MAGENTA] = { 211, 54, 130 }, /* magenta */ | |
[COLOR_CYAN] = { 42, 161, 152 }, /* cyan */ | |
[COLOR_LIGHT_GREY] = { 238, 232, 213 }, /* light grey */ | |
[COLOR_DARK_GREY] = { 0, 43, 54 }, /* dark grey */ | |
[COLOR_LIGHT_RED] = { 203, 75, 22 }, /* light red */ | |
[COLOR_LIGHT_GREEN] = { 88, 110, 117 }, /* light green */ | |
[COLOR_LIGHT_YELLOW] = { 101, 123, 131 }, /* light yellow */ | |
[COLOR_LIGHT_BLUE] = { 131, 148, 150 }, /* light blue */ | |
[COLOR_LIGHT_MAGENTA] = { 108, 113, 196 }, /* light magenta */ | |
[COLOR_LIGHT_CYAN] = { 147, 161, 161 }, /* light cyan */ | |
[COLOR_WHITE] = { 253, 246, 227 }, /* white */ | |
[COLOR_FOREGROUND] = { 7, 54, 66 }, /* black */ | |
[COLOR_BACKGROUND] = { 238, 232, 213 }, /* light grey */ | |
}; | |
static uint8_t (*get_palette(struct tsm_vte *vte))[3] | |
{ | |
if (!vte->palette_name) | |
return color_palette; | |
if (!strcmp(vte->palette_name, "solarized")) | |
return color_palette_solarized; | |
if (!strcmp(vte->palette_name, "solarized-black")) | |
return color_palette_solarized_black; | |
if (!strcmp(vte->palette_name, "solarized-white")) | |
return color_palette_solarized_white; | |
return color_palette; | |
} | |
/* Several effects may occur when non-RGB colors are used. For instance, if bold | |
* is enabled, then a dark color code is always converted to a light color to | |
* simulate bold (even though bold may actually be supported!). To support this, | |
* we need to differentiate between a set color-code and a set rgb-color. | |
* This function actually converts a set color-code into an RGB color. This must | |
* be called before passing the attribute to the console layer so the console | |
* layer can always work with RGB values and does not have to care for color | |
* codes. */ | |
static void to_rgb(struct tsm_vte *vte, struct tsm_screen_attr *attr) | |
{ | |
int8_t code; | |
code = attr->fccode; | |
if (code >= 0) { | |
/* bold causes light colors */ | |
if (attr->bold && code < 8) | |
code += 8; | |
if (code >= COLOR_NUM) | |
code = COLOR_FOREGROUND; | |
attr->fr = vte->palette[code][0]; | |
attr->fg = vte->palette[code][1]; | |
attr->fb = vte->palette[code][2]; | |
} | |
code = attr->bccode; | |
if (code >= 0) { | |
if (code >= COLOR_NUM) | |
code = COLOR_BACKGROUND; | |
attr->br = vte->palette[code][0]; | |
attr->bg = vte->palette[code][1]; | |
attr->bb = vte->palette[code][2]; | |
} | |
} | |
static void copy_fcolor(struct tsm_screen_attr *dest, | |
const struct tsm_screen_attr *src) | |
{ | |
dest->fccode = src->fccode; | |
dest->fr = src->fr; | |
dest->fg = src->fg; | |
dest->fb = src->fb; | |
} | |
static void copy_bcolor(struct tsm_screen_attr *dest, | |
const struct tsm_screen_attr *src) | |
{ | |
dest->bccode = src->bccode; | |
dest->br = src->br; | |
dest->bg = src->bg; | |
dest->bb = src->bb; | |
} | |
int tsm_vte_new(struct tsm_vte **out, struct tsm_screen *con, | |
tsm_vte_write_cb write_cb, void *data, | |
tsm_log_t log) | |
{ | |
struct tsm_vte *vte; | |
int ret; | |
if (!out || !con || !write_cb) | |
return -EINVAL; | |
vte = malloc(sizeof(*vte)); | |
if (!vte) | |
return -ENOMEM; | |
memset(vte, 0, sizeof(*vte)); | |
vte->ref = 1; | |
vte->llog = log; | |
vte->con = con; | |
vte->write_cb = write_cb; | |
vte->data = data; | |
vte->palette = get_palette(vte); | |
vte->def_attr.fccode = COLOR_FOREGROUND; | |
vte->def_attr.bccode = COLOR_BACKGROUND; | |
to_rgb(vte, &vte->def_attr); | |
ret = tsm_utf8_mach_new(&vte->mach); | |
if (ret) | |
goto err_free; | |
tsm_vte_reset(vte); | |
tsm_screen_erase_screen(vte->con, false); | |
llog_debug(vte, "new vte object"); | |
tsm_screen_ref(vte->con); | |
*out = vte; | |
return 0; | |
err_free: | |
free(vte); | |
return ret; | |
} | |
void tsm_vte_ref(struct tsm_vte *vte) | |
{ | |
if (!vte) | |
return; | |
vte->ref++; | |
} | |
void tsm_vte_unref(struct tsm_vte *vte) | |
{ | |
if (!vte || !vte->ref) | |
return; | |
if (--vte->ref) | |
return; | |
llog_debug(vte, "destroying vte object"); | |
tsm_screen_unref(vte->con); | |
tsm_utf8_mach_free(vte->mach); | |
free(vte); | |
} | |
int tsm_vte_set_palette(struct tsm_vte *vte, const char *palette) | |
{ | |
char *tmp = NULL; | |
if (!vte) | |
return -EINVAL; | |
if (palette) { | |
tmp = strdup(palette); | |
if (!tmp) | |
return -ENOMEM; | |
} | |
free(vte->palette_name); | |
vte->palette_name = tmp; | |
vte->palette = get_palette(vte); | |
vte->def_attr.fccode = COLOR_FOREGROUND; | |
vte->def_attr.bccode = COLOR_BACKGROUND; | |
to_rgb(vte, &vte->def_attr); | |
memcpy(&vte->cattr, &vte->def_attr, sizeof(vte->cattr)); | |
tsm_screen_set_def_attr(vte->con, &vte->def_attr); | |
tsm_screen_erase_screen(vte->con, false); | |
return 0; | |
} | |
/* | |
* Write raw byte-stream to pty. | |
* When writing data to the client we must make sure that we send the correct | |
* encoding. For backwards-compatibility reasons we should always send 7bit | |
* characters exclusively. However, when FLAG_7BIT_MODE is not set, then we can | |
* also send raw 8bit characters. For instance, in FLAG_8BIT_MODE we can use the | |
* GR characters as keyboard input and send them directly or even use the C1 | |
* escape characters. In unicode mode (default) we can send multi-byte utf-8 | |
* characters which are also 8bit. When sending these characters, set the \raw | |
* flag to true so this function does not perform debug checks on data we send. | |
* If debugging is disabled, these checks are also disabled and won't affect | |
* performance. | |
* For better debugging, we also use the __LINE__ and __FILE__ macros. Use the | |
* vte_write() and vte_write_raw() macros below for more convenient use. | |
* | |
* As a rule of thumb do never send 8bit characters in escape sequences and also | |
* avoid all 8bit escape codes including the C1 codes. This will guarantee that | |
* all kind of clients are always compatible to us. | |
* | |
* If SEND_RECEIVE_MODE is off (that is, local echo is on) we have to send all | |
* data directly to ourself again. However, we must avoid recursion when | |
* tsm_vte_input() itself calls vte_write*(), therefore, we increase the | |
* PARSER counter when entering tsm_vte_input() and reset it when leaving it | |
* so we never echo data that origins from tsm_vte_input(). | |
* But note that SEND_RECEIVE_MODE is inherently broken for escape sequences | |
* that request answers. That is, if we send a request to the client that awaits | |
* a response and parse that request via local echo ourself, then we will also | |
* send a response to the client even though he didn't request one. This | |
* recursion fix does not avoid this but only prevents us from endless loops | |
* here. Anyway, only few applications rely on local echo so we can safely | |
* ignore this. | |
*/ | |
static void vte_write_debug(struct tsm_vte *vte, const char *u8, size_t len, | |
bool raw, const char *file, int line) | |
{ | |
#ifdef KMSCON_ENABLE_DEBUG | |
/* in debug mode we check that escape sequences are always <0x7f so they | |
* are correctly parsed by non-unicode and non-8bit-mode clients. */ | |
size_t i; | |
if (!raw) { | |
for (i = 0; i < len; ++i) { | |
if (u8[i] & 0x80) | |
llog_warning(vte, "sending 8bit character inline to client in %s:%d", | |
file, line); | |
} | |
} | |
#endif | |
/* in local echo mode, directly parse the data again */ | |
if (!vte->parse_cnt && !(vte->flags & FLAG_SEND_RECEIVE_MODE)) { | |
if (vte->flags & FLAG_PREPEND_ESCAPE) | |
tsm_vte_input(vte, "\e", 1); | |
tsm_vte_input(vte, u8, len); | |
} | |
if (vte->flags & FLAG_PREPEND_ESCAPE) | |
vte->write_cb(vte, "\e", 1, vte->data); | |
vte->write_cb(vte, u8, len, vte->data); | |
vte->flags &= ~FLAG_PREPEND_ESCAPE; | |
} | |
#define vte_write(_vte, _u8, _len) \ | |
vte_write_debug((_vte), (_u8), (_len), false, __FILE__, __LINE__) | |
#define vte_write_raw(_vte, _u8, _len) \ | |
vte_write_debug((_vte), (_u8), (_len), true, __FILE__, __LINE__) | |
/* write to console */ | |
static void write_console(struct tsm_vte *vte, tsm_symbol_t sym) | |
{ | |
to_rgb(vte, &vte->cattr); | |
tsm_screen_write(vte->con, sym, &vte->cattr); | |
} | |
static void reset_state(struct tsm_vte *vte) | |
{ | |
vte->saved_state.cursor_x = 0; | |
vte->saved_state.cursor_y = 0; | |
vte->saved_state.origin_mode = false; | |
vte->saved_state.wrap_mode = true; | |
vte->saved_state.gl = &tsm_vte_unicode_lower; | |
vte->saved_state.gr = &tsm_vte_unicode_upper; | |
copy_fcolor(&vte->saved_state.cattr, &vte->def_attr); | |
copy_bcolor(&vte->saved_state.cattr, &vte->def_attr); | |
vte->saved_state.cattr.bold = 0; | |
vte->saved_state.cattr.underline = 0; | |
vte->saved_state.cattr.inverse = 0; | |
vte->saved_state.cattr.protect = 0; | |
} | |
static void save_state(struct tsm_vte *vte) | |
{ | |
vte->saved_state.cursor_x = tsm_screen_get_cursor_x(vte->con); | |
vte->saved_state.cursor_y = tsm_screen_get_cursor_y(vte->con); | |
vte->saved_state.cattr = vte->cattr; | |
vte->saved_state.gl = vte->gl; | |
vte->saved_state.gr = vte->gr; | |
vte->saved_state.wrap_mode = vte->flags & FLAG_AUTO_WRAP_MODE; | |
vte->saved_state.origin_mode = vte->flags & FLAG_ORIGIN_MODE; | |
} | |
static void restore_state(struct tsm_vte *vte) | |
{ | |
tsm_screen_move_to(vte->con, vte->saved_state.cursor_x, | |
vte->saved_state.cursor_y); | |
vte->cattr = vte->saved_state.cattr; | |
to_rgb(vte, &vte->cattr); | |
if (vte->flags & FLAG_BACKGROUND_COLOR_ERASE_MODE) | |
tsm_screen_set_def_attr(vte->con, &vte->cattr); | |
vte->gl = vte->saved_state.gl; | |
vte->gr = vte->saved_state.gr; | |
if (vte->saved_state.wrap_mode) { | |
vte->flags |= FLAG_AUTO_WRAP_MODE; | |
tsm_screen_set_flags(vte->con, TSM_SCREEN_AUTO_WRAP); | |
} else { | |
vte->flags &= ~FLAG_AUTO_WRAP_MODE; | |
tsm_screen_reset_flags(vte->con, TSM_SCREEN_AUTO_WRAP); | |
} | |
if (vte->saved_state.origin_mode) { | |
vte->flags |= FLAG_ORIGIN_MODE; | |
tsm_screen_set_flags(vte->con, TSM_SCREEN_REL_ORIGIN); | |
} else { | |
vte->flags &= ~FLAG_ORIGIN_MODE; | |
tsm_screen_reset_flags(vte->con, TSM_SCREEN_REL_ORIGIN); | |
} | |
} | |
/* | |
* Reset VTE state | |
* This performs a soft reset of the VTE. That is, everything is reset to the | |
* same state as when the VTE was created. This does not affect the console, | |
* though. | |
*/ | |
void tsm_vte_reset(struct tsm_vte *vte) | |
{ | |
if (!vte) | |
return; | |
vte->flags = 0; | |
vte->flags |= FLAG_TEXT_CURSOR_MODE; | |
vte->flags |= FLAG_AUTO_REPEAT_MODE; | |
vte->flags |= FLAG_SEND_RECEIVE_MODE; | |
vte->flags |= FLAG_AUTO_WRAP_MODE; | |
vte->flags |= FLAG_BACKGROUND_COLOR_ERASE_MODE; | |
tsm_screen_reset(vte->con); | |
tsm_screen_set_flags(vte->con, TSM_SCREEN_AUTO_WRAP); | |
tsm_utf8_mach_reset(vte->mach); | |
vte->state = STATE_GROUND; | |
vte->gl = &tsm_vte_unicode_lower; | |
vte->gr = &tsm_vte_unicode_upper; | |
vte->glt = NULL; | |
vte->grt = NULL; | |
vte->g0 = &tsm_vte_unicode_lower; | |
vte->g1 = &tsm_vte_unicode_upper; | |
vte->g2 = &tsm_vte_unicode_lower; | |
vte->g3 = &tsm_vte_unicode_upper; | |
memcpy(&vte->cattr, &vte->def_attr, sizeof(vte->cattr)); | |
to_rgb(vte, &vte->cattr); | |
tsm_screen_set_def_attr(vte->con, &vte->def_attr); | |
reset_state(vte); | |
} | |
static void hard_reset(struct tsm_vte *vte) | |
{ | |
tsm_vte_reset(vte); | |
tsm_screen_erase_screen(vte->con, false); | |
tsm_screen_clear_sb(vte->con); | |
tsm_screen_move_to(vte->con, 0, 0); | |
} | |
static void send_primary_da(struct tsm_vte *vte) | |
{ | |
vte_write(vte, "\e[?60;1;6;9;15c", 17); | |
} | |
/* execute control character (C0 or C1) */ | |
static void do_execute(struct tsm_vte *vte, uint32_t ctrl) | |
{ | |
switch (ctrl) { | |
case 0x00: /* NUL */ | |
/* Ignore on input */ | |
break; | |
case 0x05: /* ENQ */ | |
/* Transmit answerback message */ | |
/* TODO: is there a better answer than ACK? */ | |
vte_write(vte, "\x06", 1); | |
break; | |
case 0x07: /* BEL */ | |
/* Sound bell tone */ | |
/* TODO: I always considered this annying, however, we | |
* should at least provide some way to enable it if the | |
* user *really* wants it. | |
*/ | |
break; | |
case 0x08: /* BS */ | |
/* Move cursor one position left */ | |
tsm_screen_move_left(vte->con, 1); | |
break; | |
case 0x09: /* HT */ | |
/* Move to next tab stop or end of line */ | |
tsm_screen_tab_right(vte->con, 1); | |
break; | |
case 0x0a: /* LF */ | |
case 0x0b: /* VT */ | |
case 0x0c: /* FF */ | |
/* Line feed or newline (CR/NL mode) */ | |
if (vte->flags & FLAG_LINE_FEED_NEW_LINE_MODE) | |
tsm_screen_newline(vte->con); | |
else | |
tsm_screen_move_down(vte->con, 1, true); | |
break; | |
case 0x0d: /* CR */ | |
/* Move cursor to left margin */ | |
tsm_screen_move_line_home(vte->con); | |
break; | |
case 0x0e: /* SO */ | |
/* Map G1 character set into GL */ | |
vte->gl = vte->g1; | |
break; | |
case 0x0f: /* SI */ | |
/* Map G0 character set into GL */ | |
vte->gl = vte->g0; | |
break; | |
case 0x11: /* XON */ | |
/* Resume transmission */ | |
/* TODO */ | |
break; | |
case 0x13: /* XOFF */ | |
/* Stop transmission */ | |
/* TODO */ | |
break; | |
case 0x18: /* CAN */ | |
/* Cancel escape sequence */ | |
/* nothing to do here */ | |
break; | |
case 0x1a: /* SUB */ | |
/* Discard current escape sequence and show err-sym */ | |
write_console(vte, 0xbf); | |
break; | |
case 0x1b: /* ESC */ | |
/* Invokes an escape sequence */ | |
/* nothing to do here */ | |
break; | |
case 0x1f: /* DEL */ | |
/* Ignored */ | |
break; | |
case 0x84: /* IND */ | |
/* Move down one row, perform scroll-up if needed */ | |
tsm_screen_move_down(vte->con, 1, true); | |
break; | |
case 0x85: /* NEL */ | |
/* CR/NL with scroll-up if needed */ | |
tsm_screen_newline(vte->con); | |
break; | |
case 0x88: /* HTS */ | |
/* Set tab stop at current position */ | |
tsm_screen_set_tabstop(vte->con); | |
break; | |
case 0x8d: /* RI */ | |
/* Move up one row, perform scroll-down if needed */ | |
tsm_screen_move_up(vte->con, 1, true); | |
break; | |
case 0x8e: /* SS2 */ | |
/* Temporarily map G2 into GL for next char only */ | |
vte->glt = vte->g2; | |
break; | |
case 0x8f: /* SS3 */ | |
/* Temporarily map G3 into GL for next char only */ | |
vte->glt = vte->g3; | |
break; | |
case 0x9a: /* DECID */ | |
/* Send device attributes response like ANSI DA */ | |
send_primary_da(vte); | |
break; | |
case 0x9c: /* ST */ | |
/* End control string */ | |
/* nothing to do here */ | |
break; | |
default: | |
llog_warn(vte, "unhandled control char %u", ctrl); | |
} | |
} | |
static void do_clear(struct tsm_vte *vte) | |
{ | |
int i; | |
vte->csi_argc = 0; | |
for (i = 0; i < CSI_ARG_MAX; ++i) | |
vte->csi_argv[i] = -1; | |
vte->csi_flags = 0; | |
} | |
static void do_collect(struct tsm_vte *vte, uint32_t data) | |
{ | |
switch (data) { | |
case '!': | |
vte->csi_flags |= CSI_BANG; | |
break; | |
case '$': | |
vte->csi_flags |= CSI_CASH; | |
break; | |
case '?': | |
vte->csi_flags |= CSI_WHAT; | |
break; | |
case '>': | |
vte->csi_flags |= CSI_GT; | |
break; | |
case ' ': | |
vte->csi_flags |= CSI_SPACE; | |
break; | |
case '\'': | |
vte->csi_flags |= CSI_SQUOTE; | |
break; | |
case '"': | |
vte->csi_flags |= CSI_DQUOTE; | |
break; | |
case '*': | |
vte->csi_flags |= CSI_MULT; | |
break; | |
case '+': | |
vte->csi_flags |= CSI_PLUS; | |
break; | |
case '(': | |
vte->csi_flags |= CSI_POPEN; | |
break; | |
case ')': | |
vte->csi_flags |= CSI_PCLOSE; | |
break; | |
} | |
} | |
static void do_param(struct tsm_vte *vte, uint32_t data) | |
{ | |
int new; | |
if (data == ';') { | |
if (vte->csi_argc < CSI_ARG_MAX) | |
vte->csi_argc++; | |
return; | |
} | |
if (vte->csi_argc >= CSI_ARG_MAX) | |
return; | |
/* avoid integer overflows; max allowed value is 16384 anyway */ | |
if (vte->csi_argv[vte->csi_argc] > 0xffff) | |
return; | |
if (data >= '0' && data <= '9') { | |
new = vte->csi_argv[vte->csi_argc]; | |
if (new <= 0) | |
new = data - '0'; | |
else | |
new = new * 10 + data - '0'; | |
vte->csi_argv[vte->csi_argc] = new; | |
} | |
} | |
static bool set_charset(struct tsm_vte *vte, tsm_vte_charset *set) | |
{ | |
if (vte->csi_flags & CSI_POPEN) | |
vte->g0 = set; | |
else if (vte->csi_flags & CSI_PCLOSE) | |
vte->g1 = set; | |
else if (vte->csi_flags & CSI_MULT) | |
vte->g2 = set; | |
else if (vte->csi_flags & CSI_PLUS) | |
vte->g3 = set; | |
else | |
return false; | |
return true; | |
} | |
static void do_esc(struct tsm_vte *vte, uint32_t data) | |
{ | |
switch (data) { | |
case 'B': /* map ASCII into G0-G3 */ | |
if (set_charset(vte, &tsm_vte_unicode_lower)) | |
return; | |
break; | |
case '<': /* map DEC supplemental into G0-G3 */ | |
if (set_charset(vte, &tsm_vte_dec_supplemental_graphics)) | |
return; | |
break; | |
case '0': /* map DEC special into G0-G3 */ | |
if (set_charset(vte, &tsm_vte_dec_special_graphics)) | |
return; | |
break; | |
case 'A': /* map British into G0-G3 */ | |
/* TODO: create British charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case '4': /* map Dutch into G0-G3 */ | |
/* TODO: create Dutch charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'C': | |
case '5': /* map Finnish into G0-G3 */ | |
/* TODO: create Finnish charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'R': /* map French into G0-G3 */ | |
/* TODO: create French charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'Q': /* map French-Canadian into G0-G3 */ | |
/* TODO: create French-Canadian charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'K': /* map German into G0-G3 */ | |
/* TODO: create German charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'Y': /* map Italian into G0-G3 */ | |
/* TODO: create Italian charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'E': | |
case '6': /* map Norwegian/Danish into G0-G3 */ | |
/* TODO: create Norwegian/Danish charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'Z': /* map Spanish into G0-G3 */ | |
/* TODO: create Spanish charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'H': | |
case '7': /* map Swedish into G0-G3 */ | |
/* TODO: create Swedish charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case '=': /* map Swiss into G0-G3 */ | |
/* TODO: create Swiss charset from DEC */ | |
if (set_charset(vte, &tsm_vte_unicode_upper)) | |
return; | |
break; | |
case 'F': | |
if (vte->csi_flags & CSI_SPACE) { | |
/* S7C1T */ | |
/* Disable 8bit C1 mode */ | |
vte->flags &= ~FLAG_USE_C1; | |
return; | |
} | |
break; | |
case 'G': | |
if (vte->csi_flags & CSI_SPACE) { | |
/* S8C1T */ | |
/* Enable 8bit C1 mode */ | |
vte->flags |= FLAG_USE_C1; | |
return; | |
} | |
break; | |
} | |
/* everything below is only valid without CSI flags */ | |
if (vte->csi_flags) { | |
llog_debug(vte, "unhandled escape seq %u", data); | |
return; | |
} | |
switch (data) { | |
case 'D': /* IND */ | |
/* Move down one row, perform scroll-up if needed */ | |
tsm_screen_move_down(vte->con, 1, true); | |
break; | |
case 'E': /* NEL */ | |
/* CR/NL with scroll-up if needed */ | |
tsm_screen_newline(vte->con); | |
break; | |
case 'H': /* HTS */ | |
/* Set tab stop at current position */ | |
tsm_screen_set_tabstop(vte->con); | |
break; | |
case 'M': /* RI */ | |
/* Move up one row, perform scroll-down if needed */ | |
tsm_screen_move_up(vte->con, 1, true); | |
break; | |
case 'N': /* SS2 */ | |
/* Temporarily map G2 into GL for next char only */ | |
vte->glt = vte->g2; | |
break; | |
case 'O': /* SS3 */ | |
/* Temporarily map G3 into GL for next char only */ | |
vte->glt = vte->g3; | |
break; | |
case 'Z': /* DECID */ | |
/* Send device attributes response like ANSI DA */ | |
send_primary_da(vte); | |
break; | |
case '\\': /* ST */ | |
/* End control string */ | |
/* nothing to do here */ | |
break; | |
case '~': /* LS1R */ | |
/* Invoke G1 into GR */ | |
vte->gr = vte->g1; | |
break; | |
case 'n': /* LS2 */ | |
/* Invoke G2 into GL */ | |
vte->gl = vte->g2; | |
break; | |
case '}': /* LS2R */ | |
/* Invoke G2 into GR */ | |
vte->gr = vte->g2; | |
break; | |
case 'o': /* LS3 */ | |
/* Invoke G3 into GL */ | |
vte->gl = vte->g3; | |
break; | |
case '|': /* LS3R */ | |
/* Invoke G3 into GR */ | |
vte->gr = vte->g3; | |
break; | |
case '=': /* DECKPAM */ | |
/* Set application keypad mode */ | |
vte->flags |= FLAG_KEYPAD_APPLICATION_MODE; | |
break; | |
case '>': /* DECKPNM */ | |
/* Set numeric keypad mode */ | |
vte->flags &= ~FLAG_KEYPAD_APPLICATION_MODE; | |
break; | |
case 'c': /* RIS */ | |
/* hard reset */ | |
hard_reset(vte); | |
break; | |
case '7': /* DECSC */ | |
/* save console state */ | |
save_state(vte); | |
break; | |
case '8': /* DECRC */ | |
/* restore console state */ | |
restore_state(vte); | |
break; | |
default: | |
llog_debug(vte, "unhandled escape seq %u", data); | |
} | |
} | |
static void csi_attribute(struct tsm_vte *vte) | |
{ | |
static const uint8_t bval[6] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff }; | |
unsigned int i, code; | |
if (vte->csi_argc <= 1 && vte->csi_argv[0] == -1) { | |
vte->csi_argc = 1; | |
vte->csi_argv[0] = 0; | |
} | |
for (i = 0; i < vte->csi_argc; ++i) { | |
switch (vte->csi_argv[i]) { | |
case -1: | |
break; | |
case 0: | |
copy_fcolor(&vte->cattr, &vte->def_attr); | |
copy_bcolor(&vte->cattr, &vte->def_attr); | |
vte->cattr.bold = 0; | |
vte->cattr.underline = 0; | |
vte->cattr.inverse = 0; | |
break; | |
case 1: | |
vte->cattr.bold = 1; | |
break; | |
case 4: | |
vte->cattr.underline = 1; | |
break; | |
case 7: | |
vte->cattr.inverse = 1; | |
break; | |
case 22: | |
vte->cattr.bold = 0; | |
break; | |
case 24: | |
vte->cattr.underline = 0; | |
break; | |
case 27: | |
vte->cattr.inverse = 0; | |
break; | |
case 30: | |
vte->cattr.fccode = COLOR_BLACK; | |
break; | |
case 31: | |
vte->cattr.fccode = COLOR_RED; | |
break; | |
case 32: | |
vte->cattr.fccode = COLOR_GREEN; | |
break; | |
case 33: | |
vte->cattr.fccode = COLOR_YELLOW; | |
break; | |
case 34: | |
vte->cattr.fccode = COLOR_BLUE; | |
break; | |
case 35: | |
vte->cattr.fccode = COLOR_MAGENTA; | |
break; | |
case 36: | |
vte->cattr.fccode = COLOR_CYAN; | |
break; | |
case 37: | |
vte->cattr.fccode = COLOR_LIGHT_GREY; | |
break; | |
case 39: | |
copy_fcolor(&vte->cattr, &vte->def_attr); | |
break; | |
case 40: | |
vte->cattr.bccode = COLOR_BLACK; | |
break; | |
case 41: | |
vte->cattr.bccode = COLOR_RED; | |
break; | |
case 42: | |
vte->cattr.bccode = COLOR_GREEN; | |
break; | |
case 43: | |
vte->cattr.bccode = COLOR_YELLOW; | |
break; | |
case 44: | |
vte->cattr.bccode = COLOR_BLUE; | |
break; | |
case 45: | |
vte->cattr.bccode = COLOR_MAGENTA; | |
break; | |
case 46: | |
vte->cattr.bccode = COLOR_CYAN; | |
break; | |
case 47: | |
vte->cattr.bccode = COLOR_LIGHT_GREY; | |
break; | |
case 49: | |
copy_bcolor(&vte->cattr, &vte->def_attr); | |
break; | |
case 90: | |
vte->cattr.fccode = COLOR_DARK_GREY; | |
break; | |
case 91: | |
vte->cattr.fccode = COLOR_LIGHT_RED; | |
break; | |
case 92: | |
vte->cattr.fccode = COLOR_LIGHT_GREEN; | |
break; | |
case 93: | |
vte->cattr.fccode = COLOR_LIGHT_YELLOW; | |
break; | |
case 94: | |
vte->cattr.fccode = COLOR_LIGHT_BLUE; | |
break; | |
case 95: | |
vte->cattr.fccode = COLOR_LIGHT_MAGENTA; | |
break; | |
case 96: | |
vte->cattr.fccode = COLOR_LIGHT_CYAN; | |
break; | |
case 97: | |
vte->cattr.fccode = COLOR_WHITE; | |
break; | |
case 100: | |
vte->cattr.bccode = COLOR_DARK_GREY; | |
break; | |
case 101: | |
vte->cattr.bccode = COLOR_LIGHT_RED; | |
break; | |
case 102: | |
vte->cattr.bccode = COLOR_LIGHT_GREEN; | |
break; | |
case 103: | |
vte->cattr.bccode = COLOR_LIGHT_YELLOW; | |
break; | |
case 104: | |
vte->cattr.bccode = COLOR_LIGHT_BLUE; | |
break; | |
case 105: | |
vte->cattr.bccode = COLOR_LIGHT_MAGENTA; | |
break; | |
case 106: | |
vte->cattr.bccode = COLOR_LIGHT_CYAN; | |
break; | |
case 107: | |
vte->cattr.bccode = COLOR_WHITE; | |
break; | |
case 38: | |
/* fallthrough */ | |
case 48: | |
if (i + 2 >= vte->csi_argc || | |
vte->csi_argv[i + 1] != 5 || | |
vte->csi_argv[i + 2] < 0) { | |
llog_debug(vte, "invalid 256color SGR"); | |
break; | |
} | |
code = vte->csi_argv[i + 2]; | |
if (vte->csi_argv[i] == 38) { | |
if (code < 16) { | |
vte->cattr.fccode = code; | |
} else if (code < 232) { | |
vte->cattr.fccode = -1; | |
code -= 16; | |
vte->cattr.fb = bval[code % 6]; | |
code /= 6; | |
vte->cattr.fg = bval[code % 6]; | |
code /= 6; | |
vte->cattr.fr = bval[code % 6]; | |
} else { | |
vte->cattr.fccode = -1; | |
code = (code - 232) * 10 + 8; | |
vte->cattr.fr = code; | |
vte->cattr.fg = code; | |
vte->cattr.fb = code; | |
} | |
} else { | |
if (code < 16) { | |
vte->cattr.bccode = code; | |
} else if (code < 232) { | |
vte->cattr.bccode = -1; | |
code -= 16; | |
vte->cattr.bb = bval[code % 6]; | |
code /= 6; | |
vte->cattr.bg = bval[code % 6]; | |
code /= 6; | |
vte->cattr.br = bval[code % 6]; | |
} else { | |
vte->cattr.bccode = -1; | |
code = (code - 232) * 10 + 8; | |
vte->cattr.br = code; | |
vte->cattr.bg = code; | |
vte->cattr.bb = code; | |
} | |
} | |
i += 2; | |
break; | |
default: | |
llog_debug(vte, "unhandled SGR attr %i", | |
vte->csi_argv[i]); | |
} | |
} | |
to_rgb(vte, &vte->cattr); | |
if (vte->flags & FLAG_BACKGROUND_COLOR_ERASE_MODE) | |
tsm_screen_set_def_attr(vte->con, &vte->cattr); | |
} | |
static void csi_soft_reset(struct tsm_vte *vte) | |
{ | |
tsm_vte_reset(vte); | |
} | |
static void csi_compat_mode(struct tsm_vte *vte) | |
{ | |
/* always perform soft reset */ | |
csi_soft_reset(vte); | |
if (vte->csi_argv[0] == 61) { | |
/* Switching to VT100 compatibility mode. We do | |
* not support this mode, so ignore it. In fact, | |
* we are almost compatible to it, anyway, so | |
* there is no need to explicitely select it. | |
* However, we enable 7bit mode to avoid | |
* character-table problems */ | |
vte->flags |= FLAG_7BIT_MODE; | |
vte->gl = &tsm_vte_unicode_lower; | |
vte->gr = &tsm_vte_dec_supplemental_graphics; | |
} else if (vte->csi_argv[0] == 62 || | |
vte->csi_argv[0] == 63 || | |
vte->csi_argv[0] == 64) { | |
/* Switching to VT2/3/4 compatibility mode. We | |
* are always compatible with this so ignore it. | |
* We always send 7bit controls so we also do | |
* not care for the parameter value here that | |
* select the control-mode. | |
* VT220 defines argument 2 as 7bit mode but | |
* VT3xx up to VT5xx use it as 8bit mode. We | |
* choose to conform with the latter here. | |
* We also enable 8bit mode when VT220 | |
* compatibility is requested explicitely. */ | |
if (vte->csi_argv[1] == 1 || | |
vte->csi_argv[1] == 2) | |
vte->flags |= FLAG_USE_C1; | |
vte->flags |= FLAG_8BIT_MODE; | |
vte->gl = &tsm_vte_unicode_lower; | |
vte->gr = &tsm_vte_dec_supplemental_graphics; | |
} else { | |
llog_debug(vte, "unhandled DECSCL 'p' CSI %i, switching to utf-8 mode again", | |
vte->csi_argv[0]); | |
} | |
} | |
static inline void set_reset_flag(struct tsm_vte *vte, bool set, | |
unsigned int flag) | |
{ | |
if (set) | |
vte->flags |= flag; | |
else | |
vte->flags &= ~flag; | |
} | |
static void csi_mode(struct tsm_vte *vte, bool set) | |
{ | |
unsigned int i; | |
for (i = 0; i < vte->csi_argc; ++i) { | |
if (!(vte->csi_flags & CSI_WHAT)) { | |
switch (vte->csi_argv[i]) { | |
case -1: | |
continue; | |
case 2: /* KAM */ | |
set_reset_flag(vte, set, | |
FLAG_KEYBOARD_ACTION_MODE); | |
continue; | |
case 4: /* IRM */ | |
set_reset_flag(vte, set, | |
FLAG_INSERT_REPLACE_MODE); | |
if (set) | |
tsm_screen_set_flags(vte->con, | |
TSM_SCREEN_INSERT_MODE); | |
else | |
tsm_screen_reset_flags(vte->con, | |
TSM_SCREEN_INSERT_MODE); | |
continue; | |
case 12: /* SRM */ | |
set_reset_flag(vte, set, | |
FLAG_SEND_RECEIVE_MODE); | |
continue; | |
case 20: /* LNM */ | |
set_reset_flag(vte, set, | |
FLAG_LINE_FEED_NEW_LINE_MODE); | |
continue; | |
default: | |
llog_debug(vte, "unknown non-DEC (Re)Set-Mode %d", | |
vte->csi_argv[i]); | |
continue; | |
} | |
} | |
switch (vte->csi_argv[i]) { | |
case -1: | |
continue; | |
case 1: /* DECCKM */ | |
set_reset_flag(vte, set, FLAG_CURSOR_KEY_MODE); | |
continue; | |
case 2: /* DECANM */ | |
/* Select VT52 mode */ | |
/* We do not support VT52 mode. Is there any reason why | |
* we should support it? We ignore it here and do not | |
* mark it as to-do item unless someone has strong | |
* arguments to support it. */ | |
continue; | |
case 3: /* DECCOLM */ | |
/* If set, select 132 column mode, otherwise use 80 | |
* column mode. If neither is selected explicitely, we | |
* use dynamic mode, that is, we send SIGWCH when the | |
* size changes and we allow arbitrary buffer | |
* dimensions. On soft-reset, we automatically fall back | |
* to the default, that is, dynamic mode. | |
* Dynamic-mode can be forced to a static mode in the | |
* config. That is, everytime dynamic-mode becomes | |
* active, the terminal will be set to the dimensions | |
* that were selected in the config. This allows setting | |
* a fixed size for the terminal regardless of the | |
* display size. | |
* TODO: Implement this */ | |
continue; | |
case 4: /* DECSCLM */ | |
/* Select smooth scrolling. We do not support the | |
* classic smooth scrolling because we have a scrollback | |
* buffer. There is no need to implement smooth | |
* scrolling so ignore this here. */ | |
continue; | |
case 5: /* DECSCNM */ | |
set_reset_flag(vte, set, FLAG_INVERSE_SCREEN_MODE); | |
if (set) | |
tsm_screen_set_flags(vte->con, | |
TSM_SCREEN_INVERSE); | |
else | |
tsm_screen_reset_flags(vte->con, | |
TSM_SCREEN_INVERSE); | |
continue; | |
case 6: /* DECOM */ | |
set_reset_flag(vte, set, FLAG_ORIGIN_MODE); | |
if (set) | |
tsm_screen_set_flags(vte->con, | |
TSM_SCREEN_REL_ORIGIN); | |
else | |
tsm_screen_reset_flags(vte->con, | |
TSM_SCREEN_REL_ORIGIN); | |
continue; | |
case 7: /* DECAWN */ | |
set_reset_flag(vte, set, FLAG_AUTO_WRAP_MODE); | |
if (set) | |
tsm_screen_set_flags(vte->con, | |
TSM_SCREEN_AUTO_WRAP); | |
else | |
tsm_screen_reset_flags(vte->con, | |
TSM_SCREEN_AUTO_WRAP); | |
continue; | |
case 8: /* DECARM */ | |
set_reset_flag(vte, set, FLAG_AUTO_REPEAT_MODE); | |
continue; | |
case 18: /* DECPFF */ | |
/* If set, a form feed (FF) is sent to the printer after | |
* every screen that is printed. We don't have printers | |
* these days directly attached to terminals so we | |
* ignore this here. */ | |
continue; | |
case 19: /* DECPEX */ | |
/* If set, the full screen is printed instead of | |
* scrolling region only. We have no printer so ignore | |
* this mode. */ | |
continue; | |
case 25: /* DECTCEM */ | |
set_reset_flag(vte, set, FLAG_TEXT_CURSOR_MODE); | |
if (set) | |
tsm_screen_reset_flags(vte->con, | |
TSM_SCREEN_HIDE_CURSOR); | |
else | |
tsm_screen_set_flags(vte->con, | |
TSM_SCREEN_HIDE_CURSOR); | |
continue; | |
case 42: /* DECNRCM */ | |
set_reset_flag(vte, set, FLAG_NATIONAL_CHARSET_MODE); | |
continue; | |
default: | |
llog_debug(vte, "unknown DEC %set-Mode %d", | |
set?"S":"Res", vte->csi_argv[i]); | |
continue; | |
} | |
} | |
} | |
static void csi_dev_attr(struct tsm_vte *vte) | |
{ | |
if (vte->csi_argc <= 1 && vte->csi_argv[0] <= 0) { | |
if (vte->csi_flags == 0) { | |
send_primary_da(vte); | |
return; | |
} else if (vte->csi_flags & CSI_GT) { | |
vte_write(vte, "\e[>1;1;0c", 9); | |
return; | |
} | |
} | |
llog_debug(vte, "unhandled DA: %x %d %d %d...", vte->csi_flags, | |
vte->csi_argv[0], vte->csi_argv[1], vte->csi_argv[2]); | |
} | |
static void csi_dsr(struct tsm_vte *vte) | |
{ | |
char buf[64]; | |
unsigned int x, y, len; | |
if (vte->csi_argv[0] == 5) { | |
vte_write(vte, "\e[0n", 4); | |
} else if (vte->csi_argv[0] == 6) { | |
x = tsm_screen_get_cursor_x(vte->con); | |
y = tsm_screen_get_cursor_y(vte->con); | |
len = snprintf(buf, sizeof(buf), "\e[%u;%uR", x, y); | |
if (len >= sizeof(buf)) | |
vte_write(vte, "\e[0;0R", 6); | |
else | |
vte_write(vte, buf, len); | |
} | |
} | |
static void do_csi(struct tsm_vte *vte, uint32_t data) | |
{ | |
int num, x, y, upper, lower; | |
bool protect; | |
if (vte->csi_argc < CSI_ARG_MAX) | |
vte->csi_argc++; | |
switch (data) { | |
case 'A': /* CUU */ | |
/* move cursor up */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_move_up(vte->con, num, false); | |
break; | |
case 'B': /* CUD */ | |
/* move cursor down */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_move_down(vte->con, num, false); | |
break; | |
case 'C': /* CUF */ | |
/* move cursor forward */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_move_right(vte->con, num); | |
break; | |
case 'D': /* CUB */ | |
/* move cursor backward */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_move_left(vte->con, num); | |
break; | |
case 'd': /* VPA */ | |
/* Vertical Line Position Absolute */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
x = tsm_screen_get_cursor_x(vte->con); | |
tsm_screen_move_to(vte->con, x, num - 1); | |
break; | |
case 'e': /* VPR */ | |
/* Vertical Line Position Relative */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
x = tsm_screen_get_cursor_x(vte->con); | |
y = tsm_screen_get_cursor_y(vte->con); | |
tsm_screen_move_to(vte->con, x, y + num); | |
break; | |
case 'H': /* CUP */ | |
case 'f': /* HVP */ | |
/* position cursor */ | |
x = vte->csi_argv[0]; | |
if (x <= 0) | |
x = 1; | |
y = vte->csi_argv[1]; | |
if (y <= 0) | |
y = 1; | |
tsm_screen_move_to(vte->con, y - 1, x - 1); | |
break; | |
case 'G': /* CHA */ | |
/* Cursor Character Absolute */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
y = tsm_screen_get_cursor_y(vte->con); | |
tsm_screen_move_to(vte->con, num - 1, y); | |
break; | |
case 'J': | |
if (vte->csi_flags & CSI_WHAT) | |
protect = true; | |
else | |
protect = false; | |
if (vte->csi_argv[0] <= 0) | |
tsm_screen_erase_cursor_to_screen(vte->con, | |
protect); | |
else if (vte->csi_argv[0] == 1) | |
tsm_screen_erase_screen_to_cursor(vte->con, | |
protect); | |
else if (vte->csi_argv[0] == 2) | |
tsm_screen_erase_screen(vte->con, protect); | |
else | |
llog_debug(vte, "unknown parameter to CSI-J: %d", | |
vte->csi_argv[0]); | |
break; | |
case 'K': | |
if (vte->csi_flags & CSI_WHAT) | |
protect = true; | |
else | |
protect = false; | |
if (vte->csi_argv[0] <= 0) | |
tsm_screen_erase_cursor_to_end(vte->con, protect); | |
else if (vte->csi_argv[0] == 1) | |
tsm_screen_erase_home_to_cursor(vte->con, protect); | |
else if (vte->csi_argv[0] == 2) | |
tsm_screen_erase_current_line(vte->con, protect); | |
else | |
llog_debug(vte, "unknown parameter to CSI-K: %d", | |
vte->csi_argv[0]); | |
break; | |
case 'X': /* ECH */ | |
/* erase characters */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_erase_chars(vte->con, num); | |
break; | |
case 'm': | |
csi_attribute(vte); | |
break; | |
case 'p': | |
if (vte->csi_flags & CSI_GT) { | |
/* xterm: select X11 visual cursor mode */ | |
csi_soft_reset(vte); | |
} else if (vte->csi_flags & CSI_BANG) { | |
/* DECSTR: Soft Reset */ | |
csi_soft_reset(vte); | |
} else if (vte->csi_flags & CSI_CASH) { | |
/* DECRQM: Request DEC Private Mode */ | |
/* If CSI_WHAT is set, then enable, | |
* otherwise disable */ | |
csi_soft_reset(vte); | |
} else { | |
/* DECSCL: Compatibility Level */ | |
/* Sometimes CSI_DQUOTE is set here, too */ | |
csi_compat_mode(vte); | |
} | |
break; | |
case 'h': /* SM: Set Mode */ | |
csi_mode(vte, true); | |
break; | |
case 'l': /* RM: Reset Mode */ | |
csi_mode(vte, false); | |
break; | |
case 'r': /* DECSTBM */ | |
/* set margin size */ | |
upper = vte->csi_argv[0]; | |
if (upper < 0) | |
upper = 0; | |
lower = vte->csi_argv[1]; | |
if (lower < 0) | |
lower = 0; | |
tsm_screen_set_margins(vte->con, upper, lower); | |
break; | |
case 'c': /* DA */ | |
/* device attributes */ | |
csi_dev_attr(vte); | |
break; | |
case 'L': /* IL */ | |
/* insert lines */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_insert_lines(vte->con, num); | |
break; | |
case 'M': /* DL */ | |
/* delete lines */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_delete_lines(vte->con, num); | |
break; | |
case 'g': /* TBC */ | |
/* tabulation clear */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
tsm_screen_reset_tabstop(vte->con); | |
else if (num == 3) | |
tsm_screen_reset_all_tabstops(vte->con); | |
else | |
llog_debug(vte, "invalid parameter %d to TBC CSI", num); | |
break; | |
case '@': /* ICH */ | |
/* insert characters */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_insert_chars(vte->con, num); | |
break; | |
case 'P': /* DCH */ | |
/* delete characters */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_delete_chars(vte->con, num); | |
break; | |
case 'Z': /* CBT */ | |
/* cursor horizontal backwards tab */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_tab_left(vte->con, num); | |
break; | |
case 'I': /* CHT */ | |
/* cursor horizontal forward tab */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_tab_right(vte->con, num); | |
break; | |
case 'n': /* DSR */ | |
/* device status reports */ | |
csi_dsr(vte); | |
break; | |
case 'S': /* SU */ | |
/* scroll up */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_scroll_up(vte->con, num); | |
break; | |
case 'T': /* SD */ | |
/* scroll down */ | |
num = vte->csi_argv[0]; | |
if (num <= 0) | |
num = 1; | |
tsm_screen_scroll_down(vte->con, num); | |
break; | |
default: | |
llog_debug(vte, "unhandled CSI sequence %c", data); | |
} | |
} | |
/* map a character according to current GL and GR maps */ | |
static uint32_t vte_map(struct tsm_vte *vte, uint32_t val) | |
{ | |
/* 32, 127, 160 and 255 map to identity like all values >255 */ | |
switch (val) { | |
case 33 ... 126: | |
if (vte->glt) { | |
val = (*vte->glt)[val - 32]; | |
vte->glt = NULL; | |
} else { | |
val = (*vte->gl)[val - 32]; | |
} | |
break; | |
case 161 ... 254: | |
if (vte->grt) { | |
val = (*vte->grt)[val - 160]; | |
vte->grt = NULL; | |
} else { | |
val = (*vte->gr)[val - 160]; | |
} | |
break; | |
} | |
return val; | |
} | |
/* perform parser action */ | |
static void do_action(struct tsm_vte *vte, uint32_t data, int action) | |
{ | |
tsm_symbol_t sym; | |
switch (action) { | |
case ACTION_NONE: | |
/* do nothing */ | |
return; | |
case ACTION_IGNORE: | |
/* ignore character */ | |
break; | |
case ACTION_PRINT: | |
sym = tsm_symbol_make(vte_map(vte, data)); | |
write_console(vte, sym); | |
break; | |
case ACTION_EXECUTE: | |
do_execute(vte, data); | |
break; | |
case ACTION_CLEAR: | |
do_clear(vte); | |
break; | |
case ACTION_COLLECT: | |
do_collect(vte, data); | |
break; | |
case ACTION_PARAM: | |
do_param(vte, data); | |
break; | |
case ACTION_ESC_DISPATCH: | |
do_esc(vte, data); | |
break; | |
case ACTION_CSI_DISPATCH: | |
do_csi(vte, data); | |
break; | |
case ACTION_DCS_START: | |
break; | |
case ACTION_DCS_COLLECT: | |
break; | |
case ACTION_DCS_END: | |
break; | |
case ACTION_OSC_START: | |
break; | |
case ACTION_OSC_COLLECT: | |
break; | |
case ACTION_OSC_END: | |
break; | |
default: | |
llog_warn(vte, "invalid action %d", action); | |
} | |
} | |
/* entry actions to be performed when entering the selected state */ | |
static const int entry_action[] = { | |
[STATE_CSI_ENTRY] = ACTION_CLEAR, | |
[STATE_DCS_ENTRY] = ACTION_CLEAR, | |
[STATE_DCS_PASS] = ACTION_DCS_START, | |
[STATE_ESC] = ACTION_CLEAR, | |
[STATE_OSC_STRING] = ACTION_OSC_START, | |
[STATE_NUM] = ACTION_NONE, | |
}; | |
/* exit actions to be performed when leaving the selected state */ | |
static const int exit_action[] = { | |
[STATE_DCS_PASS] = ACTION_DCS_END, | |
[STATE_OSC_STRING] = ACTION_OSC_END, | |
[STATE_NUM] = ACTION_NONE, | |
}; | |
/* perform state transision and dispatch related actions */ | |
static void do_trans(struct tsm_vte *vte, uint32_t data, int state, int act) | |
{ | |
if (state != STATE_NONE) { | |
/* A state transition occurs. Perform exit-action, | |
* transition-action and entry-action. Even when performing a | |
* transition to the same state as the current state we do this. | |
* Use STATE_NONE if this is not the desired behavior. | |
*/ | |
do_action(vte, data, exit_action[vte->state]); | |
do_action(vte, data, act); | |
do_action(vte, data, entry_action[state]); | |
vte->state = state; | |
} else { | |
do_action(vte, data, act); | |
} | |
} | |
/* | |
* Escape sequence parser | |
* This parses the new input character \data. It performs state transition and | |
* calls the right callbacks for each action. | |
*/ | |
static void parse_data(struct tsm_vte *vte, uint32_t raw) | |
{ | |
/* events that may occur in any state */ | |
switch (raw) { | |
case 0x18: | |
case 0x1a: | |
case 0x80 ... 0x8f: | |
case 0x91 ... 0x97: | |
case 0x99: | |
case 0x9a: | |
case 0x9c: | |
do_trans(vte, raw, STATE_GROUND, ACTION_EXECUTE); | |
return; | |
case 0x1b: | |
do_trans(vte, raw, STATE_ESC, ACTION_NONE); | |
return; | |
case 0x98: | |
case 0x9e: | |
case 0x9f: | |
do_trans(vte, raw, STATE_ST_IGNORE, ACTION_NONE); | |
return; | |
case 0x90: | |
do_trans(vte, raw, STATE_DCS_ENTRY, ACTION_NONE); | |
return; | |
case 0x9d: | |
do_trans(vte, raw, STATE_OSC_STRING, ACTION_NONE); | |
return; | |
case 0x9b: | |
do_trans(vte, raw, STATE_CSI_ENTRY, ACTION_NONE); | |
return; | |
} | |
/* events that depend on the current state */ | |
switch (vte->state) { | |
case STATE_GROUND: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
case 0x80 ... 0x8f: | |
case 0x91 ... 0x9a: | |
case 0x9c: | |
do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); | |
return; | |
case 0x20 ... 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_PRINT); | |
return; | |
} | |
do_trans(vte, raw, STATE_NONE, ACTION_PRINT); | |
return; | |
case STATE_ESC: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); | |
return; | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x20 ... 0x2f: | |
do_trans(vte, raw, STATE_ESC_INT, ACTION_COLLECT); | |
return; | |
case 0x30 ... 0x4f: | |
case 0x51 ... 0x57: | |
case 0x59: | |
case 0x5a: | |
case 0x5c: | |
case 0x60 ... 0x7e: | |
do_trans(vte, raw, STATE_GROUND, ACTION_ESC_DISPATCH); | |
return; | |
case 0x5b: | |
do_trans(vte, raw, STATE_CSI_ENTRY, ACTION_NONE); | |
return; | |
case 0x5d: | |
do_trans(vte, raw, STATE_OSC_STRING, ACTION_NONE); | |
return; | |
case 0x50: | |
do_trans(vte, raw, STATE_DCS_ENTRY, ACTION_NONE); | |
return; | |
case 0x58: | |
case 0x5e: | |
case 0x5f: | |
do_trans(vte, raw, STATE_ST_IGNORE, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_ESC_INT, ACTION_COLLECT); | |
return; | |
case STATE_ESC_INT: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); | |
return; | |
case 0x20 ... 0x2f: | |
do_trans(vte, raw, STATE_NONE, ACTION_COLLECT); | |
return; | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x30 ... 0x7e: | |
do_trans(vte, raw, STATE_GROUND, ACTION_ESC_DISPATCH); | |
return; | |
} | |
do_trans(vte, raw, STATE_NONE, ACTION_COLLECT); | |
return; | |
case STATE_CSI_ENTRY: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); | |
return; | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x20 ... 0x2f: | |
do_trans(vte, raw, STATE_CSI_INT, ACTION_COLLECT); | |
return; | |
case 0x3a: | |
do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); | |
return; | |
case 0x30 ... 0x39: | |
case 0x3b: | |
do_trans(vte, raw, STATE_CSI_PARAM, ACTION_PARAM); | |
return; | |
case 0x3c ... 0x3f: | |
do_trans(vte, raw, STATE_CSI_PARAM, ACTION_COLLECT); | |
return; | |
case 0x40 ... 0x7e: | |
do_trans(vte, raw, STATE_GROUND, ACTION_CSI_DISPATCH); | |
return; | |
} | |
do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); | |
return; | |
case STATE_CSI_PARAM: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); | |
return; | |
case 0x30 ... 0x39: | |
case 0x3b: | |
do_trans(vte, raw, STATE_NONE, ACTION_PARAM); | |
return; | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x3a: | |
case 0x3c ... 0x3f: | |
do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); | |
return; | |
case 0x20 ... 0x2f: | |
do_trans(vte, raw, STATE_CSI_INT, ACTION_COLLECT); | |
return; | |
case 0x40 ... 0x7e: | |
do_trans(vte, raw, STATE_GROUND, ACTION_CSI_DISPATCH); | |
return; | |
} | |
do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); | |
return; | |
case STATE_CSI_INT: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); | |
return; | |
case 0x20 ... 0x2f: | |
do_trans(vte, raw, STATE_NONE, ACTION_COLLECT); | |
return; | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x30 ... 0x3f: | |
do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); | |
return; | |
case 0x40 ... 0x7e: | |
do_trans(vte, raw, STATE_GROUND, ACTION_CSI_DISPATCH); | |
return; | |
} | |
do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); | |
return; | |
case STATE_CSI_IGNORE: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); | |
return; | |
case 0x20 ... 0x3f: | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x40 ... 0x7e: | |
do_trans(vte, raw, STATE_GROUND, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case STATE_DCS_ENTRY: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x3a: | |
do_trans(vte, raw, STATE_DCS_IGNORE, ACTION_NONE); | |
return; | |
case 0x20 ... 0x2f: | |
do_trans(vte, raw, STATE_DCS_INT, ACTION_COLLECT); | |
return; | |
case 0x30 ... 0x39: | |
case 0x3b: | |
do_trans(vte, raw, STATE_DCS_PARAM, ACTION_PARAM); | |
return; | |
case 0x3c ... 0x3f: | |
do_trans(vte, raw, STATE_DCS_PARAM, ACTION_COLLECT); | |
return; | |
case 0x40 ... 0x7e: | |
do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); | |
return; | |
case STATE_DCS_PARAM: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x30 ... 0x39: | |
case 0x3b: | |
do_trans(vte, raw, STATE_NONE, ACTION_PARAM); | |
return; | |
case 0x3a: | |
case 0x3c ... 0x3f: | |
do_trans(vte, raw, STATE_DCS_IGNORE, ACTION_NONE); | |
return; | |
case 0x20 ... 0x2f: | |
do_trans(vte, raw, STATE_DCS_INT, ACTION_COLLECT); | |
return; | |
case 0x40 ... 0x7e: | |
do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); | |
return; | |
case STATE_DCS_INT: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x20 ... 0x2f: | |
do_trans(vte, raw, STATE_NONE, ACTION_COLLECT); | |
return; | |
case 0x30 ... 0x3f: | |
do_trans(vte, raw, STATE_DCS_IGNORE, ACTION_NONE); | |
return; | |
case 0x40 ... 0x7e: | |
do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); | |
return; | |
case STATE_DCS_PASS: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
case 0x20 ... 0x7e: | |
do_trans(vte, raw, STATE_NONE, ACTION_DCS_COLLECT); | |
return; | |
case 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x9c: | |
do_trans(vte, raw, STATE_GROUND, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_NONE, ACTION_DCS_COLLECT); | |
return; | |
case STATE_DCS_IGNORE: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
case 0x20 ... 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x9c: | |
do_trans(vte, raw, STATE_GROUND, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case STATE_OSC_STRING: | |
switch (raw) { | |
case 0x00 ... 0x06: | |
case 0x08 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x20 ... 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_OSC_COLLECT); | |
return; | |
case 0x07: | |
case 0x9c: | |
do_trans(vte, raw, STATE_GROUND, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_NONE, ACTION_OSC_COLLECT); | |
return; | |
case STATE_ST_IGNORE: | |
switch (raw) { | |
case 0x00 ... 0x17: | |
case 0x19: | |
case 0x1c ... 0x1f: | |
case 0x20 ... 0x7f: | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
case 0x9c: | |
do_trans(vte, raw, STATE_GROUND, ACTION_NONE); | |
return; | |
} | |
do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); | |
return; | |
} | |
llog_warn(vte, "unhandled input %u in state %d", raw, vte->state); | |
} | |
void tsm_vte_input(struct tsm_vte *vte, const char *u8, size_t len) | |
{ | |
int state; | |
uint32_t ucs4; | |
size_t i; | |
if (!vte || !vte->con) | |
return; | |
++vte->parse_cnt; | |
for (i = 0; i < len; ++i) { | |
if (vte->flags & FLAG_7BIT_MODE) { | |
if (u8[i] & 0x80) | |
llog_debug(vte, "receiving 8bit character U+%d from pty while in 7bit mode", | |
(int)u8[i]); | |
parse_data(vte, u8[i] & 0x7f); | |
} else if (vte->flags & FLAG_8BIT_MODE) { | |
parse_data(vte, u8[i]); | |
} else { | |
state = tsm_utf8_mach_feed(vte->mach, u8[i]); | |
if (state == TSM_UTF8_ACCEPT || | |
state == TSM_UTF8_REJECT) { | |
ucs4 = tsm_utf8_mach_get(vte->mach); | |
parse_data(vte, ucs4); | |
} | |
} | |
} | |
--vte->parse_cnt; | |
} | |
bool tsm_vte_handle_keyboard(struct tsm_vte *vte, uint32_t keysym, | |
unsigned int mods, uint32_t unicode) | |
{ | |
char val, u8[4]; | |
size_t len; | |
/* MOD1 (mostly labeled 'Alt') prepends an escape character to every | |
* input that is sent by a key. | |
* TODO: Transform this huge handler into a lookup table to save a lot | |
* of code and make such modifiers easier to implement. | |
* Also check whether altSendsEscape should be the default (xterm | |
* disables this by default, why?) and whether we should implement the | |
* fallback shifting that xterm does. */ | |
if (mods & TSM_MOD1_MASK) | |
vte->flags |= FLAG_PREPEND_ESCAPE; | |
if (mods & TSM_CONTROL_MASK) { | |
switch (keysym) { | |
case XKB_KEY_2: | |
case XKB_KEY_space: | |
vte_write(vte, "\x00", 1); | |
return true; | |
case XKB_KEY_a: | |
case XKB_KEY_A: | |
vte_write(vte, "\x01", 1); | |
return true; | |
case XKB_KEY_b: | |
case XKB_KEY_B: | |
vte_write(vte, "\x02", 1); | |
return true; | |
case XKB_KEY_c: | |
case XKB_KEY_C: | |
vte_write(vte, "\x03", 1); | |
return true; | |
case XKB_KEY_d: | |
case XKB_KEY_D: | |
vte_write(vte, "\x04", 1); | |
return true; | |
case XKB_KEY_e: | |
case XKB_KEY_E: | |
vte_write(vte, "\x05", 1); | |
return true; | |
case XKB_KEY_f: | |
case XKB_KEY_F: | |
vte_write(vte, "\x06", 1); | |
return true; | |
case XKB_KEY_g: | |
case XKB_KEY_G: | |
vte_write(vte, "\x07", 1); | |
return true; | |
case XKB_KEY_h: | |
case XKB_KEY_H: | |
vte_write(vte, "\x08", 1); | |
return true; | |
case XKB_KEY_i: | |
case XKB_KEY_I: | |
vte_write(vte, "\x09", 1); | |
return true; | |
case XKB_KEY_j: | |
case XKB_KEY_J: | |
vte_write(vte, "\x0a", 1); | |
return true; | |
case XKB_KEY_k: | |
case XKB_KEY_K: | |
vte_write(vte, "\x0b", 1); | |
return true; | |
case XKB_KEY_l: | |
case XKB_KEY_L: | |
vte_write(vte, "\x0c", 1); | |
return true; | |
case XKB_KEY_m: | |
case XKB_KEY_M: | |
vte_write(vte, "\x0d", 1); | |
return true; | |
case XKB_KEY_n: | |
case XKB_KEY_N: | |
vte_write(vte, "\x0e", 1); | |
return true; | |
case XKB_KEY_o: | |
case XKB_KEY_O: | |
vte_write(vte, "\x0f", 1); | |
return true; | |
case XKB_KEY_p: | |
case XKB_KEY_P: | |
vte_write(vte, "\x10", 1); | |
return true; | |
case XKB_KEY_q: | |
case XKB_KEY_Q: | |
vte_write(vte, "\x11", 1); | |
return true; | |
case XKB_KEY_r: | |
case XKB_KEY_R: | |
vte_write(vte, "\x12", 1); | |
return true; | |
case XKB_KEY_s: | |
case XKB_KEY_S: | |
vte_write(vte, "\x13", 1); | |
return true; | |
case XKB_KEY_t: | |
case XKB_KEY_T: | |
vte_write(vte, "\x14", 1); | |
return true; | |
case XKB_KEY_u: | |
case XKB_KEY_U: | |
vte_write(vte, "\x15", 1); | |
return true; | |
case XKB_KEY_v: | |
case XKB_KEY_V: | |
vte_write(vte, "\x16", 1); | |
return true; | |
case XKB_KEY_w: | |
case XKB_KEY_W: | |
vte_write(vte, "\x17", 1); | |
return true; | |
case XKB_KEY_x: | |
case XKB_KEY_X: | |
vte_write(vte, "\x18", 1); | |
return true; | |
case XKB_KEY_y: | |
case XKB_KEY_Y: | |
vte_write(vte, "\x19", 1); | |
return true; | |
case XKB_KEY_z: | |
case XKB_KEY_Z: | |
vte_write(vte, "\x1a", 1); | |
return true; | |
case XKB_KEY_3: | |
case XKB_KEY_bracketleft: | |
case XKB_KEY_braceleft: | |
vte_write(vte, "\x1b", 1); | |
return true; | |
case XKB_KEY_4: | |
case XKB_KEY_backslash: | |
case XKB_KEY_bar: | |
vte_write(vte, "\x1c", 1); | |
return true; | |
case XKB_KEY_5: | |
case XKB_KEY_bracketright: | |
case XKB_KEY_braceright: | |
vte_write(vte, "\x1d", 1); | |
return true; | |
case XKB_KEY_6: | |
case XKB_KEY_grave: | |
case XKB_KEY_asciitilde: | |
vte_write(vte, "\x1e", 1); | |
return true; | |
case XKB_KEY_7: | |
case XKB_KEY_slash: | |
case XKB_KEY_question: | |
vte_write(vte, "\x1f", 1); | |
return true; | |
case XKB_KEY_8: | |
vte_write(vte, "\x7f", 1); | |
return true; | |
} | |
} | |
switch (keysym) { | |
case XKB_KEY_BackSpace: | |
vte_write(vte, "\x08", 1); | |
return true; | |
case XKB_KEY_Tab: | |
case XKB_KEY_KP_Tab: | |
vte_write(vte, "\x09", 1); | |
return true; | |
case XKB_KEY_Linefeed: | |
vte_write(vte, "\x0a", 1); | |
return true; | |
case XKB_KEY_Clear: | |
vte_write(vte, "\x0b", 1); | |
return true; | |
case XKB_KEY_Pause: | |
vte_write(vte, "\x13", 1); | |
return true; | |
case XKB_KEY_Scroll_Lock: | |
/* TODO: do we need scroll lock impl.? */ | |
vte_write(vte, "\x14", 1); | |
return true; | |
case XKB_KEY_Sys_Req: | |
vte_write(vte, "\x15", 1); | |
return true; | |
case XKB_KEY_Escape: | |
vte_write(vte, "\x1b", 1); | |
return true; | |
case XKB_KEY_KP_Enter: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) { | |
vte_write(vte, "\eOM", 3); | |
return true; | |
} | |
/* fallthrough */ | |
case XKB_KEY_Return: | |
if (vte->flags & FLAG_LINE_FEED_NEW_LINE_MODE) | |
vte_write(vte, "\x0d\x0a", 2); | |
else | |
vte_write(vte, "\x0d", 1); | |
return true; | |
case XKB_KEY_Find: | |
vte_write(vte, "\e[1~", 4); | |
return true; | |
case XKB_KEY_Insert: | |
vte_write(vte, "\e[2~", 4); | |
return true; | |
case XKB_KEY_Delete: | |
vte_write(vte, "\e[3~", 4); | |
return true; | |
case XKB_KEY_Select: | |
vte_write(vte, "\e[4~", 4); | |
return true; | |
case XKB_KEY_Page_Up: | |
vte_write(vte, "\e[5~", 4); | |
return true; | |
case XKB_KEY_Page_Down: | |
vte_write(vte, "\e[6~", 4); | |
return true; | |
case XKB_KEY_Up: | |
if (vte->flags & FLAG_CURSOR_KEY_MODE) | |
vte_write(vte, "\eOA", 3); | |
else | |
vte_write(vte, "\e[A", 3); | |
return true; | |
case XKB_KEY_Down: | |
if (vte->flags & FLAG_CURSOR_KEY_MODE) | |
vte_write(vte, "\eOB", 3); | |
else | |
vte_write(vte, "\e[B", 3); | |
return true; | |
case XKB_KEY_Right: | |
if (vte->flags & FLAG_CURSOR_KEY_MODE) | |
vte_write(vte, "\eOC", 3); | |
else | |
vte_write(vte, "\e[C", 3); | |
return true; | |
case XKB_KEY_Left: | |
if (vte->flags & FLAG_CURSOR_KEY_MODE) | |
vte_write(vte, "\eOD", 3); | |
else | |
vte_write(vte, "\e[D", 3); | |
return true; | |
case XKB_KEY_KP_Insert: | |
case XKB_KEY_KP_0: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOp", 3); | |
else | |
vte_write(vte, "0", 1); | |
return true; | |
case XKB_KEY_KP_End: | |
case XKB_KEY_KP_1: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOq", 3); | |
else | |
vte_write(vte, "1", 1); | |
return true; | |
case XKB_KEY_KP_Down: | |
case XKB_KEY_KP_2: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOr", 3); | |
else | |
vte_write(vte, "2", 1); | |
return true; | |
case XKB_KEY_KP_Page_Down: | |
case XKB_KEY_KP_3: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOs", 3); | |
else | |
vte_write(vte, "3", 1); | |
return true; | |
case XKB_KEY_KP_Left: | |
case XKB_KEY_KP_4: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOt", 3); | |
else | |
vte_write(vte, "4", 1); | |
return true; | |
case XKB_KEY_KP_Begin: | |
case XKB_KEY_KP_5: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOu", 3); | |
else | |
vte_write(vte, "5", 1); | |
return true; | |
case XKB_KEY_KP_Right: | |
case XKB_KEY_KP_6: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOv", 3); | |
else | |
vte_write(vte, "6", 1); | |
return true; | |
case XKB_KEY_KP_Home: | |
case XKB_KEY_KP_7: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOw", 3); | |
else | |
vte_write(vte, "7", 1); | |
return true; | |
case XKB_KEY_KP_Up: | |
case XKB_KEY_KP_8: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOx", 3); | |
else | |
vte_write(vte, "8", 1); | |
return true; | |
case XKB_KEY_KP_Page_Up: | |
case XKB_KEY_KP_9: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOy", 3); | |
else | |
vte_write(vte, "9", 1); | |
return true; | |
case XKB_KEY_KP_Subtract: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOm", 3); | |
else | |
vte_write(vte, "-", 1); | |
return true; | |
case XKB_KEY_KP_Separator: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOl", 3); | |
else | |
vte_write(vte, ",", 1); | |
return true; | |
case XKB_KEY_KP_Delete: | |
case XKB_KEY_KP_Decimal: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOn", 3); | |
else | |
vte_write(vte, ".", 1); | |
return true; | |
case XKB_KEY_KP_Equal: | |
case XKB_KEY_KP_Divide: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOj", 3); | |
else | |
vte_write(vte, "/", 1); | |
return true; | |
case XKB_KEY_KP_Multiply: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOo", 3); | |
else | |
vte_write(vte, "*", 1); | |
return true; | |
case XKB_KEY_KP_Add: | |
if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) | |
vte_write(vte, "\eOk", 3); | |
else | |
vte_write(vte, "+", 1); | |
return true; | |
case XKB_KEY_Home: | |
if (vte->flags & FLAG_CURSOR_KEY_MODE) | |
vte_write(vte, "\eOH", 3); | |
else | |
vte_write(vte, "\e[H", 3); | |
return true; | |
case XKB_KEY_End: | |
if (vte->flags & FLAG_CURSOR_KEY_MODE) | |
vte_write(vte, "\eOF", 3); | |
else | |
vte_write(vte, "\e[F", 3); | |
return true; | |
case XKB_KEY_KP_Space: | |
vte_write(vte, " ", 1); | |
return true; | |
case XKB_KEY_F1: | |
case XKB_KEY_KP_F1: | |
vte_write(vte, "\eOP", 3); | |
return true; | |
case XKB_KEY_F2: | |
case XKB_KEY_KP_F2: | |
vte_write(vte, "\eOQ", 3); | |
return true; | |
case XKB_KEY_F3: | |
case XKB_KEY_KP_F3: | |
vte_write(vte, "\eOR", 3); | |
return true; | |
case XKB_KEY_F4: | |
case XKB_KEY_KP_F4: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[1;2S", 6); | |
else | |
vte_write(vte, "\eOS", 3); | |
return true; | |
case XKB_KEY_F5: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[15;2~", 7); | |
else | |
vte_write(vte, "\e[15~", 5); | |
return true; | |
case XKB_KEY_F6: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[17;2~", 7); | |
else | |
vte_write(vte, "\e[17~", 5); | |
return true; | |
case XKB_KEY_F7: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[18;2~", 7); | |
else | |
vte_write(vte, "\e[18~", 5); | |
return true; | |
case XKB_KEY_F8: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[19;2~", 7); | |
else | |
vte_write(vte, "\e[19~", 5); | |
return true; | |
case XKB_KEY_F9: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[20;2~", 7); | |
else | |
vte_write(vte, "\e[20~", 5); | |
return true; | |
case XKB_KEY_F10: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[21;2~", 7); | |
else | |
vte_write(vte, "\e[21~", 5); | |
return true; | |
case XKB_KEY_F11: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[23;2~", 7); | |
else | |
vte_write(vte, "\e[23~", 5); | |
return true; | |
case XKB_KEY_F12: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[24;2~", 7); | |
else | |
vte_write(vte, "\e[24~", 5); | |
return true; | |
case XKB_KEY_F13: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[25;2~", 7); | |
else | |
vte_write(vte, "\e[25~", 5); | |
return true; | |
case XKB_KEY_F14: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[26;2~", 7); | |
else | |
vte_write(vte, "\e[26~", 5); | |
return true; | |
case XKB_KEY_F15: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[28;2~", 7); | |
else | |
vte_write(vte, "\e[28~", 5); | |
return true; | |
case XKB_KEY_F16: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[29;2~", 7); | |
else | |
vte_write(vte, "\e[29~", 5); | |
return true; | |
case XKB_KEY_F17: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[31;2~", 7); | |
else | |
vte_write(vte, "\e[31~", 5); | |
return true; | |
case XKB_KEY_F18: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[32;2~", 7); | |
else | |
vte_write(vte, "\e[32~", 5); | |
return true; | |
case XKB_KEY_F19: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[33;2~", 7); | |
else | |
vte_write(vte, "\e[33~", 5); | |
return true; | |
case XKB_KEY_F20: | |
if (mods & TSM_SHIFT_MASK) | |
vte_write(vte, "\e[34;2~", 7); | |
else | |
vte_write(vte, "\e[34~", 5); | |
return true; | |
} | |
if (unicode != TSM_VTE_INVALID) { | |
if (vte->flags & FLAG_7BIT_MODE) { | |
val = unicode; | |
if (unicode & 0x80) { | |
llog_debug(vte, "invalid keyboard input in 7bit mode U+%x; mapping to '?'", | |
unicode); | |
val = '?'; | |
} | |
vte_write(vte, &val, 1); | |
} else if (vte->flags & FLAG_8BIT_MODE) { | |
val = unicode; | |
if (unicode > 0xff) { | |
llog_debug(vte, "invalid keyboard input in 8bit mode U+%x; mapping to '?'", | |
unicode); | |
val = '?'; | |
} | |
vte_write_raw(vte, &val, 1); | |
} else { | |
len = tsm_ucs4_to_utf8(tsm_symbol_make(unicode), u8); | |
vte_write_raw(vte, u8, len); | |
} | |
return true; | |
} | |
vte->flags &= ~FLAG_PREPEND_ESCAPE; | |
return false; | |
} | |
/* | |
* TSM - VT Emulator | |
* | |
* Copyright (c) 2012 David Herrmann <[email protected]> | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included | |
* in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* VTE Character Sets | |
* These are predefined charactersets that can be loaded into GL and GR. By | |
* default we use unicode_lower and unicode_upper, that is, both sets have the | |
* exact unicode mapping. unicode_lower is effectively ASCII and unicode_upper | |
* as defined by the unicode standard. | |
* Several other character sets are defined here. However, all of them are | |
* limited to the 96 character space of GL or GR. Everything beyond GR (which | |
* was not supported by the classic VTs by DEC but is available in VT emulators | |
* that support unicode/UTF8) is always mapped to unicode and cannot be changed | |
* by these character sets. Even mapping GL and GR is only available for | |
* backwards compatibility as new applications can use the Unicode functionality | |
* of the VTE. | |
* | |
* Moreover, mapping GR is almost unnecessary to support. In fact, Unicode UTF-8 | |
* support in VTE works by reading every incoming data as UTF-8 stream. This | |
* maps GL/ASCII to ASCII, as UTF-8 is backwards compatible to ASCII, however, | |
* everything that has the 8th bit set is a >=2-byte haracter in UTF-8. That is, | |
* this is in no way backwards compatible to >=VT220 8bit support. Therefore, if | |
* someone maps a character set into GR and wants to use them with this VTE, | |
* then they must already send UTF-8 characters to use GR (all GR characters are | |
* 8-bits). Hence, they can easily also send the correct UTF-8 character for the | |
* unicode mapping. | |
* The only advantage is that most characters in many sets are 3-byte UTF-8 | |
* characters and by mapping the set into GR/GL you can use 2 or 1 byte UTF-8 | |
* characters which saves bandwidth. | |
* Another reason is, if you have older applications that use the VT220 8-bit | |
* support and you put a ASCII/8bit-extension to UTF-8 converter in between, you | |
* need these mappings to have the application behave correctly if it uses GL/GR | |
* mappings extensively. | |
* | |
* Anyway, we support GL/GR mappings so here are the most commonly used maps as | |
* defined by Unicode-standard, DEC-private maps and other famous charmaps. | |
* | |
* Characters 1-32 are always the control characters (part of CL) and cannot be | |
* mapped. Characters 34-127 (94 characters) are part of GL and can be mapped. | |
* Characters 33 and 128 are not part of GL and always mapped by VTE but are | |
* included here in the maps for alignment reasons but always set to 0. | |
*/ | |
#include <errno.h> | |
#include <stdlib.h> | |
#include <string.h> | |
/* | |
* Lower Unicode character set. This maps the characters to the basic ASCII | |
* characters 33-126. These are all graphics characters defined in ASCII. The | |
* first an last entry are never used so we can safely set them to anything. | |
*/ | |
tsm_vte_charset tsm_vte_unicode_lower = { | |
[0] = 0, | |
[1] = 33, | |
[2] = 34, | |
[3] = 35, | |
[4] = 36, | |
[5] = 37, | |
[6] = 38, | |
[7] = 39, | |
[8] = 40, | |
[9] = 41, | |
[10] = 42, | |
[11] = 43, | |
[12] = 44, | |
[13] = 45, | |
[14] = 46, | |
[15] = 47, | |
[16] = 48, | |
[17] = 49, | |
[18] = 50, | |
[19] = 51, | |
[20] = 52, | |
[21] = 53, | |
[22] = 54, | |
[23] = 55, | |
[24] = 56, | |
[25] = 57, | |
[26] = 58, | |
[27] = 59, | |
[28] = 60, | |
[29] = 61, | |
[30] = 62, | |
[31] = 63, | |
[32] = 64, | |
[33] = 65, | |
[34] = 66, | |
[35] = 67, | |
[36] = 68, | |
[37] = 69, | |
[38] = 70, | |
[39] = 71, | |
[40] = 72, | |
[41] = 73, | |
[42] = 74, | |
[43] = 75, | |
[44] = 76, | |
[45] = 77, | |
[46] = 78, | |
[47] = 79, | |
[48] = 80, | |
[49] = 81, | |
[50] = 82, | |
[51] = 83, | |
[52] = 84, | |
[53] = 85, | |
[54] = 86, | |
[55] = 87, | |
[56] = 88, | |
[57] = 89, | |
[58] = 90, | |
[59] = 91, | |
[60] = 92, | |
[61] = 93, | |
[62] = 94, | |
[63] = 95, | |
[64] = 96, | |
[65] = 97, | |
[66] = 98, | |
[67] = 99, | |
[68] = 100, | |
[69] = 101, | |
[70] = 102, | |
[71] = 103, | |
[72] = 104, | |
[73] = 105, | |
[74] = 106, | |
[75] = 107, | |
[76] = 108, | |
[77] = 109, | |
[78] = 110, | |
[79] = 111, | |
[80] = 112, | |
[81] = 113, | |
[82] = 114, | |
[83] = 115, | |
[84] = 116, | |
[85] = 117, | |
[86] = 118, | |
[87] = 119, | |
[88] = 120, | |
[89] = 121, | |
[90] = 122, | |
[91] = 123, | |
[92] = 124, | |
[93] = 125, | |
[94] = 126, | |
[95] = 0, | |
}; | |
/* | |
* Upper Unicode Table | |
* This maps all characters to the upper unicode characters 161-254. These are | |
* not compatible to any older 8 bit character sets. See the Unicode standard | |
* for the definitions of each symbol. Again, the first an last entry are never | |
* used so set them to 0. | |
*/ | |
tsm_vte_charset tsm_vte_unicode_upper = { | |
[0] = 0, | |
[1] = 161, | |
[2] = 162, | |
[3] = 163, | |
[4] = 164, | |
[5] = 165, | |
[6] = 166, | |
[7] = 167, | |
[8] = 168, | |
[9] = 169, | |
[10] = 170, | |
[11] = 171, | |
[12] = 172, | |
[13] = 173, | |
[14] = 174, | |
[15] = 175, | |
[16] = 176, | |
[17] = 177, | |
[18] = 178, | |
[19] = 179, | |
[20] = 180, | |
[21] = 181, | |
[22] = 182, | |
[23] = 183, | |
[24] = 184, | |
[25] = 185, | |
[26] = 186, | |
[27] = 187, | |
[28] = 188, | |
[29] = 189, | |
[30] = 190, | |
[31] = 191, | |
[32] = 192, | |
[33] = 193, | |
[34] = 194, | |
[35] = 195, | |
[36] = 196, | |
[37] = 197, | |
[38] = 198, | |
[39] = 199, | |
[40] = 200, | |
[41] = 201, | |
[42] = 202, | |
[43] = 203, | |
[44] = 204, | |
[45] = 205, | |
[46] = 206, | |
[47] = 207, | |
[48] = 208, | |
[49] = 209, | |
[50] = 210, | |
[51] = 211, | |
[52] = 212, | |
[53] = 213, | |
[54] = 214, | |
[55] = 215, | |
[56] = 216, | |
[57] = 217, | |
[58] = 218, | |
[59] = 219, | |
[60] = 220, | |
[61] = 221, | |
[62] = 222, | |
[63] = 223, | |
[64] = 224, | |
[65] = 225, | |
[66] = 226, | |
[67] = 227, | |
[68] = 228, | |
[69] = 229, | |
[70] = 230, | |
[71] = 231, | |
[72] = 232, | |
[73] = 233, | |
[74] = 234, | |
[75] = 235, | |
[76] = 236, | |
[77] = 237, | |
[78] = 238, | |
[79] = 239, | |
[80] = 240, | |
[81] = 241, | |
[82] = 242, | |
[83] = 243, | |
[84] = 244, | |
[85] = 245, | |
[86] = 246, | |
[87] = 247, | |
[88] = 248, | |
[89] = 249, | |
[90] = 250, | |
[91] = 251, | |
[92] = 252, | |
[93] = 253, | |
[94] = 254, | |
[95] = 0, | |
}; | |
/* | |
* The DEC supplemental graphics set. For its definition see here: | |
* http://vt100.net/docs/vt220-rm/table2-3b.html | |
* Its basically a mixture of common European symbols that are not part of | |
* ASCII. Most often, this is mapped into GR to extend the basci ASCII part. | |
* | |
* This is very similar to unicode_upper, however, few symbols differ so do not | |
* mix them up! | |
*/ | |
tsm_vte_charset tsm_vte_dec_supplemental_graphics = { | |
[0] = 0, | |
[1] = 161, | |
[2] = 162, | |
[3] = 163, | |
[4] = 0, | |
[5] = 165, | |
[6] = 0, | |
[7] = 167, | |
[8] = 164, | |
[9] = 169, | |
[10] = 170, | |
[11] = 171, | |
[12] = 0, | |
[13] = 0, | |
[14] = 0, | |
[15] = 0, | |
[16] = 176, | |
[17] = 177, | |
[18] = 178, | |
[19] = 179, | |
[20] = 0, | |
[21] = 181, | |
[22] = 182, | |
[23] = 183, | |
[24] = 0, | |
[25] = 185, | |
[26] = 186, | |
[27] = 187, | |
[28] = 188, | |
[29] = 189, | |
[30] = 0, | |
[31] = 191, | |
[32] = 192, | |
[33] = 193, | |
[34] = 194, | |
[35] = 195, | |
[36] = 196, | |
[37] = 197, | |
[38] = 198, | |
[39] = 199, | |
[40] = 200, | |
[41] = 201, | |
[42] = 202, | |
[43] = 203, | |
[44] = 204, | |
[45] = 205, | |
[46] = 206, | |
[47] = 207, | |
[48] = 0, | |
[49] = 209, | |
[50] = 210, | |
[51] = 211, | |
[52] = 212, | |
[53] = 213, | |
[54] = 214, | |
[55] = 338, | |
[56] = 216, | |
[57] = 217, | |
[58] = 218, | |
[59] = 219, | |
[60] = 220, | |
[61] = 376, | |
[62] = 0, | |
[63] = 223, | |
[64] = 224, | |
[65] = 225, | |
[66] = 226, | |
[67] = 227, | |
[68] = 228, | |
[69] = 229, | |
[70] = 230, | |
[71] = 231, | |
[72] = 232, | |
[73] = 233, | |
[74] = 234, | |
[75] = 235, | |
[76] = 236, | |
[77] = 237, | |
[78] = 238, | |
[79] = 239, | |
[80] = 0, | |
[81] = 241, | |
[82] = 242, | |
[83] = 243, | |
[84] = 244, | |
[85] = 245, | |
[86] = 246, | |
[87] = 339, | |
[88] = 248, | |
[89] = 249, | |
[90] = 250, | |
[91] = 251, | |
[92] = 252, | |
[93] = 255, | |
[94] = 0, | |
[95] = 0, | |
}; | |
/* | |
* DEC special graphics character set. See here for its definition: | |
* http://vt100.net/docs/vt220-rm/table2-4.html | |
* This contains several characters to create ASCII drawings and similar. Its | |
* commonaly mapped into GR to extend the basic ASCII characters. | |
* | |
* Lower 62 characters map to ASCII 33-64, everything beyond is special and | |
* commonly used for ASCII drawings. It depends on the Unicode Standard 3.2 for | |
* the extended horizontal scan-line characters 3, 5, 7, and 9. | |
*/ | |
tsm_vte_charset tsm_vte_dec_special_graphics = { | |
[0] = 0, | |
[1] = 33, | |
[2] = 34, | |
[3] = 35, | |
[4] = 36, | |
[5] = 37, | |
[6] = 38, | |
[7] = 39, | |
[8] = 40, | |
[9] = 41, | |
[10] = 42, | |
[11] = 43, | |
[12] = 44, | |
[13] = 45, | |
[14] = 46, | |
[15] = 47, | |
[16] = 48, | |
[17] = 49, | |
[18] = 50, | |
[19] = 51, | |
[20] = 52, | |
[21] = 53, | |
[22] = 54, | |
[23] = 55, | |
[24] = 56, | |
[25] = 57, | |
[26] = 58, | |
[27] = 59, | |
[28] = 60, | |
[29] = 61, | |
[30] = 62, | |
[31] = 63, | |
[32] = 64, | |
[33] = 65, | |
[34] = 66, | |
[35] = 67, | |
[36] = 68, | |
[37] = 69, | |
[38] = 70, | |
[39] = 71, | |
[40] = 72, | |
[41] = 73, | |
[42] = 74, | |
[43] = 75, | |
[44] = 76, | |
[45] = 77, | |
[46] = 78, | |
[47] = 79, | |
[48] = 80, | |
[49] = 81, | |
[50] = 82, | |
[51] = 83, | |
[52] = 84, | |
[53] = 85, | |
[54] = 86, | |
[55] = 87, | |
[56] = 88, | |
[57] = 89, | |
[58] = 90, | |
[59] = 91, | |
[60] = 92, | |
[61] = 93, | |
[62] = 94, | |
[63] = 0, | |
[64] = 9830, | |
[65] = 9618, | |
[66] = 9225, | |
[67] = 9228, | |
[68] = 9229, | |
[69] = 9226, | |
[70] = 176, | |
[71] = 177, | |
[72] = 9252, | |
[73] = 9227, | |
[74] = 9496, | |
[75] = 9488, | |
[76] = 9484, | |
[77] = 9492, | |
[78] = 9532, | |
[79] = 9146, | |
[80] = 9147, | |
[81] = 9472, | |
[82] = 9148, | |
[83] = 9149, | |
[84] = 9500, | |
[85] = 9508, | |
[86] = 9524, | |
[87] = 9516, | |
[88] = 9474, | |
[89] = 8804, | |
[90] = 8805, | |
[91] = 960, | |
[92] = 8800, | |
[93] = 163, | |
[94] = 8901, | |
[95] = 0, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment