Repository: lutris/lutris

Issue:
Add "turn off dpms/screensaver when game is running" function
This is a feature request, not an actual issue.

When I'm playing a game on lutris/wine using a controller, my screen turns off after 600 seconds, because DPMS/screensaver is kicking in. 
This does not happen for example when I use steam/proton. I'm assuming they automatically turn off dpms+screensavers.

It would be great to have a toggle under "System options" that disables dpms/screensaver when a game is running.

The commands behind the suggested function could be:
```
/usr/bin/xset s off -dpms     # to turn off dpms/screensaver
/usr/bin/xset s on +dpms   # enable dpms/screensaver
```



Discussion:
This annoyed the shit out of me when playing some games with my controller.
I am going to add this to 0.5.4 milestone because I fell like it's pretty a pretty important.
Note: this should be implemented as an option that's enabled by default and not as default and only behavior because we have installers for game launchers (Origin, Uplay, EGS, etc) and we don't to prevent screensaver/autosuspend when there's only a game launcher running and not an actual game.
This feature should take into consideration to not enable dpms/screensaver if it was not enabled before game launch, it should save the state of the setting and restore it after game is closed, not blindly assume the user had the dpms/screensaver enabled.
I agree with @AlexanderRavenheart and also I'm not sure if the xset command is the universal way to set / unset the screensaver. We had a similar feature a while ago and had to be removed  because it wasn't reliable enough. Let's make sure it get properly implemented this time.
Perhaps source code of this Gnome Shell extension that disables suspend could help implement this properly: https://github.com/eonpatapon/gnome-shell-extension-caffeine
I've learned today that Feral gamemode does screensaver inhibit by default.
The code is  here [https://github.com/FeralInteractive/gamemode/blob/master/daemon/gamemode-dbus.c](https://github.com/FeralInteractive/gamemode/blob/master/daemon/gamemode-dbus.c) at lines 706-783.
They are using the "org.freedesktop.ScreenSaver" interface. Maybe this is already enough for devs to implement in Lutris?

Another interesting link discussing this topic/issue is here:
[https://stackoverflow.com/questions/460140/is-there-a-decent-way-to-inhibit-screensavers-in-linux](https://stackoverflow.com/questions/460140/is-there-a-decent-way-to-inhibit-screensavers-in-linux)

I have these python scripts that work quite well, could be integrated in Lutris when launching a game:

```
#!/usr/bin/env python

import dbus
import time
import signal, sys

if len(sys.argv) != 3 :
        sys.stderr.write('Usage: pm-inhibit.py <appname> <reason>\n')
        sys.exit(1)

bus = dbus.Bus(dbus.Bus.TYPE_SESSION)
devobj = bus.get_object('org.freedesktop.ScreenSaver', '/org/freedesktop/ScreenSaver')
dev = dbus.Interface(devobj, "org.freedesktop.ScreenSaver")
cookie = dev.Inhibit(sys.argv[1], sys.argv[2])

while 1 == 1 :
        time.sleep(30)

dev.UnInhibit(cookie)
```

or

```
#!/usr/bin/env python

import dbus
import time
import signal, sys
import subprocess

if len(sys.argv) <= 3 :
        sys.stderr.write('Usage: pm-inhibit-then-launch.py <appname> <reason> <app> [<args>]\n')
        sys.exit(1)

bus = dbus.Bus(dbus.Bus.TYPE_SESSION)
devobj = bus.get_object('org.freedesktop.ScreenSaver', '/org/freedesktop/ScreenSaver')
dev = dbus.Interface(devobj, "org.freedesktop.ScreenSaver")
cookie = dev.Inhibit(sys.argv[1], sys.argv[2])

popen = subprocess.Popen(sys.argv[3:], stdout=subprocess.PIPE)
popen.wait()

dev.UnInhibit(cookie)
```

Resolving patch:
diff --git a/lutris/game.py b/lutris/game.py
--- a/lutris/game.py
+++ b/lutris/game.py
@@ -19,7 +19,9 @@
 from lutris.runners import InvalidRunner, import_runner, wine
 from lutris.settings import DEFAULT_DISCORD_CLIENT_ID
 from lutris.util import audio, jobs, strings, system, xdgshortcuts
-from lutris.util.display import DISPLAY_MANAGER, disable_compositing, enable_compositing, restore_gamma
+from lutris.util.display import (
+    DISPLAY_MANAGER, SCREEN_SAVER_INHIBITOR, disable_compositing, enable_compositing, restore_gamma
+)
 from lutris.util.graphics.xrandr import turn_off_except
 from lutris.util.linux import LINUX_SYSTEM
 from lutris.util.log import logger
@@ -90,6 +92,7 @@ def __init__(self, game_id=None):
         self.original_outputs = None
         self._log_buffer = None
         self.timer = Timer()
+        self.screen_saver_inhibitor_cookie = None
 
     def __repr__(self):
         return self.__str__()
@@ -518,6 +521,9 @@ def configure_game(self, prelaunched, error=None):  # noqa: C901
         if self.runner.system_config.get("disable_compositor"):
             self.set_desktop_compositing(False)
 
+        if self.runner.system_config.get("disable_screen_saver"):
+            self.screen_saver_inhibitor_cookie = SCREEN_SAVER_INHIBITOR.inhibit(self.name)
+
         if self.runner.system_config.get("display") != "off":
             self.resolution_changed = self.restrict_to_display(self.runner.system_config.get("display"))
 
@@ -653,6 +659,10 @@ def on_game_quit(self):
         if self.compositor_disabled:
             self.set_desktop_compositing(True)
 
+        if self.screen_saver_inhibitor_cookie is not None:
+            SCREEN_SAVER_INHIBITOR.uninhibit(self.screen_saver_inhibitor_cookie)
+            self.screen_saver_inhibitor_cookie = None
+
         if self.runner.system_config.get("use_us_layout"):
             subprocess.Popen(["setxkbmap"], env=os.environ).communicate()
 
diff --git a/lutris/sysoptions.py b/lutris/sysoptions.py
--- a/lutris/sysoptions.py
+++ b/lutris/sysoptions.py
@@ -10,7 +10,7 @@
 from lutris import runners
 from lutris.discord import DiscordPresence
 from lutris.util import system
-from lutris.util.display import DISPLAY_MANAGER, USE_DRI_PRIME
+from lutris.util.display import DISPLAY_MANAGER, SCREEN_SAVER_INHIBITOR, USE_DRI_PRIME
 
 VULKAN_DATA_DIRS = [
     "/usr/local/etc/vulkan",  # standard site-local location
@@ -156,6 +156,17 @@ def get_vk_icd_choices():
         "help": _("Disable desktop effects while game is running, "
                   "reducing stuttering and increasing performance"),
     },
+    {
+        "option": "disable_screen_saver",
+        "label": _("Disable screen saver"),
+        "type": "bool",
+        "default": SCREEN_SAVER_INHIBITOR is not None,
+        "advanced": False,
+        "condition": SCREEN_SAVER_INHIBITOR is not None,
+        "help": _("Disable the screen saver while a game is running. "
+                  "Requires the screen saver's functionality "
+                  "to be exposed over DBus."),
+    },
     {
         "option": "reset_pulse",
         "type": "bool",
diff --git a/lutris/util/display.py b/lutris/util/display.py
--- a/lutris/util/display.py
+++ b/lutris/util/display.py
@@ -10,7 +10,7 @@
 except ImportError:
     DBUS_AVAILABLE = False
 
-from gi.repository import Gdk, GLib, GnomeDesktop
+from gi.repository import Gdk, GLib, GnomeDesktop, Gio
 
 from lutris.util import system
 from lutris.util.graphics.displayconfig import MutterDisplayManager
@@ -263,3 +263,42 @@ def enable_compositing():
     start_compositor, _ = _get_compositor_commands()
     if start_compositor:
         _run_command(*start_compositor)
+
+
+class DBusScreenSaverInhibitor:
+
+    """Inhibit and uninhibit the screen saver using DBus.
+    Requires the Inhibit() and UnInhibit() methods to be exposed over DBus."""
+
+    def __init__(self, name, path, interface, bus_type=Gio.BusType.SESSION):
+        self.proxy = Gio.DBusProxy.new_for_bus_sync(
+            bus_type, Gio.DBusProxyFlags.NONE, None, name, path, interface, None)
+
+    def inhibit(self, game_name):
+        """Inhibit the screen saver.
+        Returns a cookie that must be passed to the corresponding uninhibit() call."""
+        return self.proxy.Inhibit("(ss)", "Lutris", "Running game: %s" % game_name)
+
+    def uninhibit(self, cookie):
+        self.proxy.UnInhibit("(u)", cookie)
+
+
+def _get_screen_saver_inhibitor():
+    """Return the appropriate screen saver inhibitor instance.
+    Returns None if the required interface isn't available."""
+    desktop_environment = get_desktop_environment()
+    if desktop_environment is DesktopEnvironment.MATE:
+        name = "org.mate.ScreenSaver"
+        path = "/"
+    else:
+        name = "org.freedesktop.ScreenSaver"
+        path = "/org/freedesktop/ScreenSaver"
+    interface = name
+    try:
+        return DBusScreenSaverInhibitor(name, path, interface)
+    except GLib.Error as err:
+        logger.error("Error during creation of DBusScreenSaverInhibitor: %s", err.message)
+        return None
+
+
+SCREEN_SAVER_INHIBITOR = _get_screen_saver_inhibitor()


Test patch: