#include "systemproperties.h" #include "utils.h" #include #include #include "streaming/session.h" #include "streaming/streamutils.h" #ifdef Q_OS_WIN32 #define WIN32_LEAN_AND_MEAN #include #endif class SystemPropertyQueryThread : public QThread { public: SystemPropertyQueryThread(SystemProperties* properties) : QThread(properties), m_Properties(properties) { setObjectName("System Properties Async Query Thread"); } private: void run() override { bool hasHardwareAcceleration; bool rendererAlwaysFullScreen; bool supportsHdr; QSize maximumResolution; Session::getDecoderInfo(m_Properties->testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, supportsHdr, maximumResolution); // Propagate the decoder properties to the SystemProperties singleton and emit any change signals on the main thread QMetaObject::invokeMethod(m_Properties, "updateDecoderProperties", Qt::QueuedConnection, Q_ARG(bool, hasHardwareAcceleration), Q_ARG(bool, rendererAlwaysFullScreen), Q_ARG(QSize, maximumResolution), Q_ARG(bool, supportsHdr)); } private: SystemProperties* m_Properties; }; SystemProperties::SystemProperties() { versionString = QString(VERSION_STR); hasDesktopEnvironment = WMUtils::isRunningDesktopEnvironment(); isRunningWayland = WMUtils::isRunningWayland(); isRunningXWayland = isRunningWayland && QGuiApplication::platformName() == "xcb"; usesMaterial3Theme = QLibraryInfo::version() >= QVersionNumber(6, 5, 0); QString nativeArch = QSysInfo::currentCpuArchitecture(); #ifdef Q_OS_WIN32 { USHORT processArch, machineArch; // Use IsWow64Process2 on TH2 and later, because it supports ARM64 auto fnIsWow64Process2 = (decltype(IsWow64Process2)*)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process2"); if (fnIsWow64Process2 != nullptr && fnIsWow64Process2(GetCurrentProcess(), &processArch, &machineArch)) { switch (machineArch) { case IMAGE_FILE_MACHINE_I386: nativeArch = "i386"; break; case IMAGE_FILE_MACHINE_AMD64: nativeArch = "x86_64"; break; case IMAGE_FILE_MACHINE_ARM64: nativeArch = "arm64"; break; } } isWow64 = nativeArch != QSysInfo::buildCpuArchitecture(); } #else isWow64 = false; #endif if (nativeArch == "i386") { friendlyNativeArchName = "x86"; } else if (nativeArch == "x86_64") { friendlyNativeArchName = "x64"; } else { friendlyNativeArchName = nativeArch.toUpper(); } // Assume we can probably launch a browser if we're in a GUI environment hasBrowser = hasDesktopEnvironment; #ifdef HAVE_DISCORD hasDiscordIntegration = true; #else hasDiscordIntegration = false; #endif // These will be queried asynchronously to avoid blocking the UI hasHardwareAcceleration = true; rendererAlwaysFullScreen = false; supportsHdr = true; maximumResolution = QSize(0, 0); } SystemProperties::~SystemProperties() { waitForAsyncLoad(); } void SystemProperties::updateDecoderProperties(bool hasHardwareAcceleration, bool rendererAlwaysFullScreen, QSize maximumResolution, bool supportsHdr) { SDL_assert(testWindow); if (hasHardwareAcceleration != this->hasHardwareAcceleration) { this->hasHardwareAcceleration = hasHardwareAcceleration; emit hasHardwareAccelerationChanged(); } if (rendererAlwaysFullScreen != this->rendererAlwaysFullScreen) { this->rendererAlwaysFullScreen = rendererAlwaysFullScreen; emit rendererAlwaysFullScreenChanged(); } if (maximumResolution != this->maximumResolution) { this->maximumResolution = maximumResolution; emit maximumResolutionChanged(); } if (supportsHdr != this->supportsHdr) { this->supportsHdr = supportsHdr; emit supportsHdrChanged(); } SDL_DestroyWindow(testWindow); testWindow = nullptr; SDL_QuitSubSystem(SDL_INIT_VIDEO); } QRect SystemProperties::getNativeResolution(int displayIndex) { // Returns default constructed QRect if out of bounds Q_ASSERT(!monitorNativeResolutions.isEmpty()); return monitorNativeResolutions.value(displayIndex); } QRect SystemProperties::getSafeAreaResolution(int displayIndex) { // Returns default constructed QRect if out of bounds Q_ASSERT(!monitorSafeAreaResolutions.isEmpty()); return monitorSafeAreaResolutions.value(displayIndex); } int SystemProperties::getRefreshRate(int displayIndex) { // Returns 0 if out of bounds Q_ASSERT(!monitorRefreshRates.isEmpty()); return monitorRefreshRates.value(displayIndex); } void SystemProperties::startAsyncLoad() { if (systemPropertyQueryThread) { // Already started/completed return; } // This isn't actually asynchronous (due to the need to synchronize with // SdlGamepadKeyNavigation), but we don't query it in the constructor // because it's expensive. unmappedGamepads = SdlInputHandler::getUnmappedGamepads(); if (!unmappedGamepads.isEmpty()) { emit unmappedGamepadsChanged(); } // We initialize the video subsystem and test window on the main thread // because some platforms (macOS) do not support window creation on // non-main threads. if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s", SDL_GetError()); return; } // Update display related attributes (max FPS, native resolution, etc). refreshDisplays(); testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags()); if (!testWindow) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to create test window with platform flags: %s", SDL_GetError()); testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN); if (!testWindow) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create window for hardware decode test: %s", SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_VIDEO); return; } } systemPropertyQueryThread = new SystemPropertyQueryThread(this); systemPropertyQueryThread->start(); } void SystemProperties::waitForAsyncLoad() { if (systemPropertyQueryThread) { systemPropertyQueryThread->wait(); } } void SystemProperties::refreshDisplays() { if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s", SDL_GetError()); return; } monitorNativeResolutions.clear(); SDL_DisplayMode bestMode; for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) { SDL_DisplayMode desktopMode; SDL_Rect safeArea; 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, "Skipping resolution over 8K: %dx%d", desktopMode.w, desktopMode.h); } // Start at desktop mode and work our way up bestMode = desktopMode; for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) { SDL_DisplayMode mode; if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) { if (mode.w == desktopMode.w && mode.h == desktopMode.h) { if (mode.refresh_rate > bestMode.refresh_rate) { bestMode = mode; } } } } // Try to normalize values around our our standard refresh rates. // Some displays/OSes report values that are slightly off. if (bestMode.refresh_rate >= 58 && bestMode.refresh_rate <= 62) { monitorRefreshRates.append(60); } else if (bestMode.refresh_rate >= 28 && bestMode.refresh_rate <= 32) { monitorRefreshRates.append(30); } else { monitorRefreshRates.append(bestMode.refresh_rate); } } } SDL_QuitSubSystem(SDL_INIT_VIDEO); }