From 9166e705246789ae1cd3177c6315550009004eb4 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 2 Sep 2023 15:53:30 -0500 Subject: [PATCH] Replace fixed "unsupported FPS" options with fully custom FPS option Fixes #1003 --- app/gui/SettingsView.qml | 209 +++++++++++++++++++------- app/settings/streamingpreferences.cpp | 3 - app/settings/streamingpreferences.h | 2 - app/streaming/session.cpp | 8 - 4 files changed, 152 insertions(+), 70 deletions(-) diff --git a/app/gui/SettingsView.qml b/app/gui/SettingsView.qml index 3f421ef3..9d36e357 100644 --- a/app/gui/SettingsView.qml +++ b/app/gui/SettingsView.qml @@ -388,12 +388,124 @@ Flickable { } AutoResizingComboBox { - function addRefreshRateOrdered(fpsListModel, refreshRate, description) { + property int lastIndexValue + + function updateBitrateForSelection() { + // Only modify the bitrate if the values actually changed + var selectedFps = parseInt(model.get(fpsComboBox.currentIndex).video_fps) + if (StreamingPreferences.fps !== selectedFps) { + StreamingPreferences.fps = selectedFps + + StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, + StreamingPreferences.height, + StreamingPreferences.fps); + slider.value = StreamingPreferences.bitrateKbps + } + + lastIndexValue = currentIndex + } + + NavigableDialog { + function isInputValid() { + // If we have text that isn't valid, reject the input. + if (!fpsField.acceptableInput && fpsField.text) { + return false + } + + // The textbox needs to have text or placeholder text + if (!fpsField.text && !fpsField.placeholderText) { + return false + } + + return true + } + + id: customFpsDialog + standardButtons: Dialog.Ok | Dialog.Cancel + onOpened: { + // Force keyboard focus on the textbox so keyboard navigation works + fpsField.forceActiveFocus() + + // standardButton() was added in Qt 5.10, so we must check for it first + if (customFpsDialog.standardButton) { + customFpsDialog.standardButton(Dialog.Ok).enabled = customFpsDialog.isInputValid() + } + } + + onClosed: { + fpsField.clear() + } + + onRejected: { + fpsComboBox.currentIndex = fpsComboBox.lastIndexValue + } + + onAccepted: { + // Reject if there's invalid input + if (!isInputValid()) { + reject() + return + } + + var fps = fpsField.text ? fpsField.text : fpsField.placeholderText + + // Find and update the custom entry + for (var i = 0; i < fpsListModel.count; i++) { + if (fpsListModel.get(i).is_custom) { + fpsListModel.setProperty(i, "video_fps", fps) + fpsListModel.setProperty(i, "text", qsTr("Custom (%1 FPS)").arg(fps)) + + // Now update the bitrate using the custom resolution + fpsComboBox.currentIndex = i + fpsComboBox.updateBitrateForSelection() + + // Update the combobox width too + fpsComboBox.recalculateWidth() + break + } + } + } + + ColumnLayout { + Label { + text: qsTr("Enter a custom frame rate:") + font.bold: true + } + + RowLayout { + TextField { + id: fpsField + maximumLength: 4 + inputMethodHints: Qt.ImhDigitsOnly + placeholderText: fpsListModel.get(fpsComboBox.currentIndex).video_fps + validator: IntValidator{bottom:1; top:9999} + focus: true + + onTextChanged: { + // standardButton() was added in Qt 5.10, so we must check for it first + if (customFpsDialog.standardButton) { + customFpsDialog.standardButton(Dialog.Ok).enabled = customFpsDialog.isInputValid() + } + } + + Keys.onReturnPressed: { + customFpsDialog.accept() + } + + Keys.onEnterPressed: { + customFpsDialog.accept() + } + } + } + } + } + + function addRefreshRateOrdered(fpsListModel, refreshRate, description, custom) { var indexToAdd = 0 for (var j = 0; j < fpsListModel.count; j++) { var existing_fps = parseInt(fpsListModel.get(j).video_fps); - if (refreshRate === existing_fps) { + if (refreshRate === existing_fps || (custom && fpsListModel.get(j).is_custom)) { // Duplicate entry, skip indexToAdd = -1 break @@ -404,25 +516,25 @@ Flickable { } } - // Insert this display's resolution if it's not a duplicate + // Insert this frame rate if it's not a duplicate if (indexToAdd >= 0) { + // Custom values always go at the end of the list + if (custom) { + indexToAdd = fpsListModel.count + } + fpsListModel.insert(indexToAdd, { - "text": description, - "video_fps": ""+refreshRate + "text": description, + "video_fps": ""+refreshRate, + "is_custom": custom }) } return indexToAdd } - function createModel() { - var fpsListModel = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', parent, '') - - // Default entries - fpsListModel.append({"text": qsTr("%1 FPS").arg("30"), "video_fps": "30"}) - fpsListModel.append({"text": qsTr("%1 FPS").arg("60"), "video_fps": "60"}) - + function reinitialize() { // Add native refresh rate for all attached displays var done = false for (var displayIndex = 0; !done; displayIndex++) { @@ -433,21 +545,9 @@ Flickable { break } - addRefreshRateOrdered(fpsListModel, refreshRate, qsTr("%1 FPS").arg(refreshRate)) + addRefreshRateOrdered(fpsListModel, refreshRate, qsTr("%1 FPS").arg(refreshRate), false) } - // Add unsupported FPS values - if (StreamingPreferences.unsupportedFps) { - addRefreshRateOrdered(fpsListModel, 90, qsTr("%1 FPS (Unsupported)").arg(90)) - addRefreshRateOrdered(fpsListModel, 120, qsTr("%1 FPS (Unsupported)").arg(120)) - } - - return fpsListModel - } - - function reinitialize() { - model = createModel() - var saved_fps = StreamingPreferences.fps currentIndex = -1 for (var i = 0; i < model.count; i++) { @@ -462,11 +562,15 @@ Flickable { // If we didn't find one, add a custom frame rate for the current value if (currentIndex === -1) { - currentIndex = addRefreshRateOrdered(model, saved_fps, qsTr("%1 FPS (Custom)").arg(saved_fps)) + currentIndex = addRefreshRateOrdered(model, saved_fps, qsTr("Custom (%1 FPS)").arg(saved_fps), true) + } + else { + addRefreshRateOrdered(model, "", qsTr("Custom"), true) } - // Persist the selected value - activated(currentIndex) + recalculateWidth() + + lastIndexValue = currentIndex } // ignore setting the index at first, and actually set it when the component is loaded @@ -475,21 +579,31 @@ Flickable { languageChanged.connect(reinitialize) } + model: ListModel { + id: fpsListModel + // Other elements may be added at runtime + ListElement { + text: qsTr("30 FPS") + video_fps: "30" + is_custom: false + } + ListElement { + text: qsTr("60 FPS") + video_fps: "60" + is_custom: false + } + } + id: fpsComboBox maximumWidth: parent.width / 2 textRole: "text" // ::onActivated must be used, as it only listens for when the index is changed by a human onActivated : { - var selectedFps = parseInt(model.get(currentIndex).video_fps) - - // Only modify the bitrate if the values actually changed - if (StreamingPreferences.fps !== selectedFps) { - StreamingPreferences.fps = selectedFps - - StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, - StreamingPreferences.height, - StreamingPreferences.fps); - slider.value = StreamingPreferences.bitrateKbps + if (model.get(currentIndex).is_custom) { + customFpsDialog.open() + } + else { + updateBitrateForSelection() } } } @@ -1445,25 +1559,6 @@ Flickable { qsTr("HDR streaming is not supported on this PC.") } - CheckBox { - id: unlockUnsupportedFps - width: parent.width - text: qsTr("Unlock unsupported FPS options") - font.pointSize: 12 - checked: StreamingPreferences.unsupportedFps - onCheckedChanged: { - // This is called on init, so only do the work if we've - // actually changed the value. - if (StreamingPreferences.unsupportedFps != checked) { - StreamingPreferences.unsupportedFps = checked - - // The selectable FPS values depend on whether - // this option is enabled or not - fpsComboBox.reinitialize() - } - } - } - CheckBox { id: enableMdns width: parent.width diff --git a/app/settings/streamingpreferences.cpp b/app/settings/streamingpreferences.cpp index 2553d15e..c9ba8d4b 100644 --- a/app/settings/streamingpreferences.cpp +++ b/app/settings/streamingpreferences.cpp @@ -23,7 +23,6 @@ #define SER_HDR "hdr" #define SER_VIDEODEC "videodec" #define SER_WINDOWMODE "windowmode" -#define SER_UNSUPPORTEDFPS "unsupportedfps" #define SER_MDNS "mdns" #define SER_QUITAPPAFTER "quitAppAfter" #define SER_ABSMOUSEMODE "mouseacceleration" @@ -88,7 +87,6 @@ void StreamingPreferences::reload() gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool(); playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool(); multiController = settings.value(SER_MULTICONT, true).toBool(); - unsupportedFps = settings.value(SER_UNSUPPORTEDFPS, false).toBool(); enableMdns = settings.value(SER_MDNS, true).toBool(); quitAppAfter = settings.value(SER_QUITAPPAFTER, false).toBool(); absoluteMouseMode = settings.value(SER_ABSMOUSEMODE, false).toBool(); @@ -271,7 +269,6 @@ void StreamingPreferences::save() settings.setValue(SER_GAMEOPTS, gameOptimizations); settings.setValue(SER_HOSTAUDIO, playAudioOnHost); settings.setValue(SER_MULTICONT, multiController); - settings.setValue(SER_UNSUPPORTEDFPS, unsupportedFps); settings.setValue(SER_MDNS, enableMdns); settings.setValue(SER_QUITAPPAFTER, quitAppAfter); settings.setValue(SER_ABSMOUSEMODE, absoluteMouseMode); diff --git a/app/settings/streamingpreferences.h b/app/settings/streamingpreferences.h index e5f4874c..0e2cf365 100644 --- a/app/settings/streamingpreferences.h +++ b/app/settings/streamingpreferences.h @@ -111,7 +111,6 @@ public: Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged) Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged) Q_PROPERTY(bool multiController MEMBER multiController NOTIFY multiControllerChanged) - Q_PROPERTY(bool unsupportedFps MEMBER unsupportedFps NOTIFY unsupportedFpsChanged) Q_PROPERTY(bool enableMdns MEMBER enableMdns NOTIFY enableMdnsChanged) Q_PROPERTY(bool quitAppAfter MEMBER quitAppAfter NOTIFY quitAppAfterChanged) Q_PROPERTY(bool absoluteMouseMode MEMBER absoluteMouseMode NOTIFY absoluteMouseModeChanged) @@ -148,7 +147,6 @@ public: bool gameOptimizations; bool playAudioOnHost; bool multiController; - bool unsupportedFps; bool enableMdns; bool quitAppAfter; bool absoluteMouseMode; diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 2a9b66c6..55ecb497 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -812,14 +812,6 @@ bool Session::validateLaunch(SDL_Window* testWindow) } } - if (m_Preferences->unsupportedFps && m_StreamConfig.fps > 60) { - emitLaunchWarning(tr("Using unsupported FPS options may cause stuttering or lag.")); - - if (m_Preferences->enableVsync) { - emitLaunchWarning(tr("V-sync will be disabled when streaming at a higher frame rate than the display.")); - } - } - if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_MASK_AV1) { if (!(m_Computer->serverCodecModeSupport & SCM_MASK_AV1)) { if (m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_AV1) {