Skip to content

Transition to CGEvent.tapCreate for hotkey bindings (#1012)#1714

Open
NMAC427 wants to merge 3 commits intonikitabobko:mainfrom
NMAC427:cgevent
Open

Transition to CGEvent.tapCreate for hotkey bindings (#1012)#1714
NMAC427 wants to merge 3 commits intonikitabobko:mainfrom
NMAC427:cgevent

Conversation

@NMAC427
Copy link

@NMAC427 NMAC427 commented Sep 21, 2025

Replace the Carbon based HotKey library with CGEvent.tapCreate as layed out in #1012.
As a consequence, we get direct access to all key-down events, and can choose to handle them based on the current modifier state and key code. Therefore, in addition to #1012, this change also addresses all of the following issues / feature requests:

The primary user facing changes are as follows:

  • Added new modifiers lcmd, rcmd, lalt, ralt, lctrl, rctrl, lshift, rshift, and fn.
  • Add key-mapping.match-key-event-by to choose between matching events based on key codes or key symbols.

PR checklist

  • Explain your changes in the relevant commit messages rather than in the PR description. The PR description must not contain more information than the commit messages (except for images and other media).
  • Each commit must explain what/why/how and motivation in its description. https://cbea.ms/git-commit/
  • Don't forget to link the appropriate issues/discussions in commit messages (if applicable).
  • Each commit must be an atomic change (a PR may contain several commits). Don't introduce new functional changes together with refactorings in the same commit.
  • ./run-tests.sh exits with non-zero exit code.
  • Avoid merge commits, always rebase and force push.

Failure to follow the checklist with no apparent reasons will result in silent PR rejection.

@NMAC427
Copy link
Author

NMAC427 commented Dec 12, 2025

@nikitabobko Any chance you could take another look at this? Based on the reactions, it seems like this would be quite a popular feature. I've personally been running custom build of this PR for the last 2 1/2 months, regularly switching keyboard layouts, without any issues.

@joao-paulo-santos

This comment was marked as off-topic.

@nikitabobko
Copy link
Owner

Previous PR: #1691


  1. Please avoid merge commits. Use only rebase and force push.
  2. The commits must be atomic. The first commit must be the migration from Carbon API to CGEvent.tapCreate and nothing else. Right now, you do the migration, change semantics, add new features, and do breaking changes all together in a single commit.
  3. Each commit must have its respective git description.
  4. Please also take a look at https://github.com/nikitabobko/AeroSpace/blob/main/.github/pull_request_template.md

Answering questions from your previous PR

However, before I get started, I would like to get your opinion: Do you foresee any reason why we should keep around any of the keymap functionality?

Yes, we should keep the keymap functionality. Languages and keyboard layouts are wild beasts full of corner cases and exceptions. I am sure that there are people who prefer the current behavior of matching by keycodes (I am kinda such person as I type in qwerty and cyrillic).

The current default of matching by key codes should stay at least for compatibility reasons.

This usually isn't a big problem, as key-mapping.key-notation-to-key-code can be used to adjust the layout. However, when working with a custom keyboard that is remapped on the firmware level (i.e. anything running ZMK), in conjuncion with a keyboard that is remapped in software (i.e. builtin MacBook keyboard), this results in significant issues.

I find this use case unconvincing. You have to switch input sources when you connect your external keyboard anyway. If it's the only use case that we have on the table, I am hesitant to optimize for it. If somebody has other use cases, they are welcome to describe them.

I like the current algorithm of matching by key codes + manual remapping via key-mapping.key-notation-to-key-code, because it covers 99% of use cases, and it offloads the responsibility of wild west layouts onto the user.

I imagine a config option that would allow to match by key symbols instead of key codes:

[key-mapping]
  # Possible values: (key-codes|key-symbols)
  match-key-event-by = 'key-codes' # The current default

but I am still not convinced in its necessity.

@Rasalas
Copy link

Rasalas commented Dec 14, 2025

I didn't test this "patch" (and I might not understand the problems), but that AltGr and Alt are the same key stopped me from using this at first.

I have it running and I cant write the EURO sign or Backslash right now, because it registers as if I'm tapping the ALT key only.

I can probably change the config (I think I did for @ but I think I installed another app to replace it like descibed in a StackOverflow answer) but it's genuinely annoying.
If this can fix it, I'd be really grateful if @NMAC427 could follow your standards.

This and Aerospace not splitting the current Part to half (it only seems to make vertical splits(L/R)) are probably the only real problems with Aerospace.
... and macOS registering/publishing apps positions(I assume) wrong I assume ("crashing" apps break it; cant "reload" a space)

@gdlg
Copy link

gdlg commented Dec 23, 2025

Same issue as @Rasalas. I use a Bépo layout which heavily relies on AltGr for many characters including ones needed for programming. In general, there are many common international layouts which use AltGr: see https://en.wikipedia.org/wiki/AltGr_key for a list. So not being able distinguish between AltGr and Alt is a non-starter for me.

I like the current algorithm of matching by key codes + manual remapping via key-mapping.key-notation-to-key-code, because it covers 99% of use cases, and it offloads the responsibility of wild west layouts onto the user.

As far as I understand, key-mapping.key-notation-to-key-code doesn’t help for this use case because it’s not used for modifiers. Since those layouts are used by default by many countries, I wouldn’t call them wild west and it’s significantly more than just 1% of the use cases.

@kfiz
Copy link

kfiz commented Jan 3, 2026

Have to chime in here. Really like aerospace because it solves window management on a mac for me. I'm using some kind of workaround currently that is really cumbersome where I have to manually press some key combination to disable aerospace from taking over Alt and AltGr just to be able to type an @ sign or brackets { [ on my MacBook Pro which as you can imagine happens a lot. Would really appreciate to merge/implement this if it solves this problem.

…ikitabobko#1012).

Motivation: The Carbon API has been deprecated, and the `CGEvent.tapCreate` API will eventually allow us to differentiate between left and right modifier keys.
…ikitabobko#28), and support fn as modifier (fixes nikitabobko#1011).

Motivation: Many international keyboard layouts rely on the right Alt key (AltGr, https://en.wikipedia.org/wiki/AltGr_key) for an additional symbol layer. Aerospace not differentiating between Alt and AltGr prevents users from using alt for hot
…w `key-symbol` option (fixes nikitabobko#1472).

Motivation:
AeroSpace currently interprets all key bindings using a QWERTY-based key-code model. While `key-mapping.key-notation-to-key-code` can compensate for software layout changes, this approach breaks down when mixing keyboards that are remapped at different layers.
In particular, when using a hardware-remapped keyboard (e.g. ZMK firmware) alongside a software-remapped keyboard (e.g. the built-in MacBook keyboard), identical logical key presses can produce different key codes. As a result, the same shortcut may work on one keyboard but silently fail on the other. `key-mapping.match-key-event-by` allows key bindings to be matched either by key code (current behavior) or by key symbol. This ensures consistent shortcut behavior across mixed hardware/software keyboard setups, while also providing a simpler alternative to `key-mapping.preset`.
@NMAC427
Copy link
Author

NMAC427 commented Jan 5, 2026

@nikitabobko Thanks for the feedback. I reworked the PR into three atomic commits, while maintaining the default behaviour of matching based on key codes, but also allowing to switch to symbol based matching using key-mapping.match-key-event-by as you proposed.

[key-mapping]
    # Possible values: (key-code|key-symbol)
    match-key-event-by = 'key-symbol'  # default is 'key-code'

I find this use case unconvincing. You have to switch input sources when you connect your external keyboard anyway.

I use a tool called autokbisw to automatically switch input sources for my internal and external keyboard.

If it's the only use case that we have on the table, I am hesitant to optimize for it. If somebody has other use cases, they are welcome to describe them.
I like the current algorithm of matching by key codes + manual remapping via key-mapping.key-notation-to-key-code, because it covers 99% of use cases, and it offloads the responsibility of wild west layouts onto the user.

I understand your concern, but given that the audience of AeroSpace probably consists mostly of enthusiasts, I wouldn't be surprised if a decent fraction use custom keyboards + keyboard layouts for improved ergonomics. This feature has at least been requested before (#1472), and is personally my main motivation for this PR. However, if you want, I can also move the key-mapping.match-key-event-by part into a separate PR.

@micschr0
Copy link

micschr0 commented Jan 23, 2026

I have the same conflict with my German QWERTZ keymap - can't type @, {, [ without workarounds.
The workarounds felt too hacky or super cumbersome, so I stopped using AeroSpace for now.

I would really appreciate seeing this merged if it enables distinguishing left vs right alt/cmd/ctrl keys for hotkey bindings and would bring me back!

lheiskan pushed a commit to lheiskan/AeroSpace that referenced this pull request Jan 26, 2026
Extends `[key-mapping.key-notation-to-key-code]` to support
defining aliases for modifier combinations (e.g., hyper key).

Example config:
```toml
[key-mapping.key-notation-to-key-code]
hyper = 'ctrl-alt-shift-cmd'

[mode.main.binding]
hyper-c = 'workspace c'
```

Mixing modifiers and keys in a single alias is not supported.
Invalid combinations are checked and result in configuration
error messages.

Change introduces `KeyOrModifier` enum to represent either
single keys or modifier combinations.

Implements: nikitabobko#224
Depends: nikitabobko#1714
Related: nikitabobko#389
Related: nikitabobko#1223
@kfiz
Copy link

kfiz commented Feb 20, 2026

@nikitabobko I've been looking to get builds of this as suggested here https://github.com/nikitabobko/AeroSpace/blob/main/dev-docs/development.md at https://github.com/nikitabobko/AeroSpace/actions/runs/20724817788 . Are there supposed to be build artifacts somewhere that I could download for testing?

@GaetanLepage

This comment was marked as spam.

@GaetanLepage

This comment was marked as spam.

@kfiz
Copy link

kfiz commented Feb 23, 2026

RE: I was able to build it from your branch @NMAC427. I can confirm that it works flawlessly. Thanks! Can't wait for this to get merged.

@GaetanLepage Care to share how you built it. ;) I was trying to build the .app with nix but didn't succeed in doing so, the cli binary built successfully though. I'm hesitant to pull in all external deps needed by the app by just running the provided build scripts as I ideally would like nix to manage all my deps.

@CrazyMocks

This comment was marked as spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants