wayland: Don't send spurious display changes when a fullscreen window is moving

When moving a fullscreen window, the compositor movement animation can cause it to cross multiple displays, sending multiple display changed events, which can cause the window to jitter or be snapped to the wrong display in the case of exclusive fullscreen modes.

When moving fullscreen windows, only send the display changed event when the window isn't on multiple displays to avoid spurious display changed events.
main
Frank Praznik 2024-04-10 11:08:12 -04:00
parent 11de629dd2
commit 1804ad175c
1 changed files with 64 additions and 63 deletions

View File

@ -494,6 +494,58 @@ static void FlushFullscreenEvents(SDL_Window *window)
} }
} }
/* While we can't get window position from the compositor, we do at least know
* what monitor we're on, so let's send move events that put the window at the
* center of the whatever display the wl_surface_listener events give us.
*/
static void Wayland_move_window(SDL_Window *window)
{
SDL_WindowData *wind = window->driverdata;
SDL_DisplayData *display = wind->outputs[wind->num_outputs - 1];
SDL_DisplayID *displays;
displays = SDL_GetDisplays(NULL);
if (displays) {
for (int i = 0; displays[i]; ++i) {
if (SDL_GetDisplayDriverData(displays[i]) == display) {
/* We want to send a very very specific combination here:
*
* 1. A coordinate that tells the application what display we're on
* 2. Exactly (0, 0)
*
* Part 1 is useful information but is also really important for
* ensuring we end up on the right display for fullscreen, while
* part 2 is important because numerous applications use a specific
* combination of GetWindowPosition and GetGlobalMouseState, and of
* course neither are supported by Wayland. Since global mouse will
* fall back to just GetMouseState, we need the window position to
* be zero so the cursor math works without it going off in some
* random direction. See UE5 Editor for a notable example of this!
*
* This may be an issue some day if we're ever able to implement
* SDL_GetDisplayUsableBounds!
*
* -flibit
*/
if (wind->last_displayID != displays[i]) {
wind->last_displayID = displays[i];
if (wind->shell_surface_type != WAYLAND_SURFACE_XDG_POPUP) {
/* Need to catch up on fullscreen state here, as the video core may try to update
* the fullscreen window, which on Wayland involves a set fullscreen call, which
* can overwrite older pending state.
*/
FlushFullscreenEvents(window);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, display->x, display->y);
}
}
break;
}
}
SDL_free(displays);
}
}
static void SetFullscreen(SDL_Window *window, struct wl_output *output) static void SetFullscreen(SDL_Window *window, struct wl_output *output)
{ {
SDL_WindowData *wind = window->driverdata; SDL_WindowData *wind = window->driverdata;
@ -567,6 +619,11 @@ static void UpdateWindowFullscreen(SDL_Window *window, SDL_bool fullscreen)
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
SDL_UpdateFullscreenMode(window, SDL_FALSE, SDL_FALSE); SDL_UpdateFullscreenMode(window, SDL_FALSE, SDL_FALSE);
wind->fullscreen_was_positioned = SDL_FALSE; wind->fullscreen_was_positioned = SDL_FALSE;
/* Send a move event, in case it was deferred while the fullscreen window was moving and
* on multiple outputs.
*/
Wayland_move_window(window);
} }
} }
} }
@ -1249,68 +1306,12 @@ static void Wayland_MaybeUpdateScaleFactor(SDL_WindowData *window)
Wayland_HandlePreferredScaleChanged(window, factor); Wayland_HandlePreferredScaleChanged(window, factor);
} }
/* While we can't get window position from the compositor, we do at least know
* what monitor we're on, so let's send move events that put the window at the
* center of the whatever display the wl_surface_listener events give us.
*/
static void Wayland_move_window(SDL_Window *window, SDL_DisplayData *driverdata)
{
SDL_WindowData *wind = window->driverdata;
SDL_DisplayID *displays;
int i;
displays = SDL_GetDisplays(NULL);
if (displays) {
for (i = 0; displays[i]; ++i) {
if (SDL_GetDisplayDriverData(displays[i]) == driverdata) {
/* We want to send a very very specific combination here:
*
* 1. A coordinate that tells the application what display we're on
* 2. Exactly (0, 0)
*
* Part 1 is useful information but is also really important for
* ensuring we end up on the right display for fullscreen, while
* part 2 is important because numerous applications use a specific
* combination of GetWindowPosition and GetGlobalMouseState, and of
* course neither are supported by Wayland. Since global mouse will
* fall back to just GetMouseState, we need the window position to
* be zero so the cursor math works without it going off in some
* random direction. See UE5 Editor for a notable example of this!
*
* This may be an issue some day if we're ever able to implement
* SDL_GetDisplayUsableBounds!
*
* -flibit
*/
SDL_Rect bounds;
wind->last_displayID = displays[i];
if (wind->shell_surface_type != WAYLAND_SURFACE_XDG_POPUP) {
/* Need to catch up on fullscreen state here, as the video core may try to update
* the fullscreen window, which on Wayland involves a set fullscreen call, which
* can overwrite older pending state.
*/
FlushFullscreenEvents(window);
SDL_GetDisplayBounds(wind->last_displayID, &bounds);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, bounds.x, bounds.y);
}
break;
}
}
SDL_free(displays);
}
}
void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *display_data) void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *display_data)
{ {
SDL_bool send_move_event = SDL_FALSE;
for (int i = 0; i < window->num_outputs; i++) { for (int i = 0; i < window->num_outputs; i++) {
if (window->outputs[i] == display_data) { /* remove this one */ if (window->outputs[i] == display_data) { /* remove this one */
if (i == (window->num_outputs - 1)) { if (i == (window->num_outputs - 1)) {
window->outputs[i] = NULL; window->outputs[i] = NULL;
send_move_event = SDL_TRUE;
} else { } else {
SDL_memmove(&window->outputs[i], SDL_memmove(&window->outputs[i],
&window->outputs[i + 1], &window->outputs[i + 1],
@ -1324,12 +1325,10 @@ void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *dis
if (window->num_outputs == 0) { if (window->num_outputs == 0) {
SDL_free(window->outputs); SDL_free(window->outputs);
window->outputs = NULL; window->outputs = NULL;
} else if (send_move_event) { } else if (!window->is_fullscreen || window->num_outputs == 1) {
Wayland_move_window(window->sdlwindow, Wayland_move_window(window->sdlwindow);
window->outputs[window->num_outputs - 1]); Wayland_MaybeUpdateScaleFactor(window);
} }
Wayland_MaybeUpdateScaleFactor(window);
} }
static void handle_surface_enter(void *data, struct wl_surface *surface, struct wl_output *output) static void handle_surface_enter(void *data, struct wl_surface *surface, struct wl_output *output)
@ -1351,8 +1350,10 @@ static void handle_surface_enter(void *data, struct wl_surface *surface, struct
window->outputs[window->num_outputs++] = driverdata; window->outputs[window->num_outputs++] = driverdata;
/* Update the scale factor after the move so that fullscreen outputs are updated. */ /* Update the scale factor after the move so that fullscreen outputs are updated. */
Wayland_move_window(window->sdlwindow, driverdata); if (!window->is_fullscreen || window->num_outputs == 1) {
Wayland_MaybeUpdateScaleFactor(window); Wayland_move_window(window->sdlwindow);
Wayland_MaybeUpdateScaleFactor(window);
}
} }
static void handle_surface_leave(void *data, struct wl_surface *surface, struct wl_output *output) static void handle_surface_leave(void *data, struct wl_surface *surface, struct wl_output *output)