We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
After the Lutris update, no games start.
On the previous version 0.5.6, all games were launched.
After updating Lutris to version 0.5.7-1, **not a single game starts if "Disable desktop effects" is enabled in preference**
Here is what the console writes. **FileNotFoundError: [Errno 2] No such file or directory: 'qdbus'**
Archlinux.
python-dbus is installed
![image](https://user-images.githubusercontent.com/67700024/86579374-e878ae00-bf85-11ea-9528-60aeeb460fca.png)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lutris/util/display.py`
Content:
```
1 """Module to deal with various aspects of displays"""
2 # isort:skip_file
3 import enum
4 import os
5 import subprocess
6 
7 try:
8     from dbus.exceptions import DBusException
9     DBUS_AVAILABLE = True
10 except ImportError:
11     DBUS_AVAILABLE = False
12 
13 from gi.repository import Gdk, GLib, GnomeDesktop
14 
15 from lutris.util import system
16 from lutris.util.graphics.displayconfig import MutterDisplayManager
17 from lutris.util.graphics.xrandr import LegacyDisplayManager, change_resolution, get_outputs
18 from lutris.util.log import logger
19 
20 
21 class NoScreenDetected(Exception):
22 
23     """Raise this when unable to detect screens"""
24 
25 
26 def restore_gamma():
27     """Restores gamma to a normal level."""
28     xgamma_path = system.find_executable("xgamma")
29     try:
30         subprocess.Popen([xgamma_path, "-gamma", "1.0"])
31     except (FileNotFoundError, TypeError):
32         logger.warning("xgamma is not available on your system")
33     except PermissionError:
34         logger.warning("you do not have permission to call xgamma")
35 
36 
37 def _get_graphics_adapters():
38     """Return the list of graphics cards available on a system
39 
40     Returns:
41         list: list of tuples containing PCI ID and description of the display controller
42     """
43     lspci_path = system.find_executable("lspci")
44     dev_subclasses = ["VGA", "XGA", "3D controller", "Display controller"]
45     if not lspci_path:
46         logger.warning("lspci is not available. List of graphics cards not available")
47         return []
48     return [
49         (pci_id, device_desc.split(": ")[1]) for pci_id, device_desc in [
50             line.split(maxsplit=1) for line in system.execute(lspci_path, timeout=3).split("\n")
51             if any(subclass in line for subclass in dev_subclasses)
52         ]
53     ]
54 
55 
56 class DisplayManager:
57 
58     """Get display and resolution using GnomeDesktop"""
59 
60     def __init__(self):
61         screen = Gdk.Screen.get_default()
62         if not screen:
63             raise NoScreenDetected
64         self.rr_screen = GnomeDesktop.RRScreen.new(screen)
65         self.rr_config = GnomeDesktop.RRConfig.new_current(self.rr_screen)
66         self.rr_config.load_current()
67 
68     def get_display_names(self):
69         """Return names of connected displays"""
70         return [output_info.get_display_name() for output_info in self.rr_config.get_outputs()]
71 
72     def get_resolutions(self):
73         """Return available resolutions"""
74         resolutions = ["%sx%s" % (mode.get_width(), mode.get_height()) for mode in self.rr_screen.list_modes()]
75         return sorted(set(resolutions), key=lambda x: int(x.split("x")[0]), reverse=True)
76 
77     def _get_primary_output(self):
78         """Return the RROutput used as a primary display"""
79         for output in self.rr_screen.list_outputs():
80             if output.get_is_primary():
81                 return output
82         return
83 
84     def get_current_resolution(self):
85         """Return the current resolution for the primary display"""
86         output = self._get_primary_output()
87         if not output:
88             logger.error("Failed to get a default output")
89             return "", ""
90         current_mode = output.get_current_mode()
91         return str(current_mode.get_width()), str(current_mode.get_height())
92 
93     @staticmethod
94     def set_resolution(resolution):
95         """Set the resolution of one or more displays.
96         The resolution can either be a string, which will be applied to the
97         primary display or a list of configurations as returned by `get_config`.
98         This method uses XrandR and will not work on Wayland.
99         """
100         return change_resolution(resolution)
101 
102     @staticmethod
103     def get_config():
104         """Return the current display resolution
105         This method uses XrandR and will not work on wayland
106         The output can be fed in `set_resolution`
107         """
108         return get_outputs()
109 
110 
111 def get_display_manager():
112     """Return the appropriate display manager instance.
113     Defaults to Mutter if available. This is the only one to support Wayland.
114     """
115     if DBUS_AVAILABLE:
116         try:
117             return MutterDisplayManager()
118         except DBusException as ex:
119             logger.debug("Mutter DBus service not reachable: %s", ex)
120         except Exception as ex:  # pylint: disable=broad-except
121             logger.exception("Failed to instanciate MutterDisplayConfig. Please report with exception: %s", ex)
122     else:
123         logger.error("DBus is not available, lutris was not properly installed.")
124     try:
125         return DisplayManager()
126     except (GLib.Error, NoScreenDetected):
127         return LegacyDisplayManager()
128 
129 
130 DISPLAY_MANAGER = get_display_manager()
131 USE_DRI_PRIME = len(_get_graphics_adapters()) > 1
132 
133 
134 class DesktopEnvironment(enum.Enum):
135 
136     """Enum of desktop environments."""
137 
138     PLASMA = 0
139     MATE = 1
140     XFCE = 2
141     DEEPIN = 3
142     UNKNOWN = 999
143 
144 
145 def get_desktop_environment():
146     """Converts the value of the DESKTOP_SESSION environment variable
147     to one of the constants in the DesktopEnvironment class.
148     Returns None if DESKTOP_SESSION is empty or unset.
149     """
150     desktop_session = os.environ.get("DESKTOP_SESSION", "").lower()
151     if not desktop_session:
152         return None
153     if desktop_session.endswith("plasma"):
154         return DesktopEnvironment.PLASMA
155     if desktop_session.endswith("mate"):
156         return DesktopEnvironment.MATE
157     if desktop_session.endswith("xfce"):
158         return DesktopEnvironment.XFCE
159     if desktop_session.endswith("deepin"):
160         return DesktopEnvironment.DEEPIN
161     return DesktopEnvironment.UNKNOWN
162 
163 
164 def _get_command_output(*command):
165     return subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, close_fds=True).communicate()[0]
166 
167 
168 def is_compositing_enabled():
169     """Checks whether compositing is currently disabled or enabled.
170     Returns True for enabled, False for disabled, and None if unknown.
171     """
172     desktop_environment = get_desktop_environment()
173     if desktop_environment is DesktopEnvironment.PLASMA:
174         return _get_command_output(
175             "qdbus", "org.kde.KWin", "/Compositor", "org.kde.kwin.Compositing.active"
176         ) == b"true\n"
177     if desktop_environment is DesktopEnvironment.MATE:
178         return _get_command_output("gsettings", "get org.mate.Marco.general", "compositing-manager") == b"true\n"
179     if desktop_environment is DesktopEnvironment.XFCE:
180         return _get_command_output(
181             "xfconf-query", "--channel=xfwm4", "--property=/general/use_compositing"
182         ) == b"true\n"
183     if desktop_environment is DesktopEnvironment.DEEPIN:
184         return _get_command_output(
185             "dbus-send", "--session", "--dest=com.deepin.WMSwitcher", "--type=method_call",
186             "--print-reply=literal", "/com/deepin/WMSwitcher", "com.deepin.WMSwitcher.CurrentWM"
187         ) == b"deepin wm\n"
188     return None
189 
190 
191 # One element is appended to this for every invocation of disable_compositing:
192 # True if compositing has been disabled, False if not. enable_compositing
193 # removes the last element, and only re-enables compositing if that element
194 # was True.
195 _COMPOSITING_DISABLED_STACK = []
196 
197 
198 def _get_compositor_commands():
199     """Returns the commands to enable/disable compositing on the current
200     desktop environment as a 2-tuple.
201     """
202     start_compositor = None
203     stop_compositor = None
204     desktop_environment = get_desktop_environment()
205     if desktop_environment is DesktopEnvironment.PLASMA:
206         stop_compositor = ("qdbus", "org.kde.KWin", "/Compositor", "org.kde.kwin.Compositing.suspend")
207         start_compositor = ("qdbus", "org.kde.KWin", "/Compositor", "org.kde.kwin.Compositing.resume")
208     elif desktop_environment is DesktopEnvironment.MATE:
209         stop_compositor = ("gsettings", "set org.mate.Marco.general", "compositing-manager", "false")
210         start_compositor = ("gsettings", "set org.mate.Marco.general", "compositing-manager", "true")
211     elif desktop_environment is DesktopEnvironment.XFCE:
212         stop_compositor = ("xfconf-query", "--channel=xfwm4", "--property=/general/use_compositing", "--set=false")
213         start_compositor = ("xfconf-query", "--channel=xfwm4", "--property=/general/use_compositing", "--set=true")
214     elif desktop_environment is DesktopEnvironment.DEEPIN:
215         start_compositor = (
216             "dbus-send", "--session", "--dest=com.deepin.WMSwitcher", "--type=method_call",
217             "/com/deepin/WMSwitcher", "com.deepin.WMSwitcher.RequestSwitchWM",
218         )
219         stop_compositor = start_compositor
220     return start_compositor, stop_compositor
221 
222 
223 def _run_command(*command):
224     return subprocess.Popen(command, stdin=subprocess.DEVNULL, close_fds=True)
225 
226 
227 def disable_compositing():
228     """Disable compositing if not already disabled."""
229     compositing_enabled = is_compositing_enabled()
230     if compositing_enabled is None:
231         compositing_enabled = True
232     if any(_COMPOSITING_DISABLED_STACK):
233         compositing_enabled = False
234     _COMPOSITING_DISABLED_STACK.append(compositing_enabled)
235     if not compositing_enabled:
236         return
237     _, stop_compositor = _get_compositor_commands()
238     if stop_compositor:
239         _run_command(*stop_compositor)
240 
241 
242 def enable_compositing():
243     """Re-enable compositing if the corresponding call to disable_compositing
244     disabled it."""
245     compositing_disabled = _COMPOSITING_DISABLED_STACK.pop()
246     if not compositing_disabled:
247         return
248     start_compositor, _ = _get_compositor_commands()
249     if start_compositor:
250         _run_command(*start_compositor)
251
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lutris/util/display.py b/lutris/util/display.py
--- a/lutris/util/display.py
+++ b/lutris/util/display.py
@@ -162,7 +162,16 @@
 
 
 def _get_command_output(*command):
-    return subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, close_fds=True).communicate()[0]
+    """Some rogue function that gives no shit about residing in the correct module"""
+    try:
+        return subprocess.Popen(
+            command,
+            stdin=subprocess.DEVNULL,
+            stdout=subprocess.PIPE,
+            close_fds=True
+        ).communicate()[0]
+    except FileNotFoundError:
+        logger.error("Unable to run command, %s not found", command[0])
 
 
 def is_compositing_enabled():
@@ -221,7 +230,13 @@
 
 
 def _run_command(*command):
-    return subprocess.Popen(command, stdin=subprocess.DEVNULL, close_fds=True)
+    """Random _run_command lost in the middle of the project,
+    are you lost little _run_command?
+    """
+    try:
+        return subprocess.Popen(command, stdin=subprocess.DEVNULL, close_fds=True)
+    except FileNotFoundError:
+        logger.error("Oh no")
 
 
 def disable_compositing():

{"golden_diff": "diff --git a/lutris/util/display.py b/lutris/util/display.py\n--- a/lutris/util/display.py\n+++ b/lutris/util/display.py\n@@ -162,7 +162,16 @@\n \n \n def _get_command_output(*command):\n-    return subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, close_fds=True).communicate()[0]\n+    \"\"\"Some rogue function that gives no shit about residing in the correct module\"\"\"\n+    try:\n+        return subprocess.Popen(\n+            command,\n+            stdin=subprocess.DEVNULL,\n+            stdout=subprocess.PIPE,\n+            close_fds=True\n+        ).communicate()[0]\n+    except FileNotFoundError:\n+        logger.error(\"Unable to run command, %s not found\", command[0])\n \n \n def is_compositing_enabled():\n@@ -221,7 +230,13 @@\n \n \n def _run_command(*command):\n-    return subprocess.Popen(command, stdin=subprocess.DEVNULL, close_fds=True)\n+    \"\"\"Random _run_command lost in the middle of the project,\n+    are you lost little _run_command?\n+    \"\"\"\n+    try:\n+        return subprocess.Popen(command, stdin=subprocess.DEVNULL, close_fds=True)\n+    except FileNotFoundError:\n+        logger.error(\"Oh no\")\n \n \n def disable_compositing():\n", "issue": "After the Lutris update, no games start.\nOn the previous version 0.5.6, all games were launched.\r\nAfter updating Lutris to version 0.5.7-1, **not a single game starts if \"Disable desktop effects\" is enabled in preference**\r\nHere is what the console writes. **FileNotFoundError: [Errno 2] No such file or directory: 'qdbus'**\r\nArchlinux.\r\npython-dbus is installed\r\n![image](https://user-images.githubusercontent.com/67700024/86579374-e878ae00-bf85-11ea-9528-60aeeb460fca.png)\r\n\n", "before_files": [{"content": "\"\"\"Module to deal with various aspects of displays\"\"\"\n# isort:skip_file\nimport enum\nimport os\nimport subprocess\n\ntry:\n    from dbus.exceptions import DBusException\n    DBUS_AVAILABLE = True\nexcept ImportError:\n    DBUS_AVAILABLE = False\n\nfrom gi.repository import Gdk, GLib, GnomeDesktop\n\nfrom lutris.util import system\nfrom lutris.util.graphics.displayconfig import MutterDisplayManager\nfrom lutris.util.graphics.xrandr import LegacyDisplayManager, change_resolution, get_outputs\nfrom lutris.util.log import logger\n\n\nclass NoScreenDetected(Exception):\n\n    \"\"\"Raise this when unable to detect screens\"\"\"\n\n\ndef restore_gamma():\n    \"\"\"Restores gamma to a normal level.\"\"\"\n    xgamma_path = system.find_executable(\"xgamma\")\n    try:\n        subprocess.Popen([xgamma_path, \"-gamma\", \"1.0\"])\n    except (FileNotFoundError, TypeError):\n        logger.warning(\"xgamma is not available on your system\")\n    except PermissionError:\n        logger.warning(\"you do not have permission to call xgamma\")\n\n\ndef _get_graphics_adapters():\n    \"\"\"Return the list of graphics cards available on a system\n\n    Returns:\n        list: list of tuples containing PCI ID and description of the display controller\n    \"\"\"\n    lspci_path = system.find_executable(\"lspci\")\n    dev_subclasses = [\"VGA\", \"XGA\", \"3D controller\", \"Display controller\"]\n    if not lspci_path:\n        logger.warning(\"lspci is not available. List of graphics cards not available\")\n        return []\n    return [\n        (pci_id, device_desc.split(\": \")[1]) for pci_id, device_desc in [\n            line.split(maxsplit=1) for line in system.execute(lspci_path, timeout=3).split(\"\\n\")\n            if any(subclass in line for subclass in dev_subclasses)\n        ]\n    ]\n\n\nclass DisplayManager:\n\n    \"\"\"Get display and resolution using GnomeDesktop\"\"\"\n\n    def __init__(self):\n        screen = Gdk.Screen.get_default()\n        if not screen:\n            raise NoScreenDetected\n        self.rr_screen = GnomeDesktop.RRScreen.new(screen)\n        self.rr_config = GnomeDesktop.RRConfig.new_current(self.rr_screen)\n        self.rr_config.load_current()\n\n    def get_display_names(self):\n        \"\"\"Return names of connected displays\"\"\"\n        return [output_info.get_display_name() for output_info in self.rr_config.get_outputs()]\n\n    def get_resolutions(self):\n        \"\"\"Return available resolutions\"\"\"\n        resolutions = [\"%sx%s\" % (mode.get_width(), mode.get_height()) for mode in self.rr_screen.list_modes()]\n        return sorted(set(resolutions), key=lambda x: int(x.split(\"x\")[0]), reverse=True)\n\n    def _get_primary_output(self):\n        \"\"\"Return the RROutput used as a primary display\"\"\"\n        for output in self.rr_screen.list_outputs():\n            if output.get_is_primary():\n                return output\n        return\n\n    def get_current_resolution(self):\n        \"\"\"Return the current resolution for the primary display\"\"\"\n        output = self._get_primary_output()\n        if not output:\n            logger.error(\"Failed to get a default output\")\n            return \"\", \"\"\n        current_mode = output.get_current_mode()\n        return str(current_mode.get_width()), str(current_mode.get_height())\n\n    @staticmethod\n    def set_resolution(resolution):\n        \"\"\"Set the resolution of one or more displays.\n        The resolution can either be a string, which will be applied to the\n        primary display or a list of configurations as returned by `get_config`.\n        This method uses XrandR and will not work on Wayland.\n        \"\"\"\n        return change_resolution(resolution)\n\n    @staticmethod\n    def get_config():\n        \"\"\"Return the current display resolution\n        This method uses XrandR and will not work on wayland\n        The output can be fed in `set_resolution`\n        \"\"\"\n        return get_outputs()\n\n\ndef get_display_manager():\n    \"\"\"Return the appropriate display manager instance.\n    Defaults to Mutter if available. This is the only one to support Wayland.\n    \"\"\"\n    if DBUS_AVAILABLE:\n        try:\n            return MutterDisplayManager()\n        except DBusException as ex:\n            logger.debug(\"Mutter DBus service not reachable: %s\", ex)\n        except Exception as ex:  # pylint: disable=broad-except\n            logger.exception(\"Failed to instanciate MutterDisplayConfig. Please report with exception: %s\", ex)\n    else:\n        logger.error(\"DBus is not available, lutris was not properly installed.\")\n    try:\n        return DisplayManager()\n    except (GLib.Error, NoScreenDetected):\n        return LegacyDisplayManager()\n\n\nDISPLAY_MANAGER = get_display_manager()\nUSE_DRI_PRIME = len(_get_graphics_adapters()) > 1\n\n\nclass DesktopEnvironment(enum.Enum):\n\n    \"\"\"Enum of desktop environments.\"\"\"\n\n    PLASMA = 0\n    MATE = 1\n    XFCE = 2\n    DEEPIN = 3\n    UNKNOWN = 999\n\n\ndef get_desktop_environment():\n    \"\"\"Converts the value of the DESKTOP_SESSION environment variable\n    to one of the constants in the DesktopEnvironment class.\n    Returns None if DESKTOP_SESSION is empty or unset.\n    \"\"\"\n    desktop_session = os.environ.get(\"DESKTOP_SESSION\", \"\").lower()\n    if not desktop_session:\n        return None\n    if desktop_session.endswith(\"plasma\"):\n        return DesktopEnvironment.PLASMA\n    if desktop_session.endswith(\"mate\"):\n        return DesktopEnvironment.MATE\n    if desktop_session.endswith(\"xfce\"):\n        return DesktopEnvironment.XFCE\n    if desktop_session.endswith(\"deepin\"):\n        return DesktopEnvironment.DEEPIN\n    return DesktopEnvironment.UNKNOWN\n\n\ndef _get_command_output(*command):\n    return subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, close_fds=True).communicate()[0]\n\n\ndef is_compositing_enabled():\n    \"\"\"Checks whether compositing is currently disabled or enabled.\n    Returns True for enabled, False for disabled, and None if unknown.\n    \"\"\"\n    desktop_environment = get_desktop_environment()\n    if desktop_environment is DesktopEnvironment.PLASMA:\n        return _get_command_output(\n            \"qdbus\", \"org.kde.KWin\", \"/Compositor\", \"org.kde.kwin.Compositing.active\"\n        ) == b\"true\\n\"\n    if desktop_environment is DesktopEnvironment.MATE:\n        return _get_command_output(\"gsettings\", \"get org.mate.Marco.general\", \"compositing-manager\") == b\"true\\n\"\n    if desktop_environment is DesktopEnvironment.XFCE:\n        return _get_command_output(\n            \"xfconf-query\", \"--channel=xfwm4\", \"--property=/general/use_compositing\"\n        ) == b\"true\\n\"\n    if desktop_environment is DesktopEnvironment.DEEPIN:\n        return _get_command_output(\n            \"dbus-send\", \"--session\", \"--dest=com.deepin.WMSwitcher\", \"--type=method_call\",\n            \"--print-reply=literal\", \"/com/deepin/WMSwitcher\", \"com.deepin.WMSwitcher.CurrentWM\"\n        ) == b\"deepin wm\\n\"\n    return None\n\n\n# One element is appended to this for every invocation of disable_compositing:\n# True if compositing has been disabled, False if not. enable_compositing\n# removes the last element, and only re-enables compositing if that element\n# was True.\n_COMPOSITING_DISABLED_STACK = []\n\n\ndef _get_compositor_commands():\n    \"\"\"Returns the commands to enable/disable compositing on the current\n    desktop environment as a 2-tuple.\n    \"\"\"\n    start_compositor = None\n    stop_compositor = None\n    desktop_environment = get_desktop_environment()\n    if desktop_environment is DesktopEnvironment.PLASMA:\n        stop_compositor = (\"qdbus\", \"org.kde.KWin\", \"/Compositor\", \"org.kde.kwin.Compositing.suspend\")\n        start_compositor = (\"qdbus\", \"org.kde.KWin\", \"/Compositor\", \"org.kde.kwin.Compositing.resume\")\n    elif desktop_environment is DesktopEnvironment.MATE:\n        stop_compositor = (\"gsettings\", \"set org.mate.Marco.general\", \"compositing-manager\", \"false\")\n        start_compositor = (\"gsettings\", \"set org.mate.Marco.general\", \"compositing-manager\", \"true\")\n    elif desktop_environment is DesktopEnvironment.XFCE:\n        stop_compositor = (\"xfconf-query\", \"--channel=xfwm4\", \"--property=/general/use_compositing\", \"--set=false\")\n        start_compositor = (\"xfconf-query\", \"--channel=xfwm4\", \"--property=/general/use_compositing\", \"--set=true\")\n    elif desktop_environment is DesktopEnvironment.DEEPIN:\n        start_compositor = (\n            \"dbus-send\", \"--session\", \"--dest=com.deepin.WMSwitcher\", \"--type=method_call\",\n            \"/com/deepin/WMSwitcher\", \"com.deepin.WMSwitcher.RequestSwitchWM\",\n        )\n        stop_compositor = start_compositor\n    return start_compositor, stop_compositor\n\n\ndef _run_command(*command):\n    return subprocess.Popen(command, stdin=subprocess.DEVNULL, close_fds=True)\n\n\ndef disable_compositing():\n    \"\"\"Disable compositing if not already disabled.\"\"\"\n    compositing_enabled = is_compositing_enabled()\n    if compositing_enabled is None:\n        compositing_enabled = True\n    if any(_COMPOSITING_DISABLED_STACK):\n        compositing_enabled = False\n    _COMPOSITING_DISABLED_STACK.append(compositing_enabled)\n    if not compositing_enabled:\n        return\n    _, stop_compositor = _get_compositor_commands()\n    if stop_compositor:\n        _run_command(*stop_compositor)\n\n\ndef enable_compositing():\n    \"\"\"Re-enable compositing if the corresponding call to disable_compositing\n    disabled it.\"\"\"\n    compositing_disabled = _COMPOSITING_DISABLED_STACK.pop()\n    if not compositing_disabled:\n        return\n    start_compositor, _ = _get_compositor_commands()\n    if start_compositor:\n        _run_command(*start_compositor)\n", "path": "lutris/util/display.py"}], "after_files": [{"content": "\"\"\"Module to deal with various aspects of displays\"\"\"\n# isort:skip_file\nimport enum\nimport os\nimport subprocess\n\ntry:\n    from dbus.exceptions import DBusException\n    DBUS_AVAILABLE = True\nexcept ImportError:\n    DBUS_AVAILABLE = False\n\nfrom gi.repository import Gdk, GLib, GnomeDesktop\n\nfrom lutris.util import system\nfrom lutris.util.graphics.displayconfig import MutterDisplayManager\nfrom lutris.util.graphics.xrandr import LegacyDisplayManager, change_resolution, get_outputs\nfrom lutris.util.log import logger\n\n\nclass NoScreenDetected(Exception):\n\n    \"\"\"Raise this when unable to detect screens\"\"\"\n\n\ndef restore_gamma():\n    \"\"\"Restores gamma to a normal level.\"\"\"\n    xgamma_path = system.find_executable(\"xgamma\")\n    try:\n        subprocess.Popen([xgamma_path, \"-gamma\", \"1.0\"])\n    except (FileNotFoundError, TypeError):\n        logger.warning(\"xgamma is not available on your system\")\n    except PermissionError:\n        logger.warning(\"you do not have permission to call xgamma\")\n\n\ndef _get_graphics_adapters():\n    \"\"\"Return the list of graphics cards available on a system\n\n    Returns:\n        list: list of tuples containing PCI ID and description of the display controller\n    \"\"\"\n    lspci_path = system.find_executable(\"lspci\")\n    dev_subclasses = [\"VGA\", \"XGA\", \"3D controller\", \"Display controller\"]\n    if not lspci_path:\n        logger.warning(\"lspci is not available. List of graphics cards not available\")\n        return []\n    return [\n        (pci_id, device_desc.split(\": \")[1]) for pci_id, device_desc in [\n            line.split(maxsplit=1) for line in system.execute(lspci_path, timeout=3).split(\"\\n\")\n            if any(subclass in line for subclass in dev_subclasses)\n        ]\n    ]\n\n\nclass DisplayManager:\n\n    \"\"\"Get display and resolution using GnomeDesktop\"\"\"\n\n    def __init__(self):\n        screen = Gdk.Screen.get_default()\n        if not screen:\n            raise NoScreenDetected\n        self.rr_screen = GnomeDesktop.RRScreen.new(screen)\n        self.rr_config = GnomeDesktop.RRConfig.new_current(self.rr_screen)\n        self.rr_config.load_current()\n\n    def get_display_names(self):\n        \"\"\"Return names of connected displays\"\"\"\n        return [output_info.get_display_name() for output_info in self.rr_config.get_outputs()]\n\n    def get_resolutions(self):\n        \"\"\"Return available resolutions\"\"\"\n        resolutions = [\"%sx%s\" % (mode.get_width(), mode.get_height()) for mode in self.rr_screen.list_modes()]\n        return sorted(set(resolutions), key=lambda x: int(x.split(\"x\")[0]), reverse=True)\n\n    def _get_primary_output(self):\n        \"\"\"Return the RROutput used as a primary display\"\"\"\n        for output in self.rr_screen.list_outputs():\n            if output.get_is_primary():\n                return output\n        return\n\n    def get_current_resolution(self):\n        \"\"\"Return the current resolution for the primary display\"\"\"\n        output = self._get_primary_output()\n        if not output:\n            logger.error(\"Failed to get a default output\")\n            return \"\", \"\"\n        current_mode = output.get_current_mode()\n        return str(current_mode.get_width()), str(current_mode.get_height())\n\n    @staticmethod\n    def set_resolution(resolution):\n        \"\"\"Set the resolution of one or more displays.\n        The resolution can either be a string, which will be applied to the\n        primary display or a list of configurations as returned by `get_config`.\n        This method uses XrandR and will not work on Wayland.\n        \"\"\"\n        return change_resolution(resolution)\n\n    @staticmethod\n    def get_config():\n        \"\"\"Return the current display resolution\n        This method uses XrandR and will not work on wayland\n        The output can be fed in `set_resolution`\n        \"\"\"\n        return get_outputs()\n\n\ndef get_display_manager():\n    \"\"\"Return the appropriate display manager instance.\n    Defaults to Mutter if available. This is the only one to support Wayland.\n    \"\"\"\n    if DBUS_AVAILABLE:\n        try:\n            return MutterDisplayManager()\n        except DBusException as ex:\n            logger.debug(\"Mutter DBus service not reachable: %s\", ex)\n        except Exception as ex:  # pylint: disable=broad-except\n            logger.exception(\"Failed to instanciate MutterDisplayConfig. Please report with exception: %s\", ex)\n    else:\n        logger.error(\"DBus is not available, lutris was not properly installed.\")\n    try:\n        return DisplayManager()\n    except (GLib.Error, NoScreenDetected):\n        return LegacyDisplayManager()\n\n\nDISPLAY_MANAGER = get_display_manager()\nUSE_DRI_PRIME = len(_get_graphics_adapters()) > 1\n\n\nclass DesktopEnvironment(enum.Enum):\n\n    \"\"\"Enum of desktop environments.\"\"\"\n\n    PLASMA = 0\n    MATE = 1\n    XFCE = 2\n    DEEPIN = 3\n    UNKNOWN = 999\n\n\ndef get_desktop_environment():\n    \"\"\"Converts the value of the DESKTOP_SESSION environment variable\n    to one of the constants in the DesktopEnvironment class.\n    Returns None if DESKTOP_SESSION is empty or unset.\n    \"\"\"\n    desktop_session = os.environ.get(\"DESKTOP_SESSION\", \"\").lower()\n    if not desktop_session:\n        return None\n    if desktop_session.endswith(\"plasma\"):\n        return DesktopEnvironment.PLASMA\n    if desktop_session.endswith(\"mate\"):\n        return DesktopEnvironment.MATE\n    if desktop_session.endswith(\"xfce\"):\n        return DesktopEnvironment.XFCE\n    if desktop_session.endswith(\"deepin\"):\n        return DesktopEnvironment.DEEPIN\n    return DesktopEnvironment.UNKNOWN\n\n\ndef _get_command_output(*command):\n    \"\"\"Some rogue function that gives no shit about residing in the correct module\"\"\"\n    try:\n        return subprocess.Popen(\n            command,\n            stdin=subprocess.DEVNULL,\n            stdout=subprocess.PIPE,\n            close_fds=True\n        ).communicate()[0]\n    except FileNotFoundError:\n        logger.error(\"Unable to run command, %s not found\", command[0])\n\n\ndef is_compositing_enabled():\n    \"\"\"Checks whether compositing is currently disabled or enabled.\n    Returns True for enabled, False for disabled, and None if unknown.\n    \"\"\"\n    desktop_environment = get_desktop_environment()\n    if desktop_environment is DesktopEnvironment.PLASMA:\n        return _get_command_output(\n            \"qdbus\", \"org.kde.KWin\", \"/Compositor\", \"org.kde.kwin.Compositing.active\"\n        ) == b\"true\\n\"\n    if desktop_environment is DesktopEnvironment.MATE:\n        return _get_command_output(\"gsettings\", \"get org.mate.Marco.general\", \"compositing-manager\") == b\"true\\n\"\n    if desktop_environment is DesktopEnvironment.XFCE:\n        return _get_command_output(\n            \"xfconf-query\", \"--channel=xfwm4\", \"--property=/general/use_compositing\"\n        ) == b\"true\\n\"\n    if desktop_environment is DesktopEnvironment.DEEPIN:\n        return _get_command_output(\n            \"dbus-send\", \"--session\", \"--dest=com.deepin.WMSwitcher\", \"--type=method_call\",\n            \"--print-reply=literal\", \"/com/deepin/WMSwitcher\", \"com.deepin.WMSwitcher.CurrentWM\"\n        ) == b\"deepin wm\\n\"\n    return None\n\n\n# One element is appended to this for every invocation of disable_compositing:\n# True if compositing has been disabled, False if not. enable_compositing\n# removes the last element, and only re-enables compositing if that element\n# was True.\n_COMPOSITING_DISABLED_STACK = []\n\n\ndef _get_compositor_commands():\n    \"\"\"Returns the commands to enable/disable compositing on the current\n    desktop environment as a 2-tuple.\n    \"\"\"\n    start_compositor = None\n    stop_compositor = None\n    desktop_environment = get_desktop_environment()\n    if desktop_environment is DesktopEnvironment.PLASMA:\n        stop_compositor = (\"qdbus\", \"org.kde.KWin\", \"/Compositor\", \"org.kde.kwin.Compositing.suspend\")\n        start_compositor = (\"qdbus\", \"org.kde.KWin\", \"/Compositor\", \"org.kde.kwin.Compositing.resume\")\n    elif desktop_environment is DesktopEnvironment.MATE:\n        stop_compositor = (\"gsettings\", \"set org.mate.Marco.general\", \"compositing-manager\", \"false\")\n        start_compositor = (\"gsettings\", \"set org.mate.Marco.general\", \"compositing-manager\", \"true\")\n    elif desktop_environment is DesktopEnvironment.XFCE:\n        stop_compositor = (\"xfconf-query\", \"--channel=xfwm4\", \"--property=/general/use_compositing\", \"--set=false\")\n        start_compositor = (\"xfconf-query\", \"--channel=xfwm4\", \"--property=/general/use_compositing\", \"--set=true\")\n    elif desktop_environment is DesktopEnvironment.DEEPIN:\n        start_compositor = (\n            \"dbus-send\", \"--session\", \"--dest=com.deepin.WMSwitcher\", \"--type=method_call\",\n            \"/com/deepin/WMSwitcher\", \"com.deepin.WMSwitcher.RequestSwitchWM\",\n        )\n        stop_compositor = start_compositor\n    return start_compositor, stop_compositor\n\n\ndef _run_command(*command):\n    \"\"\"Random _run_command lost in the middle of the project,\n    are you lost little _run_command?\n    \"\"\"\n    try:\n        return subprocess.Popen(command, stdin=subprocess.DEVNULL, close_fds=True)\n    except FileNotFoundError:\n        logger.error(\"Oh no\")\n\n\ndef disable_compositing():\n    \"\"\"Disable compositing if not already disabled.\"\"\"\n    compositing_enabled = is_compositing_enabled()\n    if compositing_enabled is None:\n        compositing_enabled = True\n    if any(_COMPOSITING_DISABLED_STACK):\n        compositing_enabled = False\n    _COMPOSITING_DISABLED_STACK.append(compositing_enabled)\n    if not compositing_enabled:\n        return\n    _, stop_compositor = _get_compositor_commands()\n    if stop_compositor:\n        _run_command(*stop_compositor)\n\n\ndef enable_compositing():\n    \"\"\"Re-enable compositing if the corresponding call to disable_compositing\n    disabled it.\"\"\"\n    compositing_disabled = _COMPOSITING_DISABLED_STACK.pop()\n    if not compositing_disabled:\n        return\n    start_compositor, _ = _get_compositor_commands()\n    if start_compositor:\n        _run_command(*start_compositor)\n", "path": "lutris/util/display.py"}]}