mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-13 20:25:53 +08:00
[feat] add remote display resolution selection to video settings
This commit is contained in:
1 parent
14fa95beeb
commit
a3159b198e
15 files changed
+740
-43
No files matched your search
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-11-11
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DISPLAY_RESOLUTION_WIN_H_
|
||||
#define _DISPLAY_RESOLUTION_WIN_H_
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "display_info.h"
|
||||
#include "display_label.h"
|
||||
#include <display_resolution.h>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
// Injectable Win32 calls keep mode selection and failure handling testable
|
||||
// without changing the developer's desktop.
|
||||
struct WindowsResolutionApi {
|
||||
decltype(&EnumDisplaySettingsW) enumerate = &EnumDisplaySettingsW;
|
||||
decltype(&ChangeDisplaySettingsExW) change = &ChangeDisplaySettingsExW;
|
||||
};
|
||||
|
||||
inline std::vector<DEVMODEW>
|
||||
WindowsResolutionModes(const wchar_t *device, const DEVMODEW ¤t,
|
||||
const WindowsResolutionApi &api = {}) {
|
||||
std::map<std::pair<int, int>, DEVMODEW> modes;
|
||||
for (DWORD index = 0; index < 16384; ++index) {
|
||||
DEVMODEW mode{};
|
||||
mode.dmSize = sizeof(mode);
|
||||
if (!api.enumerate(device, index, &mode))
|
||||
break;
|
||||
if (mode.dmBitsPerPel != current.dmBitsPerPel ||
|
||||
(mode.dmDisplayFlags & DM_INTERLACED) || mode.dmPelsWidth > 32768 ||
|
||||
mode.dmPelsHeight > 32768 ||
|
||||
!ValidDisplayResolution(
|
||||
{int(mode.dmPelsWidth), int(mode.dmPelsHeight)}))
|
||||
continue;
|
||||
// Keep orientation, placement and scaling while choosing the nearest
|
||||
// supported refresh rate to the existing desktop mode.
|
||||
mode.dmPosition = current.dmPosition;
|
||||
mode.dmDisplayOrientation = current.dmDisplayOrientation;
|
||||
mode.dmDisplayFixedOutput = current.dmDisplayFixedOutput;
|
||||
mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL |
|
||||
DM_DISPLAYFLAGS | DM_DISPLAYFREQUENCY |
|
||||
(current.dmFields & (DM_POSITION | DM_DISPLAYORIENTATION |
|
||||
DM_DISPLAYFIXEDOUTPUT));
|
||||
const auto key =
|
||||
std::make_pair(int(mode.dmPelsWidth), int(mode.dmPelsHeight));
|
||||
const auto found = modes.find(key);
|
||||
if (found == modes.end() ||
|
||||
std::abs(double(mode.dmDisplayFrequency) - current.dmDisplayFrequency) <
|
||||
std::abs(double(found->second.dmDisplayFrequency) -
|
||||
current.dmDisplayFrequency))
|
||||
modes[key] = mode;
|
||||
}
|
||||
std::vector<DEVMODEW> result;
|
||||
for (auto it = modes.rbegin(); it != modes.rend(); ++it) {
|
||||
result.push_back(it->second);
|
||||
if (result.size() == kMaxDisplayResolutionModes)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool ApplyWindowsResolutionMode(const wchar_t *device,
|
||||
DisplayResolution requested,
|
||||
const WindowsResolutionApi &api = {}) {
|
||||
if (!ValidDisplayResolution(requested))
|
||||
return false;
|
||||
DEVMODEW current{};
|
||||
current.dmSize = sizeof(current);
|
||||
if (!api.enumerate(device, ENUM_CURRENT_SETTINGS, ¤t))
|
||||
return false;
|
||||
for (auto mode : WindowsResolutionModes(device, current, api)) {
|
||||
if (mode.dmPelsWidth != requested.width ||
|
||||
mode.dmPelsHeight != requested.height)
|
||||
continue;
|
||||
if (current.dmPelsWidth == requested.width &&
|
||||
current.dmPelsHeight == requested.height)
|
||||
return true;
|
||||
if (api.change(device, &mode, nullptr, CDS_TEST, nullptr) !=
|
||||
DISP_CHANGE_SUCCESSFUL)
|
||||
return false;
|
||||
// Dynamic change only; never persist remote choices in the user's registry.
|
||||
if (api.change(device, &mode, nullptr, 0, nullptr) !=
|
||||
DISP_CHANGE_SUCCESSFUL)
|
||||
return false;
|
||||
DEVMODEW applied{};
|
||||
applied.dmSize = sizeof(applied);
|
||||
if (api.enumerate(device, ENUM_CURRENT_SETTINGS, &applied) &&
|
||||
applied.dmPelsWidth == requested.width &&
|
||||
applied.dmPelsHeight == requested.height)
|
||||
return true;
|
||||
api.change(device, ¤t, nullptr, 0, nullptr);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool ResolveWindowsResolutionDisplay(const DisplayInfo &display,
|
||||
std::wstring &device,
|
||||
std::string &key) {
|
||||
if (display.width <= 0 || display.height <= 0)
|
||||
return false;
|
||||
MONITORINFOEXW monitor{};
|
||||
monitor.cbSize = sizeof(monitor);
|
||||
if (display.handle &&
|
||||
GetMonitorInfoW(static_cast<HMONITOR>(display.handle), &monitor)) {
|
||||
if (GetDisplayLabel(monitor.szDevice) != display.name)
|
||||
return false;
|
||||
device = monitor.szDevice;
|
||||
} else {
|
||||
// A mode change can invalidate HMONITOR before capture publishes its new
|
||||
// geometry. Resolve the same native output; the interface key below still
|
||||
// protects change requests against a different monitor occupying the slot.
|
||||
for (DWORD index = 0; index < 256; ++index) {
|
||||
DISPLAY_DEVICEW adapter{};
|
||||
adapter.cb = sizeof(adapter);
|
||||
if (!EnumDisplayDevicesW(nullptr, index, &adapter, 0))
|
||||
break;
|
||||
if ((adapter.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) &&
|
||||
GetDisplayLabel(adapter.DeviceName) == display.name) {
|
||||
device = adapter.DeviceName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (device.empty())
|
||||
return false;
|
||||
}
|
||||
DISPLAY_DEVICEW target{};
|
||||
target.cb = sizeof(target);
|
||||
if (!EnumDisplayDevicesW(device.c_str(), 0, &target,
|
||||
EDD_GET_DEVICE_INTERFACE_NAME) ||
|
||||
!target.DeviceID[0])
|
||||
return false;
|
||||
key = WideToUtf8(device) + "|" + WideToUtf8(target.DeviceID);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::vector<DisplayResolutionInfo>
|
||||
QueryWindowsDisplayResolutions(const std::vector<DisplayInfo> &displays) {
|
||||
std::vector<DisplayResolutionInfo> result;
|
||||
for (int index = 0;
|
||||
index < int(displays.size()) && index < kMaxResolutionDisplays;
|
||||
++index) {
|
||||
DisplayResolutionInfo info;
|
||||
info.display_id = index;
|
||||
std::wstring device;
|
||||
if (!ResolveWindowsResolutionDisplay(displays[index], device,
|
||||
info.display_key))
|
||||
continue;
|
||||
DEVMODEW current{};
|
||||
current.dmSize = sizeof(current);
|
||||
if (!EnumDisplaySettingsW(device.c_str(), ENUM_CURRENT_SETTINGS, ¤t))
|
||||
continue;
|
||||
info.current = {int(current.dmPelsWidth), int(current.dmPelsHeight)};
|
||||
if (!ValidDisplayResolution(info.current))
|
||||
continue;
|
||||
for (const auto &mode : WindowsResolutionModes(device.c_str(), current))
|
||||
info.modes.push_back({int(mode.dmPelsWidth), int(mode.dmPelsHeight)});
|
||||
result.push_back(std::move(info));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool
|
||||
ApplyWindowsDisplayResolution(const std::vector<DisplayInfo> &displays,
|
||||
const DisplayResolutionChange &change) {
|
||||
if (change.display_id < 0 || change.display_id >= int(displays.size()))
|
||||
return false;
|
||||
std::wstring device;
|
||||
std::string key;
|
||||
if (!ResolveWindowsResolutionDisplay(displays[change.display_id], device,
|
||||
key) ||
|
||||
key != change.display_key)
|
||||
return false;
|
||||
return ApplyWindowsResolutionMode(device.c_str(), change.resolution);
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user