karabiner 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ module Karabiner::XmlTree
2
+ attr_reader :parent
3
+
4
+ def add_child(*objects)
5
+ objects.each do |object|
6
+ childs << object
7
+ end
8
+
9
+ childs.each do |child|
10
+ child.parent = self
11
+ end
12
+ end
13
+
14
+ def search_childs(klass)
15
+ childs.select { |c| c.is_a?(klass) }
16
+ end
17
+
18
+ def to_xml(distance_between_childs = 0)
19
+ tag_name = self.class.to_s.split("::").last.downcase
20
+ newline_count = distance_between_childs + 1
21
+
22
+ [
23
+ "<#{tag_name}>",
24
+ childs.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 childs
36
+ @childs ||= []
37
+ end
38
+
39
+ def parent
40
+ @parent
41
+ end
42
+ end
data/lib/karabiner.rb ADDED
@@ -0,0 +1,56 @@
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::InvokeHistory.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
@@ -0,0 +1,35 @@
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
+ <appdef>
9
+ <appname>CHROME</appname>
10
+ <equal>com.google.Chrome</equal>
11
+ </appdef>
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
+ <appdef>
19
+ <appname>CHROME</appname>
20
+ <prefix>com</prefix>
21
+ </appdef>
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
+ <appdef>
29
+ <appname>CHROME</appname>
30
+ <suffix>Chrome</suffix>
31
+ </appdef>
32
+ EOS
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
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
@@ -0,0 +1,113 @@
1
+ require "spec_helper"
2
+
3
+ describe Karabiner::Key do
4
+ describe "#to_s" do
5
+ EXPECTED_RESULTS = {
6
+ "a" => "KeyCode::A",
7
+ "b" => "KeyCode::B",
8
+ "c" => "KeyCode::C",
9
+ "d" => "KeyCode::D",
10
+ "e" => "KeyCode::E",
11
+ "f" => "KeyCode::F",
12
+ "g" => "KeyCode::G",
13
+ "h" => "KeyCode::H",
14
+ "i" => "KeyCode::I",
15
+ "j" => "KeyCode::J",
16
+ "k" => "KeyCode::K",
17
+ "l" => "KeyCode::L",
18
+ "m" => "KeyCode::M",
19
+ "n" => "KeyCode::N",
20
+ "o" => "KeyCode::O",
21
+ "p" => "KeyCode::P",
22
+ "q" => "KeyCode::Q",
23
+ "r" => "KeyCode::R",
24
+ "s" => "KeyCode::S",
25
+ "t" => "KeyCode::T",
26
+ "u" => "KeyCode::U",
27
+ "v" => "KeyCode::V",
28
+ "w" => "KeyCode::W",
29
+ "x" => "KeyCode::X",
30
+ "y" => "KeyCode::Y",
31
+ "z" => "KeyCode::Z",
32
+ "A" => "KeyCode::A",
33
+ "B" => "KeyCode::B",
34
+ "C" => "KeyCode::C",
35
+ "D" => "KeyCode::D",
36
+ "E" => "KeyCode::E",
37
+ "F" => "KeyCode::F",
38
+ "G" => "KeyCode::G",
39
+ "H" => "KeyCode::H",
40
+ "I" => "KeyCode::I",
41
+ "J" => "KeyCode::J",
42
+ "K" => "KeyCode::K",
43
+ "L" => "KeyCode::L",
44
+ "M" => "KeyCode::M",
45
+ "N" => "KeyCode::N",
46
+ "O" => "KeyCode::O",
47
+ "P" => "KeyCode::P",
48
+ "Q" => "KeyCode::Q",
49
+ "R" => "KeyCode::R",
50
+ "S" => "KeyCode::S",
51
+ "T" => "KeyCode::T",
52
+ "U" => "KeyCode::U",
53
+ "V" => "KeyCode::V",
54
+ "W" => "KeyCode::W",
55
+ "X" => "KeyCode::X",
56
+ "Y" => "KeyCode::Y",
57
+ "Z" => "KeyCode::Z",
58
+ "0" => "KeyCode::KEY_0",
59
+ "1" => "KeyCode::KEY_1",
60
+ "2" => "KeyCode::KEY_2",
61
+ "3" => "KeyCode::KEY_3",
62
+ "4" => "KeyCode::KEY_4",
63
+ "5" => "KeyCode::KEY_5",
64
+ "6" => "KeyCode::KEY_6",
65
+ "7" => "KeyCode::KEY_7",
66
+ "8" => "KeyCode::KEY_8",
67
+ "9" => "KeyCode::KEY_9",
68
+ "Up" => "KeyCode::CURSOR_UP",
69
+ "Down" => "KeyCode::CURSOR_DOWN",
70
+ "Right" => "KeyCode::CURSOR_RIGHT",
71
+ "Left" => "KeyCode::CURSOR_LEFT",
72
+ "]" => "KeyCode::BRACKET_RIGHT",
73
+ "[" => "KeyCode::BRACKET_LEFT",
74
+ ";" => "KeyCode::SEMICOLON",
75
+ "-" => "KeyCode::MINUS",
76
+ "," => "KeyCode::COMMA",
77
+ "." => "KeyCode::DOT",
78
+ "\\" => "KeyCode::BACKSLASH",
79
+ "/" => "KeyCode::SLASH",
80
+ "=" => "KeyCode::EQUAL",
81
+ "'" => "KeyCode::QUOTE",
82
+ "Ctrl_R" => "KeyCode::CONTROL_R",
83
+ "Ctrl_L" => "KeyCode::CONTROL_L",
84
+ "Opt_R" => "KeyCode::OPTION_R",
85
+ "Opt_L" => "KeyCode::OPTION_L",
86
+ "Cmd_R" => "KeyCode::COMMAND_R",
87
+ "Cmd_L" => "KeyCode::COMMAND_L",
88
+ "Shift_R" => "KeyCode::SHIFT_R",
89
+ "Shift_L" => "KeyCode::SHIFT_L",
90
+ }.freeze
91
+
92
+ it "converts single key expression as expected" do
93
+ EXPECTED_RESULTS.each do |expression, result|
94
+ expect(described_class.new(expression).to_s).to eq(result)
95
+ end
96
+ end
97
+
98
+ it "converts double key combination as expected" do
99
+ Karabiner::Key::PREFIX_MAP.each do |prefix, vk|
100
+ key, keycode = EXPECTED_RESULTS.to_a.sample
101
+ expect(described_class.new("#{prefix}-#{key}").to_s).to eq("#{keycode}, #{vk}")
102
+ end
103
+ end
104
+
105
+ it "converts triple key combination as expected" do
106
+ key, keycode = EXPECTED_RESULTS.to_a.sample
107
+ unique_maps = Karabiner::Key::PREFIX_MAP.to_a.sort_by { rand }.uniq { |a| a[1] }
108
+ unique_maps.combination(2) do |(prefix1, vk1), (prefix2, vk2)|
109
+ expect(described_class.new("#{prefix1}-#{prefix2}-#{key}").to_s).to eq("#{keycode}, #{vk1} | #{vk2}")
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,10 @@
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("<autogen>__KeyToKey__ KeyCode::BRACKET_RIGHT, VK_COMMAND | VK_SHIFT, KeyCode::CURSOR_UP, VK_OPTION | VK_CONTROL</autogen>")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,335 @@
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
+ <?xml version="1.0"?>
38
+ <root>
39
+
40
+ </root>
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
+ <?xml version="1.0"?>
53
+ <root>
54
+ <item>
55
+ <name>Command+A to Command+B</name>
56
+ <identifier>remap.command_a_to_command_b</identifier>
57
+ <autogen>__KeyToKey__ KeyCode::A, VK_COMMAND, KeyCode::B, VK_COMMAND</autogen>
58
+ </item>
59
+ </root>
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
+ <?xml version="1.0"?>
73
+ <root>
74
+ <item>
75
+ <name>multiple remaps</name>
76
+ <identifier>remap.multiple_remaps</identifier>
77
+ <autogen>__KeyToKey__ KeyCode::A, VK_COMMAND, KeyCode::B, VK_COMMAND</autogen>
78
+ <autogen>__KeyToKey__ KeyCode::A, VK_SHIFT, KeyCode::B, VK_SHIFT</autogen>
79
+ </item>
80
+ </root>
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
+ <?xml version="1.0"?>
97
+ <root>
98
+ <item>
99
+ <name>first item</name>
100
+ <identifier>remap.first_item</identifier>
101
+ <autogen>__KeyToKey__ KeyCode::A, VK_COMMAND | VK_CONTROL, KeyCode::B, VK_COMMAND | VK_OPTION</autogen>
102
+ </item>
103
+
104
+ <item>
105
+ <name>second item</name>
106
+ <identifier>remap.second_item</identifier>
107
+ <autogen>__KeyToKey__ KeyCode::A, VK_SHIFT | VK_OPTION, KeyCode::B, VK_SHIFT | VK_COMMAND</autogen>
108
+ </item>
109
+ </root>
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
+ <?xml version="1.0"?>
124
+ <root>
125
+ <appdef>
126
+ <appname>CHROME</appname>
127
+ <equal>com.google.Chrome</equal>
128
+ </appdef>
129
+
130
+ <item>
131
+ <name>Command+K to Command+L</name>
132
+ <identifier>remap.command_k_to_command_l</identifier>
133
+ <only>CHROME</only>
134
+ <autogen>__KeyToKey__ KeyCode::K, VK_COMMAND, KeyCode::L, VK_COMMAND</autogen>
135
+ </item>
136
+ </root>
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
+ <?xml version="1.0"?>
158
+ <root>
159
+ <item>
160
+ <name>CapsLock ON</name>
161
+ <identifier>remap.capslock_on</identifier>
162
+ <config_not>notsave.private_capslock_on</config_not>
163
+ <autogen>__KeyToKey__ KeyCode::L, VK_COMMAND, KeyCode::CAPSLOCK, KeyCode::VK_CONFIG_FORCE_ON_notsave_private_capslock_on</autogen>
164
+ </item>
165
+
166
+ <item>
167
+ <name>CapsLock OFF</name>
168
+ <identifier>remap.capslock_off</identifier>
169
+ <config_only>notsave.private_capslock_on</config_only>
170
+ <autogen>__KeyToKey__ KeyCode::L, VK_COMMAND, KeyCode::CAPSLOCK, KeyCode::VK_CONFIG_FORCE_OFF_notsave_private_capslock_on</autogen>
171
+ </item>
172
+
173
+ <item>
174
+ <name>CapsLock Mode</name>
175
+ <identifier vk_config="true">notsave.private_capslock_on</identifier>
176
+ <autogen>__ShowStatusMessage__ CapsLock</autogen>
177
+ </item>
178
+ </root>
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
+ <?xml version="1.0"?>
191
+ <root>
192
+ <item>
193
+ <name>Control+LeftClick to Command+LeftClick</name>
194
+ <identifier>remap.control_leftclick_to_command_leftclick</identifier>
195
+ <autogen>__PointingButtonToPointingButton__ PointingButton::LEFT, MODIFIERFLAG_EITHER_LEFT_OR_RIGHT_CONTROL, PointingButton::LEFT, MODIFIERFLAG_EITHER_LEFT_OR_RIGHT_COMMAND</autogen>
196
+ </item>
197
+ </root>
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("iTerm")
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
+ <?xml version="1.0"?>
216
+ <root>
217
+ <item>
218
+ <name>Application shortcuts</name>
219
+ <identifier>remap.application_shortcuts</identifier>
220
+ <autogen>__KeyToKey__ KeyCode::O, VK_CONTROL, KeyCode::VK_OPEN_URL_APP_YoruFukurou</autogen>
221
+ <autogen>__KeyToKey__ KeyCode::U, VK_CONTROL, KeyCode::VK_OPEN_URL_APP_Google_Chrome</autogen>
222
+ <autogen>__KeyToKey__ KeyCode::H, VK_CONTROL, KeyCode::VK_OPEN_URL_APP_iTerm</autogen>
223
+ </item>
224
+
225
+ <item>
226
+ <name>duplicate app</name>
227
+ <identifier>remap.duplicate_app</identifier>
228
+ <autogen>__KeyToKey__ KeyCode::A, VK_CONTROL, KeyCode::VK_OPEN_URL_APP_YoruFukurou</autogen>
229
+ </item>
230
+
231
+ <vkopenurldef>
232
+ <name>KeyCode::VK_OPEN_URL_APP_YoruFukurou</name>
233
+ <url type="file">/Applications/YoruFukurou.app</url>
234
+ </vkopenurldef>
235
+
236
+ <vkopenurldef>
237
+ <name>KeyCode::VK_OPEN_URL_APP_Google_Chrome</name>
238
+ <url type="file">/Applications/Google Chrome.app</url>
239
+ </vkopenurldef>
240
+
241
+ <vkopenurldef>
242
+ <name>KeyCode::VK_OPEN_URL_APP_iTerm</name>
243
+ <url type="file">/Applications/iTerm.app</url>
244
+ </vkopenurldef>
245
+ </root>
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
+ <?xml version="1.0"?>
264
+ <root>
265
+ <item>
266
+ <name>Option</name>
267
+ <item>
268
+ <name>First</name>
269
+ <identifier>option.option_first</identifier>
270
+ </item>
271
+ <item>
272
+ <name>Second</name>
273
+ <identifier>option.option_second</identifier>
274
+ </item>
275
+ </item>
276
+ </root>
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
+ <?xml version="1.0"?>
292
+ <root>
293
+ <item>
294
+ <name>Command+A to Command+B</name>
295
+ <identifier>remap.command_a_to_command_b</identifier>
296
+ <autogen>__KeyToKey__ KeyCode::A, VK_COMMAND, KeyCode::B, VK_COMMAND</autogen>
297
+ </item>
298
+ </root>
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
+ <?xml version="1.0"?>
319
+ <root>
320
+ <item>
321
+ <name>Option</name>
322
+ <item>
323
+ <name>First</name>
324
+ <identifier>option.option_first</identifier>
325
+ </item>
326
+ <item>
327
+ <name>Second</name>
328
+ <identifier>option.option_second</identifier>
329
+ </item>
330
+ </item>
331
+ </root>
332
+ EOS
333
+ end
334
+ end
335
+ end
@@ -0,0 +1,2 @@
1
+ require "karabiner"
2
+ require "pry"