need help implementing Global Keyboard Shortcuts

I’m trying to set up a couple Global Keyboard Shortcuts for my application so I can activate a quick entry mode from any application. It looks like the Snow Leopard answer is

The documentation is here:

http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/Reference/Reference.html#//apple_ref/occ/clm/NSEvent/addGlobalMonitorForEventsMatchingMask:handler:

There’s even a short tutorial here:
http://cocoakids.net/14-global-hotkeys-in-cocoa-on-snow-leopard

My problem is I go cross-eyed trying to convert Obj-C to ASOC, not to mention how to go about using it once converted. Looks like it could just be a few lines in my applicationDidFinishLaunching_(Notification) handler.

Anyone who can help me figure this out will win my praise and gratitude (not to mention I would think I’m not alone in wanting to do this and it would be a useful but of knowledge to have here)

An alternative would be to make my application applescriptable. But that looks more complicated.

That method uses blocks, which can’t be implemented in ASObjC – even if it could, you may well run into problems given the amount of processing it would have to do. Your only hope is to use a separate Objective-C class.

How do I do that?

You’ll need to know some Objective-C. Basically, you’d add an Objective-C class that does something like the stuff in the tutorial you pointed to, and call it from your AS.

Are you just wanting the hot key to toggle the active state of your application?

No, I know I can just use applescript and/or a service for that. There’s different functions, in a statusbar menu, that I want to activate with the global shortcuts.

If its easier to make my application applescriptable than I could do that too.

The reason I asked that question is that I have class files that set up the toggle action. You would need to know some Objective-C to manipulate them to your needs though.

Here is the site that I gathered the information from. It uses Carbon but that code requires very little change to work in your app.

http://dbachrach.com/blog/2005/11/program-global-hotkeys-in-cocoa-easily/

Found an example on Google code called CocoaHotKeys. The NSNotifications are in AppleScriptObjC. Changed the CocoaHotKey handler to register the ASCII keycodes in ASOC. The Modifier keys are still in Objective-C; this might be inconvenient for some, but shouldn’t be hard to change. Feel free to make and post your own enhancements.

ASOCHotKey 1.0 example adapted by Todd Bruss
Requires Carbon Framework
created with Xcode 4.1
uses code from CocoaHotKeys by Matthias Plappert
Download: http://starplayr.com/downloads/ASOCHotKey.zip
Provided as is without warranty.

PWHotKey’s Carbon code is original and remains unchanged; there are a couple 64 to 32-bit integer conversion alerts. Tested in Lion, 64-bit. Should work in Snow Leopard, uses 10.6 SDK. See notes on ASOCHotKeyAppDelegate.applescript. Goal: help ASOC users add Global HotKeys to their AppleScriptObjC Projects with minimal effort.

I’m not sure why that app you posted is so long and involved, a simple subclass or category on NSEvent would do the job as Shane mentioned earlier in this topic.

I did it this way by creating a category on NSEvent that allows me to use global monitors in ASOC. The category’s .m file is :

#import "NSEvent+HotKeys.h"

@implementation NSEvent (HotKeys)

+(id) globalMonitorForEventsMatchingMask:(NSEventMask) mask withSelector: (SEL) aSelector target: (id) target {
    id theMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mask handler:^(NSEvent *event) {
            [target performSelector: aSelector withObject: event];
    }]; 
    return theMonitor;
}
@end

I can then call it in ASOC like any other NSEvent class method. Here is an example app that responds to option-control-/ when pressed in another app:

