roundabout,
created on Tuesday, 28 October 2025, 19:16:17 (1761678977),
received on Tuesday, 28 October 2025, 20:31:10 (1761683470)
Author identity: Vlad <vlad.muntoiu@gmail.com>
151609d1ccf5d077cf0fbd53b547bdb0433de49b
main.py
@@ -13,7 +13,7 @@ CDLL("libgtk4-layer-shell.so")
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Gtk4LayerShell", "1.0")
from gi.repository import Gtk, Gdk, GObject, Gtk4LayerShell
from gi.repository import Gtk, Gdk, GObject, Gtk4LayerShell, GLib, Gio
from pywayland.client import Display, EventQueue
from pywayland.protocol.wayland import WlRegistry, WlSeat
@@ -192,21 +192,33 @@ class Plane(Gtk.Grid):
def make_xkb_keymap_from_unicodes(unicodes):
body_lines = [
" key <KFFFE> { [ BackSpace ] };",
" key <KFFFF> { [ Return ] };",
symbols = [
"key <SP00> { [ BackSpace ] };",
"key <SP01> { [ Return ] };",
]
keycodes = [
"<SP00> = 4094;",
"<SP01> = 4095;",
]
for i, char in enumerate(unicodes):
keycode = 0x10000 + i
keycode = 0x1000 + i
keysym = f"U{ord(char):04X}"
body_lines.append(f" key <K{keycode:X}> {{ [ {keysym} ] }};")
symbols.append(f"key <K{i:03X}> {{ [ {keysym} ] }};")
keycodes.append(f"<K{i:03X}> = {keycode};")
return "xkb_keymap {\n xkb_keycodes \"kineboard\" { minimum = 8; maximum = 262144; };\n xkb_types \"kineboard\" {};\n xkb_compatibility \"kineboard\" {};\n xkb_symbols \"kineboard\" {\n" + "\n".join(body_lines) + "\n };\n};\n"
return ("xkb_keymap { xkb_keycodes \"kineboard\" { minimum = 8; maximum = 65535; "
+ " ".join(keycodes) + " }; xkb_symbols \"kineboard\" { " + " ".join(symbols)
+ " }; xkb_types \"kineboard\" {}; xkb_compatibility \"kineboard\" {}; };")
class Kineboard(Gtk.Application):
def __init__(self):
Gtk.Application.__init__(self, application_id="com.roundabout_host.roundabout.Kineboard")
Gtk.Application.__init__(
self,
application_id="com.roundabout_host.roundabout.Kineboard",
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE
)
self.character_index = {}
self.keymap_fd = None
self.vk = None
self.keymap_text = ""
@@ -222,6 +234,22 @@ class Kineboard(Gtk.Application):
self.seat = None
self.vkm = None
self.serial = 0
self.add_main_option(
"invoke",
ord("i"),
GLib.OptionFlags.NONE,
GLib.OptionArg.NONE,
"Manually bring up the keyboard",
None
)
self.add_main_option(
"hide",
ord("h"),
GLib.OptionFlags.NONE,
GLib.OptionArg.NONE,
"Hide the keyboard",
None
)
def do_startup(self):
Gtk.Application.do_startup(self)
@@ -269,14 +297,14 @@ class Kineboard(Gtk.Application):
backspace_button = Gtk.Button(child=Gtk.Image.new_from_icon_name("go-previous-symbolic"))
backspace_button.set_size_request(48, -1)
backspace_button.update_property([Gtk.AccessibleProperty.LABEL], ["Backspace"])
backspace_button.connect("clicked", lambda x: self.send_keysym(14))
backspace_button.connect("clicked", lambda x: self.send_keysym(0xFF6))
controls.append(backspace_button)
enter_button = Gtk.Button(child=Gtk.Image.new_from_icon_name("keyboard-enter-symbolic"))
enter_button.set_size_request(48, -1)
enter_button.update_property([Gtk.AccessibleProperty.LABEL], ["Enter"])
enter_button.add_css_class("suggested-action")
enter_button.connect("clicked", lambda x: self.send_keysym(28))
enter_button.connect("clicked", lambda x: self.send_keysym(0xFF7))
controls.append(enter_button)
for i, layer in enumerate(data["keys"]):
@@ -310,8 +338,10 @@ class Kineboard(Gtk.Application):
self.input_method.dispatcher["activate"] = self.activate_input
self.input_method.dispatcher["deactivate"] = self.deactivate_input
with open(module_directory / "ascii.xkb") as f:
self.keymap_text = f.read()
self.keymap_text = make_xkb_keymap_from_unicodes(self.all_characters)
self.character_index = {}
for i, character in enumerate(self.all_characters):
self.character_index[character] = i
print(self.keymap_text)
self.vk = self.vkm.create_virtual_keyboard(self.seat)
@@ -323,15 +353,18 @@ class Kineboard(Gtk.Application):
def activate_input(self, im, context):
self.window.present()
self.context = context
self.context.dispatcher["commit_state"] = self.commit_state
if self.context:
self.context.dispatcher["commit_state"] = self.commit_state
def commit_state(self, context, serial):
self.serial = serial
def deactivate_input(self, im, context):
self.window.set_visible(False)
self.context = None
self.serial = 0
if self.context:
self.context.destroy()
self.context = None
self.serial = 0
def on_global(self, registry, name, interface, version):
if interface == "zwp_input_method_v1":
@@ -351,29 +384,21 @@ class Kineboard(Gtk.Application):
self.vk.key(clock + 1, key, 0)
def typed(self, button, character):
if self.context:
self.context.commit_string(self.serial, character)
def backspace(self):
if self.context:
self.context.delete_surrounding_text(-1, 1)
def enter(self):
if self.context:
print(self.serial)
self.context.keysym(self.serial, int(time.monotonic() * 1000), 0xff0d, 1, 0)
self.context.commit_string(self.serial, "")
self.wl_display.roundtrip()
print(self.serial)
self.context.keysym(self.serial, int(time.monotonic() * 1000), 0xff0d, 0, 0)
self.context.commit_string(self.serial, "")
self.wl_display.roundtrip()
print(self.serial)
self.send_keysym(0xFF8 + self.character_index[character])
def do_activate(self):
Gtk.Application.do_activate(self)
# self.window.present()
def do_command_line(self, command_line: Gio.ApplicationCommandLine):
options = command_line.get_options_dict()
args = command_line.get_arguments()[1:]
if options.contains("invoke"):
self.activate_input(None, None)
elif options.contains("hide"):
self.deactivate_input(None, None)
return 0
if __name__ == "__main__":
kineboard = Kineboard()