diff --git a/app/backend/systemproperties.cpp b/app/backend/systemproperties.cpp index 4c1593ee..554f4334 100644 --- a/app/backend/systemproperties.cpp +++ b/app/backend/systemproperties.cpp @@ -74,6 +74,7 @@ SystemProperties::SystemProperties() Q_ASSERT(!monitorRefreshRates.isEmpty()); Q_ASSERT(!monitorNativeResolutions.isEmpty()); + Q_ASSERT(!monitorSafeAreaResolutions.isEmpty()); } QRect SystemProperties::getNativeResolution(int displayIndex) @@ -82,6 +83,12 @@ QRect SystemProperties::getNativeResolution(int displayIndex) return monitorNativeResolutions.value(displayIndex); } +QRect SystemProperties::getSafeAreaResolution(int displayIndex) +{ + // Returns default constructed QRect if out of bounds + return monitorSafeAreaResolutions.value(displayIndex); +} + int SystemProperties::getRefreshRate(int displayIndex) { // Returns 0 if out of bounds @@ -199,10 +206,12 @@ void SystemProperties::refreshDisplaysInternal() SDL_DisplayMode bestMode; for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) { SDL_DisplayMode desktopMode; + SDL_Rect safeArea; - if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) { + if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) { if (desktopMode.w <= 8192 && desktopMode.h <= 8192) { monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h)); + monitorSafeAreaResolutions.insert(displayIndex, QRect(0, 0, safeArea.w, safeArea.h)); } else { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, diff --git a/app/backend/systemproperties.h b/app/backend/systemproperties.h index 894f4c69..dd4ec6ce 100644 --- a/app/backend/systemproperties.h +++ b/app/backend/systemproperties.h @@ -30,6 +30,7 @@ public: Q_INVOKABLE void refreshDisplays(); Q_INVOKABLE QRect getNativeResolution(int displayIndex); + Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex); Q_INVOKABLE int getRefreshRate(int displayIndex); signals: @@ -52,6 +53,7 @@ private: QString unmappedGamepads; QSize maximumResolution; QList monitorNativeResolutions; + QList monitorSafeAreaResolutions; QList monitorRefreshRates; QString versionString; bool supportsHdr; diff --git a/app/gui/SettingsView.qml b/app/gui/SettingsView.qml index 60be0c34..b13ac31f 100644 --- a/app/gui/SettingsView.qml +++ b/app/gui/SettingsView.qml @@ -133,15 +133,45 @@ Flickable { AutoResizingComboBox { property int lastIndexValue + function addDetectedResolution(friendlyNamePrefix, rect) { + var indexToAdd = 0 + for (var j = 0; j < resolutionComboBox.count; j++) { + var existing_width = parseInt(resolutionListModel.get(j).video_width); + var existing_height = parseInt(resolutionListModel.get(j).video_height); + + if (rect.width === existing_width && rect.height === existing_height) { + // Duplicate entry, skip + indexToAdd = -1 + break + } + else if (rect.width * rect.height > existing_width * existing_height) { + // Candidate entrypoint after this entry + indexToAdd = j + 1 + } + } + + // Insert this display's resolution if it's not a duplicate + if (indexToAdd >= 0) { + resolutionListModel.insert(indexToAdd, + { + "text": friendlyNamePrefix+" ("+rect.width+"x"+rect.height+")", + "video_width": ""+rect.width, + "video_height": ""+rect.height, + "is_custom": false + }) + } + } + // ignore setting the index at first, and actually set it when the component is loaded Component.onCompleted: { // Refresh display data before using it to build the list SystemProperties.refreshDisplays() - // Add native resolutions for all attached displays + // Add native and safe area resolutions for all attached displays var done = false for (var displayIndex = 0; !done; displayIndex++) { var screenRect = SystemProperties.getNativeResolution(displayIndex); + var safeAreaRect = SystemProperties.getSafeAreaResolution(displayIndex); if (screenRect.width === 0) { // Exceeded max count of displays @@ -149,32 +179,8 @@ Flickable { break } - var indexToAdd = 0 - for (var j = 0; j < resolutionComboBox.count; j++) { - var existing_width = parseInt(resolutionListModel.get(j).video_width); - var existing_height = parseInt(resolutionListModel.get(j).video_height); - - if (screenRect.width === existing_width && screenRect.height === existing_height) { - // Duplicate entry, skip - indexToAdd = -1 - break - } - else if (screenRect.width * screenRect.height > existing_width * existing_height) { - // Candidate entrypoint after this entry - indexToAdd = j + 1 - } - } - - // Insert this display's resolution if it's not a duplicate - if (indexToAdd >= 0) { - resolutionListModel.insert(indexToAdd, - { - "text": "Native ("+screenRect.width+"x"+screenRect.height+")", - "video_width": ""+screenRect.width, - "video_height": ""+screenRect.height, - "is_custom": false - }) - } + addDetectedResolution(qsTr("Native"), screenRect) + addDetectedResolution(qsTr("Native (Excluding Notch)"), safeAreaRect) } // Prune resolutions that are over the decoder's maximum @@ -210,7 +216,7 @@ Flickable { if (!index_set) { // We did not find a match. This must be a custom resolution. resolutionListModel.append({ - "text": "Custom ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")", + "text": qsTr("Custom")+" ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")", "video_width": ""+StreamingPreferences.width, "video_height": ""+StreamingPreferences.height, "is_custom": true @@ -219,7 +225,7 @@ Flickable { } else { resolutionListModel.append({ - "text": "Custom", + "text": qsTr("Custom"), "video_width": "", "video_height": "", "is_custom": true diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 74ebba6a..8940e77e 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -1200,7 +1200,8 @@ void Session::updateOptimalWindowDisplayMode() // If this doesn't fit the selected resolution, use the native // resolution of the panel (unscaled). if (desktopMode.w < m_ActiveVideoWidth || desktopMode.h < m_ActiveVideoHeight) { - if (!StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) { + SDL_Rect safeArea; + if (!StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) { return; } } diff --git a/app/streaming/streamutils.cpp b/app/streaming/streamutils.cpp index 435ecc0e..4e5fdd65 100644 --- a/app/streaming/streamutils.cpp +++ b/app/streaming/streamutils.cpp @@ -193,7 +193,7 @@ bool StreamUtils::hasFastAes() #endif } -bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode) +bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode, SDL_Rect* safeArea) { #ifdef Q_OS_DARWIN #define MAX_DISPLAYS 16 @@ -223,6 +223,37 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode) break; } } + + safeArea->x = 0; + safeArea->y = 0; + safeArea->w = mode->w; + safeArea->h = mode->h; + +#if TARGET_CPU_ARM64 + // Now that we found the native full-screen mode, let's look for one that matches along + // the width but not the height and we'll assume that's the safe area full-screen mode. + // + // There doesn't appear to be a CG API or flag that will tell us that a given mode + // is a "safe area" mode, so we have to use our own (brittle) heuristics. :( + // + // To avoid potential false positives, let's avoid checking for external displays, since + // we might have scenarios like a 1920x1200 display with an alternate 1920x1080 mode + // which would falsely trigger our notch detection here. + if (CGDisplayIsBuiltin(displayIds[displayIndex])) { + for (CFIndex i = 0; i < count; i++) { + auto cgMode = (CGDisplayModeRef)(CFArrayGetValueAtIndex(modeList, i)); + auto cgModeWidth = static_cast(CGDisplayModeGetWidth(cgMode)); + auto cgModeHeight = static_cast(CGDisplayModeGetHeight(cgMode)); + + // If the modes differ by more than 100, we'll assume it's not a notch mode + if (mode->w == cgModeWidth && mode->h != cgModeHeight && mode->h <= cgModeHeight + 100) { + safeArea->w = cgModeWidth; + safeArea->h = cgModeHeight; + } + } + } +#endif + CFRelease(modeList); // Now find the SDL mode that matches the CG native mode @@ -258,7 +289,13 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode) return false; } } + + safeArea->x = 0; + safeArea->y = 0; + safeArea->w = mode->w; + safeArea->h = mode->h; #endif return true; } + diff --git a/app/streaming/streamutils.h b/app/streaming/streamutils.h index 4d61a865..282e0c36 100644 --- a/app/streaming/streamutils.h +++ b/app/streaming/streamutils.h @@ -29,7 +29,7 @@ public: void screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight); static - bool getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode); + bool getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode, SDL_Rect* safeArea); static int getDisplayRefreshRate(SDL_Window* window);