Giter Club home page Giter Club logo

Comments (32)

psych3r avatar psych3r commented on July 21, 2024 6

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.

jtroo avatar jtroo commented on July 21, 2024 6

Tests are fixed and macOS is now added to CI as well, for tests+clippy 🙂

from kanata.

jborkowski avatar jborkowski commented on July 21, 2024 4

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.

jtroo avatar jtroo commented on July 21, 2024 3

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.

Alounv avatar Alounv commented on July 21, 2024 2

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.

evanrichter avatar evanrichter commented on July 21, 2024 1

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.

evanrichter avatar evanrichter commented on July 21, 2024 1

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.

SignSpice avatar SignSpice commented on July 21, 2024

This crate may make this pretty simple to implement: https://github.com/Narsil/rdev

from kanata.

jtroo avatar jtroo commented on July 21, 2024

If you have any questions about the code or want some pointers, feel free to ask 🙂

from kanata.

SignSpice avatar SignSpice commented on July 21, 2024

@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.

SignSpice avatar SignSpice commented on July 21, 2024

Am I correct in thinking most of the work is going to be in kanata/mod.rs?

from kanata.

SignSpice avatar SignSpice commented on July 21, 2024

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.

jtroo avatar jtroo commented on July 21, 2024

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.

#[cfg(target_os = "linux")]

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 OsCodes 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 KeyCodes. 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.

SignSpice avatar SignSpice commented on July 21, 2024

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.

flamingjupiter avatar flamingjupiter commented on July 21, 2024

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.

devcarbon-com avatar devcarbon-com commented on July 21, 2024

@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.

jborkowski avatar jborkowski commented on July 21, 2024

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.

jtroo avatar jtroo commented on July 21, 2024

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.

jborkowski avatar jborkowski commented on July 21, 2024

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.

jtroo avatar jtroo commented on July 21, 2024

@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 avatar flamingjupiter commented on July 21, 2024

@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.

rafaelliu avatar rafaelliu commented on July 21, 2024

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.

devcarbon-com avatar devcarbon-com commented on July 21, 2024

@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.

rafaelliu avatar rafaelliu commented on July 21, 2024

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.

caizoryan avatar caizoryan commented on July 21, 2024

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.

devcarbon-com avatar devcarbon-com commented on July 21, 2024

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.

Alounv avatar Alounv commented on July 21, 2024

Print

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

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 on Session 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.

psych3r avatar psych3r commented on July 21, 2024

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.

rszyma avatar rszyma commented on July 21, 2024

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 avatar jtroo commented on July 21, 2024

@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.

eugenesvk avatar eugenesvk commented on July 21, 2024

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.

zane avatar zane commented on July 21, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.