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.6k stars 837 forks source link

[Tutorial] Workaround of using different profiles for 2 or more keyboards #67

Closed hirakujira closed 6 years ago

hirakujira commented 8 years ago

This is not a issue... just want to share this workaround but the Wiki is not available yet.

For me, I have 2 keyboards: Apple Internal and a PC keyboard. So I need to switch Option and Command when I use the PC keyboard, but not in the internal keyboard.

Since Karabiner-Elements haven't support to set profile for specific devices yet, the workaround is swapping the profile automatically. :)

So, use any text editor, and modify the following shell script, then save as karabinerOff.command

#! /bin/bash
mv ~/.karabiner.d/configuration/karabiner.json ~/.karabiner.d/karabiner.json 
killall karabiner_console_user_server
say "Karabiner is off"

Then save the following script as karabinerOn.command

#! /bin/bash
mv ~/.karabiner.d/karabiner.json ~/.karabiner.d/configuration/karabiner.json 
killall karabiner_console_user_server
say "Karabiner is on"

So you can click the .command file to move the profile and restart Karabiner daemon automatically. For me, I use TextExpander, which supports shell script, so I just set the commands as a snippet, which is much easier.

dpiro commented 8 years ago

this is helpful - THANKS

eriktaubeneck commented 8 years ago

This is great, thanks!

That said, Karabiner had an option to not remap Apple keyboards without the need to run a script. It would be great to have this as an option in Karabiner-Elements.

adrianluff commented 7 years ago

Karabiner Elements monitors the JSON config file for changes so it's reliable to just swap the JSON between alternate configurations rather than killing the process. This also allows you to have some keys redefined for Mac with different keys defined for PC.

Below is a script I wrote to quickly switch between a Mac and PC keyboard Karabiner Elements JSON file. It assumes you have the following files in ~/.karabiner.d/configuration:

mackeyboard.json pckeyboard.json

The script will toggle the two JSON files for you. It has zero error handling at the moment but should be reliable for most use cases.

You can run with -h for help.

#!/usr/bin/env bash

declare -i verboseMode=1

usage="Usage: $0 [-h|q|v]\n
    -h\tHelp
    -q\tQuiet mode
    -v\tVerbose mode"

while getopts "hqv" opt;
    do case ${opt} in
        h ) echo -e ${usage}
            exit 1;;
        q ) verboseMode=0;;
        v ) verboseMode=1;;
    esac
done
shift $(($OPTIND - 1))

if [[ -f ~/.karabiner.d/configuration/.pckeyboard.json ]]; then # switch to Mac
    command cp -f ~/.karabiner.d/configuration/mackeyboard.json ~/.karabiner.d/configuration/karabiner.json
    command mv -f ~/.karabiner.d/configuration/.pckeyboard.json ~/.karabiner.d/configuration/pckeyboard.json
    command mv -f ~/.karabiner.d/configuration/mackeyboard.json ~/.karabiner.d/configuration/.mackeyboard.json
    if [[ -n verboseMode ]]; then echo "Mac keyboard active in Karabiner Elements!"; fi
elif [[ -f ~/.karabiner.d/configuration/.mackeyboard.json ]]; then # switch to PC
    command cp -f ~/.karabiner.d/configuration/pckeyboard.json ~/.karabiner.d/configuration/karabiner.json
    command mv -f ~/.karabiner.d/configuration/pckeyboard.json ~/.karabiner.d/configuration/.pckeyboard.json
    command mv -f ~/.karabiner.d/configuration/.mackeyboard.json ~/.karabiner.d/configuration/mackeyboard.json
    if [[ -n verboseMode ]]; then echo "PC keyboard active in Karabiner Elements!"; fi
else # unknown state - default to Mac
    command cp -f ~/.karabiner.d/configuration/mackeyboard.json ~/.karabiner.d/configuration/karabiner.json
    command mv -f ~/.karabiner.d/configuration/mackeyboard.json ~/.karabiner.d/configuration/.mackeyboard.json
    echo "Current state unknown - defaulting to Mac keyboard active in Karabiner Elements!"
fi
zxaos commented 7 years ago

Here's an Alfred workflow that cycles through multiple profiles defined in a single karabiner.json file:

https://gist.github.com/zxaos/59cd3e967c72f576738997e46ddacb74

So you could set up karabiner.json like:

{"profiles": [
        {
            "selected": true,
            "simple_modifications": {
                "caps_lock": "f18",
                "escape": "caps_lock"
            },
            "name": "internal"
        },
        {
            "selected": false,
            "simple_modifications": {
                "left_option": "left_command",
                "right_command": "right_option",
            },
            "name": "external"
        }]}

Then you can simply toggle the "selected" key to switch which one is active. The benefit here is that you aren't limited to only two profiles, you can just keep adding them and cycling through as necessary.

Or, if you prefer the underlying python script:

from os import path
import sys
import json

targetfile = path.expanduser('~/.karabiner.d/configuration/karabiner.json')

with open(targetfile) as settings_file:
    settings = json.load(settings_file)

active_index = next(index for (index, profile) in enumerate(settings['profiles']) if profile["selected"] == True)

# disable current active profile
settings['profiles'][active_index]['selected'] = False;

# set new active profile
next_profile = (active_index + 1) % len(settings['profiles'])
settings['profiles'][next_profile]['selected'] = True;

with open(targetfile, 'w') as settings_file:
    json.dump(settings, settings_file, indent=4, separators=(',', ': '))

sys.stdout.write(settings['profiles'][next_profile]['name'])
AndrianBdn commented 7 years ago

I've created a simple Mac app that enables Karabiner Elements when Apple Thunderbolt display is connected (my keyboard is hooked to it) : https://github.com/AndrianBdn/KarabinerElementsToggler

smarsching commented 7 years ago

@AndrianBdn: Thanks for the nice idea of looking for a network adapter in order to determine which profile to use. I used this idea for creating a shell script that basically does the same job:

#!/bin/bash

karabiner_dir="$HOME/.karabiner.d/configuration"
karabiner_conf="$karabiner_dir/karabiner.json"
karabiner_conf_enabled="$karabiner_conf.enabled"
karabiner_conf_disabled="$karabiner_conf.disabled"
mac_address="02:01:02:03:04:05"

files_match() {
  [ `md5 -q "$1"` = `md5 -q "$2"` ]
}

while true; do
  if ifconfig | grep -qi "ether $mac_address"; then
    if ! files_match "$karabiner_conf_enabled" "$karabiner_conf"; then
      cp "$karabiner_conf_enabled" "$karabiner_conf"
      osascript -e "display notification \"Karabiner has been enabled\" with title \"Karabiner Elements\"" 2>/dev/null
    fi
  else
    if ! files_match "$karabiner_conf_disabled" "$karabiner_conf"; then
      cp "$karabiner_conf_disabled" "$karabiner_conf"
      osascript -e "display notification \"Karabiner has been disabled\" with title \"Karabiner Elements\"" 2>/dev/null
    fi
  fi
  sleep 1
done

When this script is saved as toggle-karabiner.sh it can be started in the background by running

./toggle-karabiner.sh &
disown %1

One could adapt this code to look for a certain USB keyboard being connected by parsing the output of ioreg -p IOUSB -l -w 0, but this would be a bit more complicated because the vendor ID and the product ID are not on the same line.

hirakujira commented 7 years ago

@smarsching Thanks for your bash script!

I changed it a bit to:

  1. Detect USB devices
  2. Use sed to enable/disable profile instead of swapping file

I think the productID is not very possible to conflict if you use external keyboard that manufactured by 3rd party. So it's not perfect, but enough for me now.

