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