duckstation/src/util/window_info.cpp
2024-11-16 14:06:14 +10:00

210 lines
5.9 KiB
C++

// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "window_info.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/heap_array.h"
#include "common/log.h"
#include "common/scoped_guard.h"
LOG_CHANNEL(WindowInfo);
#if defined(_WIN32)
#include "common/windows_headers.h"
#include <dwmapi.h>
static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd, Error* error)
{
// Partially based on Chromium ui/display/win/display_config_helper.cc.
const HMONITOR monitor = MonitorFromWindow(hwnd, 0);
if (!monitor) [[unlikely]]
{
Error::SetWin32(error, "MonitorFromWindow() failed: ", GetLastError());
return std::nullopt;
}
MONITORINFOEXW mi = {};
mi.cbSize = sizeof(mi);
if (!GetMonitorInfoW(monitor, &mi))
{
Error::SetWin32(error, "GetMonitorInfoW() failed: ", GetLastError());
return std::nullopt;
}
DynamicHeapArray<DISPLAYCONFIG_PATH_INFO> path_info;
DynamicHeapArray<DISPLAYCONFIG_MODE_INFO> mode_info;
// I guess this could fail if it changes inbetween two calls... unlikely.
for (;;)
{
UINT32 path_size = 0, mode_size = 0;
LONG res = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_size, &mode_size);
if (res != ERROR_SUCCESS)
{
Error::SetWin32(error, "GetDisplayConfigBufferSizes() failed: ", res);
return std::nullopt;
}
path_info.resize(path_size);
mode_info.resize(mode_size);
res =
QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_size, path_info.data(), &mode_size, mode_info.data(), nullptr);
if (res == ERROR_SUCCESS)
break;
if (res != ERROR_INSUFFICIENT_BUFFER)
{
Error::SetWin32(error, "QueryDisplayConfig() failed: ", res);
return std::nullopt;
}
}
for (const DISPLAYCONFIG_PATH_INFO& pi : path_info)
{
DISPLAYCONFIG_SOURCE_DEVICE_NAME sdn = {.header = {.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME),
.adapterId = pi.sourceInfo.adapterId,
.id = pi.sourceInfo.id},
.viewGdiDeviceName = {}};
LONG res = DisplayConfigGetDeviceInfo(&sdn.header);
if (res != ERROR_SUCCESS)
{
Error::SetWin32(error, "DisplayConfigGetDeviceInfo() failed: ", res);
continue;
}
if (std::wcscmp(sdn.viewGdiDeviceName, mi.szDevice) == 0)
{
// Found the monitor!
return static_cast<float>(static_cast<double>(pi.targetInfo.refreshRate.Numerator) /
static_cast<double>(pi.targetInfo.refreshRate.Denominator));
}
}
return std::nullopt;
}
static std::optional<float> GetRefreshRateFromDWM(HWND hwnd, Error* error)
{
BOOL composition_enabled;
HRESULT hr = DwmIsCompositionEnabled(&composition_enabled);
if (FAILED(hr))
{
Error::SetHResult(error, "DwmIsCompositionEnabled() failed: ", hr);
return std::nullopt;
}
DWM_TIMING_INFO ti = {};
ti.cbSize = sizeof(ti);
hr = DwmGetCompositionTimingInfo(nullptr, &ti);
if (SUCCEEDED(hr))
{
if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0)
return std::nullopt;
return static_cast<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator);
}
else
{
Error::SetHResult(error, "DwmGetCompositionTimingInfo() failed: ", hr);
return std::nullopt;
}
}
static std::optional<float> GetRefreshRateFromMonitor(HWND hwnd, Error* error)
{
HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (!mon)
{
Error::SetWin32(error, "MonitorFromWindow() failed: ", GetLastError());
return std::nullopt;
}
MONITORINFOEXW mi = {};
mi.cbSize = sizeof(mi);
if (GetMonitorInfoW(mon, &mi))
{
DEVMODEW dm = {};
dm.dmSize = sizeof(dm);
// 0/1 are reserved for "defaults".
if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1)
{
return static_cast<float>(dm.dmDisplayFrequency);
}
else
{
Error::SetWin32(error, "EnumDisplaySettingsW() failed: ", GetLastError());
return std::nullopt;
}
}
else
{
Error::SetWin32(error, "GetMonitorInfoW() failed: ", GetLastError());
return std::nullopt;
}
}
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error)
{
std::optional<float> ret;
if (wi.type != Type::Win32 || !wi.window_handle)
{
Error::SetStringView(error, "Invalid window type.");
return ret;
}
// Try DWM first, then fall back to integer values.
const HWND hwnd = static_cast<HWND>(wi.window_handle);
Error local_error;
ret = GetRefreshRateFromDisplayConfig(hwnd, &local_error);
if (!ret.has_value())
{
WARNING_LOG("GetRefreshRateFromDisplayConfig() failed: {}", local_error.GetDescription());
ret = GetRefreshRateFromDWM(hwnd, &local_error);
if (!ret.has_value())
{
WARNING_LOG("GetRefreshRateFromDWM() failed: {}", local_error.GetDescription());
ret = GetRefreshRateFromMonitor(hwnd, error);
}
}
return ret;
}
#elif defined(__APPLE__)
#include "util/platform_misc.h"
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error)
{
if (wi.type == WindowInfo::Type::MacOS)
return CocoaTools::GetViewRefreshRate(wi, error);
Error::SetStringView(error, "Invalid window type.");
return std::nullopt;
}
#else
#ifdef ENABLE_X11
#include "x11_tools.h"
#endif
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error)
{
#if defined(ENABLE_X11)
if (wi.type == WindowInfo::Type::Xlib || wi.type == WindowInfo::Type::XCB)
return GetRefreshRateFromXRandR(wi, error);
#endif
Error::SetStringView(error, "Invalid window type.");
return std::nullopt;
}
#endif