Commit bd553ea868d1babfff9b3d2fc97ab7949ddb2e4d

Cameron Gutman 2021-01-20T21:17:20

Implement support for inhibiting the screensaver on Wayland We support both the org.freedesktop.ScreenSaver D-Bus API (same as the X11 backend) and the Wayland idle_inhibit_unstable_v1 protocol. Some Wayland compositors only support one or the other, so we need both to for broad compatibility.

diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index e5be532..addd006 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -50,6 +50,7 @@
 #include "xdg-decoration-unstable-v1-client-protocol.h"
 #include "org-kde-kwin-server-decoration-manager-client-protocol.h"
 #include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
+#include "idle-inhibit-unstable-v1-client-protocol.h"
 
 #define WAYLANDVID_DRIVER_NAME "wayland"
 
@@ -179,6 +180,7 @@ Wayland_CreateDevice(int devindex)
     device->SetDisplayMode = Wayland_SetDisplayMode;
     device->GetDisplayModes = Wayland_GetDisplayModes;
     device->GetWindowWMInfo = Wayland_GetWindowWMInfo;
+    device->SuspendScreenSaver = Wayland_SuspendScreenSaver;
 
     device->PumpEvents = Wayland_PumpEvents;
 
@@ -397,6 +399,8 @@ display_handle_global(void *data, struct wl_registry *registry, uint32_t id,
         Wayland_display_add_pointer_constraints(d, id);
     } else if (strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) {
         d->key_inhibitor_manager = wl_registry_bind(d->registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
+    } else if (strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0) {
+        d->idle_inhibit_manager = wl_registry_bind(d->registry, id, &zwp_idle_inhibit_manager_v1_interface, 1);
     } else if (strcmp(interface, "wl_data_device_manager") == 0) {
         d->data_device_manager = wl_registry_bind(d->registry, id, &wl_data_device_manager_interface, SDL_min(3, version));
     } else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0) {
@@ -456,6 +460,10 @@ Wayland_VideoInit(_THIS)
 
     WAYLAND_wl_display_flush(data->display);
 
+#if SDL_USE_LIBDBUS
+    SDL_DBus_Init();
+#endif
+
     return 0;
 }
 
@@ -497,6 +505,9 @@ Wayland_VideoQuit(_THIS)
     Wayland_display_destroy_pointer_constraints(data);
     Wayland_display_destroy_relative_pointer_manager(data);
 
+    if (data->idle_inhibit_manager)
+        zwp_idle_inhibit_manager_v1_destroy(data->idle_inhibit_manager);
+
     if (data->key_inhibitor_manager)
         zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(data->key_inhibitor_manager);
 
@@ -535,6 +546,12 @@ Wayland_VideoQuit(_THIS)
     if (data->registry)
         wl_registry_destroy(data->registry);
 
+/* !!! FIXME: other subsystems use D-Bus, so we shouldn't quit it here;
+       have SDL.c do this at a higher level, or add refcounting. */
+#if SDL_USE_LIBDBUS
+    SDL_DBus_Quit();
+#endif
+
     SDL_free(data->classname);
 }
 
diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h
index 99e3c01..ea7e140 100644
--- a/src/video/wayland/SDL_waylandvideo.h
+++ b/src/video/wayland/SDL_waylandvideo.h
@@ -37,6 +37,8 @@
 #include <EGL/egl.h>
 #include "wayland-util.h"
 
+#include "../../core/linux/SDL_dbus.h"
+
 struct xkb_context;
 struct SDL_WaylandInput;
 
@@ -65,6 +67,7 @@ typedef struct {
     struct zxdg_decoration_manager_v1 *decoration_manager;
     struct org_kde_kwin_server_decoration_manager *kwin_server_decoration_manager;
     struct zwp_keyboard_shortcuts_inhibit_manager_v1 *key_inhibitor_manager;
+    struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager;
 
     EGLDisplay edpy;
     EGLContext context;
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index cba97fd..9f9cf6c 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -37,6 +37,7 @@
 #include "xdg-shell-unstable-v6-client-protocol.h"
 #include "xdg-decoration-unstable-v1-client-protocol.h"
 #include "org-kde-kwin-server-decoration-manager-client-protocol.h"
+#include "idle-inhibit-unstable-v1-client-protocol.h"
 
 static float get_window_scale_factor(SDL_Window *window) {
       return ((SDL_WindowData*)window->driverdata)->scale_factor;
@@ -838,6 +839,9 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
         }
     }
 
+    /* We may need to create an idle inhibitor for this new window */
+    Wayland_SuspendScreenSaver(_this);
+
     return 0;
 }
 
@@ -916,6 +920,44 @@ void Wayland_SetWindowTitle(_THIS, SDL_Window * window)
     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
 }
 
