Global hotkey pressed twice, like in dictation feature - objective-c

In Mac OS ML one can setup to switch dictation on by pressing fn key twice. I would like to use the same global shortcut in my application. Does anyone have experience with watching for double pressed keys?

With NSEvent you can setup a global monitor using addGlobalMonitorForEventsMatchingMask:handler: Here is the bit of code that will allow you to monitor for the fn key but you can of course change it no anything if you also filter for NSKeyDownMask.
__block double prevTimeInterval = 0;
[NSEvent addGlobalMonitorForEventsMatchingMask:NSFlagsChangedMask handler:^(NSEvent *theEvent) {
if (theEvent.keyCode == 63) {
if (theEvent.timestamp - prevTimeInterval < 0.2)
NSLog(#"Double pressed fn key");
prevTimeInterval = theEvent.timestamp;
}
}];

Related

Intercept keydown events to NSWindow given to a plugin

I am writing an audio plugin (VST) in Objective-C on OSX. My plugin gets loaded into an application and is given an NSWindow in which to add my own NSView. I need to be able to intercept keyboard events on the NSWindow which I can partly do, but not fully.
Here's what I have tried so far:
Make sure my view is the first response and handle keyDown events. This works for most keyDown events, but not carriage return or special keys like cut/copy/paste.
Use addLocalMonitorForEventsMatchingMask. This doesn't provide anything more useful than keyDown.
The NSWindow I'm given has a menu with some key equivalents for cut/copy/paste. I occasionally need to intercept these if the user has something selected in my NSView. I also occasionally need to intercept carriage return if the user is entering some data.
My UI is rendered using OpenGL so I'm not using standard Cocoa UI components apart from NSView to host my OpenGL surface.
I don't want the user to have to enable anything special to do this, like accessibility.
In my keyDown handler I have something like this:
- (void)keyDown:(NSEvent*)event
{
NSString* s = event.charactersIgnoringModifiers;
unichar modified_key = 0;
if (s && [s length] > 0)
{
modified_key = [s characterAtIndex:0];
}
if (modified_key == NSCarriageReturnCharacter)
{
// carriage return
}
}
This works in a stand alone application, but fails when it's hosted as an audio plugin. The problem I think is that the application hosting the plugin is intercepting events before they reach my event handlers.
You seem to be handling the event in a strange way. Process the NSEvent keyCode directly. You can use modifierFlags to get the modifiers.
This won't work for certain "system" key combinations (like Command + Space) for which you will need accessibility access.
- (void)keyDown:(NSEvent *)event
{
if (event.keyCode == 36)
NSLog(#"you pressed return!");
if (event.modifierFlags & NSEventModifierFlagCommand)
{
if (event.keyCode == 8)
NSLog(#"you pressed command+c!");
}
}

OS X count pressed keys without accessibility access

I found that upwork.app can count pressed keys without accessibility access but I can not understand how it is done.
I read a lot of themes like this one:
OSX: Detect system-wide keyDown events?
In all themes are saying that process should be trusted "Enable access for assistive devices" and I can not find how upwork.app can track keys without this.
This is official documentation for tracking events:
https://developer.apple.com/reference/appkit/nsevent/1535472-addglobalmonitorforeventsmatchin
Key-related events may only be monitored if accessibility is enabled or if your application is trusted for accessibility access (see AXIsProcessTrusted).
In apple mail list is saying same:
http://lists.apple.com/archives/carbon-dev/2010/Feb/msg00043.html
I think that upwork.app use some hacks.
How I can count pressed keys without accessibility access?
Still haven't received your answer in the comments, but since that might also help other people in the future I decided to answer anyway.
With the IOKit you can detect the keyboard has a device, and get the key press events like device events. I've used that to detect joystick events, but it should work with keyboards has well. I assume that the modifications that I made are enough and should work, however my Xcode is updating now, so I wasn't able to test it yet.
KeyboardWatcher.h File:
#import <Foundation/Foundation.h>
#import <IOKit/hid/IOHIDManager.h>
#import <IOKit/hid/IOHIDKeys.h>
#interface KeyboardWatcher : NSObject{
IOHIDManagerRef HIDManager;
}
#property (nonatomic) int keysPressedCount;
+(instancetype)sharedWatcher;
-(void)startWatching;
-(void)stopWatching;
#end
KeyboardWatcher.m File:
#import "KeyboardWatcher.h"
#implementation KeyboardWatcher
static KeyboardWatcher *_sharedWatcher;
+(instancetype)sharedWatcher {
#synchronized([self class]) {
if (!_sharedWatcher){
_sharedWatcher = [[KeyboardWatcher alloc] init];
}
return _sharedWatcher;
}
return nil;
}
-(instancetype)init {
self = [super init];
if (self){
self.keysPressedCount = 0;
}
return self;
}
-(void)startWatching {
[self watchDevicesOfType:kHIDUsage_GD_Keyboard];
}
-(void)watchDevicesOfType:(UInt32)deviceType {
// Create an HID Manager
HIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
// Create a Matching Dictionary
CFMutableDictionaryRef matchDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
// That will make the app just return the computer keyboards
CFDictionarySetValue(matchDict, CFSTR(kIOHIDPrimaryUsageKey), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &deviceType));
// Register the Matching Dictionary to the HID Manager
IOHIDManagerSetDeviceMatching(HIDManager, matchDict);
// Register the HID Manager on our app’s run loop
IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
// Open the HID Manager
IOReturn IOReturn = IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeNone);
// Register input calls to Handle_DeviceEventCallback function
IOHIDManagerRegisterInputValueCallback(HIDManager, Handle_DeviceEventCallback, nil);
if (IOReturn) NSLog(#"IOHIDManagerOpen failed.");
}
-(void)stopWatching {
HIDManager = NULL;
}
static void Handle_DeviceEventCallback (void *inContext, IOReturn inResult, void *inSender, IOHIDValueRef value){
IOHIDElementRef element = IOHIDValueGetElement(value); // Keyboard pressed key
uint32_t uniqueIdentifier = IOHIDElementGetCookie(element); // Unique ID of key
int elementValue = (int)IOHIDValueGetIntegerValue(value); // Actual state of key (1=pressed)
NSLog(#"Unique ID = %u; Value = %d", uniqueIdentifier, elementValue);
if (elementValue == 1) KeyboardWatcher.sharedWatcher.keysPressedCount++;
}
#end
In case you want to identify which unique ID is which key, you can use these enums (instead of importing Carbon you can just create a CGKeyboardMapping.h file and paste them there):
https://stackoverflow.com/a/16125341/4370893
At last, in order to use it, you just need to do that to start watching for keyboard events:
[[KeyboardWatcher sharedWatcher] startWatching];
Get the key pressed count with that:
[[KeyboardWatcher sharedWatcher] keysPressedCount];
And that to stop:
[[KeyboardWatcher sharedWatcher] stopWatching];
These were my references to write my original joystick code:
http://ontrak.net/xcode.htm
http://ontrak.net/xcode2.htm
https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/HID/new_api_10_5/tn2187.html
http://www.opensource.apple.com/source/IOHIDFamily/IOHIDFamily-315.7.16/IOHIDFamily/IOHIDUsageTables.h
As soon as the update finishes I will test the code and inform if it's working or not for sure.
EDIT: Just tested and it's working. Don't forget to add the IOKit framework to the project.

keyDown press failure

I want to put together a ping pong game in Xcode with Sprite Kit. But I want to do it for Mac OS X, there are no tutorials on fixing my code, and I want to make a keydown event to move the paddle. Here's my code.
-(void)keyDown:(NSEvent *)theEvent {
/* Called when a keyPress occurs */
// inside code
}
EDIT:
So I downloaded a pong game that is an Xcode project then I looked at it and saw this:
- (void)handleKeyEvent:(NSEvent*)keyEvent keyDown:(BOOL)isKeyDown {
if ([keyEvent keyCode] == LED_PONG_MOVE_UP || [keyEvent keyCode] == LED_PONG_MOVE_UP_ALT) {
self.moveUp = isKeyDown;
} else if ([keyEvent keyCode] == LED_PONG_MOVE_DOWN || [keyEvent keyCode] == LED_PONG_MOVE_DOWN_ALT) {
self.moveDown = isKeyDown;
}
}
and this:
#define LED_PONG_MOVE_UP 13 // W
#define LED_PONG_MOVE_UP_ALT 126 // Arrow Up
#define LED_PONG_MOVE_DOWN 1 // S
#define LED_PONG_MOVE_DOWN_ALT 125 // Arrow Down
So that about solves it for anyone in my place, fellow iOS haters, and Mac OS X lovers.
Notify me for more info if you need help.
It sounds like you simply need to make certain that your SKScene is designated as the first responder, which means that all events come in through your SKScene first.
So when your game (or scene) starts up, make certain that your NSWindow calls "makeFirstResponder:" with your SKScene object as the parameter.

Command-Key-Up Cocoa

I'm trying to mimic the functionality of the cmd-tab keyboard shortcut where the user can switch between applications hitting a certain key and then when they release command something happens.
I'm using this code right now but it can only detects keydown. I need this to fire on key up
- (void)flagsChanged:(NSEvent *)theEvent {
if ([theEvent modifierFlags] & NSCommandKeyMask) {
NSLog(#"Do my stuff here");
}
}
Thanks
According to the docs:
Informs the receiver that the user has pressed or released a modifier
key (Shift, Control, and so on).
What you need to do here is when you get the event in which the command key goes down, you need to set a flag somewhere, and in subsequent calls, check for the absence of the command key being down.
For instance, assuming you have an ivar called _cmdKeyDown:
- (void)flagsChanged:(NSEvent *)theEvent
{
[super flagsChanged:theEvent];
NSUInteger f = [theEvent modifierFlags];
BOOL isDown = !!(f & NSCommandKeyMask);
if (isDown != _cmdKeyDown)
{
NSLog(#"State changed. Cmd Key is: %#", isDown ? #"Down" : #"Up");
_cmdKeyDown = isDown;
}
}

CGEvent NSKeyDown only working if app is front most?

I am trying to automate some tasks (there's no applescript support) so I have to use CGEvents and post these events. Mouse clicking works fine, but NSKeyDown (enter) only works if I click on the app in the dock(which makes it front most app)...
Here's my code so far:
for (NSDictionary *dict in windowList) {
NSLog(#"%#", dict);
if ([[dict objectForKey:#"kCGWindowName"] isEqualToString:#"Some Window..."]) {
WIDK = [[dict objectForKey:#"kCGWindowNumber"] intValue];
break;
};
}
CGEventRef CGEvent;
NSEvent *customEvent;
customEvent = [NSEvent keyEventWithType:NSKeyDown
location:NSZeroPoint
modifierFlags:0
timestamp:1
windowNumber:WIDK
context:nil
characters:nil
charactersIgnoringModifiers:nil
isARepeat:NO
keyCode:36];
CGEvent = [customEvent CGEvent];
for (int i=0; i <5; i++) {
sleep(3);
CGEventPostToPSN(&psn, CGEvent);
NSLog(#"posted the event");
}
CFRelease(eOne);
The reason why I have posteventtopsn in a loop is for testing purposes (I just need it to send it once). While the program is in the loop, if I activate my app to front most, then the event works fine.
What am I doing wrong? Is there a way it can work if it in background? Thanks.
Here is a better way to post keyboard events to the front-most application:
CGEventRef a = CGEventCreateKeyboardEvent(NULL, 124, true);
CGEventRef b = CGEventCreateKeyboardEvent(NULL, 124, false);
CGEventPost(kCGHIDEventTap, a);
CGEventPost(kCGHIDEventTap, b);
CGEventPost lets you determine where the event is posted. I used this code recently to allow someone to remotely control a PPT presentation on my laptop. (Character 124 is the right arrow key.)
Note that you should be freeing the event after posting it.
You can send to a specific app (eg not the front app) by using CGEventPostToPSN.
i think it's because you create a NSEvent using "WIDK" for parameter windowNumber , and WIDK is related to your app only.