Permalink
23fdc43 Oct 17, 2018
3 contributors

Users who have contributed to this file

@yqrashawn @nikitavoloboev @narze
356 lines (281 sloc) 15 KB

Tutorial

Goku supports all the features that Karabiner Elements provides via its [JSON spec](https://pqrs.org/osx/karabiner/json.html).

If you have any question or advice on how to improve the tool, just open an issue or ask questions in the Telegram group.

Basic

tap a to insert 1, tap b to insert 2

;; main contains multiple manipulators
;; each manipulator has a description and multiple rules

;; main                -> {:main [{:des "..." :rules [rule1 rule2 ...]}]}
;; manipulator         -> {:des "..." :rules [rule1 rule2 ...]}
;; manipulator's rules -> [rule1 rule2 ...]
{:main [{:des "a to 1, b to 2, c to insert 1 2 3"
         :rules [[:a :1] [:b :2] [:c :1 :2 :3]]}]}

;; or we can separate them into two manipulators
{:main [{:des "a to 1" :rules [[:a :1]]}
        {:des "b to 2" :rules [[:b :2]]}
        {:des "c to insert 123" :rules [[:c [:1 :2 :3]]]}]}

;; in rules [:a :1] -- [<from> <to>]
;; it means from key a to key 1
;; these keycode is just original karabiner keycode prefix with colon

you can find all keycode definition in this file or use the Karabiner-EventViewer.app

tap a to insert 1 only in Google Chrome

{;; define application identifiers
 :applications {:chrome ["^com\\.google\\.Chrome$"]}
 :main [{:des "a to 1 only in chrome" :rules [[:a :1 :chrome]]}]}

;; [:a :1 :chrome] -- [<from> <to> <conditions>]
;; this means from a to 1 under the condition of chrome
;; :chrome is a predefined application condition
;; it's same with karabiner

;; and we can use multiple conditions
{:applications {:chrome ["^com\\.google\\.Chrome$"]
                :safari ["^com\\.apple\\.Safari$"]}
 :main [{:des "a to 1 only in chrome, safari" :rules [[:a :1 [:chrome :safari]]]}]}

;; we can also prefix use the opposite condition
{:applications {:chrome ["^com\\.google\\.Chrome$"]
                :safari ["^com\\.apple\\.Safari$"]}
 :main [{:des "a to 1 only outside chrome, safari" :rules [[:a :1 [:!chrome :!safari]]]}]}

other conditions

{:devices {:hhkb-bt [{:vendor_id 1278 :product_id 51966}]}

 :applications {:chromes ["^com\\.google\\.Chrome$" "^com\\.google\\.Chrome\\.canary$"]}

 :input-sources {:us {:input_mode_id ""
                      :input_source_id "com.apple.keylayout.US"
                      :language "en"}}

 :main [{:des "a to 1 multiple conditions"
         :rules [[:a :1 [:chromes :hhkb-bt :us]]]}]}

;; so this means press a to insert 1 in Google Chrome and Google Chrome Canary
;; while we are using the US input method, and the device that press a is
;; HHKB-BT keyboard.

the only condition that GokuRakuJoudo dose not support yet is keyboard type.

Command a to Control 1

{:main [{:des "command a to control 1" [:!Ca :!T1]}]}

;; this is a little bit wired, but it's convenient
;; the rule [:!Ca :!T1]
;; means from command a to control 1
;; :!Ca is keycode :a and prefix a with !C

;; here's the definition

;; !  | means mandatory
;; #  | means optional
;; C  | left_command
;; T  | left_control
;; O  | left_option
;; S  | left_shift
;; F  | fn
;; !! | mandatory command + control + optional + shift (hyper)
;; ## | optional any

;; examples

;; !CTSequal_sign  | mandatory command control shift =
;;                 | which is command control +
;; !O#Sright_arrow | mandatory option optional any right_arrow

;; karabiner definition of mandatory and optional
;; https://pqrs.org/osx/karabiner/json.html#from-event-definition-modifiers

;; rule [<from> <to>]
;; if simplified modifier is used in <to>, optional(#) definition will be
;; ignored.

from simultaneous key

Karabiner also has this functionality that can map simultaneous key presses to other events. We can use that in GokuRakuJoudo as well.

{:main [{:des "simultaneous j l press to F19" :rules [[[:j :l] :f19]]}]}
;; rule [[:j :l] :f19]
;;       <from>  <to>

;; so when from is a vector (or array), GokuRakuJoudo will parse it as
;; simultaneous key press

to shell command

We can set <to> to string to invoke shell command.

{:main [{:des "hyper 1 to cleanup personal folder"
         :rules [[:!!1 "rm -r ~/personal && mkdir ~/personal"]]}]}

;; or we can break it into two <to>

{:main [{:des "hyper 1 to cleanup personal folder"
         :rules [[:!!1 ["rm -r ~/personal" "mkdir ~/personal"]]]}]}

predefined templates

When we use karabiner to run shell commands or applescripts, we write them as string. The problem is these strings are often similar and really long. So there’s predefined templates.

It’s same with those string templates in some programming languages. In fact, it use the clojure’s string templates, which is actually java’s string templates implementation. eg.

{:templates {:launch "osascript -e 'tell application \"Alfred 3\" to run trigger \"launch%s\" in workflow \"yqrashawn.workflow.launcher\" with argument \"\"'"}
 :main [{:des "launcher mode"
         :rules [[:j [:launch "Alacritty"] :launch-mode]
                 [:k [:launch "Emacs"] :launch-mode]
                 [:l [:launch "Chrome"] :launch-mode]
                 [:m [:launch "Mail"] :launch-mode]
                 [:v [:launch "WeChat"] :launch-mode]
                 [:q [:launch "KE"] :launch-mode]
                 [:f [:launch "Finder"] :launch-mode]
                 [:9 [:launch "PDFExpert"] :launch-mode]
                 [:comma [:launch "Safari"] :launch-mode]
                 [:period [:launch "Paw"] :launch-mode]
                 [:equal_sign [:launch "Textual"] :launch-mode]
                 [:8 [:launch "KEEvents"] :launch-mode]
                 [:b [:launch "BearyChat"] :launch-mode]
                 [:t [:launch "TG"] :launch-mode]]}]}

;; This is my configuration to launch or display applications with wj wk wl etc.
;; First I define the :launch template. Then I use it in <to>.

;; [:j [:launch "Alacritty"] :launch-mode]
;; GokuRakuJoudo will parse the rule and replace "%s" with "Alacritty".
;; The "%s" is for string. If you need other formats, check out here.
;; java.util.Formatter doc:
;; https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html

Note

Don’t define conditions with the name of keycode. If you define a application condition like {:a ["^com\\.google\\.Chrome$"]}. It might work in rules but it may broke other things.

Advanced

variable condition

Karabiner’s variable condition functionality make it posible to define keyboard layers. We can use this functionality to use most keys as modifier keys. GokuRakuJoudo makes it really easy to use variable conditions.

{:main [{:des "tap w to set w-layer to 1"
         :rules [[:w ["w-layer" 1]]]}]}

;; this means tap w to set variable "w layer" to 1
;; rule [:w     ["w layer" 1]]
;;      |____| |____________|
;;       <from>     <to>

;; we can also set multiple <to>, and use the defined variable in <conditions>
{:main [{:des "tap w to insert w then set w-layer to 1"
         :rules [[:w [:w ["w-layer" 1]]]
                 [:1 [:1 :w] :w-layer]]}]}

;; rule [:w    [:w ["w layer" 1]]]
;;      |____| |_______________|
;;      <from>       <to>

The rules above are not really useful. Cause we lose w key in the first rule and we can’t set variable back in both rules. The old way in karabiner to define layer is using the to_if_alone option. We will talk about it soon.

froms and tos

So in the karabiner.json spec, there’re from event definition and to event definition. We can predefine this in GokuRakuJoudo as well.

froms definition

Since karabiner can send multiple to event triggerd by a single from event, we won’t froms definition too much. There’re two kinds of situation that we might want to use this.

;; from any key_code, consumer_key_code or pointing_button
{:froms {:from-any-consumer-key {:any "consumer_key_code"}}
 :main [{:des "disable all consumer key"
         :rules [[:from-any-consumer-key :vk_none]]}]}
{:froms {:from-any-keycode-key {:any "key_code"}}
 :main [{:des "disable keycode key"
         :rules [[:from-any-keycode-key :vk_none]]}]}
{:froms {:from-any-pointing-button {:any "pointing_button"}}
 :main [{:des "disable all pointing button"
         :rules [[:from-any-keycode-key :vk_none]
                 [{:any "pointing_button"} :vk_none]]}]}

;; configs above disable all kinds of keys.
;; rule [<from> <to> <conditions>]
;; we can put predefined from keyword into <from> section
;; or we can just put the from definition map into <from> section, since we
;; usually only use each froms one time.

;; WARNING
;; This is just a demonstration of what can be done with :any. DON'T TRY any of
;; these three rules, they will disable all your keyboard key and mouse button.

;; set simultaneous_options
{:sim [:f :j]
 :simo {:interrupt true
        :dorder :strict
        :uorder :strict_inverse
        :afterup {:set ["fj layer" 1]}}}

The simultaneous_options won’t be used frequently. You can find the the detail in the froms documentation, which is above its implementation.

tos definition

Tos is used more often than froms. It’s the same idea as froms definition. You can find the detailed tos documentation in the implementation file. There’s shot cuts for tos in rules’ <to>, like string to shell commands and multiple to definitions in vector.

You only need to use to definition if you want to use or set select_input_source, mouse_key, lazy, repeat, halt, hold_down_milliseconds.

simlayers

In karabiner, there’s two kinds of layers implementation. I’ll just call them the old layer and simlayer. I don’t know if I can explain this clearly. You may really understand this after tring these two kinds of config. If you are familier with the karabiner.json configuration, you can compare the old layer example and the new layer example.

explanation

TLDR; Basically, if you type fast, use simlayer, otherwise, use the old layer. If you don’t care about this, you can just jump to the next header, which is how to set this in GokuRakuJoudo.

The old layer has the same definition as “layers” in thoes keyboard firmware keymap editors. eg.

press w key down --> in w layer ("w layer" set to 1)
tap 1 key      --> trigger key 1's definition under w layer ("w layer" is 0)
tap 2 key      --> trigger key 2's definition under w layer ("w layer" is 0)
release w key up --> out w layer ("w layer" is 0)

There’re two problems in old layer. When we type “w1” really fast, we trigger the “1” in w layer rather than insert “w1”. When we keep press w key down, the w key won’t repeat. There won’t be a “wwwwwwwwwwwwwwwwwwww”.

The karabiner’s simlayer is based on its simultaneous functionality. It’s like this. The --> is the symbol of time.

press w key down --> if in threshold milliseconds
                    --> press 1 key ("w layer" set to 1)
                    --> in w layer and trigger the 1 definition
                        --> press 2 key even after the threshold ("w layer" is still 1)
                        --> in w layer and trigger the 2 definition
                            --- we hold the w key for 1 year ---> ("w layer" is still 1)
                            release w to set "w layer" to 0
                 --> if after threshold milliseconds
                 --> w key begin to repeat, we get "wwwwwwwwwwwww"

This solves those two problems. But we need to trigger the second key fast, or the first key starts to repeat. We need must trigger a action the same time we enter a layer. We can’t enter the layer in advance and think what we really want to do in that layer.

layer and simlayer in GokuRakuJoudo

;; simlayer
{:simlayers {:period-mode {:key :period}}
 :main [{:des "period mode"
         :rules [[:d :!S9 :period-mode] ;; .d insert (
                 [:f :!S0 :period-mode] ;; .f insert )
                 [:a [:!Sgrave_accent_and_tilde :slash] :period-mode] ;; .a insert ~/
                 [:s [:period :!S8] :period-mode]]}]} ;; .s insert .*

;; layer
;; I've thought about implement a predefined layer section, but it's just
;; already really easy to set up with what we have now.
{:main [{:des "period mode"
         :rules [[:period ["period-mode" 1] nil {:afterup ["period-mode" 0]
                                                 :alone :period}]
                 [:d :!S9 ["period-mode" 1]]
                 [:f :!S0 ["period-mode" 1]]
                 [:a [:!Sgrave_accent_and_tilde :slash ] ["period-mode" 1]]
                 [:s [:period :!S8] ["period-mode" 1]]]}]}

;; So the first rule is to define period down trigger set variable so that we
;; enter the layer.
;; rule [:period ["period-mode" 1] nil {:afterup ["period-mode" 0] :alone :period}]
;;       |_____| |_______________| |_| |_________________________________________|
;;        <from>    <to>      <conditions>         <other options>

;; so we have a <other options> here, and we know that <conditions> can be nil

;; rule is actually the manipulator in karabienr.json, checkout here
;; https://pqrs.org/osx/karabiner/json.html#complex_modifications-manipulator-definition

;; We can see there are ~type~, ~from~, ~to~, ~to_if_alone~, ~to_if_held_down~,
;; ~to_after_key_up~, ~to_delayed_action~, ~description~, ~conditions~,
;; ~parameters~. We already have <from> <to> <conditions>, and we can omit
;; ~type~ and ~description~.

;; So <other options> includs ~to_if_alone~, ~to_if_held_down~,
;; ~to_after_key_up~, ~to_delayed_action~ and ~parameters~.

;; The first 4 is same as tos definition, and we also have shotcusts for ~parameters~.

We can checkout the documentation for <other options>. And there’re also <to> documentation, <from> documentation, <conditions> documentation.