summary refs log tree commit diff stats
path: root/rust/qmk-hid-com/src
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-12-30 18:22:41 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-12-30 18:22:41 +0100
commitba9f12810f7dc4969ac175f6e959d5fe6407747d (patch)
treef4039e46985d56bac471bd8568f96b159a92b2ae /rust/qmk-hid-com/src
parentfeat(src): Make usable (diff)
downloadqmk_layout-ba9f12810f7dc4969ac175f6e959d5fe6407747d.zip
feat(treewide): Migrate the Unicode handling to a custom c program, that works via rawhid
Diffstat (limited to '')
-rw-r--r--rust/qmk-hid-com/src/cli.rs40
-rw-r--r--rust/qmk-hid-com/src/hid/mod.rs44
-rw-r--r--rust/qmk-hid-com/src/main.rs107
-rw-r--r--rust/qmk-hid-com/src/search.rs51
-rwxr-xr-xrust/qmk-hid-com/src_c/build.sh18
-rw-r--r--rust/qmk-hid-com/src_c/meson.build51
-rw-r--r--rust/qmk-hid-com/src_c/protocol/meson.build37
-rw-r--r--rust/qmk-hid-com/src_c/protocol/virtual-keyboard-unstable-v1.xml113
-rw-r--r--rust/qmk-hid-com/src_c/src/error.c20
-rw-r--r--rust/qmk-hid-com/src_c/src/error.h1
-rw-r--r--rust/qmk-hid-com/src_c/src/hid/hid.c83
-rw-r--r--rust/qmk-hid-com/src_c/src/hid/hid.h5
-rw-r--r--rust/qmk-hid-com/src_c/src/keyboard/keyboard.c135
-rw-r--r--rust/qmk-hid-com/src_c/src/keyboard/keyboard.h17
-rw-r--r--rust/qmk-hid-com/src_c/src/keyboard/virtual-keyboard-unstable-v1-client-protocol.h280
-rw-r--r--rust/qmk-hid-com/src_c/src/main.c28
16 files changed, 1030 insertions, 0 deletions
diff --git a/rust/qmk-hid-com/src/cli.rs b/rust/qmk-hid-com/src/cli.rs
new file mode 100644
index 0000000..afef1a9
--- /dev/null
+++ b/rust/qmk-hid-com/src/cli.rs
@@ -0,0 +1,40 @@
+use clap::{command, Parser, Subcommand};
+
+#[derive(Parser, Debug)]
+pub struct CliArgs {
+    #[clap(subcommand)]
+    pub command: Command,
+}
+#[derive(Clone, Debug, Subcommand)]
+pub enum Command {
+    /// List all devices, where the manufacturer string contains the search key
+    Search {
+        /// The part that must be contained in the manufacturer name
+        vendor_name: String,
+    },
+
+    /// Talk to the device specified by the [`vendor_id`]  and [`product_id`].
+    Send {
+        usage_page: u16,
+        usage_id: u16,
+        message: u8,
+    },
+
+    /// Talk to the device specified by the [`vendor_id`]  and [`product_id`].
+    Monitor { usage_page: u16, usage_id: u16 },
+
+    Inform {
+        #[command(subcommand)]
+        command: Inform,
+    },
+
+    ArrInform { values: Vec<u8> },
+}
+
+#[derive(Subcommand, Clone, Debug)]
+pub enum Inform {
+    Hex { val: String },
+    Dec { val: i64 },
+    Bin { val: String },
+    Char { val: char },
+}
diff --git a/rust/qmk-hid-com/src/hid/mod.rs b/rust/qmk-hid-com/src/hid/mod.rs
new file mode 100644
index 0000000..b80c835
--- /dev/null
+++ b/rust/qmk-hid-com/src/hid/mod.rs
@@ -0,0 +1,44 @@
+use hidapi::{HidApi, HidDevice};
+
+fn find_device(api: &HidApi, usage_id: u16, usage_page: u16) -> anyhow::Result<HidDevice> {
+    Ok({
+        let devs = api
+            .device_list()
+            .filter(|dev| dev.usage() == usage_id && dev.usage_page() == usage_page)
+            .collect::<Vec<_>>();
+
+        assert_eq!(
+            devs.len(),
+            1,
+            "Only one device should have such a usage:usage_page value"
+        );
+
+        api.open(devs[0].vendor_id(), devs[0].product_id())?
+    })
+}
+
+pub fn monitor(api: &HidApi, usage_id: u16, usage_page: u16) -> anyhow::Result<()> {
+    let device = find_device(api, usage_id, usage_page)?;
+
+    loop {
+        // Read data from device
+        let mut buf = [0u8; 4];
+        let res = device.read(&mut buf[..]).unwrap();
+
+        let value = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
+
+        println!("Read: {:?} ({} bytes) -> {value:x}", &buf[..res], res);
+    }
+}
+pub fn send(api: &HidApi, usage_id: u16, usage_page: u16, message: u8) -> anyhow::Result<()> {
+    let device = find_device(api, usage_id, usage_page)?;
+
+    // Write data to device
+    let mut buf = [0u8; 33];
+    buf[1] = message;
+
+    let res = device.write(&buf)?;
+    println!("Wrote: {:#?} byte(s)", res);
+
+    Ok(())
+}
diff --git a/rust/qmk-hid-com/src/main.rs b/rust/qmk-hid-com/src/main.rs
new file mode 100644
index 0000000..ebaa91b
--- /dev/null
+++ b/rust/qmk-hid-com/src/main.rs
@@ -0,0 +1,107 @@
+// Qmk Hid Com - A small middelware between a qmk keyboard and a wayland virtual
+// keyboard. For unicode input
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This file is part of Qmk Hid Com.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+use clap::Parser;
+use cli::{CliArgs, Command, Inform};
+
+mod cli;
+mod hid;
+mod search;
+
+fn main() -> anyhow::Result<()> {
+    let args = CliArgs::parse();
+    let api = hidapi::HidApi::new().unwrap();
+
+    match args.command {
+        Command::Search { vendor_name } => search::search(&api, vendor_name),
+        Command::Monitor {
+            usage_id,
+            usage_page,
+        } => hid::monitor(&api, usage_id, usage_page),
+        Command::Send {
+            usage_id,
+            usage_page,
+            message,
+        } => hid::send(&api, usage_id, usage_page, message),
+
+        Command::Inform { command } => {
+            let (decimal, _) = match command {
+                Inform::Hex { val } => (i64::from_str_radix(&val.to_lowercase(), 16)?, val),
+                Inform::Dec { val } => (val, val.to_string()),
+                Inform::Bin { val } => (i64::from_str_radix(&val.to_lowercase(), 2)?, val),
+                Inform::Char { val } => ((val as u32) as i64, val.to_string()),
+            };
+
+            let character = char::from_u32(decimal as u32).unwrap();
+
+            let binary = {
+                decimal
+                    .to_ne_bytes()
+                    .map(|sm| format!("{:<08b}", sm))
+                    .join(" ")
+            };
+
+            let split = {
+                let first;
+                let second;
+                let third;
+                let fourth;
+
+                if cfg!(target_endian = "big") {
+                    let decimal: u32 = decimal as u32;
+                    let decimal = decimal.to_be();
+
+                    dbg!(decimal
+                        .to_be_bytes()
+                        .map(|sm| format!("{:<08b}", sm))
+                        .join(" "));
+
+                    fourth = decimal as u8;
+                    third = (decimal >> 8) as u8;
+                    second = (decimal >> 16) as u8;
+                    first = (decimal >> 24) as u8;
+                } else {
+                    first = decimal as u8;
+                    second = (decimal >> 8) as u8;
+                    third = (decimal >> 16) as u8;
+                    fourth = (decimal >> 24) as u8;
+                }
+                [first, second, third, fourth]
+            };
+
+            println!(
+                "dec: {}, hex: {:x}, char: {:#?}, bin: {}, split: {:?}/[{}] ({})",
+                decimal,
+                decimal,
+                character,
+                binary,
+                split,
+                split
+                    .iter()
+                    .map(|val| format!("{:x}", val))
+                    .collect::<Vec<_>>()
+                    .join(", "),
+                i32::from_ne_bytes(split)
+            );
+            Ok(())
+        }
+        Command::ArrInform { values } => {
+            assert_eq!(values.len(), 4);
+            let arr = [values[0], values[1], values[2], values[3]];
+
+            let output = u32::from_le_bytes(arr);
+
+            println!("{:?} -> {output}", arr);
+
+            Ok(())
+        }
+    }
+}
diff --git a/rust/qmk-hid-com/src/search.rs b/rust/qmk-hid-com/src/search.rs
new file mode 100644
index 0000000..3c954ba
--- /dev/null
+++ b/rust/qmk-hid-com/src/search.rs
@@ -0,0 +1,51 @@
+use hidapi::HidApi;
+
+pub fn search(api: &HidApi, vendor_name: String) -> anyhow::Result<()> {
+    for device in api.device_list() {
+        if let Some(string) = device.manufacturer_string() {
+            if string.to_lowercase().contains(&vendor_name.to_lowercase()) {
+                println!(
+                    "Device matches: {} (VID) - {} (PID) {}:{} - at {} by {} via {:#?}",
+                    device.vendor_id(),
+                    device.product_id(),
+                    device.usage_page(),
+                    device.usage(),
+                    device.path().to_str()?,
+                    string,
+                    device.bus_type(),
+                );
+
+                println!(
+                    "  {} (Interface {}):",
+                    match device.product_string() {
+                        Some(s) => s,
+                        _ => "<COULD NOT FETCH>",
+                    },
+                    device.interface_number()
+                );
+                let mut descriptor = vec![0u8; 2048];
+                match device
+                    .open_device(api)
+                    .and_then(|dev| dev.get_report_descriptor(&mut descriptor))
+                {
+                    Ok(length) => {
+                        println!("    {} (descriptor)", {
+                            &descriptor[..length]
+                                .iter()
+                                .map(|val| val.to_string())
+                                .collect::<String>()
+                        })
+                    }
+                    Err(err) => println!("    Failed to retrieve descriptor ({:?})", err),
+                }
+                println!();
+            }
+        } else {
+            eprintln!(
+                "Device without manufacturer string ({})",
+                device.product_id()
+            );
+        }
+    }
+    Ok(())
+}
diff --git a/rust/qmk-hid-com/src_c/build.sh b/rust/qmk-hid-com/src_c/build.sh
new file mode 100755
index 0000000..de615f8
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/build.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env sh
+
+# Source: https://mesonbuild.com/Builtin-options.html
+# Use gcc's wrappers to support lto
+CC="gcc" AR="gcc-ar" RANLIB="gcc-ranlib" meson setup build_release \
+    -Dbuildtype=release \
+    -Ddebug=false \
+    -Doptimization=3 \
+    -Dstrip=true \
+    -Dwarning_level=everything \
+    -Dwerror=true \
+    -Db_lundef=true \
+    -Db_lto=true \
+    -Db_ndebug=false \
+    -Db_pgo=generate \
+    -Db_staticpic=true \
+    -Db_pie=false \
+    -Dc_std=gnu2x
diff --git a/rust/qmk-hid-com/src_c/meson.build b/rust/qmk-hid-com/src_c/meson.build
new file mode 100644
index 0000000..9e8357f
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/meson.build
@@ -0,0 +1,51 @@
+# This is heavily based on `wtype`
+
+project(
+    'qmk-unicode-type', 'c',
+    version: '0.1',
+    license: 'GPL-3.0-or-later',
+    default_options : [
+        'buildtype=release',
+        'default_library=static'
+    ],
+)
+
+git = find_program('git', native: true, required: false)
+
+if not git.found()
+    add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'c')
+else
+    git_commit_hash = run_command([git.full_path(), 'describe', '--always', '--tags'], check: true).stdout().strip()
+    git_branch = run_command([git.full_path(), 'rev-parse', '--abbrev-ref', 'HEAD'], check: true).stdout().strip()
+    version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash, git_branch)
+    add_project_arguments('-DVERSION=@0@'.format(version), language: 'c')
+endif
+cc = meson.get_compiler('c')
+
+
+wayland_client = dependency('wayland-client')
+libhid = dependency('hidapi-hidraw')
+rt = cc.find_library('rt')
+
+
+
+src_files = files(
+  './src/error.c',
+  './src/hid/hid.c',
+  './src/keyboard/keyboard.c',
+  './src/main.c',
+)
+
+subdir('protocol')
+
+executable(
+    meson.project_name(),
+    src_files,
+    dependencies: [
+        client_protos,
+        wayland_client,
+        libhid,
+        rt,
+    ],
+)
+
diff --git a/rust/qmk-hid-com/src_c/protocol/meson.build b/rust/qmk-hid-com/src_c/protocol/meson.build
new file mode 100644
index 0000000..1bfe9e0
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/protocol/meson.build
@@ -0,0 +1,37 @@
+wayland_scanner = find_program('wayland-scanner')
+
+wayland_scanner_code = generator(
+	wayland_scanner,
+	output: '@BASENAME@-protocol.c',
+	arguments: ['private-code', '@INPUT@', '@OUTPUT@'],
+)
+
+wayland_scanner_client = generator(
+	wayland_scanner,
+	output: '@BASENAME@-client-protocol.h',
+	arguments: ['client-header', '@INPUT@', '@OUTPUT@'],
+)
+
+
+client_protocols = [
+	['virtual-keyboard-unstable-v1.xml'],
+]
+client_protos_src = []
+client_protos_headers = []
+
+
+foreach p : client_protocols
+	xml = join_paths(p)
+	client_protos_src += wayland_scanner_code.process(xml)
+	client_protos_headers += wayland_scanner_client.process(xml)
+endforeach
+lib_client_protos = static_library(
+	'client_protos',
+	client_protos_src + client_protos_headers,
+	dependencies: [wayland_client]
+)
+
+client_protos = declare_dependency(
+	link_with: lib_client_protos,
+	sources: client_protos_headers,
+)
diff --git a/rust/qmk-hid-com/src_c/protocol/virtual-keyboard-unstable-v1.xml b/rust/qmk-hid-com/src_c/protocol/virtual-keyboard-unstable-v1.xml
new file mode 100644
index 0000000..5095c91
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/protocol/virtual-keyboard-unstable-v1.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="virtual_keyboard_unstable_v1">
+  <copyright>
+    Copyright © 2008-2011  Kristian Høgsberg
+    Copyright © 2010-2013  Intel Corporation
+    Copyright © 2012-2013  Collabora, Ltd.
+    Copyright © 2018       Purism SPC
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <interface name="zwp_virtual_keyboard_v1" version="1">
+    <description summary="virtual keyboard">
+      The virtual keyboard provides an application with requests which emulate
+      the behaviour of a physical keyboard.
+
+      This interface can be used by clients on its own to provide raw input
+      events, or it can accompany the input method protocol.
+    </description>
+
+    <request name="keymap">
+      <description summary="keyboard mapping">
+        Provide a file descriptor to the compositor which can be
+        memory-mapped to provide a keyboard mapping description.
+
+        Format carries a value from the keymap_format enumeration.
+      </description>
+      <arg name="format" type="uint" summary="keymap format"/>
+      <arg name="fd" type="fd" summary="keymap file descriptor"/>
+      <arg name="size" type="uint" summary="keymap size, in bytes"/>
+    </request>
+
+    <enum name="error">
+      <entry name="no_keymap" value="0" summary="No keymap was set"/>
+    </enum>
+
+    <request name="key">
+      <description summary="key event">
+        A key was pressed or released.
+        The time argument is a timestamp with millisecond granularity, with an
+        undefined base. All requests regarding a single object must share the
+        same clock.
+
+        Keymap must be set before issuing this request.
+
+        State carries a value from the key_state enumeration.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="key" type="uint" summary="key that produced the event"/>
+      <arg name="state" type="uint" summary="physical state of the key"/>
+    </request>
+
+    <request name="modifiers">
+      <description summary="modifier and group state">
+        Notifies the compositor that the modifier and/or group state has
+        changed, and it should update state.
+
+        The client should use wl_keyboard.modifiers event to synchronize its
+        internal state with seat state.
+
+        Keymap must be set before issuing this request.
+      </description>
+      <arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
+      <arg name="mods_latched" type="uint" summary="latched modifiers"/>
+      <arg name="mods_locked" type="uint" summary="locked modifiers"/>
+      <arg name="group" type="uint" summary="keyboard layout"/>
+    </request>
+
+    <request name="destroy" type="destructor" since="1">
+      <description summary="destroy the virtual keyboard keyboard object"/>
+    </request>
+  </interface>
+
+  <interface name="zwp_virtual_keyboard_manager_v1" version="1">
+    <description summary="virtual keyboard manager">
+      A virtual keyboard manager allows an application to provide keyboard
+      input events as if they came from a physical keyboard.
+    </description>
+
+    <enum name="error">
+      <entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
+    </enum>
+
+    <request name="create_virtual_keyboard">
+      <description summary="Create a new virtual keyboard">
+        Creates a new virtual keyboard associated to a seat.
+
+        If the compositor enables a keyboard to perform arbitrary actions, it
+        should present an error when an untrusted client requests a new
+        keyboard.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat"/>
+      <arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/>
+    </request>
+  </interface>
+</protocol>
diff --git a/rust/qmk-hid-com/src_c/src/error.c b/rust/qmk-hid-com/src_c/src/error.c
new file mode 100644
index 0000000..f970cfc
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/src/error.c
@@ -0,0 +1,20 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wayland-client.h>
+
+#include "error.h"
+
+__attribute__((noreturn)) __attribute__((format(printf, 1, 2))) void
+fail(const char *format, ...) {
+  va_list vas;
+
+  va_start(vas, format);
+  fprintf(stderr, "Error: ");
+  vfprintf(stderr, format, vas);
+  va_end(vas);
+
+  fprintf(stderr, "\n");
+
+  exit(EXIT_FAILURE);
+}
diff --git a/rust/qmk-hid-com/src_c/src/error.h b/rust/qmk-hid-com/src_c/src/error.h
new file mode 100644
index 0000000..e0d6aaf
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/src/error.h
@@ -0,0 +1 @@
+void fail(const char *format, ...);
diff --git a/rust/qmk-hid-com/src_c/src/hid/hid.c b/rust/qmk-hid-com/src_c/src/hid/hid.c
new file mode 100644
index 0000000..d468ba8
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/src/hid/hid.c
@@ -0,0 +1,83 @@
+#include <assert.h>
+#include <hidapi/hidapi.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "../error.h"
+#include "hid.h"
+
+static hid_device *find_device_by_usage(uint32_t expected_usage,
+                                        uint32_t expected_usage_page) {
+  struct hid_device_info *devs;
+  devs = hid_enumerate(0x0, 0x0);
+
+  hid_device *found_device = NULL;
+  bool found = false;
+
+  struct hid_device_info *cur_dev = devs;
+  for (; cur_dev; cur_dev = cur_dev->next) {
+    // printf("Checking device: %ls, %d (%d:%d) at %s ..\n",
+    //        cur_dev->product_string, cur_dev->product_id, cur_dev->usage,
+    //        cur_dev->usage_page, cur_dev->path);
+
+    if (cur_dev->usage == expected_usage &&
+        cur_dev->usage_page == expected_usage_page) {
+      found_device = hid_open_path(cur_dev->path);
+      found = true;
+
+      goto output;
+    }
+  }
+
+output:
+  hid_free_enumeration(devs);
+  if (!found_device) {
+    if (found) {
+      fail("Failed to open device usage (%d) and usage page (%d): %ls",
+           expected_usage, expected_usage_page, hid_error(found_device));
+    } else {
+      fail("Can't find device with usage (%d) and usage page (%d): %ls",
+           expected_usage, expected_usage_page, hid_error(found_device));
+    }
+    hid_exit();
+
+    return NULL;
+  } else {
+    return found_device;
+  }
+}
+
+uint32_t read_next(uint32_t usage, uint32_t usage_page) {
+  hid_device *handle;
+
+  if (hid_init()) {
+    fail("Failed to init hid");
+  }
+
+  handle = find_device_by_usage(usage, usage_page);
+
+  uint8_t buf[4];
+  int res = 0;
+  res = hid_read(handle, buf, sizeof(buf));
+  if (res < 0) {
+    fail("Failed to read 4 bytes from hid device: %ls", hid_error(handle));
+  } else if (res == 0) {
+    fail("Device did not send anything!");
+  }
+
+  hid_close(handle);
+  if (hid_exit()) {
+    fail("Failed to exit hid");
+  }
+
+  // Buf should only contain positive values.
+  assert(buf[0] >= 0 && buf[1] >= 0 && buf[2] >= 0 && buf[3] >= 0);
+
+  // Only lower Endian supported, TODO add the other one
+  uint32_t key_code =
+      (uint32_t)(buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24));
+
+  return key_code;
+}
diff --git a/rust/qmk-hid-com/src_c/src/hid/hid.h b/rust/qmk-hid-com/src_c/src/hid/hid.h
new file mode 100644
index 0000000..9ffb9d3
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/src/hid/hid.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <stdint.h>
+
+uint32_t read_next(uint32_t usage, uint32_t usage_page);
diff --git a/rust/qmk-hid-com/src_c/src/keyboard/keyboard.c b/rust/qmk-hid-com/src_c/src/keyboard/keyboard.c
new file mode 100644
index 0000000..4949085
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/src/keyboard/keyboard.c
@@ -0,0 +1,135 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "virtual-keyboard-unstable-v1-client-protocol.h"
+
+#include "../error.h"
+#include "keyboard.h"
+
+
+static void handle_wl_event(void *data, struct wl_registry *registry,
+                            uint32_t name, const char *interface,
+                            uint32_t version);
+static void handle_wl_event_remove(void *data, struct wl_registry *registry,
+                                   uint32_t name);
+
+const struct wl_registry_listener registry_listener = {
+    .global = handle_wl_event,
+    .global_remove = handle_wl_event_remove,
+};
+
+struct qmk_unicode_type init_qmk_unicode() {
+  struct qmk_unicode_type qmk_unicode_type;
+
+  memset(&qmk_unicode_type, 0, sizeof(qmk_unicode_type));
+  qmk_unicode_type.display = wl_display_connect(NULL);
+
+  if (qmk_unicode_type.display == NULL) {
+    fail("Wayland connection failed");
+  }
+
+  qmk_unicode_type.registry = wl_display_get_registry(qmk_unicode_type.display);
+
+  wl_registry_add_listener(qmk_unicode_type.registry, &registry_listener,
+                           &qmk_unicode_type);
+  wl_display_dispatch(qmk_unicode_type.display);
+  wl_display_roundtrip(qmk_unicode_type.display);
+
+  if (qmk_unicode_type.manager == NULL) {
+    fail("Compositor does not support the virtual keyboard protocol");
+  }
+  if (qmk_unicode_type.seat == NULL) {
+    fail("No seat found");
+  }
+
+  qmk_unicode_type.keyboard =
+      zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
+          qmk_unicode_type.manager, qmk_unicode_type.seat);
+
+  return qmk_unicode_type;
+}
+void destroy_qmk_unicode(struct qmk_unicode_type qmk_unicode_type) {
+  zwp_virtual_keyboard_v1_destroy(qmk_unicode_type.keyboard);
+  zwp_virtual_keyboard_manager_v1_destroy(qmk_unicode_type.manager);
+  wl_registry_destroy(qmk_unicode_type.registry);
+  wl_display_disconnect(qmk_unicode_type.display);
+}
+
+static void upload_keymap(struct qmk_unicode_type *qmk_unicode_type,
+                          uint32_t key_code);
+
+void type_keycode(struct qmk_unicode_type *qmk_unicode_type,
+                  uint32_t key_code) {
+  upload_keymap(qmk_unicode_type, key_code);
+
+  zwp_virtual_keyboard_v1_key(qmk_unicode_type->keyboard, 0, 1,
+                              WL_KEYBOARD_KEY_STATE_PRESSED);
+  wl_display_roundtrip(qmk_unicode_type->display);
+  usleep(2000);
+  zwp_virtual_keyboard_v1_key(qmk_unicode_type->keyboard, 0, 1,
+                              WL_KEYBOARD_KEY_STATE_RELEASED);
+  wl_display_roundtrip(qmk_unicode_type->display);
+  usleep(2000);
+}
+
+static void handle_wl_event(void *data, struct wl_registry *registry,
+                            uint32_t name, const char *interface,
+                            uint32_t version) {
+  struct qmk_unicode_type *qmk_unicode_type = (struct qmk_unicode_type *)data;
+
+  if (!strcmp(interface, wl_seat_interface.name)) {
+    qmk_unicode_type->seat = (struct wl_seat *)wl_registry_bind(
+        registry, name, &wl_seat_interface, version <= 7 ? version : 7);
+  } else if (!strcmp(interface,
+                     zwp_virtual_keyboard_manager_v1_interface.name)) {
+    qmk_unicode_type->manager =
+        (struct zwp_virtual_keyboard_manager_v1 *)wl_registry_bind(
+            registry, name, &zwp_virtual_keyboard_manager_v1_interface, 1);
+  }
+}
+
+static void
+handle_wl_event_remove(__attribute__((unused)) void *data,
+                       __attribute__((unused)) struct wl_registry *_registry,
+                       __attribute__((unused)) uint32_t _name) {}
+
+static void upload_keymap(struct qmk_unicode_type *qmk_unicode_type,
+                          uint32_t key_code) {
+  char filename[] = "/tmp/qmk_unicode_type-XXXXXX";
+  int fd = mkstemp(filename);
+  if (fd < 0) {
+    fail("Failed to create the temporary keymap file");
+  }
+  unlink(filename);
+  FILE *f = fdopen(fd, "w");
+
+  fprintf(f,
+          "xkb_keymap {\n"
+          "  xkb_keycodes \"(unnamed)\" {\n"
+          "      minimum = 8;\n"
+          "      maximum = 10;\n"
+          "      <K1> = 9;\n"
+          "  };\n"
+          // TODO: Is including "complete" here really a good idea?
+          "  xkb_types \"(unnamed)\" { include \"complete\" };\n"
+          "  xkb_compatibility \"(unnamed)\" { include \"complete\" };\n"
+          "  xkb_symbols \"(unnamed)\" {\n"
+          "      key <K1> {[U%X]};\n"
+          "  };\n"
+          "};\n",
+          key_code);
+
+  fputc('\0', f);
+  fflush(f);
+
+  uint32_t keymap_size = (uint32_t)ftell(f);
+  zwp_virtual_keyboard_v1_keymap(qmk_unicode_type->keyboard,
+                                 WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, fileno(f),
+                                 keymap_size);
+
+  wl_display_roundtrip(qmk_unicode_type->display);
+  fclose(f);
+}
diff --git a/rust/qmk-hid-com/src_c/src/keyboard/keyboard.h b/rust/qmk-hid-com/src_c/src/keyboard/keyboard.h
new file mode 100644
index 0000000..ac5d7bd
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/src/keyboard/keyboard.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <stdint.h>
+
+struct qmk_unicode_type init_qmk_unicode();
+void destroy_qmk_unicode(struct qmk_unicode_type qmk_unicode_type);
+
+void type_keycode(struct qmk_unicode_type *qmk_unicode_type, uint32_t key_code);
+
+// Private
+struct qmk_unicode_type {
+  struct wl_display *display;
+  struct wl_registry *registry;
+  struct wl_seat *seat;
+  struct zwp_virtual_keyboard_manager_v1 *manager;
+  struct zwp_virtual_keyboard_v1 *keyboard;
+};
diff --git a/rust/qmk-hid-com/src_c/src/keyboard/virtual-keyboard-unstable-v1-client-protocol.h b/rust/qmk-hid-com/src_c/src/keyboard/virtual-keyboard-unstable-v1-client-protocol.h
new file mode 100644
index 0000000..81cac0b
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/src/keyboard/virtual-keyboard-unstable-v1-client-protocol.h
@@ -0,0 +1,280 @@
+/* Generated by wayland-scanner 1.23.1 */
+
+#ifndef VIRTUAL_KEYBOARD_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define VIRTUAL_KEYBOARD_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_virtual_keyboard_unstable_v1 The virtual_keyboard_unstable_v1 protocol
+ * @section page_ifaces_virtual_keyboard_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_virtual_keyboard_v1 - virtual keyboard
+ * - @subpage page_iface_zwp_virtual_keyboard_manager_v1 - virtual keyboard manager
+ * @section page_copyright_virtual_keyboard_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2008-2011  Kristian Høgsberg
+ * Copyright © 2010-2013  Intel Corporation
+ * Copyright © 2012-2013  Collabora, Ltd.
+ * Copyright © 2018       Purism SPC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_seat;
+struct zwp_virtual_keyboard_manager_v1;
+struct zwp_virtual_keyboard_v1;
+
+#ifndef ZWP_VIRTUAL_KEYBOARD_V1_INTERFACE
+#define ZWP_VIRTUAL_KEYBOARD_V1_INTERFACE
+/**
+ * @page page_iface_zwp_virtual_keyboard_v1 zwp_virtual_keyboard_v1
+ * @section page_iface_zwp_virtual_keyboard_v1_desc Description
+ *
+ * The virtual keyboard provides an application with requests which emulate
+ * the behaviour of a physical keyboard.
+ *
+ * This interface can be used by clients on its own to provide raw input
+ * events, or it can accompany the input method protocol.
+ * @section page_iface_zwp_virtual_keyboard_v1_api API
+ * See @ref iface_zwp_virtual_keyboard_v1.
+ */
+/**
+ * @defgroup iface_zwp_virtual_keyboard_v1 The zwp_virtual_keyboard_v1 interface
+ *
+ * The virtual keyboard provides an application with requests which emulate
+ * the behaviour of a physical keyboard.
+ *
+ * This interface can be used by clients on its own to provide raw input
+ * events, or it can accompany the input method protocol.
+ */
+extern const struct wl_interface zwp_virtual_keyboard_v1_interface;
+#endif
+#ifndef ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_INTERFACE
+#define ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_INTERFACE
+/**
+ * @page page_iface_zwp_virtual_keyboard_manager_v1 zwp_virtual_keyboard_manager_v1
+ * @section page_iface_zwp_virtual_keyboard_manager_v1_desc Description
+ *
+ * A virtual keyboard manager allows an application to provide keyboard
+ * input events as if they came from a physical keyboard.
+ * @section page_iface_zwp_virtual_keyboard_manager_v1_api API
+ * See @ref iface_zwp_virtual_keyboard_manager_v1.
+ */
+/**
+ * @defgroup iface_zwp_virtual_keyboard_manager_v1 The zwp_virtual_keyboard_manager_v1 interface
+ *
+ * A virtual keyboard manager allows an application to provide keyboard
+ * input events as if they came from a physical keyboard.
+ */
+extern const struct wl_interface zwp_virtual_keyboard_manager_v1_interface;
+#endif
+
+#ifndef ZWP_VIRTUAL_KEYBOARD_V1_ERROR_ENUM
+#define ZWP_VIRTUAL_KEYBOARD_V1_ERROR_ENUM
+enum zwp_virtual_keyboard_v1_error {
+	/**
+	 * No keymap was set
+	 */
+	ZWP_VIRTUAL_KEYBOARD_V1_ERROR_NO_KEYMAP = 0,
+};
+#endif /* ZWP_VIRTUAL_KEYBOARD_V1_ERROR_ENUM */
+
+#define ZWP_VIRTUAL_KEYBOARD_V1_KEYMAP 0
+#define ZWP_VIRTUAL_KEYBOARD_V1_KEY 1
+#define ZWP_VIRTUAL_KEYBOARD_V1_MODIFIERS 2
+#define ZWP_VIRTUAL_KEYBOARD_V1_DESTROY 3
+
+
+/**
+ * @ingroup iface_zwp_virtual_keyboard_v1
+ */
+#define ZWP_VIRTUAL_KEYBOARD_V1_KEYMAP_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_virtual_keyboard_v1
+ */
+#define ZWP_VIRTUAL_KEYBOARD_V1_KEY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_virtual_keyboard_v1
+ */
+#define ZWP_VIRTUAL_KEYBOARD_V1_MODIFIERS_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_virtual_keyboard_v1
+ */
+#define ZWP_VIRTUAL_KEYBOARD_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_virtual_keyboard_v1 */
+static inline void
+zwp_virtual_keyboard_v1_set_user_data(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, void *user_data)
+{
+	wl_proxy_set_user_data((struct wl_proxy *) zwp_virtual_keyboard_v1, user_data);
+}
+
+/** @ingroup iface_zwp_virtual_keyboard_v1 */
+static inline void *
+zwp_virtual_keyboard_v1_get_user_data(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1)
+{
+	return wl_proxy_get_user_data((struct wl_proxy *) zwp_virtual_keyboard_v1);
+}
+
+static inline uint32_t
+zwp_virtual_keyboard_v1_get_version(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1)
+{
+	return wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1);
+}
+
+/**
+ * @ingroup iface_zwp_virtual_keyboard_v1
+ *
+ * Provide a file descriptor to the compositor which can be
+ * memory-mapped to provide a keyboard mapping description.
+ *
+ * Format carries a value from the keymap_format enumeration.
+ */
+static inline void
+zwp_virtual_keyboard_v1_keymap(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t format, int32_t fd, uint32_t size)
+{
+	wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_v1,
+			 ZWP_VIRTUAL_KEYBOARD_V1_KEYMAP, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1), 0, format, fd, size);
+}
+
+/**
+ * @ingroup iface_zwp_virtual_keyboard_v1
+ *
+ * A key was pressed or released.
+ * The time argument is a timestamp with millisecond granularity, with an
+ * undefined base. All requests regarding a single object must share the
+ * same clock.
+ *
+ * Keymap must be set before issuing this request.
+ *
+ * State carries a value from the key_state enumeration.
+ */
+static inline void
+zwp_virtual_keyboard_v1_key(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t time, uint32_t key, uint32_t state)
+{
+	wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_v1,
+			 ZWP_VIRTUAL_KEYBOARD_V1_KEY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1), 0, time, key, state);
+}
+
+/**
+ * @ingroup iface_zwp_virtual_keyboard_v1
+ *
+ * Notifies the compositor that the modifier and/or group state has
+ * changed, and it should update state.
+ *
+ * The client should use wl_keyboard.modifiers event to synchronize its
+ * internal state with seat state.
+ *
+ * Keymap must be set before issuing this request.
+ */
+static inline void
+zwp_virtual_keyboard_v1_modifiers(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
+{
+	wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_v1,
+			 ZWP_VIRTUAL_KEYBOARD_V1_MODIFIERS, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1), 0, mods_depressed, mods_latched, mods_locked, group);
+}
+
+/**
+ * @ingroup iface_zwp_virtual_keyboard_v1
+ */
+static inline void
+zwp_virtual_keyboard_v1_destroy(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1)
+{
+	wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_v1,
+			 ZWP_VIRTUAL_KEYBOARD_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1), WL_MARSHAL_FLAG_DESTROY);
+}
+
+#ifndef ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_ERROR_ENUM
+#define ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_ERROR_ENUM
+enum zwp_virtual_keyboard_manager_v1_error {
+	/**
+	 * client not authorized to use the interface
+	 */
+	ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_ERROR_UNAUTHORIZED = 0,
+};
+#endif /* ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_ERROR_ENUM */
+
+#define ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_CREATE_VIRTUAL_KEYBOARD 0
+
+
+/**
+ * @ingroup iface_zwp_virtual_keyboard_manager_v1
+ */
+#define ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_CREATE_VIRTUAL_KEYBOARD_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_virtual_keyboard_manager_v1 */
+static inline void
+zwp_virtual_keyboard_manager_v1_set_user_data(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1, void *user_data)
+{
+	wl_proxy_set_user_data((struct wl_proxy *) zwp_virtual_keyboard_manager_v1, user_data);
+}
+
+/** @ingroup iface_zwp_virtual_keyboard_manager_v1 */
+static inline void *
+zwp_virtual_keyboard_manager_v1_get_user_data(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1)
+{
+	return wl_proxy_get_user_data((struct wl_proxy *) zwp_virtual_keyboard_manager_v1);
+}
+
+static inline uint32_t
+zwp_virtual_keyboard_manager_v1_get_version(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1)
+{
+	return wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_manager_v1);
+}
+
+/** @ingroup iface_zwp_virtual_keyboard_manager_v1 */
+static inline void
+zwp_virtual_keyboard_manager_v1_destroy(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1)
+{
+	wl_proxy_destroy((struct wl_proxy *) zwp_virtual_keyboard_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_virtual_keyboard_manager_v1
+ *
+ * Creates a new virtual keyboard associated to a seat.
+ *
+ * If the compositor enables a keyboard to perform arbitrary actions, it
+ * should present an error when an untrusted client requests a new
+ * keyboard.
+ */
+static inline struct zwp_virtual_keyboard_v1 *
+zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1, struct wl_seat *seat)
+{
+	struct wl_proxy *id;
+
+	id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_manager_v1,
+			 ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_CREATE_VIRTUAL_KEYBOARD, &zwp_virtual_keyboard_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_manager_v1), 0, seat, NULL);
+
+	return (struct zwp_virtual_keyboard_v1 *) id;
+}
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif
diff --git a/rust/qmk-hid-com/src_c/src/main.c b/rust/qmk-hid-com/src_c/src/main.c
new file mode 100644
index 0000000..48d490e
--- /dev/null
+++ b/rust/qmk-hid-com/src_c/src/main.c
@@ -0,0 +1,28 @@
+#include <stdlib.h>
+
+#include "error.h"
+#include "hid/hid.h"
+#include "keyboard/keyboard.h"
+
+int main(int argc, const char *argv[]) {
+  if (argc != 3) {
+    fail("Usage: %s <usage> <usage_page>", argv[0]);
+  }
+
+  char *ep;
+  uint32_t usage = (uint32_t)strtoul(argv[1], &ep, 0);
+  if (*ep != 0) {
+    fail("Invalid usage: %x", usage);
+  }
+
+  uint32_t usage_page = (uint32_t)strtoul(argv[2], &ep, 0);
+  if (*ep != 0) {
+    fail("Invalid usage: %x", usage_page);
+  }
+
+  uint32_t key_value = read_next(usage, usage_page);
+
+  struct qmk_unicode_type qmk_unicode_type = init_qmk_unicode();
+  type_keycode(&qmk_unicode_type, key_value);
+  destroy_qmk_unicode(qmk_unicode_type);
+}