Merge branch 'master' into blender2.8
[blender.git] / intern / ghost / intern / GHOST_NDOFManagerCocoa.mm
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s):
19  *   Mike Erwin
20  *
21  * ***** END GPL LICENSE BLOCK *****
22  */
23
24 #define DEBUG_NDOF_DRIVER false
25
26 #include "GHOST_NDOFManagerCocoa.h"
27 #include "GHOST_SystemCocoa.h"
28
29 #include <stdint.h>
30 #include <dlfcn.h>
31
32 #if DEBUG_NDOF_DRIVER
33   #include <cstdio>
34 #endif
35
36 // static callback functions need to talk to these objects:
37 static GHOST_SystemCocoa* ghost_system = NULL;
38 static GHOST_NDOFManager* ndof_manager = NULL;
39
40 static uint16_t clientID = 0;
41
42 static bool driver_loaded = false;
43 static bool has_old_driver = false; // 3Dconnexion drivers before 10 beta 4 are "old", not all buttons will work
44 static bool has_new_driver = false; // drivers >= 10.2.2 are "new", and can process events on a separate thread
45
46 // replicate just enough of the 3Dx API for our uses, not everything the driver provides
47
48 #define kConnexionClientModeTakeOver 1
49 #define kConnexionMaskAll 0x3fff
50 #define kConnexionMaskAllButtons 0xffffffff
51 #define kConnexionCmdHandleButtons 2
52 #define kConnexionCmdHandleAxis 3
53 #define kConnexionCmdAppSpecific 10
54 #define kConnexionMsgDeviceState '3dSR'
55 #define kConnexionCtlGetDeviceID '3did'
56
57 #pragma pack(push,2) // just this struct
58 struct ConnexionDeviceState {
59         uint16_t version;
60         uint16_t client;
61         uint16_t command;
62         int16_t param;
63         int32_t value;
64         uint64_t time;
65         uint8_t report[8];
66         uint16_t buttons8; // obsolete! (pre-10.x drivers)
67         int16_t axis[6]; // tx, ty, tz, rx, ry, rz
68         uint16_t address;
69         uint32_t buttons;
70 };
71 #pragma pack(pop)
72
73 // callback functions:
74 typedef void (*AddedHandler)(uint32_t);
75 typedef void (*RemovedHandler)(uint32_t);
76 typedef void (*MessageHandler)(uint32_t, uint32_t msg_type, void* msg_arg);
77
78 // driver functions:
79 typedef int16_t (*SetConnexionHandlers_ptr)(MessageHandler, AddedHandler, RemovedHandler, bool);
80 typedef int16_t (*InstallConnexionHandlers_ptr)(MessageHandler, AddedHandler, RemovedHandler);
81 typedef void (*CleanupConnexionHandlers_ptr)();
82 typedef uint16_t (*RegisterConnexionClient_ptr)(uint32_t signature, const char* name, uint16_t mode, uint32_t mask);
83 typedef void (*SetConnexionClientButtonMask_ptr)(uint16_t clientID, uint32_t buttonMask);
84 typedef void (*UnregisterConnexionClient_ptr)(uint16_t clientID);
85 typedef int16_t (*ConnexionClientControl_ptr)(uint16_t clientID, uint32_t message, int32_t param, int32_t* result);
86
87 #define DECLARE_FUNC(name) name##_ptr name = NULL
88
89 DECLARE_FUNC(SetConnexionHandlers);
90 DECLARE_FUNC(InstallConnexionHandlers);
91 DECLARE_FUNC(CleanupConnexionHandlers);
92 DECLARE_FUNC(RegisterConnexionClient);
93 DECLARE_FUNC(SetConnexionClientButtonMask);
94 DECLARE_FUNC(UnregisterConnexionClient);
95 DECLARE_FUNC(ConnexionClientControl);
96
97
98 static void* load_func(void* module, const char* func_name)
99 {
100         void* func = dlsym(module, func_name);
101
102 #if DEBUG_NDOF_DRIVER
103         if (func) {
104                 printf("'%s' loaded :D\n", func_name);
105         }
106         else {
107                 printf("<!> %s\n", dlerror());
108         }
109 #endif
110
111         return func;
112 }
113
114 #define LOAD_FUNC(name) name = (name##_ptr) load_func(module, #name)
115
116 static void* module; // handle to the whole driver
117
118 static bool load_driver_functions()
119 {
120         if (driver_loaded) {
121                 return true;
122         }
123
124         module = dlopen("3DconnexionClient.framework/3DconnexionClient", RTLD_LAZY | RTLD_LOCAL);
125
126         if (module) {
127                 LOAD_FUNC(SetConnexionHandlers);
128
129                 if (SetConnexionHandlers != NULL) {
130                         driver_loaded = true;
131                         has_new_driver = true;
132                 }
133                 else {
134                         LOAD_FUNC(InstallConnexionHandlers);
135
136                         driver_loaded = (InstallConnexionHandlers != NULL);
137                 }
138
139                 if (driver_loaded) {
140                         LOAD_FUNC(CleanupConnexionHandlers);
141                         LOAD_FUNC(RegisterConnexionClient);
142                         LOAD_FUNC(SetConnexionClientButtonMask);
143                         LOAD_FUNC(UnregisterConnexionClient);
144                         LOAD_FUNC(ConnexionClientControl);
145
146                         has_old_driver = (SetConnexionClientButtonMask == NULL);
147                 }
148         }
149 #if DEBUG_NDOF_DRIVER
150         else {
151                 printf("<!> %s\n", dlerror());
152         }
153
154         printf("loaded: %s\n", driver_loaded ? "YES" : "NO");
155         printf("old: %s\n", has_old_driver ? "YES" : "NO");
156         printf("new: %s\n", has_new_driver ? "YES" : "NO");
157 #endif
158
159         return driver_loaded;
160         }
161
162 static void unload_driver()
163 {
164         dlclose(module);
165 }
166
167 static void DeviceAdded(uint32_t unused)
168 {
169 #if DEBUG_NDOF_DRIVER
170         printf("ndof: device added\n");
171 #endif
172
173         // determine exactly which device is plugged in
174         int32_t result;
175         ConnexionClientControl(clientID, kConnexionCtlGetDeviceID, 0, &result);
176         int16_t vendorID = result >> 16;
177         int16_t productID = result & 0xffff;
178
179         ndof_manager->setDevice(vendorID, productID);
180 }
181
182 static void DeviceRemoved(uint32_t unused)
183 {
184 #if DEBUG_NDOF_DRIVER
185         printf("ndof: device removed\n");
186 #endif
187 }
188
189 static void DeviceEvent(uint32_t unused, uint32_t msg_type, void* msg_arg)
190 {
191         if (msg_type == kConnexionMsgDeviceState) {
192                 ConnexionDeviceState* s = (ConnexionDeviceState*)msg_arg;
193
194                 // device state is broadcast to all clients; only react if sent to us
195                 if (s->client == clientID) {
196                         // TODO: is s->time compatible with GHOST timestamps? if so use that instead.
197                         GHOST_TUns64 now = ghost_system->getMilliSeconds();
198
199                         switch (s->command) {
200                                 case kConnexionCmdHandleAxis:
201                                 {
202                                         // convert to blender view coordinates
203                                         const short t[3] = {s->axis[0], -(s->axis[2]), s->axis[1]};
204                                         const short r[3] = {-(s->axis[3]), s->axis[5], -(s->axis[4])};
205
206                                         ndof_manager->updateTranslation(t, now);
207                                         ndof_manager->updateRotation(r, now);
208
209                                         ghost_system->notifyExternalEventProcessed();
210                                         break;
211                                 }
212                                 case kConnexionCmdHandleButtons:
213                                 {
214                                         int button_bits = has_old_driver ? s->buttons8 : s->buttons;
215 #ifdef DEBUG_NDOF_BUTTONS
216                                         printf("button bits: 0x%08x\n", button_bits);
217 #endif
218                                         ndof_manager->updateButtons(button_bits, now);
219                                         ghost_system->notifyExternalEventProcessed();
220                                         break;
221                                 }
222 #if DEBUG_NDOF_DRIVER
223                                 case kConnexionCmdAppSpecific:
224                                         printf("ndof: app-specific command, param = %hd, value = %d\n", s->param, s->value);
225                                         break;
226
227                                 default:
228                                         printf("ndof: mystery device command %d\n", s->command);
229 #endif
230                         }
231                 }
232         }
233 }
234
235 GHOST_NDOFManagerCocoa::GHOST_NDOFManagerCocoa(GHOST_System& sys)
236     : GHOST_NDOFManager(sys)
237 {
238         if (load_driver_functions()) {
239                 // give static functions something to talk to:
240                 ghost_system = dynamic_cast<GHOST_SystemCocoa*>(&sys);
241                 ndof_manager = this;
242
243                 uint16_t error;
244                 if (has_new_driver) {
245                         const bool separate_thread = false; // TODO: rework Mac event handler to allow this
246                         error = SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, separate_thread);
247                 }
248                 else {
249                         error = InstallConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved);
250                 }
251
252                 if (error) {
253 #if DEBUG_NDOF_DRIVER
254                         printf("ndof: error %d while setting up handlers\n", error);
255 #endif
256                         return;
257                 }
258
259                 // Pascal string *and* a four-letter constant. How old-skool.
260                 clientID = RegisterConnexionClient('blnd', "\007blender", kConnexionClientModeTakeOver, kConnexionMaskAll);
261
262                 if (!has_old_driver) {
263                         SetConnexionClientButtonMask(clientID, kConnexionMaskAllButtons);
264                 }
265         }
266 }
267
268 GHOST_NDOFManagerCocoa::~GHOST_NDOFManagerCocoa()
269 {
270         if (driver_loaded) {
271                 UnregisterConnexionClient(clientID);
272                 CleanupConnexionHandlers();
273                 unload_driver();
274
275                 ghost_system = NULL;
276                 ndof_manager = NULL;
277         }
278 }
279
280 bool GHOST_NDOFManagerCocoa::available()
281 {
282         return driver_loaded;
283 }