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.

This commit is contained in:
2026-02-09 22:01:14 -07:00
parent 29b48ff2ca
commit 234b2685d4
15 changed files with 377 additions and 351 deletions
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="main_window">
<property name="title">OSKeyboard</property>
<child>
<object class="GtkDrawingArea" id="drawing_area">
<property name="can-focus">FALSE</property>
<property name="hexpand">TRUE</property>
</object>
</child>
</object>
</interface>
+97
View File
@@ -0,0 +1,97 @@
#ifndef KEYMAP_H
#define KEYMAP_H
#include <linux/input-event-codes.h>
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
+235
View File
@@ -0,0 +1,235 @@
#include "cairo.h"
#include "glib-object.h"
#include "keymap.h"
#include <fcntl.h>
#include <gtk/gtk.h>
#include <gtk4-layer-shell/gtk4-layer-shell.h>
#include <libevdev/libevdev-uinput.h>
#include <libevdev/libevdev.h>
#include <stdio.h>
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;
}
+12
View File
@@ -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)
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/spaces/oskeyboard">
<file>keyboard.ui</file>
<file>style.css</file>
</gresource>
</gresources>
+26
View File
@@ -0,0 +1,26 @@
* {
background-color: transparent;
}
window {
margin-bottom: 25px;
}
button {
background-color: black;
border: 3px solid white;
border-radius: 15px;
background-color: black;
margin: 0;
padding: 0;
min-width: 94px;
min-height: 94px;
}
button > * {
background-color: black;
color: white;
border-radius: 15px;
font-size: 20px;
}