summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--py_modules/lsfg_vk/plugin.py6
-rw-r--r--py_modules/lsfg_vk/wrapper_service.py35
-rw-r--r--tests/test_plugin_migration.py5
-rw-r--r--tests/test_wrapper_service.py18
4 files changed, 59 insertions, 5 deletions
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index 918c3f8..a1fd40f 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -34,14 +34,14 @@ class Plugin:
async def check_lsfg_vk_installed(self):
return self.installation_service.check_installation()
- def _cleanup_runtime_state(self):
+ def _cleanup_runtime_state(self, preserve_wrapper: bool = False):
flatpak = self.flatpak_service.remove_plugin_owned_environment()
if not flatpak.get("success"):
return flatpak.get("error") or "Could not clean up Flatpak support"
profiles = self.configuration_service.reset_all_flatpak_configs()
if not profiles.get("success"):
return profiles.get("error") or "Could not remove Flatpak profiles"
- wrapper = self.wrapper_service.purge()
+ wrapper = self.wrapper_service.neutralize() if preserve_wrapper else self.wrapper_service.purge()
if not wrapper.get("success"):
return wrapper.get("error") or "Could not remove workaround state"
return None
@@ -190,7 +190,7 @@ class Plugin:
async def _uninstall(self):
decky.logger.info("decky-lsfg-vk plugin being uninstalled")
try:
- error = self._cleanup_runtime_state()
+ error = self._cleanup_runtime_state(preserve_wrapper=True)
if error:
decky.logger.warning(f"Preserving lsfg-vk files because uninstall cleanup failed: {error}")
return
diff --git a/py_modules/lsfg_vk/wrapper_service.py b/py_modules/lsfg_vk/wrapper_service.py
index 906e55b..30c2323 100644
--- a/py_modules/lsfg_vk/wrapper_service.py
+++ b/py_modules/lsfg_vk/wrapper_service.py
@@ -427,3 +427,38 @@ class WrapperService(BaseService):
"error": str(error),
"removed_files": removed or None,
}
+
+ def neutralize(self) -> Dict[str, Any]:
+ """Leave an owned passthrough wrapper for Decky-level uninstall.
+
+ Decky can remove this plugin without giving the frontend a chance to
+ clean Steam launch options first. Keeping a dependency-free wrapper
+ prevents those options from turning into a broken executable path.
+ """
+ try:
+ with self._lock:
+ _document, sidecar_exists, _ = self._read_document()
+ self._assert_wrapper_owned_or_absent()
+ self._write_file(
+ self.wrapper_path,
+ "#!/bin/sh\n"
+ f"{self.MARKER}\n"
+ "# Safe passthrough retained for existing Steam launch options.\n"
+ "exec \"$@\"\n",
+ 0o755,
+ )
+ if sidecar_exists:
+ self.sidecar_path.unlink()
+ return {
+ "success": True,
+ "message": "Replaced lsfg-vk wrapper with a safe passthrough",
+ "error": None,
+ "removed_files": [str(self.sidecar_path)] if sidecar_exists else [],
+ }
+ except Exception as error:
+ return {
+ "success": False,
+ "message": "",
+ "error": str(error),
+ "removed_files": None,
+ }
diff --git a/tests/test_plugin_migration.py b/tests/test_plugin_migration.py
index 1a7cb29..fc51470 100644
--- a/tests/test_plugin_migration.py
+++ b/tests/test_plugin_migration.py
@@ -66,13 +66,14 @@ class PluginMigrationTests(unittest.TestCase):
plugin.wrapper_service = Mock()
plugin.flatpak_service.remove_plugin_owned_environment.return_value = {"success": True}
plugin.configuration_service.reset_all_flatpak_configs.return_value = {"success": True}
- plugin.wrapper_service.purge.return_value = {"success": True}
+ plugin.wrapper_service.neutralize.return_value = {"success": True}
asyncio.run(plugin._uninstall())
plugin.flatpak_service.remove_plugin_owned_environment.assert_called_once_with()
plugin.configuration_service.reset_all_flatpak_configs.assert_called_once_with()
- plugin.wrapper_service.purge.assert_called_once_with()
+ plugin.wrapper_service.neutralize.assert_called_once_with()
+ plugin.wrapper_service.purge.assert_not_called()
plugin.installation_service.cleanup_on_uninstall.assert_called_once_with()
finally:
self._restore(previous_decky, previous_tomllib, previous_plugin)
diff --git a/tests/test_wrapper_service.py b/tests/test_wrapper_service.py
index 20a08ff..46e029b 100644
--- a/tests/test_wrapper_service.py
+++ b/tests/test_wrapper_service.py
@@ -187,6 +187,24 @@ class WrapperServiceTests(unittest.TestCase):
self.assertTrue(self.service.wrapper_path.exists())
self.assertTrue(self.service.sidecar_path.exists())
+ def test_neutralize_leaves_dependency_free_passthrough_wrapper(self):
+ self.service.set("123", self._state())
+ response = self.service.neutralize()
+ self.assertTrue(response["success"])
+ self.assertFalse(self.service.sidecar_path.exists())
+ self.assertTrue(self.service.wrapper_path.exists())
+ content = self.service.wrapper_path.read_text(encoding="utf-8")
+ self.assertIn(self.service.MARKER, content)
+ self.assertNotIn("LSFGVK_CONFIG", content)
+ self.assertEqual(self._run(123, "/usr/bin/printf", "ok").stdout, "ok")
+
+ def test_neutralize_refuses_foreign_wrapper(self):
+ self.service.wrapper_path.write_text("#!/bin/sh\necho foreign\n", encoding="utf-8")
+ response = self.service.neutralize()
+ self.assertFalse(response["success"])
+ self.assertIn("unowned", response["error"])
+ self.assertEqual(self.service.wrapper_path.read_text(encoding="utf-8"), "#!/bin/sh\necho foreign\n")
+
if __name__ == "__main__":
unittest.main()