diff options
| -rw-r--r-- | gui/keyboard.h | 16 | ||||
| -rw-r--r-- | gui/options-dialog.cpp | 44 | ||||
| -rw-r--r-- | gui/options-dialog.hpp | 2 | ||||
| -rw-r--r-- | opentrack/keybinding-worker.cpp | 13 | ||||
| -rw-r--r-- | opentrack/keybinding-worker.hpp | 5 | ||||
| -rw-r--r-- | opentrack/shortcuts.cpp | 34 | ||||
| -rw-r--r-- | opentrack/shortcuts.h | 7 | ||||
| -rw-r--r-- | opentrack/win32-joystick-shortcuts.hpp | 264 | 
8 files changed, 354 insertions, 31 deletions
| diff --git a/gui/keyboard.h b/gui/keyboard.h index 03edacc7..aa4b4a24 100644 --- a/gui/keyboard.h +++ b/gui/keyboard.h @@ -20,10 +20,17 @@ public:  #ifdef _WIN32        , w([&](Key& k)      { -        Qt::KeyboardModifiers m; -        QKeySequence k_; -        if (win_key::to_qt(k, k_, m)) -            key_pressed(static_cast<QVariant>(k_).toInt() | m); +        if(k.guid != "") +        { +            joystick_button_pressed(k.guid, k.keycode); +        } +        else +        { +            Qt::KeyboardModifiers m; +            QKeySequence k_; +            if (win_key::to_qt(k, k_, m)) +                key_pressed(static_cast<QVariant>(k_).toInt() | m); +        }      }, this->winId())  #endif      { @@ -47,4 +54,5 @@ public:  #endif  signals:      void key_pressed(QKeySequence k); +    void joystick_button_pressed(QString guid, int idx);  }; diff --git a/gui/options-dialog.cpp b/gui/options-dialog.cpp index 2778be0f..c8bf668d 100644 --- a/gui/options-dialog.cpp +++ b/gui/options-dialog.cpp @@ -12,9 +12,18 @@  #include <QLayout>  #include <QDialog> +static QString kopts_to_string(const Shortcuts::key_opts& kopts) +{ +    if (static_cast<QString>(kopts.guid) != "") +        return "Joystick button " + QString::number(kopts.button); +    if (static_cast<QString>(kopts.keycode) == "") +        return "None"; +    return kopts.keycode; +} +  OptionsDialog::OptionsDialog()  { -    ui.setupUi( this ); +    ui.setupUi(this);      connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));      connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); @@ -53,18 +62,20 @@ OptionsDialog::OptionsDialog()      tie_setting(s.s_main.center_method, ui.center_method); -    connect(ui.bind_center, &QPushButton::pressed, [&]() -> void { bind_key(s.center.keycode, ui.center_text); }); -    connect(ui.bind_zero, &QPushButton::pressed, [&]() -> void { bind_key(s.zero.keycode, ui.zero_text); }); -    connect(ui.bind_toggle, &QPushButton::pressed, [&]() -> void { bind_key(s.toggle.keycode, ui.toggle_text); }); +    connect(ui.bind_center, &QPushButton::pressed, [&]() -> void { bind_key(s.center, ui.center_text); }); +    connect(ui.bind_zero, &QPushButton::pressed, [&]() -> void { bind_key(s.zero, ui.zero_text); }); +    connect(ui.bind_toggle, &QPushButton::pressed, [&]() -> void { bind_key(s.toggle, ui.toggle_text); }); -    ui.center_text->setText(s.center.keycode == "" ? "None" : static_cast<QString>(s.center.keycode)); -    ui.toggle_text->setText(s.toggle.keycode == "" ? "None" : static_cast<QString>(s.toggle.keycode)); -    ui.zero_text->setText(s.zero.keycode == "" ? "None" : static_cast<QString>(s.zero.keycode)); +    ui.center_text->setText(kopts_to_string(s.center)); +    ui.toggle_text->setText(kopts_to_string(s.toggle)); +    ui.zero_text->setText(kopts_to_string(s.zero));  } -void OptionsDialog::bind_key(value<QString>& ret, QLabel* label) +void OptionsDialog::bind_key(Shortcuts::key_opts& kopts, QLabel* label)  { -    ret = ""; +    kopts.button = -1; +    kopts.guid = ""; +    kopts.keycode = "";      QDialog d;      auto l = new QHBoxLayout;      l->setMargin(0); @@ -73,9 +84,20 @@ void OptionsDialog::bind_key(value<QString>& ret, QLabel* label)      d.setLayout(l);      d.setFixedSize(QSize(500, 300));      d.setWindowFlags(Qt::Dialog); -    connect(k, &KeyboardListener::key_pressed, [&] (QKeySequence s) -> void { ret = s.toString(QKeySequence::PortableText); d.close(); }); +    connect(k, &KeyboardListener::key_pressed, [&] (QKeySequence s) -> void { +        kopts.keycode = s.toString(QKeySequence::PortableText); +        kopts.guid = ""; +        kopts.button = -1; +        d.close(); +    }); +    connect(k, &KeyboardListener::joystick_button_pressed, [&](QString guid, int idx) -> void { +        kopts.guid = guid; +        kopts.keycode = ""; +        kopts.button = idx; +        d.close(); +    });      d.exec(); -    label->setText(ret == "" ? "None" : static_cast<QString>(ret)); +    label->setText(kopts_to_string(kopts));      delete k;      delete l;  } diff --git a/gui/options-dialog.hpp b/gui/options-dialog.hpp index 3ef99d06..308b5b0f 100644 --- a/gui/options-dialog.hpp +++ b/gui/options-dialog.hpp @@ -19,5 +19,5 @@ private:  private slots:      void doOK();      void doCancel(); -    void bind_key(value<QString>& ret, QLabel* label); +    void bind_key(Shortcuts::key_opts &kopts, QLabel* label);  }; diff --git a/opentrack/keybinding-worker.cpp b/opentrack/keybinding-worker.cpp index 29b2cf9f..e6c023ef 100644 --- a/opentrack/keybinding-worker.cpp +++ b/opentrack/keybinding-worker.cpp @@ -72,6 +72,19 @@ void KeybindingWorker::run() {      while (!should_quit)      { +        { +            using joy_fn = std::function<void(const QString& guid, int idx)>; +             +            joy_fn f = [&](const QString& guid, int idx) -> void { +                Key k; +                k.keycode = idx; +                k.guid = guid; +                receiver(k); +            }; +             +            joy_ctx.poll(f); +        } +                  if (dinkeyboard->GetDeviceState(256, (LPVOID)keystate) != DI_OK) {              qDebug() << "Tracker::run GetDeviceState function failed!" << GetLastError();              Sleep(25); diff --git a/opentrack/keybinding-worker.hpp b/opentrack/keybinding-worker.hpp index e720ffdc..8cf59d65 100644 --- a/opentrack/keybinding-worker.hpp +++ b/opentrack/keybinding-worker.hpp @@ -15,6 +15,7 @@  #endif  #include "opentrack-compat/timer.hpp" +#include "opentrack/win32-joystick-shortcuts.hpp"  #include <QThread>  #include <QMutex>  #include <QWidget> @@ -27,6 +28,7 @@  #   include <dinput.h>  struct Key {      BYTE keycode; +    QString guid;      bool shift;      bool ctrl;      bool alt; @@ -38,7 +40,7 @@ public:      bool should_process()      { -        if (keycode == 0) +        if (keycode == 0 && guid == "")              return false;          bool ret = timer.elapsed_ms() > 100;          timer.start(); @@ -56,6 +58,7 @@ private:      LPDIRECTINPUT8 din;      LPDIRECTINPUTDEVICE8 dinkeyboard;      QMutex mtx; +    win32_joy_ctx joy_ctx;  public:      volatile bool should_quit;      std::function<void(Key&)> receiver; diff --git a/opentrack/shortcuts.cpp b/opentrack/shortcuts.cpp index 5f5ad922..0d7f79b9 100644 --- a/opentrack/shortcuts.cpp +++ b/opentrack/shortcuts.cpp @@ -34,19 +34,27 @@ void Shortcuts::bind_keyboard_shortcut(K &key, key_opts& k)      key = K();      int idx = 0;      QKeySequence code; - -    if (k.keycode == "") -        code = QKeySequence(Qt::Key_unknown); +     +    if (k.guid != "") +    { +        key.guid = k.guid; +        key.keycode = k.button; +    }      else -        code = QKeySequence::fromString(k.keycode, QKeySequence::PortableText); - -    Qt::KeyboardModifiers mods = Qt::NoModifier; -    if (code != Qt::Key_unknown) -        win_key::from_qt(code, idx, mods); -    key.shift = !!(mods & Qt::ShiftModifier); -    key.alt = !!(mods & Qt::AltModifier); -    key.ctrl = !!(mods & Qt::ControlModifier); -    key.keycode = idx; +    { +        if (k.keycode == "") +            code = QKeySequence(Qt::Key_unknown); +        else +            code = QKeySequence::fromString(k.keycode, QKeySequence::PortableText); +     +        Qt::KeyboardModifiers mods = Qt::NoModifier; +        if (code != Qt::Key_unknown) +            win_key::from_qt(code, idx, mods); +        key.shift = !!(mods & Qt::ShiftModifier); +        key.alt = !!(mods & Qt::AltModifier); +        key.ctrl = !!(mods & Qt::ControlModifier); +        key.keycode = idx; +    }  }  #endif @@ -56,6 +64,8 @@ void Shortcuts::receiver(Key &k)      std::vector<K*> ks { &keyCenter, &keyToggle, &keyZero };      for (K* k_ : ks)      { +        if (k.guid != k_->guid) +            continue;          if (k.keycode != k_->keycode)              continue;          if (!k_->should_process()) diff --git a/opentrack/shortcuts.h b/opentrack/shortcuts.h index 84231850..1643485e 100644 --- a/opentrack/shortcuts.h +++ b/opentrack/shortcuts.h @@ -47,10 +47,13 @@ public:  #endif      struct key_opts { -        value<QString> keycode; +        value<QString> keycode, guid; +        value<int> button;          key_opts(pbundle b, const QString& name) : -            keycode(b, QString("keycode-%1").arg(name), "") +            keycode(b, QString("keycode-%1").arg(name), ""), +            guid(b, QString("guid-%1").arg(name), ""), +            button(b, QString("button-%1").arg(name), -1)          {}      }; diff --git a/opentrack/win32-joystick-shortcuts.hpp b/opentrack/win32-joystick-shortcuts.hpp new file mode 100644 index 00000000..67465bce --- /dev/null +++ b/opentrack/win32-joystick-shortcuts.hpp @@ -0,0 +1,264 @@ +#pragma once + +#include <cstring> +#include <memory> +#include <vector> +#include <functional> +#include <algorithm> +#ifndef DIRECTINPUT_VERSION +#   define DIRECTINPUT_VERSION 0x800 +#endif +#include <dinput.h> +#include <windows.h> +#include "opentrack-compat/timer.hpp" +#include <QString> +#include <QDebug> + +struct win32_joy_ctx +{ +    using fn = std::function<void(const QString& guid, int btn)>; +     +    void poll(fn f) +    { +        refresh(); +        for (int i = joys.size() - 1; i >= 0; i--) +        { +            if (!joys[i]->poll(f)) +                joys.erase(joys.begin() + i); +        } +    } +     +    struct joy +    { +        LPDIRECTINPUTDEVICE8 joy_handle; +        QString guid; +         +        joy(LPDIRECTINPUTDEVICE8 handle, const QString& guid) : joy_handle(handle), guid(guid) +        { +            qDebug() << "got joy" << guid; +        } +         +        ~joy() +        { +            qDebug() << "nix joy" << guid; +            release(); +        } +         +        void release() +        { +            if (joy_handle) +            { +                (void) joy_handle->Unacquire(); +                joy_handle->Release(); +                joy_handle = nullptr; +            } +        } +         +        bool poll(fn f) +        { +            HRESULT hr; +            bool ok = false; +             +            for (int i = 0; i < 5; i++) +            { +                if (!FAILED(joy_handle->Poll())) +                { +                    ok = true; +                    break; +                } +                if ((hr = joy_handle->Acquire()) != DI_OK) +                    continue; +                else +                    ok = true; +                break; +            } +             +            if (!ok) +            { +                qDebug() << "joy acquire failed" << guid << hr; +                return false; +            } +             +            DIJOYSTATE2 js; +             +            if (FAILED(hr = joy_handle->GetDeviceState(sizeof(js), &js))) +            { +                qDebug() << "joy get state failed" << guid << hr; +                return false; +            } +             +            for (int i = 0; i < 128; i++) +                if (js.rgbButtons[i] & 0x80) +                    f(guid, i); +             +            return true; +        } +    }; +     +    static QString guid_to_string(const GUID guid) +    { +        char buf[40] = {0}; +        wchar_t szGuidW[40] = {0}; +         +        StringFromGUID2(guid, szGuidW, 40); +        WideCharToMultiByte(0, 0, szGuidW, -1, buf, 40, NULL, NULL); +         +        return QString(buf); +    } +     +    win32_joy_ctx() : dinput_handle(nullptr) +    { +        (void) CoInitialize(nullptr); +         +        HRESULT hr; +         +        if (FAILED(hr = DirectInput8Create(GetModuleHandle(nullptr), +                                           DIRECTINPUT_VERSION, +                                           IID_IDirectInput8, +                                           (void**) &dinput_handle, +                                           nullptr))) +            goto fail; +         +        return; +fail: +        qDebug() << "dinput8 failed for shortcuts" << hr; +         +        release(); +    } +     +    ~win32_joy_ctx() +    { +        release(); +    } +     +    void release() +    { +        joys = std::vector<std::shared_ptr<joy>>(); +        if (dinput_handle) +        { +            dinput_handle->Release(); +            dinput_handle = nullptr; +        } +    } +     +    void refresh() +    { +        if (!dinput_handle) +            return; +         +        if (timer_joylist.elapsed_ms() < joylist_refresh_ms) +            return; +         +        timer_joylist.start(); +         +        enum_state st(dinput_handle, joys); +    } +     +    struct enum_state +    { +        std::vector<std::shared_ptr<joy>>& joys; +        std::vector<QString> all; +        LPDIRECTINPUT8 dinput_handle; +         +        enum_state(LPDIRECTINPUT8 di, std::vector<std::shared_ptr<joy>>& joys) : joys(joys), dinput_handle(di) +        { +            HRESULT hr; +             +            if(FAILED(hr = dinput_handle->EnumDevices(DI8DEVCLASS_GAMECTRL, +                                                      EnumJoysticksCallback, +                                                      this, +                                                      DIEDFL_ATTACHEDONLY))) +            { +                qDebug() << "failed enum joysticks" << hr; +                return; +            } +             +            for (int i = joys.size() - 1; i >= 0; i--) +            { +                const auto& guid = joys[i]->guid; +                if (std::find_if(all.cbegin(), all.cend(), [&](const QString& guid2) -> bool { return guid == guid2; }) == all.cend()) +                    joys.erase(joys.begin() + i); +            } +        } +         +        static BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext) +        { +            enum_state& state = *reinterpret_cast<enum_state*>(pContext); +            const QString guid = guid_to_string(pdidInstance->guidInstance); +#if 0 +            const QString name = QString(pdidInstance->tszInstanceName); +            // the logic here is that iff multiple joysticks of same name exist, then take guids into account at all +            const int cnt_names = std::count_if(state.joys.begin(), state.joys.end(), [&](const joy& j) -> bool { return j.name == name; }); +            // this is potentially bad since replugged sticks can change guids (?) +#endif +             +            const bool exists = std::find_if(state.joys.cbegin(), +                                             state.joys.cend(), +                                             [&](const std::shared_ptr<joy>& j) -> bool { return j->guid == guid; }) != state.joys.cend(); +             +            state.all.push_back(guid); +             +            if (!exists) +            { +                HRESULT hr; +                LPDIRECTINPUTDEVICE8 h; +                if (FAILED(hr = state.dinput_handle->CreateDevice(pdidInstance->guidInstance, &h, nullptr))) +                { +                    qDebug() << "create joystick breakage" << guid << hr; +                    goto end; +                } +                if (FAILED(h->SetDataFormat(&c_dfDIJoystick2))) +                { +                    qDebug() << "format"; +                    h->Release(); +                    goto end; +                } +                 +                if (FAILED(h->SetCooperativeLevel((HWND) GetDesktopWindow(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND))) +                { +                    qDebug() << "coop"; +                    h->Release(); +                    goto end; +                } +#if 0 +                if (FAILED(hr = h->EnumObjects(EnumObjectsCallback, h, DIDFT_ALL))) +                { +                    qDebug() << "enum-objects"; +                    h->Release(); +                    goto end; +                } +#endif +                state.joys.push_back(std::make_shared<joy>(h, guid)); +            } +             +end:        return DIENUM_CONTINUE; +        } +         +#if 0 +        static BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* ctx) +        { +            if (pdidoi->dwType & DIDFT_AXIS) +            { +                DIPROPRANGE diprg; +                memset(&diprg, 0, sizeof(diprg)); +                diprg.diph.dwSize = sizeof( DIPROPRANGE ); +                diprg.diph.dwHeaderSize = sizeof( DIPROPHEADER ); +                diprg.diph.dwHow = DIPH_BYID; +                diprg.diph.dwObj = pdidoi->dwType; +                diprg.lMax = 32; +                diprg.lMin = -32; +                 +                if (FAILED(reinterpret_cast<LPDIRECTINPUTDEVICE8>(ctx)->SetProperty(DIPROP_RANGE, &diprg.diph))) +                    return DIENUM_STOP; +            } +             +            return DIENUM_CONTINUE; +        } +#endif +    }; +     +    LPDIRECTINPUT8 dinput_handle; +    std::vector<std::shared_ptr<joy>> joys; +    Timer timer_joylist; +    enum { joylist_refresh_ms = 2000 }; +};
\ No newline at end of file | 
