web-dev-qa-db-ja.com

.Net Core-クリップボードにコピーしますか?

.Net Coreを使用して(プラットフォームに依存しない方法で)クリップボードに何かをコピーすることは可能ですか?

Clipboard クラスが欠落しており、P/InvokingはWindows以外のオプションではないようです。

18
Matt Thomas

上記のOPに対するエリックのコメント

ユニバーサルクリップボード機能はないため、このクロスプラットフォームを作成する方法はありません

彼は絶対に正しい。技術的に正しい答えは次のとおりです。

いいえ、完全にプラットフォームに依存しない方法では不可能です。

彼が言ったように、クリップボードは基本的にUIの概念です。また、一部の環境にはbashcmdもインストールされていません。さらに他の環境では、これらのコマンドをパスで使用できないか、使用を許可しないようにアクセス許可が設定されています。

そして、例えばcmdが利用可能です。他のソリューションdangerousになるかもしれない深刻な落とし穴があります。たとえば、Windowsでこのプレーンテキスト文字列をコピーするようにプログラムに指示し、プログラムで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

そして、プログラムを実行できるすべてのプラットフォームで入力衛生をすべてテストし、機能させた後でも、画像をコピーすることはできません。

価値のあることについては、ターミナルウィンドウを右クリックして、そこから「コピー」を選択するだけでうまくいきます。そして、深刻な長期的な解決策を必要とするプログラムには、通常のプロセス間通信を使用します。

1
Matt Thomas

私のこのプロジェクト( https://github.com/SimonCropp/TextCopy )は、PInvokeとコマンドライン呼び出しの混合アプローチを使用しています。現在サポートしています

  • .NET Framework 4.6.1以降を搭載したWindows
  • .NET Core 2.0以上のWindows
  • Mono 5.0以降のWindows
  • .NET Core 2.0以上のOSX
  • Mono 5.20.1以降のOSX
  • .NET Core 2.0以上のLinux
  • Mono 5.20.1以降のLinux
  • ユニバーサルWindowsプラットフォームバージョン10.0.16299以降

使用法:

Install-Package TextCopy

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

または、実際のコードを使用します

ウィンドウズ

https://github.com/SimonCropp/TextCopy/blob/master/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();
}

OSX

https://github.com/SimonCropp/TextCopy/blob/master/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/SimonCropp/TextCopy/blob/master/TextCopy/LinuxClipboard.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);
        }
    }
}
22
Simon

Clipboardクラスがありません。近い将来、そのためのオプションが追加されることを期待しています。それが発生している間に... ProcessStartInfoを使用してネイティブシェルコマンドを実行できます。

私はNet Coreの初心者ですが、WindowsとMacのクリップボードに文字列を送信するには、次のコードを作成します。

OS検出クラス

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);
}

シェルクラス
ベース 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;
    }
}

クリップボードクラス

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

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

最後に、クリップボードコピーを呼び出して、クリップボードの値を取得できます。

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

それが他の人を助けることを願っています!改善は大歓迎です。

私は.netコア用のツールボックスライブラリで次のすべてのことを行っています: https://github.com/deinsoftware/toolbox (NuGetパッケージとしても利用可能)。

.Net Coreを使用して外部端末でコマンドを実行します。 https://dev.to/deinsoftware/run-a-command-in-external-terminal-with-net-core-d4l

20
equiman

まだ賞賛できないので、これを回答として投稿しますが、実際には単なるEquiman's Solutionの拡張機能です。

彼のソリューションはうまく機能しますが、複数行のテキストには適していません。

このソリューションは、変更されたコピー方法とすべてのテキスト行を保持する一時ファイルで動作します。

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();

    }
}

注:この例のように固定の一時ファイルを使用しないようにコードを拡張したり、パスを変更したい場合があります。

このソリューションはWindowsで機能しますが、Mac/Linuxなどについてはわかりませんが、原則は他のシステムにも適用されるはずです。私が覚えている限り、Linuxでは「タイプ」を「猫」に置き換える必要があるかもしれません。

私のソリューションはWindowsでのみ実行する必要があるため、これ以上調査しませんでした。

上記のコードをWindowsで使用する場合、一時ファイルのパスにスペースを含めることはできません!

クリップボードコピーにも空の行を保持する場合は、string.IsNullOrWhiteSpaceのチェックを削除する必要があります。

1
Markus Doerig