Karabiner-Elements icon indicating copy to clipboard operation
Karabiner-Elements copied to clipboard

Allow customizable and extendable complex modification settings

Open dPowNextdoor opened this issue 2 years ago • 14 comments

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
{
  "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
{
  "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:

  • Global conditions.
    • Applied by default to every rule in rules.
  • Rule-level conditions.
    • Applied by default to every manipulator in manipulators.
    • Overrides any global conditions if they have the same type.
  • Manipulator-level conditions.
    • Applied only to a single manipulator.
    • Overrides both rule-level and global conditions if they have the same type.
  • Since lower-level conditions override higher-level conditions, there is no more need for frontmost_application_unless, etc. rules since the rules that apply only to those applications are overridden only in that application's rule/manipulators.
  • Alternatively, if it's easier for development, add new optional condition.id and condition.overrides[ids] fields so users can specify which conditions they want to override manually.

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."
                    ]
                }
            ]
        }
    ]
}

dPowNextdoor avatar Jul 22 '21 18:07 dPowNextdoor

+1

hitriyvalenok avatar Jul 22 '21 21:07 hitriyvalenok

+2

jsonMartin avatar Dec 27 '21 19:12 jsonMartin

+3

Arthur-Null avatar May 03 '22 17:05 Arthur-Null

+4

rockiecxh avatar May 16 '22 10:05 rockiecxh

+5

Czilis avatar May 24 '22 18:05 Czilis