diff --git a/idd/LGCommon/PipeMsg.h b/idd/LGCommon/PipeMsg.h index c673e997..1e4d19ef 100644 --- a/idd/LGCommon/PipeMsg.h +++ b/idd/LGCommon/PipeMsg.h @@ -49,7 +49,7 @@ struct LGPipeMsg { uint32_t width; uint32_t height; - uint32_t refresh; + uint32_t refreshMilliHz; } displayMode; @@ -67,4 +67,4 @@ struct LGPipeMsg } resolutionRejected; }; -}; \ No newline at end of file +}; diff --git a/idd/LGCommon/RefreshRate.cpp b/idd/LGCommon/RefreshRate.cpp new file mode 100644 index 00000000..aa4a575e --- /dev/null +++ b/idd/LGCommon/RefreshRate.cpp @@ -0,0 +1,104 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "RefreshRate.h" + +#include +#include +#include + +static std::wstring Trim(const std::wstring& text) +{ + size_t begin = 0; + size_t end = text.size(); + while (begin < end && std::iswspace(text[begin])) + ++begin; + while (end > begin && std::iswspace(text[end - 1])) + --end; + return text.substr(begin, end - begin); +} + +static bool ToUnsigned(const std::wstring& text, unsigned& value) +{ + if (text.empty()) + return false; + + wchar_t * end = nullptr; + const unsigned long long parsed = std::wcstoull(text.c_str(), &end, 10); + if (!end || *end != L'\0' || + parsed > std::numeric_limits::max()) + return false; + + value = (unsigned)parsed; + return true; +} + +bool LGParseRefreshRate(const std::wstring& text, unsigned& refreshMilliHz) +{ + const std::wstring value = Trim(text); + const size_t decimal = value.find(L'.'); + if (value.empty() || + (decimal != std::wstring::npos && + (value.find(L'.', decimal + 1) != std::wstring::npos || + decimal == 0 || decimal + 4 < value.size()))) + return false; + + const std::wstring wholeText = value.substr(0, decimal); + const std::wstring fractionText = decimal == std::wstring::npos ? + std::wstring() : value.substr(decimal + 1); + unsigned whole; + unsigned fraction = 0; + if (!ToUnsigned(wholeText, whole) || + (decimal != std::wstring::npos && fractionText.empty()) || + (!fractionText.empty() && !ToUnsigned(fractionText, fraction))) + return false; + + if (fractionText.size() == 1) + fraction *= 100; + else if (fractionText.size() == 2) + fraction *= 10; + + const unsigned long long valueMilliHz = + (unsigned long long)whole * 1000 + fraction; + if (valueMilliHz < 23900 || valueMilliHz > 1000000) + return false; + + refreshMilliHz = (unsigned)valueMilliHz; + return true; +} + +std::wstring LGFormatRefreshRate(unsigned refreshMilliHz) +{ + std::wstring result = std::to_wstring(refreshMilliHz / 1000); + const unsigned fraction = refreshMilliHz % 1000; + if (!fraction) + return result; + + const wchar_t text[] = + { + L'.', + (wchar_t)(L'0' + fraction / 100), + (wchar_t)(L'0' + fraction / 10 % 10), + (wchar_t)(L'0' + fraction % 10), + L'\0' + }; + result.append(text); + return result; +} diff --git a/idd/LGCommon/RefreshRate.h b/idd/LGCommon/RefreshRate.h new file mode 100644 index 00000000..f1bac3bf --- /dev/null +++ b/idd/LGCommon/RefreshRate.h @@ -0,0 +1,26 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include + +bool LGParseRefreshRate(const std::wstring& text, unsigned& refreshMilliHz); +std::wstring LGFormatRefreshRate(unsigned refreshMilliHz); diff --git a/idd/LGIdd/CEdid.cpp b/idd/LGIdd/CEdid.cpp index 5d761000..7ff46827 100644 --- a/idd/LGIdd/CEdid.cpp +++ b/idd/LGIdd/CEdid.cpp @@ -86,8 +86,8 @@ static const BYTE CTA_COLORIMETRY_BT2020_RGB = (BYTE)(1 << 7); // changing these timings at runtime can make it treat the IDD as a new monitor. static const CSettings::DisplayMode EDID_DISPLAY_MODES[] = { - { 1024, 768, 60, true , false }, - { 800, 600, 60, false, false } + { 1024, 768, 60000, true , false }, + { 800, 600, 60000, false, false } }; #pragma pack(push, 1) @@ -366,7 +366,8 @@ bool CEdid::GetTiming(Timing& timing, const CSettings::DisplayMode& mode) timing.hActive = mode.width; timing.vActive = mode.height; - if (timing.hActive == 0 || timing.vActive == 0 || mode.refresh == 0) + if (timing.hActive == 0 || timing.vActive == 0 || + mode.refreshMilliHz == 0) return false; timing.hBlank = std::max(160, @@ -390,12 +391,11 @@ bool CEdid::GetTiming(Timing& timing, const CSettings::DisplayMode& mode) if (timing.vFront + timing.vSync >= timing.vBlank) return false; - const UINT64 pixelClock = + const UINT64 pixelClockMilliHz = (UINT64)(timing.hActive + timing.hBlank) * (UINT64)(timing.vActive + timing.vBlank) * - (UINT64)mode.refresh; - const UINT64 pixelClock10KHz = (pixelClock + 5000) / 10000; - timing.pixelClock = pixelClock10KHz * 10000; + (UINT64)mode.refreshMilliHz; + timing.pixelClock = (pixelClockMilliHz + 500) / 1000; return timing.pixelClock != 0; } @@ -411,7 +411,7 @@ static bool MakeDetailedTiming( timing.hBlank > 4095 || timing.vBlank > 4095) return false; - const UINT64 pixelClock10KHz = timing.pixelClock / 10000; + const UINT64 pixelClock10KHz = (timing.pixelClock + 5000) / 10000; if (pixelClock10KHz == 0 || pixelClock10KHz > 0xffff) return false; diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index de4b68c8..df4bd12b 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -185,20 +185,20 @@ bool CIndirectDeviceContext::PopulateDefaultModes() if (!GetResolutionMemoryRequirements(configuredMode.width, configuredMode.height, alignment, frameSize, requiredIVSHMEMSize)) { - DEBUG_WARN("Filtering invalid %s mode %ux%u@%u", + DEBUG_WARN("Filtering invalid %s mode %ux%u@%.3f", configuredMode.extraMode ? "extra" : "configured", configuredMode.width, configuredMode.height, - configuredMode.refresh); + configuredMode.refreshMilliHz / 1000.0); continue; } if (requiredIVSHMEMSize > m_ivshmem.GetSize()) { DEBUG_WARN( - "Filtering %s mode %ux%u@%u: requires %llu bytes of IVSHMEM, only %llu bytes are available", + "Filtering %s mode %ux%u@%.3f: requires %llu bytes of IVSHMEM, only %llu bytes are available", configuredMode.extraMode ? "extra" : "configured", configuredMode.width, configuredMode.height, - configuredMode.refresh, + configuredMode.refreshMilliHz / 1000.0, (unsigned long long)requiredIVSHMEMSize, (unsigned long long)m_ivshmem.GetSize()); continue; @@ -644,10 +644,11 @@ void CIndirectDeviceContext::ReloadSettings() CSettings::DisplayMode extraMode; if (g_settings.GetExtraMode(extraMode)) { - const unsigned refresh = g_settings.GetDefaultRefresh(); - if (extraMode.refresh != refresh) + const unsigned refreshMilliHz = + g_settings.GetDefaultRefreshMilliHz(); + if (extraMode.refreshMilliHz != refreshMilliHz) { - extraMode.refresh = refresh; + extraMode.refreshMilliHz = refreshMilliHz; if (!g_settings.SetExtraMode(extraMode)) { ReleaseSRWLockExclusive(&m_modeReloadLock); @@ -743,7 +744,38 @@ void CIndirectDeviceContext::OnSwapChainReady() if (replug) m_replugQueued.store(1); else if (doSetMode) - g_pipe.SetDisplayMode(mode.width, mode.height, mode.refresh); + g_pipe.SetDisplayMode( + mode.width, mode.height, mode.refreshMilliHz); +} + +static UINT64 GreatestCommonDivisor(UINT64 a, UINT64 b) +{ + while (b) + { + const UINT64 remainder = a % b; + a = b; + b = remainder; + } + return a; +} + +static void SetSignalRate(DISPLAYCONFIG_RATIONAL& rate, + UINT64 numerator, UINT32 denominator) +{ + const UINT64 divisor = GreatestCommonDivisor(numerator, denominator); + numerator /= divisor; + denominator /= (UINT32)divisor; + + if (numerator <= UINT32_MAX) + { + rate.Numerator = (UINT32)numerator; + rate.Denominator = denominator; + return; + } + + rate.Numerator = + (UINT32)((numerator + denominator / 2) / denominator); + rate.Denominator = 1; } static inline void FillSignalInfo(DISPLAYCONFIG_VIDEO_SIGNAL_INFO& signal, @@ -761,10 +793,9 @@ static inline void FillSignalInfo(DISPLAYCONFIG_VIDEO_SIGNAL_INFO& signal, signal.AdditionalSignalInfo.vSyncFreqDivider = monitorMode ? 0 : 1; signal.AdditionalSignalInfo.videoStandard = 255; - signal.vSyncFreq.Numerator = mode.refresh; - signal.vSyncFreq.Denominator = 1; - signal.hSyncFreq.Numerator = mode.refresh * signal.totalSize.cy; - signal.hSyncFreq.Denominator = 1; + SetSignalRate(signal.vSyncFreq, mode.refreshMilliHz, 1000); + SetSignalRate(signal.hSyncFreq, + (UINT64)mode.refreshMilliHz * signal.totalSize.cy, 1000); signal.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE; signal.pixelRate = timing.pixelClock; @@ -964,10 +995,10 @@ void CIndirectDeviceContext::SetResolution(uint32_t width, uint32_t height) } CSettings::DisplayMode mode = {}; - mode.width = width; - mode.height = height; - mode.refresh = g_settings.GetDefaultRefresh(); - mode.preferred = true; + mode.width = width; + mode.height = height; + mode.refreshMilliHz = g_settings.GetDefaultRefreshMilliHz(); + mode.preferred = true; bool modesLoaded = false; AcquireSRWLockExclusive(&m_modeReloadLock); diff --git a/idd/LGIdd/CPipeServer.cpp b/idd/LGIdd/CPipeServer.cpp index cee504aa..fcb6e03c 100644 --- a/idd/LGIdd/CPipeServer.cpp +++ b/idd/LGIdd/CPipeServer.cpp @@ -287,14 +287,15 @@ void CPipeServer::SetCursorPos(uint32_t x, uint32_t y) WriteMsg(msg); } -void CPipeServer::SetDisplayMode(uint32_t width, uint32_t height, uint32_t refresh) +void CPipeServer::SetDisplayMode( + uint32_t width, uint32_t height, uint32_t refreshMilliHz) { LGPipeMsg msg = {}; msg.size = sizeof(msg); msg.type = LGPipeMsg::SETDISPLAYMODE; - msg.displayMode.width = width; - msg.displayMode.height = height; - msg.displayMode.refresh = refresh; + msg.displayMode.width = width; + msg.displayMode.height = height; + msg.displayMode.refreshMilliHz = refreshMilliHz; WriteMsg(msg); } diff --git a/idd/LGIdd/CPipeServer.h b/idd/LGIdd/CPipeServer.h index 4f15370c..861371f9 100644 --- a/idd/LGIdd/CPipeServer.h +++ b/idd/LGIdd/CPipeServer.h @@ -66,7 +66,8 @@ class CPipeServer void SetDeviceContext(CIndirectDeviceContext* context); void SetCursorPos(uint32_t x, uint32_t y); - void SetDisplayMode(uint32_t width, uint32_t height, uint32_t refresh); + void SetDisplayMode( + uint32_t width, uint32_t height, uint32_t refreshMilliHz); void SetGPUStatus(bool software); void ResolutionRejected(uint32_t width, uint32_t height, uint32_t requiredSizeMiB); diff --git a/idd/LGIdd/CSettings.cpp b/idd/LGIdd/CSettings.cpp index 4bab5d8c..56176863 100644 --- a/idd/LGIdd/CSettings.cpp +++ b/idd/LGIdd/CSettings.cpp @@ -21,6 +21,7 @@ #include "CSettings.h" #include "CDebug.h" #include "DefaultDisplayModes.h" +#include "RefreshRate.h" #include @@ -34,14 +35,16 @@ CSettings::CSettings() CSettings::DisplayModes CSettings::LoadModes() { - const unsigned defaultRefresh = GetDefaultRefresh(); + const unsigned defaultRefreshMilliHz = GetDefaultRefreshMilliHz(); DisplayModes displayModes; bool hasPreferred = false; DisplayMode m; if (GetExtraMode(m)) { - DEBUG_INFO("ExtraMode: %ux%u@%u%s", m.width, m.height, m.refresh, m.preferred ? "*" : ""); + const std::wstring refresh = LGFormatRefreshRate(m.refreshMilliHz); + DEBUG_INFO("ExtraMode: %ux%u@%ls%s", m.width, m.height, + refresh.c_str(), m.preferred ? "*" : ""); displayModes.push_back(m); hasPreferred = m.preferred; } @@ -54,11 +57,12 @@ CSettings::DisplayModes CSettings::LoadModes() for (int i = 0; i < ARRAYSIZE(DefaultDisplayModes); ++i) { - m.width = DefaultDisplayModes[i][0]; - m.height = DefaultDisplayModes[i][1]; - m.refresh = defaultRefresh; - m.preferred = !hasPreferred && (i == DefaultPreferredDisplayMode); - m.extraMode = false; + m.width = DefaultDisplayModes[i][0]; + m.height = DefaultDisplayModes[i][1]; + m.refreshMilliHz = defaultRefreshMilliHz; + m.preferred = !hasPreferred && + (i == DefaultPreferredDisplayMode); + m.extraMode = false; displayModes.push_back(m); } return displayModes; @@ -79,8 +83,9 @@ CSettings::DisplayModes CSettings::LoadModes() bool CSettings::SetExtraMode(const DisplayMode& mode) { WCHAR buf[64]; - _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%ux%u@%u%s", - mode.width, mode.height, mode.refresh, + const std::wstring refresh = LGFormatRefreshRate(mode.refreshMilliHz); + _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%ux%u@%ls%s", + mode.width, mode.height, refresh.c_str(), mode.preferred ? L"*" : L""); HKEY hKey = NULL; @@ -226,24 +231,45 @@ bool CSettings::GetExtraMode(DisplayMode& mode) return true; } -unsigned CSettings::GetDefaultRefresh() const +unsigned CSettings::GetDefaultRefreshMilliHz() const { - DWORD refresh = 60; - DWORD cb = sizeof(refresh); - HKEY hKey = nullptr; + HKEY hKey = nullptr; + LONG status = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, LGIDD_REGKEY, 0, KEY_QUERY_VALUE, &hKey); + if (status != ERROR_SUCCESS) + return 60000; - LONG st = RegOpenKeyExW(HKEY_LOCAL_MACHINE, LGIDD_REGKEY, 0, KEY_QUERY_VALUE, &hKey); - if (st == ERROR_SUCCESS) + DWORD type = 0; + DWORD size = 0; + status = RegQueryValueExW( + hKey, L"DefaultRefresh", nullptr, &type, nullptr, &size); + if (status != ERROR_SUCCESS) { - DWORD type = 0; - st = RegGetValueW(hKey, nullptr, L"DefaultRefresh", RRF_RT_REG_DWORD, &type, &refresh, &cb); RegCloseKey(hKey); + return 60000; } - if (st != ERROR_SUCCESS || refresh < 30 || refresh > 1000) - return 60; + unsigned refreshMilliHz = 0; + if (type == REG_DWORD && size == sizeof(DWORD)) + { + DWORD refresh = 0; + status = RegQueryValueExW(hKey, L"DefaultRefresh", nullptr, &type, + (LPBYTE)&refresh, &size); + if (status == ERROR_SUCCESS && refresh >= 24 && refresh <= 1000) + refreshMilliHz = refresh * 1000; + } + else if ((type == REG_SZ || type == REG_EXPAND_SZ) && size && + size % sizeof(wchar_t) == 0) + { + std::vector value(size / sizeof(wchar_t) + 1, L'\0'); + status = RegQueryValueExW(hKey, L"DefaultRefresh", nullptr, &type, + (LPBYTE)value.data(), &size); + if (status == ERROR_SUCCESS) + LGParseRefreshRate(value.data(), refreshMilliHz); + } - return refresh; + RegCloseKey(hKey); + return refreshMilliHz ? refreshMilliHz : 60000; } bool CSettings::ReadModesValue(std::vector &out) const @@ -320,16 +346,14 @@ bool CSettings::ParseModeString(const std::wstring& in, DisplayMode& out) if (!toUnsigned(s.substr(0, xPos), out.width) || !toUnsigned(s.substr(xPos + 1, atPos - (xPos + 1)), out.height) || - !toUnsigned(s.substr(atPos + 1), out.refresh)) + !LGParseRefreshRate(s.substr(atPos + 1), out.refreshMilliHz)) return false; // sanity check if (out.width < 640 || out.height < 480 || out.width > 16384 || - out.height > 16384 || - out.refresh < 30 || - out.refresh > 1000) + out.height > 16384) return false; out.extraMode = false; diff --git a/idd/LGIdd/CSettings.h b/idd/LGIdd/CSettings.h index 7b932557..7076b7c9 100644 --- a/idd/LGIdd/CSettings.h +++ b/idd/LGIdd/CSettings.h @@ -30,7 +30,7 @@ class CSettings { unsigned width; unsigned height; - unsigned refresh; + unsigned refreshMilliHz; bool preferred; bool extraMode; }; @@ -41,7 +41,7 @@ class CSettings DisplayModes LoadModes(); bool SetExtraMode(const DisplayMode & mode); bool GetExtraMode(DisplayMode & mode); - unsigned GetDefaultRefresh() const; + unsigned GetDefaultRefreshMilliHz() const; std::wstring ReadStringValue(const wchar_t* name, const wchar_t* defaultValue = nullptr); bool ReadBoolValue(const wchar_t* name, bool defaultValue = false); diff --git a/idd/LGIddHelper/CConfigWindow.cpp b/idd/LGIddHelper/CConfigWindow.cpp index 1db2a7ec..63d45bba 100644 --- a/idd/LGIddHelper/CConfigWindow.cpp +++ b/idd/LGIddHelper/CConfigWindow.cpp @@ -19,6 +19,7 @@ */ #include "CConfigWindow.h" +#include "RefreshRate.h" #include "CListBox.h" #include "CGroupBox.h" #include "CEditWidget.h" @@ -160,7 +161,7 @@ LRESULT CConfigWindow::onCreate() m_modeWidth.reset(new CEditWidget(WS_TABSTOP | ES_LEFT | ES_NUMBER, m_hwnd)); m_modeHeight.reset(new CEditWidget(WS_TABSTOP | ES_LEFT | ES_NUMBER, m_hwnd)); - m_modeRefresh.reset(new CEditWidget(WS_TABSTOP | ES_LEFT | ES_NUMBER, m_hwnd)); + m_modeRefresh.reset(new CEditWidget(WS_TABSTOP | ES_LEFT, m_hwnd)); m_modePreferred.reset(new CCheckbox(L"prefer", 0, m_hwnd)); m_modeUpdate.reset(new CButton(L"Update", WS_TABSTOP, m_hwnd)); @@ -173,11 +174,11 @@ LRESULT CConfigWindow::onCreate() m_modeRevert.reset(new CButton(L"Revert", WS_TABSTOP, m_hwnd)); m_defRefreshLabel.reset(new CStaticWidget(L"Default refresh:", SS_CENTERIMAGE, m_hwnd)); - m_defRefresh.reset(new CEditWidget(ES_LEFT | ES_NUMBER | WS_TABSTOP, m_hwnd)); + m_defRefresh.reset(new CEditWidget(ES_LEFT | WS_TABSTOP, m_hwnd)); m_defRefreshHz.reset(new CStaticWidget(L"Hz", SS_CENTERIMAGE, m_hwnd)); if (m_defaultRefresh) - m_defRefresh->setNumericValue(*m_defaultRefresh); + m_defRefresh->setValue(LGFormatRefreshRate(*m_defaultRefresh)); else m_defRefresh->disable(); @@ -253,7 +254,7 @@ void CConfigWindow::onModeListSelectChange() auto &mode = (*m_modes)[index]; m_modeWidth->setNumericValue(mode.width); m_modeHeight->setNumericValue(mode.height); - m_modeRefresh->setNumericValue(mode.refresh); + m_modeRefresh->setValue(LGFormatRefreshRate(mode.refreshMilliHz)); m_modePreferred->setChecked(mode.preferred); } EnableWindow(*m_modeUpdate, TRUE); @@ -286,7 +287,6 @@ LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd) { mode.width = m_modeWidth->getNumericValue(); mode.height = m_modeHeight->getNumericValue(); - mode.refresh = m_modeRefresh->getNumericValue(); mode.preferred = m_modePreferred->isChecked(); } catch (std::logic_error&) @@ -294,6 +294,12 @@ LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd) return 0; } + unsigned refreshMilliHz; + if (!LGParseRefreshRate( + m_modeRefresh->getValue(), refreshMilliHz)) + return 0; + mode.refreshMilliHz = refreshMilliHz; + m_modeBox->clear(); m_modeBox->setSel(updateModeList(index)); } @@ -319,14 +325,10 @@ LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd) } else if (m_defRefresh && hwnd == *m_defRefresh && code == EN_CHANGE && m_defaultRefresh) { - try - { - m_defaultRefresh = m_defRefresh->getNumericValue(); - } - catch (std::logic_error &) - { + unsigned refreshMilliHz; + if (!LGParseRefreshRate(m_defRefresh->getValue(), refreshMilliHz)) return 0; - } + m_defaultRefresh = refreshMilliHz; } else if (m_prefNoGPU && hwnd == *m_prefNoGPU && code == BN_CLICKED && m_noGPU) { @@ -377,7 +379,7 @@ LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd) } if (m_defaultRefresh) - m_defRefresh->setNumericValue(*m_defaultRefresh); + m_defRefresh->setValue(LGFormatRefreshRate(*m_defaultRefresh)); else m_defRefresh->disable(); } diff --git a/idd/LGIddHelper/CPipeClient.cpp b/idd/LGIddHelper/CPipeClient.cpp index 61dfad47..09e86df9 100644 --- a/idd/LGIddHelper/CPipeClient.cpp +++ b/idd/LGIddHelper/CPipeClient.cpp @@ -514,8 +514,10 @@ void CPipeClient::HandleSetDisplayMode(const LGPipeMsg& msg) DEVMODE dm = displays[lgIndex].mode; dm.dmPelsWidth = msg.displayMode.width; dm.dmPelsHeight = msg.displayMode.height; - dm.dmDisplayFrequency = msg.displayMode.refresh; - dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; + dm.dmDisplayFrequency = + (msg.displayMode.refreshMilliHz + 500) / 1000; + dm.dmFields = + DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; LONG result = ChangeDisplaySettingsEx(displays[lgIndex].device.DeviceName, &dm, NULL, CDS_UPDATEREGISTRY, NULL); diff --git a/idd/LGIddHelper/CRegistrySettings.cpp b/idd/LGIddHelper/CRegistrySettings.cpp index 76396d7e..9029a648 100644 --- a/idd/LGIddHelper/CRegistrySettings.cpp +++ b/idd/LGIddHelper/CRegistrySettings.cpp @@ -25,10 +25,11 @@ #include #include "DefaultDisplayModes.h" +#include "RefreshRate.h" #define LGIDD_REGKEY L"SOFTWARE\\LookingGlass\\IDD" -const DWORD DEFAULT_REFRESH = 120; +const DWORD DEFAULT_REFRESH = 120000; CRegistrySettings::CRegistrySettings() : hKey(nullptr) {} @@ -60,7 +61,8 @@ static std::basic_string trim(const std::basic_string &s) return s.substr(b, e - b); } -static std::wregex displayMode(L"(\\d+)x(\\d+)@(\\d+)(\\*)?"); +static std::wregex displayMode( + L"(\\d+)x(\\d+)@(\\d+(?:\\.\\d{1,3})?)(\\*)?"); static std::optional parseDisplayMode(const std::wstring &str) { @@ -71,9 +73,10 @@ static std::optional parseDisplayMode(const std::wstring &str) return {}; DisplayMode mode; - mode.width = std::stoul(match[1]); + mode.width = std::stoul(match[1]); mode.height = std::stoul(match[2]); - mode.refresh = std::stoul(match[3]); + if (!LGParseRefreshRate(match[3], mode.refreshMilliHz)) + return {}; mode.preferred = match[4] == L"*"; return mode; } @@ -81,16 +84,17 @@ static std::optional parseDisplayMode(const std::wstring &str) std::vector CRegistrySettings::getDefaultModes() { auto defaultRefresh = getDefaultRefresh(); - int refresh = defaultRefresh ? *defaultRefresh : DEFAULT_REFRESH; + const unsigned refreshMilliHz = + defaultRefresh ? *defaultRefresh : DEFAULT_REFRESH; std::vector result; for (int i = 0; i < ARRAYSIZE(DefaultDisplayModes); ++i) { DisplayMode mode; - mode.width = DefaultDisplayModes[i][0]; - mode.height = DefaultDisplayModes[i][1]; - mode.refresh = refresh; - mode.preferred = i == DefaultPreferredDisplayMode; + mode.width = DefaultDisplayModes[i][0]; + mode.height = DefaultDisplayModes[i][1]; + mode.refreshMilliHz = refreshMilliHz; + mode.preferred = i == DefaultPreferredDisplayMode; result.emplace_back(mode); } return result; @@ -159,7 +163,7 @@ std::wstring DisplayMode::toString() serialized.push_back('x'); serialized.append(std::to_wstring(height)); serialized.push_back('@'); - serialized.append(std::to_wstring(refresh)); + serialized.append(LGFormatRefreshRate(refreshMilliHz)); if (preferred) serialized.push_back('*'); return serialized; @@ -167,24 +171,50 @@ std::wstring DisplayMode::toString() std::optional CRegistrySettings::getDefaultRefresh() { - DWORD result, cbData = sizeof result; - - LSTATUS status = RegGetValue(hKey, nullptr, L"DefaultRefresh", RRF_RT_REG_DWORD, nullptr, &result, &cbData); - switch (status) - { - case ERROR_SUCCESS: - return result; - case ERROR_FILE_NOT_FOUND: + DWORD type = 0; + DWORD size = 0; + LSTATUS status = RegQueryValueExW( + hKey, L"DefaultRefresh", nullptr, &type, nullptr, &size); + if (status == ERROR_FILE_NOT_FOUND) return DEFAULT_REFRESH; - default: - DEBUG_ERROR_HR(status, "RegGetValue(Modes)"); + if (status != ERROR_SUCCESS) + { + DEBUG_ERROR_HR(status, "RegQueryValueEx(DefaultRefresh)"); return {}; } + + if (type == REG_DWORD && size == sizeof(DWORD)) + { + DWORD refresh = 0; + status = RegQueryValueExW(hKey, L"DefaultRefresh", nullptr, &type, + (LPBYTE)&refresh, &size); + if (status == ERROR_SUCCESS && refresh >= 24 && refresh <= 1000) + return refresh * 1000; + } + else if ((type == REG_SZ || type == REG_EXPAND_SZ) && size && + size % sizeof(wchar_t) == 0) + { + std::vector value(size / sizeof(wchar_t) + 1, L'\0'); + status = RegQueryValueExW(hKey, L"DefaultRefresh", nullptr, &type, + (LPBYTE)value.data(), &size); + if (status == ERROR_SUCCESS) + { + unsigned refreshMilliHz; + if (LGParseRefreshRate(value.data(), refreshMilliHz)) + return refreshMilliHz; + } + } + + DEBUG_ERROR("Invalid DefaultRefresh value"); + return {}; } -LSTATUS CRegistrySettings::setDefaultRefresh(DWORD refresh) +LSTATUS CRegistrySettings::setDefaultRefresh(DWORD refreshMilliHz) { - return RegSetValueEx(hKey, L"DefaultRefresh", 0, REG_DWORD, (LPBYTE) &refresh, sizeof(DWORD)); + const std::wstring value = LGFormatRefreshRate(refreshMilliHz); + return RegSetValueExW(hKey, L"DefaultRefresh", 0, REG_SZ, + (const BYTE *)value.c_str(), + (DWORD)((value.size() + 1) * sizeof(wchar_t))); } std::optional CRegistrySettings::getNoGPU() diff --git a/idd/LGIddHelper/CRegistrySettings.h b/idd/LGIddHelper/CRegistrySettings.h index 15414986..0abc9bf7 100644 --- a/idd/LGIddHelper/CRegistrySettings.h +++ b/idd/LGIddHelper/CRegistrySettings.h @@ -28,7 +28,7 @@ struct DisplayMode { unsigned width; unsigned height; - unsigned refresh; + unsigned refreshMilliHz; bool preferred; std::wstring toString(); @@ -49,7 +49,7 @@ public: LSTATUS setModes(const std::vector &modes); std::optional getDefaultRefresh(); - LSTATUS setDefaultRefresh(DWORD refresh); + LSTATUS setDefaultRefresh(DWORD refreshMilliHz); std::optional getNoGPU(); LSTATUS setNoGPU(bool noGPU);