Skip to content

Commit

Permalink
Display modifier key combinations
Browse files Browse the repository at this point in the history
  • Loading branch information
adamws committed Dec 5, 2024
1 parent e48c224 commit cede78c
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 33 deletions.
90 changes: 80 additions & 10 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,62 @@ pub const KeyData = extern struct {
pressed: bool,
keycode: u8,
keysym: c_ulong,
string: [32]u8,
string: [16]u8,

comptime {
// this type is is copied a lot, keep it small
std.debug.assert(@sizeOf(KeyData) <= 64);
}
};

const Modifier = enum {
ctrl,
alt,
super,
hyper,
shift,
alt_gr,

// order determines the order of concatenation when constructing pressed key combo string

const ModifierLookupKV = struct { []const u8, Modifier };
const lookup_slice = [_]ModifierLookupKV{
// zig fmt: off
.{ "Control_L", .ctrl }, .{ "Control_R", .ctrl },
.{ "Alt_L", .alt }, .{ "Alt_R", .alt }, .{ "Meta_L", .alt }, .{ "Meta_R", .alt },
.{ "Super_L", .super }, .{ "Super_R", .super },
.{ "Hyper_L", .hyper }, .{ "Hyper_R", .hyper },
.{ "Shift_L", .shift }, .{ "Shift_R", .shift },
.{ "ISO_Level3_Shift", .alt_gr },
// zig fmt: on
};
const lookup = std.StaticStringMap(Modifier).initComptime(lookup_slice);

pub fn fromKeySymbol(value: []const u8) ?Modifier {
// Ignore Alt_R on windows, otherwise polish diacritics will be displayed like this: `Alt+ą`.
// On linux it works ok because right alt is reported as `ISO_Level3_Shift`
// for which we do not define prefix. This might be a problem with keyboard layout handling.
if (builtin.target.os.tag == .windows and std.mem.eql(u8, value, "Alt_R")) return null;
return lookup.get(value);
}

pub fn prefix(self: Modifier) []const u8 {
return switch (self) {
.ctrl => "Ctrl+",
.alt => "Alt+",
.super => "Super+",
.hyper => "Hyper+",
.shift => "",
.alt_gr => "",
};
}
};

