Quartz Event Taps and Touch Bar
Quartz Event Taps and Touch Bar
- Subject: Quartz Event Taps and Touch Bar
- From: Bill Cheeseman <email@hidden>
- Date: Fri, 21 Apr 2017 09:56:11 -0400
Here's the first half of the AppDelegate.m file referred to in my previous message. The second half, the -logEvent: method, will follow in my next message.
//
// AppDelegate.m
// Touch Bar Monitor
//
// Created by Bill Cheeseman on 2017-04-15.
// Copyright © 2017 PFiddlesoft. All rights reserved.
//
#import "AppDelegate.h"
// Apple's macOS Sierra 10.12.2 AppKit Release Notes indicate that the Touch Bar API is now considered to have become available in Sierra 10.12.2, and that's what the header files say. It was actually introduced in the second release of Sierra 10.12.1, but there is no convenient way to distinguish between the two 10.12.1 releases.
@interface AppDelegate ()
@property CFRunLoopSourceRef runLoopSource;
@property CFMachPortRef eventTap;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
// Install a Quartz Event Taps event tap to monitor all input events, including system events and events addressed to the active application (even if Touch Bar Monitor itself is the active application.) Experimentation shows that passing kCGEventMaskForAllEvents causes all input events to be tapped, including all events posted by the Touch Bar -- events of type NSEventTypeDirectTouch (37). Experimentation also shows that you cannot limit tapping to Touch Bar events alone by passing NSEventMaskDirectTouch instead of kCGEventMaskForAllEvents. In the callback function myCGEventCallback(), below, logging is nevertheless restricted to NSEventTypeDirectTouch so as to report information about Touch Bar events only.
// The behavior of Quartz Event Taps with respect to Touch Bar events is very different from that of Cocoa's -addGlobalMonitorForEventsMatchingMask:handler: method, in that the latter does not monitor most Touch Bar events. This difference is apparently the result of an Apple policy to discourage developers of normal Cocoa applications from handling users' Touch Bar touches specially. Apple's macOS Sierra 10.12.2 AppKit Release Notes state that "Direct touch events are noted by the new event type, NSEventTypeDirectTouch. While there is a corresponding NSEventMaskDirectTouch, you cannot acquire direct touch events via -nextEventMatchingMask: or similar tracking methods." The Cocoa global event monitoring method is apparently one of the "-nextEventMatchingMask: or similar tracking methods" referred to in the Release Notes.
// Another difference between Quartz Event Taps and the Cocoa global event monitoring method is that the latter does monitor some Touch Bar events and reports them to be traditional Key Down-Key Up events. The Touch Bar items that it reports as traditional keyboard events are the Escape ("esc") and Function (e.g., "F1", "F2") items, which are designed to be treated exactly like the equivalent hardware keys on a traditional Mac keyboard which the Touch Bar replaces.
// The marked differences in behavior between Quartz Event Taps and the Cocoa global event monitoring method makes clear that Apple's Touch Bar documentation is targeted at developers of non-assistive applications. Apple's documentation suggests that there is no API for the Touch Bar and that developers should treat Touch Bar input the same way they treat all user input: "There is no need, and no API, for your app to know whether or not there is a Touch Bar available. Whether your app is running on a machine that supports the Touch Bar or not, your app’s onscreen user interface (UI) appears and behaves the same way." In fact, developers of assistive applications are able to monitor, modify and intercept Touch Bar events by using Quartz Event Taps.
_eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, kCGEventMaskForAllEvents, myCGEventCallback, (__bridge void * _Nullable)(self));
if (!_eventTap) {
NSLog(@"Failed to create event tap!");
return;
}
_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, _eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(_eventTap, true);
CFRunLoopRun();
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
CFRelease(_eventTap);
CFRelease(_runLoopSource);
}
-(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
return YES;
}
CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *refcon) {
// This asynchronous callback function reports information only about Touch Bar events by limiting its logging to events of type NSEventTypeDirectTouch.
// Touch Bar events provide very little information that might be used to identify the Touch Bar items that generate them. The -[NSTouch locationInView:] method is probably the most useful, because its horizontal, or x, value is the horizontal location of the touch in the Touch Bar.
// The NSEvent description by itself distinguishes between different Touch Bar events only by the location of the cursor onscreen at the time of the event and the event's timestamp. The only other traditional NSEvent information that might be used to distinguish between any two Touch Bar events is the target application's process identifier (PID). All other information about a Touch Bar event is contained in a set of NSTouch objects associated with the Touch Bar event, obtained through the event's allTouches() function. This information is limited to the touch and describes very little about the Touch Bar item the user touched to generate the event. The one piece of Touch Bar information that is useful to identify the Touch Bar item is its -locationInView: method providing the horizontal distance of the touch from the left end of the Touch Bar.
// Note that the Cocoa log description of a Touch Bar event shows obsolete information, in that the type of the event is reported as "Reserved2" instead of "Direct Touch". The event type value by itself is correctly logged as NSEventTypeDirectTouch (37).
id self = (__bridge id)(refcon);
NSEvent *cocoaEvent = [NSEvent eventWithCGEvent:cgEvent];
NSUInteger eventType = [cocoaEvent type];
if (eventType == NSEventTypeDirectTouch) {
// Log interesting information about the event.
[self logEvent:cocoaEvent];
// Test intercepting and blocking Touch Bar events.
int64_t targetPID = CGEventGetIntegerValueField(cgEvent, kCGEventTargetUnixProcessID);
NSString *targetAppName = [[NSRunningApplication runningApplicationWithProcessIdentifier:(pid_t)targetPID] localizedName];
if ([targetAppName isEqualToString:@"Microsoft Word"]) {
// To intercept and block all Touch Bar events targeted at MS Word, uncomment the 'return nil' statement.
// To block only those events that relate to a specific MS Word Touch Bar item, you should block MS Word events whose locationInView x-coordinates are within the width of the affected item. I have not yet worked out a way to detect the controls in the Touch Bar so as to be able to do this.
// To send modified events, you should edit the incoming events of interest or create suitable replacement events and return them instead of the incoming event. I have not yet worked out how to do this.
// return nil;
}
}
// Return the incoming event. WARNING: If you return nil here, your computer will receive no input from any device and you will have to restart it with the power switch in order to recover control.
return cgEvent;
}
--
Bill Cheeseman - email@hidden
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden