mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-03 17:34:12 +02:00
aaeaff55cc
The 3D mouse only worked on Linux, through spacenavd. This adds a second backend that reads the device directly over USB through hidapi, with no 3Dconnexion driver or SDK: the route to Windows and macOS (discussion #599), and usable on Linux without spacenavd. SpaceMouseHid decodes the raw reports from the device's own report descriptor -- where each axis and button sits, its range, absolute or relative -- so no per-model table is needed, with the classic report 1/2/3 layout as a fallback when the descriptor cannot be read and the 0x1c button list newer devices send. Absolute axes are rescaled to +-500 exactly as spacenavd does, so both backends give QET the same values. HidBackend polls from the main thread (fast while moving, slow when still), emits one sample per poll, and looks for a device every 3 s so plugging one in or back in needs no restart. QET_SPACEMOUSE_BACKEND (auto, spnav, hid) picks the backend; auto keeps libspnav on Linux when it is found and uses hidapi otherwise. hidapi is found through pkg-config as hidapi-hidraw (Linux) or hidapi (MSYS2, Homebrew). A sample arriving in the same millisecond as the previous one now counts for no time instead of a full period, so a burst of queued samples no longer moves the view further than the time it covers. Tested without a device: tst_spacemousehid (descriptor parsing, broken and hostile descriptors, every report form, recordings from real devices once they are added to fixtures/spacemouse), and end to end on Linux through a virtual USB device created with /dev/uhid: the same moves give byte-identical screenshots through the hidapi and libspnav backends, an absolute axis is rescaled as spacenavd does, buttons trigger their bound action, and unplugging and replugging while QET runs (including with a dialog open that a device button opened) reconnects cleanly. Not tested on Windows, macOS or real hardware. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
3.5 KiB
C++
108 lines
3.5 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#ifndef SPACEMOUSEHID_H
|
|
#define SPACEMOUSEHID_H
|
|
|
|
#include "spacemousemotion.h"
|
|
|
|
#include <QByteArray>
|
|
#include <QList>
|
|
#include <QSet>
|
|
#include <QVector>
|
|
|
|
/**
|
|
Decoding of a 3D mouse's raw USB HID reports, for the backends that read
|
|
the device directly (HidBackend) rather than through a driver that has
|
|
already decoded them (spacenavd). No I/O here, so it is unit-tested from
|
|
recorded bytes (tests/qttest/tst_spacemousehid).
|
|
*/
|
|
namespace SpaceMouseHid
|
|
{
|
|
/// USB vendor ids 3Dconnexion devices use: Logitech's for the older
|
|
/// ones, 3Dconnexion's own for everything since about 2011.
|
|
constexpr unsigned short VENDOR_LOGITECH = 0x046d;
|
|
constexpr unsigned short VENDOR_3DCONNEXION = 0x256f;
|
|
|
|
/// @return whether a HID interface is a 3D mouse: a 3Dconnexion
|
|
/// vendor id and the Generic Desktop "multi-axis controller" usage.
|
|
/// Logitech devices that do not report a usage are matched by
|
|
/// product id instead, from the list of their 3D mice.
|
|
bool isSpaceMouse(unsigned short vendor, unsigned short product,
|
|
unsigned short usage_page, unsigned short usage);
|
|
|
|
/// Where one value sits in the reports, per the report descriptor.
|
|
struct Field
|
|
{
|
|
int report_id = 0; ///< 0 when the device does not number its reports
|
|
int bit_offset = 0; ///< after the report id byte, if any
|
|
int bit_size = 0;
|
|
int logical_min = 0;
|
|
int logical_max = 0;
|
|
bool relative = false;
|
|
|
|
bool isValid() const { return bit_size > 0; }
|
|
};
|
|
|
|
/// What the report descriptor says about the six axes and the buttons.
|
|
struct Layout
|
|
{
|
|
Field axes[6]; ///< x, y, z, rx, ry, rz
|
|
QVector<Field> buttons; ///< button n (0-based) is buttons[n]
|
|
bool numbered_reports = false;
|
|
|
|
bool hasAxes() const;
|
|
};
|
|
|
|
/// @return the layout described by \a descriptor, or one where
|
|
/// hasAxes() is false if it describes no axes (or is empty)
|
|
Layout parseDescriptor(const QByteArray &descriptor);
|
|
|
|
/// @return the fixed layout of the classic 3Dconnexion reports
|
|
/// (1 = translation, 2 = rotation, 3 = button bitmask), used when
|
|
/// the descriptor cannot be read
|
|
Layout fallbackLayout();
|
|
|
|
/**
|
|
@brief The Decoder class
|
|
Turns a stream of raw reports into samples and button presses.
|
|
A device can send translation and rotation in separate reports, so
|
|
the decoder keeps the last value of every axis and reports all six
|
|
each time any of them changes.
|
|
*/
|
|
class Decoder
|
|
{
|
|
public:
|
|
struct Result
|
|
{
|
|
bool motion = false; ///< sample is new
|
|
SpaceMouseSample sample;
|
|
QList<int> pressed; ///< buttons pressed since the previous report
|
|
};
|
|
|
|
explicit Decoder(const Layout &layout = fallbackLayout());
|
|
Result feed(const QByteArray &report);
|
|
|
|
private:
|
|
Layout m_layout;
|
|
int m_axes[6] = {0, 0, 0, 0, 0, 0};
|
|
QSet<int> m_down; ///< buttons currently held
|
|
};
|
|
}
|
|
|
|
#endif // SPACEMOUSEHID_H
|