init commit

This commit is contained in:
2026-02-04 19:34:55 -07:00
commit 4f9fd2b7c2
8 changed files with 436 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
osk
*.o
*.env
+36
View File
@@ -0,0 +1,36 @@
# Variables for compiler and flags
CC = gcc
PKG_CONFIG = pkg-config
PACKAGES = gtk4 libevdev gtk4-layer-shell-0
# Fetch flags and libs using pkg-config
CFLAGS = $(shell $(PKG_CONFIG) --cflags $(PACKAGES))
LIBS = $(shell $(PKG_CONFIG) --libs $(PACKAGES))
# Target executable name
TARGET = osk
# Source files
SRC = main.c resources.c
OBJ = $(SRC:.c=.o)
# Default rule: build the target
all: $(TARGET)
# Rule to link the final executable
$(TARGET): resources.c $(OBJ)
$(CC) -o $(TARGET) $(OBJ) $(LIBS)
# Rule to compile .c files to .o files
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Rule to generate resources.c from XML
resources.c: resources.xml
glib-compile-resources resources.xml --target=resources.c --generate-source
# Clean up generated files
clean:
rm -f $(TARGET) $(OBJ) resources.c
.PHONY: all clean
+54
View File
@@ -0,0 +1,54 @@
# A Simple On-Screen Keyboard
`osk` is an On-Screen Keyboard written in C, using GTK bindings, and is meant for use on Linux / Wayland / Hyprland setups and the Spaces ecosystem, more to come soon on that.
Currently, the default (and only) option is to toggle `osk` when `osk` is ran again. This keeps the keyboard in memory, keeping the launch nice and responsive.
## Setup
There are a few steps necessary in order to have this OSK work.
First, check that you have the `uinput` kernel module loaded:
```
lsmod | grep uinput
# If there is no output, try this:
sudo modprobe uinput
```
Second, open `/etc/udev/rules.d/99-uinput.rules` in your text editor of choice and add this line to the file:
```
KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"
```
Then run the following command:
```
sudo usermod -aG input username
```
Log out and back in, and you'll be all set.
Alternatively, change the group to `uinput` in the `/etc/udev/rules.d/99-uinput.rules` file and in the `usermod` command, as the `input` group has access to other input devices, not just virtual input devices.
## Build from source
Run the following commands:
```
git clone https://github.com/weave-space/oskeyboard
cd oskeyboard
make
./osk
```
## Customization
There are two ways to customize `oskeyboard`: editing `keymap.h` and `styles.css`. Currently there is a limitation of custom layouts, as the number of rows and the number of keys per row are hardcoded in `main.c`. That will be remedied in the near future.
Any changes to `keymap.h` or `styles.css` require you to recompile, simply run `make` to do so.
## TODO
- [ ] Responsive design sizes
- [ ] CLI flags
- [ ] Man page
- [ ] Documentation
- [ ] In-depth testing
- [ ] Easier customization & custom layouts
## Credits
- Google Gemini 3 (Explanation of docs and concepts.)
- https://docs.gtk.org/gtk4/index.html
+84
View File
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="main_window">
<property name="title">OSKeyboard</property>
<child>
<object class="GtkGrid" id="keyboard_container">
<property name="row-spacing">15</property>
<property name="halign">center</property>
<property name="valign">center</property>
<child>
<object class="GtkGrid" id="kbd_row_0">
<style><class name="keyboard_row"></class></style>
<layout>
<property name="row">0</property>
<property name="column">0</property>
</layout>
<property name="column-spacing">15</property>
<!-- <property name="halign">left</property> -->
<property name="valign">center</property>
</object>
</child>
<child>
<object class="GtkGrid" id="kbd_row_1">
<style><class name="keyboard_row"></class></style>
<layout>
<property name="row">1</property>
<property name="column">0</property>
</layout>
<property name="column-spacing">15</property>
<!-- <property name="halign">left</property> -->
<property name="valign">center</property>
</object>
</child>
<child>
<object class="GtkGrid" id="kbd_row_2">
<style><class name="keyboard_row"></class></style>
<layout>
<property name="row">2</property>
<property name="column">0</property>
</layout>
<property name="column-spacing">15</property>
<!-- <property name="halign">left</property> -->
<property name="valign">center</property>
</object>
</child>
<child>
<object class="GtkGrid" id="kbd_row_3">
<style><class name="keyboard_row"></class></style>
<layout>
<property name="row">3</property>
<property name="column">0</property>
</layout>
<property name="column-spacing">15</property>
<!-- <property name="halign">left</property> -->
<property name="valign">center</property>
</object>
</child>
<child>
<object class="GtkGrid" id="kbd_row_4">
<style><class name="keyboard_row"></class></style>
<layout>
<property name="row">4</property>
<property name="column">0</property>
</layout>
<property name="column-spacing">15</property>
<!-- <property name="halign">left</property> -->
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</interface>
+40
View File
@@ -0,0 +1,40 @@
#ifndef KEYMAP_H
#define KEYMAP_H
#include <linux/input-event-codes.h>
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 */
{"Caps Lock", "Caps Lock", KEY_CAPSLOCK}, {"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
+186
View File
@@ -0,0 +1,186 @@
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <gtk4-layer-shell/gtk4-layer-shell.h>
#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"))
};
// Map keys from keymap.h to the UI grids
int current_key = 0;
int row_counts[] = {14, 14, 13, 12, 6}; // Matches keymap.h layout
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;
// Dynamic width logic
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;
}
+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;
}