-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enabled delete and up/down arrow keys #54
Enabled delete and up/down arrow keys #54
Conversation
- Installed a `sendEvent` hook to snoop and then optionally process and consume raw keydowns - Installed a wrapper to make async keystrokes synchronous - Added key handlers for several naked keystrokes, gated on appropriate document focus
Hi! Thanks so much for the PR. It's much appreciated, and code looks good to me. However, this will overwrite the default arrow key behaviour (scrolling) in Fastmail, right? In that case, maybe it would make more sense to have a modifier, like shift up/down to change message. Or at least gate it behind a setting so users can opt in. Thoughts? The delete keypress makes sense to catch, as long as it isn't activated when typing a message or searching! |
You're right! I hadn't noticed that an unmodified arrow was a scroll gesture (I use the spacebar for this). Obviously, we have different usage patterns for email reading! I'm okay with a preference, a modifier, or both. This is your show, so what would you prefer? I am happy to write whatever is needed to satisfy everyone. |
As far as I can tell, shiftup/down does not have any default behaviour in Fastmail, so that seems like good fit for this (and should be easier to implement than a new setting). Both work fine for me though. shiftup/down keys does have a meaning when typing a message (extend selection) so make sure it's not interfering with that if you go with that solution! |
I went with a setting, defaulting to the existing behavior. While a modifier is simpler, it becomes a chord instead of a single key, which sort of defeats the purpose. I'm going to annotate the PR to show you the mechanism by which these key handers don't get in the way of composition. |
@implementation FastmateApplication | ||
@dynamic delegate; | ||
|
||
- (void)sendEvent:(NSEvent *)event { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It all starts here. Instead of using the NSApplication sendEvent
handler, we insert ourselves here. Every keyDown event gets passed to our applicationDelegate for processing. If the delegate "consumes" the event, it returns @YES
, and we stop processing. If the delegate doesn't like the event (for several reasons, I'll describe momentarily) it returns @NO
and we pass the original event on to the default application controller.
return [self.mainWebViewController deleteMessage]; | ||
|
||
default: | ||
return NO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next step is here in the delegate. If it's not one of the three raw keys, tell the application controller we didn't want the event.
- (BOOL)handleKey:(NSEvent *)event { | ||
switch (event.keyCode) { | ||
case kVK_UpArrow: | ||
if ([NSUserDefaults.standardUserDefaults boolForKey:ArrowNavigatesMessageListKey]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here (and line 206) we look at the new setting, and either toss the event (letting the default behavior occur) or we attempt to have the mainWebViewController
deal with it.
- (void)focusSearchField { | ||
[self.webView evaluateJavaScript:@"Fastmate.focusSearch()" completionHandler:nil]; | ||
} | ||
|
||
- (BOOL)nextMessage { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we bridge back into the world of JavaScript, and again attempt to handle the key. Note that instead of fire-and-forget, these three functions return a BOOL
.
focusSearch: function() { | ||
Fastmate.simulateKeyPress("/"); | ||
}, | ||
|
||
nextMessage: function() { | ||
var content = document.getElementById("v54"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, part 1:
Element v54
appears to be the main navigation window. It must be present, and it must have focus (or be the active element, in JS speak). If it is, we call your keystroke simulator, and return true
. If it is not, we assume search, composition, or something else not-the-message-browser has focus, and we return false
, allowing everything to unwind back to the FastmateApplication
, where the event will get forwarded back to the default handler.
deleteMessage: function() { | ||
var deleteButton = document.getElementById("v34"); | ||
var content = document.getElementById("v54"); | ||
if (deleteButton != null && deleteButton.textContent == 'Delete' && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, part 2:
Similar to the arrow rules, we check out the state of the display, and if it appears to be the message list, and we can locate the Delete
button, we ask the button to do its action.
waiting = FALSE; | ||
}]; | ||
|
||
while (waiting) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sidenote: JS is async, and we need to let the caller know if we consumed the keystroke successfully. To do that, we create a spinloop waiting for the JS to complete execution. On paper, this is a terrible idea. In practice, it takes almost no time at all to decide if any given key will be handled, so it's a solution I can live with.
I appreciate the detailed walkthrough 😊 it looks great to me. Will take a more thorough look tomorrow when I’m not on mobile and we can get this merged! |
Added in v1.7.0. Thanks so much for contributing! |
Installed a
sendEvent
hook to snoop and then optionally process and consume raw keydownsInstalled a wrapper to make async keystrokes synchronous
Added key handlers for several naked keystrokes, gated on appropriate document focus
If you have any questions, please ask. If you opt to reject these changes because they are too invasive (or fragile) no offense will be taken. :-)