| /* |
| * 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 <fcntl.h> |
| #include <libtsm.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/select.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include "dbus.h" |
| #include "fb.h" |
| #include "font.h" |
| #include "image.h" |
| #include "input.h" |
| #include "main.h" |
| #include "shl_pty.h" |
| #include "term.h" |
| #include "util.h" |
| |
| unsigned int term_num_terminals = 4; |
| static terminal_t* terminals[TERM_MAX_TERMINALS]; |
| static uint32_t current_terminal = 0; |
| |
| struct term { |
| struct tsm_screen* screen; |
| struct tsm_vte* vte; |
| struct shl_pty* pty; |
| int pty_bridge; |
| int pid; |
| tsm_age_t age; |
| int w_in_char, h_in_char; |
| }; |
| |
| struct _terminal_t { |
| unsigned vt; |
| bool active; |
| bool input_enable; |
| uint32_t background; |
| bool background_valid; |
| fb_t* fb; |
| struct term* term; |
| char** exec; |
| }; |
| |
| |
| static char* interactive_cmd_line[] = { |
| "/sbin/agetty", |
| "-", |
| "9600", |
| "xterm", |
| NULL |
| }; |
| |
| static bool in_background = false; |
| static bool hotplug_occured = false; |
| |
| |
| static void __attribute__ ((noreturn)) term_run_child(terminal_t* terminal) |
| { |
| /* XXX figure out how to fix "top" for xterm-256color */ |
| setenv("TERM", "xterm", 1); |
| if (terminal->exec) { |
| execve(terminal->exec[0], terminal->exec, environ); |
| exit(1); |
| } else { |
| while (1) |
| sleep(1000000); |
| } |
| } |
| |
| 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; |
| uint8_t br, bb, bg; |
| uint32_t luminance; |
| |
| if (age && terminal->term->age && age <= terminal->term->age) |
| return 0; |
| |
| if (terminal->background_valid) { |
| br = (terminal->background >> 16) & 0xFF; |
| bg = (terminal->background >> 8) & 0xFF; |
| bb = (terminal->background) & 0xFF; |
| luminance = (3 * br + bb + 4 * bg) >> 3; |
| |
| /* |
| * FIXME: black is chosen on a dark background, but it uses the |
| * default color for light backgrounds |
| */ |
| if (luminance > 128) { |
| front_color = 0; |
| back_color = terminal->background; |
| } else { |
| front_color = (attr->fr << 16) | (attr->fg << 8) | attr->fb; |
| back_color = terminal->background; |
| } |
| } else { |
| 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->fb, posx, posy, *ch, |
| front_color, back_color); |
| else |
| font_fillchar(terminal->fb, posx, posy, |
| front_color, back_color); |
| |
| return 0; |
| } |
| |
| static void term_redraw(terminal_t* terminal) |
| { |
| if (fb_lock(terminal->fb)) { |
| terminal->term->age = |
| tsm_screen_draw(terminal->term->screen, term_draw_cell, terminal); |
| fb_unlock(terminal->fb); |
| } |
| } |
| |
| void term_key_event(terminal_t* terminal, uint32_t keysym, int32_t unicode) |
| { |
| if (!terminal->input_enable) |
| return; |
| |
| 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 void term_esc_show_image(terminal_t* terminal, char* params) |
| { |
| char* tok; |
| image_t* image; |
| int status; |
| |
| image = image_create(); |
| if (!image) { |
| LOG(ERROR, "Out of memory when creating an image.\n"); |
| return; |
| } |
| for (tok = strtok(params, ";"); tok; tok = strtok(NULL, ";")) { |
| if (strncmp("file=", tok, 5) == 0) { |
| image_set_filename(image, tok + 5); |
| } else if (strncmp("location=", tok, 9) == 0) { |
| uint32_t x, y; |
| if (sscanf(tok + 9, "%u,%u", &x, &y) != 2) { |
| LOG(ERROR, "Error parsing image location.\n"); |
| goto done; |
| } |
| image_set_location(image, x, y); |
| } else if (strncmp("offset=", tok, 7) == 0) { |
| int32_t x, y; |
| if (sscanf(tok + 7, "%d,%d", &x, &y) != 2) { |
| LOG(ERROR, "Error parsing image offset.\n"); |
| goto done; |
| } |
| image_set_offset(image, x, y); |
| } else if (strncmp("scale=", tok, 6) == 0) { |
| uint32_t s; |
| if (sscanf(tok + 6, "%u", &s) != 1) { |
| LOG(ERROR, "Error parsing image scale.\n"); |
| goto done; |
| } |
| if (s == 0) |
| s = image_get_auto_scale(term_getfb(terminal)); |
| image_set_scale(image, s); |
| } |
| } |
| |
| status = image_load_image_from_file(image); |
| if (status != 0) { |
| LOG(WARNING, "Term ESC image_load_image_from_file %s failed: %d:%s.", |
| image_get_filename(image), status, strerror(status)); |
| } else { |
| term_show_image(terminal, image); |
| } |
| done: |
| image_destroy(image); |
| } |
| |
| static void term_esc_draw_box(terminal_t* terminal, char* params) |
| { |
| char* tok; |
| uint32_t color = 0; |
| uint32_t w = 1; |
| uint32_t h = 1; |
| uint32_t locx, locy; |
| bool use_location = false; |
| int32_t offx, offy; |
| bool use_offset = false; |
| uint32_t scale = 1; |
| fb_stepper_t s; |
| int32_t startx, starty; |
| |
| for (tok = strtok(params, ";"); tok; tok = strtok(NULL, ";")) { |
| if (strncmp("color=", tok, 6) == 0) { |
| color = strtoul(tok + 6, NULL, 0); |
| } else if (strncmp("size=", tok, 5) == 0) { |
| if (sscanf(tok + 5, "%u,%u", &w, &h) != 2) { |
| LOG(ERROR, "Error parsing box size.\n"); |
| goto done; |
| } |
| } else if (strncmp("location=", tok, 9) == 0) { |
| if (sscanf(tok + 9, "%u,%u", &locx, &locy) != 2) { |
| LOG(ERROR, "Error parsing box location.\n"); |
| goto done; |
| } |
| use_location = true; |
| } else if (strncmp("offset=", tok, 7) == 0) { |
| if (sscanf(tok + 7, "%d,%d", &offx, &offy) != 2) { |
| LOG(ERROR, "Error parsing box offset.\n"); |
| goto done; |
| } |
| use_offset = true; |
| } else if (strncmp("scale=", tok, 6) == 0) { |
| if (sscanf(tok + 6, "%u", &scale) != 1) { |
| LOG(ERROR, "Error parsing box scale.\n"); |
| goto done; |
| } |
| if (scale == 0) |
| scale = image_get_auto_scale(term_getfb(terminal)); |
| } |
| } |
| |
| w *= scale; |
| h *= scale; |
| offx *= scale; |
| offy *= scale; |
| |
| if (!fb_lock(terminal->fb)) |
| goto done; |
| |
| if (use_offset && use_location) { |
| LOG(WARNING, "Box offset and location set, using location."); |
| use_offset = false; |
| } |
| |
| if (use_location) { |
| startx = locx; |
| starty = locy; |
| } else { |
| startx = (fb_getwidth(terminal->fb) - w)/2; |
| starty = (fb_getheight(terminal->fb) - h)/2; |
| } |
| |
| if (use_offset) { |
| startx += offx; |
| starty += offy; |
| } |
| |
| if (!fb_stepper_init(&s, terminal->fb, startx, starty, w, h)) |
| goto done_fb; |
| |
| do { |
| do { |
| } while (fb_stepper_step_x(&s, color)); |
| } while (fb_stepper_step_y(&s)); |
| |
| done_fb: |
| fb_unlock(terminal->fb); |
| done: |
| ; |
| } |
| |
| static void term_esc_input(terminal_t* terminal, char* params) |
| { |
| if (strcmp(params, "1") == 0 || |
| strcasecmp(params, "on") == 0 || |
| strcasecmp(params, "true") == 0) |
| term_input_enable(terminal, true); |
| else if (strcmp(params, "0") == 0 || |
| strcasecmp(params, "off") == 0 || |
| strcasecmp(params, "false") == 0) |
| term_input_enable(terminal, false); |
| else |
| LOG(ERROR, "Invalid parameter for input escape.\n"); |
| } |
| |
| /* |
| * Assume all one or two digit sequences followed by ; are xterm OSC escapes. |
| */ |
| static bool is_xterm_osc(char *osc) |
| { |
| if (isdigit(osc[0])) { |
| if (osc[1] == ';') |
| return true; |
| if (isdigit(osc[1]) && osc[2] == ';') |
| return true; |
| } |
| return false; |
| } |
| |
| static void term_osc_cb(struct tsm_vte *vte, const uint32_t *osc_string, |
| size_t osc_len, void *data) |
| { |
| terminal_t* terminal = (terminal_t*)data; |
| size_t i; |
| char *osc; |
| |
| for (i = 0; i < osc_len; i++) |
| if (osc_string[i] >= 128) |
| return; /* we only want to deal with ASCII */ |
| |
| osc = malloc(osc_len + 1); |
| if (!osc) { |
| LOG(WARNING, "Out of memory when processing OSC.\n"); |
| return; |
| } |
| |
| for (i = 0; i < osc_len; i++) |
| osc[i] = (char)osc_string[i]; |
| osc[i] = '\0'; |
| |
| if (strncmp(osc, "image:", 6) == 0) |
| term_esc_show_image(terminal, osc + 6); |
| else if (strncmp(osc, "box:", 4) == 0) |
| term_esc_draw_box(terminal, osc + 4); |
| else if (strncmp(osc, "input:", 6) == 0) |
| term_esc_input(terminal, osc + 6); |
| else if (is_xterm_osc(osc)) |
| ; /* Ignore it. */ |
| else |
| LOG(WARNING, "Unknown OSC escape sequence \"%s\", ignoring.", osc); |
| |
| free(osc); |
| } |
| |
| #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) |
| { |
| char buffer[KMSG_LINE_MAX]; |
| int len = snprintf(buffer, KMSG_LINE_MAX, "<%i>frecon[%d]: %s: ", sev, |
| getpid(), subs); |
| if (len < 0) |
| return; |
| if (len < KMSG_LINE_MAX - 1) |
| vsnprintf(buffer+len, KMSG_LINE_MAX - len, format, args); |
| fprintf(stderr, "%s\n", buffer); |
| } |
| |
| static int term_resize(terminal_t* term, int scaling) |
| { |
| uint32_t char_width, char_height; |
| int status; |
| |
| if (!scaling) |
| scaling = fb_getscaling(term->fb); |
| |
| font_init(scaling); |
| font_get_size(&char_width, &char_height); |
| |
| term->term->w_in_char = fb_getwidth(term->fb) / char_width; |
| term->term->h_in_char = fb_getheight(term->fb) / char_height; |
| |
| status = tsm_screen_resize(term->term->screen, |
| term->term->w_in_char, term->term->h_in_char); |
| if (status < 0) { |
| font_free(); |
| return -1; |
| } |
| |
| status = shl_pty_resize(term->term->pty, term->term->w_in_char, |
| term->term->h_in_char); |
| if (status < 0) { |
| font_free(); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| void term_set_num_terminals(unsigned new_num) |
| { |
| if (new_num < 1) |
| term_num_terminals = 1; |
| else if (new_num > TERM_MAX_TERMINALS) |
| term_num_terminals = TERM_MAX_TERMINALS; |
| else |
| term_num_terminals = new_num; |
| } |
| |
| static bool term_is_interactive(unsigned int vt) |
| { |
| if (command_flags.no_login) |
| return false; |
| |
| if (vt == TERM_SPLASH_TERMINAL) |
| return command_flags.enable_vt1; |
| |
| return true; |
| } |
| |
| /* |
| * Set the area not covered by any characters, possibly existing on the right |
| * side and bottom of the screen, to the background color. |
| */ |
| static void term_clear_border(terminal_t* terminal) |
| { |
| fb_stepper_t s; |
| uint32_t char_width, char_height; |
| font_get_size(&char_width, &char_height); |
| |
| if (!fb_lock(terminal->fb)) |
| return; |
| |
| if (fb_stepper_init(&s, terminal->fb, |
| terminal->term->w_in_char * char_width, 0, |
| fb_getwidth(terminal->fb) - terminal->term->w_in_char * char_width, terminal->term->h_in_char * char_height)) { |
| do { |
| do { |
| } while (fb_stepper_step_x(&s, terminal->background)); |
| } while (fb_stepper_step_y(&s)); |
| } |
| |
| if (fb_stepper_init(&s, terminal->fb, |
| 0, terminal->term->h_in_char * char_height, |
| fb_getwidth(terminal->fb), fb_getheight(terminal->fb) - terminal->term->h_in_char * char_height)) { |
| do { |
| do { |
| } while (fb_stepper_step_x(&s, terminal->background)); |
| } while (fb_stepper_step_y(&s)); |
| } |
| |
| fb_unlock(terminal->fb); |
| } |
| |
| terminal_t* term_init(unsigned vt, int pts_fd) |
| { |
| const int scrollback_size = 200; |
| int status; |
| terminal_t* new_terminal; |
| bool interactive = term_is_interactive(vt); |
| |
| new_terminal = (terminal_t*)calloc(1, sizeof(*new_terminal)); |
| if (!new_terminal) |
| return NULL; |
| |
| new_terminal->vt = vt; |
| new_terminal->background_valid = false; |
| new_terminal->input_enable = true; |
| |
| new_terminal->fb = fb_init(); |
| |
| if (!new_terminal->fb) { |
| LOG(ERROR, "Failed to create fb on VT%u.", vt); |
| 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; |
| } |
| |
| if (interactive) |
| new_terminal->exec = interactive_cmd_line; |
| else |
| new_terminal->exec = NULL; |
| |
| status = tsm_screen_new(&new_terminal->term->screen, |
| log_tsm, new_terminal->term); |
| if (status < 0) { |
| LOG(ERROR, "Failed to create new screen on VT%u.", vt); |
| 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) { |
| LOG(ERROR, "Failed to create new VT%u.", vt); |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| if (command_flags.enable_osc) |
| tsm_vte_set_osc_cb(new_terminal->term->vte, term_osc_cb, (void *)new_terminal); |
| |
| new_terminal->term->pty_bridge = shl_pty_bridge_new(); |
| if (new_terminal->term->pty_bridge < 0) { |
| LOG(ERROR, "Failed to create pty bridge on VT%u.", vt); |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| status = shl_pty_open(&new_terminal->term->pty, |
| term_read_cb, new_terminal, 1, 1, pts_fd); |
| |
| if (status < 0) { |
| LOG(ERROR, "Failed to open pty on VT%u.", vt); |
| term_close(new_terminal); |
| return NULL; |
| } else if (status == 0) { |
| term_run_child(new_terminal); |
| exit(1); |
| } |
| |
| status = mkdir(FRECON_RUN_DIR, S_IRWXU); |
| if (status == 0 || (status < 0 && errno == EEXIST)) { |
| char path[32]; |
| snprintf(path, sizeof(path), FRECON_VT_PATH, vt); |
| unlink(path); /* In case it already exists. Ignore return codes. */ |
| if (symlink(ptsname(shl_pty_get_fd(new_terminal->term->pty)), path) < 0) |
| LOG(ERROR, "Failed to symlink pts name %s to %s, %d:%s", |
| path, |
| ptsname(shl_pty_get_fd(new_terminal->term->pty)), |
| errno, strerror(errno)); |
| } |
| |
| status = shl_pty_bridge_add(new_terminal->term->pty_bridge, new_terminal->term->pty); |
| if (status) { |
| LOG(ERROR, "Failed to add pty bridge on VT%u.", vt); |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| new_terminal->term->pid = shl_pty_get_child(new_terminal->term->pty); |
| |
| status = term_resize(new_terminal, 0); |
| |
| if (status < 0) { |
| LOG(ERROR, "Failed to resize VT%u.", vt); |
| term_close(new_terminal); |
| return NULL; |
| } |
| |
| return new_terminal; |
| } |
| |
| void term_activate(terminal_t* terminal) |
| { |
| term_set_current_to(terminal); |
| terminal->active = true; |
| fb_setmode(terminal->fb); |
| term_redraw(terminal); |
| } |
| |
| void term_deactivate(terminal_t* terminal) |
| { |
| if (!terminal->active) |
| return; |
| |
| terminal->active = false; |
| } |
| |
| void term_close(terminal_t* term) |
| { |
| char path[32]; |
| if (!term) |
| return; |
| |
| snprintf(path, sizeof(path), FRECON_VT_PATH, term->vt); |
| unlink(path); |
| |
| if (term->fb) { |
| fb_close(term->fb); |
| term->fb = NULL; |
| } |
| |
| if (term->term) { |
| if (term->term->pty) { |
| if (term->term->pty_bridge >= 0) { |
| shl_pty_bridge_remove(term->term->pty_bridge, term->term->pty); |
| shl_pty_bridge_free(term->term->pty_bridge); |
| term->term->pty_bridge = -1; |
| } |
| shl_pty_close(term->term->pty); |
| term->term->pty = NULL; |
| } |
| free(term->term); |
| term->term = NULL; |
| } |
| |
| font_free(); |
| free(term); |
| } |
| |
| bool term_is_child_done(terminal_t* terminal) |
| { |
| int status; |
| int ret; |
| ret = waitpid(terminal->term->pid, &status, WNOHANG); |
| |
| if ((ret == -1) && (errno == ECHILD)) { |
| return false; |
| } |
| 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_fds(terminal_t* terminal, fd_set* read_set, fd_set* exception_set, int* maxfd) |
| { |
| if (term_is_valid(terminal)) { |
| if (terminal->term->pty_bridge >= 0) { |
| *maxfd = MAX(*maxfd, terminal->term->pty_bridge); |
| FD_SET(terminal->term->pty_bridge, read_set); |
| FD_SET(terminal->term->pty_bridge, exception_set); |
| } |
| } |
| } |
| |
| const char* term_get_ptsname(terminal_t* terminal) |
| { |
| return ptsname(shl_pty_get_fd(terminal->term->pty)); |
| } |
| |
| void term_set_background(terminal_t* terminal, uint32_t bg) |
| { |
| terminal->background = bg; |
| terminal->background_valid = true; |
| } |
| |
| int term_show_image(terminal_t* terminal, image_t* image) |
| { |
| return image_show(image, terminal->fb); |
| } |
| |
| void term_write_message(terminal_t* terminal, char* message) |
| { |
| FILE* fp; |
| |
| fp = fopen(term_get_ptsname(terminal), "w"); |
| if (fp) { |
| fputs(message, fp); |
| fclose(fp); |
| } |
| } |
| |
| static void term_hide_cursor(terminal_t* terminal) |
| { |
| term_write_message(terminal, "\033[?25l"); |
| } |
| |
| __attribute__ ((unused)) |
| static void term_show_cursor(terminal_t* terminal) |
| { |
| term_write_message(terminal, "\033[?25h"); |
| } |
| |
| fb_t* term_getfb(terminal_t* terminal) |
| { |
| return terminal->fb; |
| } |
| |
| terminal_t* term_get_terminal(int num) |
| { |
| return terminals[num]; |
| } |
| |
| void term_set_terminal(int num, terminal_t* terminal) |
| { |
| terminals[num] = terminal; |
| } |
| |
| int term_create_splash_term(int pts_fd) |
| { |
| terminal_t* terminal = term_init(TERM_SPLASH_TERMINAL, pts_fd); |
| |
| if (!terminal) { |
| LOG(ERROR, "Could not create splash term."); |
| return -1; |
| } |
| term_set_terminal(TERM_SPLASH_TERMINAL, terminal); |
| |
| // Hide the cursor on the splash screen |
| term_hide_cursor(terminal); |
| return 0; |
| } |
| |
| void term_destroy_splash_term(void) |
| { |
| terminal_t *terminal; |
| if (command_flags.enable_vt1) { |
| return; |
| } |
| terminal = term_get_terminal(TERM_SPLASH_TERMINAL); |
| term_set_terminal(TERM_SPLASH_TERMINAL, NULL); |
| term_close(terminal); |
| } |
| |
| void term_set_current(uint32_t t) |
| { |
| if (t >= TERM_MAX_TERMINALS) |
| LOG(ERROR, "set_current: larger than array size"); |
| else |
| if (t >= term_num_terminals) |
| LOG(ERROR, "set_current: larger than num terminals"); |
| else |
| current_terminal = t; |
| } |
| |
| uint32_t term_get_current(void) |
| { |
| return current_terminal; |
| } |
| |
| terminal_t *term_get_current_terminal(void) |
| { |
| return terminals[current_terminal]; |
| } |
| |
| void term_set_current_terminal(terminal_t* terminal) |
| { |
| terminals[current_terminal] = terminal; |
| } |
| |
| void term_set_current_to(terminal_t* terminal) |
| { |
| if (!terminal) { |
| if (terminals[current_terminal]) |
| term_close(terminals[current_terminal]); |
| terminals[current_terminal] = NULL; |
| current_terminal = 0; |
| return; |
| } |
| |
| for (unsigned i = 0; i < term_num_terminals; i++) { |
| if (terminal == terminals[i]) { |
| current_terminal = i; |
| return; |
| } |
| } |
| LOG(ERROR, "set_current_to: terminal not in array"); |
| } |
| |
| int term_switch_to(unsigned int vt) |
| { |
| terminal_t *terminal; |
| if (vt == term_get_current()) { |
| terminal = term_get_current_terminal(); |
| if (term_is_valid(terminal)) { |
| if (!term_is_active(terminal)) |
| term_activate(terminal); |
| return vt; |
| } |
| } |
| |
| if (vt >= term_num_terminals) |
| return -EINVAL; |
| |
| terminal = term_get_current_terminal(); |
| if (term_is_active(terminal)) |
| term_deactivate(terminal); |
| |
| if (vt == TERM_SPLASH_TERMINAL |
| && !term_get_terminal(TERM_SPLASH_TERMINAL) |
| && !command_flags.enable_vt1) { |
| term_set_current(vt); |
| /* Splash term is already gone, returning to Chrome. */ |
| term_background(false); |
| return vt; |
| } |
| |
| term_foreground(); |
| |
| term_set_current(vt); |
| terminal = term_get_current_terminal(); |
| if (!terminal) { |
| /* No terminal where we are switching to, create new one. */ |
| term_set_current_terminal(term_init(vt, -1)); |
| terminal = term_get_current_terminal(); |
| if (!term_is_valid(terminal)) { |
| LOG(ERROR, "Term init failed VT%u.", vt); |
| return -1; |
| } |
| term_activate(terminal); |
| } else { |
| term_activate(terminal); |
| } |
| |
| return vt; |
| } |
| |
| void term_monitor_hotplug(void) |
| { |
| unsigned int t; |
| |
| if (in_background) { |
| hotplug_occured = true; |
| return; |
| } |
| |
| if (!drm_rescan()) |
| return; |
| |
| for (t = 0; t < term_num_terminals; t++) { |
| if (!terminals[t]) |
| continue; |
| if (!terminals[t]->fb) |
| continue; |
| fb_buffer_destroy(terminals[t]->fb); |
| font_free(); |
| } |
| |
| for (t = 0; t < term_num_terminals; t++) { |
| if (!terminals[t]) |
| continue; |
| if (!terminals[t]->fb) |
| continue; |
| fb_buffer_init(terminals[t]->fb); |
| term_resize(terminals[t], 0); |
| if (current_terminal == t && terminals[t]->active) |
| fb_setmode(terminals[t]->fb); |
| terminals[t]->term->age = 0; |
| term_redraw(terminals[t]); |
| } |
| } |
| |
| void term_redrm(terminal_t* terminal) |
| { |
| fb_buffer_destroy(terminal->fb); |
| font_free(); |
| fb_buffer_init(terminal->fb); |
| term_resize(terminal, 0); |
| terminal->term->age = 0; |
| term_redraw(terminal); |
| } |
| |
| void term_clear(terminal_t* terminal) |
| { |
| term_clear_border(terminal); |
| tsm_screen_erase_screen(terminal->term->screen, false); |
| term_redraw(terminal); |
| } |
| |
| void term_zoom(bool zoom_in) |
| { |
| int scaling = font_get_scaling(); |
| if (zoom_in && scaling < 4) |
| scaling++; |
| else if (!zoom_in && scaling > 1) |
| scaling--; |
| else |
| return; |
| |
| unsigned int t; |
| for (t = 0; t < term_num_terminals; t++) { |
| if (terminals[t]) |
| font_free(); |
| } |
| for (t = 0; t < term_num_terminals; t++) { |
| terminal_t* term = terminals[t]; |
| if (term) { |
| term_resize(term, scaling); |
| term->term->age = 0; |
| term_redraw(term); |
| } |
| } |
| } |
| |
| /* |
| * Put frecon in background. Give up DRM master. |
| * onetry - if true, do not retry to notify Chrome multiple times. For use at |
| * time when Chrome may be not around yet to receive the message. |
| */ |
| void term_background(bool onetry) |
| { |
| int retry = onetry ? 1 : 5; |
| if (in_background) |
| return; |
| in_background = true; |
| drm_dropmaster(NULL); |
| while (!dbus_take_display_ownership() && retry--) { |
| if (onetry) |
| break; |
| LOG(ERROR, "Chrome failed to take display ownership. %s", |
| retry ? "Trying again." : "Giving up, Chrome is probably dead."); |
| if (retry > 0) |
| usleep(500 * 1000); |
| } |
| } |
| |
| void term_foreground(void) |
| { |
| int ret; |
| int retry = 5; |
| |
| if (!in_background) |
| return; |
| in_background = false; |
| |
| /* LOG(INFO, "TIMING: Console switch time start."); */ /* Keep around for timing it in the future. */ |
| while (!dbus_release_display_ownership() && retry--) { |
| LOG(ERROR, "Chrome did not release master. %s", |
| retry ? "Trying again." : "Frecon will steal master."); |
| if (retry > 0) |
| usleep(500 * 1000); |
| } |
| |
| /* LOG(INFO, "TIMING: Console switch setmaster."); */ |
| ret = drm_setmaster(NULL); |
| if (ret < 0) |
| LOG(ERROR, "Could not set master when switching to foreground %m."); |
| |
| if (hotplug_occured) { |
| hotplug_occured = false; |
| term_monitor_hotplug(); |
| } |
| } |
| |
| void term_suspend_done(void* ignore) |
| { |
| term_monitor_hotplug(); |
| } |
| |
| void term_input_enable(terminal_t* terminal, bool input_enable) |
| { |
| terminal->input_enable = input_enable; |
| } |