Karules: Ruby DSL For Karabiner JSONs - A Goku Alternative
Karabiner-Elements is a powerful keyboard customizer for macOS, allowing you to remap keys and create complex macros. However, configuring Karabiner can be a daunting task, especially when dealing with complex setups. This is where Karules comes in – a Ruby DSL (Domain Specific Language) that simplifies the creation of Karabiner JSON configurations. If you're familiar with Goku but looking for a different style, Karules might be the perfect solution for you.
What is Karules?
Karules is a Ruby DSL designed to streamline the process of building Karabiner JSON configurations. It offers a more readable and maintainable syntax compared to manually writing JSON, making it easier to manage complex keyboard mappings. Karules is inspired by tools like Goku, but it offers a different approach to configuration, focusing on a more Ruby-centric style.
Why Use a DSL for Karabiner?
Configuring Karabiner-Elements directly with JSON can become cumbersome and difficult to manage, especially as your configuration grows. A DSL like Karules provides several advantages:
- Readability: DSLs offer a more human-readable syntax, making your configuration easier to understand and modify.
- Maintainability: With a structured approach, your configuration becomes easier to maintain and debug.
- Reusability: DSLs allow you to define reusable components and functions, reducing code duplication.
- Abstraction: DSLs abstract away the complexities of the underlying JSON structure, allowing you to focus on the logic of your key mappings.
Karules vs. Goku
Goku is another popular tool for building Karabiner configurations using a DSL. While both Karules and Goku aim to simplify Karabiner configuration, they differ in their approach. The creator of Karules found Goku to be challenging to understand and learn, which led to the development of Karules with a more intuitive Ruby-style syntax. If you've found Goku difficult to grasp, Karules might offer a more accessible alternative.
Key Features of Karules
Karules provides a set of features that make it a powerful tool for Karabiner configuration:
- Ruby-based Syntax: Karules leverages the expressiveness of Ruby, allowing you to write configurations in a familiar and readable style. This makes it easier for Ruby developers to get started with Karabiner customization.
- Modularity: Karules encourages modularity by allowing you to define groups of mappings and modes, making your configuration more organized and reusable. This is crucial for complex setups that involve multiple layers of key remappings and conditional behaviors.
- Application-Specific Mappings: You can define mappings that are specific to certain applications, allowing you to tailor your keyboard layout to different workflows. This ensures that your key remappings are context-aware and don't interfere with other applications.
- Modes: Karules supports the concept of modes, which allow you to create different layers of key mappings that can be toggled on and off. This is useful for creating function layers, mouse modes, and other advanced behaviors. Modes enable you to significantly expand the functionality of your keyboard without sacrificing the usability of standard keys.
- Conditions: Karules allows you to define conditions for your mappings, so they are only active under certain circumstances. This is useful for creating mappings that are only active in certain applications or when certain modifiers are held down. Conditions add a layer of precision to your keyboard customization, ensuring that your remappings behave as expected in different contexts.
Getting Started with Karules
To get started with Karules, you'll need to have Ruby installed on your system. Once you have Ruby set up, you can install the karules gem:
gem install karules
After installing Karules, you can create a configuration file (e.g., ~/.config/karules/config.rb) and start defining your key mappings. Here's a basic example:
# frozen_string_literal: true
require "karules"
class MyKaRules < KaRules
def config
m("caps_lock -any", "left_control", to_if_alone: "escape")
end
end
MyKaRules.new.call
This example remaps the Caps Lock key to Left Control, and if pressed alone, it acts as Escape. This is a common remapping that many users find helpful for improving keyboard ergonomics and workflow.
Example Configuration: A Deeper Dive
Let's explore a more comprehensive example to showcase the capabilities of Karules:
# frozen_string_literal: true
require "karules"
class MyKaRules < KaRules
def key_mode(key, mode)
m(
key,
to_if_alone: { key_code: key, halt: true },
to_after_key_up: mode_off(mode),
to_delayed_action: { to_if_canceled: { key_code: key }, to_if_invoked: mode_on(mode) },
parameters: {
"basic.to_if_held_down_threshold_milliseconds": 300,
"basic.to_delayed_action_delay_milliseconds": 300
}
)
end
def config
# Optional: specify custom Karabiner config path
# Default: $XDG_CONFIG_HOME/karabiner/karabiner.json or ~/.config/karabiner/karabiner.json
# karabiner_path "~/custom/path/karabiner.json"
apps(slack: "^com\\.tinyspeck\\.slackmacgap{{content}}quot;, ghostty: "^com\\.mitchellh\\.ghostty{{content}}quot;)
group("Caps Lock") do
# m("caps_lock -any", "left_control", to_if_alone: "escape")
m("caps_lock -any", "left_control")
end
group("Mouse buttons") do
m("pointing_button:button5 -any", "f3") # mission control
# m("pointing_button:button5 -any", "tab +right_command")
end
group("Tmux") do
app_unless(:ghostty) do
# Example: Focus terminal app, wait, then send Ctrl+A
# Replace with your own terminal focus script
m("a +control", ["!open -a 'Terminal'", { key_code: "vk_none", hold_down_milliseconds: 100 }, "a +control"])
end
end
group("Tab mode") do
m("tab", "right_option lazy", to_if_alone: "tab")
m("j +right_option", "down_arrow")
m("k +right_option", "up_arrow")
app_if(:slack) do
m("h +right_option", "f6 +shift")
m("l +right_option", "f6")
m("semicolon +right_option", "right_arrow")
end
m("h +right_option", "left_arrow")
m("l +right_option", "right_arrow")
m("w +right_option", "right_arrow +right_option")
m("b +right_option", "left_arrow +right_option")
m("u +right_option", "page_up")
m("d +right_option", "page_down")
end
group("Mouse mode", enabled: false) do
default_mode("mouse-mode")
scroll = "mouse-scroll"
step = 1000
mult1 = 0.5
mult2 = 2
wheel = 50
# m("fn -any", mode_on, to_if_alone: "fn", to_after_key_up: mode_off)
key_mode("d", "mouse-mode")
mode_if do
m("left_shift +right_shift", mode_off)
m("right_shift +left_shift", mode_off)
end
m("left_shift +right_shift", mode_on)
m("right_shift +left_shift", mode_on)
mode_if do
mode_if(scroll) do
m("j -any", { mouse_key: { vertical_wheel: wheel } })
m("k -any", { mouse_key: { vertical_wheel: -wheel } })
m("h -any", { mouse_key: { horizontal_wheel: wheel } })
m("l -any", { mouse_key: { horizontal_wheel: -wheel } })
end
# normal movement
m("j -any", { mouse_key: { y: step } })
m("k -any", { mouse_key: { y: -step } })
m("h -any", { mouse_key: { x: -step } })
m("l -any", { mouse_key: { x: step } })
# mode modifiers
m("s -any", mode_on(scroll), to_after_key_up: mode_off(scroll))
m("c -any", { mouse_key: { speed_multiplier: mult1 } })
m("f -any", { mouse_key: { speed_multiplier: mult2 } })
# buttons
m("b -any", { pointing_button: "button1" })
m("spacebar -any", { pointing_button: "button1" })
m("n -any", { pointing_button: "button2" })
# position
m("u -any", { software_function: { set_mouse_cursor_position: { x: "20%", y: "20%" } } })
m("i -any", { software_function: { set_mouse_cursor_position: { x: "80%", y: "20%" } } })
m("o -any", { software_function: { set_mouse_cursor_position: { x: "20%", y: "80%" } } })
m("p -any", { software_function: { set_mouse_cursor_position: { x: "80%", y: "80%" } } })
m("m -any", { software_function: { set_mouse_cursor_position: { x: "50%", y: "50%" } } })
end
end
group("MacOS double CmdQ") do
default_mode("macos-q-command")
m("q +command", "q +command", conditions: mode_if)
m("q +command", mode_on, to_delayed_action: { to_if_canceled: mode_off, to_if_invoked: mode_off })
end
# Example: Switch between terminal windows/tabs
# Replace with your own terminal switching script
max = 9
group("terminal 1-" + max.to_s) do
app_if(:ghostty) do
(1..max).each { |i| m("#{i} +left_command", "#{i} +command") }
end
(1..max).each { |i| m("#{i} +left_option", "#{i} +command") }
end
# Example: Application launcher shortcuts
group("Apps") do
m("j +right_command", "!open -a 'Terminal'")
m("k +right_command", "!open -a 'Safari'")
m("semicolon +right_command", "!open -a 'Mail'")
m("f +right_command", "!open -a 'Finder'")
m("s +right_command", "!open -a 'Slack'")
m("c +right_command", "!open -a 'Google Chrome'")
m("n +right_command", "!open -a 'Notes'")
# You can also map to other key combinations
m("t +right_command", "t +control +command +option")
end
end
end
MyKaRules.new.call
This configuration demonstrates several key features of Karules:
- Key Modes: The
key_modemethod defines a reusable pattern for creating key mappings that toggle modes. This is used in the Mouse Mode section to define a key that activates mouse control when held down. - Application-Specific Mappings: The
apps,app_if, andapp_unlessmethods allow you to define mappings that are specific to certain applications. For example, the Tmux group includes mappings that are only active when Ghostty is not the active application. - Groups: The
groupmethod allows you to organize your mappings into logical groups. This makes your configuration more readable and maintainable. Groups can also be enabled or disabled, allowing you to easily toggle sections of your configuration. - Modes: The Mouse Mode section demonstrates the use of modes to create a separate layer of key mappings that are only active when the mode is enabled. This allows you to create complex behaviors without interfering with your standard key mappings.
Generating the Karabiner JSON
Once you've defined your configuration in Karules, you need to generate the Karabiner JSON file. You can do this by running your Ruby script:
ruby ~/.config/karules/config.rb
This will generate a karabiner.json file in the default Karabiner configuration directory (~/.config/karabiner/karabiner.json or $XDG_CONFIG_HOME/karabiner/karabiner.json). You can then load this configuration into Karabiner-Elements.
Community and Contributions
Karules is an open-source project, and contributions are welcome. If you have any comments, suggestions, or bug reports, feel free to submit them to the Karules GitHub repository. The Karules community is dedicated to making keyboard customization more accessible and efficient for everyone.
Conclusion
Karules offers a powerful and flexible way to build Karabiner JSON configurations using a Ruby DSL. Its readable syntax, modularity, and support for application-specific mappings and modes make it a great choice for both simple and complex keyboard customizations. If you're looking for an alternative to Goku or simply prefer a Ruby-style DSL, Karules is definitely worth exploring.
For more information on Karabiner-Elements and keyboard customization, visit the official Karabiner-Elements website.