Files
qelectrotech-source-mirror/sources/spacemouse/connexionbackend.h
T
ispyisail 4381c6caa3 Read the 3D mouse through 3DxWare on macOS when it is installed
On macOS, 3DxWare installs a driver extension that takes the 3D mouse
over. With it installed, HidBackend opens the device but receives
nothing, so the mouse did nothing in QElectroTech until 3DxWare was
uninstalled (discussion #599, PR #1028). Most Mac owners of a 3D mouse
have 3DxWare installed.

ConnexionBackend asks 3DxWare for the motion instead, through
3DconnexionClient.framework, as Blender does. SpaceMouseListener tries
it first. When 3DxWare is not installed, or is installed but its driver
is not running, it falls back to HidBackend, so the device works in
both setups. Take-over mode stops 3DxWare's own actions in QET, so the
view does not move twice.

The library is loaded at run time from where 3DxWare installs it:
nothing is linked or bundled, and the build needs no SDK. The few
declarations are written here, from Blender's
GHOST_NDOFManagerCocoa.mm, because 3Dconnexion's SDK headers may not be
redistributed. 3DxWare's axes are y up and z away from the user; they
are mapped to QET's raw USB convention by comparing Blender's 3DxWare
and spacenavd code paths.

The release script signs with the hardened runtime, which refuses a
library another team signed. misc/qelectrotech.entitlements adds
com.apple.security.cs.disable-library-validation (Blender's notarized
build carries the same one), and MacQetDeploy_arm64_cmake.sh now passes
it to all four signings of the app, including the re-sign inside the
DMG.

Tested: tst_spacemouseconnexion runs the backend on every platform
against fakeconnexion, a stand-in library that answers from its own
thread as 3DxWare does: registration, the axis mapping, buttons, other
clients' messages, 3DxWare not installed or not running, deletion
with a message in flight. Flipping an axis sign or dropping the client
check turns it red. realLibrary() loads the real framework when
3DxWare is installed. Linux Qt 6 build with the 3D mouse enabled: all
18 tests pass.

Not tested: on a Mac with a real device. The axis signs and whether
buttons arrive as a bitmask with current 3DxWare are unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:54:07 +12:00

82 lines
2.8 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 CONNEXIONBACKEND_H
#define CONNEXIONBACKEND_H
#include "spacemousebackend.h"
#include <QLibrary>
/**
@brief The ConnexionBackend class
SpaceMouseBackend that reads the device through 3Dconnexion's own macOS
driver, 3DxWare, the way Blender does. Only compiled in on macOS
(cmake/find_spacemouse.cmake).
3DxWare installs a driver extension that takes the device over, so
with 3DxWare installed HidBackend can open the device but receives
nothing (discussion #599). This backend asks 3DxWare for the motion
instead. SpaceMouseListener tries it first and falls back to HidBackend
when 3DxWare is not installed or not running, so the device works
either way.
The client library, 3DconnexionClient.framework, is loaded at run time
from where 3DxWare installs it: nothing is linked, bundled or needed to
build. The few declarations below are written here, as Blender does,
rather than taken from 3Dconnexion's SDK, whose headers may not be
redistributed.
3DxWare delivers its messages on a thread of its own; they are passed
to the main thread before any signal is emitted.
*/
class ConnexionBackend : public SpaceMouseBackend
{
Q_OBJECT
public:
static const char DEFAULT_LIBRARY[];
explicit ConnexionBackend(QObject *parent = nullptr,
const QString &library = QString::fromLatin1(DEFAULT_LIBRARY));
~ConnexionBackend() override;
bool isAvailable() const override { return m_client != 0; }
/// 3DxWare's six axes, in its own order and signs, as QET's
/// raw USB convention (+x right, +y towards the user, +z down).
static SpaceMouseSample sampleFromAxes(const qint16 axis[6]);
/// The 0-based buttons set in \a now but not in \a before.
static QList<int> newlyPressed(quint32 before, quint32 now);
private:
static void onMessage(quint32 connection, quint32 type, void *argument);
void handleState(quint16 client, quint16 command,
const qint16 axis[6], quint32 buttons);
void shutdown();
QLibrary m_library;
quint16 m_client = 0;
bool m_handlers_installed = false;
quint32 m_buttons = 0;
void (*m_cleanup)() = nullptr;
void (*m_unregister)(quint16) = nullptr;
};
#endif // CONNEXIONBACKEND_H