1 #include "libslic3r/libslic3r.h"
2 #include "libslic3r/PresetBundle.hpp"
3 #include "Mouse3DController.hpp"
4 
5 #include "Camera.hpp"
6 #include "GUI_App.hpp"
7 #include "GLCanvas3D.hpp"
8 #include "Plater.hpp"
9 #include "NotificationManager.hpp"
10 
11 #include <wx/glcanvas.h>
12 
13 #include <boost/nowide/convert.hpp>
14 #include <boost/log/trivial.hpp>
15 #include "I18N.hpp"
16 
17 #include <bitset>
18 
19 //unofficial linux lib
20 //#include <spnav.h>
21 
22 // WARN: If updating these lists, please also update resources/udev/90-3dconnexion.rules
23 
24 static const std::vector<int> _3DCONNEXION_VENDORS =
25 {
26     0x046d,  // LOGITECH = 1133 // Logitech (3Dconnexion is made by Logitech)
27     0x256F   // 3DCONNECTION = 9583 // 3Dconnexion
28 };
29 
30 // See: https://github.com/FreeSpacenav/spacenavd/blob/a9eccf34e7cac969ee399f625aef827f4f4aaec6/src/dev.c#L202
31 static const std::vector<int> _3DCONNEXION_DEVICES =
32 {
33     0xc603,	/* 50691 spacemouse plus XT */
34     0xc605,	/* 50693 cadman */
35     0xc606,	/* 50694 spacemouse classic */
36     0xc621,	/* 50721 spaceball 5000 */
37     0xc623,	/* 50723 space traveller */
38     0xc625,	/* 50725 space pilot */
39     0xc626,	/* 50726 space navigator *TESTED* */
40     0xc627,	/* 50727 space explorer */
41     0xc628,	/* 50728 space navigator for notebooks*/
42     0xc629,	/* 50729 space pilot pro*/
43     0xc62b,	/* 50731 space mouse pro*/
44     0xc62e,	/* 50734 spacemouse wireless (USB cable) *TESTED* */
45     0xc62f,	/* 50735 spacemouse wireless receiver */
46     0xc631,	/* 50737 spacemouse pro wireless *TESTED* */
47     0xc632,	/* 50738 spacemouse pro wireless receiver */
48     0xc633,	/* 50739 spacemouse enterprise */
49     0xc635,	/* 50741 spacemouse compact *TESTED* */
50     0xc636,	/* 50742 spacemouse module */
51     0xc640,	/* 50752 nulooq */
52     0xc652, /* 50770 3Dconnexion universal receiver *TESTED* */
53 };
54 
55 namespace Slic3r {
56 namespace GUI {
57 
58 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
59 template<typename T>
update_maximum(std::atomic<T> & maximum_value,T const & value)60 void update_maximum(std::atomic<T>& maximum_value, T const& value) noexcept
61 {
62     T prev_value = maximum_value;
63     while (prev_value < value && ! maximum_value.compare_exchange_weak(prev_value, value)) ;
64 }
65 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
66 
append_translation(const Vec3d & translation,size_t input_queue_max_size)67 void Mouse3DController::State::append_translation(const Vec3d& translation, size_t input_queue_max_size)
68 {
69     std::scoped_lock<std::mutex> lock(m_input_queue_mutex);
70     while (m_input_queue.size() >= input_queue_max_size)
71         m_input_queue.pop_front();
72     m_input_queue.emplace_back(QueueItem::translation(translation));
73 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
74     update_maximum(input_queue_max_size_achieved, m_input_queue.size());
75 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
76 }
77 
append_rotation(const Vec3f & rotation,size_t input_queue_max_size)78 void Mouse3DController::State::append_rotation(const Vec3f& rotation, size_t input_queue_max_size)
79 {
80     std::scoped_lock<std::mutex> lock(m_input_queue_mutex);
81     while (m_input_queue.size() >= input_queue_max_size)
82         m_input_queue.pop_front();
83     m_input_queue.emplace_back(QueueItem::rotation(rotation.cast<double>()));
84 #ifdef WIN32
85 	if (rotation.x() != 0.0f)
86         ++ m_mouse_wheel_counter;
87 #endif // WIN32
88 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
89     update_maximum(input_queue_max_size_achieved, m_input_queue.size());
90 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
91 }
92 
append_button(unsigned int id,size_t)93 void Mouse3DController::State::append_button(unsigned int id, size_t /* input_queue_max_size */)
94 {
95     std::scoped_lock<std::mutex> lock(m_input_queue_mutex);
96     m_input_queue.emplace_back(QueueItem::buttons(id));
97 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
98     update_maximum(input_queue_max_size_achieved, m_input_queue.size());
99 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
100 }
101 
102 #ifdef _WIN32
103 #if ENABLE_CTRL_M_ON_WINDOWS
format_device_string(int vid,int pid)104 static std::string format_device_string(int vid, int pid)
105 {
106     std::string ret;
107 
108     switch (vid)
109     {
110     case 0x046d: { ret = "LOGITECH"; break; }
111     case 0x256F: { ret = "3DCONNECTION"; break; }
112     default:     { ret = "UNKNOWN"; break; }
113     }
114 
115     ret += "::";
116 
117     switch (pid)
118     {
119     case 0xc603: { ret += "spacemouse plus XT"; break; }
120     case 0xc605: { ret += "cadman"; break; }
121     case 0xc606: { ret += "spacemouse classic"; break; }
122     case 0xc621: { ret += "spaceball 5000"; break; }
123     case 0xc623: { ret += "space traveller"; break; }
124     case 0xc625: { ret += "space pilot"; break; }
125     case 0xc626: { ret += "space navigator"; break; }
126     case 0xc627: { ret += "space explorer"; break; }
127     case 0xc628: { ret += "space navigator for notebooks"; break; }
128     case 0xc629: { ret += "space pilot pro"; break; }
129     case 0xc62b: { ret += "space mouse pro"; break; }
130     case 0xc62e: { ret += "spacemouse wireless (USB cable)"; break; }
131     case 0xc62f: { ret += "spacemouse wireless receiver"; break; }
132     case 0xc631: { ret += "spacemouse pro wireless"; break; }
133     case 0xc632: { ret += "spacemouse pro wireless receiver"; break; }
134     case 0xc633: { ret += "spacemouse enterprise"; break; }
135     case 0xc635: { ret += "spacemouse compact"; break; }
136     case 0xc636: { ret += "spacemouse module"; break; }
137     case 0xc640: { ret += "nulooq"; break; }
138     case 0xc652: { ret += "3Dconnexion universal receiver"; break; }
139     default:     { ret += "UNKNOWN"; break; }
140     }
141 
142     return ret;
143 }
144 
detect_attached_device()145 static std::string detect_attached_device()
146 {
147     std::string ret;
148 
149     // Initialize the hidapi library
150     int res = hid_init();
151     if (res != 0)
152         BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library";
153     else {
154         // Enumerates devices
155         hid_device_info* devices = hid_enumerate(0, 0);
156         if (devices == nullptr)
157             BOOST_LOG_TRIVIAL(trace) << "detect_attached_device() - no HID device enumerated.";
158         else {
159             // Searches for 1st connected 3Dconnexion device
160             struct DeviceData
161             {
162                 unsigned short usage_page{ 0 };
163                 unsigned short usage{ 0 };
164 
165                 DeviceData(unsigned short usage_page, unsigned short usage)
166                     : usage_page(usage_page), usage(usage)
167                 {}
168 
169                 // https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
170                 // Usage page 1 - Generic Desktop Controls
171                 // Usage page 1, usage 8 - Multi-axis Controller
172                 bool has_valid_usage() const { return usage_page == 1 && usage == 8; }
173             };
174 
175             // When using 3Dconnexion universal receiver, multiple devices are detected sharing the same vendor_id and product_id.
176             // To choose from them the right one we use: usage_page == 1 and usage == 8
177             // When only a single device is detected, as for wired connections, vendor_id and product_id are enough
178 
179             // First we count all the valid devices from the enumerated list,
180 
181             hid_device_info* current = devices;
182             typedef std::pair<unsigned short, unsigned short> DeviceIds;
183             typedef std::vector<DeviceData> DeviceDataList;
184             typedef std::map<DeviceIds, DeviceDataList> DetectedDevices;
185             DetectedDevices detected_devices;
186             while (current != nullptr) {
187                 unsigned short vendor_id = 0;
188                 unsigned short product_id = 0;
189 
190                 for (size_t i = 0; i < _3DCONNEXION_VENDORS.size(); ++i) {
191                     if (_3DCONNEXION_VENDORS[i] == current->vendor_id) {
192                         vendor_id = current->vendor_id;
193                         break;
194                     }
195                 }
196 
197                 if (vendor_id != 0) {
198                     for (size_t i = 0; i < _3DCONNEXION_DEVICES.size(); ++i) {
199                         if (_3DCONNEXION_DEVICES[i] == current->product_id) {
200                             product_id = current->product_id;
201                             DeviceIds detected_device(vendor_id, product_id);
202                             DetectedDevices::iterator it = detected_devices.find(detected_device);
203                             if (it == detected_devices.end())
204                                 it = detected_devices.insert(DetectedDevices::value_type(detected_device, DeviceDataList())).first;
205 
206                             it->second.emplace_back(current->usage_page, current->usage);
207                         }
208                     }
209                 }
210 
211                 current = current->next;
212             }
213 
214             // Free enumerated devices
215             hid_free_enumeration(devices);
216 
217             unsigned short vendor_id = 0;
218             unsigned short product_id = 0;
219             if (!detected_devices.empty()) {
220                 // Then we'll decide the choosing logic to apply in dependence of the device count and operating system
221                 for (const DetectedDevices::value_type& device : detected_devices) {
222                     if (device.second.size() == 1) {
223                         if (device.second.front().has_valid_usage()) {
224                             vendor_id = device.first.first;
225                             product_id = device.first.second;
226                             break;
227                         }
228                     }
229                     else {
230                         bool found = false;
231                         for (const DeviceData& data : device.second) {
232                             if (data.has_valid_usage()) {
233                                 vendor_id = device.first.first;
234                                 product_id = device.first.second;
235                                 found = true;
236                                 break;
237                             }
238                         }
239 
240                         if (found)
241                             break;
242                     }
243                 }
244             }
245 
246             if (vendor_id != 0 && product_id != 0) {
247                 ret = format_device_string(static_cast<int>(vendor_id), static_cast<int>(product_id));
248                 BOOST_LOG_TRIVIAL(trace) << "Detected device: " << std::hex << vendor_id << std::dec << "::" << std::hex << product_id << std::dec << " " << ret;
249             }
250             else
251                 BOOST_LOG_TRIVIAL(trace) << "No 3DConnexion device detected";
252         }
253 
254         // Finalize the hidapi library
255         hid_exit();
256     }
257 
258     return ret;
259 }
260 #endif // ENABLE_CTRL_M_ON_WINDOWS
261 
262 // Called by Win32 HID enumeration callback.
device_attached(const std::string & device)263 void Mouse3DController::device_attached(const std::string &device)
264 {
265 	int vid = 0;
266 	int pid = 0;
267 	if (sscanf(device.c_str(), "\\\\?\\HID#VID_%x&PID_%x&", &vid, &pid) == 2) {
268 //    BOOST_LOG_TRIVIAL(trace) << boost::format("Mouse3DController::device_attached(VID_%04xxPID_%04x)") % vid % pid;
269 //    BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::device_attached: " << device;
270 	    if (std::find(_3DCONNEXION_VENDORS.begin(), _3DCONNEXION_VENDORS.end(), vid) != _3DCONNEXION_VENDORS.end()) {
271 			// Signal the worker thread to wake up and enumerate HID devices, if not connected at the moment.
272 			// The message may come multiple times per each USB device. For example, some USB wireless dongles register as multiple HID sockets
273 			// for multiple devices to connect to.
274 			// Never mind, enumeration will be performed until connected.
275 		    m_wakeup = true;
276 			m_stop_condition.notify_all();
277 #if ENABLE_CTRL_M_ON_WINDOWS
278             m_device_str = format_device_string(vid, pid);
279             if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end()) {
280                 std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
281                 m_params = m_params_ui = it_params->second;
282             }
283             else
284                 m_params_by_device[format_device_string(vid, pid)] = Params();
285             m_connected = true;
286 #endif // ENABLE_CTRL_M_ON_WINDOWS
287         }
288 	}
289 }
290 
291 #if ENABLE_CTRL_M_ON_WINDOWS
device_detached(const std::string & device)292 void Mouse3DController::device_detached(const std::string& device)
293 {
294     int vid = 0;
295     int pid = 0;
296     if (sscanf(device.c_str(), "\\\\?\\HID#VID_%x&PID_%x&", &vid, &pid) == 2) {
297         if (std::find(_3DCONNEXION_VENDORS.begin(), _3DCONNEXION_VENDORS.end(), vid) != _3DCONNEXION_VENDORS.end()) {
298             std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
299             m_params_by_device[format_device_string(vid, pid)] = m_params_ui;
300         }
301     }
302     m_device_str = "";
303     m_connected = false;
304 }
305 #endif // ENABLE_CTRL_M_ON_WINDOWS
306 
307 // Filter out mouse scroll events produced by the 3DConnexion driver.
process_mouse_wheel()308 bool Mouse3DController::State::process_mouse_wheel()
309 {
310     std::scoped_lock<std::mutex> lock(m_input_queue_mutex);
311     if (m_mouse_wheel_counter == 0)
312         // No 3DConnexion rotation has been captured since the last mouse scroll event.
313         return false;
314     if (std::find_if(m_input_queue.begin(), m_input_queue.end(), [](const QueueItem &item){ return item.is_rotation(); }) != m_input_queue.end()) {
315         // There is a rotation stored in the queue. Suppress one mouse scroll event.
316         -- m_mouse_wheel_counter;
317         return true;
318     }
319     m_mouse_wheel_counter = 0;
320     return true;
321 }
322 #endif // _WIN32
323 
apply(const Mouse3DController::Params & params,Camera & camera)324 bool Mouse3DController::State::apply(const Mouse3DController::Params &params, Camera& camera)
325 {
326     if (! wxGetApp().IsActive())
327         return false;
328 
329     std::deque<QueueItem> input_queue;
330     {
331     	// Atomically move m_input_queue to input_queue.
332     	std::scoped_lock<std::mutex> lock(m_input_queue_mutex);
333     	input_queue = std::move(m_input_queue);
334         m_input_queue.clear();
335     }
336 
337     for (const QueueItem &input_queue_item : input_queue) {
338     	if (input_queue_item.is_translation()) {
339             Vec3d translation = params.swap_yz ? Vec3d(input_queue_item.vector.x(), - input_queue_item.vector.z(), input_queue_item.vector.y()) : input_queue_item.vector;
340             double zoom_factor = camera.min_zoom() / camera.get_zoom();
341 	        camera.set_target(camera.get_target() + zoom_factor * params.translation.scale * (translation.x() * camera.get_dir_right() + translation.z() * camera.get_dir_up()));
342             if (translation.y() != 0.0)
343                 camera.update_zoom(params.zoom.scale * translation.y());
344         } else if (input_queue_item.is_rotation()) {
345             Vec3d rot = params.rotation.scale * input_queue_item.vector * (PI / 180.);
346             if (params.swap_yz)
347                 rot = Vec3d(rot.x(), -rot.z(), rot.y());
348             camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y()));
349 	    } else {
350 	    	assert(input_queue_item.is_buttons());
351 	        switch (input_queue_item.type_or_buttons) {
352 	        case 0: camera.update_zoom(1.0); break;
353 	        case 1: camera.update_zoom(-1.0); break;
354             default: break;
355 	        }
356     	}
357     }
358 
359     return ! input_queue.empty();
360 }
361 
362 // Load the device parameter database from appconfig. To be called on application startup.
load_config(const AppConfig & appconfig)363 void Mouse3DController::load_config(const AppConfig &appconfig)
364 {
365 	// We do not synchronize m_params_by_device with the background thread explicitely
366 	// as there should be a full memory barrier executed once the background thread is started.
367 	m_params_by_device.clear();
368 
369 	for (const std::string &device_name : appconfig.get_mouse_device_names()) {
370 	    double translation_speed 	= 4.0;
371 	    float  rotation_speed 		= 4.0;
372 	    double translation_deadzone = Params::DefaultTranslationDeadzone;
373 	    float  rotation_deadzone 	= Params::DefaultRotationDeadzone;
374 	    double zoom_speed 			= 2.0;
375         bool   swap_yz              = false;
376         appconfig.get_mouse_device_translation_speed(device_name, translation_speed);
377 	    appconfig.get_mouse_device_translation_deadzone(device_name, translation_deadzone);
378 	    appconfig.get_mouse_device_rotation_speed(device_name, rotation_speed);
379 	    appconfig.get_mouse_device_rotation_deadzone(device_name, rotation_deadzone);
380 	    appconfig.get_mouse_device_zoom_speed(device_name, zoom_speed);
381         appconfig.get_mouse_device_swap_yz(device_name, swap_yz);
382         // clamp to valid values
383 	    Params params;
384 	    params.translation.scale = Params::DefaultTranslationScale * std::clamp(translation_speed, 0.1, 10.0);
385 	    params.translation.deadzone = std::clamp(translation_deadzone, 0.0, Params::MaxTranslationDeadzone);
386 	    params.rotation.scale = Params::DefaultRotationScale * std::clamp(rotation_speed, 0.1f, 10.0f);
387 	    params.rotation.deadzone = std::clamp(rotation_deadzone, 0.0f, Params::MaxRotationDeadzone);
388 	    params.zoom.scale = Params::DefaultZoomScale * std::clamp(zoom_speed, 0.1, 10.0);
389         params.swap_yz = swap_yz;
390         m_params_by_device[device_name] = std::move(params);
391 	}
392 }
393 
394 // Store the device parameter database back to appconfig. To be called on application closeup.
save_config(AppConfig & appconfig) const395 void Mouse3DController::save_config(AppConfig &appconfig) const
396 {
397 	// We do not synchronize m_params_by_device with the background thread explicitely
398 	// as there should be a full memory barrier executed once the background thread is stopped.
399 
400 	for (const std::pair<std::string, Params> &key_value_pair : m_params_by_device) {
401 		const std::string &device_name = key_value_pair.first;
402 		const Params      &params      = key_value_pair.second;
403 	    // Store current device parameters into the config
404         appconfig.set_mouse_device(device_name, params.translation.scale / Params::DefaultTranslationScale, params.translation.deadzone,
405             params.rotation.scale / Params::DefaultRotationScale, params.rotation.deadzone, params.zoom.scale / Params::DefaultZoomScale, params.swap_yz);
406     }
407 }
408 
apply(Camera & camera)409 bool Mouse3DController::apply(Camera& camera)
410 {
411     // check if the user unplugged the device
412     if (! m_connected) {
413         // hides the settings dialog if the user un-plug the device
414         m_show_settings_dialog = false;
415         m_settings_dialog_closed_by_user = false;
416     }
417 
418 #if ENABLE_CTRL_M_ON_WINDOWS
419 #ifdef _WIN32
420     {
421         std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
422         if (m_params_ui_changed) {
423             m_params = m_params_ui;
424             m_params_ui_changed = false;
425         }
426     }
427 #endif // _WIN32
428 #endif // ENABLE_CTRL_M_ON_WINDOWS
429 
430     return m_state.apply(m_params, camera);
431 }
432 
render_settings_dialog(GLCanvas3D & canvas) const433 void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
434 {
435     if (! m_show_settings_dialog || ! m_connected)
436         return;
437 
438     // when the user clicks on [X] or [Close] button we need to trigger
439     // an extra frame to let the dialog disappear
440     if (m_settings_dialog_closed_by_user) {
441         m_show_settings_dialog = false;
442         m_settings_dialog_closed_by_user = false;
443         canvas.request_extra_frame();
444         return;
445     }
446 
447     Params params_copy;
448     bool   params_changed = false;
449     {
450     	std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
451     	params_copy = m_params_ui;
452     }
453 
454     Size cnv_size = canvas.get_canvas_size();
455 
456     ImGuiWrapper& imgui = *wxGetApp().imgui();
457     imgui.set_next_window_pos(0.5f * (float)cnv_size.get_width(), 0.5f * (float)cnv_size.get_height(), ImGuiCond_Always, 0.5f, 0.5f);
458 
459     static ImVec2 last_win_size(0.0f, 0.0f);
460     bool shown = true;
461     if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) {
462         if (shown) {
463             ImVec2 win_size = ImGui::GetWindowSize();
464             if (last_win_size.x != win_size.x || last_win_size.y != win_size.y) {
465                 // when the user clicks on [X] button, the next time the dialog is shown
466                 // has a dummy size, so we trigger an extra frame to let it have the correct size
467                 last_win_size = win_size;
468                 canvas.request_extra_frame();
469             }
470 
471             const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator);
472             imgui.text_colored(color, _L("Device:"));
473             ImGui::SameLine();
474             imgui.text(m_device_str);
475 
476             ImGui::Separator();
477             imgui.text_colored(color, _L("Speed:"));
478 
479             float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale;
480             if (imgui.slider_float(_L("Translation") + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
481             	params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale;
482             	params_changed = true;
483             }
484 
485             float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale;
486             if (imgui.slider_float(_L("Rotation") + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
487             	params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale;
488             	params_changed = true;
489             }
490 
491             float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale;
492             if (imgui.slider_float(_L("Zoom"), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
493             	params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale;
494             	params_changed = true;
495             }
496 
497             ImGui::Separator();
498             imgui.text_colored(color, _L("Deadzone:"));
499 
500             float translation_deadzone = (float)params_copy.translation.deadzone;
501             if (imgui.slider_float(_L("Translation") + "/" + _L("Zoom"), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
502             	params_copy.translation.deadzone = (double)translation_deadzone;
503             	params_changed = true;
504             }
505 
506             float rotation_deadzone = params_copy.rotation.deadzone;
507             if (imgui.slider_float(_L("Rotation") + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
508             	params_copy.rotation.deadzone = rotation_deadzone;
509             	params_changed = true;
510             }
511 
512             ImGui::Separator();
513             imgui.text_colored(color, _L("Options:"));
514 
515             bool swap_yz = params_copy.swap_yz;
516             if (imgui.checkbox(_L("Swap Y/Z axes"), swap_yz)) {
517                 params_copy.swap_yz = swap_yz;
518                 params_changed = true;
519             }
520 
521 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
522             ImGui::Separator();
523             ImGui::Separator();
524             imgui.text_colored(color, "DEBUG:");
525             imgui.text_colored(color, "Vectors:");
526             Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast<float>();
527             Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast<float>();
528             ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
529             ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
530 
531             imgui.text_colored(color, "Queue size:");
532 
533             int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) };
534             ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly);
535 
536             int input_queue_size_param = int(params_copy.input_queue_max_size);
537             if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) {
538                 if (input_queue_size_param > 0) {
539 	            	params_copy.input_queue_max_size = input_queue_size_param;
540     	        	params_changed = true;
541                 }
542             }
543 
544             ImGui::Separator();
545             imgui.text_colored(color, "Camera:");
546             Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>();
547             ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
548 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
549 
550             ImGui::Separator();
551             if (imgui.button(_L("Close"))) {
552                 // the user clicked on the [Close] button
553                 m_settings_dialog_closed_by_user = true;
554                 canvas.set_as_dirty();
555             }
556         }
557         else {
558             // the user clicked on the [X] button
559             m_settings_dialog_closed_by_user = true;
560             canvas.set_as_dirty();
561         }
562     }
563 
564     imgui.end();
565 
566     if (params_changed) {
567         // Synchronize front end parameters to back end.
568     	std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
569         auto pthis = const_cast<Mouse3DController*>(this);
570 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
571         if (params_copy.input_queue_max_size != params_copy.input_queue_max_size)
572         	// Reset the statistics counter.
573             m_state.input_queue_max_size_achieved = 0;
574 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
575         pthis->m_params_ui = params_copy;
576         pthis->m_params_ui_changed = true;
577     }
578 }
579 
580 #if __APPLE__
581 
connected(std::string device_name)582 void Mouse3DController::connected(std::string device_name)
583 {
584     assert(! m_connected);
585     assert(m_device_str.empty());
586 	m_device_str = device_name;
587     // Copy the parameters for m_device_str into the current parameters.
588     if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end()) {
589     	std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
590     	m_params = m_params_ui = it_params->second;
591     }
592     m_connected = true;
593 }
594 
disconnected()595 void Mouse3DController::disconnected()
596 {
597     // Copy the current parameters for m_device_str into the parameter database.
598     assert(m_connected == ! m_device_str.empty());
599     if (m_connected) {
600         std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
601         m_params_by_device[m_device_str] = m_params_ui;
602 	    m_device_str.clear();
603 	    m_connected = false;
604 		wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected);
605 
606         wxGetApp().plater()->CallAfter([]() {
607         	Plater *plater = wxGetApp().plater();
608         	if (plater != nullptr) {
609 	        	plater->get_camera().recover_from_free_camera();
610     	   		plater->set_current_canvas_as_dirty();
611     	   	}
612     	});
613     }
614 }
615 
handle_input(const DataPacketAxis & packet)616 bool Mouse3DController::handle_input(const DataPacketAxis& packet)
617 {
618     if (! wxGetApp().IsActive())
619         return false;
620 
621     {
622     	// Synchronize parameters between the UI thread and the background thread.
623     	//FIXME is this necessary on OSX? Are these notifications triggered from the main thread or from a worker thread?
624     	std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
625     	if (m_params_ui_changed) {
626     		m_params = m_params_ui;
627     		m_params_ui_changed = false;
628     	}
629     }
630 
631     bool updated = false;
632     // translation
633     double deadzone = m_params.translation.deadzone;
634     Vec3d translation(std::abs(packet[0]) > deadzone ? -packet[0] : 0.0,
635                       std::abs(packet[1]) > deadzone ?  packet[1] : 0.0,
636                       std::abs(packet[2]) > deadzone ?  packet[2] : 0.0);
637     if (! translation.isApprox(Vec3d::Zero())) {
638         m_state.append_translation(translation, m_params.input_queue_max_size);
639         updated = true;
640     }
641     // rotation
642     deadzone = m_params.rotation.deadzone;
643     Vec3f rotation(std::abs(packet[3]) > deadzone ? (float)packet[3] : 0.0,
644                    std::abs(packet[4]) > deadzone ? (float)packet[4] : 0.0,
645                    std::abs(packet[5]) > deadzone ? (float)packet[5] : 0.0);
646     if (! rotation.isApprox(Vec3f::Zero())) {
647         m_state.append_rotation(rotation, m_params.input_queue_max_size);
648         updated = true;
649     }
650 
651     if (updated) {
652         wxGetApp().plater()->set_current_canvas_as_dirty();
653         // ask for an idle event to update 3D scene
654         wxWakeUpIdle();
655     }
656     return updated;
657 }
658 
659 #else //__APPLE__
660 
661 // Initialize the application.
init()662 void Mouse3DController::init()
663 {
664 #if ENABLE_CTRL_M_ON_WINDOWS
665 #ifdef _WIN32
666     m_device_str = detect_attached_device();
667     if (!m_device_str.empty()) {
668         m_connected = true;
669         if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end())
670             m_params = m_params_ui = it_params->second;
671     }
672 #endif // _WIN32
673 #endif // ENABLE_CTRL_M_ON_WINDOWS
674 
675 	assert(! m_thread.joinable());
676     if (! m_thread.joinable()) {
677     	m_stop = false;
678 #ifndef _WIN32
679     	// Don't start the background thread on Windows, as the HID messages are sent as Windows messages.
680 	    m_thread = std::thread(&Mouse3DController::run, this);
681 #endif // _WIN32
682 	}
683 }
684 
685 // Closing the application.
shutdown()686 void Mouse3DController::shutdown()
687 {
688     if (m_thread.joinable()) {
689     	// Stop the worker thread, if running.
690     	{
691     		// Notify the worker thread to cancel wait on detection polling.
692 			std::lock_guard<std::mutex> lock(m_stop_condition_mutex);
693 			m_stop = true;
694 		}
695 		m_stop_condition.notify_all();
696 		// Wait for the worker thread to stop.
697         m_thread.join();
698         m_stop = false;
699 	}
700 
701 #if ENABLE_CTRL_M_ON_WINDOWS
702 #ifdef _WIN32
703     if (!m_device_str.empty())
704         m_params_by_device[m_device_str] = m_params_ui;
705 #endif // _WIN32
706 #endif // ENABLE_CTRL_M_ON_WINDOWS
707 }
708 
709 // Main routine of the worker thread.
run()710 void Mouse3DController::run()
711 {
712     // Initialize the hidapi library
713     int res = hid_init();
714     if (res != 0) {
715     	// Give up.
716 #if defined(__unix__) || defined(__unix) || defined(unix)
717     	if (res == -1)
718     		// Hopefully this error code comes from our bundled patched hidapi. In that case, -1 is returned by hid_wrapper_udev_init() and it mean
719 			BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library: failed to load libudev.so.1 or libudev.so.0";
720     	else if (res == -2)
721     		// Hopefully this error code comes from our bundled patched hidapi. In that case, -2 is returned by hid_wrapper_udev_init() and it mean
722 			BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library: failed to resolve some function from libudev.so.1 or libudev.so.0";
723     	else
724 #endif // unixes
725 	        BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library";
726         return;
727     }
728 
729 #ifdef _WIN32
730     // Enumerate once just after thread start.
731 	m_wakeup = true;
732 #endif // _WIN32
733 
734     for (;;) {
735         {
736         	std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
737         	if (m_stop)
738         		break;
739         	if (m_params_ui_changed) {
740                 m_params = m_params_ui;
741         		m_params_ui_changed = false;
742         	}
743         }
744     	if (m_device == nullptr)
745     		// Polls the HID devices, blocks for maximum 2 seconds.
746     		m_connected = this->connect_device();
747     	else
748     		// Waits for 3DConnexion mouse input for maximum 100ms, then repeats.
749         	this->collect_input();
750     }
751 
752     this->disconnect_device();
753 
754     // Finalize the hidapi library
755     hid_exit();
756 }
757 
connect_device()758 bool Mouse3DController::connect_device()
759 {
760     if (m_stop)
761     	return false;
762 
763     {
764     	// Wait for 2 seconds, but cancellable by m_stop.
765     	std::unique_lock<std::mutex> lock(m_stop_condition_mutex);
766 #ifdef _WIN32
767     	// Wait indifinetely for the stop signal.
768         m_stop_condition.wait(lock, [this]{ return m_stop || m_wakeup; });
769         m_wakeup = false;
770 #else
771         m_stop_condition.wait_for(lock, std::chrono::seconds(2), [this]{ return m_stop; });
772 #endif
773     }
774 
775     if (m_stop)
776     	return false;
777 
778     // Enumerates devices
779     hid_device_info* devices = hid_enumerate(0, 0);
780     if (devices == nullptr) {
781         BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::connect_device() - no HID device enumerated.";
782         return false;
783     }
784 
785 #ifdef _WIN32
786     BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::connect_device() - enumerating HID devices.";
787 #endif // _WIN32
788 
789     // Searches for 1st connected 3Dconnexion device
790     struct DeviceData
791     {
792         std::string path;
793         unsigned short usage_page;
794         unsigned short usage;
795 
796         DeviceData()
797             : path(""), usage_page(0), usage(0)
798         {}
799         DeviceData(const std::string& path, unsigned short usage_page, unsigned short usage)
800             : path(path), usage_page(usage_page), usage(usage)
801         {}
802 
803         // https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
804         // Usage page 1 - Generic Desktop Controls
805         // Usage page 1, usage 8 - Multi-axis Controller
806         bool has_valid_usage() const { return usage_page == 1 && usage == 8; }
807     };
808 
809 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
810     hid_device_info* cur = devices;
811     std::cout << std::endl << "======================================================================================================================================" << std::endl;
812     std::cout << "Detected devices:" << std::endl;
813     while (cur != nullptr) {
814         std::cout << "\"";
815         std::wcout << ((cur->manufacturer_string != nullptr) ? cur->manufacturer_string : L"Unknown");
816         std::cout << "/";
817         std::wcout << ((cur->product_string != nullptr) ? cur->product_string : L"Unknown");
818         std::cout << "\" code: " << cur->vendor_id << "/" << cur->product_id << " (" << std::hex << cur->vendor_id << "/" << cur->product_id << std::dec << ")";
819         std::cout << " serial number: '";
820         std::wcout << ((cur->serial_number != nullptr) ? cur->serial_number : L"Unknown");
821         std::cout << "' usage page: " << cur->usage_page << " usage: " << cur->usage << " interface number: " << cur->interface_number << std::endl;
822 
823         cur = cur->next;
824     }
825 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
826 
827     // When using 3Dconnexion universal receiver, multiple devices are detected sharing the same vendor_id and product_id.
828     // To choose from them the right one we use:
829     // On Windows and Mac: usage_page == 1 and usage == 8
830     // On Linux: as usage_page and usage are not defined (see hidapi.h) we try all detected devices until one is succesfully open
831     // When only a single device is detected, as for wired connections, vendor_id and product_id are enough
832 
833     // First we count all the valid devices from the enumerated list,
834 
835     hid_device_info* current = devices;
836     typedef std::pair<unsigned short, unsigned short> DeviceIds;
837     typedef std::vector<DeviceData> DeviceDataList;
838     typedef std::map<DeviceIds, DeviceDataList> DetectedDevices;
839     DetectedDevices detected_devices;
840 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
841     std::cout << std::endl << "Detected 3D connexion devices:" << std::endl;
842 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
843     while (current != nullptr) {
844         unsigned short vendor_id = 0;
845         unsigned short product_id = 0;
846 
847         for (size_t i = 0; i < _3DCONNEXION_VENDORS.size(); ++i) {
848             if (_3DCONNEXION_VENDORS[i] == current->vendor_id) {
849                 vendor_id = current->vendor_id;
850                 break;
851             }
852         }
853 
854         if (vendor_id != 0) {
855             for (size_t i = 0; i < _3DCONNEXION_DEVICES.size(); ++i) {
856                 if (_3DCONNEXION_DEVICES[i] == current->product_id) {
857                     product_id = current->product_id;
858                     DeviceIds detected_device(vendor_id, product_id);
859                     DetectedDevices::iterator it = detected_devices.find(detected_device);
860                     if (it == detected_devices.end())
861                         it = detected_devices.insert(DetectedDevices::value_type(detected_device, DeviceDataList())).first;
862 
863                     it->second.emplace_back(current->path, current->usage_page, current->usage);
864 
865 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
866                     std::wcout << "\"" << ((current->manufacturer_string != nullptr) ? current->manufacturer_string : L"Unknown");
867                     std::cout << "/";
868                     std::wcout << ((current->product_string != nullptr) ? current->product_string : L"Unknown");
869                     std::cout << "\" code: " << current->vendor_id << "/" << current->product_id << " (" << std::hex << current->vendor_id << "/" << current->product_id << std::dec << ")";
870                     std::cout << " serial number: '";
871                     std::wcout << ((current->serial_number != nullptr) ? current->serial_number : L"Unknown");
872                     std::cout << "' usage page: " << current->usage_page << " usage: " << current->usage << std::endl;
873 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
874                 }
875             }
876         }
877 
878         current = current->next;
879     }
880 
881     // Free enumerated devices
882     hid_free_enumeration(devices);
883 
884     if (detected_devices.empty())
885         return false;
886 
887     std::string path;
888     unsigned short vendor_id = 0;
889     unsigned short product_id = 0;
890 
891     // Then we'll decide the choosing logic to apply in dependence of the device count and operating system
892 
893     for (const DetectedDevices::value_type& device : detected_devices) {
894         if (device.second.size() == 1) {
895 #if defined(__linux__)
896             hid_device* test_device = hid_open(device.first.first, device.first.second, nullptr);
897             if (test_device == nullptr) {
898                 BOOST_LOG_TRIVIAL(error) << "3DConnexion device cannot be opened: " << device.second.front().path <<
899                     " You may need to update /etc/udev/rules.d";
900             } else {
901                 hid_close(test_device);
902 #else
903             if (device.second.front().has_valid_usage()) {
904 #endif // __linux__
905                 vendor_id = device.first.first;
906                 product_id = device.first.second;
907                 break;
908             }
909         }
910         else {
911             bool found = false;
912 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
913             std::cout << std::endl;
914 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
915             for (const DeviceData& data : device.second) {
916 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
917                 std::cout << "Test device: " << std::hex << device.first.first << std::dec << "/" << std::hex << device.first.second << std::dec << " \"" << data.path << "\"";
918 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
919 
920 #ifdef __linux__
921                 hid_device* test_device = hid_open_path(data.path.c_str());
922                 if (test_device != nullptr) {
923                     path = data.path;
924                     vendor_id = device.first.first;
925                     product_id = device.first.second;
926                     found = true;
927 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
928                     std::cout << "-> PASSED" << std::endl;
929 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
930                     hid_close(test_device);
931                     break;
932                 }
933 #else // !__linux__
934                 if (data.has_valid_usage()) {
935                     path = data.path;
936                     vendor_id = device.first.first;
937                     product_id = device.first.second;
938                     found = true;
939 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
940                     std::cout << "-> PASSED" << std::endl;
941 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
942                     break;
943                 }
944 #endif // __linux__
945                 else {
946                     BOOST_LOG_TRIVIAL(error) << "3DConnexion device cannot be opened: " << data.path <<
947                         " You may need to update /etc/udev/rules.d";
948 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
949                     std::cout << "-> NOT PASSED" << std::endl;
950 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
951                 }
952             }
953 
954             if (found)
955                 break;
956         }
957     }
958 
959     if (path.empty()) {
960         if ((vendor_id != 0) && (product_id != 0)) {
961             // Open the 3Dconnexion device using vendor_id and product_id
962 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
963             std::cout << std::endl << "Opening device: " << std::hex << vendor_id << std::dec << "/" << std::hex << product_id << std::dec << " using hid_open()" << std::endl;
964 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
965             m_device = hid_open(vendor_id, product_id, nullptr);
966         }
967         else
968             return false;
969     }
970     else {
971         // Open the 3Dconnexion device using the device path
972 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
973         std::cout << std::endl << "Opening device: " << std::hex << vendor_id << std::dec << "/" << std::hex << product_id << std::dec << "\"" << path << "\" using hid_open_path()" << std::endl;
974 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
975         m_device = hid_open_path(path.c_str());
976     }
977 
978     if (m_device != nullptr) {
979         wchar_t buffer[1024];
980         hid_get_manufacturer_string(m_device, buffer, 1024);
981         m_device_str = boost::nowide::narrow(buffer);
982         // #3479 seems to show that sometimes an extra whitespace is added, so we remove it
983         boost::algorithm::trim(m_device_str);
984 
985         hid_get_product_string(m_device, buffer, 1024);
986         m_device_str += "/" + boost::nowide::narrow(buffer);
987         // #3479 seems to show that sometimes an extra whitespace is added, so we remove it
988         boost::algorithm::trim(m_device_str);
989 
990         BOOST_LOG_TRIVIAL(info) << "Connected 3DConnexion device:";
991         BOOST_LOG_TRIVIAL(info) << "Manufacturer/product: " << m_device_str;
992         BOOST_LOG_TRIVIAL(info) << "Manufacturer id.....: " << vendor_id << " (" << std::hex << vendor_id << std::dec << ")";
993         BOOST_LOG_TRIVIAL(info) << "Product id..........: " << product_id << " (" << std::hex << product_id << std::dec << ")";
994         if (!path.empty())
995             BOOST_LOG_TRIVIAL(info) << "Path................: '" << path << "'";
996 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
997         std::cout << "Opened device." << std::endl;
998 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
999         // Copy the parameters for m_device_str into the current parameters.
1000         if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end()) {
1001 	    	std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
1002 	    	m_params = m_params_ui = it_params->second;
1003 	    }
1004     }
1005 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
1006     else {
1007         std::cout << std::endl << "Unable to connect to device:" << std::endl;
1008         std::cout << "Manufacturer/product: " << m_device_str << std::endl;
1009         std::cout << "Manufacturer id.....: " << vendor_id << " (" << std::hex << vendor_id << std::dec << ")" << std::endl;
1010         std::cout << "Product id..........: " << product_id << " (" << std::hex << product_id << std::dec << ")" << std::endl;
1011         std::cout << "Path................: '" << path << "'" << std::endl;
1012     }
1013 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
1014 
1015     return (m_device != nullptr);
1016 }
1017 
1018 void Mouse3DController::disconnect_device()
1019 {
1020     if (m_device) {
1021 	    hid_close(m_device);
1022 	    m_device = nullptr;
1023 	    BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str;
1024         // Copy the current parameters for m_device_str into the parameter database.
1025         {
1026 	        std::scoped_lock<std::mutex> lock(m_params_ui_mutex);
1027 	        m_params_by_device[m_device_str] = m_params_ui;
1028 	    }
1029 	    m_device_str.clear();
1030 	    m_connected = false;
1031 #ifdef _WIN32
1032 	    // Enumerate once immediately after disconnect.
1033 	    m_wakeup = true;
1034 #endif // _WIN32
1035         wxGetApp().plater()->CallAfter([]() {
1036         	Plater *plater = wxGetApp().plater();
1037         	if (plater != nullptr) {
1038 	        	plater->get_camera().recover_from_free_camera();
1039     	   		plater->set_current_canvas_as_dirty();
1040     	   	}
1041     	});
1042     }
1043 }
1044 
1045 void Mouse3DController::collect_input()
1046 {
1047     DataPacketRaw packet = { 0 };
1048     // Read packet, block maximum 100 ms. That means when closing the application, closing the application will be delayed by 100 ms.
1049     int res = hid_read_timeout(m_device, packet.data(), packet.size(), 100);
1050     if (res < 0) {
1051         // An error occourred (device detached from pc ?). Close the 3Dconnexion device.
1052         this->disconnect_device();
1053     } else
1054 		this->handle_input(packet, res, m_params, m_state);
1055 }
1056 
1057 #ifdef _WIN32
1058 bool Mouse3DController::handle_raw_input_win32(const unsigned char *data, const int packet_length)
1059 {
1060     if (! wxGetApp().IsActive())
1061         return false;
1062 
1063     if (packet_length == 7 || packet_length == 13) {
1064         DataPacketRaw packet;
1065     	memcpy(packet.data(), data, packet_length);
1066         handle_packet(packet, packet_length, m_params, m_state);
1067 #if ENABLE_CTRL_M_ON_WINDOWS
1068         m_connected = true;
1069 #endif // ENABLE_CTRL_M_ON_WINDOWS
1070     }
1071 
1072     return true;
1073 }
1074 #endif /* _WIN32 */
1075 
1076 // Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by the worker thread.
1077 bool Mouse3DController::handle_input(const DataPacketRaw& packet, const int packet_length, const Params &params, State &state_in_out)
1078 {
1079     if (! wxGetApp().IsActive())
1080         return false;
1081 
1082     int res = packet_length;
1083     bool updated = false;
1084 
1085     if (res == 7 || res == 13 ||
1086         // On Mac button packets can be 3 bytes long
1087        	((res == 3) && (packet[0] == 3)))
1088         updated = handle_packet(packet, res, params, state_in_out);
1089 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
1090     else if (res > 0)
1091         std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl;
1092 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
1093 
1094     if (updated) {
1095         wxGetApp().plater()->set_current_canvas_as_dirty();
1096         // ask for an idle event to update 3D scene
1097         wxWakeUpIdle();
1098     }
1099     return updated;
1100 }
1101 
1102 // Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by handle_input() from the worker thread.
1103 bool Mouse3DController::handle_packet(const DataPacketRaw& packet, const int packet_length, const Params &params, State &state_in_out)
1104 {
1105     switch (packet[0])
1106     {
1107     case 1: // Translation + Rotation
1108         {
1109             bool updated = handle_packet_translation(packet, params, state_in_out);
1110             if (packet_length == 13)
1111 	            updated |= handle_packet_rotation(packet, 7, params, state_in_out);
1112 
1113             if (updated)
1114                 return true;
1115 
1116             break;
1117         }
1118     case 2: // Rotation
1119         {
1120             if (handle_packet_rotation(packet, 1, params, state_in_out))
1121                 return true;
1122 
1123             break;
1124         }
1125     case 3: // Button
1126         {
1127             if (params.buttons_enabled && handle_packet_button(packet, packet.size() - 1, params, state_in_out))
1128                 return true;
1129 
1130             break;
1131         }
1132     case 23: // Battery charge
1133         {
1134 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
1135             std::cout << "3DConnexion - battery level: " << (int)packet[1] << " percent" << std::endl;
1136 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
1137             break;
1138         }
1139     default:
1140         {
1141 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
1142             std::cout << "3DConnexion - Got unknown data packet of code: " << (int)packet[0] << std::endl;
1143 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
1144             break;
1145         }
1146     }
1147 
1148     return false;
1149 }
1150 
1151 // Convert a signed 16bit word from a 3DConnexion mouse HID packet into a double coordinate, apply a dead zone.
1152 static double convert_input(int coord_byte_low, int coord_byte_high, double deadzone)
1153 {
1154     int value = coord_byte_low | (coord_byte_high << 8);
1155     if (value >= 32768)
1156     	value = value - 65536;
1157     double ret = (double)value / 350.0;
1158     return (std::abs(ret) > deadzone) ? ret : 0.0;
1159 }
1160 
1161 // Unpack raw 3DConnexion HID packet, decode state of translation axes into state_in_out. Called by handle_input() from the worker thread.
1162 bool Mouse3DController::handle_packet_translation(const DataPacketRaw& packet, const Params &params, State &state_in_out)
1163 {
1164     double deadzone = params.translation.deadzone;
1165     Vec3d translation(-convert_input(packet[1], packet[2], deadzone),
1166         convert_input(packet[3], packet[4], deadzone),
1167         convert_input(packet[5], packet[6], deadzone));
1168 
1169     if (!translation.isApprox(Vec3d::Zero()))
1170     {
1171         state_in_out.append_translation(translation, params.input_queue_max_size);
1172         return true;
1173     }
1174 
1175     return false;
1176 }
1177 
1178 // Unpack raw 3DConnexion HID packet, decode state of rotation axes into state_in_out. Called by the handle_input() from worker thread.
1179 bool Mouse3DController::handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte, const Params &params, State &state_in_out)
1180 {
1181     double deadzone = (double)params.rotation.deadzone;
1182     Vec3f rotation((float)convert_input(packet[first_byte + 0], packet[first_byte + 1], deadzone),
1183         (float)convert_input(packet[first_byte + 2], packet[first_byte + 3], deadzone),
1184         (float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone));
1185 
1186     if (!rotation.isApprox(Vec3f::Zero()))
1187     {
1188         state_in_out.append_rotation(rotation, params.input_queue_max_size);
1189         return true;
1190     }
1191 
1192     return false;
1193 }
1194 
1195 // Unpack raw 3DConnexion HID packet, decode button state into state_in_out. Called by handle_input() from the worker thread.
1196 bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size, const Params &params, State &state_in_out)
1197 {
1198     unsigned int data = 0;
1199     for (unsigned int i = 1; i < packet_size; ++i)
1200     {
1201         data |= packet[i] << 8 * (i - 1);
1202     }
1203 
1204     const std::bitset<32> data_bits{ data };
1205     for (size_t i = 0; i < data_bits.size(); ++i)
1206     {
1207         if (data_bits.test(i))
1208         {
1209             state_in_out.append_button((unsigned int)i, params.input_queue_max_size);
1210             return true;
1211         }
1212     }
1213 
1214     return false;
1215 }
1216 
1217 #endif //__APPLE__
1218 
1219 } // namespace GUI
1220 } // namespace Slic3r
1221