Files
spice2x.github.io/src/spice2x/hooks/graphics/graphics_windowed.cpp
T
bicarus 7a1f46d1be gitadora: (arena model) fix window settings not working (#494)
## Link to GitHub Issue, if one exists
#477 

## Description of change
F11 menu (window settings) did not work for arena model because the game
engine races to create many windows, so our logic of grabbing the first
window created doesn't always get the main window.

## Testing
2026-01-02 15:07:32 -08:00

496 lines
16 KiB
C++

#include "graphics.h"
#include "avs/game.h"
#include "cfg/screen_resize.h"
#include "overlay/overlay.h"
#include "util/logging.h"
#include "util/utils.h"
#include "touch/touch.h"
#if 0
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
void graphics_load_windowed_parameters();
void graphics_wm_style_changed(HWND hWnd, bool changed);
void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect);
std::optional<int> GRAPHICS_WINDOW_STYLE;
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
std::optional<std::string> GRAPHICS_WINDOW_POS;
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false;
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
bool GRAPHICS_IIDX_WSUB = false;
std::optional<std::string> GRAPHICS_WSUB_SIZE;
std::optional<std::string> GRAPHICS_WSUB_POS;
int GRAPHICS_WSUB_WIDTH = 1280;
int GRAPHICS_WSUB_HEIGHT = 720;
int GRAPHICS_WSUB_X = 0;
int GRAPHICS_WSUB_Y = 0;
bool GRAPHICS_WSUB_BORDERLESS = false;
bool GRAPHICS_WSUB_ALWAYS_ON_TOP = false;
// these flags are carefully constructed to ensure maximum compatibility
// (e.g., DDR likes to hang when SetWindowPos is called with certain params)
static const DWORD SETWINDOWPOS_NOOP =
SWP_NOMOVE |
SWP_NOSIZE |
SWP_NOREDRAW |
SWP_NOCOPYBITS |
SWP_NOACTIVATE |
SWP_NOSENDCHANGING |
SWP_DEFERERASE |
SWP_NOZORDER |
SWP_ASYNCWINDOWPOS;
void graphics_capture_initial_window(HWND hWnd) {
if (!GRAPHICS_WINDOWED) {
return;
}
graphics_load_windowed_parameters();
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
cfg::SCREENRESIZE->init_window_style_ex = GetWindowLong(hWnd, GWL_EXSTYLE);
log_debug(
"graphics-windowed",
"graphics_capture_initial_window called, hWnd={}",
fmt::ptr(hWnd));
RECT rect;
if (!GetClientRect(hWnd, &rect)) {
log_warning(
"graphics",
"graphics_capture_initial_window - GetClientRect failed, GLE: {}",
GetLastError());
return;
}
const int client_w = rect.right - rect.left;
const int client_h = rect.bottom - rect.top;
cfg::SCREENRESIZE->init_client_width = client_w;
cfg::SCREENRESIZE->init_client_height = client_h;
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
log_debug(
"graphics-windowed",
"graphics_capture_initial_window initial window size {}x{}, ratio {}",
client_w, client_h, cfg::SCREENRESIZE->init_client_aspect_ratio);
// ensure frame size is captured
graphics_wm_style_changed(hWnd, false);
// if there was no user-supplied dimension, seed it with the current size
// so that the next resize operation will work
if (cfg::SCREENRESIZE->client_width == 0) {
cfg::SCREENRESIZE->client_width = client_w;
}
if (cfg::SCREENRESIZE->client_height == 0) {
cfg::SCREENRESIZE->client_height = client_h;
}
// apply the config that was loaded from disk
// resize must be done before applying the border
if (cfg::SCREENRESIZE->enable_window_resize) {
log_info(
"graphics-windowed", "change window rect - window offset: {}x{}, client size: {}x{}",
cfg::SCREENRESIZE->window_offset_x,
cfg::SCREENRESIZE->window_offset_y,
cfg::SCREENRESIZE->client_width,
cfg::SCREENRESIZE->client_height);
graphics_move_resize_window(hWnd);
}
// ddr hangs when window frame doesn't have overlapped
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
log_info(
"graphics-windowed", "change window style - decoration: {}",
cfg::SCREENRESIZE->window_decoration);
graphics_update_window_style(hWnd);
}
if (cfg::SCREENRESIZE->window_always_on_top) {
log_info("graphics-windowed", "change window z-order - always on top");
graphics_update_z_order(hWnd, true);
}
// ensure spicetouch coordinates are initialized
update_spicetouch_window_dimensions(hWnd);
log_debug("graphics-windowed", "graphics_capture_initial_window returned");
}
void graphics_load_windowed_parameters() {
if (!GRAPHICS_WINDOWED) {
return;
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters called");
if (GRAPHICS_WINDOW_STYLE.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_STYLE");
cfg::SCREENRESIZE->window_decoration = GRAPHICS_WINDOW_STYLE.value();
}
if (GRAPHICS_WINDOW_SIZE.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_SIZE");
cfg::SCREENRESIZE->enable_window_resize = true;
cfg::SCREENRESIZE->client_keep_aspect_ratio = false;
cfg::SCREENRESIZE->client_width = GRAPHICS_WINDOW_SIZE.value().first;
cfg::SCREENRESIZE->client_height = GRAPHICS_WINDOW_SIZE.value().second;
}
if (GRAPHICS_WINDOW_POS.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_POS");
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_WINDOW_POS.value(), result)) {
cfg::SCREENRESIZE->enable_window_resize = true;
cfg::SCREENRESIZE->window_offset_x = result.first;
cfg::SCREENRESIZE->window_offset_y = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -windowpos");
}
}
// only override if true; don't stomp on user setting
if (GRAPHICS_WINDOW_ALWAYS_ON_TOP) {
cfg::SCREENRESIZE->window_always_on_top = true;
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
}
void graphics_load_windowed_subscreen_parameters() {
if (!GRAPHICS_WINDOWED) {
return;
}
log_debug("graphics-windowed", "graphics_load_windowed_subscreen_parameters called");
if (GRAPHICS_WSUB_SIZE.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WSUB_SIZE");
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_WSUB_SIZE.value(), result)) {
GRAPHICS_WSUB_WIDTH = result.first;
GRAPHICS_WSUB_HEIGHT = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -wsubsize");
}
}
if (GRAPHICS_WSUB_POS.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WSUB_POS");
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_WSUB_POS.value(), result)) {
GRAPHICS_WSUB_X = result.first;
GRAPHICS_WSUB_Y = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -wsubpos");
}
}
}
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (!GRAPHICS_WINDOWED) {
return;
}
switch (uMsg) {
case WM_MOVE: {
log_debug("graphics-windowed", "graphics_windowed_wndproc called with WM_MOVE");
RECT rect;
if (GetWindowRect(hWnd, &rect)) {
cfg::SCREENRESIZE->window_offset_x = rect.left;
cfg::SCREENRESIZE->window_offset_y = rect.top;
}
break;
}
case WM_SIZE: {
log_debug("graphics-windowed", "graphics_windowed_wndproc called with WM_SIZE");
if (wParam == SIZE_MINIMIZED) {
break;
}
RECT rect;
if (GetClientRect(hWnd, &rect)) {
cfg::SCREENRESIZE->client_width = rect.right - rect.left;
cfg::SCREENRESIZE->client_height = rect.bottom - rect.top;
}
break;
}
case WM_SIZING: {
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
graphics_wm_sizing_aspect_ratio(
static_cast<int>(wParam), *reinterpret_cast<LPRECT>(lParam));
}
break;
}
case WM_STYLECHANGED: {
graphics_wm_style_changed(hWnd, true);
break;
}
case WM_GETMINMAXINFO: {
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
auto info = reinterpret_cast<MINMAXINFO *>(lParam);
info->ptMinTrackSize.y =
cfg::SCREENRESIZE->window_deco_height +
((info->ptMinTrackSize.x - cfg::SCREENRESIZE->window_deco_width) /
cfg::SCREENRESIZE->init_client_aspect_ratio);
}
break;
}
}
}
void graphics_wm_style_changed(HWND hWnd, bool changed) {
log_debug("graphics-windowed", "graphics_wm_style_changed called");
RECT rect;
// ensure the style change takes in effect before doing the calculations
if (changed) {
// ensure client size doesn't change as a result of this
// since SetWindowPos will still send WM_SIZE and WM_MOVE
const auto client_w = cfg::SCREENRESIZE->client_width;
const auto client_h = cfg::SCREENRESIZE->client_height;
const auto flags = SETWINDOWPOS_NOOP | SWP_FRAMECHANGED;
SetWindowPos(
hWnd,
nullptr,
0, 0, 0, 0,
flags);
cfg::SCREENRESIZE->client_width = client_w;
cfg::SCREENRESIZE->client_height = client_h;
}
// get window size with decoration...
if (!GetWindowRect(hWnd, &rect)) {
log_warning(
"graphics",
"graphics_wm_style_changed - GetWindowRect failed, GLE: {}",
GetLastError());
return;
}
const int window_w = rect.right - rect.left;
const int window_h = rect.bottom - rect.top;
// get client area (without decoration)...
if (!GetClientRect(hWnd, &rect)) {
return;
}
const int client_w = rect.right - rect.left;
const int client_h = rect.bottom - rect.top;
// update window decoration size
cfg::SCREENRESIZE->window_deco_width = window_w - client_w;
cfg::SCREENRESIZE->window_deco_height = window_h - client_h;
log_debug(
"graphics-windowed",
"graphics_wm_style_changed updating frame dimensions {}x{}",
cfg::SCREENRESIZE->window_deco_width,
cfg::SCREENRESIZE->window_deco_height);
// adjust window to ensure client area remains the same
if (changed) {
graphics_move_resize_window(hWnd);
}
log_debug("graphics-windowed", "graphics_wm_style_changed returned");
}
void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect) {
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio called");
const auto deco_w = cfg::SCREENRESIZE->window_deco_width;
const auto deco_h = cfg::SCREENRESIZE->window_deco_height;
const LONG desired_w = (rect.right - rect.left) - deco_w;
const LONG desired_h = (rect.bottom - rect.top) - deco_h;
const auto aspect_ratio = cfg::SCREENRESIZE->init_client_aspect_ratio;
// based on http://playtechs.blogspot.com/2007/10/forcing-window-to-maintain-particular.html
switch (edge) {
case WMSZ_BOTTOM:
case WMSZ_TOP: {
const LONG w = deco_w + (desired_h * aspect_ratio);
rect.right = rect.left + w;
break;
}
case WMSZ_LEFT:
case WMSZ_RIGHT: {
const LONG h = deco_h + (desired_w / aspect_ratio);
rect.bottom = rect.top + h;
break;
}
case WMSZ_TOPLEFT:
case WMSZ_TOPRIGHT:
case WMSZ_BOTTOMLEFT:
case WMSZ_BOTTOMRIGHT: {
int w;
int h;
if (desired_h * aspect_ratio < desired_w) {
w = rect.right - rect.left;
h = deco_h + (desired_w / aspect_ratio);
} else {
w = deco_w + (desired_h * aspect_ratio);
h = rect.bottom - rect.top;
}
if (edge == WMSZ_TOPLEFT) {
rect.left = rect.right - w;
rect.top = rect.bottom - h;
} else if (edge == WMSZ_TOPRIGHT) {
rect.right = rect.left + w;
rect.top = rect.bottom - h;
} else if (edge == WMSZ_BOTTOMLEFT) {
rect.left = rect.right - w;
rect.bottom = rect.top + h;
} else if (edge == WMSZ_BOTTOMRIGHT) {
rect.right = rect.left + w;
rect.bottom = rect.top + h;
}
break;
}
default:
break;
}
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned");
}
void graphics_update_window_style(HWND hWnd) {
if (!GRAPHICS_WINDOWED) {
return;
}
if (graphics_window_change_crashes_game()) {
return;
}
log_debug(
"graphics-windowed",
"graphics_update_window_style called for hWnd={}",
fmt::ptr(hWnd));
// update frame style
auto style = cfg::SCREENRESIZE->init_window_style;
auto style_ex = cfg::SCREENRESIZE->init_window_style_ex;
switch (cfg::SCREENRESIZE->window_decoration) {
case cfg::WindowDecorationMode::Borderless:
style &= ~WS_OVERLAPPEDWINDOW;
style_ex &= ~WS_EX_CLIENTEDGE;
style_ex &= ~WS_EX_WINDOWEDGE;
break;
case cfg::WindowDecorationMode::ResizableFrame:
style |= WS_OVERLAPPEDWINDOW;
break;
case cfg::WindowDecorationMode::Default:
default:
break;
}
log_debug(
"graphics-windowed",
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
style);
const auto ret = SetWindowLong(hWnd, GWL_STYLE, style);
(void)ret;
log_debug(
"graphics-windowed",
"graphics_update_window_style - SetWindowLong(GWL_STYLE) returned {:x}, GLE={}",
ret,
GetLastError());
const auto ret2 = SetWindowLong(hWnd, GWL_EXSTYLE, style_ex);
(void)ret2;
log_debug(
"graphics-windowed",
"graphics_update_window_style SetWindowLong(GWL_EXSTYLE) returned {:x}, GLE={}",
ret2,
GetLastError());
// SetWindowPos must be called after SetWindowLong if the frame style changed
// this will be done in WM_STYLECHANGED handler
}
void graphics_update_z_order(HWND hWnd, bool always_on_top) {
if (!GRAPHICS_WINDOWED) {
return;
}
log_debug("graphics-windowed", "graphics_update_z_order called");
HWND insert_after = nullptr;
if (always_on_top) {
insert_after = HWND_TOPMOST;
} else {
insert_after = HWND_NOTOPMOST;
}
auto flags = SETWINDOWPOS_NOOP;
flags &= ~SWP_NOZORDER;
SetWindowPos(
hWnd,
insert_after,
0, 0, 0, 0,
flags);
log_debug("graphics-windowed", "graphics_update_z_order returned");
}
void graphics_move_resize_window(HWND hWnd) {
if (!GRAPHICS_WINDOWED) {
return;
}
log_debug("graphics-windowed", "graphics_move_resize_window called");
cfg::SCREENRESIZE->client_width =
CLAMP(cfg::SCREENRESIZE->client_width, 640, 1920 * 8);
cfg::SCREENRESIZE->client_height =
CLAMP(cfg::SCREENRESIZE->client_height, 480, 1080 * 8);
const auto w = cfg::SCREENRESIZE->client_width + cfg::SCREENRESIZE->window_deco_width;
const auto h = cfg::SCREENRESIZE->client_height + cfg::SCREENRESIZE->window_deco_height;
auto flags = SETWINDOWPOS_NOOP;
flags &= ~SWP_NOSIZE;
flags &= ~SWP_NOMOVE;
SetWindowPos(
hWnd,
nullptr,
cfg::SCREENRESIZE->window_offset_x,
cfg::SCREENRESIZE->window_offset_y,
w, h,
flags);
log_debug("graphics-windowed", "graphics_move_resize_window returned");
}
bool graphics_window_change_crashes_game() {
static std::once_flag flag;
static bool result = false;
std::call_once(flag, []() {
// ddr crashes when frame style changes
result = avs::game::is_model("MDX");
if (result) {
log_warning(
"graphics-windowed",
"ignoring changes to window style due to incompatibility with this game");
}
});
return result;
}