diff --git a/src/Tests/ClipboardServiceTests.cs b/src/Tests/ClipboardServiceTests.cs index 3d7f54ba..3d08319d 100644 --- a/src/Tests/ClipboardServiceTests.cs +++ b/src/Tests/ClipboardServiceTests.cs @@ -1,4 +1,5 @@ -using TextCopy; +using System.Runtime.InteropServices; +using TextCopy; public class ClipboardServiceTests { @@ -11,6 +12,19 @@ public async Task Simple() await VerifyInnerAsync("🅢"); } + [Test] + public async Task PassesCancellationToken() + { + // A token that is never cancelled must not interfere with normal operation. + using var source = new CancellationTokenSource(); + var token = source.Token; + + await ClipboardService.SetTextAsync("Foo", token); + + var actual = await ClipboardService.GetTextAsync(token); + Assert.AreEqual("Foo", actual); + } + static void VerifyInner(string expected) { ClipboardService.SetText(expected); diff --git a/src/TextCopy/BashRunner.cs b/src/TextCopy/BashRunner.cs index 9c42d35b..e033a43a 100644 --- a/src/TextCopy/BashRunner.cs +++ b/src/TextCopy/BashRunner.cs @@ -4,10 +4,34 @@ static class BashRunner { public static string Run(string commandLine) { - StringBuilder errorBuilder = new(); - StringBuilder outputBuilder = new(); var arguments = $"-c \"{commandLine}\""; - using Process process = new() + using var process = StartBash(arguments, out var outputBuilder, out var errorBuilder); + if (!process.DoubleWaitForExit()) + { + var timeoutError = $@"Process timed out. Command line: bash {arguments}. +Output: {outputBuilder} +Error: {errorBuilder}"; + throw new(timeoutError); + } + + return GetResult(process, arguments, outputBuilder, errorBuilder); + } + + public static async Task RunAsync(string commandLine, Cancellation cancellation) + { + var arguments = $"-c \"{commandLine}\""; + using var process = StartBash(arguments, out var outputBuilder, out var errorBuilder); + + await process.WaitForExitAsync(cancellation); + + return GetResult(process, arguments, outputBuilder, errorBuilder); + } + + static Process StartBash(string arguments, out StringBuilder outputBuilder, out StringBuilder errorBuilder) + { + var output = new StringBuilder(); + var error = new StringBuilder(); + var process = new Process { StartInfo = new() { @@ -20,17 +44,17 @@ public static string Run(string commandLine) } }; process.Start(); - process.OutputDataReceived += (_, args) => { outputBuilder.AppendLine(args.Data); }; + process.OutputDataReceived += (_, args) => { output.AppendLine(args.Data); }; process.BeginOutputReadLine(); - process.ErrorDataReceived += (_, args) => { errorBuilder.AppendLine(args.Data); }; + process.ErrorDataReceived += (_, args) => { error.AppendLine(args.Data); }; process.BeginErrorReadLine(); - if (!process.DoubleWaitForExit()) - { - var timeoutError = $@"Process timed out. Command line: bash {arguments}. -Output: {outputBuilder} -Error: {errorBuilder}"; - throw new(timeoutError); - } + outputBuilder = output; + errorBuilder = error; + return process; + } + + static string GetResult(Process process, string arguments, StringBuilder outputBuilder, StringBuilder errorBuilder) + { if (process.ExitCode == 0) { return outputBuilder.ToString(); diff --git a/src/TextCopy/LinuxClipboard_2.0.cs b/src/TextCopy/LinuxClipboard_2.0.cs index 1b3d6465..b577149b 100644 --- a/src/TextCopy/LinuxClipboard_2.0.cs +++ b/src/TextCopy/LinuxClipboard_2.0.cs @@ -9,11 +9,18 @@ static LinuxClipboard() isWsl = Environment.GetEnvironmentVariable("WSL_DISTRO_NAME") != null; } - public static Task SetTextAsync(string text, Cancellation cancellation) + public static async Task SetTextAsync(string text, Cancellation cancellation) { - SetText(text); - - return Task.CompletedTask; + var tempFileName = Path.GetTempFileName(); + File.WriteAllText(tempFileName, text); + try + { + await BashRunner.RunAsync(SetCommand(tempFileName), cancellation); + } + finally + { + File.Delete(tempFileName); + } } public static void SetText(string text) @@ -22,14 +29,7 @@ public static void SetText(string text) File.WriteAllText(tempFileName, text); try { - if (isWsl) - { - BashRunner.Run($"cat {tempFileName} | clip.exe "); - } - else - { - BashRunner.Run($"cat {tempFileName} | xsel -i --clipboard "); - } + BashRunner.Run(SetCommand(tempFileName)); } finally { @@ -37,9 +37,23 @@ public static void SetText(string text) } } - public static Task GetTextAsync(Cancellation cancellation) + static string SetCommand(string tempFileName) => + isWsl + ? $"cat {tempFileName} | clip.exe " + : $"cat {tempFileName} | xsel -i --clipboard "; + + public static async Task GetTextAsync(Cancellation cancellation) { - return Task.FromResult(GetText()); + var tempFileName = Path.GetTempFileName(); + try + { + await BashRunner.RunAsync(GetCommand(tempFileName), cancellation); + return File.ReadAllText(tempFileName); + } + finally + { + File.Delete(tempFileName); + } } public static string GetText() @@ -47,14 +61,7 @@ public static string GetText() var tempFileName = Path.GetTempFileName(); try { - if (isWsl) - { - BashRunner.Run($"powershell.exe -NoProfile Get-Clipboard > {tempFileName}"); - } - else - { - BashRunner.Run($"xsel -o --clipboard > {tempFileName}"); - } + BashRunner.Run(GetCommand(tempFileName)); return File.ReadAllText(tempFileName); } finally @@ -62,5 +69,10 @@ public static string GetText() File.Delete(tempFileName); } } + + static string GetCommand(string tempFileName) => + isWsl + ? $"powershell.exe -NoProfile Get-Clipboard > {tempFileName}" + : $"xsel -o --clipboard > {tempFileName}"; } #endif diff --git a/src/TextCopy/LinuxClipboard_2.1.cs b/src/TextCopy/LinuxClipboard_2.1.cs index d6be710c..29df19d3 100644 --- a/src/TextCopy/LinuxClipboard_2.1.cs +++ b/src/TextCopy/LinuxClipboard_2.1.cs @@ -13,13 +13,7 @@ public static async Task SetTextAsync(string text, Cancellation cancellation) { var tempFileName = Path.GetTempFileName(); await File.WriteAllTextAsync(tempFileName, text, cancellation); - - if (cancellation.IsCancellationRequested) - { - return; - } - - InnerSetText(tempFileName); + await InnerSetTextAsync(tempFileName, cancellation); } public static void SetText(string text) @@ -48,6 +42,30 @@ static void InnerSetText(string tempFileName) } } + static async Task InnerSetTextAsync(string tempFileName, Cancellation cancellation) + { + try + { + if (cancellation.IsCancellationRequested) + { + return; + } + + if (isWsl) + { + await BashRunner.RunAsync($"cat {tempFileName} | clip.exe ", cancellation); + } + else + { + await BashRunner.RunAsync($"cat {tempFileName} | xsel -i --clipboard ", cancellation); + } + } + finally + { + File.Delete(tempFileName); + } + } + public static string? GetText() { var tempFileName = Path.GetTempFileName(); @@ -67,7 +85,7 @@ static void InnerSetText(string tempFileName) var tempFileName = Path.GetTempFileName(); try { - InnerGetText(tempFileName); + await InnerGetTextAsync(tempFileName, cancellation); return await File.ReadAllTextAsync(tempFileName, cancellation); } finally @@ -87,5 +105,17 @@ static void InnerGetText(string tempFileName) BashRunner.Run($"xsel -o --clipboard > {tempFileName}"); } } + + static async Task InnerGetTextAsync(string tempFileName, Cancellation cancellation) + { + if (isWsl) + { + await BashRunner.RunAsync($"powershell.exe -NoProfile Get-Clipboard > {tempFileName}", cancellation); + } + else + { + await BashRunner.RunAsync($"xsel -o --clipboard > {tempFileName}", cancellation); + } + } } #endif