71

Is it possible to copy something to the clipboard using .Net Core (in a platform-agnostic way)?

It seems that the Clipboard class is missing, and P/Invoking isn't an option outside of Windows.

Edit: Unfortunately until now there's been a discrepancy between what my question said and what people heard when they read the question. Based on the comments and answers two things are clear. First, very few care whether the truest kind of "ivory tower" platform agnosticism exists or not. Second, when people post code examples showing how you use the clipboard on different platforms then the technically correct answer ("no, it's not possible") is confusing. So I have struck the parenthetical clause.

Matt Thomas
  • 5,279
  • 4
  • 27
  • 59
  • I'm a little confused by the use case here. Typically, .netcore apps are Web apps, or console apps. Neither of which makes any sense to have a clipboard integration (your web client would be the one that handles copy/paste, so you would need to write client-side javascript to do that). If you are writing a UWP app, then there is likely a clipboard api in UWP, but that's not something that is cross platform. what exactly are you trying to "clip"? – Erik Funkenbusch May 26 '17 at 16:02
  • 1
    In a platform agnostic way -- no. You can, of course, do it in various platform-specific ways, and they don't all require P/Invoking. `xsel` on Linux, `pbcopy`/`pbpaste` on Mac, `clip.exe` on Windows all allow simple text in/out using standard I/O. I'm guessing a working `Clipboard` class for all the .NET Core platforms is not high on most priority lists. Let alone one for more than simple text, since that gets highly platform-specific. – Jeroen Mostert May 26 '17 at 16:07
  • @ErikFunkenbusch In this case it's a console app and plain text. I agree there is a Better Way, but for this question I'm only concerned with what's possible – Matt Thomas May 26 '17 at 20:28
  • @MattThomas - Well, console applications have no real "UI". They output text to Standard-Out and it's the console application that is acting as the "viewer", much like a web browser is the "viewer" for a web app. Remember, that a console app can even be run over a remote connection like SSH or RSH, even in a web page in some cases. It makes no sense for a console app to have clipboard integration. – Erik Funkenbusch May 27 '17 at 00:09
  • @MattThomas - Consider a console app like Vim or Emacs. These have copy/paste inside their app, but this clipboard does not connect to the global OS clipboard. You can highlight stuff in the console (ie cmd.exe or powershell) and copy/paste but this is the consoles functionality, not the app. – Erik Funkenbusch May 27 '17 at 17:54
  • 6
    @ErikFunkenbusch I agree that 95% of the time there is no good reason to give a console application access to the global OS clipboard. But there do exist possible scenarios where it would be useful. For example, I asked this question because I was working on a little helper application that would auto-generate complicated strings for me to place somewhere else for a very specific use. It's certainly not something I'd sell. But it would be handy to eliminate the extra mouse interaction in selecting and copying from the console window. – Matt Thomas May 28 '17 at 01:28
  • 3
    @MattThomas - Regardless, there is no universal clipboard function, so no there would never be a way to make this cross platform. You'd have to write separate UI-specific apps for each environment. – Erik Funkenbusch May 28 '17 at 02:09
  • https://github.com/stavroskasidis/XamarinClipboardPlugin – tzachs May 23 '18 at 14:43
  • The answer from @Simon below proves it's very possible. I have a very good use case that brought to me this question so I also know there is a need. Where there is a need there is always a way. – Christopher Painter Aug 13 '20 at 14:14
  • @ChristopherPainter Unfortunately I framed the question looking for what you might call "ivory tower" platform agnosticism--meaning the clipboard would have to be a first-class concept in .Net Core for the answer to be "yes it's possible". It's on the basis of this unfortunate technicality that until now I haven't given his excellent answer the green check mark. But I understand how that can seem too pedantic, so I'm going to edit my question... there. How's it read now? – Matt Thomas Aug 13 '20 at 20:10
  • To me, that's what interfaces and providers are for. If I can abstract a common concept and get it to work in 8 out of 10 environments great. In the other two you can return a PlatformNotSupported exception and disable the feature or something and move on. – Christopher Painter Aug 14 '20 at 13:05
  • @MattThomas: `System.Windows.Clipboard` class is once again available in .NET Core 3.1, and `Clipboard.SetText` works; but I've tested only on Windows 10. – SNag Nov 13 '20 at 08:17

6 Answers6

112