const TypingDisplay = struct {
string_buffer: CountingStringRingBuffer(capacity, max_string_len) = .{},
active_modifiers: Modifiers = Modifiers.initEmpty(),

const Modifiers = std.EnumSet(Modifier);

const capacity = 256; // size of key representations history
const max_string_len = 32; // maximal length of string representation of key (or key combo)
Expand All @@ -145,36 +191,60 @@ const TypingDisplay = struct {
const max_repeat_indicator = std.fmt.comptimePrint("…{}×", .{max_repeat});

pub fn update(self: *TypingDisplay, key: KeyData, backspace_mode: BackspaceMode) void {
const symbol = keySymbol(key);
if (symbol == null) return;
const symbol = keySymbol(key) orelse return;
std.debug.print("Symbol: {s}\n", .{symbol});

std.debug.print("Symbol: {s}\n", .{symbol.?});
if (Modifier.fromKeySymbol(symbol)) |modifier| {
self.active_modifiers.setPresent(modifier, key.pressed);
}

if (key.pressed == false) return;

if (backspace_mode == .full and std.mem.eql(u8, symbol.?, "BackSpace")) {
if (backspace_mode == .full and std.mem.eql(u8, symbol, "BackSpace")) {
self.string_buffer.backspace();
} else {
self.push(key, symbol.?);
self.push(key, symbol);
}
}

fn push(self: *TypingDisplay, key: KeyData, key_symbol: [:0]const u8) void {
var text_: [*:0]const u8 = undefined;
var index: usize = 0;
var string: [max_string_len]u8 = .{0} ** max_string_len;

inline for (@typeInfo(Modifier).Enum.fields) |enumField| {
const modifier: Modifier = @enumFromInt(enumField.value);
if (self.active_modifiers.contains(modifier)) {
const prefix = modifier.prefix();
std.mem.copyForwards(u8, string[index..string.len], prefix);
index += prefix.len;
}
}

const tmp = index;
if (symbols_lookup.get(key_symbol)) |lookup| {
std.debug.print("Replacement: '{s}'\n", .{lookup});
text_ = lookup;
std.mem.copyForwards(u8, string[index..string.len], lookup);
index += lookup.len;
} else {
text_ = @ptrCast(&key.string);
var j: usize = 0;
while(key.string[j] != 0) : ({ index += 1; j += 1; }) {
string[index] = key.string[j];
}
}
std.debug.assert(index < max_string_len);

// check if anything has been appended to string buffer,
// avoids displaying only modifiers
if (tmp != index) {
self.string_buffer.push(string[0..index]) catch unreachable;
}
self.string_buffer.push(std.mem.sliceTo(text_, 0)) catch unreachable;
}

fn keySymbol(key: KeyData) ?[:0]const u8 {
if (backend.keysymToString(key.keysym)) |symbol| {
return std.mem.sliceTo(symbol, 0);
}
std.debug.print("No Symbol for {any}\n", .{key});
return null;
}

Expand Down
2 changes: 2 additions & 0 deletions src/symbols_lookup.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");

const SymbolsLookupKV = struct { []const u8, [:0]const u8 };
// TODO: symbols subsitution should be configurable
// TODO: should check if there are no too long lookups (currently max 16)
const symbols_lookup_slice = [_]SymbolsLookupKV{
// zig fmt: off
.{ "Escape", "Esc" },
Expand Down Expand Up @@ -68,3 +69,4 @@ const symbols_lookup_slice = [_]SymbolsLookupKV{
// zig fmt: on
};
pub const symbols_lookup = std.StaticStringMap([:0]const u8).initComptime(symbols_lookup_slice);

40 changes: 22 additions & 18 deletions src/win32.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ var hook: ?c.HHOOK = null;
fn keyEventToString(vk: u32, scan_code: u32, string: []u8) !void {
var keyboard_state: [256]u8 = .{0} ** 256;

const modifiers = [_]c_int{c.VK_SHIFT, c.VK_MENU, c.VK_CONTROL};
// intentionally ignore c.VK_CONTROL (modifiers are tracked in upper layer), makes it
// easier to display Ctrl+ key combos
const modifiers = [_]c_int{c.VK_SHIFT, c.VK_MENU};

for (modifiers) |m| {
const value: c_short = c.GetKeyState(m);
Expand All @@ -44,22 +46,24 @@ fn lowLevelKeyboardProc(nCode: c.INT, wParam: c.WPARAM, lParam: c.LPARAM) callco
var key: KeyData = std.mem.zeroInit(KeyData, .{});
key.keycode = @intCast(keyboard.vkCode);

keyEventToString(keyboard.vkCode, keyboard.scanCode, &key.string) catch {};
const extended: bool = ((keyboard.flags & c.LLKHF_EXTENDED) != 0);

var index = @as(c_ulong, @intCast(keyboard.scanCode));
if (extended) index += 0x100;
if (index >= kbd_en_vscname.len) {
index = 0;
}
key.keysym = index;

if (wParam == c.WM_KEYDOWN or wParam == c.WM_SYSKEYDOWN) {
key.pressed = true;

app_state_l.last_char_timestamp = std.time.timestamp();
keyEventToString(keyboard.vkCode, keyboard.scanCode, &key.string) catch {};
const extended: bool = ((keyboard.flags & c.LLKHF_EXTENDED) != 0);

var index = @as(c_ulong, @intCast(keyboard.scanCode));
if (extended) index += 0x100;
if (index >= kbd_en_vscname.len) {
index = 0;
if (key.string[0] != 0) {
// update only for keys which produce output,
// this will not include modifiers
app_state_l.last_char_timestamp = std.time.timestamp();
}
key.keysym = index;
std.debug.print("Pressed vk: '{}', scancode: '{}' extended: {}, string: '{s}', symbol: '{}'\n", .{
keyboard.vkCode, keyboard.scanCode, extended, std.mem.sliceTo(&key.string, 0), key.keysym
});
} else {
key.pressed = false;
}
Expand Down Expand Up @@ -106,9 +110,9 @@ pub fn keysymToString(keysym: c_ulong) [*c]const u8 {
const kbd_en_vscname = [_][]const u8 {
// zig fmt: off
"", "Escape", "", "", "", "", "", "", "", "", "", "", "", "", "BackSpace", "Tab",
"", "", "", "", "", "", "", "", "", "", "", "", "Return", "Ctrl", "", "",
"", "", "", "", "", "", "", "", "", "", "Shift", "", "", "", "", "",
"", "", "", "", "", "", "Right Shift", "KP_Multiply", "Alt", "space", "Caps_Lock", "F1", "F2", "F3", "F4", "F5",
"", "", "", "", "", "", "", "", "", "", "", "", "Return", "Control_L", "", "",
"", "", "", "", "", "", "", "", "", "", "Shift_L", "", "", "", "", "",
"", "", "", "", "", "", "Shift_R", "KP_Multiply", "Alt_L", "space", "Caps_Lock", "F1", "F2", "F3", "F4", "F5",
"F6", "F7", "F8", "F9", "F10", "Pause", "Scroll_Lock", "KP_7", "KP_8", "KP_9", "KP_Subtract", "KP_4", "KP_5", "KP_6", "KP_Add", "KP_1",
"KP_2", "KP_3", "KP_0", "KP_Separator", "Sys Req", "", "", "F11", "F12", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
Expand All @@ -123,9 +127,9 @@ const kbd_en_vscname = [_][]const u8 {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
// extended
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "KP_Enter", "Right Ctrl", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "KP_Enter", "Control_R", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "KP_Divide", "", "Print", "Right Alt", "", "", "", "", "", "", "",
"", "", "", "", "", "KP_Divide", "", "Print", "Alt_R", "", "", "", "", "", "", "",
"", "", "", "", "", "Num_Lock", "Break", "Home", "Up", "Prior", "", "Left", "", "Right", "", "End",
"Down", "Next", "Insert", "Delete", "<00>", "", "Help", "", "", "", "", "Super_L", "Super_R", "Application", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
Expand Down
18 changes: 13 additions & 5 deletions src/x11.zig
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ const X11InputContext = struct {
self: *X11InputContext,
device_event: *const x11.XIDeviceEvent,
key_data: *KeyData,
) c_int {
) void {
var buf: [16]u8 = .{0} ** 16;
var e: x11.XKeyPressedEvent = std.mem.zeroInit(
x11.XKeyPressedEvent,
.{
Expand All @@ -74,12 +75,19 @@ const X11InputContext = struct {
const len = x11.Xutf8LookupString(
self.xic,
&e,
&key_data.string,
32,
&buf,
buf.len,
&key_data.keysym,
&status,
);
return len;

if (len != x11.NoSymbol) {
switch(key_data.keysym) {
// avoid ctrl sequences, just take the character value
32...126 => key_data.string[0] = @truncate(key_data.keysym),
else => std.mem.copyForwards(u8, &key_data.string, &buf),
}
}
}
};

Expand Down Expand Up @@ -169,10 +177,10 @@ pub fn listener(app_state: *AppState, window_handle: *anyopaque) !void {

var key: KeyData = std.mem.zeroInit(KeyData, .{});
key.keycode = keycode;
input_ctx.lookupString(device_event, &key);

if (cookie.evtype == x11.XI_KeyPress) {
key.pressed = true;
_ = input_ctx.lookupString(device_event, &key);

if (key.string[0] != 0) {
// update only for keys which produce output,
Expand Down

0 comments on commit cede78c

Please sign in to comment.