From: Joseph Coffland Date: Mon, 12 Apr 2021 08:55:44 +0000 (-0700) Subject: Touchscreen keyboard X-Git-Url: https://git.buildbotics.com/?a=commitdiff_plain;h=d6991029a35141eaf7207e67c6cedcdad750d5a4;p=bbctrl-firmware Touchscreen keyboard --- diff --git a/src/kbd/Makefile b/src/kbd/Makefile new file mode 100644 index 0000000..4a624fa --- /dev/null +++ b/src/kbd/Makefile @@ -0,0 +1,32 @@ +NAME = bbkbd + +PKG_CONFIG = pkg-config +PKGS = fontconfig freetype2 x11 xtst xft xinerama + +CDEFS = -D_DEFAULT_SOURCE -DXINERAMA +CFLAGS += -I. `$(PKG_CONFIG) --cflags $(PKGS)` $(CDEFS) +CFLAGS += -MD -MP -MT $@ -MF build/dep/$(@F).d +CFLAGS += -Wall -Werror -g +LDFLAGS += `$(PKG_CONFIG) --libs $(PKGS)` + +SRC = $(wildcard *.c) +OBJ := $(patsubst %.c,build/%.o,$(SRC)) + +all: $(NAME) + +build/%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +$(NAME): $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) + +tidy: + rm -f *~ \#* + +clean: tidy + rm -rf $(NAME) *.o build + +.PHONY: all clean tidy + +# Dependencies +-include $(shell mkdir -p build/dep) $(wildcard build/dep/*) diff --git a/src/kbd/bbkbd.c b/src/kbd/bbkbd.c new file mode 100644 index 0000000..ee9ce34 --- /dev/null +++ b/src/kbd/bbkbd.c @@ -0,0 +1,138 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#include "keyboard.h" +#include "button.h" +#include "drw.h" +#include "util.h" +#include "config.h" + +#include +#include + +#define DEFAULT_FONT "DejaVu Sans:bold:size=22" + +static const char *font = DEFAULT_FONT; +static int button_x = -60; +static int button_y = 0; +static bool running = true; + + +void signaled(int sig) { + running = false; + print_dbg("Signal %d received\n", sig); +} + + +void usage(char *argv0, int ret) { + const char *usage = + "usage: %s [-hdb] [-f ] [-b ]\n" + "Options:\n" + " -h - Print this help screen and exit\n" + " -d - Enable debug\n" + " -f - Font string, default: " DEFAULT_FONT "\n" + " -b - Button screen position.\n"; + + fprintf(ret ? stderr : stdout, usage, argv0); + exit(ret); +} + + +void parse_args(int argc, char *argv[]) { + for (int i = 1; argv[i]; i++) { + if (!strcmp(argv[i], "-h")) usage(argv[0], 0); + else if (!strcmp(argv[i], "-d")) debug = true; + else if (!strcmp(argv[i], "-f")) { + if (argc - 1 <= i) usage(argv[0], 1); + font = argv[++i]; + + } else if (!strcmp(argv[i], "-b")) { + if (argc - 2 <= i) usage(argv[0], 1); + button_x = atoi(argv[++i]); + button_y = atoi(argv[++i]); + + } else { + fprintf(stderr, "Invalid argument: %s\n", argv[i]); + usage(argv[0], 1); + } + } +} + + +int main(int argc, char *argv[]) { + signal(SIGTERM, signaled); + signal(SIGINT, signaled); + + parse_args(argc, argv); + + // Check locale support + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fprintf(stderr, "warning: no locale support"); + + // Init + Display *dpy = XOpenDisplay(0); + if (!dpy) die("cannot open display"); + + int size = 60; + Button *btn = button_create(dpy, 0, button_x, button_y, size, size * 0.5, + font); + Keyboard *kbd = keyboard_create(dpy, layers, font, colors); + btn->kbd = kbd; + + // Event loop + while (running) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100000; // 100ms + + int xfd = ConnectionNumber(dpy); + fd_set fds; + FD_ZERO(&fds); + FD_SET(xfd, &fds); + int r = select(xfd + 1, &fds, 0, 0, &tv); + + if (r == -1) break; + + while (r && XPending(dpy)) { + XEvent ev; + XNextEvent(dpy, &ev); + + if (ev.xany.window == kbd->win) + keyboard_event(kbd, &ev); + + if (ev.xany.window == btn->win) + button_event(btn, &ev); + } + } + + // Cleanup + button_destroy(btn); + keyboard_destroy(kbd); + XCloseDisplay(dpy); + + return 0; +} diff --git a/src/kbd/button.c b/src/kbd/button.c new file mode 100644 index 0000000..14940d7 --- /dev/null +++ b/src/kbd/button.c @@ -0,0 +1,144 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#include "button.h" +#include "util.h" + +#include + + +void button_draw(Button *btn) { + drw_rect(btn->drw, 0, 0, 100, 100, 1, 1); + + const char *label = "⌨"; + int h = btn->drw->fonts[0].xfont->height * 2; + int y = (btn->h - h) / 2; + int w = drw_fontset_getwidth(btn->drw, label); + int x = (btn->w - w) / 2; + drw_text(btn->drw, x, y, w, h, 0, label, 0); + + drw_map(btn->drw, btn->win, 0, 0, 100, 100); +} + + +void button_event(Button *btn, XEvent *e) { + switch (e->type) { + case MotionNotify: { + int x = e->xmotion.x; + int y = e->xmotion.y; + btn->mouse_in = 0 <= x && x < btn->w && 0 <= y && y < btn->h; + break; + } + + case ButtonPress: break; + + case ButtonRelease: + if (e->xbutton.button == 1 && btn->mouse_in && btn->kbd) + keyboard_toggle(btn->kbd); + break; + + case Expose: if (!e->xexpose.count) button_draw(btn); break; + } +} + + +Button *button_create(Display *dpy, Keyboard *kbd, int x, int y, int w, int h, + const char *font) { + Button *btn = (Button *)calloc(1, sizeof(Button)); + btn->kbd = kbd; + + int screen = DefaultScreen(dpy); + Window root = RootWindow(dpy, screen); + + // Dimensions + Dim dim = get_display_dims(dpy, screen); + x = x < 0 ? dim.width - w : 0; + y = y < 0 ? dim.height - h : 0; + btn->w = w; + btn->h = h; + + // Create drawable + Drw *drw = btn->drw = drw_create(dpy, screen, root, w, h); + + // Setup font + if (!drw_fontset_create(drw, &font, 1)) die("no fonts could be loaded"); + + // Init color scheme + const char *colors[] = {"#bbbbbb", "#132a33"}; + btn->scheme = drw_scm_create(drw, colors, 2); + drw_setscheme(drw, btn->scheme); + + XSetWindowAttributes wa; + wa.override_redirect = true; + + btn->win = XCreateWindow + (dpy, root, x, y, w, h, 0, CopyFromParent, CopyFromParent, + CopyFromParent, CWOverrideRedirect | CWBorderPixel | CWBackingPixel, &wa); + + // Enable window events + XSelectInput(dpy, btn->win, ButtonReleaseMask | ButtonPressMask | + ExposureMask | PointerMotionMask); + + // Set window properties + XWMHints *wmHints = XAllocWMHints(); + wmHints->input = false; + wmHints->flags = InputHint; + + const char *name = "bbkbd-button"; + XTextProperty str; + XStringListToTextProperty((char **)&name, 1, &str); + + XClassHint *classHints = XAllocClassHint(); + classHints->res_class = (char *)name; + classHints->res_name = (char *)name; + + XSetWMProperties(dpy, btn->win, &str, &str, 0, 0, 0, wmHints, classHints); + + XFree(classHints); + XFree(wmHints); + XFree(str.value); + + // Set window type + Atom atom = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", false); + Atom type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_UTILITY", false); + XChangeProperty(dpy, btn->win, atom, XA_ATOM, 32, PropModeReplace, + (unsigned char *)&type, 1); + + // Raise window to top of stack + XMapRaised(dpy, btn->win); + + return btn; +} + + +void button_destroy(Button *btn) { + drw_sync(btn->drw); + drw_free(btn->drw); + + free(btn->scheme); + free(btn); +} diff --git a/src/kbd/button.h b/src/kbd/button.h new file mode 100644 index 0000000..1a8b669 --- /dev/null +++ b/src/kbd/button.h @@ -0,0 +1,50 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#pragma once + +#include "keyboard.h" + +#include + + +typedef struct { + Keyboard *kbd; + Window win; + Drw *drw; + Clr *scheme; + + int w; + int h; + bool mouse_in; +} Button; + + +Button *button_create(Display *dpy, Keyboard *kbd, int x, int y, int w, int h, + const char *font); +void button_destroy(Button *btn); +void button_event(Button *btn, XEvent *e); diff --git a/src/kbd/config.h b/src/kbd/config.h new file mode 100644 index 0000000..164235a --- /dev/null +++ b/src/kbd/config.h @@ -0,0 +1,231 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#pragma once + +#include "keyboard.h" + + +static const char *colors[SchemeLast][2] = { + // fg bg + [SchemeNorm] = {"#bbbbbb", "#132a33"}, + [SchemeNormABC] = {"#ffffff", "#14313d"}, + [SchemePress] = {"#ffffff", "#259937"}, + [SchemeHighlight] = {"#58a7c6", "#005577"}, +}; + + +static Key _main[] = { + {"q", "Q", XK_q, 1}, + {"w", "W", XK_w, 1}, + {"e", "E", XK_e, 1}, + {"r", "R", XK_r, 1}, + {"t", "T", XK_t, 1}, + {"y", "Y", XK_y, 1}, + {"u", "U", XK_u, 1}, + {"i", "I", XK_i, 1}, + {"o", "O", XK_o, 1}, + {"p", "P", XK_p, 1}, + {"7", "&", XK_7, 1}, + {"8", "*", XK_8, 1}, + {"9", "(", XK_9, 1}, + {"-", "_", XK_minus, 1}, + + {0}, // New row + + {"a", "A", XK_a, 1}, + {"s", "S", XK_s, 1}, + {"d", "D", XK_d, 1}, + {"f", "F", XK_f, 1}, + {"g", "G", XK_g, 1}, + {"h", "H", XK_h, 1}, + {"j", "J", XK_j, 1}, + {"k", "K", XK_k, 1}, + {"l", "L", XK_l, 1}, + {";", ":", XK_colon, 1}, + {"4", "$", XK_4, 1}, + {"5", "%", XK_5, 1}, + {"6", "^", XK_6, 1}, + {"=", "+", XK_equal, 1}, + + {0}, // New row + + {"z", "Z", XK_z, 1}, + {"x", "X", XK_x, 1}, + {"c", "C", XK_c, 1}, + {"v", "V", XK_v, 1}, + {"b", "B", XK_b, 1}, + {"n", "N", XK_n, 1}, + {"m", "M", XK_m, 1}, + {"Tab", 0, XK_Tab, 1}, + {"⇍ Bksp", 0, XK_BackSpace, 2}, + {"1", "!", XK_1, 1}, + {"2", "@", XK_2, 1}, + {"3", "#", XK_3, 1}, + {"/", "?", XK_slash, 1}, + + {0}, // New row + {"⌨", 0, XK_Cancel, 1}, + {"Shift", 0, XK_Shift_L, 1}, + {"↓", 0, XK_Down, 1}, + {"↑", 0, XK_Up, 1}, + {"Space", 0, XK_space, 2}, + {"Esc", 0, XK_Escape, 1}, + {"Ctrl", 0, XK_Control_L, 1}, + {"↲ Enter", 0, XK_Return, 2}, + {"0", ")", XK_0, 1}, + {",", "<", XK_comma, 1}, + {".", ">", XK_period, 1}, + {"\\", "|", XK_slash, 1}, + + {0}, {0} // End +}; + + +static Key _alt[] = { + {0, 0, XK_Q, 1}, + {0, 0, XK_W, 1}, + {0, 0, XK_E, 1}, + {0, 0, XK_R, 1}, + {0, 0, XK_T, 1}, + {0, 0, XK_Y, 1}, + {0, 0, XK_U, 1}, + {0, 0, XK_I, 1}, + {0, 0, XK_O, 1}, + {0, 0, XK_P, 1}, + {"7", 0, XK_7, 1}, + {"8", 0, XK_8, 1}, + {"9", 0, XK_9, 1}, + {"-", 0, XK_minus, 1}, + + {0}, // New row + + {0, 0, XK_A, 1}, + {0, 0, XK_S, 1}, + {0, 0, XK_D, 1}, + {0, 0, XK_F, 1}, + {0, 0, XK_G, 1}, + {0, 0, XK_H, 1}, + {0, 0, XK_J, 1}, + {0, 0, XK_K, 1}, + {0, 0, XK_L, 1}, + {";",":", XK_colon, 1}, + {"4", 0, XK_4, 1}, + {"5", 0, XK_5, 1}, + {"6", 0, XK_6, 1}, + {"+", 0, XK_plus, 1}, + + {0}, // New row + + {0, 0, XK_Z, 1}, + {0, 0, XK_X, 1}, + {0, 0, XK_C, 1}, + {0, 0, XK_V, 1}, + {0, 0, XK_B, 1}, + {0, 0, XK_N, 1}, + {0, 0, XK_M, 1}, + {"Tab", 0, XK_Tab, 1}, + {"⇍ Bksp", 0, XK_BackSpace, 2}, + {"1", 0, XK_1, 1}, + {"2", 0, XK_2, 1}, + {"3", 0, XK_3, 1}, + {"/", 0, XK_slash, 1}, + + {0}, // New row + {"⌨", 0, XK_Cancel, 1}, + {"Shift", 0, XK_Shift_L, 1}, + {"↓", 0, XK_Down, 1}, + {"↑", 0, XK_Up, 1}, + {"Space", 0, XK_space, 2}, + {"Esc", 0, XK_Escape, 1}, + {"Ctrl", 0, XK_Control_L, 1}, + {"↲ Enter", 0, XK_Return, 2}, + {"0", 0, XK_0, 1}, + {".", 0, XK_period, 1}, + {"=", 0, XK_equal, 1}, + {"*", 0, XK_asterisk, 1}, + + {0}, {0} // End +}; + + +Key _symbols[] = { + {"1", "!", XK_1, 1}, + {"2", "@", XK_2, 1}, + {"3", "#", XK_3, 1}, + {"4", "$", XK_4, 1}, + {"5", "%", XK_5, 1}, + {"6", "^", XK_6, 1}, + {"7", "&", XK_7, 1}, + {"8", "*", XK_8, 1}, + {"9", "(", XK_9, 1}, + {"0", ")", XK_0, 1}, + + {0}, // New row + + {"'", "\"", XK_apostrophe, 1}, + {"`", "~", XK_grave, 1}, + {"-", "_", XK_minus, 1}, + {"=", "+", XK_plus, 1}, + {"[", "{", XK_bracketleft, 1}, + {"]", "}", XK_bracketright, 1}, + {",", "<", XK_comma, 1}, + {".", ">", XK_period, 1}, + {"/", "?", XK_slash, 1}, + {"\\", "|", XK_backslash, 1}, + + {0}, // New row + + {"", 0, XK_Shift_L|XK_bar, 1}, + {"⇤", 0, XK_Home, 1}, + {"←", 0, XK_Left, 1}, + {"→", 0, XK_Right, 1}, + {"⇥", 0, XK_End, 1}, + {"⇊", 0, XK_Next, 1}, + {"⇈", 0, XK_Prior, 1}, + {"Tab", 0, XK_Tab, 1}, + {"⇍ Bksp", 0, XK_BackSpace, 2}, + + {0}, // New row + {"⌨", 0, XK_Cancel, 1}, + {"Shift", 0, XK_Shift_L, 1}, + {"↓", 0, XK_Down, 1}, + {"↑", 0, XK_Up, 1}, + {"", 0, XK_space, 2}, + {"Esc", 0, XK_Escape, 1}, + {"Ctrl", 0, XK_Control_L, 1}, + {"↲ Enter", 0, XK_Return, 2}, + + {0}, {0} // End +}; + + +static Key *layers[] = { + _main, + _alt, + 0 +}; diff --git a/src/kbd/drw.c b/src/kbd/drw.c new file mode 100644 index 0000000..896326f --- /dev/null +++ b/src/kbd/drw.c @@ -0,0 +1,407 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#include "drw.h" +#include "util.h" + +#include +#include + + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const uint8_t utfbyte[] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uint8_t utfmask[] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + + +static long utf8decodebyte(const char c, size_t *i) { + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((uint8_t)c & utfmask[*i]) == utfbyte[*i]) + return (uint8_t)c & ~utfmask[*i]; + + return 0; +} + + +static size_t utf8validate(long *u, size_t i) { + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + + for (i = 1; *u > utfmax[i]; ++i) continue; + + return i; +} + + +static size_t utf8decode(const char *c, long *u, size_t clen) { + size_t i, j, len, type; + + *u = UTF_INVALID; + if (!clen) return 0; + long udecoded = utf8decodebyte(c[0], &len); + + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) return j; + } + + if (j < len) return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + + +Drw *drw_create(Display *dpy, int screen, Window root, unsigned w, unsigned h) { + Drw *drw = calloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, 0); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + + +void drw_resize(Drw *drw, unsigned w, unsigned h) { + if (!drw) return; + + drw->w = w; + drw->h = h; + if (drw->drawable) XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + + +void drw_free(Drw *drw) { + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + + +/// This function is an implementation detail. Library users should use +/// drw_fontset_create instead. +static Fnt *xfont_create(Drw *drw, const char *fontname, + FcPattern *fontpattern) { + XftFont *xfont = 0; + FcPattern *pattern = 0; + + if (fontname) { + // Using the pattern found at font->xfont->pattern does not yield the + // same substitution results as using the pattern returned by + // FcNameParse; using the latter results in the desired fallback + // behaviour whereas the former just results in missing-character + // rectangles being drawn, at least with some fonts. + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return 0; + } + + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", + fontname); + XftFontClose(drw->dpy, xfont); + return 0; + } + + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return 0; + } + + } else die("no font specified."); + + // Do not allow using color fonts. This is a workaround for a BadLength + // error from Xft with color glyphs. Modelled on the Xterm workaround. See + // https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + // https://lists.suckless.org/dev/1701/30932.html + // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + // and lots more all over the internet. + FcBool iscol; + if (FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && + iscol) { + XftFontClose(drw->dpy, xfont); + return 0; + } + + Fnt *font = calloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + + +static void xfont_free(Fnt *font) { + if (!font) return; + if (font->pattern) FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + + +Fnt *drw_fontset_create(Drw *drw, const char *fonts[], size_t count) { + Fnt *cur, *ret = 0; + + if (!drw || !fonts) return 0; + + for (size_t i = 1; i <= count; i++) + if ((cur = xfont_create(drw, fonts[count - i], 0))) { + cur->next = ret; + ret = cur; + } + + return drw->fonts = ret; +} + + +void drw_fontset_free(Fnt *font) { + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + + +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname) { + if (!drw || !dest || !clrname) return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + + +/// Wrapper to create color schemes. The caller has to call free(3) on the +/// returned color scheme when done using it. +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) { + Clr *ret; + + // need at least two colors for a scheme + if (!drw || !clrnames || clrcount < 2 || + !(ret = calloc(clrcount, sizeof(XftColor)))) + die("error, cannot create color scheme (drw=%d) (clrcount=%d)", drw, + clrcount); + + for (size_t i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + + return ret; +} + + +void drw_setfontset(Drw *drw, Fnt *set) {if (drw) drw->fonts = set;} +void drw_setscheme(Drw *drw, Clr *scm) {if (drw) drw->scheme = scm;} + + +void drw_rect(Drw *drw, int x, int y, unsigned w, unsigned h, int filled, + int invert) { + if (!drw || !drw->scheme) return; + + XSetForeground(drw->dpy, drw->gc, + invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + + if (filled) XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + + +int drw_text(Drw *drw, int x, int y, unsigned w, unsigned h, unsigned lpad, + const char *text, int invert) { + char buf[1024]; + int ty; + XftDraw *d = 0; + Fnt *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) return 0; + + if (!render) w = ~w; + else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + Fnt *usedfont = drw->fonts; + + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = 0; + + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = + charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + + } else nextfont = curfont; + break; + } + } + + if (!charexists || nextfont) break; + else charexists = 0; + } + + if (utf8strlen) { + unsigned ew = 0; + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, 0); + + // shorten text if necessary + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, 0); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') continue; + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + + x += ew; + w -= ew; + } + } + + if (!*text) break; + else if (nextfont) { + charexists = 0; + usedfont = nextfont; + + } else { + // Regardless of whether or not a fallback font is found, the + // character must be drawn. + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) + // Refer to the comment in xfont_create for more information. + die("the first font in the cache must be loaded from a font string."); + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + + FcConfigSubstitute(0, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, 0, match); + + if (usedfont && + XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + continue; + curfont->next = usedfont; + + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + + if (d) XftDrawDestroy(d); + + return x + (render ? w : 0); +} + + +void drw_map(Drw *drw, Window win, int x, int y, unsigned w, unsigned h) { + if (!drw) return; + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); +} + + +void drw_sync(Drw *drw) {XSync(drw->dpy, False);} + + +unsigned drw_fontset_getwidth(Drw *drw, const char *text) { + if (!drw || !drw->fonts || !text) return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + + +void drw_font_getexts(Fnt *font, const char *text, unsigned len, unsigned *w, + unsigned *h) { + XGlyphInfo ext; + + if (!font || !text) return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) *w = ext.xOff; + if (h) *h = font->h; +} diff --git a/src/kbd/drw.h b/src/kbd/drw.h new file mode 100644 index 0000000..56ebb2f --- /dev/null +++ b/src/kbd/drw.h @@ -0,0 +1,90 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#pragma once + +#include +#include + + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + + +typedef struct Fnt { + Display *dpy; + unsigned h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum {ColFg, ColBg}; // Clr scheme index +typedef XftColor Clr; + +typedef struct { + unsigned w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + + +// Drawable abstraction +Drw *drw_create(Display *dpy, int screen, Window win, unsigned w, unsigned h); +void drw_resize(Drw *drw, unsigned w, unsigned h); +void drw_free(Drw *drw); + +// Fnt abstraction +Fnt *drw_fontset_create(Drw *drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt *set); +unsigned drw_fontset_getwidth(Drw *drw, const char *text); +void drw_font_getexts(Fnt *font, const char *text, unsigned len, unsigned *w, + unsigned *h); + +// Colorscheme abstraction +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +// Drawing context manipulation +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +// Drawing functions +void drw_rect(Drw *drw, int x, int y, unsigned w, unsigned h, int filled, + int invert); +int drw_text(Drw *drw, int x, int y, unsigned w, unsigned h, unsigned lpad, + const char *text, int invert); + +// Map functions +void drw_map(Drw *drw, Window win, int x, int y, unsigned w, unsigned h); +void drw_sync(Drw *drw); diff --git a/src/kbd/keyboard.c b/src/kbd/keyboard.c new file mode 100644 index 0000000..147aadc --- /dev/null +++ b/src/kbd/keyboard.c @@ -0,0 +1,396 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#include "keyboard.h" + +#include + +#include + + +static int create_window(Display *dpy, int root, const char *name, Dim dim, + int x, int y, bool override, unsigned long fg, + unsigned long bg) { + XSetWindowAttributes wa; + wa.override_redirect = override; + wa.border_pixel = fg; + wa.background_pixel = bg; + + int win = XCreateWindow + (dpy, root, x, y, dim.width, dim.height, 0, CopyFromParent, CopyFromParent, + CopyFromParent, CWOverrideRedirect | CWBorderPixel | CWBackingPixel, &wa); + + // Enable window events + XSelectInput(dpy, win, StructureNotifyMask | ButtonReleaseMask | + ButtonPressMask | ExposureMask | PointerMotionMask | + LeaveWindowMask); + + // Set window properties + XWMHints *wmHints = XAllocWMHints(); + wmHints->input = false; + wmHints->flags = InputHint; + + XTextProperty str; + XStringListToTextProperty((char **)&name, 1, &str); + + XClassHint *classHints = XAllocClassHint(); + classHints->res_class = (char *)name; + classHints->res_name = (char *)name; + + XSetWMProperties(dpy, win, &str, &str, 0, 0, 0, wmHints, classHints); + + XFree(classHints); + XFree(wmHints); + XFree(str.value); + + // Set window type + Atom atom = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", false); + Atom type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", false); + XChangeProperty(dpy, win, atom, XA_ATOM, 32, PropModeReplace, + (unsigned char *)&type, 1); + + return win; +} + + +static bool is_modifier(Key *k) {return k && IsModifierKey(k->keysym);} + + +static int key_scheme(Keyboard *kbd, Key *k) { + if (k->pressed) return SchemePress; + if (k == kbd->focus) return SchemeHighlight; + if (k->keysym == XK_Return || (XK_a <= k->keysym && k->keysym <= XK_z) || + (XK_Cyrillic_io <= k->keysym && k->keysym <= XK_Cyrillic_hardsign)) + return SchemeNormABC; + + return SchemeNorm; +} + + +Key *keyboard_find_key(Keyboard *kbd, int x, int y) { + Key *keys = kbd->keys; + + for (int i = 0; i < kbd->nkeys; i++) + if (keys[i].keysym && keys[i].x < x && x < keys[i].x + keys[i].w && + keys[i].y < y && y < keys[i].y + keys[i].h) + return &keys[i]; + + return 0; +} + + +void keyboard_draw_key(Keyboard *kbd, Key *k) { + Drw *drw = kbd->drw; + + drw_setscheme(drw, kbd->scheme[key_scheme(kbd, k)]); + drw_rect(drw, k->x, k->y, k->w, k->h, 1, 1); + + const char *label = k->label; + if (!label) label = XKeysymToString(k->keysym); + if (kbd->shifted && k->label2) label = k->label2; + + int h = drw->fonts[0].xfont->height * 2; + int y = k->y + (k->h - h) / 2; + int w = drw_fontset_getwidth(drw, label); + int x = k->x + (k->w - w) / 2; + drw_text(drw, x, y, w, h, 0, label, 0); + + drw_map(drw, kbd->win, k->x, k->y, k->w, k->h); +} + + +void keyboard_draw(Keyboard *kbd) { + for (int i = 0; i < kbd->nkeys; i++) + if (kbd->keys[i].keysym) + keyboard_draw_key(kbd, &kbd->keys[i]); +} + + +void keyboard_update(Keyboard *kbd) { + int y = 0; + int r = kbd->nrows; + int h = (kbd->dim.height - 1) / r; + Key *keys = kbd->keys; + + for (int i = 0; i < kbd->nkeys; i++, r--) { + int base = 0; + + for (int j = i; j < kbd->nkeys && keys[j].keysym; j++) + base += keys[j].width; + + for (int x = 0; i < kbd->nkeys && keys[i].keysym; i++) { + keys[i].x = x; + keys[i].y = y; + keys[i].w = keys[i].width * (kbd->dim.width - 1) / base; + keys[i].h = r == 1 ? kbd->dim.height - y - 1 : h; + x += keys[i].w; + } + + if (base) keys[i - 1].w = kbd->dim.width - 1 - keys[i - 1].x; + y += h; + } + + keyboard_draw(kbd); +} + + +void keyboard_init_layer(Keyboard *kbd) { + Key *layer = kbd->layers[kbd->layer]; + + // Count keys + kbd->nkeys = 0; + + for (int i = 0; ; i++) { + if (0 < i && !layer[i].keysym && !layer[i - 1].keysym) { + kbd->nkeys--; + break; + } + + kbd->nkeys++; + } + + kbd->keys = calloc(1, sizeof(Key) * kbd->nkeys); + memcpy(kbd->keys, layer, sizeof(Key) * kbd->nkeys); + + // Count rows + kbd->nrows = 1; + + for (int i = 0; i < kbd->nkeys; i++) + if (!kbd->keys[i].keysym) { + kbd->nrows++; + + if (i && !kbd->keys[i - 1].keysym) { + kbd->nrows--; + break; + } + } +} + + +void keyboard_next_layer(Keyboard *kbd) { + if (!kbd->layers[++kbd->layer]) kbd->layer = 0; + + print_dbg("Cycling to layer %d\n", kbd->layer); + + keyboard_init_layer(kbd); + keyboard_update(kbd); +} + + +void keyboard_press_key(Keyboard *kbd, Key *k) { + if (k->pressed) return; + + if (k->keysym == XK_Shift_L || k->keysym == XK_Shift_R) { + kbd->shifted = true; + keyboard_draw(kbd); + } + + simulate_key(kbd->drw->dpy, k->modifier, true); + simulate_key(kbd->drw->dpy, k->keysym, true); + k->pressed = true; + keyboard_draw_key(kbd, k); +} + + +void keyboard_unpress_key(Keyboard *kbd, Key *k) { + if (!k->pressed) return; + + if (k->keysym == XK_Shift_L || k->keysym == XK_Shift_R) { + kbd->shifted = false; + keyboard_draw(kbd); + } + + simulate_key(kbd->drw->dpy, k->keysym, false); + simulate_key(kbd->drw->dpy, k->modifier, false); + k->pressed = false; + keyboard_draw_key(kbd, k); +} + + +void keyboard_unpress_all(Keyboard *kbd) { + for (int i = 0; i < kbd->nkeys; i++) + keyboard_unpress_key(kbd, &kbd->keys[i]); +} + + +void keyboard_mouse_motion(Keyboard *kbd, int x, int y) { + Key *k = keyboard_find_key(kbd, x, y); + Key *focus = kbd->focus; + + if (k == focus) return; + kbd->focus = k; + + if (focus && !is_modifier(focus)) + keyboard_unpress_key(kbd, focus); + + if (k && kbd->is_pressing && !is_modifier(k)) + keyboard_press_key(kbd, k); + + if (k) keyboard_draw_key(kbd, k); + if (focus) keyboard_draw_key(kbd, focus); +} + + +void keyboard_mouse_press(Keyboard *kbd, int x, int y) { + kbd->is_pressing = true; + + Key *k = keyboard_find_key(kbd, x, y); + if (k) { + if (is_modifier(k) && k->pressed) keyboard_unpress_key(kbd, k); + else keyboard_press_key(kbd, k); + } +} + + +void keyboard_mouse_release(Keyboard *kbd, int x, int y) { + kbd->is_pressing = false; + + Key *k = keyboard_find_key(kbd, x, y); + if (k) { + switch (k->keysym) { + case XK_Cancel: keyboard_next_layer(kbd); break; + case XK_Break: raise(SIGINT); break; + default: break; + } + + if (!is_modifier(k)) keyboard_unpress_all(kbd); + } +} + + +void keyboard_resize(Keyboard *kbd, int width, int height) { + if (width == kbd->dim.width && height == kbd->dim.height) return; + + kbd->dim.width = width; + kbd->dim.height = height; + drw_resize(kbd->drw, width, height); + keyboard_update(kbd); +} + + +void keyboard_event(Keyboard *kbd, XEvent *e) { + switch (e->type) { + case LeaveNotify: keyboard_mouse_motion(kbd, -1, -1); break; + + case MotionNotify: + keyboard_mouse_motion(kbd, e->xmotion.x, e->xmotion.y); + break; + + case ButtonPress: + if (e->xbutton.button == 1) + keyboard_mouse_press(kbd, e->xbutton.x, e->xbutton.y); + break; + + case ButtonRelease: + if (e->xbutton.button == 1) + keyboard_mouse_release(kbd, e->xbutton.x, e->xbutton.y); + break; + + case ConfigureNotify: + keyboard_resize(kbd, e->xconfigure.width, e->xconfigure.height); + break; + + case Expose: + if (!e->xexpose.count) keyboard_draw(kbd); + break; + } +} + + +void keyboard_toggle(Keyboard *kbd) { + kbd->visible = !kbd->visible; + + if (kbd->visible) XMapRaised(kbd->drw->dpy, kbd->win); + else { + XUnmapWindow(kbd->drw->dpy, kbd->win); + keyboard_unpress_all(kbd); + } +} + + +Keyboard *keyboard_create(Display *dpy, Key **layers, const char *font, + const char *colors[SchemeLast][2]) { + Keyboard *kbd = calloc(1, sizeof(Keyboard)); + kbd->layers = layers; + + // Init screen + int screen = DefaultScreen(dpy); + Window root = RootWindow(dpy, screen); + + // Get display size + Dim dim = get_display_dims(dpy, screen); + + // Init keyboard layer + keyboard_init_layer(kbd); // Computes kbd->nrows + kbd->dim.width = dim.width; + kbd->dim.height = dim.height * kbd->nrows / 18; + + // Create drawable + Drw *drw = kbd->drw = + drw_create(dpy, screen, root, kbd->dim.width, kbd->dim.height); + + // Setup fonts + if (!drw_fontset_create(drw, &font, 1)) die("no fonts could be loaded"); + + // Init color schemes + for (int i = 0; i < SchemeLast; i++) + kbd->scheme[i] = drw_scm_create(drw, colors[i], 2); + + drw_setscheme(drw, kbd->scheme[SchemeNorm]); + + // Create window + int y = dim.height - kbd->dim.height; + Clr *clr = kbd->scheme[SchemeNorm]; + kbd->win = create_window(dpy, root, "bbkbd", kbd->dim, 0, y, false, + clr[ColFg].pixel, clr[ColBg].pixel); + + // Init keyboard + keyboard_update(kbd); + + return kbd; +} + + +void keyboard_destroy(Keyboard *kbd) { + Display *dpy = kbd->drw->dpy; + + keyboard_unpress_all(kbd); + + drw_sync(kbd->drw); + drw_free(kbd->drw); + + for (int i = 0; i < SchemeLast; i++) + free(kbd->scheme[i]); + + XSync(dpy, false); + XDestroyWindow(dpy, kbd->win); + XSync(dpy, false); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + + free(kbd->keys); + free(kbd); +} diff --git a/src/kbd/keyboard.h b/src/kbd/keyboard.h new file mode 100644 index 0000000..826db84 --- /dev/null +++ b/src/kbd/keyboard.h @@ -0,0 +1,74 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#pragma once + +#include "drw.h" +#include "util.h" + +#include + + +enum { + SchemeNorm, SchemeNormABC, SchemePress, SchemeHighlight, SchemeLast +}; + +typedef struct { + char *label; + char *label2; + KeySym keysym; + unsigned width; + KeySym modifier; + int x, y, w, h; + bool pressed; +} Key; + +typedef struct { + Window win; + Drw *drw; + Dim dim; + int layer; + int nrows; + int nkeys; + bool is_pressing; + bool shifted; + bool visible; + + Key *focus; + Key **layers; + Key *keys; + char *font; + Clr *scheme[SchemeLast]; +} Keyboard; + + +void keyboard_destroy(Keyboard *kbd); +Keyboard *keyboard_create(Display *dpy, Key **layers, const char *font, + const char *colors[SchemeLast][2]); + +void keyboard_event(Keyboard *kbd, XEvent *e); +void keyboard_toggle(Keyboard *kbd); diff --git a/src/kbd/util.c b/src/kbd/util.c new file mode 100644 index 0000000..6690367 --- /dev/null +++ b/src/kbd/util.c @@ -0,0 +1,145 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#include "util.h" + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef XINERAMA +#include +#endif + + +bool debug = false; + + +void die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { + fputc(' ', stderr); + perror(NULL); + + } else fputc('\n', stderr); + + exit(1); +} + + +void print_dbg(const char *fmt, ...) { + if (!debug) return; + + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fflush(stderr); +} + + + +int find_unused_keycode(Display *dpy) { + // Derived from: + // https://stackoverflow.com/questions/44313966/ + // c-xtest-emitting-key-presses-for-every-unicode-character + + int keycode_low, keycode_high; + XDisplayKeycodes(dpy, &keycode_low, &keycode_high); + + int keysyms_per_keycode = 0; + KeySym *keysyms = + XGetKeyboardMapping(dpy, keycode_low, keycode_high - keycode_low, + &keysyms_per_keycode); + + for (int i = keycode_low; i <= keycode_high; i++) { + bool key_is_empty = true; + + for (int j = 0; j < keysyms_per_keycode; j++) { + int symindex = (i - keycode_low) * keysyms_per_keycode + j; + if (keysyms[symindex]) key_is_empty = false; + else break; + } + + if (key_is_empty) { + XFree(keysyms); + return i; + } + } + + XFree(keysyms); + return 1; +} + + +void simulate_key(Display *dpy, KeySym keysym, bool press) { + if (!keysym) return; + + KeyCode code = XKeysymToKeycode(dpy, keysym); + + if (!code) { + static int tmp_keycode = 0; + if (!tmp_keycode) tmp_keycode = find_unused_keycode(dpy); + + code = tmp_keycode; + XChangeKeyboardMapping(dpy, tmp_keycode, 1, &keysym, 1); + XSync(dpy, false); + } + + XTestFakeKeyEvent(dpy, code, press, 0); +} + + +Dim get_display_dims(Display *dpy, int screen) { + Dim dim; + +#ifdef XINERAMA + if (XineramaIsActive(dpy)) { + int i = 0; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &i); + dim.width = info[0].width; + dim.height = info[0].height; + XFree(info); + return dim; + } +#endif + + dim.width = DisplayWidth(dpy, screen); + dim.height = DisplayHeight(dpy, screen); + + return dim; +} diff --git a/src/kbd/util.h b/src/kbd/util.h new file mode 100644 index 0000000..202a149 --- /dev/null +++ b/src/kbd/util.h @@ -0,0 +1,44 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2021, Buildbotics LLC, All rights reserved. + + This Source describes Open Hardware and is licensed under the + CERN-OHL-S v2. + + You may redistribute and modify this Source and make products + using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl). + This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS + FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable + conditions. + + Source location: https://github.com/buildbotics + + As per CERN-OHL-S v2 section 4, should You produce hardware based on + these sources, You must maintain the Source Location clearly visible on + the external case of the CNC Controller or other product you make using + this Source. + + For more information, email info@buildbotics.com + +\******************************************************************************/ + +#pragma once + +#include + +#include + +extern bool debug; + +typedef struct { + int width; + int height; +} Dim; + +void die(const char *fmt, ...); +void print_dbg(const char *fmt, ...); +void simulate_key(Display *dpy, KeySym keysym, bool press); +Dim get_display_dims(Display *dpy, int screen);