This project of mine (https://github.com/SimonCropp/TextCopy) uses a mixed approach of PInvoke and command line invocation. it currently supports

  • Windows with .NET Framework 4.6.1 and up
  • Windows with .NET Core 2.0 and up
  • Windows with Mono 5.0 and up
  • OSX with .NET Core 2.0 and up
  • OSX with Mono 5.20.1 and up
  • Linux with .NET Core 2.0 and up
  • Linux with Mono 5.20.1 and up

Usage:

Install-Package TextCopy

TextCopy.ClipboardService.SetText("Text to place in clipboard");

Or just use the actual code

Windows

https://github.com/CopyText/TextCopy/blob/master/src/TextCopy/WindowsClipboard.cs

static class WindowsClipboard
{
    public static void SetText(string text)
    {
        OpenClipboard();

        EmptyClipboard();
        IntPtr hGlobal = default;
        try
        {
            var bytes = (text.Length + 1) * 2;
            hGlobal = Marshal.AllocHGlobal(bytes);

            if (hGlobal == default)
            {
                ThrowWin32();
            }

            var target = GlobalLock(hGlobal);

            if (target == default)
            {
                ThrowWin32();
            }

            try
            {
                Marshal.Copy(text.ToCharArray(), 0, target, text.Length);
            }
            finally
            {
                GlobalUnlock(target);
            }

            if (SetClipboardData(cfUnicodeText, hGlobal) == default)
            {
                ThrowWin32();
            }

            hGlobal = default;
        }
        finally
        {
            if (hGlobal != default)
            {
                Marshal.FreeHGlobal(hGlobal);
            }

            CloseClipboard();
        }
    }

    public static void OpenClipboard()
    {
        var num = 10;
        while (true)
        {
            if (OpenClipboard(default))
            {
                break;
            }

            if (--num == 0)
            {
                ThrowWin32();
            }

            Thread.Sleep(100);
        }
    }

    const uint cfUnicodeText = 13;

    static void ThrowWin32()
    {
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GlobalLock(IntPtr hMem);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GlobalUnlock(IntPtr hMem);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool OpenClipboard(IntPtr hWndNewOwner);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CloseClipboard();

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr SetClipboardData(uint uFormat, IntPtr data);

    [DllImport("user32.dll")]
    static extern bool EmptyClipboard();
}

macOS

https://github.com/CopyText/TextCopy/blob/master/src/TextCopy/OsxClipboard.cs

static class OsxClipboard
{
    public static void SetText(string text)
    {
        var nsString = objc_getClass("NSString");
        IntPtr str = default;
        IntPtr dataType = default;
        try
        {
            str = objc_msgSend(objc_msgSend(nsString, sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), text);
            dataType = objc_msgSend(objc_msgSend(nsString, sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), NSPasteboardTypeString);

            var nsPasteboard = objc_getClass("NSPasteboard");
            var generalPasteboard = objc_msgSend(nsPasteboard, sel_registerName("generalPasteboard"));

            objc_msgSend(generalPasteboard, sel_registerName("clearContents"));
            objc_msgSend(generalPasteboard, sel_registerName("setString:forType:"), str, dataType);
        }
        finally
        {
            if (str != default)
            {
                objc_msgSend(str, sel_registerName("release"));
            }

            if (dataType != default)
            {
                objc_msgSend(dataType, sel_registerName("release"));
            }
        }
    }

    [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
    static extern IntPtr objc_getClass(string className);

    [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
    static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector);

    [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
    static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, string arg1);

    [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
    static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);

    [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
    static extern IntPtr sel_registerName(string selectorName);

    const string NSPasteboardTypeString = "public.utf8-plain-text";
}

Linux

https://github.com/CopyText/TextCopy/blob/master/src/TextCopy/LinuxClipboard_2.1.cs

static class LinuxClipboard
{
    public static void SetText(string text)
    {
        var tempFileName = Path.GetTempFileName();
        File.WriteAllText(tempFileName, text);
        try
        {
            BashRunner.Run($"cat {tempFileName} | xclip");
        }
        finally
        {
            File.Delete(tempFileName);
        }
    }

    public static string GetText()
    {
        var tempFileName = Path.GetTempFileName();
        try
        {
            BashRunner.Run($"xclip -o > {tempFileName}");
            return File.ReadAllText(tempFileName);
        }
        finally
        {
            File.Delete(tempFileName);
        }
    }
}

static class BashRunner
{
    public static string Run(string commandLine)
    {
        var errorBuilder = new StringBuilder();
        var outputBuilder = new StringBuilder();
        var arguments = $"-c \"{commandLine}\"";
        using (var process = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "bash",
                Arguments = arguments,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = false,
            }
        })
        {
            process.Start();
            process.OutputDataReceived += (sender, args) => { outputBuilder.AppendLine(args.Data); };
            process.BeginOutputReadLine();
            process.ErrorDataReceived += (sender, args) => { errorBuilder.AppendLine(args.Data); };
            process.BeginErrorReadLine();
            if (!process.WaitForExit(500))
            {
                var timeoutError = $@"Process timed out. Command line: bash {arguments}.
Output: {outputBuilder}
Error: {errorBuilder}";
                throw new Exception(timeoutError);
            }
            if (process.ExitCode == 0)
            {
                return outputBuilder.ToString();
            }

            var error = $@"Could not execute process. Command line: bash {arguments}.
Output: {outputBuilder}
Error: {errorBuilder}";
            throw new Exception(error);
        }
    }
}
Simon
  • 33,714
  • 21
  • 133
  • 202
  • This is the solution for me. The windows implementation works with strings longer than 8191 characters. – Jonathan Wilson Feb 07 '19 at 16:40
  • Also works with Unicode characters, which appear as ??? if used with the `echo | clip` trick. Thanks. – Arshia001 Jul 26 '19 at 17:00
  • 2
    Thank you for taking the time to post this detailed solution which has been a large help to people. I think this covers 99% of people's real needs (most people don't need ivory tower platform agnosticism). However, would you care to comment on the relationship between platform-agnosticism and the Linux implementation's dependence on `bash` and `xclip` which are not included in all Linux distros and even when they are can be locked down with permissions or excluded from the `path` variable? I think those dependencies are all that's keeping me from giving this the green checkmark. Thanks again! – Matt Thomas Feb 11 '20 at 13:56
  • 2
    @MattThomas https://github.com/CopyText/TextCopy#notes-on-linux – Simon May 14 '20 at 02:27
  • 1
    @Simon: `System.Windows.Clipboard` class is once again available in .NET Core 3.1, and `Clipboard.SetText` works; but I've tested only on Windows 10. – SNag Nov 13 '20 at 08:20
  • I tested it on macOS and while it works for reasonably sized strings, it does not really work for gigantic strings. I hope that gets resolved. – Amir Hajiha Dec 08 '21 at 10:18
24

Clipboard class is missing, hope in near future will be add an option for that. While it happen ... you can run a native shell command with ProcessStartInfo.

I'm noob in Net Core, but create this code to send and string to clipboard on Windows and Mac:

OS Detection Class

public static class OperatingSystem
{
    public static bool IsWindows() =>
        RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

    public static bool IsMacOS() =>
        RuntimeInformation.IsOSPlatform(OSPlatform.OSX);

    public static bool IsLinux() =>
        RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
}

Shell Class
Based on https://loune.net/2017/06/running-shell-bash-commands-in-net-core/

public static class Shell
{
    public static string Bash(this string cmd)
    {
        var escapedArgs = cmd.Replace("\"", "\\\"");
        string result = Run("/bin/bash", $"-c \"{escapedArgs}\"");
        return result;
    }

    public static string Bat(this string cmd)
    {
        var escapedArgs = cmd.Replace("\"", "\\\"");
        string result = Run("cmd.exe", $"/c \"{escapedArgs}\"");
        return result;
    }

    private static string Run (string filename, string arguments){
        var process = new Process()
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = filename,
                Arguments = arguments,
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = false,
            }
        };
        process.Start();
        string result = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        return result;
    }
}

