diff --git a/src/mumble/GKey.cpp b/src/mumble/GKey.cpp new file mode 100644 index 00000000000..600f2337506 --- /dev/null +++ b/src/mumble/GKey.cpp @@ -0,0 +1,169 @@ +/* Copyright (C) 2015, Jordan J Klassen + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* SPECIFICATION + * The code interfacing with the Logitech G-Keys DLL was implemented using the + * following spec: + * + * The G-keys DLL lives in + * "C:\Program Files\Logitech Gaming Software\SDK\G-key\x64\LogitechGkey.dll" for x64 and + * "C:\Program Files\Logitech Gaming Software\SDK\G-key\x86\LogitechGkey.dll" for x86. + * + * Its location can also be read from the registry, using the following keys: + * + * x86: + * "HKEY_CLASSES_ROOT\CLSID\{7bded654-f278-4977-a20f-6e72a0d07859}\ServerBinary" + * + * x64: + * "HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{7bded654-f278-4977-a20f-6e72a0d07859}\ServerBinary" + * + * The registry keys are needed if a user installed the Logitech Gaming + * Software in a non-standard location. + * + * The DLL has an init function, it's called "LogiGkeyInit". It takes a + * pointer, but the parameter must always be NULL. The function returns a BOOL + * as a status code. + * + * The DLL also has a shutdown function, called "LogiGkeyShutdown". It takes + * no parameters and does not return anything. + * + * You can poll for button states with the DLL using the functions + * "LogiGkeyIsMouseButtonPressed" and "LogiGkeyIsKeyboardGkeyPressed". + * + * The function "LogiGkeyIsMouseButtonPressed" takes a single int parameter, a + * button number. Mouse button numbers run from 6 up to and including 20. The + * function returns a BOOL that is true if the button is pressed, and false if + * not. + * + * The function "LogiGkeyIsKeyboardGkeyPressed" takes two int parameters, a + * button number and a mode number. Keyboard button numbers run from 1 up to + * and including 29. The mode number can 1, 2 or 3. The mode checks the button + * state in a specific mode. Typically, one queries all buttons for all modes, + * so one ends up with 29*3 calls to the function. The function returns a BOOL + * that is true if the button in the given mode is pressed, and false if not. + * + * There are also two functions, "LogiGkeyGetMouseButtonString" and + * "LogiGkeyGetKeyboardGkeyString". They take the same parameters as the + * polling functions above, but do not check whether the button is pressed or + * not. Instead, they return the name of the button being queried as a pointer + * to a NUL-terminated array of wchar_t's. Presumably, the pointer will be + * NULL if the name cannot be retrieved or translated. +*/ + +/* USAGE + * In order to use the gkeys on a logitech keyboard, any user must have the + * Logitech Gaming Software version 8.55+ installed on their computer. Then + * (re)start mumble. When mumble initializes the library, the LGS (Logitech + * Gaming Software) will create a profile called "mumble", featuring the mumble + * icon. In LGS, right click this icon, and select either "Set as Default" or + * "Set as Persistent". (See "What are persistent and default profiles?" in the + * LGS help by clicking on the "?" icon on the LGS window, or at + * http://www.logitech.com/assets/51813/3/lgs-guide.pdf). If mumble is not set + * as the default or persistent profile, then your keys will not be active + * unless mumble is the active window. + */ + +#include "mumble_pch.hpp" + +#include "GKey.h" + +#ifdef Q_CC_GNU +#define RESOLVE(var) { var = reinterpret_cast<__typeof__(var)>(qlLogiGkey.resolve(#var)); bValid = bValid && (var != NULL); } +#else +#define RESOLVE(var) { * reinterpret_cast(&var) = static_cast(qlLogiGkey.resolve(#var)); bValid = bValid && (var != NULL); } +#endif + +const QUuid GKeyLibrary::quMouse = QUuid(QString::fromLatin1(GKEY_MOUSE_GUID)); +const QUuid GKeyLibrary::quKeyboard = QUuid(QString::fromLatin1(GKEY_KEYBOARD_GUID)); + +GKeyLibrary::GKeyLibrary() +{ + QStringList alternatives; + + HKEY key = NULL; + DWORD type = 0; + WCHAR wcLocation[510]; + DWORD len = 510; + if (RegOpenKeyEx(GKEY_LOGITECH_DLL_REG_HKEY, GKEY_LOGITECH_DLL_REG_PATH, NULL, KEY_READ, &key) == ERROR_SUCCESS) { + LONG err = RegQueryValueEx(key, L"", NULL, &type, reinterpret_cast(wcLocation), &len); + if (err == ERROR_SUCCESS && type == REG_SZ) { + QString qsLocation = QString::fromUtf16(reinterpret_cast(wcLocation), len / 2); + qWarning("GKeyLibrary: Found ServerBinary with libLocation = \"%s\", len = %d", qPrintable(qsLocation), len); + alternatives << qsLocation; + } else { + qWarning("GKeyLibrary: Error looking up ServerBinary (Error: 0x%x, Type: 0x%x, len: %d)", err, type, len); + } + } + + alternatives << QString::fromLatin1(GKEY_LOGITECH_DLL_DEFAULT_LOCATION); + foreach(const QString &lib, alternatives) { + qlLogiGkey.setFileName(lib); + + if (qlLogiGkey.load()) { + bValid = true; + break; + } + } + + RESOLVE(LogiGkeyInit); + RESOLVE(LogiGkeyShutdown); + RESOLVE(LogiGkeyIsMouseButtonPressed); + RESOLVE(LogiGkeyIsKeyboardGkeyPressed); + RESOLVE(LogiGkeyGetMouseButtonString); + RESOLVE(LogiGkeyGetKeyboardGkeyString); + + if (bValid) + bValid = LogiGkeyInit(NULL); +} + +GKeyLibrary::~GKeyLibrary() { + if (LogiGkeyShutdown != NULL) + LogiGkeyShutdown(); +} + +bool GKeyLibrary::isValid() const { + return bValid; +} + +bool GKeyLibrary::isMouseButtonPressed(int button) { + return LogiGkeyIsMouseButtonPressed(button); +} + +bool GKeyLibrary::isKeyboardGkeyPressed(int key, int mode) { + return LogiGkeyIsKeyboardGkeyPressed(key, mode); +} + +QString GKeyLibrary::getMouseButtonString(int button) { + return QString::fromWCharArray(LogiGkeyGetMouseButtonString(button)); +} + +QString GKeyLibrary::getKeyboardGkeyString(int key, int mode) { + return QString::fromWCharArray(LogiGkeyGetKeyboardGkeyString(key, mode)); +} diff --git a/src/mumble/GKey.h b/src/mumble/GKey.h new file mode 100644 index 00000000000..e9b3e27f643 --- /dev/null +++ b/src/mumble/GKey.h @@ -0,0 +1,86 @@ +/* Copyright (C) 2015, Jordan J Klassen + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MUMBLE_MUMBLE_GKEY_H +#define MUMBLE_MUMBLE_GKEY_H + +#include +#include +#include + +#define GKEY_LOGITECH_DLL_REG_HKEY HKEY_CLASSES_ROOT +#ifdef _M_X64 +#define GKEY_LOGITECH_DLL_REG_PATH L"Wow6432Node\\CLSID\\{7bded654-f278-4977-a20f-6e72a0d07859}\\ServerBinary" +#define GKEY_LOGITECH_DLL_DEFAULT_LOCATION "C:/Program Files/Logitech Gaming Software/SDK/G-key/x64/LogitechGkey.dll" +#else +#define GKEY_LOGITECH_DLL_REG_PATH L"CLSID\\{7bded654-f278-4977-a20f-6e72a0d07859}\\ServerBinary" +#define GKEY_LOGITECH_DLL_DEFAULT_LOCATION "C:/Program Files/Logitech Gaming Software/SDK/G-key/x86/LogitechGkey.dll" +#endif + +#define GKEY_MIN_MOUSE_BUTTON 6 +#define GKEY_MAX_MOUSE_BUTTON 20 +#define GKEY_MIN_KEYBOARD_BUTTON 1 +#define GKEY_MAX_KEYBOARD_BUTTON 29 +#define GKEY_MIN_KEYBOARD_MODE 1 +#define GKEY_MAX_KEYBOARD_MODE 3 + +#define GKEY_BUTTON_MOUSE 1 +#define GKEY_BUTTON_KEYBOARD 2 + +#define GKEY_MOUSE_GUID "c41e60af-9022-46cf-bc39-37981082d716" +#define GKEY_KEYBOARD_GUID "153e64e6-98c8-4e03-80ef-5ffd33d25b8a" + +class GKeyLibrary +{ +public: + GKeyLibrary(); + virtual ~GKeyLibrary(); + bool isValid() const; + static const QUuid quMouse; + static const QUuid quKeyboard; + + bool isMouseButtonPressed(int button); + bool isKeyboardGkeyPressed(int key, int mode); + QString getMouseButtonString(int button); + QString getKeyboardGkeyString(int key, int mode); + +protected: + QLibrary qlLogiGkey; + bool bValid; + + BOOL (*LogiGkeyInit)(void *); + void (*LogiGkeyShutdown)(); + BOOL (*LogiGkeyIsMouseButtonPressed)(int button); + BOOL (*LogiGkeyIsKeyboardGkeyPressed)(int key, int mode); + wchar_t *(*LogiGkeyGetMouseButtonString)(int button); + wchar_t *(*LogiGkeyGetKeyboardGkeyString)(int key, int mode); +}; + +#endif // MUMBLE_MUMBLE_GKEY_H diff --git a/src/mumble/GlobalShortcut_win.cpp b/src/mumble/GlobalShortcut_win.cpp index 054b20b3d27..3218e25f520 100644 --- a/src/mumble/GlobalShortcut_win.cpp +++ b/src/mumble/GlobalShortcut_win.cpp @@ -151,6 +151,13 @@ void GlobalShortcutWin::run() { hhMouse = SetWindowsHookEx(WH_MOUSE_LL, HookMouse, hSelf, 0); } +#ifdef USE_GKEY + if (g.s.bEnableGKey) { + gkey = new GKeyLibrary(); + qWarning("GlobalShortcutWin: GKeys initialized, isValid: %d", gkey->isValid()); + } +#endif + QTimer * timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timeTicked())); timer->start(20); @@ -159,6 +166,10 @@ void GlobalShortcutWin::run() { exec(); +#ifdef USE_GKEY + delete gkey; +#endif + if (bHook) { UnhookWindowsHookEx(hhKeyboard); UnhookWindowsHookEx(hhMouse); @@ -557,6 +568,26 @@ void GlobalShortcutWin::timeTicked() { handleButton(ql, rgdod[j].dwData & 0x80); } } +#ifdef USE_GKEY + if (g.s.bEnableGKey && gkey->isValid()) { + for (int button = GKEY_MIN_MOUSE_BUTTON; button <= GKEY_MAX_MOUSE_BUTTON; button++) { + QList ql; + ql << button; + ql << GKeyLibrary::quMouse; + handleButton(ql, gkey->isMouseButtonPressed(button)); + } + for (int mode = GKEY_MIN_KEYBOARD_MODE; mode <= GKEY_MAX_KEYBOARD_MODE; mode++) { + for (int key = GKEY_MIN_KEYBOARD_BUTTON; key <= GKEY_MAX_KEYBOARD_BUTTON; key++) { + QList ql; + // Store the key and mode in one int + // bit 0..15: mode, bit 16..31: key + ql << (key | (mode << 16)); + ql << GKeyLibrary::quKeyboard; + handleButton(ql, gkey->isKeyboardGkeyPressed(key, mode)); + } + } + } +#endif } QString GlobalShortcutWin::buttonName(const QVariant &v) { @@ -575,6 +606,24 @@ QString GlobalShortcutWin::buttonName(const QVariant &v) { QString device=guid.toString(); QString name=QLatin1String("Unknown"); + +#ifdef USE_GKEY + if (g.s.bEnableGKey && gkey->isValid()) { + bool isGKey = false; + if (guid == GKeyLibrary::quMouse) { + isGKey = true; + name = gkey->getMouseButtonString(type); + } else if (guid == GKeyLibrary::quKeyboard) { + isGKey = true; + name = gkey->getKeyboardGkeyString(type & 0xFFFF, type >> 16); + } + if (isGKey) { + device = QLatin1String("GKey:"); + return device + name; // Example output: "Gkey:G6/M1" + } + } +#endif + InputDevice *id = gsw->qhInputDevices.value(guid); if (guid == GUID_SysMouse) device=QLatin1String("M:"); diff --git a/src/mumble/GlobalShortcut_win.h b/src/mumble/GlobalShortcut_win.h index 07017aee2bf..18dd979b7ef 100644 --- a/src/mumble/GlobalShortcut_win.h +++ b/src/mumble/GlobalShortcut_win.h @@ -34,6 +34,10 @@ #include "GlobalShortcut.h" #include "Timer.h" +#ifdef USE_GKEY +#include "GKey.h" +#endif + #define DIRECTINPUT_VERSION 0x0800 #include @@ -70,6 +74,9 @@ class GlobalShortcutWin : public GlobalShortcutEngine { unsigned int uiHardwareDevices; Timer tDoubleClick; bool bHook; +#ifdef USE_GKEY + GKeyLibrary *gkey; +#endif static BOOL CALLBACK EnumSuitableDevicesCB(LPCDIDEVICEINSTANCE, LPDIRECTINPUTDEVICE8, DWORD, DWORD, LPVOID); static BOOL CALLBACK EnumDevicesCB(LPCDIDEVICEINSTANCE, LPVOID); static BOOL CALLBACK EnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef); diff --git a/src/mumble/Settings.cpp b/src/mumble/Settings.cpp index 2c0c43b85a9..b05242f7245 100644 --- a/src/mumble/Settings.cpp +++ b/src/mumble/Settings.cpp @@ -412,7 +412,8 @@ Settings::Settings() { bShortcutEnable = true; bSuppressMacEventTapWarning = false; bEnableEvdev = false; - + bEnableGKey = true; + for (int i=Log::firstMsgType; i<=Log::lastMsgType; ++i) { qmMessages.insert(i, Settings::LogConsole | Settings::LogBalloon | Settings::LogTTS); qmMessageSounds.insert(i, QString()); @@ -730,6 +731,7 @@ void Settings::load(QSettings* settings_ptr) { SAVELOAD(bShortcutEnable, "shortcut/enable"); SAVELOAD(bSuppressMacEventTapWarning, "shortcut/mac/suppresswarning"); SAVELOAD(bEnableEvdev, "shortcut/linux/evdev/enable"); + SAVELOAD(bEnableGKey, "shortcut/gkey"); int nshorts = settings_ptr->beginReadArray(QLatin1String("shortcuts")); for (int i=0; i qlShortcuts; enum MessageLog { LogNone = 0x00, LogConsole = 0x01, LogTTS = 0x02, LogBalloon = 0x04, LogSoundfile = 0x08}; diff --git a/src/mumble/mumble.pro b/src/mumble/mumble.pro index 0996270bd1d..c660b8ba225 100644 --- a/src/mumble/mumble.pro +++ b/src/mumble/mumble.pro @@ -374,6 +374,15 @@ win32 { !CONFIG(no-wasapi) { CONFIG *= wasapi } + !CONFIG(no-gkey) { + CONFIG *= gkey + } + + CONFIG(gkey) { + HEADERS *= GKey.h + SOURCES *= GKey.cpp + DEFINES *= USE_GKEY + } !CONFIG(mumble_dll) { !CONFIG(no-elevation) { @@ -436,7 +445,7 @@ unix { LIBS += -lxar } - LIBS += -framework ScriptingBridge + LIBS += -framework ScriptingBridge OBJECTIVE_SOURCES += Overlay_macx.mm } else { SOURCES += Overlay_unix.cpp