#!/bin/bash
karabiner_dir="$HOME/.karabiner.d/configuration"
karabiner_conf="$karabiner_dir/karabiner.json"
productID='610' #productID should be conveted to decimal
function conf_enabled() {
  if grep -q 'true' "$karabiner_conf"; then
    return 1
  else
    return 0
  fi
}
while true; do
  if ioreg -p IOUSB -l -w 0 | grep 'idProduct' | grep -qi $productID; then
    if conf_enabled; then
      sed -i '' 's/false/true/g' "$karabiner_conf"
      osascript -e "display notification \"Karabiner has been enabled\" with title \"Karabiner Elements\"" 2>/dev/null
    fi
  else
    if ! conf_enabled; then
      sed -i '' 's/true/false/g' "$karabiner_conf"
      osascript -e "display notification \"Karabiner has been disabled\" with title \"Karabiner Elements\"" 2>/dev/null
    fi
  fi
  sleep 5
done
lhanson commented 7 years ago

Thanks for the script ideas! Here's an improvement to the check for productId:

if ioreg -p IOUSB -l -w 0 | grep -qi "\"idProduct\" = $productId$"; then

This eliminates the extra grep invocation and also ensures that the entire productId matches to the end of the line. Previously the script would match a subset of the ID string.

smarsching commented 7 years ago

FYI, there seems to be progress on adding support for matching certain devices directly into Karabiner Elements. The code for parsing the corresponding configuration structures is already there, and there also seems to be some work on using this information at runtime (e.g. 3db15dc549ca04ffad1760244fb89824c782d039 added some code for this). Of course I do not know how complete this code is and when it will be usable, but it looks like things are progressing quickly.

smarsching commented 7 years ago

As of version 0.90.57 devices can be ignored by specifying them in the configuration file (can be done via the preferences GUI).

This is not just simpler than any of the workarounds discussed in this issue, but also has the advantage that it will actually work while the device for which the remappings should be active is connected.

As this issue seems to be resolved now, I think it should be closed.

zxaos commented 7 years ago

Doesn't resolve all of the use cases.

I don't want to disable all of the remapping when using a specific device, I want to use different ones. Turning them all off doesn't help me at all, I still need to use the switching scripts.

wolfwolker commented 7 years ago

Nice workarounds all you mention here, but in the future I would like to have a third select box in the ui to select in which device should be applied each modification (ie all, internal, usb). I imagine this is a big change but it would be awesome. PS: thanks for all your work.

mbigras commented 7 years ago

Just checking in on this, like @smarsching said there is 3db15dc which looks like it might have some device specific options.

I tried @zxaos 's workflow but it didn't work for. I also noticed there is a devices array in the config file now as shown below:

{
    "profiles": [
        {
            "devices": [
                {
                    "identifiers": {
                        "is_keyboard": true,
                        "is_pointing_device": false,
                        "product_id": 1957,
                        "vendor_id": 1118
                    },
                    "ignore": false,
                    "keyboard_type": 0
                },
                {
                    "identifiers": {
                        "is_keyboard": true,
                        "is_pointing_device": false,
                        "product_id": 601,
                        "vendor_id": 1452
                    },
                    "ignore": false,
                    "keyboard_type": 0
                }
            ],
            "fn_function_keys": {
                "f1": "vk_consumer_brightness_down",
                "f10": "mute",
                "f11": "volume_down",
                "f12": "volume_up",
                "f2": "vk_consumer_brightness_up",
                "f3": "vk_mission_control",
                "f4": "vk_launchpad",
                "f5": "vk_consumer_illumination_down",
                "f6": "vk_consumer_illumination_up",
                "f7": "vk_consumer_previous",
                "f8": "vk_consumer_play",
                "f9": "vk_consumer_next"
            },
            "name": "Default profile",
            "selected": true,
            "simple_modifications": {
                "caps_lock": "left_control"
            }
        }
    ]
}

I tried putting some settings inside the array, but it didn't work:

