/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2021 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "PrecompiledHeader.h" #include "WindowInfo.h" #include "Console.h" #if defined(_WIN32) && !defined(_UWP) #include "RedtapeWindows.h" #include static bool GetRefreshRateFromDWM(HWND hwnd, float* refresh_rate) { static HMODULE dwm_module = nullptr; static HRESULT(STDAPICALLTYPE * is_composition_enabled)(BOOL * pfEnabled) = nullptr; static HRESULT(STDAPICALLTYPE * get_timing_info)(HWND hwnd, DWM_TIMING_INFO * pTimingInfo) = nullptr; static bool load_tried = false; if (!load_tried) { load_tried = true; dwm_module = LoadLibraryW(L"dwmapi.dll"); if (dwm_module) { std::atexit([]() { FreeLibrary(dwm_module); dwm_module = nullptr; }); is_composition_enabled = reinterpret_cast(GetProcAddress(dwm_module, "DwmIsCompositionEnabled")); get_timing_info = reinterpret_cast(GetProcAddress(dwm_module, "DwmGetCompositionTimingInfo")); } } BOOL composition_enabled; if (!is_composition_enabled || FAILED(is_composition_enabled(&composition_enabled) || !get_timing_info)) return false; DWM_TIMING_INFO ti = {}; ti.cbSize = sizeof(ti); HRESULT hr = get_timing_info(nullptr, &ti); if (SUCCEEDED(hr)) { if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0) return false; *refresh_rate = static_cast(ti.rateRefresh.uiNumerator) / static_cast(ti.rateRefresh.uiDenominator); return true; } return false; } static bool GetRefreshRateFromMonitor(HWND hwnd, float* refresh_rate) { HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (!mon) return false; 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) { *refresh_rate = static_cast(dm.dmDisplayFrequency); return true; } } return false; } bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate) { if (wi.type != Type::Win32 || !wi.window_handle) return false; // Try DWM first, then fall back to integer values. const HWND hwnd = static_cast(wi.window_handle); return GetRefreshRateFromDWM(hwnd, refresh_rate) || GetRefreshRateFromMonitor(hwnd, refresh_rate); } #else #if defined(X11_API) && defined(HAS_XRANDR) #include "common/ScopedGuard.h" #include #include static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) { Display* display = static_cast(wi.display_connection); Window window = static_cast(reinterpret_cast(wi.window_handle)); if (!display || !window) return false; XRRScreenResources* res = XRRGetScreenResources(display, window); if (!res) { Console.Error("(GetRefreshRateFromXRandR) XRRGetScreenResources() failed"); return false; } ScopedGuard res_guard([res]() { XRRFreeScreenResources(res); }); int num_monitors; XRRMonitorInfo* mi = XRRGetMonitors(display, window, True, &num_monitors); if (num_monitors < 0) { Console.Error("(GetRefreshRateFromXRandR) XRRGetMonitors() failed"); return false; } else if (num_monitors > 1) { Console.Warning("(GetRefreshRateFromXRandR) XRRGetMonitors() returned %d monitors, using first", num_monitors); } ScopedGuard mi_guard([mi]() { XRRFreeMonitors(mi); }); if (mi->noutput <= 0) { Console.Error("(GetRefreshRateFromXRandR) Monitor has no outputs"); return false; } else if (mi->noutput > 1) { Console.Warning("(GetRefreshRateFromXRandR) Monitor has %d outputs, using first", mi->noutput); } XRROutputInfo* oi = XRRGetOutputInfo(display, res, mi->outputs[0]); if (!oi) { Console.Error("(GetRefreshRateFromXRandR) XRRGetOutputInfo() failed"); return false; } ScopedGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); }); XRRCrtcInfo* ci = XRRGetCrtcInfo(display, res, oi->crtc); if (!ci) { Console.Error("(GetRefreshRateFromXRandR) XRRGetCrtcInfo() failed"); return false; } ScopedGuard ci_guard([ci]() { XRRFreeCrtcInfo(ci); }); XRRModeInfo* mode = nullptr; for (int i = 0; i < res->nmode; i++) { if (res->modes[i].id == ci->mode) { mode = &res->modes[i]; break; } } if (!mode) { Console.Error("(GetRefreshRateFromXRandR) Failed to look up mode %d (of %d)", static_cast(ci->mode), res->nmode); return false; } if (mode->dotClock == 0 || mode->hTotal == 0 || mode->vTotal == 0) { Console.Error("(GetRefreshRateFromXRandR) Modeline is invalid: %ld/%d/%d", mode->dotClock, mode->hTotal, mode->vTotal); return false; } *refresh_rate = static_cast(mode->dotClock) / (static_cast(mode->hTotal) * static_cast(mode->vTotal)); return true; } #endif // X11_API && defined(HAS_XRANDR) bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate) { #if defined(X11_API) && defined(HAS_XRANDR) if (wi.type == WindowInfo::Type::X11) return GetRefreshRateFromXRandR(wi, refresh_rate); #endif return false; } #endif