Skip to content

Instantly share code, notes, and snippets.

@dvdhrm
Created September 19, 2012 11:28
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dvdhrm/3749173 to your computer and use it in GitHub Desktop.
Save dvdhrm/3749173 to your computer and use it in GitHub Desktop.
TSM library as one file
/*
* TSM - Unicode Handling
*
* Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
* 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 <dh.herrmann@googlemail.com>
*
* 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