Update v0.4 -> v0.5
- Fix hotkey w/ command treated as command alone
- Fix Alfred swallowing space and triggering escape
Merge branch 'fix-alfred-swallowing-space-and-triggering-escape'
Fix Alfred swallowing space and triggering escape
This would probably happen with a lot of applications, but it was always
Alfred for me. It's been bugging me since the start, and the fix ends up
taking me 2 minutes to find the solution to, once I massively simplified
the callback and realised it wasn't the cause for this particular issue.
This is a correction to the initial arguments to `CGEvent.tapCreate`, to
specify that I want the event tap placed at the point where events enter
the login session, not an application, by which time a global hotkey may
have already handled an event or eaten it, nor the window server itself,
which solves the issue but requires the process to run as root.
Merge branch 'fix-hotkey-with-command-only-treated-as-command-alone'
Fix hotkey w/ command treated as command alone
This literally goes against the entire purpose of the app; crazy, what a
regression! I assume 5abc0eb3b6cf276f06bba649b34c75a3d5263246 introduced
This should fix lots of other problems, as I simplified the callback, as
promised, though not in the exact way I had the idea to in 5abc0eb.
As far as I can tell, it's impossible to know the state of all keys on a
given event, so I cannot test that only command is held down, or that no
other keys are pressed when it's released, or the like.
I did find a way to check when command is pressed (as an only modifier),
not just merely checking that modifier flags were changed and command is
down, which can just be a remnant of pressing command + another modifier
key, then releasing the other key before releasing command; this was one
of the many cases we were trying to code for with the messy conditionals
in the older solution, but all these silly hacks are unnecessary now.
We now simply check if the event keycode is command key, save it only if
it's exactly that and command is down, and on the next event, if there's
a saved event, we know it's exactly command press, so if the current one
is a command key event as well and command is up, that's a release, then
we can send an escape; since we always clear the saved event on the next
one, whether it's a command release or not, we know the previous event's
always a command press if it's saved.
Since this solution doesn't check the other keys which are down, you can
hold a letter key, for example, and quickly press command and release in
less than the time it takes the letter to repeat, and it would act as an
escape. This may or may not be what some people want, but I don't really
like it. It's not a huge deal, but it's probably the main issue with how
it works now. I wanted to even save the down/up state of all keys in the
callback's `userInfo`, but that's too much extra effort for now.
Update v0.3 -> v0.4
- Fix `UnsafeMutableRawPointer storeBytes(of:as:)` no longer working
- Fix Safari exiting full-screen on switch to next/previous tab hotkey
- This should fix all bugs where escape is triggered after releasing
a non-command modifier key which leaves only command pressed, then
Merge branch 'fix-safari-next/previous-tab-hotkey-exits-full-screen'
Fix Safari exiting full-screen on switch to next/previous tab hotkey
Command + option + left/right arrow key switches to the previous or next
tab, but when releasing the keys, if command is released last, the issue
can be reliably reproduced.
This happens because the arrow keys aren't considered a modifier flag as
command/option/etc. are, so the app treats it as equivalent to something
like a command + c (which it should); this means the callback clears the
previous key event so that when the command is released, it doesn't look
at the previous event, see the command flag is active, compare it to the
current event, see the command flag is inactive, then conclude that it's
been pressed and released. If there are other modifier keys in a hotkey,
releasing the non-command keys first count, as (`.flagsChanged`) events,
are recorded as such, with the command flag active, then when command is
released, comparing the previous `.flagsChanged` event which has command
flag active, and the current `.flagsChanged` event which has the flag as
inactive, results in a conclusion of command pressed and released.
This is fixed in a quick n' easy way by only clearing the previous event
which precedes non-`.flagsChanged` event when the previous event has the
command flag active, and no other modifier flags. This way, an arroe key
is pressed, it doesn't clear the previous event, because command is held
_with_ option, so when the option key's released, it reaches an existing
conditional that triggers when the current `.flagsChanged` event command
flag is active and the previous event has some active modifier flags, it
does nothing (a noop branch), so when command is released, it's compared
against the _previous_ previous event, which has command active, but not
on its own, and therefore, not triggering an escape.
This is all a bit complicated, so I'm sure there're bugs still, but it's
a small change that fixes the immediate problem, and honestly, I believe
the main or only issue that is the cause of all the annoying behaviour I
have experienced so far.
If there remain issues (or I introduced one with this change), I have an
idea of how to simplify the callback (an overhaul): store all key events
and compare previous and current event, triggering an escape on:
- both are `.flagsChanged` events
- the previous event has no keys pressed except command
- the current event has no keys pressed
Currently, the branch which triggers an escape checks that:
- the _current_ event is a `.flagsChanged`
- the previous event has no _flags_ pressed except command
- the current event has no _flags_ pressed
because I was too lazy to figure out how to check every key at the time,
and I think this is why we need hacky solutions which decide when to set
or clear or ignore the previous event on different conditions, that work
around the various quirks fueled by the wellspring (the sloppy condition
at the heart of the engine).
Merge branch 'fix-unsafe-mutable-raw-pointer-store-bytes'
Fix `UnsafeMutableRawPointer storeBytes(of:as:)` no longer working
I am not sure when this stopped working, and my already-compiled copy of
the app still works as it always did, but I experienced the same problem
one of the app's prospective users, that a version built from source was
crashing (assuming a compiler update broke it).
At first, I thought the problem was a mismatch between the `userInfo` in
`AppDelegate` initialized as an `UnsafeMutablePointer` and the callback,
which takes an `UnsafeMutableRawPointer`. However, translating it to the
latter before doing anything with it, then using `storeBytes(of:as:)` in
`AppDelegate` made the app crash earlier, despite not being passed in to
the callback by that point. Using the `UnsafeMutablePointer` API doesn't
break anything, so we just convert it that direction instead, inside the
callback before using it.
Update v0.2 -> v0.3
- Set `LSApplicationCategoryType` to `public.app-category.utilities`
- Enable Hardened Runtime to facilitate notarization
Enable Hardened Runtime to facilitate notarization
Fix `currentKeyDown` uninitialized before non-`.flagsChanged` event
This was leading to a crash when enabling Hardened Runtime and the first
event was a `.flagsChanged`, as the callback tries to load the memory on
the `.flagsChanged`, but immediately resets it (initializes to `nil`) on
a `.keyDown` or `.keyUp` event.
I knew it would somehow cause problems to not initialize this, but I put
it off at the time because I couldn't figure out how to do so. It took a
while before I gave up the first time, and yet finding out how to do the
same thing second time round was a doddle after I spent another while on
debugging the damn thing; figures.
I also figured out how to allocate enough space for a given type using a
`UnsafeMutableRawPointer` instead of an `UnsafeMutablePointer`, so I can
pass that in to the callback as the exact type it's asking for, and then
I didn't even need to learn how to initialize an `UnsafeMutablePointer`;
figures again. I haven't done that here yet, I don't think that one is a
big deal, but I'll probably do it too at some point.
Set `LSApplicationCategoryType` to `public.app-category.utilities`
Update v0.1 -> v0.2
- Fix flag-release-while-command-held treated as command-pressed-alone
Fix flag-release-while-command-held treated as command-pressed-alone
Change Metalt into Commandeer
Metalt remaps left option + $KEY to esc + $KEY. Commandeer remaps
command to escape when pressed alone.
Increase timeout to 200ms
Add basic README explaining what, why, and how to use