Merge pull request #42 from OpenVR-Advanced-Settings/media-keys
Update keyboard functions, Add media control keys to Utilities page
@@ -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.
|
||||
<a name="accessibility_page"></a>
|
||||
## - Accessibility Page:
|
||||
|
||||
|
||||
@@ -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/*
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 45 KiB |
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path fill="white" d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
|
||||
|
After Width: | Height: | Size: 184 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path fill="white" d="M10 8.64L15.27 12 10 15.36V8.64M8 5v14l11-7L8 5z"/></svg>
|
||||
|
After Width: | Height: | Size: 201 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path fill="white" d="M6 18l8.5-6L6 6v12zm2-8.14L11.03 12 8 14.14V9.86zM16 6h2v12h-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 216 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path fill="white" d="M6 6h2v12H6zm3.5 6l8.5 6V6l-8.5 6zm6.5 2.14L12.97 12 16 9.86v4.28z"/></svg>
|
||||
|
After Width: | Height: | Size: 219 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path fill="white" d="M16 8v8H8V8h8m2-2H6v12h12V6z"/></svg>
|
||||
|
After Width: | Height: | Size: 181 B |
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<UINT>( inputCount ), input, sizeof( INPUT ) ) )
|
||||
const auto success
|
||||
= SendInput( static_cast<UINT>( 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 <windows.h>.
|
||||
|
||||
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<unsigned int>( 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
|
||||
|
||||
@@ -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
|
||||
|
||||