NDOF: Rework input handling on macOS
This patch modifies the logic behind handling NDOF device events on macOS so that it can benefit from the `Blender` profile available in 3DConnexion driver v10.8.7 and later. A new device command was introduced: `kConnexionCmdAppEvent`, which is sent by the driver upong getting an apprioriate NDOF device button input. This allow the driver to consumes all NDOF device input and then send appropriate app events based on its configuration instead of forwarding raw data to the application directly. When using 3DConnexion driver versions prior to v10.8.7, the behavior is unchanged. This approach allows for supporting all of the SpaceMouse Enterprise buttons, long presses included (solving issue #119206 on macOS) Co-authored-by: Sergey Sharybin <sergey@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/126694
This commit is contained in:
@@ -510,13 +510,6 @@ void GHOST_NDOFManager::updateButton(int button_number, bool press, uint64_t tim
|
||||
{
|
||||
GHOST_NDOF_ButtonT button = static_cast<GHOST_NDOF_ButtonT>(button_number);
|
||||
|
||||
/* For bit-mask devices button mapping isn't unified, therefore check the button map. */
|
||||
if (std::find(bitmask_devices_.begin(), bitmask_devices_.end(), device_type_) !=
|
||||
bitmask_devices_.end())
|
||||
{
|
||||
button = hid_map_[button_number];
|
||||
}
|
||||
|
||||
if (button == GHOST_NDOF_BUTTON_INVALID) {
|
||||
CLOG_INFO(
|
||||
LOG, 2, "button=%d, press=%d (mapped to none, ignoring!)", button_number, int(press));
|
||||
@@ -578,7 +571,11 @@ void GHOST_NDOFManager::updateButtonsBitmask(int button_bits, uint64_t time)
|
||||
else {
|
||||
button_depressed_ &= ~mask; /* Clear this button's bit. */
|
||||
}
|
||||
updateButton(button_number, press, time);
|
||||
|
||||
/* Bitmask devices don't have unified keymaps, so button numbers needs to be looked up in the
|
||||
* map. */
|
||||
int button = hid_map_[button_number];
|
||||
updateButton(button, press, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "GHOST_NDOFManagerCocoa.hh"
|
||||
#include "GHOST_SystemCocoa.hh"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
@@ -21,19 +22,23 @@ static GHOST_NDOFManager *ndof_manager = nullptr;
|
||||
static uint16_t clientID = 0;
|
||||
|
||||
static bool driver_loaded = false;
|
||||
static bool has_old_driver =
|
||||
false; /* 3Dconnexion drivers before 10 beta 4 are "old", not all buttons will work. */
|
||||
static bool has_new_driver =
|
||||
false; /* drivers >= 10.2.2 are "new", and can process events on a separate thread. */
|
||||
|
||||
/* 3DxMacCore version >= minimal_version is considered "new".
|
||||
* It was firstly introduced in 3DxWare v10.8.4 r3716 and can process
|
||||
* (not yet documented in SDK manual) kConnexionCmdAppEvent events. */
|
||||
static NSString *new_driver_minimal_version = @"1.3.4.473";
|
||||
|
||||
/* Replicate just enough of the 3Dx API for our uses, not everything the driver provides. */
|
||||
|
||||
#define kConnexionClientModeTakeOver 1
|
||||
#define kConnexionMaskAll 0x3fff
|
||||
#define kConnexionMaskAxis 0x3f00
|
||||
#define kConnexionMaskNoButtons 0x0
|
||||
#define kConnexionMaskAllButtons 0xffffffff
|
||||
#define kConnexionCmdHandleButtons 2
|
||||
#define kConnexionCmdHandleAxis 3
|
||||
#define kConnexionCmdAppSpecific 10
|
||||
#define kConnexionCmdAppEvent 11
|
||||
#define kConnexionMsgDeviceState '3dSR'
|
||||
#define kConnexionCtlGetDeviceID '3did'
|
||||
|
||||
@@ -46,8 +51,8 @@ struct ConnexionDeviceState {
|
||||
int32_t value;
|
||||
uint64_t time;
|
||||
uint8_t report[8];
|
||||
uint16_t buttons8; /* Obsolete! (pre-10.x drivers). */
|
||||
int16_t axis[6]; /* TX, TY, TZ, RX, RY, RZ. */
|
||||
uint16_t appEventPressed;
|
||||
int16_t axis[6]; /* TX, TY, TZ, RX, RY, RZ. */
|
||||
uint16_t address;
|
||||
uint32_t buttons;
|
||||
};
|
||||
@@ -60,7 +65,6 @@ typedef void (*MessageHandler)(uint32_t, uint32_t msg_type, void *msg_arg);
|
||||
|
||||
/* Driver functions: */
|
||||
typedef int16_t (*SetConnexionHandlers_ptr)(MessageHandler, AddedHandler, RemovedHandler, bool);
|
||||
typedef int16_t (*InstallConnexionHandlers_ptr)(MessageHandler, AddedHandler, RemovedHandler);
|
||||
typedef void (*CleanupConnexionHandlers_ptr)();
|
||||
typedef uint16_t (*RegisterConnexionClient_ptr)(uint32_t signature,
|
||||
const char *name,
|
||||
@@ -76,7 +80,6 @@ typedef int16_t (*ConnexionClientControl_ptr)(uint16_t clientID,
|
||||
#define DECLARE_FUNC(name) name##_ptr name = nullptr
|
||||
|
||||
DECLARE_FUNC(SetConnexionHandlers);
|
||||
DECLARE_FUNC(InstallConnexionHandlers);
|
||||
DECLARE_FUNC(CleanupConnexionHandlers);
|
||||
DECLARE_FUNC(RegisterConnexionClient);
|
||||
DECLARE_FUNC(SetConnexionClientButtonMask);
|
||||
@@ -117,12 +120,6 @@ static bool load_driver_functions()
|
||||
|
||||
if (SetConnexionHandlers != nullptr) {
|
||||
driver_loaded = true;
|
||||
has_new_driver = true;
|
||||
}
|
||||
else {
|
||||
LOAD_FUNC(InstallConnexionHandlers);
|
||||
|
||||
driver_loaded = (InstallConnexionHandlers != nullptr);
|
||||
}
|
||||
|
||||
if (driver_loaded) {
|
||||
@@ -131,8 +128,6 @@ static bool load_driver_functions()
|
||||
LOAD_FUNC(SetConnexionClientButtonMask);
|
||||
LOAD_FUNC(UnregisterConnexionClient);
|
||||
LOAD_FUNC(ConnexionClientControl);
|
||||
|
||||
has_old_driver = (SetConnexionClientButtonMask == nullptr);
|
||||
}
|
||||
}
|
||||
#if DEBUG_NDOF_DRIVER
|
||||
@@ -141,8 +136,6 @@ static bool load_driver_functions()
|
||||
}
|
||||
|
||||
printf("loaded: %s\n", driver_loaded ? "YES" : "NO");
|
||||
printf("old: %s\n", has_old_driver ? "YES" : "NO");
|
||||
printf("new: %s\n", has_new_driver ? "YES" : "NO");
|
||||
#endif
|
||||
|
||||
return driver_loaded;
|
||||
@@ -162,8 +155,8 @@ static void DeviceAdded(uint32_t /*unused*/)
|
||||
/* Determine exactly which device is plugged in. */
|
||||
int32_t result;
|
||||
ConnexionClientControl(clientID, kConnexionCtlGetDeviceID, 0, &result);
|
||||
int16_t vendorID = result >> 16;
|
||||
int16_t productID = result & 0xffff;
|
||||
const int16_t vendorID = result >> 16;
|
||||
const int16_t productID = result & 0xffff;
|
||||
|
||||
ndof_manager->setDevice(vendorID, productID);
|
||||
}
|
||||
@@ -183,7 +176,7 @@ static void DeviceEvent(uint32_t /*unused*/, uint32_t msg_type, void *msg_arg)
|
||||
/* Device state is broadcast to all clients; only react if sent to us. */
|
||||
if (s->client == clientID) {
|
||||
/* TODO: is s->time compatible with GHOST timestamps? if so use that instead. */
|
||||
uint64_t now = ghost_system->getMilliSeconds();
|
||||
const uint64_t now = ghost_system->getMilliSeconds();
|
||||
|
||||
switch (s->command) {
|
||||
case kConnexionCmdHandleAxis: {
|
||||
@@ -198,7 +191,7 @@ static void DeviceEvent(uint32_t /*unused*/, uint32_t msg_type, void *msg_arg)
|
||||
break;
|
||||
}
|
||||
case kConnexionCmdHandleButtons: {
|
||||
int button_bits = has_old_driver ? s->buttons8 : s->buttons;
|
||||
const int button_bits = s->buttons;
|
||||
#ifdef DEBUG_NDOF_BUTTONS
|
||||
printf("button bits: 0x%08x\n", button_bits);
|
||||
#endif
|
||||
@@ -206,6 +199,16 @@ static void DeviceEvent(uint32_t /*unused*/, uint32_t msg_type, void *msg_arg)
|
||||
ghost_system->notifyExternalEventProcessed();
|
||||
break;
|
||||
}
|
||||
case kConnexionCmdAppEvent: {
|
||||
const int button_number = s->value;
|
||||
const bool pressed = s->appEventPressed;
|
||||
#ifdef DEBUG_NDOF_BUTTONS
|
||||
printf("button number: %d, pressed: %d\n", button_number, pressed);
|
||||
#endif
|
||||
ndof_manager->updateButton(button_number, pressed, now);
|
||||
ghost_system->notifyExternalEventProcessed();
|
||||
break;
|
||||
}
|
||||
#if DEBUG_NDOF_DRIVER
|
||||
case kConnexionCmdAppSpecific:
|
||||
printf("ndof: app-specific command, param = %hd, value = %d\n", s->param, s->value);
|
||||
@@ -226,14 +229,7 @@ GHOST_NDOFManagerCocoa::GHOST_NDOFManagerCocoa(GHOST_System &sys) : GHOST_NDOFMa
|
||||
ghost_system = dynamic_cast<GHOST_SystemCocoa *>(&sys);
|
||||
ndof_manager = this;
|
||||
|
||||
uint16_t error;
|
||||
if (has_new_driver) {
|
||||
const bool separate_thread = false; /* TODO: rework Mac event handler to allow this. */
|
||||
error = SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, separate_thread);
|
||||
}
|
||||
else {
|
||||
error = InstallConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved);
|
||||
}
|
||||
const uint16_t error = SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, true);
|
||||
|
||||
if (error) {
|
||||
#if DEBUG_NDOF_DRIVER
|
||||
@@ -242,13 +238,27 @@ GHOST_NDOFManagerCocoa::GHOST_NDOFManagerCocoa(GHOST_System &sys) : GHOST_NDOFMa
|
||||
return;
|
||||
}
|
||||
|
||||
const NSDictionary *dictInfos =
|
||||
[NSBundle bundleWithPath:@"/Library/Frameworks/3DconnexionClient.framework"]
|
||||
.infoDictionary;
|
||||
NSString *strVersion = [dictInfos objectForKey:(NSString *)kCFBundleVersionKey];
|
||||
const auto compare = [strVersion compare:new_driver_minimal_version];
|
||||
const bool has_new_driver = compare != NSOrderedAscending;
|
||||
|
||||
/* New driver makes use of kConnexionCmdAppEvent events, which require to have all buttons
|
||||
* unmasked. Basically, this means that driver consumes all NDOF device input and then sends
|
||||
* appropriate app events based on its configuration instead of forwarding raw data to the
|
||||
* application. When using an older driver, the old solution with all buttons forwarded
|
||||
* (masked) is preferred. */
|
||||
const uint32_t client_mask = has_new_driver ? kConnexionMaskAxis : kConnexionMaskAll;
|
||||
const uint32_t button_mask = has_new_driver ? kConnexionMaskNoButtons :
|
||||
kConnexionMaskAllButtons;
|
||||
|
||||
/* Pascal string *and* a four-letter constant. How old-school. */
|
||||
clientID = RegisterConnexionClient(
|
||||
'blnd', "\007blender", kConnexionClientModeTakeOver, kConnexionMaskAll);
|
||||
'blnd', "\007blender", kConnexionClientModeTakeOver, client_mask);
|
||||
|
||||
if (!has_old_driver) {
|
||||
SetConnexionClientButtonMask(clientID, kConnexionMaskAllButtons);
|
||||
}
|
||||
SetConnexionClientButtonMask(clientID, button_mask);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user