w32_common: refactor and improve window state handling

Refactored and split the `reinit_window_state` code into four
separate functions:
- `update_window_style` used to update window styles without
modifying the window rect.
- `fit_window_on_screen` used to adjust the window size when it is
larger than the screen size. Added a helper function `fit_rect` to
fit one rect on another without using any data from w32 struct.
- `update_fullscreen_state` used to calculate the new fullscreen
state and adjust the window rect accordingly.
- `update_window_state` used to display the window on screen with
new size, position and ontop state.

This commit fixes three issues:
- fixed #4753 by skipping `fit_window_on_screen` for a maximized
window, since maximized window should already fit on the screen.
It should be noted that this bug was only reproducible with
`--fit-border` option which is enabled by default. The cause of the
bug is that after calling the `add_window_borders` for a maximized
window, the rect in result is slightly larger than the screen rect,
which is okay, `SetWindowPos` will interpret it as a maximized state
later, so no auto-fitting to screen size is needed here.
- fixed #5215 by skipping `fit_window_on_screen` when leaving fullscreen.
On a multi-monitor system if the mpv window was stretched to cover
multiple monitors, its size was reset after switching back from
fullscreen to fit the size of the active monitor. Also, when changing
`--ontop` and `--border` options, now only the
`update_window_style` and `update_window_state` functions are used,
so `fit_window_on_screen` is not used for them too.
- fixed #2451 by moving the `ITaskbarList2_MarkFullscreenWindow`
below the `SetWindowPos`. If the taskbar is notified about fullscreen
state before the window is shown on screen, the taskbar button could
be missing until Alt-TAB is pressed, usually it was reproducible on
Windows 8.

Other changes:
- In `update_fullscreen_state` the `reset window bounds` debug
message now reports client area size and position, instead of window area
size and position. This is done for consistency with debug messages
in handling fullscreen state above in this function, since they also print
window bounds of the client area.
- Refactored `gui_thread_reconfig`. Added a new window flag `fit_on_screen`
to fit the window on screen even when leaving fullscreen. This is needed
for the case when the new video opened while the window is still in the
fullscreen state.
- Moved parent and fullscreen state checks out from the WM_MOVING to
`snap_to_screen_edges` function for consistency with other functions.
There's no point in keeping these checks out of the function body.
This commit is contained in:
pavelxdd 2017-12-17 03:20:53 +03:00 committed by James Ross-Gowan
parent ebd5ae3721
commit d13f9d0886
1 changed files with 161 additions and 141 deletions

View File

