on 2007-03-19 7:48 AM, Bill Cheeseman at email@hidden wrote:
> Of course, these things usually turn out to be my own bug, after all, so
> I'll post some of my code in the next message, just in case somebody has the
> time to skim through it.
My test app is a Cocoa app. It wraps a single event tap in a Cocoa class I
call PFEventTap. The test app puts up a single window with 5 buttons:
Install Event Tap, Remove Event Tap, Enable Event Tap, Disable Event Tap,
and Log All Event Taps. It did it this way to ensure that each of my
function calls is isolated in time and each function is called only once.
Each of the buttons works correctly; that is, left or right clicking on a
Finder icon works or doesn't work, as expected, after pushing one of the
first four buttons. The Log All Event Taps button allows me to run
CGGetEventTapList() at will, at any time, outputting the result as a Cocoa
NSArray and logging it to console.
Here's how I count and list the CGGetEventTapList() array:
- (unsigned)allEventTapsCount {
// Returns the count of all Event Taps installed on the system. Not all
Event Taps included in the count are necessarily managed using PFEventTaps.
// Covers the CGGetEventTapList function to return the count of
installed Event Taps.
CGTableCount count;
CGError err = CGGetEventTapList(0, NULL, &count);
if (err == kCGErrorSuccess) return count;
return 0;
}
- (NSArray *)allEventTaps {
// Returns an array of all Event Taps installed on the system. Each
element of the array is a dictionary. Not all Event Taps in the array are
necessarily managed using PFEventTaps.
// Covers the CGGetEventTapList function to return the list of installed
Event Taps.
CGTableCount preCount = [self allEventTapsCount];
CGEventTapInformation *list = (CGEventTapInformation *)calloc(preCount,
sizeof(CGEventTapInformation));
CGTableCount postCount; // should always equal preCount
CGError err = CGGetEventTapList(preCount, list, &postCount);
if (err == kCGErrorSuccess) {
NSMutableArray *returnArray = [[[NSMutableArray alloc]
initWithCapacity:postCount] autorelease];
int i;
for (i = 0; i < postCount; i++) {
NSDictionary *info = [NSDictionary
dictionaryWithObjects:[NSArray arrayWithObjects:[NSNumber
numberWithUnsignedInt:list[i].eventTapID], [NSNumber
numberWithUnsignedInt:list[i].tapPoint], [NSNumber
numberWithUnsignedInt:list[i].options], [NSNumber
numberWithUnsignedLongLong:list[i].eventsOfInterest], [NSNumber
numberWithInt:list[i].tappingProcess], [NSNumber
numberWithInt:list[i].processBeingTapped], [NSNumber
numberWithBool:list[i].enabled], [NSNumber
numberWithFloat:list[i].minUsecLatency], [NSNumber
numberWithFloat:list[i].avgUsecLatency], [NSNumber
numberWithFloat:list[i].maxUsecLatency], nil] forKeys:[NSArray
arrayWithObjects:@"eventTapID", @"tapPoint", @"options",
@"eventsOfInterest", @"tappingProcess", @"processBeingTapped", @"enabled",
@"minUsecLatency", @"avgUsecLatency", @"maxUsecLatency", nil]];
[returnArray addObject:info];
}
free(list);
return [[returnArray copy] autorelease];
}
return nil; // error
}
Here's how I install an event tap, after allocating one of my PFEventTap
objects:
- (id)initWithPid:(int)pid eventMask:(CGEventMask)mask
appendAtTail:(BOOL)tailPlacement listenOnly:(BOOL)listenOnlyOption
notificationDelegate:(id)delegate callbackSelector:(SEL)callback
contextInfo:(void *)contextInfo { // designated initializer
// Initializes a newly created PFEventTap object using the target
application's process ID. The target application must be running.
// Covers the CGEventTapCreateForPSN function. Key up and down events
are available only if the "Enable access for assistive devices" setting in
the Universal Access pane of System Preferences is turned on.
// This method saves a pointer to the receiver and the incoming
contextInfo (if any) in the CGEventTapCreateForPSN function's refcon
parameter. When the system's event tap mechanism calls the callback
function, it retrieves them from its refcon parameter and uses them, along
with the information passed by the system in the callback function's other
parameters, to create an NSInvocation object. It then uses the invocation
object to invoke the client's Objective-C callback selector. The callback
function obtains the receiver's notificationDelegate and callbackSelector
from the receiver as it was saved in the CGEventTapCreateForPSN function's
refcon parameter upon initialization here. This mechanism allows the
client's callback selector to be written as an Objective-C method instead of
a C function.
// Use the -[PFEventTapManager eventMaskByAddingType:toMask:] utility
method to build a mask by adding types.
if ((self = [super init])) {
// Set up parameters for the CGEventTapCreateForPSN function call.
ProcessSerialNumber psn;
GetProcessForPID(pid, &psn);
CGEventTapPlacement placement = (tailPlacement) ?
kCGTailAppendEventTap : kCGHeadInsertEventTap;
CGEventTapOptions option = (listenOnlyOption) ?
kCGEventTapOptionListenOnly : 0x00000000; // the kCGEventTapOptionDefault
constant is missing from CGEvent.h in Mac OS X 10.4 Tiger
// Set up refcon parameter for the CGEventTapCreateForPSN function
call.
NSMutableDictionary *tempRefcon = [[NSMutableDictionary alloc]
initWithCapacity:2];
[tempRefcon setObject:self forKey:@"eventTapKey"];
if (contextInfo) [tempRefcon setObject:contextInfo
forKey:@"contextInfoKey"];
id refcon = [tempRefcon copy];
[tempRefcon release];
// Create and install the event tap.
machPortRef = CGEventTapCreateForPSN(&psn, placement, option, mask,
(CGEventTapCallBack)callbackFunction, (void *)refcon);
if (machPortRef) {
// Create the run loop source.
eventSourceRef = CFMachPortCreateRunLoopSource(NULL,
machPortRef, 0);
if (eventSourceRef) {
// Add it to the run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSourceRef,
kCFRunLoopCommonModes);
// Set instance variables.
notificationDelegate = [delegate retain]; // private
callbackSelector = callback; // private
registeredEventTapLocation = 3; // there is no constant for
the case where the Event Type is created for a specific target application,
but the system appears to assign 3 (which is the next available constant
slot in the CGEventTapLocation enum).
registeredEventTapOptions = option;
registeredEventMask = mask;
NSLog(@"\n\tclient path = %@", [[NSBundle mainBundle]
bundlePath]);
NSLog(@"\n\tclient pid = %@", [NSNumber
numberWithInt:[PFEventTap pidFromPath:[[NSBundle mainBundle] bundlePath]]]);
clientPid = [[NSNumber numberWithInt:[PFEventTap
pidFromPath:[[NSBundle mainBundle] bundlePath]]] retain];
targetPid = [[NSNumber numberWithInt:pid] retain];
// The pendingEvent iVar is reinitialized in the callback
function every time this Event Tap is triggered.
} else {
//#ifdef PF_DEBUG
NSLog(@"PreFab PFEventTap
-initWithPid:eventType:appendAtTail:listenOnly:notificationDelegate:callback
Selector:contextInfo: method failed to add run loop source for pid %d.",
pid);
//#endif
CFMachPortInvalidate(machPortRef);
CFRelease(machPortRef);
machPortRef = NULL;
[self release];
self = nil;
}
} else {
//#ifdef PF_DEBUG
NSLog(@"PreFab PFEventTap
-initWithPid:eventType:appendAtTail:listenOnly:notificationDelegate:callback
Selector:contextInfo: method failed to create event tap for pid %d.", pid);
//#endif
[self release];
self = nil;
}
}
return self;
}
Bill Cheeseman
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Quartz-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden