Skip to content

Instantly share code, notes, and snippets.

@Chubek
Created July 7, 2025 03:51
Show Gist options
  • Save Chubek/c7ae971e37754a2e690ecddeb1ee8692 to your computer and use it in GitHub Desktop.
Save Chubek/c7ae971e37754a2e690ecddeb1ee8692 to your computer and use it in GitHub Desktop.
A small virtual keyboard driver for Linux (LKM)

clemore_virtdev.c is the Linux virtual keyboard driver for my key remapper, Clemore.

After compiling it with this Makefile:

obj-m += clemore_virtdev.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

(you need to compile it to an ELF raw object using gcc -c -o clemore_virtdev.o clemore_virtdev.c first)

... you can load it with insmod clemore_virtdev.ko. After that, anything you write to /sys/devices/kernel/clemore_virtdev/key_event will be emitted from it as if a real keyboard. Just remember to prefix it with \x31. e.g. printf '\x32a' > /sys/devices/kernel/clemore_virtdev/key_event would emit 'a' key.

Please visit my Github frontpage. +70 projects, in many languages big and small, all GPL licences.

Clemore, my keyremapper, will come out sooner or later. It picks up after keyd. For now, I am focused on my LL(1) parser generator in Perl, which WIP you can see here.

Thanks, Chubak

#include <stdint.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/version.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/input.h>
#include <linux/bits.h>
static struct input_dev *kb_dev;
// Sysfs write callback
static ssize_t key_event_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
uint8_t keycode, state;
if (count < 2) {
pr_err("clemore_virtual_kbd: Need 2 bytes (keycode + state)\n");
return -EINVAL;
}
keycode = buf[0];
state = buf[1];
if (keycode >= KEY_CNT) {
pr_err("clemore_virtual_kbd: Invalid keycode %u\n", keycode);
return -EINVAL;
}
if (state != 0 && state != 1) {
pr_err("clemore_virtual_kbd: Invalid state %u (must be 0 or 1)\n", state);
return -EINVAL;
}
input_report_key(kb_dev, keycode, state);
input_sync(kb_dev);
pr_debug("clemore_virtual_kbd: Sent key %u %s\n", keycode, state ? "PRESS" : "RELEASE");
return count;
}
static DEVICE_ATTR(key_event, 0220, NULL, key_event_store);
static int __init virtual_keyboard_init(void)
{
int ret;
pr_info("clemore_virtual_kbd: Initializing driver\n");
kb_dev = input_allocate_device();
if (!kb_dev) {
pr_err("clemore_virtual_kbd: Allocation failed\n");
return -ENOMEM;
}
kb_dev->name = "Clemore Virtual Keyboard";
kb_dev->id.bustype = BUS_VIRTUAL;
kb_dev->id.vendor = CLEMORE_VirtDevVID;
kb_dev->id.product = CLEMORE_VirtDevPID;
kb_dev->id.version = CLEMORE_VirtDevVersion;
__set_bit(EV_KEY, kb_dev->evbit);
bitmap_fill(kb_dev->keybit, KEY_CNT);
ret = input_register_device(kb_dev);
if (ret) {
pr_err("clemore_virtual_kbd: Registration failed: %d\n", ret);
input_free_device(kb_dev);
return ret;
}
ret = device_create_file(&kb_dev->dev, &dev_attr_key_event.attr);
if (ret) {
pr_err("clemore_virtual_kbd: Sysfs creation failed: %d\n", ret);
input_unregister_device(kb_dev);
// No need to call input_free_device; input_unregister_device frees it
return ret;
}
pr_info("clemore_virtual_kbd: Driver loaded\n");
return 0;
}
static void __exit virtual_keyboard_exit(void)
{
pr_info("clemore_virtual_kbd: Unloading driver\n");
if (kb_dev) {
device_remove_file(&kb_dev->dev, &dev_attr_key_event.attr);
input_unregister_device(kb_dev);
}
pr_info("clemore_virtual_kbd: Driver unloaded\n");
}
module_init(virtual_keyboard_init);
module_exit(virtual_keyboard_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jim Jumblaya Jackson aka God of Thunder");
MODULE_DESCRIPTION("Virtual Keyboard Driver");
MODULE_VERSION("1.0");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment