mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-02-10 22:08:38 +00:00
Add and event and flag to report when a window has been occluded
Adds the SDL_EVENT_WINDOW_OCCLUDED events and the window flag SDL_WINDOW_OCCLUDED to report when the window occlusion state has changed, so that the application can take appropriate measures, as it may wish to suspend drawing, throttle, or otherwise behave in a more energy efficient manner when the window is not visible. When the window is no longer occluded, the SDL_EVENT_WINDOW_EXPOSED event is sent and the occlusion flag is cleared. This is handled on macOS via the window occlusion state event (available as of 10.9), and via the xdg-shell protocol on Wayland (version 6, wayland-protocols 1.32, passed through in libdecor 0.1.2).
This commit is contained in:
@@ -253,6 +253,7 @@ static void SDL_LogEvent(const SDL_Event *event)
|
||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_ICCPROF_CHANGED);
|
||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DISPLAY_CHANGED);
|
||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED);
|
||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_OCCLUDED);
|
||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DESTROYED);
|
||||
#undef SDL_WINDOWEVENT_CASE
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent,
|
||||
}
|
||||
window->flags |= SDL_WINDOW_HIDDEN;
|
||||
break;
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
window->flags &= ~SDL_WINDOW_OCCLUDED;
|
||||
break;
|
||||
case SDL_EVENT_WINDOW_MOVED:
|
||||
window->undefined_x = SDL_FALSE;
|
||||
window->undefined_y = SDL_FALSE;
|
||||
@@ -144,6 +147,12 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent,
|
||||
}
|
||||
window->last_displayID = (SDL_DisplayID)data1;
|
||||
break;
|
||||
case SDL_EVENT_WINDOW_OCCLUDED:
|
||||
if (window->flags & SDL_WINDOW_OCCLUDED) {
|
||||
return 0;
|
||||
}
|
||||
window->flags |= SDL_WINDOW_OCCLUDED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -162,7 +171,8 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent,
|
||||
if (windowevent == SDL_EVENT_WINDOW_MOVED ||
|
||||
windowevent == SDL_EVENT_WINDOW_RESIZED ||
|
||||
windowevent == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
|
||||
windowevent == SDL_EVENT_WINDOW_EXPOSED) {
|
||||
windowevent == SDL_EVENT_WINDOW_EXPOSED ||
|
||||
windowevent == SDL_EVENT_WINDOW_OCCLUDED) {
|
||||
SDL_FilterEvents(RemoveSupercededWindowEvents, &event);
|
||||
}
|
||||
posted = (SDL_PushEvent(&event) > 0);
|
||||
|
||||
@@ -562,6 +562,14 @@ static void Cocoa_SetKeyboardFocus(SDL_Window *window)
|
||||
SDL_SetKeyboardFocus(window);
|
||||
}
|
||||
|
||||
static void Cocoa_SendExposedEventIfVisible(SDL_Window *window)
|
||||
{
|
||||
NSWindow *nswindow = ((__bridge SDL_CocoaWindowData*)window->driverdata).nswindow;
|
||||
if ([nswindow occlusionState] & NSWindowOcclusionStateVisible) {
|
||||
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@implementation Cocoa_WindowListener
|
||||
|
||||
- (void)listen:(SDL_CocoaWindowData *)data
|
||||
@@ -600,6 +608,7 @@ static void Cocoa_SetKeyboardFocus(SDL_Window *window)
|
||||
[center addObserver:self selector:@selector(windowDidExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:window];
|
||||
[center addObserver:self selector:@selector(windowDidFailToEnterFullScreen:) name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
|
||||
[center addObserver:self selector:@selector(windowDidFailToExitFullScreen:) name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
|
||||
[center addObserver:self selector:@selector(windowDidChangeOcclusionState:) name:NSWindowDidChangeOcclusionStateNotification object:window];
|
||||
} else {
|
||||
[window setDelegate:self];
|
||||
}
|
||||
@@ -733,6 +742,7 @@ static void Cocoa_SetKeyboardFocus(SDL_Window *window)
|
||||
[center removeObserver:self name:NSWindowDidExitFullScreenNotification object:window];
|
||||
[center removeObserver:self name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
|
||||
[center removeObserver:self name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
|
||||
[center removeObserver:self name:NSWindowDidChangeOcclusionStateNotification object:window];
|
||||
} else {
|
||||
[window setDelegate:nil];
|
||||
}
|
||||
@@ -825,7 +835,16 @@ static void Cocoa_SetKeyboardFocus(SDL_Window *window)
|
||||
|
||||
- (void)windowDidExpose:(NSNotification *)aNotification
|
||||
{
|
||||
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
Cocoa_SendExposedEventIfVisible(_data.window);
|
||||
}
|
||||
|
||||
- (void)windowDidChangeOcclusionState:(NSNotification *)aNotification
|
||||
{
|
||||
if ([_data.nswindow occlusionState] & NSWindowOcclusionStateVisible) {
|
||||
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
} else {
|
||||
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowWillMove:(NSNotification *)aNotification
|
||||
@@ -1669,7 +1688,7 @@ static int Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL_
|
||||
self.layer.backgroundColor = CGColorGetConstantColor(color);
|
||||
}
|
||||
|
||||
SDL_SendWindowEvent(_sdlWindow, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
Cocoa_SendExposedEventIfVisible(_sdlWindow);
|
||||
}
|
||||
|
||||
- (BOOL)wantsUpdateLayer
|
||||
@@ -1687,7 +1706,7 @@ static int Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL_
|
||||
CFStringRef color = transparent ? kCGColorClear : kCGColorBlack;
|
||||
self.layer.backgroundColor = CGColorGetConstantColor(color);
|
||||
ScheduleContextUpdates((__bridge SDL_CocoaWindowData *)_sdlWindow->driverdata);
|
||||
SDL_SendWindowEvent(_sdlWindow, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
Cocoa_SendExposedEventIfVisible(_sdlWindow);
|
||||
}
|
||||
|
||||
- (void)rightMouseDown:(NSEvent *)theEvent
|
||||
|
||||
@@ -218,7 +218,7 @@ SDL_WAYLAND_SYM(bool, libdecor_configuration_get_window_state, (struct libdecor_
|
||||
enum libdecor_window_state *))
|
||||
SDL_WAYLAND_SYM(int, libdecor_dispatch, (struct libdecor *, int))
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR) || defined(SDL_HAVE_LIBDECOR_GET_MIN_MAX)
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR) || defined(SDL_HAVE_LIBDECOR_VER_0_1_2)
|
||||
/* Only found in libdecor 0.1.1 or higher, so failure to load them is not fatal. */
|
||||
SDL_WAYLAND_SYM_OPT(void, libdecor_frame_get_min_content_size, (struct libdecor_frame *,\
|
||||
int *,\
|
||||
|
||||
@@ -752,7 +752,7 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
|
||||
} else if (SDL_strcmp(interface, "wl_seat") == 0) {
|
||||
Wayland_display_add_input(d, id, version);
|
||||
} else if (SDL_strcmp(interface, "xdg_wm_base") == 0) {
|
||||
d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, SDL_min(version, 3));
|
||||
d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, SDL_min(version, 6));
|
||||
xdg_wm_base_add_listener(d->shell.xdg, &shell_listener_xdg, NULL);
|
||||
} else if (SDL_strcmp(interface, "wl_shm") == 0) {
|
||||
d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
|
||||
|
||||
@@ -376,6 +376,23 @@ static void ConfigureWindowGeometry(SDL_Window *window)
|
||||
/* Unconditionally send the window and drawable size, the video core will deduplicate when required. */
|
||||
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window_width, window_height);
|
||||
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->drawable_width, data->drawable_height);
|
||||
|
||||
/* Send an exposure event if the window is in the shown state and the size has changed,
|
||||
* even if the window is occluded, as the client needs to commit a new frame for the
|
||||
* changes to take effect.
|
||||
*
|
||||
* The occlusion state is immediately set again afterward, if necessary.
|
||||
*/
|
||||
if (data->surface_status == WAYLAND_SURFACE_STATUS_SHOWN) {
|
||||
if ((drawable_size_changed || window_size_changed) ||
|
||||
(!data->suspended && (window->flags & SDL_WINDOW_OCCLUDED))) {
|
||||
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
}
|
||||
|
||||
if (data->suspended) {
|
||||
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void EnsurePopupPositionIsValid(SDL_Window *window)
|
||||
@@ -535,14 +552,13 @@ static const struct wl_callback_listener surface_frame_listener;
|
||||
|
||||
static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time)
|
||||
{
|
||||
SDL_Window *w;
|
||||
SDL_WindowData *wind = (SDL_WindowData *)data;
|
||||
|
||||
/*
|
||||
* wl_surface.damage_buffer is the preferred method of setting the damage region
|
||||
* on compositor version 4 and above.
|
||||
*/
|
||||
if (wl_compositor_get_version(wind->waylandData->compositor) >= 4) {
|
||||
if (wl_compositor_get_version(wind->waylandData->compositor) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
|
||||
wl_surface_damage_buffer(wind->surface, 0, 0,
|
||||
wind->drawable_width, wind->drawable_height);
|
||||
} else {
|
||||
@@ -554,11 +570,18 @@ static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time
|
||||
wind->surface_status = WAYLAND_SURFACE_STATUS_SHOWN;
|
||||
|
||||
/* If any child windows are waiting on this window to be shown, show them now */
|
||||
for (w = wind->sdlwindow->first_child; w != NULL; w = w->next_sibling) {
|
||||
for (SDL_Window *w = wind->sdlwindow->first_child; w != NULL; w = w->next_sibling) {
|
||||
if (w->driverdata->surface_status == WAYLAND_SURFACE_STATUS_SHOW_PENDING) {
|
||||
Wayland_ShowWindow(SDL_GetVideoDevice(), w);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the window was initially set to the suspended state, send the occluded event now,
|
||||
* as we don't want to mark the window as occluded until at least one frame has been submitted.
|
||||
*/
|
||||
if (wind->suspended) {
|
||||
SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_OCCLUDED, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
wl_callback_destroy(cb);
|
||||
@@ -616,6 +639,7 @@ static void handle_configure_xdg_toplevel(void *data,
|
||||
SDL_bool maximized = SDL_FALSE;
|
||||
SDL_bool floating = SDL_TRUE;
|
||||
SDL_bool focused = SDL_FALSE;
|
||||
SDL_bool suspended = SDL_FALSE;
|
||||
wl_array_for_each (state, states) {
|
||||
switch (*state) {
|
||||
case XDG_TOPLEVEL_STATE_FULLSCREEN:
|
||||
@@ -635,6 +659,8 @@ static void handle_configure_xdg_toplevel(void *data,
|
||||
case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
|
||||
floating = SDL_FALSE;
|
||||
break;
|
||||
case XDG_TOPLEVEL_STATE_SUSPENDED:
|
||||
suspended = SDL_TRUE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -721,6 +747,7 @@ static void handle_configure_xdg_toplevel(void *data,
|
||||
wind->requested_window_width = width;
|
||||
wind->requested_window_height = height;
|
||||
wind->floating = floating;
|
||||
wind->suspended = suspended;
|
||||
if (wind->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
|
||||
wind->surface_status = WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME;
|
||||
}
|
||||
@@ -732,9 +759,25 @@ static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_tople
|
||||
SDL_SendWindowEvent(window->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
|
||||
}
|
||||
|
||||
static void handle_xdg_configure_toplevel_bounds(void *data,
|
||||
struct xdg_toplevel *xdg_toplevel,
|
||||
int32_t width, int32_t height)
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
void handle_xdg_toplevel_wm_capabilities(void *data,
|
||||
struct xdg_toplevel *xdg_toplevel,
|
||||
struct wl_array *capabilities)
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
static const struct xdg_toplevel_listener toplevel_listener_xdg = {
|
||||
handle_configure_xdg_toplevel,
|
||||
handle_close_xdg_toplevel
|
||||
handle_close_xdg_toplevel,
|
||||
handle_xdg_configure_toplevel_bounds, /* Version 4 */
|
||||
handle_xdg_toplevel_wm_capabilities /* Version 5 */
|
||||
};
|
||||
|
||||
static void handle_configure_xdg_popup(void *data,
|
||||
@@ -824,11 +867,11 @@ static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = {
|
||||
* minimum content size limit. The internal limits must always be overridden
|
||||
* to ensure that very small windows don't cause errors or crashes.
|
||||
*
|
||||
* On versions of libdecor that expose the function to get the minimum content
|
||||
* On libdecor >= 0.1.2, which exposes the function to get the minimum content
|
||||
* size limit, this function is a no-op.
|
||||
*
|
||||
* Can be removed if the minimum required version of libdecor is raised
|
||||
* to a version that guarantees the availability of this function.
|
||||
* Can be removed if the minimum required version of libdecor is raised to
|
||||
* 0.1.2 or higher.
|
||||
*/
|
||||
static void OverrideLibdecorLimits(SDL_Window *window)
|
||||
{
|
||||
@@ -836,7 +879,7 @@ static void OverrideLibdecorLimits(SDL_Window *window)
|
||||
if (libdecor_frame_get_min_content_size == NULL) {
|
||||
libdecor_frame_set_min_content_size(window->driverdata->shell_surface.libdecor.frame, window->min_w, window->min_h);
|
||||
}
|
||||
#elif !defined(SDL_HAVE_LIBDECOR_GET_MIN_MAX)
|
||||
#elif !defined(SDL_HAVE_LIBDECOR_VER_0_1_2)
|
||||
libdecor_frame_set_min_content_size(window->driverdata->shell_surface.libdecor.frame, window->min_w, window->min_h);
|
||||
#endif
|
||||
}
|
||||
@@ -847,7 +890,7 @@ static void OverrideLibdecorLimits(SDL_Window *window)
|
||||
* function is a no-op.
|
||||
*
|
||||
* Can be replaced with a direct call if the minimum required version of libdecor is raised
|
||||
* to a version that guarantees the availability of this function.
|
||||
* to 0.1.2 or higher.
|
||||
*/
|
||||
static void LibdecorGetMinContentSize(struct libdecor_frame *frame, int *min_w, int *min_h)
|
||||
{
|
||||
@@ -855,7 +898,7 @@ static void LibdecorGetMinContentSize(struct libdecor_frame *frame, int *min_w,
|
||||
if (libdecor_frame_get_min_content_size != NULL) {
|
||||
libdecor_frame_get_min_content_size(frame, min_w, min_h);
|
||||
}
|
||||
#elif defined(SDL_HAVE_LIBDECOR_GET_MIN_MAX)
|
||||
#elif defined(SDL_HAVE_LIBDECOR_VER_0_1_2)
|
||||
libdecor_frame_get_min_content_size(frame, min_w, min_h);
|
||||
#endif
|
||||
}
|
||||
@@ -876,6 +919,7 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
|
||||
SDL_bool fullscreen = SDL_FALSE;
|
||||
SDL_bool maximized = SDL_FALSE;
|
||||
SDL_bool tiled = SDL_FALSE;
|
||||
SDL_bool suspended = SDL_FALSE;
|
||||
SDL_bool floating;
|
||||
|
||||
static const enum libdecor_window_state tiled_states = (LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT |
|
||||
@@ -887,6 +931,9 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
|
||||
maximized = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) != 0;
|
||||
focused = (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) != 0;
|
||||
tiled = (window_state & tiled_states) != 0;
|
||||
#ifdef SDL_HAVE_LIBDECOR_VER_0_1_2
|
||||
suspended = (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) != 0;
|
||||
#endif
|
||||
}
|
||||
floating = !(fullscreen || maximized || tiled);
|
||||
|
||||
@@ -999,8 +1046,9 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
|
||||
wind->floating_height = height;
|
||||
}
|
||||
|
||||
/* Store the new floating state. */
|
||||
/* Store the new state. */
|
||||
wind->floating = floating;
|
||||
wind->suspended = suspended;
|
||||
|
||||
/* Calculate the new window geometry */
|
||||
wind->requested_window_width = width;
|
||||
@@ -1039,9 +1087,13 @@ static void decoration_frame_close(struct libdecor_frame *frame, void *user_data
|
||||
|
||||
static void decoration_frame_commit(struct libdecor_frame *frame, void *user_data)
|
||||
{
|
||||
SDL_WindowData *wind = user_data;
|
||||
|
||||
SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
/* libdecor decoration subsurfaces are synchronous, so the client needs to
|
||||
* commit a frame to trigger an update of the decoration surfaces.
|
||||
*/
|
||||
SDL_WindowData *wind = (SDL_WindowData *)user_data;
|
||||
if (!wind->suspended && wind->surface_status == WAYLAND_SURFACE_STATUS_SHOWN) {
|
||||
SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static struct libdecor_frame_interface libdecor_frame_interface = {
|
||||
@@ -1565,6 +1617,11 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
|
||||
* HideWindow was called immediately before ShowWindow.
|
||||
*/
|
||||
WAYLAND_wl_display_roundtrip(c->display);
|
||||
|
||||
/* Send an exposure event to signal that the client should draw. */
|
||||
if (data->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME) {
|
||||
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup)
|
||||
|
||||
@@ -122,6 +122,7 @@ struct SDL_WindowData
|
||||
int system_min_required_height;
|
||||
SDL_DisplayID last_displayID;
|
||||
SDL_bool floating;
|
||||
SDL_bool suspended;
|
||||
SDL_bool is_fullscreen;
|
||||
SDL_bool in_fullscreen_transition;
|
||||
SDL_bool fullscreen_was_positioned;
|
||||
|
||||
Reference in New Issue
Block a user