-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 354b28d
Showing
8 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
tap 'caskroom/cask' | ||
|
||
cask 'hammerspoon' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2017 Jason Rudolph | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# ControlEscape.spoon | ||
|
||
Supercharge your <kbd>control</kbd> key. Tap it for <kbd>escape</kbd>. Hold it for <kbd>control</kbd>. | ||
|
||
### A more useful control key | ||
|
||
As Steve Losh points out in his [Modern Space Cadet](http://stevelosh.com/blog/2012/10/a-modern-space-cadet/#control-escape) post, there are "keys you hold down to change how other keys behave, but that (usually) don’t do anything if you use them on their own" (e.g., <kbd>control</kbd>), and there are "keys that you press and release but don’t want to 'repeat' as you hold them" (e.g., <kbd>escape</kbd>). "There are two [distinctly] different ways to use [these keys] and they’re each only useful in one of those ways. This means that we can *combine* them onto a single key without losing any useful functionality!" | ||
|
||
With that in mind, we can teach the <kbd>control</kbd> key to pull double duty: | ||
|
||
- If <kbd>control</kbd> is pressed and released in isolation, we can interpret it as <kbd>escape</kbd> | ||
- If <kbd>control</kbd> is held down and used in combination with another key, then we can let it provide the normal <kbd>control</kbd> key behavior | ||
|
||
### Goes great with Touch Bar | ||
|
||
If you've got one of those fancy new Touch Bar Macs, you might find yourself longing for the good ol' days of a tactile <kbd>escape</kbd> key. Look no further. 😅 | ||
|
||
### Optional: A more useful caps lock key | ||
|
||
What's more useful than a "more useful control key?" A more useful *and more-easily reachable* control key, of course. By repurposing the anachronistic caps lock key, we can skip the acrobatics needed to reach the physical control key. We can use <kbd>caps lock</kbd> to give us <kbd>control</kbd> and <kbd>escape</kbd> right on the home row. | ||
|
||
Say goodbye to [Emacs pinky](http://wiki.c2.com/?EmacsPinky). 👋 | ||
|
||
## Dependencies | ||
|
||
This functionality is developed and tested with the following dependencies: | ||
|
||
- macOS Sierra 10.12 | ||
- [Hammerspoon 0.9.54][hammerspoon] | ||
|
||
## Installation | ||
|
||
#### Just the basics | ||
|
||
```sh | ||
mkdir -p ~/.hammerspoon/Spoons | ||
|
||
git clone https://github.com/jasonrudolph/ControlEscape.spoon.git ~/.hammerspoon/Spoons/ControlEscape.spoon | ||
|
||
cd ~/.hammerspoon/Spoons/ControlEscape.spoon | ||
|
||
script/setup | ||
``` | ||
|
||
#### Optional: You're just one step away from a [more useful caps lock key](#optional-a-more-useful-caps-lock-key) | ||
|
||
With one more step, you'll be able to hold <kbd>caps lock</kbd> for <kbd>control</kbd>, and tap <kbd>caps lock</kbd> for <kbd>escape</kbd>: | ||
|
||
``` | ||
script/remap-caps-lock-to-control | ||
``` | ||
|
||
## Where can I get this code as a Spoon? | ||
|
||
If you prefer to roll your own Hammerspoon config, this code is also available as a [Spoon](https://github.com/Hammerspoon/hammerspoon/blob/0.9.54/SPOONS.md#how-do-i-install-a-spoon). Just look for `ControlEscape.spoon.zip` included with the [latest release](https://github.com/jasonrudolph/ControlEscape.spoon/releases/latest). | ||
|
||
## Shout-outs | ||
|
||
Shout-out to [@arbelt](https://github.com/arbelt) and [@jasoncodes](https://github.com/jasoncodes) for the original implementation of this functionality. ⚡️🍻🌟 | ||
|
||
This code is an adaptation of their work: | ||
|
||
- https://gist.github.com/arbelt/b91e1f38a0880afb316dd5b5732759f1 | ||
- https://github.com/jasoncodes/dotfiles/blob/ac9f3ac/hammerspoon/control_escape.lua | ||
|
||
## You might also like... | ||
|
||
Still reading? Dude, you're pretty serious about your keyboard. :neckbeard: | ||
|
||
Lucky for you, there's more where this came from. Check out [jasonrudolph/keyboard](https://github.com/jasonrudolph/keyboard#features) for even more fun. ⌨🤓 | ||
|
||
[hammerspoon]: http://www.hammerspoon.org |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
[ | ||
{ | ||
"Constant" : [ | ||
|
||
], | ||
"submodules" : [ | ||
|
||
], | ||
"Function" : [ | ||
|
||
], | ||
"Variable" : [ | ||
|
||
], | ||
"stripped_doc" : [ | ||
|
||
], | ||
"type" : "Module", | ||
"Deprecated" : [ | ||
|
||
], | ||
"desc" : "Make the `control` key more useful...", | ||
"Constructor" : [ | ||
|
||
], | ||
"items" : [ | ||
{ | ||
"def" : "ControlEscape:start()", | ||
"name" : "start", | ||
"doc" : "Start sending `escape` when `control` is pressed and released in isolation", | ||
"stripped_doc" : [ | ||
"Start sending `escape` when `control` is pressed and released in isolation" | ||
], | ||
"notes" : [ | ||
|
||
], | ||
"signature" : "ControlEscape:start()", | ||
"type" : "Method", | ||
"returns" : [ | ||
|
||
], | ||
"desc" : "Start sending `escape` when `control` is pressed and released in isolation", | ||
"parameters" : [ | ||
|
||
] | ||
}, | ||
{ | ||
"def" : "ControlEscape:stop()", | ||
"name" : "stop", | ||
"doc" : "Stop sending `escape` when `control` is pressed and released in isolation", | ||
"stripped_doc" : [ | ||
"Stop sending `escape` when `control` is pressed and released in isolation" | ||
], | ||
"notes" : [ | ||
|
||
], | ||
"signature" : "ControlEscape:stop()", | ||
"type" : "Method", | ||
"returns" : [ | ||
|
||
], | ||
"desc" : "Stop sending `escape` when `control` is pressed and released in isolation", | ||
"parameters" : [ | ||
|
||
] | ||
} | ||
], | ||
"Method" : [ | ||
{ | ||
"def" : "ControlEscape:start()", | ||
"name" : "start", | ||
"doc" : "Start sending `escape` when `control` is pressed and released in isolation", | ||
"stripped_doc" : [ | ||
"Start sending `escape` when `control` is pressed and released in isolation" | ||
], | ||
"notes" : [ | ||
|
||
], | ||
"signature" : "ControlEscape:start()", | ||
"type" : "Method", | ||
"returns" : [ | ||
|
||
], | ||
"desc" : "Start sending `escape` when `control` is pressed and released in isolation", | ||
"parameters" : [ | ||
|
||
] | ||
}, | ||
{ | ||
"def" : "ControlEscape:stop()", | ||
"name" : "stop", | ||
"doc" : "Stop sending `escape` when `control` is pressed and released in isolation", | ||
"stripped_doc" : [ | ||
"Stop sending `escape` when `control` is pressed and released in isolation" | ||
], | ||
"notes" : [ | ||
|
||
], | ||
"signature" : "ControlEscape:stop()", | ||
"type" : "Method", | ||
"returns" : [ | ||
|
||
], | ||
"desc" : "Stop sending `escape` when `control` is pressed and released in isolation", | ||
"parameters" : [ | ||
|
||
] | ||
} | ||
], | ||
"Command" : [ | ||
|
||
], | ||
"doc" : "Make the `control` key more useful: If the `control` key is tapped, treat it\nas the `escape` key. If the `control` key is held down and used in\ncombination with another key, then provide the normal `control` key\nbehavior.", | ||
"Field" : [ | ||
|
||
], | ||
"name" : "ControlEscape" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
--- === ControlEscape === | ||
--- | ||
--- Make the `control` key more useful: If the `control` key is tapped, treat it | ||
--- as the `escape` key. If the `control` key is held down and used in | ||
--- combination with another key, then provide the normal `control` key | ||
--- behavior. | ||
|
||
local obj={} | ||
obj.__index = obj | ||
|
||
-- Metadata | ||
obj.name = 'ControlEscape' | ||
obj.version = '0.1' | ||
obj.author = 'Jason Rudolph <[email protected]>' | ||
obj.homepage = 'https://github.com/jasonrudolph/keyboard' | ||
obj.license = 'MIT - https://opensource.org/licenses/MIT' | ||
|
||
function obj:init() | ||
self.sendEscape = false | ||
self.lastModifiers = {} | ||
|
||
-- If `control` is held for this long, don't send `escape` | ||
local CANCEL_DELAY_SECONDS = 0.150 | ||
self.controlKeyTimer = hs.timer.delayed.new(CANCEL_DELAY_SECONDS, function() | ||
self.sendEscape = false | ||
end) | ||
|
||
-- Create an eventtap to run each time the modifier keys change (i.e., each | ||
-- time a key like control, shift, option, or command is pressed or released) | ||
self.controlTap = hs.eventtap.new({hs.eventtap.event.types.flagsChanged}, | ||
function(event) | ||
local newModifiers = event:getFlags() | ||
|
||
-- If this change to the modifier keys does not invole a *change* to the | ||
-- up/down state of the `control` key (i.e., it was up before and it's | ||
-- still up, or it was down before and it's still down), then don't take | ||
-- any action. | ||
if self.lastModifiers['ctrl'] == newModifiers['ctrl'] then | ||
return false | ||
end | ||
|
||
-- If the `control` key has changed to the down state, then start the | ||
-- timer. If the `control` key changes to the up state before the timer | ||
-- expires, then send `escape`. | ||
if not self.lastModifiers['ctrl'] then | ||
self.lastModifiers = newModifiers | ||
self.sendEscape = true | ||
self.controlKeyTimer:start() | ||
else | ||
if self.sendEscape then | ||
hs.eventtap.keyStroke({}, 'escape', 0) | ||
end | ||
self.lastModifiers = newModifiers | ||
self.controlKeyTimer:stop() | ||
end | ||
return false | ||
end | ||
) | ||
|
||
-- Create an eventtap to run each time a normal key (i.e., a non-modifier key) | ||
-- enters the down state. We only want to send `escape` if `control` is | ||
-- pressed and released in isolation. If `control` is pressed in combination | ||
-- with any other key, we don't want to send `escape`. | ||
self.keyDownEventTap = hs.eventtap.new({hs.eventtap.event.types.keyDown}, | ||
function(event) | ||
self.sendEscape = false | ||
return false | ||
end | ||
) | ||
end | ||
|
||
--- ControlEscape:start() | ||
--- Method | ||
--- Start sending `escape` when `control` is pressed and released in isolation | ||
function obj:start() | ||
self.controlTap:start() | ||
self.keyDownEventTap:start() | ||
end | ||
|
||
--- ControlEscape:stop() | ||
--- Method | ||
--- Stop sending `escape` when `control` is pressed and released in isolation | ||
function obj:stop() | ||
-- Stop monitoring keystrokes | ||
self.controlTap:stop() | ||
self.keyDownEventTap:stop() | ||
|
||
-- Reset state | ||
self.controlKeyTimer:stop() | ||
self.sendEscape = false | ||
self.lastModifiers = {} | ||
end | ||
|
||
return obj |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#!/bin/sh | ||
|
||
# See https://github.com/Hammerspoon/hammerspoon/blob/0.9.54/SPOONS.md#generating | ||
hs -c "hs.doc.builder.genJSON(\"$(pwd)\")" | grep -v "^--" > docs.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#!/bin/sh | ||
|
||
# See https://developer.apple.com/library/content/technotes/tn2450 | ||
hidutil property --set '{"UserKeyMapping":[{"HIDKeyboardModifierMappingSrc":0x700000039,"HIDKeyboardModifierMappingDst":0x7000000E0}]}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#!/bin/sh | ||
|
||
MINIMUM_HAMMERSPOON_VERSION=0.9.54 | ||
|
||
set -e | ||
|
||
which -s brew || (echo "Homebrew is required: http://brew.sh/" && exit 1) | ||
|
||
brew bundle check || brew bundle | ||
|
||
# Prepare custom settings for Hammerspoon | ||
mkdir -p ~/.hammerspoon | ||
if ! grep -sq "ControlEscape" ~/.hammerspoon/init.lua; then | ||
echo "hs.loadSpoon('ControlEscape'):start() -- Load Hammerspoon bits from https://github.com/jasonrudolph/ControlEscape.spoon" >> ~/.hammerspoon/init.lua | ||
fi | ||
|
||
# If Hammerspoon is already running, kill it so we can pick up the new config | ||
# when opening Hammerspoon below | ||
killall Hammerspoon || true | ||
|
||
# Open Hammerspoon | ||
open /Applications/Hammerspoon.app | ||
|
||
# Enable Hammerspoon at startup | ||
osascript -e 'tell application "System Events" to make login item at end with properties {path:"/Applications/Hammerspoon.app", hidden:true}' > /dev/null | ||
|
||
# Output Hammerspoon version | ||
echo | ||
echo "Hammerspoon version $MINIMUM_HAMMERSPOON_VERSION or greater required. Actual installed version:" | ||
brew cask info hammerspoon | grep "hammerspoon:" | ||
echo | ||
|
||
echo "Done! Be sure to verify your version of Hammerspoon above. If your version is older than $MINIMUM_HAMMERSPOON_VERSION, run 'brew update && script/setup' to install a compatible version." |