script HotKeysAppDelegate
	property parent : class "NSObject"
	property keyMontitor : missing value
	
	on applicationWillFinishLaunching_(aNotification)
		set keyMontitor to current application's NSEvent's globalMonitorForEventsMatchingMask_withSelector_target_(current application's NSKeyDownMask, "check:", me)
	end applicationWillFinishLaunching_
	
	on check_(theEvent)
		if theEvent's type() is current application's NSKeyDown then
			-- keyCode 44 is "/" and modifierFlag 786432 is Control+Option
			if (theEvent's keyCode() as integer is 44) and (current application's NSEvent's modifierFlags() as integer is 786432) then
				activate
				log "key pressed"
				--current application's NSEvent's removeMonitor_(keyMontitor)
			end if
		else
			log "Not a key press"
		end if
	end check_
	
end script

Ric

You’re example is shorter, but it doesn’t look like the hot keys are registered and ASOC is stuck having to check every keydown event for a match. That could be more CPU intensive. My adaptation from CocoaHotKeys only triggers the ASOC code after the registered hot key has been pressed. It’s less work for ASOC to handle.

Thank you for your example. One nice thing about NSEvents however is I think they will work gestures.

Yeah, it’s true that ASOC would have more to handle with my method, but the checking of the keyDown could easily be moved to the objective C method if performance were an issue. I don’t know then how the two approaches would differ in their cpu usage since I don’t know anything about those Carbon methods. I also don’t know whether Carbon will continue to be a viable technology — I thought I read something lately indicating that it might be phased out (or no longer supported), but I can’t find where I saw that.

This is probably a better example that behaves more like your Carbon method and moves the checking of the key presses to the block in the objective C method. Like your method, it uses notifications, so any class in the project could process the hot keys. It registers an array of hot keys, and the userInfo of the notification that is sent contains the name of the hot key.

#import "NSEvent+HotKeys.h"

@implementation NSEvent (HotKeys)

+(void)registerHotKeys:(NSArray *) hotKeys notificationName:(NSString *) name {
    id center = [NSNotificationCenter defaultCenter];
    [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) {
        for (id aKey in hotKeys){
           NSUInteger theKey = [[aKey valueForKey:@"keyCode"] integerValue];
           NSUInteger modifier = [[aKey valueForKey:@"modifier"] integerValue];
            if ([event keyCode] == theKey && [NSEvent modifierFlags] == modifier) {
                [center postNotificationName:name object:self userInfo:[aKey valueForKey:@"keyName"]];
            } 
        }
    }]; 
}
@end

And it is used like this:

script HotKeysAppDelegate
	property parent : class "NSObject"
	
	on applicationWillFinishLaunching_(aNotification)
		current application's NSEvent's registerHotKeys_notificationName_({{keyName:"hotKey1", keyCode:44, modifier:786432}, {keyName:"hotKey2", keyCode:47, modifier:786432}}, "RDKeyDownNotification")
		set Noter to current application's NSNotificationCenter's defaultCenter()
		Noter's addObserver_selector_name_object_(me, "hotKeyDetected:", "RDKeyDownNotification", missing value)
	end applicationWillFinishLaunching_
	
	on hotKeyDetected_(aNotification)
		log aNotification's userInfo()
	end hotKeyDetected_
	
end script

Ric

Ric,

I was thinking Apple could obsolete Carbon. I read somewhere that the non-UI portions are 64-bit which should include the Carbon Event Manager. It is definitely possible for that to get axed down the road.

You’re second Hot Key example looks great. I will give it a shot. NSNotifications are wonderful. :slight_smile:

Appreciate it!

Todd

Relax – the non-UI Carbon stuff is safe. Especially those parts where there is no Cocoa equivalent.

Hello,

I have made separate posts relating to this post, but since none of them helped me, I decided to post directed to this post. The problem I am having is when I build this application, I get the following error:

Undefined symbols for architecture x86_64:
“_main”, referenced from:
start in crt1.10.6.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

StefanK told me this meant that a framework was missing. Could someone post the steps? I have an ObjC category for NSEvent and the .m part is the right script. I am also calling it from AppDelegate using the script above. What did I miss?

Have you added the Carbon framework to the project?

What application?

Ric

I added the Carbon framework and it still didn’t work…

Sorry rdelmar, I meant my application, the one I’m building :P.

“didn’t work” isn’t much use to anyone. Did you get the same error message, a new one, or what?

If you’re using the code I posted above, then you don’t need the carbon framework. You should post the code so we can see what you are doing.

Ric

Here is my AppDelegate. The purpose of my app is to “lock” the screen.

script AppDelegate
	property parent : class "NSObject"
    
    on lockScreen() 
    do shell script "'/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession' -suspend"
    end lockScreen    
    
    on hotKeyDetected_(aNotification)
        lockScreen()
    end hotKeyDetected_
	
	on applicationWillFinishLaunching_(aNotification)
        current application's NSEvent's registerHotKeys_notificationName_({{keyName:"hotKey1", keyCode:44, modifier:786432}, {keyName:"hotKey2", keyCode:47, modifier:786432}}, "RDKeyDownNotification")
        set Noter to current application's NSNotificationCenter's defaultCenter()
        Noter's addObserver_selector_name_object_(me, "hotKeyDetected:", "RDKeyDownNotification", missing value)
	end applicationWillFinishLaunching_
	
	on applicationShouldTerminate_(sender)
		-- Insert code here to do any housekeeping before your application quits 
		return current application's NSTerminateNow
	end applicationShouldTerminate_
	
end script

Here is the .m file of my ObjC category

#import "NSEvent+HotKeys.h"

@implementation NSEvent (HotKeys)

+(void)registerHotKeys:(NSArray *) hotKeys notificationName:(NSString *) name {
    id center = [NSNotificationCenter defaultCenter];
    [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) {
        for (id aKey in hotKeys){
            NSUInteger theKey = [[aKey valueForKey:@"keyCode"] integerValue];
            NSUInteger modifier = [[aKey valueForKey:@"modifier"] integerValue];
            if ([event keyCode] == theKey && [NSEvent modifierFlags] == modifier) {
                [center postNotificationName:name object:self userInfo:[aKey valueForKey:@"keyName"]];
            } 
        }
    }]; 
}
@end