wvkbd/keyboard.c
Willow Barraco c45446a794
Do not write texts outside of keys
This is a bit hacky. The main problem is that there is no way to tell
cairo to limit the width. It will wrap text accordingly to width and
height, it will add ellipsizes if it overlow the box, but if a word
width is larger than the box width, it will write it.

To avoid that, I make sure we don't go too much to the left, and I
redraw the background at the right of the keys. This is not visible
cause we damage track correctly the updated buffer coordinates.

I also moved the damage tracking from do_rectangle and draw_text to
higher draw_key and draw_layout.
2023-08-31 18:04:26 +02:00

623 lines
16 KiB
C

#include "proto/virtual-keyboard-unstable-v1-client-protocol.h"
#include <linux/input-event-codes.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/mman.h>
#include "keyboard.h"
#include "drw.h"
#include "os-compatibility.h"
#define MAX_LAYERS 25
/* lazy die macro */
#define die(...) \
fprintf(stderr, __VA_ARGS__); \
exit(1)
#ifndef KEYMAP
#error "make sure to define KEYMAP"
#endif
#include KEYMAP
void
kbd_switch_layout(struct kbd *kb, struct layout *l, size_t layer_index) {
kb->prevlayout = kb->layout;
if ((kb->layer_index != kb->last_abc_index) && (kb->layout->abc)) {
kb->last_abc_layout = kb->layout;
kb->last_abc_index = kb->layer_index;
}
kb->layer_index = layer_index;
kb->layout = l;
if (kb->debug)
fprintf(stderr, "Switching to layout %s, layer_index %ld\n", kb->layout->name, layer_index);
if (!l->keymap_name)
fprintf(stderr,"Layout has no keymap!"); //sanity check
if ((!kb->prevlayout) ||
(strcmp(kb->prevlayout->keymap_name, kb->layout->keymap_name) != 0)) {
fprintf(stderr, "Switching to keymap %s\n", kb->layout->keymap_name);
create_and_upload_keymap(kb, kb->layout->keymap_name, 0, 0);
}
kbd_draw_layout(kb);
}
void
kbd_next_layer(struct kbd *kb, struct key *k, bool invert) {
size_t layer_index = kb->layer_index;
if ((kb->mods & Ctrl) || (kb->mods & Alt) || (kb->mods & AltGr) || ((bool)kb->compose)) {
// with modifiers ctrl/alt/altgr: switch to the first layer
layer_index = 0;
kb->mods = 0;
} else if ((kb->mods & Shift) || (kb->mods & CapsLock) || (invert)) {
// with modifiers shift/capslock or invert set: switch to the previous layout in the layer sequence
if (layer_index > 0) {
layer_index--;
} else {
size_t layercount = 0;
for (size_t i = 0; layercount == 0; i++) {
if (kb->landscape) {
if (kb->landscape_layers[i] == NumLayouts) layercount = i;
} else {
if (kb->layers[i] == NumLayouts) layercount = i;
}
}
layer_index = layercount - 1;
}
if (!invert) kb->mods ^= Shift;
} else {
// normal behaviour: switch to the next layout in the layer sequence
layer_index++;
size_t layercount = 0;
for (size_t i = 0; layercount == 0; i++) {
if (kb->landscape) {
if (kb->landscape_layers[i] == NumLayouts) layercount = i;
} else {
if (kb->layers[i] == NumLayouts) layercount = i;
}
}
if (layer_index >= layercount) {
if (kb->debug)
fprintf(stderr, "wrapping layer_index back to start\n");
layer_index = 0;
}
}
enum layout_id layer;
if (kb->landscape) {
layer = kb->landscape_layers[layer_index];
} else {
layer = kb->layers[layer_index];
}
if (((bool)kb->compose) && (k)) {
kb->compose = 0;
kbd_draw_key(kb, k, Unpress);
}
kbd_switch_layout(kb, &kb->layouts[layer], layer_index);
}
uint8_t
kbd_get_rows(struct layout *l) {
uint8_t rows = 0;
struct key *k = l->keys;
while (k->type != Last) {
if (k->type == EndRow) {
rows++;
}
k++;
}
return rows + 1;
}
enum layout_id *
kbd_init_layers(char *layer_names_list) {
enum layout_id *layers;
uint8_t numlayers = 0;
bool found;
char *s;
int i;
layers = malloc(MAX_LAYERS * sizeof(enum layout_id));
s = strtok(layer_names_list, ",");
while (s != NULL) {
if (numlayers + 1 == MAX_LAYERS) {
fprintf(stderr, "too many layers specified");
exit(3);
}
found = false;
for (i = 0; i < NumLayouts - 1; i++) {
if (layouts[i].name && strcmp(layouts[i].name, s) == 0) {
fprintf(stderr, "layer #%d = %s\n", numlayers + 1, s);
layers[numlayers++] = i;
found = true;
break;
}
}
if (!found) {
fprintf(stderr, "No such layer: %s\n", s);
exit(3);
}
s = strtok(NULL, ",");
}
layers[numlayers] = NumLayouts; // mark the end of the sequence
if (numlayers == 0) {
fprintf(stderr, "No layers defined\n");
exit(3);
}
return layers;
}
void
kbd_init(struct kbd *kb, struct layout *layouts,
char *layer_names_list, char *landscape_layer_names_list) {
int i;
fprintf(stderr, "Initializing keyboard\n");
kb->layouts = layouts;
for (i = 0; i < NumLayouts - 1; i++)
;
fprintf(stderr, "Found %d layouts\n", i);
kb->layer_index = 0;
kb->last_abc_index = 0;
if (layer_names_list)
kb->layers = kbd_init_layers(layer_names_list);
if (landscape_layer_names_list)
kb->landscape_layers = kbd_init_layers(landscape_layer_names_list);
i = 0;
enum layout_id lid = kb->layers[0];
while (lid != NumLayouts) {
lid = kb->layers[++i];
}
fprintf(stderr, "Found %d layers\n", i);
enum layout_id layer;
if (kb->landscape) {
layer = kb->landscape_layers[kb->layer_index];
} else {
layer = kb->layers[kb->layer_index];
}
kb->layout = &kb->layouts[layer];
kb->last_abc_layout = &kb->layouts[layer];
/* upload keymap */
create_and_upload_keymap(kb, kb->layout->keymap_name, 0, 0);
}
void
kbd_init_layout(struct layout *l, uint32_t width, uint32_t height) {
uint32_t x = 0, y = 0;
uint8_t rows = kbd_get_rows(l);
l->keyheight = height / rows;
struct key *k = l->keys;
double rowlength = kbd_get_row_length(k);
double rowwidth = 0.0;
while (k->type != Last) {
if (k->type == EndRow) {
y += l->keyheight;
x = 0;
rowwidth = 0.0;
rowlength = kbd_get_row_length(k + 1);
} else if (k->width > 0) {
k->x = x;
k->y = y;
k->w = ((double)width / rowlength) * k->width;
x += k->w;
rowwidth += k->width;
if (x < (rowwidth / rowlength) * (double)width) {
k->w++;
x++;
}
}
k->h = l->keyheight;
k++;
}
}
double
kbd_get_row_length(struct key *k) {
double l = 0.0;
while ((k->type != Last) && (k->type != EndRow)) {
l += k->width;
k++;
}
return l;
}
struct key *
kbd_get_key(struct kbd *kb, uint32_t x, uint32_t y) {
struct layout *l = kb->layout;
struct key *k = l->keys;
if (kb->debug)
fprintf(stderr, "get key: +%d+%d\n", x, y);
while (k->type != Last) {
if ((k->type != EndRow) && (k->type != Pad) && (k->type != Pad) &&
(x >= k->x) && (y >= k->y) && (x < k->x + k->w) && (y < k->y + k->h)) {
return k;
}
k++;
}
return NULL;
}
size_t
kbd_get_layer_index(struct kbd *kb, struct layout *l) {
for (size_t i = 0; i < NumLayouts - 1; i++) {
if (l == &kb->layouts[i]) {
return i;
}
}
return 0;
}
void
kbd_unpress_key(struct kbd *kb, uint32_t time) {
bool unlatch_shift = false;
if (kb->last_press) {
unlatch_shift = (kb->mods & Shift) == Shift;
if (unlatch_shift) {
kb->mods ^= Shift;
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods, 0, 0, 0);
}
if (kb->last_press->type == Copy) {
zwp_virtual_keyboard_v1_key(kb->vkbd, time, 127, // COMP key
WL_KEYBOARD_KEY_STATE_RELEASED);
} else {
if ((kb->last_press->code == KEY_SPACE) && (unlatch_shift)) {
//shift + space is tab
zwp_virtual_keyboard_v1_key(kb->vkbd, time, KEY_TAB,
WL_KEYBOARD_KEY_STATE_RELEASED);
} else {
zwp_virtual_keyboard_v1_key(kb->vkbd, time, kb->last_press->code,
WL_KEYBOARD_KEY_STATE_RELEASED);
}
}
if (kb->compose >= 2) {
kb->compose = 0;
kbd_switch_layout(kb, kb->last_abc_layout, kb->last_abc_index);
} else if (unlatch_shift) {
kbd_draw_layout(kb);
} else {
kbd_draw_key(kb, kb->last_press, Unpress);
}
kb->last_press = NULL;
}
}
void
kbd_release_key(struct kbd *kb, uint32_t time) {
kbd_unpress_key(kb, time);
if (kb->print_intersect && kb->last_swipe) {
printf("\n");
// Important so autocompleted words get typed in time
fflush(stdout);
kbd_draw_layout(kb);
kb->last_swipe = NULL;
}
drwsurf_flip(kb->surf);
}
void
kbd_motion_key(struct kbd *kb, uint32_t time, uint32_t x, uint32_t y) {
// Output intersecting keys
// (for external 'swiping'-based accelerators).
if (kb->print_intersect) {
if (kb->last_press) {
kbd_unpress_key(kb, time);
// Redraw last press as a swipe.
kbd_draw_key(kb, kb->last_swipe, Swipe);
}
struct key *intersect_key;
intersect_key = kbd_get_key(kb, x, y);
if (intersect_key &&
(!kb->last_swipe || intersect_key->label != kb->last_swipe->label)) {
kbd_print_key_stdout(kb, intersect_key);
kb->last_swipe = intersect_key;
kbd_draw_key(kb, kb->last_swipe, Swipe);
}
} else {
kbd_unpress_key(kb, time);
}
drwsurf_flip(kb->surf);
}
void
kbd_press_key(struct kbd *kb, struct key *k, uint32_t time) {
if ((kb->compose == 1) && (k->type != Compose) && (k->type != Mod)) {
if ((k->type == NextLayer) || (k->type == BackLayer) || ((k->type == Code) && (k->code == KEY_SPACE))) {
kb->compose = 0;
if (kb->debug)
fprintf(stderr, "showing layout index\n");
kbd_switch_layout(kb, &kb->layouts[Index], 0);
return;
} else if (k->layout) {
kb->compose++;
if (kb->debug)
fprintf(stderr, "showing compose %d\n", kb->compose);
kbd_switch_layout(kb, k->layout, kbd_get_layer_index(kb, k->layout));
return;
} else {
return;
}
}
switch (k->type) {
case Code:
if (k->code_mod) {
if (k->reset_mod) {
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, k->code_mod, 0, 0, 0);
} else {
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods ^ k->code_mod, 0,
0, 0);
}
} else {
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods, 0, 0, 0);
}
kb->last_swipe = kb->last_press = k;
kbd_draw_key(kb, k, Press);
if ((k->code == KEY_SPACE) && (kb->mods & Shift)) {
//shift space is tab
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, 0, 0, 0, 0);
zwp_virtual_keyboard_v1_key(kb->vkbd, time, KEY_TAB,
WL_KEYBOARD_KEY_STATE_PRESSED);
} else {
zwp_virtual_keyboard_v1_key(kb->vkbd, time, kb->last_press->code,
WL_KEYBOARD_KEY_STATE_PRESSED);
}
if (kb->print || kb->print_intersect)
kbd_print_key_stdout(kb, k);
if (kb->compose) {
if (kb->debug)
fprintf(stderr, "pressing composed key\n");
kb->compose++;
}
break;
case Mod:
kb->mods ^= k->code;
if (k->code == Shift) {
kbd_draw_layout(kb);
}
if (kb->mods & k->code) {
kbd_draw_key(kb, k, Press);
} else {
kbd_draw_key(kb, k, Unpress);
}
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods, 0, 0, 0);
break;
case Layout:
// switch to the layout determined by the key
kbd_switch_layout(kb, k->layout, kbd_get_layer_index(kb, k->layout));
//reset previous layout to default/first so we don't get any weird cycles
kb->last_abc_index = 0;
if (kb->landscape) {
kb->last_abc_layout = &kb->layouts[kb->landscape_layers[0]];
} else {
kb->last_abc_layout = &kb->layouts[kb->layers[0]];
}
break;
case Compose:
// switch to the associated layout determined by the *next* keypress
if (kb->compose == 0) {
kb->compose = 1;
} else {
kb->compose = 0;
}
if ((bool)kb->compose) {
kbd_draw_key(kb, k, Press);
} else {
kbd_draw_key(kb, k, Unpress);
}
break;
case NextLayer: //(also handles previous layer when shift modifier is on, or "first layer" with other modifiers)
kbd_next_layer(kb, k, false);
break;
case BackLayer: //triggered when "Abc" keys are pressed
// switch to the last active alphabetical layout
if (kb->last_abc_layout) {
kb->compose = 0;
kbd_switch_layout(kb, kb->last_abc_layout, kb->last_abc_index);
//reset previous layout to default/first so we don't get any weird cycles
kb->last_abc_index = 0;
if (kb->landscape) {
kb->last_abc_layout = &kb->layouts[kb->landscape_layers[0]];
} else {
kb->last_abc_layout = &kb->layouts[kb->layers[0]];
}
}
break;
case Copy:
// copy code as unicode chr by setting a temporary keymap
kb->last_swipe = kb->last_press = k;
kbd_draw_key(kb, k, Press);
if (kb->debug)
fprintf(stderr, "pressing copy key\n");
create_and_upload_keymap(kb, kb->layout->keymap_name, k->code, k->code_mod);
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods, 0, 0, 0);
zwp_virtual_keyboard_v1_key(kb->vkbd, time, 127, // COMP key
WL_KEYBOARD_KEY_STATE_PRESSED);
if (kb->print || kb->print_intersect)
kbd_print_key_stdout(kb, k);
break;
default:
break;
}
drwsurf_flip(kb->surf);
}
void
kbd_print_key_stdout(struct kbd *kb, struct key *k) {
/* printed keys may slightly differ from the actual output
* we generally print what is on the key LABEL and only support the normal
* and shift layers. Other modifiers produce no output (Ctrl,Alt)
* */
bool handled = true;
if (k->type == Code) {
switch (k->code) {
case KEY_SPACE:
printf(" ");
break;
case KEY_ENTER:
printf("\n");
break;
case KEY_BACKSPACE:
printf("\b");
break;
case KEY_TAB:
printf("\t");
break;
default:
handled = false;
break;
}
} else if (k->type != Copy) {
return;
}
if (!handled) {
if ((kb->mods & Shift) || (kb->mods & CapsLock))
printf("%s", k->shift_label);
else if (!(kb->mods & Ctrl) && !(kb->mods & Alt) && !(kb->mods & Super))
printf("%s", k->label);
}
fflush(stdout);
}
void
kbd_draw_key(struct kbd *kb, struct key *k, enum key_draw_type type) {
struct drwsurf *d = kb->surf;
const char *label = (kb->mods & Shift) ? k->shift_label : k->label;
if (kb->debug)
fprintf(stderr, "Draw key +%d+%d %dx%d -> %s\n", k->x, k->y, k->w, k->h,
label);
struct clr_scheme *scheme = (k->scheme == 0) ? &(kb->scheme) : &(kb->scheme1);
switch (type) {
case Unpress:
draw_inset(d, k->x, k->y, k->w, k->h, KBD_KEY_BORDER, scheme->fg);
break;
case Press:
draw_inset(d, k->x, k->y, k->w, k->h, KBD_KEY_BORDER, scheme->high);
break;
case Swipe:
draw_over_inset(d, k->x, k->y, k->w, k->h, KBD_KEY_BORDER, scheme->swipe);
break;
}
drw_draw_text(d, scheme->text, k->x, k->y, k->w, k->h, KBD_KEY_BORDER, label);
// cleanup cairo mess right side if words too long
uint32_t right_part_x = k->x + k->w - 2 * KBD_KEY_BORDER;
drw_do_rectangle(d, kb->scheme.bg, right_part_x, k->y,
kb->w - right_part_x, kb->h, false);
wl_surface_damage(d->surf, k->x, k->y, k->w, k->h);
}
void
kbd_draw_layout(struct kbd *kb) {
struct drwsurf *d = kb->surf;
struct key *next_key = kb->layout->keys;
if (kb->debug)
fprintf(stderr, "Draw layout\n");
drw_fill_rectangle(d, kb->scheme.bg, 0, 0, kb->w, kb->h);
while (next_key->type != Last) {
if ((next_key->type == Pad) || (next_key->type == EndRow)) {
next_key++;
continue;
}
if ((next_key->type == Mod && kb->mods & next_key->code) ||
(next_key->type == Compose && kb->compose)) {
kbd_draw_key(kb, next_key, Press);
} else {
kbd_draw_key(kb, next_key, Unpress);
}
next_key++;
}
wl_surface_damage(d->surf, 0, 0, kb->w, kb->h);
}
void
kbd_resize(struct kbd *kb, struct layout *layouts, uint8_t layoutcount) {
struct drwsurf *d = kb->surf;
fprintf(stderr, "Resize %dx%d %d, %d layouts\n", kb->w, kb->h, kb->scale,
layoutcount);
drwsurf_resize(d, kb->w, kb->h, kb->scale);
for (int i = 0; i < layoutcount; i++) {
if (kb->debug) {
if (layouts[i].name)
fprintf(stderr, "Initialising layout %s, keymap %s\n", layouts[i].name, layouts[i].keymap_name);
else
fprintf(stderr, "Initialising unnamed layout %d, keymap %s\n", i, layouts[i].keymap_name);
}
kbd_init_layout(&layouts[i], kb->w, kb->h);
}
kbd_draw_layout(kb);
}
void
draw_inset(struct drwsurf *ds, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, uint32_t border, Color color) {
drw_fill_rectangle(ds, color, x + border, y + border, width - (border * 2),
height - (border * 2));
}
void
draw_over_inset(struct drwsurf *ds, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, uint32_t border, Color color) {
drw_over_rectangle(ds, color, x + border, y + border, width - (border * 2),
height - (border * 2));
}
void
create_and_upload_keymap(struct kbd *kb, const char *name, uint32_t comp_unichr,
uint32_t comp_shift_unichr) {
int keymap_index = -1;
for (int i = 0; i < NUMKEYMAPS; i++) {
if (!strcmp(keymap_names[i], name)) {
keymap_index = i;
}
}
if (keymap_index == -1) {
fprintf(stderr, "No such keymap defined: %s\n", name);
exit(9);
}
const char *keymap_template = keymaps[keymap_index];
size_t keymap_size = strlen(keymap_template) + 64;
char *keymap_str = malloc(keymap_size);
sprintf(keymap_str, keymap_template, comp_unichr, comp_shift_unichr);
keymap_size = strlen(keymap_str);
int keymap_fd = os_create_anonymous_file(keymap_size);
if (keymap_fd < 0) {
die("could not create keymap fd\n");
}
void *ptr =
mmap(NULL, keymap_size, PROT_READ | PROT_WRITE, MAP_SHARED, keymap_fd, 0);
if (ptr == (void *)-1) {
die("could not map keymap data\n");
}
if (kb->vkbd == NULL) {
die("kb.vkbd = NULL\n");
}
strcpy(ptr, keymap_str);
zwp_virtual_keyboard_v1_keymap(kb->vkbd, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
keymap_fd, keymap_size);
free((void *)keymap_str);
}