summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEike Ziller <git@eikeziller.de>2017-11-19 21:18:26 +0100
committerEike Ziller <git@eikeziller.de>2017-11-23 20:23:28 +0100
commit7bacbdf10b97040c9ed282a22485759bd082fa63 (patch)
tree2a02884f633ddbc7ede387a5bf2aa8d7b0396e5f
parentf548847ea43006b2dc540e84d11d3c3662a94148 (diff)
Add virtual joystick output for macOS
Based of the foohid IOKit driver for implementing virtual HID devices. Issue: #236
-rw-r--r--proto-iokit-foohid/CMakeLists.txt4
-rw-r--r--proto-iokit-foohid/README.md17
-rw-r--r--proto-iokit-foohid/foohidjoystick.cpp162
-rw-r--r--proto-iokit-foohid/foohidjoystick.h51
-rw-r--r--proto-iokit-foohid/iokitprotocol.cpp51
-rw-r--r--proto-iokit-foohid/iokitprotocol.h34
-rw-r--r--proto-iokit-foohid/iokitprotocoldialog.cpp30
-rw-r--r--proto-iokit-foohid/iokitprotocoldialog.h21
-rw-r--r--proto-iokit-foohid/lang/nl_NL.ts40
-rw-r--r--proto-iokit-foohid/lang/ru_RU.ts40
-rw-r--r--proto-iokit-foohid/lang/stub.ts40
11 files changed, 490 insertions, 0 deletions
diff --git a/proto-iokit-foohid/CMakeLists.txt b/proto-iokit-foohid/CMakeLists.txt
new file mode 100644
index 00000000..b997bf60
--- /dev/null
+++ b/proto-iokit-foohid/CMakeLists.txt
@@ -0,0 +1,4 @@
+IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ otr_module(proto-iokit-foohid)
+ otr_prop(TARGET opentrack-proto-iokit-foohid LINK_FLAGS "-framework IOKit")
+endif()
diff --git a/proto-iokit-foohid/README.md b/proto-iokit-foohid/README.md
new file mode 100644
index 00000000..a9d7a34e
--- /dev/null
+++ b/proto-iokit-foohid/README.md
@@ -0,0 +1,17 @@
+# Virtual Joystick Support for macOS
+
+Adds support for a virtual joystick output, based on the foohid driver for virtual HID devices.
+
+## Usage
+
+Install the latest [foohid HID driver](https://github.com/unbit/foohid/releases/).
+
+Select "Virtual Joystick" as the output in OpenTrack.
+
+The X, Y, Z, Yaw, Pitch, and Roll tracking axes are mapped to the joystick's X, Y, Z, RX, RY,
+and RZ axes respectively.
+
+# Building
+
+The implementation only uses standard libraries that are pre-installed on macOS. There are no
+special requirements for building.
diff --git a/proto-iokit-foohid/foohidjoystick.cpp b/proto-iokit-foohid/foohidjoystick.cpp
new file mode 100644
index 00000000..17a4bcb5
--- /dev/null
+++ b/proto-iokit-foohid/foohidjoystick.cpp
@@ -0,0 +1,162 @@
+/* Copyright (c) 2017, Eike Ziller *
+ * *
+ * Permission to use, copy, modify, and/or distribute this software for any *
+ * purpose with or without fee is hereby granted, provided that the above *
+ * copyright notice and this permission notice appear in all copies. *
+ */
+
+#include "foohidjoystick.h"
+
+#include <QCoreApplication>
+
+const char FOOHID_SERVICE_NAME[] = "it_unbit_foohid";
+
+enum class FooHIDMethod {
+ Create = 0,
+ Destroy,
+ Send,
+ List,
+ Subscribe
+};
+
+// Joystick USB descriptor
+static unsigned char report_descriptor[] = {
+ 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+ 0x09, 0x04, // USAGE (Joystick)
+ 0xa1, 0x01, // COLLECTION (Application)
+ 0x09, 0x01, // USAGE (Pointer)
+ 0xa1, 0x00, // COLLECTION (Physical)
+ 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+ 0x09, 0x30, // USAGE (X)
+ 0x09, 0x31, // USAGE (Y)
+ 0x09, 0x32, // USAGE (Z)
+ 0x09, 0x33, // USAGE (RX)
+ 0x09, 0x34, // USAGE (RY)
+ 0x09, 0x35, // USAGE (RZ)
+ 0x15, 0x00, // LOGICAL_MINIMUM (0)
+ 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
+ 0x75, 0x08, // REPORT_SIZE (8)
+ 0x95, 0x06, // REPORT_COUNT (6)
+ 0x81, 0x02, // INPUT (Data,Var,Abs)
+ 0xc0, // END_COLLECTION
+ 0xc0 // END_COLLECTION
+};
+
+static bool connectToService(io_connect_t *connection, QString *errorMessage)
+{
+ io_iterator_t iterator;
+ io_service_t service;
+ // Get an iterator over all IOService instances
+ kern_return_t ret = IOServiceGetMatchingServices(kIOMasterPortDefault,
+ IOServiceMatching(FOOHID_SERVICE_NAME),
+ &iterator);
+ if (ret != KERN_SUCCESS) {
+ *errorMessage = QCoreApplication::translate("iokit-foohid", "Unable to find FooHID IOService.");
+ return false;
+ }
+ // Iterate over services and try to open connection
+ bool found = false;
+ while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
+ ret = IOServiceOpen(service, mach_task_self(), 0, connection);
+ if (ret == KERN_SUCCESS) {
+ found = true;
+ break;
+ }
+ IOObjectRelease(service);
+ }
+ IOObjectRelease(iterator);
+ if (!found) {
+ *errorMessage = QCoreApplication::translate("iokit-foohid", "Unable to connect to FooHID IOService.");
+ return false;
+ }
+ return true;
+}
+
+static void disconnectFromService(io_connect_t connection)
+{
+ IOServiceClose(connection);
+ // ignore errors
+}
+
+FooHIDJoystick::FooHIDJoystick(const QByteArray &name, const QByteArray &serialNumber)
+ : name(name), serialNumber(serialNumber)
+{
+ connectionOpened = connectToService(&connection, &_errorMessage);
+ _hasError = !connectionOpened;
+ if (!_hasError) {
+ // first try to destroy device, in case it was left-over (from a crash/interrupt)
+ destroyDevice();
+ deviceCreated = createDevice();
+ _hasError = !deviceCreated;
+ if (!deviceCreated)
+ _errorMessage = QCoreApplication::translate("iokit-foohid",
+ "Failed to create virtual joystick");
+ }
+}
+
+FooHIDJoystick::~FooHIDJoystick()
+{
+ if (deviceCreated)
+ destroyDevice();
+ if (connectionOpened)
+ disconnectFromService(connection);
+}
+
+bool FooHIDJoystick::hasError() const
+{
+ return _hasError;
+}
+
+QString FooHIDJoystick::errorMessage() const
+{
+ return _errorMessage;
+}
+
+void FooHIDJoystick::setValue(JoystickValues newValues)
+{
+ values = newValues;
+ if (!sendToDevice()) {
+ _hasError = true;
+ _errorMessage = QCoreApplication::translate("iokit-foohid",
+ "Failed to send values to virtual joystick");
+ }
+}
+
+bool FooHIDJoystick::createDevice() const
+{
+ uint64_t params[8];
+ params[0] = uint64_t(name.constData()); // pointer to name
+ params[1] = uint64_t(name.size()); // size of name without \0
+ params[2] = uint64_t(report_descriptor); // pointer to report descriptor
+ params[3] = sizeof(report_descriptor); // size of report descriptor
+ params[4] = uint64_t(serialNumber.constData()); // pointer to serial number
+ params[5] = uint64_t(serialNumber.size()); // size of serial number without \0
+ params[6] = uint64_t(2); // vendor ID
+ params[7] = uint64_t(3); // device ID
+
+ kern_return_t ret = IOConnectCallScalarMethod(connection, int(FooHIDMethod::Create), params,
+ sizeof(params)/sizeof(params[0]), NULL, 0);
+ return ret == KERN_SUCCESS;
+}
+
+bool FooHIDJoystick::sendToDevice() const
+{
+ uint64_t params[4];
+ params[0] = uint64_t(name.constData()); // pointer to name
+ params[1] = uint64_t(name.size()); // size of name without \0
+ params[2] = uint64_t(&values); // pointer to values
+ params[3] = sizeof(struct JoystickValues); // length of value struct
+ kern_return_t ret = IOConnectCallScalarMethod(connection, int(FooHIDMethod::Send), params,
+ sizeof(params)/sizeof(params[0]), NULL, 0);
+ return ret == KERN_SUCCESS;
+}
+
+void FooHIDJoystick::destroyDevice() const
+{
+ uint64_t params[2];
+ params[0] = uint64_t(name.constData()); // pointer to name
+ params[1] = uint64_t(name.size()); // size of name without \0
+ IOConnectCallScalarMethod(connection, int(FooHIDMethod::Destroy), params,
+ sizeof(params)/sizeof(params[0]), NULL, 0);
+ // ignore failure
+}
diff --git a/proto-iokit-foohid/foohidjoystick.h b/proto-iokit-foohid/foohidjoystick.h
new file mode 100644
index 00000000..a1f74304
--- /dev/null
+++ b/proto-iokit-foohid/foohidjoystick.h
@@ -0,0 +1,51 @@
+/* Copyright (c) 2017, Eike Ziller *
+ * *
+ * Permission to use, copy, modify, and/or distribute this software for any *
+ * purpose with or without fee is hereby granted, provided that the above *
+ * copyright notice and this permission notice appear in all copies. *
+ */
+
+#pragma once
+
+#include <QByteArray>
+#include <QString>
+
+#include <IOKit/IOKitLib.h>
+
+struct JoystickValues {
+ uint8_t x;
+ uint8_t y;
+ uint8_t z;
+ uint8_t rx;
+ uint8_t ry;
+ uint8_t rz;
+};
+
+class FooHIDJoystick
+{
+public:
+ FooHIDJoystick(const QByteArray &name, const QByteArray &serialNumber);
+ ~FooHIDJoystick();
+
+ bool hasError() const;
+ QString errorMessage() const;
+
+ int minValue() const { return 0; }
+ int range() const { return 255; }
+
+ void setValue(JoystickValues newValues);
+
+private:
+ bool createDevice() const;
+ bool sendToDevice() const;
+ void destroyDevice() const;
+
+ const QByteArray name;
+ const QByteArray serialNumber;
+ io_connect_t connection = 0;
+ JoystickValues values = {127, 127};
+ QString _errorMessage;
+ bool _hasError = true;
+ bool connectionOpened = false;
+ bool deviceCreated = false;
+};
diff --git a/proto-iokit-foohid/iokitprotocol.cpp b/proto-iokit-foohid/iokitprotocol.cpp
new file mode 100644
index 00000000..4af6ad5b
--- /dev/null
+++ b/proto-iokit-foohid/iokitprotocol.cpp
@@ -0,0 +1,51 @@
+/* Copyright (c) 2017, Eike Ziller *
+ * *
+ * Permission to use, copy, modify, and/or distribute this software for any *
+ * purpose with or without fee is hereby granted, provided that the above *
+ * copyright notice and this permission notice appear in all copies. *
+ */
+
+#include "iokitprotocol.h"
+
+#include "foohidjoystick.h"
+#include "iokitprotocoldialog.h"
+
+#include <QDebug>
+
+IOKitProtocol::IOKitProtocol()
+{
+ joystick = std::make_unique<FooHIDJoystick>("OpenTrack Virtual Joystick",
+ "SN 983457");
+ if (joystick->hasError())
+ qWarning("%s\n", qPrintable(joystick->errorMessage()));
+}
+
+bool IOKitProtocol::correct()
+{
+ return joystick && !joystick->hasError();
+}
+
+static uint8_t valueToStick(FooHIDJoystick *stick, double min, double value, double max)
+{
+ return uint8_t(qBound(stick->minValue(),
+ int(round((value - min) * stick->range() / (max - min) - stick->minValue())),
+ stick->minValue() + stick->range()));
+}
+
+void IOKitProtocol::pose(const double *headpose)
+{
+ const uint8_t x = valueToStick(joystick.get(), -75., headpose[0], +75.);
+ const uint8_t y = valueToStick(joystick.get(), -75., headpose[1], +75.);
+ const uint8_t z = valueToStick(joystick.get(), -75., headpose[2], +75.);
+ const uint8_t rx = valueToStick(joystick.get(), -180., headpose[3], +180.);
+ const uint8_t ry = valueToStick(joystick.get(), -180., headpose[4], +180.);
+ const uint8_t rz = valueToStick(joystick.get(), -180., headpose[5], +180.);
+ joystick->setValue({x, y, z, rx, ry, rz});
+}
+
+QString IOKitProtocol::game_name()
+{
+ return QString();
+}
+
+OPENTRACK_DECLARE_PROTOCOL(IOKitProtocol, IOKitProtocolDialog, IOKitProtocolMetadata)
diff --git a/proto-iokit-foohid/iokitprotocol.h b/proto-iokit-foohid/iokitprotocol.h
new file mode 100644
index 00000000..f3f4089f
--- /dev/null
+++ b/proto-iokit-foohid/iokitprotocol.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2017, Eike Ziller *
+ * *
+ * Permission to use, copy, modify, and/or distribute this software for any *
+ * purpose with or without fee is hereby granted, provided that the above *
+ * copyright notice and this permission notice appear in all copies. *
+ */
+
+#pragma once
+
+#include "api/plugin-api.hpp"
+
+#include <QCoreApplication>
+
+class FooHIDJoystick;
+
+class IOKitProtocol : public IProtocol
+{
+public:
+ IOKitProtocol();
+
+ bool correct() final;
+ void pose(const double *headpose) final;
+ QString game_name() final;
+
+private:
+ std::unique_ptr<FooHIDJoystick> joystick;
+};
+
+class IOKitProtocolMetadata : public Metadata
+{
+public:
+ QString name() { return QString(QCoreApplication::translate("iokit-foohid", "Virtual joystick")); }
+ QIcon icon() { return QIcon(":/images/facetracknoir.png"); }
+};
diff --git a/proto-iokit-foohid/iokitprotocoldialog.cpp b/proto-iokit-foohid/iokitprotocoldialog.cpp
new file mode 100644
index 00000000..443a30b6
--- /dev/null
+++ b/proto-iokit-foohid/iokitprotocoldialog.cpp
@@ -0,0 +1,30 @@
+/* Copyright (c) 2017, Eike Ziller *
+ * *
+ * Permission to use, copy, modify, and/or distribute this software for any *
+ * purpose with or without fee is hereby granted, provided that the above *
+ * copyright notice and this permission notice appear in all copies. *
+ */
+
+#include "iokitprotocoldialog.h"
+
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QVBoxLayout>
+
+IOKitProtocolDialog::IOKitProtocolDialog()
+{
+ setLayout(new QVBoxLayout);
+ layout()->addWidget(new QLabel(tr("No settings available.")));
+ layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding));
+ auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
+ layout()->addWidget(buttonBox);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+}
+
+void IOKitProtocolDialog::register_protocol(IProtocol *)
+{
+}
+
+void IOKitProtocolDialog::unregister_protocol()
+{
+}
diff --git a/proto-iokit-foohid/iokitprotocoldialog.h b/proto-iokit-foohid/iokitprotocoldialog.h
new file mode 100644
index 00000000..0417ba26
--- /dev/null
+++ b/proto-iokit-foohid/iokitprotocoldialog.h
@@ -0,0 +1,21 @@
+/* Copyright (c) 2017, Eike Ziller *
+ * *
+ * Permission to use, copy, modify, and/or distribute this software for any *
+ * purpose with or without fee is hereby granted, provided that the above *
+ * copyright notice and this permission notice appear in all copies. *
+ */
+
+#pragma once
+
+#include "api/plugin-api.hpp"
+
+class IOKitProtocolDialog : public IProtocolDialog
+{
+ Q_OBJECT
+
+public:
+ IOKitProtocolDialog();
+
+ void register_protocol(IProtocol *protocol) final;
+ void unregister_protocol() final;
+};
diff --git a/proto-iokit-foohid/lang/nl_NL.ts b/proto-iokit-foohid/lang/nl_NL.ts
new file mode 100644
index 00000000..f5861f6e
--- /dev/null
+++ b/proto-iokit-foohid/lang/nl_NL.ts
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>IOKitProtocolDialog</name>
+ <message>
+ <location filename="../iokitprotocoldialog.cpp" line="+17"/>
+ <source>No settings available.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>iokit-foohid</name>
+ <message>
+ <location filename="../iokitprotocol.h" line="+32"/>
+ <source>Virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../foohidjoystick.cpp" line="+54"/>
+ <source>Unable to find FooHID IOService.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source>Unable to connect to FooHID IOService.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+23"/>
+ <source>Failed to create virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+28"/>
+ <source>Failed to send values to virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/proto-iokit-foohid/lang/ru_RU.ts b/proto-iokit-foohid/lang/ru_RU.ts
new file mode 100644
index 00000000..15ab759f
--- /dev/null
+++ b/proto-iokit-foohid/lang/ru_RU.ts
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>IOKitProtocolDialog</name>
+ <message>
+ <location filename="../iokitprotocoldialog.cpp" line="+17"/>
+ <source>No settings available.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>iokit-foohid</name>
+ <message>
+ <location filename="../iokitprotocol.h" line="+32"/>
+ <source>Virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../foohidjoystick.cpp" line="+54"/>
+ <source>Unable to find FooHID IOService.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source>Unable to connect to FooHID IOService.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+23"/>
+ <source>Failed to create virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+28"/>
+ <source>Failed to send values to virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/proto-iokit-foohid/lang/stub.ts b/proto-iokit-foohid/lang/stub.ts
new file mode 100644
index 00000000..889ade33
--- /dev/null
+++ b/proto-iokit-foohid/lang/stub.ts
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>IOKitProtocolDialog</name>
+ <message>
+ <location filename="../iokitprotocoldialog.cpp" line="+17"/>
+ <source>No settings available.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>iokit-foohid</name>
+ <message>
+ <location filename="../iokitprotocol.h" line="+32"/>
+ <source>Virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../foohidjoystick.cpp" line="+54"/>
+ <source>Unable to find FooHID IOService.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source>Unable to connect to FooHID IOService.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+23"/>
+ <source>Failed to create virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+28"/>
+ <source>Failed to send values to virtual joystick</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>