Возможно ли скопировать что-то в буфер обмена с использованием .Net Core (в агрегированном виде платформы)?
Кажется, что класс Clipboard
отсутствует, а P/Invoking не является опцией вне Windows.
Возможно ли скопировать что-то в буфер обмена с использованием .Net Core (в агрегированном виде платформы)?
Кажется, что класс Clipboard
отсутствует, а P/Invoking не является опцией вне Windows.
Верхом на куртках Эрика комментарий к ОП выше:
нет универсальной функции буфера обмена, так что нет никакого способа сделать эту кроссплатформенную
Он абсолютно правильный. Итак, технически правильный ответ:
По его словам, буфер обмена является принципиально концепцией пользовательского интерфейса. Кроме того, в некоторых средах не установлены ни bash
ни cmd
. Тем не менее, в других средах эти команды недоступны в пути или не установлены разрешения, запрещающие их использование.
И даже для тех сред, в которых есть, например, cmd
, существуют серьезные ошибки, которые могут сделать другие решения опасными. Например, что происходит, когда кто-то говорит вашей программе скопировать эту строку в Process.Start($"cmd/c echo {input} | clip")
обычного текста в 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
И после того, как вы проверили всю входную санитарию и работали на всех платформах, на которых могла запускаться ваша программа, вы все равно не сможете копировать изображения.
Для чего это стоит, просто щелкнув правой кнопкой мыши в окне терминала и выбрав "Копировать" оттуда, отлично работает для меня. А для тех программ, которые требуют серьезного долгосрочного решения, я использую обычное межпроцессное взаимодействие.
Класс буфера обмена отсутствует, надеюсь, в скором будущем появится возможность для этого. Пока это происходит... вы можете запустить собственную команду оболочки с ProcessStartInfo.
Я noob в Net Core, но создать этот код для отправки и строки в буфер обмена на Windows и Mac:
Класс обнаружения ОС
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
Основано на 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();
}
}
}
Затем, наконец, вы можете вызвать Clipboard Copy и получить значение в буфере обмена.
var dirPath = @"C:\MyPath";
Clipboard.Copy(dirPath);
Надеюсь, это поможет другим! Улучшения приветствуются.
Я работаю в библиотеке ToolBox для ядра .net со всеми этими вещами: https://github.com/deinsoftware/toolbox (также доступна в виде пакета NuGet).
Запустите команду на внешнем терминале с .Net Core: https://dev.to/deinsoftware/run-a-command-in-external-terminal-with-net-core-d4l
Этот мой проект (https://github.com/SimonCropp/TextCopy) использует смешанный подход PInvoke и вызова командной строки. в настоящее время поддерживает
Использование:
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();
}
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";
}
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 = [email protected]"Process timed out. Command line: bash {arguments}.
Output: {outputBuilder}
Error: {errorBuilder}";
throw new Exception(timeoutError);
}
if (process.ExitCode == 0)
{
return outputBuilder.ToString();
}
var error = [email protected]"Could not execute process. Command line: bash {arguments}.
Output: {outputBuilder}
Error: {errorBuilder}";
throw new Exception(error);
}
}
}
Поскольку я пока не могу похвалить, я опубликую это как ответ, хотя на самом деле это всего лишь усовершенствование Equiman Solution:
Его решение прекрасно работает, но не для многострочных текстов.
Это решение будет работать с измененным методом копирования и временным файлом для хранения всех строк текста:
public static void Copy(string val)
{
string[] lines = val.Split('\n');
if (lines.Length == 0)
$"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
. string.IsNullOrWhiteSpace
.