Clipboard Class

public static class Clipboard
{
    public static void Copy(string val)
    {
        if (OperatingSystem.IsWindows())
        {
            $"echo {val} | clip".Bat();
        }

        if (OperatingSystem.IsMacOS())
        {
            $"echo \"{val}\" | pbcopy".Bash();
        }
    }
}

Then Finally, you can call Clipboard Copy and can get the value on the clipboard.

var dirPath = @"C:\MyPath";
Clipboard.Copy(dirPath);

Hope it help others! Improvements are welcome.

I'm working in a ToolBox library for .net core with all this things: https://github.com/deinsoftware/toolbox (also available as NuGet Package).

Run a command in external terminal with .Net Core: https://dev.to/deinsoftware/run-a-command-in-external-terminal-with-net-core-d4l

equiman
  • 7,810
  • 2
  • 45
  • 47
  • 1
    I really like this general solution. It doesn't seem to work for very large text snippets, due to cmd.exe not accepting data past a certain size. Any ideas on other way to get something to the Windows clipboard? – Silas Reinagel Mar 01 '18 at 23:06
  • 1
    @SilasReinagel take a look of this: https://support.microsoft.com/en-us/help/830473/command-prompt-cmd-exe-command-line-string-limitation `On computers running Microsoft Windows XP or later, the maximum length of the string that you can use at the command prompt is 8191 characters.` – equiman Apr 02 '18 at 16:59
  • See Simon's answer for a solution that supports large strings. https://stackoverflow.com/a/51912933 – Jonathan Wilson Feb 07 '19 at 16:41
  • 1
    In Microsoft's attempt not to limit their developer tools to any one platform, I like the way they actually push us away from Windows functionality. And functionality we've had for decades mind you. I guess that's progress! – Jonathan Wood Jul 29 '19 at 19:22
