OS X: add 3Dconnexion device support.

pull/19/head
Thomas Buck 2016-06-19 21:24:37 +02:00 committed by whitequark
parent 251948bdbd
commit fb62e0494b
1 changed files with 151 additions and 0 deletions

View File

@ -1218,6 +1218,155 @@ void SolveSpace::ExitNow(void) {
[NSApp stop:nil];
}
/*
* Normally we would just link to the 3DconnexionClient framework.
* We don't want to (are not allowed to) distribute the official
* framework, so we're trying to use the one installed on the users
* computer. There are some different versions of the framework,
* the official one and re-implementations using an open source driver
* for older devices (spacenav-plus). So weak-linking isn't an option,
* either. The only remaining way is using CFBundle to dynamically
* load the library at runtime, and also detect its availability.
*
* We're also defining everything needed from the 3DconnexionClientAPI,
* so we're not depending on the API headers.
*/
#pragma pack(push,2)
enum {
kConnexionClientModeTakeOver = 1,
kConnexionClientModePlugin = 2
};
#define kConnexionMsgDeviceState '3dSR'
#define kConnexionMaskButtons 0x00FF
#define kConnexionMaskAxis 0x3F00
typedef struct {
uint16_t version;
uint16_t client;
uint16_t command;
int16_t param;
int32_t value;
UInt64 time;
uint8_t report[8];
uint16_t buttons8;
int16_t axis[6];
uint16_t address;
uint32_t buttons;
} ConnexionDeviceState, *ConnexionDeviceStatePtr;
#pragma pack(pop)
typedef void (*ConnexionAddedHandlerProc)(io_connect_t);
typedef void (*ConnexionRemovedHandlerProc)(io_connect_t);
typedef void (*ConnexionMessageHandlerProc)(io_connect_t, natural_t, void *);
typedef OSErr (*InstallConnexionHandlersProc)(ConnexionMessageHandlerProc, ConnexionAddedHandlerProc, ConnexionRemovedHandlerProc);
typedef void (*CleanupConnexionHandlersProc)(void);
typedef UInt16 (*RegisterConnexionClientProc)(UInt32, UInt8 *, UInt16, UInt32);
typedef void (*UnregisterConnexionClientProc)(UInt16);
static BOOL connexionShiftIsDown = NO;
static UInt16 connexionClient = 0;
static UInt32 connexionSignature = 'SoSp';
static UInt8 *connexionName = (UInt8 *)"SolveSpace";
static CFBundleRef spaceBundle = NULL;
static InstallConnexionHandlersProc installConnexionHandlers = NULL;
static CleanupConnexionHandlersProc cleanupConnexionHandlers = NULL;
static RegisterConnexionClientProc registerConnexionClient = NULL;
static UnregisterConnexionClientProc unregisterConnexionClient = NULL;
static void connexionAdded(io_connect_t con) {}
static void connexionRemoved(io_connect_t con) {}
static void connexionMessage(io_connect_t con, natural_t type, void *arg) {
if (type != kConnexionMsgDeviceState) {
return;
}
ConnexionDeviceState *device = (ConnexionDeviceState *)arg;
dispatch_async(dispatch_get_main_queue(), ^(void){
SolveSpace::SS.GW.SpaceNavigatorMoved(
(double)device->axis[0] * -0.25,
(double)device->axis[1] * -0.25,
(double)device->axis[2] * 0.25,
(double)device->axis[3] * -0.0005,
(double)device->axis[4] * -0.0005,
(double)device->axis[5] * -0.0005,
(connexionShiftIsDown == YES) ? 1 : 0
);
});
}
static void connexionInit() {
NSString *bundlePath = @"/Library/Frameworks/3DconnexionClient.framework";
NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
spaceBundle = CFBundleCreate(kCFAllocatorDefault, (__bridge CFURLRef)bundleURL);
// Don't continue if no Spacemouse driver is installed on this machine
if (spaceBundle == NULL) {
return;
}
installConnexionHandlers = (InstallConnexionHandlersProc)
CFBundleGetFunctionPointerForName(spaceBundle,
CFSTR("InstallConnexionHandlers"));
cleanupConnexionHandlers = (CleanupConnexionHandlersProc)
CFBundleGetFunctionPointerForName(spaceBundle,
CFSTR("CleanupConnexionHandlers"));
registerConnexionClient = (RegisterConnexionClientProc)
CFBundleGetFunctionPointerForName(spaceBundle,
CFSTR("RegisterConnexionClient"));
unregisterConnexionClient = (UnregisterConnexionClientProc)
CFBundleGetFunctionPointerForName(spaceBundle,
CFSTR("UnregisterConnexionClient"));
// Only continue if all required symbols have been loaded
if ((installConnexionHandlers == NULL) || (cleanupConnexionHandlers == NULL)
|| (registerConnexionClient == NULL) || (unregisterConnexionClient == NULL)) {
CFRelease(spaceBundle);
spaceBundle = NULL;
return;
}
installConnexionHandlers(&connexionMessage, &connexionAdded, &connexionRemoved);
connexionClient = registerConnexionClient(connexionSignature, connexionName,
kConnexionClientModeTakeOver, kConnexionMaskButtons | kConnexionMaskAxis);
// Monitor modifier flags to detect Shift button state changes
[NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyDownMask | NSFlagsChangedMask)
handler:^(NSEvent *event) {
if (event.modifierFlags & NSShiftKeyMask) {
connexionShiftIsDown = YES;
}
return event;
}];
[NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyUpMask | NSFlagsChangedMask)
handler:^(NSEvent *event) {
if (!(event.modifierFlags & NSShiftKeyMask)) {
connexionShiftIsDown = NO;
}
return event;
}];
}
static void connexionClose() {
if (spaceBundle == NULL) {
return;
}
unregisterConnexionClient(connexionClient);
cleanupConnexionHandlers();
CFRelease(spaceBundle);
}
int main(int argc, const char *argv[]) {
[NSApplication sharedApplication];
ApplicationDelegate *delegate = [[ApplicationDelegate alloc] init];
@ -1228,11 +1377,13 @@ int main(int argc, const char *argv[]) {
[[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:nil topLevelObjects:nil];
SolveSpace::InitMainMenu([NSApp mainMenu]);
connexionInit();
SolveSpace::SS.Init();
[GW makeKeyAndOrderFront:nil];
[NSApp run];
connexionClose();
SolveSpace::SK.Clear();
SolveSpace::SS.Clear();