From 78c1e6c7686caa3ef3b9cd9effd74f47741457c1 Mon Sep 17 00:00:00 2001 From: David Gerber Date: Wed, 20 Nov 2024 22:47:38 +0100 Subject: [PATCH] Make clipboard support use AWT instead of JavaFX Fixes the Telegram bugs for me. Let's see if it fixes defnax' issues. --- .../ui/controller/MainWindowController.java | 10 +-- .../controller/chat/ChatViewController.java | 12 +-- .../debug/PropertiesWindowController.java | 11 +-- .../ui/controller/file/FileResultView.java | 7 +- .../file/FileSearchViewController.java | 7 +- .../controller/forum/ForumViewController.java | 11 +-- .../messaging/MessagingWindowController.java | 3 +- .../java/io/xeres/ui/custom/EditorView.java | 8 +- .../ui/support/clipboard/ClipboardUtils.java | 75 +++++++++++++++++++ .../{util => clipboard}/ImageSelection.java | 4 +- .../ui/support/contentline/ContentImage.java | 10 +-- .../io/xeres/ui/support/util/UiUtils.java | 9 +-- 12 files changed, 108 insertions(+), 59 deletions(-) create mode 100644 ui/src/main/java/io/xeres/ui/support/clipboard/ClipboardUtils.java rename ui/src/main/java/io/xeres/ui/support/{util => clipboard}/ImageSelection.java (94%) diff --git a/ui/src/main/java/io/xeres/ui/controller/MainWindowController.java b/ui/src/main/java/io/xeres/ui/controller/MainWindowController.java index 886723e7..163518ee 100644 --- a/ui/src/main/java/io/xeres/ui/controller/MainWindowController.java +++ b/ui/src/main/java/io/xeres/ui/controller/MainWindowController.java @@ -38,6 +38,7 @@ import io.xeres.ui.custom.ReadOnlyTextField; import io.xeres.ui.custom.led.LedControl; import io.xeres.ui.custom.led.LedStatus; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.tray.TrayService; import io.xeres.ui.support.uri.*; import io.xeres.ui.support.util.TooltipUtils; @@ -50,8 +51,6 @@ import javafx.geometry.Pos; import javafx.scene.control.*; import javafx.scene.image.ImageView; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.scene.input.MouseButton; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; @@ -349,12 +348,7 @@ public void onHidden() private void copyOwnId() { var rsIdResponse = locationClient.getRSId(OWN_LOCATION_ID, Type.ANY); - rsIdResponse.subscribe(reply -> Platform.runLater(() -> { - var clipboard = Clipboard.getSystemClipboard(); - var content = new ClipboardContent(); - content.putString(reply.rsId()); - clipboard.setContent(content); - })); + rsIdResponse.subscribe(reply -> Platform.runLater(() -> ClipboardUtils.copyTextToClipboard(reply.rsId()))); } private void showQrCode() diff --git a/ui/src/main/java/io/xeres/ui/controller/chat/ChatViewController.java b/ui/src/main/java/io/xeres/ui/controller/chat/ChatViewController.java index 602556bd..68aaf219 100644 --- a/ui/src/main/java/io/xeres/ui/controller/chat/ChatViewController.java +++ b/ui/src/main/java/io/xeres/ui/controller/chat/ChatViewController.java @@ -32,6 +32,7 @@ import io.xeres.ui.custom.asyncimage.ImageCache; import io.xeres.ui.support.chat.ChatCommand; import io.xeres.ui.support.chat.NicknameCompleter; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.contextmenu.XContextMenu; import io.xeres.ui.support.markdown.MarkdownService; import io.xeres.ui.support.preference.PreferenceService; @@ -52,7 +53,10 @@ import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.ImageView; -import javafx.scene.input.*; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; @@ -313,10 +317,8 @@ private void createRoomTreeContextMenu() copyLinkItem.setId(COPY_LINK_MENU_ID); copyLinkItem.setGraphic(new FontIcon(MaterialDesignL.LINK_VARIANT)); copyLinkItem.setOnAction(event -> { - var clipboardContent = new ClipboardContent(); var chatRoomInfo = ((RoomHolder) event.getSource()).getRoomInfo(); - clipboardContent.putString(ChatRoomUriFactory.generate(chatRoomInfo.getName(), chatRoomInfo.getId())); - Clipboard.getSystemClipboard().setContent(clipboardContent); + ClipboardUtils.copyTextToClipboard(ChatRoomUriFactory.generate(chatRoomInfo.getName(), chatRoomInfo.getId())); }); var xContextMenu = new XContextMenu(subscribeItem, unsubscribeItem, new SeparatorMenuItem(), copyLinkItem); @@ -656,7 +658,7 @@ private void handleInputKeys(KeyEvent event) if (PASTE_KEY.match(event)) { - var image = Clipboard.getSystemClipboard().getImage(); + var image = ClipboardUtils.getImageFromClipboard(); if (image != null) { imagePreview.setImage(image); diff --git a/ui/src/main/java/io/xeres/ui/controller/debug/PropertiesWindowController.java b/ui/src/main/java/io/xeres/ui/controller/debug/PropertiesWindowController.java index 9991822e..96c35d68 100644 --- a/ui/src/main/java/io/xeres/ui/controller/debug/PropertiesWindowController.java +++ b/ui/src/main/java/io/xeres/ui/controller/debug/PropertiesWindowController.java @@ -20,14 +20,13 @@ package io.xeres.ui.controller.debug; import io.xeres.ui.controller.WindowController; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.contextmenu.XContextMenu; import javafx.beans.property.SimpleStringProperty; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import net.rgielen.fxweaver.core.FxmlView; import org.springframework.stereotype.Component; @@ -69,11 +68,9 @@ public void initialize() tableName.setSortType(ASCENDING); copyAll.setOnAction(event -> { - var clipboardContent = new ClipboardContent(); var sb = new StringBuilder(); getSortedProperties().forEach((k, v) -> sb.append(k).append(": ").append(showLineSeparator(v)).append("\n")); - clipboardContent.putString(showLineSeparator(sb.toString())); - Clipboard.getSystemClipboard().setContent(clipboardContent); + ClipboardUtils.copyTextToClipboard(showLineSeparator(sb.toString())); }); } @@ -104,9 +101,7 @@ private void createPropertiesTableViewContextMenu() @SuppressWarnings("unchecked") var entry = (Map.Entry) event.getSource(); if (entry != null) { - var clipboardContent = new ClipboardContent(); - clipboardContent.putString(entry.getKey() + " = " + showLineSeparator(entry.getValue())); - Clipboard.getSystemClipboard().setContent(clipboardContent); + ClipboardUtils.copyTextToClipboard(entry.getKey() + " = " + showLineSeparator(entry.getValue())); } }); var xContextMenu = new XContextMenu>(copyItem); diff --git a/ui/src/main/java/io/xeres/ui/controller/file/FileResultView.java b/ui/src/main/java/io/xeres/ui/controller/file/FileResultView.java index 23f735e6..8c65c639 100644 --- a/ui/src/main/java/io/xeres/ui/controller/file/FileResultView.java +++ b/ui/src/main/java/io/xeres/ui/controller/file/FileResultView.java @@ -23,6 +23,7 @@ import io.xeres.common.i18n.I18nUtils; import io.xeres.common.id.Sha1Sum; import io.xeres.ui.client.FileClient; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.contextmenu.XContextMenu; import io.xeres.ui.support.uri.FileUriFactory; import javafx.application.Platform; @@ -34,8 +35,6 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.scene.layout.StackPane; import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.materialdesign2.MaterialDesignF; @@ -174,9 +173,7 @@ private void createFilesTableViewContextMenu() copyLinkItem.setOnAction(event -> { if (event.getSource() instanceof FileResult file) { - var clipboardContent = new ClipboardContent(); - clipboardContent.putString(FileUriFactory.generate(file.name(), file.size(), Sha1Sum.fromString(file.hash()))); - Clipboard.getSystemClipboard().setContent(clipboardContent); + ClipboardUtils.copyTextToClipboard(FileUriFactory.generate(file.name(), file.size(), Sha1Sum.fromString(file.hash()))); } }); diff --git a/ui/src/main/java/io/xeres/ui/controller/file/FileSearchViewController.java b/ui/src/main/java/io/xeres/ui/controller/file/FileSearchViewController.java index 57d08b07..ffca0cac 100644 --- a/ui/src/main/java/io/xeres/ui/controller/file/FileSearchViewController.java +++ b/ui/src/main/java/io/xeres/ui/controller/file/FileSearchViewController.java @@ -24,6 +24,7 @@ import io.xeres.ui.client.NotificationClient; import io.xeres.ui.controller.Controller; import io.xeres.ui.controller.TabActivation; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.contextmenu.XContextMenu; import io.xeres.ui.support.uri.SearchUri; import io.xeres.ui.support.uri.SearchUriFactory; @@ -35,8 +36,6 @@ import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TextField; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.scene.input.KeyCode; import net.rgielen.fxweaver.core.FxmlView; import org.apache.commons.lang3.StringUtils; @@ -158,10 +157,8 @@ private void createContextMenu() copyLinkItem.setId(COPY_LINK_MENU_ID); copyLinkItem.setGraphic(new FontIcon(MaterialDesignL.LINK_VARIANT)); copyLinkItem.setOnAction(event -> { - var clipboardContent = new ClipboardContent(); var fileResultView = (FileResultView) event.getSource(); - clipboardContent.putString(SearchUriFactory.generate(StringUtils.left(fileResultView.getText(), 50), fileResultView.getText())); - Clipboard.getSystemClipboard().setContent(clipboardContent); + ClipboardUtils.copyTextToClipboard(SearchUriFactory.generate(StringUtils.left(fileResultView.getText(), 50), fileResultView.getText())); }); var xContextMenu = new XContextMenu(copyLinkItem); diff --git a/ui/src/main/java/io/xeres/ui/controller/forum/ForumViewController.java b/ui/src/main/java/io/xeres/ui/controller/forum/ForumViewController.java index a173f878..9a502deb 100644 --- a/ui/src/main/java/io/xeres/ui/controller/forum/ForumViewController.java +++ b/ui/src/main/java/io/xeres/ui/controller/forum/ForumViewController.java @@ -35,6 +35,7 @@ import io.xeres.ui.custom.ProgressPane; import io.xeres.ui.custom.asyncimage.ImageCache; import io.xeres.ui.model.forum.ForumMapper; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.contentline.Content; import io.xeres.ui.support.contextmenu.XContextMenu; import io.xeres.ui.support.markdown.MarkdownService; @@ -52,8 +53,6 @@ import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.control.cell.TreeItemPropertyValueFactory; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.scene.layout.GridPane; import javafx.scene.text.TextFlow; import net.rgielen.fxweaver.core.FxmlView; @@ -320,10 +319,8 @@ private void createForumTreeContextMenu() copyLinkItem.setId(COPY_LINK_MENU_ID); copyLinkItem.setGraphic(new FontIcon(MaterialDesignL.LINK_VARIANT)); copyLinkItem.setOnAction(event -> { - var clipboardContent = new ClipboardContent(); var forumGroup = ((ForumGroup) event.getSource()); - clipboardContent.putString(ForumUriFactory.generate(forumGroup.getName(), forumGroup.getGxsId())); - Clipboard.getSystemClipboard().setContent(clipboardContent); + ClipboardUtils.copyTextToClipboard(ForumUriFactory.generate(forumGroup.getName(), forumGroup.getGxsId())); }); var xContextMenu = new XContextMenu(subscribeItem, unsubscribeItem, new SeparatorMenuItem(), copyLinkItem); @@ -351,10 +348,8 @@ private void createForumMessageTableViewContextMenu() copyLinkItem.setId(COPY_LINK_MENU_ID); copyLinkItem.setGraphic(new FontIcon(MaterialDesignL.LINK_VARIANT)); copyLinkItem.setOnAction(event -> { - var clipboardContent = new ClipboardContent(); @SuppressWarnings("unchecked") var forumMessage = ((TreeItem) event.getSource()).getValue(); - clipboardContent.putString(ForumUriFactory.generate(forumMessage.getName(), forumMessage.getGxsId(), forumMessage.getMessageId())); - Clipboard.getSystemClipboard().setContent(clipboardContent); + ClipboardUtils.copyTextToClipboard(ForumUriFactory.generate(forumMessage.getName(), forumMessage.getGxsId(), forumMessage.getMessageId())); }); var xContextMenu = new XContextMenu(replyItem, new SeparatorMenuItem(), copyLinkItem); diff --git a/ui/src/main/java/io/xeres/ui/controller/messaging/MessagingWindowController.java b/ui/src/main/java/io/xeres/ui/controller/messaging/MessagingWindowController.java index 6fca3c28..9dc385a6 100644 --- a/ui/src/main/java/io/xeres/ui/controller/messaging/MessagingWindowController.java +++ b/ui/src/main/java/io/xeres/ui/controller/messaging/MessagingWindowController.java @@ -38,6 +38,7 @@ import io.xeres.ui.custom.asyncimage.ImageCache; import io.xeres.ui.model.profile.Profile; import io.xeres.ui.support.chat.ChatCommand; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.markdown.MarkdownService; import io.xeres.ui.support.uri.FileUri; import io.xeres.ui.support.uri.FileUriFactory; @@ -382,7 +383,7 @@ private void handleInputKeys(KeyEvent event) { if (PASTE_KEY.match(event)) { - var image = Clipboard.getSystemClipboard().getImage(); + var image = ClipboardUtils.getImageFromClipboard(); if (image != null) { sendImageViewToMessage(new ImageView(image)); diff --git a/ui/src/main/java/io/xeres/ui/custom/EditorView.java b/ui/src/main/java/io/xeres/ui/custom/EditorView.java index f170eda6..5530963a 100644 --- a/ui/src/main/java/io/xeres/ui/custom/EditorView.java +++ b/ui/src/main/java/io/xeres/ui/custom/EditorView.java @@ -21,6 +21,7 @@ import io.xeres.common.i18n.I18nUtils; import io.xeres.ui.client.LocationClient; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.contentline.Content; import io.xeres.ui.support.markdown.MarkdownService; import io.xeres.ui.support.util.ImageUtils; @@ -31,7 +32,10 @@ import javafx.fxml.FXMLLoader; import javafx.scene.control.*; import javafx.scene.image.ImageView; -import javafx.scene.input.*; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.VBox; import javafx.scene.text.TextFlow; import javafx.stage.Window; @@ -392,7 +396,7 @@ private void handleInputKeys(KeyEvent event) if (PASTE_KEY.match(event)) { - var image = Clipboard.getSystemClipboard().getImage(); + var image = ClipboardUtils.getImageFromClipboard(); if (image != null) { var imageView = new ImageView(image); diff --git a/ui/src/main/java/io/xeres/ui/support/clipboard/ClipboardUtils.java b/ui/src/main/java/io/xeres/ui/support/clipboard/ClipboardUtils.java new file mode 100644 index 00000000..98e77a35 --- /dev/null +++ b/ui/src/main/java/io/xeres/ui/support/clipboard/ClipboardUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 by David Gerber - https://zapek.com + * + * This file is part of Xeres. + * + * Xeres is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Xeres is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xeres. If not, see . + */ + +package io.xeres.ui.support.clipboard; + +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.Image; + +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.image.BufferedImage; +import java.io.IOException; + +/** + * Utility class to use the clipboard. This implementation uses AWT because the clipboard support of JavaFX is, quite frankly, a + * royal piece of shit. + *

+ * Fails to work with some bitmaps (for example from Telegram, Windows 10 and print screen, Chrome, ...). + *

+ * Fails with data URIs because it tries to find out if the image is a supported format and even though it is, the URL is "wrong" for it. + */ +public final class ClipboardUtils +{ + private ClipboardUtils() + { + throw new UnsupportedOperationException("Utility class"); + } + + public static Image getImageFromClipboard() + { + var transferable = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); + if (transferable != null && transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) + { + BufferedImage image; + try + { + image = (BufferedImage) transferable.getTransferData(DataFlavor.imageFlavor); + } + catch (UnsupportedFlavorException | IOException e) + { + throw new RuntimeException(e); + } + return SwingFXUtils.toFXImage(image, null); + } + return null; + } + + public static void copyImageToClipboard(Image image) + { + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new ImageSelection(SwingFXUtils.fromFXImage(image, null)), null); + } + + public static void copyTextToClipboard(String text) + { + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(text), null); + } +} diff --git a/ui/src/main/java/io/xeres/ui/support/util/ImageSelection.java b/ui/src/main/java/io/xeres/ui/support/clipboard/ImageSelection.java similarity index 94% rename from ui/src/main/java/io/xeres/ui/support/util/ImageSelection.java rename to ui/src/main/java/io/xeres/ui/support/clipboard/ImageSelection.java index 2c9ff1be..e73f2a0c 100644 --- a/ui/src/main/java/io/xeres/ui/support/util/ImageSelection.java +++ b/ui/src/main/java/io/xeres/ui/support/clipboard/ImageSelection.java @@ -17,7 +17,7 @@ * along with Xeres. If not, see . */ -package io.xeres.ui.support.util; +package io.xeres.ui.support.clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; @@ -27,7 +27,7 @@ /** * This class is needed to save images to the clipboard using AWT. */ -public class ImageSelection implements Transferable +class ImageSelection implements Transferable { private final BufferedImage image; diff --git a/ui/src/main/java/io/xeres/ui/support/contentline/ContentImage.java b/ui/src/main/java/io/xeres/ui/support/contentline/ContentImage.java index 4f612439..a36f08f9 100644 --- a/ui/src/main/java/io/xeres/ui/support/contentline/ContentImage.java +++ b/ui/src/main/java/io/xeres/ui/support/contentline/ContentImage.java @@ -21,7 +21,7 @@ import io.xeres.common.i18n.I18nUtils; import io.xeres.ui.custom.ResizeableImageView; -import io.xeres.ui.support.util.ImageSelection; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.util.UiUtils; import javafx.embed.swing.SwingFXUtils; import javafx.event.ActionEvent; @@ -47,7 +47,6 @@ import org.kordamp.ikonli.materialdesign2.MaterialDesignI; import javax.imageio.ImageIO; -import java.awt.*; import java.io.File; import java.io.IOException; import java.time.Instant; @@ -104,12 +103,7 @@ public Node getNode() private static void copyToClipboard(ActionEvent event) { - // We cannot use ClipboardContent/putImage() here because - // JavaFX tries to find out if the image url is a supported format - // and this fails with data: URLs. It would have worked with a null - // URL, but one would have to copy the Image from a ByteBufferArray which - // is more complicated than just using AWT, which gets it right (sigh). - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new ImageSelection(SwingFXUtils.fromFXImage(getImageViewFromEvent(event).getImage(), null)), null); + ClipboardUtils.copyImageToClipboard(getImageViewFromEvent(event).getImage()); } private static void saveAs(ActionEvent event) diff --git a/ui/src/main/java/io/xeres/ui/support/util/UiUtils.java b/ui/src/main/java/io/xeres/ui/support/util/UiUtils.java index 949fbaf7..7a09f2bc 100644 --- a/ui/src/main/java/io/xeres/ui/support/util/UiUtils.java +++ b/ui/src/main/java/io/xeres/ui/support/util/UiUtils.java @@ -22,6 +22,7 @@ import atlantafx.base.theme.Styles; import io.xeres.common.AppName; import io.xeres.ui.custom.DisclosedHyperlink; +import io.xeres.ui.support.clipboard.ClipboardUtils; import io.xeres.ui.support.uri.UriService; import javafx.application.Platform; import javafx.css.PseudoClass; @@ -35,8 +36,6 @@ import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.scene.layout.*; import javafx.stage.Stage; import javafx.stage.Window; @@ -202,11 +201,7 @@ private static Alert buildAlert(AlertType alertType, String title, String messag } alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); // Without this, long texts get truncated. Go figure why this isn't the default... - copyButton.setOnAction(event -> { - var clipboardContent = new ClipboardContent(); - clipboardContent.putString(generateAlertErrorString(alertType, title, message, stackTrace)); - Clipboard.getSystemClipboard().setContent(clipboardContent); - }); + copyButton.setOnAction(event -> ClipboardUtils.copyTextToClipboard(generateAlertErrorString(alertType, title, message, stackTrace))); return alert; }