├── .gitignore
├── .rspec
├── .travis.yml
├── CHANGELOG.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── karabiner
└── karabiner-dsl
├── img
├── disabled.png
└── enabled.png
├── karabiner.gemspec
├── lib
├── karabiner.rb
└── karabiner
│ ├── appdef.rb
│ ├── cli.rb
│ ├── config.rb
│ ├── dsl
│ ├── group.rb
│ ├── item.rb
│ └── root.rb
│ ├── group.rb
│ ├── history.rb
│ ├── item.rb
│ ├── key.rb
│ ├── namespace.rb
│ ├── property.rb
│ ├── remap.rb
│ ├── root.rb
│ ├── version.rb
│ ├── vkopenurldef.rb
│ └── xml_tree.rb
└── spec
├── lib
├── karabiner
│ ├── appdef_spec.rb
│ ├── item_spec.rb
│ ├── karabiner_spec.rb
│ ├── key_spec.rb
│ └── remap_spec.rb
└── karabiner_spec.rb
└── spec_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | *.bundle
19 | *.so
20 | *.o
21 | *.a
22 | mkmf.log
23 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | -c
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 2.3.0
3 | branches:
4 | only:
5 | - master
6 | script:
7 | - 'bundle exec rspec .'
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [v0.4.0](https://github.com/k0kubun/karabiner-dsl/tree/v0.4.0) (2016-07-12)
4 | [Full Changelog](https://github.com/k0kubun/karabiner-dsl/compare/v0.3.1...v0.4.0)
5 |
6 | **Closed issues:**
7 |
8 | - Add support for consumer key [\#6](https://github.com/k0kubun/karabiner-dsl/issues/6)
9 |
10 | **Merged pull requests:**
11 |
12 | - Improve keycode support [\#7](https://github.com/k0kubun/karabiner-dsl/pull/7) ([ardecvz](https://github.com/ardecvz))
13 |
14 | ## [v0.3.1](https://github.com/k0kubun/karabiner-dsl/tree/v0.3.1) (2016-05-17)
15 | [Full Changelog](https://github.com/k0kubun/karabiner-dsl/compare/v0.3.0...v0.3.1)
16 |
17 | ## [v0.3.0](https://github.com/k0kubun/karabiner-dsl/tree/v0.3.0) (2015-12-31)
18 | [Full Changelog](https://github.com/k0kubun/karabiner-dsl/compare/v0.2.1...v0.3.0)
19 |
20 | **Merged pull requests:**
21 |
22 | - Can invoke the application of any place [\#5](https://github.com/k0kubun/karabiner-dsl/pull/5) ([elim](https://github.com/elim))
23 |
24 | ## [v0.2.1](https://github.com/k0kubun/karabiner-dsl/tree/v0.2.1) (2015-08-30)
25 | [Full Changelog](https://github.com/k0kubun/karabiner-dsl/compare/v0.2.0...v0.2.1)
26 |
27 | ## [v0.2.0](https://github.com/k0kubun/karabiner-dsl/tree/v0.2.0) (2015-07-08)
28 | [Full Changelog](https://github.com/k0kubun/karabiner-dsl/compare/v0.1.2...v0.2.0)
29 |
30 | ## [v0.1.2](https://github.com/k0kubun/karabiner-dsl/tree/v0.1.2) (2015-05-21)
31 | [Full Changelog](https://github.com/k0kubun/karabiner-dsl/compare/v0.1.1...v0.1.2)
32 |
33 | **Merged pull requests:**
34 |
35 | - Add supporting BACKQUOTE [\#4](https://github.com/k0kubun/karabiner-dsl/pull/4) ([cnosuke](https://github.com/cnosuke))
36 |
37 | ## [v0.1.1](https://github.com/k0kubun/karabiner-dsl/tree/v0.1.1) (2014-11-16)
38 | [Full Changelog](https://github.com/k0kubun/karabiner-dsl/compare/v0.0.8...v0.1.1)
39 |
40 | ## [v0.0.8](https://github.com/k0kubun/karabiner-dsl/tree/v0.0.8) (2014-11-16)
41 | [Full Changelog](https://github.com/k0kubun/karabiner-dsl/compare/v0.1.0...v0.0.8)
42 |
43 | ## [v0.1.0](https://github.com/k0kubun/karabiner-dsl/tree/v0.1.0) (2014-11-16)
44 |
45 |
46 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in karabiner.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Takashi Kokubun
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Karabiner DSL [](https://travis-ci.org/k0kubun/karabiner-dsl)
2 |
3 | Lightweight keyremap configuration DSL for [Karabiner](https://pqrs.org/osx/karabiner/index.html.en)
4 |
5 | ## Why Karabiner DSL?
6 |
7 | Original [Karabiner's configuration](https://pqrs.org/osx/karabiner/xml.html.en) is very hard to write.
8 | Karabiner DSL is its wrapper, which is easy-to-write and readable.
9 |
10 | If you write Karabiner's config with Karabiner DSL, you can update your keyremap configuration quickly.
11 |
12 | ## Installation
13 |
14 | First of all, you have to install [Karabiner](https://pqrs.org/osx/karabiner/index.html.en).
15 | Karabiner is a keyboard remap utility for Mac OSX.
16 |
17 | Then execute:
18 |
19 | ```bash
20 | $ gem install karabiner
21 | ```
22 |
23 | Then `karabiner` executable will be installed.
24 | This gem provides only `karabiner dsl` subcommand and other subcommands are delegated to original CLI for Karabiner.app.
25 |
26 | ## Usage
27 | ### 1. Create ~/.karabiner
28 |
29 | ```rb
30 | item "Command+G to open Google Chrome" do
31 | remap "Cmd-g", to: invoke("Google Chrome")
32 | end
33 | ```
34 |
35 | ### 2. Execute karabiner dsl command
36 |
37 | ```bash
38 | $ karabiner dsl
39 | ```
40 |
41 | Then `karabiner dsl` will update Karabiner's config as you expected.
42 |
43 | 
44 |
45 | ### 3. Enable your favorite configurations
46 |
47 | 
48 |
49 | Enjoy!
50 |
51 | ## How to write ~/.karabiner
52 | ### Basics
53 |
54 | karabiner-dsl's DSL is a superset of Ruby.
55 | So you can use any Ruby methods in ~/.karabiner.
56 |
57 | #### item
58 |
59 | ```rb
60 | item "configuration unit" do
61 | ...
62 | end
63 | ```
64 |
65 | In karabiner-dsl, any Karabiner's configuration unit is expressed in `item` and its `do ~ end` block.
66 | You can group some remap configurations in one item and enable them in one click.
67 |
68 | #### remap
69 |
70 | ```rb
71 | item "remap example" do
72 | remap "Cmd-a", to: "C-a"
73 | end
74 | ```
75 |
76 | If you want to add remap configuration, you have to call `remap` method.
77 | In this example, Command+A will be remapped to Control+A.
78 |
79 | You have to write "key expression" to specify keys to remap.
80 |
81 | #### key expression
82 |
83 | - `a`, `A`, `1`, `;`, `tab`, `Tab`, `space`, `up`, `down`
84 | - any string without `-` or `+` will be regarded as a single key
85 | - strings are case insensitive
86 | - `Volume Mute`, `Mute`, `Brightness Down`, `Brightness_Down`
87 | - support system (consumer) keys
88 | - words could be separated by space ` ` or underscore `_`
89 | - nil, `nil`, `none`
90 | - you can drop any key by using nil or the corresponding aliases
91 |
92 | #### key combination
93 |
94 | | Key combination | Regarded as | Notes |
95 | | ---------------------------------------------------------- | --------------------- | ------ |
96 | | `C-a`, `Ctrl-a`, `C+a`, `Ctrl+a` | Control + A | `C-` is a short alias for `Ctrl-` |
97 | | `M-a`, `Opt-a`, `Alt-a`, `M+a`, `Opt+a`, `Alt+a` | Option + A | `M-` and `Alt-` are aliases for `Opt-` |
98 | | `Shift-a`, `Shift+a` | Shift + A (capital A) | if you write just `A`, it will be regarded as small a |
99 | | `Cmd-a`, `Cmd+a` | Command + A | |
100 | | `Cmd-Shift-a`, `Cmd+Shift-a`, `Cmd-Shift+a`, `Cmd+Shift+a` | Command + Shift + A | you can use any combination of Ctrl, Opt, Shift, Cmd |
101 |
102 | #### available single keys
103 |
104 | [Karabiner DSL keycode and alias reference](https://github.com/k0kubun/karabiner-dsl/blob/master/lib/karabiner/key.rb)
105 |
106 | [Karabiner full keycode reference](https://pqrs.org/osx/karabiner/xml.html.en#keycode-list)
107 |
108 | *Note:* Karabiner DSL is designed to work with all keycodes which are supported by Karabiner
109 | (listed in the full keycode reference).
110 |
111 | ```
112 | a b c ... x y z
113 | 0 1 2 ... 7 8 9
114 |
115 | F1 F2 ... F11 F12
116 | \ [ ] ; ' ` , . / - =
117 | Up Down Right Left
118 | Space Tab Delete ... Forward_Delete Esc Capslock
119 | Mute Volume_Down Volume_Up ... Prev Play Next
120 |
121 | nil none
122 |
123 | Ctrl_R Ctrl_L
124 | Opt_R Opt_L Alt_R Alt_L
125 | Cmd_R Cmd_L
126 | Shift_R Shift_L
127 | ```
128 |
129 | ## Sample
130 |
131 | ```rb
132 | item "Application shortcuts" do
133 | remap "C-o", to: invoke("YoruFukurou") # Invoke the app under the /Applications.
134 | remap "C-u", to: invoke("Google Chrome") # ditto.
135 | remap "C-h", to: invoke("/Users/johndoe/Applications/iTerm.app") # Invoke the app of the specified path.
136 | end
137 |
138 | item "Copy date" do
139 | remap "Cmd-d", to: execute("date|pbcopy")
140 | end
141 |
142 | item "Control+PNBF to Up/Down/Left/Right" do
143 | remap "C-p", to: "Up"
144 | remap "C-n", to: "Down"
145 | remap "C-b", to: "Left"
146 | remap "C-f", to: "Right"
147 | end
148 |
149 | appdef "HIPCHAT", equal: "com.hipchat.HipChat"
150 | item "HipChat Room Change", only: "HIPCHAT" do
151 | remap "Cmd-K", to: "Cmd-Shift-["
152 | remap "Cmd-J", to: "Cmd-Shift-]"
153 | end
154 |
155 | item "Window change in the same app" do
156 | remap "Opt-tab", to: "Cmd-F1"
157 | end
158 | ```
159 |
160 | ## Linux alternative
161 | You could achieve the same functionality on Linux/X11 with a sister project [xkremap](https://github.com/k0kubun/xkremap).
162 | It has a similar syntax and doesn't need any Ruby interpreter to run.
163 |
164 | ## Contributing
165 |
166 | 1. Fork it ( https://github.com/k0kubun/karabiner-dsl/fork )
167 | 2. Create your feature branch (`git checkout -b my-new-feature`)
168 | 3. Commit your changes (`git commit -am 'Add some feature'`)
169 | 4. Push to the branch (`git push origin my-new-feature`)
170 | 5. Create a new Pull Request
171 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
3 | task :spec do
4 | system("bundle exec rspec")
5 | end
6 |
7 | task default: :spec
8 |
--------------------------------------------------------------------------------
/bin/karabiner:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | lib = File.dirname(__FILE__) + "/../lib"
4 | $:.unshift lib unless $:.include? lib
5 |
6 | require "karabiner"
7 |
8 | DOTFILE_PATH = File.expand_path("~/.karabiner")
9 |
10 | if ARGV.first == "dsl"
11 | karabiner = Karabiner.new(DOTFILE_PATH)
12 | karabiner.apply_configuration
13 | else
14 | system("/Applications/Karabiner.app/Contents/Library/bin/karabiner #{ARGV.join(' ')}")
15 | end
16 |
--------------------------------------------------------------------------------
/bin/karabiner-dsl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | lib = File.dirname(__FILE__) + "/../lib"
4 | $:.unshift lib unless $:.include? lib
5 |
6 | require "karabiner"
7 |
8 | DOTFILE_PATH = File.expand_path("~/.karabiner")
9 |
10 | karabiner = Karabiner.new(DOTFILE_PATH)
11 | karabiner.apply_configuration
12 |
--------------------------------------------------------------------------------
/img/disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k0kubun/karabiner-ruby/638a01112afd71c8158521a4a69198ec2710dd3e/img/disabled.png
--------------------------------------------------------------------------------
/img/enabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k0kubun/karabiner-ruby/638a01112afd71c8158521a4a69198ec2710dd3e/img/enabled.png
--------------------------------------------------------------------------------
/karabiner.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'karabiner/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "karabiner"
8 | spec.version = Karabiner::VERSION
9 | spec.authors = ["Takashi Kokubun"]
10 | spec.email = ["takashikkbn@gmail.com"]
11 | spec.summary = %q{Lightweight keyremap configuration DSL}
12 | spec.description = %q{Lightweight keyremap configuration DSL for Karabiner}
13 | spec.homepage = "https://github.com/k0kubun/karabiner-dsl"
14 | spec.license = "MIT"
15 |
16 | spec.files = `git ls-files -z`.split("\x0")
17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19 | spec.require_paths = ["lib"]
20 |
21 | spec.add_development_dependency "bundler"
22 | spec.add_development_dependency "rake", "~> 10.3.2"
23 | spec.add_development_dependency "rspec", "~> 3.0.0"
24 | spec.add_development_dependency "pry"
25 | spec.add_dependency 'unindent', '~> 1.0'
26 | end
27 |
--------------------------------------------------------------------------------
/lib/karabiner.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/cli"
2 | require "karabiner/version"
3 | require "karabiner/root"
4 | require "unindent"
5 | require "fileutils"
6 |
7 | class Karabiner
8 | XML_FILE_NAME = "private.xml"
9 | XML_DIR = File.expand_path("~/Library/Application Support/Karabiner")
10 |
11 | def initialize(config_path)
12 | @config_path = config_path
13 | Karabiner::History.clear_histroy
14 | end
15 | attr_reader :config_path
16 |
17 | def apply_configuration
18 | replace_private_xml
19 | CLI.reload_xml
20 |
21 | puts "Successfully updated Karabiner configuration"
22 | end
23 |
24 | private
25 |
26 | def replace_private_xml
27 | FileUtils.mkdir_p(XML_DIR)
28 |
29 | xml_path = File.join(XML_DIR, XML_FILE_NAME)
30 | File.write(xml_path, new_xml)
31 | end
32 |
33 | def new_xml
34 | return @new_xml if defined?(@new_xml)
35 | validate_config_existence
36 |
37 | root = Root.new
38 | config = File.read(config_path)
39 | root.instance_eval(config)
40 | @new_xml = root.to_xml.gsub(/ *$/, "").concat("\n")
41 | end
42 |
43 | def validate_config_existence
44 | return if File.exists?(config_path)
45 |
46 | File.write(config_path, <<-EOS.unindent)
47 | #!/usr/bin/env ruby
48 |
49 | # # Example
50 | # item "Command+E to Command+W", not: "TERMINAL" do
51 | # identifier "option.not_terminal_opt_e"
52 | # autogen "__KeyToKey__ KeyCode::E, VK_COMMAND, KeyCode::W, ModifierFlag::COMMAND_L"
53 | # end
54 | EOS
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/karabiner/appdef.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/xml_tree"
2 |
3 | class Karabiner::Appdef
4 | include Karabiner::XmlTree
5 |
6 | AVAILABLE_OPTIONS = %i[
7 | equal
8 | prefix
9 | suffix
10 | ].freeze
11 |
12 | def initialize(appname, options)
13 | property = Karabiner::Property.new("appname", appname)
14 | add_child(property)
15 |
16 | options.each do |option, value|
17 | raise "Unavailable option: #{property}" unless AVAILABLE_OPTIONS.include?(option)
18 |
19 | property = Karabiner::Property.new(option, value)
20 | add_child(property)
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/karabiner/cli.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/namespace"
2 |
3 | module Karabiner::CLI
4 | CLI_PATH = "/Applications/Karabiner.app/Contents/Library/bin/karabiner"
5 |
6 | def self.reload_xml
7 | system("#{CLI_PATH} reloadxml")
8 | end
9 |
10 | def self.current_config
11 | changed = `#{CLI_PATH} changed`
12 | config_by_changed(changed)
13 | end
14 |
15 | private
16 |
17 | def self.config_by_changed(changed)
18 | config = {}
19 | changed.each_line do |line|
20 | property, value = line.strip.split("=")
21 | config[property] = value
22 | end
23 | config
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/karabiner/config.rb:
--------------------------------------------------------------------------------
1 | require "forwardable"
2 | require "karabiner/namespace"
3 |
4 | class Karabiner::Config
5 | extend Forwardable
6 |
7 | def_delegator :@parent, :item
8 | def_delegator :@parent, :group
9 |
10 | def initialize(name)
11 | @name = name
12 | end
13 |
14 | attr_writer :parent
15 | end
16 |
--------------------------------------------------------------------------------
/lib/karabiner/dsl/group.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/namespace"
2 | require "karabiner/item"
3 |
4 | module Karabiner::DSL::Group
5 | def item(name = nil, options = {}, &block)
6 | item = Karabiner::Item.new(name, options)
7 | item.instance_exec(&block)
8 | add_child(item)
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/karabiner/dsl/item.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/history"
2 | require "karabiner/namespace"
3 | require "karabiner/property"
4 | require "karabiner/remap"
5 | require "karabiner/vkopenurldef"
6 |
7 | module Karabiner::DSL::Item
8 | AVAILABLE_PROPERTIES = %i[
9 | name
10 | identifier
11 | autogen
12 | ].freeze
13 |
14 | def remap(target, options = {})
15 | remap = Karabiner::Remap.new(target, options[:to])
16 | add_child(remap)
17 | end
18 |
19 | def show_message(message)
20 | property = Karabiner::Property.new("autogen", "__ShowStatusMessage__ #{message}")
21 | add_child(property)
22 | end
23 |
24 | def invoke(application)
25 | Karabiner::History.register_application(application)
26 | Karabiner::Vkopenurldef.application_keycode(application)
27 | end
28 |
29 | def execute(script)
30 | Karabiner::History.register_script(script)
31 | Karabiner::Vkopenurldef.script_keycode(script)
32 | end
33 |
34 | private
35 |
36 | def method_missing(property, value = '', options = {})
37 | if AVAILABLE_PROPERTIES.include?(property)
38 | property = Karabiner::Property.new(property, value, options)
39 | add_child(property)
40 | else
41 | super
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/karabiner/dsl/root.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/namespace"
2 | require "karabiner/appdef"
3 | require "karabiner/config"
4 | require "karabiner/item"
5 | require "karabiner/group"
6 | require "karabiner/dsl/group"
7 |
8 | module Karabiner::DSL::Root
9 | include Karabiner::DSL::Group
10 |
11 | def group(name, &block)
12 | group = Karabiner::Group.new(name)
13 | group.instance_exec(&block)
14 | add_child(group)
15 | end
16 |
17 | def config(name, &block)
18 | config = Karabiner::Config.new(name)
19 | config.parent = self
20 | config.instance_exec(&block)
21 | add_config(config)
22 | end
23 |
24 | def appdef(appname = '', options = {})
25 | appdef = Karabiner::Appdef.new(appname, options)
26 | add_child(appdef)
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/karabiner/group.rb:
--------------------------------------------------------------------------------
1 | require "forwardable"
2 | require "karabiner/namespace"
3 | require "karabiner/dsl/group"
4 |
5 | class Karabiner::Group
6 | extend Forwardable
7 | include Karabiner::XmlTree
8 | include Karabiner::DSL::Group
9 |
10 | def_delegator :@item, :to_xml
11 | def_delegator :@item, :add_child
12 |
13 | def initialize(name)
14 | @item = Karabiner::Item.new(name, skip_identifier: true)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/karabiner/history.rb:
--------------------------------------------------------------------------------
1 | require "set"
2 |
3 | module Karabiner::History
4 | def self.clear_histroy
5 | registered_applications.clear
6 | registered_scripts.clear
7 | end
8 |
9 | def self.register_application(application)
10 | registered_applications.add(application)
11 | end
12 |
13 | def self.register_script(script)
14 | registered_scripts.add(script)
15 | end
16 |
17 | def self.registered_applications
18 | @@registered_applications ||= Set.new
19 | end
20 |
21 | def self.registered_scripts
22 | @@registered_scripts ||= Set.new
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/karabiner/item.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/dsl/item"
2 | require "karabiner/xml_tree"
3 |
4 | class Karabiner::Item
5 | include Karabiner::XmlTree
6 | include Karabiner::DSL::Item
7 |
8 | AVAILABLE_OPTIONS = %i[
9 | not
10 | only
11 | config_not
12 | config_only
13 | device_not
14 | device_only
15 | ].freeze
16 |
17 | def initialize(name, options = {})
18 | @skip_identifier = options.delete(:skip_identifier)
19 |
20 | if name
21 | property = Karabiner::Property.new("name", name)
22 | add_child(property)
23 | end
24 |
25 | options.each do |option, value|
26 | raise "Unavailable option: #{option}" unless AVAILABLE_OPTIONS.include?(option)
27 |
28 | property = Karabiner::Property.new(option, value)
29 | add_child(property)
30 | end
31 | end
32 |
33 | def to_xml
34 | validate_name_existence
35 | generate_identifier unless @skip_identifier
36 |
37 | super
38 | end
39 |
40 | private
41 |
42 | def validate_name_existence
43 | properties = search_children(Karabiner::Property)
44 | raise "Name property does not exist" unless properties.map(&:attr).include?("name")
45 | end
46 |
47 | def generate_identifier
48 | properties = search_children(Karabiner::Property)
49 | return if properties.map(&:attr).include?("identifier")
50 |
51 | name = properties.find { |p| p.attr == "name" }
52 | generated_identifier = name.value.gsub(/[^a-zA-Z0-9]/, "_").downcase
53 | identifier = Karabiner::Property.new("identifier", "remap.#{generated_identifier}")
54 | children[1, 0] = identifier
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/karabiner/key.rb:
--------------------------------------------------------------------------------
1 | class Karabiner::Key
2 | def self.normalize_input(raw_input)
3 | if raw_input.match(/^VK_/)
4 | raw_input
5 | else
6 | raw_input = raw_input.tr(' ', '_').tr('+', '-').upcase
7 | end
8 | end
9 |
10 | def self.normalize_and_freeze!(map)
11 | map.tap do |h|
12 | h.keys.each { |k| h[normalize_input(k)] = h.delete(k) }
13 | end
14 | map.freeze
15 | end
16 |
17 | # Karabiner full keycode reference:
18 | # https://pqrs.org/osx/karabiner/xml.html#keycode-list
19 | KEYCODE_MAP = normalize_and_freeze!({
20 | "nil" => "VK_NONE",
21 | "none" => "VK_NONE",
22 | "0" => "KEY_0",
23 | "1" => "KEY_1",
24 | "2" => "KEY_2",
25 | "3" => "KEY_3",
26 | "4" => "KEY_4",
27 | "5" => "KEY_5",
28 | "6" => "KEY_6",
29 | "7" => "KEY_7",
30 | "8" => "KEY_8",
31 | "9" => "KEY_9",
32 | "Up" => "CURSOR_UP",
33 | "Down" => "CURSOR_DOWN",
34 | "Right" => "CURSOR_RIGHT",
35 | "Left" => "CURSOR_LEFT",
36 | "]" => "BRACKET_RIGHT",
37 | "[" => "BRACKET_LEFT",
38 | ";" => "SEMICOLON",
39 | "-" => "MINUS",
40 | "," => "COMMA",
41 | "." => "DOT",
42 | "\\" => "BACKSLASH",
43 | "/" => "SLASH",
44 | "=" => "EQUAL",
45 | "'" => "QUOTE",
46 | "`" => "BACKQUOTE",
47 | "Ctrl_R" => "CONTROL_R",
48 | "Ctrl_L" => "CONTROL_L",
49 | "Alt_R" => "OPTION_R",
50 | "Alt_L" => "OPTION_L",
51 | "Opt_R" => "OPTION_R",
52 | "Opt_L" => "OPTION_L",
53 | "Cmd_R" => "COMMAND_R",
54 | "Cmd_L" => "COMMAND_L",
55 | "Esc" => "ESCAPE",
56 | })
57 | CONSUMER_MAP = normalize_and_freeze!({
58 | "Brightness Down" => "BRIGHTNESS_DOWN",
59 | "Brightness Up" => "BRIGHTNESS_UP",
60 | "Keyboardlight Off" => "KEYBOARDLIGHT_OFF",
61 | "Keyboardlight Low" => "KEYBOARDLIGHT_LOW",
62 | "Keyboardlight High" => "KEYBOARDLIGHT_HIGH",
63 | "Keyboard Light Off" => "KEYBOARDLIGHT_OFF",
64 | "Keyboard Light Low" => "KEYBOARDLIGHT_LOW",
65 | "Keyboard Light High" => "KEYBOARDLIGHT_HIGH",
66 | "Music Prev" => "MUSIC_PREV",
67 | "Music Play" => "MUSIC_PLAY",
68 | "Music Next" => "MUSIC_NEXT",
69 | "Prev" => "MUSIC_PREV",
70 | "Play" => "MUSIC_PLAY",
71 | "Next" => "MUSIC_NEXT",
72 | "Volume Mute" => "VOLUME_MUTE",
73 | "Volume Down" => "VOLUME_DOWN",
74 | "Volume Up" => "VOLUME_UP",
75 | "Mute" => "VOLUME_MUTE",
76 | "Eject" => "EJECT",
77 | "Power" => "POWER",
78 | "Numlock" => "NUMLOCK",
79 | "Num Lock" => "NUMLOCK",
80 | "Video Mirror" => "VIDEO_MIRROR",
81 | })
82 | PREFIX_MAP = normalize_and_freeze!({
83 | "C" => "VK_CONTROL",
84 | "Ctrl" => "VK_CONTROL",
85 | "Cmd" => "VK_COMMAND",
86 | "Shift" => "VK_SHIFT",
87 | "M" => "VK_OPTION",
88 | "Opt" => "VK_OPTION",
89 | "Alt" => "VK_OPTION",
90 | })
91 | PREFIX_EXPRESSION = "(#{PREFIX_MAP.keys.map { |k| k + '-' }.join('|')})"
92 |
93 | def initialize(expression)
94 | @expression = expression
95 | end
96 |
97 | def to_s
98 | key_combination(@expression)
99 | end
100 |
101 | private
102 |
103 | def key_combination(raw_combination)
104 | return "KeyCode::VK_NONE" if raw_combination.nil?
105 | raw_combination = normalize_key_combination(raw_combination)
106 | raw_prefixes, raw_key = split_key_combination(raw_combination)
107 | return key_expression(raw_key) if raw_prefixes.empty?
108 |
109 | prefixes = raw_prefixes.map { |raw_prefix| PREFIX_MAP[raw_prefix] }
110 | "#{key_expression(raw_key)}, #{prefixes.join(' | ')}"
111 | end
112 |
113 | def key_expression(raw_key)
114 | case raw_key
115 | when /^#{Regexp.union(KEYCODE_MAP.keys)}$/
116 | "KeyCode::#{KEYCODE_MAP[raw_key]}"
117 | when /^#{Regexp.union(CONSUMER_MAP.keys)}$/
118 | "ConsumerKeyCode::#{CONSUMER_MAP[raw_key]}"
119 | else
120 | "KeyCode::#{raw_key}"
121 | end
122 | end
123 |
124 | def normalize_key_combination(raw_combination)
125 | self.class.normalize_input(raw_combination)
126 | end
127 |
128 | def split_key_combination(raw_combination)
129 | prefixes = []
130 | key = raw_combination.dup
131 |
132 | while key.match(/^#{PREFIX_EXPRESSION}/)
133 | key.gsub!(/^#{PREFIX_EXPRESSION}/) do
134 | prefixes << $1.gsub(/-$/, "")
135 | ""
136 | end
137 | end
138 |
139 | [prefixes, key]
140 | end
141 | end
142 |
--------------------------------------------------------------------------------
/lib/karabiner/namespace.rb:
--------------------------------------------------------------------------------
1 | # Files which require this file can avoid deep nesting for class declaration
2 | class Karabiner
3 | module DSL
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/karabiner/property.rb:
--------------------------------------------------------------------------------
1 | class Karabiner::Property
2 | include Karabiner::XmlTree
3 |
4 | def initialize(attr, value, options = {})
5 | @attr = attr.to_s
6 | @value = value
7 | @options = options
8 | end
9 | attr_accessor :attr, :value
10 |
11 | def to_xml
12 | open_tag = @options.map { |a, v| "#{a}=\"#{v}\"" }.unshift(attr).join(" ")
13 | "<#{open_tag}>#{value}#{attr}>"
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/karabiner/remap.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/key"
2 | require "karabiner/property"
3 |
4 | class Karabiner::Remap < Karabiner::Property
5 | def initialize(from, to)
6 | tos = [to].flatten
7 |
8 | super(
9 | "autogen",
10 | [
11 | "__KeyToKey__ #{Karabiner::Key.new(from)}",
12 | *tos.map { |to| Karabiner::Key.new(to) },
13 | ].join(", "),
14 | )
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/karabiner/root.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/dsl/root"
2 | require "karabiner/history"
3 | require "karabiner/vkopenurldef"
4 |
5 | class Karabiner::Root
6 | include Karabiner::XmlTree
7 | include Karabiner::DSL::Root
8 |
9 | def initialize
10 | @configs = []
11 | end
12 |
13 | def to_xml
14 | add_registered_applications
15 | add_registered_scripts
16 |
17 | [
18 | "",
19 | super(1),
20 | ].join("\n")
21 | end
22 |
23 | private
24 |
25 | def add_registered_applications
26 | Karabiner::History.registered_applications.each do |application|
27 | vkopenurldef = Karabiner::Vkopenurldef.for_application(application)
28 | add_child(vkopenurldef)
29 | end
30 | end
31 |
32 | def add_registered_scripts
33 | Karabiner::History.registered_scripts.each do |script|
34 | vkopenurldef = Karabiner::Vkopenurldef.for_script(script)
35 | add_child(vkopenurldef)
36 | end
37 | end
38 |
39 | def add_config(config)
40 | @configs << config
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/karabiner/version.rb:
--------------------------------------------------------------------------------
1 | class Karabiner
2 | VERSION = "0.4.0"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/karabiner/vkopenurldef.rb:
--------------------------------------------------------------------------------
1 | require "karabiner/xml_tree"
2 |
3 | class Karabiner::Vkopenurldef
4 | include Karabiner::XmlTree
5 |
6 | def self.application_keycode(application)
7 | "VK_OPEN_URL_APP_#{File.basename(application, '.app').gsub(/ /, "_")}"
8 | end
9 |
10 | def self.script_keycode(script)
11 | "VK_OPEN_URL_SHELL_#{script.gsub(/[^a-zA-Z]/, "_")}"
12 | end
13 |
14 | def self.for_application(application)
15 | self.new.tap do |definition|
16 |
17 | if application =~ %r|\A/.+app\z|
18 | app_url = application
19 | else
20 | app_url = "/Applications/#{application}.app"
21 | end
22 |
23 | name = Karabiner::Property.new("name", "KeyCode::#{application_keycode(application)}")
24 | url = Karabiner::Property.new("url", app_url, type: "file")
25 | definition.add_child(name, url)
26 | end
27 | end
28 |
29 | def self.for_script(script)
30 | self.new.tap do |definition|
31 | name = Karabiner::Property.new("name", "KeyCode::#{script_keycode(script)}")
32 | url = Karabiner::Property.new("url", "", type: "shell")
33 | definition.add_child(name, url)
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/karabiner/xml_tree.rb:
--------------------------------------------------------------------------------
1 | module Karabiner::XmlTree
2 | attr_reader :parent
3 |
4 | def add_child(*objects)
5 | objects.each do |object|
6 | children << object
7 | end
8 |
9 | children.each do |child|
10 | child.parent = self
11 | end
12 | end
13 |
14 | def search_children(klass)
15 | children.select { |c| c.is_a?(klass) }
16 | end
17 |
18 | def to_xml(distance_between_children = 0)
19 | tag_name = self.class.to_s.split("::").last.downcase
20 | newline_count = distance_between_children + 1
21 |
22 | [
23 | "<#{tag_name}>",
24 | children.map(&:to_xml).join("\n" * newline_count).gsub(/^/, " "),
25 | "#{tag_name}>",
26 | ].join("\n")
27 | end
28 |
29 | protected
30 |
31 | attr_writer :parent
32 |
33 | private
34 |
35 | def children
36 | @children ||= []
37 | end
38 |
39 | def parent
40 | @parent
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/lib/karabiner/appdef_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Karabiner::Appdef do
4 | describe "#to_xml" do
5 | it "returns valid xml from appdef with equal" do
6 | appdef = Karabiner::Appdef.new("CHROME", equal: "com.google.Chrome")
7 | expect(appdef.to_xml).to eq(<<-EOS.unindent.strip)
8 |
9 | CHROME
10 | com.google.Chrome
11 |
12 | EOS
13 | end
14 |
15 | it "returns valid xml from appdef with prefix" do
16 | appdef = Karabiner::Appdef.new("CHROME", prefix: "com")
17 | expect(appdef.to_xml).to eq(<<-EOS.unindent.strip)
18 |
19 | CHROME
20 | com
21 |
22 | EOS
23 | end
24 |
25 | it "returns valid xml from appdef with suffix" do
26 | appdef = Karabiner::Appdef.new("CHROME", suffix: "Chrome")
27 | expect(appdef.to_xml).to eq(<<-EOS.unindent.strip)
28 |
29 | CHROME
30 | Chrome
31 |
32 | EOS
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/lib/karabiner/item_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Karabiner::Item do
4 | describe "#to_xml" do
5 | it "generates valid identifier from name" do
6 | item = described_class.new("Change MUSIC_PLAY to 9")
7 | expect(item.to_xml).to eq(<<-EOS.unindent.strip)
8 | -
9 | Change MUSIC_PLAY to 9
10 | remap.change_music_play_to_9
11 |
12 | EOS
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/lib/karabiner/karabiner_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Karabiner::CLI do
4 | describe ".current_config" do
5 | subject { described_class.current_config }
6 |
7 | let(:cli_path) { Karabiner::CLI::CLI_PATH }
8 |
9 | before do
10 | allow_any_instance_of(Kernel).to receive(:'`').with("#{cli_path} changed").and_return(<<-EOS.unindent)
11 | remap.command_k_to_command_l=1
12 | repeat.initial_wait=100
13 | repeat.wait=20
14 | option.terminal_command_option=1
15 | notsave.automatically_enable_keyboard_device=1
16 | EOS
17 | end
18 |
19 | it "returns config hash" do
20 | expect(subject).to eq({
21 | "option.terminal_command_option" => "1",
22 | "remap.command_k_to_command_l" => "1",
23 | "repeat.initial_wait" => "100",
24 | "repeat.wait" => "20",
25 | "notsave.automatically_enable_keyboard_device" => "1",
26 | })
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/lib/karabiner/key_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Karabiner::Key do
4 | describe ".normalize_input" do
5 | it { expect(described_class.normalize_input('Cmd+Volume Up')).to eq('CMD-VOLUME_UP') }
6 | end
7 |
8 | describe "#to_s" do
9 | EXPECTED_RESULTS = {
10 | "nil" => "KeyCode::VK_NONE",
11 | "none" => "KeyCode::VK_NONE",
12 | "A" => "KeyCode::A",
13 | "B" => "KeyCode::B",
14 | "C" => "KeyCode::C",
15 | "D" => "KeyCode::D",
16 | "E" => "KeyCode::E",
17 | "F" => "KeyCode::F",
18 | "G" => "KeyCode::G",
19 | "H" => "KeyCode::H",
20 | "I" => "KeyCode::I",
21 | "J" => "KeyCode::J",
22 | "K" => "KeyCode::K",
23 | "L" => "KeyCode::L",
24 | "M" => "KeyCode::M",
25 | "N" => "KeyCode::N",
26 | "O" => "KeyCode::O",
27 | "P" => "KeyCode::P",
28 | "Q" => "KeyCode::Q",
29 | "R" => "KeyCode::R",
30 | "S" => "KeyCode::S",
31 | "T" => "KeyCode::T",
32 | "U" => "KeyCode::U",
33 | "V" => "KeyCode::V",
34 | "W" => "KeyCode::W",
35 | "X" => "KeyCode::X",
36 | "Y" => "KeyCode::Y",
37 | "Z" => "KeyCode::Z",
38 | "0" => "KeyCode::KEY_0",
39 | "1" => "KeyCode::KEY_1",
40 | "2" => "KeyCode::KEY_2",
41 | "3" => "KeyCode::KEY_3",
42 | "4" => "KeyCode::KEY_4",
43 | "5" => "KeyCode::KEY_5",
44 | "6" => "KeyCode::KEY_6",
45 | "7" => "KeyCode::KEY_7",
46 | "8" => "KeyCode::KEY_8",
47 | "9" => "KeyCode::KEY_9",
48 | "Up" => "KeyCode::CURSOR_UP",
49 | "Down" => "KeyCode::CURSOR_DOWN",
50 | "Right" => "KeyCode::CURSOR_RIGHT",
51 | "Left" => "KeyCode::CURSOR_LEFT",
52 | "]" => "KeyCode::BRACKET_RIGHT",
53 | "[" => "KeyCode::BRACKET_LEFT",
54 | ";" => "KeyCode::SEMICOLON",
55 | "-" => "KeyCode::MINUS",
56 | "," => "KeyCode::COMMA",
57 | "." => "KeyCode::DOT",
58 | "\\" => "KeyCode::BACKSLASH",
59 | "/" => "KeyCode::SLASH",
60 | "=" => "KeyCode::EQUAL",
61 | "'" => "KeyCode::QUOTE",
62 | "`" => "KeyCode::BACKQUOTE",
63 | "Ctrl_R" => "KeyCode::CONTROL_R",
64 | "Ctrl_L" => "KeyCode::CONTROL_L",
65 | "Alt_R" => "KeyCode::OPTION_R",
66 | "Alt_L" => "KeyCode::OPTION_L",
67 | "Opt_R" => "KeyCode::OPTION_R",
68 | "Opt_L" => "KeyCode::OPTION_L",
69 | "Cmd_R" => "KeyCode::COMMAND_R",
70 | "Cmd_L" => "KeyCode::COMMAND_L",
71 | "Shift_R" => "KeyCode::SHIFT_R",
72 | "Shift_L" => "KeyCode::SHIFT_L",
73 | "Esc" => "KeyCode::ESCAPE",
74 |
75 | "Brightness Down" => "ConsumerKeyCode::BRIGHTNESS_DOWN",
76 | "Brightness Up" => "ConsumerKeyCode::BRIGHTNESS_UP",
77 | "Keyboardlight Off" => "ConsumerKeyCode::KEYBOARDLIGHT_OFF",
78 | "Keyboardlight Low" => "ConsumerKeyCode::KEYBOARDLIGHT_LOW",
79 | "Keyboardlight High" => "ConsumerKeyCode::KEYBOARDLIGHT_HIGH",
80 | "Keyboard Light Off" => "ConsumerKeyCode::KEYBOARDLIGHT_OFF",
81 | "Keyboard Light Low" => "ConsumerKeyCode::KEYBOARDLIGHT_LOW",
82 | "Keyboard Light High" => "ConsumerKeyCode::KEYBOARDLIGHT_HIGH",
83 | "Music Prev" => "ConsumerKeyCode::MUSIC_PREV",
84 | "Music Play" => "ConsumerKeyCode::MUSIC_PLAY",
85 | "Music Next" => "ConsumerKeyCode::MUSIC_NEXT",
86 | "Prev" => "ConsumerKeyCode::MUSIC_PREV",
87 | "Play" => "ConsumerKeyCode::MUSIC_PLAY",
88 | "Next" => "ConsumerKeyCode::MUSIC_NEXT",
89 | "Volume Mute" => "ConsumerKeyCode::VOLUME_MUTE",
90 | "Volume Down" => "ConsumerKeyCode::VOLUME_DOWN",
91 | "Volume Up" => "ConsumerKeyCode::VOLUME_UP",
92 | "Mute" => "ConsumerKeyCode::VOLUME_MUTE",
93 | "Eject" => "ConsumerKeyCode::EJECT",
94 | "Power" => "ConsumerKeyCode::POWER",
95 | "Numlock" => "ConsumerKeyCode::NUMLOCK",
96 | "Num Lock" => "ConsumerKeyCode::NUMLOCK",
97 | "Video Mirror" => "ConsumerKeyCode::VIDEO_MIRROR",
98 | }.freeze
99 |
100 | it "converts single key expression as expected" do
101 | EXPECTED_RESULTS.each do |expression, result|
102 | expect(described_class.new(expression).to_s).to eq(result)
103 | end
104 | end
105 |
106 | it "converts double key combination as expected" do
107 | Karabiner::Key::PREFIX_MAP.each do |prefix, vk|
108 | key, keycode = EXPECTED_RESULTS.to_a.sample
109 | expect(described_class.new("#{prefix}-#{key}").to_s).to eq("#{keycode}, #{vk}")
110 | end
111 | end
112 |
113 | it "converts triple key combination as expected" do
114 | key, keycode = EXPECTED_RESULTS.to_a.sample
115 | unique_maps = Karabiner::Key::PREFIX_MAP.to_a.sort_by { rand }.uniq { |a| a[1] }
116 | unique_maps.combination(2) do |(prefix1, vk1), (prefix2, vk2)|
117 | expect(described_class.new("#{prefix1}-#{prefix2}-#{key}").to_s).to eq("#{keycode}, #{vk1} | #{vk2}")
118 | end
119 | end
120 |
121 | it "converts key expression in case-insensitive manner" do
122 | EXPECTED_RESULTS.each do |expression, result|
123 | expect(described_class.new(expression.swapcase).to_s).to eq(result)
124 | end
125 | end
126 |
127 | it "accepts nil class as key expression" do
128 | expect(described_class.new(nil).to_s).to eq("KeyCode::VK_NONE")
129 | end
130 | end
131 | end
132 |
--------------------------------------------------------------------------------
/spec/lib/karabiner/remap_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Karabiner::Remap do
4 | describe "#to_xml" do
5 | it "converts key remap to autogen tag" do
6 | expect(Karabiner::Remap.new("Cmd-Shift-]", "Opt-Ctrl-Up").to_xml).
7 | to eq("__KeyToKey__ KeyCode::BRACKET_RIGHT, VK_COMMAND | VK_SHIFT, KeyCode::CURSOR_UP, VK_OPTION | VK_CONTROL")
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/lib/karabiner_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "tempfile"
3 |
4 | describe Karabiner do
5 | let!(:config) { Tempfile.new(".karabiner") }
6 | let(:xml_dir) { "/tmp" }
7 | let(:xml_path) { File.join(xml_dir, Karabiner::XML_FILE_NAME) }
8 | let(:result) { File.read(xml_path) }
9 |
10 | before do
11 | stub_const("Karabiner::XML_DIR", xml_dir)
12 | allow(Karabiner::CLI).to receive(:reload_xml)
13 |
14 | # Silence stdout
15 | allow_any_instance_of(Kernel).to receive(:puts)
16 | end
17 |
18 | after do
19 | config.close!
20 | end
21 |
22 | def prepare_karabiner(karabiner)
23 | config.write(karabiner)
24 | config.rewind
25 | end
26 |
27 | def expect_result(expected_result)
28 | karabiner = Karabiner.new(config.path)
29 | karabiner.apply_configuration
30 | expect(result).to eq(expected_result)
31 | end
32 |
33 | it "accepts blank config" do
34 | prepare_karabiner("")
35 |
36 | expect_result(<<-EOS.unindent)
37 |
38 |
39 |
40 |
41 | EOS
42 | end
43 |
44 | it "accepts cmd combination" do
45 | prepare_karabiner(<<-EOS)
46 | item "Command+A to Command+B" do
47 | remap "Cmd-A", to: "Cmd-B"
48 | end
49 | EOS
50 |
51 | expect_result(<<-EOS.unindent)
52 |
53 |
54 | -
55 | Command+A to Command+B
56 | remap.command_a_to_command_b
57 | __KeyToKey__ KeyCode::A, VK_COMMAND, KeyCode::B, VK_COMMAND
58 |
59 |
60 | EOS
61 | end
62 |
63 | it "accepts multiple remaps" do
64 | prepare_karabiner(<<-EOS)
65 | item "multiple remaps" do
66 | remap "Cmd-A", to: "Cmd-B"
67 | remap "Shift-A", to: "Shift-B"
68 | end
69 | EOS
70 |
71 | expect_result(<<-EOS.unindent)
72 |
73 |
74 | -
75 | multiple remaps
76 | remap.multiple_remaps
77 | __KeyToKey__ KeyCode::A, VK_COMMAND, KeyCode::B, VK_COMMAND
78 | __KeyToKey__ KeyCode::A, VK_SHIFT, KeyCode::B, VK_SHIFT
79 |
80 |
81 | EOS
82 | end
83 |
84 | it "accepts multiple items" do
85 | prepare_karabiner(<<-EOS)
86 | item "first item" do
87 | remap "Cmd-C-A", to: "Cmd-M-B"
88 | end
89 |
90 | item "second item" do
91 | remap "Shift-Opt-A", to: "Shift-Cmd-B"
92 | end
93 | EOS
94 |
95 | expect_result(<<-EOS.unindent)
96 |
97 |
98 | -
99 | first item
100 | remap.first_item
101 | __KeyToKey__ KeyCode::A, VK_COMMAND | VK_CONTROL, KeyCode::B, VK_COMMAND | VK_OPTION
102 |
103 |
104 | -
105 | second item
106 | remap.second_item
107 | __KeyToKey__ KeyCode::A, VK_SHIFT | VK_OPTION, KeyCode::B, VK_SHIFT | VK_COMMAND
108 |
109 |
110 | EOS
111 | end
112 |
113 | it "accepts appdef and app option" do
114 | prepare_karabiner(<<-EOS)
115 | appdef "CHROME", equal: "com.google.Chrome"
116 |
117 | item "Command+K to Command+L", only: "CHROME" do
118 | remap "Cmd-K", to: "Cmd-L"
119 | end
120 | EOS
121 |
122 | expect_result(<<-EOS.unindent)
123 |
124 |
125 |
126 | CHROME
127 | com.google.Chrome
128 |
129 |
130 | -
131 | Command+K to Command+L
132 | remap.command_k_to_command_l
133 | CHROME
134 | __KeyToKey__ KeyCode::K, VK_COMMAND, KeyCode::L, VK_COMMAND
135 |
136 |
137 | EOS
138 | end
139 |
140 | it "accepts config and show_message" do
141 | prepare_karabiner(<<-EOS)
142 | item "CapsLock ON", config_not: "notsave.private_capslock_on" do
143 | remap "Cmd-L", to: ["capslock", "VK_CONFIG_FORCE_ON_notsave_private_capslock_on"]
144 | end
145 |
146 | item "CapsLock OFF", config_only: "notsave.private_capslock_on" do
147 | remap "Cmd-L", to: ["capslock", "VK_CONFIG_FORCE_OFF_notsave_private_capslock_on"]
148 | end
149 |
150 | item "CapsLock Mode" do
151 | identifier "notsave.private_capslock_on", vk_config: "true"
152 | show_message "CapsLock"
153 | end
154 | EOS
155 |
156 | expect_result(<<-EOS.unindent)
157 |
158 |
159 | -
160 | CapsLock ON
161 | remap.capslock_on
162 | notsave.private_capslock_on
163 | __KeyToKey__ KeyCode::L, VK_COMMAND, KeyCode::CAPSLOCK, KeyCode::VK_CONFIG_FORCE_ON_notsave_private_capslock_on
164 |
165 |
166 | -
167 | CapsLock OFF
168 | remap.capslock_off
169 | notsave.private_capslock_on
170 | __KeyToKey__ KeyCode::L, VK_COMMAND, KeyCode::CAPSLOCK, KeyCode::VK_CONFIG_FORCE_OFF_notsave_private_capslock_on
171 |
172 |
173 | -
174 | CapsLock Mode
175 | notsave.private_capslock_on
176 | __ShowStatusMessage__ CapsLock
177 |
178 |
179 | EOS
180 | end
181 |
182 | it "accepts implicit autogen selection" do
183 | prepare_karabiner(<<-EOS)
184 | item "Control+LeftClick to Command+LeftClick" do
185 | autogen "__PointingButtonToPointingButton__ PointingButton::LEFT, MODIFIERFLAG_EITHER_LEFT_OR_RIGHT_CONTROL, PointingButton::LEFT, MODIFIERFLAG_EITHER_LEFT_OR_RIGHT_COMMAND"
186 | end
187 | EOS
188 |
189 | expect_result(<<-EOS.unindent)
190 |
191 |
192 | -
193 | Control+LeftClick to Command+LeftClick
194 | remap.control_leftclick_to_command_leftclick
195 | __PointingButtonToPointingButton__ PointingButton::LEFT, MODIFIERFLAG_EITHER_LEFT_OR_RIGHT_CONTROL, PointingButton::LEFT, MODIFIERFLAG_EITHER_LEFT_OR_RIGHT_COMMAND
196 |
197 |
198 | EOS
199 | end
200 |
201 | it "application invoking" do
202 | prepare_karabiner(<<-EOS)
203 | item "Application shortcuts" do
204 | remap "C-o", to: invoke("YoruFukurou")
205 | remap "C-u", to: invoke("Google Chrome")
206 | remap "C-h", to: invoke("/Users/johndoe/Applications/iTerm.app")
207 | end
208 |
209 | item "duplicate app" do
210 | remap "C-a", to: invoke("YoruFukurou")
211 | end
212 | EOS
213 |
214 | expect_result(<<-EOS.unindent)
215 |
216 |
217 | -
218 | Application shortcuts
219 | remap.application_shortcuts
220 | __KeyToKey__ KeyCode::O, VK_CONTROL, KeyCode::VK_OPEN_URL_APP_YoruFukurou
221 | __KeyToKey__ KeyCode::U, VK_CONTROL, KeyCode::VK_OPEN_URL_APP_Google_Chrome
222 | __KeyToKey__ KeyCode::H, VK_CONTROL, KeyCode::VK_OPEN_URL_APP_iTerm
223 |
224 |
225 | -
226 | duplicate app
227 | remap.duplicate_app
228 | __KeyToKey__ KeyCode::A, VK_CONTROL, KeyCode::VK_OPEN_URL_APP_YoruFukurou
229 |
230 |
231 |
232 | KeyCode::VK_OPEN_URL_APP_YoruFukurou
233 | /Applications/YoruFukurou.app
234 |
235 |
236 |
237 | KeyCode::VK_OPEN_URL_APP_Google_Chrome
238 | /Applications/Google Chrome.app
239 |
240 |
241 |
242 | KeyCode::VK_OPEN_URL_APP_iTerm
243 | /Users/johndoe/Applications/iTerm.app
244 |
245 |
246 | EOS
247 | end
248 |
249 | it "accepts group items" do
250 | prepare_karabiner(<<-EOS)
251 | group "Option" do
252 | item "First" do
253 | identifier "option.option_first"
254 | end
255 |
256 | item "Second" do
257 | identifier "option.option_second"
258 | end
259 | end
260 | EOS
261 |
262 | expect_result(<<-EOS.unindent)
263 |
264 |
265 | -
266 | Option
267 |
-
268 | First
269 | option.option_first
270 |
271 | -
272 | Second
273 | option.option_second
274 |
275 |
276 |
277 | EOS
278 | end
279 |
280 | context "when items are surrounded by config" do
281 | it "accepts cmd combination" do
282 | prepare_karabiner(<<-EOS)
283 | config "Default" do
284 | item "Command+A to Command+B" do
285 | remap "Cmd-A", to: "Cmd-B"
286 | end
287 | end
288 | EOS
289 |
290 | expect_result(<<-EOS.unindent)
291 |
292 |
293 | -
294 | Command+A to Command+B
295 | remap.command_a_to_command_b
296 | __KeyToKey__ KeyCode::A, VK_COMMAND, KeyCode::B, VK_COMMAND
297 |
298 |
299 | EOS
300 | end
301 |
302 | it "accepts group items" do
303 | prepare_karabiner(<<-EOS)
304 | config "Original" do
305 | group "Option" do
306 | item "First" do
307 | identifier "option.option_first"
308 | end
309 |
310 | item "Second" do
311 | identifier "option.option_second"
312 | end
313 | end
314 | end
315 | EOS
316 |
317 | expect_result(<<-EOS.unindent)
318 |
319 |
320 | -
321 | Option
322 |
-
323 | First
324 | option.option_first
325 |
326 | -
327 | Second
328 | option.option_second
329 |
330 |
331 |
332 | EOS
333 | end
334 | end
335 | end
336 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "karabiner"
2 | require "pry"
3 |
--------------------------------------------------------------------------------