pqrs-org / Karabiner-Elements

Karabiner-Elements is a powerful utility for keyboard customization on macOS Sierra (10.12) or later.
https://pqrs.org/osx/karabiner/
The Unlicense
18.53k stars 834 forks source link

Allow customizable and extendable complex modification settings #2774

Open dPowNextdoor opened 3 years ago

dPowNextdoor commented 3 years ago

Issue

Writing complex modification rules for Karabiner is an all but manual process. There are external GUIs out there that format the JSON for you, but they don't have autocomplete, nor do they work for specific, environment-specific conditions, variables, etc. As such, they offer no benefit other than formatting JSON.

Furthermore, as stated here and reiterated here, Karabiner is currently very limited in that it ignores rules that are defined after other rules even if they technically have different conditions.

These issues include, but are not limited to:

Less specific rules overriding more specific rules

I can't write the below config and have both rules activated because the first overrides the second. This doesn't appear terrible at first until you have many rules, either within one file or in separate files, and they end up not working as expected because an earlier rule conflicts with a later rule.

Then you're forced to rewrite all previous rules to include condition_unless or to dissect how all imported rules work to find the issue all BY HAND.

Example: Fails without `frontmost_application_unless` in first rule ```json { "title": "Ctrl --> Command except in IDE, but I don't work!", "rules": [ { "description": "Ctrl --> Command", "manipulators": [ { "type": "basic", "from": { "key_code": "left_control", "modifiers": { "optional": [ "any" ] } }, "to": [ { "key_code": "left_command" } ] }, { "type": "basic", "from": { "key_code": "left_control", "modifiers": { "optional": [ "any" ] } }, "to": [ { "key_code": "left_control" } ], "conditions": [ { "type": "frontmost_application_if", "bundle_identifiers": [ "com.jetbrains.*" ] } ] } ] } ] } ```

Related rules/manipulators all require possibly infinite amounts of duplicated code

If there exists a rule with many related manipulators, I have to duplicate the conditions for that rule in every single entry, resulting in a config that is both difficult to read and less configurable. Then, If I decide I want to add a new application in frontmost_application_if, I have to do it multiple times even though it's all for the same rule.

Example: Fails without `device_if` in every rule/manipulator ```json { "title": "Make Mac keys work like PC (Linux/Windows) on external keyboard", "rules": [ { "description": "Ctrl/Command to behave like PC (PC: Ctrl=Ctrl, Super=Command, Alt=Option)", "manipulators": [ { "type": "basic", "from": { "key_code": "left_control", "modifiers": { "optional": [ "any" ] } }, "to": [ { "key_code": "left_command" } ], "conditions": [ "Oh no, I forgot to add device_if here :'c ", "Ugh, after hours of debugging, finally tracked down that bug.", "[some time later]", "Wait, now I want to add a new external keyboard!", "Looks like I have to edit every single rule I have D:< " ] }, { "type": "basic", "from": { "key_code": "left_command", "modifiers": { "optional": [ "any" ] } }, "to": [ { "key_code": "left_option" } ], "conditions": [ { "type": "device_if", "identifiers": [ { "vendor_id": 1234 } ] } ] }, { "type": "basic", "from": { "key_code": "left_option", "modifiers": { "optional": [ "any" ] } }, "to": [ { "key_code": "left_control" } ], "conditions": [ { "type": "device_if", "identifiers": [ { "vendor_id": 1234 } ] } ] } ] } ] } ```

TL;DR

The current rule format has many issues and needs revisiting. Changing them would solve some of the most common issues users experience.

{
    "title": "My key combo with lots of duplicated code that isn't user friendly",
    "rules": [
        {
            "description": "I'm not following the DRY principle :(",
            "manipulators": [
                {
                    "type": "basic",
                    "from": {},
                    "to": [],
                    "conditions": [
                        "duplicate_condition_objects",
                        "e.g. frontmost_application_unless"
                    ]
                },
                {
                    "type": "basic",
                    "from": {},
                    "to": [],
                    "conditions": [
                        "duplicate_condition_objects",
                        "e.g. frontmost_application_unless"
                    ]
                },
                {
                    "type": "basic",
                    "from": {},
                    "to": [],
                    "conditions": [
                        "man, so much repeated code just so I can use",
                        "e.g. frontmost_application_if",
                        "And those are required b/c I can't be above them",
                        "due to a dependency, other import, or something else"
                    ]
                }
            ]
        }
    ]
}

Yes, some of the examples (especially the jetbrains one) above could be solved by just reordering them, but that was just a minimally-reproducible example. In a real life scenario that's not always possible depending on what rules a user imported or certain rule dependencies (or at least not reasonable, even if possible, because you'd have to e.g. dissect rules downloaded from the internet).

Priority: HIGH

It seems that most reported issues are related to this.

Proposal

Make Karabiner complex modifications extendable. Allow:

This would be an incredibly better way to write rules/conditions, especially since the entries' contents have to be written manually. It also means that similar manipulators can be grouped into one rule instead of spreading their logic across multiple rules (e.g. frontmost_application_unless) and/or breaking because the user accidentally imported them in the wrong order (e.g. frontmost_application_if not working due to a previous/global rule using the same key combo).

Also, this idea already has support from other users, so it's clearly a highly desired feature.

This would result in solving a majority of the issues users post and probably attract new users!

Examples

Simple

{
    "title": "My key combo that is user friendly",
    "rules": [
        {
            "description": "Yay, I'm following the DRY principle!",
            "conditions": [
                "condition_objects for all my manipulators"
            ],
            "manipulators": [
                {
                    "type": "basic",
                    "from": {},
                    "to": []
                },
                {
                    "type": "basic",
                    "from": {},
                    "to": []
                }
            ]
        }
    ]
}

Better application-only rules

