unfettered-keyboard/graphics.c
2024-04-15 22:11:20 -04:00

452 lines
11 KiB
C

// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
#include <fcft/fcft.h>
#include <linux/input-event-codes.h>
#include <pixman.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <utf8proc.h>
#include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon.h>
#include "graphics.h"
#include "layout.h"
#include "modifier.h"
#include "ufkbd.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
enum label_align {
LABEL_ALIGNL = 0x1,
LABEL_ALIGNR = 0x2,
LABEL_ALIGNU = 0x4,
LABEL_ALIGND = 0x8,
};
struct draw_data {
struct ufkbd_ctx *ufkbd;
pixman_image_t *img;
};
static const char *fonts[] = {
"Sans"
};
struct key_label {
xkb_keysym_t sym;
const char *label;
};
// List of custom key labels, sorted by XKB keysym.
static const struct key_label keylabels[] = {
{ .sym = XKB_KEY_BackSpace, .label = "\u232B", },
{ .sym = XKB_KEY_Tab, .label = "\u2B7E", },
{ .sym = XKB_KEY_Return, .label = "\u23CE", },
{ .sym = XKB_KEY_Escape, .label = "Esc", },
{ .sym = XKB_KEY_Left, .label = "\u2190", },
{ .sym = XKB_KEY_Up, .label = "\u2191", },
{ .sym = XKB_KEY_Right, .label = "\u2192", },
{ .sym = XKB_KEY_Down, .label = "\u2193", },
{ .sym = XKB_KEY_Shift_L, .label = "\u21E7", },
{ .sym = XKB_KEY_Control_L, .label = "Ctrl", },
{ .sym = XKB_KEY_Alt_L, .label = "Alt", },
{ .sym = XKB_KEY_Delete, .label = "\u2326", },
{ .sym = XKB_KEY_XF86Fn, .label = "Fn", },
};
static int compare_key_label(const void *va, const void *vb)
{
const struct key_label *a = va;
const struct key_label *b = vb;
return a->sym - b->sym;
}
static void draw_part(struct ufkbd_graphics_ctx *graphics,
struct fcft_font *font,
xkb_keysym_t keysym,
bool pressed,
int x1, int y1, int x2, int y2,
enum label_align align,
pixman_image_t *img)
{
pixman_image_t *color;
struct key_label pattern;
struct fcft_text_run *text;
const struct key_label *label;
ssize_t len;
size_t i;
int pos = 0;
int min = 0, max = 0;
int level;
if (pressed)
color = graphics->key_pressed;
else
color = graphics->key_default;
pattern.sym = keysym;
label = bsearch(&pattern, keylabels,
ARRAY_SIZE(keylabels),
sizeof(*keylabels),
compare_key_label);
if (label != NULL) {
len = utf8proc_decompose((const utf8proc_uint8_t *) label->label, 0,
graphics->buf,
graphics->size / sizeof(utf8proc_int32_t),
UTF8PROC_NULLTERM);
if (len <= 0)
return;
} else {
((utf8proc_int32_t *) graphics->buf)[0] = keysym;
len = 1;
}
text = fcft_rasterize_text_run_utf32(font, len, graphics->buf,
FCFT_SUBPIXEL_DEFAULT);
if (text == NULL)
return;
for (i = 0; i < text->count; i++) {
if (max < pos + text->glyphs[i]->width)
max = pos + text->glyphs[i]->width;
pos += text->glyphs[i]->advance.x;
if (min > pos)
min = pos;
}
if (align & LABEL_ALIGNL)
pos = x1;
else if (align & LABEL_ALIGNR)
pos = x2 - max;
else
pos = (x1 + x2 + min - max) / 2;
if (align & LABEL_ALIGNU)
level = y1 + font->ascent;
else if (align & LABEL_ALIGND)
level = y2 - font->descent;
else
level = (y1 + y2 + font->ascent) / 2;
for (i = 0; i < text->count; i++) {
pixman_image_composite32(PIXMAN_OP_OVER, color, text->glyphs[i]->pix, img,
0, 0, 0, 0,
pos, level - text->glyphs[i]->y,
text->glyphs[i]->width, text->glyphs[i]->height);
pos += text->glyphs[i]->advance.x;
}
fcft_text_run_destroy(text);
}
static inline bool is_key_pressed(const struct ufkbd_ctx *ctx,
const struct ufkbd_key *key,
int part)
{
if (key->presses[part])
return true;
return false;
}
static int draw_single(struct ufkbd_ctx *ctx, struct ufkbd_key *key,
int x1, int y1, int x2, int y2,
pixman_image_t *img)
{
bool keycap = true;
int width = x2 - x1 + 1, height = y2 - y1 + 1;
size_t i;
for (i = 0; i < UFKBD_PART_MAX; i++) {
if (is_key_pressed(ctx, key, i)) {
keycap = false;
break;
}
}
pixman_image_composite32(PIXMAN_OP_SRC, ctx->graphics->fill_bg, NULL, img,
0, 0, 0, 0, x1, y1, width, height);
if (keycap)
pixman_image_composite32(PIXMAN_OP_SRC, ctx->graphics->fill_keycap, NULL, img,
0, 0, 0, 0, x1 + 2, y1 + 2, width - 4, height - 4);
if (key->keyids[UFKBD_PART_CENTER] != -1) {
draw_part(ctx->graphics, ctx->graphics->font,
key->keysyms[UFKBD_PART_CENTER],
is_key_pressed(ctx, key, UFKBD_PART_CENTER),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, 0, img);
}
if (key->keyids[UFKBD_PART_TL] != -1) {
draw_part(ctx->graphics, ctx->graphics->font_sec,
key->keysyms[UFKBD_PART_TL],
is_key_pressed(ctx, key, UFKBD_PART_TL),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, LABEL_ALIGNL | LABEL_ALIGNU, img);
}
if (key->keyids[UFKBD_PART_TR] != -1) {
draw_part(ctx->graphics, ctx->graphics->font_sec,
key->keysyms[UFKBD_PART_TR],
is_key_pressed(ctx, key, UFKBD_PART_TR),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, LABEL_ALIGNR | LABEL_ALIGNU, img);
}
if (key->keyids[UFKBD_PART_BL] != -1) {
draw_part(ctx->graphics, ctx->graphics->font_sec,
key->keysyms[UFKBD_PART_BL],
is_key_pressed(ctx, key, UFKBD_PART_BL),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, LABEL_ALIGNL | LABEL_ALIGND, img);
}
if (key->keyids[UFKBD_PART_BR] != -1) {
draw_part(ctx->graphics, ctx->graphics->font_sec,
key->keysyms[UFKBD_PART_BR],
is_key_pressed(ctx, key, UFKBD_PART_BR),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, LABEL_ALIGNR | LABEL_ALIGND, img);
}
if (key->keyids[UFKBD_PART_LEFT] != -1) {
draw_part(ctx->graphics, ctx->graphics->font_sec,
key->keysyms[UFKBD_PART_LEFT],
is_key_pressed(ctx, key, UFKBD_PART_LEFT),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, LABEL_ALIGNL, img);
}
if (key->keyids[UFKBD_PART_RIGHT] != -1) {
draw_part(ctx->graphics, ctx->graphics->font_sec,
key->keysyms[UFKBD_PART_RIGHT],
is_key_pressed(ctx, key, UFKBD_PART_RIGHT),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, LABEL_ALIGNR, img);
}
if (key->keyids[UFKBD_PART_TOP] != -1) {
draw_part(ctx->graphics, ctx->graphics->font_sec,
key->keysyms[UFKBD_PART_TOP],
is_key_pressed(ctx, key, UFKBD_PART_TOP),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, LABEL_ALIGNU, img);
}
if (key->keyids[UFKBD_PART_BOTTOM] != -1) {
draw_part(ctx->graphics, ctx->graphics->font_sec,
key->keysyms[UFKBD_PART_BOTTOM],
is_key_pressed(ctx, key, UFKBD_PART_BOTTOM),
x1 + 2, y1 + 2, x2 - 2, y2 - 2, LABEL_ALIGND, img);
}
return 0;
}
void ufkbd_graphics_draw_key(struct ufkbd_ctx *ctx, struct ufkbd_key *key,
int x1, int y1, int x2, int y2)
{
pixman_image_t *img;
void *ptr;
unsigned int scale = ctx->graphics->scale;
size_t stride;
int ret;
ret = ctx->drv->draw_begin(ctx->drv_data, &stride, &ptr);
if (ret)
return;
// We don't need the full image, just the part that we're touching.
img = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8,
x2 * scale + 1, y2 * scale + 1,
ptr, stride);
if (img == NULL)
return;
ret = draw_single(ctx, key, x1 * scale, y1 * scale, x2 * scale, y2 * scale, img);
if (!ret)
ctx->drv->draw_touch(ctx->drv_data, x1 * scale, y1 * scale, (x2 * scale) - (x1 * scale) + 1, (y2 * scale) - (y1 * scale) + 1);
pixman_image_unref(img);
ctx->drv->draw_end(ctx->drv_data);
}
static void draw_from_layout(struct ufkbd_key *key,
int x1, int y1, int x2, int y2,
void *data)
{
struct draw_data *ctx = data;
unsigned int scale = ctx->ufkbd->graphics->scale;
draw_single(ctx->ufkbd, key, x1 * scale, y1 * scale,
x2 * scale, y2 * scale, ctx->img);
}
void ufkbd_graphics_draw_layout(struct ufkbd_ctx *ctx, struct ufkbd_layout *layout,
size_t width, size_t height)
{
struct draw_data data;
pixman_image_t *img;
void *ptr;
size_t stride;
int ret;
ret = ctx->drv->draw_begin(ctx->drv_data, &stride, &ptr);
if (ret)
return;
img = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8,
width * ctx->graphics->scale,
height * ctx->graphics->scale,
ptr, stride);
if (img == NULL)
return;
pixman_image_composite32(PIXMAN_OP_SRC, ctx->graphics->fill_bg, NULL, img,
0, 0, 0, 0, 0, 0, width * ctx->graphics->scale, height * ctx->graphics->scale);
ctx->drv->draw_touch(ctx->drv_data, 0, 0,
width * ctx->graphics->scale - 1,
height * ctx->graphics->scale - 1);
data.ufkbd = ctx;
data.img = img;
ufkbd_layout_foreach(ctx->layout, draw_from_layout, &data);
pixman_image_unref(img);
ctx->drv->draw_end(ctx->drv_data);
}
int ufkbd_graphics_set_scale(struct ufkbd_graphics_ctx *graphics, unsigned int scale)
{
struct fcft_font *font, *font_sec;
graphics->scale = scale;
snprintf(graphics->buf, graphics->size, "size=%u", 10 * scale);
font = fcft_from_name(ARRAY_SIZE(fonts), fonts, graphics->buf);
if (font == NULL) {
fprintf(stderr, "Failed to resize key label font when setting scale\n");
return -1;
}
snprintf(graphics->buf, graphics->size, "size=%u", 8 * scale);
font_sec = fcft_from_name(ARRAY_SIZE(fonts), fonts, graphics->buf);
if (font_sec == NULL) {
fprintf(stderr, "Failed to resize secondary key label font when setting scale\n");
goto err_free_font;
}
fcft_destroy(graphics->font);
fcft_destroy(graphics->font_sec);
graphics->font = font;
graphics->font_sec = font_sec;
return 0;
err_free_font:
fcft_destroy(font);
return -1;
}
struct ufkbd_graphics_ctx *ufkbd_graphics_init(void)
{
struct ufkbd_graphics_ctx *graphics;
pixman_color_t color;
graphics = malloc(sizeof(*graphics));
if (graphics == NULL)
return NULL;
graphics->scale = 1;
fcft_init(FCFT_LOG_COLORIZE_AUTO, false, FCFT_LOG_CLASS_DEBUG);
graphics->font = fcft_from_name(ARRAY_SIZE(fonts), fonts, "size=10");
if (graphics->font == NULL)
goto err_free_ctx;
graphics->font_sec = fcft_from_name(ARRAY_SIZE(fonts), fonts, "size=8");
if (graphics->font_sec == NULL)
goto err_free_font;
graphics->size = 256;
graphics->buf = malloc(graphics->size);
if (graphics->buf == NULL)
goto err_free_font_sec;
color.red = 0;
color.green = 0;
color.blue = 0;
color.alpha = 65535;
graphics->fill_bg = pixman_image_create_solid_fill(&color);
if (graphics->fill_bg == NULL)
goto err_free_buf;
color.red = 16383;
color.green = 16383;
color.blue = 16383;
color.alpha = 65535;
graphics->fill_keycap = pixman_image_create_solid_fill(&color);
if (graphics->fill_keycap == NULL)
goto err_unref_bg;
color.red = 65535;
color.green = 65535;
color.blue = 65535;
color.alpha = 65535;
graphics->key_default = pixman_image_create_solid_fill(&color);
if (graphics->key_default == NULL)
goto err_unref_keycap;
color.red = 0;
color.green = 40959;
color.blue = 65535;
color.alpha = 65535;
graphics->key_pressed = pixman_image_create_solid_fill(&color);
if (graphics->key_pressed == NULL)
goto err_unref_key_def;
return graphics;
err_unref_key_def:
pixman_image_unref(graphics->key_default);
err_unref_keycap:
pixman_image_unref(graphics->fill_keycap);
err_unref_bg:
pixman_image_unref(graphics->fill_bg);
err_free_buf:
free(graphics->buf);
err_free_font_sec:
fcft_destroy(graphics->font_sec);
err_free_font:
fcft_destroy(graphics->font);
err_free_ctx:
free(graphics);
return NULL;
}
void ufkbd_graphics_uninit(struct ufkbd_graphics_ctx *graphics)
{
pixman_image_unref(graphics->key_pressed);
pixman_image_unref(graphics->key_default);
pixman_image_unref(graphics->fill_keycap);
pixman_image_unref(graphics->fill_bg);
fcft_destroy(graphics->font_sec);
fcft_destroy(graphics->font);
fcft_fini();
free(graphics->buf);
free(graphics);
}