Repository navigation
fix: save and import user configuration when the Linux file dialog portal fails - #3071
Merged
Merged
Conversation
On Linux Electron routes file dialogs through xdg-desktop-portal. When the portal's FileChooser backend is missing or broken the portal aborts the request with response code 2, which Chromium logs as 'Request ended (non-user cancelled)' and Electron surfaces as a plain cancellation, so Export device configuration silently did nothing. Keep the native save dialog (now always with an absolute default path), but detect the immediate, never-shown cancellation and fall back to the renderer save path, which does not use the portal. Notify the user that the file was saved to the downloads folder instead of showing a dialog.
…ails The previous fix only detected that the Linux xdg-desktop-portal file chooser never appeared. The save then fell back to the renderer's file-saver download, which returns no path and wrote nothing, while the notification still claimed the configuration reached the downloads folder. Import had no fallback at all, so it silently did nothing. The main process now writes the configuration itself, into the remembered configuration folder when it still exists and otherwise into the downloads folder, without overwriting an existing file, and the notification reports the actual path. The open dialog reports portalFailed so the renderer can pick the import file through its own file input instead of the native dialog.
…fails The import fallback added to the previous commit fell back to the renderer's <input type="file"> picker, but Chromium's own file chooser also goes through xdg-desktop-portal, so it hit the same broken backend and the import still did nothing. Replace it with a picker Agent draws itself: the main process lists the .json/.bin files of the import folder, a modal lets the user choose one, and the main process reads the selection. Read only files of that folder, so the new IPC channel cannot be abused to read arbitrary files.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Export device configuration does nothing on Linux, and Import device configuration does nothing either. The log ends with:
Root cause
Request ended (non-user cancelled)is Chromium's message fordbus_xdg::ResponseCode::kOther(response code 2): thexdg-desktop-portalFileChooserbackend aborted the request.ui/shell_dialogs/select_file_dialog_linux_portal.ccmaps any portal error onto a regular cancellation (FileSelectionCanceled), so Electron resolves{ canceled: true }and Agent shows nothing.This is a known portal-backend failure, not a user cancel and not a path problem, for example
xdg-desktop-portal-gnomewithout Nautilus (nix-community/home-manager#9996) or Ubuntu 26.04 (signalapp/Signal-Desktop#8040).#3069 blamed a relative
defaultPathand only made it absolute. That cannot explain this report: the path is already absolute, and code2originates in the backend.Fix
Keep the native dialogs (still always with an absolute default path so they work where the portal is healthy), but stop depending on them:
packages/uhk-agent/src/services/file-dialog.service.tsUserConfiguration (1).json). Report the written path.portalFailedinstead of a silent cancel..json/.binfiles of the import folder and read the file the in-app picker chose. The read channel only accepts files inside that folder.packages/uhk-common/src/models/file-dialog.ts—OpenUserConfigDialogResult.portalFailed,SaveUserConfigDialogResult.savedWithoutDialog,UserConfigFileInfo,ListUserConfigFilesResult.packages/uhk-common/src/util/ipcEvents.ts—fileDialog.listUserConfigFiles,fileDialog.readUserConfigFile.packages/uhk-web/src/renderer/services/electron-file-dialog.service.ts— the save is now a pass-through (the main process writes the file); the open falls back to an in-app picker.packages/uhk-web/src/renderer/components/user-config-file-picker/— the picker Agent draws itself (a Bootstrap modal listing the configuration files of the import folder).packages/uhk-web/src/app/store/effects/user-config.ts— the notification names the actual written path instead of asserting the downloads folder.This replaces the first iteration of this PR, which fell back to the renderer's
file-saverdownload: that path returns no path, wrote nothing, and still claimed success, so the reporter sawSaved the user configuration without a save dialog nullwith no file on disk.Why an in-app picker instead of
<input type="file">The first version of the open fallback used the renderer's
<input type="file">picker on the assumption that it is a different code path from Electron'sdialog.showOpenDialog. It is not: Chromium serves both throughui/shell_dialogs/select_file_dialog_linux_portal.cc, so the renderer picker hits the same broken backend (and, invoked after the awaited IPC round-trip, can also be blocked for lack of user activation). The reporter's retest on the2f2b968dbuild confirmed it: export worked, import still loggedthe open dialog closed after 67 ms without appearingand did nothing. Agent therefore draws its own picker and reads the file in the main process, avoiding the portal entirely.Verification
npx tsc --noEmit -p packages/uhk-agent/tsconfig.json— cleannpx tsc --noEmit -p packages/uhk-web/tsconfig.json— clean (only pre-existing errors ingroup-macros-by-name.test.ts)npx ng build --configuration=production --project=uhk-renderer— success (only pre-existing warnings)npm testinpackages/uhk-web— 33/33 passnpx eslint <changed files>— cleanNotes
xdg-desktop-portal-gtk(or add Nautilus for the GNOME backend); this change only prevents the silent failure.