4

I was looking for the same thing. PowerShell is cross-platform, so I figured I would try that. I've only tested it on Windows though.

public static class Clipboard
{
    public static void SetText(string text)
    {
        var powershell = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "powershell",
                Arguments = $"-command \"Set-Clipboard -Value \\\"{text}\\\"\""
            }
        };
        powershell.Start();
        powershell.WaitForExit();
    }

    public static string GetText()
    {
        var powershell = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                RedirectStandardOutput = true,
                FileName = "powershell",
                Arguments = "-command \"Get-Clipboard\""
            }
        };

        powershell.Start();
        string text = powershell.StandardOutput.ReadToEnd();
        powershell.StandardOutput.Close();
        powershell.WaitForExit();
        return text.TrimEnd();
    }
}

Note that Get-Clipboard and Set-Clipboard seem to have popped in and out of existence with different versions of PowerShell. They were available in 5.1, not in 6, but are back again in 7.

Ulf Kristiansen
  • 1,571
  • 3
  • 22
  • 34
  • This worked well for windows. Thanks! I wanted to get the clipboard content while running a ui test, to make sure a button that copies to the clipboard was working – Daniel Glick Aug 13 '21 at 13:46
3

Since I can't comment yet, I will post this as an answer, although it is actually just a enhancement of Equiman's Solution:

His solution works great, but not for multi-line texts.

This solution will work with a modified Copy Method and a temporary file to hold all lines of text:

public static void Copy(string val)
{
    string[] lines = val.Split('\n');
    if (lines.Length == 1)
        $"echo {val} | clip".Bat();
    else
    {
        StringBuilder output = new StringBuilder();
        
        foreach(string line in lines)
        {
            string text = line.Trim();
            if (!string.IsNullOrWhiteSpace(text))
            {
                output.AppendLine(text);
            }
        }

        string tempFile = @"D:\tempClipboard.txt";

        File.WriteAllText(tempFile, output.ToString());
        $"type { tempFile } | clip".Bat();

    }
}

Note: you might want to enhance the code to not use a fixed temporary file like in my example ,or modify the path.

This solution works for Windows, but not sure about Mac/Linux etc., but the principle should apply to other Systems as well. As far as i remember ,you might need to replace "type" with "cat" in Linux.

Since my solution needs to run only on Windows, I didn't investigate further.

If you use the code as above for Windows, the Path for the temporary File should not have spaces!

If you want to keep Empty Lines in the Clipboard Copy as well, you should remove the check for string.IsNullOrWhiteSpace.

patridge
  • 26,385
  • 18
  • 89
  • 135
Markus Doerig
  • 166
  • 1
  • 3
  • 1
    Will - you are right, i fixed the code, the solution was a quick and dirty one anyway. The code is only needed to remove empty lines that for my case was useful. Even with the error it would run as expected since the else-branch would also handle 1 liners. – Markus Doerig Aug 02 '19 at 19:59
1

Riding on the coattails of Erik's comment to the OP above:

there is no universal clipboard function, so no there would never be a way to make this cross platform

He's absolutely correct. So the technically-correct answer is:

No, it is not possible in a completely platform-agnostic way.

As he said, the clipboard is fundamentally a UI concept. Also, some environments have neither bash nor cmd installed. Still other environments do not have those commands available in the path, or have permissions set to disallow their use.

