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;
}
}
Related
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.
I'm very new to Xcode and Objective C. So most probably this is going to be an easy question (:
I'm trying to create an app that would do something if Caps Lock key is pressed.
And I need to see if Caps lock is pressed even if app is not in focus.
I've managed to check Caps Lock state even if my app is not in focus.
But I have an issue with getting out from while loop.
I cannot understand why my app cannot see what is happening to Caps lock key once it gets inside while loop.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_spam1 = NO;
[NSEvent addGlobalMonitorForEventsMatchingMask:NSFlagsChangedMask handler:^(NSEvent* event){
[NSThread sleepForTimeInterval:1];
if(event.keyCode == 0x39){ //CAPS LOCK key was pressed
if([event modifierFlags] & NSAlphaShiftKeyMask){
_spam1 = YES;
NSLog(#"caps lock is on");
} else {
_spam1 = NO;
NSLog(#"caps lock is off”);
}
//if I comment this part
//I can see if caps lock is on or off just fine
while(_spam1){
NSLog(#"Spam %#" , event);
NSLog(#"Spam %lu" , [event modifierFlags] & NSAlphaShiftKeyMask);
[NSThread sleepForTimeInterval:1];
if([event modifierFlags] & NSAlphaShiftKeyMask){
//_spam1 = YES;
} else {
_spam1 = NO;
NSLog(#“stop while loop”);
break;
}
}
}
}];
}
Work with framework, not agains:
Create a new subclass of NSView and override one of following methods. Set the new subclassed view in Interface builder. ( read more in Event handling guide )
So your code should look like this:
//multiKey handling
unichar SPACE_CHARACTER = 0x0020;
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
{
id responder = [[self window] firstResponder];
if (responder != self)
{
return [super performKeyEquivalent:theEvent];
}
NSUInteger numberOfPressedCharacters = [[theEvent charactersIgnoringModifiers] length];
NSEventType eventType = [theEvent type];
if (eventType == NSKeyDown && numberOfPressedCharacters == 1)
{
unichar key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];
if (key == SPACE_CHARACTER)
{
[self spaceBarPressed];
return YES;
}
}
return NO;
}
or
//single key handling (no modifiers like shift, ctrl...)
- (void)keyDown:(NSEvent *)theEvent
{
// code belongs here
}
Maybe I'm missing something, but why do you have the while loop at all, as your code comment says it works without it?
In outline what is happening is as follows:
Your call to addGlobalMonitorForEventsMatchingMask requests the system to call the block you pass every time an event occurs (that matches). This is done asynchronously, and for this to happen you must be going around your event loop.
The standard application model is event driven, the system sets up the main event loop and calls your code to handle the various events - including any required calls to your event monitoring block. After your code handles the event it returns, the event loop goes around and obtains the next event, calls your code to handle it, etc., etc.
When your block is called it is passed a reference to the event it needs to process. In a single call that event will not be changed asynchronously by the system - the system obtains the event as part of the event loop processing and calls your block, until that call returns the event loop cannot continue.
So after all that preamble what happens with your while loop is the call to sleepForTimeInterval causes your application to pause within the call to the block - the block does not return and so its caller, the event loop, does not continue either. On waking up your loop continues to process exactly the same event it was passed when called, your block then continues around its loop getting nowhere...
HTH
I'm working on an OpenGL game in Cocoa, and need to capture when the user attempts to copy or paste (via command+c, or command+v).
So far, I have an NSView<NSTextInputClient> as the first-respondant of my NSWindow. It is successful in allowing non-ASCII characters to be typed into my game (中文維基百科, for example) but I'm lost trying to capture Copy&Paste.
I think I might be able to get a working solution using flagsChanged combined with keyDown, but this feels like a hack, and I'm sure someone out there knows a better solution. :)
Edit: For clarity, I'm looking mainly for the existence of a predefined Copy event or keycode. The rationale being that if I manually define Copy as "Command+C" then that might break if a user has remapped his/her keys or is using some accessibility tool.
You can use the NSEvent class method: + (id)addGlobalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(void (^)(NSEvent*))block
or you can implement - (void)keyDown:(NSEvent *)theEvent
- (void)keyDown:(NSEvent *)theEvent {
if ([theEvent modifierFlags] & NSCommandKeyMask) {
NSString *character = [theEvent charactersIgnoringModifiers];
if ([character isEqualToString:#"c"]) {
NSLog(#"Capture Copy&Paste Key");
}
}
[super keyDown:theEvent];
}
Cocoa noob here. I'm wondering how I can capture the Enter and tab keys onKeyDown whilst the user is typing in an NSTextView?
Thanks!
The easiest way is to implement the - (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector delegate method and look for the insertNewline: and insertTab: selectors.
- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector
{
if (aSelector == #selector(insertNewline:)) {
// Handle the Enter key
return YES;
} else if (aSelector == #selector(insertTab:)) {
// Handle the Tab key
return YES;
}
return NO;
}
You should handle keyDown:(NSEvent*)theEvent message of NSTextView (i.e. write your own descendant).
In this event you will have key code in [theEvent keyCode].
For return there is a constant kVK_Return, for tab - kVK_Tab, etc.
You should add Carbon framework (and #import Carbon/Carbon.h) to access these constants.
I just experimented with the addLocalMonitorForEventsMatchingMask:handler: method in NSEvent and came across the following question: How do I find out if only certain modifiers were pressed?
A short example to set this question into context: I wanted to listen for the shortcut "⌘+W". Therefore I wrote the following code:
[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *theEvent) {
if ([theEvent modifierFlags] & NSCommandKeyMask && [theEvent keyCode] == 13) {
[self.window performClose:self];
}
return theEvent;
}];
This works well, however the shortcut will be triggered, even if more modifier keys are pressed, e.g. "⌃+⌘+W" or "⇧+⌃+⌥+⌘+W". Is there a way to circumvent this?
A simple solution would be to check for all other modifier keys and ensure they are not pressed. This seems tedious and error prone - besides it's ugly enough as it is now with the unary "&". In addition you may get into trouble if (for some reason) another modifier key is added to keyboard layouts.
As always I'm thankful for any recommendations.
I think this'll do it:
// Mask out everything but the key flags
NSUInteger flags = [theEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
if( flags == NSCommandKeyMask ){
// Got it!
}
Hat tip to SpaceDog for pointing out the deprecation of the original mask name, NSDeviceIndependentModifierFlagsMask.
Swift 5 version
if event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .command {
// Got it!
}
#JoshCaswell answer has been outdated thanks to Apple, because NSDeviceIndependentModifierFlagsMask has been deprecated since 10.12.
His answer updated to the new syntax is
// Mask out everything but the key flags
NSUInteger flags = [theEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
if( flags == NSCommandKeyMask ){
// Got it!
}
NSDeviceIndependentModifierFlagsMask has been replaced with NSEventModifierFlagDeviceIndependentFlagsMask because it makes a world of difference...
Maybe even better...
if event.modifierFlags.contains(.shift){
// do it
}