[FancyZones]Monitor identification (#19077)

* moved monitors identifying

* changed device id

* get wmi info

* convert old data

* save/load applied layouts

* changed monitor identification

* id comparison

* save/load app zone history

* moved com and security init

* update ids in editor

* lib fix

* updated tests

* changed comparison

* tests

* updated id comparison

* updated log

* moved definition

* spell check

* resolve conflicts

* refactoring

* update serial numbers if possible
This commit is contained in:
Seraphima Zykova
2022-07-01 17:29:02 +02:00
committed by GitHub
parent 3cb0638c7e
commit 35bb4280d0
30 changed files with 1084 additions and 408 deletions

View File

@@ -1,49 +1,280 @@
#include "pch.h"
#include "MonitorUtils.h"
#include <WbemCli.h>
#include <comutil.h>
#include <FancyZonesLib/WindowUtils.h>
#include <FancyZonesLib/util.h>
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
namespace MonitorUtils
{
constexpr int CUSTOM_POSITIONING_LEFT_TOP_PADDING = 16;
inline int RectWidth(const RECT& rect)
namespace WMI
{
return rect.right - rect.left;
}
inline int RectHeight(const RECT& rect)
{
return rect.bottom - rect.top;
}
RECT FitOnScreen(const RECT& windowRect, const RECT& originMonitorRect, const RECT& destMonitorRect)
{
// New window position on active monitor. If window fits the screen, this will be final position.
int left = destMonitorRect.left + (windowRect.left - originMonitorRect.left);
int top = destMonitorRect.top + (windowRect.top - originMonitorRect.top);
int W = RectWidth(windowRect);
int H = RectHeight(windowRect);
if ((left < destMonitorRect.left) || (left + W > destMonitorRect.right))
FancyZonesDataTypes::DeviceId SplitWMIDeviceId(const std::wstring& str) noexcept
{
// Set left window border to left border of screen (add padding). Resize window width if needed.
left = destMonitorRect.left + CUSTOM_POSITIONING_LEFT_TOP_PADDING;
W = min(W, RectWidth(destMonitorRect) - CUSTOM_POSITIONING_LEFT_TOP_PADDING);
}
if ((top < destMonitorRect.top) || (top + H > destMonitorRect.bottom))
{
// Set top window border to top border of screen (add padding). Resize window height if needed.
top = destMonitorRect.top + CUSTOM_POSITIONING_LEFT_TOP_PADDING;
H = min(H, RectHeight(destMonitorRect) - CUSTOM_POSITIONING_LEFT_TOP_PADDING);
// format: DISPLAY\{device id}\{instance id}
// example: DISPLAY\GSM0001\4&125707d6&0&UID28741_0
// output: { GSM0001, 4&125707d6&0&UID28741 }
size_t nameStartPos = str.find_first_of('\\');
size_t uidStartPos = str.find_last_of('\\');
size_t uidEndPos = str.find_last_of('_');
if (nameStartPos == std::string::npos || uidStartPos == std::string::npos || uidEndPos == std::string::npos)
{
return { .id = str };
}
return { .id = str.substr(nameStartPos + 1, uidStartPos - nameStartPos - 1), .instanceId = str.substr(uidStartPos + 1, uidEndPos - uidStartPos - 1) };
}
return { .left = left,
.top = top,
.right = left + W,
.bottom = top + H };
std::wstring GetWMIProp(IWbemClassObject* wbemClassObj, std::wstring_view prop)
{
if (!wbemClassObj)
{
return {};
}
VARIANT vtProp{};
// Get the value of the Name property
auto hres = wbemClassObj->Get(prop.data(), 0, &vtProp, 0, 0);
if (FAILED(hres))
{
Logger::error(L"Get {} Error code = {} ", prop, get_last_error_or_default(hres));
return {};
}
std::wstring result{};
switch (vtProp.vt)
{
case VT_BSTR: //BSTR
{
result = vtProp.bstrVal;
}
break;
case VT_ARRAY: // parray
case 8195: // also parray
{
std::u32string str(static_cast<const char32_t*>(vtProp.parray->pvData));
std::wstring wstr;
for (const char32_t& c : str)
{
wstr += (wchar_t)c;
}
result = wstr;
}
break;
default:
break;
}
VariantClear(&vtProp);
return result;
}
std::vector<FancyZonesDataTypes::MonitorId> GetHardwareMonitorIds()
{
HRESULT hres;
// Obtain the initial locator to Windows Management
// on a particular host computer.
IWbemLocator* pLocator = 0;
hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLocator);
if (FAILED(hres))
{
Logger::error(L"Failed to create IWbemLocator object. {}", get_last_error_or_default(hres));
return {};
}
IWbemServices* pServices = 0;
hres = pLocator->ConnectServer(_bstr_t(L"ROOT\\WMI"), NULL, NULL, 0, NULL, 0, 0, &pServices);
if (FAILED(hres))
{
Logger::error(L"Could not connect WMI server. {}", get_last_error_or_default(hres));
pLocator->Release();
return {};
}
// Set the IWbemServices proxy so that impersonation
// of the user (client) occurs.
hres = CoSetProxyBlanket(pServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
if (FAILED(hres))
{
Logger::error(L"Could not set proxy blanket. {}", get_last_error_or_default(hres));
pServices->Release();
pLocator->Release();
return {};
}
// Use the IWbemServices pointer to make requests of WMI.
// Make requests here:
IEnumWbemClassObject* pEnumerator = NULL;
hres = pServices->ExecQuery(bstr_t("WQL"), bstr_t("SELECT * FROM WmiMonitorID"), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
if (FAILED(hres))
{
Logger::error(L"Query for monitors failed. {}", get_last_error_or_default(hres));
pServices->Release();
pLocator->Release();
return {};
}
IWbemClassObject* pClassObject;
ULONG uReturn = 0;
std::vector<FancyZonesDataTypes::MonitorId> result{};
while (pEnumerator)
{
hres = pEnumerator->Next(WBEM_INFINITE, 1, &pClassObject, &uReturn);
if (0 == uReturn)
{
break;
}
LPSAFEARRAY pFieldArray = NULL;
hres = pClassObject->GetNames(NULL, WBEM_FLAG_ALWAYS, NULL, &pFieldArray);
if (FAILED(hres))
{
Logger::error(L"Failed to get field names. {}", get_last_error_or_default(hres));
break;
}
auto name = GetWMIProp(pClassObject, L"InstanceName");
FancyZonesDataTypes::MonitorId data{};
data.deviceId = SplitWMIDeviceId(name);
data.serialNumber = GetWMIProp(pClassObject, L"SerialNumberID");
Logger::info(L"InstanceName: {}", name);
Logger::info(L"Serial number: {}", data.serialNumber);
result.emplace_back(std::move(data));
pClassObject->Release();
pClassObject = NULL;
}
pServices->Release();
pLocator->Release();
pEnumerator->Release();
return result;
}
}
namespace Display
{
FancyZonesDataTypes::DeviceId SplitDisplayDeviceId(const std::wstring& str) noexcept
{
// format: \\?\DISPLAY#{device id}#{instance id}#{some other id}
// example: \\?\DISPLAY#GSM1388#4&125707d6&0&UID8388688#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// output: { GSM1388, 4&125707d6&0&UID8388688 }
size_t nameStartPos = str.find_first_of('#');
size_t uidStartPos = str.find('#', nameStartPos + 1);
size_t uidEndPos = str.find('#', uidStartPos + 1);
if (nameStartPos == std::string::npos || uidStartPos == std::string::npos || uidEndPos == std::string::npos)
{
return { str, L"" };
}
return { .id = str.substr(nameStartPos + 1, uidStartPos - nameStartPos - 1), .instanceId = str.substr(uidStartPos + 1, uidEndPos - uidStartPos - 1) };
}
FancyZonesDataTypes::DeviceId ConvertObsoleteDeviceId(const std::wstring& str) noexcept
{
// format: {device id}#{instance id}
// example: GSM1388#4&125707d6&0&UID8388688
// output: { GSM1388, 4&125707d6&0&UID8388688 }
size_t dividerPos = str.find_first_of('#');
if (dividerPos == std::string::npos)
{
return { str, L"" };
}
return { .id = str.substr(0, dividerPos), .instanceId = str.substr(dividerPos + 1) };
}
std::vector<FancyZonesDataTypes::MonitorId> GetDisplays()
{
auto allMonitors = FancyZonesUtils::GetAllMonitorInfo<&MONITORINFOEX::rcWork>();
std::unordered_map<std::wstring, DWORD> displayDeviceIdxMap;
std::vector<FancyZonesDataTypes::MonitorId> result{};
for (auto& monitorData : allMonitors)
{
auto monitorInfo = monitorData.second;
DISPLAY_DEVICE displayDevice{ .cb = sizeof(displayDevice) };
std::wstring deviceId;
auto enumRes = EnumDisplayDevicesW(monitorInfo.szDevice, displayDeviceIdxMap[monitorInfo.szDevice], &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME);
if (!enumRes)
{
Logger::error(L"EnumDisplayDevicesW error: {}", get_last_error_or_default(GetLastError()));
continue;
}
Logger::info(L"Display: {}", displayDevice.DeviceID);
result.push_back({ .monitor = monitorData.first, .deviceId = SplitDisplayDeviceId(displayDevice.DeviceID) });
}
return result;
}
}
namespace
{
inline int RectWidth(const RECT& rect)
{
return rect.right - rect.left;
}
inline int RectHeight(const RECT& rect)
{
return rect.bottom - rect.top;
}
RECT FitOnScreen(const RECT& windowRect, const RECT& originMonitorRect, const RECT& destMonitorRect)
{
// New window position on active monitor. If window fits the screen, this will be final position.
int left = destMonitorRect.left + (windowRect.left - originMonitorRect.left);
int top = destMonitorRect.top + (windowRect.top - originMonitorRect.top);
int W = RectWidth(windowRect);
int H = RectHeight(windowRect);
if ((left < destMonitorRect.left) || (left + W > destMonitorRect.right))
{
// Set left window border to left border of screen (add padding). Resize window width if needed.
left = destMonitorRect.left + CUSTOM_POSITIONING_LEFT_TOP_PADDING;
W = min(W, RectWidth(destMonitorRect) - CUSTOM_POSITIONING_LEFT_TOP_PADDING);
}
if ((top < destMonitorRect.top) || (top + H > destMonitorRect.bottom))
{
// Set top window border to top border of screen (add padding). Resize window height if needed.
top = destMonitorRect.top + CUSTOM_POSITIONING_LEFT_TOP_PADDING;
H = min(H, RectHeight(destMonitorRect) - CUSTOM_POSITIONING_LEFT_TOP_PADDING);
}
return { .left = left,
.top = top,
.right = left + W,
.bottom = top + H };
}
}
void OpenWindowOnActiveMonitor(HWND window, HMONITOR monitor) noexcept
{
// By default Windows opens new window on primary monitor.
@@ -73,4 +304,25 @@ namespace MonitorUtils
}
}
}
std::vector<FancyZonesDataTypes::MonitorId> IdentifyMonitors() noexcept
{
Logger::info(L"Identifying monitors");
auto displays = Display::GetDisplays();
auto monitors = WMI::GetHardwareMonitorIds();
for (const auto& monitor : monitors)
{
for (auto& display : displays)
{
if (monitor.deviceId.id == display.deviceId.id)
{
display.serialNumber = monitor.serialNumber;
}
}
}
return displays;
}
}