diff options
| -rw-r--r-- | proto-iokit-foohid/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | proto-iokit-foohid/README.md | 17 | ||||
| -rw-r--r-- | proto-iokit-foohid/foohidjoystick.cpp | 162 | ||||
| -rw-r--r-- | proto-iokit-foohid/foohidjoystick.h | 51 | ||||
| -rw-r--r-- | proto-iokit-foohid/iokitprotocol.cpp | 51 | ||||
| -rw-r--r-- | proto-iokit-foohid/iokitprotocol.h | 34 | ||||
| -rw-r--r-- | proto-iokit-foohid/iokitprotocoldialog.cpp | 30 | ||||
| -rw-r--r-- | proto-iokit-foohid/iokitprotocoldialog.h | 21 | ||||
| -rw-r--r-- | proto-iokit-foohid/lang/nl_NL.ts | 40 | ||||
| -rw-r--r-- | proto-iokit-foohid/lang/ru_RU.ts | 40 | ||||
| -rw-r--r-- | proto-iokit-foohid/lang/stub.ts | 40 | 
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> | 
