///////////////////////////////////////////////////////
// This program is a daemon that causes the "enter" key
// (not the return key) on your Mac keyboard to act like
// a right-mouse button.
//
// It requires Mac OS 10.4 (or later, probably) because is uses an event tap
// callback to capture and convert the enter key event at a low level,
// and is designed to run as a lauchd daemon. Event taps and launchd are
// both new to Mac OS 10.4.
//
// The daemon's ability to capture and convert the enter key is not foolproof.
// The enter key will not act as a right mouse button for anything that
// bypasses the event tap system. I've noticed that Parallels, for example,
// does this. Also, if other event taps are installed, they could have a higher
// priority. It looks like event taps installed after this one could request
// higher priority.
//
// NOTE: This program must be run as the root user OR access for assistive
// devices must be enabled to this program to work. When launched with launchd
// it runs as a root process. However, if you want to run it manually I suggest
// you turn on access for assistive devices (in the universal access pane of
// system preferences.)
//
// NOTE: the log stuff is essentially #ifdef'ed out when compiled in release mode.
// Under debug mode these just resolve to printf statements.
#include <CoreServices/CoreServices.h>
#include <ApplicationServices/ApplicationServices.h>
#include <signal.h>
#include <launch.h>
#include "log.h"
#define SAFE_RELEASE(ref) if ((ref) != NULL) { CFRelease(ref); (ref) = NULL; }
static CFStringRef myRunLoopMode = CFSTR("myRunLoopMode");
CGEventRef myEventTapCallback(
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon)
{
if (type == kCGEventKeyDown || type == kCGEventKeyUp)
{
CGKeyCode keycode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
#ifdef _DEBUG
{
CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("code: %d, type: %c, auto: %d"), (int)keycode, (type == kCGEventKeyDown) ? 'D' : 'U', (int)(CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) != 0));
LOGLINE_CF(msg);
SAFE_RELEASE(msg);
}
#endif
if (keycode == 76 /* the "enter" key--note: not the return key */)
{
bool isAutoRepeat = (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) != 0);
if (isAutoRepeat)
return NULL;
// change it to a right mouse button event
CGEventSetType(event, (type == kCGEventKeyDown) ? kCGEventRightMouseDown : kCGEventRightMouseUp);
}
}
return event;
}
// This is a callback to handle SIGTERM and SIGINT signals
void sigtermHandler(int sig)
{
CFRunLoopRef rl = CFRunLoopGetCurrent();
if (rl == NULL)
exit(1); // something when wrong. Better just exit
else
CFRunLoopStop(rl);
}
int main (int argc, const char * argv[])
{
// Check-in with launchd, as recommended.
// I couldn't find any documentation on this other than the SampleD code.
// I am following the SampleD model. As far as I can tell, this is what is
// meant by checking in. NOTE: In the sample code I've seen, the call to
// launch_data_new_string() is not paired with some kind of
// a free/dealloc/delete call.
{
launch_data_t checkin_request = launch_data_new_string(LAUNCH_KEY_CHECKIN);
if (checkin_request == NULL)
exit(1); // need to add some logging or something here
else
{
launch_data_t checkin_response = launch_msg(checkin_request);
if (checkin_response == NULL)
exit(1); // need to add some logging or something here
}
}
///////////////////////////////////////////////////
// Setup the event tap to convert enter key presses to right mouse button presses
CFRunLoopRef rl = NULL; // My run loop
CFMachPortRef mp = NULL; // The event tap
CFRunLoopSourceRef rls = NULL; // The event source for event tap
// get the runloop
rl = CFRunLoopGetCurrent();
if (rl != NULL)
CFRetain(rl);
// create the event tap to watch key up/down events. myEventTapCallback is
// called when these events occur
if (rl != NULL)
{
mp = CGEventTapCreate(
kCGHIDEventTap,
kCGHeadInsertEventTap,
0x00000000 /* kCGEventTapOptionDefault */,
CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp),
myEventTapCallback,
NULL);
}
// create the source for the event tap
if (mp != NULL)
{
rls = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mp, 0);
}
// add the source to the run loop
if (rls != NULL)
{
CFRunLoopAddSource(rl, rls, myRunLoopMode);
}
/////////////////////////////////////////////////////
// Now start the runloop
if (rls != NULL)
{
LOGLINE_C("starting...");
// as recommended for launchd daemons, we handle SEGTERM signals.
// sigtermHandler just tells the runloop to stop. Everything seemed to
// work fine even when I didn't handle SIGTERM, but this makes me
// feel better because my cleanup code gets called.
signal(SIGTERM, &sigtermHandler);
SInt32 result = CFRunLoopRunInMode(myRunLoopMode, 3153600000.0, false); // run for ~100 years (60*60*24*365*100 seconds)
#ifdef _DEBUG
char buf[1024];
sprintf(buf, "done! result: %i", result);
LOGLINE_C(buf);
#else
result; // makes warnign go away
#endif
}
/////////////////////////////////////////////////////
// cleanup
if (rls != NULL)
CFRunLoopRemoveSource(rl, rls, kCFRunLoopCommonModes); // remove the source from the runloop
SAFE_RELEASE(rls);
SAFE_RELEASE(mp);
SAFE_RELEASE(rl);
return 0;
}