<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dot.git/config/hammerspoon/local-example.lua, branch master</title>
<subtitle>Shell, editor and terminal configuration, and the tools that go with them
</subtitle>
<id>https://git.soarez.net/sz/dot.git/atom?h=master</id>
<link rel='self' href='https://git.soarez.net/sz/dot.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/'/>
<updated>2026-10-01T15:05:25+00:00</updated>
<entry>
<title>hammerspoon: Caps Lock sends F20, and stays that way</title>
<updated>2026-10-01T15:05:25+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-01T15:05:25+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=6c7eb1b380cec100663c7c11d0252f9e6b46d34a'/>
<id>urn:sha1:6c7eb1b380cec100663c7c11d0252f9e6b46d34a</id>
<content type='text'>
hidutil remaps inside the IOKit HID driver, below the window server, so the
key stops being Caps Lock before anything above the driver sees it: no toggle
to suppress, and F20 is a real key to other apps, to System Settings
shortcuts, to a password field. An eventtap cannot do this -- Caps Lock is not
reachable from there, which is why hs.hid exists as its own IOKit module.

What hidutil will not do is cover a keyboard attached after it ran, so dotlib
re-applies on USB attach, on wake, and on a Caps Lock event. That last one is
the general answer: a mapped keyboard cannot produce a Caps Lock event, so an
event arriving at all is proof of a keyboard the remap missed, and the fault
triggers its own repair. The watchers are there to get the common cases before
a key is pressed, since the repairing press is spent repairing.

No launchd job and no link/ part. The remap lives and dies with Hammerspoon,
which already owns the keyboard on these machines, and a plist would only have
bought independence from it for the second or two before it starts -- a user
agent does not run at the login window either.

Nothing is bound to f20 here; bind it like any other key. A machine gives the
key back with capslock_off() from its local.lua, which loads last: shadowing
cannot undo a side effect that has already run. hs.ipc is required so
capslock_status() answers from a shell.
</content>
</entry>
<entry>
<title>hammerspoon: the example's header is init.lua's, so bindings move either way</title>
<updated>2026-09-21T15:53:13+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-21T15:53:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=f31a7a6d69713d45463d53765c9e39aa06290f8c'/>
<id>urn:sha1:f31a7a6d69713d45463d53765c9e39aa06290f8c</id>
<content type='text'>
It unpacked only hotkey and mods, so a line copied from init.lua -- which
writes `hotkey('9', csa, type_text(...))` with bare names -- did not run here,
and a line moved the other way had to be rewritten. The header is now the same
block init.lua has, name for name, and the examples use the bare names too.

Unused names in it are harmless, and keeping them whole is the point: paste a
binding in either direction and it works as written.
</content>
</entry>
<entry>
<title>hammerspoon: shared helpers move to dotlib.lua, which local.lua can require</title>
<updated>2026-09-21T15:30:47+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-21T15:30:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=94a8f1ff31a804a4dfa46ce287e30bf4691ae637'/>
<id>urn:sha1:94a8f1ff31a804a4dfa46ce287e30bf4691ae637</id>
<content type='text'>
A machine's local.lua is a separate chunk, so init.lua's locals were invisible
to it and anything it wanted -- hotkey(), the mod tables, type_text -- had to
be copied. They live in dotlib.lua now, linked beside init.lua, and both files
reach them the same way: require("dotlib").

init.lua keeps its own vocabulary by aliasing what it uses, so the 500 lines
below are untouched. It prepends its own directory to package.path first:
Hammerspoon already puts the config directory there (Contents/Resources/
setup.lua), but taking it from this file's path holds however the config was
found. A machine that pulls without re-linking has no dotlib.lua beside
init.lua, which would lose every hotkey silently -- that case notifies and
names the fix.

Checked with a stubbed hs: the module's hotkey, focus, execute, type_text and
type_current_date all behave, and local-example.lua binds its four keys
through it.
</content>
</entry>
<entry>
<title>hammerspoon, ghostty: example files for their machine-local configs</title>
<updated>2026-09-21T14:41:45+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-21T14:41:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=7f0049952bba4ef0e22bf25dc85da6cdfe38f104'/>
<id>urn:sha1:7f0049952bba4ef0e22bf25dc85da6cdfe38f104</id>
<content type='text'>
ssh and git each ship one; these two hooks were discoverable only by reading
the config that loads them. Both examples carry what is easy to get wrong:
init.lua's hotkey() helper and mod tables are locals, so a local.lua calls
hs.hotkey.bind directly, and a binding there can only be shadowed, never
deleted; in Ghostty a single-value key replaces while keybind accumulates, and
`ignore` is how a binding is switched off rather than added to.

Checked rather than assumed: the lua parses, and the Ghostty example validates
and, dropped in as ~/.config/ghostty-local, does what it says -- font-size,
command and background-opacity replaced, super+shift+n added, alt+enter turned
off.
</content>
</entry>
</feed>
