Blender V5.0
GHOST_NDOFManagerCocoa.mm
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2010-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#define DEBUG_NDOF_DRIVER false
6
9#import <Cocoa/Cocoa.h>
10
11#include <dlfcn.h>
12#include <stdint.h>
13
14#if DEBUG_NDOF_DRIVER
15# include <cstdio>
16#endif
17
18/* Static callback functions need to talk to these objects: */
21
22static uint16_t clientID = 0;
23
24static bool driver_loaded = false;
25
26/* 3DxMacCore version >= minimal_version is considered "new".
27 * It was firstly introduced in 3DxWare v10.8.4 r3716 and can process
28 * (not yet documented in SDK manual) kConnexionCmdAppEvent events. */
29static NSString *new_driver_minimal_version = @"1.3.4.473";
30
31/* Replicate just enough of the 3Dx API for our uses, not everything the driver provides. */
32
33#define kConnexionClientModeTakeOver 1
34#define kConnexionMaskAll 0x3fff
35#define kConnexionMaskAxis 0x3f00
36#define kConnexionMaskNoButtons 0x0
37#define kConnexionMaskAllButtons 0xffffffff
38#define kConnexionCmdHandleButtons 2
39#define kConnexionCmdHandleAxis 3
40#define kConnexionCmdAppSpecific 10
41#define kConnexionCmdAppEvent 11
42#define kConnexionMsgDeviceState '3dSR'
43#define kConnexionCtlGetDeviceID '3did'
44
45#pragma pack(push, 2) /* Just this struct. */
47 uint16_t version;
48 uint16_t client;
49 uint16_t command;
50 int16_t param;
53 uint8_t report[8];
55 int16_t axis[6]; /* TX, TY, TZ, RX, RY, RZ. */
56 uint16_t address;
57 uint32_t buttons;
58};
59#pragma pack(pop)
60
61/* Callback functions: */
62typedef void (*AddedHandler)(uint32_t);
63typedef void (*RemovedHandler)(uint32_t);
64typedef void (*MessageHandler)(uint32_t, uint32_t msg_type, void *msg_arg);
65
66/* Driver functions: */
69typedef uint16_t (*RegisterConnexionClient_ptr)(uint32_t signature,
70 const char *name,
71 uint16_t mode,
72 uint32_t mask);
73typedef void (*SetConnexionClientButtonMask_ptr)(uint16_t clientID, uint32_t buttonMask);
74typedef void (*UnregisterConnexionClient_ptr)(uint16_t clientID);
75typedef int16_t (*ConnexionClientControl_ptr)(uint16_t clientID,
76 uint32_t message,
77 int32_t param,
79
80#define DECLARE_FUNC(name) name##_ptr name = nullptr
81
82DECLARE_FUNC(SetConnexionHandlers);
83DECLARE_FUNC(CleanupConnexionHandlers);
84DECLARE_FUNC(RegisterConnexionClient);
85DECLARE_FUNC(SetConnexionClientButtonMask);
86DECLARE_FUNC(UnregisterConnexionClient);
87DECLARE_FUNC(ConnexionClientControl);
88
89static void *load_func(void *module, const char *func_name)
90{
91 void *func = dlsym(module, func_name);
92
93#if DEBUG_NDOF_DRIVER
94 if (func) {
95 printf("'%s' loaded :D\n", func_name);
96 }
97 else {
98 printf("<!> %s\n", dlerror());
99 }
100#endif
101
102 return func;
103}
104
105#define LOAD_FUNC(name) name = (name##_ptr)load_func(module, #name)
106
107static void *module; /* Handle to the whole driver. */
108
110{
111 if (driver_loaded) {
112 return true;
113 }
114
115 module = dlopen("/Library/Frameworks/3DconnexionClient.framework/3DconnexionClient",
116 RTLD_LAZY | RTLD_LOCAL);
117
118 if (module) {
119 LOAD_FUNC(SetConnexionHandlers);
120
121 if (SetConnexionHandlers != nullptr) {
122 driver_loaded = true;
123 }
124
125 if (driver_loaded) {
126 LOAD_FUNC(CleanupConnexionHandlers);
127 LOAD_FUNC(RegisterConnexionClient);
128 LOAD_FUNC(SetConnexionClientButtonMask);
129 LOAD_FUNC(UnregisterConnexionClient);
130 LOAD_FUNC(ConnexionClientControl);
131 }
132 }
133#if DEBUG_NDOF_DRIVER
134 else {
135 printf("<!> %s\n", dlerror());
136 }
137
138 printf("loaded: %s\n", driver_loaded ? "YES" : "NO");
139#endif
140
141 return driver_loaded;
142}
143
144static void unload_driver()
145{
146 dlclose(module);
147}
148
149static void DeviceAdded(uint32_t /*unused*/)
150{
151#if DEBUG_NDOF_DRIVER
152 printf("ndof: device added\n");
153#endif
154
155 /* Determine exactly which device is plugged in. */
157 ConnexionClientControl(clientID, kConnexionCtlGetDeviceID, 0, &result);
158 const int16_t vendorID = result >> 16;
159 const int16_t productID = result & 0xffff;
160
161 ndof_manager->setDevice(vendorID, productID);
162}
163
164static void DeviceRemoved(uint32_t /*unused*/)
165{
166#if DEBUG_NDOF_DRIVER
167 printf("ndof: device removed\n");
168#endif
169}
170
171static void DeviceEvent(uint32_t /*unused*/, uint32_t msg_type, void *msg_arg)
172{
173 if (msg_type == kConnexionMsgDeviceState) {
175
176 /* Device state is broadcast to all clients; only react if sent to us. */
177 if (s->client == clientID) {
178 /* TODO: is s->time compatible with GHOST timestamps? if so use that instead. */
179 const uint64_t now = ghost_system->getMilliSeconds();
180
181 switch (s->command) {
183 /* convert to blender view coordinates. */
184 const int t[3] = {s->axis[0], -(s->axis[2]), s->axis[1]};
185 const int r[3] = {-(s->axis[3]), s->axis[5], -(s->axis[4])};
186
187 ndof_manager->updateTranslation(t, now);
188 ndof_manager->updateRotation(r, now);
189
190 ghost_system->notifyExternalEventProcessed();
191 break;
192 }
194 const int button_bits = s->buttons;
195#ifdef DEBUG_NDOF_BUTTONS
196 printf("button bits: 0x%08x\n", button_bits);
197#endif
198 ndof_manager->updateButtonsBitmask(button_bits, now);
199 ghost_system->notifyExternalEventProcessed();
200 break;
201 }
203 const int button_number = s->value;
204 const bool pressed = s->appEventPressed;
205#ifdef DEBUG_NDOF_BUTTONS
206 printf("button number: %d, pressed: %d\n", button_number, pressed);
207#endif
208 ndof_manager->updateButton(GHOST_NDOF_ButtonT(button_number), pressed, now);
209 ghost_system->notifyExternalEventProcessed();
210 break;
211 }
212#if DEBUG_NDOF_DRIVER
214 printf("ndof: app-specific command, param = %hd, value = %d\n", s->param, s->value);
215 break;
216
217 default:
218 printf("ndof: mystery device command %d\n", s->command);
219#endif
220 }
221 }
222 }
223}
224
226{
227 if (load_driver_functions()) {
228 /* Give static functions something to talk to: */
229 ghost_system = dynamic_cast<GHOST_SystemCocoa *>(&sys);
230 ndof_manager = this;
231
232 const uint16_t error = SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, true);
233
234 if (error) {
235#if DEBUG_NDOF_DRIVER
236 printf("ndof: error %d while setting up handlers\n", error);
237#endif
238 return;
239 }
240
241 const NSDictionary *dictInfos =
242 [NSBundle bundleWithPath:@"/Library/Frameworks/3DconnexionClient.framework"]
243 .infoDictionary;
244 NSString *strVersion = [dictInfos objectForKey:(NSString *)kCFBundleVersionKey];
245 const auto compare = [strVersion compare:new_driver_minimal_version];
246 const bool has_new_driver = compare != NSOrderedAscending;
247
248 /* New driver makes use of kConnexionCmdAppEvent events, which require to have all buttons
249 * unmasked. Basically, this means that driver consumes all NDOF device input and then sends
250 * appropriate app events based on its configuration instead of forwarding raw data to the
251 * application. When using an older driver, the old solution with all buttons forwarded
252 * (masked) is preferred. */
253 const uint32_t client_mask = has_new_driver ? kConnexionMaskAxis : kConnexionMaskAll;
254 const uint32_t button_mask = has_new_driver ? kConnexionMaskNoButtons :
256
257 /* Pascal string *and* a four-letter constant. How old-school. */
258 clientID = RegisterConnexionClient(
259 'blnd', "\007blender", kConnexionClientModeTakeOver, client_mask);
260
261 SetConnexionClientButtonMask(clientID, button_mask);
262 }
263}
264
266{
267 if (driver_loaded) {
268 UnregisterConnexionClient(clientID);
269 CleanupConnexionHandlers();
271
272 ghost_system = nullptr;
273 ndof_manager = nullptr;
274 }
275}
276
static void DeviceAdded(uint32_t)
void(* CleanupConnexionHandlers_ptr)()
static GHOST_NDOFManager * ndof_manager
static void DeviceRemoved(uint32_t)
#define kConnexionMaskNoButtons
uint16_t(* RegisterConnexionClient_ptr)(uint32_t signature, const char *name, uint16_t mode, uint32_t mask)
#define kConnexionCmdAppSpecific
int16_t(* SetConnexionHandlers_ptr)(MessageHandler, AddedHandler, RemovedHandler, bool)
#define kConnexionCmdAppEvent
void(* SetConnexionClientButtonMask_ptr)(uint16_t clientID, uint32_t buttonMask)
#define kConnexionCmdHandleButtons
static bool load_driver_functions()
void(* RemovedHandler)(uint32_t)
static void unload_driver()
#define kConnexionMaskAxis
static NSString * new_driver_minimal_version
static uint16_t clientID
void(* MessageHandler)(uint32_t, uint32_t msg_type, void *msg_arg)
static bool driver_loaded
#define DECLARE_FUNC(name)
#define kConnexionMsgDeviceState
#define kConnexionMaskAll
#define kConnexionCtlGetDeviceID
#define kConnexionCmdHandleAxis
static void * load_func(void *module, const char *func_name)
#define kConnexionMaskAllButtons
static void DeviceEvent(uint32_t, uint32_t msg_type, void *msg_arg)
void(* AddedHandler)(uint32_t)
void(* UnregisterConnexionClient_ptr)(uint16_t clientID)
#define kConnexionClientModeTakeOver
#define LOAD_FUNC(name)
int16_t(* ConnexionClientControl_ptr)(uint16_t clientID, uint32_t message, int32_t param, int32_t *result)
static GHOST_SystemCocoa * ghost_system
GHOST_NDOF_ButtonT
unsigned long long int uint64_t
GHOST_NDOFManager(GHOST_System &)
#define printf(...)
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static void error(const char *str)
static struct PyModuleDef module
Definition python.cpp:796
const char * name