| 1 |
const std = @import("std");
|
| 2 |
const xlib = @import("../x11/xlib.zig");
|
| 3 |
const config = @import("../config/config.zig");
|
| 4 |
|
| 5 |
pub const max_chord_len: u8 = 4;
|
| 6 |
/// How long (in milliseconds) the user has between key presses within
|
| 7 |
/// a chord before the sequence is abandoned and state is reset.
|
| 8 |
pub const timeout_ms: i64 = 1000;
|
| 9 |
|
| 10 |
/// Tracks the in-progress key-chord sequence.
|
| 11 |
///
|
| 12 |
/// Owned by `WindowManager`. Call `update` on every key press; it returns
|
| 13 |
/// whether the sequence is still live. Call `reset` to abandon the
|
| 14 |
/// current sequence and release the keyboard grab if one is held.
|
| 15 |
pub const ChordState = struct {
|
| 16 |
keys: [max_chord_len]config.Key_Press = .{config.Key_Press{}} ** max_chord_len,
|
| 17 |
index: u8 = 0,
|
| 18 |
last_timestamp: i64 = 0,
|
| 19 |
keyboard_grabbed: bool = false,
|
| 20 |
|
| 21 |
/// Push a new key press onto the sequence and update the timestamp.
|
| 22 |
///
|
| 23 |
/// Returns `false` if the sequence is already at maximum length, in which
|
| 24 |
/// case the caller should call `reset` before retrying.
|
| 25 |
pub fn push(self: *ChordState, key: config.Key_Press) bool {
|
| 26 |
if (self.index >= max_chord_len) return false;
|
| 27 |
self.keys[self.index] = key;
|
| 28 |
self.index += 1;
|
| 29 |
self.last_timestamp = std.time.milliTimestamp();
|
| 30 |
|
| 31 |
return true;
|
| 32 |
}
|
| 33 |
|
| 34 |
/// Returns true if the sequence has timed out and should be reset.
|
| 35 |
pub fn is_timed_out(self: *const ChordState) bool {
|
| 36 |
if (self.index == 0) return false;
|
| 37 |
return (std.time.milliTimestamp() - self.last_timestamp) >= timeout_ms;
|
| 38 |
}
|
| 39 |
|
| 40 |
/// Clears the sequence and releases the keyboard grab if one is held.
|
| 41 |
///
|
| 42 |
/// `display` may be null only during early startup before the connection
|
| 43 |
/// is open, in normal operation it should always be provided.
|
| 44 |
pub fn reset(self: *ChordState, display: ?*xlib.Display) void {
|
| 45 |
self.keys = .{config.Key_Press{}} ** max_chord_len;
|
| 46 |
self.index = 0;
|
| 47 |
self.last_timestamp = 0;
|
| 48 |
|
| 49 |
if (self.keyboard_grabbed) {
|
| 50 |
if (display) |dpy| {
|
| 51 |
_ = xlib.XUngrabKeyboard(dpy, xlib.CurrentTime);
|
| 52 |
}
|
| 53 |
self.keyboard_grabbed = false;
|
| 54 |
}
|
| 55 |
}
|
| 56 |
|
| 57 |
/// Try to grab the keyboard for exclusive input during a partial match.
|
| 58 |
///
|
| 59 |
/// Sets `keyboard_grabbed` on success. Safe to call repeatedly,
|
| 60 |
/// does nothing if already grabbed.
|
| 61 |
pub fn grab_keyboard(self: *ChordState, display: *xlib.Display, root: xlib.Window) void {
|
| 62 |
if (self.keyboard_grabbed) return;
|
| 63 |
|
| 64 |
const result = xlib.XGrabKeyboard(display, root, xlib.True, xlib.GrabModeAsync, xlib.GrabModeAsync, xlib.CurrentTime);
|
| 65 |
if (result == xlib.GrabSuccess) {
|
| 66 |
self.keyboard_grabbed = true;
|
| 67 |
}
|
| 68 |
}
|
| 69 |
};
|