|
| 1 | +import { sessionBus, Variant } from "dbus-next"; |
| 2 | + |
| 3 | +function uriToPath(uri) { |
| 4 | + return uri.replace("file://", ""); |
| 5 | +} |
| 6 | + |
| 7 | +export async function pickFolderPersist() { |
| 8 | + console.log("π PORTAL PICKER: Starting folder picker"); |
| 9 | + |
| 10 | + // Only use portals in Flatpak |
| 11 | + const isFlatpak = process.env.FLATPAK_ID !== undefined; |
| 12 | + console.log("π PORTAL PICKER: Running in Flatpak?", isFlatpak); |
| 13 | + |
| 14 | + if (!isFlatpak) { |
| 15 | + console.log("π PORTAL PICKER: Not in Flatpak, skipping portals"); |
| 16 | + throw new Error("NOT_FLATPAK"); |
| 17 | + } |
| 18 | + |
| 19 | + try { |
| 20 | + const bus = sessionBus(); |
| 21 | + console.log("π PORTAL PICKER: Got session bus"); |
| 22 | + |
| 23 | + const fcObj = await bus.getProxyObject("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); |
| 24 | + const fileChooser = fcObj.getInterface("org.freedesktop.portal.FileChooser"); |
| 25 | + |
| 26 | + const options = { |
| 27 | + directory: new Variant("b", true), |
| 28 | + multiple: new Variant("b", false), |
| 29 | + modal: new Variant("b", true), |
| 30 | + title: new Variant("s", "Choose a folder"), |
| 31 | + }; |
| 32 | + |
| 33 | + const appId = process.env.FLATPAK_ID || "io.gitlab.yphil.emulsion"; |
| 34 | + console.log("π PORTAL PICKER: Using app ID:", appId); |
| 35 | + |
| 36 | + console.log("π PORTAL PICKER: Calling OpenFile..."); |
| 37 | + const requestPath = await fileChooser.OpenFile(appId, "", options); |
| 38 | + console.log("π PORTAL PICKER: Request path:", requestPath); |
| 39 | + |
| 40 | + const reqObj = await bus.getProxyObject("org.freedesktop.portal.Desktop", requestPath); |
| 41 | + const reqIface = reqObj.getInterface("org.freedesktop.portal.Request"); |
| 42 | + |
| 43 | + console.log("π PORTAL PICKER: Waiting for response..."); |
| 44 | + |
| 45 | + return new Promise((resolve, reject) => { |
| 46 | + const timeout = setTimeout(() => { |
| 47 | + console.log("π PORTAL PICKER: Timeout"); |
| 48 | + reqIface.removeListener("Response", handler); |
| 49 | + reject(new Error("PORTAL_TIMEOUT")); |
| 50 | + }, 30000); |
| 51 | + |
| 52 | + const handler = (code, results) => { |
| 53 | + console.log("π PORTAL PICKER: Response received - Code:", code); |
| 54 | + clearTimeout(timeout); |
| 55 | + reqIface.removeListener("Response", handler); |
| 56 | + |
| 57 | + if (code === 0 && results && results.uris && results.uris.length > 0) { |
| 58 | + const path = uriToPath(results.uris[0].value); |
| 59 | + console.log("π PORTAL PICKER: Returning path:", path); |
| 60 | + resolve({ path }); |
| 61 | + } else { |
| 62 | + console.log("π PORTAL PICKER: User cancelled"); |
| 63 | + reject(new Error("User cancelled")); |
| 64 | + } |
| 65 | + }; |
| 66 | + |
| 67 | + reqIface.on("Response", handler); |
| 68 | + }); |
| 69 | + |
| 70 | + } catch (e) { |
| 71 | + console.error("π PORTAL PICKER: Error:", e.message); |
| 72 | + if (e.message === "NOT_FLATPAK") { |
| 73 | + throw e; |
| 74 | + } |
| 75 | + throw new Error("PORTAL_NOT_AVAILABLE"); |
| 76 | + } |
| 77 | +} |
0 commit comments