From e35cab33f0b2d6dd493f87bc004f4f705cc14deb Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sun, 2 Aug 2026 21:22:42 +1200 Subject: [PATCH] Extract a SpaceMouseBackend seam ahead of a future Windows/macOS backend The user asked for phase 2 (Windows/macOS via 3Dconnexion's proprietary 3DxWare SDK) on top of #635. This sandbox has no 3DxWare SDK, no Windows toolchain, and no macOS toolchain -- nothing to compile, link, or run a single line of platform code against, unlike the Linux/libspnav path, which was built and actually tested here for real. Writing 3DxWare integration code that has never even built would be a materially weaker, unverifiable thing sitting in this PR, so it is not in this commit. What is: the structural seam that makes adding it later a contained, reviewable change instead of a rewrite of code that already works. ## Before SpaceMouseListener did three unrelated things in one class: own the libspnav connection, read spnav events, and apply motion to the active DiagramView. A Windows/macOS backend would have had to either duplicate all of the DiagramView-facing logic (the pan/zoom calls, the Z-to-zoom- factor mapping, the "which view is active" lookup -- all already verified) or bolt onto the same class with a maze of #ifdefs. Either way, touching that file again would put the already-tested Linux path back in scope for review. ## After - SpaceMouseBackend: a tiny interface (isAvailable(), a motion(dx,dy,dz) signal). A backend's only job is owning one platform's connection to the driver/daemon and translating its native event into this one signal. - SpnavBackend: the libspnav code from the previous commit, moved behind that interface with no behaviour change -- still spnav_open() in the constructor, still a QSocketNotifier on spnav_fd(), still silent when no daemon/device is present. - SpaceMouseListener: now backend-agnostic. Owns whichever backend the platform provides, applies its motion to the active DiagramView exactly as before. The DiagramView-facing code (pan/zoom calls, zoomFactorForZAxis) did not need to change at all -- only its input changed from a spnav_event_motion struct to three plain ints. A future 3DxWare backend implements SpaceMouseBackend, is selected in SpaceMouseListener's constructor behind its own QET_SPACEMOUSE_BACKEND_3DXWARE guard (see the comment marking exactly where), and never has to touch SpnavBackend or SpaceMouseListener's DiagramView-facing half. ## CMake: one user option, one define per backend QET_ENABLE_SPACEMOUSE is unchanged as the single option a user sets. Internally, find_spacemouse.cmake now decides *which* backend (if any) that resolves to: on Linux with libspnav found, QET_SPACEMOUSE_BACKEND_SPNAV plus the umbrella QET_SPACEMOUSE_SUPPORT. Turning the option on anywhere else today downgrades cleanly with a warning naming discussion #599, instead of trying (and failing) to find libspnav on a platform that doesn't ship it. Adding 3DxWare later means adding one more branch here, not restructuring this file. ## Verified this is a pure refactor, not just "still compiles" Reconfigured and rebuilt both ways from scratch: - option off: unchanged from before -- no new source files compiled, zero new object code. - option on: both new files compile with zero warnings, binary still links against libspnav.so.0 (confirmed via ldd), and run to completion in this environment (which has no spacenavd) with zero crashes and zero spnav-related output -- identical to before the refactor. - zoomFactorForZAxis re-linked and re-run in isolation: identical output to the pre-refactor commit (z=0 -> exactly 1.0, z=+-350 -> 1.35/0.65), confirming the math moved unchanged rather than being reimplemented. --- CMakeLists.txt | 6 +- cmake/find_spacemouse.cmake | 52 +++++++---- cmake/qet_compilation_vars.cmake | 8 ++ sources/spacemouse/spacemousebackend.h | 71 ++++++++++++++ sources/spacemouse/spacemouselistener.cpp | 108 +++++++++------------- sources/spacemouse/spacemouselistener.h | 63 +++++++------ sources/spacemouse/spnavbackend.cpp | 78 ++++++++++++++++ sources/spacemouse/spnavbackend.h | 58 ++++++++++++ 8 files changed, 329 insertions(+), 115 deletions(-) create mode 100644 sources/spacemouse/spacemousebackend.h create mode 100644 sources/spacemouse/spnavbackend.cpp create mode 100644 sources/spacemouse/spnavbackend.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b9ab0212..1923f6f36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,9 +146,11 @@ else() ) endif() -if(QET_SPACEMOUSE_ENABLED) - set(QET_SPACEMOUSE_LIBRARIES PkgConfig::SPNAV) +if(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED) + list(APPEND QET_SPACEMOUSE_LIBRARIES PkgConfig::SPNAV) endif() +# A future Windows/macOS (3DxWare) backend would list.APPEND its own SDK +# libraries here, behind its own *_ENABLED guard, alongside this one. target_link_libraries( ${PROJECT_NAME} diff --git a/cmake/find_spacemouse.cmake b/cmake/find_spacemouse.cmake index 8275b50aa..6f47ae803 100644 --- a/cmake/find_spacemouse.cmake +++ b/cmake/find_spacemouse.cmake @@ -20,28 +20,44 @@ message(" - find_spacemouse") # so none of this runs, and the default build is entirely unaffected: no new # dependency, no new source files, no new symbols. # -# When it is on, libspnav is looked for via its pkg-config file (present on -# every distro package of it this was checked against: Debian/Ubuntu's -# libspnav-dev ships /usr/share/pkgconfig/spnav.pc). If it isn't found, this -# does not hard-fail the whole configure -- it downgrades the option back to -# off with a clear message, so a developer who doesn't have the library -# installed still gets a normal build instead of a configure error for an -# opt-in feature they didn't ask to block on. +# When it is on, the platform decides which backend (if any) is available -- +# see sources/spacemouse/spacemousebackend.h for what a backend is. Only one +# exists today: SpnavBackend, Linux, via libspnav. A Windows/macOS backend +# would need 3Dconnexion's proprietary 3DxWare SDK and is not implemented +# (see discussion #599's own phase split); until it is, turning this option +# on there downgrades cleanly with a warning rather than failing configure. set(QET_SPACEMOUSE_ENABLED FALSE) +set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED FALSE) if(QET_ENABLE_SPACEMOUSE) - find_package(PkgConfig) - if(PkgConfig_FOUND) - pkg_check_modules(SPNAV IMPORTED_TARGET spnav) - endif() + if(UNIX AND NOT APPLE) + # libspnav is looked for via its pkg-config file (present on every + # distro package of it this was checked against: Debian/Ubuntu's + # libspnav-dev ships /usr/share/pkgconfig/spnav.pc). Not found does + # not hard-fail the whole configure -- it downgrades the option back + # to off with a clear message, so a developer who doesn't have the + # library installed still gets a normal build instead of a configure + # error for an opt-in feature they didn't ask to block on. + find_package(PkgConfig) + if(PkgConfig_FOUND) + pkg_check_modules(SPNAV IMPORTED_TARGET spnav) + endif() - if(SPNAV_FOUND) - set(QET_SPACEMOUSE_ENABLED TRUE) - add_definitions(-DQET_SPACEMOUSE_SUPPORT) - message("QET_ENABLE_SPACEMOUSE ON (libspnav ${SPNAV_VERSION} found)") + if(SPNAV_FOUND) + set(QET_SPACEMOUSE_ENABLED TRUE) + set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED TRUE) + add_definitions(-DQET_SPACEMOUSE_SUPPORT) + add_definitions(-DQET_SPACEMOUSE_BACKEND_SPNAV) + message("QET_ENABLE_SPACEMOUSE ON (backend: libspnav ${SPNAV_VERSION})") + else() + message(WARNING "QET_ENABLE_SPACEMOUSE is ON but libspnav was not found via pkg-config " + "(install libspnav-dev, or the equivalent for your distribution) -- " + "building WITHOUT 3D mouse support.") + endif() else() - message(WARNING "QET_ENABLE_SPACEMOUSE is ON but libspnav was not found via pkg-config " - "(install libspnav-dev, or the equivalent for your distribution) -- " - "building WITHOUT 3D mouse support.") + message(WARNING "QET_ENABLE_SPACEMOUSE is ON but no 3D mouse backend is implemented yet " + "for this platform (only Linux/libspnav exists today -- see " + "https://github.com/qelectrotech/qelectrotech-source-mirror/discussions/599) " + "-- building WITHOUT 3D mouse support.") endif() endif() diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index 7f3ec60c3..17d01d290 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -796,11 +796,19 @@ endif() if(QET_SPACEMOUSE_ENABLED) list(APPEND QET_SRC_FILES + ${QET_DIR}/sources/spacemouse/spacemousebackend.h ${QET_DIR}/sources/spacemouse/spacemouselistener.cpp ${QET_DIR}/sources/spacemouse/spacemouselistener.h ) endif() +if(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED) + list(APPEND QET_SRC_FILES + ${QET_DIR}/sources/spacemouse/spnavbackend.cpp + ${QET_DIR}/sources/spacemouse/spnavbackend.h + ) +endif() + set(TS_FILES ${QET_DIR}/lang/qet_ar.ts ${QET_DIR}/lang/qet_ca.ts diff --git a/sources/spacemouse/spacemousebackend.h b/sources/spacemouse/spacemousebackend.h new file mode 100644 index 000000000..0eb294521 --- /dev/null +++ b/sources/spacemouse/spacemousebackend.h @@ -0,0 +1,71 @@ +/* + 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 . +*/ +#ifndef SPACEMOUSEBACKEND_H +#define SPACEMOUSEBACKEND_H + +#include + +/** + @brief The SpaceMouseBackend class + Platform seam for discussion #599's 3D mouse support. A backend owns + one platform's connection to the actual 6-DOF device driver -- opening + it, pumping whatever event source that platform uses, closing it -- and + reports motion through the one signal below. Everything platform- + independent (which DiagramView to apply motion to, the pan/zoom + primitives to call, the Z-to-zoom-factor mapping) lives in + SpaceMouseListener instead, once, so it does not have to be duplicated + or re-verified per backend. + + SpnavBackend (Linux, spacenavd/libspnav) is the only implementation so + far -- built, linked, and its "no daemon/device present" path actually + run in the environment this was written in. A Windows/macOS backend + (3Dconnexion's proprietary 3DxWare SDK) would implement this same + interface and be selected in SpaceMouseListener's constructor, without + changing SpnavBackend or anything downstream of the motion signal. + Deliberately not attempted here: this was written on Linux with no + 3DxWare SDK and no Windows/macOS toolchain available to compile, + link, or run a single line of it against, and shipping platform code + that has never even built would be a materially different, weaker + thing than everything else in this feature. +*/ +class SpaceMouseBackend : public QObject +{ + Q_OBJECT + + public: + explicit SpaceMouseBackend(QObject *parent = nullptr) : QObject(parent) {} + ~SpaceMouseBackend() override = default; + + /// True once this backend actually has a live connection to a + /// driver/daemon. False is the ordinary case -- no daemon + /// running, no device attached -- not an error; see + /// SpaceMouseListener's class comment for why that distinction + /// matters. + virtual bool isAvailable() const = 0; + + signals: + /// One raw device sample. dx/dy are the two axes + /// SpaceMouseListener maps to horizontal/vertical pan, dz the + /// one it maps to zoom. Units and range are whatever the + /// backend's own driver reports -- SpaceMouseListener's own + /// scale/divisor constants are what turn them into pixels and a + /// zoom factor, not this signal. + void motion(int dx, int dy, int dz); +}; + +#endif // SPACEMOUSEBACKEND_H diff --git a/sources/spacemouse/spacemouselistener.cpp b/sources/spacemouse/spacemouselistener.cpp index e66df5338..1752fd2bd 100644 --- a/sources/spacemouse/spacemouselistener.cpp +++ b/sources/spacemouse/spacemouselistener.cpp @@ -17,24 +17,27 @@ */ #include "spacemouselistener.h" +#include "spacemousebackend.h" +#ifdef QET_SPACEMOUSE_BACKEND_SPNAV +# include "spnavbackend.h" +#endif + #include "../diagramview.h" #include "../projectview.h" #include "../qetdiagrameditor.h" #include #include -#include - -#include namespace { - //A spnav motion delta is an integer on roughly the same order of + //A device motion delta is an integer on roughly the same order of //magnitude as a QWheelEvent::angleDelta() tick (about +-120 per //detent, more under a hard push/twist -- exact range depends on the - //user's spacenavd sensitivity setting, which is configured once - //outside QET and out of scope here). DiagramView::wheelEvent() - //already turns such a tick into a small per-event zoom step via - //zoom(1 + value/1000); reused as a starting point. + //backend and the user's own driver-level sensitivity setting, which + //is configured outside QET and out of scope here). + //DiagramView::wheelEvent() already turns such a tick into a small + //per-event zoom step via zoom(1 + value/1000); reused as a starting + //point. // //Neither this divisor nor PAN_SCALE below has been calibrated //against real hardware -- there is none in the environment this was @@ -46,38 +49,40 @@ namespace { /** @brief SpaceMouseListener::SpaceMouseListener - Try to connect to spacenavd. Failure -- no daemon running, no device - attached -- is left silent: it is the expected state for most users and - must never surface as an error dialog or a log warning on every - ordinary startup. + Construct whichever backend is available for this platform and connect + its motion() signal. If none is compiled in, or the one that is can't + reach a device, isAvailable() simply stays false -- see the class + comment. @param parent */ SpaceMouseListener::SpaceMouseListener(QObject *parent) : QObject(parent) { - if (spnav_open() == -1) { - return; - } +#ifdef QET_SPACEMOUSE_BACKEND_SPNAV + m_backend = new SpnavBackend(this); +#endif + //A future Windows/macOS backend (3Dconnexion's proprietary 3DxWare + //SDK) slots in here behind its own QET_SPACEMOUSE_BACKEND_3DXWARE + //guard, without changing anything below this constructor. - m_available = true; - m_notifier = new QSocketNotifier(spnav_fd(), QSocketNotifier::Read, this); - connect(m_notifier, &QSocketNotifier::activated, - this, &SpaceMouseListener::readEvents); + if (m_backend) { + connect(m_backend, &SpaceMouseBackend::motion, + this, &SpaceMouseListener::applyMotion); + } } /** - @brief SpaceMouseListener::~SpaceMouseListener + @brief SpaceMouseListener::isAvailable + @return whether the platform backend has a live device connection */ -SpaceMouseListener::~SpaceMouseListener() +bool SpaceMouseListener::isAvailable() const { - if (m_available) { - spnav_close(); - } + return m_backend && m_backend->isAvailable(); } /** @brief SpaceMouseListener::zoomFactorForZAxis - @param z : raw Z-axis (push/pull) delta from a spnav motion event + @param z : raw Z-axis (push/pull) delta from a device motion sample @return the multiplicative factor DiagramView::zoom() expects */ qreal SpaceMouseListener::zoomFactorForZAxis(int z) @@ -86,45 +91,22 @@ qreal SpaceMouseListener::zoomFactorForZAxis(int z) } /** - @brief SpaceMouseListener::readEvents - Called when the spacenavd socket has data available. Drains every event - currently queued -- spnav_poll_event() returns 0 once the queue is - empty -- rather than handling just one per activation, so events cannot - silently back up if several arrive between two Qt event loop turns. -*/ -void SpaceMouseListener::readEvents() -{ - spnav_event event; - while (spnav_poll_event(&event)) - { - if (event.type == SPNAV_EVENT_MOTION) { - dispatchMotion(event.motion); - } - //Button events (SPNAV_EVENT_BUTTON) are deliberately not - //handled: mapping device buttons to QET actions is the "Related, - //not proposed here" follow-up in discussion #599, not this - //phase. - } -} - -/** - @brief SpaceMouseListener::dispatchMotion - Apply one motion event to whichever DiagramView is currently active. + @brief SpaceMouseListener::applyMotion + Apply one motion sample to whichever DiagramView is currently active. X/Y translation pans it, Z translation zooms it -- the same two primitives (scrollbars, DiagramView::zoom()) DiagramView::wheelEvent() already drives from a physical wheel, so there is no new navigation logic here, only a new input source feeding the existing one. - A 6-DOF device also reports rotation (rx, ry, rz); QET's view has - nothing rotation maps to, so those three axes are read by nothing here. - - Which of the three translation axes is "left/right" vs "forward/back" - vs "up/down" on the physical device, and their sign, is a hardware - convention this could not be checked against real hardware while - writing it -- see the PR description. - @param motion + Which of a device's three translation axes is "left/right" vs + "forward/back" vs "up/down", and their sign, is a hardware convention + this could not be checked against real hardware while writing it -- see + the PR description. + @param dx + @param dy + @param dz */ -void SpaceMouseListener::dispatchMotion(const spnav_event_motion &motion) +void SpaceMouseListener::applyMotion(int dx, int dy, int dz) { auto *editor = qobject_cast(qApp->activeWindow()); if (!editor) { @@ -141,15 +123,15 @@ void SpaceMouseListener::dispatchMotion(const spnav_event_motion &motion) return; } - if (motion.x || motion.y) + if (dx || dy) { view->horizontalScrollBar()->setValue( - view->horizontalScrollBar()->value() - qRound(motion.x * PAN_SCALE)); + view->horizontalScrollBar()->value() - qRound(dx * PAN_SCALE)); view->verticalScrollBar()->setValue( - view->verticalScrollBar()->value() - qRound(motion.y * PAN_SCALE)); + view->verticalScrollBar()->value() - qRound(dy * PAN_SCALE)); } - if (motion.z) { - view->zoom(zoomFactorForZAxis(motion.z)); + if (dz) { + view->zoom(zoomFactorForZAxis(dz)); } } diff --git a/sources/spacemouse/spacemouselistener.h b/sources/spacemouse/spacemouselistener.h index 5424a07f9..d91cc213d 100644 --- a/sources/spacemouse/spacemouselistener.h +++ b/sources/spacemouse/spacemouselistener.h @@ -20,27 +20,31 @@ #include -class QSocketNotifier; -struct spnav_event_motion; +class SpaceMouseBackend; /** @brief The SpaceMouseListener class - Phase 1 (Linux, libspnav) of https://github.com/qelectrotech/qelectrotech-source-mirror/discussions/599 : - bridges a 3Dconnexion SpaceMouse/SpacePilot 6-DOF device, via the - spacenavd daemon and libspnav, to DiagramView's existing pan/zoom - primitives (the same horizontalScrollBar()/verticalScrollBar()/zoom() - calls DiagramView::wheelEvent() already uses for a physical wheel). + bridges a 3Dconnexion SpaceMouse/SpacePilot 6-DOF device to + DiagramView's existing pan/zoom primitives (the same + horizontalScrollBar()/verticalScrollBar()/zoom() calls + DiagramView::wheelEvent() already uses for a physical wheel). - Only compiled in when the QET_ENABLE_SPACEMOUSE CMake option is on. Even - then, constructing one is always safe: when no spacenavd is running or - no device is attached -- the expected state for the overwhelming - majority of users, even of a build with the option on -- it silently - does nothing rather than failing or nagging the user. There is exactly - one of these, owned by QETApp, because a physical 6-DOF device is a - single ambient input source for the whole application, not something - tied to one window; motion is applied to whichever DiagramView is - currently active (see targetView()). + Everything here is platform-independent: which DiagramView to apply + motion to, the pan/zoom calls, and the Z-to-zoom-factor mapping. + Talking to the actual device driver is a SpaceMouseBackend's job (see + its class comment) -- this class owns one and applies whatever it + reports, without knowing or caring which platform it came from. + + Only compiled in when QET_SPACEMOUSE_SUPPORT is defined. Even then, + constructing one is always safe: if no backend is available for this + platform, or the one that exists can't reach a driver/daemon (no + device attached -- the expected state for the overwhelming majority of + users, even of a build with the option on), this silently does nothing + rather than failing or nagging the user. There is exactly one of + these, owned by QETApp, because a physical 6-DOF device is a single + ambient input source for the whole application, not something tied to + one window. */ class SpaceMouseListener : public QObject { @@ -48,32 +52,27 @@ class SpaceMouseListener : public QObject public: explicit SpaceMouseListener(QObject *parent = nullptr); - ~SpaceMouseListener() override; + ~SpaceMouseListener() override = default; - /// True once a connection to spacenavd was established. - /// False is the common case, not an error -- see the class - /// comment -- so callers should not warn the user when this - /// is false. - bool isAvailable() const {return m_available;} + /// True once the platform backend has a live connection to a + /// driver/daemon. False is the common case, not an error -- see + /// the class comment -- so callers should not warn the user + /// when this is false. + bool isAvailable() const; /// Pure translation from a device Z-axis delta to the /// multiplicative factor DiagramView::zoom() expects. A free /// function so the mapping can be unit-tested without a live - /// spacenavd connection or a real device. + /// backend connection or a real device. static qreal zoomFactorForZAxis(int z); private slots: - /// Drain and dispatch every event currently queued on the - /// spacenavd socket. Connected to a QSocketNotifier on that - /// socket's fd rather than polled on a timer, so this is - /// idle-cost-free between events. - void readEvents(); + /// Apply one motion sample -- from whichever backend is in use + /// -- to whichever DiagramView is currently active. + void applyMotion(int dx, int dy, int dz); private: - void dispatchMotion(const spnav_event_motion &motion); - - bool m_available = false; - QSocketNotifier *m_notifier = nullptr; + SpaceMouseBackend *m_backend = nullptr; }; #endif // SPACEMOUSELISTENER_H diff --git a/sources/spacemouse/spnavbackend.cpp b/sources/spacemouse/spnavbackend.cpp new file mode 100644 index 000000000..f27d75107 --- /dev/null +++ b/sources/spacemouse/spnavbackend.cpp @@ -0,0 +1,78 @@ +/* + 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 . +*/ +#include "spnavbackend.h" + +#include + +#include + +/** + @brief SpnavBackend::SpnavBackend + Try to connect to spacenavd. Failure -- no daemon running, no device + attached -- is left silent: it is the expected state for most users and + must never surface as an error dialog or a log warning on every + ordinary startup. + @param parent +*/ +SpnavBackend::SpnavBackend(QObject *parent) : + SpaceMouseBackend(parent) +{ + if (spnav_open() == -1) { + return; + } + + m_available = true; + m_notifier = new QSocketNotifier(spnav_fd(), QSocketNotifier::Read, this); + connect(m_notifier, &QSocketNotifier::activated, + this, &SpnavBackend::readEvents); +} + +/** + @brief SpnavBackend::~SpnavBackend +*/ +SpnavBackend::~SpnavBackend() +{ + if (m_available) { + spnav_close(); + } +} + +/** + @brief SpnavBackend::readEvents + Called when the spacenavd socket has data available. Drains every event + currently queued -- spnav_poll_event() returns 0 once the queue is + empty -- rather than handling just one per activation, so events cannot + silently back up if several arrive between two Qt event loop turns. +*/ +void SpnavBackend::readEvents() +{ + spnav_event event; + while (spnav_poll_event(&event)) + { + if (event.type == SPNAV_EVENT_MOTION) { + //A 6-DOF device also reports rotation (rx, ry, rz); QET's + //view has nothing rotation maps to, so those three axes are + //read by nothing here. + emit motion(event.motion.x, event.motion.y, event.motion.z); + } + //Button events (SPNAV_EVENT_BUTTON) are deliberately not + //handled: mapping device buttons to QET actions is the "Related, + //not proposed here" follow-up in discussion #599, not this + //phase. + } +} diff --git a/sources/spacemouse/spnavbackend.h b/sources/spacemouse/spnavbackend.h new file mode 100644 index 000000000..76c85f63f --- /dev/null +++ b/sources/spacemouse/spnavbackend.h @@ -0,0 +1,58 @@ +/* + 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 . +*/ +#ifndef SPNAVBACKEND_H +#define SPNAVBACKEND_H + +#include "spacemousebackend.h" + +class QSocketNotifier; + +/** + @brief The SpnavBackend class + Linux SpaceMouseBackend, via the spacenavd daemon and libspnav. Only + compiled in when QET_SPACEMOUSE_BACKEND_SPNAV is defined (set by + cmake/find_spacemouse.cmake once it has actually found libspnav). + + Connecting is always safe even when no daemon is running or no device + is attached -- the expected state for the overwhelming majority of + users, even of a build with 3D mouse support compiled in -- this + silently leaves isAvailable() false rather than failing loudly. +*/ +class SpnavBackend : public SpaceMouseBackend +{ + Q_OBJECT + + public: + explicit SpnavBackend(QObject *parent = nullptr); + ~SpnavBackend() override; + + bool isAvailable() const override {return m_available;} + + private slots: + /// Drain and emit every event currently queued on the spacenavd + /// socket. Connected to a QSocketNotifier on that socket's fd + /// rather than polled on a timer, so this is idle-cost-free + /// between events. + void readEvents(); + + private: + bool m_available = false; + QSocketNotifier *m_notifier = nullptr; +}; + +#endif // SPNAVBACKEND_H