diff --git a/docs/man/kmscon.1.xml.in b/docs/man/kmscon.1.xml.in index f38262a3..950ab73c 100644 --- a/docs/man/kmscon.1.xml.in +++ b/docs/man/kmscon.1.xml.in @@ -327,6 +327,18 @@ (default: 50) + + + + + Enable mouse/touchpad/trackpoint in kmscon (default: off). + It allows to select, and copy/paste text with a pointing device, + and also to scroll with the mouse wheel. + It's still experimental, and may not work with specific devices. + + + + Grabs / Keyboard Shortcuts: diff --git a/docs/man/kmscon.conf.1.xml.in b/docs/man/kmscon.conf.1.xml.in index d8b3bac6..0a3005d5 100644 --- a/docs/man/kmscon.conf.1.xml.in +++ b/docs/man/kmscon.conf.1.xml.in @@ -68,6 +68,7 @@ xkb-model=pc102 xkb-layout=de xkb-repeat-delay=200 xkb-repeat-rate=65 +mouse ### Video Options ### drm @@ -262,6 +263,17 @@ font-name=Ubuntu Mono + + + + Enable mouse/touchpad/trackpoint in kmscon (default: off). + It allows to select, and copy/paste text with a pointing device, + and also to scroll with the mouse wheel. + It's still experimental, and may not work with specific devices. + + + + ### Grabs/Keyboard-Shortcuts ### diff --git a/meson.build b/meson.build index af837860..2b5266ee 100644 --- a/meson.build +++ b/meson.build @@ -47,7 +47,7 @@ systemdsystemunitdir = systemd_deps.get_variable('systemdsystemunitdir', default fs = import('fs') xkbcommon_deps = dependency('xkbcommon', version: '>=0.5.0') -libtsm_deps = dependency('libtsm', version: '>=4.1.0') +libtsm_deps = dependency('libtsm', version: '>=4.3.0') libudev_deps = dependency('libudev', version: '>=172') dl_deps = dependency('dl') threads_deps = dependency('threads') diff --git a/src/kmscon_conf.c b/src/kmscon_conf.c index fd38bfd6..2be56f8c 100644 --- a/src/kmscon_conf.c +++ b/src/kmscon_conf.c @@ -110,6 +110,8 @@ static void print_help() "\t Initial delay for key-repeat in ms\n" "\t --xkb-repeat-rate [50]\n" "\t Delay between two key repeats in ms\n" + "\t --mouse [off]\n" + "\t Enable experimental mouse support\n" "\n" "Grabs / Keyboard-Shortcuts:\n" "\t --grab-scroll-up [Up]\n" @@ -733,6 +735,7 @@ int kmscon_conf_new(struct conf_ctx **out) CONF_OPTION_STRING(0, "xkb-compose-file", &conf->xkb_compose_file, ""), CONF_OPTION_UINT(0, "xkb-repeat-delay", &conf->xkb_repeat_delay, 250), CONF_OPTION_UINT(0, "xkb-repeat-rate", &conf->xkb_repeat_rate, 50), + CONF_OPTION_BOOL(0, "mouse", &conf->mouse, false), /* Grabs / Keyboard-Shortcuts */ CONF_OPTION_GRAB(0, "grab-scroll-up", &conf->grab_scroll_up, &def_grab_scroll_up), diff --git a/src/kmscon_conf.h b/src/kmscon_conf.h index e62ba18b..1964d22a 100644 --- a/src/kmscon_conf.h +++ b/src/kmscon_conf.h @@ -111,6 +111,8 @@ struct kmscon_conf_t { unsigned int xkb_repeat_delay; /* keyboard key-repeat rate */ unsigned int xkb_repeat_rate; + /* Enable mouse support */ + bool mouse; /* Grabs / Keyboard-Shortcuts */ /* scroll-up grab */ diff --git a/src/kmscon_main.c b/src/kmscon_main.c index 14497134..2322d646 100644 --- a/src/kmscon_main.c +++ b/src/kmscon_main.c @@ -476,7 +476,7 @@ static void app_monitor_event(struct uterm_monitor *mon, case UTERM_MONITOR_INPUT: log_debug("new input device %s on seat %s", ev->dev_node, seat->name); - kmscon_seat_add_input(seat->seat, ev->dev_node); + kmscon_seat_add_input(seat->seat, ev->dev_node, seat->conf->mouse); break; } break; diff --git a/src/kmscon_seat.c b/src/kmscon_seat.c index 692762bf..ade4433c 100644 --- a/src/kmscon_seat.c +++ b/src/kmscon_seat.c @@ -577,7 +577,7 @@ static int seat_vt_event(struct uterm_vt *vt, struct uterm_vt_event *ev, } static void seat_input_event(struct uterm_input *input, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, void *data) { struct kmscon_seat *seat = data; @@ -766,7 +766,7 @@ int kmscon_seat_new(struct kmscon_seat **out, if (ret) goto err_conf; - ret = uterm_input_register_cb(seat->input, seat_input_event, seat); + ret = uterm_input_register_key_cb(seat->input, seat_input_event, seat); if (ret) goto err_input; @@ -788,7 +788,7 @@ int kmscon_seat_new(struct kmscon_seat **out, return 0; err_input_cb: - uterm_input_unregister_cb(seat->input, seat_input_event, seat); + uterm_input_unregister_key_cb(seat->input, seat_input_event, seat); err_input: uterm_input_unref(seat->input); err_conf: @@ -834,7 +834,7 @@ void kmscon_seat_free(struct kmscon_seat *seat) } uterm_vt_deallocate(seat->vt); - uterm_input_unregister_cb(seat->input, seat_input_event, seat); + uterm_input_unregister_key_cb(seat->input, seat_input_event, seat); uterm_input_unref(seat->input); kmscon_conf_free(seat->conf_ctx); free(seat->name); @@ -924,12 +924,12 @@ void kmscon_seat_refresh_display(struct kmscon_seat *seat, } } -int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node) +int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node, bool mouse) { if (!seat || !node) return -EINVAL; - uterm_input_add_dev(seat->input, node); + uterm_input_add_dev(seat->input, node, mouse); return 0; } diff --git a/src/kmscon_seat.h b/src/kmscon_seat.h index 439e3582..a179caf4 100644 --- a/src/kmscon_seat.h +++ b/src/kmscon_seat.h @@ -90,7 +90,7 @@ void kmscon_seat_remove_display(struct kmscon_seat *seat, struct uterm_display *disp); void kmscon_seat_refresh_display(struct kmscon_seat *seat, struct uterm_display *disp); -int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node); +int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node, bool mouse); void kmscon_seat_remove_input(struct kmscon_seat *seat, const char *node); const char *kmscon_seat_get_name(struct kmscon_seat *seat); diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index 7fa8eb8e..d5be714c 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -37,6 +37,7 @@ #include #include "conf.h" #include "eloop.h" +#include "font.h" #include "kmscon_conf.h" #include "kmscon_seat.h" #include "kmscon_terminal.h" @@ -59,6 +60,17 @@ struct screen { bool pending; }; +struct kmscon_pointer { + bool visible; + bool select; + int32_t x; + int32_t y; + unsigned int posx; + unsigned int posy; + char *copy; + int copy_len; +}; + struct kmscon_terminal { unsigned long ref; struct ev_eloop *eloop; @@ -82,6 +94,8 @@ struct kmscon_terminal { struct kmscon_font_attr font_attr; struct kmscon_font *font; struct kmscon_font *bold_font; + + struct kmscon_pointer pointer; }; static void do_clear_margins(struct screen *scr) @@ -130,6 +144,35 @@ static void do_clear_margins(struct screen *scr) static int font_set(struct kmscon_terminal *term); + +static void coord_to_cell(struct kmscon_terminal *term, int32_t x, int32_t y, unsigned int *posx, unsigned int *posy) +{ + int fw = term->font->attr.width; + int fh = term->font->attr.height; + int w = tsm_screen_get_width(term->console); + int h = tsm_screen_get_height(term->console); + + *posx = x / fw; + *posy = y / fh; + + if (*posx >= w) + *posx = w; + + if (*posy >= h) + *posy = h; +} + +static void draw_pointer(struct screen *scr) +{ + struct tsm_screen_attr attr; + + if (!scr->term->pointer.visible) + return; + + tsm_vte_get_def_attr(scr->term->vte, &attr); + kmscon_text_draw_pointer(scr->txt, scr->term->pointer.x, scr->term->pointer.y, &attr); +} + static void do_redraw_screen(struct screen *scr) { int ret; @@ -142,6 +185,7 @@ static void do_redraw_screen(struct screen *scr) kmscon_text_prepare(scr->txt); tsm_screen_draw(scr->term->console, kmscon_text_draw_cb, scr->txt); + draw_pointer(scr); kmscon_text_render(scr->txt); ret = uterm_display_swap(scr->disp, false); @@ -201,6 +245,49 @@ static bool has_kms_display(struct kmscon_terminal *term) return false; } +/* + * Align the pointer maximum to the minimum width and height of all screens + * according to their orientation, as kmscon only support mirroring, and one + * terminal size for all screens. + */ +static void update_pointer_max_all(struct kmscon_terminal *term) +{ + struct shl_dlist *iter; + struct screen *scr; + struct uterm_mode *mode; + unsigned int max_x = INT_MAX; + unsigned int max_y = INT_MAX; + unsigned int sw, sh; + + if (!term->awake) + return; + + shl_dlist_for_each(iter, &term->screens) { + scr = shl_dlist_entry(iter, struct screen, list); + + mode = uterm_display_get_current(scr->disp); + if (!mode) + continue; + + if (scr->txt->orientation == OR_NORMAL || scr->txt->orientation == OR_UPSIDE_DOWN) { + sw = uterm_mode_get_width(mode); + sh = uterm_mode_get_height(mode); + } else { + sw = uterm_mode_get_height(mode); + sh = uterm_mode_get_width(mode); + } + if (!sw || !sh) + continue; + + if (sw < max_x) + max_x = sw; + if (sh < max_y) + max_y = sh; + } + if (max_x < INT_MAX && max_y < INT_MAX) + uterm_input_set_pointer_max(term->input, max_x, max_y); +} + static void redraw_all_test(struct kmscon_terminal *term) { struct shl_dlist *iter; @@ -245,6 +332,15 @@ static void osc_event(struct tsm_vte *vte, const char *osc_string, } } +static void mouse_event(struct tsm_vte *vte, enum tsm_mouse_track_mode track_mode, + bool track_pixels, void *data) +{ + struct kmscon_terminal *term = data; + + term->pointer.select = false; + tsm_screen_selection_reset(term->console); +} + /* * Resize terminal * We support multiple monitors per terminal. As some software-rendering @@ -264,6 +360,8 @@ static void terminal_resize(struct kmscon_terminal *term, { bool resize = false; + update_pointer_max_all(term); + if (!term->min_cols || (cols > 0 && cols < term->min_cols)) { term->min_cols = cols; resize = true; @@ -500,7 +598,7 @@ static void rm_display(struct kmscon_terminal *term, struct uterm_display *disp) } static void input_event(struct uterm_input *input, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, void *data) { struct kmscon_terminal *term = data; @@ -585,6 +683,124 @@ static void input_event(struct uterm_input *input, } } +static void start_selection(struct tsm_screen *console, unsigned int x, unsigned int y) +{ + tsm_screen_selection_reset(console); + tsm_screen_selection_start(console, x, y); +} + +static void update_selection(struct tsm_screen *console, unsigned int x, unsigned int y) +{ + tsm_screen_selection_target(console, x, y); +} + +static void copy_selection(struct kmscon_terminal *term) +{ + if (term->pointer.copy) { + free(term->pointer.copy); + term->pointer.copy = NULL; + term->pointer.copy_len = 0; + } + term->pointer.copy_len = tsm_screen_selection_copy(term->console, &term->pointer.copy); +} + +static void forward_pointer_event(struct kmscon_terminal *term, struct uterm_input_pointer_event *ev) +{ + unsigned int event; + + switch (ev->event) { + case UTERM_MOVED: + event = TSM_MOUSE_EVENT_MOVED; + break; + case UTERM_BUTTON: + if (ev->pressed) + event = TSM_MOUSE_EVENT_PRESSED; + else + event = TSM_MOUSE_EVENT_RELEASED; + break; + default: + return; + } + tsm_vte_handle_mouse(term->vte, term->pointer.posx, term->pointer.posy, + term->pointer.x, term->pointer.y, ev->button, event, 0); +} + +static void handle_pointer_button(struct kmscon_terminal *term, struct uterm_input_pointer_event *ev) +{ + switch(ev->button) { + case 0: + if (ev->pressed) { + if (ev->double_click) { + tsm_screen_selection_word(term->console, term->pointer.posx, term->pointer.posy); + copy_selection(term); + term->pointer.select = false; + } else { + term->pointer.select = true; + start_selection(term->console, term->pointer.posx, term->pointer.posy); + } + } else { + if (term->pointer.select) + copy_selection(term); + term->pointer.select = false; + } + break; + case 1: + term->pointer.select = false; + tsm_screen_selection_reset(term->console); + break; + case 2: + if (ev->pressed) { + kmscon_pty_write(term->pty, term->pointer.copy, term->pointer.copy_len); + tsm_screen_selection_reset(term->console); + } + } +} + +static void pointer_event(struct uterm_input *input, + struct uterm_input_pointer_event *ev, + void *data) +{ + struct kmscon_terminal *term = data; + + if (ev->event == UTERM_MOVED) { + term->pointer.x = ev->pointer_x; + term->pointer.y = ev->pointer_y; + + coord_to_cell(term, term->pointer.x, term->pointer.y, &term->pointer.posx, &term->pointer.posy); + term->pointer.visible = true; + } + + if (tsm_vte_get_mouse_mode(term->vte) != TSM_MOUSE_TRACK_DISABLE && + ev->event != UTERM_SYNC) { + forward_pointer_event(term, ev); + return; + } + + switch (ev->event) { + default: + break; + case UTERM_MOVED: + if (term->pointer.select) + update_selection(term->console,term->pointer.posx, term->pointer.posy); + break; + case UTERM_BUTTON: + handle_pointer_button(term, ev); + break; + case UTERM_WHEEL: + if (ev->wheel > 0) + tsm_screen_sb_up(term->console, 3); + else + tsm_screen_sb_down(term->console, 3); + break; + case UTERM_SYNC: + redraw_all(term); + break; + case UTERM_HIDE_TIMEOUT: + term->pointer.visible = false; + break; + } +} + static void rm_all_screens(struct kmscon_terminal *term) { struct shl_dlist *iter; @@ -615,6 +831,8 @@ static int terminal_open(struct kmscon_terminal *term) return ret; term->opened = true; + + update_pointer_max_all(term); redraw_all(term); return 0; } @@ -631,7 +849,8 @@ static void terminal_destroy(struct kmscon_terminal *term) terminal_close(term); rm_all_screens(term); - uterm_input_unregister_cb(term->input, input_event, term); + uterm_input_unregister_pointer_cb(term->input, pointer_event, term); + uterm_input_unregister_key_cb(term->input, input_event, term); ev_eloop_rm_fd(term->ptyfd); kmscon_pty_unref(term->pty); kmscon_font_unref(term->bold_font); @@ -745,6 +964,7 @@ int kmscon_terminal_register(struct kmscon_session **out, BUILD_BACKSPACE_SENDS_DELETE); tsm_vte_set_osc_cb(term->vte, osc_event, (void *)term); + tsm_vte_set_mouse_cb(term->vte, mouse_event, (void *)term); ret = tsm_vte_set_palette(term->vte, term->conf->palette); if (ret) @@ -792,15 +1012,21 @@ int kmscon_terminal_register(struct kmscon_session **out, if (ret) goto err_pty; - ret = uterm_input_register_cb(term->input, input_event, term); + ret = uterm_input_register_key_cb(term->input, input_event, term); if (ret) goto err_ptyfd; + if (term->conf->mouse) { + ret = uterm_input_register_pointer_cb(term->input, pointer_event, term); + if (ret) + goto err_input; + } + ret = kmscon_seat_register_session(seat, &term->session, session_event, term); if (ret) { log_error("cannot register session for terminal: %d", ret); - goto err_input; + goto err_pointer; } ev_eloop_ref(term->eloop); @@ -809,8 +1035,10 @@ int kmscon_terminal_register(struct kmscon_session **out, log_debug("new terminal object %p", term); return 0; +err_pointer: + uterm_input_unregister_pointer_cb(term->input, pointer_event, term); err_input: - uterm_input_unregister_cb(term->input, input_event, term); + uterm_input_unregister_key_cb(term->input, input_event, term); err_ptyfd: ev_eloop_rm_fd(term->ptyfd); err_pty: diff --git a/src/meson.build b/src/meson.build index dee686c3..3f6db43f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -103,6 +103,7 @@ uterm_srcs = [ 'uterm_vt.c', 'uterm_input.c', 'uterm_input_uxkb.c', + 'uterm_input_pointer.c', embed_gen.process('uterm_input_fallback.xkb', extra_args: keymap_regex), ] uterm_dep = [ diff --git a/src/text.c b/src/text.c index 6d1b496d..29cb079d 100644 --- a/src/text.c +++ b/src/text.c @@ -466,6 +466,30 @@ int kmscon_text_draw(struct kmscon_text *txt, return txt->ops->draw(txt, id, ch, len, width, posx, posy, attr); } +/** + * kmscon_text_draw_pointer: + * @txt: valid text renderer + * @x: X-position of the center of the pointer in pixel + * @y: Y-position of the center of the pointer in pixel + * @attr: glyph attributes + * + * This draws a single I glyph at the requested position. The position is a + * a pixel position! You must precede this call with kmscon_text_prepare(). + * Use this function to feed the mouse pointer into the rendering pipeline + * and finally call kmscon_text_render(). + * + * Returns: 0 on success or negative error code if it couldn't be drawn. + */ +int kmscon_text_draw_pointer(struct kmscon_text *txt, + unsigned int x, unsigned int y, + const struct tsm_screen_attr *attr) +{ + if (!txt || !txt->rendering || !txt->ops->draw_pointer) + return -EINVAL; + + return txt->ops->draw_pointer(txt, x, y, attr); +} + /** * kmscon_text_render: * @txt: valid text renderer diff --git a/src/text.h b/src/text.h index 0c51ce94..4fde896b 100644 --- a/src/text.h +++ b/src/text.h @@ -82,6 +82,10 @@ struct kmscon_text_ops { unsigned int width, unsigned int posx, unsigned int posy, const struct tsm_screen_attr *attr); + int (*draw_pointer) (struct kmscon_text *txt, + unsigned int x, + unsigned int y, + const struct tsm_screen_attr *attr); int (*render) (struct kmscon_text *txt); void (*abort) (struct kmscon_text *txt); }; @@ -113,6 +117,9 @@ int kmscon_text_draw(struct kmscon_text *txt, unsigned int width, unsigned int posx, unsigned int posy, const struct tsm_screen_attr *attr); +int kmscon_text_draw_pointer(struct kmscon_text *txt, + unsigned int x, unsigned int y, + const struct tsm_screen_attr *attr); int kmscon_text_render(struct kmscon_text *txt); void kmscon_text_abort(struct kmscon_text *txt); diff --git a/src/text_bblit.c b/src/text_bblit.c index 0c1a3240..1d6dcc3a 100644 --- a/src/text_bblit.c +++ b/src/text_bblit.c @@ -38,6 +38,7 @@ #include #include "font_rotate.h" #include "shl_log.h" +#include "shl_misc.h" #include "text.h" #include "uterm_video.h" @@ -242,6 +243,73 @@ static int bblit_draw(struct kmscon_text *txt, return ret; } +static int bblit_draw_pointer(struct kmscon_text *txt, + unsigned int pointer_x, unsigned int pointer_y, + const struct tsm_screen_attr *attr) +{ + struct uterm_video_buffer *bb_glyph; + struct uterm_mode *mode; + uint32_t ch = 'I'; + uint64_t id = ch; + unsigned int sw, sh; + unsigned int m_x, m_y, x, y; + int ret; + + mode = uterm_display_get_current(txt->disp); + if (!mode) + return -EINVAL; + sw = uterm_mode_get_width(mode); + sh = uterm_mode_get_height(mode); + + if (txt->orientation == OR_NORMAL || txt->orientation == OR_UPSIDE_DOWN) { + m_x = SHL_DIV_ROUND_UP(FONT_WIDTH(txt), 2); + m_y = SHL_DIV_ROUND_UP(FONT_HEIGHT(txt), 2); + } else { + m_x = SHL_DIV_ROUND_UP(FONT_HEIGHT(txt), 2); + m_y = SHL_DIV_ROUND_UP(FONT_WIDTH(txt), 2); + } + + ret = find_glyph(txt, &bb_glyph, id, &ch, 1, attr); + if (ret) + return ret; + + switch (txt->orientation) { + default: + case OR_NORMAL: + x = pointer_x; + y = pointer_y; + break; + case OR_UPSIDE_DOWN: + x = sw - pointer_x; + y = sh - pointer_y; + break; + case OR_RIGHT: + x = sw - pointer_y; + y = pointer_x; + break; + case OR_LEFT: + x = pointer_y; + y = sh - pointer_x; + break; + } + if (x < m_x) + x = m_x; + if (x + m_x > sw) + x = sw - m_x; + if (y < m_y) + y = m_y; + if (y + m_y > sh) + y = sh - m_y; + x -= m_x; + y -= m_y; + + /* draw glyph */ + ret = uterm_display_fake_blend(txt->disp, bb_glyph, x, y, + attr->fr, attr->fg, attr->fb, + attr->br, attr->bg, attr->bb); + return ret; +} + struct kmscon_text_ops kmscon_text_bblit_ops = { .name = "bblit", .owner = NULL, @@ -252,6 +320,7 @@ struct kmscon_text_ops kmscon_text_bblit_ops = { .rotate = bblit_rotate, .prepare = NULL, .draw = bblit_draw, + .draw_pointer = bblit_draw_pointer, .render = NULL, .abort = NULL, }; diff --git a/src/text_bbulk.c b/src/text_bbulk.c index f22d2dc3..ae2cdca4 100644 --- a/src/text_bbulk.c +++ b/src/text_bbulk.c @@ -32,7 +32,6 @@ * pushes all of them at once to the video device. */ -#include #include #include #include @@ -41,6 +40,7 @@ #include "font_rotate.h" #include "shl_hashtable.h" #include "shl_log.h" +#include "shl_misc.h" #include "text.h" #include "uterm_video.h" @@ -48,6 +48,8 @@ struct bbulk { struct uterm_video_blend_req *reqs; + unsigned int req_len; + unsigned int req_total_len; struct shl_hashtable *glyphs; struct shl_hashtable *bold_glyphs; }; @@ -101,10 +103,12 @@ static int bbulk_set(struct kmscon_text *txt) txt->rows = sw / FONT_HEIGHT(txt); txt->cols = sh / FONT_WIDTH(txt); } - bb->reqs = malloc(sizeof(*bb->reqs) * txt->cols * txt->rows); + + bb->req_total_len = txt->cols * txt->rows + 1; // + 1 for the pointer + bb->reqs = malloc(sizeof(*bb->reqs) * bb->req_total_len); if (!bb->reqs) return -ENOMEM; - memset(bb->reqs, 0, sizeof(*bb->reqs) * txt->cols * txt->rows); + memset(bb->reqs, 0, sizeof(*bb->reqs) * bb->req_total_len); for (i = 0; i < txt->rows; ++i) { for (j = 0; j < txt->cols; ++j) { @@ -277,13 +281,94 @@ static int bbulk_draw(struct kmscon_text *txt, return 0; } +static int bblit_draw_pointer(struct kmscon_text *txt, + unsigned int pointer_x, unsigned int pointer_y, + const struct tsm_screen_attr *attr) +{ + struct bbulk *bb = txt->data; + struct uterm_video_blend_req *req; + struct uterm_video_buffer *bb_glyph; + struct uterm_mode *mode; + uint32_t ch = 'I'; + uint64_t id = ch; + unsigned int sw, sh; + unsigned int m_x, m_y, x, y; + int ret; + + mode = uterm_display_get_current(txt->disp); + if (!mode) + return -EINVAL; + sw = uterm_mode_get_width(mode); + sh = uterm_mode_get_height(mode); + + if (txt->orientation == OR_NORMAL || txt->orientation == OR_UPSIDE_DOWN) { + m_x = SHL_DIV_ROUND_UP(FONT_WIDTH(txt), 2); + m_y = SHL_DIV_ROUND_UP(FONT_HEIGHT(txt), 2); + } else { + m_x = SHL_DIV_ROUND_UP(FONT_HEIGHT(txt), 2); + m_y = SHL_DIV_ROUND_UP(FONT_WIDTH(txt), 2); + } + + // pointer is the last request + req = &bb->reqs[bb->req_total_len - 1]; + + ret = find_glyph(txt, &bb_glyph, id, &ch, 1, attr); + if (ret) + return ret; + + req->buf = bb_glyph; + + switch (txt->orientation) { + default: + case OR_NORMAL: + x = pointer_x; + y = pointer_y; + break; + case OR_UPSIDE_DOWN: + x = sw - pointer_x; + y = sh - pointer_y; + break; + case OR_RIGHT: + x = sw - pointer_y; + y = pointer_x; + break; + case OR_LEFT: + x = pointer_y; + y = sh - pointer_x; + break; + } + if (x < m_x) + x = m_x; + if (x + m_x > sw) + x = sw - m_x; + if (y < m_y) + y = m_y; + if (y + m_y > sh) + y = sh - m_y; + x -= m_x; + y -= m_y; + + req->x = x; + req->y = y; + + req->fr = attr->fr; + req->fg = attr->fg; + req->fb = attr->fb; + req->br = attr->br; + req->bg = attr->bg; + req->bb = attr->bb; + + bb->req_len = bb->req_total_len; + return 0; +} + static int bbulk_render(struct kmscon_text *txt) { struct bbulk *bb = txt->data; int ret; ret = uterm_display_fake_blendv(txt->disp, bb->reqs, - txt->cols * txt->rows); + bb->req_len); return ret; } @@ -293,9 +378,9 @@ static int bbulk_prepare(struct kmscon_text *txt) int i; // Clear previous requests - for (i = 0; i < txt->rows * txt->cols; ++i) + for (i = 0; i < bb->req_total_len; ++i) bb->reqs[i].buf = NULL; - + bb->req_len = txt->cols * txt->rows; return 0; } @@ -309,6 +394,7 @@ struct kmscon_text_ops kmscon_text_bbulk_ops = { .rotate = bbulk_rotate, .prepare = bbulk_prepare, .draw = bbulk_draw, + .draw_pointer = bblit_draw_pointer, .render = bbulk_render, .abort = NULL, }; diff --git a/src/text_gltex.c b/src/text_gltex.c index 6a1c8f39..4997514e 100644 --- a/src/text_gltex.c +++ b/src/text_gltex.c @@ -344,7 +344,7 @@ static struct atlas *get_atlas(struct kmscon_text *txt, unsigned int num) log_debug("new atlas of size %ux%u for %zu", width, height, newsize); - nsize = txt->cols * txt->rows; + nsize = txt->cols * txt->rows + 1; // +1 for the mouse pointer atlas->cache_pos = malloc(sizeof(GLfloat) * nsize * 2 * 6); if (!atlas->cache_pos) @@ -602,6 +602,7 @@ static int gltex_draw(struct kmscon_text *txt, struct gltex *gt = txt->data; struct atlas *atlas; struct glyph *glyph; + float gl_x1, gl_x2, gl_y1, gl_y2; int ret, i, idx; if (!width) @@ -618,45 +619,39 @@ static int gltex_draw(struct kmscon_text *txt, if (atlas->cache_num >= atlas->cache_size) return -ERANGE; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 0] = - gt->advance_x * posx - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 1] = - 1 - gt->advance_y * posy; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 2] = - gt->advance_x * posx - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 3] = - 1 - (gt->advance_y * posy + gt->advance_y); - atlas->cache_pos[atlas->cache_num * 2 * 6 + 4] = - gt->advance_x * posx + width * gt->advance_x - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 5] = - 1 - (gt->advance_y * posy + gt->advance_y); - - atlas->cache_pos[atlas->cache_num * 2 * 6 + 6] = - gt->advance_x * posx - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 7] = - 1 - gt->advance_y * posy; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 8] = - gt->advance_x * posx + width * gt->advance_x - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 9] = - 1 - (gt->advance_y * posy + gt->advance_y); - atlas->cache_pos[atlas->cache_num * 2 * 6 + 10] = - gt->advance_x * posx + width * gt->advance_x - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 11] = - 1 - gt->advance_y * posy; - - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 0] = glyph->texoff; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 1] = 0.0; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 2] = glyph->texoff; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 3] = 1.0; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 4] = glyph->texoff + width; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 5] = 1.0; - - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 6] = glyph->texoff; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 7] = 0.0; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 8] = glyph->texoff + width; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 9] = 1.0; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 10] = glyph->texoff + width; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 11] = 0.0; + idx = atlas->cache_num * 2 * 6; + gl_x1 = gt->advance_x * posx - 1.0; + gl_x2 = gl_x1 + width * gt->advance_x; + gl_y1 = 1.0 - gt->advance_y * posy; + gl_y2 = gl_y1 - gt->advance_y; + + atlas->cache_pos[idx + 0] = gl_x1; + atlas->cache_pos[idx + 1] = gl_y1; + atlas->cache_pos[idx + 2] = gl_x1; + atlas->cache_pos[idx + 3] = gl_y2; + atlas->cache_pos[idx + 4] = gl_x2; + atlas->cache_pos[idx + 5] = gl_y2; + + atlas->cache_pos[idx + 6] = gl_x1; + atlas->cache_pos[idx + 7] = gl_y1; + atlas->cache_pos[idx + 8] = gl_x2; + atlas->cache_pos[idx + 9] = gl_y2; + atlas->cache_pos[idx + 10] = gl_x2; + atlas->cache_pos[idx + 11] = gl_y1; + + atlas->cache_texpos[idx + 0] = glyph->texoff; + atlas->cache_texpos[idx + 1] = 0.0; + atlas->cache_texpos[idx + 2] = glyph->texoff; + atlas->cache_texpos[idx + 3] = 1.0; + atlas->cache_texpos[idx + 4] = glyph->texoff + width; + atlas->cache_texpos[idx + 5] = 1.0; + + atlas->cache_texpos[idx + 6] = glyph->texoff; + atlas->cache_texpos[idx + 7] = 0.0; + atlas->cache_texpos[idx + 8] = glyph->texoff + width; + atlas->cache_texpos[idx + 9] = 1.0; + atlas->cache_texpos[idx + 10] = glyph->texoff + width; + atlas->cache_texpos[idx + 11] = 0.0; for (i = 0; i < 6; ++i) { idx = atlas->cache_num * 3 * 6 + i * 3; @@ -682,6 +677,92 @@ static int gltex_draw(struct kmscon_text *txt, return 0; } +static int gltex_draw_pointer(struct kmscon_text *txt, + unsigned int x, unsigned int y, + const struct tsm_screen_attr *attr) +{ + struct gltex *gt = txt->data; + struct atlas *atlas; + struct glyph *glyph; + float gl_x1, gl_x2, gl_y1, gl_y2; + unsigned int sw, sh; + int ret, i, idx; + uint32_t ch = 'I'; + uint64_t id = ch; + + ret = find_glyph(txt, &glyph, id, &ch, 1, attr); + if (ret) + return ret; + + atlas = glyph->atlas; + + if (atlas->cache_num >= atlas->cache_size) + return -ERANGE; + + if(txt->orientation == OR_NORMAL || txt->orientation == OR_UPSIDE_DOWN) { + sw = gt->sw; + sh = gt->sh; + } else { + sw = gt->sh; + sh = gt->sw; + } + + if (x > sw) + x = sw; + + if (y > sh) + y = sh; + + gl_x1 = x * 2.0 / sw - 1.0 - gt->advance_x / 2.0; + gl_y1 = 1.0 - y * 2.0 / sh + gt->advance_y / 2.0; + gl_x2 = gl_x1 + gt->advance_x; + gl_y2 = gl_y1 - gt->advance_y; + + idx = atlas->cache_num * 2 * 6; + + atlas->cache_pos[idx + 0] = gl_x1; + atlas->cache_pos[idx + 1] = gl_y1; + atlas->cache_pos[idx + 2] = gl_x1; + atlas->cache_pos[idx + 3] = gl_y2; + atlas->cache_pos[idx + 4] = gl_x2; + atlas->cache_pos[idx + 5] = gl_y2; + + atlas->cache_pos[idx + 6] = gl_x1; + atlas->cache_pos[idx + 7] = gl_y1; + atlas->cache_pos[idx + 8] = gl_x2; + atlas->cache_pos[idx + 9] = gl_y2; + atlas->cache_pos[idx + 10] = gl_x2; + atlas->cache_pos[idx + 11] = gl_y1; + + atlas->cache_texpos[idx + 0] = glyph->texoff; + atlas->cache_texpos[idx + 1] = 0.0; + atlas->cache_texpos[idx + 2] = glyph->texoff; + atlas->cache_texpos[idx + 3] = 1.0; + atlas->cache_texpos[idx + 4] = glyph->texoff + 1.0; + atlas->cache_texpos[idx + 5] = 1.0; + + atlas->cache_texpos[idx + 6] = glyph->texoff; + atlas->cache_texpos[idx + 7] = 0.0; + atlas->cache_texpos[idx + 8] = glyph->texoff + 1.0; + atlas->cache_texpos[idx + 9] = 1.0; + atlas->cache_texpos[idx + 10] = glyph->texoff + 1.0; + atlas->cache_texpos[idx + 11] = 0.0; + + for (i = 0; i < 6; ++i) { + idx = atlas->cache_num * 3 * 6 + i * 3; + atlas->cache_fgcol[idx + 0] = attr->fr / 255.0; + atlas->cache_fgcol[idx + 1] = attr->fg / 255.0; + atlas->cache_fgcol[idx + 2] = attr->fb / 255.0; + atlas->cache_bgcol[idx + 0] = attr->br / 255.0; + atlas->cache_bgcol[idx + 1] = attr->bg / 255.0; + atlas->cache_bgcol[idx + 2] = attr->bb / 255.0; + } + + ++atlas->cache_num; + + return 0; +} + static int gltex_render(struct kmscon_text *txt) { struct gltex *gt = txt->data; @@ -748,6 +829,7 @@ struct kmscon_text_ops kmscon_text_gltex_ops = { .rotate = gltex_rotate, .prepare = gltex_prepare, .draw = gltex_draw, + .draw_pointer = gltex_draw_pointer, .render = gltex_render, .abort = NULL, }; diff --git a/src/text_pixman.c b/src/text_pixman.c index 8f4da68c..5c29e11a 100644 --- a/src/text_pixman.c +++ b/src/text_pixman.c @@ -36,6 +36,7 @@ #include "font_rotate.h" #include "shl_hashtable.h" #include "shl_log.h" +#include "shl_misc.h" #include "text.h" #include "uterm_video.h" @@ -431,7 +432,7 @@ static int tp_draw(struct kmscon_text *txt, * image for each glyph here. * libc malloc() is pretty fast, but this still costs us a lot of * rendering performance. */ - if (!fc.red && !fc.green && !fc.blue) { + if (fc.red == 0xff00 && fc.green == 0xff00 && fc.blue == 0xff00) { col = tp->white; pixman_image_ref(col); } else { @@ -491,6 +492,119 @@ static int tp_draw(struct kmscon_text *txt, return 0; } +static int tp_draw_pointer(struct kmscon_text *txt, + unsigned int pointer_x, unsigned int pointer_y, + const struct tsm_screen_attr *attr) +{ + struct tp_pixman *tp = txt->data; + struct uterm_mode *mode; + struct tp_glyph *glyph; + uint32_t ch = 'I'; + uint64_t id = ch; + int ret; + unsigned int x, y, w, h, sw, sh; + unsigned int m_x, m_y; + uint32_t bc; + pixman_color_t fc; + pixman_image_t *col; + + mode = uterm_display_get_current(txt->disp); + if (!mode) + return -EINVAL; + sw = uterm_mode_get_width(mode); + sh = uterm_mode_get_height(mode); + + ret = find_glyph(txt, &glyph, id, &ch, 1, attr); + if (ret) + return ret; + + bc = (attr->br << 16) | (attr->bg << 8) | (attr->bb); + fc.red = attr->fr << 8; + fc.green = attr->fg << 8; + fc.blue = attr->fb << 8; + fc.alpha = 0xffff; + + /* TODO: We _really_ should fix pixman to allow something like + * pixman_image_set_solid_fill(img, &fc) to avoid allocating a pixman + * image for each glyph here. + * libc malloc() is pretty fast, but this still costs us a lot of + * rendering performance. */ + if (attr->fr == 0xff && attr->fg == 0xff && attr->fb == 0xff) { + col = tp->white; + pixman_image_ref(col); + } else { + col = pixman_image_create_solid_fill(&fc); + if (!col) { + log_error("cannot create pixman color image"); + return -ENOMEM; + } + } + + if (txt->orientation == OR_NORMAL || txt->orientation == OR_UPSIDE_DOWN) { + w = FONT_WIDTH(txt); + h = FONT_HEIGHT(txt); + } else { + w = FONT_HEIGHT(txt); + h = FONT_WIDTH(txt); + } + + m_x = SHL_DIV_ROUND_UP(w, 2); + m_y = SHL_DIV_ROUND_UP(h, 2); + + switch (txt->orientation) { + default: + case OR_NORMAL: + x = pointer_x; + y = pointer_y; + break; + case OR_UPSIDE_DOWN: + x = sw - pointer_x; + y = sh - pointer_y; + break; + case OR_RIGHT: + x = sw - pointer_y; + y = pointer_x; + break; + case OR_LEFT: + x = pointer_y; + y = sh - pointer_x; + break; + } + if (x < m_x) + x = m_x; + if (x + m_x > sw) + x = sw - m_x; + if (y < m_y) + y = m_y; + if (y + m_y > sh) + y = sh - m_y; + x -= m_x; + y -= m_y; + + if (!bc) { + pixman_image_composite(PIXMAN_OP_SRC, + col, + glyph->surf, + tp->surf[tp->cur], + 0, 0, 0, 0, + x, y, w, h); + } else { + pixman_fill(tp->c_data, tp->c_stride / 4, tp->c_bpp, + x, y, w, h, bc); + + pixman_image_composite(PIXMAN_OP_OVER, + col, + glyph->surf, + tp->surf[tp->cur], + 0, 0, 0, 0, + x, y, w, h); + } + + pixman_image_unref(col); + + return 0; +} + static int tp_render(struct kmscon_text *txt) { struct tp_pixman *tp = txt->data; @@ -519,6 +633,7 @@ struct kmscon_text_ops kmscon_text_pixman_ops = { .rotate = tp_rotate, .prepare = tp_prepare, .draw = tp_draw, + .draw_pointer = tp_draw_pointer, .render = tp_render, .abort = NULL, }; diff --git a/src/uterm_input.c b/src/uterm_input.c index 5e64dfea..cec0798e 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -51,15 +51,27 @@ static void input_free_dev(struct uterm_input_dev *dev); -static void notify_key(struct uterm_input_dev *dev, - uint16_t type, - uint16_t code, - int32_t value) +static void notify_event(struct uterm_input_dev *dev, + uint16_t type, + uint16_t code, + int32_t value) { - if (type != EV_KEY) - return; - - uxkb_dev_process(dev, value, code); + switch (type) { + case EV_KEY: + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + uxkb_dev_process(dev, value, code); + pointer_dev_button(dev, code, value); + break; + case EV_REL: + pointer_dev_rel(dev, code, value); + break; + case EV_ABS: + pointer_dev_abs(dev, code, value); + break; + case EV_SYN: + pointer_dev_sync(dev); + break; + } } static void input_data_dev(struct ev_fd *fd, int mask, void *data) @@ -93,7 +105,7 @@ static void input_data_dev(struct ev_fd *fd, int mask, void *data) } else { n = len / sizeof(*ev); for (i = 0; i < n; i++) - notify_key(dev, ev[i].type, ev[i].code, + notify_event(dev, ev[i].type, ev[i].code, ev[i].value); } } @@ -112,8 +124,8 @@ static int input_wake_up_dev(struct uterm_input_dev *dev) dev->node, errno); return -EFAULT; } - - uxkb_dev_wake_up(dev); + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + uxkb_dev_wake_up(dev); ret = ev_eloop_new_fd(dev->input->eloop, &dev->fd, dev->rfd, EV_READABLE, input_data_dev, dev); @@ -131,7 +143,8 @@ static void input_sleep_dev(struct uterm_input_dev *dev) if (dev->rfd < 0) return; - uxkb_dev_sleep(dev); + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + uxkb_dev_sleep(dev); dev->repeating = false; ev_timer_update(dev->repeat_timer, NULL); @@ -141,6 +154,80 @@ static void input_sleep_dev(struct uterm_input_dev *dev) dev->rfd = -1; } +static int input_init_keyboard(struct uterm_input_dev *dev) +{ + dev->num_syms = 1; + dev->event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms); + if (!dev->event.keysyms) + return -1; + dev->event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms); + if (!dev->event.codepoints) + goto err_syms; + dev->repeat_event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms); + if (!dev->repeat_event.keysyms) + goto err_codepoints; + dev->repeat_event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms); + if (!dev->repeat_event.codepoints) + goto err_rsyms; + + if (uxkb_dev_init(dev)) + goto err_rcodepoints; + + return 0; + +err_rcodepoints: + free(dev->repeat_event.codepoints); +err_rsyms: + free(dev->repeat_event.keysyms); +err_codepoints: + free(dev->event.codepoints); +err_syms: + free(dev->event.keysyms); + return -1; +} + +static void input_exit_keyboard(struct uterm_input_dev *dev) +{ + uxkb_dev_destroy(dev); + free(dev->repeat_event.codepoints); + free(dev->repeat_event.keysyms); + free(dev->event.codepoints); + free(dev->event.keysyms); +} + +static int input_init_abs(struct uterm_input_dev *dev) +{ + struct input_absinfo info; + int ret, fd; + + fd = open(dev->node, O_NONBLOCK | O_CLOEXEC | O_RDONLY); + if (fd < 0) + return 0; + + ret = ioctl(fd, EVIOCGABS(ABS_X), &info); + if (ret < 0) + goto err_closefd; + + dev->pointer.min_x = info.minimum; + dev->pointer.max_x = info.maximum; + + ret = ioctl(fd, EVIOCGABS(ABS_Y), &info); + if (ret < 0) + goto err_closefd; + + dev->pointer.min_y = info.minimum; + dev->pointer.max_y = info.maximum; + + llog_debug(dev->input, "ABSX min %d max %d ABSY min %d max %d\n", + dev->pointer.min_x, dev->pointer.max_x, + dev->pointer.min_y, dev->pointer.max_y); + return 0; + +err_closefd: + close(fd); + return ret; +} + static void input_new_dev(struct uterm_input *input, const char *node, unsigned int capabilities) @@ -155,28 +242,28 @@ static void input_new_dev(struct uterm_input *input, dev->input = input; dev->rfd = -1; dev->capabilities = capabilities; + dev->pointer.kind = POINTER_NONE; dev->node = strdup(node); if (!dev->node) goto err_free; - dev->num_syms = 1; - dev->event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms); - if (!dev->event.keysyms) - goto err_node; - dev->event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms); - if (!dev->event.codepoints) - goto err_syms; - dev->repeat_event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms); - if (!dev->repeat_event.keysyms) - goto err_codepoints; - dev->repeat_event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms); - if (!dev->repeat_event.codepoints) - goto err_rsyms; - - ret = uxkb_dev_init(dev); - if (ret) - goto err_rcodepoints; + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) { + ret = input_init_keyboard(dev); + if (ret) + goto err_node; + } + if (dev->capabilities & UTERM_DEVICE_HAS_ABS) { + ret = input_init_abs(dev); + if (ret) + goto err_node; + if (dev->capabilities & UTERM_DEVICE_HAS_TOUCH) + dev->pointer.kind = POINTER_TOUCHPAD; + else + dev->pointer.kind = POINTER_VMOUSE; + } + if (dev->capabilities & UTERM_DEVICE_HAS_REL) + dev->pointer.kind = POINTER_MOUSE; if (input->awake > 0) { ret = input_wake_up_dev(dev); @@ -189,15 +276,8 @@ static void input_new_dev(struct uterm_input *input, return; err_kbd: - uxkb_dev_destroy(dev); -err_rcodepoints: - free(dev->repeat_event.codepoints); -err_rsyms: - free(dev->repeat_event.keysyms); -err_codepoints: - free(dev->event.codepoints); -err_syms: - free(dev->event.keysyms); + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + input_exit_keyboard(dev); err_node: free(dev->node); err_free: @@ -209,15 +289,22 @@ static void input_free_dev(struct uterm_input_dev *dev) llog_debug(dev->input, "free device %s", dev->node); input_sleep_dev(dev); shl_dlist_unlink(&dev->list); - uxkb_dev_destroy(dev); - free(dev->repeat_event.codepoints); - free(dev->repeat_event.keysyms); - free(dev->event.codepoints); - free(dev->event.keysyms); + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + input_exit_keyboard(dev); free(dev->node); free(dev); } +static void hide_pointer_timer(struct ev_timer *timer, uint64_t num, void *data) +{ + struct uterm_input *input = data; + struct uterm_input_pointer_event pev; + + pev.event = UTERM_HIDE_TIMEOUT; + + shl_hook_call(input->pointer_hook, input, &pev); +} + SHL_EXPORT int uterm_input_new(struct uterm_input **out, struct ev_eloop *eloop, @@ -261,10 +348,19 @@ int uterm_input_new(struct uterm_input **out, input->repeat_rate = repeat_rate; shl_dlist_init(&input->devices); - ret = shl_hook_new(&input->hook); + ret = shl_hook_new(&input->key_hook); if (ret) goto err_free; + ret = shl_hook_new(&input->pointer_hook); + if (ret) + goto err_hook; + + ret = ev_eloop_new_timer(input->eloop, &input->hide_pointer, NULL, + hide_pointer_timer, input); + if (ret) + goto err_hook_pointer; + /* xkbcommon won't use the XKB_DEFAULT_OPTIONS environment * variable if options is an empty string. * So if all variables are empty, use NULL instead. @@ -280,15 +376,22 @@ int uterm_input_new(struct uterm_input **out, ret = uxkb_desc_init(input, model, layout, variant, options, locale, keymap, compose_file, compose_file_len); if (ret) - goto err_hook; + goto err_hide_timer; llog_debug(input, "new object %p", input); ev_eloop_ref(input->eloop); *out = input; return 0; +err_hide_timer: + ev_eloop_rm_timer(input->hide_pointer); + +err_hook_pointer: + shl_hook_free(input->pointer_hook); + err_hook: - shl_hook_free(input->hook); + shl_hook_free(input->key_hook); + err_free: free(input); return ret; @@ -321,7 +424,7 @@ void uterm_input_unref(struct uterm_input *input) } uxkb_desc_destroy(input); - shl_hook_free(input->hook); + shl_hook_free(input->key_hook); ev_eloop_unref(input->eloop); free(input); } @@ -338,6 +441,8 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, unsigned int capabilities = 0; unsigned long evbits[NLONGS(EV_CNT)] = { 0 }; unsigned long keybits[NLONGS(KEY_CNT)] = { 0 }; + unsigned long relbits[NLONGS(REL_CNT)] = { 0 }; + unsigned long absbits[NLONGS(ABS_CNT)] = { 0 }; fd = open(node, O_NONBLOCK | O_CLOEXEC | O_RDONLY); if (fd < 0) @@ -365,6 +470,32 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, break; } } + if (input_bit_is_set(keybits, BTN_LEFT)) + capabilities |= UTERM_DEVICE_HAS_MOUSE_BTN; + if (input_bit_is_set(keybits, BTN_TOUCH)) + capabilities |= UTERM_DEVICE_HAS_TOUCH; + } + + if (input_bit_is_set(evbits, EV_SYN) && + input_bit_is_set(evbits, EV_REL)) { + ret = ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbits)), relbits); + if (ret < 0) + goto err_ioctl; + if (input_bit_is_set(relbits, REL_X) && + input_bit_is_set(relbits, REL_Y)) + capabilities |= UTERM_DEVICE_HAS_REL; + if (input_bit_is_set(relbits, REL_WHEEL)) + capabilities |= UTERM_DEVICE_HAS_WHEEL; + } + + if (input_bit_is_set(evbits, EV_SYN) && + input_bit_is_set(evbits, EV_ABS)) { + ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits); + if (ret < 0) + goto err_ioctl; + if (input_bit_is_set(absbits, ABS_X) && + input_bit_is_set(absbits, ABS_Y)) + capabilities |= UTERM_DEVICE_HAS_ABS; } if (input_bit_is_set(evbits, EV_LED)) @@ -380,8 +511,10 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, return 0; } +#define HAS_ALL(caps, flags) (((caps) & (flags)) == (flags)) + SHL_EXPORT -void uterm_input_add_dev(struct uterm_input *input, const char *node) +void uterm_input_add_dev(struct uterm_input *input, const char *node, bool mouse) { unsigned int capabilities; @@ -389,12 +522,20 @@ void uterm_input_add_dev(struct uterm_input *input, const char *node) return; capabilities = probe_device_capabilities(input, node); - if (!(capabilities & UTERM_DEVICE_HAS_KEYS)) { - llog_debug(input, "ignoring non-useful device %s", node); + if (HAS_ALL(capabilities, UTERM_DEVICE_HAS_KEYS)) { + input_new_dev(input, node, capabilities); return; } - - input_new_dev(input, node, capabilities); + if (HAS_ALL(capabilities, UTERM_DEVICE_HAS_REL | UTERM_DEVICE_HAS_MOUSE_BTN) || + HAS_ALL(capabilities, UTERM_DEVICE_HAS_ABS | UTERM_DEVICE_HAS_TOUCH) || + HAS_ALL(capabilities, UTERM_DEVICE_HAS_ABS | UTERM_DEVICE_HAS_MOUSE_BTN)) { + if (mouse) + input_new_dev(input, node, capabilities); + else + llog_debug(input, "ignoring pointer device %s", node); + } else { + llog_debug(input, "ignoring non-useful device %s", node); + } } SHL_EXPORT @@ -418,25 +559,47 @@ void uterm_input_remove_dev(struct uterm_input *input, const char *node) } SHL_EXPORT -int uterm_input_register_cb(struct uterm_input *input, - uterm_input_cb cb, +int uterm_input_register_key_cb(struct uterm_input *input, + uterm_input_key_cb cb, void *data) { if (!input || !cb) return -EINVAL; - return shl_hook_add_cast(input->hook, cb, data, false); + return shl_hook_add_cast(input->key_hook, cb, data, false); } SHL_EXPORT -void uterm_input_unregister_cb(struct uterm_input *input, - uterm_input_cb cb, +void uterm_input_unregister_key_cb(struct uterm_input *input, + uterm_input_key_cb cb, void *data) { if (!input || !cb) return; - shl_hook_rm_cast(input->hook, cb, data); + shl_hook_rm_cast(input->key_hook, cb, data); +} + +SHL_EXPORT +int uterm_input_register_pointer_cb(struct uterm_input *input, + uterm_input_pointer_cb cb, + void *data) +{ + if (!input || !cb) + return -EINVAL; + + return shl_hook_add_cast(input->pointer_hook, cb, data, false); +} + +SHL_EXPORT +void uterm_input_unregister_pointer_cb(struct uterm_input *input, + uterm_input_pointer_cb cb, + void *data) +{ + if (!input || !cb) + return; + + shl_hook_rm_cast(input->pointer_hook, cb, data); } SHL_EXPORT @@ -496,3 +659,15 @@ bool uterm_input_is_awake(struct uterm_input *input) return input->awake > 0; } + +SHL_EXPORT +void uterm_input_set_pointer_max(struct uterm_input *input, + unsigned int max_x, + unsigned int max_y) +{ + if (!input) + return; + + input->pointer_max_x = max_x; + input->pointer_max_y = max_y; +} diff --git a/src/uterm_input.h b/src/uterm_input.h index 91d704bc..55a05d9f 100644 --- a/src/uterm_input.h +++ b/src/uterm_input.h @@ -61,7 +61,7 @@ enum uterm_input_modifier { /* keep in sync with TSM_VTE_INVALID */ #define UTERM_INPUT_INVALID 0xffffffff -struct uterm_input_event { +struct uterm_input_key_event { bool handled; /* user-controlled, default is false */ uint16_t keycode; /* linux keycode - KEY_* - linux/input.h */ uint32_t ascii; /* ascii keysym for @keycode */ @@ -72,11 +72,33 @@ struct uterm_input_event { uint32_t *codepoints; /* ucs4 unicode value or UTERM_INPUT_INVALID */ }; +enum uterm_input_pointer_type { + UTERM_MOVED, + UTERM_BUTTON, + UTERM_WHEEL, + UTERM_SYNC, + UTERM_HIDE_TIMEOUT, +}; + +struct uterm_input_pointer_event { + enum uterm_input_pointer_type event; + int32_t pointer_x; + int32_t pointer_y; + int32_t wheel; + uint8_t button; + bool pressed; + bool double_click; +}; + #define UTERM_INPUT_HAS_MODS(_ev, _mods) (((_ev)->mods & (_mods)) == (_mods)) -typedef void (*uterm_input_cb) (struct uterm_input *input, - struct uterm_input_event *ev, - void *data); +typedef void (*uterm_input_key_cb) (struct uterm_input *input, + struct uterm_input_key_event *ev, + void *data); + +typedef void (*uterm_input_pointer_cb) (struct uterm_input *input, + struct uterm_input_pointer_event *ev, + void *data); int uterm_input_new(struct uterm_input **out, struct ev_eloop *eloop, const char *model, const char *layout, const char *variant, @@ -87,16 +109,24 @@ int uterm_input_new(struct uterm_input **out, struct ev_eloop *eloop, void uterm_input_ref(struct uterm_input *input); void uterm_input_unref(struct uterm_input *input); -void uterm_input_add_dev(struct uterm_input *input, const char *node); +void uterm_input_add_dev(struct uterm_input *input, const char *node, bool mouse); void uterm_input_remove_dev(struct uterm_input *input, const char *node); -int uterm_input_register_cb(struct uterm_input *input, uterm_input_cb cb, +int uterm_input_register_key_cb(struct uterm_input *input, uterm_input_key_cb cb, + void *data); +void uterm_input_unregister_key_cb(struct uterm_input *input, uterm_input_key_cb cb, + void *data); + +int uterm_input_register_pointer_cb(struct uterm_input *input, uterm_input_pointer_cb cb, void *data); -void uterm_input_unregister_cb(struct uterm_input *input, uterm_input_cb cb, +void uterm_input_unregister_pointer_cb(struct uterm_input *input, uterm_input_pointer_cb cb, void *data); void uterm_input_sleep(struct uterm_input *input); void uterm_input_wake_up(struct uterm_input *input); bool uterm_input_is_awake(struct uterm_input *input); +void uterm_input_set_pointer_max(struct uterm_input *input, + unsigned int max_x, + unsigned int max_y); #endif /* UTERM_UTERM_INPUT_H */ diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index 816bfd20..2a99a283 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -42,6 +42,36 @@ enum uterm_input_device_capability { UTERM_DEVICE_HAS_KEYS = (1 << 0), UTERM_DEVICE_HAS_LEDS = (1 << 1), + UTERM_DEVICE_HAS_REL = (1 << 2), + UTERM_DEVICE_HAS_ABS = (1 << 3), + UTERM_DEVICE_HAS_MOUSE_BTN = (1 << 4), + UTERM_DEVICE_HAS_TOUCH = (1 << 5), + UTERM_DEVICE_HAS_WHEEL = (1 << 6), +}; + +enum pointer_kind { + POINTER_NONE, + POINTER_MOUSE, + POINTER_TOUCHPAD, + POINTER_VMOUSE, +}; + +struct uterm_input_pointer { + /* For pointers (mouse/trackpad/trackpoint/touchscreen) */ + enum pointer_kind kind; + enum uterm_input_pointer_type action; + struct timespec last_click; + int32_t x; + int32_t y; + + bool touchpaddown; + int32_t off_x; + int32_t off_y; + + int32_t min_x; + int32_t max_x; + int32_t min_y; + int32_t max_y; }; struct uterm_input_dev { @@ -52,17 +82,21 @@ struct uterm_input_dev { int rfd; char *node; struct ev_fd *fd; + + /* For keyboards */ struct xkb_state *state; struct xkb_compose_state *compose_state; /* Used in sleep/wake up to store the key's pressed/released state. */ char key_state_bits[SHL_DIV_ROUND_UP(KEY_CNT, CHAR_BIT)]; unsigned int num_syms; - struct uterm_input_event event; - struct uterm_input_event repeat_event; + struct uterm_input_key_event event; + struct uterm_input_key_event repeat_event; bool repeating; struct ev_timer *repeat_timer; + + struct uterm_input_pointer pointer; }; struct uterm_input { @@ -74,11 +108,16 @@ struct uterm_input { unsigned int repeat_rate; unsigned int repeat_delay; - struct shl_hook *hook; + struct shl_hook *key_hook; struct xkb_context *ctx; struct xkb_keymap *keymap; struct xkb_compose_table *compose_table; + struct shl_hook *pointer_hook; + int32_t pointer_max_x; + int32_t pointer_max_y; + struct ev_timer *hide_pointer; + struct shl_dlist devices; }; @@ -106,4 +145,12 @@ int uxkb_dev_process(struct uterm_input_dev *dev, void uxkb_dev_sleep(struct uterm_input_dev *dev); void uxkb_dev_wake_up(struct uterm_input_dev *dev); +void pointer_dev_rel(struct uterm_input_dev *dev, + uint16_t code, int32_t value); +void pointer_dev_abs(struct uterm_input_dev *dev, + uint16_t code, int32_t value); +void pointer_dev_button(struct uterm_input_dev *dev, + uint16_t code, int32_t value); +void pointer_dev_sync(struct uterm_input_dev *dev); + #endif /* UTERM_INPUT_INTERNAL_H */ diff --git a/src/uterm_input_pointer.c b/src/uterm_input_pointer.c new file mode 100644 index 00000000..851c381d --- /dev/null +++ b/src/uterm_input_pointer.c @@ -0,0 +1,177 @@ +#include +#include +#include +#include "eloop.h" +#include "shl_hook.h" +#include "shl_llog.h" +#include "uterm_input.h" +#include "uterm_input_internal.h" + + +static void pointer_update_inactivity_timer(struct uterm_input_dev *dev) +{ + struct itimerspec spec; + + spec.it_interval.tv_nsec = 0; + spec.it_interval.tv_sec = 0; + spec.it_value.tv_nsec = 0; + spec.it_value.tv_sec = 20; + ev_timer_update(dev->input->hide_pointer, &spec); +} + +static void pointer_dev_send_move(struct uterm_input_dev *dev) +{ + struct uterm_input_pointer_event pev = {0}; + + pev.event = UTERM_MOVED; + pev.pointer_x = dev->pointer.x; + pev.pointer_y = dev->pointer.y; + + shl_hook_call(dev->input->pointer_hook, dev->input, &pev); +} + +static void pointer_dev_send_wheel(struct uterm_input_dev *dev, int32_t value) +{ + struct uterm_input_pointer_event pev = {0}; + + pev.event = UTERM_WHEEL; + pev.wheel = value; + + shl_hook_call(dev->input->pointer_hook, dev->input, &pev); +} + +static void pointer_dev_send_button(struct uterm_input_dev *dev, uint8_t button, bool pressed, bool dbl_click) +{ + struct uterm_input_pointer_event pev = {0}; + + pev.event = UTERM_BUTTON; + pev.button = button; + pev.pressed = pressed; + pev.double_click = dbl_click; + + shl_hook_call(dev->input->pointer_hook, dev->input, &pev); +} + +void pointer_dev_sync(struct uterm_input_dev *dev) +{ + struct uterm_input_pointer_event pev = {0}; + + pev.event = UTERM_SYNC; + + shl_hook_call(dev->input->pointer_hook, dev->input, &pev); + pointer_update_inactivity_timer(dev); + dev->pointer.touchpaddown = false; +} + +void pointer_dev_rel(struct uterm_input_dev *dev, + uint16_t code, int32_t value) +{ + switch (code) { + case REL_X: + dev->pointer.x += value; + if (dev->pointer.x < 0) + dev->pointer.x = 0; + if (dev->pointer.x > dev->input->pointer_max_x) + dev->pointer.x = dev->input->pointer_max_x; + pointer_dev_send_move(dev); + break; + case REL_Y: + dev->pointer.y += value; + if (dev->pointer.y < 0) + dev->pointer.y = 0; + if (dev->pointer.y > dev->input->pointer_max_y) + dev->pointer.y = dev->input->pointer_max_y; + pointer_dev_send_move(dev); + break; + case REL_WHEEL: + pointer_dev_send_wheel(dev, value); + break; + default: + break; + } +} + +void pointer_dev_abs(struct uterm_input_dev *dev, + uint16_t code, int32_t value) +{ + switch (code) { + case ABS_X: + if (dev->pointer.kind == POINTER_TOUCHPAD) { + if (dev->pointer.touchpaddown == true) + dev->pointer.off_x = dev->pointer.x - value; + + dev->pointer.x = dev->pointer.off_x + value; + if (dev->pointer.x < 0) { + dev->pointer.x = 0; + dev->pointer.off_x = -value; + } + if (dev->pointer.x > dev->input->pointer_max_x) { + dev->pointer.x = dev->input->pointer_max_x; + dev->pointer.off_x = dev->input->pointer_max_x - value; + } + } else if (dev->pointer.kind == POINTER_VMOUSE) { + dev->pointer.x = ((value - dev->pointer.min_x) * dev->input->pointer_max_x) / (dev->pointer.max_x - dev->pointer.min_x); + } else { + return; + } + break; + case ABS_Y: + if (dev->pointer.kind == POINTER_TOUCHPAD) { + if (dev->pointer.touchpaddown == true) + dev->pointer.off_y = dev->pointer.y - value; + + dev->pointer.y = dev->pointer.off_y + value; + if (dev->pointer.y < 0) { + dev->pointer.y = 0; + dev->pointer.off_y = -value; + } + if (dev->pointer.y > dev->input->pointer_max_y) { + dev->pointer.y = dev->input->pointer_max_y; + dev->pointer.off_y = dev->input->pointer_max_y - value; + } + } else if (dev->pointer.kind == POINTER_VMOUSE) { + dev->pointer.y = ((value - dev->pointer.min_y) * dev->input->pointer_max_y) / (dev->pointer.max_y - dev->pointer.min_y); + } else { + return; + } + break; + default: + return; + } + pointer_dev_send_move(dev); +} + +void pointer_dev_button(struct uterm_input_dev *dev, + uint16_t code, int32_t value) +{ + struct timespec tp; + uint64_t elapsed; + bool pressed = (value == 1); + bool dbl_click = false; + + switch (code) { + case BTN_LEFT: + if (pressed) { + clock_gettime(CLOCK_MONOTONIC, &tp); + elapsed = (tp.tv_sec - dev->pointer.last_click.tv_sec) * 1000 + (tp.tv_nsec - dev->pointer.last_click.tv_nsec) / 1000000; + dbl_click = (elapsed < 500); + dev->pointer.last_click = tp; + } + pointer_dev_send_button(dev, 0, pressed, dbl_click); + break; + case BTN_RIGHT: + pointer_dev_send_button(dev, 1, pressed, false); + break; + case BTN_TOOL_DOUBLETAP: + case BTN_TOOL_TRIPLETAP: + case BTN_MIDDLE: + pointer_dev_send_button(dev, 2, pressed, false); + break; + case BTN_TOUCH: + dev->pointer.touchpaddown = true; + break; + default: + break; + } +} + diff --git a/src/uterm_input_uxkb.c b/src/uterm_input_uxkb.c index 1d28a8da..926b6725 100644 --- a/src/uterm_input_uxkb.c +++ b/src/uterm_input_uxkb.c @@ -209,7 +209,7 @@ static void timer_event(struct ev_timer *timer, uint64_t num, void *data) struct uterm_input_dev *dev = data; dev->repeat_event.handled = false; - shl_hook_call(dev->input->hook, dev->input, &dev->repeat_event); + shl_hook_call(dev->input->key_hook, dev->input, &dev->repeat_event); } int uxkb_dev_init(struct uterm_input_dev *dev) @@ -338,7 +338,7 @@ static inline int uxkb_dev_resize_event(struct uterm_input_dev *dev, size_t s) } static int uxkb_dev_fill_event(struct uterm_input_dev *dev, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, xkb_keycode_t code, int num_syms, const xkb_keysym_t *syms) @@ -532,7 +532,7 @@ int uxkb_dev_process(struct uterm_input_dev *dev, return -ENOKEY; dev->event.handled = false; - shl_hook_call(dev->input->hook, dev->input, &dev->event); + shl_hook_call(dev->input->key_hook, dev->input, &dev->event); return 0; } diff --git a/src/uterm_vt.c b/src/uterm_vt.c index 55f7c57e..1760a429 100644 --- a/src/uterm_vt.c +++ b/src/uterm_vt.c @@ -573,7 +573,7 @@ static int real_deactivate(struct uterm_vt *vt) return -EINPROGRESS; } -static void real_input(struct uterm_vt *vt, struct uterm_input_event *ev) +static void real_input(struct uterm_vt *vt, struct uterm_input_key_event *ev) { int id; struct vt_stat vts; @@ -696,7 +696,7 @@ static int fake_deactivate(struct uterm_vt *vt) return vt_call_deactivate(vt, false); } -static void fake_input(struct uterm_vt *vt, struct uterm_input_event *ev) +static void fake_input(struct uterm_vt *vt, struct uterm_input_key_event *ev) { if (ev->handled) return; @@ -749,7 +749,7 @@ static void fake_close(struct uterm_vt *vt) */ static void vt_input(struct uterm_input *input, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, void *data) { struct uterm_vt *vt = data; @@ -890,7 +890,7 @@ int uterm_vt_allocate(struct uterm_vt_master *vtm, if (ret) goto err_sig1; - ret = uterm_input_register_cb(vt->input, vt_input, vt); + ret = uterm_input_register_key_cb(vt->input, vt_input, vt); if (ret) goto err_sig2; @@ -930,7 +930,7 @@ int uterm_vt_allocate(struct uterm_vt_master *vtm, return 0; err_input: - uterm_input_unregister_cb(vt->input, vt_input, vt); + uterm_input_unregister_key_cb(vt->input, vt_input, vt); err_sig2: ev_eloop_unregister_signal_cb(vtm->eloop, SIGUSR2, vt_sigusr2, vt); err_sig1: diff --git a/tests/test_input.c b/tests/test_input.c index 8cf22943..ae1f1069 100644 --- a/tests/test_input.c +++ b/tests/test_input.c @@ -89,7 +89,7 @@ static void print_modifiers(unsigned int mods) } static void input_arrived(struct uterm_input *input, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, void *data) { char s[32]; @@ -148,16 +148,16 @@ static void monitor_event(struct uterm_monitor *mon, 0, 0, log_llog, NULL); if (ret) return; - ret = uterm_input_register_cb(input, input_arrived, NULL); + ret = uterm_input_register_key_cb(input, input_arrived, NULL); if (ret) return; uterm_input_wake_up(input); } else if (ev->type == UTERM_MONITOR_FREE_SEAT) { - uterm_input_unregister_cb(input, input_arrived, NULL); + uterm_input_unregister_key_cb(input, input_arrived, NULL); uterm_input_unref(input); } else if (ev->type == UTERM_MONITOR_NEW_DEV) { if (ev->dev_type == UTERM_MONITOR_INPUT) - uterm_input_add_dev(input, ev->dev_node); + uterm_input_add_dev(input, ev->dev_node, true); } else if (ev->type == UTERM_MONITOR_FREE_DEV) { if (ev->dev_type == UTERM_MONITOR_INPUT) uterm_input_remove_dev(input, ev->dev_node);