// SPDX-License-Identifier: GPL-3.0-only /* * Copyright (c) 2024, Richard Acayan. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #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); }