Skip to content

Commit 2a4eafc

Browse files
author
NightVsKnight
committed
Got Hotkeys working to swap between sessions
This first working version takes 1-3 seconds to destroy the current session and start a new session. I will work to improve this performance.
1 parent 1049555 commit 2a4eafc

File tree

17 files changed

+677
-218
lines changed

17 files changed

+677
-218
lines changed

app/backend/computermanager.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,45 @@ int ComputerManager::getComputerIndex(QString computerName)
493493
return -1;
494494
}
495495

496+
NvComputer* ComputerManager::getComputer(QString computerName)
497+
{
498+
QReadLocker lock(&m_Lock);
499+
// NOTE: Does **not** need to be sorted...
500+
auto hosts = m_KnownHosts.values();
501+
for (int i = 0; i < hosts.size(); i++) {
502+
auto host = hosts[i];
503+
if (host->name.toLower() == computerName.toLower()) {
504+
return host;
505+
}
506+
}
507+
return nullptr;
508+
}
509+
510+
bool ComputerManager::getApp(QString computerName, QString appName, NvApp& app)
511+
{
512+
QReadLocker lock(&m_Lock);
513+
auto computer = getComputer(computerName);
514+
if (!computer) {
515+
qDebug() << "getApp: Computer not found: computerName=" << computerName;
516+
return false;
517+
}
518+
return getApp(computer, appName, app);
519+
}
520+
521+
bool ComputerManager::getApp(NvComputer* computer, QString appName, NvApp& app)
522+
{
523+
QReadLocker lock(&m_Lock);
524+
auto appList = computer->appList;
525+
for (int i = 0; i < appList.size(); i++) {
526+
auto& it = appList[i];
527+
if (it.name.toLower() == appName.toLower()) {
528+
app = it;
529+
return true;
530+
}
531+
}
532+
return false;
533+
}
534+
496535
class DeferredHostDeletionTask : public QRunnable
497536
{
498537
public:

app/backend/computermanager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ class ComputerManager : public QObject
227227

228228
Q_INVOKABLE int getComputerIndex(QString computerName);
229229

230+
// Used by hotkeymodel to show the computer state
231+
NvComputer* getComputer(QString computerName);
232+
233+
// Used by hotkeymodel to show the app state
234+
bool getApp(QString computerName, QString appName, NvApp& app);
235+
bool getApp(NvComputer* computer, QString appName, NvApp& app);
236+
230237
// computer is deleted inside this call
231238
void deleteHost(NvComputer* computer);
232239

app/gui/CenteredGridView.qml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ GridView {
77
property bool hasBrokenMargins: this.synchronousDrag === undefined
88

99
property int minMargin: 10
10-
property real availableWidth: (parent.width - 2 * minMargin)
10+
property real availableWidth: parent ? (parent.width - 2 * minMargin) : minMargin
1111
property int itemsPerRow: availableWidth / cellWidth
1212
property real horizontalMargin: itemsPerRow < count && availableWidth >= cellWidth ?
1313
(availableWidth % cellWidth) / 2 : minMargin

app/gui/HotkeyView.qml

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
import QtQuick 2.9
2+
import QtQuick.Layouts 1.3
3+
import QtQuick.Controls 2.2
4+
5+
import StreamingPreferences 1.0
6+
7+
import SdlGamepadKeyNavigation 1.0
8+
9+
import HotkeyManager 1.0
10+
11+
CenteredGridView {
12+
property string tag: "HotkeyView"
13+
14+
id: hotkeyGrid
15+
focus: true
16+
activeFocusOnTab: true
17+
topMargin: 20
18+
bottomMargin: 5
19+
cellWidth: 310; cellHeight: 330;
20+
objectName: qsTr("Hotkeys")
21+
22+
Component.onCompleted: {
23+
// Don't show any highlighted item until interacting with them.
24+
// We do this here instead of onActivated to avoid losing the user's
25+
// selection when backing out of a different page of the app.
26+
// NOTE:(pv) Sometimes logs `Object or context destroyed during incubation`.
27+
currentIndex = -1
28+
}
29+
30+
// Note: Any initialization done here that is critical for streaming must
31+
// also be done in CliStartStreamSegue.qml, since this code does not run
32+
// for command-line initiated streams.
33+
StackView.onActivated: {
34+
// This is a bit of a hack to do this here as opposed to main.qml, but
35+
// we need it enabled before calling getConnectedGamepads() and PcView
36+
// is never destroyed, so it should be okay.
37+
SdlGamepadKeyNavigation.enable()
38+
39+
// Highlight the first item if a gamepad is connected
40+
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
41+
currentIndex = 0
42+
}
43+
}
44+
45+
model: hotkeyModel
46+
47+
delegate: NavigableItemDelegate {
48+
width: 220; height: 287;
49+
grid: hotkeyGrid
50+
51+
property alias hotkeyContextMenu: hotkeyContextMenuLoader.item
52+
53+
Image {
54+
//
55+
// Same/similar as AppView.appIcon...
56+
//
57+
property bool isPlaceholder: false
58+
59+
id: appIcon
60+
anchors.horizontalCenter: parent.horizontalCenter
61+
y: 10
62+
source: model.appBoxArt
63+
64+
onSourceSizeChanged: {
65+
// Nearly all of Nvidia's official box art does not match the dimensions of placeholder
66+
// images, however the one known exception is Overcooked. Therefore, we only execute
67+
// the image size checks if this is not an app collector game. We know the officially
68+
// supported games all have box art, so this check is not required.
69+
if (!model.isAppCollectorGame &&
70+
((sourceSize.width == 130 && sourceSize.height == 180) || // GFE 2.0 placeholder image
71+
(sourceSize.width == 628 && sourceSize.height == 888) || // GFE 3.0 placeholder image
72+
(sourceSize.width == 200 && sourceSize.height == 266))) // Our no_app_image.png
73+
{
74+
isPlaceholder = true
75+
}
76+
else
77+
{
78+
isPlaceholder = false
79+
}
80+
81+
width = 200
82+
height = 267
83+
}
84+
85+
// Display a tooltip with the full name if it's truncated
86+
ToolTip.text: model.appName
87+
ToolTip.delay: 1000
88+
ToolTip.timeout: 5000
89+
ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText || appNameText.truncated)
90+
91+
//
92+
// Different than AppView.appIcon...
93+
//
94+
95+
visible: model.isComputerOnline && model.isComputerPaired
96+
}
97+
98+
Image {
99+
id: computerIcon
100+
visible: !(model.isComputerOnline && model.isComputerPaired)
101+
anchors.horizontalCenter: parent.horizontalCenter
102+
anchors.verticalCenter: parent.verticalCenter
103+
source: "qrc:/res/desktop_windows-48px.svg"
104+
sourceSize {
105+
width: 200
106+
height: 200
107+
}
108+
}
109+
110+
Image {
111+
// TODO: Tooltip
112+
id: stateIcon
113+
visible: !model.isComputerStatusUnknown && !(model.isComputerOnline && model.isComputerPaired)
114+
anchors.horizontalCenter: parent.horizontalCenter
115+
anchors.verticalCenter: parent.verticalCenter
116+
anchors.verticalCenterOffset: -40
117+
source: !model.isComputerOnline ? "qrc:/res/warning_FILL1_wght300_GRAD200_opsz24.svg" : "qrc:/res/baseline-lock-24px.svg"
118+
sourceSize {
119+
width: 40
120+
height: 40
121+
}
122+
}
123+
124+
BusyIndicator {
125+
id: statusUnknownSpinner
126+
visible: model.isComputerStatusUnknown
127+
anchors.horizontalCenter: parent.horizontalCenter
128+
anchors.verticalCenter: parent.verticalCenter
129+
anchors.verticalCenterOffset: -40
130+
width: 40
131+
height: 40
132+
}
133+
134+
Label {
135+
id: computerNameText
136+
visible: true // always
137+
text: model.computerName
138+
139+
width: parent.width
140+
anchors.top: parent.top
141+
anchors.topMargin: 10
142+
font.pointSize: 22
143+
horizontalAlignment: Text.AlignHCenter
144+
wrapMode: Text.Wrap
145+
elide: Text.ElideRight
146+
}
147+
148+
Label {
149+
id: appNameText
150+
// The boxart has the app name in it, so this is redundant when the computer is online and paired.
151+
visible: !(model.isComputerOnline && model.isComputerPaired)
152+
text: model.appName
153+
154+
anchors.centerIn: parent
155+
156+
font.pointSize: 22
157+
horizontalAlignment: Text.AlignHCenter
158+
wrapMode: Text.Wrap
159+
elide: Text.ElideRight
160+
}
161+
162+
Label {
163+
text: model.hotkeyNumber
164+
visible: true // always
165+
style: Text.Outline
166+
styleColor: "black"
167+
168+
bottomPadding: 4
169+
font.pointSize: 36
170+
anchors.horizontalCenter: parent.horizontalCenter
171+
anchors.bottom: parent.bottom
172+
173+
horizontalAlignment: Text.AlignHCenter
174+
verticalAlignment: Text.AlignVCenter
175+
wrapMode: Text.Wrap
176+
elide: Text.ElideRight
177+
178+
ToolTip.text: qsTr("Ctrl+Alt+Shift+%1").arg(model.hotkeyNumber)
179+
ToolTip.delay: 1000
180+
ToolTip.timeout: 3000
181+
ToolTip.visible: hovered
182+
}
183+
184+
onClicked: {
185+
// Only allow clicking on the box art for non-running games.
186+
// For running games, buttons will appear to resume or quit which
187+
// will handle starting the game and clicks on the box art will
188+
// be ignored.
189+
if (!model.isAppRunning) {
190+
launchApp(model.computerName, model.appName)
191+
} else {
192+
log(tag, "app is already running")
193+
}
194+
}
195+
196+
onPressAndHold: {
197+
// popup() ensures the menu appears under the mouse cursor
198+
if (hotkeyContextMenu.popup) {
199+
hotkeyContextMenu.popup()
200+
}
201+
else {
202+
// Qt 5.9 doesn't have popup()
203+
hotkeyContextMenu.open()
204+
}
205+
}
206+
207+
MouseArea {
208+
anchors.fill: parent
209+
acceptedButtons: Qt.RightButton;
210+
onClicked: {
211+
parent.onPressAndHold()
212+
}
213+
}
214+
215+
Keys.onMenuPressed: {
216+
// We must use open() here so the menu is positioned on
217+
// the ItemDelegate and not where the mouse cursor is
218+
hotkeyContextMenu.open()
219+
}
220+
221+
Keys.onReturnPressed: {
222+
// Open the app context menu if activated via the gamepad or keyboard
223+
// for running games. If the game isn't running, the above onClicked
224+
// method will handle the launch.
225+
if (model.isAppRunning) {
226+
// This will be keyboard/gamepad driven so use
227+
// open() instead of popup()
228+
hotkeyContextMenu.open()
229+
}
230+
}
231+
232+
Keys.onEnterPressed: {
233+
// Open the app context menu if activated via the gamepad or keyboard
234+
// for running games. If the game isn't running, the above onClicked
235+
// method will handle the launch.
236+
if (model.isAppRunning) {
237+
// This will be keyboard/gamepad driven so use
238+
// open() instead of popup()
239+
hotkeyContextMenu.open()
240+
}
241+
}
242+
243+
Keys.onDeletePressed: {
244+
hotkeyDelete(model)
245+
}
246+
247+
Loader {
248+
id: hotkeyContextMenuLoader
249+
asynchronous: true
250+
sourceComponent: NavigableMenu {
251+
id: hotkeyContextMenu
252+
MenuItem {
253+
text: qsTr("PC Status: %1").arg(model.isComputerOnline ? qsTr("Online") : qsTr("Offline"))
254+
font.bold: true
255+
enabled: false
256+
}
257+
NavigableMenuItem {
258+
parentMenu: hotkeyContextMenu
259+
text: model.isAppRunning ? qsTr("Resume Game") : qsTr("Launch Game")
260+
onTriggered: launchApp(model.computerName, model.appName)
261+
}
262+
// TODO:(pv) drag and drop (that qml is admittedly a bit advanced for me)
263+
}
264+
}
265+
}
266+
267+
function hotkeyDelete(model) {
268+
deleteHotkeyDialog.computerName = model.computerName
269+
deleteHotkeyDialog.appName = model.appName
270+
deleteHotkeyDialog.open()
271+
}
272+
273+
NavigableMessageDialog {
274+
id: deleteHotkeyDialog
275+
// don't allow edits to the rest of the window while open
276+
property string computerName : ""
277+
property string appName : ""
278+
text: qsTr("Are you sure you want to remove Hotkey \"%1\" \"%2\"?").arg(computerName).arg(appName)
279+
standardButtons: Dialog.Yes | Dialog.No
280+
onAccepted: {
281+
HotkeyManager.remove(computerName, appName)
282+
displayToast(qsTr("Hotkey \"%1\" \"%2\" removed").arg(computerName).arg(appName))
283+
}
284+
}
285+
}

0 commit comments

Comments
 (0)