4 * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
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:
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
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.
27 * Wayland Terminal console helpers
31 #include <linux/input.h>
36 #include <wayland-client.h>
37 #include <xkbcommon/xkbcommon.h>
44 #include "tsm_unicode.h"
45 #include "tsm_screen.h"
47 #include "uterm_video.h"
49 #include "wlt_terminal.h"
50 #include "wlt_toolkit.h"
52 #define LOG_SUBSYSTEM "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;
62 struct tsm_screen *scr;
64 struct kmscon_pty *pty;
68 struct kmscon_font_attr font_attr;
69 struct kmscon_font *font_normal;
79 bool selection_started;
85 struct wl_data_source *copy;
90 static int draw_cell(struct tsm_screen *scr,
91 uint32_t id, const uint32_t *ch, size_t len,
93 unsigned int posx, unsigned int posy,
94 const struct tsm_screen_attr *attr, void *data)
96 struct wlt_terminal *term = data;
97 const struct kmscon_glyph *glyph;
99 unsigned int x, y, tmp, width, height, i, r, g, b;
101 const struct uterm_video_buffer *buf;
102 unsigned int fr, fg, fb, br, bg, bb;
109 ret = kmscon_font_render_empty(term->font_normal, &glyph);
111 ret = kmscon_font_render(term->font_normal, id, ch, len,
115 ret = kmscon_font_render_inval(term->font_normal, &glyph);
121 x = posx * term->font_normal->attr.width;
122 y = posy * term->font_normal->attr.height;
140 tmp = x + buf->width;
141 if (tmp < x || x >= term->buffer.width)
143 if (tmp > term->buffer.width)
144 width = term->buffer.width - x;
148 tmp = y + buf->height;
149 if (tmp < y || y >= term->buffer.height)
151 if (tmp > term->buffer.height)
152 height = term->buffer.height - y;
154 height = buf->height;
156 dst = term->buffer.data;
157 dst = &dst[y * term->buffer.stride + x * 4];
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. */
165 for (i = 0; i < width; ++i) {
170 } else if (src[i] == 255) {
185 val = (0xff << 24) | (r << 16) | (g << 8) | b;
186 ((uint32_t*)dst)[i] = val;
188 dst += term->buffer.stride;
195 static void draw_background(struct wlt_terminal *term)
199 unsigned int i, j, w, h;
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;
209 h = term->buffer.height;
210 h /= term->font_normal->attr.height;
211 h *= term->font_normal->attr.height;
213 dst = term->buffer.data;
214 for (i = 0; i < term->buffer.height; ++i) {
215 line = (uint32_t*)dst;
220 for ( ; j < term->buffer.width; ++j)
221 line[j] = 0xff << 24;
222 dst += term->buffer.stride;
226 static void widget_redraw(struct wlt_widget *widget, unsigned int flags,
229 struct wlt_terminal *term = data;
231 draw_background(term);
232 tsm_screen_draw(term->scr, NULL, draw_cell, NULL, term);
235 static void widget_resize(struct wlt_widget *widget, unsigned int flags,
236 struct wlt_rect *alloc, void *data)
238 struct wlt_terminal *term = data;
241 wlt_window_get_buffer(term->wnd, alloc, &term->buffer);
242 memcpy(&term->alloc, alloc, sizeof(*alloc));
244 /* don't allow children */
248 term->cols = term->buffer.width / term->font_normal->attr.width;
251 term->rows = term->buffer.height / term->font_normal->attr.height;
255 ret = tsm_screen_resize(term->scr, term->cols, term->rows);
257 log_error("cannot resize TSM screen: %d", ret);
258 kmscon_pty_resize(term->pty, term->cols, term->rows);
261 static void widget_prepare_resize(struct wlt_widget *widget,
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,
270 struct wlt_terminal *term = data;
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. */
281 if (flags & WLT_WINDOW_MAXIMIZED ||
282 flags & WLT_WINDOW_FULLSCREEN) {
283 /* if maximized, always use requested size */
285 *new_height = height;
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
294 if (*new_width >= width) {
295 *new_width += term->font_normal->attr.width;
297 w = width - *new_width;
298 w /= term->font_normal->attr.width;
301 w *= term->font_normal->attr.width;
305 if (*new_width < *min_width) {
306 w = *min_width - *new_width;
307 w /= term->font_normal->attr.width;
309 w *= term->font_normal->attr.width;
313 if (*new_height >= height) {
314 *new_height += term->font_normal->attr.height;
316 h = height - *new_height;
317 h /= term->font_normal->attr.height;
320 h *= term->font_normal->attr.height;
324 if (*new_height < *min_height) {
325 h = *min_height - *new_height;
326 h /= term->font_normal->attr.height;
328 h *= term->font_normal->attr.height;
334 static void paste_event(struct ev_fd *fd, int mask, void *data)
336 struct wlt_terminal *term = data;
340 if (mask & EV_READABLE) {
341 ret = read(term->paste_fd, buf, sizeof(buf));
344 } else if (ret < 0) {
347 log_error("error on paste-fd (%d): %m", errno);
351 kmscon_pty_write(term->pty, buf, ret);
356 log_error("error on paste FD");
366 close(term->paste_fd);
367 ev_eloop_rm_fd(term->paste);
371 static void copy_target(void *data, struct wl_data_source *w_source,
376 static void copy_send(void *data, struct wl_data_source *w_source,
377 const char *mime, int32_t fd)
379 struct wlt_terminal *term = data;
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,
390 static void copy_cancelled(void *data, struct wl_data_source *w_source)
392 struct wlt_terminal *term = data;
394 wl_data_source_destroy(w_source);
395 if (term->copy == w_source)
399 static const struct wl_data_source_listener copy_listener = {
400 .target = copy_target,
402 .cancelled = copy_cancelled,
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)
409 struct wlt_terminal *term = data;
411 struct kmscon_font *font;
414 if (handled || state != WL_KEYBOARD_KEY_STATE_PRESSED)
417 ucs4 = xkb_keysym_to_utf32(sym) ? : TSM_VTE_INVALID;
419 if (conf_grab_matches(wlt_conf.grab_scroll_up,
421 tsm_screen_sb_up(term->scr, 1);
422 wlt_window_schedule_redraw(term->wnd);
425 if (conf_grab_matches(wlt_conf.grab_scroll_down,
427 tsm_screen_sb_down(term->scr, 1);
428 wlt_window_schedule_redraw(term->wnd);
431 if (conf_grab_matches(wlt_conf.grab_page_up,
433 tsm_screen_sb_page_up(term->scr, 1);
434 wlt_window_schedule_redraw(term->wnd);
437 if (conf_grab_matches(wlt_conf.grab_page_down,
439 tsm_screen_sb_page_down(term->scr, 1);
440 wlt_window_schedule_redraw(term->wnd);
444 if (conf_grab_matches(wlt_conf.grab_zoom_in,
446 if (term->font_attr.points + 1 < term->font_attr.points)
449 ++term->font_attr.points;
450 ret = kmscon_font_find(&font, &term->font_attr,
451 wlt_conf.font_engine);
453 --term->font_attr.points;
454 log_error("cannot create font");
456 kmscon_font_unref(term->font_normal);
457 term->font_normal = font;
458 wlt_window_schedule_redraw(term->wnd);
462 if (conf_grab_matches(wlt_conf.grab_zoom_out,
464 if (term->font_attr.points - 1 < 1)
467 --term->font_attr.points;
468 ret = kmscon_font_find(&font, &term->font_attr,
469 wlt_conf.font_engine);
471 ++term->font_attr.points;
472 log_error("cannot create font");
474 kmscon_font_unref(term->font_normal);
475 term->font_normal = font;
476 wlt_window_schedule_redraw(term->wnd);
481 if (conf_grab_matches(wlt_conf.grab_paste,
484 log_debug("cannot paste selection, previous paste still in progress");
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");
493 } else if (ret == -EAGAIN) {
494 log_debug("unknown mime-time for pasting selection");
496 } else if (ret < 0) {
497 log_error("cannot paste selection: %d", ret);
501 term->paste_fd = ret;
502 ret = ev_eloop_new_fd(term->eloop, &term->paste, ret,
503 EV_READABLE, paste_event, term);
506 log_error("cannot create eloop fd: %d", ret);
513 if (conf_grab_matches(wlt_conf.grab_copy,
516 wl_data_source_destroy(term->copy);
517 free(term->copy_buf);
521 ret = wlt_display_new_data_source(term->disp, &term->copy);
523 log_error("cannot create data source");
527 term->copy_len = tsm_screen_selection_copy(term->scr,
529 if (term->copy_len < 0) {
530 if (term->copy_len != -ENOENT)
531 log_error("cannot copy TSM selection: %d",
533 wl_data_source_destroy(term->copy);
538 wl_data_source_offer(term->copy, "text/plain;charset=utf-8");
539 wl_data_source_add_listener(term->copy, ©_listener, term);
540 wlt_display_set_selection(term->disp, term->copy);
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);
554 static void pointer_motion(struct wlt_widget *widget,
555 unsigned int x, unsigned int y, void *data)
557 struct wlt_terminal *term = data;
558 unsigned int posx, posy;
560 if (!wlt_rect_contains(&term->alloc, x, y)) {
561 term->pointer_x = -1;
562 term->pointer_y = -1;
564 } else if (term->pointer_x == x - term->alloc.x &&
565 term->pointer_y == y - term->alloc.y) {
568 term->pointer_x = x - term->alloc.x;
569 term->pointer_y = y - term->alloc.y;
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;
578 tsm_screen_selection_start(term->scr, posx, posy);
580 posx = term->pointer_x / term->font_normal->attr.width;
581 posy = term->pointer_y / term->font_normal->attr.height;
583 tsm_screen_selection_target(term->scr, posx, posy);
586 wlt_window_schedule_redraw(term->wnd);
590 static void pointer_enter(struct wlt_widget *widget,
591 unsigned int x, unsigned int y, void *data)
593 struct wlt_terminal *term = data;
595 pointer_motion(widget, x, y, term);
598 static void pointer_leave(struct wlt_widget *widget, void *data)
600 struct wlt_terminal *term = data;
602 term->pointer_x = -1;
603 term->pointer_y = -1;
606 static void pointer_button(struct wlt_widget *widget,
607 uint32_t button, uint32_t state, void *data)
609 struct wlt_terminal *term = data;
611 if (button != BTN_LEFT)
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;
620 term->sel_start_x = term->pointer_x;
621 term->sel_start_y = term->pointer_y;
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);
630 term->in_selection = false;
634 static void vte_event(struct tsm_vte *vte, const char *u8, size_t len,
637 struct wlt_terminal *term = data;
639 kmscon_pty_write(term->pty, u8, len);
642 static void pty_input(struct kmscon_pty *pty, const char *u8, size_t len,
645 struct wlt_terminal *term = data;
648 term->pty_open = false;
650 term->cb(term, WLT_TERMINAL_HUP, term->data);
652 tsm_vte_input(term->vte, u8, len);
653 wlt_window_schedule_redraw(term->wnd);
657 static void pty_event(struct ev_fd *fd, int mask, void *data)
659 struct wlt_terminal *term = data;
661 kmscon_pty_dispatch(term->pty);
664 static void widget_destroy(struct wlt_widget *widget, void *data)
666 struct wlt_terminal *term = data;
669 ev_eloop_rm_fd(term->paste);
670 close(term->paste_fd);
672 tsm_vte_unref(term->vte);
673 tsm_screen_unref(term->scr);
677 int wlt_terminal_new(struct wlt_terminal **out, struct wlt_window *wnd)
679 struct wlt_terminal *term;
685 term = malloc(sizeof(*term));
688 memset(term, 0, sizeof(*term));
690 term->disp = wlt_window_get_display(wnd);
691 term->eloop = wlt_window_get_eloop(wnd);
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;
705 ret = kmscon_font_find(&term->font_normal, &term->font_attr,
706 wlt_conf.font_engine);
708 log_error("cannot create font");
712 ret = tsm_screen_new(&term->scr, log_llog);
714 log_error("cannot create tsm-screen object");
717 tsm_screen_set_max_sb(term->scr, wlt_conf.sb_size);
719 ret = tsm_vte_new(&term->vte, term->scr, vte_event, term, log_llog);
721 log_error("cannot create tsm-vte object");
724 tsm_vte_set_palette(term->vte, wlt_conf.palette);
726 ret = kmscon_pty_new(&term->pty, pty_input, term);
728 log_error("cannot create pty object");
731 kmscon_pty_set_term(term->pty, "xterm-256color");
733 ret = kmscon_pty_set_term(term->pty, wlt_conf.term);
737 ret = kmscon_pty_set_argv(term->pty, wlt_conf.argv);
741 ret = ev_eloop_new_fd(term->eloop, &term->pty_fd,
742 kmscon_pty_get_fd(term->pty),
743 EV_READABLE, pty_event, term);
747 ret = wlt_window_create_widget(wnd, &term->widget, term);
749 log_error("cannot create terminal widget");
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,
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);
764 ev_eloop_rm_fd(term->pty_fd);
766 kmscon_pty_unref(term->pty);
768 tsm_vte_unref(term->vte);
770 tsm_screen_unref(term->scr);
772 kmscon_font_unref(term->font_normal);
778 void wlt_terminal_destroy(struct wlt_terminal *term)
783 wlt_widget_destroy(term->widget);
786 int wlt_terminal_open(struct wlt_terminal *term, wlt_terminal_cb cb,
800 kmscon_pty_close(term->pty);
801 ret = kmscon_pty_open(term->pty, term->cols, term->rows);
805 term->pty_open = true;