Skip to content
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

Merged
merged 5 commits into from
Dec 14, 2021

Conversation

kalkwarf
Copy link
Contributor

  • 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

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. :-)

- 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
@joelekstrom
Copy link
Owner

joelekstrom commented Dec 12, 2021

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!

@kalkwarf
Copy link
Contributor Author

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.

@joelekstrom
Copy link
Owner

joelekstrom commented Dec 13, 2021

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!

@kalkwarf
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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;
Copy link
Contributor Author

@kalkwarf kalkwarf Dec 13, 2021

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]) {
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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");
Copy link
Contributor Author

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' &&
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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.

@joelekstrom
Copy link
Owner

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!

@joelekstrom joelekstrom merged commit 5f6f66b into joelekstrom:master Dec 14, 2021
@joelekstrom
Copy link
Owner

Added in v1.7.0. Thanks so much for contributing!

@kalkwarf kalkwarf deleted the additional-keyhandlers branch February 6, 2022 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants