Fix clipboard threading crash

This commit is contained in:
crschnick
2025-06-01 08:16:35 +00:00
parent 90d2712796
commit 1388fca72e
4 changed files with 85 additions and 38 deletions
@@ -2,6 +2,7 @@ package io.xpipe.app.browser.file;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.GlobalClipboard;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileEntry;
import io.xpipe.core.util.FailableRunnable;
@@ -23,6 +24,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class BrowserClipboard {
@@ -32,44 +34,33 @@ public class BrowserClipboard {
private static final DataFormat DATA_FORMAT = new DataFormat("application/xpipe-file-list");
static {
Toolkit.getDefaultToolkit()
.getSystemClipboard()
.addFlavorListener(e -> ThreadHelper.runFailableAsync(new FailableRunnable<>() {
@Override
@SuppressWarnings("unchecked")
public void run() {
// Fix clipboard open issues: https://stackoverflow.com/a/51797746
ThreadHelper.sleep(20);
Clipboard clipboard = (Clipboard) e.getSource();
try {
if (!clipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor)) {
return;
}
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
// Sometimes file data can contain invalid chars. Why?
var files = data.stream()
.filter(file ->
file.toString().chars().noneMatch(value -> Character.isISOControl(value)))
.map(f -> f.toPath())
.toList();
if (files.size() == 0) {
return;
}
var entries = new ArrayList<BrowserEntry>();
for (Path file : files) {
entries.add(BrowserLocalFileSystem.getLocalBrowserEntry(file));
}
currentCopyClipboard.setValue(
new Instance(UUID.randomUUID(), null, entries, BrowserFileTransferMode.COPY));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).expected().omit().handle();
}
GlobalClipboard.addListener(new Consumer<>() {
@Override
@SuppressWarnings("unchecked")
public void accept(Clipboard clipboard) {
try {
if (!clipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor)) {
return;
}
}));
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
// Sometimes file data can contain invalid chars. Why?
var files = data.stream().filter(file -> file.toString().chars().noneMatch(value -> Character.isISOControl(value))).map(f -> f.toPath()).toList();
if (files.size() == 0) {
return;
}
var entries = new ArrayList<BrowserEntry>();
for (Path file : files) {
entries.add(BrowserLocalFileSystem.getLocalBrowserEntry(file));
}
currentCopyClipboard.setValue(new Instance(UUID.randomUUID(), null, entries, BrowserFileTransferMode.COPY));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).expected().omit().handle();
}
}
});
}
@SneakyThrows
@@ -122,7 +122,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
var pubSource = Path.of(source + ".pub");
if (Files.exists(pubSource)) {
var pubTarget = sync.getTargetLocation().apply(pubSource);
DataStorageSyncHandler.getInstance()
handler
.addDataFile(
pubSource, pubTarget, sync.getPerUser().test(pubSource));
}
@@ -111,6 +111,7 @@ public abstract class OperationMode {
TrackEvent.info("Initial setup");
AppMainWindow.loadingText("initializingApp");
GlobalTimer.init();
GlobalClipboard.init();
AppProperties.init(args);
NodeCallback.init();
AppLogs.init();
@@ -0,0 +1,55 @@
package io.xpipe.app.util;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.file.BrowserFileTransferMode;
import io.xpipe.app.browser.file.BrowserLocalFileSystem;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.FileEntry;
import io.xpipe.core.util.FailableRunnable;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import lombok.SneakyThrows;
import lombok.Value;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class GlobalClipboard {
private static final List<Consumer<Clipboard>> clipboardListeners = new ArrayList<>();
public static synchronized void addListener(Consumer<Clipboard> listener) {
clipboardListeners.add(listener);
}
public static void init() {
// Only access from one thread to fix https://bugs.openjdk.org/browse/JDK-8332271
Toolkit.getDefaultToolkit()
.getSystemClipboard()
.addFlavorListener(e -> {
// Fix clipboard open issues: https://stackoverflow.com/a/51797746
ThreadHelper.sleep(20);
var cp = (Clipboard) e.getSource();
ThreadHelper.runFailableAsync(() -> {
synchronized (GlobalClipboard.class) {
for (Consumer<Clipboard> clipboardListener : clipboardListeners) {
clipboardListener.accept(cp);
}
}
});
});
}
}