mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-30 06:34:13 +02:00
124b7e4f6e90100a3d2f64fafa52e6c1917df4d4
9 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e7f19de2da |
Mention ConnexionBackend in SpaceMouseBackend's class comment
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
72cf86ece0 |
Open the 3D mouse non-exclusively on macOS
hidapi's macOS backend opens every device with kIOHIDOptionsTypeSeizeDevice unless told otherwise (hid_init() calls hid_darwin_set_open_exclusive(1) for backward compatibility). When 3DxWare is running it already holds the SpacePilot/SpaceMouse, so the seize fails, hid_open_path() returns NULL and HidBackend::scan() keeps retrying every 3 s without ever finding the device. Reported in discussion #599: a SpacePilot Pro (046d:c629) is listed by hid_enumerate() and works in 3DxWare, but has no effect in QET's macOS build. Enumeration never opens a device, so it did not exercise this. Guarded on HID_API_VERSION >= 0.12, where the setter first appeared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
aaeaff55cc |
Add a hidapi 3D mouse backend that needs no 3Dconnexion driver
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> |
||
|
|
17ebbffbca |
Add 3D mouse speed, direction, dead zone and twist-to-zoom settings
The 3D mouse's pan and zoom speeds were fixed guesses, and each sample was applied as it came, so the speed on screen depended on how often the driver sends samples -- different for every platform and device. Motion now goes through SpaceMouseMotion::map(), which scales each sample by the time since the previous one, and applies the user's settings from a new "Mouvement" section of Configuration > Souris 3D: pan and zoom speed, a dead zone, inverting each axis, and zooming by push/pull (as before) or by twisting the cap. The defaults keep the previous behaviour. Zoom is now exponential in the deflection, so the factor stays positive however hard the cap is pulled (1 + z/1000 went negative past z = -1000) and an equal push and pull cancel out. Sub- pixel pan is carried over between samples instead of being rounded away. The backend now reports all six axes. tst_spacemousemotion covers the mapping without a device and is built whether or not QET_ENABLE_SPACEMOUSE is on. The new behaviour was also checked end to end with tools/spnav-shim (qelectrotech-docker): twist with a dead zone of 10 ignores push/pull and small drift, and a twist of 60 gives the same frame as a push of 50 with the defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
04e2279a7f |
Make the 3D mouse pan and zoom the element editor too
The 3D mouse only acted on the diagram editor: SpaceMouseListener ignored every other window, so the element editor did not move at all (reported by scorpio810 in PR #635 with a SpacePilot Pro). applyMotion() now also handles a QETElementEditor, driving its ElementView through the same scrollbar pan and a new ElementView::zoom(factor), which keeps the wheel zoom's clamping. The element editor's scene rect only covers what is on screen, so it is grown before each pan sample, as its middle-button pan does; without that the scrollbars have no range and the pan does nothing. Verified under Xvfb with an LD_PRELOAD stand-in for libspnav feeding recorded motion samples: element editor zooms 1.63x for ten z=50 samples and pans; diagram editor screenshots are byte-identical to master's for the same input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
83b9f32bd0 |
Add device-button-to-action bindings, for any 3D mouse backend
Discussion #599's own scope explicitly deferred this ("Related, not proposed here... a natural follow-up once basic pan/zoom motion works"). Basic pan/zoom now works (previous commits on this branch), so this adds it -- generically, for whichever SpaceMouseBackend is in use, not tied to libspnav specifically, matching the seam the previous commit built. ## Reuses ShortcutManager instead of inventing a second action registry ShortcutManager is already an app-wide registry of every named, rebindable action -- undo, redo, rotate selection, cut/copy/paste, autonum configure, and dozens more -- each carried by a live QAction or QAbstractButton. A device button binding to one of *those* ids, rather than to a bespoke QET-3D-mouse-only action list, means the discussion's own examples (rotate/mirror/undo) are available for free, and any action added to the app in the future is automatically bindable too. Added ShortcutManager::trigger(id): find the first still-alive target for an id and call QAction::trigger() or QAbstractButton::click(), whichever it is. Deliberately not disambiguated by which window is currently active, unlike SpaceMouseListener's own pan/zoom dispatch -- a target's owning top-level window isn't reliably discoverable from a bare QAction. Correct in the overwhelming common case of one open editor window; documented in the header as a known simplification, not silently assumed correct. ## The binding itself: SpaceMouseButtonMap A thin QSettings-backed button-number -> action-id map, unbound by default for every button on every device -- nothing happens on any button press until the user opens Configuration > Souris 3D and binds something, matching this whole feature's "silent until asked for" default. ## Backend side: SpaceMouseBackend::buttonPressed(int) Added to the platform interface alongside the existing motion() signal. SpnavBackend now handles SPNAV_EVENT_BUTTON (previously explicitly ignored) and emits on press only -- release is not reported, since nothing downstream has a use for it. A future non-spnav backend implements the same signal and gets button support for free through SpaceMouseListener::applyButton(), without that logic being duplicated or re-verified per backend -- the same reasoning the previous commit's seam was built around. ## Configuration UI: SpaceMouseConfigPage Modelled directly on the existing ShortcutsConfigPage -- same QTableWidget shape, same "persist on applyConf(), not live" contract -- one row per binding: button number (spin box, unbounded, since button count and numbering genuinely vary from 2 to 30+ across real devices and this could not be checked against hardware) and action (combo box populated from ShortcutManager::instance().allShortcuts(), the exact same live registry the Shortcuts page itself lists). Only added to the Configuration dialog when QET_SPACEMOUSE_SUPPORT is compiled in. ## Verified, including the one thing that doesn't need hardware to prove Rebuilt from scratch both ways: option off adds zero new object code (confirmed via a forced rebuild of the one unconditionally-changed file, shortcutmanager.cpp, which alone picked up new warning-free code); option on compiles all four new/changed files warning-free and links clean. The backend's button *detection* (SPNAV_EVENT_BUTTON -> buttonPressed signal) still cannot be verified without a real device or daemon -- same limitation as the motion path from the previous commits, stated plainly rather than glossed over. What *is* fully verified, because none of it needs hardware: - SpaceMouseButtonMap: unbound by default, set/read-back, clearing via an empty id, enumeration -- all confirmed via a standalone harness linked against the real compiled objects. - ShortcutManager::trigger(): registered a real QAction, confirmed trigger() fires it exactly once and returns true; confirmed it returns false (not a crash) for an unknown id. - SpnavBackend: constructs safely with no daemon present (isAvailable() false, as it must be), and both its motion and buttonPressed signals are correctly wired per Qt's own metaobject data (QSignalSpy). - The configuration page end-to-end, via a real Xvfb session: opened Configuration > Souris 3D, confirmed the action combo box lists the live, real ShortcutManager registry (undo, rotate, cut/copy/paste, dozens more -- not a mock), added rows, edited the button number, removed rows, selected "Éditeur de schémas — Pivoter" (Rotate -- the discussion's own example) for button 3, clicked OK, and confirmed via the actual settings file that it persisted exactly as "buttons\3=diagrameditor.rotate_selection". Reopened the dialog and confirmed it read back correctly. This is a full, real round trip through the UI, not a claim. |
||
|
|
e35cab33f0 |
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. |
||
|
|
2e4bb486bb |
Add 3D mouse (SpaceMouse/SpacePilot) pan/zoom support via libspnav
Implements discussion #599's phase 1 (Linux, libspnav): a 3Dconnexion 6-DOF device pans and zooms the active diagram view, via spacenavd. ## Off by default, zero cost when off QET_ENABLE_SPACEMOUSE (cmake/developer_options.cmake) is OFF. Verified in two separate build directories from a clean configure: with it off, the new cmake/find_spacemouse.cmake step runs and does nothing (no library lookup, no definition, no new source files compiled), and qetapp.cpp/.h produce zero new object code -- both are entirely #ifdef'd out. The default build is byte-for-byte the same shape as before this commit. With it on, libspnav is located via its pkg-config file (spnav.pc, shipped by libspnav-dev on Debian/Ubuntu and equivalent packages elsewhere). If the option is on but the library isn't found, this does not hard-fail configure: it downgrades back to off with a warning, so an opt-in feature never blocks a developer who doesn't have the library installed. ## No new navigation logic -- a new input source for the existing one DiagramView::wheelEvent() already turns a physical wheel's delta into horizontalScrollBar()/verticalScrollBar() calls for pan and a zoom(1 + value/1000) call for zoom -- see diagramview.cpp:661-687. SpaceMouseListener calls exactly those same primitives from spnav motion events instead of wheel events. It does not reimplement panning or zooming. ## Safe by construction even when compiled in The overwhelming majority of users of a build with the option on still won't have spacenavd running or a device attached -- that must never surface as an error dialog or a startup warning. SpaceMouseListener:: isAvailable() reflects this: spnav_open() failing is treated as the ordinary case, not an error, and the object then does nothing at all. Verified for real in this environment, which genuinely has no spacenavd running: built with the option on, ran the resulting binary to completion, and confirmed zero crashes and zero spnav-related output of any kind -- the silence is the point. Motion is read via a QSocketNotifier on spnav_fd() (event-driven, no polling loop, no idle cost) and applied to whichever DiagramView is currently active, found via qApp->activeWindow() -> QETDiagramEditor -> currentProjectView() -> currentDiagram(): a 6-DOF device is one ambient input source for the whole application, not something tied to a particular window, so there is exactly one listener, owned by QETApp. ## What could not be verified without hardware The Z-axis-to-zoom-factor mapping (SpaceMouseListener::zoomFactorForZAxis) is a pure function specifically so it could be tested without a live device: confirmed a centered device (z=0) yields exactly 1.0 (an exact no-op, not an epsilon-off value that could trip DiagramView::zoom()'s >=1 branch), and that push/pull produce symmetric zoom-in/out factors. What genuinely cannot be checked in this environment: which physical axis is "left/right" vs "up/down" vs "push/forward", their sign, and whether the ZOOM_DIVISOR/PAN_SCALE constants feel right on real hardware. Both are named constants specifically so recalibrating them is a one-line change once someone with a device tries it -- flagged plainly in the PR rather than presented as verified. ## Not in this commit Windows/macOS (proprietary 3DxWare SDK, materially bigger lift) and device button mapping are both explicitly out of scope for this phase per the discussion. |