From 234b2685d46b3e5ac0bdeeae6b090fe89535d11d Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Mon, 9 Feb 2026 22:01:14 -0700 Subject: [PATCH] re-implemented keyboard for better performance and keypress detection. there is no longer css customization, but the keyboard is now responsive to the screen width. --- .../index/keymap.h.ABBD7372264C183C.idx | Bin 0 -> 2310 bytes .../clangd/index/main.c.8CF723096C569150.idx | Bin 0 -> 7564 bytes .gitignore | 3 +- Makefile | 36 --- README.md | 12 +- keyboard.ui | 84 ------- keymap.h | 40 --- main.c | 184 -------------- meson.build | 12 + src/keyboard.ui | 13 + src/keymap.h | 97 ++++++++ src/main.c | 235 ++++++++++++++++++ src/meson.build | 12 + resources.xml => src/resources.xml | 0 style.css => src/style.css | 0 15 files changed, 377 insertions(+), 351 deletions(-) create mode 100644 .cache/clangd/index/keymap.h.ABBD7372264C183C.idx create mode 100644 .cache/clangd/index/main.c.8CF723096C569150.idx delete mode 100644 Makefile delete mode 100644 keyboard.ui delete mode 100644 keymap.h delete mode 100644 main.c create mode 100644 meson.build create mode 100644 src/keyboard.ui create mode 100644 src/keymap.h create mode 100644 src/main.c create mode 100644 src/meson.build rename resources.xml => src/resources.xml (100%) rename style.css => src/style.css (100%) diff --git a/.cache/clangd/index/keymap.h.ABBD7372264C183C.idx b/.cache/clangd/index/keymap.h.ABBD7372264C183C.idx new file mode 100644 index 0000000000000000000000000000000000000000..ea89c801016155eb6703b754c22aa63f3f18303a GIT binary patch literal 2310 zcmYk73pCVO9LMhr{>I?%(I74{4TkYd<^9%*kRc}0!<$NV7%MvLS)|r!^Jv>vop$Tk z+BmHZ=|SnCT2d;dZ0#}G9#lH3QtevX?*3-Dcf05Oe>4Bj?|Z-Zaen`MW5Yv3|KTAN zk{({TAtOIU9U&xuN1mUzwh4Ym;3?eix8owe%T<5Tg#8@f2I(J94jLy=C>y~X6mbAVL=NG5c zbVsy`?)guXHIQ(qtSVG#>yEuI#u3M4&C z&Kb?J>sh9a)DU9yx%xa+Kw2tq4x^FUJErkxCyV-X+Hx_n($6-fG~)2=NIPWy&+5g$f36d2A{1=Z*p>M;7Z z9}cHCOG_WKNC*0BvYMu8<`J-#*3d-rc`#D)HRE}y3g|Ia=zBU65fL8xXhyZUHn z3*zW7;W*q%wKxdHVZ-}`$A94fw#W7qH#h%Oo}aXi3)m4mQXHA#{nf(Fxmtj2u`R`F znig>b&1%|!#aK+Sf8O)1_<}?oz$Vy);?XthDu*f(^Z^Hpf*sLJ3!9u%Rcabx09fcC z^d#7zjIqd&YZ(Ie#@^J#v}Rj#>+eHK94ZQRCFB5=e z=Nm>9ag|2apq_{4*%S4YTqt_`s9Ov;5=UAQ{7TYiuu@SX0c?luD7NnN@+tUisUzS3 z96&KCN*GdEk;J4CXXm==*m{9_Hl9t@q((ulEKs8EM-K<*29^eZIzf~`w`%POx6Z?V z^CG}eIEtDCDBL{H9Z!w`yh6CbnV2vavc*OZT9w$%+>NfXeLdbVyuwxrSbzm|tNx6t zJyqfOGzPE-_MqPW;w+n#K>0Gji}7NL$;ZRxPk%HsW9l%5x`wkzZSUwVhz(4W#6jOg z%R~n_1QwGHYNq}9ks~s`9=vbuw z+t#fsVdFSFjy}OR*BrXhouXPweor&q0*?^Z@E|(B>P3Nu~6R$fr0$z%j z(yd773Qltg&d&uLhQsItxq3-O@6CTK04x*9T!_i`XXU%DUhY+5Gi*lHp3bK<)Z2Ut z0b`6QCWi{lTX~`olRN%(4lPDaWuOi+526zskxbQ=oRwArUV@iU6G25$!@!>BN*sYB zC?+Qocq@-1;w^paJ72CiQVVKRY)aK8lh=OkFn_rluoHHo_{rHDuLk;a4*>SWo)o+2 zry9!cPBsFz!d7&vdW|t#pQG0d*ccmAHCI$*@W$YNE8rMW%se#HqI&Z7ZSQ2;0LKgC zod}L=PFp3-I({0k5B8xZtBS@r=N5XL0W851iksqhUYE32o(CL_qvsNn_ro!bJG`zc zu?4oE>KBG}BfO0VF97z#eiRFg=a*kO9efF}19qS|{JRI$xt#WHz~aOKeH^#6>SC@zWETgMgi}Gc}1kVEu{gnfO=0HkLNF#Dsh-;H|s` z2!3j@Go&Y4`!T3}u`e|tw*sh@R{~MLkO`~`JHCGbY9nk!)m}pt`mMmm$M1%l-oFkNqhoH;pEL`i7YqQ-{&z>hj3Wz*)iwbjVs@ zg^bNdZ|;oyM{l7_=tpnyOgw$dKhYza+BW_HW-tfOp)){v896D+VdZ&gauekrW?0<+ Q&AWGPMjTC~sRYyQKdUtVApigX literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/main.c.8CF723096C569150.idx b/.cache/clangd/index/main.c.8CF723096C569150.idx new file mode 100644 index 0000000000000000000000000000000000000000..3193f7cd8879003f6c3c7c7c3fab0faf5933d32c GIT binary patch literal 7564 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 - - - OSKeyboard - - - - 15 - center - center - - - - - - 0 - 0 - - 15 - - center - - - - - - - - - 1 - 0 - - 15 - - center - - - - - - - - - 2 - 0 - - 15 - - center - - - - - - - - - 3 - 0 - - 15 - - center - - - - - - - - - 4 - 0 - - 15 - - center - - - - - - - diff --git a/keymap.h b/keymap.h deleted file mode 100644 index 459133b..0000000 --- a/keymap.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef KEYMAP_H -#define KEYMAP_H - -#include - -typedef struct { -const char *label; -const char *shift_label; -int key_code; -} KeyMapping; - -static const KeyMapping key_map[] = { - /* Row 0 */ - {"`", "~", KEY_GRAVE}, {"1", "!", KEY_1}, {"2", "#", KEY_2}, {"3", "#", KEY_3}, {"4", "$", KEY_4}, - {"5", "%", KEY_5}, {"6", "^", KEY_6}, {"7", "&", KEY_7}, {"8", "*", KEY_8}, {"9", "(", KEY_9}, - {"0", ")", KEY_0}, {"-", "_", KEY_MINUS}, {"=", "+", KEY_EQUAL}, {"Backspace", "Delete", KEY_BACKSPACE}, - - /* Row 1 */ - {"Tab", "Tab", KEY_TAB}, {"q", "Q", KEY_Q}, {"w", "W", KEY_W}, {"e", "E", KEY_E}, {"r", "R", KEY_R}, - {"t", "T", KEY_T}, {"y", "Y", KEY_Y}, {"u", "U", KEY_U}, {"i", "I", KEY_I}, {"o", "O", KEY_O}, - {"p", "P", KEY_P}, {"[", "{", KEY_LEFTBRACE}, {"]", "}", KEY_RIGHTBRACE}, {"\\", "|", KEY_BACKSLASH}, - - /* Row 2 */ - {"Escape", "Escape", KEY_ESC}, {"a", "A", KEY_A}, {"s", "S", KEY_S}, {"d", "D", KEY_D}, - {"f", "F", KEY_F}, {"g", "G", KEY_G}, {"h", "H", KEY_H}, {"j", "J", KEY_J}, {"k", "K", KEY_K}, - {"l", "L", KEY_L}, {";", ":", KEY_SEMICOLON}, {"'", "\"", KEY_APOSTROPHE}, {"Enter", "Enter", KEY_ENTER}, - - /* Row 3 */ - {"Shift", "Shift", KEY_LEFTSHIFT}, {"z", "Z", KEY_Z}, {"x", "X", KEY_X}, {"c", "C", KEY_C}, - {"v", "V", KEY_V}, {"b", "B", KEY_B}, {"n", "N", KEY_N}, {"m", "M", KEY_M}, {",", "<", KEY_COMMA}, - {".", ">", KEY_DOT}, {"/", "?", KEY_SLASH}, {"Shift", "Shift", KEY_RIGHTSHIFT}, - - /* Row 4 */ - {"Ctrl", "Ctrl", KEY_LEFTCTRL}, {"Super", "Super", KEY_LEFTMETA}, {"Alt", "Alt", KEY_LEFTALT}, - {"Space", "Space", KEY_SPACE}, {"Alt", "Alt", KEY_RIGHTALT}, {"Ctrl", "Ctrl", KEY_RIGHTCTRL} -}; - -#define KEY_MAP_SIZE (sizeof(key_map) / sizeof(KeyMapping)) - -#endif diff --git a/main.c b/main.c deleted file mode 100644 index 293b91b..0000000 --- a/main.c +++ /dev/null @@ -1,184 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "keymap.h" - -GtkWidget *window = NULL; -struct libevdev *dev; -struct libevdev_uinput *uidev; -bool shift_active = false; -GList *key_buttons = NULL; - -static void apply_css() { - GtkCssProvider *provider = gtk_css_provider_new(); - gtk_css_provider_load_from_resource(provider, "/org/spaces/oskeyboard/style.css"); - gtk_style_context_add_provider_for_display( - gdk_display_get_default(), - GTK_STYLE_PROVIDER(provider), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - ); - g_object_unref(provider); -} - -void press_key(int key_code) { - if (!uidev) return; - libevdev_uinput_write_event(uidev, EV_KEY, key_code, 1); - libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); - libevdev_uinput_write_event(uidev, EV_KEY, key_code, 0); - libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); -} - -static void update_labels() { - for (GList *l = key_buttons; l != NULL; l = l->next) { - GtkButton *btn = GTK_BUTTON(l->data); - const char *current = gtk_button_get_label(btn); - - for (int i = 0; i < KEY_MAP_SIZE; i++) { - if (shift_active && g_strcmp0(current, key_map[i].label) == 0) { - gtk_button_set_label(btn, key_map[i].shift_label); - break; - } else if (!shift_active && g_strcmp0(current, key_map[i].shift_label) == 0) { - gtk_button_set_label(btn, key_map[i].label); - break; - } - } - } -} - -static void on_key_clicked(GtkButton *button, gpointer user_data) { - const char *label = gtk_button_get_label(button); - - if (g_strcmp0(label, "Shift") == 0) { - shift_active = !shift_active; - update_labels(); - return; - } - - for (int i = 0; i < KEY_MAP_SIZE; i++) { - if (g_strcmp0(label, key_map[i].label) == 0 || g_strcmp0(label, key_map[i].shift_label) == 0) { - if (shift_active) { - libevdev_uinput_write_event(uidev, EV_KEY, KEY_LEFTSHIFT, 1); - libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); - } - - press_key(key_map[i].key_code); - - if (shift_active) { - libevdev_uinput_write_event(uidev, EV_KEY, KEY_LEFTSHIFT, 0); - libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); - shift_active = false; - update_labels(); - } - return; - } - } -} - -void add_key(GtkGrid *grid, const char *label, int col, int width) { - GtkWidget *button = gtk_button_new_with_label(label); - gtk_widget_set_hexpand(button, TRUE); - gtk_widget_set_halign(button, GTK_ALIGN_FILL); - gtk_widget_set_size_request(button, 94 * width, 94); - - g_signal_connect(button, "clicked", G_CALLBACK(on_key_clicked), NULL); - gtk_grid_attach(grid, button, col, 0, width, 1); - key_buttons = g_list_append(key_buttons, button); -} - -void init_evdev_keyboard() { - dev = libevdev_new(); - libevdev_set_name(dev, "OSKeyboard"); - libevdev_enable_event_type(dev, EV_KEY); - - for (int i = 0; i < KEY_MAP_SIZE; i++) { - libevdev_enable_event_code(dev, EV_KEY, key_map[i].key_code, NULL); - } - - int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); - if (fd < 0) { - fprintf(stderr, "Failed to open /dev/uinput: %s\n", strerror(errno)); - return; - } - - int err = libevdev_uinput_create_from_device(dev, fd, &uidev); - if (err != 0) { - fprintf(stderr, "Failed to create uinput device: %s\n", strerror(-err)); - close(fd); - return; - } -} - -static void activate(GtkApplication *app, gpointer user_data) { - if (window) { - if (gtk_widget_get_visible(window)) { - gtk_widget_set_visible(window, FALSE); - } else { - gtk_window_present(GTK_WINDOW(window)); - } - return; - } - - apply_css(); - - GtkBuilder *builder = gtk_builder_new_from_resource("/org/spaces/oskeyboard/keyboard.ui"); - window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window")); - - gtk_layer_init_for_window(GTK_WINDOW(window)); - gtk_layer_set_layer(GTK_WINDOW(window), GTK_LAYER_SHELL_EDGE_BOTTOM); - gtk_layer_auto_exclusive_zone_enable(GTK_WINDOW(window)); - gtk_layer_set_keyboard_mode(GTK_WINDOW(window), FALSE); - gtk_layer_set_anchor(GTK_WINDOW(window), GTK_LAYER_SHELL_EDGE_BOTTOM, TRUE); - gtk_layer_set_anchor(GTK_WINDOW(window), GTK_LAYER_SHELL_EDGE_LEFT, TRUE); - gtk_layer_set_anchor(GTK_WINDOW(window), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); - - gtk_window_set_application(GTK_WINDOW(window), app); - - GtkGrid *rows[] = { - GTK_GRID(gtk_builder_get_object(builder, "kbd_row_0")), - GTK_GRID(gtk_builder_get_object(builder, "kbd_row_1")), - GTK_GRID(gtk_builder_get_object(builder, "kbd_row_2")), - GTK_GRID(gtk_builder_get_object(builder, "kbd_row_3")), - GTK_GRID(gtk_builder_get_object(builder, "kbd_row_4")) - }; - - int current_key = 0; - int row_counts[] = {14, 14, 13, 12, 6}; - - for (int r = 0; r < 5; r++) { - int current_col = 0; - for (int c = 0; c < row_counts[r]; c++) { - if (current_key >= KEY_MAP_SIZE) break; - - const char *label = key_map[current_key].label; - int width = 1; - - if (g_strcmp0(label, "Space") == 0) width = 7; - else if (g_strcmp0(label, "Backspace") == 0) width = 2; - else if (g_strcmp0(label, "Enter") == 0) width = 3; - else if (g_strcmp0(label, "Shift") == 0) width = 3; - else if (g_strcmp0(label, "Alt") == 0) width = 2; - else if (g_strcmp0(label, "Ctrl") == 0) width = 3; - else if (g_strcmp0(label, "Caps Lock") == 0) width = 2; - - add_key(rows[r], label, current_col, width); - current_col += width; - current_key++; - } - } - - init_evdev_keyboard(); - gtk_window_present(GTK_WINDOW(window)); - g_object_unref(builder); -} - -int main(int argc, char **argv) { - GtkApplication *app = gtk_application_new("org.spaces.oskeyboard", G_APPLICATION_DEFAULT_FLAGS); - g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); - int status = g_application_run(G_APPLICATION(app), argc, argv); - g_object_unref(app); - return status; -} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..4ed821e --- /dev/null +++ b/meson.build @@ -0,0 +1,12 @@ +project('oskeyboard', 'c', + version : '0.1', + default_options : ['warning_level=0']) + +# Import the GNOME module to handle GResources +gnome = import('gnome') + +gtk_dep = dependency('gtk4') +libevdev_dep = dependency('libevdev') +layer_shell_dep = dependency('gtk4-layer-shell-0') + +subdir('src') diff --git a/src/keyboard.ui b/src/keyboard.ui new file mode 100644 index 0000000..2d58fd9 --- /dev/null +++ b/src/keyboard.ui @@ -0,0 +1,13 @@ + + + + OSKeyboard + + + + FALSE + TRUE + + + + diff --git a/src/keymap.h b/src/keymap.h new file mode 100644 index 0000000..842e896 --- /dev/null +++ b/src/keymap.h @@ -0,0 +1,97 @@ +#ifndef KEYMAP_H +#define KEYMAP_H + +#include + +typedef struct { + const char *label; + const char *shift_label; + int key_code; + float weight; +} KeyDef; + +typedef struct { + KeyDef *keys; + int key_count; + float total_weight; +} RowDef; + +static const RowDef keys[] = { + // Top Row (0) + {(KeyDef[]){{"`", "~", KEY_GRAVE, 1.0}, + {"1", "!", KEY_1, 1.0}, + {"2", "@", KEY_2, 1.0}, + {"3", "#", KEY_3, 1.0}, + {"4", "$", KEY_4, 1.0}, + {"5", "%", KEY_5, 1.0}, + {"6", "^", KEY_6, 1.0}, + {"7", "&", KEY_7, 1.0}, + {"8", "*", KEY_8, 1.0}, + {"9", "(", KEY_9, 1.0}, + {"0", ")", KEY_0, 1.0}, + {"-", "_", KEY_MINUS, 1.0}, + {"=", "+", KEY_EQUAL, 1.0}, + {"Backspace", "Backspace", KEY_BACKSPACE, 1.5}}, + 14, 14.5}, + + // Row 1 + {(KeyDef[]){{"Tab", "Tab", KEY_TAB, 1.5}, + {"q", "Q", KEY_Q, 1.0}, + {"w", "W", KEY_W, 1.0}, + {"e", "E", KEY_E, 1.0}, + {"r", "R", KEY_R, 1.0}, + {"t", "T", KEY_T, 1.0}, + {"y", "Y", KEY_Y, 1.0}, + {"u", "U", KEY_U, 1.0}, + {"i", "I", KEY_I, 1.0}, + {"o", "O", KEY_O, 1.0}, + {"p", "P", KEY_P, 1.0}, + {"[", "{", KEY_LEFTBRACE, 1.0}, + {"]", "}", KEY_RIGHTBRACE, 1.0}, + {"\\", "|", KEY_BACKSLASH, 1.0}}, + 14, 14.5}, + + // Row 2 + {(KeyDef[]){{"Escape", "Escape", KEY_ESC, 2.0}, + {"a", "A", KEY_A, 1.0}, + {"s", "S", KEY_S, 1.0}, + {"d", "D", KEY_D, 1.0}, + {"f", "F", KEY_F, 1.0}, + {"g", "G", KEY_G, 1.0}, + {"h", "H", KEY_H, 1.0}, + {"j", "J", KEY_J, 1.0}, + {"k", "K", KEY_K, 1.0}, + {"l", "L", KEY_L, 1.0}, + {";", ":", KEY_SEMICOLON, 1.0}, + {"'", "\"", KEY_APOSTROPHE, 1.0}, + {"Enter", "Enter", KEY_ENTER, 2.0}}, + 13, 15.0}, + + // Row 3 + {(KeyDef[]){{"Shift", "Shift", KEY_LEFTSHIFT, 2.5}, + {"z", "Z", KEY_Z, 1.0}, + {"x", "X", KEY_X, 1.0}, + {"c", "C", KEY_C, 1.0}, + {"v", "V", KEY_V, 1.0}, + {"b", "B", KEY_B, 1.0}, + {"n", "N", KEY_N, 1.0}, + {"m", "M", KEY_M, 1.0}, + {",", "<", KEY_COMMA, 1.0}, + {".", ">", KEY_DOT, 1.0}, + {"/", "?", KEY_SLASH, 1.0}, + {"Shift", "Shift", KEY_RIGHTSHIFT, 2.0}}, + 12, 14.5}, + + // Row 4 + {(KeyDef[]){{"Ctrl", "Ctrl", KEY_LEFTCTRL, 2.0}, + {"Super", "Super", KEY_LEFTMETA, 1.0}, + {"Alt", "Alt", KEY_LEFTALT, 1.0}, + {"Space", "Space", KEY_SPACE, 6.0}, + {"Alt", "Alt", KEY_RIGHTALT, 1.0}, + {"Ctrl", "Ctrl", KEY_RIGHTCTRL, 1.0}}, + 6, 12.0}, +}; + +#define KEY_MAP_SIZE (sizeof(keys) / sizeof(RowDef)) + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..4006d7a --- /dev/null +++ b/src/main.c @@ -0,0 +1,235 @@ +#include "cairo.h" +#include "glib-object.h" +#include "keymap.h" +#include +#include +#include +#include +#include +#include + +GtkWidget *window = NULL; +GtkDrawingArea *area = NULL; + +struct libevdev *dev; +struct libevdev_uinput *uidev; + +static gboolean shift_active = FALSE; +static gboolean alt_active = FALSE; +static gboolean ctrl_active = FALSE; + +void send_keypress(int key_code) { + if (!uidev) + return; + libevdev_uinput_write_event(uidev, EV_KEY, key_code, 1); + libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); + libevdev_uinput_write_event(uidev, EV_KEY, key_code, 0); + libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); +} + +static void on_click_pressed(GtkGestureClick *gesture, int n_press, double x, + double y, gpointer data) { + GtkWidget *widget = + gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)); + int width = gtk_widget_get_width(widget); + int height = gtk_widget_get_height(widget); + + int row_height = height / 5; + int row_index = (int)(y / row_height); + if (row_index < 0 || row_index >= 5) + return; + + RowDef row = keys[row_index]; + double unit_width = (double)width / row.total_weight; + + double current_x = 0; + for (int i = 0; i < row.key_count; i++) { + double next_x = current_x + (row.keys[i].weight * unit_width); + + if (x >= current_x && x <= next_x) { + KeyDef *clicked_key = &row.keys[i]; + + if (clicked_key->key_code == KEY_LEFTSHIFT || + clicked_key->key_code == KEY_RIGHTSHIFT) { + shift_active = !shift_active; + gtk_widget_queue_draw(GTK_WIDGET(area)); + return; + } + + if (clicked_key->key_code == KEY_LEFTALT || + clicked_key->key_code == KEY_RIGHTALT) { + alt_active = !alt_active; + return; + } + + if (clicked_key->key_code == KEY_LEFTCTRL || + clicked_key->key_code == KEY_RIGHTCTRL) { + ctrl_active = !ctrl_active; + return; + } + + if (shift_active) { + libevdev_uinput_write_event(uidev, EV_KEY, KEY_LEFTSHIFT, 1); + libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); + } + + if (alt_active) { + libevdev_uinput_write_event(uidev, EV_KEY, KEY_LEFTALT, 1); + libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); + } + + if (ctrl_active) { + libevdev_uinput_write_event(uidev, EV_KEY, KEY_LEFTCTRL, 1); + libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); + } + + send_keypress(clicked_key->key_code); + + if (shift_active) { + libevdev_uinput_write_event(uidev, EV_KEY, KEY_LEFTSHIFT, 0); + libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); + + shift_active = FALSE; + gtk_widget_queue_draw(GTK_WIDGET(area)); + } + + if (alt_active) { + libevdev_uinput_write_event(uidev, EV_KEY, KEY_LEFTALT, 0); + libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); + + alt_active = FALSE; + } + + if (ctrl_active) { + libevdev_uinput_write_event(uidev, EV_KEY, KEY_LEFTCTRL, 0); + libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); + + ctrl_active = FALSE; + } + + break; + } + current_x = next_x; + } +} + +static void draw_key(cairo_t *ct, KeyDef key, int x, int y, int key_width, + int key_height) { + cairo_set_source_rgba(ct, 1.0, 1.0, 1.0, 1.0); + cairo_set_line_width(ct, 3.0); + cairo_rectangle(ct, x, y, key_width, key_height); + cairo_stroke(ct); + + cairo_select_font_face(ct, "Hack Nerd Font Mono", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(ct, key_height * 0.17); + + const char *label_text = shift_active ? key.shift_label : key.label; + + cairo_text_extents_t extents; + cairo_text_extents(ct, label_text, &extents); + + double tx = x + (key_width - extents.width) / 2.0 - extents.x_bearing; + double ty = y + (key_height - extents.height) / 2.0 - extents.y_bearing; + + cairo_set_source_rgba(ct, 1.0, 1.0, 1.0, 1.0); + cairo_move_to(ct, tx, ty); + cairo_show_text(ct, label_text); +} + +static void draw_row(cairo_t *ct, RowDef row, int row_i, int width, + int height) { + int key_count = row.key_count; + float total_weight = row.total_weight; + + double key_width = width / total_weight; + int key_height = height / 5; + double y = key_height * row_i; + + double x = 0; + for (int i = 0; i < key_count; i++) { + double weighted_key_width = key_width * row.keys[i].weight; + draw_key(ct, row.keys[i], x, y, weighted_key_width, key_height); + x += weighted_key_width; + } +} + +static void draw_keyboard(GtkDrawingArea *area, cairo_t *ct, int width, + int height, gpointer data) { + cairo_set_source_rgba(ct, 0.0, 0.0, 0.0, 1.0); + cairo_paint(ct); + for (int i = 0; i < 5; i++) { + draw_row(ct, keys[i], i, width, height); + } +} + +void init_evdev_keyboard() { + dev = libevdev_new(); + libevdev_set_name(dev, "OSKeyboard"); + libevdev_enable_event_type(dev, EV_KEY); + + for (int r = 0; r < 5; r++) { + for (int k = 0; k < keys[r].key_count; k++) { + libevdev_enable_event_code(dev, EV_KEY, keys[r].keys[k].key_code, NULL); + } + } + + int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (fd < 0) { + fprintf(stderr, "Failed to open /dev/uinput: %s\n", strerror(errno)); + return; + } + + int err = libevdev_uinput_create_from_device(dev, fd, &uidev); + if (err != 0) { + fprintf(stderr, "Failed to create uinput device: %s\n", strerror(-err)); + close(fd); + return; + } +} + +static void activate(GtkApplication *app, gpointer user_data) { + if (window) { + if (gtk_widget_get_visible(window)) { + gtk_widget_set_visible(window, FALSE); + } else { + gtk_window_present(GTK_WINDOW(window)); + } + return; + } + + GtkBuilder *builder = + gtk_builder_new_from_resource("/org/spaces/oskeyboard/keyboard.ui"); + window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window")); + + gtk_layer_init_for_window(GTK_WINDOW(window)); + gtk_layer_set_layer(GTK_WINDOW(window), GTK_LAYER_SHELL_EDGE_BOTTOM); + gtk_layer_auto_exclusive_zone_enable(GTK_WINDOW(window)); + gtk_layer_set_keyboard_mode(GTK_WINDOW(window), FALSE); + gtk_layer_set_anchor(GTK_WINDOW(window), GTK_LAYER_SHELL_EDGE_BOTTOM, TRUE); + gtk_layer_set_anchor(GTK_WINDOW(window), GTK_LAYER_SHELL_EDGE_LEFT, TRUE); + gtk_layer_set_anchor(GTK_WINDOW(window), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); + gtk_widget_set_size_request(window, 1000, 500); + gtk_window_set_application(GTK_WINDOW(window), app); + + area = GTK_DRAWING_AREA(gtk_builder_get_object(builder, "drawing_area")); + gtk_drawing_area_set_draw_func(area, draw_keyboard, NULL, NULL); + + GtkGesture *gesture = gtk_gesture_click_new(); + g_signal_connect(gesture, "pressed", G_CALLBACK(on_click_pressed), NULL); + gtk_widget_add_controller(GTK_WIDGET(area), GTK_EVENT_CONTROLLER(gesture)); + + init_evdev_keyboard(); + gtk_window_present(GTK_WINDOW(window)); + g_object_unref(builder); +} + +int main(int argc, char **argv) { + GtkApplication *app = + gtk_application_new("org.spaces.oskeyboard", G_APPLICATION_DEFAULT_FLAGS); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + + int status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + return status; +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..4fb6466 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,12 @@ +# Compile the resources into a C file +res = gnome.compile_resources( + 'oskeyboard-resources', + 'resources.xml', + source_dir: '.', # Look for .ui and .css in the current directory +) + +executable('osk', + 'main.c', + res, # Add the compiled resource file to the sources + dependencies : [gtk_dep, libevdev_dep, layer_shell_dep], + install : true) diff --git a/resources.xml b/src/resources.xml similarity index 100% rename from resources.xml rename to src/resources.xml diff --git a/style.css b/src/style.css similarity index 100% rename from style.css rename to src/style.css