diff --git a/core/dylib/src/main/java/ch/cyberduck/core/preferences/ApplicationPreferences.java b/core/dylib/src/main/java/ch/cyberduck/core/preferences/ApplicationPreferences.java index 67326ed656d..614826f0a3e 100644 --- a/core/dylib/src/main/java/ch/cyberduck/core/preferences/ApplicationPreferences.java +++ b/core/dylib/src/main/java/ch/cyberduck/core/preferences/ApplicationPreferences.java @@ -53,6 +53,7 @@ import ch.cyberduck.core.urlhandler.LaunchServicesSchemeHandler; import ch.cyberduck.core.urlhandler.WorkspaceSchemeHandler; import ch.cyberduck.core.webloc.WeblocFileWriter; +import ch.cyberduck.ui.pasteboard.WorkspacePasteboardService; public class ApplicationPreferences extends UserDefaultsPreferences { @@ -121,6 +122,7 @@ protected void setFactories() { this.setDefault("factory.urlfilewriter.class", WeblocFileWriter.class.getName()); this.setDefault("factory.quicklook.class", QuartzQuickLook.class.getName()); this.setDefault("factory.hardwareaddress.class", IOKitHardwareAddress.class.getName()); + this.setDefault("factory.pasteboardservice.class", WorkspacePasteboardService.class.getName()); } @Override diff --git a/core/dylib/src/main/java/ch/cyberduck/ui/pasteboard/WorkspacePasteboardService.java b/core/dylib/src/main/java/ch/cyberduck/ui/pasteboard/WorkspacePasteboardService.java new file mode 100644 index 00000000000..39a5549b47a --- /dev/null +++ b/core/dylib/src/main/java/ch/cyberduck/ui/pasteboard/WorkspacePasteboardService.java @@ -0,0 +1,45 @@ +package ch.cyberduck.ui.pasteboard; + +/* + * Copyright (c) 2002-2025 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program 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. + * + * This program 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. + */ + +import ch.cyberduck.binding.application.NSPasteboard; +import ch.cyberduck.binding.foundation.NSArray; +import ch.cyberduck.binding.foundation.NSString; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class WorkspacePasteboardService implements PasteboardService { + private static final Logger log = LogManager.getLogger(WorkspacePasteboardService.class.getName()); + + @Override + public boolean add(final Type type, final String content) { + switch(type) { + case url: + case string: + final NSPasteboard pboard = NSPasteboard.generalPasteboard(); + pboard.declareTypes(NSArray.arrayWithObject(NSString.stringWithString(NSPasteboard.StringPboardType)), null); + if(!pboard.setStringForType(content, NSPasteboard.StringPboardType)) { + log.error("Error writing content to {}", NSPasteboard.StringPboardType); + } + return true; + default: + log.warn("Unsupported pasteboard type {}", type); + return false; + + } + } +} diff --git a/core/src/main/csharp/ch/cyberduck/ui/pasteboard/ClipboardService.cs b/core/src/main/csharp/ch/cyberduck/ui/pasteboard/ClipboardService.cs new file mode 100644 index 00000000000..122ce43fcc7 --- /dev/null +++ b/core/src/main/csharp/ch/cyberduck/ui/pasteboard/ClipboardService.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) 2015-2017 iterate GmbH. All rights reserved. +// + +using System.Threading; +using org.apache.logging.log4j; +using System; +using System.Windows.Forms; +using ch.cyberduck.ui.pasteboard; + +namespace Ch.Cyberduck.Ui.Pasteboard +{ + public class ClipboardService : PasteboardService + { + public static Logger Logger { get; } = LogManager.getLogger(typeof(ClipboardService).AssemblyQualifiedName); + + public bool add(PasteboardService.Type type, string content) + { + if (string.IsNullOrWhiteSpace(content)) + { + // Content is empty, do not set clipboard. + return false; + } + if (type == PasteboardService.Type.@string || type == PasteboardService.Type.url) + { + // needs STA threading model + Thread thread = new Thread(() => + { + try + { + Clipboard.SetText(content); + } + catch (Exception exception) + { + Logger.error($"Failed to set clipboard to value {content}", exception); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + return true; + } + else + { + Logger.error($"Unsupported pasteboard type {type}"); + return false; + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/ui/pasteboard/PasteboardService.java b/core/src/main/java/ch/cyberduck/ui/pasteboard/PasteboardService.java new file mode 100644 index 00000000000..388c96c06d9 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/ui/pasteboard/PasteboardService.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2016 iterate GmbH. All rights reserved. + */ + +package ch.cyberduck.ui.pasteboard; + +public interface PasteboardService { + + enum Type { + string, + url, + filename + } + + boolean add(Type type, String content); +} diff --git a/core/src/main/java/ch/cyberduck/ui/pasteboard/PasteboardServiceFactory.java b/core/src/main/java/ch/cyberduck/ui/pasteboard/PasteboardServiceFactory.java new file mode 100644 index 00000000000..1a4140ace0a --- /dev/null +++ b/core/src/main/java/ch/cyberduck/ui/pasteboard/PasteboardServiceFactory.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2016 iterate GmbH. All rights reserved. + */ + +package ch.cyberduck.ui.pasteboard; + +import ch.cyberduck.core.Factory; + +public class PasteboardServiceFactory extends Factory { + protected PasteboardServiceFactory() { + super("factory.pasteboardservice.class"); + } + + public static PasteboardService get() { + return new PasteboardServiceFactory().create(); + } +} diff --git a/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BrowserController.java b/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BrowserController.java index 09d4c9dbf00..dc148ad300b 100644 --- a/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BrowserController.java +++ b/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BrowserController.java @@ -126,6 +126,8 @@ import ch.cyberduck.ui.cocoa.toolbar.BrowserToolbarValidator; import ch.cyberduck.ui.cocoa.view.BookmarkCell; import ch.cyberduck.ui.cocoa.view.OutlineCell; +import ch.cyberduck.ui.pasteboard.PasteboardService; +import ch.cyberduck.ui.pasteboard.PasteboardServiceFactory; import ch.cyberduck.ui.quicklook.QuickLook; import ch.cyberduck.ui.quicklook.QuickLookFactory; @@ -2609,11 +2611,7 @@ public void cleanup(final DescriptiveUrl url) { public void callback(final int returncode) { switch(returncode) { case SheetCallback.CANCEL_OPTION: - final NSPasteboard pboard = NSPasteboard.generalPasteboard(); - pboard.declareTypes(NSArray.arrayWithObject(NSString.stringWithString(NSPasteboard.StringPboardType)), null); - if(!pboard.setStringForType(url.getUrl(), NSPasteboard.StringPboardType)) { - log.error("Error writing URL to {}", NSPasteboard.StringPboardType); - } + PasteboardServiceFactory.get().add(PasteboardService.Type.url, url.getUrl()); } } @@ -2655,11 +2653,7 @@ public void cleanup(final DescriptiveUrl url) { public void callback(final int returncode) { switch(returncode) { case SheetCallback.CANCEL_OPTION: - final NSPasteboard pboard = NSPasteboard.generalPasteboard(); - pboard.declareTypes(NSArray.arrayWithObject(NSString.stringWithString(NSPasteboard.StringPboardType)), null); - if(!pboard.setStringForType(url.getUrl(), NSPasteboard.StringPboardType)) { - log.error("Error writing URL to {}", NSPasteboard.StringPboardType); - } + PasteboardServiceFactory.get().add(PasteboardService.Type.url, url.getUrl()); } } diff --git a/osx/src/main/java/ch/cyberduck/ui/cocoa/delegate/CopyURLMenuDelegate.java b/osx/src/main/java/ch/cyberduck/ui/cocoa/delegate/CopyURLMenuDelegate.java index 339e1af8804..b4c057d3d3e 100644 --- a/osx/src/main/java/ch/cyberduck/ui/cocoa/delegate/CopyURLMenuDelegate.java +++ b/osx/src/main/java/ch/cyberduck/ui/cocoa/delegate/CopyURLMenuDelegate.java @@ -20,13 +20,12 @@ */ import ch.cyberduck.binding.application.NSEvent; -import ch.cyberduck.binding.application.NSPasteboard; -import ch.cyberduck.binding.foundation.NSArray; -import ch.cyberduck.binding.foundation.NSString; import ch.cyberduck.core.DescriptiveUrl; import ch.cyberduck.core.Path; import ch.cyberduck.core.UrlProvider; import ch.cyberduck.core.pool.SessionPool; +import ch.cyberduck.ui.pasteboard.PasteboardService; +import ch.cyberduck.ui.pasteboard.PasteboardServiceFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -68,10 +67,6 @@ public void handle(final List selected) { url.append("\n"); } } - final NSPasteboard pboard = NSPasteboard.generalPasteboard(); - pboard.declareTypes(NSArray.arrayWithObject(NSString.stringWithString(NSPasteboard.StringPboardType)), null); - if(!pboard.setStringForType(url.toString(), NSPasteboard.StringPboardType)) { - log.error("Error writing URL to {}", NSPasteboard.StringPboardType); - } + PasteboardServiceFactory.get().add(PasteboardService.Type.string, url.toString()); } } diff --git a/windows/src/main/csharp/ch/cyberduck/ui/controller/BrowserController.cs b/windows/src/main/csharp/ch/cyberduck/ui/controller/BrowserController.cs index 1d8fadabd9d..c5ebbd8f428 100644 --- a/windows/src/main/csharp/ch/cyberduck/ui/controller/BrowserController.cs +++ b/windows/src/main/csharp/ch/cyberduck/ui/controller/BrowserController.cs @@ -39,6 +39,7 @@ using ch.cyberduck.core.worker; using ch.cyberduck.ui.browser; using ch.cyberduck.ui.comparator; +using ch.cyberduck.ui.pasteboard; using ch.cyberduck.ui.Views; using Ch.Cyberduck.Core; using Ch.Cyberduck.Core.Local; @@ -3705,7 +3706,7 @@ public override void cleanup(object result) switch (option) { case 1: - Clipboard.SetText(url.getUrl()); + PasteboardServiceFactory.get().add(PasteboardService.Type.url, url.getUrl()); break; } }); @@ -3750,7 +3751,7 @@ public override void cleanup(object result) switch (option) { case 1: - Clipboard.SetText(url.getUrl()); + PasteboardServiceFactory.get().add(PasteboardService.Type.url, url.getUrl()); break; } }); diff --git a/windows/src/main/csharp/ch/cyberduck/ui/core/preferences/ApplicationPreferences.cs b/windows/src/main/csharp/ch/cyberduck/ui/core/preferences/ApplicationPreferences.cs index 3598a39a711..24a00dd7e6c 100644 --- a/windows/src/main/csharp/ch/cyberduck/ui/core/preferences/ApplicationPreferences.cs +++ b/windows/src/main/csharp/ch/cyberduck/ui/core/preferences/ApplicationPreferences.cs @@ -20,7 +20,6 @@ using ch.cyberduck.core.cryptomator; using ch.cyberduck.core.cryptomator.random; using ch.cyberduck.core.local; -using ch.cyberduck.core.serviceloader; using Ch.Cyberduck.Core; using Ch.Cyberduck.Core.AquaticPrime; using Ch.Cyberduck.Core.Date; @@ -34,6 +33,7 @@ using Ch.Cyberduck.Core.Sparkle; using Ch.Cyberduck.Core.Urlhandler; using Ch.Cyberduck.Ui.Controller; +using Ch.Cyberduck.Ui.Pasteboard; using Ch.Cyberduck.Ui.Winforms.Threading; using CoreApplicationPreferences = Ch.Cyberduck.Core.Preferences.ApplicationPreferences; using Rendezvous = Ch.Cyberduck.Core.Bonjour.Rendezvous; @@ -119,6 +119,7 @@ protected override void setFactories() } this.setDefault("factory.vault.class", typeof(CryptoVault).AssemblyQualifiedName); this.setDefault("factory.securerandom.class", typeof(FastSecureRandomProvider).AssemblyQualifiedName); + this.setDefault("factory.pasteboardservice.class", typeof(ClipboardService).AssemblyQualifiedName); } } } diff --git a/windows/src/main/csharp/ch/cyberduck/ui/winforms/controls/ClickLinkLabel.cs b/windows/src/main/csharp/ch/cyberduck/ui/winforms/controls/ClickLinkLabel.cs index 48dbc16b6d2..51e034edcee 100644 --- a/windows/src/main/csharp/ch/cyberduck/ui/winforms/controls/ClickLinkLabel.cs +++ b/windows/src/main/csharp/ch/cyberduck/ui/winforms/controls/ClickLinkLabel.cs @@ -21,6 +21,7 @@ using System.Windows.Forms; using Ch.Cyberduck.Core; using ch.cyberduck.core; +using ch.cyberduck.ui.pasteboard; using ch.cyberduck.core.local; namespace Ch.Cyberduck.Ui.Winforms.Controls @@ -33,7 +34,7 @@ public ClickLinkLabel() { ContextMenuStrip contextMenu = new ContextMenuStrip(); ToolStripItem addItem = contextMenu.Items.Add(LocaleFactory.localizedString("Copy URL", "Browser")); - addItem.Click += (sender, args) => Clipboard.SetText(Text); + addItem.Click += (sender, args) => PasteboardServiceFactory.get().add(PasteboardService.Type.url, Text); ContextMenuStrip = contextMenu; } }