Replace fixed "unsupported FPS" options with fully custom FPS option

Fixes #1003
This commit is contained in:
Cameron Gutman 2023-09-02 15:53:30 -05:00
parent 41f8fa95fe
commit 9166e70524
4 changed files with 152 additions and 70 deletions

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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) {