@ -126,6 +126,9 @@ struct vo_w32_state {
// UTF-16 decoding state for WM_CHAR and VK_PACKET
int high_surrogate;
// Whether to fit the window on screen on next window state updating
bool fit_on_screen;
ITaskbarList2 *taskbar_list;
ITaskbarList3 *taskbar_list3;
UINT tbtnCreatedMsg;
@ -604,6 +607,9 @@ static void update_playback_state(struct vo_w32_state *w32)
static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc)
{
if (w32->parent || w32->current_fs || IsMaximized(w32->window))
return false;
if (!w32->opts->snap_window) {
w32->snapped = false;
return false;
@ -751,127 +757,148 @@ static DWORD update_style(struct vo_w32_state *w32, DWORD style)
return style;
}
// Update the window title, position, size, and border style.
static void reinit_window_state(struct vo_w32_state *w32)
static void update_window_style(struct vo_w32_state *w32)
{
HWND layer = HWND_NOTOPMOST;
RECT r;
if (w32->parent)
return;
bool new_fs = w32->toggle_fs ? !w32->current_fs : w32->opts->fullscreen;
// SetWindowLongPtr can trigger a WM_SIZE event, so window rect
// has to be saved now and restored after setting the new style.
const RECT wr = w32->windowrc;
const DWORD style = GetWindowLongPtrW(w32->window, GWL_STYLE);
SetWindowLongPtrW(w32->window, GWL_STYLE, update_style(w32, style));
w32->windowrc = wr;
}
// Adjust rc size and position if its size is larger than rc2.
// returns true if the rectangle was modified.
static bool fit_rect(RECT *rc, RECT *rc2)
{
// Calculate old size and maximum new size
int o_w = rect_w(*rc), o_h = rect_h(*rc);
int n_w = rect_w(*rc2), n_h = rect_h(*rc2);
if (o_w <= n_w && o_h <= n_h)
return false;
// Apply letterboxing
const float o_asp = o_w / (float)MPMAX(o_h, 1);
const float n_asp = n_w / (float)MPMAX(n_h, 1);
if (o_asp > n_asp) {
n_h = n_w / o_asp;
} else {
n_w = n_h * o_asp;
}
// Calculate new position and save the rect
const int x = rc->left + o_w / 2 - n_w / 2;
const int y = rc->top + o_h / 2 - n_h / 2;
SetRect(rc, x, y, x + n_w, y + n_h);
return true;
}
// Adjust window size and position if its size is larger than the screen size.
static void fit_window_on_screen(struct vo_w32_state *w32)
{
if (w32->parent || w32->current_fs || IsMaximized(w32->window))
return;
RECT screen = w32->screenrc;
if (w32->opts->border && w32->opts->fit_border)
subtract_window_borders(w32->window, &screen);
if (fit_rect(&w32->windowrc, &screen)) {
MP_VERBOSE(w32, "adjusted window bounds: %d:%d:%d:%d\n",
(int)w32->windowrc.left, (int)w32->windowrc.top,
(int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc));
}
}
// Calculate new fullscreen state and change window size and position.
// returns true if fullscreen state was changed.
static bool update_fullscreen_state(struct vo_w32_state *w32)
{
if (w32->parent)
return false;
bool new_fs = w32->opts->fullscreen;
if (w32->toggle_fs) {
new_fs = !w32->current_fs;
w32->toggle_fs = false;
}
bool toggle_fs = w32->current_fs != new_fs;
w32->current_fs = new_fs;
w32->toggle_fs = false;
update_screen_rect(w32);
if (toggle_fs) {
RECT rc;
char msg[50];
if (w32->current_fs) {
// Save window rect when switching to fullscreen.
rc = w32->prev_windowrc = w32->windowrc;
sprintf(msg, "save window bounds");
} else {
// Restore window rect when switching from fullscreen.
rc = w32->windowrc = w32->prev_windowrc;
sprintf(msg, "restore window bounds");
}
MP_VERBOSE(w32, "%s: %d:%d:%d:%d\n", msg,
(int)rc.left, (int)rc.top, (int)rect_w(rc), (int)rect_h(rc));
}
if (w32->current_fs)
w32->windowrc = w32->screenrc;
MP_VERBOSE(w32, "reset window bounds: %d:%d:%d:%d\n",
(int)w32->windowrc.left, (int)w32->windowrc.top,
(int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc));
return toggle_fs;
}
static void update_window_state(struct vo_w32_state *w32)
{
if (w32->parent)
return;
RECT wr = w32->windowrc;
add_window_borders(w32->window, &wr);
SetWindowPos(w32->window, w32->opts->ontop ? HWND_TOPMOST : HWND_NOTOPMOST,
wr.left, wr.top, rect_w(wr), rect_h(wr),
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
// Notify the taskbar about the fullscreen state only after the window
// is visible, to make sure the taskbar item has already been created
if (w32->taskbar_list) {
ITaskbarList2_MarkFullscreenWindow(w32->taskbar_list,
w32->window, w32->current_fs);
}
DWORD style = update_style(w32, GetWindowLongPtrW(w32->window, GWL_STYLE));
if (w32->opts->ontop)
layer = HWND_TOPMOST;
// xxx not sure if this can trigger any unwanted messages (WM_MOVE/WM_SIZE)
update_screen_rect(w32);
if (w32->current_fs) {
// Save window position and size when switching to fullscreen.
if (toggle_fs) {
w32->prev_windowrc = w32->windowrc;
MP_VERBOSE(w32, "save window bounds: %d:%d:%d:%d\n",
(int)w32->windowrc.left, (int)w32->windowrc.top,
(int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc));
}
w32->windowrc = w32->screenrc;
} else {
// Restore window position and size when switching from fullscreen.
if (toggle_fs) {
w32->windowrc = w32->prev_windowrc;
MP_VERBOSE(w32, "restore window bounds: %d:%d:%d:%d\n",
(int)w32->windowrc.left, (int)w32->windowrc.top,
(int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc));
}
}
r = w32->windowrc;
SetWindowLongPtrW(w32->window, GWL_STYLE, style);
RECT cr = r;
add_window_borders(w32->window, &r);
// Check on client area size instead of window size on --fit-border=no
long o_w;
long o_h;
if( w32->opts->fit_border ) {
o_w = r.right - r.left;
o_h = r.bottom - r.top;
} else {
o_w = cr.right - cr.left;
o_h = cr.bottom - cr.top;
}
const int screen_w = rect_w(w32->screenrc);
const int screen_h = rect_h(w32->screenrc);
if ( !w32->current_fs && ( o_w > screen_w || o_h > screen_h ) )
{
MP_VERBOSE(w32, "requested window size larger than the screen\n");
// Use the aspect of the client area, not the full window size.
// Basically, try to compute the maximum window size.
long n_w;
long n_h;
if( w32->opts->fit_border ) {
n_w = screen_w - (r.right - cr.right) - (cr.left - r.left);
n_h = screen_h - (r.bottom - cr.bottom) - (cr.top - r.top);
} else {
n_w = screen_w;
n_h = screen_h;
}
// Letterbox
double asp = (cr.right - cr.left) / (double)(cr.bottom - cr.top);
double s_asp = n_w / (double)n_h;
if (asp > s_asp) {
n_h = n_w / asp;
} else {
n_w = n_h * asp;
}
// Save new size
w32->windowrc.right = w32->windowrc.left + n_w;
w32->windowrc.bottom = w32->windowrc.top + n_h;
// Get old window center
long o_cx = r.left + (r.right - r.left) / 2;
long o_cy = r.top + (r.bottom - r.top) / 2;
// Add window borders to the new window size
r = (RECT){.right = n_w, .bottom = n_h};
add_window_borders(w32->window, &r);
// Get top and left border size for client area position calculation
long b_top = -r.top;
long b_left = -r.left;
// Center the final window around the old window center
n_w = r.right - r.left;
n_h = r.bottom - r.top;
r.left = o_cx - n_w / 2;
r.top = o_cy - n_h / 2;
r.right = r.left + n_w;
r.bottom = r.top + n_h;
// Save new client area position
OffsetRect(&w32->windowrc, r.left + b_left - w32->windowrc.left,
r.top + b_top - w32->windowrc.top);
}
MP_VERBOSE(w32, "reset window bounds: %d:%d:%d:%d\n",
(int) r.left, (int) r.top, (int)(r.right - r.left),
(int)(r.bottom - r.top));
SetWindowPos(w32->window, layer, r.left, r.top, r.right - r.left,
r.bottom - r.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
signal_events(w32, VO_EVENT_RESIZE);
}
static void reinit_window_state(struct vo_w32_state *w32)
{
if (w32->parent)
return;
// The order matters: fs state should be updated prior to changing styles
bool toggle_fs = update_fullscreen_state(w32);
update_window_style(w32);
// Assume that the window has already been fit on screen before switching fs
if (!toggle_fs || w32->fit_on_screen) {
fit_window_on_screen(w32);
// The fullscreen state might still be active, so set the flag
// to fit on screen next time the window leaves the fullscreen.
w32->fit_on_screen = w32->current_fs;
}
// Show and activate the window after all window state parameters were set
update_window_state(w32);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
@ -916,7 +943,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
case WM_MOVING: {
w32->moving = true;
RECT *rc = (RECT*)lParam;
if (!w32->current_fs && !w32->parent && snap_to_screen_edges(w32, rc))
if (snap_to_screen_edges(w32, rc))
return TRUE;
break;
}
@ -1261,50 +1288,41 @@ static void gui_thread_reconfig(void *ptr)
bool reset_size = w32->o_dwidth != vo->dwidth ||
w32->o_dheight != vo->dheight;
bool pos_init = false;
w32->o_dwidth = vo->dwidth;
w32->o_dheight = vo->dheight;
// the desired size is ignored in wid mode, it always matches the window size.
if (!w32->parent) {
if (w32->window_bounds_initialized) {
// restore vo_dwidth/vo_dheight, which are reset against our will
// in vo_config()
RECT r;
GetClientRect(w32->window, &r);
vo->dwidth = r.right;
vo->dheight = r.bottom;
} else {
if (!w32->parent && !w32->window_bounds_initialized) {
SetRect(&w32->windowrc, geo.win.x0, geo.win.y0,
geo.win.x0 + vo->dwidth, geo.win.y0 + vo->dheight);
w32->prev_windowrc = w32->windowrc;
w32->window_bounds_initialized = true;
reset_size = true;
pos_init = true;
w32->windowrc.left = w32->prev_windowrc.left = geo.win.x0;
w32->windowrc.top = w32->prev_windowrc.top = geo.win.y0;
w32->fit_on_screen = true;
goto finish;
}
if (reset_size) {
vo->dwidth = w32->o_dwidth;
vo->dheight = w32->o_dheight;
w32->prev_windowrc.right = w32->prev_windowrc.left + vo->dwidth;
w32->prev_windowrc.bottom = w32->prev_windowrc.top + vo->dheight;
}
} else {
// The rect which size is going to be modified.
RECT *rc = &w32->windowrc;
// The desired size always matches the window size in wid mode.
if (!reset_size || w32->parent) {
RECT r;
GetClientRect(w32->window, &r);
// Restore vo_dwidth and vo_dheight, which were reset in vo_config()
vo->dwidth = r.right;
vo->dheight = r.bottom;
} else {
if (w32->current_fs)
rc = &w32->prev_windowrc;
w32->fit_on_screen = true;
}
// Recenter window around old position on new video size
// excluding the case when initial position handled by win_state.
if (!pos_init) {
w32->windowrc.left += rect_w(w32->windowrc) / 2 - vo->dwidth / 2;
w32->windowrc.top += rect_h(w32->windowrc) / 2 - vo->dheight / 2;
}
w32->windowrc.right = w32->windowrc.left + vo->dwidth;
w32->windowrc.bottom = w32->windowrc.top + vo->dheight;
// Save new window size and position.
const int x = rc->left + rect_w(*rc) / 2 - vo->dwidth / 2;
const int y = rc->top + rect_h(*rc) / 2 - vo->dheight / 2;
SetRect(rc, x, y, x + vo->dwidth, y + vo->dheight);
finish:
reinit_window_state(w32);
}
@ -1538,10 +1556,11 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
reinit_window_state(w32);
return VO_TRUE;
case VOCTRL_ONTOP:
reinit_window_state(w32);
update_window_state(w32);
return VO_TRUE;
case VOCTRL_BORDER:
reinit_window_state(w32);
update_window_style(w32);
update_window_state(w32);
return VO_TRUE;
case VOCTRL_GET_FULLSCREEN:
*(bool *)arg = w32->current_fs;
@ -1568,6 +1587,7 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
const int y = rc->top + rect_h(*rc) / 2 - s[1] / 2;
SetRect(rc, x, y, x + s[0], y + s[1]);
w32->fit_on_screen = true;
reinit_window_state(w32);
return VO_TRUE;
}