--- /dev/null
+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/*)
--- /dev/null
+/******************************************************************************\
+
+ 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 <signal.h>
+#include <locale.h>
+
+#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 <font>] [-b <x> <y>]\n"
+ "Options:\n"
+ " -h - Print this help screen and exit\n"
+ " -d - Enable debug\n"
+ " -f <font> - Font string, default: " DEFAULT_FONT "\n"
+ " -b <x> <y> - 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;
+}
--- /dev/null
+/******************************************************************************\
+
+ 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 <X11/Xatom.h>
+
+
+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);
+}
--- /dev/null
+/******************************************************************************\
+
+ 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 <stdbool.h>
+
+
+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);
--- /dev/null
+/******************************************************************************\
+
+ 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
+};
--- /dev/null
+/******************************************************************************\
+
+ 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 <string.h>
+#include <stdint.h>
+
+
+#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;
+}
--- /dev/null
+/******************************************************************************\
+
+ 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 <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+
+
+#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);
--- /dev/null
+/******************************************************************************\
+
+ 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 <X11/Xatom.h>
+
+#include <signal.h>
+
+
+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);
+}
--- /dev/null
+/******************************************************************************\
+
+ 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 <stdbool.h>
+
+
+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);
--- /dev/null
+/******************************************************************************\
+
+ 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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XTest.h>
+
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#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;
+}
--- /dev/null
+/******************************************************************************\
+
+ 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 <X11/Xlib.h>
+
+#include <stdbool.h>
+
+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);