From ee3fa58bfa56b315821ed200f060cc659451268b Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:26:38 -0700 Subject: [PATCH] graphics: option to change primary monitor before launching game (#608) ## Description of change Adds `-mainmonitor` option which changes the monitor layout before launching the game. This is much more reliable than the old `-monitor` option which operated at DX9 level. This works for TDJ/UFC subscreens, for example. Rename `-monitor` option to `-dx9mainadapter` to discourage use. `-monitor` will continue to work, of course. Changing of primary monitor happens first before any orientation changes / refresh rate changes, which means those options will result in the logically correct configuration. Create a new option category for `Monitor` (rotate, refresh rate, etc). Add a monitor selection widget for `-mainmonitor` option in the configurator. Improve how we log names of monitors in the log during boot - should be less of "Generic PnP Monitor" after this. --- src/spice2x/cfg/option.h | 1 + src/spice2x/hooks/graphics/graphics.cpp | 253 ++++++++++++++++++------ src/spice2x/hooks/graphics/graphics.h | 4 +- src/spice2x/launcher/launcher.cpp | 18 +- src/spice2x/launcher/options.cpp | 28 ++- src/spice2x/launcher/options.h | 3 +- src/spice2x/overlay/windows/config.cpp | 25 +++ src/spice2x/util/sysutils.cpp | 108 ++++++++++ src/spice2x/util/sysutils.h | 10 +- 9 files changed, 377 insertions(+), 73 deletions(-) diff --git a/src/spice2x/cfg/option.h b/src/spice2x/cfg/option.h index 2c7d2bd..89ad050 100644 --- a/src/spice2x/cfg/option.h +++ b/src/spice2x/cfg/option.h @@ -20,6 +20,7 @@ enum class OptionPickerType { CpuAffinity, FilePath, DirectoryPath, + Monitor, }; struct OptionDefinition { diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index 58eb5fd..9796b7c 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -58,7 +58,6 @@ bool GRAPHICS_CAPTURE_CURSOR = false; bool GRAPHICS_LOG_HRESULT = false; bool GRAPHICS_SDVX_FORCE_720 = false; bool GRAPHICS_SHOW_CURSOR = false; -graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL; bool GRAPHICS_WINDOWED = false; std::vector GRAPHICS_WINDOWS; UINT GRAPHICS_FORCE_REFRESH = 0; @@ -1085,84 +1084,224 @@ static std::string get_dmdo_string(DWORD dmdo) { } } -void update_monitor_on_boot() { +void change_primary_monitor(const std::string &monitor_name) { + log_misc("graphics", "try changing primary monitor to {}...", monitor_name); + + // for WinXP, since these are Vista+ or 7+ APIs + const auto user32 = LoadLibraryA("user32.dll"); + if (!user32) { + log_warning("graphics", "can't find user32.dll???"); + return; + } + const auto GetDisplayConfigBufferSizes_addr = + reinterpret_cast( + GetProcAddress(user32, "GetDisplayConfigBufferSizes")); + const auto QueryDisplayConfig_addr = + reinterpret_cast( + GetProcAddress(user32, "QueryDisplayConfig")); + const auto DisplayConfigGetDeviceInfo_addr = + reinterpret_cast( + GetProcAddress(user32, "DisplayConfigGetDeviceInfo")); + const auto SetDisplayConfig_addr = + reinterpret_cast( + GetProcAddress(user32, "SetDisplayConfig")); + if (GetDisplayConfigBufferSizes_addr == nullptr || QueryDisplayConfig_addr == nullptr || + DisplayConfigGetDeviceInfo_addr == nullptr || SetDisplayConfig_addr == nullptr) { + log_warning("graphics", "cannot change primary monitor, OS does not support required APIs)"); + return; + } + + UINT32 path_count = 0; + UINT32 mode_count = 0; + std::vector paths; + std::vector modes; + bool succeeded = false; + + // in a retry loop, try to query for display config + // retry loop is needed because it can fail with ERROR_INSUFFICIENT_BUFFER + for (int attempt = 0; attempt < 5; ++attempt) { + auto status = GetDisplayConfigBufferSizes_addr(QDC_DATABASE_CURRENT, &path_count, &mode_count); + if (status != ERROR_SUCCESS) { + log_warning("graphics", "GetDisplayConfigBufferSizes failed: {}", status); + return; + } + + paths.resize(path_count); + modes.resize(mode_count); + DISPLAYCONFIG_TOPOLOGY_ID topology_id = DISPLAYCONFIG_TOPOLOGY_INTERNAL; + status = QueryDisplayConfig_addr( + QDC_DATABASE_CURRENT, + &path_count, + paths.data(), + &mode_count, + modes.data(), + &topology_id); + + if (status == ERROR_SUCCESS) { + // Shrink to actual returned counts + paths.resize(path_count); + modes.resize(mode_count); + succeeded = true; + break; + } + + if (status != ERROR_INSUFFICIENT_BUFFER) { + log_warning("graphics", "QueryDisplayConfig failed: {}", status); + return; + } + + Sleep(500); + } + if (!succeeded) { + log_warning("graphics", "QueryDisplayConfig failed after reaching max retries"); + return; + } + + LONG x = 0; + LONG y = 0; + bool found = false; + + // find the new main monitor + for (auto& mode : modes) { + if (mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) { + continue; + } + + DISPLAYCONFIG_SOURCE_DEVICE_NAME name = {}; + name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + name.header.size = sizeof(name); + name.header.adapterId = mode.adapterId; + name.header.id = mode.id; + if (DisplayConfigGetDeviceInfo_addr(&name.header) != ERROR_SUCCESS) { + continue; + } + + const auto device_name = std::string(ws2s(name.viewGdiDeviceName)); + if (monitor_name == device_name) { + x = mode.sourceMode.position.x; + y = mode.sourceMode.position.y; + found = true; + log_info( + "graphics", + "new main monitor target found: {}, old position: ({}, {})", + monitor_name, + x, y); + break; + } + } + if (!found) { + log_fatal( + "graphics", + "new main monitor target not found, check -mainmonitor option: {}", + monitor_name); + return; + } + + // update monitor positions so that the new monitor is at (0, 0) + for (auto& mode : modes) { + if (mode.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) { + mode.sourceMode.position.x -= x; + mode.sourceMode.position.y -= y; + } + } + + // finally, commit the new display config + const auto status = SetDisplayConfig_addr( + path_count, + paths.data(), + mode_count, + modes.data(), + SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG); + + if (status != ERROR_SUCCESS) { + log_fatal("graphics", "SetDisplayConfig failed, check -mainmonitor option: {}", status); + } + + // a little extra time for windows to settle and redraw things + Sleep(2000); +} + +void update_monitor_on_boot(std::optional target_orientation, UINT target_refresh_rate) { // note: all of this is only being done for the primary motnior // get current settings DEVMODEA dm = {}; dm.dmSize = sizeof(dm); if (!EnumDisplaySettingsExA(NULL, ENUM_CURRENT_SETTINGS, &dm, 0)) { - log_info("graphics", "EnumDisplaySettingsExa failed {}", get_last_error_string()); + log_warning("graphics", "EnumDisplaySettingsExa failed {}", get_last_error_string()); return; } bool needs_update = false; - // convert orientation values, and figure out if resolution needs to be swapped - bool rotate_resolution = false; - DWORD orientation = DMDO_DEFAULT; - switch (GRAPHICS_ADJUST_ORIENTATION) { - case ORIENTATION_CW: - orientation = DMDO_90; - if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) { - rotate_resolution = true; - } - break; - case ORIENTATION_CCW: - orientation = DMDO_270; - if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) { - rotate_resolution = true; - } - break; - case ORIENTATION_FLIPPED: - orientation = DMDO_180; - if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) { - rotate_resolution = true; - } - break; - default: - orientation = DMDO_DEFAULT; - if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) { - rotate_resolution = true; - } - break; - } - - // update orientation (and resolution if it must be swapped) - if (dm.dmDisplayOrientation != orientation) { - log_misc("graphics", - "current orientation {} => desired orientation {}", - get_dmdo_string(dm.dmDisplayOrientation), get_dmdo_string(orientation)); - - const DWORD originalWidth = dm.dmPelsWidth; - const DWORD originalHeight = dm.dmPelsHeight; - - // change orientation - dm.dmDisplayOrientation = orientation; - dm.dmFields |= DM_DISPLAYORIENTATION; - - // rotate resolution - if (rotate_resolution) { - dm.dmPelsWidth = originalHeight; - dm.dmPelsHeight = originalWidth; - dm.dmFields |= DM_PELSHEIGHT | DM_PELSWIDTH; + if (target_orientation.has_value()) { + // convert orientation values, and figure out if resolution needs to be swapped + bool rotate_resolution = false; + DWORD orientation = DMDO_DEFAULT; + switch (target_orientation.value()) { + case ORIENTATION_CW: + orientation = DMDO_90; + if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) { + rotate_resolution = true; + } + break; + case ORIENTATION_CCW: + orientation = DMDO_270; + if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) { + rotate_resolution = true; + } + break; + case ORIENTATION_FLIPPED: + orientation = DMDO_180; + if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) { + rotate_resolution = true; + } + break; + default: + orientation = DMDO_DEFAULT; + if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) { + rotate_resolution = true; + } + break; } - needs_update = true; + // update orientation (and resolution if it must be swapped) + if (dm.dmDisplayOrientation != orientation) { + log_misc("graphics", + "current orientation {} => desired orientation {}", + get_dmdo_string(dm.dmDisplayOrientation), get_dmdo_string(orientation)); + + const DWORD originalWidth = dm.dmPelsWidth; + const DWORD originalHeight = dm.dmPelsHeight; + + // change orientation + dm.dmDisplayOrientation = orientation; + dm.dmFields |= DM_DISPLAYORIENTATION; + + // rotate resolution + if (rotate_resolution) { + dm.dmPelsWidth = originalHeight; + dm.dmPelsHeight = originalWidth; + dm.dmFields |= DM_PELSHEIGHT | DM_PELSWIDTH; + } + + needs_update = true; + } } // update refresh rate - if (GRAPHICS_FORCE_REFRESH > 0) { + if (target_refresh_rate > 0) { log_misc("graphics", "current refresh rate {} => desired refresh rate {}", - dm.dmDisplayFrequency, GRAPHICS_FORCE_REFRESH); + dm.dmDisplayFrequency, target_refresh_rate); - dm.dmDisplayFrequency = GRAPHICS_FORCE_REFRESH; + dm.dmDisplayFrequency = target_refresh_rate; dm.dmFields |= DM_DISPLAYFREQUENCY; needs_update = true; } if (!needs_update) { + // nothing to do return; } @@ -1170,11 +1309,11 @@ void update_monitor_on_boot() { if (result != DISP_CHANGE_SUCCESSFUL) { log_fatal( "graphics", - "failed to update display settings ({}px x {}px @ {}Hz): {}", + "failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options", dm.dmPelsWidth, dm.dmPelsHeight, dm.dmDisplayFrequency, - get_last_error_string()); + result); } else { log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)", dm.dmPelsWidth, diff --git a/src/spice2x/hooks/graphics/graphics.h b/src/spice2x/hooks/graphics/graphics.h index 882fe74..c09343c 100644 --- a/src/spice2x/hooks/graphics/graphics.h +++ b/src/spice2x/hooks/graphics/graphics.h @@ -30,7 +30,6 @@ extern bool GRAPHICS_LOG_HRESULT; extern bool GRAPHICS_SDVX_FORCE_720; extern bool GRAPHICS_SHOW_CURSOR; extern bool GRAPHICS_WINDOWED; -extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION; extern std::vector GRAPHICS_WINDOWS; extern UINT GRAPHICS_FORCE_REFRESH; extern std::optional GRAPHICS_FORCE_REFRESH_SUB; @@ -109,4 +108,5 @@ bool graphics_window_resize_breaks_game(); bool graphics_window_move_and_resize_breaks_game(); void graphics_load_windowed_subscreen_parameters(); -void update_monitor_on_boot(); \ No newline at end of file +void change_primary_monitor(const std::string &monitor_name); +void update_monitor_on_boot(std::optional target_orientation, UINT target_refresh_rate); \ No newline at end of file diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index fd3dbe7..026b7f3 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -351,9 +351,9 @@ int main_implementation(int argc, char *argv[]) { GRAPHICS_FORCE_SINGLE_ADAPTER = true; GRAPHICS_PREVENT_SECONDARY_WINDOW = true; } - if (options[launcher::Options::DisplayAdapter].is_active() && - options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) { - D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32(); + if (options[launcher::Options::DX9DisplayAdapter].is_active() && + options[launcher::Options::DX9DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) { + D3D9_ADAPTER = options[launcher::Options::DX9DisplayAdapter].value_uint32(); // when we fix up adapter numbers, we only fix the first adapter, and not any subsequent // adapters, so we can't deal with multi-monitor games @@ -374,12 +374,15 @@ int main_implementation(int argc, char *argv[]) { if (options[launcher::Options::AllowEA3Verbose].value_bool()) { avs::ea3::EA3_DEBUG_VERBOSE = true; } + + std::optional monitor_orientation; if (options[launcher::Options::spice2x_AutoOrientation].is_active()) { - GRAPHICS_ADJUST_ORIENTATION = + monitor_orientation = (graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32(); } else if (options[launcher::Options::AdjustOrientation].value_bool()) { - GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_CW; + monitor_orientation = ORIENTATION_CW; } + if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) { D3D9_DEVICE_HOOK_DISABLE = true; // touch emulation gets disabled, might as well turn these on @@ -2134,7 +2137,10 @@ int main_implementation(int argc, char *argv[]) { } // fix up monitor - update_monitor_on_boot(); + if (options[launcher::Options::PrimaryMonitor].is_active()) { + change_primary_monitor(options[launcher::Options::PrimaryMonitor].value_text()); + } + update_monitor_on_boot(monitor_orientation, GRAPHICS_FORCE_REFRESH); // initialize raw input RI_MGR = std::make_unique(); diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index ba3e214..52cdd68 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -23,6 +23,7 @@ static const std::vector CATEGORY_ORDER_BASIC = { "Game Options", "Common", "Network", + "Monitor", "Graphics (Common)", "Graphics (Full Screen)", "Graphics (Windowed)", @@ -206,9 +207,24 @@ static const std::vector OPTION_DEFINITIONS = { .category = "Common", }, { - .title = "Monitor", + // PrimaryMonitor + .title = "Change Main Monitor", + .name = "mainmonitor", + .desc = "Changes the primary monitor before launching the game. It will be restored on exit.\n\n" + "This may fail to work properly on hybrid laptops with iGPU + dGPU.", + .type = OptionType::Text, + .setting_name = "\\\\.\\DISPLAY2", + .category = "Monitor", + .picker = OptionPickerType::Monitor, + }, + { + // DX9DisplayAdapter + .title = "DX9 Primary Display Adapter Override", .name = "monitor", - .desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n" + .display_name = "dx9mainadapter", + .aliases = "dx9mainadapter", + .desc = "Prefer to use Change Main Monitor option instead of this one.\n\n" + "Sets the display that the game will be opened in, for multiple monitors.\n\n" "0 is the primary monitor, 1 is the second monitor, and so on.\n\n" "Not all games will respect this. Not recommended for multi-monitor games like Lightning Model / Valkyrie Model modes. " "Disable Full Screen Optimizations for best results.", @@ -216,19 +232,19 @@ static const std::vector OPTION_DEFINITIONS = { .category = "Graphics (Full Screen)", }, { - .title = "Only Use One Monitor", + .title = "Only Use Main Monitor For Full Screen", .name = "graphics-force-single-adapter", .desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n" "May cause unstable framerate and desyncs, especially if monitors have different refresh rates!", .type = OptionType::Bool, - .category = "Graphics (Full Screen)", + .category = "Monitor", }, { .title = "Monitor Refresh Rate", .name = "graphics-force-refresh", .desc = "Change the refresh rate for the primary monitor before launching the game. It will be restored on exit.", .type = OptionType::Integer, - .category = "Graphics (Common)", + .category = "Monitor", }, { // FullscreenResolution @@ -1851,7 +1867,7 @@ static const std::vector OPTION_DEFINITIONS = { .aliases= "autoorientation", .desc = "Change the orientation of the primary display before launching the game. It will be restored on exit", .type = OptionType::Enum, - .category = "Graphics (Common)", + .category = "Monitor", // match graphics_orientation enum .elements = { {"0", "Portrait"}, diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index 1441d01..32684b9 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -26,7 +26,8 @@ namespace launcher { ExecuteScript, CaptureCursor, ShowCursor, - DisplayAdapter, + PrimaryMonitor, + DX9DisplayAdapter, GraphicsForceSingleAdapter, GraphicsForceRefresh, FullscreenResolution, diff --git a/src/spice2x/overlay/windows/config.cpp b/src/spice2x/overlay/windows/config.cpp index 3aa4f43..c23abf6 100644 --- a/src/spice2x/overlay/windows/config.cpp +++ b/src/spice2x/overlay/windows/config.cpp @@ -34,6 +34,7 @@ #include "util/resutils.h" #include "util/scope_guard.h" #include "util/time.h" +#include "util/sysutils.h" #include "util/utils.h" #ifdef min @@ -4237,6 +4238,8 @@ namespace overlay::windows { return "File Picker"; case OptionPickerType::DirectoryPath: return "Folder Picker"; + case OptionPickerType::Monitor: + return "Monitor Picker"; default: return "Unknown Picker"; }; @@ -4467,6 +4470,28 @@ namespace overlay::windows { ImGui::EndDisabled(); } + } else if (definition.picker == OptionPickerType::Monitor) { + const auto &monitors = sysutils::enumerate_monitors(); + if (monitors.empty()) { + ImGui::TextUnformatted("Failed to fetch monitors. Requires Win7+"); + } else { + ImGui::TextUnformatted("Pick from monitors:"); + ImGui::SetNextItemWidth(300.f); + if (ImGui::BeginListBox("##monitors")) { + for (const auto &monitor : monitors) { + const bool is_selected = option.value == monitor.display_name; + auto friendly = wchar_to_u8(monitor.friendly_name.c_str()); + if (ImGui::Selectable( + fmt::format("{} ({})", monitor.display_name, friendly).c_str(), + is_selected)) { + option.value = monitor.display_name; + } + } + ImGui::EndListBox(); + } + } + + } else { ImGui::TextUnformatted("No picker available for this option. How did you get here?"); } diff --git a/src/spice2x/util/sysutils.cpp b/src/spice2x/util/sysutils.cpp index 4d3ff7f..4466e43 100644 --- a/src/spice2x/util/sysutils.cpp +++ b/src/spice2x/util/sysutils.cpp @@ -1,12 +1,15 @@ #include "sysutils.h" #include +#include + #define WIN32_NO_STATUS #include #undef WIN32_NO_STATUS #include "util/libutils.h" #include "util/logging.h" +#include "util/utils.h" #if 0 #define log_dbug(module, format_str, ...) logger::push( \ @@ -222,6 +225,21 @@ namespace sysutils { log_dbug("gpuinfo", "{} {} flags : 0x{:x}", prefix.c_str(), index, adapter->StateFlags); if (!is_monitor) { + // get extended info for better friendly name of monitors + const auto &monitors = enumerate_monitors(); + for (const auto& monitor : monitors) { + if (monitor.display_name == adapter->DeviceName) { + const auto friendly = ws2s(monitor.friendly_name.c_str()); + log_misc( + "gpuinfo", "{} {} friendly name : {}", + prefix.c_str(), + index, + friendly); + break; + } + } + + // resolution, refresh rate DEVMODEA devmode = {}; devmode.dmSize = sizeof(devmode); if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) { @@ -236,11 +254,13 @@ namespace sysutils { log_misc("gpuinfo", "EnumDisplaySettingsA failed"); } + // primary? log_misc( "gpuinfo", "{} {} is primary : {}", prefix.c_str(), index, (adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no"); + } } @@ -298,4 +318,92 @@ namespace sysutils { return; } } + + static std::vector enumerate_monitors_internal() { + // for WinXP, since these are Vista+ or 7+ APIs + const auto user32 = LoadLibraryA("user32.dll"); + if (!user32) { + log_warning("graphics", "can't find user32.dll???"); + return {}; + } + const auto GetDisplayConfigBufferSizes_addr = + reinterpret_cast( + GetProcAddress(user32, "GetDisplayConfigBufferSizes")); + const auto QueryDisplayConfig_addr = + reinterpret_cast( + GetProcAddress(user32, "QueryDisplayConfig")); + const auto DisplayConfigGetDeviceInfo_addr = + reinterpret_cast( + GetProcAddress(user32, "DisplayConfigGetDeviceInfo")); + if (GetDisplayConfigBufferSizes_addr == nullptr || + QueryDisplayConfig_addr == nullptr || + DisplayConfigGetDeviceInfo_addr == nullptr) { + log_warning("sysutils", "OS does not support display config APIs"); + return {}; + } + + UINT32 path_count = 0; + UINT32 mode_count = 0; + auto status = GetDisplayConfigBufferSizes_addr(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count); + if (status != ERROR_SUCCESS) { + log_warning("sysutils", "GetDisplayConfigBufferSizes failed: {}", status); + return {}; + } + + std::vector paths(path_count); + std::vector modes(mode_count); + status = QueryDisplayConfig_addr( + QDC_ONLY_ACTIVE_PATHS, + &path_count, + paths.data(), + &mode_count, + modes.data(), + nullptr); + if (status != ERROR_SUCCESS) { + log_warning("sysutils", "QueryDisplayConfig failed: {}", status); + return {}; + } + + // shrink to actual returned items + paths.resize(path_count); + modes.resize(mode_count); + + std::vector result; + for (const auto& path : paths) { + MonitorEntry entry; + + // device ID (\\.\DISPLAYn) + DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {}; + source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + source_name.header.size = sizeof(source_name); + source_name.header.adapterId = path.sourceInfo.adapterId; + source_name.header.id = path.sourceInfo.id; + if (DisplayConfigGetDeviceInfo_addr(&source_name.header) != ERROR_SUCCESS) { + continue; + } + entry.display_name = ws2s(std::wstring(source_name.viewGdiDeviceName)); + + // friendly name + DISPLAYCONFIG_TARGET_DEVICE_NAME target_name = {}; + target_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + target_name.header.size = sizeof(target_name); + target_name.header.adapterId = path.targetInfo.adapterId; + target_name.header.id = path.targetInfo.id; + if (DisplayConfigGetDeviceInfo_addr(&target_name.header) == ERROR_SUCCESS) { + entry.friendly_name = target_name.monitorFriendlyDeviceName; + } else { + entry.friendly_name = L"(unknown)"; + } + + // done + result.emplace_back(entry); + } + + return result; + } + + const std::vector& enumerate_monitors() { + static const std::vector monitors = enumerate_monitors_internal(); + return monitors; + } } diff --git a/src/spice2x/util/sysutils.h b/src/spice2x/util/sysutils.h index 6fc682d..58a010a 100644 --- a/src/spice2x/util/sysutils.h +++ b/src/spice2x/util/sysutils.h @@ -1,10 +1,18 @@ #pragma once #include +#include #include namespace sysutils { void print_smbios(); void print_gpus(); void print_os(); -} + + struct MonitorEntry { + std::string display_name; // \\.\DISPLAY1 + std::wstring friendly_name; // Dell S2204T; wstring so that it can be converted to utf8 or string + }; + + const std::vector &enumerate_monitors(); +} \ No newline at end of file