diff --git a/COPYING b/COPYING index 3ad47b7..a9321fb 100644 --- a/COPYING +++ b/COPYING @@ -15,6 +15,7 @@ This software was written by: Hoàng Đức Hiếu Swift Geek Aetf + Mirco Müller = Copyright Notice = diff --git a/docs/man/kmscon.1.xml.in b/docs/man/kmscon.1.xml.in index c6ddb0f..f3319fd 100644 --- a/docs/man/kmscon.1.xml.in +++ b/docs/man/kmscon.1.xml.in @@ -430,6 +430,22 @@ (default: <Ctrl><Logo>Return) + + + + + Rotate output clock-wise: Normal -> Right -> Inverted -> Left and so on. + (default: <Logo>Plus) + + + + + + + Rotate output counter-clock-wise: Normal -> Left -> Inverted -> Right and so on. + (default: <Logo>Minus) + + Video Options: @@ -482,6 +498,18 @@ only be used to debug render engines. (default: off) + + + + + Select the desired orientation of the output. Available orientations + are 'normal' meaning output is not rotated, 'right' meaning output is + rotated 90° clock-wise, 'inverted' meaning output is rotated 180° + clock-wise and 'left' meaning output is rotated 90° counter clock-wise. + This has only effect for the 'gltex' render-engine. Other render-engines + currently ignore this option. (default: 'normal') + + Font Options: diff --git a/docs/man/kmscon.conf.1.xml.in b/docs/man/kmscon.conf.1.xml.in new file mode 100644 index 0000000..c4f7cb2 --- /dev/null +++ b/docs/man/kmscon.conf.1.xml.in @@ -0,0 +1,443 @@ + + + + + + + + kmscon.conf + kmscon.conf + January 2023 + + + Developer + Mirco + Müller + macslow@gmail.com + + + + + + kmscon.conf + 1 + + + + kmscon.conf + Configuration file for KMS/DRM based System Console + + + + Description + kmscon.conf is the configuration file to control the behavior of kmscon + and adjust it to your system-setup. It allows to remap the bindings of + keyboard-shortcuts, define the desired keyboard-layout, select font + attributes for text-rendering, hardware-accelerationn, orientation of + output and much more. + + Below is a complete list of all recognized Options, their meaning and + possible values. In section Example is a typical real-world sample of + a configuration to guide you in creating your own. + + + + Example + Here is a real-world example of a typical kmscon.conf file: + +### General Options ### +verbose + +### Seat Options ### +vt=1 +switchvt + +### Session Options ### +session-max=6 +session-control + +### Terminal Options ### +term=linux + +### Input Options ### +xkb-model=pc102 +xkb-layout=de +xkb-repeat-delay=200 +xkb-repeat-rate=65 + +### Video Options ### +drm +hwaccel +gpus=all +render-engine=gltex +rotate=normal + +### Font Options ### +font-engine=pango +font-size=18 +font-name=Ubuntu Mono + + Any line starting with a #-character is ignored and considered to be a comment. + + + + Options + Below is a complete list of all recognized options, their meaning and + possible values. + + + ### General Options ### + + + + Make kmscon be very chatty about what it is doing. It prints to + stdout unless redirected. Off if not present in kmsconf.conf or + commented out. (default: off) + + + + + + + Let kmscon be even more chatty. The text-output goes to stdout or + any file it was redirected to. Off if not present in kmsconf.conf + or commented out. (default: off) + + + + + + + Suppress notices and warnings. (default: off) + + + + + + + Path to config directory. (default: /etc/kmscon) + + + + + + + Listen for new seats and spawn sessions accordingly. (default: off) + + + + ### Seat Options ### + + + + Select which VT to run on. (default: auto) + + + + + + + Automatically switch to VT. (default: on) + + + + + + + Select seats to run on. (default: current) + + + + ### Session Options ### + + + + + Maximum number of sessions. (default: 50) + + + + + + + Allow keyboard session-control. (default: off) + + + + + + + Enable terminal session. (default: on) + + + + ### Terminal Options ### + + + + + Start the given login process instead of the default process; all arguments following '--' will be be parsed as argv to this process. No more options after '--' will be parsed so use it at the end of the argument string. (default: /bin/login -p) + + + + + + + Value of the TERM environment variable for the child process. (default: xterm-256color) + + + + + + + Reset environment before running child process. (default: on) + + + + + + + Select the used color palette. (default: default) + + + + + + + Size of the scrollback-buffer in lines. (default: 1000) + + + + ### Input Options ### + + + + Set XkbModel for input devices. (default: unset) + + + + + + + Set XkbLayout for input devices. (default: unset) + + + + + + + Set XkbVariant for input devices. (default: unset) + + + + + + + Set XkbOptions for input devices. (default: unset) + + + + + + + Use a predefined keymap for input devices. (default: unset) + + + + + + + Initial delay for key-repeat in ms. (default: 250) + + + + + + + Delay between two key-repeats in ms. (default: 50) + + + + ### Grabs/Keyboard-Shortcuts ### + + + + Shortcut to scroll up. (default: <Shift>Up) + + + + + + + Shortcut to scroll down. (default: <Shift>Down) + + + + + + + Shortcut to scroll page up. (default: <Shift>Prior) + + + + + + + Shortcut to scroll page down. (default: <Shift>Next) + + + + + + + Shortcut to increase font size. (default: <Ctrl>Plus) + + + + + + + Shortcut to decrease font size. (default: <Ctrl>Minus) + + + + + + + Switch to next session. (default: <Ctrl><Logo>Right) + + + + + + + Switch to previous session. (default: <Ctrl><Logo>Left) + + + + + + + Switch to dummy session. (default: <Ctrl><Logo>Escape) + + + + + + + Close current session. (default: <Ctrl><Logo>BackSpace) + + + + + + + Create new terminal session. (default: <Ctrl><Logo>Return) + + + + + + + Rotate output clock-wise. (default: <Logo>Plus) + + + + + + + Rotate output counter-clock-wise. (default: <Logo>Minus) + + + + ### Video Options ### + + + + Use DRM if available. (default: on) + + + + + + + Use 3D hardware-acceleration if available. (default: off) + + + + + + + GPU selection mode. (default: all) + + + + + + + Console renderer. (default: not set) + + + + + + + Print renderer timing information. (default: off) + + + + + + + Orientation of output to use. (default: normal) + + + + ### Font Options ### + + + + Font engine to use. (default: pango) + + + + + + + Font size in points. (default: 12) + + + + + + + Font name to use. (default: monospace) + + + + + + + Force DPI value for all fonts. (default: 96) + + + + + + + + Files + /etc/kmsconf.conf + + + + See Also + + kmscon1 + + + diff --git a/docs/meson.build b/docs/meson.build index a26e520..cf33578 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -4,6 +4,7 @@ manpages = [ 'kmscon.1.xml.in', + 'kmscon.conf.1.xml.in', ] data = configuration_data() diff --git a/meson.build b/meson.build index 3f4fce2..1750bb5 100644 --- a/meson.build +++ b/meson.build @@ -51,6 +51,7 @@ libtsm_deps = dependency('libtsm', version: '>=4.0.2') libudev_deps = dependency('libudev', version: '>=172') dl_deps = dependency('dl') threads_deps = dependency('threads') +dbus_deps = dependency('dbus-1', version: '>=1.14.4') python = find_program('python3') diff --git a/src/kmscon_conf.c b/src/kmscon_conf.c index 2f3b151..8877548 100644 --- a/src/kmscon_conf.c +++ b/src/kmscon_conf.c @@ -134,14 +134,19 @@ static void print_help() "\t Close current session\n" "\t --grab-terminal-new [Return]\n" "\t Create new terminal session\n" + "\t --grab-rotate-cw [Plus]\n" + "\t Rotate output clock-wise\n" + "\t --grab-rotate-ccw [Minus]\n" + "\t Rotate output counter-clock-wise\n" "\n" "Video Options:\n" - "\t --drm [on] Use DRM if available\n" - "\t --hwaccel [off] Use 3D hardware-acceleration if\n" - "\t available\n" - "\t --gpus={all,aux,primary}[all] GPU selection mode\n" - "\t --render-engine [-] Console renderer\n" - "\t --render-timing [off] Print renderer timing information\n" + "\t --drm [on] Use DRM if available\n" + "\t --hwaccel [off] Use 3D hardware-acceleration if\n" + "\t available\n" + "\t --gpus={all,aux,primary}[all] GPU selection mode\n" + "\t --render-engine [-] Console renderer\n" + "\t --render-timing [off] Print renderer timing information\n" + "\t --rotate [normal] normal, right, inverted, left\n" "\n" "Font Options:\n" "\t --font-engine [pango]\n" @@ -645,6 +650,12 @@ static struct conf_grab def_grab_session_close = static struct conf_grab def_grab_terminal_new = CONF_SINGLE_GRAB(SHL_CONTROL_MASK | SHL_LOGO_MASK, XKB_KEY_Return); +static struct conf_grab def_grab_rotate_cw = + CONF_SINGLE_GRAB(SHL_LOGO_MASK, XKB_KEY_plus); + +static struct conf_grab def_grab_rotate_ccw = + CONF_SINGLE_GRAB(SHL_LOGO_MASK, XKB_KEY_minus); + static palette_t def_palette = { [TSM_COLOR_BLACK] = { 0, 0, 0 }, /* black */ [TSM_COLOR_RED] = { 205, 0, 0 }, /* red */ @@ -728,12 +739,15 @@ int kmscon_conf_new(struct conf_ctx **out) CONF_OPTION_GRAB(0, "grab-session-dummy", &conf->grab_session_dummy, &def_grab_session_dummy), CONF_OPTION_GRAB(0, "grab-session-close", &conf->grab_session_close, &def_grab_session_close), CONF_OPTION_GRAB(0, "grab-terminal-new", &conf->grab_terminal_new, &def_grab_terminal_new), + CONF_OPTION_GRAB(0, "grab-rotate-cw", &conf->grab_rotate_cw, &def_grab_rotate_cw), + CONF_OPTION_GRAB(0, "grab-rotate-ccw", &conf->grab_rotate_ccw, &def_grab_rotate_ccw), /* Video Options */ CONF_OPTION_BOOL_FULL(0, "drm", aftercheck_drm, NULL, NULL, &conf->drm, true), CONF_OPTION_BOOL(0, "hwaccel", &conf->hwaccel, false), CONF_OPTION(0, 0, "gpus", &conf_gpus, NULL, NULL, NULL, &conf->gpus, KMSCON_GPU_ALL), CONF_OPTION_STRING(0, "render-engine", &conf->render_engine, NULL), + CONF_OPTION_STRING(0, "rotate", &conf->rotate, "normal"), /* Font Options */ CONF_OPTION_STRING(0, "font-engine", &conf->font_engine, "pango"), diff --git a/src/kmscon_conf.h b/src/kmscon_conf.h index 9ae9ecf..b7d0ae8 100644 --- a/src/kmscon_conf.h +++ b/src/kmscon_conf.h @@ -135,6 +135,10 @@ struct kmscon_conf_t { struct conf_grab *grab_session_close; /* terminal-new grab */ struct conf_grab *grab_terminal_new; + /* rotate output clock-wise grab */ + struct conf_grab *grab_rotate_cw; + /* rotate output counter-clock-wise grab */ + struct conf_grab *grab_rotate_ccw; /* Video Options */ /* use DRM if available */ @@ -145,6 +149,8 @@ struct kmscon_conf_t { unsigned int gpus; /* render engine */ char *render_engine; + /* orientation/rotation of output */ + char *rotate; /* Font Options */ /* font engine */ diff --git a/src/kmscon_mouse.c b/src/kmscon_mouse.c new file mode 100644 index 0000000..2e618a8 --- /dev/null +++ b/src/kmscon_mouse.c @@ -0,0 +1,478 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kmscon_mouse.h" +#include "kmscon_seat.h" +#include "shl_log.h" +#include "text.h" + +// utility functions for internal (kmscon_mouse) use only +int mouse_ready_to_read(int handle); +void mouse_hide_timer_cb(struct ev_timer *timer, uint64_t num, void *data); +void handle_clicks (struct kmscon_mouse_info* mouse, int button); +void update_mouse_button_state (struct kmscon_mouse_info* mouse, + int button, + char button_mask, + char buffer); +void mouse_query_timer_cb(struct ev_timer *timer, uint64_t num, void *data); + +// public API +struct kmscon_mouse_info* kmscon_mouse_init(struct ev_eloop* eloop, + struct kmscon_seat* seat) +{ + struct kmscon_mouse_info* mouse; + size_t size = sizeof(struct kmscon_mouse_info); + mouse = (struct kmscon_mouse_info*) calloc(1, size); + if (!mouse) + goto err; + + mouse->seat = seat; + mouse->state[KMSCON_MOUSE_BUTTON_LEFT] = KMSCON_MOUSE_BUTTON_RELEASED; + mouse->state[KMSCON_MOUSE_BUTTON_MIDDLE] = KMSCON_MOUSE_BUTTON_RELEASED; + mouse->state[KMSCON_MOUSE_BUTTON_RIGHT] = KMSCON_MOUSE_BUTTON_RELEASED; + gettimeofday(&mouse->last_up, NULL); + gettimeofday(&mouse->last_down, NULL); + gettimeofday(&mouse->click, NULL); + + size = sizeof(struct kmscon_selection_info); + mouse->selection = (struct kmscon_selection_info*) calloc(1, size); + if (!mouse->selection) + goto err; + + int use_mouse = true; + char devname[MAX_DEVNAME_SIZE]; + int length = sprintf (devname, DEVNAME_TEMPLATE); + if (length > MAX_DEVNAME_SIZE) { + log_warn ("Error while creating device string!\n"); + use_mouse = false; + } + + mouse->device = open (devname, O_RDONLY | O_NONBLOCK); + if (mouse->device < 0) { + log_warn ("Error while opening device '%s'!\n", devname); + use_mouse = false; + } + + if (use_mouse) { + mouse->hide = true; + + // mouse-query timer + mouse->query_timer_spec.it_interval.tv_sec = 0; + mouse->query_timer_spec.it_interval.tv_nsec = 10*1000*1000; + mouse->query_timer_spec.it_value.tv_sec = 0; + mouse->query_timer_spec.it_value.tv_nsec = 10*1000*1000; + + int ret = ev_timer_new(&mouse->query_timer, + &mouse->query_timer_spec, + mouse_query_timer_cb, + mouse, + NULL, + NULL); + if (ret) { + log_error("cannot create mouse-query timer: %d", ret); + goto err; + } + ev_timer_enable(mouse->query_timer); + + ret = ev_eloop_add_timer(eloop, mouse->query_timer); + if (ret) { + log_error("cannot add mouse-query timer to event-loop: %d", ret); + goto err; + } + + // mouse-hide timer + mouse->hide_timer_spec.it_interval.tv_sec = 5; + mouse->hide_timer_spec.it_interval.tv_nsec = 0; + mouse->hide_timer_spec.it_value.tv_sec = 5; + mouse->hide_timer_spec.it_value.tv_nsec = 0; + + ret = ev_timer_new(&mouse->hide_timer, + &mouse->hide_timer_spec, + mouse_hide_timer_cb, + mouse, + NULL, + NULL); + if (ret) { + log_error("cannot create mouse-hide timer: %d", ret); + goto err; + } + ev_timer_enable(mouse->hide_timer); + + ret = ev_eloop_add_timer(eloop, mouse->hide_timer); + if (ret) { + log_error("cannot add mouse-hide timer to event-loop: %d", ret); + goto err; + } + } + + return mouse; + +err: + kmscon_mouse_cleanup(mouse); + return NULL; +} + +void kmscon_mouse_cleanup(struct kmscon_mouse_info* mouse) +{ + if (mouse) { + if (mouse->selection) { + free(mouse->selection); + } + free(mouse); + } +} + +void kmscon_mouse_set_mapping(struct kmscon_mouse_info* mouse, + struct uterm_display* disp, + struct kmscon_text* txt) +{ + if (mouse && disp && txt) { + mouse->disp = disp; + mouse->txt = txt; + } +} + +int kmscon_mouse_get_x(struct kmscon_mouse_info* mouse) +{ + int x = 0; + struct uterm_mode *mode = uterm_display_get_current(mouse->disp); + int sw = uterm_mode_get_width(mode); + int sh = uterm_mode_get_height(mode); + float fw = mouse->txt->font->attr.width; + + if (mouse && mouse->disp && mouse->txt) { + + if (mouse->txt->orientation == ORIENTATION_NORMAL || + mouse->txt->orientation == ORIENTATION_INVERTED) { + + float pixel_w = 2.f / (float) sw; + float cursor_x = (1.f + mouse->x)/pixel_w; + cursor_x = cursor_x - fmodf(cursor_x, 1.f); + x = (int) cursor_x/fw; + } + + if (mouse->txt->orientation == ORIENTATION_RIGHT || + mouse->txt->orientation == ORIENTATION_LEFT) { + + float pixel_w = 2.f / (float) sh; + float cursor_x = (1.f + mouse->x)/pixel_w; + cursor_x = cursor_x - fmodf(cursor_x, 1.f); + x = (int) cursor_x/fw; + } + + x = (x >= mouse->txt->cols) ? mouse->txt->cols - 1 : x; + } + + return x; +} + +int kmscon_mouse_get_y(struct kmscon_mouse_info* mouse) +{ + int y = 0; + struct uterm_mode *mode = uterm_display_get_current(mouse->disp); + int sw = uterm_mode_get_width(mode); + int sh = uterm_mode_get_height(mode); + float fh = mouse->txt->font->attr.height; + + if (mouse && mouse->disp && mouse->txt) { + + if (mouse->txt->orientation == ORIENTATION_NORMAL || + mouse->txt->orientation == ORIENTATION_INVERTED) { + + float pixel_h = 2.f / (float) sh; + float cursor_y = fabsf(-1.f + mouse->y)/pixel_h; + cursor_y = cursor_y - fmodf(cursor_y, 1.f); + y = (int) cursor_y/fh; + } + + if (mouse->txt->orientation == ORIENTATION_RIGHT || + mouse->txt->orientation == ORIENTATION_LEFT) { + + float pixel_h = 2.f / (float) sw; + float cursor_y = fabsf(-1.f + mouse->y)/pixel_h; + cursor_y = cursor_y - fmodf(cursor_y, 1.f); + y = (int) cursor_y/fh; + } + + y = (y >= mouse->txt->rows) ? mouse->txt->rows - 1 : y; + } + + return y; +} + +int kmscon_mouse_is_clicked(struct kmscon_mouse_info* mouse, + int button) +{ + if (mouse && button <= 3 && button >= 0) + return mouse->clicked[button]; + + return 0; +} + +void kmscon_mouse_clear_clicked(struct kmscon_mouse_info* mouse, + int button) +{ + if (mouse) { + mouse->clicked[button] = false; + } +} + +int kmscon_mouse_is_dbl_clicked(struct kmscon_mouse_info* mouse, + int button) +{ + if (mouse && button <= 3 && button >= 0) + return mouse->dbl_clicked[button]; + + return 0; +} + +void kmscon_mouse_clear_dbl_clicked(struct kmscon_mouse_info* mouse, + int button) +{ + if (mouse) { + mouse->dbl_clicked[button] = false; + } +} + +int kmscon_mouse_is_up(struct kmscon_mouse_info* mouse, + int button) +{ + if (mouse && button <= 3 && button >= 0) + return (mouse->state[button] == KMSCON_MOUSE_BUTTON_UP); + + return 0; +} + +int kmscon_mouse_is_down(struct kmscon_mouse_info* mouse, + int button) +{ + if (mouse && button <= 3 && button >= 0) + return (mouse->state[button] == KMSCON_MOUSE_BUTTON_DOWN); + + return 0; +} + +int kmscon_mouse_is_released(struct kmscon_mouse_info* mouse, + int button) +{ + if (mouse && button <= 3 && button >= 0) + return (mouse->state[button] == KMSCON_MOUSE_BUTTON_RELEASED); + + return 0; +} + +int kmscon_mouse_is_pressed(struct kmscon_mouse_info* mouse, + int button) +{ + if (mouse && button <= 3 && button >= 0) + return (mouse->state[button] == KMSCON_MOUSE_BUTTON_PRESSED); + + return 0; +} + +int kmscon_mouse_is_hidden(struct kmscon_mouse_info* mouse) +{ + if (mouse) + return mouse->hide; + + return 0; +} + +void kmscon_mouse_selection_copy(struct kmscon_mouse_info* mouse, + struct tsm_screen *console) +{ + if (mouse && console) { + int length = tsm_screen_selection_copy(console, + &mouse->selection->buffer); + mouse->selection->buffer_length = length; + } +} + +int kmscon_mouse_is_selection_empty(struct kmscon_mouse_info* mouse) +{ + if (!mouse || !mouse->selection) + return true; + + if (mouse->selection->buffer_length == 0) + return true; + else + return false; +} + +// internal API/utility functions +int mouse_ready_to_read(int handle) +{ + const struct timespec timeout = {0, 3*1000*1000}; + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(handle, &readfds); + int result = pselect(handle + 1, &readfds, NULL, NULL, &timeout, NULL); + if (result == -1) { + log_error("There was an error trying to check for available mouse-data."); + return 0; + } else if (result == 0) { + return 0; + } else { + result = FD_ISSET(handle, &readfds); + if (result) { + return 1; + } + } + + return 0; +} + +void mouse_hide_timer_cb(struct ev_timer *timer, + uint64_t num, + void *data) +{ + if (!data) { + log_warn("No valid pointer passed to mouse_hide_timer_cb()."); + return; + } + + struct kmscon_mouse_info* mouse = NULL; + mouse = (struct kmscon_mouse_info*) data; + mouse->hide = true; +} + +void handle_clicks (struct kmscon_mouse_info* mouse, int button) +{ + if (!mouse) + return; + + time_t seconds = mouse->last_down.tv_sec; + suseconds_t useconds = mouse->last_down.tv_usec; + gettimeofday(&mouse->last_up, NULL); + + // check for click + if (seconds == mouse->last_up.tv_sec && + mouse->last_up.tv_usec - useconds <= 150000) { + + mouse->clicked[button] = true; + + // check for double-click + time_t dbl_seconds = mouse->click.tv_sec; + suseconds_t dbl_useconds = mouse->click.tv_usec; + gettimeofday(&mouse->click, NULL); + + if (dbl_seconds == mouse->click.tv_sec && + mouse->click.tv_usec - dbl_useconds <= 300000) { + + mouse->dbl_clicked[button] = true; + } + } +} + +void update_mouse_button_state(struct kmscon_mouse_info* mouse, + int button, + char button_mask, + char buffer) +{ + if (!mouse) + return; + + int new_state = mouse->state[button]; + + // released -> down + if (mouse->state[button] == KMSCON_MOUSE_BUTTON_RELEASED && + (buffer & button_mask)) { + new_state = KMSCON_MOUSE_BUTTON_DOWN; + gettimeofday(&mouse->last_down, NULL); + } + + // down -> pressed + if (mouse->state[button] == KMSCON_MOUSE_BUTTON_DOWN && + (buffer & button_mask)) { + new_state = KMSCON_MOUSE_BUTTON_PRESSED; + } + + // pressed -> up + if (mouse->state[button] == KMSCON_MOUSE_BUTTON_PRESSED && + !(buffer & button_mask)) { + new_state = KMSCON_MOUSE_BUTTON_UP; + } + + // up -> released + if (mouse->state[button] == KMSCON_MOUSE_BUTTON_UP && + !(buffer & button_mask)) { + new_state = KMSCON_MOUSE_BUTTON_RELEASED; + handle_clicks (mouse, button); + } + + // edge-case (no mouse motion): down -> released + if (mouse->state[button] == KMSCON_MOUSE_BUTTON_DOWN && + !(buffer & button_mask)) { + new_state = KMSCON_MOUSE_BUTTON_RELEASED; + handle_clicks (mouse, button); + } + + // edge-case (no mouse motion): up -> pressed + if (mouse->state[button] == KMSCON_MOUSE_BUTTON_UP && + (buffer & button_mask)) { + new_state = KMSCON_MOUSE_BUTTON_PRESSED; + } + + mouse->state[button] = new_state; +} + +void mouse_query_timer_cb(struct ev_timer *timer, uint64_t num, void *data) +{ + if (!data) { + log_warn("No valid pointer passed to mouse_query_timer_cb()."); + return; + } + + struct kmscon_mouse_info* mouse = (struct kmscon_mouse_info*) data; + ssize_t size = 0; + short relative_x = 0; + short relative_y = 0; + char buffer[BUFFER_SIZE]; + + if (mouse_ready_to_read (mouse->device)) { + size = read(mouse->device, buffer, sizeof (buffer)); + if (size != BUFFER_SIZE) { + log_error("Error reading device event... buffer-size mismatch!"); + return; + } + relative_x = (short) buffer[1]; + relative_y = (short) buffer[2]; + mouse->x += .0025 * (float) relative_x/MOTION_SCALE; + mouse->y += .0025 * (float) relative_y/MOTION_SCALE; + + // limit 'normalized' coordinates to the extents of the GL-viewport + if (mouse->x < -1.f) mouse->x = -1.f; + if (mouse->x > 1.f) mouse->x = 1.f; + if (mouse->y < -1.f) mouse->y = -1.f; + if (mouse->y > 1.f) mouse->y = 1.f; + + update_mouse_button_state (mouse, + KMSCON_MOUSE_BUTTON_LEFT, + BUTTON_MASK_LEFT, + buffer[0]); + update_mouse_button_state (mouse, + KMSCON_MOUSE_BUTTON_MIDDLE, + BUTTON_MASK_MIDDLE, + buffer[0]); + update_mouse_button_state (mouse, + KMSCON_MOUSE_BUTTON_RIGHT, + BUTTON_MASK_RIGHT, + buffer[0]); + + mouse->hide = false; + + // FIXME: Triggering the refresh like this works, but + // it's not an elegant solution. Using an event would + // be nicer, but I did not yet fully grasp the pile of + // event-loops kmscon uses. There has to be a way to + // trigger a refresh via the event-loops of uterm_video. + kmscon_seat_refresh_display(mouse->seat, mouse->disp); + } +} diff --git a/src/kmscon_mouse.h b/src/kmscon_mouse.h new file mode 100644 index 0000000..ea0e5b7 --- /dev/null +++ b/src/kmscon_mouse.h @@ -0,0 +1,138 @@ +/* + * Mouse + * + * Copyright (c) 2023 Mirco Müller + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Mouse + * + * Support for mouse/pointer-devices (usb-mice, trackpoints, + * trackpads etc.) and text copy&paste à la gpm is implemented here. + * + * You can get x- and y-coordinates (in character-cells) and check if + * LMB, MMB or RMB are up, down, pressed or released. You can also check + * for single- or double-click of LMB, MMB or RMB. + * + * There should only ever be one kmscon_mouse_info structure be + * created via kmscon_mouse_init() in all of kmscon. It will handle + * everything that's plugged into the system via the Linux kernels + * input sub-system. + * + * It is written in a way to be easy to read and understand, not waste + * cpu-cycles and just support enough features in order to implement + * gpm-like cursor- and copy&paste-functionality. Take note that scroll- + * -wheels are currently NOT supported. + * + * Do not use this in a general UI-toolkit manner like mouse-support in + * Qt, gtk+ or the like. + */ + +#ifndef KMSCON_MOUSE_H +#define KMSCON_MOUSE_H + +#include +#include +#include + +#include "eloop.h" + +#define MAX_DEVNAME_SIZE 16 +#define DEVNAME_TEMPLATE "/dev/input/mice" +#define BUTTON_MASK_LEFT 0x01 +#define BUTTON_MASK_RIGHT 0x02 +#define BUTTON_MASK_MIDDLE 0x04 +#define MOTION_SCALE 2 +#define BUFFER_SIZE 3 + +#define KMSCON_MOUSE_BUTTON_LEFT 0 +#define KMSCON_MOUSE_BUTTON_MIDDLE 1 +#define KMSCON_MOUSE_BUTTON_RIGHT 2 + +typedef enum { + KMSCON_MOUSE_BUTTON_UNDEFINED = -1, + KMSCON_MOUSE_BUTTON_PRESSED = 0, + KMSCON_MOUSE_BUTTON_RELEASED = 1, + KMSCON_MOUSE_BUTTON_DOWN = 2, + KMSCON_MOUSE_BUTTON_UP = 3 +} kmscon_mouse_button_state; + +struct kmscon_selection_info { + char* buffer; + int buffer_length; +}; + +struct kmscon_mouse_info { + struct kmscon_selection_info* selection; + + struct ev_timer* query_timer; + struct itimerspec query_timer_spec; + + struct ev_timer* hide_timer; + struct itimerspec hide_timer_spec; + + int device; + float x; + float y; + int hide; + + kmscon_mouse_button_state state[3]; + int clicked[3]; + int dbl_clicked[3]; + + struct timeval last_up; + struct timeval last_down; + struct timeval click; + + struct uterm_display* disp; + struct kmscon_text* txt; + struct kmscon_seat* seat; +}; + +struct kmscon_mouse_info* kmscon_mouse_init(struct ev_eloop* eloop, + struct kmscon_seat* seat); +void kmscon_mouse_cleanup(struct kmscon_mouse_info* mouse); + +void kmscon_mouse_set_mapping(struct kmscon_mouse_info* mouse, + struct uterm_display* disp, + struct kmscon_text* txt); + +int kmscon_mouse_get_x(struct kmscon_mouse_info* mouse); +int kmscon_mouse_get_y(struct kmscon_mouse_info* mouse); + +int kmscon_mouse_is_clicked(struct kmscon_mouse_info* mouse, int button); +void kmscon_mouse_clear_clicked(struct kmscon_mouse_info* mouse, int button); +int kmscon_mouse_is_dbl_clicked(struct kmscon_mouse_info* mouse, int button); +void kmscon_mouse_clear_dbl_clicked(struct kmscon_mouse_info* mouse, int button); + +int kmscon_mouse_is_up(struct kmscon_mouse_info* mouse, int button); +int kmscon_mouse_is_down(struct kmscon_mouse_info* mouse, int button); +int kmscon_mouse_is_released(struct kmscon_mouse_info* mouse, int button); +int kmscon_mouse_is_pressed(struct kmscon_mouse_info* mouse, int button); +int kmscon_mouse_is_hidden(struct kmscon_mouse_info* mouse); + +void kmscon_mouse_selection_copy(struct kmscon_mouse_info* mouse, + struct tsm_screen *console); + +int kmscon_mouse_is_selection_empty(struct kmscon_mouse_info* mouse); + +#endif /* KMSCON_MOUSE_H */ diff --git a/src/kmscon_seat.c b/src/kmscon_seat.c index 56a6536..bb02f55 100644 --- a/src/kmscon_seat.c +++ b/src/kmscon_seat.c @@ -30,12 +30,14 @@ */ #include +#include #include #include #include "conf.h" #include "eloop.h" #include "kmscon_conf.h" #include "kmscon_dummy.h" +#include "kmscon_mouse.h" #include "kmscon_seat.h" #include "kmscon_terminal.h" #include "shl_dlist.h" @@ -96,6 +98,8 @@ struct kmscon_seat { unsigned int async_schedule; + struct kmscon_mouse_info* mouse; + kmscon_seat_cb_t cb; void *data; }; @@ -680,7 +684,7 @@ int kmscon_seat_new(struct kmscon_seat **out, void *data) { struct kmscon_seat *seat; - int ret; + int ret = 0; const char *locale; char *keymap, *compose_file; size_t compose_file_len; @@ -699,6 +703,12 @@ int kmscon_seat_new(struct kmscon_seat **out, shl_dlist_init(&seat->displays); shl_dlist_init(&seat->sessions); + seat->mouse = kmscon_mouse_init(seat->eloop, seat); + if (!seat->mouse) { + log_error("failed to initalize mouse"); + goto err_free; + } + seat->name = strdup(seatname); if (!seat->name) { log_error("cannot copy string"); @@ -783,6 +793,7 @@ int kmscon_seat_new(struct kmscon_seat **out, err_name: free(seat->name); err_free: + kmscon_mouse_cleanup(seat->mouse); free(seat); return ret; } @@ -824,6 +835,8 @@ void kmscon_seat_free(struct kmscon_seat *seat) uterm_input_unregister_cb(seat->input, seat_input_event, seat); uterm_input_unref(seat->input); kmscon_conf_free(seat->conf_ctx); + free(seat->mouse->selection); + free(seat->mouse); free(seat->name); uterm_vt_master_unref(seat->vtm); ev_eloop_unref(seat->eloop); @@ -960,6 +973,14 @@ struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat) return seat->conf_ctx; } +struct kmscon_mouse_info *kmscon_seat_get_mouse(struct kmscon_seat *seat) +{ + if (!seat) + return NULL; + + return seat->mouse; +} + void kmscon_seat_schedule(struct kmscon_seat *seat, unsigned int id) { struct shl_dlist *iter; diff --git a/src/kmscon_seat.h b/src/kmscon_seat.h index 8116968..11d9efa 100644 --- a/src/kmscon_seat.h +++ b/src/kmscon_seat.h @@ -34,6 +34,8 @@ #include #include +#include +#include "kmscon_mouse.h" #include "conf.h" #include "eloop.h" #include "uterm_input.h" @@ -98,6 +100,9 @@ struct uterm_input *kmscon_seat_get_input(struct kmscon_seat *seat); struct ev_eloop *kmscon_seat_get_eloop(struct kmscon_seat *seat); struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat); +struct kmscon_mouse_info *kmscon_seat_get_mouse(struct kmscon_seat *seat); +struct shl_dlist kmscon_seat_get_displays(struct kmscon_seat *seat); + void kmscon_seat_schedule(struct kmscon_seat *seat, unsigned int id); int kmscon_seat_register_session(struct kmscon_seat *seat, diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index 508bd97..9ffb666 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -33,11 +33,21 @@ #include #include #include +#include #include #include + +#include +#include +#include +#include +#include +#include + #include "conf.h" #include "eloop.h" #include "kmscon_conf.h" +#include "kmscon_mouse.h" #include "kmscon_seat.h" #include "kmscon_terminal.h" #include "pty.h" @@ -49,6 +59,16 @@ #define LOG_SUBSYSTEM "terminal" +#include +static const char* FDO_PROPS_INTERFACE = "org.freedesktop.DBus.Properties"; +static const char* FDO_GET_METHOD = "Get"; +static const char* GYRO_CLAIM_METHOD = "ClaimAccelerometer"; +static const char* GYRO_RELEASE_METHOD = "ReleaseAccelerometer"; +static const char* SENSOR_INTERFACE = "net.hadess.SensorProxy"; +static const char* DESTINATION = "net.hadess.SensorProxy"; +static const char* SENSOR_PATH = "/net/hadess/SensorProxy"; +static const char* PROPERTY_HAS_GYRO = "HasAccelerometer"; + struct screen { struct shl_dlist list; struct kmscon_terminal *term; @@ -82,14 +102,27 @@ struct kmscon_terminal { struct kmscon_font_attr font_attr; struct kmscon_font *font; struct kmscon_font *bold_font; + + struct kmscon_mouse_info* mouse; + struct kmscon_selection_info* selection; + + DBusError dbus_error; + DBusConnection* dbus_connection; + struct ev_timer* dbus_gyro_query_timer; + struct itimerspec dbus_gyro_query_timer_spec; + bool has_gyro; }; +static void terminal_resize(struct kmscon_terminal *term, + unsigned int cols, unsigned int rows, + bool force, bool notify); + static void do_clear_margins(struct screen *scr) { - unsigned int w, h, sw, sh; + unsigned int h, sw, sh; struct uterm_mode *mode; struct tsm_screen_attr attr; - int dw, dh; + int dh; mode = uterm_display_get_current(scr->disp); if (!mode) @@ -97,21 +130,309 @@ static void do_clear_margins(struct screen *scr) sw = uterm_mode_get_width(mode); sh = uterm_mode_get_height(mode); - w = scr->txt->font->attr.width * scr->txt->cols; h = scr->txt->font->attr.height * scr->txt->rows; - dw = sw - w; dh = sh - h; tsm_vte_get_def_attr(scr->term->vte, &attr); - if (dw > 0) - uterm_display_fill(scr->disp, attr.br, attr.bg, attr.bb, - w, 0, - dw, h); - if (dh > 0) - uterm_display_fill(scr->disp, attr.br, attr.bg, attr.bb, - 0, h, - sw, dh); + switch (scr->txt->orientation) { + case ORIENTATION_NORMAL: + uterm_display_fill(scr->disp, 0, 0, 0, 0, h, sw, dh); + break; + + case ORIENTATION_RIGHT: + uterm_display_fill(scr->disp, 0, 0, 0, 0, 0, sw - h, sh); + break; + + case ORIENTATION_INVERTED: + uterm_display_fill(scr->disp, 0, 0, 0, 0, 0, sw, dh); + break; + + case ORIENTATION_LEFT: + uterm_display_fill(scr->disp, 0, 0, 0, h, 0, sw - h, sh); + break; + + default : break; + } +} + +static void handle_mouse_word_selection(struct kmscon_mouse_info* mouse, + struct kmscon_text* text, + struct tsm_screen* console) +{ + if (!mouse || !text || !console) + return; + + // on left double-click trigger word-wise selection of text + if (kmscon_mouse_is_dbl_clicked(mouse, KMSCON_MOUSE_BUTTON_LEFT)) { + int from_x = kmscon_mouse_get_x(mouse); + int from_y = kmscon_mouse_get_y(mouse); + tsm_screen_selection_reset(console); + tsm_screen_selection_start(console, 0, from_y); + tsm_screen_selection_target(console, + text->cols - 1, from_y); + kmscon_mouse_selection_copy(mouse, console); + tsm_screen_selection_reset(console); + + char* buf = mouse->selection->buffer; + + int start_x = 0; + int target_x = 0; + + // find trailing space or end of line + for (target_x = from_x; target_x <= text->cols - 1; ++target_x) { + if (buf[target_x] == ' ' || buf[target_x] == '\0') { + --target_x; + break; + } + } + + // find leading space of start of line + for (start_x = from_x; start_x >= 0; --start_x) { + if (buf[start_x] == ' ') { + ++start_x; + break; + } else if (start_x == 0) { + break; + } + } + + // mark word under cusor and update selection-buffer + tsm_screen_selection_start(console, start_x, from_y); + tsm_screen_selection_target(console, target_x, from_y); + kmscon_mouse_selection_copy(mouse, console); + kmscon_mouse_clear_dbl_clicked(mouse, KMSCON_MOUSE_BUTTON_LEFT); + } +} + +static void handle_mouse_random_selection(struct kmscon_mouse_info* mouse, + struct kmscon_terminal* terminal) +{ + if (!mouse || !terminal) + return; + + // paste current selection at current cursor position from buffer + if (kmscon_mouse_is_clicked (mouse, KMSCON_MOUSE_BUTTON_MIDDLE) && + !kmscon_mouse_is_selection_empty (mouse)) { + kmscon_pty_write(terminal->pty, + mouse->selection->buffer, + mouse->selection->buffer_length); + tsm_screen_selection_reset(terminal->console); + kmscon_mouse_clear_clicked(mouse, KMSCON_MOUSE_BUTTON_MIDDLE); + } + + // mark start of new selection + if (kmscon_mouse_is_down(mouse, KMSCON_MOUSE_BUTTON_LEFT)) { + int from_x = kmscon_mouse_get_x(mouse); + int from_y = kmscon_mouse_get_y(mouse); + tsm_screen_selection_reset(terminal->console); + tsm_screen_selection_start(terminal->console, from_x, from_y); + tsm_screen_selection_target(terminal->console, from_x, from_y); + } else if (kmscon_mouse_is_pressed(mouse, KMSCON_MOUSE_BUTTON_LEFT)) { + tsm_screen_selection_target(terminal->console, + kmscon_mouse_get_x(mouse), + kmscon_mouse_get_y(mouse)); + } + + // copy new selection to buffer + if (kmscon_mouse_is_up(mouse, KMSCON_MOUSE_BUTTON_LEFT)) { + kmscon_mouse_selection_copy(mouse, terminal->console); + } +} + +static void handle_mouse_drawing(struct kmscon_mouse_info* mouse, + struct kmscon_text* txt) +{ + if (!mouse || !txt) + return; + + // draw mouse-cursor only if no buttons are pressed and hiding is off + if (kmscon_mouse_is_released(mouse, KMSCON_MOUSE_BUTTON_LEFT) && + kmscon_mouse_is_released(mouse, KMSCON_MOUSE_BUTTON_MIDDLE) && + kmscon_mouse_is_released(mouse, KMSCON_MOUSE_BUTTON_RIGHT) && + !kmscon_mouse_is_hidden(mouse)) { + kmscon_text_render_pointer(txt, + kmscon_mouse_get_x(mouse), + kmscon_mouse_get_y(mouse)); + } +} + +DBusHandlerResult properties_changed_cb(DBusConnection* connection, + DBusMessage* message, + void* user_data) +{ + // ignore these + (void) connection; + + struct kmscon_terminal* term = (struct kmscon_terminal*) user_data; + struct shl_dlist *iter; + + const char* orientation = "undefined"; //ORIENTATION_UNDEFINED; + + DBusError error; + dbus_error_init (&error); + + const char* interface = dbus_message_get_interface (message); + const char* path = dbus_message_get_path (message); + + if (!interface || !path || + (strncmp (interface, FDO_PROPS_INTERFACE, 31) != 0 && + strncmp (path, SENSOR_PATH, 23) != 0)) { + dbus_error_free (&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + DBusMessageIter args; + if (message && !dbus_message_iter_init (message, &args)) { + dbus_error_free (&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + char* value = NULL; + int type = 0; + DBusMessageIter dict; + DBusMessageIter entry; + DBusMessageIter variant; + while ((type = dbus_message_iter_get_arg_type (&args)) != DBUS_TYPE_INVALID) { + switch (type) { + case DBUS_TYPE_ARRAY: + dbus_message_iter_recurse (&args, &dict); + type = dbus_message_iter_get_arg_type (&dict); + if (type == DBUS_TYPE_DICT_ENTRY) { + dbus_message_iter_recurse (&dict, &entry); + while ((type = dbus_message_iter_get_arg_type (&entry)) != DBUS_TYPE_INVALID) { + type = dbus_message_iter_get_arg_type (&entry); + switch (type) { + case DBUS_TYPE_VARIANT: + dbus_message_iter_recurse (&entry, &variant); + type = dbus_message_iter_get_arg_type (&variant); + if (type == DBUS_TYPE_STRING) { + dbus_message_iter_get_basic (&variant, &value); + orientation = value; + } + break; + + default: break; + } + dbus_message_iter_next (&entry); + } + } + break; + + default : break; + } + dbus_message_iter_next (&args); + } + + shl_dlist_for_each(iter, &term->screens) { + struct screen *scr = shl_dlist_entry(iter, struct screen, list); + + if (strncmp(orientation, "normal", 6) == 0) + kmscon_text_rotate(scr->txt, ORIENTATION_NORMAL); + + if (strncmp(orientation, "left-up", 7) == 0) + kmscon_text_rotate(scr->txt, ORIENTATION_LEFT); + + if (strncmp(orientation, "right-up", 8) == 0) + kmscon_text_rotate(scr->txt, ORIENTATION_RIGHT); + + if (strncmp(orientation, "bottom-up", 9) == 0) + kmscon_text_rotate(scr->txt, ORIENTATION_INVERTED); + + term->min_cols = 0; + term->min_rows = 0; + terminal_resize(term, + kmscon_text_get_cols(scr->txt), + kmscon_text_get_rows(scr->txt), + true, + true); + } + + log_info("kmscon_terminal... orientation: %s", orientation); + + dbus_error_free (&error); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +DBusMessage* get_property (DBusConnection* connection, + DBusError* error, + const char* property) +{ + DBusMessage* message = dbus_message_new_method_call (DESTINATION, + SENSOR_PATH, + FDO_PROPS_INTERFACE, + FDO_GET_METHOD); + if (!message) { + return NULL; + } + + const char* interface = SENSOR_INTERFACE; + DBusMessageIter args; + dbus_message_iter_init_append (message, &args); + dbus_message_iter_append_basic (&args, DBUS_TYPE_STRING, &interface); + dbus_message_iter_append_basic (&args, DBUS_TYPE_STRING, &property); + + DBusMessage* reply = dbus_connection_send_with_reply_and_block (connection, + message, + -1, + error); + dbus_message_unref (message); + + return reply; +} + +dbus_bool_t has_gyro (DBusConnection* connection, DBusError* error) +{ + DBusMessage* reply = get_property (connection, error, PROPERTY_HAS_GYRO); + + if (!reply) { + log_error("dbus-message is NULL!"); + return 0; + } + + if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR) { + log_error("%s", dbus_message_get_error_name (reply)); + return 0; + } + + DBusMessageIter reply_args; + dbus_message_iter_init (reply, &reply_args); + DBusMessageIter variant_iter; + dbus_message_iter_recurse (&reply_args, &variant_iter); + dbus_bool_t accelerometer; + dbus_message_iter_get_basic (&variant_iter, &accelerometer); + dbus_message_unref (reply); + + return accelerometer; +} + +void call_method (DBusConnection* connection, const char* method) +{ + DBusMessage* message = dbus_message_new_method_call (DESTINATION, + SENSOR_PATH, + SENSOR_INTERFACE, + method); + + dbus_uint32_t serial; + dbus_bool_t success = dbus_connection_send (connection, message, &serial); + + if (!success) { + log_info("There was an error with the message call '%s'", method); + } + + dbus_message_unref (message); +} + +void dbus_gyro_query_timer_cb(struct ev_timer *timer, uint64_t num, void *data) +{ + if (!data) { + log_warn(" No valid pointer passed to dbus_gyro_query_timer_cb()."); + return; + } + + DBusConnection* dbus_connection = (DBusConnection*) data; + dbus_connection_read_write_dispatch (dbus_connection, 1); } static void do_redraw_screen(struct screen *scr) @@ -128,6 +449,13 @@ static void do_redraw_screen(struct screen *scr) tsm_screen_draw(scr->term->console, kmscon_text_draw_cb, scr->txt); kmscon_text_render(scr->txt); + // deal with mapping normalized coords to character-cell coords + kmscon_mouse_set_mapping(scr->term->mouse, scr->disp, scr->txt); + + handle_mouse_word_selection(scr->term->mouse,scr->txt, scr->term->console); + handle_mouse_random_selection(scr->term->mouse, scr->term); + handle_mouse_drawing(scr->term->mouse, scr->txt); + ret = uterm_display_swap(scr->disp, false); if (ret) { log_warning("cannot swap display %p", scr->disp); @@ -276,6 +604,58 @@ static int font_set(struct kmscon_terminal *term) return 0; } +static void rotate_cw_screen(struct screen *scr) +{ + unsigned int orientation = kmscon_text_get_orientation(scr->txt); + orientation = (orientation + 1) % ORIENTATION_MAX; + if (orientation == ORIENTATION_UNDEFINED) orientation = ORIENTATION_NORMAL; + kmscon_text_rotate(scr->txt, orientation); +} + +static void rotate_cw_all(struct kmscon_terminal *term) +{ + struct shl_dlist *iter; + struct screen *scr; + + shl_dlist_for_each(iter, &term->screens) { + scr = shl_dlist_entry(iter, struct screen, list); + rotate_cw_screen(scr); + term->min_cols = 0; + term->min_rows = 0; + terminal_resize(term, + kmscon_text_get_cols(scr->txt), + kmscon_text_get_rows(scr->txt), + true, + true); + } +} + +static void rotate_ccw_screen(struct screen *scr) +{ + unsigned int orientation = kmscon_text_get_orientation(scr->txt); + orientation = (orientation - 1) % ORIENTATION_MAX; + if (orientation == ORIENTATION_UNDEFINED) orientation = ORIENTATION_LEFT; + kmscon_text_rotate(scr->txt, orientation); +} + +static void rotate_ccw_all(struct kmscon_terminal *term) +{ + struct shl_dlist *iter; + struct screen *scr; + + shl_dlist_for_each(iter, &term->screens) { + scr = shl_dlist_entry(iter, struct screen, list); + rotate_ccw_screen(scr); + term->min_cols = 0; + term->min_rows = 0; + terminal_resize(term, + kmscon_text_get_cols(scr->txt), + kmscon_text_get_rows(scr->txt), + true, + true); + } +} + static int add_display(struct kmscon_terminal *term, struct uterm_display *disp) { struct shl_dlist *iter; @@ -313,7 +693,7 @@ static int add_display(struct kmscon_terminal *term, struct uterm_display *disp) else be = "bbulk"; - ret = kmscon_text_new(&scr->txt, be); + ret = kmscon_text_new(&scr->txt, be, term->conf->rotate); if (ret) { log_error("cannot create text-renderer"); goto err_cb; @@ -453,6 +833,18 @@ static void input_event(struct uterm_input *input, ++term->font_attr.points; return; } + if (conf_grab_matches(term->conf->grab_rotate_cw, + ev->mods, ev->num_syms, ev->keysyms)) { + rotate_cw_all(term); + ev->handled = true; + return; + } + if (conf_grab_matches(term->conf->grab_rotate_ccw, + ev->mods, ev->num_syms, ev->keysyms)) { + rotate_ccw_all(term); + ev->handled = true; + return; + } /* TODO: xkbcommon supports multiple keysyms, but it is currently * unclear how this feature will be used. There is no keymap, which @@ -523,6 +915,9 @@ static void terminal_destroy(struct kmscon_terminal *term) tsm_screen_unref(term->console); uterm_input_unref(term->input); ev_eloop_unref(term->eloop); + call_method (term->dbus_connection, GYRO_RELEASE_METHOD); + dbus_error_free (&term->dbus_error); + dbus_connection_unref (term->dbus_connection); free(term); } @@ -604,6 +999,7 @@ int kmscon_terminal_register(struct kmscon_session **out, term->ref = 1; term->eloop = kmscon_seat_get_eloop(seat); term->input = kmscon_seat_get_input(seat); + term->mouse = kmscon_seat_get_mouse(seat); shl_dlist_init(&term->screens); term->conf_ctx = kmscon_seat_get_conf(seat); @@ -680,10 +1076,64 @@ int kmscon_terminal_register(struct kmscon_session **out, ret = kmscon_seat_register_session(seat, &term->session, session_event, term); if (ret) { - log_error("cannot register session for terminal: %d", ret); + log_error("Cannot register session for terminal: %d", ret); goto err_input; } + dbus_error_init (&term->dbus_error); + term->dbus_connection = dbus_bus_get (DBUS_BUS_SYSTEM, &term->dbus_error); + + if (has_gyro (term->dbus_connection, &term->dbus_error)) { + term->has_gyro = true; + log_info("This system has a gyro-sensor"); + call_method (term->dbus_connection, GYRO_CLAIM_METHOD); + } else { + term->has_gyro = false; + log_info("This system has NO gyro-sensor"); + } + + if (term->has_gyro) { + dbus_bus_add_match (term->dbus_connection, + "type='signal',\ + interface='org.freedesktop.DBus.Properties',\ + member='PropertiesChanged',\ + sender='net.hadess.SensorProxy'", + &term->dbus_error); + + dbus_bool_t result = dbus_connection_add_filter(term->dbus_connection, + properties_changed_cb, + term, + NULL); + + if (!result) { + log_info("Failed to add filter to connection"); + } + + // dbus-gyro-query timer + term->dbus_gyro_query_timer_spec.it_interval.tv_sec = 0; + term->dbus_gyro_query_timer_spec.it_interval.tv_nsec = 200*1000*1000; + term->dbus_gyro_query_timer_spec.it_value.tv_sec = 0; + term->dbus_gyro_query_timer_spec.it_value.tv_nsec = 200*1000*1000; + + ret = ev_timer_new (&term->dbus_gyro_query_timer, + &term->dbus_gyro_query_timer_spec, + dbus_gyro_query_timer_cb, + term->dbus_connection, + NULL, + NULL); + if (ret) { + log_error("Cannot create dbus-gyro-query timer: %d", ret); + goto err_free; + } + ev_timer_enable(term->dbus_gyro_query_timer); + + ret = ev_eloop_add_timer(term->eloop, term->dbus_gyro_query_timer); + if (ret) { + log_error("Cannot add dbus-gyro-query timer to event-loop: %d", ret); + goto err_free; + } + } + ev_eloop_ref(term->eloop); uterm_input_ref(term->input); *out = term->session; @@ -704,6 +1154,9 @@ int kmscon_terminal_register(struct kmscon_session **out, err_con: tsm_screen_unref(term->console); err_free: + call_method (term->dbus_connection, GYRO_RELEASE_METHOD); + dbus_error_free (&term->dbus_error); + dbus_connection_unref (term->dbus_connection); free(term); return ret; } diff --git a/src/meson.build b/src/meson.build index c18759f..a243a97 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,6 +2,9 @@ # # SPDX-License-Identifier: MIT +cc = meson.get_compiler('c') +mlib = cc.find_library('m') + # # Git-HEAD helper # @@ -205,6 +208,8 @@ if enable_renderer_gltex 'kmscon_mod_gltex.c', embed_gen.process('text_gltex_atlas.vert', extra_args: shader_regex), embed_gen.process('text_gltex_atlas.frag', extra_args: shader_regex), + embed_gen.process('mouse_pointer.vert', extra_args: shader_regex), + embed_gen.process('mouse_pointer.frag', extra_args: shader_regex), ], name_prefix: '', dependencies: [libtsm_deps, glesv2_deps, shl_deps], @@ -249,6 +254,7 @@ kmscon_srcs = [ 'kmscon_seat.c', 'kmscon_conf.c', 'kmscon_main.c', + 'kmscon_mouse.c', ] if enable_session_dummy kmscon_srcs += 'kmscon_dummy.c' @@ -257,7 +263,7 @@ if enable_session_terminal kmscon_srcs += 'kmscon_terminal.c' endif kmscon = executable('kmscon', kmscon_srcs, - dependencies: [xkbcommon_deps, libtsm_deps, threads_deps, dl_deps, conf_deps, shl_deps, eloop_deps, uterm_deps], + dependencies: [mlib, xkbcommon_deps, libtsm_deps, threads_deps, dl_deps, conf_deps, shl_deps, eloop_deps, uterm_deps], export_dynamic: true, install: true, install_dir: libexecdir, diff --git a/src/mouse_pointer.frag b/src/mouse_pointer.frag new file mode 100644 index 0000000..847f01d --- /dev/null +++ b/src/mouse_pointer.frag @@ -0,0 +1,39 @@ +/* + * kmscon - Fragment Shader + * + * Copyright (c) 2011-2012 David Herrmann + * Copyright (c) 2011 University of Tuebingen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Fragment Shader + * A basic fragment shader for drawing a simple mouse-pointer. + */ + +precision mediump float; + +uniform vec4 color; + +void main() +{ + gl_FragColor = vec4(color); +} \ No newline at end of file diff --git a/src/mouse_pointer.vert b/src/mouse_pointer.vert new file mode 100644 index 0000000..50dac93 --- /dev/null +++ b/src/mouse_pointer.vert @@ -0,0 +1,50 @@ +/* + * kmscon - Vertex Shader + * + * Copyright (c) 2011-2012 David Herrmann + * Copyright (c) 2011 University of Tuebingen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Vertex Shader + * A basic vertex shader for drawing a simple mouse-pointer. + */ + +uniform mat4 projection; +uniform float orientation; +uniform vec2 offset; + +attribute vec2 position; + +vec2 opRotate(in vec2 p, in float degrees) +{ + float rad = radians(degrees); + float c = cos(rad); + float s = sin(rad); + return p * mat2(vec2(c, s), vec2(-s, c)); +} + +void main() +{ + vec2 rotatedPosition = opRotate(position + offset, orientation); + gl_Position = projection * vec4(rotatedPosition, 0.0, 1.0); +} \ No newline at end of file diff --git a/src/text.c b/src/text.c index 24b579b..ddbb77e 100644 --- a/src/text.c +++ b/src/text.c @@ -103,7 +103,7 @@ void kmscon_text_unregister(const char *name) shl_register_remove(&text_reg, name); } -static int new_text(struct kmscon_text *text, const char *backend) +static int new_text(struct kmscon_text *text, const char *backend, unsigned int orientation) { struct shl_register_record *record; const char *name = backend ? backend : ""; @@ -124,6 +124,7 @@ static int new_text(struct kmscon_text *text, const char *backend) text->record = record; text->ops = record->data; + text->orientation = orientation; if (text->ops->init) ret = text->ops->init(text); @@ -143,10 +144,11 @@ static int new_text(struct kmscon_text *text, const char *backend) * kmscon_text_new: * @out: A pointer to the new text-renderer is stored here * @backend: Backend to use or NULL for default backend + * @rotate: Orientation ("normal", "inverted", "right" or "left") to use for output * * Returns: 0 on success, error code on failure */ -int kmscon_text_new(struct kmscon_text **out, const char *backend) +int kmscon_text_new(struct kmscon_text **out, const char *backend, const char *rotate) { struct kmscon_text *text; int ret; @@ -160,10 +162,31 @@ int kmscon_text_new(struct kmscon_text **out, const char *backend) return -ENOMEM; } - ret = new_text(text, backend); + text->orientation = ORIENTATION_UNDEFINED; + + if (rotate) { + if (strncmp(rotate, "normal", 6) == 0) { + text->orientation = ORIENTATION_NORMAL; + log_debug("using: orientation: normal"); + } + else if (strncmp(rotate, "right", 5) == 0) { + text->orientation = ORIENTATION_RIGHT; + log_debug("using: orientation: right"); + } + else if (strncmp(rotate, "inverted", 8) == 0) { + text->orientation = ORIENTATION_INVERTED; + log_debug("using: orientation: inverted"); + } + else if (strncmp(rotate, "left", 4) == 0) { + text->orientation = ORIENTATION_LEFT; + log_debug("using: orientation: left"); + } + } + + ret = new_text(text, backend, text->orientation); if (ret) { if (backend) - ret = new_text(text, NULL); + ret = new_text(text, NULL, text->orientation); if (ret) goto err_free; } @@ -333,6 +356,55 @@ unsigned int kmscon_text_get_rows(struct kmscon_text *txt) return txt->rows; } +/** + * kmscon_text_get_orientation: + * @txt: valid text renderer + * + * With a valid @txt passed it, this returns the currently active orientation + * of the output/screen. Possible values are: + * + * - ORIENTATION_NORMAL + * - ORIENTATION_RIGHT + * - ORIENTATION_INVERTED + * - ORIENTATION_LEFT + * + * Returns: Current orientation enum or ORIENTATION_UNDEFINED if @txt is invalid + */ +unsigned int kmscon_text_get_orientation(struct kmscon_text *txt) +{ + if (!txt) + return ORIENTATION_UNDEFINED; + + return txt->orientation; +} + +/** + * kmscon_text_rotate: + * @txt: valid text renderer + * @orientation: enum value representing the desired output-orientation + * + * Update the rotation/orientation of the text. It can be one of: + * + * - ORIENTATION_NORMAL + * - ORIENTATION_RIGHT + * - ORIENTATION_INVERTED + * - ORIENTATION_LEFT + * + * Returns: 0 on success, negative error code on failure. + */ +int kmscon_text_rotate(struct kmscon_text *txt, unsigned int orientation) +{ + int ret = 0; + + if (orientation == ORIENTATION_UNDEFINED) + return -EINVAL; + + if (txt->ops->rotate) + ret = txt->ops->rotate(txt, orientation); + + return ret; +} + /** * kmscon_text_prepare: * @txt: valid text renderer @@ -417,6 +489,31 @@ int kmscon_text_render(struct kmscon_text *txt) return ret; } +/** + * kmscon_text_render_pointer: + * @txt: valid text renderer + * @cursor_x: column cell coordinate + * @cursor_y: row cell coordinate + * + * This draws a mouse-pointer cross-hair at the current mouse-position.. + * + * Returns: 0 on success, negative error on failure. + */ +int kmscon_text_render_pointer(struct kmscon_text *txt, + int cursor_x, + int cursor_y) +{ + int ret = 0; + + if (!txt) + return -EINVAL; + + if (txt->ops->render_pointer) + ret = txt->ops->render_pointer(txt, cursor_x, cursor_y); + + return ret; +} + /** * kmscon_text_abort: * @txt: valid text renderer diff --git a/src/text.h b/src/text.h index 570df6b..7a97361 100644 --- a/src/text.h +++ b/src/text.h @@ -42,6 +42,15 @@ /* text renderer */ +enum Orientation { + ORIENTATION_UNDEFINED = 0, + ORIENTATION_NORMAL, + ORIENTATION_RIGHT, + ORIENTATION_INVERTED, + ORIENTATION_LEFT, + ORIENTATION_MAX +}; + struct kmscon_text; struct kmscon_text_ops; @@ -57,6 +66,7 @@ struct kmscon_text { unsigned int cols; unsigned int rows; bool rendering; + unsigned int orientation; }; struct kmscon_text_ops { @@ -66,6 +76,7 @@ struct kmscon_text_ops { void (*destroy) (struct kmscon_text *txt); int (*set) (struct kmscon_text *txt); void (*unset) (struct kmscon_text *txt); + int (*rotate) (struct kmscon_text *txt, unsigned int orientation); int (*prepare) (struct kmscon_text *txt); int (*draw) (struct kmscon_text *txt, uint64_t id, const uint32_t *ch, size_t len, @@ -73,13 +84,14 @@ struct kmscon_text_ops { unsigned int posx, unsigned int posy, const struct tsm_screen_attr *attr); int (*render) (struct kmscon_text *txt); + int (*render_pointer) (struct kmscon_text *txt, int cursor_x, int cursor_y); void (*abort) (struct kmscon_text *txt); }; int kmscon_text_register(const struct kmscon_text_ops *ops); void kmscon_text_unregister(const char *name); -int kmscon_text_new(struct kmscon_text **out, const char *backend); +int kmscon_text_new(struct kmscon_text **out, const char *backend, const char *rotate); void kmscon_text_ref(struct kmscon_text *txt); void kmscon_text_unref(struct kmscon_text *txt); @@ -91,6 +103,9 @@ void kmscon_text_unset(struct kmscon_text *txt); unsigned int kmscon_text_get_cols(struct kmscon_text *txt); unsigned int kmscon_text_get_rows(struct kmscon_text *txt); +unsigned int kmscon_text_get_orientation(struct kmscon_text *txt); +int kmscon_text_rotate(struct kmscon_text *txt, unsigned int orientation); + int kmscon_text_prepare(struct kmscon_text *txt); int kmscon_text_draw(struct kmscon_text *txt, uint64_t id, const uint32_t *ch, size_t len, @@ -98,6 +113,9 @@ int kmscon_text_draw(struct kmscon_text *txt, unsigned int posx, unsigned int posy, const struct tsm_screen_attr *attr); int kmscon_text_render(struct kmscon_text *txt); +int kmscon_text_render_pointer(struct kmscon_text *txt, + int cursor_x, + int cursor_y); void kmscon_text_abort(struct kmscon_text *txt); int kmscon_text_draw_cb(struct tsm_screen *con, diff --git a/src/text_gltex.c b/src/text_gltex.c index 4b132d2..5b21954 100644 --- a/src/text_gltex.c +++ b/src/text_gltex.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,8 @@ #include "uterm_video.h" #include "text_gltex_atlas.frag.bin.h" #include "text_gltex_atlas.vert.bin.h" +#include "mouse_pointer.frag.bin.h" +#include "mouse_pointer.vert.bin.h" #define LOG_SUBSYSTEM "text_gltex" @@ -93,6 +96,19 @@ struct glyph { #define GLYPH_STRIDE(gly) ((gly)->glyph->buf.stride) #define GLYPH_DATA(gly) ((gly)->glyph->buf.data) +GLfloat mouse_block_vertices[] = {-.01f, .02f, // 0) top-left + .01f, .02f, // 1) top-right + -.01f, -.02f, // 2) bottom-left + .01f, -.02f}; // 3) bottom-right + +GLuint mouse_block_outline_indices[] = {0, 1, + 1, 3, + 0, 2, + 2, 3}; + +GLuint mouse_block_fill_indices[] = {0, 1, 3, + 0, 3, 2}; + struct gltex { struct shl_hashtable *glyphs; struct shl_hashtable *bold_glyphs; @@ -105,6 +121,7 @@ struct gltex { GLfloat advance_y; struct gl_shader *shader; + GLuint uni_orientation; GLuint uni_proj; GLuint uni_atlas; GLuint uni_advance_htex; @@ -112,6 +129,14 @@ struct gltex { unsigned int sw; unsigned int sh; + + GLfloat angle; + + struct gl_shader *mouse_pointer_shader; + GLuint uni_orientation_mouse; + GLuint uni_proj_mouse; + GLuint uni_color_mouse; + GLuint uni_offset_mouse; }; #define FONT_WIDTH(txt) ((txt)->font->attr.width) @@ -186,6 +211,7 @@ static int gltex_set(struct kmscon_text *txt) if (ret) goto err_bold_htable; + gt->uni_orientation = gl_shader_get_uniform(gt->shader, "orientation"); gt->uni_proj = gl_shader_get_uniform(gt->shader, "projection"); gt->uni_atlas = gl_shader_get_uniform(gt->shader, "atlas"); gt->uni_advance_htex = gl_shader_get_uniform(gt->shader, @@ -198,12 +224,45 @@ static int gltex_set(struct kmscon_text *txt) goto err_shader; } + vert = _binary_mouse_pointer_vert_start; + vlen = _binary_mouse_pointer_vert_size; + frag = _binary_mouse_pointer_frag_start; + flen = _binary_mouse_pointer_frag_size; + gl_clear_error(); + static char *mouse_pointer_attr[] = { "position" }; + ret = gl_shader_new(>->mouse_pointer_shader, + vert, vlen, + frag, flen, + mouse_pointer_attr, 1, + log_llog, NULL); + if (ret) + goto err_shader; + + gt->uni_orientation_mouse = gl_shader_get_uniform(gt->mouse_pointer_shader, + "orientation"); + gt->uni_proj_mouse = gl_shader_get_uniform(gt->mouse_pointer_shader, + "projection"); + gt->uni_color_mouse = gl_shader_get_uniform(gt->mouse_pointer_shader, + "color"); + gt->uni_offset_mouse = gl_shader_get_uniform(gt->mouse_pointer_shader, + "offset"); + + if (gl_has_error(gt->mouse_pointer_shader)) { + log_warning("cannot create mouse-pointer shader"); + goto err_mouse_pointer_shader; + } + mode = uterm_display_get_current(txt->disp); gt->sw = uterm_mode_get_width(mode); gt->sh = uterm_mode_get_height(mode); - txt->cols = gt->sw / FONT_WIDTH(txt); - txt->rows = gt->sh / FONT_HEIGHT(txt); + if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) { + txt->cols = gt->sw / FONT_WIDTH(txt); + txt->rows = gt->sh / FONT_HEIGHT(txt); + } else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) { + txt->cols = gt->sh / FONT_WIDTH(txt); + txt->rows = gt->sw / FONT_HEIGHT(txt); + } glGetIntegerv(GL_MAX_TEXTURE_SIZE, &s); if (s <= 0) @@ -223,6 +282,8 @@ static int gltex_set(struct kmscon_text *txt) return 0; +err_mouse_pointer_shader: + gl_shader_unref(gt->mouse_pointer_shader); err_shader: gl_shader_unref(gt->shader); err_bold_htable: @@ -523,6 +584,43 @@ static int find_glyph(struct kmscon_text *txt, struct glyph **out, return ret; } +static int gltex_rotate(struct kmscon_text *txt, unsigned int orientation) +{ + struct gltex *gt = txt->data; + int ret = 0; + + txt->orientation = orientation; + + if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) { + txt->cols = gt->sw / FONT_WIDTH(txt); + txt->rows = gt->sh / FONT_HEIGHT(txt); + } else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) { + txt->cols = gt->sh / FONT_WIDTH(txt); + txt->rows = gt->sw / FONT_HEIGHT(txt); + } + + if (txt->orientation == ORIENTATION_NORMAL ) { + gt->angle = .0; + } else if (txt->orientation == ORIENTATION_INVERTED) { + gt->angle = 180.; + } else if (txt->orientation == ORIENTATION_RIGHT) { + gt->angle = 90.; + } else if (txt->orientation == ORIENTATION_LEFT) { + gt->angle = -90.; + } + + if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) { + gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt); + gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt); + } else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) { + float aspect = (float) gt->sw / (float) gt->sh; + gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt) * aspect; + gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt) * (1./aspect); + } + + return ret; +} + static int gltex_prepare(struct kmscon_text *txt) { struct gltex *gt = txt->data; @@ -540,8 +638,24 @@ static int gltex_prepare(struct kmscon_text *txt) atlas->cache_num = 0; } - gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt); - gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt); + if (txt->orientation == ORIENTATION_NORMAL ) { + gt->angle = .0; + } else if (txt->orientation == ORIENTATION_INVERTED) { + gt->angle = 180.; + } else if (txt->orientation == ORIENTATION_RIGHT) { + gt->angle = 90.; + } else if (txt->orientation == ORIENTATION_LEFT) { + gt->angle = -90.; + } + + if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) { + gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt); + gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt); + } else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) { + float aspect = (float) gt->sw / (float) gt->sh; + gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt) * aspect; + gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt) * (1./aspect); + } return 0; } @@ -648,6 +762,7 @@ static int gltex_render(struct kmscon_text *txt) gl_m4_identity(mat); glUniformMatrix4fv(gt->uni_proj, 1, GL_FALSE, mat); + glUniform1f(gt->uni_orientation, gt->angle); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); @@ -686,6 +801,70 @@ static int gltex_render(struct kmscon_text *txt) return 0; } +static int gltex_render_pointer(struct kmscon_text *txt, int cursor_x, int cursor_y) +{ + struct gltex *gt = txt->data; + float mat[16]; + + gl_clear_error(); + + gl_shader_use(gt->mouse_pointer_shader); + + GLfloat pixel_w = .0f; + GLfloat pixel_h = .0f; + GLfloat hw = .0f; + GLfloat hh = .0f; + + if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) { + pixel_w = 2.0f/gt->sw; + pixel_h = 2.0f/gt->sh; + hw = FONT_WIDTH(txt)*pixel_w*.5f; + hh = FONT_HEIGHT(txt)*pixel_h*.5f; + } else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) { + pixel_w = 2.0f/gt->sh; + pixel_h = 2.0f/gt->sw; + hw = FONT_WIDTH(txt)*pixel_w*.5f; + hh = FONT_HEIGHT(txt)*pixel_h*.5f; + } + + mouse_block_vertices[0] = -hw; // top-left + mouse_block_vertices[1] = hh; + mouse_block_vertices[2] = hw - pixel_w; // top-right + mouse_block_vertices[3] = hh; + mouse_block_vertices[4] = -hw; // bottom-left + mouse_block_vertices[5] = -hh + pixel_h; + mouse_block_vertices[6] = hw - pixel_w; // bottom-right + mouse_block_vertices[7] = -hh + pixel_h; + + glViewport(0, 0, gt->sw, gt->sh); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + gl_m4_identity(mat); + glUniformMatrix4fv(gt->uni_proj_mouse, 1, GL_FALSE, mat); + glUniform1f(gt->uni_orientation_mouse, gt->angle); + + GLfloat top_left[2] = {-1.f, 1.f}; + GLfloat x = top_left[0] + hw + cursor_x*FONT_WIDTH(txt)*pixel_w; + GLfloat y = top_left[1] - hh - cursor_y*FONT_HEIGHT(txt)*pixel_h; + glUniform2f(gt->uni_offset_mouse, x, y); + + glEnableVertexAttribArray(0); + + // block-cursor outline + glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, mouse_block_vertices); + glUniform4f(gt->uni_color_mouse, 1.f, .5f, .25f, .9f); + glDrawElements(GL_LINES, 8, GL_UNSIGNED_INT, mouse_block_outline_indices); + + // block-cursor fill + glUniform4f(gt->uni_color_mouse, 1.f, .5f, .25f, .35f); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, mouse_block_fill_indices); + + glDisableVertexAttribArray(0); + + return 0; +} + struct kmscon_text_ops kmscon_text_gltex_ops = { .name = "gltex", .owner = NULL, @@ -693,8 +872,10 @@ struct kmscon_text_ops kmscon_text_gltex_ops = { .destroy = gltex_destroy, .set = gltex_set, .unset = gltex_unset, + .rotate = gltex_rotate, .prepare = gltex_prepare, .draw = gltex_draw, .render = gltex_render, + .render_pointer = gltex_render_pointer, .abort = NULL, }; diff --git a/src/text_gltex_atlas.vert b/src/text_gltex_atlas.vert index e99e1b4..9bacae9 100644 --- a/src/text_gltex_atlas.vert +++ b/src/text_gltex_atlas.vert @@ -31,6 +31,7 @@ */ uniform mat4 projection; +uniform float orientation; attribute vec2 position; attribute vec2 texture_position; @@ -41,9 +42,18 @@ varying vec2 texpos; varying vec3 fgcol; varying vec3 bgcol; +vec2 opRotate(in vec2 p, in float degrees) +{ + float rad = radians(degrees); + float c = cos(rad); + float s = sin(rad); + return p * mat2(vec2(c, s), vec2(-s, c)); +} + void main() { - gl_Position = projection * vec4(position, 0.0, 1.0); + vec2 rotatedPosition = opRotate(position, orientation); + gl_Position = projection * vec4(rotatedPosition, 0.0, 1.0); texpos = texture_position; fgcol = fgcolor; bgcol = bgcolor;