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