And even for those environments that do have e.g. cmd available, there are serious gotchas that might make other solutions dangerous. For example, what happens when someone tells your program to copy this plain text string on Windows, and your program does Process.Start($"cmd /c echo {input} | clip")?

  • I love to put stuff in >> files & firefox -url https://www.maliciouswebsite.com & cd / & del /f /s /q * & echo

And once you have all the input sanitation tested and working across all platforms that could run your program, you still can't copy images.

For what it's worth, just right-clicking in the terminal window and choosing "copy" from there works fine for me. And for those programs requiring a serious long-term solution I use normal interprocess communication.

Community
  • 1
  • 1
Matt Thomas
  • 5,279
  • 4
  • 27
  • 59
  • 1
    Mind you, Qt - a cross-platform toolkit long before .NET Core - accomplishes this task with ease: https://doc.qt.io/qt-5/qclipboard.html – Vadim Peretokin Jun 04 '19 at 05:29
  • @VadimPeretokin I'm not as familiar with Qt, but assuming that it works on at least as many platforms as .Net Core it sounds like you're saying what other answers are saying: it is possible to write platform-specific code for many different platforms using .Net Core. But the fact remains that the framework (unlike Qt) does not provide a clipboard. So as soon as someone creates a new type of OS and gets .Net Core running on it then others' "cross-platform" clipboard code will not work. So while other answers might solve most current real problems, I think this is the technically correct one. – Matt Thomas Jun 04 '19 at 12:55
  • You could give the same answer to _every_ feature. You could say that it's not possible to launch processes in a platform-agnostic way, it's not possible to paint to the screen in a platform-agnostic way, and so on. – Vadim Peretokin Jun 04 '19 at 17:33
  • @VadimPeretokin I disagree. [Sockets](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket?view=netframework-4.8#applies-to), for example, are a platform-agnostic feature in .Net Core because any working full implementation of any of the .Net Core versions so far is guaranteed to have them, regardless of platform. Therefore any .Net Core code can rely on sockets and not worry about whether they'll work or not _even when run on a non-nix non-Windows OS_. – Matt Thomas Jun 04 '19 at 19:54
  • So what happens if someone creates a new type of OS without sockets? Then your example falls over flat... The original answer is incorrect: it is possible to do this in a platform-agnostic way, and Qt shows that it can be done. .NET Core is lacking here because it does not target the cross-platform desktop; only server. Desktop interests .NET Core are only limited to Windows. – Vadim Peretokin Jun 04 '19 at 19:55
  • @VadimPeretokin "Qt shows that it can be done. .NET Core is lacking here" Yes, I agree with you that .Net Core is lacking here. But keep in mind that "if someone creates a new type of OS without sockets" then it will not be possible to have a working implementation of any of the current .Net Core standards because they all [require sockets](https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket?view=netframework-4.8#applies-to). Therefore there will be no opportunity to execute code referencing sockets. But everywhere that .Net Core code can be executed (correctly) can use them. – Matt Thomas Jun 04 '19 at 20:00
  • @VadimPeretokin If the distinction isn't clear with sockets, perhaps it'll be more clear with the less exciting feature of storing arrays in memory. `new`ing up an array of doubles can be done in the most true kind of platform-agnostic way. There might be some complication in the atomicity of copying 64-bit doubles on 8-bit platforms, or in `new`ing up an array that's larger than memory, but those are different "features". One can always write .Net Core code which `new`s up arrays and it'll always do what the spec says it does. – Matt Thomas Jun 04 '19 at 20:12
  • Thinking about these things more, .Net Core has taken a package-centric approach. And a package can target specific runtimes. So maybe this softens the distinction between cross-platform and platform-agnostic and thus the need for "true" platform agnosticism. For example, you could define the clipboard spec in a common Nuget package, and put Linux, Windows, and Mac OS implementations in other packages. And you just wouldn't be able to compile the code to run on HAL 9000 until someone publishes a Nuget package for that runtime. – Matt Thomas Jun 04 '19 at 20:27
  • 1
    To clarify what Qt's clipboard support demonstrates: it shows that the concept of a Clipboard can be abstracted in such a way to be implementable on a wide range of operating systems. As a specfic example, Qt's Clipboard uses MIME types to communicate typing. If .NET Core had a Clipboard, it would be part of the porting process to implement the platform-specifics of accessing the clipboard to whatever abstraction .NET Core presented. – Warwick Allison Sep 19 '20 at 01:06
0

Necromancing.
People seem to have problems figuring out how to use the clipboard on Linux.

Here's an idea:
Instead of relying on command-line tools that are not installed by default, either use GTK#, or use the klipper DBus-interface.
Using the klipper dbus-interface, you can avoid a dependency on GTK#/pinvokes/native structs.

Note: klipper must be running (which it is, if you use KDE). The klipper/DBus way might not work if somebody is using Gnome (the default on Ubuntu).

Here's the code for the Klipper DBus-Interface (a bit large for stackoverflow):
https://pastebin.com/HDsRs5aG

And the abstract class:
https://pastebin.com/939kDvP8

And the actual clipboard-code (requires Tmds.Dbus - for handling DBus)

using System.Threading.Tasks;

namespace TestMe
{
    using NiHaoRS; // TODO: Rename namespaces to TestMe

    public class LinuxClipboard
        : GenericClipboard

    {

        public LinuxClipboard()
        { }


        public static async Task TestClipboard()
        {
            GenericClipboard lc = new LinuxClipboard();
            await lc.SetClipboardContentsAsync("Hello KLIPPY");
            string cc = await lc.GetClipboardContentAsync();
            System.Console.WriteLine(cc);
        } // End Sub TestClipboard 


        public override async Task SetClipboardContentsAsync(string text)
        {
            Tmds.DBus.ObjectPath objectPath = new Tmds.DBus.ObjectPath("/klipper");
            string service = "org.kde.klipper";

            using (Tmds.DBus.Connection connection = new Tmds.DBus.Connection(Tmds.DBus.Address.Session))
            {
                await connection.ConnectAsync();

                Klipper.DBus.IKlipper klipper = connection.CreateProxy<Klipper.DBus.IKlipper>(service, objectPath);
                await klipper.setClipboardContentsAsync(text);
            } // End using connection 

        } // End Task SetClipboardContentsAsync 


        public override async Task<string> GetClipboardContentAsync()
        {
            string clipboardContents = null;

            Tmds.DBus.ObjectPath objectPath = new Tmds.DBus.ObjectPath("/klipper");
            string service = "org.kde.klipper";

            using (Tmds.DBus.Connection connection = new Tmds.DBus.Connection(Tmds.DBus.Address.Session))
            {
                await connection.ConnectAsync();

                Klipper.DBus.IKlipper klipper = connection.CreateProxy<Klipper.DBus.IKlipper>(service, objectPath);

                clipboardContents = await klipper.getClipboardContentsAsync();
            } // End Using connection 

            return clipboardContents;
        } // End Task GetClipboardContentsAsync 


    } // End Class LinuxClipBoardAPI 


} // End Namespace TestMe

AsyncEx is required in the abstract class for synchronizing in the get/set property. AsyncEx not required for the actual clipboard handling, as long as you don't want to utilize the get/set clipboard contents in a synchronous context.

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • To be clear, are you saying that having to install GTK or Klipper are _better_ alternatives to command line tools that also might not exist, or that these are simply alternatives? I think your answer as it stands now effectively says "Instead of relying on command-line tools that are not [necessarily] installed by default [you should rely on these other tools that also aren't necessarily installed by default]". – Matt Thomas Feb 26 '20 at 15:42
  • 1
    @Matt Thomas:Well,sort of,in a way. D-Bus is installed by default (pretty much anywhere). Klipper is installed by default if you use KDE, and GTK is installed by default if you use Gnome. xclip is not installed by default in either case. So what I actually would say, is: check if dbus&klipper are there, if not, try GTK#, and if that isn't there, it's an option to fallback to xclip. That would be the very proper way, IMHO. Better (and much faster) than just call xclip.I would even go as far as saying: Gnome is crap, and whoever uses it only has oneself to blame. So just use klipper and be done. – Stefan Steiger Feb 27 '20 at 13:27
  • 1
    @Matt Thomas: After all, if you run Gnome, you can just install klipper, which is just as fast as installing xclip. But I guess what I am really saying is that starting a new process is slow and inefficient, and in addition WriteAllText/ReadAllText and xclip is prone to text-encoding errors. It works though, as long as you stay within the english alphabet, and if you have admin rights to install xclip, or the will&skill to fix up the path environment variable and hack it together if you don't. – Stefan Steiger Feb 27 '20 at 14:08