{
    "profiles": [
        {
            "devices": [
                {
                    "identifiers": {
                        "is_keyboard": true,
                        "is_pointing_device": false,
                        "product_id": 1957,
                        "vendor_id": 1118
                    },
                    "ignore": false,
                    "keyboard_type": 0,
                    "name": "Microsoft keyboard",
                    "selected": true,
                    "simple_modifications": {
                        "caps_lock": "left_control",
                        "left_command": "left_option"
                    }
                },
                {
                    "identifiers": {
                        "is_keyboard": true,
                        "is_pointing_device": false,
                        "product_id": 601,
                        "vendor_id": 1452
                    },
                    "ignore": false,
                    "keyboard_type": 0
                }
            ],
.
.
.

@zxaos is there anything you recommend I try to figure out why the workflow isn't working for me?

zxaos commented 7 years ago

@mbigras The script should toggle between objects in the "profiles" array - for your first configuration there only has the one profile, and your second configuration is missing a "selected": false key for the script to toggle.

There's an example of an updated configuration with device stanzas below. Make sure each profile has a selected key and a name key.

For debugging, you could also download the python script directly and run it from the command line so you can more easily step through it, but try just adding the missing keys first 😄

{
    "profiles": [
        {
            "devices": [
                {
                    "identifiers": {
                        "is_keyboard": true,
                        "is_pointing_device": false,
                        "product_id": 627,
                        "vendor_id": 1452
                    },
                    "ignore": false,
                    "keyboard_type": 0
                }
            ],
            "fn_function_keys": {
                "f1": "vk_consumer_brightness_down",
                "f10": "mute",
                "f11": "volume_down",
                "f12": "volume_up",
                "f2": "vk_consumer_brightness_up",
                "f3": "vk_mission_control",
                "f4": "vk_launchpad",
                "f5": "vk_consumer_illumination_down",
                "f6": "vk_consumer_illumination_up",
                "f7": "vk_consumer_previous",
                "f8": "vk_consumer_play",
                "f9": "vk_consumer_next"
            },
            "name": "internal",
            "selected": true,
            "simple_modifications": {
                "caps_lock": "f18",
                "escape": "caps_lock"
            }
        },
        {
            "fn_function_keys": {
                "f1": "vk_consumer_brightness_down",
                "f10": "mute",
                "f11": "volume_down",
                "f12": "volume_up",
                "f2": "vk_consumer_brightness_up",
                "f3": "vk_mission_control",
                "f4": "vk_launchpad",
                "f5": "vk_consumer_illumination_down",
                "f6": "vk_consumer_illumination_up",
                "f7": "vk_consumer_previous",
                "f8": "vk_consumer_play",
                "f9": "vk_consumer_next"
            },
            "name": "external",
            "selected": false,
            "simple_modifications": {
                "caps_lock": "f18",
                "escape": "caps_lock",
                "left_command": "left_option",
                "left_option": "left_command",
                "right_command": "right_option",
                "right_option": "right_command"
            }
        }
    ]
}
mbigras commented 7 years ago

Thank you @zxaos! working keymap is here https://gist.github.com/mbigras/6661dd08e6ac0f7c4556ce5a72355bfb

frnhr commented 7 years ago

Thanks to all here for a good workaround! I think I have a clever addition to it that I'd like to share...

I'm using Python script by @zxaos pretty much as-is. But instead of running it manually, I have added it as a shortcut using BetterTouchTool. It has a nice new feature of being able to bind to "Key Sequence" which I'm using to map a triple-keystroke of my most often mistaken key.

Here is the full use case: I strike a key while on the wrong profile. I get angry 😠 But I can immediately do a rather satisfying triple keystroke on that same key - that triggers BetterTouchTool to execute the Python script which in turn switches the profile and I can continue typing. As an added bonus, I can set BetterTouchTool to also send a backspace to delete the offending character, and also to type the desired character in its place.

Granted, In my case I only need to swap one pair of keys between devices, so your mileage may vary if that is not the case for you.

Another thing: BetterTouchTool can't directly execute a script, but it can run Apple Script, and it is fairly simple to execute scripts from there (plus show a notification, totally optional):

set activeProfile to do shell script "python ~/.karabiner_switch.py"
display notification "Activated: " & activeProfile

And the beauty of it all is that once the desired profile has been activated in this way, repeating the angry triple keystroke on the same key will not trigger any more profile changes (i.e. won't switch back to the wrong profile). Unless on another device, of course, which is good.

BTT config:

screenshot_22_12_2016__03_36

cwsmith commented 7 years ago

I found that KE 0.90.75 only updates the profile when the ~/.config/karabiner/karabiner.json file is changed; not the ~/.karabiner.d/configuration/karabiner.json file.

zxaos commented 7 years ago

@cwsmith Yeah, that's mentioned in the changelog that pops up when you upgrade! 😄

You'll need to update the paths in any scripts you use.

awinecki commented 7 years ago

Hey guys, I am regularly using an Apple keyboard and a standard USB one, oftenly switching. As I struggled to switch between the two layouts, I've created an Alfred Workflow to help with this.

I just saw @zxaos answer with an Alfred Workflow as well, but I've already created my own so I guess I can share it. The difference is that mine doesn't simply cycle through your profiles, but gives you a full list in a visually appealing way and then you can choose one you want to switch to.

So if you're using Alfred, have a look at Karabiner Elements Profile Switcher, or download directly from Packal directory.

Hope this helps some of you out there. Here's a screenshot:

zjunothing commented 7 years ago

I have got a nice workaround i think:

(Assume i have two profile: Debug & Default ) Write a script (eg. named Debug-profile.sh): gsed -i '/"name": "Debug"/!b;n;c"selected": true,' ~/.config/karabiner/karabiner.json; gsed -i '/"name": "Default"/!b;n;c"selected": false,' ~/.config/karabiner/karabiner.json and a script (eg. named Default-profile.sh): gsed -i '/"name": "Debug"/!b;n;c"selected": false,' ~/.config/karabiner/karabiner.json; gsed -i '/"name": "Default"/!b;n;c"selected": true,' ~/.config/karabiner/karabiner.json (if you are using linux, just use sed, if your are using mac, just install gsed(gun-sed) via brew install gsed

and if you want switch to one profile ,just run the debug-profile.sh or default-profile.sh, you can surely add them in your .bashrc and make alias.

starsy commented 7 years ago

I have made some enhancements to KBE to remove the pain of switching profiles for 2+ keyboards by supporting per-device configuration. Please take a look at https://github.com/starsy/Karabiner-Elements if you are interested in. The release/download link is here: https://github.com/starsy/Karabiner-Elements/releases/tag/v1.0_merged . Pull request is here: https://github.com/tekezo/Karabiner-Elements/pull/752. It works pretty stably on my MBPR, I think it worths a try. Hope it can solve your problems too. Please don't hesitate to let me know if any issues or questions.

image

jlippold commented 7 years ago

@starsy I tried to use your fork but the dropdowns aren't populated

http://s3.amazonaws.com/PicUp/ugSH70.png

starsy commented 7 years ago

@jlippold maybe you were using the old release, please try the latest one and let me know: https://github.com/starsy/Karabiner-Elements/releases/tag/v1.1

frnhr commented 7 years ago

I just wanna say: for me the original Python script by @zxaos works with current Karabiner elements on a new Mac: karabiner-elements_preferences

caseymhunt commented 7 years ago

@starsy This is exactly what I need, however I require the complex modifications to enable me to remap caps lock to a hyper key modifier (all modifier keys).

My specific use case is that I need to remap keys on an external keyboard, remap caps lock on the MBP keyboard, and most importantly prevent the external remap from affecting the MBP keyboard.

wincent commented 7 years ago

See: https://github.com/tekezo/Karabiner-Elements/commit/fd90e595e1d8c1cf972e6694b85be294b773954c

stale[bot] commented 6 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.