Skip to content

fix: save and import user configuration when the Linux file dialog portal fails - #3071

Merged
mondalaci merged 3 commits into
masterfrom
fix-export-dialog-portal-fallback
Oct 26, 2026
Merged

mondalaci merged 3 commits into
masterfrom
fix-export-dialog-portal-fallback

Conversation

@mondalaci

@mondalaci mondalaci commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Problem

Export device configuration does nothing on Linux, and Import device configuration does nothing either. The log ends with:

[FileDialogService] save user configuration dialog, defaultPath: /home/henrik/UserConfiguration.json
[6293:1015/232605.430028:ERROR:components/dbus/xdg/request.cc:173] Request ended (non-user cancelled).

Root cause

Request ended (non-user cancelled) is Chromium's message for dbus_xdg::ResponseCode::kOther (response code 2): the xdg-desktop-portal FileChooser backend aborted the request. ui/shell_dialogs/select_file_dialog_linux_portal.cc maps 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-gnome without Nautilus (nix-community/home-manager#9996) or Ubuntu 26.04 (signalapp/Signal-Desktop#8040).

#3069 blamed a relative defaultPath and only made it absolute. That cannot explain this report: the path is already absolute, and code 2 originates 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.ts
    • Detect a dialog that resolves almost instantly (the portal aborting before any dialog can appear) as opposed to a real user cancel.
    • Save: write the configuration in the main process itself, into the remembered configuration folder when it still exists and otherwise into the downloads folder, creating the folder and never overwriting an existing file (UserConfiguration (1).json). Report the written path.
    • Open: report portalFailed instead of a silent cancel.
    • List the .json/.bin files 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-saver download: that path returns no path, wrote nothing, and still claimed success, so the reporter saw Saved the user configuration without a save dialog null with 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's dialog.showOpenDialog. It is not: Chromium serves both through ui/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 the 2f2b968d build confirmed it: export worked, import still logged the open dialog closed after 67 ms without appearing and 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 — clean
  • npx tsc --noEmit -p packages/uhk-web/tsconfig.json — clean (only pre-existing errors in group-macros-by-name.test.ts)
  • npx ng build --configuration=production --project=uhk-renderer — success (only pre-existing warnings)
  • npm test in packages/uhk-web — 33/33 pass
  • npx eslint <changed files> — clean

Notes

  • Export no longer depends on any dialog, so it is deterministic whenever the portal is broken.
  • Import does not depend on any Chromium file chooser either: it lists the files of the import folder (remembered folder, else downloads) and reads the chosen one in the main process. A configuration that is not in that folder can be imported by copying it there; the dialog shows the folder.
  • The environment-level remedy is to install/select xdg-desktop-portal-gtk (or add Nautilus for the GNOME backend); this change only prevents the silent failure.
  • Supersedes fix: open the device configuration export dialog on Linux #3069.
  • Needs retesting by the reporter on Arch (available again around Oct 26).

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.
@mondalaci mondalaci changed the title fix: export device configuration when the Linux file dialog portal fails fix: save and import user configuration when the Linux file dialog portal fails Oct 19, 2026
…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.
@mondalaci
mondalaci merged commit 9fe6abd into master Oct 26, 2026
6 checks passed
@mondalaci
mondalaci deleted the fix-export-dialog-portal-fallback branch October 26, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant