Preferred monitor: chosen in Optionen > Allgemein, used at the next start

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>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-25 16:06:20 +01:00
1 parent c7d54a6cfd
commit 9bc557cd95
10 files changed
+407 -14

No files matched your search

+160
View File
@@ -0,0 +1,160 @@
// 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