Comments (32)
I've added macos support utilizing Karabiner-DriverKit-VirtualHIDDevice like kmonad.
So far it's working flawlessly with my configuration.
However, mouse support is not added.
You can try it using my fork. Just keep in mind that similar to linux, sudo is needed.
All devices are captured by default. To choose specific keyboards, use kanata -l
to list all connected keyboards.
Then use them with macos-dev
which works exactly like linux-dev
.
I updated that section of the documentation too.
@jtroo, I'd love to submit a PR if you don't mind missing the mouse support at first.
I think it wouldn't be an issue to add it later.
I am super excited and would love to hear feedback on this, seeing that I learned rust just to port kanata to macos!
from kanata.
Tests are fixed and macOS is now added to CI as well, for tests+clippy 🙂
from kanata.
Hey @nightscape,
I just found a solution for described strange behavior. I tested it on my macOS and it works.
I committed a dirty solution draft; I will finish this when I find the time.
tldr; CGEventTapPostEvent instead of CGEventPost
jborkowski@af5642f#diff-5839d342b266362459039b922c3c6bbce6565c86cedb63848e082b925e417e93R38
from kanata.
PR is merged! I'll have to figure out how to get builds using the GitHub actions (or if someone knows how, a PR would be welcome).
Additionally, the missing functionality is documented here:
https://github.com/jtroo/kanata/blob/main/docs/platform-known-issues.adoc
from kanata.
Can't believe I missed this macOS compatibility in December. I discovered it today, and I switch from kmonad to kanata today.
And, boy, it's amazing! My config file went from 500 lines to less than 100 (mainly because of all the layers I had to do for the repeat behavior). I love it ❤️
from kanata.
I would say that fork is not ready yet. Here, I simply pressed the 'j' key:
> ./target/release/kanata -c ./cfg_samples/mbp14.kbd
13:49:25 [INFO] kanata v1.4.0-prerelease-1 starting
13:49:25 [INFO] process unmapped keys: false
13:49:25 [INFO] NOTE: kanata was compiled to never allow cmd
13:49:25 [INFO] config parsed
13:49:25 [INFO] Sleeping for 2s. Please release all keys and don't press additional ones.
13:49:27 [INFO] entering the processing loop
13:49:27 [INFO] entering the event loop
13:49:27 [INFO] Init: catching only releases and sending immediately
13:49:27 [INFO] Starting kanata proper
kanata(7222,0x16bc73000) malloc: *** error for object 0x12d810e18: pointer being realloc'd was not allocated
kanata(7222,0x16bc73000) malloc: *** set a breakpoint in malloc_error_break to debug
zsh: abort ./target/release/kanata -c ./cfg_samples/mbp14.kbd
from kanata.
the problem is that macos requires a CGEventTapProxy
state pointer to be passed back when submitting events. These are given to the original event callback when kanata receives a keypress. rdev
drops this state here so we need to patch rdev to pass this value as well to our callback, or attach it to the Event
struct when built for macos
from kanata.
This crate may make this pretty simple to implement: https://github.com/Narsil/rdev
from kanata.
If you have any questions about the code or want some pointers, feel free to ask 🙂
from kanata.
@jtroo There will probably be a lot, I mostly use Clojure, Rust is quite new to me.
I think I'm starting to wrap my head around your code a little bit.
The best way would probably be to learn from its source and implement into your codebase using the same style, etc. But I think it would make sense to start by simply substituting the reading and writing part to rdev
's external api, and let kanata handle the remapping logic. But perhaps only in the case of MacOS?
It might work to stub it out completely for Linux and Windows as well, but first goal is just to get it working so I can get back to my real work without going crazy trying to adjust to not having the keys the same machines.
The interface for rdev looks like this for capturing:
#[cfg(feature = "unstable_grab")]
use rdev::{grab, Event, EventType, Key};
#[cfg(feature = "unstable_grab")]
let callback = |event: Event| -> Option<Event> {
if let EventType::KeyPress(Key::CapsLock) = event.event_type {
println!("Consuming and cancelling CapsLock");
None // CapsLock is now effectively disabled
}
else { Some(event) }
};
// This will block.
#[cfg(feature = "unstable_grab")]
if let Err(error) = grab(callback) {
println!("Error: {:?}", error)
}
And like this for writing:
use rdev::{simulate, Button, EventType, Key, SimulateError};
use std::{thread, time};
fn send(event_type: &EventType) {
let delay = time::Duration::from_millis(20);
match simulate(event_type) {
Ok(()) => (),
Err(SimulateError) => {
println!("We could not send {:?}", event_type);
}
}
// Let ths OS catchup (at least MacOS)
thread::sleep(delay);
}
send(&EventType::KeyPress(Key::KeyS));
send(&EventType::KeyRelease(Key::KeyS));
send(&EventType::MouseMove { x: 0.0, y: 0.0 });
send(&EventType::MouseMove { x: 400.0, y: 400.0 });
send(&EventType::ButtonPress(Button::Left));
send(&EventType::ButtonRelease(Button::Right));
send(&EventType::Wheel {
delta_x: 0,
delta_y: 1,
});```
from kanata.
Am I correct in thinking most of the work is going to be in kanata/mod.rs
?
from kanata.
Also, if you could point out to me where the input is captured and where the final keycodes are sent, that would be great. :)
from kanata.
kanata/mod.rs
shouldn't need to change very much.
There are three directories that handle most of the OS-specific code: kanata
, oskbd
, and keys
.
In the mod.rs
files you can see the conditional compilation.
Line 70 in 03a9627
You can add the following to the mod.rs
files for MacOS specific files:
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
pub use macos::*;
The keys
directory contains the mappings between OsCode
, keyberon's KeyCode
, and integers. The OsCode
s are the OS-specific numbers which are different on each platform, frustratingly enough. For each platform they get mapped to the same keyberon library's KeyCode
s. There's lots of code but it's pretty simple, though it is tedious. Using regular expression substitution should help :). The Windows one should be more useful to initally copy.
https://github.com/jtroo/kanata/blob/main/src/keys/windows.rs
The oskbd
directory contains the mechanisms for reading/writing OS codes. E.g. on Linux, evdev
is used to read/write from /dev/input
and /dev/uinput
.
https://github.com/jtroo/kanata/blob/main/src/oskbd/linux.rs
The kanata
directory contains the event loop which receives OS codes and sends them to the processing loop. Hopefully MacOS is more similar to Linux than Windows,
https://github.com/jtroo/kanata/blob/main/src/kanata/linux.rs
from kanata.
Thanks for the pointers!
It would seem that rdev
does not have all the keycodes that you have for windows and linux, but all the important-to-me ones.
use crate::rdev::Key;
use core_graphics::event::CGKeyCode;
use std::convert::TryInto;
/// Option
const ALT: CGKeyCode = 58;
/// Option_Right
const ALT_GR: CGKeyCode = 61;
const BACKSPACE: CGKeyCode = 51;
const CAPS_LOCK: CGKeyCode = 57;
/// Control Right does not exist on Mac
const CONTROL_LEFT: CGKeyCode = 59;
const DOWN_ARROW: CGKeyCode = 125;
const ESCAPE: CGKeyCode = 53;
const F1: CGKeyCode = 122;
const F10: CGKeyCode = 109;
const F11: CGKeyCode = 103;
const F12: CGKeyCode = 111;
const F2: CGKeyCode = 120;
const F3: CGKeyCode = 99;
const F4: CGKeyCode = 118;
const F5: CGKeyCode = 96;
const F6: CGKeyCode = 97;
const F7: CGKeyCode = 98;
const F8: CGKeyCode = 100;
const F9: CGKeyCode = 101;
const FUNCTION: CGKeyCode = 63;
const LEFT_ARROW: CGKeyCode = 123;
const META_LEFT: CGKeyCode = 55;
const META_RIGHT: CGKeyCode = 54;
const RETURN: CGKeyCode = 36;
const RIGHT_ARROW: CGKeyCode = 124;
const SHIFT_LEFT: CGKeyCode = 56;
const SHIFT_RIGHT: CGKeyCode = 60;
const SPACE: CGKeyCode = 49;
const TAB: CGKeyCode = 48;
const UP_ARROW: CGKeyCode = 126;
const BACK_QUOTE: CGKeyCode = 50;
const NUM1: CGKeyCode = 18;
const NUM2: CGKeyCode = 19;
const NUM3: CGKeyCode = 20;
const NUM4: CGKeyCode = 21;
const NUM5: CGKeyCode = 23;
const NUM6: CGKeyCode = 22;
const NUM7: CGKeyCode = 26;
const NUM8: CGKeyCode = 28;
const NUM9: CGKeyCode = 25;
const NUM0: CGKeyCode = 29;
const MINUS: CGKeyCode = 27;
const EQUAL: CGKeyCode = 24;
const KEY_Q: CGKeyCode = 12;
const KEY_W: CGKeyCode = 13;
const KEY_E: CGKeyCode = 14;
const KEY_R: CGKeyCode = 15;
const KEY_T: CGKeyCode = 17;
const KEY_Y: CGKeyCode = 16;
const KEY_U: CGKeyCode = 32;
const KEY_I: CGKeyCode = 34;
const KEY_O: CGKeyCode = 31;
const KEY_P: CGKeyCode = 35;
const LEFT_BRACKET: CGKeyCode = 33;
const RIGHT_BRACKET: CGKeyCode = 30;
const KEY_A: CGKeyCode = 0;
const KEY_S: CGKeyCode = 1;
const KEY_D: CGKeyCode = 2;
const KEY_F: CGKeyCode = 3;
const KEY_G: CGKeyCode = 5;
const KEY_H: CGKeyCode = 4;
const KEY_J: CGKeyCode = 38;
const KEY_K: CGKeyCode = 40;
const KEY_L: CGKeyCode = 37;
const SEMI_COLON: CGKeyCode = 41;
const QUOTE: CGKeyCode = 39;
const BACK_SLASH: CGKeyCode = 42;
const KEY_Z: CGKeyCode = 6;
const KEY_X: CGKeyCode = 7;
const KEY_C: CGKeyCode = 8;
const KEY_V: CGKeyCode = 9;
const KEY_B: CGKeyCode = 11;
const KEY_N: CGKeyCode = 45;
const KEY_M: CGKeyCode = 46;
const COMMA: CGKeyCode = 43;
const DOT: CGKeyCode = 47;
const SLASH: CGKeyCode = 44;
pub fn code_from_key(key: Key) -> Option<CGKeyCode> {
match key {
Key::Alt => Some(ALT),
Key::AltGr => Some(ALT_GR),
Key::Backspace => Some(BACKSPACE),
Key::CapsLock => Some(CAPS_LOCK),
Key::ControlLeft => Some(CONTROL_LEFT),
Key::DownArrow => Some(DOWN_ARROW),
Key::Escape => Some(ESCAPE),
Key::F1 => Some(F1),
Key::F10 => Some(F10),
Key::F11 => Some(F11),
Key::F12 => Some(F12),
Key::F2 => Some(F2),
Key::F3 => Some(F3),
Key::F4 => Some(F4),
Key::F5 => Some(F5),
Key::F6 => Some(F6),
Key::F7 => Some(F7),
Key::F8 => Some(F8),
Key::F9 => Some(F9),
Key::LeftArrow => Some(LEFT_ARROW),
Key::MetaLeft => Some(META_LEFT),
Key::MetaRight => Some(META_RIGHT),
Key::Return => Some(RETURN),
Key::RightArrow => Some(RIGHT_ARROW),
Key::ShiftLeft => Some(SHIFT_LEFT),
Key::ShiftRight => Some(SHIFT_RIGHT),
Key::Space => Some(SPACE),
Key::Tab => Some(TAB),
Key::UpArrow => Some(UP_ARROW),
Key::BackQuote => Some(BACK_QUOTE),
Key::Num1 => Some(NUM1),
Key::Num2 => Some(NUM2),
Key::Num3 => Some(NUM3),
Key::Num4 => Some(NUM4),
Key::Num5 => Some(NUM5),
Key::Num6 => Some(NUM6),
Key::Num7 => Some(NUM7),
Key::Num8 => Some(NUM8),
Key::Num9 => Some(NUM9),
Key::Num0 => Some(NUM0),
Key::Minus => Some(MINUS),
Key::Equal => Some(EQUAL),
Key::KeyQ => Some(KEY_Q),
Key::KeyW => Some(KEY_W),
Key::KeyE => Some(KEY_E),
Key::KeyR => Some(KEY_R),
Key::KeyT => Some(KEY_T),
Key::KeyY => Some(KEY_Y),
Key::KeyU => Some(KEY_U),
Key::KeyI => Some(KEY_I),
Key::KeyO => Some(KEY_O),
Key::KeyP => Some(KEY_P),
Key::LeftBracket => Some(LEFT_BRACKET),
Key::RightBracket => Some(RIGHT_BRACKET),
Key::KeyA => Some(KEY_A),
Key::KeyS => Some(KEY_S),
Key::KeyD => Some(KEY_D),
Key::KeyF => Some(KEY_F),
Key::KeyG => Some(KEY_G),
Key::KeyH => Some(KEY_H),
Key::KeyJ => Some(KEY_J),
Key::KeyK => Some(KEY_K),
Key::KeyL => Some(KEY_L),
Key::SemiColon => Some(SEMI_COLON),
Key::Quote => Some(QUOTE),
Key::BackSlash => Some(BACK_SLASH),
Key::KeyZ => Some(KEY_Z),
Key::KeyX => Some(KEY_X),
Key::KeyC => Some(KEY_C),
Key::KeyV => Some(KEY_V),
Key::KeyB => Some(KEY_B),
Key::KeyN => Some(KEY_N),
Key::KeyM => Some(KEY_M),
Key::Comma => Some(COMMA),
Key::Dot => Some(DOT),
Key::Slash => Some(SLASH),
Key::Function => Some(FUNCTION),
Key::Unknown(code) => code.try_into().ok(),
_ => None,
}
}
pub fn key_from_code(code: CGKeyCode) -> Key {
match code {
ALT => Key::Alt,
ALT_GR => Key::AltGr,
BACKSPACE => Key::Backspace,
CAPS_LOCK => Key::CapsLock,
CONTROL_LEFT => Key::ControlLeft,
DOWN_ARROW => Key::DownArrow,
ESCAPE => Key::Escape,
F1 => Key::F1,
F10 => Key::F10,
F11 => Key::F11,
F12 => Key::F12,
F2 => Key::F2,
F3 => Key::F3,
F4 => Key::F4,
F5 => Key::F5,
F6 => Key::F6,
F7 => Key::F7,
F8 => Key::F8,
F9 => Key::F9,
LEFT_ARROW => Key::LeftArrow,
META_LEFT => Key::MetaLeft,
META_RIGHT => Key::MetaRight,
RETURN => Key::Return,
RIGHT_ARROW => Key::RightArrow,
SHIFT_LEFT => Key::ShiftLeft,
SHIFT_RIGHT => Key::ShiftRight,
SPACE => Key::Space,
TAB => Key::Tab,
UP_ARROW => Key::UpArrow,
BACK_QUOTE => Key::BackQuote,
NUM1 => Key::Num1,
NUM2 => Key::Num2,
NUM3 => Key::Num3,
NUM4 => Key::Num4,
NUM5 => Key::Num5,
NUM6 => Key::Num6,
NUM7 => Key::Num7,
NUM8 => Key::Num8,
NUM9 => Key::Num9,
NUM0 => Key::Num0,
MINUS => Key::Minus,
EQUAL => Key::Equal,
KEY_Q => Key::KeyQ,
KEY_W => Key::KeyW,
KEY_E => Key::KeyE,
KEY_R => Key::KeyR,
KEY_T => Key::KeyT,
KEY_Y => Key::KeyY,
KEY_U => Key::KeyU,
KEY_I => Key::KeyI,
KEY_O => Key::KeyO,
KEY_P => Key::KeyP,
LEFT_BRACKET => Key::LeftBracket,
RIGHT_BRACKET => Key::RightBracket,
KEY_A => Key::KeyA,
KEY_S => Key::KeyS,
KEY_D => Key::KeyD,
KEY_F => Key::KeyF,
KEY_G => Key::KeyG,
KEY_H => Key::KeyH,
KEY_J => Key::KeyJ,
KEY_K => Key::KeyK,
KEY_L => Key::KeyL,
SEMI_COLON => Key::SemiColon,
QUOTE => Key::Quote,
BACK_SLASH => Key::BackSlash,
KEY_Z => Key::KeyZ,
KEY_X => Key::KeyX,
KEY_C => Key::KeyC,
KEY_V => Key::KeyV,
KEY_B => Key::KeyB,
KEY_N => Key::KeyN,
KEY_M => Key::KeyM,
COMMA => Key::Comma,
DOT => Key::Dot,
SLASH => Key::Slash,
FUNCTION => Key::Function,
code => Key::Unknown(code.into()),
}
}
#[cfg(test)]
mod test {
use super::{code_from_key, key_from_code};
#[test]
fn test_reversible() {
for code in 0..=65535 {
let key = key_from_code(code);
match code_from_key(key) {
Some(code2) => assert_eq!(code, code2),
None => panic!("Could not convert back code: {:?}", code),
}
}
}
}
For the mean time, I've restored sanity by using Barrier to control my mac from my linux box. :P
from kanata.
For the mean time, I've restored sanity by using Barrier to control my mac from my linux box. :P
What did you do to get kanata and Barrier working together i.e. for kanata remappings and layers be activated on client Barrier machines? Perhaps that's possible from Linux to MacOS only? I'm trying to get it working on two Windows machines...
from kanata.
@flamingjupiter Mostly just-works®, although I did use the settings in Barrier to remap ctrl to cmd.
On linux at least, the remapping happens first, and then the result gets sent over Barrier.
I have not tried with windows, however, so I do not know if the way Barrier captures keybindings there will circumvent Kanata.
Perhaps a little too convoluted, but maybe you could run Barrier and Kanata under WSL?
from kanata.
Hi @jtroo
I switch back from linux to macOS, and I can't live without kanata
anymore. I've started implementing macOS support here. I encountered weird behavior because some config works okay e.g. minimal and other not.
I prepared example problematic configuration.
(defcfg)
(defsrc 1 2 grv)
(deflayer start 5 2 3)
Remapping 1
to 5
works fine, but 2
and grave
produces the following logs. Do you have any idea what could be wrong?
λ cargo run -- -c ./cfg_samples/minimal2.kbd -d -t
warning: `kanata` (bin "kanata") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.13s
Running `target/debug/kanata -c ./cfg_samples/minimal.kbd -d -t`
00:16:04 [INFO] kanata v1.0.8 starting
00:16:04 [INFO] process unmapped keys: false
00:16:04 [INFO] NOTE: kanata was compiled to never allow cmd
00:16:04 [INFO] config parsed
00:16:04 [INFO] Sleeping for 2s. Please release all keys and don't press additional ones.
z00:16:06 [INFO] entering the processing loop
00:16:06 [INFO] entering the event loop
00:16:06 [INFO] Init: catching only releases and sending immediately
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Press }
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:16:06 [INFO] Init: releasing KEY_GRAVE
00:16:06 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_GRAVE, value: Release }
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Repeat }
00:18:39 [DEBUG] (2) kanata::kanata: repeat Kb2
00:18:39 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_2, value: Release }
00:18:39 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_2, value: Press }
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:430] Kb2 is pressed
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:432] Kb2 is contained
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Release }
00:18:39 [DEBUG] (2) kanata::kanata: key release Kb2
00:18:39 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_2, value: Release }
00:18:39 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_2, value: Press }
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Press }
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:430] Kb2 is pressed
00:18:39 [DEBUG] (2) kanata::kanata: key press Kb2
00:18:39 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_2, value: Press }
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Release }
00:18:39 [DEBUG] (2) kanata::kanata: key release Kb2
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Press }
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:430] Kb2 is pressed
00:18:39 [DEBUG] (2) kanata::kanata: key press Kb2
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Repeat }
00:18:39 [DEBUG] (2) kanata::kanata: repeat Kb2
00:18:39 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_2, value: Press }
00:18:39 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_2, value: Release }
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:430] Kb2 is pressed
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:432] Kb2 is contained
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
00:18:39 [DEBUG] (1) kanata::kanata::macos: event loop: KeyEvent { code: KEY_2, value: Press }
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Repeat }
00:18:39 [DEBUG] (2) kanata::kanata: repeat Kb2
00:18:39 [INFO] pressed LControl+Space+Escape, exiting
thread 'main' panicked at 'pressed LControl+Space+Escape, exiting', src/kanata/mod.rs:762:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:430] Kb2 is pressed
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:432] Kb2 is contained
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Release }
00:18:39 [DEBUG] (2) kanata::kanata: key release Kb2
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
00:18:39 [ERROR] Handle event KeyEvent { code: KEY_2, value: Press }
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:430] Kb2 is pressed
00:18:39 [DEBUG] (2) kanata::kanata: key press Kb2
00:18:39 [TRACE] (2) kanata::kanata: [src/kanata/mod.rs:629] blocking on channel
from kanata.
Very cool that you're porting to macos @jborkowski!
Based on the logs, it's panicking inside check_for_exit. A bit strange since you're not pressing any of the relevant keys. You might want to log or debug inside of that function and see what values are being used in there. Maybe there's some overlapping and incorrect conversions happening?
from kanata.
I diagnosed the issue. The strange behavior occurs during grabbing and then simulating unchanged keycode; I suppose the problem occurs in the core-graphics
crate; To be precise: during grabbing a physical key (key remains pressed), and at the same time simulating pressing the key programmatically.
The solution looks easy to solve; if this keycode is mapped in the configuration and does not change keycode behavior, then pass-through immediately this keycode event (without sending it to KbdOut).
WDYT @jtroo ?
from kanata.
@jborkowski that sounds like very strange behaviour in core-graphics
, I wonder if the issue might by something else? Bear in mind I don't have a device to test myself and am unfamiliar with the MacOS keyboard remapping landscape, so my wonderings could be totally off.
if this keycode is mapped in the configuration and does not change keycode behavior ...
I think this could be possible by checking against the active layer keyberon layer and the action at the appropriate index. The downside of doing this is that if the key doesn't go through processing, actions like one-shot and tap-hold-release won't activate on them.
from kanata.
@flamingjupiter Mostly just-works®, although I did use the settings in Barrier to remap ctrl to cmd.
On linux at least, the remapping happens first, and then the result gets sent over Barrier.
I have not tried with windows, however, so I do not know if the way Barrier captures keybindings there will circumvent Kanata.
Perhaps a little too convoluted, but maybe you could run Barrier and Kanata under WSL?
I see, good to know about it on the Linux and macOS side. I got it working flawlessly (barrier with Windows server and Windows client, the server using kanata and Barrier sending remapped keystrokes) with kanata wintercept version. Your suggestion is possibly an alternative though, it avoids using the interception driver, thanks.
from kanata.
Not a rust programmer, but played with mac’s EventTap - which rdev seems to use. Not sure if you know already, but I don’t believe it will work on password fields. Personally I think it’s still valuable, just wanted to call that out
from kanata.
@rafaelliu I'm not entirely sure if this applies to EventTap or not, but I know with karabiner I had to go to system settings -> privacy & security, and give it special access. Maybe the same would work with EventTap for Kanata?
from kanata.
AFAIK this is a MacOS security feature. Karabiner doesn't use EventTap, it implements its own driver and operates at a lower level.
from kanata.
What is the status on this? I've been using kanata on Linux and would be great to have it run on my macbook too, I can contribute/test if required as well...
from kanata.
Hi @caizoryan ,
I believe @jborkowski 's adaptation here can be build and run on Mac, although I'm not sure if it's fully functional or just proof-of-concept at this point.
from kanata.
Interestingly enough, adding this print removes the bug itself and some basic functionnalities work (tap-dance, macro,...) but others don't (like trying to use tap-hold with modifiers). Possibly this is because it cause a slight delay.
Though the logs themselves seem quite correct in terms of press and release events.
![Screenshot 2023-11-13 at 12 33 08](https://private-user-images.githubusercontent.com/34238160/282448554-8aaeaca4-acd5-45a9-9253-7af63e675225.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTgxMTk3MjcsIm5iZiI6MTcxODExOTQyNywicGF0aCI6Ii8zNDIzODE2MC8yODI0NDg1NTQtOGFhZWFjYTQtYWNkNS00NWE5LTkyNTMtN2FmNjNlNjc1MjI1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA2MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNjExVDE1MjM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWJhNTFjOWMzMDE3ODQwNGM3NTIwOTJjMGU1YWFlOTI0MmI5YzA3ZjY5ZWU4NTdhMzFiMTEwMjc2ZGM4NjQ0ZjkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.GxuL33S-7YFTFplZkNr2UkKxPuEvoNYxTPLLDHLoCzM)
rdev
I also tried to use the rdev
simulate
function.
- In the original version, the event is posted on HID and so immediately grabbed again creating a loop.
- I forked
rdev
and posted onSession
instead, no loop but then the modifiers do not apply.
I noticed here that they post on HID (so the modifiers work) but they add an identifier to the even so that they can ignore it when grabbed. This way they have modifiers without loop. I'll try this.
from kanata.
Forgot to mention, Karabiner driver has to be installed first.
run /Applications/.Karabiner-VirtualHIDDevice-Manager.app/Contents/MacOS/Karabiner-VirtualHIDDevice-Manager activate
to activate it.
from kanata.
This is amazing! Go ahead and make a PR already, I can't wait to leave a comment to improve the code.
from kanata.
@jtroo, I'd love to submit a PR if you don't mind missing the mouse support at first. I think it wouldn't be an issue to add it later. I am super excited and would love to hear feedback on this, seeing that I learned rust just to port kanata to macos!
Please feel free to open a PR 🙂 I would be happy to accept it even with mouse support missing.
from kanata.
Very welcome news! How does the Mac version interoperate with Karabiner Elements itself? Is it similar to Autohotkey on Windows where they're incompatible and you could only use one or is there a way to make them work together (or make one a "higher priority" than the other?)
from kanata.
I was able to build on my system, but one of the tests appears to be failing.
❯ sw_vers
ProductName: macOS
ProductVersion: 14.0
BuildVersion: 23A344
❯ uname -mrs
Darwin 23.0.0 arm64
Reproduction steps
Assuming Nix is installed and flakes are enabled.
❯ cat <<'EOF' > flake.nix
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
};
outputs = { self, nixpkgs }: let
pkgs = nixpkgs.legacyPackages.aarch64-darwin;
lib = nixpkgs.lib;
in {
packages.aarch64-darwin.default = pkgs.rust.packages.stable.rustPlatform.buildRustPackage rec {
pname = "kanata";
version = "1.5.0-prerelease-3";
src = pkgs.fetchFromGitHub {
owner = "jtroo";
repo = pname;
rev = "568a0c8a6a5650d950328db7d41799acbb0d5a8e";
sha256 = "sha256-eh8VaMVNi/Eg3DyLIKryKcLUgeKODFZtA+hfPocUVkM=";
};
cargoHash = "sha256-1LE2rTXUz6dwxQ1CQ6RXiKLYbh+G2ghPAywMhQyWUU0=";
# https://github.com/NixOS/nixpkgs/issues/166205
env = let cc = pkgs.stdenv.cc; in lib.optionalAttrs cc.isClang {
NIX_LDFLAGS = "-l${cc.libcxx.cxxabi.libName}";
};
buildInputs = with pkgs; [ darwin.IOKit ];
checkFlags = [
# "--skip=tests::parse_default"
];
meta = with lib; {
description = "A tool to improve keyboard comfort and usability with advanced customization";
homepage = "https://github.com/jtroo/kanata";
license = licenses.lgpl3Only;
maintainers = with maintainers; [ ];
platforms = platforms.darwin;
mainProgram = "kanata";
};
};
};
}
EOF
❯ git add .
❯ nix build .
Actual results
running 9 tests
test kanata::apply_speed_modifiers ... ok
test tcp_server::layer_change_serializes ... ok
test tests::sizeof_state ... ok
test tests::parse_all_keys ... ok
test tests::parse_default ... FAILED
test tests::parse_f13_f24 ... ok
test tests::parse_jtroo ... ok
test tests::parse_minimal ... ok
test tests::parse_simple ... ok
failures:
---- tests::parse_default stdout ----
thread 'tests::parse_default' panicked at src/tests.rs:30:74:
called `Result::unwrap()` on an `Err` value: x Error in configuration file
,-[./cfg_samples/kanata.kbd:672:1]
672 | (deflayer misc
673 | _ _ _ _ _ _ _ _ _ @é @è _ ì #|random custom key for testing|# _
: |
: `-- Error here
674 | _ _ @ab1 _ _ _ ins @{ @} [ ] _ _ +
`----
help: Unknown key/action: ì
from kanata.
Related Issues (20)
- Feature request: intercept all mice in Wintercept build HOT 1
- defoverrides is ignored in unmod and unshift HOT 2
- Directional Keys Not Working in Windows Task View HOT 6
- Bug: Home row mods advanced issue with block-unmapped-keys HOT 3
- Bug: unable to globally swap caps and esc HOT 2
- Feature request: modular per-platform config.adoc HOT 9
- Feature request: togglable `caps-word` HOT 2
- Bug: In WSL space is inserted after shift or control when running kanata in host HOT 1
- Bug: windows iconbar icon isn't removed when app is killed HOT 1
- Bug: Dead keys cause keyboard modifier state to get corrupt - witnessed on colemak HOT 7
- Feature request: support platform-specific IPC which has greater convenience than TCP
- Feature request: Auto Mouse Keys layer HOT 2
- Feature request: support BSD HOT 5
- Unable to send key input due to key having not been physically released yet. HOT 3
- Bug: Release 1.6.1 macos_x86_64 executable isn't what it says. HOT 6
- Feature request: Show keystroke or can let other app know the real key is stroked. HOT 2
- Feature request: Can we have a more flexible unmod? HOT 1
- See if windows shift workaround can be compiled out for winiov2 HOT 1
- Bug: kanata does not work properly with listary HOT 3
- `release-key` releases both sides of a mod and not just one, e.g. `lctl` and `rctl` or `lmet` and `rmet` HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from kanata.