452 lines
11 KiB
C
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);
|
|
}
|