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!
Related
Is there a way to programatically (e.g. from Python code) to prevent my Mac from dimming and subsequently locking the screen? Of course, after my application is done, I would like to enable normal operation again. I know about caffiniate, but that applies to the whole application...that is not what I want. At some point in my code I want to disable dimming and then at some other point I want to enable it again.. Any tips, hints, suggestions?
You can try IOKit. Use IOPMAssertionDeclareUserActivity will wake the screen if necessary and keep it awake until the user's display sleep Energy Saver settings:
IOReturn ret;
CGError err;
IOPMAssertionID assertId;
ret = IOPMAssertionDeclareUserActivity(CFSTR("Stay awake!"), kIOPMUserActiveLocal, &assertId);
if (ret == kIOReturnSuccess)
{
// The screen is on
}
However, from the documentation for that method:
"If you prefer to hold the display awake for a longer period and you know how long you'd like to hold it, consider taking assertion kIOPMAssertionTypePreventUserIdleDisplaySleep using IOPMAssertionCreateWithDescription API instead."
Sounds closer to what you want. But I haven't tried it so I don't have a sample.
As an experiment, I am trying to achieve the following:
Let spacebar work as a modifier key - like the Shift key - where holding the spacebar key down and typing keys print different letters. Releasing the spacebar would set the state back to normal, and just pressing it behaves like a normal space key.
I was thinking of handling the keydown and keyup event, but apparently handleEvent:client: in IMKServerInput Protocol seems to only catch key down and mouse events.
Without much experience with cocoa, I’ve tried some methods with no success:
went through the Technical Note 2128 via internet archive, which gave me the suitable explanations of plist items. Still, nothing about keyup.
tried adding NSKeyUpMask to recognizedEvents: in IMKStateSetting Protocol, but that didn’t seem to catch the event either.
tested a bit with addLocalMonitorForEventsMatchingMask:handler: but nothing happens.
failed to find a way to make NSFlagsChanged event fire with spacebar.
read about Quartz Event Service and CGEventTap which seems to handle user inputs in lower level. Didn’t go further to this route, yet.
IOHIDManager?
I reached to a conclusion that IMKit is only capable of passively receiving events.
Since it is not an application, there is no keyUp: method to override - AFAIK, IMKit does not inherit NSResponder class.
Unfortunately cocoa is way too broad and has much less (or overflowed with non-helping) documentations for a novice like me to dive in.
Can anyone help me to the right direction?
I tried all possible alternatives one by one, and eventually achieved it by creating a global EventTap
with CGEventTap.
The code basically looks like this:
// Create an event tap.
CGEventMask eventMask = ((1 << kCGEventKeyDown) | (1 << kCGEventKeyUp));
CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
0,
eventMask,
myCGEventCallback,
NULL);
if (!eventTap) {
NSLog(#"failed to create event tap\n");
return NO;
} else {
// Create a run loop source.
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
// Add to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
// Enable the event tap.
CGEventTapEnable(eventTap, true);
return YES;
}
where myCGEventCallback handles the global states.
Meanwhile here are some of what I've found out:
According to The Key-Input Message Sequence document, the application only passes the keydown event to the Input Method Kit, after trying other bunch of handlers in the chain. You cannot let IMKServerInput 'catch' the NSKeyUp event. Just adding an NSKeyUpMask to recognizedEvents: would not work.
addLocalMonitorForEventsMatchingMask:handler: and CGEventTapCreateForPSN would not catch the event. I suppose this is because though an Input Method may run as a separate process, the event itself is fired from the application, like TextEdit, and handed over to the Input `Method.
IOHIDManager: is for adding new hardware devices and making drivers.
Creating a global EventTap requires either running the process with sudo privilege -- copying the Input Method to /Library/Input Methods does not run with sudo privilege --, or registering the application to the Accessibility control. That's in System Preferences → Security & Privacy → Privacy tab → Accessibility, in Mavericks.
IMKStateSetting protocol's recognizedEvents: method should enable NSKeyUp events:
- (NSUInteger)recognizedEvents:(id)sender {
return NSKeyDownMask | NSKeyUpMask;
}
A client calls this method to check whether an input method supports an event. The default implementation returns NSKeyDownMask.
However, in testing my Input Method still does not catch NSKeyUp events. This seems like a bug.
I've filed the following Radar, and hope others will duplicate it:
rdar://21376535
I am working on a cross platform application that is over a decade old. The UI is by Qt and backend rendering is done with OpenGL. OpenGL contexts are managed in the back end, not by Qt.
I have recently added error checking and reporting for all OpenGL code in our app. Occasionally a situation arises where the first render initiated by Qt causes an "invalid drawable" error message in the terminal and all subsequent OpenGl calls fail with an "invalid framebuffer" error reported. These invalid drawable error messages have been treated as innocuous in the past, since before the user sees it the drawable eventually becomes valid and the scene is rendered correctly. However, with the new OpenGL error check/report it's not possible since there are large numbers of errors reported.
I would like to test if the drawable is valid. If it is not, it should return before the render starts. How can I verify that the drawable is valid?
MacBook Pro, OS X Mountain Lion (10.8.3), ati graphics card
I don't know at what API level you're working. I'm not sure it's possible to detect the problem after the fact. That is, if all you have is a context (perhaps implicit as the thread's current context) that failed to connect to its drawable.
I presume that Qt is using Cocoa under the hood. I further assume it has created an NSOpenGLContext and is invoking -setView: on it. You get that "invalid drawable" error if, at the time of that call, the view's window doesn't have a window device.
One common technique is to defer setting the context's view until the view has -drawRect: called on it, since at that point you're sure that the view has a window and the window has a device. (Although that ignores the possibility of forced drawing outside of the normal window display mechanism. For example, -cacheDisplayInRect:toBitmapImageRep:.)
If you just want to know at the point of the call to -setView: whether it's safe or not, I think you can rely on checking the value of [[view window] windowNumber]. The docs for -windowNumber say:
If the window doesn’t have a window device, the value returned will be equal to or less than 0.
Another approach is to prevent the problem rather than detect it. The strategy for that is basically to make sure the window has been shown and drawn before the call to -setView:. You may be able to force that by ordering it on-screen and invoking -display on it.
Ken Thomases post gave me the basic info I needed to find a workable solution. An additional conditional was needed. Here's what worked
//----------------------------------------------------------------------------
bool vtkCocoaRenderWindow::IsDrawable()
{
// you must initialize it first
// else it always evaluates false
this->Initialize();
// first check that window is valid
NSView *theView = (NSView*)this->GetWindowId();
bool win =[[theView window] windowNumber]>0;
// then check that the drawable is valid
NSOpenGLContext *context = (NSOpenGLContext *)this->GetContextId();
bool ok = [context view] != nil;
return win && ok;
}
In order to block ALL keyboard access, mouse access and keyboard shortcut events in one of my projects, I:
Created a full screen transparent borderless window, in front of other windows, but invisible.
Handle all keyboard and mouse events with simple return; the window itself.
Make the window modal [NSApp runModalForWindow:myWindow] in order to block keyboard shortcuts.
Release window from touchpad's gesture events only.
But this guy made it look simple in a tiny app -MACIFIER:
How did he do it?
not really sure if this would be usable, but you could use the program hotkeynet (generally used for gaming, but I have had success using other methods) and map every single key/mouse action to do nothing. I did something similar by blocking access to a specific program with it in about 20-30 minutes.
not sure if it will help; but it might be the solution you need?
I believe you can use Quartz Event Services. In particular, have a look at CGEventTapCreate, and note the 4th parameter, which allows you to specify what kinds of events you'd like to intercept. The available kinds of events are listed in the CGEventType enum.
If you set your tap to be an active filter, returning NULL from the callback will delete the event.
I'm trying to use CGAssociateMouseAndMouseCursorPosition(NO) in a program. This disconnects the mouse from the on screen cursor when your application is "in the foreground". Unfortunately it also disconnects it when Mission Control or the application switcher or who knows what else comes up.
So far I know:
The application is still active.
The window is still key.
Nothing is sent to the default notification center when these things come up.
The application stops receiving mouse moved events, but an NSEvent addGlobalMonitorForEventsMatchingMask:handler: also does not receive them, which is strange to say the least. It should receive any events not delivered to my application. (I was planning to detect the missing events to know when to associate the mouse again.
So, is there a way to detect when my application is no longer in control, specifically because Mission Control or the switch has taken over? They really expect the mouse to work and I need to restore that association for them.
I share your surprise that a global event monitor isn't seeing the events. In a similar situation, I used a Quartz Event Tap for a similar purpose. The Cocoa global event monitor is quite similar to event taps, so I figured it would work.
I put the tap on kCGAnnotatedSessionEventTap and compared the result from CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) to getpid() to determine when the events were going to another app (e.g. Mission Control or Exposé). (I disable the tab when my app resigns active status, so it should only receive events destined for another app when this sort of overlay UI is presented.)
By the way, you mentioned monitoring the default notification center, but, if there's a notification about Mission Control or the like, it's more likely to come to the distributed notification center (NSDistributedNotificationCenter). So, it's worth checking that.
I needed to check for mission control being active and ended up with an approach along the lines of Ken's answer.
Sharing is caring so here is the smallest sensible complete code that worked for me: (Swift 5)
import Foundation
import AppKit
let dockPid = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.dock").first?.processIdentifier
var eventTargetPid: Int32?
let eventTap = CGEvent.tapCreate(
tap: .cgAnnotatedSessionEventTap,
place: .headInsertEventTap,
options: .listenOnly,
eventsOfInterest: CGEventMask(
(1 << CGEventType.mouseMoved.rawValue)
| (1 << CGEventType.keyDown.rawValue)
),
callback: { (tapProxy, type, event, _:UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? in
// Now, each time the mouse moves this var will receive the event's target pid
eventTargetPid = Int32(event.getIntegerValueField(.eventTargetUnixProcessID))
return nil
},
userInfo: nil
)!
// Add the event tap to our runloop
CFRunLoopAddSource(
CFRunLoopGetCurrent(),
CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0),
.commonModes
)
let periodSeconds = 1.0
// Add a timer for periodic checking
CFRunLoopAddTimer(CFRunLoopGetCurrent(), CFRunLoopTimerCreateWithHandler(
kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + periodSeconds, periodSeconds, 0, 0,
{ timer in
guard eventTargetPid != dockPid else {
print("Dock")
return
}
print("Not dock")
// Do things. This code will not run if the dock is getting events, which seems to always be the case if mission control or command switcher are active
}), .commonModes)
CFRunLoopRun()
This simply checks whether the dock was the one to receive the last event of interest (here that includes mouse movement and key-downs).
It covers most cases, but will report the wrong value between the command switcher or mission-control hiding and the first event being sent to a non-dock app. This is fine in my use-case but could be an issue for other ones.
Also, of course, when the dock at the bottom is active, this will detect that too.
Have you tried asking NSRunningApplication?