Repository: lutris/lutris

Issue:
[Feature Request] Add Esync toggle + check and warn low limits
It would be nice to have a Esync toggle that allows to easily change the value of `WINEESYNC` envar (1 for on and 0 for off) if an esync build (they all have esync in their name) is in use. But to avoid issues on distributions that still use low file descriptor limit, Lutris should check it with `ulimit -r' and if that returns a value that it lower than 1048576, turning the toggle on will display a warning message that will deny turning it on and suggest to increase the limits value (and link https://github.com/lutris/lutris/wiki/How-to:-Esync).


Discussion:

Resolving patch:
diff --git a/lutris/gui/config_boxes.py b/lutris/gui/config_boxes.py
--- a/lutris/gui/config_boxes.py
+++ b/lutris/gui/config_boxes.py
@@ -152,6 +152,9 @@ def call_widget_generator(self, option, option_key, value, default):
         elif option_type == 'bool':
             self.generate_checkbox(option, value)
             self.tooltip_default = 'Enabled' if default else 'Disabled'
+        elif option_type == 'extended_bool':
+            self.generate_checkbox_with_callback(option, value)
+            self.tooltip_default = 'Enabled' if default else 'Disabled'
         elif option_type == 'range':
             self.generate_range(option_key,
                                 option["min"],
@@ -197,10 +200,32 @@ def generate_checkbox(self, option, value=None):
         self.wrapper.pack_start(checkbox, True, True, 5)
         self.option_widget = checkbox
 
+    #Checkbox with callback
+    def generate_checkbox_with_callback(self, option, value=None):
+        """Generate a checkbox. With callback"""
+        checkbox = Gtk.CheckButton(label=option["label"])
+        if option['active'] is True:
+            checkbox.set_sensitive(True)
+        else:
+            checkbox.set_sensitive(False)
+        if value is True:
+            checkbox.set_active(value)
+        checkbox.connect("toggled", self.checkbox_toggle_with_callback, option['option'], option['callback'], option['callback_on'])
+        self.wrapper.pack_start(checkbox, True, True, 5)
+        self.option_widget = checkbox
+
     def checkbox_toggle(self, widget, option_name):
         """Action for the checkbox's toggled signal."""
         self.option_changed(widget, option_name, widget.get_active())
 
+    def checkbox_toggle_with_callback(self, widget, option_name, callback, callback_on=None):
+        """Action for the checkbox's toggled signal. With callback method"""
+        if widget.get_active() == callback_on or callback_on is None:
+            if callback(self.config):
+                self.option_changed(widget, option_name, widget.get_active())
+            else:
+                widget.set_active(False)
+
     # Entry
     def generate_entry(self, option_name, label, value=None):
         """Generate an entry box."""
diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py
--- a/lutris/runners/wine.py
+++ b/lutris/runners/wine.py
@@ -17,6 +17,7 @@
 from lutris.runners.runner import Runner
 from lutris.thread import LutrisThread
 from lutris.gui.dialogs import FileDialog
+from lutris.gui.dialogs import ErrorDialog
 
 WINE_DIR = os.path.join(settings.RUNNER_DIR, "wine")
 MIN_SAFE_VERSION = '3.0'  # Wine installers must run with at least this version
@@ -436,6 +437,13 @@ def get_wine_version_exe(version):
 def is_version_installed(version):
     return os.path.isfile(get_wine_version_exe(version))
 
+def is_esync_limit_set():
+    nolimit = subprocess.Popen("ulimit -Hn", shell=True, stdout=subprocess.PIPE).stdout.read()
+    nolimit = int(nolimit)
+    if nolimit > 1048576:
+        return False
+    else:
+        return True
 
 def get_default_version():
     """Return the default version of wine. Prioritize 64bit builds"""
@@ -474,6 +482,10 @@ def support_legacy_version(version):
         version += '-i386'
     return version
 
+def is_version_esync(version):
+    if version.find('esync'):
+        return True
+    return False
 
 def get_real_executable(windows_executable, working_dir=None):
     """Given a Windows executable, return the real program
@@ -609,6 +621,17 @@ def get_dxvk_choices():
                 version_choices.append((version, version))
             return version_choices
 
+        def esync_limit_callback(config):
+            if not is_esync_limit_set():
+                ErrorDialog("Your limits are not set correctly."
+                            " Please increase them as described here:"
+                            " <a href='https://github.com/lutris/lutris/wiki/How-to:-Esync'>"
+                            "https://github.com/lutris/lutris/wiki/How-to:-Esync</a>")
+                return False
+            if is_version_esync(config['version']):
+                ErrorDialog("Your wine version may not support esync, if you are unsure please check.")
+            return True
+
         self.runner_options = [
             {
                 'option': 'version',
@@ -640,6 +663,15 @@ def get_dxvk_choices():
                 'choices': get_dxvk_choices,
                 'default': dxvk.DXVK_LATEST
             },
+            {
+                'option': 'esync',
+                'label': 'Enable Esync',
+                'type': 'extended_bool',
+                'help': 'Enable eventfd-based synchronization (esync)',
+                'callback': esync_limit_callback,
+                'callback_on' : True,
+                'active' : True
+            },
             {
                 'option': 'x360ce-path',
                 'label': "Path to the game's executable, for x360ce support",
@@ -1068,6 +1100,7 @@ def get_env(self, os_env=True):
         if self.prefix_path:
             env['WINEPREFIX'] = self.prefix_path
 
+        env["WINEESYNC"] = "1" if self.runner_config.get('esync') else "0"
         overrides = self.get_dll_overrides()
         if overrides:
             env['WINEDLLOVERRIDES'] = get_overrides_env(overrides)


Test patch: