keytrans

package module
v0.1.27 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 29, 2026 License: BSD-3-Clause Imports: 18 Imported by: 0

README

keytrans

keytrans is a pure-Go, zero-CGO, fallback-aware keyboard layout and text translation library for Unix windowing systems (X11 and Wayland).

Why keytrans?

In the Go desktop development ecosystem, handling localized keyboard input (e.g. typing Cyrillic, Greek, or Hanja) and detecting keyboard shortcuts is notoriously complex:

  1. Wrappers around libxkbcommon or libX11 is problematic on systems where ffi is unavailable.
  2. Pure-Go implementations ignore layout groups entirely, falling back to English QWERTY, or fail to handle modifiers like AltGr (Level 3 shift) and NumLock on the keypad.

keytrans bridges this gap. It provides a Unified Keyboard Translation Pipeline that automatically selects the most robust available backend on the host OS.

The Fallback Chain

When initializing an X11 translator, keytrans attempts the following backends in order:

┌──────────────────────────────────────────────┐
│ 1. libxkbcommon (FFI)                        │ -> Best. Native multi-layout, uses xkbcommon.
└──────────────────────┬───────────────────────┘
                       ▼ (fails or -tags noffi)
┌──────────────────────────────────────────────┐
│ 2. libX11 XIM (FFI)                          │ -> Native X11 input method (Xutf8LookupString).
└──────────────────────┬───────────────────────┘
                       ▼ (fails or -tags noffi)
┌──────────────────────────────────────────────┐
│ 3. purexkb (Pure Go)                         │ -> Resolves layout rules from the X server and compiles via xkb-go.
└──────────────────────┬───────────────────────┘
                       ▼ (fails or xkeyboard-config missing)
┌──────────────────────────────────────────────┐
│ 4. dynamicxkb (Pure Go)                      │ -> Reconstructs XKB keymap dynamically in Go memory.
└──────────────────────┬───────────────────────┘
                       ▼ (fails or error)
┌──────────────────────────────────────────────┐
│ 5. xkbcomp (Pure Go)                         │ -> Runs `xkbcomp $DISPLAY` and parses map with xkb-go.
└──────────────────────┬───────────────────────┘
                       ▼ (fails or xkbcomp missing)
┌──────────────────────────────────────────────┐
│ 6. Core X11 Heuristics (Pure Go)             │ -> Reverse-engineers ModMap & keypad.
└──────────────────────────────────────────────┘

If the system has no dynamic loading capabilities or if compiled with -tags noffi, keytrans gracefully falls back to purely Go-based parsing (dynamicxkb, xkbcomp or corex11 heuristics), keeping compilation 100% clean and portable.

Manual Backend Selection

While the automatic fallback chain is recommended for production, you can force the translator to use a specific backend during development, testing, or debugging.

To do this, specify the PreferredBackend field in OSInfo:

info := keytrans.OSInfo{
	XgbConn:          conn,
	WindowID:         uint32(winID),
	PreferredBackend: "corex11", // Force Core X11 heuristics
}

Supported backend strings:

  • "libxkbcommon" (requires libxkbcommon.so.0 and FFI support)
  • "libX11-XIM" (requires libX11.so.6 and FFI support)
  • "purexkb" (requires xkeyboard-config)
  • "dynamicxkb" (pure Go, adaptive on-the-fly reconstruction)
  • "xkbcomp" (requires xkbcomp binary available in $PATH)
  • "corex11" (pure Go fallback, always available)
Forcing via Environment Variable

Alternatively, you can force a specific backend globally using the KEYTRANS_BACKEND environment variable:

export KEYTRANS_BACKEND="corex11"
./my_app

Important: Unlike the programmatic PreferredBackend option, if the backend requested via KEYTRANS_BACKEND fails to initialize (e.g. if you request libxkbcommon but the shared library is missing), keytrans will immediately log a critical error and terminate the application (os.Exit(1)).

Installation

go get github.com/unxed/keytrans

Quick Start

1. Initializing the Translator (X11 example via XGB)
package main

import (
	"github.com/jezek/xgb"
	"github.com/unxed/keytrans"
)

func main() {
	// Connect to X server using XGB
	conn, _ := xgb.NewConn()
	defer conn.Close()

	// Setup OS-specific connection info
	info := keytrans.OSInfo{
		DisplayString:    ":0",
		XgbConn:          conn,
		WindowID:         uint32(winID), // Required for XIM (libX11) backend
		PreferredBackend: "",            // Optional: force a specific backend (e.g. "corex11")
	}

	// Create fallback-aware translator
	translator := keytrans.NewX11Translator(info)
	defer translator.Close()

	println("Active backend:", translator.Name())
}
2. Handling Key Events in your Loop

