feat: Windows display mode matching (refresh rate, HDR)
This commit is contained in:
@@ -14,6 +14,7 @@ add_executable(${BINARY_NAME} WIN32
|
||||
"mpv/utils.cpp"
|
||||
"mpv/mpv_container.cpp"
|
||||
"mpv/mpv_core.cpp"
|
||||
"mpv/display_mode_manager.cpp"
|
||||
"mpv/mpv_player.cpp"
|
||||
"mpv/mpv_plugin.cpp"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "mpv/display_mode_manager.h"
|
||||
#include "utils.h"
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
@@ -45,6 +46,10 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
||||
// Recover display mode if a prior crash left it changed.
|
||||
mpv::DisplayModeManager::RecoverIfNeeded(
|
||||
::GetAncestor(window.GetHandle(), GA_ROOT));
|
||||
|
||||
::MSG msg;
|
||||
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
||||
::TranslateMessage(&msg);
|
||||
|
||||
@@ -0,0 +1,640 @@
|
||||
#include "display_mode_manager.h"
|
||||
|
||||
#include "sdk_26100.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mpv {
|
||||
|
||||
static const wchar_t* kRegistryPath = L"Software\\Plezy\\DisplayModeOverride";
|
||||
static const wchar_t* kRegDeviceName = L"DeviceName";
|
||||
static const wchar_t* kRegOriginalRefreshRate = L"OriginalRefreshRate";
|
||||
static const wchar_t* kRegOriginalWidth = L"OriginalWidth";
|
||||
static const wchar_t* kRegOriginalHeight = L"OriginalHeight";
|
||||
static const wchar_t* kRegOriginalHDR = L"OriginalHDREnabled";
|
||||
static const wchar_t* kRegModeChanged = L"ModeChanged";
|
||||
static const wchar_t* kRegHDRChanged = L"HDRChanged";
|
||||
|
||||
DisplayModeManager::DisplayModeManager() {}
|
||||
|
||||
DisplayModeManager::~DisplayModeManager() {}
|
||||
|
||||
// --- Monitor identification ---
|
||||
|
||||
std::wstring DisplayModeManager::GetMonitorDeviceName(HWND window) {
|
||||
HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST);
|
||||
if (!monitor) return {};
|
||||
|
||||
MONITORINFOEXW mi = {};
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (!GetMonitorInfoW(monitor, &mi)) return {};
|
||||
|
||||
return mi.szDevice;
|
||||
}
|
||||
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> DisplayModeManager::GetDisplayConfigPaths() {
|
||||
UINT32 path_count = 0;
|
||||
UINT32 mode_count = 0;
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
|
||||
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
|
||||
|
||||
constexpr UINT32 flags = QDC_ONLY_ACTIVE_PATHS;
|
||||
LONG result;
|
||||
|
||||
// Retry loop for ERROR_INSUFFICIENT_BUFFER (Kodi pattern).
|
||||
do {
|
||||
if (GetDisplayConfigBufferSizes(flags, &path_count, &mode_count) != ERROR_SUCCESS)
|
||||
return {};
|
||||
|
||||
paths.resize(path_count);
|
||||
modes.resize(mode_count);
|
||||
|
||||
result = QueryDisplayConfig(flags, &path_count, paths.data(),
|
||||
&mode_count, modes.data(), nullptr);
|
||||
} while (result == ERROR_INSUFFICIENT_BUFFER);
|
||||
|
||||
if (result != ERROR_SUCCESS) return {};
|
||||
|
||||
paths.resize(path_count);
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::optional<DisplayConfigId> DisplayModeManager::GetDisplayTargetId(
|
||||
const std::wstring& gdi_device_name) {
|
||||
// Follows Kodi's GetDisplayTargetId: iterate QueryDisplayConfig paths,
|
||||
// match via DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME.viewGdiDeviceName.
|
||||
DISPLAYCONFIG_SOURCE_DEVICE_NAME source = {};
|
||||
source.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
|
||||
source.header.size = sizeof(source);
|
||||
|
||||
for (const auto& path : GetDisplayConfigPaths()) {
|
||||
source.header.adapterId = path.sourceInfo.adapterId;
|
||||
source.header.id = path.sourceInfo.id;
|
||||
|
||||
if (DisplayConfigGetDeviceInfo(&source.header) == ERROR_SUCCESS &&
|
||||
gdi_device_name == source.viewGdiDeviceName) {
|
||||
return DisplayConfigId{path.targetInfo.adapterId, path.targetInfo.id};
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool DisplayModeManager::IsWin11_24H2OrNewer() {
|
||||
// Win11 24H2 = build 26100+
|
||||
OSVERSIONINFOEXW osvi = {};
|
||||
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
||||
osvi.dwBuildNumber = 26100;
|
||||
|
||||
DWORDLONG condition_mask = 0;
|
||||
VER_SET_CONDITION(condition_mask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
|
||||
|
||||
return VerifyVersionInfoW(&osvi, VER_BUILDNUMBER, condition_mask) != FALSE;
|
||||
}
|
||||
|
||||
// --- Refresh rate / resolution ---
|
||||
|
||||
std::vector<DisplayMode> DisplayModeManager::EnumerateDisplayModes(HWND window) {
|
||||
std::wstring device_name = GetMonitorDeviceName(window);
|
||||
if (device_name.empty()) return {};
|
||||
|
||||
std::vector<DisplayMode> modes;
|
||||
DEVMODEW dm = {};
|
||||
dm.dmSize = sizeof(dm);
|
||||
|
||||
for (DWORD i = 0; EnumDisplaySettingsW(device_name.c_str(), i, &dm); i++) {
|
||||
DisplayMode mode;
|
||||
mode.width = dm.dmPelsWidth;
|
||||
mode.height = dm.dmPelsHeight;
|
||||
mode.refresh_rate = dm.dmDisplayFrequency;
|
||||
modes.push_back(mode);
|
||||
}
|
||||
|
||||
// Remove duplicates.
|
||||
std::sort(modes.begin(), modes.end(), [](const DisplayMode& a, const DisplayMode& b) {
|
||||
if (a.width != b.width) return a.width < b.width;
|
||||
if (a.height != b.height) return a.height < b.height;
|
||||
return a.refresh_rate < b.refresh_rate;
|
||||
});
|
||||
modes.erase(std::unique(modes.begin(), modes.end(), [](const DisplayMode& a, const DisplayMode& b) {
|
||||
return a.width == b.width && a.height == b.height && a.refresh_rate == b.refresh_rate;
|
||||
}), modes.end());
|
||||
|
||||
return modes;
|
||||
}
|
||||
|
||||
DisplayMode DisplayModeManager::GetCurrentMode(HWND window) {
|
||||
std::wstring device_name = GetMonitorDeviceName(window);
|
||||
DisplayMode mode = {};
|
||||
|
||||
if (device_name.empty()) return mode;
|
||||
|
||||
DEVMODEW dm = {};
|
||||
dm.dmSize = sizeof(dm);
|
||||
if (EnumDisplaySettingsW(device_name.c_str(), ENUM_CURRENT_SETTINGS, &dm)) {
|
||||
mode.width = dm.dmPelsWidth;
|
||||
mode.height = dm.dmPelsHeight;
|
||||
mode.refresh_rate = dm.dmDisplayFrequency;
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
void DisplayModeManager::SaveOriginalMode(HWND window) {
|
||||
original_device_name_ = GetMonitorDeviceName(window);
|
||||
if (original_device_name_.empty()) return;
|
||||
|
||||
original_devmode_ = {};
|
||||
original_devmode_.dmSize = sizeof(original_devmode_);
|
||||
EnumDisplaySettingsW(original_device_name_.c_str(), ENUM_CURRENT_SETTINGS,
|
||||
&original_devmode_);
|
||||
}
|
||||
|
||||
bool DisplayModeManager::SetDisplayMode(HWND window, DWORD width, DWORD height,
|
||||
DWORD refresh_rate) {
|
||||
std::wstring device_name = GetMonitorDeviceName(window);
|
||||
if (device_name.empty()) return false;
|
||||
|
||||
// Save original mode if not already saved.
|
||||
if (!mode_changed_) {
|
||||
SaveOriginalMode(window);
|
||||
}
|
||||
|
||||
DEVMODEW dm = {};
|
||||
dm.dmSize = sizeof(dm);
|
||||
dm.dmPelsWidth = width;
|
||||
dm.dmPelsHeight = height;
|
||||
dm.dmDisplayFrequency = refresh_rate;
|
||||
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
|
||||
bool changed = false;
|
||||
|
||||
// Kodi's Win8+ workaround for exact integer refresh rates (24, 48, 60 Hz).
|
||||
// Write desired mode to registry, apply from registry, restore registry.
|
||||
// Source: xbmc/windowing/windows/WinSystemWin32.cpp:940-970.
|
||||
if (refresh_rate == 24 || refresh_rate == 48 || refresh_rate == 60) {
|
||||
DEVMODEW registry_dm = {};
|
||||
registry_dm.dmSize = sizeof(registry_dm);
|
||||
if (EnumDisplaySettingsW(device_name.c_str(), ENUM_REGISTRY_SETTINGS, ®istry_dm)) {
|
||||
LONG rc = ChangeDisplaySettingsExW(device_name.c_str(), &dm, nullptr,
|
||||
CDS_UPDATEREGISTRY | CDS_NORESET, nullptr);
|
||||
if (rc == DISP_CHANGE_SUCCESSFUL) {
|
||||
rc = ChangeDisplaySettingsExW(device_name.c_str(), nullptr, nullptr,
|
||||
CDS_FULLSCREEN, nullptr);
|
||||
if (rc == DISP_CHANGE_SUCCESSFUL) changed = true;
|
||||
|
||||
// Restore original registry settings.
|
||||
registry_dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
|
||||
ChangeDisplaySettingsExW(device_name.c_str(), ®istry_dm, nullptr,
|
||||
CDS_UPDATEREGISTRY | CDS_NORESET, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Standard path / fallback.
|
||||
if (!changed) {
|
||||
LONG rc = ChangeDisplaySettingsExW(device_name.c_str(), &dm, nullptr,
|
||||
CDS_FULLSCREEN, nullptr);
|
||||
if (rc == DISP_CHANGE_SUCCESSFUL) changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
mode_changed_ = true;
|
||||
WriteRecoveryState();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool DisplayModeManager::RestoreOriginalMode(HWND window) {
|
||||
if (!mode_changed_ || original_device_name_.empty()) return false;
|
||||
|
||||
original_devmode_.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
|
||||
|
||||
LONG rc = ChangeDisplaySettingsExW(original_device_name_.c_str(),
|
||||
&original_devmode_, nullptr,
|
||||
CDS_FULLSCREEN, nullptr);
|
||||
|
||||
if (rc == DISP_CHANGE_SUCCESSFUL) {
|
||||
mode_changed_ = false;
|
||||
if (!hdr_changed_) ClearRecoveryState();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: restore registry defaults.
|
||||
rc = ChangeDisplaySettingsExW(original_device_name_.c_str(), nullptr, nullptr, 0, nullptr);
|
||||
mode_changed_ = (rc != DISP_CHANGE_SUCCESSFUL);
|
||||
if (!mode_changed_ && !hdr_changed_) ClearRecoveryState();
|
||||
return rc == DISP_CHANGE_SUCCESSFUL;
|
||||
}
|
||||
|
||||
// --- HDR ---
|
||||
|
||||
bool DisplayModeManager::IsHDRSupported(HWND window) {
|
||||
std::wstring device_name = GetMonitorDeviceName(window);
|
||||
if (device_name.empty()) return false;
|
||||
|
||||
auto target_id = GetDisplayTargetId(device_name);
|
||||
if (!target_id) return false;
|
||||
|
||||
// Follows Kodi's GetDisplayHDRStatus pattern.
|
||||
if (IsWin11_24H2OrNewer()) {
|
||||
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 info = {};
|
||||
info.header.type = static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2);
|
||||
info.header.size = sizeof(info);
|
||||
info.header.adapterId = target_id->adapter_id;
|
||||
info.header.id = target_id->id;
|
||||
|
||||
if (DisplayConfigGetDeviceInfo(&info.header) == ERROR_SUCCESS) {
|
||||
return info.highDynamicRangeSupported == TRUE;
|
||||
}
|
||||
} else {
|
||||
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO info = {};
|
||||
info.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
|
||||
info.header.size = sizeof(info);
|
||||
info.header.adapterId = target_id->adapter_id;
|
||||
info.header.id = target_id->id;
|
||||
|
||||
if (DisplayConfigGetDeviceInfo(&info.header) == ERROR_SUCCESS) {
|
||||
// advancedColorSupported=1 && wideColorEnforced=0 => true HDR screen.
|
||||
// advancedColorSupported=1 && wideColorEnforced=1 => SDR screen with ACM (Win11 22H2+).
|
||||
// Source: Kodi DisplayUtilsWin32.cpp:157-172.
|
||||
return info.advancedColorSupported && !info.wideColorEnforced;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DisplayModeManager::IsHDREnabled(HWND window) {
|
||||
std::wstring device_name = GetMonitorDeviceName(window);
|
||||
if (device_name.empty()) return false;
|
||||
|
||||
auto target_id = GetDisplayTargetId(device_name);
|
||||
if (!target_id) return false;
|
||||
|
||||
if (IsWin11_24H2OrNewer()) {
|
||||
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 info = {};
|
||||
info.header.type = static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2);
|
||||
info.header.size = sizeof(info);
|
||||
info.header.adapterId = target_id->adapter_id;
|
||||
info.header.id = target_id->id;
|
||||
|
||||
if (DisplayConfigGetDeviceInfo(&info.header) == ERROR_SUCCESS) {
|
||||
return info.activeColorMode == DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR;
|
||||
}
|
||||
} else {
|
||||
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO info = {};
|
||||
info.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
|
||||
info.header.size = sizeof(info);
|
||||
info.header.adapterId = target_id->adapter_id;
|
||||
info.header.id = target_id->id;
|
||||
|
||||
if (DisplayConfigGetDeviceInfo(&info.header) == ERROR_SUCCESS) {
|
||||
bool hdr_supported = info.advancedColorSupported && !info.wideColorEnforced;
|
||||
return hdr_supported && info.advancedColorEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisplayModeManager::SaveOriginalHDRState(HWND window) {
|
||||
original_hdr_device_name_ = GetMonitorDeviceName(window);
|
||||
original_hdr_enabled_ = IsHDREnabled(window);
|
||||
}
|
||||
|
||||
bool DisplayModeManager::SetHDREnabled(HWND window, bool enabled) {
|
||||
std::wstring device_name = GetMonitorDeviceName(window);
|
||||
if (device_name.empty()) return false;
|
||||
|
||||
auto target_id = GetDisplayTargetId(device_name);
|
||||
if (!target_id) return false;
|
||||
|
||||
// Save original state if not already saved.
|
||||
if (!hdr_changed_) {
|
||||
SaveOriginalHDRState(window);
|
||||
}
|
||||
|
||||
// Save DEVMODEW before toggle — Windows changes display mode on HDR state change.
|
||||
// Source: Kodi WIN32Util.cpp:1252-1257.
|
||||
DEVMODEW pre_toggle_dm = {};
|
||||
pre_toggle_dm.dmSize = sizeof(pre_toggle_dm);
|
||||
EnumDisplaySettingsW(device_name.c_str(), ENUM_CURRENT_SETTINGS, &pre_toggle_dm);
|
||||
|
||||
// Toggle HDR.
|
||||
LONG result;
|
||||
if (IsWin11_24H2OrNewer()) {
|
||||
DISPLAYCONFIG_SET_HDR_STATE state = {};
|
||||
state.header.type = static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE);
|
||||
state.header.size = sizeof(state);
|
||||
state.header.adapterId = target_id->adapter_id;
|
||||
state.header.id = target_id->id;
|
||||
state.enableHdr = enabled ? TRUE : FALSE;
|
||||
result = DisplayConfigSetDeviceInfo(&state.header);
|
||||
} else {
|
||||
DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE state = {};
|
||||
state.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
|
||||
state.header.size = sizeof(state);
|
||||
state.header.adapterId = target_id->adapter_id;
|
||||
state.header.id = target_id->id;
|
||||
state.enableAdvancedColor = enabled ? TRUE : FALSE;
|
||||
result = DisplayConfigSetDeviceInfo(&state.header);
|
||||
}
|
||||
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
// Restore DEVMODEW after toggle — Windows may have changed the display mode.
|
||||
// Source: Kodi WIN32Util.cpp:1276-1288.
|
||||
if (pre_toggle_dm.dmDisplayFrequency != 0) {
|
||||
pre_toggle_dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
|
||||
ChangeDisplaySettingsExW(device_name.c_str(), &pre_toggle_dm, nullptr,
|
||||
CDS_FULLSCREEN, nullptr);
|
||||
}
|
||||
|
||||
hdr_changed_ = true;
|
||||
WriteRecoveryState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayModeManager::RestoreOriginalHDRState(HWND window) {
|
||||
if (!hdr_changed_ || original_hdr_device_name_.empty()) return false;
|
||||
|
||||
bool current = IsHDREnabled(window);
|
||||
if (current == original_hdr_enabled_) {
|
||||
hdr_changed_ = false;
|
||||
if (!mode_changed_) ClearRecoveryState();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Need to actually toggle back.
|
||||
auto target_id = GetDisplayTargetId(original_hdr_device_name_);
|
||||
if (!target_id) return false;
|
||||
|
||||
// Save DEVMODEW before restore toggle.
|
||||
DEVMODEW pre_toggle_dm = {};
|
||||
pre_toggle_dm.dmSize = sizeof(pre_toggle_dm);
|
||||
EnumDisplaySettingsW(original_hdr_device_name_.c_str(), ENUM_CURRENT_SETTINGS, &pre_toggle_dm);
|
||||
|
||||
LONG result;
|
||||
if (IsWin11_24H2OrNewer()) {
|
||||
DISPLAYCONFIG_SET_HDR_STATE state = {};
|
||||
state.header.type = static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE);
|
||||
state.header.size = sizeof(state);
|
||||
state.header.adapterId = target_id->adapter_id;
|
||||
state.header.id = target_id->id;
|
||||
state.enableHdr = original_hdr_enabled_ ? TRUE : FALSE;
|
||||
result = DisplayConfigSetDeviceInfo(&state.header);
|
||||
} else {
|
||||
DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE state = {};
|
||||
state.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
|
||||
state.header.size = sizeof(state);
|
||||
state.header.adapterId = target_id->adapter_id;
|
||||
state.header.id = target_id->id;
|
||||
state.enableAdvancedColor = original_hdr_enabled_ ? TRUE : FALSE;
|
||||
result = DisplayConfigSetDeviceInfo(&state.header);
|
||||
}
|
||||
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
// Restore DEVMODEW after toggle.
|
||||
if (pre_toggle_dm.dmDisplayFrequency != 0) {
|
||||
pre_toggle_dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
|
||||
ChangeDisplaySettingsExW(original_hdr_device_name_.c_str(), &pre_toggle_dm,
|
||||
nullptr, CDS_FULLSCREEN, nullptr);
|
||||
}
|
||||
|
||||
hdr_changed_ = false;
|
||||
if (!mode_changed_) ClearRecoveryState();
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Crash recovery (Windows Registry) ---
|
||||
|
||||
bool DisplayModeManager::WriteRegistryDWORD(const wchar_t* value_name, DWORD value) {
|
||||
HKEY key;
|
||||
if (RegCreateKeyExW(HKEY_CURRENT_USER, kRegistryPath, 0, nullptr,
|
||||
0, KEY_WRITE, nullptr, &key, nullptr) != ERROR_SUCCESS)
|
||||
return false;
|
||||
LONG result = RegSetValueExW(key, value_name, 0, REG_DWORD,
|
||||
reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(key);
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
bool DisplayModeManager::WriteRegistryString(const wchar_t* value_name,
|
||||
const std::wstring& value) {
|
||||
HKEY key;
|
||||
if (RegCreateKeyExW(HKEY_CURRENT_USER, kRegistryPath, 0, nullptr,
|
||||
0, KEY_WRITE, nullptr, &key, nullptr) != ERROR_SUCCESS)
|
||||
return false;
|
||||
LONG result = RegSetValueExW(key, value_name, 0, REG_SZ,
|
||||
reinterpret_cast<const BYTE*>(value.c_str()),
|
||||
static_cast<DWORD>((value.size() + 1) * sizeof(wchar_t)));
|
||||
RegCloseKey(key);
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
bool DisplayModeManager::ReadRegistryDWORD(const wchar_t* value_name, DWORD& value) {
|
||||
HKEY key;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, kRegistryPath, 0, KEY_READ, &key) != ERROR_SUCCESS)
|
||||
return false;
|
||||
DWORD size = sizeof(value);
|
||||
DWORD type = 0;
|
||||
LONG result = RegQueryValueExW(key, value_name, nullptr, &type,
|
||||
reinterpret_cast<BYTE*>(&value), &size);
|
||||
RegCloseKey(key);
|
||||
return result == ERROR_SUCCESS && type == REG_DWORD;
|
||||
}
|
||||
|
||||
bool DisplayModeManager::ReadRegistryString(const wchar_t* value_name, std::wstring& value) {
|
||||
HKEY key;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, kRegistryPath, 0, KEY_READ, &key) != ERROR_SUCCESS)
|
||||
return false;
|
||||
DWORD size = 0;
|
||||
DWORD type = 0;
|
||||
RegQueryValueExW(key, value_name, nullptr, &type, nullptr, &size);
|
||||
if (type != REG_SZ || size == 0) {
|
||||
RegCloseKey(key);
|
||||
return false;
|
||||
}
|
||||
value.resize(size / sizeof(wchar_t));
|
||||
LONG result = RegQueryValueExW(key, value_name, nullptr, nullptr,
|
||||
reinterpret_cast<BYTE*>(&value[0]), &size);
|
||||
RegCloseKey(key);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
// Remove trailing null.
|
||||
while (!value.empty() && value.back() == L'\0') value.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayModeManager::DeleteRegistryValue(const wchar_t* value_name) {
|
||||
HKEY key;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, kRegistryPath, 0, KEY_WRITE, &key) != ERROR_SUCCESS)
|
||||
return false;
|
||||
RegDeleteValueW(key, value_name);
|
||||
RegCloseKey(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisplayModeManager::WriteRecoveryState() {
|
||||
std::wstring device = mode_changed_ ? original_device_name_ : original_hdr_device_name_;
|
||||
if (device.empty()) return;
|
||||
|
||||
WriteRegistryString(kRegDeviceName, device);
|
||||
WriteRegistryDWORD(kRegModeChanged, mode_changed_ ? 1 : 0);
|
||||
WriteRegistryDWORD(kRegHDRChanged, hdr_changed_ ? 1 : 0);
|
||||
|
||||
if (mode_changed_) {
|
||||
WriteRegistryDWORD(kRegOriginalRefreshRate, original_devmode_.dmDisplayFrequency);
|
||||
WriteRegistryDWORD(kRegOriginalWidth, original_devmode_.dmPelsWidth);
|
||||
WriteRegistryDWORD(kRegOriginalHeight, original_devmode_.dmPelsHeight);
|
||||
}
|
||||
|
||||
if (hdr_changed_) {
|
||||
WriteRegistryDWORD(kRegOriginalHDR, original_hdr_enabled_ ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayModeManager::ClearRecoveryState() {
|
||||
// Delete the entire key.
|
||||
RegDeleteKeyW(HKEY_CURRENT_USER, kRegistryPath);
|
||||
}
|
||||
|
||||
bool DisplayModeManager::RecoverIfNeeded(HWND window) {
|
||||
DWORD mode_changed = 0, hdr_changed = 0;
|
||||
std::wstring device_name;
|
||||
|
||||
if (!ReadRegistryString(kRegDeviceName, device_name)) return false;
|
||||
ReadRegistryDWORD(kRegModeChanged, mode_changed);
|
||||
ReadRegistryDWORD(kRegHDRChanged, hdr_changed);
|
||||
|
||||
if (!mode_changed && !hdr_changed) {
|
||||
RegDeleteKeyW(HKEY_CURRENT_USER, kRegistryPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool recovered = false;
|
||||
|
||||
// Restore refresh rate / resolution.
|
||||
if (mode_changed) {
|
||||
DWORD width = 0, height = 0, refresh = 0;
|
||||
ReadRegistryDWORD(kRegOriginalWidth, width);
|
||||
ReadRegistryDWORD(kRegOriginalHeight, height);
|
||||
ReadRegistryDWORD(kRegOriginalRefreshRate, refresh);
|
||||
|
||||
if (width > 0 && height > 0 && refresh > 0) {
|
||||
DEVMODEW dm = {};
|
||||
dm.dmSize = sizeof(dm);
|
||||
dm.dmPelsWidth = width;
|
||||
dm.dmPelsHeight = height;
|
||||
dm.dmDisplayFrequency = refresh;
|
||||
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
|
||||
LONG rc = ChangeDisplaySettingsExW(device_name.c_str(), &dm, nullptr,
|
||||
CDS_FULLSCREEN, nullptr);
|
||||
if (rc == DISP_CHANGE_SUCCESSFUL) recovered = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore HDR state.
|
||||
if (hdr_changed) {
|
||||
DWORD hdr_was_enabled = 0;
|
||||
ReadRegistryDWORD(kRegOriginalHDR, hdr_was_enabled);
|
||||
|
||||
auto target_id = GetDisplayTargetId(device_name);
|
||||
if (target_id) {
|
||||
// Save DEVMODEW before toggle.
|
||||
DEVMODEW pre_dm = {};
|
||||
pre_dm.dmSize = sizeof(pre_dm);
|
||||
EnumDisplaySettingsW(device_name.c_str(), ENUM_CURRENT_SETTINGS, &pre_dm);
|
||||
|
||||
LONG result;
|
||||
if (IsWin11_24H2OrNewer()) {
|
||||
DISPLAYCONFIG_SET_HDR_STATE state = {};
|
||||
state.header.type = static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE);
|
||||
state.header.size = sizeof(state);
|
||||
state.header.adapterId = target_id->adapter_id;
|
||||
state.header.id = target_id->id;
|
||||
state.enableHdr = hdr_was_enabled ? TRUE : FALSE;
|
||||
result = DisplayConfigSetDeviceInfo(&state.header);
|
||||
} else {
|
||||
DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE state = {};
|
||||
state.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
|
||||
state.header.size = sizeof(state);
|
||||
state.header.adapterId = target_id->adapter_id;
|
||||
state.header.id = target_id->id;
|
||||
state.enableAdvancedColor = hdr_was_enabled ? TRUE : FALSE;
|
||||
result = DisplayConfigSetDeviceInfo(&state.header);
|
||||
}
|
||||
|
||||
if (result == ERROR_SUCCESS) {
|
||||
recovered = true;
|
||||
// Restore display mode after HDR toggle.
|
||||
if (pre_dm.dmDisplayFrequency != 0) {
|
||||
pre_dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
|
||||
ChangeDisplaySettingsExW(device_name.c_str(), &pre_dm, nullptr,
|
||||
CDS_FULLSCREEN, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up registry regardless of success.
|
||||
RegDeleteKeyW(HKEY_CURRENT_USER, kRegistryPath);
|
||||
return recovered;
|
||||
}
|
||||
|
||||
// --- Refresh rate matching ---
|
||||
|
||||
DWORD DisplayModeManager::FindBestRefreshRate(double video_fps,
|
||||
const std::vector<DisplayMode>& modes,
|
||||
DWORD current_width,
|
||||
DWORD current_height) {
|
||||
if (video_fps <= 0) return 0;
|
||||
|
||||
// Collect unique refresh rates available at the current resolution.
|
||||
std::vector<DWORD> rates;
|
||||
for (const auto& mode : modes) {
|
||||
if (mode.width == current_width && mode.height == current_height) {
|
||||
if (std::find(rates.begin(), rates.end(), mode.refresh_rate) == rates.end()) {
|
||||
rates.push_back(mode.refresh_rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rates.empty()) return 0;
|
||||
|
||||
DWORD best_rate = 0;
|
||||
int best_multiplier = 0;
|
||||
|
||||
for (DWORD rate : rates) {
|
||||
double ratio = static_cast<double>(rate) / video_fps;
|
||||
double rounded = std::round(ratio);
|
||||
|
||||
// Must be a positive integer multiple (1x, 2x, 3x, ...).
|
||||
if (rounded < 1.0) continue;
|
||||
|
||||
int multiplier = static_cast<int>(rounded);
|
||||
double deviation = std::abs(ratio - rounded) / rounded;
|
||||
|
||||
// Within 0.5% tolerance (covers 23.976 -> 24Hz, 29.97 -> 30Hz, etc.).
|
||||
if (deviation > 0.005) continue;
|
||||
|
||||
// Prefer lowest multiplier (exact match > 2x > 3x > ...).
|
||||
// Among equal multipliers, prefer higher rate (shouldn't happen, but safe).
|
||||
if (best_rate == 0 || multiplier < best_multiplier ||
|
||||
(multiplier == best_multiplier && rate > best_rate)) {
|
||||
best_rate = rate;
|
||||
best_multiplier = multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
return best_rate;
|
||||
}
|
||||
|
||||
} // namespace mpv
|
||||
@@ -0,0 +1,139 @@
|
||||
#ifndef DISPLAY_MODE_MANAGER_H_
|
||||
#define DISPLAY_MODE_MANAGER_H_
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mpv {
|
||||
|
||||
struct DisplayMode {
|
||||
DWORD width;
|
||||
DWORD height;
|
||||
DWORD refresh_rate;
|
||||
};
|
||||
|
||||
// Identifiers for a display target in the DisplayConfig API.
|
||||
struct DisplayConfigId {
|
||||
LUID adapter_id;
|
||||
UINT32 id;
|
||||
};
|
||||
|
||||
// Manages Windows display mode switching (refresh rate, HDR) for video playback.
|
||||
// Pure Win32 utility — no mpv or Flutter dependency.
|
||||
//
|
||||
// References:
|
||||
// ChangeDisplaySettingsExW: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changedisplaysettingsexw
|
||||
// EnumDisplaySettingsW: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaysettingsw
|
||||
// DisplayConfigGetDeviceInfo: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-displayconfiggetdeviceinfo
|
||||
// DisplayConfigSetDeviceInfo: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-displayconfigsetdeviceinfo
|
||||
// Kodi impl: xbmc/platform/win32/DisplayUtilsWin32.cpp, xbmc/platform/win32/WIN32Util.cpp
|
||||
class DisplayModeManager {
|
||||
public:
|
||||
DisplayModeManager();
|
||||
~DisplayModeManager();
|
||||
|
||||
// --- Refresh rate / resolution ---
|
||||
|
||||
// Enumerate available display modes for the monitor containing the window.
|
||||
std::vector<DisplayMode> EnumerateDisplayModes(HWND window);
|
||||
|
||||
// Get the current display mode.
|
||||
DisplayMode GetCurrentMode(HWND window);
|
||||
|
||||
// Save the current mode for later restoration.
|
||||
void SaveOriginalMode(HWND window);
|
||||
|
||||
// Change the display mode (refresh rate and/or resolution).
|
||||
// Uses CDS_FULLSCREEN flag. Implements Kodi's Win8+ workaround for 24/48/60Hz.
|
||||
// Returns true on success.
|
||||
bool SetDisplayMode(HWND window, DWORD width, DWORD height, DWORD refresh_rate);
|
||||
|
||||
// Restore the previously saved display mode.
|
||||
bool RestoreOriginalMode(HWND window);
|
||||
|
||||
// Returns true if a mode change has been applied (and not yet restored).
|
||||
bool IsModeChanged() const { return mode_changed_; }
|
||||
|
||||
// --- HDR ---
|
||||
|
||||
// Check if the display supports HDR (not just ACM/WCG).
|
||||
// Uses advancedColorSupported && !wideColorEnforced (pre-24H2)
|
||||
// or highDynamicRangeSupported (Win11 24H2+).
|
||||
bool IsHDRSupported(HWND window);
|
||||
|
||||
// Check if HDR is currently enabled.
|
||||
bool IsHDREnabled(HWND window);
|
||||
|
||||
// Save the current HDR state for later restoration.
|
||||
void SaveOriginalHDRState(HWND window);
|
||||
|
||||
// Enable or disable system HDR.
|
||||
// Saves/restores DEVMODEW around the toggle (Windows changes display mode on HDR state change).
|
||||
// Uses SET_HDR_STATE (type 16) on Win11 24H2+, SET_ADVANCED_COLOR_STATE (type 10) on older.
|
||||
bool SetHDREnabled(HWND window, bool enabled);
|
||||
|
||||
// Restore the previously saved HDR state.
|
||||
bool RestoreOriginalHDRState(HWND window);
|
||||
|
||||
// Returns true if an HDR state change has been applied (and not yet restored).
|
||||
bool IsHDRChanged() const { return hdr_changed_; }
|
||||
|
||||
// --- Crash recovery ---
|
||||
|
||||
// Write current override state to registry for crash recovery.
|
||||
void WriteRecoveryState();
|
||||
|
||||
// Clear the recovery state from registry.
|
||||
void ClearRecoveryState();
|
||||
|
||||
// Check for and recover from a prior crash that left display settings changed.
|
||||
// Should be called early in app startup. Returns true if recovery was performed.
|
||||
static bool RecoverIfNeeded(HWND window);
|
||||
|
||||
// --- Refresh rate matching ---
|
||||
|
||||
// Find the best matching refresh rate for a given video fps from available modes.
|
||||
// Returns 0 if no suitable match found.
|
||||
static DWORD FindBestRefreshRate(double video_fps,
|
||||
const std::vector<DisplayMode>& modes,
|
||||
DWORD current_width, DWORD current_height);
|
||||
|
||||
private:
|
||||
// Get the GDI device name for the monitor containing the window.
|
||||
static std::wstring GetMonitorDeviceName(HWND window);
|
||||
|
||||
// Get the DisplayConfig target ID for a given GDI device name.
|
||||
// Follows Kodi's GetDisplayTargetId pattern.
|
||||
static std::optional<DisplayConfigId> GetDisplayTargetId(const std::wstring& gdi_device_name);
|
||||
|
||||
// Get all active display config paths (with retry for ERROR_INSUFFICIENT_BUFFER).
|
||||
static std::vector<DISPLAYCONFIG_PATH_INFO> GetDisplayConfigPaths();
|
||||
|
||||
// Check if running on Win11 24H2 or newer.
|
||||
static bool IsWin11_24H2OrNewer();
|
||||
|
||||
// Registry helpers for crash recovery.
|
||||
static bool WriteRegistryDWORD(const wchar_t* value_name, DWORD value);
|
||||
static bool WriteRegistryString(const wchar_t* value_name, const std::wstring& value);
|
||||
static bool ReadRegistryDWORD(const wchar_t* value_name, DWORD& value);
|
||||
static bool ReadRegistryString(const wchar_t* value_name, std::wstring& value);
|
||||
static bool DeleteRegistryValue(const wchar_t* value_name);
|
||||
|
||||
// Stored original mode for restoration.
|
||||
std::wstring original_device_name_;
|
||||
DEVMODEW original_devmode_ = {};
|
||||
bool mode_changed_ = false;
|
||||
|
||||
// Stored original HDR state for restoration.
|
||||
std::wstring original_hdr_device_name_;
|
||||
bool original_hdr_enabled_ = false;
|
||||
bool hdr_changed_ = false;
|
||||
};
|
||||
|
||||
} // namespace mpv
|
||||
|
||||
#endif // DISPLAY_MODE_MANAGER_H_
|
||||
@@ -369,6 +369,75 @@ void MpvPlayerPlugin::HandleMethodCall(
|
||||
} else if (method == "isInitialized") {
|
||||
bool initialized = player_ && player_->IsInitialized();
|
||||
result->Success(flutter::EncodableValue(initialized));
|
||||
|
||||
// --- Display mode matching ---
|
||||
} else if (method == "getDisplayModes") {
|
||||
HWND hwnd = GetWindow();
|
||||
auto modes = display_mode_manager_.EnumerateDisplayModes(hwnd);
|
||||
flutter::EncodableList list;
|
||||
for (const auto& mode : modes) {
|
||||
flutter::EncodableMap m;
|
||||
m[flutter::EncodableValue("width")] = flutter::EncodableValue(static_cast<int32_t>(mode.width));
|
||||
m[flutter::EncodableValue("height")] = flutter::EncodableValue(static_cast<int32_t>(mode.height));
|
||||
m[flutter::EncodableValue("refreshRate")] = flutter::EncodableValue(static_cast<int32_t>(mode.refresh_rate));
|
||||
list.push_back(flutter::EncodableValue(m));
|
||||
}
|
||||
result->Success(flutter::EncodableValue(list));
|
||||
} else if (method == "getCurrentDisplayMode") {
|
||||
HWND hwnd = GetWindow();
|
||||
auto mode = display_mode_manager_.GetCurrentMode(hwnd);
|
||||
flutter::EncodableMap m;
|
||||
m[flutter::EncodableValue("width")] = flutter::EncodableValue(static_cast<int32_t>(mode.width));
|
||||
m[flutter::EncodableValue("height")] = flutter::EncodableValue(static_cast<int32_t>(mode.height));
|
||||
m[flutter::EncodableValue("refreshRate")] = flutter::EncodableValue(static_cast<int32_t>(mode.refresh_rate));
|
||||
result->Success(flutter::EncodableValue(m));
|
||||
} else if (method == "setDisplayMode") {
|
||||
const auto* args = method_call.arguments();
|
||||
if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
|
||||
result->Error("INVALID_ARGS", "Expected map argument");
|
||||
return;
|
||||
}
|
||||
const auto& map = std::get<flutter::EncodableMap>(*args);
|
||||
auto get_int = [&map](const char* key) -> int {
|
||||
auto it = map.find(flutter::EncodableValue(key));
|
||||
if (it != map.end() && std::holds_alternative<int32_t>(it->second))
|
||||
return std::get<int32_t>(it->second);
|
||||
return 0;
|
||||
};
|
||||
HWND hwnd = GetWindow();
|
||||
bool success = display_mode_manager_.SetDisplayMode(
|
||||
hwnd, get_int("width"), get_int("height"), get_int("refreshRate"));
|
||||
result->Success(flutter::EncodableValue(success));
|
||||
} else if (method == "restoreDisplayMode") {
|
||||
HWND hwnd = GetWindow();
|
||||
bool success = display_mode_manager_.RestoreOriginalMode(hwnd);
|
||||
result->Success(flutter::EncodableValue(success));
|
||||
} else if (method == "isHDRSupported") {
|
||||
HWND hwnd = GetWindow();
|
||||
result->Success(flutter::EncodableValue(display_mode_manager_.IsHDRSupported(hwnd)));
|
||||
} else if (method == "isHDREnabled") {
|
||||
HWND hwnd = GetWindow();
|
||||
result->Success(flutter::EncodableValue(display_mode_manager_.IsHDREnabled(hwnd)));
|
||||
} else if (method == "setSystemHDR") {
|
||||
const auto* args = method_call.arguments();
|
||||
if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
|
||||
result->Error("INVALID_ARGS", "Expected map argument");
|
||||
return;
|
||||
}
|
||||
const auto& map = std::get<flutter::EncodableMap>(*args);
|
||||
auto it = map.find(flutter::EncodableValue("enabled"));
|
||||
if (it == map.end() || !std::holds_alternative<bool>(it->second)) {
|
||||
result->Error("INVALID_ARGS", "Missing 'enabled'");
|
||||
return;
|
||||
}
|
||||
bool enabled = std::get<bool>(it->second);
|
||||
HWND hwnd = GetWindow();
|
||||
bool success = display_mode_manager_.SetHDREnabled(hwnd, enabled);
|
||||
result->Success(flutter::EncodableValue(success));
|
||||
} else if (method == "restoreSystemHDR") {
|
||||
HWND hwnd = GetWindow();
|
||||
bool success = display_mode_manager_.RestoreOriginalHDRState(hwnd);
|
||||
result->Success(flutter::EncodableValue(success));
|
||||
} else {
|
||||
result->NotImplemented();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "display_mode_manager.h"
|
||||
#include "mpv_core.h"
|
||||
#include "mpv_player.h"
|
||||
|
||||
@@ -45,6 +46,7 @@ class MpvPlayerPlugin : public flutter::Plugin {
|
||||
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> event_sink_;
|
||||
|
||||
std::unique_ptr<MpvPlayer> player_;
|
||||
DisplayModeManager display_mode_manager_;
|
||||
std::optional<int32_t> proc_id_;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef SDK_26100_H_
|
||||
#define SDK_26100_H_
|
||||
|
||||
// Win11 24H2 (SDK 10.0.26100.0) struct shims for HDR APIs.
|
||||
// These types may not be present in older Windows SDK versions.
|
||||
// Based on Kodi's SDK_26100.h (xbmc/platform/win32/SDK_26100.h).
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
enum {
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_RESERVED1 = 14,
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2 = 15,
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE = 16,
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_WCG_STATE = 17,
|
||||
};
|
||||
|
||||
typedef enum _DISPLAYCONFIG_ADVANCED_COLOR_MODE {
|
||||
DISPLAYCONFIG_ADVANCED_COLOR_MODE_SDR,
|
||||
DISPLAYCONFIG_ADVANCED_COLOR_MODE_WCG,
|
||||
DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR
|
||||
} DISPLAYCONFIG_ADVANCED_COLOR_MODE;
|
||||
|
||||
typedef struct _DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 {
|
||||
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
|
||||
union {
|
||||
struct {
|
||||
UINT32 advancedColorSupported : 1;
|
||||
UINT32 advancedColorActive : 1;
|
||||
UINT32 reserved1 : 1;
|
||||
UINT32 advancedColorLimitedByPolicy : 1;
|
||||
UINT32 highDynamicRangeSupported : 1;
|
||||
UINT32 highDynamicRangeUserEnabled : 1;
|
||||
UINT32 wideColorSupported : 1;
|
||||
UINT32 wideColorUserEnabled : 1;
|
||||
UINT32 reserved : 24;
|
||||
};
|
||||
UINT32 value;
|
||||
};
|
||||
DISPLAYCONFIG_COLOR_ENCODING colorEncoding;
|
||||
UINT32 bitsPerColorChannel;
|
||||
DISPLAYCONFIG_ADVANCED_COLOR_MODE activeColorMode;
|
||||
} DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2;
|
||||
|
||||
typedef struct _DISPLAYCONFIG_SET_HDR_STATE {
|
||||
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
|
||||
union {
|
||||
struct {
|
||||
UINT32 enableHdr : 1;
|
||||
UINT32 reserved : 31;
|
||||
};
|
||||
UINT32 value;
|
||||
};
|
||||
} DISPLAYCONFIG_SET_HDR_STATE;
|
||||
|
||||
#endif // SDK_26100_H_
|
||||
Reference in New Issue
Block a user