Steam file dialog: keep spaces in the chosen path, cancel when GTK 3 is missing or the bridge reconnects

This commit is contained in:
maxjivi05 authored and MaxsTechReview committed 2026-10-24 13:18:04 -04:00
1 parent 5a56d9f785
commit 8d66570d33
2 files changed
+39 -14

No files matched your search

@@ -199,9 +199,12 @@ TOUR_JS = r"""
FILE_BINDING = "droiddeckOpenFileDialog"
FILE_JS = r"""
(() => {
if (window.__droiddeckFiles) return "present";
const self = { next: 0, pending: new Map() };
const cancelled = () => ({ result: 2, message: "" });
if (window.__droiddeckFiles) {
window.__droiddeckFiles.cancelAll();
return "present";
}
const self = { next: 0, pending: new Map() };
const open = function (options) {
return new Promise((resolve, reject) => {
if (typeof window.__BINDING__ !== "function") { reject(cancelled()); return; }
@@ -218,6 +221,11 @@ FILE_JS = r"""
if (typeof path === "string" && path) item.resolve(path); else item.reject(cancelled());
return true;
};
self.cancelAll = () => {
const items = Array.from(self.pending.values());
self.pending.clear();
for (const item of items) item.reject(cancelled());
};
const patch = (w) => {
const system = w?.SteamClient?.System;
if (system && system.OpenFileDialog !== open) system.OpenFileDialog = open;
@@ -657,10 +665,16 @@ class Helper:
def chooser_done(self):
ident, proc = self.chooser
self.chooser = None
path = proc.stdout.read().decode("utf-8", "replace").strip()
out = proc.stdout.read().decode("utf-8", "replace")
proc.stdout.close()
proc.wait()
self.finish_chooser(ident, path if proc.returncode == 0 and path else None)
if proc.returncode == 2:
say("file dialog needs GTK 3 and PyGObject, which come with the desktop")
try:
path = json.loads(out) if proc.returncode == 0 else None
except ValueError:
path = None
self.finish_chooser(ident, path if isinstance(path, str) and path else None)
def finish_chooser(self, ident, path):
say("file dialog %s" % ("chose " + path if path else "cancelled"))
@@ -878,10 +892,13 @@ def chooser_main():
options = json.load(sys.stdin)
except ValueError:
return 1
path = choose_file(options if isinstance(options, dict) else {})
try:
path = choose_file(options if isinstance(options, dict) else {})
except (ImportError, ValueError):
return 2
if not path:
return 1
print(path)
print(json.dumps(path))
return 0
+16 -8
View File
@@ -26,7 +26,11 @@ window.droiddeckOpenFileDialog = payload => asked.push(JSON.parse(payload));
const script = require('fs').readFileSync(process.argv[2], 'utf8');
(async () => {
assert.strictEqual(eval(script), 'installed');
const orphan = SteamClient.System.OpenFileDialog({ strTitle: 'Before reconnect' });
assert.strictEqual(eval(script), 'present');
await assert.rejects(orphan, e => e.result === 2);
assert.strictEqual(window.__droiddeckFiles.done(1, '/late'), false);
asked.length = 0;
assert.notStrictEqual(SteamClient.System.OpenFileDialog, native);
assert.strictEqual(popup.window.SteamClient.System.OpenFileDialog, SteamClient.System.OpenFileDialog);
popupCallbacks.forEach(cb => cb(later));
@@ -34,11 +38,11 @@ const script = require('fs').readFileSync(process.argv[2], 'utf8');
const options = { strTitle: 'Pick', bChooseDirectory: true };
const chosen = SteamClient.System.OpenFileDialog(options);
const cancelled = later.window.SteamClient.System.OpenFileDialog({ strTitle: 'Other' });
assert.deepStrictEqual(asked, [{ id: 1, options }, { id: 2, options: { strTitle: 'Other' } }]);
assert.strictEqual(window.__droiddeckFiles.done(1, '/root/Storage/Games'), true);
assert.strictEqual(window.__droiddeckFiles.done(1, '/again'), false);
assert.deepStrictEqual(asked, [{ id: 2, options }, { id: 3, options: { strTitle: 'Other' } }]);
assert.strictEqual(window.__droiddeckFiles.done(2, '/root/Storage/Games'), true);
assert.strictEqual(window.__droiddeckFiles.done(2, '/again'), false);
assert.strictEqual(await chosen, '/root/Storage/Games');
window.__droiddeckFiles.done(2, null);
window.__droiddeckFiles.done(3, null);
await assert.rejects(cancelled, e => e.result === 2);
delete window.droiddeckOpenFileDialog;
await assert.rejects(SteamClient.System.OpenFileDialog({}), e => e.result === 2);
@@ -97,8 +101,9 @@ class FileDialogBridgeTest(unittest.TestCase):
self.assertEqual(self.helper.chooser, (1, first))
def test_the_chosen_path_or_a_cancel_goes_back_to_the_page(self):
for code, out, expected in ((0, b"/root/Storage/My Game/game.sh\n", '"/root/Storage/My Game/game.sh"'),
(1, b"", "null"), (0, b"\n", "null")):
for code, out, expected in ((0, b'"/root/Storage/My Game/game.sh"\n', '"/root/Storage/My Game/game.sh"'),
(0, b'" spaced name "\n', '" spaced name "'),
(1, b"", "null"), (2, b"", "null"), (0, b"\n", "null"), (0, b"not json\n", "null")):
proc = mock.Mock(stdout=io.BytesIO(out), returncode=code)
self.helper.chooser = (7, proc)
self.helper.chooser_done()
@@ -113,8 +118,11 @@ class FileDialogBridgeTest(unittest.TestCase):
self.helper.page.evaluate.assert_not_called()
def test_choose_file_mode_prints_the_path_and_fails_on_cancel(self):
for chosen, code, printed in (("/root/Storage/a.exe", 0, "/root/Storage/a.exe\n"), (None, 1, "")):
with mock.patch.dict(LIVE["chooser_main"].__globals__, {"choose_file": mock.Mock(return_value=chosen)}), \
for chooser, code, printed in ((mock.Mock(return_value="/root/Storage/a.exe "), 0, '"/root/Storage/a.exe "\n'),
(mock.Mock(return_value=None), 1, ""),
(mock.Mock(side_effect=ValueError("Namespace Gtk not available for version 3.0")), 2, ""),
(mock.Mock(side_effect=ImportError("No module named 'gi'")), 2, "")):
with mock.patch.dict(LIVE["chooser_main"].__globals__, {"choose_file": chooser}), \
mock.patch.object(sys, "stdin", io.StringIO('{"strTitle": "Pick"}')), \
mock.patch.object(sys, "stdout", io.StringIO()) as out:
self.assertEqual(LIVE["chooser_main"](), code)