When you receive a raw KeyPress or KeyRelease event from your window manager, feed it directly to the translator:

// Inside your event loop
switch e := ev.(type) {
case xproto.KeyPressEvent:
    // Translate keycode & state into logical key and Unicode character
    event := translator.TranslateX11(e.Detail, e.State, true)

    if event.Char != 0 {
        fmt.Printf("User typed: %c\n", event.Char)
    }
    fmt.Printf("Virtual Key Code: 0x%X\n", event.VirtualKeyCode)
}
3. Handling Keyboard Layout Changes (MappingNotify)

When a user changes their keyboard layout settings (e.g., via setxkbmap or by plugging in a new keyboard), X11 sends a MappingNotify event. Since keytrans does not run its own event loop, the recommended approach is to handle this event in your application's loop and recreate the translator to reload the mapping:

switch e := ev.(type) {
case xproto.MappingNotifyEvent:
    // Recreate the translator to reload the updated mapping
    translator.Close()
    translator = keytrans.NewX11Translator(info)
}

Output Format: Win32-style Events

keytrans produces keyboard events using the unified winkeys.InputEvent structure, which follows the Win32 input model. This design decision was made for several practical reasons:

  1. Heritage: The library was originally written for the vtui project, which relies on this format.
  2. Industry Standard: The Win32 model is a well-known, simple, and unambiguous way to describe keyboard input. It provides a clean separation between the logical key (Virtual Key Code), the state of modifiers (Shift, Ctrl, Alt), and the resulting Unicode character. This makes event processing straightforward and predictable.
  3. Maximum Flexibility: You are not locked into the Win32 ecosystem. If you prefer to handle key logic using raw X11 keycodes, you can do so—simply use the Char field from the translated event to get the localized Unicode text and ignore the Virtual Key Code entirely.

Build Tags


If you want to completely disable FFI and ensure that your binary doesn't even attempt to dynamically load C libraries at runtime (e.g. for static scratch builds), build your project with the `noffi` tag:

```bash
go build -tags noffi .

This will strip out the libxkbcommon and libX11 backends, compiling only the pure Go implementations.

Compatibility

Important note for developers and AI models: We cannot rely on group information in X11 events, as some X servers always report 0. Additionally, we cannot rely on the Mod5 value in X11 events, because certain X servers set this flag for any group other than 0, regardless of whether AltGr is pressed. Please ensure all future development takes these factors into consideration.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type OSInfo

type OSInfo struct {
	DisplayString    string      // e.g., ":0" for X11, "wayland-0" for Wayland
	XgbConn          interface{} // *xgb.Conn (passed as interface{} to avoid forcing xgb dep if unused)
	WindowID         uint32      // Required for XIM (X Input Method) backend
	PreferredBackend string      // Force a specific translator backend ("libxkbcommon", "libX11-XIM", "xkbcomp", "corex11")
}

OSInfo provides context to the translator (e.g., connection handles).

type Translator

type Translator interface {
	// Name returns the name of the active backend (e.g., "libxkbcommon", "corex11").
	Name() string

	// TranslateX11 translates an X11 KeyPress/KeyRelease event.
	// detail: X11 keycode
	// state: X11 modifier state mask
	// isDown: true for KeyPress, false for KeyRelease
	TranslateX11(detail uint8, state uint16, isDown bool) winkeys.InputEvent

	// TranslateWayland translates a Wayland keyboard event.
	// Wayland splits modifier state updates from key events, so the translator
	// must maintain internal state.
	// keycode: evdev keycode
	// isDown: true for KeyPress, false for KeyRelease
	TranslateWayland(keycode uint32, isDown bool) winkeys.InputEvent

	// UpdateWaylandModifiers updates the internal state for Wayland.
	UpdateWaylandModifiers(modsDepressed, modsLatched, modsLocked, group uint32)

	// Close releases any resources held by the translator.
	Close()
}

Translator defines the interface for translating OS-specific raw keyboard events into unified winkeys.InputEvent structures.

func NewX11Translator

func NewX11Translator(info OSInfo) Translator

NewX11Translator attempts to initialize the best available X11 keyboard translator using a fallback chain.

Fallback order: 1. libxkbcommon (FFI) - Best, native multi-layout support, standard. 2. libX11 XIM (FFI) - Extremely robust for X11, handles complex IMEs. 3. xkbcomp (Pure Go) - Parses X server map using xkb-go. 4. Core X11 (Pure Go) - Reverse-engineers modifiers using smart heuristics.

Directories

Path Synopsis
cmd
keytrans-check command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL