blob: 8e1a6caa43a2f7dbcc38d638f184b03b2c0d062a [file] [log] [blame]
/*
* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <ctype.h>
#include <libtsm.h>
#include <paths.h>
#include <stdio.h>
#include <sys/select.h>
#include "font.h"
#include "input.h"
#include "keysym.h"
#include "shl_pty.h"
#include "term.h"
#include "util.h"
#include "video.h"
struct term {
struct tsm_screen *screen;
struct tsm_vte *vte;
struct shl_pty *pty;
int pty_bridge;
int pid;
tsm_age_t age;
int char_x, char_y;
int pitch;
uint32_t *dst_image;
int shift_state;
int control_state;
};
static struct term term;
static void __attribute__ ((noreturn)) term_run_child()
{
char **argv = (char *[]) {
getenv("SHELL") ? : _PATH_BSHELL,
"-il",
NULL
};
printf("Welcome to frecon!\n");
printf("running %s\n", argv[0]);
/* XXX figure out how to fix "top" for xterm-256color */
setenv("TERM", "xterm", 1);
execve(argv[0], argv, environ);
exit(1);
}
static int term_draw_cell(struct tsm_screen *screen, uint32_t id,
const uint32_t *ch, size_t len,
unsigned int cwidth, unsigned int posx,
unsigned int posy,
const struct tsm_screen_attr *attr,
tsm_age_t age, void *data)
{
uint32_t front_color, back_color;
if (age && term.age && age <= term.age)
return 0;
front_color = (attr->fr << 16) | (attr->fg << 8) | attr->fb;
back_color = (attr->br << 16) | (attr->bg << 8) | attr->bb;
if (attr->inverse) {
uint32_t tmp = front_color;
front_color = back_color;
back_color = tmp;
}
if (len)
font_render(term.dst_image, posx, posy, term.pitch, *ch,
front_color, back_color);
else
font_fillchar(term.dst_image, posx, posy, term.pitch,
front_color, back_color);
return 0;
}
void term_redraw()
{
term.dst_image = video_lock();
term.age = tsm_screen_draw(term.screen, term_draw_cell, NULL);
video_unlock();
}
void term_key_event(uint32_t keysym, int32_t unicode)
{
if (tsm_vte_handle_keyboard(term.vte, keysym, 0, 0, unicode))
tsm_screen_sb_reset(term.screen);
term_redraw();
}
static void term_read_cb(struct shl_pty *pty, char *u8, size_t len, void *data)
{
struct term *term = data;
tsm_vte_input(term->vte, u8, len);
term_redraw();
}
static void term_write_cb(struct tsm_vte *vte, const char *u8, size_t len,
void *data)
{
struct term *term = data;
int r;
r = shl_pty_write(term->pty, u8, len);
if (r < 0)
printf("OOM in pty-write (%d)", r);
shl_pty_dispatch(term->pty);
}
static const char *sev2str_table[] = {
"FATAL",
"ALERT",
"CRITICAL",
"ERROR",
"WARNING",
"NOTICE",
"INFO",
"DEBUG"
};
static const char *sev2str(unsigned int sev)
{
if (sev > 7)
return "DEBUG";
return sev2str_table[sev];
}
#ifdef __clang__
__attribute__((__format__ (__printf__, 7, 0)))
#endif
static void log_tsm(void *data, const char *file, int line, const char *fn,
const char *subs, unsigned int sev, const char *format,
va_list args)
{
fprintf(stderr, "%s: %s: ", sev2str(sev), subs);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
}
static int term_special_key(struct input_key_event *ev)
{
switch (ev->code) {
case KEY_LEFTSHIFT:
case KEY_RIGHTSHIFT:
term.shift_state = ! !ev->value;
return 1;
case KEY_LEFTCTRL:
case KEY_RIGHTCTRL:
term.control_state = ! !ev->value;
return 1;
}
if (term.shift_state && ev->value) {
switch (ev->code) {
case KEY_PAGEUP:
tsm_screen_sb_page_up(term.screen, 1);
term_redraw();
return 1;
case KEY_PAGEDOWN:
tsm_screen_sb_page_down(term.screen, 1);
term_redraw();
return 1;
case KEY_UP:
tsm_screen_sb_up(term.screen, 1);
term_redraw();
return 1;
case KEY_DOWN:
tsm_screen_sb_down(term.screen, 1);
term_redraw();
return 1;
}
}
return 0;
}
static void term_get_keysym_and_unicode(struct input_key_event *event,
uint32_t *keysym, uint32_t *unicode)
{
struct {
uint32_t code;
uint32_t keysym;
} non_ascii_keys[] = {
{ KEY_ESC, KEYSYM_ESC},
{ KEY_HOME, KEYSYM_HOME},
{ KEY_LEFT, KEYSYM_LEFT},
{ KEY_UP, KEYSYM_UP},
{ KEY_RIGHT, KEYSYM_RIGHT},
{ KEY_DOWN, KEYSYM_DOWN},
{ KEY_PAGEUP, KEYSYM_PAGEUP},
{ KEY_PAGEDOWN, KEYSYM_PAGEDOWN},
{ KEY_END, KEYSYM_END},
{ KEY_INSERT, KEYSYM_INSERT},
{ KEY_DELETE, KEYSYM_DELETE},
};
for (unsigned i = 0; i < ARRAY_SIZE(non_ascii_keys); i++) {
if (non_ascii_keys[i].code == event->code) {
*keysym = non_ascii_keys[i].keysym;
*unicode = -1;
return;
}
}
if (event->code >= ARRAY_SIZE(keysym_table) / 2) {
*keysym = '?';
} else {
*keysym = keysym_table[event->code * 2 + term.shift_state];
if ((term.control_state) && isascii(*keysym))
*keysym = tolower(*keysym) - 'a' + 1;
}
*unicode = *keysym;
}
int term_run()
{
int pty_fd = term.pty_bridge;
fd_set read_set, exception_set;
while (1) {
FD_ZERO(&read_set);
FD_ZERO(&exception_set);
FD_SET(pty_fd, &read_set);
FD_SET(pty_fd, &exception_set);
int maxfd = input_setfds(&read_set, &exception_set);
maxfd = MAX(maxfd, pty_fd) + 1;
select(maxfd, &read_set, NULL, &exception_set, NULL);
if (FD_ISSET(pty_fd, &exception_set))
return -1;
struct input_key_event *event;
event = input_get_event(&read_set, &exception_set);
if (event) {
if (!term_special_key(event) && event->value) {
uint32_t keysym, unicode;
term_get_keysym_and_unicode(event,
&keysym,
&unicode);
term_key_event(keysym, unicode);
}
input_put_event(event);
}
if (FD_ISSET(pty_fd, &read_set)) {
shl_pty_bridge_dispatch(term.pty_bridge, 0);
}
}
return 0;
}
int term_init(int32_t width, int32_t height, int32_t pitch, int32_t scaling)
{
const int scrollback_size = 200;
uint32_t char_width, char_height;
int ret;
font_init(scaling);
font_get_size(&char_width, &char_height);
term.char_x = width / char_width;
term.char_y = height / char_height;
term.pitch = pitch;
ret = tsm_screen_new(&term.screen, log_tsm, &term);
if (ret < 0)
return ret;
tsm_screen_set_max_sb(term.screen, scrollback_size);
ret = tsm_vte_new(&term.vte, term.screen, term_write_cb, &term, log_tsm,
&term);
if (ret < 0)
return ret;
term.pty_bridge = shl_pty_bridge_new();
if (term.pty_bridge < 0)
return term.pty_bridge;
ret = shl_pty_open(&term.pty, term_read_cb, &term, term.char_x,
term.char_y);
if (ret < 0) {
return ret;
} else if (!ret) {
term_run_child();
exit(1);
}
ret = shl_pty_bridge_add(term.pty_bridge, term.pty);
if (ret) {
shl_pty_close(term.pty);
return ret;
}
term.pid = shl_pty_get_child(term.pty);
ret = tsm_screen_resize(term.screen, term.char_x, term.char_y);
if (ret < 0)
return ret;
ret = shl_pty_resize(term.pty, term.char_x, term.char_y);
if (ret < 0)
return ret;
return 0;
}
void term_close()
{
}