wlt: fix shl_hook API changes
[platform/upstream/kmscon.git] / src / kmscon_terminal.c
1 /*
2  * kmscon - Terminal
3  *
4  * Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
5  * Copyright (c) 2011 University of Tuebingen
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files
9  * (the "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included
16  * in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  */
26
27 /*
28  * Terminal
29  * A terminal gets assigned an input stream and several output objects and then
30  * runs a fully functional terminal emulation on it.
31  */
32
33 #include <errno.h>
34 #include <inttypes.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include "conf.h"
38 #include "eloop.h"
39 #include "kmscon_conf.h"
40 #include "kmscon_seat.h"
41 #include "kmscon_terminal.h"
42 #include "log.h"
43 #include "pty.h"
44 #include "shl_dlist.h"
45 #include "text.h"
46 #include "tsm_screen.h"
47 #include "tsm_vte.h"
48 #include "uterm_input.h"
49 #include "uterm_video.h"
50
51 #define LOG_SUBSYSTEM "terminal"
52
53 struct screen {
54         struct shl_dlist list;
55         struct kmscon_terminal *term;
56         struct uterm_display *disp;
57         struct kmscon_font *font;
58         struct kmscon_font *bold_font;
59         struct kmscon_text *txt;
60
61         bool swapping;
62         bool pending;
63 };
64
65 struct kmscon_terminal {
66         unsigned long ref;
67         struct ev_eloop *eloop;
68         struct uterm_input *input;
69         bool opened;
70         bool awake;
71
72         struct conf_ctx *conf_ctx;
73         struct kmscon_conf_t *conf;
74         struct kmscon_session *session;
75
76         struct shl_dlist screens;
77         unsigned int min_cols;
78         unsigned int min_rows;
79
80         struct tsm_screen *console;
81         struct tsm_vte *vte;
82         struct kmscon_pty *pty;
83         struct ev_fd *ptyfd;
84 };
85
86 static void do_redraw_screen(struct screen *scr)
87 {
88         int ret;
89
90         if (!scr->term->awake)
91                 return;
92
93         scr->pending = false;
94         tsm_screen_draw(scr->term->console, kmscon_text_prepare_cb,
95                         kmscon_text_draw_cb, kmscon_text_render_cb, scr->txt);
96         ret = uterm_display_swap(scr->disp, false);
97         if (ret) {
98                 log_warning("cannot swap display %p", scr->disp);
99                 return;
100         }
101
102         scr->swapping = true;
103 }
104
105 static void redraw_screen(struct screen *scr)
106 {
107         if (!scr->term->awake)
108                 return;
109
110         if (scr->swapping)
111                 scr->pending = true;
112         else
113                 do_redraw_screen(scr);
114 }
115
116 static void redraw_all(struct kmscon_terminal *term)
117 {
118         struct shl_dlist *iter;
119         struct screen *scr;
120
121         if (!term->awake)
122                 return;
123
124         shl_dlist_for_each(iter, &term->screens) {
125                 scr = shl_dlist_entry(iter, struct screen, list);
126                 redraw_screen(scr);
127         }
128 }
129
130 static void redraw_all_test(struct kmscon_terminal *term)
131 {
132         struct shl_dlist *iter;
133         struct screen *scr;
134
135         if (!term->awake)
136                 return;
137
138         shl_dlist_for_each(iter, &term->screens) {
139                 scr = shl_dlist_entry(iter, struct screen, list);
140                 if (uterm_display_is_swapping(scr->disp))
141                         scr->swapping = true;
142                 redraw_screen(scr);
143         }
144 }
145
146 static void display_event(struct uterm_display *disp,
147                           struct uterm_display_event *ev, void *data)
148 {
149         struct screen *scr = data;
150
151         if (ev->action != UTERM_PAGE_FLIP)
152                 return;
153
154         scr->swapping = false;
155         if (scr->pending)
156                 do_redraw_screen(scr);
157 }
158
159 /*
160  * Resize terminal
161  * We support multiple monitors per terminal. As some software-rendering
162  * backends to not support scaling, we always use the smalles cols/rows that are
163  * provided so wider displays will have black margins.
164  * This can be extended to support scaling but that would mean we need to check
165  * whether the text-renderer backend supports that, first (TODO).
166  *
167  * If @force is true, then the console/pty are notified even though the size did
168  * not changed. If @notify is false, then console/pty are not notified even
169  * though the size might have changed. force = true and notify = false doesn't
170  * make any sense, though.
171  */
172 static void terminal_resize(struct kmscon_terminal *term,
173                             unsigned int cols, unsigned int rows,
174                             bool force, bool notify)
175 {
176         bool resize = false;
177
178         if (!term->min_cols || (cols > 0 && cols < term->min_cols)) {
179                 term->min_cols = cols;
180                 resize = true;
181         }
182         if (!term->min_rows || (rows > 0 && rows < term->min_rows)) {
183                 term->min_rows = rows;
184                 resize = true;
185         }
186
187         if (!notify || (!resize && !force))
188                 return;
189
190         /* shrinking always succeeds */
191         tsm_screen_resize(term->console, term->min_cols, term->min_rows);
192         kmscon_pty_resize(term->pty, term->min_cols, term->min_rows);
193         redraw_all(term);
194 }
195
196 static int add_display(struct kmscon_terminal *term, struct uterm_display *disp)
197 {
198         struct shl_dlist *iter;
199         struct screen *scr;
200         int ret;
201         unsigned int cols, rows;
202         struct kmscon_font_attr attr = { "", 0, 20, false, false, 0, 0 };
203         const char *be;
204         bool opengl;
205
206         attr.ppi = term->conf->font_ppi;
207         attr.points = term->conf->font_size;
208         strncpy(attr.name, term->conf->font_name, KMSCON_FONT_MAX_NAME - 1);
209         attr.name[KMSCON_FONT_MAX_NAME - 1] = 0;
210
211         shl_dlist_for_each(iter, &term->screens) {
212                 scr = shl_dlist_entry(iter, struct screen, list);
213                 if (scr->disp == disp)
214                         return 0;
215         }
216
217         scr = malloc(sizeof(*scr));
218         if (!scr) {
219                 log_error("cannot allocate memory for display %p", disp);
220                 return -ENOMEM;
221         }
222         memset(scr, 0, sizeof(*scr));
223         scr->term = term;
224         scr->disp = disp;
225
226         ret = uterm_display_register_cb(scr->disp, display_event, scr);
227         if (ret) {
228                 log_error("cannot register display callback: %d", ret);
229                 goto err_free;
230         }
231
232         ret = kmscon_font_find(&scr->font, &attr, term->conf->font_engine);
233         if (ret) {
234                 log_error("cannot create font");
235                 goto err_cb;
236         }
237
238         attr.bold = true;
239         ret = kmscon_font_find(&scr->bold_font, &attr, term->conf->font_engine);
240         if (ret) {
241                 log_error("cannot create bold font");
242                 scr->bold_font = scr->font;
243                 kmscon_font_ref(scr->bold_font);
244         }
245
246         ret = uterm_display_use(scr->disp, &opengl);
247         if (term->conf->render_engine)
248                 be = term->conf->render_engine;
249         else if (ret >= 0 && opengl)
250                 be = "gltex";
251         else
252                 be = "bbulk";
253
254         ret = kmscon_text_new(&scr->txt, be);
255         if (ret) {
256                 log_error("cannot create text-renderer");
257                 goto err_font;
258         }
259
260         ret = kmscon_text_set(scr->txt, scr->font, scr->bold_font, scr->disp);
261         if (ret) {
262                 log_error("cannot set text-renderer parameters");
263                 goto err_text;
264         }
265
266         cols = kmscon_text_get_cols(scr->txt);
267         rows = kmscon_text_get_rows(scr->txt);
268         terminal_resize(term, cols, rows, false, true);
269
270         shl_dlist_link(&term->screens, &scr->list);
271
272         log_debug("added display %p to terminal %p", disp, term);
273         redraw_screen(scr);
274         uterm_display_ref(scr->disp);
275         return 0;
276
277 err_text:
278         kmscon_text_unref(scr->txt);
279 err_font:
280         kmscon_font_unref(scr->bold_font);
281         kmscon_font_unref(scr->font);
282 err_cb:
283         uterm_display_unregister_cb(scr->disp, display_event, scr);
284 err_free:
285         free(scr);
286         return ret;
287 }
288
289 static void free_screen(struct screen *scr, bool update)
290 {
291         struct shl_dlist *iter;
292         struct screen *ent;
293         struct kmscon_terminal *term = scr->term;
294
295         log_debug("destroying terminal screen %p", scr);
296         shl_dlist_unlink(&scr->list);
297         kmscon_text_unref(scr->txt);
298         kmscon_font_unref(scr->bold_font);
299         kmscon_font_unref(scr->font);
300         uterm_display_unregister_cb(scr->disp, display_event, scr);
301         uterm_display_unref(scr->disp);
302         free(scr);
303
304         if (!update)
305                 return;
306
307         term->min_cols = 0;
308         term->min_rows = 0;
309         shl_dlist_for_each(iter, &term->screens) {
310                 ent = shl_dlist_entry(iter, struct screen, list);
311                 terminal_resize(term,
312                                 kmscon_text_get_cols(ent->txt),
313                                 kmscon_text_get_rows(ent->txt),
314                                 false, false);
315         }
316
317         terminal_resize(term, 0, 0, true, true);
318 }
319
320 static void rm_display(struct kmscon_terminal *term, struct uterm_display *disp)
321 {
322         struct shl_dlist *iter;
323         struct screen *scr;
324
325         shl_dlist_for_each(iter, &term->screens) {
326                 scr = shl_dlist_entry(iter, struct screen, list);
327                 if (scr->disp == disp)
328                         break;
329         }
330
331         if (iter == &term->screens)
332                 return;
333
334         log_debug("removed display %p from terminal %p", disp, term);
335         free_screen(scr, true);
336 }
337
338 static void input_event(struct uterm_input *input,
339                         struct uterm_input_event *ev,
340                         void *data)
341 {
342         struct kmscon_terminal *term = data;
343
344         if (!term->opened || !term->awake || ev->handled)
345                 return;
346
347         if (conf_grab_matches(term->conf->grab_scroll_up,
348                               ev->mods, ev->num_syms, ev->keysyms)) {
349                 tsm_screen_sb_up(term->console, 1);
350                 redraw_all(term);
351                 ev->handled = true;
352                 return;
353         }
354         if (conf_grab_matches(term->conf->grab_scroll_down,
355                               ev->mods, ev->num_syms, ev->keysyms)) {
356                 tsm_screen_sb_down(term->console, 1);
357                 redraw_all(term);
358                 ev->handled = true;
359                 return;
360         }
361         if (conf_grab_matches(term->conf->grab_page_up,
362                               ev->mods, ev->num_syms, ev->keysyms)) {
363                 tsm_screen_sb_page_up(term->console, 1);
364                 redraw_all(term);
365                 ev->handled = true;
366                 return;
367         }
368         if (conf_grab_matches(term->conf->grab_page_down,
369                               ev->mods, ev->num_syms, ev->keysyms)) {
370                 tsm_screen_sb_page_down(term->console, 1);
371                 redraw_all(term);
372                 ev->handled = true;
373                 return;
374         }
375
376         /* TODO: xkbcommon supports multiple keysyms, but it is currently
377          * unclear how this feature will be used. There is no keymap, which
378          * uses this, yet. */
379         if (ev->num_syms > 1)
380                 return;
381
382         if (tsm_vte_handle_keyboard(term->vte, ev->keysyms[0], ev->ascii,
383                                     ev->mods, ev->codepoints[0])) {
384                 tsm_screen_sb_reset(term->console);
385                 redraw_all(term);
386                 ev->handled = true;
387         }
388 }
389
390 static void rm_all_screens(struct kmscon_terminal *term)
391 {
392         struct shl_dlist *iter;
393         struct screen *scr;
394
395         while ((iter = term->screens.next) != &term->screens) {
396                 scr = shl_dlist_entry(iter, struct screen, list);
397                 free_screen(scr, false);
398         }
399
400         term->min_cols = 0;
401         term->min_rows = 0;
402 }
403
404 static int terminal_open(struct kmscon_terminal *term)
405 {
406         int ret;
407         unsigned short width, height;
408
409         if (term->opened)
410                 return -EALREADY;
411
412         tsm_vte_hard_reset(term->vte);
413         width = tsm_screen_get_width(term->console);
414         height = tsm_screen_get_height(term->console);
415         ret = kmscon_pty_open(term->pty, width, height);
416         if (ret)
417                 return ret;
418
419         term->opened = true;
420         redraw_all(term);
421         return 0;
422 }
423
424 static void terminal_close(struct kmscon_terminal *term)
425 {
426         kmscon_pty_close(term->pty);
427         term->opened = false;
428 }
429
430 static void terminal_destroy(struct kmscon_terminal *term)
431 {
432         log_debug("free terminal object %p", term);
433
434         terminal_close(term);
435         rm_all_screens(term);
436         uterm_input_unregister_cb(term->input, input_event, term);
437         ev_eloop_rm_fd(term->ptyfd);
438         kmscon_pty_unref(term->pty);
439         tsm_vte_unref(term->vte);
440         tsm_screen_unref(term->console);
441         uterm_input_unref(term->input);
442         ev_eloop_unref(term->eloop);
443         free(term);
444 }
445
446 static int session_event(struct kmscon_session *session,
447                          struct kmscon_session_event *ev, void *data)
448 {
449         struct kmscon_terminal *term = data;
450
451         switch (ev->type) {
452         case KMSCON_SESSION_DISPLAY_NEW:
453                 add_display(term, ev->disp);
454                 break;
455         case KMSCON_SESSION_DISPLAY_GONE:
456                 rm_display(term, ev->disp);
457                 break;
458         case KMSCON_SESSION_ACTIVATE:
459                 term->awake = true;
460                 if (!term->opened)
461                         terminal_open(term);
462                 redraw_all_test(term);
463                 break;
464         case KMSCON_SESSION_DEACTIVATE:
465                 term->awake = false;
466                 break;
467         case KMSCON_SESSION_UNREGISTER:
468                 terminal_destroy(term);
469                 break;
470         }
471
472         return 0;
473 }
474
475 static void pty_input(struct kmscon_pty *pty, const char *u8, size_t len,
476                                                                 void *data)
477 {
478         struct kmscon_terminal *term = data;
479
480         if (!len) {
481                 terminal_close(term);
482                 terminal_open(term);
483         } else {
484                 tsm_vte_input(term->vte, u8, len);
485                 redraw_all(term);
486         }
487 }
488
489 static void pty_event(struct ev_fd *fd, int mask, void *data)
490 {
491         struct kmscon_terminal *term = data;
492
493         kmscon_pty_dispatch(term->pty);
494 }
495
496 static void write_event(struct tsm_vte *vte, const char *u8, size_t len,
497                         void *data)
498 {
499         struct kmscon_terminal *term = data;
500
501         kmscon_pty_write(term->pty, u8, len);
502 }
503
504 int kmscon_terminal_register(struct kmscon_session **out,
505                              struct kmscon_seat *seat)
506 {
507         struct kmscon_terminal *term;
508         int ret;
509
510         if (!out || !seat)
511                 return -EINVAL;
512
513         term = malloc(sizeof(*term));
514         if (!term)
515                 return -ENOMEM;
516
517         memset(term, 0, sizeof(*term));
518         term->ref = 1;
519         term->eloop = kmscon_seat_get_eloop(seat);
520         term->input = kmscon_seat_get_input(seat);
521         shl_dlist_init(&term->screens);
522
523         term->conf_ctx = kmscon_seat_get_conf(seat);
524         term->conf = conf_ctx_get_mem(term->conf_ctx);
525
526         ret = tsm_screen_new(&term->console, log_llog);
527         if (ret)
528                 goto err_free;
529         tsm_screen_set_max_sb(term->console, term->conf->sb_size);
530         if (term->conf->render_timing)
531                 tsm_screen_set_opts(term->console,
532                                     TSM_SCREEN_OPT_RENDER_TIMING);
533
534         ret = tsm_vte_new(&term->vte, term->console, write_event, term,
535                           log_llog);
536         if (ret)
537                 goto err_con;
538         tsm_vte_set_palette(term->vte, term->conf->palette);
539
540         ret = kmscon_pty_new(&term->pty, pty_input, term);
541         if (ret)
542                 goto err_vte;
543
544         kmscon_pty_set_env_reset(term->pty, term->conf->reset_env);
545
546         ret = kmscon_pty_set_term(term->pty, term->conf->term);
547         if (ret)
548                 goto err_pty;
549
550         ret = kmscon_pty_set_colorterm(term->pty, "kmscon");
551         if (ret)
552                 goto err_pty;
553
554         ret = kmscon_pty_set_argv(term->pty, term->conf->argv);
555         if (ret)
556                 goto err_pty;
557
558         ret = kmscon_pty_set_seat(term->pty, kmscon_seat_get_name(seat));
559         if (ret)
560                 goto err_pty;
561
562         ret = ev_eloop_new_fd(term->eloop, &term->ptyfd,
563                               kmscon_pty_get_fd(term->pty),
564                               EV_READABLE, pty_event, term);
565         if (ret)
566                 goto err_pty;
567
568         ret = uterm_input_register_cb(term->input, input_event, term);
569         if (ret)
570                 goto err_ptyfd;
571
572         ret = kmscon_seat_register_session(seat, &term->session, session_event,
573                                            term);
574         if (ret) {
575                 log_error("cannot register session for terminal: %d", ret);
576                 goto err_input;
577         }
578
579         ev_eloop_ref(term->eloop);
580         uterm_input_ref(term->input);
581         *out = term->session;
582         log_debug("new terminal object %p", term);
583         return 0;
584
585 err_input:
586         uterm_input_unregister_cb(term->input, input_event, term);
587 err_ptyfd:
588         ev_eloop_rm_fd(term->ptyfd);
589 err_pty:
590         kmscon_pty_unref(term->pty);
591 err_vte:
592         tsm_vte_unref(term->vte);
593 err_con:
594         tsm_screen_unref(term->console);
595 err_free:
596         free(term);
597         return ret;
598 }