mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-28 20:49:50 +00:00
desktop: fix saving files with '[', '*', or '?' in name on Linux (#7290)
* desktop: fix saving files with '[', '*', or '?' in name on Linux
JFileChooser's BasicFileChooserUI treats filenames containing
'[', '*', or '?' as glob patterns on Unix when the file does not
exist on disk. The Save button's ApproveSelectionAction then
applies a GlobFilter and returns without saving — the dialog
stays open and nothing is written.
Regression introduced in #2789 (commit 02d00944f, 2023-07-28),
which started pre-populating the filename via selectedFile =
File(filename). Before that the save dialog ran in
DIRECTORIES_ONLY mode and the JDK glob check never saw the
filename.
Fix: on Linux save dialogs, find the Save button by identity
(BasicFileChooserUI.getApproveSelectionAction() is public and
returns the exact ActionListener wired on the button) and
replace it with a wrapper that delegates to the original
action for non-glob names and calls approveSelection()
directly for glob names.
Windows (per the JDK only '*' and '?' are glob chars there)
and macOS (uses AWT FileDialog) are unaffected. Open dialogs
keep glob filtering for power-user search.
* desktop: always bypass JFileChooser glob-on-save on Linux
Earlier in this branch we added a heuristic to delegate to the
original ApproveSelectionAction for non-'[' names so that typing
'*.pdf' in the save dialog would still glob-filter the listing.
That preserved a Swing-only quirk that no native OS save dialog
implements:
- macOS uses NSSavePanel (already used in this app via AWT
FileDialog) — no glob-on-save.
- Native Windows / Linux GTK / KDE save dialogs — no glob-on-save.
- JFileChooser on Windows — has it, but '*' and '?' aren't even
legal NTFS filename chars, so users won't be typing them.
It also blocks legitimate filenames containing '*' or '?' on Linux
(legal on POSIX filesystems) from being saved, since they too get
intercepted as glob.
Replace the Save button's action with a handler that always treats
the typed text as a literal filename. The result: any filename can
be saved on Linux, behaviour aligns with macOS and with every
native OS save dialog. The open dialog is untouched, so '*.pdf'
glob-filtering there still works.
* desktop: restore directory-only save dialog on Linux
The previous commit installed the literal-filename glob bypass for
every Linux save dialog (!isLoad && isLinux), including the
DIRECTORIES_ONLY mode used when filename == null — e.g. the
"Save QR code as image" flow (saveTempImageUncompressed →
saveDialog.awaitResult() with no params).
In that mode the bypass broke directory selection entirely:
selecting a subfolder + Save traversed into it instead of approving
it, and Save with an empty filename field no-oped. Net result:
QR-code image saving was unusable on Linux.
The glob-on-save bug only arises because a filename is pre-populated
(selectedFile = File(filename)), which never happens in
DIRECTORIES_ONLY mode. Gate the bypass on filename != null so the
directory-save path keeps JFileChooser's original approve action.
* desktop: simplify Linux save glob bypass via getDefaultButton
---------
Co-authored-by: shum <github.shum@liber.li>
This commit is contained in:
+31
@@ -14,10 +14,12 @@ import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.awt.FileDialog
|
||||
import java.awt.event.ActionListener
|
||||
import java.io.File
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.filechooser.FileFilter
|
||||
import javax.swing.filechooser.FileNameExtensionFilter
|
||||
import javax.swing.plaf.basic.BasicFileChooserUI
|
||||
|
||||
@Composable
|
||||
actual fun DefaultDialog(
|
||||
@@ -77,6 +79,13 @@ fun FrameWindowScope.FileDialogChooserMultiple(
|
||||
fileChooser.dialogTitle = title
|
||||
fileChooser.isMultiSelectionEnabled = allowMultiple && isLoad
|
||||
fileChooser.isAcceptAllFileFilterUsed = fileFilter == null
|
||||
// Only install the glob bypass for the real file-save case (filename != null). When filename
|
||||
// is null the dialog runs in DIRECTORIES_ONLY mode (e.g. "Save QR code as image"), where the
|
||||
// Save button must approve the selected directory — the literal-filename handler would instead
|
||||
// traverse into it (or no-op on an empty field), making directory selection impossible.
|
||||
if (!isLoad && filename != null && desktopPlatform.isLinux()) {
|
||||
installUnixSaveGlobBypass(fileChooser)
|
||||
}
|
||||
if (fileFilter != null && fileFilterDescription != null) {
|
||||
fileChooser.addChoosableFileFilter(object: FileFilter() {
|
||||
override fun accept(file: File?): Boolean = fileFilter(file)
|
||||
@@ -120,6 +129,28 @@ fun FrameWindowScope.FileDialogChooserMultiple(
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the Save button's action with a literal-filename handler. This bypasses JFileChooser's
|
||||
// glob-on-save behaviour, which mis-handles '[' as a glob char on Unix (breaking filenames like
|
||||
// '[1].pdf') and is not a feature of any native OS save dialog — macOS NSSavePanel and native
|
||||
// Windows / Linux GTK / KDE save dialogs all treat the typed filename as a literal name.
|
||||
private fun installUnixSaveGlobBypass(fc: JFileChooser) {
|
||||
val ui = fc.ui as? BasicFileChooserUI ?: return
|
||||
val original: ActionListener = ui.approveSelectionAction
|
||||
val btn = ui.getDefaultButton(fc) ?: return
|
||||
btn.removeActionListener(original)
|
||||
btn.addActionListener {
|
||||
val name = ui.fileName?.takeIf { it.isNotEmpty() } ?: return@addActionListener
|
||||
val typed = File(name)
|
||||
val target = if (typed.isAbsolute) typed else File(fc.currentDirectory, name)
|
||||
if (target.isDirectory && fc.isTraversable(target)) {
|
||||
fc.currentDirectory = target
|
||||
} else {
|
||||
fc.selectedFile = target
|
||||
fc.approveSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Has graphic glitches on many Linux distributions, so use only on non-Linux systems. Also file filter doesn't work on Windows
|
||||
* */
|
||||
|
||||
Reference in New Issue
Block a user