| /* |
| * 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 <stdio.h> |
| #include <sys/select.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include "dbus_interface.h" |
| #include "font.h" |
| #include "input.h" |
| #include "main.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; |
| }; |
| |
| static void __attribute__ ((noreturn)) term_run_child(unsigned int term_id) |
| { |
| printf("Welcome to frecon!\n"); |
| printf("running %s\n", command_flags.exec[term_id][0]); |
| /* XXX figure out how to fix "top" for xterm-256color */ |
| setenv("TERM", "xterm", 1); |
| execve(command_flags.exec[term_id][0], command_flags.exec[term_id], 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) |
| { |
| terminal_t *terminal = (terminal_t*)data; |
| uint32_t front_color, back_color; |
| |
| if (age && terminal->term->age && age <= terminal->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(terminal->term->dst_image, posx, posy, terminal->term->pitch, *ch, |
| front_color, back_color); |
| else |
| font_fillchar(terminal->term->dst_image, posx, posy, terminal->term->pitch, |
| front_color, back_color); |
| |
| return 0; |
| } |
| |
| void term_redraw(terminal_t *terminal) |
| { |
| uint32_t *video_buffer; |
| video_buffer = video_lock(terminal->video); |
| if (video_buffer != NULL) { |
| terminal->term->dst_image = video_buffer; |
| terminal->term->age = |
| tsm_screen_draw(terminal->term->screen, term_draw_cell, terminal); |
| video_unlock(terminal->video); |
| } |
| } |
| |
| void term_key_event(terminal_t* terminal, uint32_t keysym, int32_t unicode) |
| { |
| |
| if (tsm_vte_handle_keyboard(terminal->term->vte, keysym, 0, 0, unicode)) |
| tsm_screen_sb_reset(terminal->term->screen); |
| |
| term_redraw(terminal); |
| } |
| |
| static void term_read_cb(struct shl_pty *pty, char *u8, size_t len, void *data) |
| { |
| terminal_t *terminal = (terminal_t*)data; |
| |
| tsm_vte_input(terminal->term->vte, u8, len); |
| |
| term_redraw(terminal); |
| } |
| |
| 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) |
| LOG(ERROR, "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"); |
| } |
| |
| terminal_t* term_init(unsigned int term_id) |
| { |
| const int scrollback_size = 200; |
| uint32_t char_width, char_height; |
| int status; |
| terminal_t *new_terminal; |
| |
| new_terminal = (terminal_t*)calloc(1, sizeof(*new_terminal)); |
| if (!new_terminal) |
| return NULL; |
| |
| new_terminal->video = video_init(); |
| if (!new_terminal->video) { |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| new_terminal->term = (struct term*)calloc(1, sizeof(*new_terminal->term)); |
| if (!new_terminal->term) { |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| font_init(video_getscaling(new_terminal->video)); |
| font_get_size(&char_width, &char_height); |
| |
| new_terminal->term->char_x = |
| video_getwidth(new_terminal->video) / char_width; |
| new_terminal->term->char_y = |
| video_getheight(new_terminal->video) / char_height; |
| new_terminal->term->pitch = video_getpitch(new_terminal->video); |
| |
| status = tsm_screen_new(&new_terminal->term->screen, |
| log_tsm, new_terminal->term); |
| if (new_terminal < 0) { |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| |
| tsm_screen_set_max_sb(new_terminal->term->screen, scrollback_size); |
| |
| status = tsm_vte_new(&new_terminal->term->vte, new_terminal->term->screen, |
| term_write_cb, new_terminal->term, log_tsm, new_terminal->term); |
| |
| if (status < 0) { |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| new_terminal->term->pty_bridge = shl_pty_bridge_new(); |
| if (new_terminal->term->pty_bridge < 0) { |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| status = shl_pty_open(&new_terminal->term->pty, |
| term_read_cb, new_terminal, new_terminal->term->char_x, |
| new_terminal->term->char_y); |
| if (status < 0) { |
| term_close(new_terminal); |
| return NULL; |
| } else if (status == 0) { |
| term_run_child(term_id); |
| exit(1); |
| } |
| |
| status = shl_pty_bridge_add(new_terminal->term->pty_bridge, new_terminal->term->pty); |
| if (status) { |
| shl_pty_close(new_terminal->term->pty); |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| new_terminal->term->pid = shl_pty_get_child(new_terminal->term->pty); |
| |
| status = tsm_screen_resize(new_terminal->term->screen, |
| new_terminal->term->char_x, new_terminal->term->char_y); |
| if (status < 0) { |
| shl_pty_close(new_terminal->term->pty); |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| status = shl_pty_resize(new_terminal->term->pty, new_terminal->term->char_x, new_terminal->term->char_y); |
| if (status < 0) { |
| shl_pty_close(new_terminal->term->pty); |
| term_close(new_terminal); |
| return NULL; |
| } |
| new_terminal->active = true; |
| video_setmode(new_terminal->video); |
| term_redraw(new_terminal); |
| |
| return new_terminal; |
| } |
| |
| void term_set_dbus(terminal_t *term, dbus_t* dbus) |
| { |
| term->dbus = dbus; |
| } |
| |
| void term_close(terminal_t *term) |
| { |
| if (!term) |
| return; |
| |
| if (term->video) { |
| video_close(term->video); |
| term->video = NULL; |
| } |
| |
| if (term->term) { |
| free(term->term); |
| term->term = NULL; |
| } |
| |
| free(term); |
| } |
| |
| bool term_is_child_done(terminal_t* terminal) |
| { |
| int status; |
| int ret; |
| ret = waitpid(terminal->term->pid, &status, WNOHANG); |
| |
| return ret != 0; |
| } |
| |
| void term_page_up(terminal_t* terminal) |
| { |
| tsm_screen_sb_page_up(terminal->term->screen, 1); |
| term_redraw(terminal); |
| } |
| |
| void term_page_down(terminal_t* terminal) |
| { |
| tsm_screen_sb_page_down(terminal->term->screen, 1); |
| term_redraw(terminal); |
| } |
| |
| void term_line_up(terminal_t* terminal) |
| { |
| tsm_screen_sb_up(terminal->term->screen, 1); |
| term_redraw(terminal); |
| } |
| |
| void term_line_down(terminal_t* terminal) |
| { |
| tsm_screen_sb_down(terminal->term->screen, 1); |
| term_redraw(terminal); |
| } |
| |
| bool term_is_valid(terminal_t* terminal) |
| { |
| return ((terminal != NULL) && (terminal->term != NULL)); |
| } |
| |
| int term_fd(terminal_t* terminal) |
| { |
| if (term_is_valid(terminal)) |
| return terminal->term->pty_bridge; |
| else |
| return -1; |
| } |
| |
| void term_dispatch_io(terminal_t* terminal, fd_set* read_set) |
| { |
| if (term_is_valid(terminal)) |
| if (FD_ISSET(terminal->term->pty_bridge, read_set)) |
| shl_pty_bridge_dispatch(terminal->term->pty_bridge, 0); |
| } |
| |
| bool term_exception(terminal_t* terminal, fd_set* exception_set) |
| { |
| if (term_is_valid(terminal)) { |
| if (terminal->term->pty_bridge >= 0) { |
| return FD_ISSET(terminal->term->pty_bridge, |
| exception_set); |
| } |
| } |
| |
| return false; |
| } |
| |
| bool term_is_active(terminal_t* terminal) |
| { |
| if (term_is_valid(terminal)) |
| return terminal->active; |
| |
| return false; |
| } |
| |
| void term_add_fd(terminal_t* terminal, fd_set* read_set, fd_set* exception_set) |
| { |
| if (term_is_valid(terminal)) { |
| if (terminal->term->pty_bridge >= 0) { |
| FD_SET(terminal->term->pty_bridge, read_set); |
| FD_SET(terminal->term->pty_bridge, exception_set); |
| } |
| } |
| } |