diff --git a/Readme.md b/Readme.md index 76248ba..010b8f2 100644 --- a/Readme.md +++ b/Readme.md @@ -230,6 +230,7 @@ Any changes to controller settings are immediately applied by Revive (Requires R - **Steam Desktop Overlay Width:** Allows to increase the size of the Steam desktop overlay. Useful for multi-monitor setups. +- **Media Control Keys:** Allows controlling a media player through the media keys. This is the same as having a keyboard with media keys and then pressing them. Should support most common media players. ## - Accessibility Page: diff --git a/advancedSettings.pro b/advancedSettings.pro index 17c0b17..00dbaec 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -139,4 +139,5 @@ DISTFILES += \ src/res/qml/SettingsPage.qml \ src/res/qml/StatisticsPage.qml \ src/res/qml/SteamVRPage.qml \ - src/res/qml/UtilitiesPage.qml + src/res/qml/UtilitiesPage.qml \ + src/res/qml/media_keys/* diff --git a/docs/screenshots/UtilitiesPage.png b/docs/screenshots/UtilitiesPage.png index 786066f..0cddbae 100644 Binary files a/docs/screenshots/UtilitiesPage.png and b/docs/screenshots/UtilitiesPage.png differ diff --git a/src/res/qml/UtilitiesPage.qml b/src/res/qml/UtilitiesPage.qml index 76841cf..f085376 100644 --- a/src/res/qml/UtilitiesPage.qml +++ b/src/res/qml/UtilitiesPage.qml @@ -320,6 +320,155 @@ MyStackViewPage { } } + GroupBox { + id: mediaKeysGroupBox + Layout.fillWidth: true + + label: MyText { + leftPadding: 10 + text: "Media Control Keys" + bottomPadding: -10 + } + background: Rectangle { + color: "transparent" + border.color: "#ffffff" + radius: 8 + } + ColumnLayout { + anchors.fill: parent + + Rectangle { + color: "#ffffff" + height: 1 + Layout.fillWidth: true + Layout.bottomMargin: 5 + } + + + RowLayout { + property bool playButtonIsPlay: false + MyPushButton2 { + id: previousSongButton + Layout.preferredHeight: 48 + Layout.preferredWidth: 48 + contentItem: Image { + source: "media_keys/outline_skip_previous_white_24dp.svg" + sourceSize: Qt.size( imgSP.sourceSize.width*2, imgSP.sourceSize.height*2 ) + Image{ + id: imgSP + source: parent.source + width:0 + height:0 + } + anchors.fill: parent + } + onClicked: { + UtilitiesTabController.sendMediaPreviousSong() + } + + background: Rectangle { + opacity: parent.down ? 1.0 : (parent.activeFocus ? 0.5 : 0.0) + color: "#406288" + radius: 4 + anchors.fill: parent + } + } + MyPushButton2 { + id: stopSongButton + Layout.preferredHeight: 48 + Layout.preferredWidth: 48 + + contentItem: Image { + source: "media_keys/outline_stop_white_24dp.svg" + sourceSize: Qt.size( imgS.sourceSize.width*2, imgS.sourceSize.height*2 ) + Image{ + id: imgS + source: parent.source + width:0 + height:0 + } + anchors.fill: parent + } + onClicked: { + UtilitiesTabController.sendMediaStopSong() + playPauseSongButton.contentItem.source = "media_keys/outline_play_arrow_white_24dp.svg" + parent.playButtonIsPlay = true + } + + background: Rectangle { + opacity: parent.down ? 1.0 : (parent.activeFocus ? 0.5 : 0.0) + color: "#406288" + radius: 4 + anchors.fill: parent + } + } + MyPushButton2 { + id: playPauseSongButton + Layout.preferredHeight: 48 + Layout.preferredWidth: 48 + contentItem: Image { + // Assume that music is playing when people enter VR. + source: "media_keys/outline_pause_white_24dp.svg" + sourceSize: Qt.size( imgP.sourceSize.width*2, imgP.sourceSize.height*2 ) + Image{ + id: imgP + source: parent.source + width:0 + height:0 + } + + anchors.fill: parent + } + onClicked: { + UtilitiesTabController.sendMediaPausePlay() + if (parent.playButtonIsPlay) { + playPauseSongButton.contentItem.source = "media_keys/outline_pause_white_24dp.svg" + parent.playButtonIsPlay = false + } else { + playPauseSongButton.contentItem.source = "media_keys/outline_play_arrow_white_24dp.svg" + parent.playButtonIsPlay = true + } + + } + + background: Rectangle { + opacity: parent.down ? 1.0 : (parent.activeFocus ? 0.5 : 0.0) + color: "#406288" + radius: 4 + anchors.fill: parent + } + } + MyPushButton2 { + id: nextSongButton + Layout.preferredHeight: 48 + Layout.preferredWidth: 48 + + contentItem: Image { + source: "media_keys/outline_skip_next_white_24dp.svg" + sourceSize: Qt.size( imgSN.sourceSize.width*2, imgSN.sourceSize.height*2 ) + Image{ + id: imgSN + source: parent.source + width:0 + height:0 + } + anchors.fill: parent + } + onClicked: { + UtilitiesTabController.sendMediaNextSong() + } + + background: Rectangle { + opacity: parent.down ? 1.0 : (parent.activeFocus ? 0.5 : 0.0) + color: "#406288" + radius: 4 + anchors.fill: parent + } + } + } + } + } + Item { Layout.fillHeight: true } diff --git a/src/res/qml/media_keys/outline_pause_white_24dp.svg b/src/res/qml/media_keys/outline_pause_white_24dp.svg new file mode 100644 index 0000000..f59d7c9 --- /dev/null +++ b/src/res/qml/media_keys/outline_pause_white_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/res/qml/media_keys/outline_play_arrow_white_24dp.svg b/src/res/qml/media_keys/outline_play_arrow_white_24dp.svg new file mode 100644 index 0000000..2b248b9 --- /dev/null +++ b/src/res/qml/media_keys/outline_play_arrow_white_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/res/qml/media_keys/outline_skip_next_white_24dp.svg b/src/res/qml/media_keys/outline_skip_next_white_24dp.svg new file mode 100644 index 0000000..23fa6ea --- /dev/null +++ b/src/res/qml/media_keys/outline_skip_next_white_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/res/qml/media_keys/outline_skip_previous_white_24dp.svg b/src/res/qml/media_keys/outline_skip_previous_white_24dp.svg new file mode 100644 index 0000000..1d08e59 --- /dev/null +++ b/src/res/qml/media_keys/outline_skip_previous_white_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/res/qml/media_keys/outline_stop_white_24dp.svg b/src/res/qml/media_keys/outline_stop_white_24dp.svg new file mode 100644 index 0000000..2123cee --- /dev/null +++ b/src/res/qml/media_keys/outline_stop_white_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/tabcontrollers/KeyboardInput.h b/src/tabcontrollers/KeyboardInput.h index f660eb3..cb1226a 100644 --- a/src/tabcontrollers/KeyboardInput.h +++ b/src/tabcontrollers/KeyboardInput.h @@ -15,8 +15,12 @@ public: virtual void sendKeyboardInput( QString input ) = 0; virtual void sendKeyboardEnter() = 0; - virtual void sendKeyboardBackspace( int count ) = 0; + virtual void sendKeyboardBackspace( const int count ) = 0; virtual void sendKeyboardAltTab() = 0; + virtual void sendMediaNextSong() = 0; + virtual void sendMediaPreviousSong() = 0; + virtual void sendMediaPausePlay() = 0; + virtual void sendMediaStopSong() = 0; }; } // namespace advsettings diff --git a/src/tabcontrollers/UtilitiesTabController.cpp b/src/tabcontrollers/UtilitiesTabController.cpp index 0684b46..49812fa 100644 --- a/src/tabcontrollers/UtilitiesTabController.cpp +++ b/src/tabcontrollers/UtilitiesTabController.cpp @@ -83,11 +83,28 @@ void UtilitiesTabController::sendKeyboardAltTab() keyboardInput->sendKeyboardAltTab(); } -void UtilitiesTabController::sendKeyboardBackspace( int count ) +void UtilitiesTabController::sendKeyboardBackspace( const int count ) { keyboardInput->sendKeyboardBackspace( count ); } +void UtilitiesTabController::sendMediaNextSong() +{ + keyboardInput->sendMediaNextSong(); +} +void UtilitiesTabController::sendMediaPreviousSong() +{ + keyboardInput->sendMediaPreviousSong(); +} +void UtilitiesTabController::sendMediaPausePlay() +{ + keyboardInput->sendMediaPausePlay(); +} +void UtilitiesTabController::sendMediaStopSong() +{ + keyboardInput->sendMediaStopSong(); +} + bool UtilitiesTabController::alarmEnabled() const { return m_alarmEnabled; diff --git a/src/tabcontrollers/UtilitiesTabController.h b/src/tabcontrollers/UtilitiesTabController.h index 375e2e4..1eb601d 100644 --- a/src/tabcontrollers/UtilitiesTabController.h +++ b/src/tabcontrollers/UtilitiesTabController.h @@ -69,7 +69,11 @@ public slots: void sendKeyboardInput( QString input ); void sendKeyboardEnter(); void sendKeyboardAltTab(); - void sendKeyboardBackspace( int count ); + void sendKeyboardBackspace( const int count ); + void sendMediaNextSong(); + void sendMediaPreviousSong(); + void sendMediaPausePlay(); + void sendMediaStopSong(); void setAlarmEnabled( bool enabled, bool notify = true ); void setAlarmIsModal( bool modal, bool notify = true ); diff --git a/src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp b/src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp index 4bb4aea..f9b78c8 100644 --- a/src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp +++ b/src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp @@ -17,7 +17,7 @@ void KeyboardInputDummy::sendKeyboardEnter() // noop } -void KeyboardInputDummy::sendKeyboardBackspace( int count ) +void KeyboardInputDummy::sendKeyboardBackspace( const int count ) { // noop UNREFERENCED_PARAMETER( count ); @@ -28,4 +28,24 @@ void KeyboardInputDummy::sendKeyboardAltTab() // noop } +void KeyboardInputDummy::sendMediaNextSong() +{ + // noop +} + +void KeyboardInputDummy::sendMediaPreviousSong() +{ + // noop +} + +void KeyboardInputDummy::sendMediaPausePlay() +{ + // noop +} + +void KeyboardInputDummy::sendMediaStopSong() +{ + // noop +} + } // namespace advsettings diff --git a/src/tabcontrollers/keyboardinput/KeyboardInputDummy.h b/src/tabcontrollers/keyboardinput/KeyboardInputDummy.h index 66e5fa5..cf63f1f 100644 --- a/src/tabcontrollers/keyboardinput/KeyboardInputDummy.h +++ b/src/tabcontrollers/keyboardinput/KeyboardInputDummy.h @@ -11,8 +11,12 @@ class KeyboardInputDummy : public KeyboardInput public: virtual void sendKeyboardInput( QString input ) override; virtual void sendKeyboardEnter() override; - virtual void sendKeyboardBackspace( int count ) override; + virtual void sendKeyboardBackspace( const int count ) override; virtual void sendKeyboardAltTab() override; + virtual void sendMediaNextSong() override; + virtual void sendMediaPreviousSong() override; + virtual void sendMediaPausePlay() override; + virtual void sendMediaStopSong() override; }; } // namespace advsettings diff --git a/src/tabcontrollers/keyboardinput/KeyboardInputWindows.cpp b/src/tabcontrollers/keyboardinput/KeyboardInputWindows.cpp index 9ae37ac..8e5a84a 100644 --- a/src/tabcontrollers/keyboardinput/KeyboardInputWindows.cpp +++ b/src/tabcontrollers/keyboardinput/KeyboardInputWindows.cpp @@ -4,6 +4,20 @@ namespace advsettings { +/*! +Represents the state of a keyboard button press. Can be either Up or Down. + +Implemented because neither Qt nor Windows.h included a sensible key state enum. +*/ +enum class KeyStatus +{ + Up, + Down, +}; + +/*! +Fills out an INPUT struct ip with scanCode and keyup status. +*/ void fillKiStruct( INPUT& ip, WORD scanCode, bool keyup ) { ip.type = INPUT_KEYBOARD; @@ -21,11 +35,18 @@ void fillKiStruct( INPUT& ip, WORD scanCode, bool keyup ) ip.ki.time = 0; }; -void sendKeyboardInputRaw( int inputCount, LPINPUT input ) +/*! +Calls SendInput on an INPUT struct and has associated error handling. + +inputCount can't be deduced from input because of implicit conversions from +arrays to pointers when passed as arguments. +*/ +void sendKeyboardInputRaw( const int inputCount, const LPINPUT input ) { - if ( ( inputCount > 0 ) - && !SendInput( - static_cast( inputCount ), input, sizeof( INPUT ) ) ) + const auto success + = SendInput( static_cast( inputCount ), input, sizeof( INPUT ) ); + + if ( ( inputCount > 0 ) && !success ) { char* err; auto errCode = GetLastError(); @@ -50,6 +71,62 @@ void sendKeyboardInputRaw( int inputCount, LPINPUT input ) } } +/*! +Returns an INPUT struct with the corresponding virtualKeyCode and keyStatus set. + +All fields except ki.wVk and ki.dwFlags are zero. +The INPUT struct is used with the SendInput Windows function. +Official Docs: +https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-taginput +*/ +INPUT createInputStruct( const WORD virtualKeyCode, const KeyStatus keyStatus ) +{ + // Zero init to ensure random data doesn't muck something up. + INPUT input = {}; + + input.type = INPUT_KEYBOARD; + + input.ki.wVk = virtualKeyCode; + + if ( keyStatus == KeyStatus::Up ) + { + input.ki.dwFlags = KEYEVENTF_KEYUP; + } + else if ( keyStatus == KeyStatus::Down ) + { + // Struct is already zero initialized, but this is here for clarity. + // Compiler will likely sort this out, otherwise the performance hit is + // negligible. + // There is no corresponding KEYDOWN event, you just don't + // set KEYEVENTF_KEYUP. + input.ki.dwFlags = 0; + } + + // The sizeof(INPUT) on MSVC is 28. Returning by value is a non-issue + // compared to the simplicity of not having to pass a ref to an already + // existing INPUT struct, and possibly forgetting to zero it out. + return input; +} + +/*! +Sends a key press to Windows. The virtualKeyCode is a Windows specific define +found in . + +Virtual Key Codes offical docs: +https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes +*/ +void sendKeyPressAndRelease( const WORD virtualKeyCode ) +{ + const auto press = createInputStruct( virtualKeyCode, KeyStatus::Down ); + const auto release = createInputStruct( virtualKeyCode, KeyStatus::Up ); + + constexpr auto numberOfActions = 2; + + INPUT actions[numberOfActions] = { press, release }; + + sendKeyboardInputRaw( numberOfActions, actions ); +} + void KeyboardInputWindows::sendKeyboardInput( QString input ) { int len = input.length(); @@ -122,44 +199,85 @@ void KeyboardInputWindows::sendKeyboardInput( QString input ) } } +/*! +Sends a single Enter keypress to Windows. +*/ void KeyboardInputWindows::sendKeyboardEnter() { - LPINPUT ips = new INPUT[2]; - fillKiStruct( ips[0], VK_RETURN, false ); - fillKiStruct( ips[1], VK_RETURN, true ); - - sendKeyboardInputRaw( 2, ips ); - delete[] ips; + sendKeyPressAndRelease( VK_RETURN ); } -void KeyboardInputWindows::sendKeyboardBackspace( int count ) +/*! +Sends count amount of backspaces to Windows. count of 0 or below will do +nothing. +*/ +void KeyboardInputWindows::sendKeyboardBackspace( const int count ) { - if ( count > 0 ) + // We can only send a positive amount of key presses. + if ( count <= 0 ) { - // We ensure that count is nonnegative, therefore safe cast. - LPINPUT ips = new INPUT[static_cast( count ) * 2]; - for ( int i = 0; i < count; i++ ) - { - fillKiStruct( ips[2 * i], VK_BACK, false ); - fillKiStruct( ips[2 * i + 1], VK_BACK, true ); - } + return; + } - sendKeyboardInputRaw( count * 2, ips ); - delete[] ips; + for ( int presses = 0; presses < count; ++presses ) + { + sendKeyPressAndRelease( VK_BACK ); } } +/*! +Sends an Alt + Tab combination to Windows. + +Used for tabbing out of game windows blocking other programs on desktop while in +VR. At least one use case was tabbing out of a fullscreen game to be able to use +a music player. +*/ void KeyboardInputWindows::sendKeyboardAltTab() { - LPINPUT ips = new INPUT[4]; - // VK_MENU is alt - fillKiStruct( ips[0], VK_MENU, false ); - fillKiStruct( ips[1], VK_TAB, false ); - fillKiStruct( ips[2], VK_TAB, true ); - fillKiStruct( ips[3], VK_MENU, true ); + // VK_MENU is Alt. + const auto pressAlt = createInputStruct( VK_MENU, KeyStatus::Down ); + const auto pressTab = createInputStruct( VK_TAB, KeyStatus::Down ); + const auto releaseAlt = createInputStruct( VK_MENU, KeyStatus::Up ); + const auto releaseTab = createInputStruct( VK_TAB, KeyStatus::Up ); - sendKeyboardInputRaw( 4, ips ); - delete[] ips; + constexpr auto numberOfActions = 4; + + INPUT actions[numberOfActions] + = { pressAlt, pressTab, releaseAlt, releaseTab }; + + sendKeyboardInputRaw( numberOfActions, actions ); +} + +/*! +Sends a media key Next Song button to Windows. +*/ +void KeyboardInputWindows::sendMediaNextSong() +{ + sendKeyPressAndRelease( VK_MEDIA_NEXT_TRACK ); +} + +/*! +Sends a media key Previous Song button to Windows. +*/ +void KeyboardInputWindows::sendMediaPreviousSong() +{ + sendKeyPressAndRelease( VK_MEDIA_PREV_TRACK ); +} + +/*! +Sends a media key Play/Pause button to Windows. +*/ +void KeyboardInputWindows::sendMediaPausePlay() +{ + sendKeyPressAndRelease( VK_MEDIA_PLAY_PAUSE ); +} + +/*! +Sends a media key Stop button to Windows. +*/ +void KeyboardInputWindows::sendMediaStopSong() +{ + sendKeyPressAndRelease( VK_MEDIA_STOP ); } } // namespace advsettings diff --git a/src/tabcontrollers/keyboardinput/KeyboardInputWindows.h b/src/tabcontrollers/keyboardinput/KeyboardInputWindows.h index e376703..12ceb44 100644 --- a/src/tabcontrollers/keyboardinput/KeyboardInputWindows.h +++ b/src/tabcontrollers/keyboardinput/KeyboardInputWindows.h @@ -11,8 +11,12 @@ class KeyboardInputWindows : public KeyboardInput public: virtual void sendKeyboardInput( QString input ) override; virtual void sendKeyboardEnter() override; - virtual void sendKeyboardBackspace( int count ) override; + virtual void sendKeyboardBackspace( const int count ) override; virtual void sendKeyboardAltTab() override; + virtual void sendMediaNextSong() override; + virtual void sendMediaPreviousSong() override; + virtual void sendMediaPausePlay() override; + virtual void sendMediaStopSong() override; }; } // namespace advsettings