blob: 68e6601b7ef7f90392ce11cf6c531b991440ab1e [file] [log] [blame]
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -07001/*
2 * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7#include <ctype.h>
8#include <libtsm.h>
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -07009#include <stdio.h>
Dominik Behr93899452014-08-18 22:16:21 -070010#include <sys/select.h>
David Sodmanbf3f2842014-11-12 08:26:58 -080011#include <sys/types.h>
12#include <sys/wait.h>
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070013
Dominik Behr580462b2014-11-20 13:50:05 -080014#include "dbus_interface.h"
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070015#include "font.h"
16#include "input.h"
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070017#include "shl_pty.h"
18#include "term.h"
19#include "util.h"
20#include "video.h"
21
Stéphane Marchesin08c08e72016-01-07 16:44:08 -080022static terminal_t* terminals[MAX_TERMINALS];
23
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070024struct term {
Stéphane Marchesin00ff1872015-12-14 13:40:09 -080025 struct tsm_screen* screen;
26 struct tsm_vte* vte;
27 struct shl_pty* pty;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070028 int pty_bridge;
29 int pid;
30 tsm_age_t age;
31 int char_x, char_y;
32 int pitch;
Stéphane Marchesin00ff1872015-12-14 13:40:09 -080033 uint32_t* dst_image;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070034};
35
David Sodmanf0a925a2015-05-04 11:19:19 -070036struct _terminal_t {
Stéphane Marchesin00ff1872015-12-14 13:40:09 -080037 uint32_t background;
38 bool background_valid;
39 video_t* video;
40 dbus_t* dbus;
41 struct term* term;
42 bool active;
43 char** exec;
David Sodmanf0a925a2015-05-04 11:19:19 -070044};
45
David Sodman8ef20062015-01-06 09:23:40 -080046
Stéphane Marchesin00ff1872015-12-14 13:40:09 -080047static char* interactive_cmd_line[] = {
David Sodman8ef20062015-01-06 09:23:40 -080048 "/sbin/agetty",
49 "-",
50 "9600",
51 "xterm",
52 NULL
53};
54
55
Stéphane Marchesin00ff1872015-12-14 13:40:09 -080056static char* noninteractive_cmd_line[] = {
David Sodman8ef20062015-01-06 09:23:40 -080057 "/bin/cat",
58 NULL
59};
60
61
62static void __attribute__ ((noreturn)) term_run_child(terminal_t* terminal)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070063{
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070064 /* XXX figure out how to fix "top" for xterm-256color */
65 setenv("TERM", "xterm", 1);
David Sodman8ef20062015-01-06 09:23:40 -080066 execve(terminal->exec[0], terminal->exec, environ);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070067 exit(1);
68}
69
Stéphane Marchesin00ff1872015-12-14 13:40:09 -080070static int term_draw_cell(struct tsm_screen* screen, uint32_t id,
Stéphane Marchesin8fc13522015-12-14 17:02:28 -080071 const uint32_t* ch, size_t len,
72 unsigned int cwidth, unsigned int posx,
73 unsigned int posy,
74 const struct tsm_screen_attr* attr,
75 tsm_age_t age, void* data)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070076{
Stéphane Marchesin00ff1872015-12-14 13:40:09 -080077 terminal_t* terminal = (terminal_t*)data;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070078 uint32_t front_color, back_color;
David Sodmanf0a925a2015-05-04 11:19:19 -070079 uint8_t br, bb, bg;
Stéphane Marchesinedece332015-12-14 15:10:58 -080080 uint32_t luminance;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070081
David Sodmanbbcb0522014-09-19 10:34:07 -070082 if (age && terminal->term->age && age <= terminal->term->age)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -070083 return 0;
84
David Sodmanf0a925a2015-05-04 11:19:19 -070085 if (terminal->background_valid) {
86 br = (terminal->background >> 16) & 0xFF;
87 bg = (terminal->background >> 8) & 0xFF;
88 bb = (terminal->background) & 0xFF;
Stéphane Marchesinedece332015-12-14 15:10:58 -080089 luminance = (3 * br + bb + 4 * bg) >> 3;
David Sodmanf0a925a2015-05-04 11:19:19 -070090
Stéphane Marchesinedece332015-12-14 15:10:58 -080091 /*
92 * FIXME: black is chosen on a dark background, but it uses the
93 * default color for light backgrounds
94 */
95 if (luminance > 128) {
David Sodmanf0a925a2015-05-04 11:19:19 -070096 front_color = 0;
97 back_color = terminal->background;
98 } else {
99 front_color = (attr->fr << 16) | (attr->fg << 8) | attr->fb;
100 back_color = terminal->background;
101 }
102 } else {
103 front_color = (attr->fr << 16) | (attr->fg << 8) | attr->fb;
104 back_color = (attr->br << 16) | (attr->bg << 8) | attr->bb;
105 }
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700106
107 if (attr->inverse) {
108 uint32_t tmp = front_color;
109 front_color = back_color;
110 back_color = tmp;
111 }
112
113 if (len)
David Sodmanbbcb0522014-09-19 10:34:07 -0700114 font_render(terminal->term->dst_image, posx, posy, terminal->term->pitch, *ch,
115 front_color, back_color);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700116 else
David Sodmanbbcb0522014-09-19 10:34:07 -0700117 font_fillchar(terminal->term->dst_image, posx, posy, terminal->term->pitch,
118 front_color, back_color);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700119
120 return 0;
121}
122
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800123void term_redraw(terminal_t* terminal)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700124{
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800125 uint32_t* video_buffer;
David Sodmanbbcb0522014-09-19 10:34:07 -0700126 video_buffer = video_lock(terminal->video);
127 if (video_buffer != NULL) {
128 terminal->term->dst_image = video_buffer;
129 terminal->term->age =
130 tsm_screen_draw(terminal->term->screen, term_draw_cell, terminal);
131 video_unlock(terminal->video);
132 }
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700133}
134
David Sodmanbbcb0522014-09-19 10:34:07 -0700135void term_key_event(terminal_t* terminal, uint32_t keysym, int32_t unicode)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700136{
David Sodmanbbcb0522014-09-19 10:34:07 -0700137 if (tsm_vte_handle_keyboard(terminal->term->vte, keysym, 0, 0, unicode))
138 tsm_screen_sb_reset(terminal->term->screen);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700139
David Sodmanbbcb0522014-09-19 10:34:07 -0700140 term_redraw(terminal);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700141}
142
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800143static void term_read_cb(struct shl_pty* pty, char* u8, size_t len, void* data)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700144{
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800145 terminal_t* terminal = (terminal_t*)data;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700146
David Sodmanbbcb0522014-09-19 10:34:07 -0700147 tsm_vte_input(terminal->term->vte, u8, len);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700148
David Sodmanbbcb0522014-09-19 10:34:07 -0700149 term_redraw(terminal);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700150}
151
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800152static void term_write_cb(struct tsm_vte* vte, const char* u8, size_t len,
153 void* data)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700154{
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800155 struct term* term = data;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700156 int r;
157
158 r = shl_pty_write(term->pty, u8, len);
159 if (r < 0)
David Sodmanbbcb0522014-09-19 10:34:07 -0700160 LOG(ERROR, "OOM in pty-write (%d)", r);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700161
162 shl_pty_dispatch(term->pty);
163}
164
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800165static const char* sev2str_table[] = {
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700166 "FATAL",
167 "ALERT",
168 "CRITICAL",
169 "ERROR",
170 "WARNING",
171 "NOTICE",
172 "INFO",
173 "DEBUG"
174};
175
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800176static const char* sev2str(unsigned int sev)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700177{
178 if (sev > 7)
179 return "DEBUG";
180
181 return sev2str_table[sev];
182}
183
Dominik Behr00003502014-08-15 16:42:37 -0700184#ifdef __clang__
185__attribute__((__format__ (__printf__, 7, 0)))
186#endif
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800187static void log_tsm(void* data, const char* file, int line, const char* fn,
Stéphane Marchesin8fc13522015-12-14 17:02:28 -0800188 const char* subs, unsigned int sev, const char* format,
189 va_list args)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700190{
191 fprintf(stderr, "%s: %s: ", sev2str(sev), subs);
192 vfprintf(stderr, format, args);
193 fprintf(stderr, "\n");
194}
195
Stéphane Marchesin78310662016-01-06 16:09:39 -0800196static int term_resize(terminal_t* term)
197{
198 uint32_t char_width, char_height;
199 int status;
200
201 font_init(video_getscaling(term->video));
202 font_get_size(&char_width, &char_height);
203
204 term->term->char_x = video_getwidth(term->video) / char_width;
205 term->term->char_y = video_getheight(term->video) / char_height;
206 term->term->pitch = video_getpitch(term->video);
207
208 status = tsm_screen_resize(term->term->screen,
209 term->term->char_x, term->term->char_y);
210 if (status < 0) {
211 font_free();
212 return -1;
213 }
214
215 status = shl_pty_resize(term->term->pty, term->term->char_x,
216 term->term->char_y);
217 if (status < 0) {
218 font_free();
219 return -1;
220 }
221
222 return 0;
223}
224
David Sodmanf0a925a2015-05-04 11:19:19 -0700225terminal_t* term_init(bool interactive, video_t* video)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700226{
227 const int scrollback_size = 200;
David Sodmanbbcb0522014-09-19 10:34:07 -0700228 int status;
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800229 terminal_t* new_terminal;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700230
David Sodmanbbcb0522014-09-19 10:34:07 -0700231 new_terminal = (terminal_t*)calloc(1, sizeof(*new_terminal));
David Sodmanbf3f2842014-11-12 08:26:58 -0800232 if (!new_terminal)
233 return NULL;
David Sodmanbbcb0522014-09-19 10:34:07 -0700234
David Sodmanf0a925a2015-05-04 11:19:19 -0700235 new_terminal->background_valid = false;
236
David Sodman30a94ef2015-07-26 17:37:12 -0700237 if (video) {
David Sodmanf0a925a2015-05-04 11:19:19 -0700238 new_terminal->video = video;
David Sodman30a94ef2015-07-26 17:37:12 -0700239 video_addref(video);
240 }
David Sodmanf0a925a2015-05-04 11:19:19 -0700241 else
242 new_terminal->video = video_init();
243
David Sodmanbf3f2842014-11-12 08:26:58 -0800244 if (!new_terminal->video) {
245 term_close(new_terminal);
246 return NULL;
247 }
248
249 new_terminal->term = (struct term*)calloc(1, sizeof(*new_terminal->term));
250 if (!new_terminal->term) {
251 term_close(new_terminal);
252 return NULL;
253 }
254
David Sodman8ef20062015-01-06 09:23:40 -0800255 if (interactive)
256 new_terminal->exec = interactive_cmd_line;
257 else
258 new_terminal->exec = noninteractive_cmd_line;
259
David Sodmanbbcb0522014-09-19 10:34:07 -0700260 status = tsm_screen_new(&new_terminal->term->screen,
261 log_tsm, new_terminal->term);
zhuo-hao471f1e22015-08-12 14:55:56 +0800262 if (status < 0) {
David Sodmanbbcb0522014-09-19 10:34:07 -0700263 term_close(new_terminal);
264 return NULL;
265 }
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700266
David Sodmanbbcb0522014-09-19 10:34:07 -0700267 tsm_screen_set_max_sb(new_terminal->term->screen, scrollback_size);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700268
David Sodmanbbcb0522014-09-19 10:34:07 -0700269 status = tsm_vte_new(&new_terminal->term->vte, new_terminal->term->screen,
270 term_write_cb, new_terminal->term, log_tsm, new_terminal->term);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700271
David Sodmanbbcb0522014-09-19 10:34:07 -0700272 if (status < 0) {
273 term_close(new_terminal);
274 return NULL;
275 }
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700276
David Sodmanbbcb0522014-09-19 10:34:07 -0700277 new_terminal->term->pty_bridge = shl_pty_bridge_new();
278 if (new_terminal->term->pty_bridge < 0) {
279 term_close(new_terminal);
280 return NULL;
281 }
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700282
David Sodmanbbcb0522014-09-19 10:34:07 -0700283 status = shl_pty_open(&new_terminal->term->pty,
Stéphane Marchesin78310662016-01-06 16:09:39 -0800284 term_read_cb, new_terminal, 1, 1);
David Sodman8ef20062015-01-06 09:23:40 -0800285
David Sodmanbbcb0522014-09-19 10:34:07 -0700286 if (status < 0) {
287 term_close(new_terminal);
288 return NULL;
289 } else if (status == 0) {
David Sodman8ef20062015-01-06 09:23:40 -0800290 term_run_child(new_terminal);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700291 exit(1);
292 }
293
David Sodmanbbcb0522014-09-19 10:34:07 -0700294 status = shl_pty_bridge_add(new_terminal->term->pty_bridge, new_terminal->term->pty);
295 if (status) {
296 shl_pty_close(new_terminal->term->pty);
297 term_close(new_terminal);
298 return NULL;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700299 }
300
David Sodmanbbcb0522014-09-19 10:34:07 -0700301 new_terminal->term->pid = shl_pty_get_child(new_terminal->term->pty);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700302
Stéphane Marchesin78310662016-01-06 16:09:39 -0800303 status = term_resize(new_terminal);
David Sodmanbbcb0522014-09-19 10:34:07 -0700304 if (status < 0) {
305 shl_pty_close(new_terminal->term->pty);
306 term_close(new_terminal);
307 return NULL;
308 }
David Sodman8ef20062015-01-06 09:23:40 -0800309
310 if (interactive)
311 new_terminal->active = true;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700312
David Sodmanbbcb0522014-09-19 10:34:07 -0700313 return new_terminal;
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700314}
315
David Sodman8ef20062015-01-06 09:23:40 -0800316void term_activate(terminal_t* terminal)
317{
David Sodman8ef20062015-01-06 09:23:40 -0800318 input_set_current(terminal);
319 terminal->active = true;
320 video_setmode(terminal->video);
321 term_redraw(terminal);
322}
323
David Sodmanf0a925a2015-05-04 11:19:19 -0700324void term_deactivate(terminal_t* terminal)
325{
326 if (!terminal->active)
327 return;
328
David Sodmanf0a925a2015-05-04 11:19:19 -0700329 terminal->active = false;
330 video_release(terminal->video);
331}
332
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800333void term_set_dbus(terminal_t* term, dbus_t* dbus)
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700334{
David Sodmanbbcb0522014-09-19 10:34:07 -0700335 term->dbus = dbus;
336}
337
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800338void term_close(terminal_t* term)
David Sodmanbbcb0522014-09-19 10:34:07 -0700339{
340 if (!term)
341 return;
342
David Sodmanbf3f2842014-11-12 08:26:58 -0800343 if (term->video) {
344 video_close(term->video);
345 term->video = NULL;
346 }
347
David Sodmanbbcb0522014-09-19 10:34:07 -0700348 if (term->term) {
349 free(term->term);
350 term->term = NULL;
351 }
352
Haixia Shi95d680e2015-04-27 20:29:17 -0700353 font_free();
David Sodmanbbcb0522014-09-19 10:34:07 -0700354 free(term);
Stéphane Marchesinae37e6c2014-08-08 18:19:40 -0700355}
David Sodmanbf3f2842014-11-12 08:26:58 -0800356
357bool term_is_child_done(terminal_t* terminal)
358{
359 int status;
360 int ret;
361 ret = waitpid(terminal->term->pid, &status, WNOHANG);
362
David Sodmanf0a925a2015-05-04 11:19:19 -0700363 if ((ret == -1) && (errno == ECHILD)) {
364 return false;
365 }
David Sodmanbf3f2842014-11-12 08:26:58 -0800366 return ret != 0;
367}
368
369void term_page_up(terminal_t* terminal)
370{
371 tsm_screen_sb_page_up(terminal->term->screen, 1);
372 term_redraw(terminal);
373}
374
375void term_page_down(terminal_t* terminal)
376{
377 tsm_screen_sb_page_down(terminal->term->screen, 1);
378 term_redraw(terminal);
379}
380
381void term_line_up(terminal_t* terminal)
382{
383 tsm_screen_sb_up(terminal->term->screen, 1);
384 term_redraw(terminal);
385}
386
387void term_line_down(terminal_t* terminal)
388{
389 tsm_screen_sb_down(terminal->term->screen, 1);
390 term_redraw(terminal);
391}
392
393bool term_is_valid(terminal_t* terminal)
394{
395 return ((terminal != NULL) && (terminal->term != NULL));
396}
397
398int term_fd(terminal_t* terminal)
399{
400 if (term_is_valid(terminal))
401 return terminal->term->pty_bridge;
402 else
403 return -1;
404}
405
406void term_dispatch_io(terminal_t* terminal, fd_set* read_set)
407{
408 if (term_is_valid(terminal))
409 if (FD_ISSET(terminal->term->pty_bridge, read_set))
410 shl_pty_bridge_dispatch(terminal->term->pty_bridge, 0);
411}
412
413bool term_exception(terminal_t* terminal, fd_set* exception_set)
414{
415 if (term_is_valid(terminal)) {
416 if (terminal->term->pty_bridge >= 0) {
417 return FD_ISSET(terminal->term->pty_bridge,
418 exception_set);
419 }
420 }
421
422 return false;
423}
424
425bool term_is_active(terminal_t* terminal)
426{
427 if (term_is_valid(terminal))
428 return terminal->active;
429
430 return false;
431}
432
433void term_add_fd(terminal_t* terminal, fd_set* read_set, fd_set* exception_set)
434{
435 if (term_is_valid(terminal)) {
436 if (terminal->term->pty_bridge >= 0) {
437 FD_SET(terminal->term->pty_bridge, read_set);
438 FD_SET(terminal->term->pty_bridge, exception_set);
439 }
440 }
441}
David Sodman8ef20062015-01-06 09:23:40 -0800442
443const char* term_get_ptsname(terminal_t* terminal)
444{
445 return ptsname(shl_pty_get_fd(terminal->term->pty));
446}
David Sodmanf0a925a2015-05-04 11:19:19 -0700447
448void term_set_background(terminal_t* terminal, uint32_t bg)
449{
450 terminal->background = bg;
451 terminal->background_valid = true;
452}
453
454int term_show_image(terminal_t* terminal, image_t* image)
455{
456 return image_show(image, terminal->video);
457}
458
459void term_write_message(terminal_t* terminal, char* message)
460{
Stéphane Marchesin00ff1872015-12-14 13:40:09 -0800461 FILE* fp;
David Sodmanf0a925a2015-05-04 11:19:19 -0700462
463 fp = fopen(term_get_ptsname(terminal), "w");
464 if (fp) {
465 fputs(message, fp);
466 fclose(fp);
467 }
468}
469
470void term_hide_cursor(terminal_t* terminal)
471{
472 term_write_message(terminal, "\033[?25l");
473}
474
475void term_show_cursor(terminal_t* terminal)
476{
477 term_write_message(terminal, "\033[?25h");
478}
David Sodman30a94ef2015-07-26 17:37:12 -0700479
480video_t* term_getvideo(terminal_t* terminal)
481{
482 return terminal->video;
483}
Stéphane Marchesin08c08e72016-01-07 16:44:08 -0800484
485terminal_t* term_get_terminal(int num)
486{
487 return terminals[num];
488}
489
490void term_set_terminal(int num, terminal_t* terminal)
491{
492 terminals[num] = terminal;
493}