The Internet-Optionen frame becomes Bildschirm with a Monitor dropdown (Hauptmonitor or a connected monitor, kept by device path in [display] Monitor=). The borderless window and the render size use that monitor, falling back to the main monitor when it is not connected. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
161 lines
5.8 KiB
C++
161 lines
5.8 KiB
C++
// Preferred monitor (request of 2026-10-25): a setting in the graphics options
|
|
// picks the monitor the game starts on; with none set, or the preferred one
|
|
// not connected, the system's main monitor.
|
|
//
|
|
// The game window and its render size are decided when the display mode is
|
|
// set at startup (borderless.cpp, widescreen.cpp ApplyRenderSize): both ask
|
|
// GameMonitorRect. A change applies at the next start.
|
|
//
|
|
// Monitors are told apart by their device path (DisplayConfigGetDeviceInfo,
|
|
// target name: "\\?\DISPLAY#GSM5B7F#..."), which survives restarts, monitor
|
|
// order changes and other ports' changes, unlike the GDI names \\.\DISPLAYn.
|
|
//
|
|
// sacredK.ini:
|
|
// [display]
|
|
// Monitor=<device path> ; empty: the main monitor
|
|
|
|
#include "mods/monitor.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "re/config.h"
|
|
#include "re/log.h"
|
|
|
|
namespace mods::monitor {
|
|
namespace {
|
|
|
|
constexpr const char* kSection = "display";
|
|
constexpr const char* kKey = "Monitor";
|
|
|
|
std::string Utf8(const wchar_t* text) {
|
|
const int size = WideCharToMultiByte(CP_UTF8, 0, text, -1, nullptr, 0, nullptr, nullptr);
|
|
if (size <= 1) {
|
|
return {};
|
|
}
|
|
std::string result(static_cast<size_t>(size - 1), '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, text, -1, result.data(), size, nullptr, nullptr);
|
|
return result;
|
|
}
|
|
|
|
struct Target {
|
|
std::wstring gdiName; // \\.\DISPLAYn of the source showing it
|
|
std::string id;
|
|
std::wstring name;
|
|
};
|
|
|
|
// The active display paths: which monitor shows which GDI display.
|
|
std::vector<Target> Targets() {
|
|
UINT32 pathCount = 0;
|
|
UINT32 modeCount = 0;
|
|
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
|
|
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
|
|
LONG result;
|
|
do {
|
|
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount) != ERROR_SUCCESS) {
|
|
return {};
|
|
}
|
|
paths.resize(pathCount);
|
|
modes.resize(modeCount);
|
|
result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(), &modeCount, modes.data(),
|
|
nullptr);
|
|
} while (result == ERROR_INSUFFICIENT_BUFFER);
|
|
if (result != ERROR_SUCCESS) {
|
|
return {};
|
|
}
|
|
paths.resize(pathCount);
|
|
std::vector<Target> targets;
|
|
for (const DISPLAYCONFIG_PATH_INFO& path : paths) {
|
|
DISPLAYCONFIG_SOURCE_DEVICE_NAME source = {};
|
|
source.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
|
|
source.header.size = sizeof(source);
|
|
source.header.adapterId = path.sourceInfo.adapterId;
|
|
source.header.id = path.sourceInfo.id;
|
|
DISPLAYCONFIG_TARGET_DEVICE_NAME target = {};
|
|
target.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
|
|
target.header.size = sizeof(target);
|
|
target.header.adapterId = path.targetInfo.adapterId;
|
|
target.header.id = path.targetInfo.id;
|
|
if (DisplayConfigGetDeviceInfo(&source.header) != ERROR_SUCCESS ||
|
|
DisplayConfigGetDeviceInfo(&target.header) != ERROR_SUCCESS) {
|
|
continue;
|
|
}
|
|
targets.push_back({source.viewGdiDeviceName, Utf8(target.monitorDevicePath), target.monitorFriendlyDeviceName});
|
|
}
|
|
return targets;
|
|
}
|
|
|
|
BOOL CALLBACK AddMonitor(HMONITOR handle, HDC, LPRECT, LPARAM data) {
|
|
auto* gdi = reinterpret_cast<std::vector<MONITORINFOEXW>*>(data);
|
|
MONITORINFOEXW info = {};
|
|
info.cbSize = sizeof(info);
|
|
if (GetMonitorInfoW(handle, &info)) {
|
|
gdi->push_back(info);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::vector<Monitor> List() {
|
|
std::vector<MONITORINFOEXW> gdi;
|
|
EnumDisplayMonitors(nullptr, nullptr, &AddMonitor, reinterpret_cast<LPARAM>(&gdi));
|
|
const std::vector<Target> targets = Targets();
|
|
std::vector<Monitor> monitors;
|
|
for (const MONITORINFOEXW& info : gdi) {
|
|
Monitor monitor{"", L"", info.rcMonitor, (info.dwFlags & MONITORINFOF_PRIMARY) != 0};
|
|
for (const Target& target : targets) {
|
|
if (target.gdiName == info.szDevice) { // mirrored displays: the first monitor
|
|
monitor.id = target.id;
|
|
monitor.name = target.name;
|
|
break;
|
|
}
|
|
}
|
|
if (monitor.id.empty()) {
|
|
monitor.id = Utf8(info.szDevice); // no display config (remote desktop, ...)
|
|
}
|
|
if (monitor.name.empty()) {
|
|
monitor.name = L"Monitor " + std::to_wstring(monitors.size() + 1);
|
|
}
|
|
monitors.push_back(monitor);
|
|
}
|
|
std::stable_sort(monitors.begin(), monitors.end(),
|
|
[](const Monitor& a, const Monitor& b) { return a.primary && !b.primary; });
|
|
return monitors;
|
|
}
|
|
|
|
std::string PreferredId() {
|
|
return re::ConfigString(kSection, kKey, "");
|
|
}
|
|
|
|
void SetPreferredId(const std::string& id) {
|
|
re::SetConfigString(kSection, kKey, id.c_str());
|
|
}
|
|
|
|
RECT GameMonitorRect() {
|
|
const std::vector<Monitor> monitors = List();
|
|
const std::string preferred = PreferredId();
|
|
const Monitor* chosen = nullptr;
|
|
for (const Monitor& monitor : monitors) {
|
|
if ((!preferred.empty() && monitor.id == preferred) || (!chosen && monitor.primary)) {
|
|
chosen = &monitor;
|
|
}
|
|
}
|
|
if (!chosen) {
|
|
// No monitor reported: the main monitor's rectangle as Windows has it.
|
|
MONITORINFO info = {sizeof(info)};
|
|
GetMonitorInfoW(MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY), &info);
|
|
return info.rcMonitor;
|
|
}
|
|
const bool found = !preferred.empty() && chosen->id == preferred;
|
|
static bool logged = false;
|
|
if (!logged) {
|
|
logged = true;
|
|
re::Log("monitor: %ls %ldx%ld at (%ld,%ld)%s", chosen->name.c_str(), chosen->rect.right - chosen->rect.left,
|
|
chosen->rect.bottom - chosen->rect.top, chosen->rect.left, chosen->rect.top,
|
|
found ? "" : preferred.empty() ? ", main monitor" : ", preferred monitor not connected: main monitor");
|
|
}
|
|
return chosen->rect;
|
|
}
|
|
|
|
} // namespace mods::monitor
|