mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-10 14:14:18 +02:00
Compare commits
8 Commits
ac40d110fb
...
d43c535cc9
| Author | SHA1 | Date | |
|---|---|---|---|
| d43c535cc9 | |||
| 59dc59cf2f | |||
| 469c8cbc70 | |||
| e7f19de2da | |||
| 4c9a307cb9 | |||
| 4381c6caa3 | |||
| 149ae961cc | |||
| 4f309770f0 |
@@ -25,6 +25,7 @@ message(" - find_spacemouse")
|
||||
# spnav Linux, through the spacenavd daemon (libspnav)
|
||||
# hid any platform, directly over USB (hidapi), no 3Dconnexion driver
|
||||
# auto spnav on Linux when libspnav is found, hid otherwise
|
||||
# On macOS the 3DxWare backend is added to whichever of these is chosen.
|
||||
# A backend whose library is not found downgrades the option to off with a
|
||||
# warning, rather than failing configure for an opt-in feature.
|
||||
set(QET_SPACEMOUSE_BACKEND "auto" CACHE STRING "3D mouse backend: auto, spnav or hid")
|
||||
@@ -33,6 +34,7 @@ set_property(CACHE QET_SPACEMOUSE_BACKEND PROPERTY STRINGS auto spnav hid)
|
||||
set(QET_SPACEMOUSE_ENABLED FALSE)
|
||||
set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED FALSE)
|
||||
set(QET_SPACEMOUSE_BACKEND_HID_ENABLED FALSE)
|
||||
set(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED FALSE)
|
||||
|
||||
if(QET_ENABLE_SPACEMOUSE)
|
||||
find_package(PkgConfig)
|
||||
@@ -73,6 +75,16 @@ if(QET_ENABLE_SPACEMOUSE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# macOS: with 3DxWare installed the device can only be read through
|
||||
# 3DxWare, so its backend comes too, whichever backend was asked for.
|
||||
# It loads 3DxWare's library at run time and needs nothing to build.
|
||||
if(APPLE)
|
||||
set(QET_SPACEMOUSE_ENABLED TRUE)
|
||||
set(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED TRUE)
|
||||
add_definitions(-DQET_SPACEMOUSE_BACKEND_CONNEXION)
|
||||
message("QET_ENABLE_SPACEMOUSE ON (backend: 3DxWare when installed)")
|
||||
endif()
|
||||
|
||||
if(QET_SPACEMOUSE_ENABLED)
|
||||
add_definitions(-DQET_SPACEMOUSE_SUPPORT)
|
||||
else()
|
||||
|
||||
@@ -905,6 +905,13 @@ if(QET_SPACEMOUSE_BACKEND_HID_ENABLED)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED)
|
||||
list(APPEND QET_SRC_FILES
|
||||
${QET_DIR}/sources/spacemouse/connexionbackend.cpp
|
||||
${QET_DIR}/sources/spacemouse/connexionbackend.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(TS_FILES
|
||||
${QET_DIR}/lang/qet_ar.ts
|
||||
${QET_DIR}/lang/qet_ca.ts
|
||||
|
||||
@@ -398,10 +398,12 @@ done
|
||||
|
||||
echo "-- Signing main executable..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||
"$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
|
||||
echo "-- Signing bundle..."
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$BUNDLE"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
--entitlements "${current_dir}/misc/qelectrotech.entitlements" "$BUNDLE"
|
||||
|
||||
echo
|
||||
echo "Verifying bundle signature..."
|
||||
@@ -502,8 +504,10 @@ find "$MOUNT_POINT/$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
|
||||
done
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
|
||||
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
|
||||
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
|
||||
"$MOUNT_POINT/$BUNDLE"
|
||||
|
||||
echo "Verifying bundle signature inside DMG..."
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- 3D mouse with 3DxWare installed: lets QElectroTech load
|
||||
3Dconnexion's client library, which 3Dconnexion signs, at run time
|
||||
(sources/spacemouse/connexionbackend.h). The hardened runtime
|
||||
refuses it otherwise, and the 3D mouse does nothing. -->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -227,6 +227,7 @@ int exportPdf(QETProject &project, const QString &output,
|
||||
// Rewrite the URI link annotations into native internal GoTo actions, so
|
||||
// the cross-references jump inside the document in any PDF viewer.
|
||||
PdfLinks::convertUriToGoTo(output);
|
||||
PdfLinks::removeUnusedPdfxNamespace(output);
|
||||
|
||||
out << "Exported " << diagrams.size() << " page(s) -> " << output << "\n";
|
||||
return 0;
|
||||
|
||||
+52
-30
@@ -42,7 +42,9 @@
|
||||
#include "qetinformation.h"
|
||||
#include "qetproject.h"
|
||||
#include "diagramsortkeys.h"
|
||||
#include <QTextStream>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cassert>
|
||||
#include <math.h>
|
||||
|
||||
@@ -99,6 +101,43 @@ namespace {
|
||||
QString b = terminalSortKey(cond->terminal2);
|
||||
return (a <= b) ? (a + QLatin1Char('>') + b) : (b + QLatin1Char('>') + a);
|
||||
}
|
||||
|
||||
/// Serialize @p items and append them to a new @p tag block of @p root,
|
||||
/// in stacking order (@p stack_rank). items() cannot be trusted for
|
||||
/// this: with NoIndex, the first removeItem() on the scene sorts Qt's
|
||||
/// item list by pointer address, so from then on items() hands them over
|
||||
/// in a per-run order (bugtracker #343). Stacking order is what reloading
|
||||
/// the file rebuilds, so the drawing is unchanged and a resave is stable.
|
||||
template <typename T>
|
||||
void appendInStackingOrder(QDomDocument &document, QDomElement &root,
|
||||
const QString &tag, const QVector<T *> &items,
|
||||
const QHash<const QGraphicsItem *, int> &stack_rank)
|
||||
{
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
struct Entry { int rank; QString xml_text; QDomElement xml; };
|
||||
QVector<Entry> sorted;
|
||||
for (T *item : items) {
|
||||
Entry entry{stack_rank.value(item, INT_MAX), QString(),
|
||||
item->toXml(document)};
|
||||
// Only an item the stacking query missed needs a tiebreak.
|
||||
if (entry.rank == INT_MAX) {
|
||||
QTextStream stream(&entry.xml_text);
|
||||
entry.xml.save(stream, 0);
|
||||
}
|
||||
sorted.append(entry);
|
||||
}
|
||||
std::stable_sort(sorted.begin(), sorted.end(),
|
||||
[](const Entry &a, const Entry &b) {
|
||||
return a.rank != b.rank ? a.rank < b.rank
|
||||
: a.xml_text < b.xml_text;
|
||||
});
|
||||
|
||||
auto block = document.createElement(tag);
|
||||
for (const auto &entry : sorted)
|
||||
block.appendChild(entry.xml);
|
||||
root.appendChild(block);
|
||||
}
|
||||
}
|
||||
|
||||
int Diagram::xGrid = 10;
|
||||
@@ -1223,37 +1262,20 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
|
||||
dom_root.appendChild(dom_conductors);
|
||||
}
|
||||
|
||||
if (!list_texts.isEmpty()) {
|
||||
auto dom_texts = document.createElement(QStringLiteral("inputs"));
|
||||
for (auto dti : list_texts) {
|
||||
dom_texts.appendChild(dti->toXml(document));
|
||||
}
|
||||
dom_root.appendChild(dom_texts);
|
||||
}
|
||||
|
||||
if (!list_images.isEmpty()) {
|
||||
auto dom_images = document.createElement(QStringLiteral("images"));
|
||||
for (auto dii : list_images) {
|
||||
dom_images.appendChild(dii->toXml(document));
|
||||
}
|
||||
dom_root.appendChild(dom_images);
|
||||
}
|
||||
|
||||
if (!list_shapes.isEmpty()) {
|
||||
auto dom_shapes = document.createElement(QStringLiteral("shapes"));
|
||||
for (auto dii : list_shapes) {
|
||||
dom_shapes.appendChild(dii -> toXml(document));
|
||||
}
|
||||
dom_root.appendChild(dom_shapes);
|
||||
}
|
||||
|
||||
if (table_vector.size()) {
|
||||
auto tables = document.createElement(QStringLiteral("tables"));
|
||||
for (auto table : table_vector) {
|
||||
tables.appendChild(table->toXml(document));
|
||||
}
|
||||
dom_root.appendChild(tables);
|
||||
// A rect query, unlike items(), returns true stacking order (z, then
|
||||
// insertion order) even with NoIndex.
|
||||
QHash<const QGraphicsItem *, int> stack_rank;
|
||||
{
|
||||
const QList<QGraphicsItem *> stacked = items(
|
||||
QRectF(-1e9, -1e9, 2e9, 2e9), Qt::IntersectsItemBoundingRect,
|
||||
Qt::AscendingOrder);
|
||||
for (int i = 0 ; i < stacked.size() ; ++i)
|
||||
stack_rank.insert(stacked.at(i), i);
|
||||
}
|
||||
appendInStackingOrder(document, dom_root, QStringLiteral("inputs"), list_texts, stack_rank);
|
||||
appendInStackingOrder(document, dom_root, QStringLiteral("images"), list_images, stack_rank);
|
||||
appendInStackingOrder(document, dom_root, QStringLiteral("shapes"), list_shapes, stack_rank);
|
||||
appendInStackingOrder(document, dom_root, QStringLiteral("tables"), table_vector, stack_rank);
|
||||
|
||||
if (!strip_vector.isEmpty()) {
|
||||
dom_root.appendChild(TerminalStripItemXml::toXml(strip_vector, document));
|
||||
|
||||
@@ -388,6 +388,29 @@ void convertUriToGoTo(const QString &pdfPath)
|
||||
out.close();
|
||||
}
|
||||
|
||||
void removeUnusedPdfxNamespace(const QString &pdfPath)
|
||||
{
|
||||
QFile f(pdfPath);
|
||||
if (!f.open(QIODevice::ReadOnly)) return;
|
||||
QByteArray data = f.readAll();
|
||||
f.close();
|
||||
|
||||
// Qt only fills the pdfxid namespace in when it writes PDF/X-4.
|
||||
if (data.contains("pdfxid:GTS_PDFXVersion")) return;
|
||||
|
||||
static const QByteArray decl =
|
||||
" xmlns:pdfxid=\"http://www.npes.org/pdfx/ns/id/\"";
|
||||
const int pos = data.indexOf(decl);
|
||||
if (pos == -1) return;
|
||||
|
||||
// Same length, so the stream /Length and the xref offsets stay valid.
|
||||
data.replace(pos, decl.size(), QByteArray(decl.size(), ' '));
|
||||
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) return;
|
||||
f.write(data);
|
||||
f.close();
|
||||
}
|
||||
|
||||
void convertComponentInfoAnnotations(const QString &pdfPath,
|
||||
const QList<ComponentInfo> &annotations)
|
||||
{
|
||||
|
||||
@@ -74,6 +74,15 @@ namespace PdfLinks {
|
||||
*/
|
||||
void convertUriToGoTo(const QString &pdfPath);
|
||||
|
||||
/**
|
||||
Post-process a Qt-generated PDF file: blank out the PDF/X namespace
|
||||
declaration Qt 6 writes into the XMP metadata of every PDF. Adobe
|
||||
Acrobat draws small text too bold when it is present (bugtracker #340).
|
||||
Replaced in place with spaces, so no offset changes. No-op for a real
|
||||
PDF/X file or when the declaration is absent.
|
||||
*/
|
||||
void removeUnusedPdfxNamespace(const QString &pdfPath);
|
||||
|
||||
struct ComponentInfo {
|
||||
QString contents;
|
||||
};
|
||||
|
||||
@@ -874,6 +874,7 @@ void ProjectPrintWindow::print()
|
||||
// Convert URI link annotations into native internal GoTo/FitR
|
||||
// actions so cross-references jump inside the document.
|
||||
PdfLinks::convertUriToGoTo(pdfFile);
|
||||
PdfLinks::removeUnusedPdfxNamespace(pdfFile);
|
||||
|
||||
this->close();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#include "connexionbackend.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
//The part of 3DconnexionClient's interface QET uses, with the
|
||||
//values Blender uses (intern/ghost/intern/GHOST_NDOFManagerCocoa.mm).
|
||||
constexpr quint32 MSG_DEVICE_STATE = 0x33645352; // '3dSR'
|
||||
constexpr quint16 CMD_HANDLE_BUTTONS = 2;
|
||||
constexpr quint16 CMD_HANDLE_AXIS = 3;
|
||||
constexpr quint16 MODE_TAKE_OVER = 1;
|
||||
constexpr quint32 MASK_ALL = 0x3fff;
|
||||
constexpr quint32 MASK_ALL_BUTTONS = 0xffffffff;
|
||||
constexpr quint32 SIGNATURE = 0x51456c54; // 'QElT'
|
||||
|
||||
//ConnexionDeviceState is packed to 2 bytes. Offsets of the fields read.
|
||||
constexpr int STATE_CLIENT = 2;
|
||||
constexpr int STATE_COMMAND = 4;
|
||||
constexpr int STATE_AXIS = 30;
|
||||
constexpr int STATE_BUTTONS = 44;
|
||||
|
||||
using MessageHandler = void (*)(quint32, quint32, void *);
|
||||
using DeviceHandler = void (*)(quint32);
|
||||
using SetConnexionHandlers = qint16 (*)(MessageHandler, DeviceHandler, DeviceHandler, bool);
|
||||
using RegisterConnexionClient = quint16 (*)(quint32, const quint8 *, quint16, quint32);
|
||||
using SetConnexionClientButtonMask = void (*)(quint16, quint32);
|
||||
using CleanupConnexionHandlers = void (*)();
|
||||
using UnregisterConnexionClient = void (*)(quint16);
|
||||
|
||||
//3DxWare's callbacks carry no context, so the one client lives here.
|
||||
//The mutex is shared with 3DxWare's thread.
|
||||
QMutex instance_mutex;
|
||||
ConnexionBackend *instance = nullptr;
|
||||
|
||||
void deviceChanged(quint32) {}
|
||||
}
|
||||
|
||||
const char ConnexionBackend::DEFAULT_LIBRARY[] =
|
||||
"/Library/Frameworks/3DconnexionClient.framework/3DconnexionClient";
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::ConnexionBackend
|
||||
Load 3DxWare's client library and register with it. Not installed, or
|
||||
installed but not running, leaves isAvailable() false: the ordinary
|
||||
case, never reported as an error.
|
||||
@param parent
|
||||
@param library : the client library; only tests pass another
|
||||
*/
|
||||
ConnexionBackend::ConnexionBackend(QObject *parent, const QString &library) :
|
||||
SpaceMouseBackend(parent),
|
||||
m_library(library)
|
||||
{
|
||||
{
|
||||
QMutexLocker lock(&instance_mutex);
|
||||
if (instance) {
|
||||
return; //3DxWare takes one set of handlers per process
|
||||
}
|
||||
}
|
||||
if (!m_library.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto set_handlers = reinterpret_cast<SetConnexionHandlers>(
|
||||
m_library.resolve("SetConnexionHandlers"));
|
||||
const auto register_client = reinterpret_cast<RegisterConnexionClient>(
|
||||
m_library.resolve("RegisterConnexionClient"));
|
||||
const auto button_mask = reinterpret_cast<SetConnexionClientButtonMask>(
|
||||
m_library.resolve("SetConnexionClientButtonMask"));
|
||||
m_cleanup = reinterpret_cast<CleanupConnexionHandlers>(
|
||||
m_library.resolve("CleanupConnexionHandlers"));
|
||||
m_unregister = reinterpret_cast<UnregisterConnexionClient>(
|
||||
m_library.resolve("UnregisterConnexionClient"));
|
||||
if (!set_handlers || !register_client || !m_cleanup || !m_unregister) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
QMutexLocker lock(&instance_mutex);
|
||||
instance = this;
|
||||
}
|
||||
//Fails while 3DxWare is installed but its driver is not running.
|
||||
if (set_handlers(&ConnexionBackend::onMessage, deviceChanged, deviceChanged, true) != 0) {
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
m_handlers_installed = true;
|
||||
|
||||
//3DxWare only sends a client its messages while that application
|
||||
//is in front, and recognises it by its executable's name (a Pascal
|
||||
//string). Take-over mode stops 3DxWare's own actions in QET, so the
|
||||
//view never moves twice.
|
||||
QByteArray name = QFileInfo(QCoreApplication::applicationFilePath())
|
||||
.fileName().toUtf8().left(255);
|
||||
name.prepend(char(name.size()));
|
||||
m_client = register_client(SIGNATURE,
|
||||
reinterpret_cast<const quint8 *>(name.constData()),
|
||||
MODE_TAKE_OVER, MASK_ALL);
|
||||
if (!m_client) {
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
if (button_mask) {
|
||||
button_mask(m_client, MASK_ALL_BUTTONS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::~ConnexionBackend
|
||||
*/
|
||||
ConnexionBackend::~ConnexionBackend()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::sampleFromAxes
|
||||
3DxWare reports y up and z away from the user; the raw USB reports, and
|
||||
so every other backend, have y towards the user and z down. Derived from
|
||||
Blender, whose 3DxWare and spacenavd code paths must agree.
|
||||
@param axis : TX, TY, TZ, RX, RY, RZ as 3DxWare sends them
|
||||
@return the same movement in QET's convention
|
||||
*/
|
||||
SpaceMouseSample ConnexionBackend::sampleFromAxes(const qint16 axis[6])
|
||||
{
|
||||
SpaceMouseSample sample;
|
||||
sample.x = axis[0];
|
||||
sample.y = -axis[2];
|
||||
sample.z = -axis[1];
|
||||
sample.rx = axis[3];
|
||||
sample.ry = -axis[5];
|
||||
sample.rz = -axis[4];
|
||||
return sample;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::newlyPressed
|
||||
@param before : the button bitmask of the previous message
|
||||
@param now : the button bitmask of this one
|
||||
@return the 0-based buttons pressed since \a before, as HidBackend numbers them
|
||||
*/
|
||||
QList<int> ConnexionBackend::newlyPressed(quint32 before, quint32 now)
|
||||
{
|
||||
QList<int> pressed;
|
||||
const quint32 down = now & ~before;
|
||||
for (int bit = 0; bit < 32; ++bit) {
|
||||
if (down & (quint32(1) << bit)) {
|
||||
pressed.append(bit);
|
||||
}
|
||||
}
|
||||
return pressed;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::onMessage
|
||||
Runs on 3DxWare's thread. Copies what matters out of the message and
|
||||
hands it to the main thread.
|
||||
@param type : the message type
|
||||
@param argument : a ConnexionDeviceState for MSG_DEVICE_STATE
|
||||
*/
|
||||
void ConnexionBackend::onMessage(quint32, quint32 type, void *argument)
|
||||
{
|
||||
if (type != MSG_DEVICE_STATE || !argument) {
|
||||
return;
|
||||
}
|
||||
const char *state = static_cast<const char *>(argument);
|
||||
quint16 client;
|
||||
quint16 command;
|
||||
qint16 axis[6];
|
||||
quint32 buttons;
|
||||
std::memcpy(&client, state + STATE_CLIENT, sizeof client);
|
||||
std::memcpy(&command, state + STATE_COMMAND, sizeof command);
|
||||
std::memcpy(axis, state + STATE_AXIS, sizeof axis);
|
||||
std::memcpy(&buttons, state + STATE_BUTTONS, sizeof buttons);
|
||||
|
||||
//Queued with the backend as context: if it is deleted before the
|
||||
//main thread gets to it, Qt drops the call.
|
||||
QMutexLocker lock(&instance_mutex);
|
||||
ConnexionBackend *backend = instance;
|
||||
if (!backend) {
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(backend, [=]() {
|
||||
backend->handleState(client, command, axis, buttons);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::handleState
|
||||
On the main thread: turn one device state into signals.
|
||||
*/
|
||||
void ConnexionBackend::handleState(quint16 client, quint16 command,
|
||||
const qint16 axis[6], quint32 buttons)
|
||||
{
|
||||
if (client != m_client) {
|
||||
return; //3DxWare sends every state to every client
|
||||
}
|
||||
if (command == CMD_HANDLE_AXIS) {
|
||||
emit motion(sampleFromAxes(axis));
|
||||
} else if (command == CMD_HANDLE_BUTTONS) {
|
||||
//State first: a button can open a dialog whose event loop
|
||||
//delivers the next message before emit returns.
|
||||
const QList<int> pressed = newlyPressed(m_buttons, buttons);
|
||||
m_buttons = buttons;
|
||||
for (int button : pressed) {
|
||||
emit buttonPressed(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ConnexionBackend::shutdown
|
||||
Unregister and stop 3DxWare's callbacks. The library stays loaded, since
|
||||
3DxWare's thread may still be returning from one.
|
||||
*/
|
||||
void ConnexionBackend::shutdown()
|
||||
{
|
||||
if (m_client && m_unregister) {
|
||||
m_unregister(m_client);
|
||||
}
|
||||
m_client = 0;
|
||||
if (m_handlers_installed && m_cleanup) {
|
||||
m_cleanup();
|
||||
}
|
||||
m_handlers_installed = false;
|
||||
|
||||
QMutexLocker lock(&instance_mutex);
|
||||
if (instance == this) {
|
||||
instance = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
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
|
||||
@@ -36,7 +36,9 @@
|
||||
Two implementations, chosen at build time (cmake/find_spacemouse.cmake):
|
||||
SpnavBackend (Linux, through spacenavd/libspnav) and HidBackend (any
|
||||
platform, directly over USB through hidapi, with no 3Dconnexion driver
|
||||
or SDK). Both report the same values for the same movement.
|
||||
or SDK). On macOS a third, ConnexionBackend, reads through 3DxWare and
|
||||
is tried first at run time. All report the same values for the same
|
||||
movement.
|
||||
*/
|
||||
class SpaceMouseBackend : public QObject
|
||||
{
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
#ifdef QET_SPACEMOUSE_BACKEND_HID
|
||||
# include "hidbackend.h"
|
||||
#endif
|
||||
#ifdef QET_SPACEMOUSE_BACKEND_CONNEXION
|
||||
# include "connexionbackend.h"
|
||||
#endif
|
||||
|
||||
#include "../diagramview.h"
|
||||
#include "../editor/elementview.h"
|
||||
@@ -48,10 +51,24 @@ SpaceMouseListener::SpaceMouseListener(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_settings(SpaceMouseSettings::load())
|
||||
{
|
||||
#if defined(QET_SPACEMOUSE_BACKEND_CONNEXION)
|
||||
//When 3DxWare is installed and running it has the device to
|
||||
//itself, so ask it first; otherwise read the device directly.
|
||||
auto *connexion = new ConnexionBackend(this);
|
||||
if (connexion->isAvailable()) {
|
||||
m_backend = connexion;
|
||||
} else {
|
||||
delete connexion;
|
||||
}
|
||||
#endif
|
||||
#if defined(QET_SPACEMOUSE_BACKEND_SPNAV)
|
||||
m_backend = new SpnavBackend(this);
|
||||
if (!m_backend) {
|
||||
m_backend = new SpnavBackend(this);
|
||||
}
|
||||
#elif defined(QET_SPACEMOUSE_BACKEND_HID)
|
||||
m_backend = new HidBackend(this);
|
||||
if (!m_backend) {
|
||||
m_backend = new HidBackend(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_backend) {
|
||||
|
||||
@@ -203,6 +203,23 @@ add_test(NAME tst_spacemousehid COMMAND tst_spacemousehid)
|
||||
target_include_directories(tst_spacemousehid PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_spacemousehid PRIVATE Qt::Test Qt::Core)
|
||||
|
||||
# ConnexionBackend -- the macOS 3DxWare backend. Built on every platform and
|
||||
# run against fakeconnexion, a stand-in for 3DxWare's client library that
|
||||
# exports the same calls and answers from a thread of its own.
|
||||
add_library(fakeconnexion MODULE fakeconnexion.cpp)
|
||||
target_link_libraries(fakeconnexion PRIVATE Qt::Core)
|
||||
add_executable(
|
||||
tst_spacemouseconnexion
|
||||
tst_spacemouseconnexion.cpp
|
||||
${QET_DIR}/sources/spacemouse/connexionbackend.cpp
|
||||
${QET_DIR}/sources/spacemouse/spacemousebackend.h)
|
||||
add_dependencies(tst_spacemouseconnexion fakeconnexion)
|
||||
add_test(NAME tst_spacemouseconnexion COMMAND tst_spacemouseconnexion)
|
||||
target_compile_definitions(tst_spacemouseconnexion PRIVATE
|
||||
FAKE_CONNEXION="$<TARGET_FILE:fakeconnexion>")
|
||||
target_include_directories(tst_spacemouseconnexion PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_spacemouseconnexion PRIVATE Qt::Test Qt::Core)
|
||||
|
||||
# CrashHandler::formatInt() -- the async-signal-safe decimal formatter the
|
||||
# signal handler uses for the "Signal: N" line of a crash dump. Compiles
|
||||
# crashhandler.cpp and logring.cpp alongside; the handler deliberately
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/*
|
||||
A stand-in for 3DxWare's 3DconnexionClient.framework, for
|
||||
tst_spacemouseconnexion: it exports the calls ConnexionBackend makes,
|
||||
plus fake*() controls the test uses to script 3DxWare's side. Like the
|
||||
real driver, it delivers messages on a thread of its own.
|
||||
*/
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#define FAKE_EXPORT extern "C" Q_DECL_EXPORT
|
||||
|
||||
namespace {
|
||||
using MessageHandler = void (*)(uint32_t, uint32_t, void *);
|
||||
using DeviceHandler = void (*)(uint32_t);
|
||||
|
||||
MessageHandler handler = nullptr;
|
||||
int16_t handlers_result = 0;
|
||||
uint16_t client_result = 7;
|
||||
std::string registered_name;
|
||||
uint32_t registered_mode = 0;
|
||||
uint32_t registered_mask = 0;
|
||||
uint32_t registered_buttons = 0;
|
||||
int unregisters = 0;
|
||||
int cleanups = 0;
|
||||
}
|
||||
|
||||
FAKE_EXPORT int16_t SetConnexionHandlers(MessageHandler message, DeviceHandler, DeviceHandler, bool)
|
||||
{
|
||||
if (handlers_result == 0) {
|
||||
handler = message;
|
||||
}
|
||||
return handlers_result;
|
||||
}
|
||||
|
||||
FAKE_EXPORT void CleanupConnexionHandlers()
|
||||
{
|
||||
handler = nullptr;
|
||||
++cleanups;
|
||||
}
|
||||
|
||||
FAKE_EXPORT uint16_t RegisterConnexionClient(uint32_t, const uint8_t *name, uint16_t mode, uint32_t mask)
|
||||
{
|
||||
registered_name.assign(reinterpret_cast<const char *>(name) + 1, name[0]);
|
||||
registered_mode = mode;
|
||||
registered_mask = mask;
|
||||
return client_result;
|
||||
}
|
||||
|
||||
FAKE_EXPORT void SetConnexionClientButtonMask(uint16_t, uint32_t mask)
|
||||
{
|
||||
registered_buttons = mask;
|
||||
}
|
||||
|
||||
FAKE_EXPORT void UnregisterConnexionClient(uint16_t)
|
||||
{
|
||||
++unregisters;
|
||||
}
|
||||
|
||||
/// Reset, and set what SetConnexionHandlers and RegisterConnexionClient return.
|
||||
FAKE_EXPORT void fakeReset(int16_t handlers, uint16_t client)
|
||||
{
|
||||
handler = nullptr;
|
||||
handlers_result = handlers;
|
||||
client_result = client;
|
||||
registered_name.clear();
|
||||
registered_mode = registered_mask = registered_buttons = 0;
|
||||
unregisters = cleanups = 0;
|
||||
}
|
||||
|
||||
/// What the backend registered with: name, mode, mask, button mask, and
|
||||
/// how many times it unregistered and cleaned up.
|
||||
FAKE_EXPORT const char *fakeRegistration(uint32_t *mode, uint32_t *mask, uint32_t *buttons,
|
||||
int *unregistered, int *cleaned)
|
||||
{
|
||||
*mode = registered_mode;
|
||||
*mask = registered_mask;
|
||||
*buttons = registered_buttons;
|
||||
*unregistered = unregisters;
|
||||
*cleaned = cleanups;
|
||||
return registered_name.c_str();
|
||||
}
|
||||
|
||||
/// Send one ConnexionDeviceState (packed to 2 bytes, 48 bytes long) from
|
||||
/// another thread, as 3DxWare does.
|
||||
FAKE_EXPORT void fakeSend(uint16_t client, uint16_t command, const int16_t axis[6], uint32_t buttons)
|
||||
{
|
||||
unsigned char state[48] = {};
|
||||
std::memcpy(state + 2, &client, 2);
|
||||
std::memcpy(state + 4, &command, 2);
|
||||
std::memcpy(state + 30, axis, 12);
|
||||
std::memcpy(state + 44, &buttons, 4);
|
||||
std::thread driver([&state]() {
|
||||
if (handler) {
|
||||
handler(0, 0x33645352, state); // '3dSR'
|
||||
}
|
||||
});
|
||||
driver.join();
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/*
|
||||
ConnexionBackend, the macOS backend that reads a 3D mouse through
|
||||
3DxWare. It is tested on every platform against fakeconnexion, which
|
||||
exports the calls 3DxWare's client library does and sends messages from
|
||||
its own thread as 3DxWare does. What only a Mac with 3DxWare can check
|
||||
-- that the real library behaves like the fake -- is not tested here.
|
||||
*/
|
||||
#include "spacemouse/connexionbackend.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QLibrary>
|
||||
#include <QSignalSpy>
|
||||
#include <QtTest>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace {
|
||||
constexpr quint16 CLIENT = 7;
|
||||
constexpr quint16 AXIS = 3;
|
||||
constexpr quint16 BUTTONS = 2;
|
||||
|
||||
using Reset = void (*)(qint16, quint16);
|
||||
using Registration = const char *(*)(quint32 *, quint32 *, quint32 *, int *, int *);
|
||||
using Send = void (*)(quint16, quint16, const qint16 *, quint32);
|
||||
}
|
||||
|
||||
class TstSpaceMouseConnexion : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void init();
|
||||
void noLibrary();
|
||||
void driverNotRunning();
|
||||
void registration();
|
||||
void motion();
|
||||
void otherClients();
|
||||
void buttons();
|
||||
void deletedBeforeDelivery();
|
||||
void oneClientPerProcess();
|
||||
void realLibrary();
|
||||
|
||||
private:
|
||||
QList<SpaceMouseSample> collect(ConnexionBackend &backend,
|
||||
const std::function<void()> &send);
|
||||
|
||||
QLibrary m_fake{QStringLiteral(FAKE_CONNEXION)};
|
||||
Reset m_reset = nullptr;
|
||||
Registration m_registration = nullptr;
|
||||
Send m_send = nullptr;
|
||||
};
|
||||
|
||||
/*
|
||||
A fake that fails to load fails every test that uses it, in init(),
|
||||
rather than here: realLibrary() does not use it, and CI runs it alone
|
||||
under code-signing settings that may refuse the fake too.
|
||||
*/
|
||||
void TstSpaceMouseConnexion::initTestCase()
|
||||
{
|
||||
if (m_fake.load()) {
|
||||
m_reset = reinterpret_cast<Reset>(m_fake.resolve("fakeReset"));
|
||||
m_registration = reinterpret_cast<Registration>(m_fake.resolve("fakeRegistration"));
|
||||
m_send = reinterpret_cast<Send>(m_fake.resolve("fakeSend"));
|
||||
}
|
||||
}
|
||||
|
||||
void TstSpaceMouseConnexion::init()
|
||||
{
|
||||
if (qstrcmp(QTest::currentTestFunction(), "realLibrary") == 0) {
|
||||
return;
|
||||
}
|
||||
QVERIFY2(m_reset && m_registration && m_send, qPrintable(m_fake.errorString()));
|
||||
m_reset(0, CLIENT);
|
||||
}
|
||||
|
||||
QList<SpaceMouseSample> TstSpaceMouseConnexion::collect(
|
||||
ConnexionBackend &backend, const std::function<void()> &send)
|
||||
{
|
||||
QList<SpaceMouseSample> samples;
|
||||
auto c = connect(&backend, &SpaceMouseBackend::motion,
|
||||
[&samples](const SpaceMouseSample &s) { samples.append(s); });
|
||||
send();
|
||||
QCoreApplication::processEvents();
|
||||
disconnect(c);
|
||||
return samples;
|
||||
}
|
||||
|
||||
/// 3DxWare not installed: the ordinary case.
|
||||
void TstSpaceMouseConnexion::noLibrary()
|
||||
{
|
||||
ConnexionBackend backend(nullptr, QStringLiteral("/nonexistent/3DconnexionClient"));
|
||||
QVERIFY(!backend.isAvailable());
|
||||
}
|
||||
|
||||
/// Installed but its driver is not running: SetConnexionHandlers fails, and
|
||||
/// nothing is left registered for 3DxWare to call.
|
||||
void TstSpaceMouseConnexion::driverNotRunning()
|
||||
{
|
||||
m_reset(-36, CLIENT);
|
||||
{
|
||||
ConnexionBackend backend(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
QVERIFY(!backend.isAvailable());
|
||||
}
|
||||
quint32 mode, mask, buttons;
|
||||
int unregistered, cleaned;
|
||||
m_registration(&mode, &mask, &buttons, &unregistered, &cleaned);
|
||||
QCOMPARE(unregistered, 0);
|
||||
QCOMPARE(cleaned, 0);
|
||||
|
||||
//And a registration refused (client 0) leaves nothing either.
|
||||
m_reset(0, 0);
|
||||
{
|
||||
ConnexionBackend backend(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
QVERIFY(!backend.isAvailable());
|
||||
}
|
||||
m_registration(&mode, &mask, &buttons, &unregistered, &cleaned);
|
||||
QCOMPARE(cleaned, 1);
|
||||
}
|
||||
|
||||
/// Registers by executable name, in take-over mode, for everything, and
|
||||
/// undoes it all on destruction.
|
||||
void TstSpaceMouseConnexion::registration()
|
||||
{
|
||||
quint32 mode, mask, buttons;
|
||||
int unregistered, cleaned;
|
||||
{
|
||||
ConnexionBackend backend(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
QVERIFY(backend.isAvailable());
|
||||
const QString name = QString::fromUtf8(
|
||||
m_registration(&mode, &mask, &buttons, &unregistered, &cleaned));
|
||||
QCOMPARE(name, QFileInfo(QCoreApplication::applicationFilePath()).fileName());
|
||||
QCOMPARE(mode, 1u);
|
||||
QCOMPARE(mask, 0x3fffu);
|
||||
QCOMPARE(buttons, 0xffffffffu);
|
||||
QCOMPARE(unregistered, 0);
|
||||
}
|
||||
m_registration(&mode, &mask, &buttons, &unregistered, &cleaned);
|
||||
QCOMPARE(unregistered, 1);
|
||||
QCOMPARE(cleaned, 1);
|
||||
}
|
||||
|
||||
/// Motion arrives from 3DxWare's thread and is emitted on this one, in
|
||||
/// QET's axis convention.
|
||||
void TstSpaceMouseConnexion::motion()
|
||||
{
|
||||
ConnexionBackend backend(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
QVERIFY(backend.isAvailable());
|
||||
|
||||
QThread *emitted_on = nullptr;
|
||||
connect(&backend, &SpaceMouseBackend::motion,
|
||||
[&emitted_on]() { emitted_on = QThread::currentThread(); });
|
||||
const qint16 axis[6] = {10, 20, 30, 40, 50, 60};
|
||||
const QList<SpaceMouseSample> samples = collect(backend, [&]() {
|
||||
m_send(CLIENT, AXIS, axis, 0);
|
||||
});
|
||||
QCOMPARE(samples.size(), 1);
|
||||
QCOMPARE(emitted_on, QThread::currentThread());
|
||||
|
||||
const SpaceMouseSample &s = samples.first();
|
||||
QCOMPARE(s.x, 10);
|
||||
QCOMPARE(s.y, -30);
|
||||
QCOMPARE(s.z, -20);
|
||||
QCOMPARE(s.rx, 40);
|
||||
QCOMPARE(s.ry, -60);
|
||||
QCOMPARE(s.rz, -50);
|
||||
}
|
||||
|
||||
/// 3DxWare sends every state to every client; only ours counts.
|
||||
void TstSpaceMouseConnexion::otherClients()
|
||||
{
|
||||
ConnexionBackend backend(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
const qint16 axis[6] = {1, 2, 3, 4, 5, 6};
|
||||
QCOMPARE(collect(backend, [&]() { m_send(CLIENT + 1, AXIS, axis, 0); }).size(), 0);
|
||||
}
|
||||
|
||||
/// A press is a bit set that was clear; 0-based, as HidBackend numbers them.
|
||||
void TstSpaceMouseConnexion::buttons()
|
||||
{
|
||||
ConnexionBackend backend(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
QSignalSpy spy(&backend, &SpaceMouseBackend::buttonPressed);
|
||||
const qint16 still[6] = {};
|
||||
|
||||
m_send(CLIENT, BUTTONS, still, 0b101);
|
||||
QCoreApplication::processEvents();
|
||||
QCOMPARE(spy.size(), 2);
|
||||
QCOMPARE(spy.at(0).at(0).toInt(), 0);
|
||||
QCOMPARE(spy.at(1).at(0).toInt(), 2);
|
||||
|
||||
m_send(CLIENT, BUTTONS, still, 0b111);
|
||||
m_send(CLIENT, BUTTONS, still, 0);
|
||||
m_send(CLIENT, BUTTONS, still, 0b100);
|
||||
QCoreApplication::processEvents();
|
||||
QCOMPARE(spy.size(), 4);
|
||||
QCOMPARE(spy.at(2).at(0).toInt(), 1);
|
||||
QCOMPARE(spy.at(3).at(0).toInt(), 2);
|
||||
|
||||
QCOMPARE(ConnexionBackend::newlyPressed(0, 0x80000000u), QList<int>{31});
|
||||
QVERIFY(ConnexionBackend::newlyPressed(0b11, 0b01).isEmpty());
|
||||
}
|
||||
|
||||
/// A message queued for a backend deleted before the main thread reads it
|
||||
/// is dropped, not delivered to freed memory.
|
||||
void TstSpaceMouseConnexion::deletedBeforeDelivery()
|
||||
{
|
||||
auto *backend = new ConnexionBackend(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
const qint16 axis[6] = {1, 2, 3, 4, 5, 6};
|
||||
m_send(CLIENT, AXIS, axis, 0);
|
||||
delete backend;
|
||||
QCoreApplication::processEvents();
|
||||
//And 3DxWare calling after shutdown finds no one to call.
|
||||
m_send(CLIENT, AXIS, axis, 0);
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
/// 3DxWare takes one set of handlers per process.
|
||||
void TstSpaceMouseConnexion::oneClientPerProcess()
|
||||
{
|
||||
ConnexionBackend first(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
ConnexionBackend second(nullptr, QStringLiteral(FAKE_CONNEXION));
|
||||
QVERIFY(first.isAvailable());
|
||||
QVERIFY(!second.isAvailable());
|
||||
|
||||
const qint16 axis[6] = {5, 0, 0, 0, 0, 0};
|
||||
QCOMPARE(collect(first, [&]() { m_send(CLIENT, AXIS, axis, 0); }).size(), 1);
|
||||
}
|
||||
|
||||
/// On a Mac with 3DxWare installed: this process can load 3DxWare's real
|
||||
/// library. A Developer ID build with the hardened runtime needs
|
||||
/// misc/qelectrotech.entitlements for that; an ad-hoc signature does not
|
||||
/// enforce library validation, so CI cannot show the difference. Whether
|
||||
/// 3DxWare's driver then answers depends on the machine, so it is not checked.
|
||||
void TstSpaceMouseConnexion::realLibrary()
|
||||
{
|
||||
QLibrary library(QString::fromLatin1(ConnexionBackend::DEFAULT_LIBRARY));
|
||||
if (!QFileInfo::exists(library.fileName())) {
|
||||
QSKIP("3DxWare is not installed");
|
||||
}
|
||||
QVERIFY2(library.load(), qPrintable(library.errorString()));
|
||||
QVERIFY(library.resolve("SetConnexionHandlers"));
|
||||
|
||||
ConnexionBackend backend;
|
||||
qInfo("3DxWare's driver %s", backend.isAvailable() ? "answered" : "did not answer");
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TstSpaceMouseConnexion)
|
||||
#include "tst_spacemouseconnexion.moc"
|
||||
Reference in New Issue
Block a user