What is the trick behind "launch application holding [modifier key]"? - macos

iTunes and Reeder (and I'm sure lots of other) applications has an ability to modify startup behavior whenever they are launched while holding ⌥ (option) key. I tried looking at NSApplicationDelegate methods, but none seem to add any sort of hint to what I'm looking for. How is this functionality achieved?

In your application delegate's applicationDidFinishLaunching: method
NSUInteger flags = ([NSEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask);
BOOL isOptionPressed = (flags == NSAlternateKeyMask);
(Edited to match awesome answer Declaring and checking/comparing (bitmask-)enums in Objective-C
Previously this was
BOOL isOptionPressed = (0 != (flags & NSAlternateKeyMask));
But the zero checking is not necessary to check if a bit mask for equality, unless the bit mask itself represents all zeros in binary.
The provided link gives greater detail.
)

Francis McGrew's answer needs improvement before it can work, but I still see no solution via that route. The answer as stated does not compile. An actual NSEvent pointer is needed for modifierFlags, which is not a class method as the answer would suggest.
One would hope that the needed event might be obtained by the following.
NSEvent *event = [NSApp currentEvent];
However when called from applicationDidFinishLaunching the resulting event is nil. At least this is true in my testing on Snow Leopard.
applicationDidFinishLaunching has an NSNotification argument but I don't know how to make any use of it.
I found a similar question on cocobuilder.com from 2007 and the answer there is basically to call the carbon function GetCurrentKeyModifiers and convert the carbon flags to cocoa form.
See http://www.cocoabuilder.com/archive/cocoa/176882-detecting-modifier-keys-at-launch.html

Related

How Do I Disable Alt-Drag Selection on an NSTextView?

I do not know, and cannot find, the standard technical term for the drag-select functionality when the alt/option button is pressed over an NSTextView.
When alt is pressed, the crosshair appears and text can be selected/highlighted in columns/vertically, as in Xcode.
I would like to disable this functionality in my NSTextViews, how can I do that please?
Digging into the disassembly of AppKit quickly reveals a hook which tells NSTextView whether the ALT+drag interaction should be enabled. It's the private method called _allowsMultipleTextSelectionByMouse. Altering its behavior does the trick but it is achievable only via private API. In this case, it should be pretty safe to do so.
There are following two ways for altering the aforementioned method:
Approach 1: NSUserDefaults
The method internally accesses the NSProhibitMultipleTextSelectionByMouse of NSUserDefaults. You can control the boolean value for this key on the app level. The downsides of this approach are:
It can possibly be reenabled by the users of your app (e.g. via the defaults command line tool).
It affects all instances of NSTextView in your app even those you don't necessarily want to alter.
Approach 2: Swizzling
The approach I decided to go with is a simple swizzle of this method in my subclass of NSTextView.
internal final class MyTextView: NSTextView {
internal static func initializeClass() {
try? MyTextView.swizzle(
Selector(("_allowsMultipleTextSelectionByMouse")),
with: #selector(getter: swizzled_allowsMultipleTextSelectionByMouse)
)
}
#objc dynamic internal var swizzled_allowsMultipleTextSelectionByMouse: Bool {
return false
}
}
And somewhere in the app bootstrapping code like the main function you have to trigger the class initialization code by MyTextView.initializeClass().
The code snippet above uses my own wrapper around the swizzling API but there are surely some libraries out there to use or you can follow the advices from this article by PSPDFKit.

NSAlert beginSheetModalForWindow:completionHandler:

While setting up an NSAlert object to be displayed as a modal sheet in Xcode 5.0.2, I hit an interesting surprise.
I was planning on using beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:
As I started to enter it, Xcode autofilled beginSheetModalForWindow:completionHandler: for me (even though I cannot find this in any NSAlert documentation).
I prefer to use completion handlers rather than delegate/selector as a callback mechanism, so I went ahead and tried it. I was pleasantly surprised to find that it worked perfectly.
Three quick questions before I commit to this.
Am I missing something in the documentation?
Is it "safe" to use this feature if it is undocumented? (i.e. will it magically disappear as mysteriously as it appeared?)
I'd rather not hardcode the response values based on what I'm seeing via logging. Does anybody know the "proper" NS...Button constants?
This call is “safe” but it’s 10.9+ only. Here it is from the header file:
#if NS_BLOCKS_AVAILABLE
- (void)beginSheetModalForWindow:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
#endif
It appears they just accidentally left it out of the current docs. The headers are generally considered the “truth” in Cocoa, though—they authoritatively tell you what’s deprecated and what’s new. (Unlike in X11, for instance, where the documentation was declared to be correct over the actual implementations or the headers.)
These are the constants you want to use inside your completionHandler block:
/* These are additional NSModalResponse values used by NSAlert's -runModal and -beginSheetModalForWindow:completionHandler:.
By default, NSAlert return values are position dependent, with this mapping:
first (rightmost) button = NSAlertFirstButtonReturn
second button = NSAlertSecondButtonReturn
third button = NSAlertThirdButtonReturn
buttonPosition 3+x = NSAlertThirdButtonReturn + x
Note that these return values do not apply to an NSAlert created via +alertWithMessageText:defaultButton:alternateButton:otherButton:informativeTextWithFormat:, which instead uses the same return values as NSRunAlertPanel. See NSAlertDefaultReturn, etc. in NSPanel.h
*/
enum {
NSAlertFirstButtonReturn = 1000,
NSAlertSecondButtonReturn = 1001,
NSAlertThirdButtonReturn = 1002
};

OS X: Detect system-wide keyDown events?

I'm working on a typing-tutor application for Mac OS X that needs to have keystrokes forwarded to it, even when the application is not in focus.
Is there a way to have the system forward keystrokes to the app, possibly through NSDistributedNotificationCenter? I've googled myself silly, and haven't been able to find an answer...
EDIT: Sample code below.
Thanks #NSGod for pointing me in the right direction -- I ended up adding a global events monitor using the method addGlobalMonitorForEventsMatchingMask:handler:, which works beautifully. For completeness, my implementation looks like this:
// register for keys throughout the device...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
NSString *chars = [[event characters] lowercaseString];
unichar character = [chars characterAtIndex:0];
NSLog(#"keydown globally! Which key? This key: %c", character);
}];
For me, the tricky part was using blocks, so I'll give a little description in case it helps anyone:
The thing to notice about the above code is that it's all one single method call on NSEvent. The block is supplied as an argument, directly to the function. You could think of it kind of like an inline delegate method. Just because this took a while to sink in for me, I'm going to work through it step by step here:
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
This first bit is no problem. You're calling a class method on NSEvent, and telling it which event you're looking to monitor, in this case NSKeyDownMask. A list of masks for supported event types can be found here.
Now, we come to the tricky part: handler, which expects a block:
handler:^(NSEvent *event){
It took me a few compile errors to get this right, but (thank you Apple) they were very constructive error messages. The first thing to notice is the carat ^. That signals the start of the block. After that, within the parentheses,
NSEvent *event
Which declares the variable that you'll be using within the block to capture the event. You could call it
NSEvent *someCustomNameForAnEvent
doesn't matter, you'll just be using that name within the block. Then, that's just about all there is to it. Make sure to close your curly brace, and bracket to finish the method call:
}];
And you're done! This really is kind of a 'one-liner'. It doesn't matter where you execute this call within your app -- I do it in the AppDelegate's applicationDidFinishLaunching method. Then, within the block, you can call other methods from within your app.
If you are okay with a minimum requirement of OS X 10.6+, and can suffice with "read-only" access to the stream of events, you can install a global event monitor in Cocoa:
Cocoa Event-Handling Guide: Monitoring Events.
If you need to support OS X 10.5 and earlier, and read-only access is okay, and don't mind working with the Carbon Event Manager, you can basically do the Carbon-equivalent using GetEventMonitorTarget(). (You will be hard-pressed to find any (official) documentation on that method though). That API was first available in OS X 10.3, I believe.
If you need read-write access to the event stream, then you will need to look at a slightly lower-level API that is part of ApplicationServices > CoreGraphics:CGEventTapCreate() and friends. This was first available in 10.4.
Note that all 3 methods will require that the user have "Enable access for assistive devices" enabled in the System Preferences > Universal Access preference pane (at least for key events).
I'm posting the code that worked for my case.
I'm adding the global event handler after the app launches. My shortcut makes ctrl+alt+cmd+T open my app.
- (void) applicationWillFinishLaunching:(NSNotification *)aNotification
{
// Register global key handler, passing a block as a callback function
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
// Activate app when pressing cmd+ctrl+alt+T
if([event modifierFlags] == 1835305 && [[event charactersIgnoringModifiers] compare:#"t"] == 0) {
[NSApp activateIgnoringOtherApps:YES];
}
}];
}
The issue I find with this is that any key registered globally by another app will not be cought... or at least in my case, perhaps I am doing something wrong.
If your program needs to display all keys, like "Command-Shift-3" for example, then it will not see that go by to display it... since it is taken up by the OS.
Or did someone figure that out? I'd love to know...
As NSGod already pointed out you can also use CoreGraphics.
In your class (e.g. in -init):
CFRunLoopRef runloop = (CFRunLoopRef)CFRunLoopGetCurrent();
CGEventMask interestedEvents = NSKeyDown;
CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
0, interestedEvents, myCGEventCallback, self);
// by passing self as last argument, you can later send events to this class instance
CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
eventTap, 0);
CFRunLoopAddSource((CFRunLoopRef)runloop, source, kCFRunLoopCommonModes);
CFRunLoopRun();
Outside of the class, but in the same .m file:
CGEventRef myCGEventCallback(CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon)
{
if(type == NX_KEYDOWN)
{
// we convert our event into plain unicode
UniChar myUnichar[2];
UniCharCount actualLength;
UniCharCount outputLength = 1;
CGEventKeyboardGetUnicodeString(event, outputLength, &actualLength, myUnichar);
// do something with the key
NSLog(#"Character: %c", *myUnichar);
NSLog(#"Int Value: %i", *myUnichar);
// you can now also call your class instance with refcon
[(id)refcon sendUniChar:*myUnichar];
}
// send event to next application
return event;
}

CGWindowID from AXUIElement

I'm trying to automate a foreign OSX application using the accessibility API. Some of
the state of the application isn't available through the API, so I acquire it through
screen scraping. To do this, I need to get CGWindowID for an accessibility object with
a 'Window Role'.
Is there any direct way of acquiring CGWindowID of a 'Window Role' accessibility object?
I can get it heuristically, by matching various attributes of the window, such as
the size, title and location, but this is really hacky, and I'd feel better if my
application would also support the corner cases, even if they are unlikely.
There is a function from at least Leopard and up (and still around as of 10.7.3):
extern "C" AXError _AXUIElementGetWindow(AXUIElementRef, CGWindowID* out);
Usual caveats about using something like this apply though. It may change as soon as the next OS update!
There’s no way to do that; the accessibility hierarchy is completely decoupled from the actual window/view hierarchy. I think your matching will work best.
The correct current declaration in a Swift Bridging Header is:
#import <AppKit/AppKit.h>
AXError _AXUIElementGetWindow(AXUIElementRef element, uint32_t *identifier);
As used here: https://github.com/rxhanson/Rectangle/blob/master/Rectangle/Rectangle-Bridging-Header.h

Cocoa Won't Capture Shift Modifier?

I have an application in which I'm trying to capture the shift key modifier to perform an action, however when I run the program and press and normal key without the shift key modifier I get a beep and the modifier and key are not sent to my keyDown event. The relevant code is:
NSString* eventChars = [theEvent charactersIgnoringModifiers];
if ([eventChars isEqualTo:#"w"]) {
newPlayerRow++;
direction = eUp;
} else if ([eventChars isEqualTo:#"x"]) {
newPlayerRow--;
direction = eDown;
} else if ([eventChars isEqualTo:#"a"]) {
newPlayerCol--;
direction = eLeft;
} else if ([eventChars isEqualTo:#"d"]) {
newPlayerCol++;
direction = eRight;
} else {
[super keyDown:theEvent];
return;
}
// handle the player firing a bullet
if (([theEvent modifierFlags] & (NSShiftKeyMask | NSAlphaShiftKeyMask)) != 0) {
NSLog(#"Shift key");
[self fireBulletAtColumn:newPlayerCol row:newPlayerRow inDirection:direction];
[self setNeedsDisplay:YES];
} else {
...
}
I'm not sure what is causing this, but I'd like to be able to capture shift key presses. Thanks in advance for any help with this problem.
EDIT: Also I'm using a MacBook keyboard if that makes any difference.
EDIT: This is definitely a shift-centric problem as changing (NSShiftKeyMask | NSAlphaShiftKeyMask) to NSControlKeyMask does have the desired effect.
First, -charactersIgnoringModifiers doesn't ignore the shift key, so you will still get shifted characters (i.e UPPERCASE and !%#$%^&*) returned from it. What's probably happening in your function is: You press shift-w, your -isEqualTo: returns false because you're comparing a lowercase 'w' and an uppercase 'W', and so you return before getting to the shift-detection code at the bottom. The simplest solution is to just check for both.
However, if you want, for example, Arabic keyboardists to be able to easily use your app, you really shouldn't hardcode characters that may not even appear on the user's keyboard. The value returned by -keyCode refers to a key's position on the keyboard, not the represented character. For starters, the constants beginning in 'kVK_ANSI_' and 'kVK_' in Events.h (you may have to link to Carbon.framework and #include <Carbon/Carbon.h> to use those constants) can be compared to what -keyCode returns, and they refer to the key positions a QWERTY-using USian expects. So you can be (pretty) sure that, regardless of keyboard layout, the keycodes for 'wasd' (kVK_ANSI_W, kVK_ANSI_A, etc.) will refer to that triangle in the top left of your user's keyboard.
If you just want to be notified of modifier key presses without a character key you should be overriding the NSResponder method flagsChanged.
- (void)flagsChanged:(NSEvent *)theEvent
Have you tried logging the values of eventChars and modifierFlags using NSLog? Perhaps it's something other than what you expected it to be. If the log statement doesn't show up in your output, then this code is not running at all and you have a problem somewhere else.
NSLog(#"Chars: %#, modifier flags: 0x%x", eventChars, [theEvent modifierFlags]);
Some other things worth noting:
isEqualTo: is the AppleScript equality operator. It should work, but won't be as efficient because it goes through compare:. The proper method is either isEqualToString: or the more-generic isEqual:, either of which may do a straight equality comparison.
The traditional set of movement keys in the QWERTY letter region is wsad, not wxad. This uses the inverted-T arrangement. You may also want to support the arrow keys, as many of us do have keyboards with their arrows in the inverted T arrangement.
Furthermore, you should make these configurable by the user, and you should respond to key codes rather than letters because different layouts (French, Dvorak, etc.) will generate different letters. I have a table of key codes, adapted from Inside Macintosh, on my website. Use characters only for display purposes.
Shouldn't it be “fireBulletFromColumn:row:inDirection:”? I don't know about you, but saying that the method fires a bullet at a cell tells me (and will tell you, after you haven't looked at the code for a year) that it fires the bullet toward that cell; replacing “At” with “From” avoids this moment of confusion.
From your code it looks like the code that checks for the modifier is never reached.
Anything that is not "w,x,a,d" is sent to the superclass (see the else branch).
You probably want to move "[super keyDown:theEvent]; return;" at the end of your keyDown method.
Solving the immediate problem:
Building on what Boaz said, the -charactersIgnoringModifiers does not include the Shift key (read the documentation) and will return capitalized characters when the Shift key is down. That is why the Control key worked and the Shift key didn't. Switch the string to lowercase before you test it:
eventChars = [[theEvent charactersIgnoringModifiers] lowercaseString];
After that, (as Peter suggested) change all the isEqualTo:'s to isEqualToString: (besides being correct, it's also documented as being faster).
However:
If you are planning on releasing this app to other people, you should really be using key codes (as Peter and Boaz suggested) so it will work on as many different keyboards and setups as possible.
Create a view in your preferences with an input for each command you have and record the key code for the key the user presses. Store these in your NSUserDefaults and check against those instead of your hard coded strings. You can create a default set for your current "wasd" keys.
See projects like Shortcut Recorder for examples of how to do that (or search your friend).

Resources