Skip to content

Commit

Permalink
feat(macos): add limited macOS support (#652)
Browse files Browse the repository at this point in the history
Some notable bits of missing functionality are:
- missing mouse functionality (input and output)
- missing unicode functionality
  • Loading branch information
psych3r authored Dec 9, 2023
1 parent 089ad03 commit 2090665
Show file tree
Hide file tree
Showing 16 changed files with 2,463 additions and 20 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ is-terminal = "=0.4.7"
# Otherwise any changes to the local files will not reflect in the compiled
# binary.
kanata-keyberon = { path = "keyberon" }
kanata-parser = { path = "parser" }
kanata-parser = { path = "parser" }

[target.'cfg(target_os = "macos")'.dependencies]
karabiner-driverkit = "0.1.0"

[target.'cfg(target_os = "linux")'.dependencies]
evdev = "=0.12.0"
Expand Down
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

## What does this do?

This is a software keyboard remapper for Linux and Windows. A short summary of
This is a cross-platform software keyboard remapper for Linux, macOS and Windows. A short summary of
the features:

- multiple layers of key functionality
Expand Down Expand Up @@ -76,7 +76,7 @@ Using `cargo install`:

cargo install kanata

# On Linux, this may not work without `sudo`, see below
# On Linux and macOS, this may not work without `sudo`, see below
kanata --cfg <your_configuration_file>

Build and run yourself in Linux:
Expand All @@ -96,6 +96,22 @@ Build and run yourself in Windows.
cargo build # --release optional, not really perf sensitive
target\debug\kanata --cfg <your_configuration_file>

Build and run yourself in macOS:

First, install the [Karabiner VirtualHiDDevice Driver](https://github.com/pqrs-org/Karabiner-DriverKit-VirtualHIDDevice/blob/main/dist/Karabiner-DriverKit-VirtualHIDDevice-3.1.0.pkg).

To activate it:

`/Applications/.Karabiner-VirtualHIDDevice-Manager.app/Contents/MacOS/Karabiner-VirtualHIDDevice-Manager activate`


git clone https://github.com/jtroo/kanata && cd kanata
cargo build # --release optional, not really perf sensitive

# sudo is needed to gain permission to intercept the keyboard

sudo target/debug/kanata --cfg <your_configuration_file>

The full configuration guide is [found here](./docs/config.adoc).

Sample configuration files are found in [cfg_samples](./cfg_samples). The
Expand Down
31 changes: 30 additions & 1 deletion docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,12 @@ that are not allowed in `defsrc` by default, at least according to the symbol
shown. You can use `deflocalkeys` to define additional key names that can be
used in `defsrc`, `deflayer` and anywhere else in the configuration.

There are three variants of deflocalkeys:
There are four variants of deflocalkeys:

- `deflocalkeys-win`
- `deflocalkeys-wintercept`
- `deflocalkeys-linux`
- `deflocalkeys-macos`


Only one of each deflocalkeys-* variant is allowed. The variants that are not
Expand All @@ -201,6 +202,10 @@ Please contribute to the document if you are able!
ì 13
)
(deflocalkeys-macos
ì 13
)
(defsrc
grv 1 2 3 4 5 6 7 8 9 0 - ì bspc
)
Expand Down Expand Up @@ -637,6 +642,30 @@ This configuration item does not affect Wayland or no-desktop environments.
)
----

[[macos-only-macos-dev-names-include]]
=== macOS only: macos-dev-names-include
<<table-of-contents,Back to ToC>>

This option defines a list of device names that should be included.
By default, kanata will try to detect which input devices are keyboards and try
to intercept them all. However, you may specify exact keyboard devices to intercept
using the `macos-dev-names-include` configuration.
Device names that do not exist in the list will be ignored.
This option is parsed identically to `linux-dev`.

Use `kanata -l` or `kanata --list` to list the available keyboards.

.Example:
[source]
----
(defcfg
macos-dev-names-include (
"Device name 1"
"Device name 2"
)
)
----

[[windows-only-windows-altgr]]
=== Windows only: windows-altgr
<<table-of-contents,Back to ToC>>
Expand Down
4 changes: 1 addition & 3 deletions example_tcp_client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ fn main() {
)
.expect("connect to kanata");
log::info!("successfully connected");
let writer_stream = kanata_conn
.try_clone()
.expect("clone writer");
let writer_stream = kanata_conn.try_clone().expect("clone writer");
let reader_stream = kanata_conn;
std::thread::spawn(move || write_to_kanata(writer_stream));
read_from_kanata(reader_stream);
Expand Down
24 changes: 19 additions & 5 deletions parser/src/cfg/defcfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub struct CfgOptions {
target_os = "unknown"
))]
pub windows_interception_mouse_hwid: Option<[u8; HWID_ARR_SZ]>,
#[cfg(any(target_os = "macos", target_os = "unknown"))]
pub macos_dev_names_include: Option<Vec<String>>,
}

impl Default for CfgOptions {
Expand Down Expand Up @@ -77,6 +79,8 @@ impl Default for CfgOptions {
target_os = "unknown"
))]
windows_interception_mouse_hwid: None,
#[cfg(any(target_os = "macos", target_os = "unknown"))]
macos_dev_names_include: None,
}
}
}
Expand Down Expand Up @@ -117,7 +121,7 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
"linux-dev" => {
#[cfg(any(target_os = "linux", target_os = "unknown"))]
{
cfg.linux_dev = parse_linux_dev(val)?;
cfg.linux_dev = parse_dev(val)?;
if cfg.linux_dev.is_empty() {
bail_expr!(
val,
Expand All @@ -129,7 +133,7 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
"linux-dev-names-include" => {
#[cfg(any(target_os = "linux", target_os = "unknown"))]
{
let dev_names = parse_linux_dev(val)?;
let dev_names = parse_dev(val)?;
if dev_names.is_empty() {
log::warn!("linux-dev-names-include is empty");
}
Expand All @@ -139,7 +143,7 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
"linux-dev-names-exclude" => {
#[cfg(any(target_os = "linux", target_os = "unknown"))]
{
cfg.linux_dev_names_exclude = Some(parse_linux_dev(val)?);
cfg.linux_dev_names_exclude = Some(parse_dev(val)?);
}
}
"linux-unicode-u-code" => {
Expand Down Expand Up @@ -238,6 +242,16 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
cfg.windows_interception_mouse_hwid = Some(hwid_slice?);
}
}
"macos-dev-names-include" => {
#[cfg(any(target_os = "macos", target_os = "unknown"))]
{
let dev_names = parse_dev(val)?;
if dev_names.is_empty() {
log::warn!("macos-dev-names-include is empty");
}
cfg.macos_dev_names_include = Some(dev_names);
}
}

"process-unmapped-keys" => {
cfg.process_unmapped_keys = parse_defcfg_val_bool(val, label)?
Expand Down Expand Up @@ -348,8 +362,8 @@ pub fn parse_colon_separated_text(paths: &str) -> Vec<String> {
all_paths
}

#[cfg(any(target_os = "linux", target_os = "unknown"))]
pub fn parse_linux_dev(val: &SExpr) -> Result<Vec<String>> {
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "unknown"))]
pub fn parse_dev(val: &SExpr) -> Result<Vec<String>> {
Ok(match val {
SExpr::Atom(a) => {
let devs = parse_colon_separated_text(a.t.trim_matches('"'));
Expand Down
4 changes: 4 additions & 0 deletions parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ fn parse_cfg(
const DEF_LOCAL_KEYS: &str = "deflocalkeys-win";
#[cfg(all(feature = "interception_driver", target_os = "windows"))]
const DEF_LOCAL_KEYS: &str = "deflocalkeys-wintercept";
#[cfg(target_os = "macos")]
const DEF_LOCAL_KEYS: &str = "deflocalkeys-macos";
#[cfg(any(target_os = "linux", target_os = "unknown"))]
const DEF_LOCAL_KEYS: &str = "deflocalkeys-linux";

Expand Down Expand Up @@ -402,6 +404,7 @@ pub fn parse_cfg_raw_string(
"deflocalkeys-win",
"deflocalkeys-wintercept",
"deflocalkeys-linux",
"deflocalkeys-macos",
] {
if let Some(result) = root_exprs
.iter()
Expand Down Expand Up @@ -633,6 +636,7 @@ fn error_on_unknown_top_level_atoms(exprs: &[Spanned<Vec<SExpr>>]) -> Result<()>
| "defsrc"
| "deflayer"
| "defoverrides"
| "deflocalkeys-macos"
| "deflocalkeys-linux"
| "deflocalkeys-win"
| "deflocalkeys-wintercept"
Expand Down
10 changes: 5 additions & 5 deletions parser/src/cfg/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1211,10 +1211,10 @@ fn parse_device_paths() {

#[test]
#[cfg(any(target_os = "linux", target_os = "unknown"))]
fn test_parse_linux_dev() {
fn test_parse_dev() {
// The old colon separated devices format
assert_eq!(
parse_linux_dev(&SExpr::Atom(Spanned {
parse_dev(&SExpr::Atom(Spanned {
t: "\"Keyboard2:Input Device 1:pci-0000\\:00\\:14.0-usb-0\\:1\\:1.0-event\""
.to_string(),
span: Span::default(),
Expand All @@ -1226,15 +1226,15 @@ fn test_parse_linux_dev() {
"pci-0000:00:14.0-usb-0:1:1.0-event"
]
);
parse_linux_dev(&SExpr::Atom(Spanned {
parse_dev(&SExpr::Atom(Spanned {
t: "\"\"".to_string(),
span: Span::default(),
}))
.expect_err("'' is not a valid device name/path, this should fail");

// The new device list format
assert_eq!(
parse_linux_dev(&SExpr::List(Spanned {
parse_dev(&SExpr::List(Spanned {
t: vec![
SExpr::Atom(Spanned {
t: "Keyboard2".to_string(),
Expand Down Expand Up @@ -1263,7 +1263,7 @@ fn test_parse_linux_dev() {
r"backslashes\do\not\escape\:\anything"
]
);
parse_linux_dev(&SExpr::List(Spanned {
parse_dev(&SExpr::List(Spanned {
t: vec![
SExpr::Atom(Spanned {
t: "Device1".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion parser/src/keys/linux.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// This file is taken from the original ktrl project's keys.rs file with modifications.

use super::OsCode;

impl OsCode {
pub(super) const fn as_u16_linux(self) -> u16 {
self as u16
}

pub(super) const fn from_u16_linux(code: u16) -> Option<Self> {
match code {
0 => Some(OsCode::KEY_RESERVED),
Expand Down
Loading

0 comments on commit 2090665

Please sign in to comment.