+void
+Wayland_SuspendScreenSaver(_THIS)
+{
+    SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
+
+#if SDL_USE_LIBDBUS
+    if (SDL_DBus_ScreensaverInhibit(_this->suspend_screensaver)) {
+        return;
+    }
+#endif
+
+    /* The idle_inhibit_unstable_v1 protocol suspends the screensaver
+       on a per wl_surface basis, but SDL assumes that suspending
+       the screensaver can be done independently of any window.
+       
+       To reconcile these differences, we propagate the idle inhibit
+       state to each window. If there is no window active, we will
+       be able to inhibit idle once the first window is created.
+    */
+    if (data->idle_inhibit_manager) {
+        SDL_Window *window = _this->windows;
+        while (window) {
+            SDL_WindowData *win_data = window->driverdata;
+
+            if (_this->suspend_screensaver && !win_data->idle_inhibitor) {
+                win_data->idle_inhibitor =
+                    zwp_idle_inhibit_manager_v1_create_inhibitor(data->idle_inhibit_manager,
+                                                                 win_data->surface);
+            } else if (!_this->suspend_screensaver && win_data->idle_inhibitor) {
+                zwp_idle_inhibitor_v1_destroy(win_data->idle_inhibitor);
+                win_data->idle_inhibitor = NULL;
+            }
+
+            window = window->next;
+        }
+    }
+}
+
 void Wayland_DestroyWindow(_THIS, SDL_Window *window)
 {
     SDL_VideoData *data = _this->driverdata;
@@ -937,6 +979,10 @@ void Wayland_DestroyWindow(_THIS, SDL_Window *window)
             org_kde_kwin_server_decoration_release(wind->kwin_server_decoration);
         }
 
+        if (wind->idle_inhibitor) {
+            zwp_idle_inhibitor_v1_destroy(wind->idle_inhibitor);
+        }
+
         if (data->shell.xdg) {
             if (wind->shell_surface.xdg.roleobj.toplevel) {
                 xdg_toplevel_destroy(wind->shell_surface.xdg.roleobj.toplevel);
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 4a7472c..88a9768 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -66,6 +66,7 @@ typedef struct {
     struct zxdg_toplevel_decoration_v1 *server_decoration;
     struct org_kde_kwin_server_decoration *kwin_server_decoration;
     struct zwp_keyboard_shortcuts_inhibitor_v1 *key_inhibitor;
+    struct zwp_idle_inhibitor_v1 *idle_inhibitor;
 
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
     struct qt_extended_surface *extended_surface;
@@ -97,6 +98,7 @@ extern int Wayland_CreateWindow(_THIS, SDL_Window *window);
 extern void Wayland_SetWindowSize(_THIS, SDL_Window * window);
 extern void Wayland_SetWindowTitle(_THIS, SDL_Window * window);
 extern void Wayland_DestroyWindow(_THIS, SDL_Window *window);
+extern void Wayland_SuspendScreenSaver(_THIS);
 
 extern SDL_bool
 Wayland_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info);
diff --git a/wayland-protocols/idle-inhibit-unstable-v1.xml b/wayland-protocols/idle-inhibit-unstable-v1.xml
new file mode 100644
index 0000000..9c06cdc
--- /dev/null
+++ b/wayland-protocols/idle-inhibit-unstable-v1.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="idle_inhibit_unstable_v1">
+
+  <copyright>
+    Copyright © 2015 Samsung Electronics Co., Ltd
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <interface name="zwp_idle_inhibit_manager_v1" version="1">
+    <description summary="control behavior when display idles">
+      This interface permits inhibiting the idle behavior such as screen
+      blanking, locking, and screensaving.  The client binds the idle manager
+      globally, then creates idle-inhibitor objects for each surface.
+
+      Warning! The protocol described in this file is experimental and
+      backward incompatible changes may be made. Backward compatible changes
+      may be added together with the corresponding interface version bump.
+      Backward incompatible changes are done by bumping the version number in
+      the protocol and interface names and resetting the interface version.
+      Once the protocol is to be declared stable, the 'z' prefix and the
+      version number in the protocol and interface names are removed and the
+      interface version number is reset.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the idle inhibitor object">
+	Destroy the inhibit manager.
+      </description>
+    </request>
+
+    <request name="create_inhibitor">
+      <description summary="create a new inhibitor object">
+	Create a new inhibitor object associated with the given surface.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_idle_inhibitor_v1"/>
+      <arg name="surface" type="object" interface="wl_surface"
+	   summary="the surface that inhibits the idle behavior"/>
+    </request>
+
+  </interface>
+
+  <interface name="zwp_idle_inhibitor_v1" version="1">
+    <description summary="context object for inhibiting idle behavior">
+      An idle inhibitor prevents the output that the associated surface is
+      visible on from being set to a state where it is not visually usable due
+      to lack of user interaction (e.g. blanked, dimmed, locked, set to power
+      save, etc.)  Any screensaver processes are also blocked from displaying.
+
+      If the surface is destroyed, unmapped, becomes occluded, loses
+      visibility, or otherwise becomes not visually relevant for the user, the
+      idle inhibitor will not be honored by the compositor; if the surface
+      subsequently regains visibility the inhibitor takes effect once again.
+      Likewise, the inhibitor isn't honored if the system was already idled at
+      the time the inhibitor was established, although if the system later
+      de-idles and re-idles the inhibitor will take effect.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the idle inhibitor object">
+	Remove the inhibitor effect from the associated wl_surface.
+      </description>
+    </request>
+
+  </interface>
+</protocol>