I have a status bar app. I'm using this code to capture very user's key press in the system:
_keybordEventMonitor =
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSKeyDownMask) handler:^(NSEvent *incomingEvent)
{
[self inputKeyboardEventHandler: incomingEvent];
}];
It captures everything fine, but doesn't capture global system hotkeys like cmd + space or cmd+shift+3.
Accessibility APIs are enabled. Any ideas?
PS: I tried using CGEventTap and it's kind of works, but had it's own problems and since I'm a cocoa noob, I prefer to keep things simple for now.
Related
I need to programmatically disable/suppress system-wide touch gestures on Mac OS. I'm referring to gestures such as the 4-finger swipe between spaces, etc.
I've looked to EventTap but that doesn't appear to be an option (despite previous reports here - perhaps it's changed under 10.8)
I've also tried numerous ways of changing the the system preferences programatically. For example, I've tried using IOConnectSetCFProperties on the service having located it using IORegistryEntryCreateCFProperties.
I've also delved into the trackpad preference pane to see how they do it, and I tried to reproduce it (ignore any create/release inconsistencies, this is just test code):
NSInteger zero = 0;
CFNumberRef numberWith0 = CFNumberCreate(kCFAllocatorDefault, kCFNumberNSIntegerType, &zero);
CFMutableDictionaryRef propertyDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(propertyDict, #"TrackpadFourFingerHorizSwipeGesture", numberWith0);
io_connect_t connect = getEVSHandle(); // Found in the MachineSettings framework
if (!connect)
{
NSLog(#"Unable to get EVS handle");
}
kern_return_t status = IOConnectSetCFProperties(connect, propertyDict);
if (status != KERN_SUCCESS)
{
NSLog(#"Unable to get set IO properties");
}
CFRelease(propertyDict);
CFPreferencesSetValue(CFSTR("com.apple.trackpad.fourFingerHorizSwipeGesture"), _numberWith0, kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
CFPreferencesSetValue(CFSTR("TrackpadFourFingerHorizSwipeGesture"), _numberWith0, CFSTR("com.apple.driver.AppleBluetoothMultitouch.trackpad"), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
CFPreferencesSynchronize(kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
status = BSKernelPreferenceChanged(CFSTR("com.apple.driver.AppleBluetoothMultitouch.trackpad"));
In this case it appears to work, there are no errors and the option becomes disabled in the system preference pane, however the four finger gesture continues to work. I suspect that logging out then in will have an effect, but I haven't tried because that's not good enough in any case.
It's worth noting that the Pref Pane itself also calls BSKernelPreferenceChanged, but I don't know which framework that might be in order to link to it. Perhaps that's the key to the problem...
UPDATE: Actually I've now found it and linked it to. Adding that call made no difference, although it returns 1 which may indicate an error. I've added the call to the code above.
Finally I tried this from the terminal:
defaults write -globalDomain com.apple.trackpad.fourFingerHorizSwipeGesture 0
defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad TrackpadFourFingerHorizSwipeGesture 0
That doesn't have an immediate effect either.
I don't believe that this isn't possible, there must be a way...
MAS compatibility is not required.
I'm also trying to do this.
Event taps does not work, neither does having a view that is first responder.
From Apple docs:
However, there are certain system-wide gestures, such as a four-finger swipe. for which the system implementation takes precedence over any gesture handling an application performs.
The only way i've been able to stop the system wide gestures is using CGDisplayCapture. This gives my application exclusive access to all events... but also a fullscreen drawing context.
Perhaps it's possible to see what calls are made to the quartz event services when entering this mode
https://developer.apple.com/library/mac/#documentation/graphicsimaging/Conceptual/QuartzDisplayServicesConceptual/Articles/DisplayCapture.html
I think you are looking in the wrong spot for disabling the touch events. The way OSX (and many other systems) is that the first responder in the view chain to handle the event will stop the event from propagating. You will need to write event handlers in your views for each of the touch events you want to handle, and if they exist, the OS will stop sending the events all the way to finder or whatever other application is next in line to handle the touch events.
See: http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/EventOverview/HandlingTouchEvents/HandlingTouchEvents.html
Specifically: Handling Multi-Touch Events (call setAcceptsTouchEvents, then implement touches...WithEvent...)
Hope that helps!
I need to record several thousand short soundbites of my own speech (I'm training an acoustic model for a speech recognition engine)
So for each one, a line of text presents itself on the screen and I have to speak it, and capture the audio into a .WAV
I found a sample project for recording the audio; now I am trying to figure out how to do keyboard input.
I would like to push the SPACEBAR down to start recording and release it to terminate the recording.
Can anyone get me started? ( an example would be ideal! )
Sorry, this is probably really really basic -- I haven't done any coding in OS X before (though I have done a lot of iOS work so I am no stranger to Xcode and some of the frameworks)
If you create a basic Cocoa application, you can use the following methods of NSResponder, of which NSView is a subclass, to capture your desired key up/down events:
-(void)keyDown:(NSEvent*)event;
-(void)keyUp:(NSEvent*)event;
Use [event keyCode] to get the key pressed.
is there a way to add an application to all spaces programmatically? I'd like my application to be on all spaces by default.
The methods you need are in NSWindow.
For Lion use:
- (void)setCollectionBehavior:(NSWindowCollectionBehavior)behavior
For pre-Lion override the following to return YES:
- (BOOL)canBeVisibleOnAllSpaces
This piece of code works for me (at least on 10.6.8 in a little project I recently worked on):
-(void)windowDidLoad {
// Make the window visible on all Spaces
if([[self window] respondsToSelector: #selector(setCollectionBehavior:)]) {
[[self window] setCollectionBehavior: NSWindowCollectionBehaviorCanJoinAllSpaces];
}
else if([[self window] respondsToSelector: #selector(canBeVisibleOnAllSpaces)]) {
[[self window] canBeVisibleOnAllSpaces]; // AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER_BUT_DEPRECATED
}
}
I put this code in a (custom subclass of a) WindowController for the main app window.
Ok. Just setting the workspaces-app-bindings programmatically didn't work. I tried:
1) Verified no entries were in System Preferences->Spaces
2) defaults write com.apple.dock workspaces-app-bindings -dict-add com.apple.mail 65544
3) killall Dock (also needed to kill System Preferences )
4) Opened System Preferences->Spaces to verify the Mail app entry
appeared and was set to Every Space
5) Launched Mail, but it was still stuck to Space 1
6) Only when I went back into System Preferences->Spaces and changed the
Mail app *from* Every Space and then *back* to Every Space did the Mail
app stick to every space
So clearly system preferences is doing something extra to activate the setting. Does anyone know what this could be? Thanks!
Update: So I was able to get this working by using the applescript api instead of the user defaults api. The following post tells how to append an entry using applescript. Then just kill the dock.
Applescript; opening an app in Space number N
Use the defaults-command that ships with OS X, like so:
defaults write com.apple.dock workspaces-app-bindings -dict-add com.apple.mail 65544
By issuing the above command, you set the application identified by “com.apple.mail” to appear on every space. 65544 is a magic value saying “every space”. If the key-value pair (identifier + settings) exists it will be overwritten.
Note that you have to reload the Dock (killall Dock) and somehow execute these commands from within your application. From within objective-c you can use the following snippet to quit the Dock:
NSRunningApplication *dock = [NSRunningApplicationrunningApplicationWithBundleIdentifier:#"com.apple.dock"];
[dock terminate];
From within AppleScript use the following:
quit application "Dock"
Your app delegate should look like this...
#import "alwaysOnTopAppDelegate.h"
#implementation alwaysOnTopAppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
}
#end
In a Cocoa App, I'm trying to find a way to catch system events like the app switcher usually launched with Cmd-Tab or spotlight, usually launched by Cmd-Space. I'm looking for either a way to catch the key event or any another way that would tell me that one of those event is about to happen, and ideally cancel it.
Apple Screen Sharing remote desktop app does it, so it should be possible. It catches those events and send them to the connected remote computer.
Here is what I already tried :
Catching events with the sendEvent method in NSApplication. I see all events like the Cmd keydown, the Tab keydown, but when both are pressed, I see nothing.
Registering a Carbon Hot key listener. I can register anything like Cmd+Q, but again, when I register Cmd+Tab, it does not respond.
Any other ideas ?
See Event Taps.
Found it!
In my WindowViewController.m file
#import <Carbon/Carbon.h>
void *oldHotKeyMode;
- (void)windowDidBecomeKey:(NSNotification *)notification{
oldHotKeyMode = PushSymbolicHotKeyMode(kHIHotKeyModeAllDisabled);
}
- (void)windowDidResignKey:(NSNotification *)notification{
PopSymbolicHotKeyMode(oldHotKeyMode);
}
This is pretty magic ! and it passes the new Apple sandboxing requirement for the Mac App Store !
I will describe you how to catch cmd+tab. But please note that will work only in fullscreen mode. I beleive there is no way to this in windowed mode. Code is pretty simple. It is a minor fix of SDL mac code - update for handling cmd+tab in fullscreen mode.
NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES ];
if ( event == nil ) {
break;
}
if (([event type] == NSKeyDown) &&
([event modifierFlags] & NSCommandKeyMask)
&&([[event characters] characterAtIndex:0] == '\t')
{
do something here
}
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;
}