mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
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.
This commit is contained in:
@@ -20,6 +20,7 @@ enum class OptionPickerType {
|
|||||||
CpuAffinity,
|
CpuAffinity,
|
||||||
FilePath,
|
FilePath,
|
||||||
DirectoryPath,
|
DirectoryPath,
|
||||||
|
Monitor,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OptionDefinition {
|
struct OptionDefinition {
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ bool GRAPHICS_CAPTURE_CURSOR = false;
|
|||||||
bool GRAPHICS_LOG_HRESULT = false;
|
bool GRAPHICS_LOG_HRESULT = false;
|
||||||
bool GRAPHICS_SDVX_FORCE_720 = false;
|
bool GRAPHICS_SDVX_FORCE_720 = false;
|
||||||
bool GRAPHICS_SHOW_CURSOR = false;
|
bool GRAPHICS_SHOW_CURSOR = false;
|
||||||
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
|
|
||||||
bool GRAPHICS_WINDOWED = false;
|
bool GRAPHICS_WINDOWED = false;
|
||||||
std::vector<HWND> GRAPHICS_WINDOWS;
|
std::vector<HWND> GRAPHICS_WINDOWS;
|
||||||
UINT GRAPHICS_FORCE_REFRESH = 0;
|
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<decltype(GetDisplayConfigBufferSizes) *>(
|
||||||
|
GetProcAddress(user32, "GetDisplayConfigBufferSizes"));
|
||||||
|
const auto QueryDisplayConfig_addr =
|
||||||
|
reinterpret_cast<decltype(QueryDisplayConfig) *>(
|
||||||
|
GetProcAddress(user32, "QueryDisplayConfig"));
|
||||||
|
const auto DisplayConfigGetDeviceInfo_addr =
|
||||||
|
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
|
||||||
|
GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
|
||||||
|
const auto SetDisplayConfig_addr =
|
||||||
|
reinterpret_cast<decltype(SetDisplayConfig) *>(
|
||||||
|
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<DISPLAYCONFIG_PATH_INFO> paths;
|
||||||
|
std::vector<DISPLAYCONFIG_MODE_INFO> 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<graphics_orientation> target_orientation, UINT target_refresh_rate) {
|
||||||
// note: all of this is only being done for the primary motnior
|
// note: all of this is only being done for the primary motnior
|
||||||
|
|
||||||
// get current settings
|
// get current settings
|
||||||
DEVMODEA dm = {};
|
DEVMODEA dm = {};
|
||||||
dm.dmSize = sizeof(dm);
|
dm.dmSize = sizeof(dm);
|
||||||
if (!EnumDisplaySettingsExA(NULL, ENUM_CURRENT_SETTINGS, &dm, 0)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool needs_update = false;
|
bool needs_update = false;
|
||||||
|
|
||||||
// convert orientation values, and figure out if resolution needs to be swapped
|
if (target_orientation.has_value()) {
|
||||||
bool rotate_resolution = false;
|
// convert orientation values, and figure out if resolution needs to be swapped
|
||||||
DWORD orientation = DMDO_DEFAULT;
|
bool rotate_resolution = false;
|
||||||
switch (GRAPHICS_ADJUST_ORIENTATION) {
|
DWORD orientation = DMDO_DEFAULT;
|
||||||
case ORIENTATION_CW:
|
switch (target_orientation.value()) {
|
||||||
orientation = DMDO_90;
|
case ORIENTATION_CW:
|
||||||
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
|
orientation = DMDO_90;
|
||||||
rotate_resolution = true;
|
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
|
||||||
}
|
rotate_resolution = true;
|
||||||
break;
|
}
|
||||||
case ORIENTATION_CCW:
|
break;
|
||||||
orientation = DMDO_270;
|
case ORIENTATION_CCW:
|
||||||
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
|
orientation = DMDO_270;
|
||||||
rotate_resolution = true;
|
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
|
||||||
}
|
rotate_resolution = true;
|
||||||
break;
|
}
|
||||||
case ORIENTATION_FLIPPED:
|
break;
|
||||||
orientation = DMDO_180;
|
case ORIENTATION_FLIPPED:
|
||||||
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
|
orientation = DMDO_180;
|
||||||
rotate_resolution = true;
|
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
|
||||||
}
|
rotate_resolution = true;
|
||||||
break;
|
}
|
||||||
default:
|
break;
|
||||||
orientation = DMDO_DEFAULT;
|
default:
|
||||||
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
|
orientation = DMDO_DEFAULT;
|
||||||
rotate_resolution = true;
|
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
|
||||||
}
|
rotate_resolution = true;
|
||||||
break;
|
}
|
||||||
}
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// update refresh rate
|
||||||
if (GRAPHICS_FORCE_REFRESH > 0) {
|
if (target_refresh_rate > 0) {
|
||||||
log_misc("graphics",
|
log_misc("graphics",
|
||||||
"current refresh rate {} => desired refresh rate {}",
|
"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;
|
dm.dmFields |= DM_DISPLAYFREQUENCY;
|
||||||
needs_update = true;
|
needs_update = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needs_update) {
|
if (!needs_update) {
|
||||||
|
// nothing to do
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1170,11 +1309,11 @@ void update_monitor_on_boot() {
|
|||||||
if (result != DISP_CHANGE_SUCCESSFUL) {
|
if (result != DISP_CHANGE_SUCCESSFUL) {
|
||||||
log_fatal(
|
log_fatal(
|
||||||
"graphics",
|
"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.dmPelsWidth,
|
||||||
dm.dmPelsHeight,
|
dm.dmPelsHeight,
|
||||||
dm.dmDisplayFrequency,
|
dm.dmDisplayFrequency,
|
||||||
get_last_error_string());
|
result);
|
||||||
} else {
|
} else {
|
||||||
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
|
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
|
||||||
dm.dmPelsWidth,
|
dm.dmPelsWidth,
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ extern bool GRAPHICS_LOG_HRESULT;
|
|||||||
extern bool GRAPHICS_SDVX_FORCE_720;
|
extern bool GRAPHICS_SDVX_FORCE_720;
|
||||||
extern bool GRAPHICS_SHOW_CURSOR;
|
extern bool GRAPHICS_SHOW_CURSOR;
|
||||||
extern bool GRAPHICS_WINDOWED;
|
extern bool GRAPHICS_WINDOWED;
|
||||||
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
|
|
||||||
extern std::vector<HWND> GRAPHICS_WINDOWS;
|
extern std::vector<HWND> GRAPHICS_WINDOWS;
|
||||||
extern UINT GRAPHICS_FORCE_REFRESH;
|
extern UINT GRAPHICS_FORCE_REFRESH;
|
||||||
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
|
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
|
||||||
@@ -109,4 +108,5 @@ bool graphics_window_resize_breaks_game();
|
|||||||
bool graphics_window_move_and_resize_breaks_game();
|
bool graphics_window_move_and_resize_breaks_game();
|
||||||
void graphics_load_windowed_subscreen_parameters();
|
void graphics_load_windowed_subscreen_parameters();
|
||||||
|
|
||||||
void update_monitor_on_boot();
|
void change_primary_monitor(const std::string &monitor_name);
|
||||||
|
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
|
||||||
@@ -351,9 +351,9 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||||
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
|
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::DisplayAdapter].is_active() &&
|
if (options[launcher::Options::DX9DisplayAdapter].is_active() &&
|
||||||
options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
|
options[launcher::Options::DX9DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
|
||||||
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32();
|
D3D9_ADAPTER = options[launcher::Options::DX9DisplayAdapter].value_uint32();
|
||||||
|
|
||||||
// when we fix up adapter numbers, we only fix the first adapter, and not any subsequent
|
// 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
|
// 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()) {
|
if (options[launcher::Options::AllowEA3Verbose].value_bool()) {
|
||||||
avs::ea3::EA3_DEBUG_VERBOSE = true;
|
avs::ea3::EA3_DEBUG_VERBOSE = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<graphics_orientation> monitor_orientation;
|
||||||
if (options[launcher::Options::spice2x_AutoOrientation].is_active()) {
|
if (options[launcher::Options::spice2x_AutoOrientation].is_active()) {
|
||||||
GRAPHICS_ADJUST_ORIENTATION =
|
monitor_orientation =
|
||||||
(graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32();
|
(graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32();
|
||||||
} else if (options[launcher::Options::AdjustOrientation].value_bool()) {
|
} 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()) {
|
if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) {
|
||||||
D3D9_DEVICE_HOOK_DISABLE = true;
|
D3D9_DEVICE_HOOK_DISABLE = true;
|
||||||
// touch emulation gets disabled, might as well turn these on
|
// touch emulation gets disabled, might as well turn these on
|
||||||
@@ -2134,7 +2137,10 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fix up monitor
|
// 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
|
// initialize raw input
|
||||||
RI_MGR = std::make_unique<rawinput::RawInputManager>();
|
RI_MGR = std::make_unique<rawinput::RawInputManager>();
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
|
|||||||
"Game Options",
|
"Game Options",
|
||||||
"Common",
|
"Common",
|
||||||
"Network",
|
"Network",
|
||||||
|
"Monitor",
|
||||||
"Graphics (Common)",
|
"Graphics (Common)",
|
||||||
"Graphics (Full Screen)",
|
"Graphics (Full Screen)",
|
||||||
"Graphics (Windowed)",
|
"Graphics (Windowed)",
|
||||||
@@ -206,9 +207,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.category = "Common",
|
.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",
|
.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"
|
"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. "
|
"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.",
|
"Disable Full Screen Optimizations for best results.",
|
||||||
@@ -216,19 +232,19 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.category = "Graphics (Full Screen)",
|
.category = "Graphics (Full Screen)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Only Use One Monitor",
|
.title = "Only Use Main Monitor For Full Screen",
|
||||||
.name = "graphics-force-single-adapter",
|
.name = "graphics-force-single-adapter",
|
||||||
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n"
|
.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!",
|
"May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
|
||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
.category = "Graphics (Full Screen)",
|
.category = "Monitor",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Monitor Refresh Rate",
|
.title = "Monitor Refresh Rate",
|
||||||
.name = "graphics-force-refresh",
|
.name = "graphics-force-refresh",
|
||||||
.desc = "Change the refresh rate for the primary monitor before launching the game. It will be restored on exit.",
|
.desc = "Change the refresh rate for the primary monitor before launching the game. It will be restored on exit.",
|
||||||
.type = OptionType::Integer,
|
.type = OptionType::Integer,
|
||||||
.category = "Graphics (Common)",
|
.category = "Monitor",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// FullscreenResolution
|
// FullscreenResolution
|
||||||
@@ -1851,7 +1867,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.aliases= "autoorientation",
|
.aliases= "autoorientation",
|
||||||
.desc = "Change the orientation of the primary display before launching the game. It will be restored on exit",
|
.desc = "Change the orientation of the primary display before launching the game. It will be restored on exit",
|
||||||
.type = OptionType::Enum,
|
.type = OptionType::Enum,
|
||||||
.category = "Graphics (Common)",
|
.category = "Monitor",
|
||||||
// match graphics_orientation enum
|
// match graphics_orientation enum
|
||||||
.elements = {
|
.elements = {
|
||||||
{"0", "Portrait"},
|
{"0", "Portrait"},
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ namespace launcher {
|
|||||||
ExecuteScript,
|
ExecuteScript,
|
||||||
CaptureCursor,
|
CaptureCursor,
|
||||||
ShowCursor,
|
ShowCursor,
|
||||||
DisplayAdapter,
|
PrimaryMonitor,
|
||||||
|
DX9DisplayAdapter,
|
||||||
GraphicsForceSingleAdapter,
|
GraphicsForceSingleAdapter,
|
||||||
GraphicsForceRefresh,
|
GraphicsForceRefresh,
|
||||||
FullscreenResolution,
|
FullscreenResolution,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include "util/resutils.h"
|
#include "util/resutils.h"
|
||||||
#include "util/scope_guard.h"
|
#include "util/scope_guard.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
|
#include "util/sysutils.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
#ifdef min
|
#ifdef min
|
||||||
@@ -4237,6 +4238,8 @@ namespace overlay::windows {
|
|||||||
return "File Picker";
|
return "File Picker";
|
||||||
case OptionPickerType::DirectoryPath:
|
case OptionPickerType::DirectoryPath:
|
||||||
return "Folder Picker";
|
return "Folder Picker";
|
||||||
|
case OptionPickerType::Monitor:
|
||||||
|
return "Monitor Picker";
|
||||||
default:
|
default:
|
||||||
return "Unknown Picker";
|
return "Unknown Picker";
|
||||||
};
|
};
|
||||||
@@ -4467,6 +4470,28 @@ namespace overlay::windows {
|
|||||||
ImGui::EndDisabled();
|
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 {
|
} else {
|
||||||
ImGui::TextUnformatted("No picker available for this option. How did you get here?");
|
ImGui::TextUnformatted("No picker available for this option. How did you get here?");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#include "sysutils.h"
|
#include "sysutils.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#define WIN32_NO_STATUS
|
#define WIN32_NO_STATUS
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#undef WIN32_NO_STATUS
|
#undef WIN32_NO_STATUS
|
||||||
|
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/utils.h"
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define log_dbug(module, format_str, ...) logger::push( \
|
#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);
|
log_dbug("gpuinfo", "{} {} flags : 0x{:x}", prefix.c_str(), index, adapter->StateFlags);
|
||||||
|
|
||||||
if (!is_monitor) {
|
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 = {};
|
DEVMODEA devmode = {};
|
||||||
devmode.dmSize = sizeof(devmode);
|
devmode.dmSize = sizeof(devmode);
|
||||||
if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) {
|
if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) {
|
||||||
@@ -236,11 +254,13 @@ namespace sysutils {
|
|||||||
log_misc("gpuinfo", "EnumDisplaySettingsA failed");
|
log_misc("gpuinfo", "EnumDisplaySettingsA failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// primary?
|
||||||
log_misc(
|
log_misc(
|
||||||
"gpuinfo", "{} {} is primary : {}",
|
"gpuinfo", "{} {} is primary : {}",
|
||||||
prefix.c_str(),
|
prefix.c_str(),
|
||||||
index,
|
index,
|
||||||
(adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no");
|
(adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,4 +318,92 @@ namespace sysutils {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<MonitorEntry> 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<decltype(GetDisplayConfigBufferSizes) *>(
|
||||||
|
GetProcAddress(user32, "GetDisplayConfigBufferSizes"));
|
||||||
|
const auto QueryDisplayConfig_addr =
|
||||||
|
reinterpret_cast<decltype(QueryDisplayConfig) *>(
|
||||||
|
GetProcAddress(user32, "QueryDisplayConfig"));
|
||||||
|
const auto DisplayConfigGetDeviceInfo_addr =
|
||||||
|
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
|
||||||
|
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<DISPLAYCONFIG_PATH_INFO> paths(path_count);
|
||||||
|
std::vector<DISPLAYCONFIG_MODE_INFO> 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<MonitorEntry> 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<MonitorEntry>& enumerate_monitors() {
|
||||||
|
static const std::vector<MonitorEntry> monitors = enumerate_monitors_internal();
|
||||||
|
return monitors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace sysutils {
|
namespace sysutils {
|
||||||
void print_smbios();
|
void print_smbios();
|
||||||
void print_gpus();
|
void print_gpus();
|
||||||
void print_os();
|
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<MonitorEntry> &enumerate_monitors();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user