Repository: lutris/lutris

Issue:
Make wine desktop integration optional
Currently `WinePrefixManager.setup_defaults()` gets called every time a game is launched at [this location](https://github.com/lutris/lutris/blob/2a7f48108c2f017b2bcbb51f90d2f86f176188ff/lutris/runners/wine.py#L807). This resets any user configuration to these values, in particular the "Desktop Integration" settings for linking the desktop and documents folder. While this can be useful, it should not be forced at every launch. I like to keep my saves in a separate Wine directory, so they aren't deleted when I uninstall a game. It was very confusing to see them disappear every launch. So please don't reset to the default values before running a game, it should be done after prefix creation only.
Make wine desktop integration optional
Currently `WinePrefixManager.setup_defaults()` gets called every time a game is launched at [this location](https://github.com/lutris/lutris/blob/2a7f48108c2f017b2bcbb51f90d2f86f176188ff/lutris/runners/wine.py#L807). This resets any user configuration to these values, in particular the "Desktop Integration" settings for linking the desktop and documents folder. While this can be useful, it should not be forced at every launch. I like to keep my saves in a separate Wine directory, so they aren't deleted when I uninstall a game. It was very confusing to see them disappear every launch. So please don't reset to the default values before running a game, it should be done after prefix creation only.


Discussion:
While we don't have plans to change the way Lutris applies settings, we can make sandboxing optional. When we do, be sure to enable the option globally since the sandboxing will always be the default behavior.
I'd actually prefer a wine "home" setting instead of a simple on/off switch, but I'm not sure if that's too much too ask.
We also plans to let users set their home directory to something else but that's not Wine specific ;)
Added the pull request #744 to allow sandbox customization.
Can you test with your config ?

I used only one field for all directories. Not sure if granularity is useful. 
While we don't have plans to change the way Lutris applies settings, we can make sandboxing optional. When we do, be sure to enable the option globally since the sandboxing will always be the default behavior.
I'd actually prefer a wine "home" setting instead of a simple on/off switch, but I'm not sure if that's too much too ask.
We also plans to let users set their home directory to something else but that's not Wine specific ;)
Added the pull request #744 to allow sandbox customization.
Can you test with your config ?

I used only one field for all directories. Not sure if granularity is useful. 

Resolving patch:
diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py
--- a/lutris/runners/wine.py
+++ b/lutris/runners/wine.py
@@ -139,7 +139,7 @@ def create_prefix(prefix, wine_path=None, arch='win32', overrides={},
     logger.info('%s Prefix created in %s', arch, prefix)
     prefix_manager = WinePrefixManager(prefix)
     prefix_manager.setup_defaults()
-
+    self.sandbox(prefix_manager)
 
 def winekill(prefix, arch='win32', wine_path=None, env=None, initial_pids=None):
     """Kill processes in Wine prefix."""
@@ -323,22 +323,6 @@ def detect_prefix_arch(directory=None):
                 return 'win32'
     logger.debug("Can't detect prefix arch for %s", directory)
 
-
-def disable_desktop_integration(prefix):
-    """Remove links to user directories in a prefix."""
-    if not prefix:
-        raise ValueError('Missing prefix')
-    user = os.getenv('USER')
-    user_dir = os.path.join(prefix, "drive_c/users/", user)
-    # Replace symlinks
-    if os.path.exists(user_dir):
-        for item in os.listdir(user_dir):
-            path = os.path.join(user_dir, item)
-            if os.path.islink(path):
-                os.unlink(path)
-                os.makedirs(path)
-
-
 def set_drive_path(prefix, letter, path):
     dosdevices_path = os.path.join(prefix, "dosdevices")
     if not os.path.exists(dosdevices_path):
@@ -723,6 +707,21 @@ def get_wine_version_choices():
                 'advanced': True,
                 'help': "Sets WINEDLLOVERRIDES when launching the game."
             },
+            {
+                'option': 'sandbox',
+                'type': 'bool',
+                'label': 'Create a sandbox for wine folders',
+                'default': True,
+                'help': ("Do not use $HOME for desktop integration folders.\n"
+                         "By default, it use the directories in the confined "
+                         "Windows environment.")
+            },
+            {
+                'option': 'sandbox_dir',
+                'type': 'directory_chooser',
+                'label': 'Sandbox directory',
+                'help': ("Custom directory for desktop integration folders.")
+            }
         ]
 
     @property
@@ -883,8 +882,8 @@ def prelaunch(self):
         if not os.path.exists(os.path.join(self.prefix_path, 'user.reg')):
             create_prefix(self.prefix_path, arch=self.wine_arch)
         prefix_manager = WinePrefixManager(self.prefix_path)
-        prefix_manager.setup_defaults()
         prefix_manager.configure_joypads()
+        self.sandbox(prefix_manager)
         self.set_regedit_keys()
         return True
 
@@ -953,6 +952,13 @@ def setup_x360ce(self, x360ce_path):
             x360ce_config.populate_controllers()
             x360ce_config.write(os.path.join(x360ce_path, 'x360ce.ini'))
 
+    def sandbox(self, wine_prefix):
+        sandbox = self.runner_config.get('sandbox', True)
+
+        if (sandbox):
+            sandbox_dir = self.runner_config.get('sandbox_dir', None)
+            wine_prefix.desktop_integration(desktop_dir=sandbox_dir)
+
     def play(self):
         game_exe = self.game_exe
         arguments = self.game_config.get('args', '')
diff --git a/lutris/util/wineprefix.py b/lutris/util/wineprefix.py
--- a/lutris/util/wineprefix.py
+++ b/lutris/util/wineprefix.py
@@ -1,8 +1,10 @@
 import os
+import time
 from lutris.util.wineregistry import WineRegistry
 from lutris.util.log import logger
 from lutris.util import joypad
 
+desktop_folders = ["Desktop", "My Documents", "My Music", "My Videos", "My Pictures"]
 
 class WinePrefixManager:
     """Class to allow modification of Wine prefixes without the use of Wine"""
@@ -12,7 +14,6 @@ def __init__(self, path):
         self.path = path
 
     def setup_defaults(self):
-        self.sandbox()
         self.override_dll("winemenubuilder.exe", "")
 
     def get_registry_path(self, key):
@@ -48,14 +49,44 @@ def override_dll(self, dll, mode):
             return
         self.set_registry_key(key, dll, mode)
 
-    def sandbox(self):
+    def desktop_integration(self, desktop_dir=None):
+        """Overwrite desktop integration"""
+
         user = os.getenv('USER')
         user_dir = os.path.join(self.path, "drive_c/users/", user)
-        # Replace symlinks
+
+        if (not desktop_dir):
+            desktop_dir = user_dir
+
         if os.path.exists(user_dir):
-            for item in os.listdir(user_dir):
+            # Replace desktop integration symlinks
+            for item in desktop_folders:
                 path = os.path.join(user_dir, item)
-                if os.path.islink(path):
+                old_path = path + ".winecfg"
+
+                if (os.path.islink(path)):
+                    os.unlink(path)
+                elif (os.path.isdir(path)):
+                    try:
+                        os.rmdir(path)
+                    # We can't delete nonempty dir, so we rename as wine do.
+                    except OSError:
+                        os.rename(path, old_path)
+
+                if (desktop_dir != user_dir):
+                    src_path = os.path.join(desktop_dir, item)
+                    os.makedirs(src_path, exist_ok=True)
+                    os.symlink(src_path, path)
+                else:
+                    # We use first the renamed dir, otherwise we make it.
+                    if (os.path.isdir(old_path)):
+                        os.rename(old_path, path)
+                    else:
+                        os.makedirs(path, exist_ok=True)
+
+            # Security: Remove other symlinks.
+            for item in os.listdir(user_dir):
+                if not item in desktop_folders and os.path.islink(path):
                     os.unlink(path)
                     os.makedirs(path)
 


Test patch: