~habibalamin/Commandeer

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).
1 files changed, 6 insertions(+), 4 deletions(-)

M Commandeer/CommandPressToEscapePressTransformer.swift
M Commandeer/CommandPressToEscapePressTransformer.swift => Commandeer/CommandPressToEscapePressTransformer.swift +6 -4
@@ 32,10 32,12 @@ func commandPressToEscapePressTransformer(proxy: CGEventTapProxy,
  // `UnsafeMutableRawPointer` -> `UnsafeMutablePointer`
  let refcon = refcon?.bindMemory(to: CGEvent?.self, capacity: 1)

  if (event.type != .flagsChanged) {
    refcon?.initialize(to: nil)
  } else if let currentKeyDown = refcon?.pointee {
    if (keyEventNoFlagsPressed(event) &&
  if let currentKeyDown = refcon?.pointee {
    if (event.type != .flagsChanged) {
      if (keyEventCmdPressedAlone(currentKeyDown)) {
        refcon?.initialize(to: nil)
      }
    } else if (keyEventNoFlagsPressed(event) &&
      keyEventCmdPressedAlone(currentKeyDown))
    {
      refcon?.initialize(to: nil)