{
    "title": "Ctrl --> Command except in IDE, except NOW I WORK!",
    "rules": [
        {
            "description": "Ctrl --> Command",
            "manipulators": [
                {
                    "type": "basic",
                    "from": {
                        "key_code": "left_control",
                        "modifiers": {
                            "optional": [
                                "any"
                            ]
                        }
                    },
                    "to": [
                        {
                            "key_code": "left_command"
                        }
                    ]
                },
                {
                    "type": "basic",
                    "from": {
                        "key_code": "left_control",
                        "modifiers": {
                            "optional": [
                                "any"
                            ]
                        }
                    },
                    "to": [
                        {
                            "key_code": "left_control"
                        }
                    ],
                    "conditions": [
                        {
                            "type": "frontmost_application_if",
                            "bundle_identifiers": [
                                "com.jetbrains.*"
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

All levels of conditions

{
    "title": "My key combo that is SUPER user friendly",
    "conditions": [
        "default conditions to apply to all rules",
        "e.g. device_if"
    ],
    "rules": [
        {
            "description": "Wow, this is both DRY and way easier to read!",
            "manipulators": [
                "manipulators without unnecessary code duplication from global conditions"
            ]
        },
        {
            "description": "Oh cool, I can keep the global rules I need and delete the ones I don't!",
            "conditions": [
                "Conditions to only apply to this rule's manipulators.",
                "Maintains global, root-level rules",
                "or overrides them if applicable."
            ],
            "manipulators": [
                {
                  "yay I'm not duplicating the global rules"
                },
                {
                  "nor do I have to add superfluous e.g. frontmost_application_unless",
                  "conditions to every single entry I contain!"
                },
                {
                    "type": "Even my custom manipulator is easier to write",
                    "from": {},
                    "to": [],
                    "conditions": [
                        "Extra condition to add only for me.",

                        "Alternatively, override the global rule(s) I don't want",
                        "while keeping the rest of them.",

                        "Alternatively, override the rule-level rules",
                        "so the other rules don't have to add",
                        "_unless entries."
                    ]
                }
            ]
        }
    ]
}
hitriyvalenok commented 3 years ago

+1

jsonMartin commented 2 years ago

+2

Arthur-Null commented 2 years ago

+3

rockiecxh commented 2 years ago

+4

Czilis commented 2 years ago

+5

skaiteo commented 1 year ago

+6

ObiWanCanOweMe commented 1 year ago

+7

excalq commented 1 year ago

+8

palashkulsh commented 1 year ago

it will be a helpful feature

tshu-w commented 1 year ago

Vote for 10086

c-vetter commented 1 year ago

I'd like to offer some perspective to those who come to this software in hopes of rather complex behavior at the push of a button, as I did.

I am using both Windows and Mac (the latter being the new addition) and my setup is such that I have created a custom keyboard layout that I now use on both systems for all the simple stuff. For more complex stuff, I need additional software, especially in order to have more or less the same hotkeys on both systems.

I've spent a few days trying to get even a single function to work with Karabiner that would take minutes with AutoHotKey. Obviously, being new to Karabiner, I cannot expect the same speed yet. But it just doesn't work and will likely never work, because, and that is the main point: according to what I have seen in my trial, Karabiner is not an automation tool, it is really focussed on remapping your keyboard. I think it's already doing too much in that regard, which enables this kind of confusion, at least that's part of what happened to me.

Therefore, for anybody who is dissattisfied with the feature scope here, accept the reality that your use-case differs from the app's target and look for an automation tool that allows you to capture and send keyboard events and do whatever you want based on that. Hammerspoon seems to be such a tool, for example.


To the authors of Karabiner: This is not intended to talk badly about your work or the app itself. I think it is a very nice tool for what it is supposed to do, and I was especially happy with the event viewer, which is definitely better than that of AHK.

hicaicai commented 9 months ago

+9

Laurensdc commented 7 months ago

I've got the following custom script:

{
    "description": "hjkl for Guitar Pro",
    "manipulators": [
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "h"
            },
            "to": [
                {
                    "key_code": "left_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "j"
            },
            "to": [
                {
                    "key_code": "down_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "k"
            },
            "to": [
                {
                    "key_code": "up_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "l"
            },
            "to": [
                {
                    "key_code": "right_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "h",
                "modifiers": {
                    "mandatory": [
                        "command"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "left_arrow",
                    "modifiers": [
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "j",
                "modifiers": {
                    "mandatory": [
                        "command"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "down_arrow",
                    "modifiers": [
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "k",
                "modifiers": {
                    "mandatory": [
                        "command"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "up_arrow",
                    "modifiers": [
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "l",
                "modifiers": {
                    "mandatory": [
                        "command"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "right_arrow",
                    "modifiers": [
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "h",
                "modifiers": {
                    "mandatory": [
                        "control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "left_arrow",
                    "modifiers": [
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "j",
                "modifiers": {
                    "mandatory": [
                        "control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "down_arrow",
                    "modifiers": [
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "k",
                "modifiers": {
                    "mandatory": [
                        "control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "up_arrow",
                    "modifiers": [
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "l",
                "modifiers": {
                    "mandatory": [
                        "control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "right_arrow",
                    "modifiers": [
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "h",
                "modifiers": {
                    "mandatory": [
                        "shift"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "left_arrow",
                    "modifiers": [
                        "shift"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "j",
                "modifiers": {
                    "mandatory": [
                        "shift"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "down_arrow",
                    "modifiers": [
                        "shift"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "k",
                "modifiers": {
                    "mandatory": [
                        "shift"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "up_arrow",
                    "modifiers": [
                        "shift"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "l",
                "modifiers": {
                    "mandatory": [
                        "shift"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "right_arrow",
                    "modifiers": [
                        "shift"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "h",
                "modifiers": {
                    "mandatory": [
                        "shift",
                        "command"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "left_arrow",
                    "modifiers": [
                        "shift",
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "j",
                "modifiers": {
                    "mandatory": [
                        "shift",
                        "command"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "down_arrow",
                    "modifiers": [
                        "shift",
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "k",
                "modifiers": {
                    "mandatory": [
                        "shift",
                        "command"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "up_arrow",
                    "modifiers": [
                        "shift",
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "l",
                "modifiers": {
                    "mandatory": [
                        "shift",
                        "command"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "right_arrow",
                    "modifiers": [
                        "shift",
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "h",
                "modifiers": {
                    "mandatory": [
                        "shift",
                        "control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "left_arrow",
                    "modifiers": [
                        "shift",
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "j",
                "modifiers": {
                    "mandatory": [
                        "shift",
                        "control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "down_arrow",
                    "modifiers": [
                        "shift",
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "k",
                "modifiers": {
                    "mandatory": [
                        "shift",
                        "control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "up_arrow",
                    "modifiers": [
                        "shift",
                        "command"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "bundle_identifiers": [
                        "^com\\.arobas-music\\.guitarpro7"
                    ],
                    "type": "frontmost_application_if"
                }
            ],
            "from": {
                "key_code": "l",
                "modifiers": {
                    "mandatory": [
                        "shift",
                        "control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "right_arrow",
                    "modifiers": [
                        "shift",
                        "command"
                    ]
                }
            ],
            "type": "basic"
        }
    ]
}

Which could be reduced to something like:

{
  "description": "hjkl for Guitar Pro",
  "manipulators": [
    {
      "conditions": [
        {
          "bundle_identifiers": ["^com\\.arobas-music\\.guitarpro7"],
          "type": "frontmost_application_if"
        }
      ],
      "from": {
        "key_code": "h",
        "modifiers": {
          "optional": ["command, control, shift"]
        }
      },
      "to": [
        {
          "key_code": "left_arrow",
          "modifiers": ["pass_through"]
        }
      ],
      "type": "basic"
    },
    {
      "conditions": [
        {
          "bundle_identifiers": ["^com\\.arobas-music\\.guitarpro7"],
          "type": "frontmost_application_if"
        }
      ],
      "from": {
        "key_code": "j",
        "modifiers": {
          "mandatory": ["command, control, shift"]
        }
      },
      "to": [
        {
          "key_code": "down_arrow",
          "modifiers": ["pass_through"]
        }
      ],
      "type": "basic"
    },
    {
      "conditions": [
        {
          "bundle_identifiers": ["^com\\.arobas-music\\.guitarpro7"],
          "type": "frontmost_application_if"
        }
      ],
      "from": {
        "key_code": "k",
        "modifiers": {
          "mandatory": ["command, control, shift"]
        }
      },
      "to": [
        {
          "key_code": "up_arrow",
          "modifiers": ["pass_through"]
        }
      ],
      "type": "basic"
    },
    {
      "conditions": [
        {
          "bundle_identifiers": ["^com\\.arobas-music\\.guitarpro7"],
          "type": "frontmost_application_if"
        }
      ],
      "from": {
        "key_code": "l",
        "modifiers": {
          "mandatory": ["command, control, shift"]
        }
      },
      "to": [
        {
          "key_code": "right_arrow",
          "modifiers": ["pass_through"]
        }
      ],
      "type": "basic"
    }
  ]
}

The second one would really be great to have.

wkoutre commented 6 months ago

My two cents after finding this thread:

I'm a software developer who wanted to remap some things when using WhatsApp Desktop:

This was as simple as using ChatGPT:

Create a complex rule for Karabiner Elements on macOS that changes the Return key for the WhatsApp desktop app to be "Shift+Return" and make "Cmd + Return" be the "Return" key

After a couple more messages, 2 working rules were created (attached). return_to_shift-return.json cmd-return_to_return.json

Maybe this helps someone!

lesiw commented 2 weeks ago

Instead of a rules engine, it would be nice if I could write Lua, or really, any embedded language.

Expressing complex conditionals in a configuration file is always going to be painful to some degree.

nejmeijer commented 5 days ago

Thanks for the detailed input! A good way to steer development towards this would be to send in a pull request with an attempt at implementing this so there is something to start from.