├── .github └── workflows │ └── test.yml ├── .gitignore ├── Changes ├── LICENSE ├── META6.json ├── README.md ├── bin ├── kcmg └── kcmg_safari_tabs_activate ├── dist.ini ├── lib └── Karabiner │ ├── CompModGenerator.rakumod │ ├── Template.rakumod │ └── Templates │ ├── ActivateApps.rakumod │ └── SafariTabs.rakumod ├── resources ├── double.json ├── safaritabs.json └── triple.json └── t ├── 01-basic.rakutest ├── 02-ActiveApps_tmpl.rakutest ├── 03-SafariTabs_tmpl.rakutest └── data ├── ActivateApps.cfg └── NoTemplate.cfg /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags-ignore: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | raku: 13 | strategy: 14 | matrix: 15 | os: 16 | - macOS-latest 17 | raku-version: 18 | - 'latest' 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: Raku/setup-raku@v1 23 | with: 24 | raku-version: ${{ matrix.raku-version }} 25 | - name: Install Dependencies 26 | run: zef install --/test --test-depends --deps-only . 27 | - name: Install App::Prove6 28 | run: zef install --/test App::Prove6 29 | - name: Run Tests 30 | run: prove6 -l t 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .precomp/ 2 | /Karabiner-CompModGenerator-* 3 | /*.json 4 | /*.cfg 5 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Karabiner-CompModGenerator 2 | 3 | {{$NEXT}} 4 | 5 | 0.0.20 2022-03-15T06:05:28-04:00 6 | - fix another bug that occurred when two arg config line followed 3 arg 7 | 8 | 0.0.19 2022-03-15T04:53:53-04:00 9 | - fix bug for app,app,key config lines for ActivateApps template 10 | 11 | 0.0.18 2022-01-17T11:52:16-05:00 12 | - streamline code 13 | 14 | 0.0.17 2022-01-16T21:02:21-05:00 15 | - yup, more doc refinements 16 | 17 | 0.0.16 2022-01-16T19:14:16-05:00 18 | - several improvements to docs 19 | 20 | 0.0.15 2022-01-16T14:22:08-05:00 21 | - document template files 22 | - more main documentation improvements 23 | 24 | 0.0.14 2022-01-16T13:28:27-05:00 25 | - improve code quality; h/t to @gfldex on Discord for help 26 | - improve and update documentation 27 | 28 | 0.0.13 2022-01-16T06:01:50-05:00 29 | - update docs to describe new SafariTabs template 30 | 31 | 0.0.12 2022-01-16T05:34:12-05:00 32 | - misc. code refinements 33 | - fix broken links in docs 34 | - remove file cruft 35 | - ensure tabnum attribute in SafariTabs is Numeric 36 | 37 | 0.0.11 2022-01-15T19:22:46-05:00 38 | - add script for changing tabs 39 | - fix bug in template 40 | 41 | 0.0.10 2022-01-15T19:08:29-05:00 42 | - fix git config to add back in template files 43 | 44 | 0.0.9 2022-01-15T18:16:02-05:00 45 | - add new template for SafariTabs 46 | 47 | 0.0.8 2022-01-15T16:10:32-05:00 48 | - Move to OO paradigm; major rewrite 49 | 50 | 0.0.7 2022-01-13T10:33:41-05:00 51 | - doc tweaks 52 | - fix bug with resources not getting found 53 | 54 | 0.0.6 2022-01-13T09:56:47-05:00 55 | - fix doc formatting 56 | 57 | 0.0.5 2022-01-13T09:37:19-05:00 58 | - improve documentation 59 | - ignore blank lines in config file 60 | - trim config file lines 61 | - improve code style 62 | 63 | 0.0.4 2022-01-13T00:55:46-05:00 64 | - improve documentation 65 | - allow all modifier keys 66 | 67 | 0.0.3 2022-01-13T00:35:09-05:00 68 | - workflow fixes 69 | - adjustments to meta file 70 | - test for OS 71 | - minor test tweaks 72 | 73 | 0.0.2 2022-01-12T19:24:04-05:00 74 | - README fixes 75 | 76 | 0.0.1 2022-01-12T18:11:34-05:00 77 | - Initial version 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2000-2006, The Perl Foundation. 4 | 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": "zef:sdondley", 3 | "authors": [ 4 | "Steve Dondley" 5 | ], 6 | "build-depends": [ 7 | ], 8 | "depends": [ 9 | "Mac::Applications::List", 10 | "Template::Classic" 11 | ], 12 | "description": "Generate complex modifcations for the L app on macOS", 13 | "license": "Artistic-2.0", 14 | "name": "Karabiner::CompModGenerator", 15 | "perl": "6.d", 16 | "provides": { 17 | "Karabiner::CompModGenerator": "lib/Karabiner/CompModGenerator.rakumod", 18 | "Karabiner::Template": "lib/Karabiner/Template.rakumod", 19 | "Karabiner::Templates::ActivateApps": "lib/Karabiner/Templates/ActivateApps.rakumod", 20 | "Karabiner::Templates::SafariTabs": "lib/Karabiner/Templates/SafariTabs.rakumod" 21 | }, 22 | "resources": [ 23 | "double.json", 24 | "triple.json", 25 | "safaritabs.json" 26 | ], 27 | "source-url": "https://github.com/sdondley/Karabiner-CompModGenerator.git", 28 | "tags": [ 29 | ], 30 | "test-depends": [ 31 | ], 32 | "version": "0.0.20" 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/sdondley/Karabiner-CompModGenerator/workflows/test/badge.svg)](https://github.com/sdondley/Karabiner-CompModGenerator/actions) 2 | 3 | NAME 4 | ==== 5 | 6 | Karabiner::CompModGenerator - Generate complex modifcations for the [Karabiner-Elements](https://karabiner-elements.pqrs.org) app on macOS 7 | 8 | SYNOPSIS 9 | ======== 10 | 11 | From the command line: 12 | 13 | kcmg ActivateApps.cfg 14 | 15 | DESCRIPTION 16 | =========== 17 | 18 | This module generates json files containing "complex modification" rules for use with the the [Karabiner-Elements](https://karabiner-elements.pqrs.org) app on macOS. The goal of the module is to make it easier to create and regenerate complex modification files and avoid the headache of editing json files directly. 19 | 20 | The files containing the complex modifications are generated from templates, so the rules you can create are limited by the templates provided by the module, which currently include: 21 | 22 | * **ActivateApps** – opens/activiates applications by pressing a modifier key while double or triple tapping another key 23 | 24 | * **SafariTabs** – activate Safari and a specific tab by pressing a modifier key and a double tap (best used in conjunction with Safari's "pinned" tab feature) 25 | 26 | More templates will be added in the future. Feel welcome to contribute your own template modules to extend `Karabiner::CompModGenerator`'s capabilities. 27 | 28 | Follow the [USAGE](#USAGE) instructions below for more details. 29 | 30 | INSTALLATION 31 | ============ 32 | 33 | Assuming Raku and zef is already installed, install the module with: 34 | 35 | `zef install Karabiner::CompModGenerator` 36 | 37 | Once you get the module installed follow the [USAGE](#USAGE) instructions to learn how to generate new rules for use with Karabiner-Elements. 38 | 39 | If you don't have Raku with zef installed yet, it's easiest to install them both with homebrew if you already have brew installed: 40 | 41 | `brew install rakudo-star` 42 | 43 | If you don't have brew installed, install it with: 44 | 45 | `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` 46 | 47 | Note, however, that the homebrew install may be months out of date. 48 | 49 | To ensure you get the absolute latest version of Raku, [see this page](https://course.raku.org/essentials/how-to-install-rakudo/) for other installation options. Whatever method you choose to install Raku, just be sure the `zef` command gets installed and is working on your machine as well. 50 | 51 | USAGE 52 | ===== 53 | 54 | The four steps to generating and using the complex modification files are: 55 | 56 | 1. creating a configuration file; see [Configuration File](#Configuration File) for details 57 | 58 | 2. running the `kcmg` command, followed by the path to your configuration file, to create the json file containing the complex modification rules 59 | 60 | 3. copy the json file into Karabiner-Elements configuration directory on your drive 61 | 62 | 4. open Karabiner-Elements and load the new rules 63 | 64 | The json file created in step 2 above gets saved to the same directory you ran the command from. It has the same base file name as your configuration file but with a `.json` file extension. Place this file into your Karbiner-Elements configuration directory. By default, this directory is at `~/.config/Karabiner/assets/complex_modifications`. 65 | 66 | Now, with the new json file in place, open Karabiner-Elements and do the following: 67 | 68 | 1. click the "Complex Modifications" tab 69 | 70 | 2. click the "Add rule" button 71 | 72 | 3. click "Enable" for all the rules or individual rules you wish to use 73 | 74 | Configuration File 75 | ------------------ 76 | 77 | A configuration file is a text file that contains the comma separated values that get inserted into a template file. Each line in the file outputs a new rule that ends up in json file that's output by the `kcmg` command. An associated template module, as determined by the name of the configuration file, contains the logic for processing the configuration file. 78 | 79 | You can use any text editor to create the configuration files. 80 | 81 | ### Naming Your Configuration File 82 | 83 | A configuration file can have any file extention. However, the first part of your file name (aka the base name), **must exactly match the name of an installed template module.** For example, if you want your configuration file to use the `Karabiner::Template::ActivateApps` template, name your file something like `ActivateApps.cfg` or `ActivateApps.txt`. 84 | 85 | ### Writing Your Configuration File 86 | 87 | Here is a sample configuration file for use with the `ActivateApps` template: 88 | 89 | # Filename: ActivateApps.cfg 90 | # lines beginning with the '#' character get ignored 91 | 92 | # LINE FORMAT: 93 | # 1st app name, 2nd app name*, key, modifier* 94 | # The '*' indicates an optional field 95 | 96 | Adobe Photoshop 2021, Preview, p, command 97 | zoom.us, z, option 98 | 99 | Blank lines and lines beginning with the '#' sign are are comments and are ignored. 100 | 101 | The uncommented lines contain the data that the template inserts into a pre-defined json template file. Each piece of data is delimited with a comma. Spaces before and after a comma are ignored. 102 | 103 | The sample configuration file above creates the following three shortcuts: 104 | 105 | * assigns ⌘-p-p (hold down command key and double tap "p") to open Adobe Photoshop 106 | 107 | * assigns ⌘-p-p-p (hold down command key and triple tap "p")to open Preview 108 | 109 | * assigns ⌘-z-z (hold down command key and double tap "z") to open Zoom 110 | 111 | Notice the second app name is optional. If only one app name is provided, only one shortcut (a double tap) will be generated. If two app names are provided, the first app is assigned to the double tap shortcut and the second app is assigned to the triple tap shortcut. 112 | 113 | The "modifier" argument is also optional. If not provided, it defaults to the "command" key when using the `ActivateApps` template. You may use "option," "control," "shift," or "command" for the modifier key. 114 | 115 | **PRO TIP for ActiveApps template:** The app name in the configuration file must exactly match the name of the offical app name as installed on your Mac. The official name can differ substantially than the app's common name. For example, the app name for "Zoom" is "zoom.us". To ensure you app name correct, use the [Mac::Application::List](Mac::Application::List) module installed with this module to list out the apps installed on your machine. Alternatively, use the Karabiner-EventViewer application. The module will warn you if it does not recognize the name of an app in your configuration file. 116 | 117 | AUTHOR 118 | ====== 119 | 120 | Steve Dondley 121 | 122 | COPYRIGHT AND LICENSE 123 | ===================== 124 | 125 | Copyright 2022 Steve Dondley 126 | 127 | This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0. 128 | 129 | -------------------------------------------------------------------------------- /bin/kcmg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | use lib 'lib'; 3 | use Karabiner::CompModGenerator; 4 | 5 | # TODO: add option for listing available templates 6 | sub MAIN($config_file) { 7 | my $file_out = basename $config_file; 8 | 9 | "$file_out.json".IO.spurt(generate_output($config_file)); 10 | } 11 | -------------------------------------------------------------------------------- /bin/kcmg_safari_tabs_activate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | osascript -e ' 3 | tell application "Safari" 4 | reopen 5 | activate 6 | end tell 7 | delay .1 8 | ' 9 | 10 | osascript -e "tell application \"System Events\" to keystroke \"$1\" using command down" 11 | 12 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = Karabiner-CompModGenerator 2 | 3 | [ReadmeFromPod] 4 | ; enable = false 5 | filename = lib/Karabiner/CompModGenerator.rakumod 6 | 7 | [UploadToZef] 8 | 9 | [PruneFiles] 10 | ; match = ^ 'xt/' 11 | 12 | [Badges] 13 | provider = github-actions/test 14 | -------------------------------------------------------------------------------- /lib/Karabiner/CompModGenerator.rakumod: -------------------------------------------------------------------------------- 1 | unit module Karabiner::CompModGenerator; 2 | 3 | sub generate_output(Str:D $config!) is export(:MANDATORY) { 4 | if !$config.IO.f { 5 | die "file $config does not exist"; 6 | } 7 | # TODO: Add test to ensure template exists 8 | my $tmpl = basename $config; 9 | my $tmpl_module = "Karabiner::Templates::$tmpl"; 10 | require ::($tmpl_module); 11 | 12 | # process the lines in the config file 13 | my @descriptions; 14 | for $config.IO.slurp.lines>>.trim -> $l { 15 | next if $l ~~ /^\s*\#/ || !$l; 16 | my @args = $l.split(',').map: *.trim; 17 | say @args; 18 | @descriptions.push(::($tmpl).create(|@args)); 19 | } 20 | my $out = @descriptions.join(",\n"); 21 | return ::($tmpl).get_top ~ $out ~ ::($tmpl).get_bot; 22 | } 23 | 24 | sub basename (Str:D $file) is export(:MANDATORY) { 25 | return $file.IO.extension('').basename; 26 | } 27 | 28 | =begin pod 29 | 30 | =head1 NAME 31 | 32 | Karabiner::CompModGenerator - Generate complex modifcations for the L app on macOS 33 | 34 | =head1 SYNOPSIS 35 | 36 | From the command line: 37 | =begin code 38 | 39 | kcmg ActivateApps.cfg 40 | 41 | =end code 42 | 43 | =head1 DESCRIPTION 44 | 45 | This module generates json files containing "complex modification" rules for 46 | use with the the L app 47 | on macOS. The goal of the module is to make it easier to create and regenerate 48 | complex modification files and avoid the headache of editing json files 49 | directly. 50 | 51 | The files containing the complex modifications are generated from templates, so 52 | the rules you can create are limited by the templates provided by the module, 53 | which currently include: 54 | 55 | =item B – opens/activiates applications by pressing a modifier 56 | key while double or triple tapping another key 57 | =item B – activate Safari and a specific tab by pressing a modifier 58 | key and a double tap (best used in conjunction with Safari's "pinned" tab 59 | feature) 60 | 61 | More templates will be added in the future. Feel welcome to contribute your own 62 | template modules to extend C's capabilities. 63 | 64 | Follow the L instructions below for more details. 65 | 66 | =head1 INSTALLATION 67 | 68 | Assuming Raku and zef is already installed, install the module with: 69 | 70 | C 71 | 72 | Once you get the module installed follow the L instructions to 73 | learn how to generate new rules for use with Karabiner-Elements. 74 | 75 | If you don't have Raku with zef installed yet, it's easiest to install them 76 | both with homebrew if you already have brew installed: 77 | 78 | C 79 | 80 | If you don't have brew installed, install it with: 81 | 82 | C 84 | 85 | Note, however, that the homebrew install may be months out of date. 86 | 87 | To ensure you get the absolute latest version of Raku, L for other 89 | installation options. Whatever method you choose to install Raku, just be sure 90 | the C command gets installed and is working on your machine as well. 91 | 92 | =head1 USAGE 93 | 94 | The four steps to generating and using the complex modification files are: 95 | 96 | 1. creating a configuration file; see L 97 | for details 98 | 99 | 2. running the C command, followed by the path to your configuration 100 | file, to create the json file containing the complex modification rules 101 | 102 | 3. copy the json file into Karabiner-Elements configuration directory on your 103 | drive 104 | 105 | 4. open Karabiner-Elements and load the new rules 106 | 107 | The json file created in step 2 above gets saved to the same directory you ran 108 | the command from. It has the same base file name as your configuration file but 109 | with a C<.json> file extension. Place this file into your Karbiner-Elements 110 | configuration directory. By default, this directory is at 111 | C<~/.config/Karabiner/assets/complex_modifications>. 112 | 113 | Now, with the new json file in place, open Karabiner-Elements and do the 114 | following: 115 | 116 | 1. click the "Complex Modifications" tab 117 | 118 | 2. click the "Add rule" button 119 | 120 | 3. click "Enable" for all the rules or individual rules you wish to use 121 | 122 | =head2 Configuration File 123 | 124 | A configuration file is a text file that contains the comma separated values 125 | that get inserted into a template file. Each line in the file outputs a new 126 | rule that ends up in json file that's output by the C command. An 127 | associated template module, as determined by the name of the configuration 128 | file, contains the logic for processing the configuration file. 129 | 130 | You can use any text editor to create the configuration files. 131 | 132 | =head3 Naming Your Configuration File 133 | 134 | A configuration file can have any file extention. However, the first part of 135 | your file name (aka the base name), B For example, if you want your configuration file to 137 | use the C template, name your file something 138 | like C or C. 139 | 140 | =head3 Writing Your Configuration File 141 | 142 | Here is a sample configuration file for use with the C template: 143 | 144 | =begin code 145 | # Filename: ActivateApps.cfg 146 | # lines beginning with the '#' character get ignored 147 | 148 | # LINE FORMAT: 149 | # 1st app name, 2nd app name*, key, modifier* 150 | # The '*' indicates an optional field 151 | 152 | Adobe Photoshop 2021, Preview, p, command 153 | zoom.us, z, option 154 | =end code 155 | 156 | Blank lines and lines beginning with the '#' sign are are comments and are 157 | ignored. 158 | 159 | The uncommented lines contain the data that the template inserts into a 160 | pre-defined json template file. Each piece of data is delimited with a comma. 161 | Spaces before and after a comma are ignored. 162 | 163 | The sample configuration file above creates the following three shortcuts: 164 | 165 | =item assigns ⌘-p-p (hold down command key and double tap "p") to open Adobe Photoshop 166 | =item assigns ⌘-p-p-p (hold down command key and triple tap "p")to open Preview 167 | =item assigns ⌘-z-z (hold down command key and double tap "z") to open Zoom 168 | 169 | Notice the second app name is optional. If only one app name is provided, only 170 | one shortcut (a double tap) will be generated. If two app names are provided, 171 | the first app is assigned to the double tap shortcut and the second app is 172 | assigned to the triple tap shortcut. 173 | 174 | The "modifier" argument is also optional. If not provided, it defaults to the 175 | "command" key when using the C template. You may use "option," 176 | "control," "shift," or "command" for the modifier key. 177 | 178 | B The app name in the configuration file must 179 | exactly match the name of the offical app name as installed on your Mac. The 180 | official name can differ substantially than the app's common name. For example, 181 | the app name for "Zoom" is "zoom.us". To ensure you app name correct, use the 182 | L module installed with this module to list out the 183 | apps installed on your machine. Alternatively, use the Karabiner-EventViewer 184 | application. The module will warn you if it does not recognize the name of an 185 | app in your configuration file. 186 | 187 | =head1 AUTHOR 188 | 189 | Steve Dondley 190 | 191 | =head1 COPYRIGHT AND LICENSE 192 | 193 | Copyright 2022 Steve Dondley 194 | 195 | This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0. 196 | 197 | =end pod 198 | -------------------------------------------------------------------------------- /lib/Karabiner/Template.rakumod: -------------------------------------------------------------------------------- 1 | unit class Template; 2 | use Template::Classic; 3 | 4 | has Str $.key where .chars == 1; 5 | has Str $.mod where (* ~~ any [ 'command', 'option', 'shift', 'control' ]) = 'command'; 6 | 7 | method rule_generator($tmpl) { 8 | my (@values, @usage_names); 9 | 10 | my @attribute_data = self.^attributes».name».substr(2).map: { $_, self."$_"() }; 11 | for @attribute_data -> $pair { 12 | push @usage_names, $pair[0]; 13 | push @values, $pair[1]; 14 | } 15 | my @params = @usage_names.map: { Parameter.new(:name('$' ~ $_)) }; 16 | 17 | my &generate-rule := template Signature.new(:@params, 18 | :returns(Seq)), 19 | %?RESOURCES{$tmpl}.slurp; 20 | 21 | return generate-rule(|@values).eager.join.trim-trailing; 22 | } 23 | 24 | method get_bot() { 25 | q:to/BOT/; 26 | 27 | ] 28 | } 29 | BOT 30 | } 31 | 32 | -------------------------------------------------------------------------------- /lib/Karabiner/Templates/ActivateApps.rakumod: -------------------------------------------------------------------------------- 1 | use Karabiner::Template; 2 | use Mac::Applications::List; 3 | unit class ActivateApps is Template; 4 | 5 | my $tmpl = 'double.json'; 6 | my $apps = MacAppList.new.find_apps; 7 | has Str $.app1 where * ~~ $apps.any; 8 | has Str $.app2 where * ~~ $apps.any; 9 | 10 | multi method create(|c($app1, $key)) { 11 | self.create(|c, 'command') 12 | } 13 | multi method create($app1, $key, $mod) { 14 | if $mod.chars == 1 { 15 | return self.create($app1, $key, $mod, 'command'); 16 | } 17 | $tmpl = 'double.json'; 18 | my $a = self.bless(:$app1, :$key, :$mod); 19 | $a.rule_generator($tmpl); 20 | } 21 | multi method create($app1, $app2, $key, $mod) { 22 | $tmpl = 'triple.json'; 23 | my $a = self.bless(:$app1, :$app2, :$key, :$mod); 24 | $a.rule_generator($tmpl); 25 | } 26 | 27 | method get_top() { 28 | q:to/TOP/; 29 | {"title": "Activating Apps", 30 | "rules": [ 31 | TOP 32 | } 33 | 34 | =begin pod 35 | 36 | Here is a sample configuration file for use with the C template: 37 | 38 | =begin code 39 | # lines beginning with the '#' character get ignored 40 | # The '*' indicates an optional field 41 | # 1st app name,2nd app name*,key,modifier* 42 | 43 | Adobe Photoshop 2021,Preview,p,command 44 | zoom.us,z,option 45 | =end code 46 | 47 | This configuration file: 48 | 49 | =item assigns ⌘-p-p (hold down command key and double tap "p") to open Adobe Photoshop 50 | =item assigns ⌘-p-p-p (hold down command key and triple tap "p")to open Preview 51 | =item assigns ⌘-z-z (hold down command key and double tap "z") to open Zoom 52 | 53 | Notice the second app name is optional. If only one app name is provided, only 54 | one shortcut (double tap) will be generated. If two app names are provided, the 55 | first app is assigned to the double tap shortcut and the second app is assigned 56 | to the triple tap shortcut. 57 | 58 | The "modifier" argument is also optional. If not provided, it defaults to the 59 | "command" key. You may use "option," "control," "shift," or "command" for the 60 | modifier key. 61 | 62 | B You must type in the exact name of the app. To ensure you get the 63 | correct app name, you can use the L module installed 64 | with this module to list out the apps installed on your machine. Alternatively, 65 | use the Karabiner-EventViewer application. 66 | 67 | =end pod 68 | -------------------------------------------------------------------------------- /lib/Karabiner/Templates/SafariTabs.rakumod: -------------------------------------------------------------------------------- 1 | use Karabiner::Template; 2 | use Mac::Applications::List; 3 | unit class SafariTabs is Template; 4 | 5 | my $tmpl = 'safaritabs.json'; 6 | 7 | has Str $.tabname; 8 | has Str $.tabnum where *.Num > 0; 9 | 10 | 11 | multi method create(|c($tabname, $tabnum, $key)) { 12 | self.create(|c, 'option') 13 | } 14 | multi method create($tabname, $tabnum, $key, $mod) { 15 | my $a = self.bless(:$tabname, :$tabnum, :$key, :$mod); 16 | $a.rule_generator($tmpl); 17 | } 18 | 19 | method get_top() { 20 | q:to/TOP/; 21 | {"title": "Activate Safari Tabs", 22 | "rules": [ 23 | TOP 24 | } 25 | 26 | =begin pod 27 | 28 | Here is a sample configuration file for use with the C template: 29 | 30 | =begin code 31 | # Tab name,Tab number,key,modifier* 32 | # * denotes optional fields 33 | # default modifier is "option" key 34 | 35 | Raku docs,1,r,control 36 | Twitter,2,t 37 | =end code 38 | 39 | =item assigns ^-r-r (hold down control key and double tap "r") to activate 40 | Safari and switch to the first tab 41 | =item assigns ⌥-t-t (hold down option key and double tap "t") to activate 42 | Safari and switch to the second tab 43 | 44 | B Use Safari's L 46 | feature so the tabs are fixed to the left-hand side of your tab list to give 47 | tab numbers consistency between different browser windows. 48 | 49 | =end pod 50 | 51 | -------------------------------------------------------------------------------- /resources/double.json: -------------------------------------------------------------------------------- 1 | { "description": "Activate <%=$app1%>, Double tap cmd-<%=$key%>", 2 | "manipulators": [ 3 | { 4 | "type": "basic", 5 | "conditions": [ 6 | { 7 | "type": "variable_if", 8 | "name": "k<%=$mod%> pressed", 9 | "value": 1 10 | } 11 | ], 12 | "from": { 13 | "key_code": "<%=$key%>", 14 | "modifiers": { "mandatory": ["left_<%=$mod%>"] } 15 | }, 16 | "to": [ 17 | { "shell_<%=$mod%>": "open -a '<%=$app1%>'" } 18 | ] 19 | }, 20 | { 21 | "type": "basic", 22 | "from": { 23 | "key_code": "<%=$key%>", 24 | "modifiers": { "mandatory": ["left_<%=$mod%>"] } 25 | }, 26 | "to": [ 27 | { 28 | "set_variable": { 29 | "name": "k<%=$mod%> pressed", 30 | "value": 1 31 | } 32 | } 33 | ], 34 | "to_delayed_action": { 35 | "to_if_invoked": [ 36 | { 37 | "set_variable": { 38 | "name": "k<%=$mod%> pressed", 39 | "value": 0 40 | } 41 | }, 42 | { 43 | "key_code": "<%=$key%>", 44 | "modifiers": "right_<%=$mod%>" 45 | } 46 | ], 47 | "to_if_canceled": [ 48 | { 49 | "set_variable": { 50 | "name": "k<%=$mod%> pressed", 51 | "value": 0 52 | } 53 | } 54 | ] 55 | } 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /resources/safaritabs.json: -------------------------------------------------------------------------------- 1 | { "description": "<%=$tabname%> tab, Double tap <%=$mod%>-<%=$key%>", 2 | "manipulators": [ 3 | { 4 | "type": "basic", 5 | "conditions": [ 6 | { 7 | "type": "variable_if", 8 | "name": "key pressed", 9 | "value": 1 10 | } 11 | ], 12 | "from": { 13 | "key_code": "<%=$key%>", 14 | "modifiers": { "mandatory": ["left_<%=$mod%>"] } 15 | }, 16 | "to": [ 17 | { "shell_command": "/bin/bash -c 'kcmg_safari_tabs_activate <%=$tabnum%>'" } 18 | ] 19 | }, 20 | { 21 | "type": "basic", 22 | "from": { 23 | "key_code": "<%=$key%>", 24 | "modifiers": { "mandatory": [ "left_<%=$mod%>" ] } 25 | }, 26 | "to": [ 27 | { 28 | "set_variable": { 29 | "name": "key pressed", 30 | "value": 1 31 | } 32 | } 33 | ], 34 | "to_delayed_action": { 35 | "to_if_invoked": [ 36 | { 37 | "set_variable": { 38 | "name": "key pressed", 39 | "value": 0 40 | } 41 | }, 42 | { 43 | "key_code": "<%=$key%>", 44 | "modifiers": "right_<%=$mod%>" 45 | } 46 | ], 47 | "to_if_canceled": [ 48 | { 49 | "set_variable": { 50 | "name": "key pressed", 51 | "value": 0 52 | } 53 | } 54 | ] 55 | } 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /resources/triple.json: -------------------------------------------------------------------------------- 1 | { "description": "Activate <%=$app1%>/<%=$app2%>, Double/Triple tap cmd-<%=$key%>", 2 | "manipulators": [ 3 | { 4 | "type": "basic", 5 | "conditions": [ 6 | { 7 | "type": "variable_if", 8 | "name": "k<%=$mod%> pressed", 9 | "value": 2 10 | } 11 | ], 12 | "from": { 13 | "key_code": "<%=$key%>", 14 | "modifiers": { "mandatory": ["left_<%=$mod%>"] } 15 | }, 16 | "to": [ 17 | { "shell_<%=$mod%>": "open -a '<%=$app2%>'" }, 18 | { 19 | "set_variable": { 20 | "name":"k<%=$mod%> pressed", 21 | "value": 0 22 | } 23 | } 24 | ] 25 | }, 26 | { 27 | "type": "basic", 28 | "conditions": [ 29 | { 30 | "type": "variable_if", 31 | "name": "k<%=$mod%> pressed", 32 | "value": 1 33 | } 34 | ], 35 | "from": { 36 | "key_code": "<%=$key%>", 37 | "modifiers": { "mandatory": ["left_<%=$mod%>"] } 38 | }, 39 | "to": [ 40 | { 41 | "set_variable": { 42 | "name": "k<%=$mod%> pressed", 43 | "value": 2 44 | } 45 | } 46 | ], 47 | "to_delayed_action": { 48 | "to_if_invoked": [ 49 | { 50 | "set_variable": { 51 | "name": "k<%=$mod%> pressed", 52 | "value": 0 53 | } 54 | }, 55 | { "shell_<%=$mod%>": "open -a '<%=$app1%>'" } 56 | ], 57 | "to_if_canceled": [ 58 | { 59 | "set_variable": { 60 | "name": "k<%=$mod%> pressed", 61 | "value": 0 62 | } 63 | } 64 | ] 65 | } 66 | }, 67 | { 68 | "type": "basic", 69 | "from": { 70 | "key_code": "<%=$key%>", 71 | "modifiers": { "mandatory": ["left_<%=$mod%>"] } 72 | }, 73 | "to": [ 74 | { 75 | "set_variable": { 76 | "name": "k<%=$mod%> pressed", 77 | "value": 1 78 | } 79 | } 80 | ], 81 | "to_delayed_action": { 82 | "to_if_invoked": [ 83 | { 84 | "set_variable": { 85 | "name": "k<%=$mod%> pressed", 86 | "value": 0 87 | } 88 | }, 89 | { 90 | 91 | "key_code": "<%=$key%>", 92 | "modifiers": "right_<%=$mod%>" 93 | } 94 | ] 95 | } 96 | } 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /t/01-basic.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Karabiner::CompModGenerator; 3 | 4 | 5 | plan 5; 6 | 7 | 8 | "ActivateApps.json".IO.unlink; 9 | 10 | 11 | # 1 - check binary fails without argument 12 | my $proc = run('bin/kcmg'); 13 | isnt $proc.exitcode, 0, 'fails without argument'; 14 | 15 | # 2 16 | isnt (run 'bin/kcmg', '--', 't/blah.cfg').exitcode, 0, 'detects non-existent config file'; 17 | 18 | # 3 19 | run 'bin/kcmg', '--', 't/data/ActivateApps.cfg'; 20 | is "ActivateApps.json".IO.f, True, 'creates output file'; 21 | 22 | # 4 23 | my $file_contents = "ActivateApps.json".IO.slurp; 24 | is $file_contents ~~ /command/, 'command', 'puts content into file'; 25 | 26 | "ActivateApps.json".IO.unlink; 27 | 28 | # 5 29 | dies-ok { run 'bin/kcmg', '--', 't/data/NoTemplate.cfg' }, 'detect when template does not exist and dies.'; 30 | 31 | 32 | done-testing; 33 | -------------------------------------------------------------------------------- /t/02-ActiveApps_tmpl.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Karabiner::Templates::ActivateApps; 3 | 4 | plan 1; 5 | my $template; 6 | lives-ok { 7 | $template = ActivateApps.create('Safari', 's', 'option'); 8 | $template = ActivateApps.create('Mail', 's'); 9 | $template = ActivateApps.create('Mail', 'Safari', 's'); 10 | $template = ActivateApps.create('Safari', 'Mail', 's', 'shift'); 11 | }, 'generates files without dying'; 12 | 13 | 14 | done-testing; 15 | 16 | 17 | -------------------------------------------------------------------------------- /t/03-SafariTabs_tmpl.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Karabiner::Templates::SafariTabs; 3 | 4 | plan 1; 5 | my $template; 6 | lives-ok { 7 | $template = SafariTabs.create('Grammarly', '1', 'g', 'option'); 8 | $template = SafariTabs.create('Amazon', '2', 'a'); 9 | }, 'generates files without dying'; 10 | 11 | 12 | done-testing; 13 | 14 | 15 | -------------------------------------------------------------------------------- /t/data/ActivateApps.cfg: -------------------------------------------------------------------------------- 1 | # karabiner config file 2 | #Activity Monitor,Audio Hijack,a 3 | Notes,s 4 | Notes,Safari,n,option 5 | Safari,z 6 | Notes,Safari,k,option 7 | Notes,Safari,m 8 | -------------------------------------------------------------------------------- /t/data/NoTemplate.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdondley/Karabiner-CompModGenerator/3ce69ee73d8a06a6ec264d9917f65f585e75e6e0/t/data/NoTemplate.cfg --------------------------------------------------------------------------------