wlt: fix shl_hook API changes
[platform/upstream/kmscon.git] / src / wlt_terminal.c
1 /*
2  * wlt - Terminals
3  *
4  * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files
8  * (the "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 /*
27  * Wayland Terminal console helpers
28  */
29
30 #include <errno.h>
31 #include <linux/input.h>
32 #include <stdbool.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <wayland-client.h>
37 #include <xkbcommon/xkbcommon.h>
38 #include "conf.h"
39 #include "eloop.h"
40 #include "font.h"
41 #include "log.h"
42 #include "pty.h"
43 #include "shl_misc.h"
44 #include "tsm_unicode.h"
45 #include "tsm_screen.h"
46 #include "tsm_vte.h"
47 #include "uterm_video.h"
48 #include "wlt_main.h"
49 #include "wlt_terminal.h"
50 #include "wlt_toolkit.h"
51
52 #define LOG_SUBSYSTEM "wlt_terminal"
53
54 struct wlt_terminal {
55         struct ev_eloop *eloop;
56         struct wlt_window *wnd;
57         struct wlt_display *disp;
58         struct wlt_widget *widget;
59         struct wlt_shm_buffer buffer;
60         struct wlt_rect alloc;
61
62         struct tsm_screen *scr;
63         struct tsm_vte *vte;
64         struct kmscon_pty *pty;
65         struct ev_fd *pty_fd;
66         bool pty_open;
67
68         struct kmscon_font_attr font_attr;
69         struct kmscon_font *font_normal;
70         unsigned int cols;
71         unsigned int rows;
72
73         wlt_terminal_cb cb;
74         void *data;
75
76         int pointer_x;
77         int pointer_y;
78         bool in_selection;
79         bool selection_started;
80         int sel_start_x;
81         int sel_start_y;
82
83         int paste_fd;
84         struct ev_fd *paste;
85         struct wl_data_source *copy;
86         char *copy_buf;
87         int copy_len;
88 };
89
90 static int draw_cell(struct tsm_screen *scr,
91                      uint32_t id, const uint32_t *ch, size_t len,
92                      unsigned int chwidth,
93                      unsigned int posx, unsigned int posy,
94                      const struct tsm_screen_attr *attr, void *data)
95 {
96         struct wlt_terminal *term = data;
97         const struct kmscon_glyph *glyph;
98         int ret;
99         unsigned int x, y, tmp, width, height, i, r, g, b;
100         uint8_t *dst, *src;
101         const struct uterm_video_buffer *buf;
102         unsigned int fr, fg, fb, br, bg, bb;
103         uint32_t val;
104
105         if (!chwidth)
106                 return 0;
107
108         if (!len)
109                 ret = kmscon_font_render_empty(term->font_normal, &glyph);
110         else
111                 ret = kmscon_font_render(term->font_normal, id, ch, len,
112                                          &glyph);
113
114         if (ret) {
115                 ret = kmscon_font_render_inval(term->font_normal, &glyph);
116                 if (ret)
117                         return ret;
118         }
119
120         buf = &glyph->buf;
121         x = posx * term->font_normal->attr.width;
122         y = posy * term->font_normal->attr.height;
123
124         if (attr->inverse) {
125                 fr = attr->br;
126                 fg = attr->bg;
127                 fb = attr->bb;
128                 br = attr->fr;
129                 bg = attr->fg;
130                 bb = attr->fb;
131         } else {
132                 fr = attr->fr;
133                 fg = attr->fg;
134                 fb = attr->fb;
135                 br = attr->br;
136                 bg = attr->bg;
137                 bb = attr->bb;
138         }
139
140         tmp = x + buf->width;
141         if (tmp < x || x >= term->buffer.width)
142                 return 0;
143         if (tmp > term->buffer.width)
144                 width = term->buffer.width - x;
145         else
146                 width = buf->width;
147
148         tmp = y + buf->height;
149         if (tmp < y || y >= term->buffer.height)
150                 return 0;
151         if (tmp > term->buffer.height)
152                 height = term->buffer.height - y;
153         else
154                 height = buf->height;
155
156         dst = term->buffer.data;
157         dst = &dst[y * term->buffer.stride + x * 4];
158         src = buf->data;
159
160         /* Division by 256 instead of 255 increases
161          * speed by like 20% on slower machines.
162          * Downside is, full white is 254/254/254
163          * instead of 255/255/255. */
164         while (height--) {
165                 for (i = 0; i < width; ++i) {
166                         if (src[i] == 0) {
167                                 r = br;
168                                 g = bg;
169                                 b = bb;
170                         } else if (src[i] == 255) {
171                                 r = fr;
172                                 g = fg;
173                                 b = fb;
174                         } else {
175                                 r = fr * src[i] +
176                                     br * (255 - src[i]);
177                                 r /= 256;
178                                 g = fg * src[i] +
179                                     bg * (255 - src[i]);
180                                 g /= 256;
181                                 b = fb * src[i] +
182                                     bb * (255 - src[i]);
183                                 b /= 256;
184                         }
185                         val = (0xff << 24) | (r << 16) | (g << 8) | b;
186                         ((uint32_t*)dst)[i] = val;
187                 }
188                 dst += term->buffer.stride;
189                 src += buf->stride;
190         }
191
192         return 0;
193 }
194
195 static void draw_background(struct wlt_terminal *term)
196 {
197         uint8_t *dst;
198         uint32_t *line;
199         unsigned int i, j, w, h;
200
201         /* when maximized, we might have a right and bottom border. So draw
202          * a black background for everything beyond grid-size.
203          * TODO: we should catch the color from tsm_screen instead of using
204          * black background by default. */
205         w = term->buffer.width;
206         w /= term->font_normal->attr.width;
207         w *= term->font_normal->attr.width;
208
209         h = term->buffer.height;
210         h /= term->font_normal->attr.height;
211         h *= term->font_normal->attr.height;
212
213         dst = term->buffer.data;
214         for (i = 0; i < term->buffer.height; ++i) {
215                 line = (uint32_t*)dst;
216                 if (i >= h)
217                         j = 0;
218                 else
219                         j = w;
220                 for ( ; j < term->buffer.width; ++j)
221                         line[j] = 0xff << 24;
222                 dst += term->buffer.stride;
223         }
224 }
225
226 static void widget_redraw(struct wlt_widget *widget, unsigned int flags,
227                           void *data)
228 {
229         struct wlt_terminal *term = data;
230
231         draw_background(term);
232         tsm_screen_draw(term->scr, NULL, draw_cell, NULL, term);
233 }
234
235 static void widget_resize(struct wlt_widget *widget, unsigned int flags,
236                           struct wlt_rect *alloc, void *data)
237 {
238         struct wlt_terminal *term = data;
239         int ret;
240
241         wlt_window_get_buffer(term->wnd, alloc, &term->buffer);
242         memcpy(&term->alloc, alloc, sizeof(*alloc));
243
244         /* don't allow children */
245         alloc->width = 0;
246         alloc->height = 0;
247
248         term->cols = term->buffer.width / term->font_normal->attr.width;
249         if (term->cols < 1)
250                 term->cols = 1;
251         term->rows = term->buffer.height / term->font_normal->attr.height;
252         if (term->rows < 1)
253                 term->rows = 1;
254
255         ret = tsm_screen_resize(term->scr, term->cols, term->rows);
256         if (ret)
257                 log_error("cannot resize TSM screen: %d", ret);
258         kmscon_pty_resize(term->pty, term->cols, term->rows);
259 }
260
261 static void widget_prepare_resize(struct wlt_widget *widget,
262                                   unsigned int flags,
263                                   unsigned int width, unsigned int height,
264                                   unsigned int *min_width,
265                                   unsigned int *min_height,
266                                   unsigned int *new_width,
267                                   unsigned int *new_height,
268                                   void *data)
269 {
270         struct wlt_terminal *term = data;
271         unsigned int w, h;
272
273         /* We are a catch-all handler. That is, we use all space that is
274          * available. We must be called _last_, which is guaranteed by
275          * registering the widget as last widget.
276          * All previous handlers put their size constraints into the arguemnts
277          * and we need to make sure to not break them.
278          * Every redraw-handler is guaranteed to work for every size, but still,
279          * we should try to avoid invalid-sizes to not generate artifacts. */
280
281         if (flags & WLT_WINDOW_MAXIMIZED ||
282             flags & WLT_WINDOW_FULLSCREEN) {
283                 /* if maximized, always use requested size */
284                 *new_width = width;
285                 *new_height = height;
286         } else {
287                 /* In normal mode, we want the console to "snap" to grid-sizes.
288                  * That is, resizing is in steps instead of smooth. To guarantee
289                  * that, we use the font-width/height and try to make the
290                  * console as big as possible to fit the needed size.
291                  * However, we also must make sure the minimal size is always
292                  * guaranteed. */
293
294                 if (*new_width >= width) {
295                         *new_width += term->font_normal->attr.width;
296                 } else {
297                         w = width - *new_width;
298                         w /= term->font_normal->attr.width;
299                         if (!w)
300                                 w = 1;
301                         w *= term->font_normal->attr.width;
302                         *new_width += w;
303                 }
304
305                 if (*new_width < *min_width) {
306                         w = *min_width - *new_width;
307                         w /= term->font_normal->attr.width;
308                         w += 1;
309                         w *= term->font_normal->attr.width;
310                         *new_width += w;
311                 }
312
313                 if (*new_height >= height) {
314                         *new_height += term->font_normal->attr.height;
315                 } else {
316                         h = height - *new_height;
317                         h /= term->font_normal->attr.height;
318                         if (!h)
319                                 h = 1;
320                         h *= term->font_normal->attr.height;
321                         *new_height += h;
322                 }
323
324                 if (*new_height < *min_height) {
325                         h = *min_height - *new_height;
326                         h /= term->font_normal->attr.height;
327                         h += 1;
328                         h *= term->font_normal->attr.height;
329                         *new_height += h;
330                 }
331         }
332 }
333
334 static void paste_event(struct ev_fd *fd, int mask, void *data)
335 {
336         struct wlt_terminal *term = data;
337         char buf[4096];
338         int ret;
339
340         if (mask & EV_READABLE) {
341                 ret = read(term->paste_fd, buf, sizeof(buf));
342                 if (ret == 0) {
343                         goto err_close;
344                 } else if (ret < 0) {
345                         if (errno == EAGAIN)
346                                 return;
347                         log_error("error on paste-fd (%d): %m", errno);
348                         goto err_close;
349                 }
350
351                 kmscon_pty_write(term->pty, buf, ret);
352                 return;
353         }
354
355         if (mask & EV_ERR) {
356                 log_error("error on paste FD");
357                 goto err_close;
358         }
359
360         if (mask & EV_HUP)
361                 goto err_close;
362
363         return;
364
365 err_close:
366         close(term->paste_fd);
367         ev_eloop_rm_fd(term->paste);
368         term->paste = NULL;
369 }
370
371 static void copy_target(void *data, struct wl_data_source *w_source,
372                         const char *target)
373 {
374 }
375
376 static void copy_send(void *data, struct wl_data_source *w_source,
377                       const char *mime, int32_t fd)
378 {
379         struct wlt_terminal *term = data;
380         int ret;
381
382         /* TODO: make this non-blocking */
383         ret = write(fd, term->copy_buf, term->copy_len);
384         if (ret != term->copy_len)
385                 log_warning("cannot write whole selection: %d/%d", ret,
386                             term->copy_len);
387         close(fd);
388 }
389
390 static void copy_cancelled(void *data, struct wl_data_source *w_source)
391 {
392         struct wlt_terminal *term = data;
393
394         wl_data_source_destroy(w_source);
395         if (term->copy == w_source)
396                 term->copy = NULL;
397 }
398
399 static const struct wl_data_source_listener copy_listener = {
400         .target = copy_target,
401         .send = copy_send,
402         .cancelled = copy_cancelled,
403 };
404
405 static bool widget_key(struct wlt_widget *widget, unsigned int mask,
406                        uint32_t sym, uint32_t ascii, uint32_t state,
407                        bool handled, void *data)
408 {
409         struct wlt_terminal *term = data;
410         uint32_t ucs4;
411         struct kmscon_font *font;
412         int ret;
413
414         if (handled || state != WL_KEYBOARD_KEY_STATE_PRESSED)
415                 return false;
416
417         ucs4 = xkb_keysym_to_utf32(sym) ? : TSM_VTE_INVALID;
418
419         if (conf_grab_matches(wlt_conf.grab_scroll_up,
420                               mask, 1, &sym)) {
421                 tsm_screen_sb_up(term->scr, 1);
422                 wlt_window_schedule_redraw(term->wnd);
423                 return true;
424         }
425         if (conf_grab_matches(wlt_conf.grab_scroll_down,
426                               mask, 1, &sym)) {
427                 tsm_screen_sb_down(term->scr, 1);
428                 wlt_window_schedule_redraw(term->wnd);
429                 return true;
430         }
431         if (conf_grab_matches(wlt_conf.grab_page_up,
432                               mask, 1, &sym)) {
433                 tsm_screen_sb_page_up(term->scr, 1);
434                 wlt_window_schedule_redraw(term->wnd);
435                 return true;
436         }
437         if (conf_grab_matches(wlt_conf.grab_page_down,
438                               mask, 1, &sym)) {
439                 tsm_screen_sb_page_down(term->scr, 1);
440                 wlt_window_schedule_redraw(term->wnd);
441                 return true;
442         }
443
444         if (conf_grab_matches(wlt_conf.grab_zoom_in,
445                               mask, 1, &sym)) {
446                 if (term->font_attr.points + 1 < term->font_attr.points)
447                         return true;
448
449                 ++term->font_attr.points;
450                 ret = kmscon_font_find(&font, &term->font_attr,
451                                        wlt_conf.font_engine);
452                 if (ret) {
453                         --term->font_attr.points;
454                         log_error("cannot create font");
455                 } else {
456                         kmscon_font_unref(term->font_normal);
457                         term->font_normal = font;
458                         wlt_window_schedule_redraw(term->wnd);
459                 }
460                 return true;
461         }
462         if (conf_grab_matches(wlt_conf.grab_zoom_out,
463                               mask, 1, &sym)) {
464                 if (term->font_attr.points - 1 < 1)
465                         return true;
466
467                 --term->font_attr.points;
468                 ret = kmscon_font_find(&font, &term->font_attr,
469                                        wlt_conf.font_engine);
470                 if (ret) {
471                         ++term->font_attr.points;
472                         log_error("cannot create font");
473                 } else {
474                         kmscon_font_unref(term->font_normal);
475                         term->font_normal = font;
476                         wlt_window_schedule_redraw(term->wnd);
477                 }
478                 return true;
479         }
480
481         if (conf_grab_matches(wlt_conf.grab_paste,
482                               mask, 1, &sym)) {
483                 if (term->paste) {
484                         log_debug("cannot paste selection, previous paste still in progress");
485                         return true;
486                 }
487
488                 ret = wlt_display_get_selection_fd(term->disp,
489                                                 "text/plain;charset=utf-8");
490                 if (ret == -ENOENT) {
491                         log_debug("no selection to paste");
492                         return true;
493                 } else if (ret == -EAGAIN) {
494                         log_debug("unknown mime-time for pasting selection");
495                         return true;
496                 } else if (ret < 0) {
497                         log_error("cannot paste selection: %d", ret);
498                         return true;
499                 }
500
501                 term->paste_fd = ret;
502                 ret = ev_eloop_new_fd(term->eloop, &term->paste, ret,
503                                       EV_READABLE, paste_event, term);
504                 if (ret) {
505                         close(ret);
506                         log_error("cannot create eloop fd: %d", ret);
507                         return true;
508                 }
509
510                 return true;
511         }
512
513         if (conf_grab_matches(wlt_conf.grab_copy,
514                               mask, 1, &sym)) {
515                 if (term->copy) {
516                         wl_data_source_destroy(term->copy);
517                         free(term->copy_buf);
518                         term->copy = NULL;
519                 }
520
521                 ret = wlt_display_new_data_source(term->disp, &term->copy);
522                 if (ret) {
523                         log_error("cannot create data source");
524                         return true;
525                 }
526
527                 term->copy_len = tsm_screen_selection_copy(term->scr,
528                                                            &term->copy_buf);
529                 if (term->copy_len < 0) {
530                         if (term->copy_len != -ENOENT)
531                                 log_error("cannot copy TSM selection: %d",
532                                           term->copy_len);
533                         wl_data_source_destroy(term->copy);
534                         term->copy = NULL;
535                         return true;
536                 }
537
538                 wl_data_source_offer(term->copy, "text/plain;charset=utf-8");
539                 wl_data_source_add_listener(term->copy, &copy_listener, term);
540                 wlt_display_set_selection(term->disp, term->copy);
541
542                 return true;
543         }
544
545         if (tsm_vte_handle_keyboard(term->vte, sym, ascii, mask, ucs4)) {
546                 tsm_screen_sb_reset(term->scr);
547                 wlt_window_schedule_redraw(term->wnd);
548                 return true;
549         }
550
551         return false;
552 }
553
554 static void pointer_motion(struct wlt_widget *widget,
555                            unsigned int x, unsigned int y, void *data)
556 {
557         struct wlt_terminal *term = data;
558         unsigned int posx, posy;
559
560         if (!wlt_rect_contains(&term->alloc, x, y)) {
561                 term->pointer_x = -1;
562                 term->pointer_y = -1;
563                 return;
564         } else if (term->pointer_x == x - term->alloc.x &&
565                    term->pointer_y == y - term->alloc.y) {
566                 return;
567         } else {
568                 term->pointer_x = x - term->alloc.x;
569                 term->pointer_y = y - term->alloc.y;
570         }
571
572         if (term->in_selection) {
573                 if (!term->selection_started) {
574                         term->selection_started = true;
575                         posx = term->sel_start_x / term->font_normal->attr.width;
576                         posy = term->sel_start_y / term->font_normal->attr.height;
577
578                         tsm_screen_selection_start(term->scr, posx, posy);
579                 } else {
580                         posx = term->pointer_x / term->font_normal->attr.width;
581                         posy = term->pointer_y / term->font_normal->attr.height;
582
583                         tsm_screen_selection_target(term->scr, posx, posy);
584                 }
585
586                 wlt_window_schedule_redraw(term->wnd);
587         }
588 }
589
590 static void pointer_enter(struct wlt_widget *widget,
591                           unsigned int x, unsigned int y, void *data)
592 {
593         struct wlt_terminal *term = data;
594
595         pointer_motion(widget, x, y, term);
596 }
597
598 static void pointer_leave(struct wlt_widget *widget, void *data)
599 {
600         struct wlt_terminal *term = data;
601
602         term->pointer_x = -1;
603         term->pointer_y = -1;
604 }
605
606 static void pointer_button(struct wlt_widget *widget,
607                            uint32_t button, uint32_t state, void *data)
608 {
609         struct wlt_terminal *term = data;
610
611         if (button != BTN_LEFT)
612                 return;
613
614         if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
615                 if (!term->in_selection &&
616                     term->pointer_x >= 0 && term->pointer_y >= 0) {
617                         term->in_selection = true;
618                         term->selection_started = false;
619
620                         term->sel_start_x = term->pointer_x;
621                         term->sel_start_y = term->pointer_y;
622                 }
623         } else {
624                 if (term->pointer_x == term->sel_start_x &&
625                     term->pointer_y == term->sel_start_y) {
626                         tsm_screen_selection_reset(term->scr);
627                         wlt_window_schedule_redraw(term->wnd);
628                 }
629
630                 term->in_selection = false;
631         }
632 }
633
634 static void vte_event(struct tsm_vte *vte, const char *u8, size_t len,
635                       void *data)
636 {
637         struct wlt_terminal *term = data;
638
639         kmscon_pty_write(term->pty, u8, len);
640 }
641
642 static void pty_input(struct kmscon_pty *pty, const char *u8, size_t len,
643                       void *data)
644 {
645         struct wlt_terminal *term = data;
646
647         if (!len) {
648                 term->pty_open = false;
649                 if (term->cb)
650                         term->cb(term, WLT_TERMINAL_HUP, term->data);
651         } else {
652                 tsm_vte_input(term->vte, u8, len);
653                 wlt_window_schedule_redraw(term->wnd);
654         }
655 }
656
657 static void pty_event(struct ev_fd *fd, int mask, void *data)
658 {
659         struct wlt_terminal *term = data;
660
661         kmscon_pty_dispatch(term->pty);
662 }
663
664 static void widget_destroy(struct wlt_widget *widget, void *data)
665 {
666         struct wlt_terminal *term = data;
667
668         if (term->paste) {
669                 ev_eloop_rm_fd(term->paste);
670                 close(term->paste_fd);
671         }
672         tsm_vte_unref(term->vte);
673         tsm_screen_unref(term->scr);
674         free(term);
675 }
676
677 int wlt_terminal_new(struct wlt_terminal **out, struct wlt_window *wnd)
678 {
679         struct wlt_terminal *term;
680         int ret;
681
682         if (!out || !wnd)
683                 return -EINVAL;
684
685         term = malloc(sizeof(*term));
686         if (!term)
687                 return -ENOMEM;
688         memset(term, 0, sizeof(*term));
689         term->wnd = wnd;
690         term->disp = wlt_window_get_display(wnd);
691         term->eloop = wlt_window_get_eloop(wnd);
692         term->cols = 80;
693         term->rows = 24;
694
695         term->font_attr.ppi = wlt_conf.font_ppi;
696         term->font_attr.points = wlt_conf.font_size;
697         term->font_attr.bold = false;
698         term->font_attr.italic = false;
699         term->font_attr.width = 0;
700         term->font_attr.height = 0;
701         strncpy(term->font_attr.name, wlt_conf.font_name,
702                 KMSCON_FONT_MAX_NAME - 1);
703         term->font_attr.name[KMSCON_FONT_MAX_NAME - 1] = 0;
704
705         ret = kmscon_font_find(&term->font_normal, &term->font_attr,
706                                wlt_conf.font_engine);
707         if (ret) {
708                 log_error("cannot create font");
709                 goto err_free;
710         }
711
712         ret = tsm_screen_new(&term->scr, log_llog);
713         if (ret) {
714                 log_error("cannot create tsm-screen object");
715                 goto err_font;
716         }
717         tsm_screen_set_max_sb(term->scr, wlt_conf.sb_size);
718
719         ret = tsm_vte_new(&term->vte, term->scr, vte_event, term, log_llog);
720         if (ret) {
721                 log_error("cannot create tsm-vte object");
722                 goto err_scr;
723         }
724         tsm_vte_set_palette(term->vte, wlt_conf.palette);
725
726         ret = kmscon_pty_new(&term->pty, pty_input, term);
727         if (ret) {
728                 log_error("cannot create pty object");
729                 goto err_vte;
730         }
731         kmscon_pty_set_term(term->pty, "xterm-256color");
732
733         ret = kmscon_pty_set_term(term->pty, wlt_conf.term);
734         if (ret)
735                 goto err_pty;
736
737         ret = kmscon_pty_set_argv(term->pty, wlt_conf.argv);
738         if (ret)
739                 goto err_pty;
740
741         ret = ev_eloop_new_fd(term->eloop, &term->pty_fd,
742                               kmscon_pty_get_fd(term->pty),
743                               EV_READABLE, pty_event, term);
744         if (ret)
745                 goto err_pty;
746
747         ret = wlt_window_create_widget(wnd, &term->widget, term);
748         if (ret) {
749                 log_error("cannot create terminal widget");
750                 goto err_pty_fd;
751         }
752
753         wlt_widget_set_destroy_cb(term->widget, widget_destroy);
754         wlt_widget_set_redraw_cb(term->widget, widget_redraw);
755         wlt_widget_set_resize_cb(term->widget, widget_prepare_resize,
756                                  widget_resize);
757         wlt_widget_set_keyboard_cb(term->widget, widget_key);
758         wlt_widget_set_pointer_cb(term->widget, pointer_enter, pointer_leave,
759                                   pointer_motion, pointer_button);
760         *out = term;
761         return 0;
762
763 err_pty_fd:
764         ev_eloop_rm_fd(term->pty_fd);
765 err_pty:
766         kmscon_pty_unref(term->pty);
767 err_vte:
768         tsm_vte_unref(term->vte);
769 err_scr:
770         tsm_screen_unref(term->scr);
771 err_font:
772         kmscon_font_unref(term->font_normal);
773 err_free:
774         free(term);
775         return ret;
776 }
777
778 void wlt_terminal_destroy(struct wlt_terminal *term)
779 {
780         if (!term)
781                 return;
782
783         wlt_widget_destroy(term->widget);
784 }
785
786 int wlt_terminal_open(struct wlt_terminal *term, wlt_terminal_cb cb,
787                       void *data)
788 {
789         int ret;
790
791         if (!term)
792                 return -EINVAL;
793
794         if (term->pty_open)
795                 return -EALREADY;
796
797         term->cb = cb;
798         term->data = data;
799
800         kmscon_pty_close(term->pty);
801         ret = kmscon_pty_open(term->pty, term->cols, term->rows);
802         if (ret)
803                 return ret;
804
805         term->pty_open = true;
806         return 0;
807 }