Skip to content

Commit 57491dc

Browse files
authored
Fixes #5451. Linux UnixClipboard Selection Regression (#5452)
1 parent 8237d33 commit 57491dc

3 files changed

Lines changed: 177 additions & 18 deletions

File tree

Terminal.Gui/Drivers/DriverImpl.cs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public DriverImpl (IComponentFactory componentFactory,
7979
_terminalSize = new Size (outputBuffer.Cols, outputBuffer.Rows);
8080
AnsiStartupGate = ansiStartupGate;
8181

82-
CreateClipboard ();
82+
Clipboard = CreateClipboard ();
8383

8484
Driver.Force16ColorsChanged += OnDriverOnForce16ColorsChanged;
8585
}
@@ -158,27 +158,61 @@ public void Dispose ()
158158
public IAnsiStartupGate? AnsiStartupGate { get; }
159159

160160
/// <inheritdoc/>
161-
public IClipboard? Clipboard { get; set; } = new FakeClipboard ();
161+
public IClipboard? Clipboard { get; set; }
162162

163163
/// <inheritdoc/>
164164
public ProgressIndicator? ProgressIndicator { get; internal set; }
165165

166-
private void CreateClipboard ()
166+
internal static IClipboard CreateClipboard (
167+
Func<bool> isWindows,
168+
Func<bool> isMac,
169+
Func<bool> isWsl,
170+
Func<bool> isLinux,
171+
Func<IClipboard> createWindowsClipboard,
172+
Func<IClipboard> createMacClipboard,
173+
Func<IClipboard> createWslClipboard,
174+
Func<IClipboard> createUnixClipboard,
175+
IClipboard fallbackClipboard
176+
)
167177
{
168-
PlatformID p = Environment.OSVersion.Platform;
178+
if (isWindows ())
179+
{
180+
return createWindowsClipboard ();
181+
}
169182

170-
if (p is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows)
183+
if (isMac ())
171184
{
172-
Clipboard = new WindowsClipboard ();
185+
return createMacClipboard ();
173186
}
174-
else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
187+
188+
if (isWsl ())
175189
{
176-
Clipboard = new MacOSXClipboard ();
190+
return createWslClipboard ();
177191
}
178-
else if (PlatformDetection.IsWSL ())
192+
193+
if (!isLinux ())
179194
{
180-
Clipboard = new WSLClipboard ();
195+
return fallbackClipboard;
181196
}
197+
198+
IClipboard unixClipboard = createUnixClipboard ();
199+
200+
return !unixClipboard.IsSupported ? fallbackClipboard : unixClipboard;
201+
}
202+
203+
private static IClipboard CreateClipboard ()
204+
{
205+
PlatformID p = Environment.OSVersion.Platform;
206+
207+
return CreateClipboard (() => p is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows,
208+
() => RuntimeInformation.IsOSPlatform (OSPlatform.OSX),
209+
PlatformDetection.IsWSL,
210+
PlatformDetection.IsLinux,
211+
() => new WindowsClipboard (),
212+
() => new MacOSXClipboard (),
213+
() => new WSLClipboard (),
214+
() => new UnixClipboard (),
215+
new FakeClipboard ());
182216
}
183217

184218
internal void InitializeProgressIndicator ()

Terminal.Gui/Drivers/UnixHelpers/UnixClipboard.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,27 @@ namespace Terminal.Gui.Drivers;
77
/// <remarks>If xclip is not installed, this implementation will not work.</remarks>
88
internal class UnixClipboard : ClipboardBase
99
{
10+
private readonly IClipboardProcessRunner _processRunner;
1011
private string _xclipPath = string.Empty;
11-
public UnixClipboard () => IsSupported = CheckSupport ();
12+
13+
public UnixClipboard () : this (new ClipboardProcessRunnerImpl ()) { }
14+
15+
internal UnixClipboard (IClipboardProcessRunner processRunner)
16+
{
17+
_processRunner = processRunner ?? throw new ArgumentNullException (nameof (processRunner));
18+
IsSupported = CheckSupport ();
19+
}
20+
1221
public override bool IsSupported { get; }
1322

1423
protected override string GetClipboardDataImpl ()
1524
{
1625
string tempFileName = Path.GetTempFileName ();
17-
var xclipargs = "-selection clipboard -o";
26+
string xclipArgs = "-selection clipboard -o";
1827

1928
try
2029
{
21-
(int exitCode, string _) = ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false);
30+
(int exitCode, string _) = _processRunner.Bash ($"{_xclipPath} {xclipArgs} > {tempFileName}", waitForOutput: false);
2231

2332
if (exitCode == 0)
2433
{
@@ -27,7 +36,7 @@ protected override string GetClipboardDataImpl ()
2736
}
2837
catch (Exception e)
2938
{
30-
throw new NotSupportedException ($"\"{_xclipPath} {xclipargs}\" failed.", e);
39+
throw new NotSupportedException ($"\"{_xclipPath} {xclipArgs}\" failed.", e);
3140
}
3241
finally
3342
{
@@ -39,15 +48,15 @@ protected override string GetClipboardDataImpl ()
3948

4049
protected override void SetClipboardDataImpl (string text)
4150
{
42-
var xclipargs = "-selection clipboard -i";
51+
string xclipArgs = "-selection clipboard -i";
4352

4453
try
4554
{
46-
ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs}", text);
55+
_processRunner.Bash ($"{_xclipPath} {xclipArgs}", text);
4756
}
4857
catch (Exception e)
4958
{
50-
throw new NotSupportedException ($"\"{_xclipPath} {xclipargs} < {text}\" failed", e);
59+
throw new NotSupportedException ($"\"{_xclipPath} {xclipArgs} < {text}\" failed", e);
5160
}
5261
}
5362

@@ -56,7 +65,7 @@ private bool CheckSupport ()
5665
#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
5766
try
5867
{
59-
(int exitCode, string result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
68+
(int exitCode, string result) = _processRunner.Bash ("which xclip", waitForOutput: true);
6069

6170
if (exitCode == 0 && result.FileExists ())
6271
{
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
namespace UnitTestsParallelizable.Drivers;
2+
3+
public class DriverImplClipboardTests
4+
{
5+
[Fact]
6+
public void UnixClipboard_Set_Then_Get_RoundTrips_Through_Xclip_Runner ()
7+
{
8+
UnixClipboardProcessRunner processRunner = new ();
9+
UnixClipboard clipboard = new (processRunner);
10+
11+
Assert.True (clipboard.IsSupported);
12+
13+
clipboard.SetClipboardData ("hello");
14+
15+
Assert.Equal ("hello", clipboard.GetClipboardData ());
16+
Assert.Contains ("-selection clipboard -i", processRunner.LastSetCommandLine);
17+
Assert.Contains ("-selection clipboard -o", processRunner.LastGetCommandLine);
18+
}
19+
20+
[Fact]
21+
public void CreateClipboard_Linux_AttachedToTerminal_And_UnixSupported_Uses_UnixClipboard ()
22+
{
23+
FakeClipboard fallbackClipboard = new ();
24+
FakeClipboard unixClipboard = new ();
25+
26+
IClipboard clipboard = DriverImpl.CreateClipboard (isWindows: () => false,
27+
isMac: () => false,
28+
isWsl: () => false,
29+
isLinux: () => true,
30+
createWindowsClipboard: () => throw new InvalidOperationException (),
31+
createMacClipboard: () => throw new InvalidOperationException (),
32+
createWslClipboard: () => throw new InvalidOperationException (),
33+
createUnixClipboard: () => unixClipboard,
34+
fallbackClipboard: fallbackClipboard);
35+
36+
Assert.Same (unixClipboard, clipboard);
37+
}
38+
39+
[Fact]
40+
public void CreateClipboard_NotLinux_Uses_FallbackClipboard ()
41+
{
42+
FakeClipboard fallbackClipboard = new ();
43+
44+
IClipboard clipboard = DriverImpl.CreateClipboard (isWindows: () => false,
45+
isMac: () => false,
46+
isWsl: () => false,
47+
isLinux: () => false,
48+
createWindowsClipboard: () => throw new InvalidOperationException (),
49+
createMacClipboard: () => throw new InvalidOperationException (),
50+
createWslClipboard: () => throw new InvalidOperationException (),
51+
createUnixClipboard: () => throw new InvalidOperationException (),
52+
fallbackClipboard: fallbackClipboard);
53+
54+
Assert.Same (fallbackClipboard, clipboard);
55+
}
56+
57+
[Fact]
58+
public void CreateClipboard_Linux_AttachedToTerminal_But_UnixUnsupported_Uses_FallbackClipboard ()
59+
{
60+
FakeClipboard fallbackClipboard = new ();
61+
FakeClipboard unsupportedUnixClipboard = new (isSupportedAlwaysFalse: true);
62+
63+
IClipboard clipboard = DriverImpl.CreateClipboard (isWindows: () => false,
64+
isMac: () => false,
65+
isWsl: () => false,
66+
isLinux: () => true,
67+
createWindowsClipboard: () => throw new InvalidOperationException (),
68+
createMacClipboard: () => throw new InvalidOperationException (),
69+
createWslClipboard: () => throw new InvalidOperationException (),
70+
createUnixClipboard: () => unsupportedUnixClipboard,
71+
fallbackClipboard: fallbackClipboard);
72+
73+
Assert.Same (fallbackClipboard, clipboard);
74+
}
75+
76+
private sealed class UnixClipboardProcessRunner : IClipboardProcessRunner
77+
{
78+
private string _clipboardText = string.Empty;
79+
80+
public string LastGetCommandLine { get; private set; } = string.Empty;
81+
public string LastSetCommandLine { get; private set; } = string.Empty;
82+
83+
public (int exitCode, string result) Bash (string commandLine, string inputText = "", bool waitForOutput = false)
84+
{
85+
if (commandLine == "which xclip")
86+
{
87+
return (0, "/usr/bin/xclip");
88+
}
89+
90+
if (commandLine.Contains ("-selection clipboard -i", StringComparison.Ordinal))
91+
{
92+
LastSetCommandLine = commandLine;
93+
_clipboardText = inputText;
94+
95+
return (0, string.Empty);
96+
}
97+
98+
if (!commandLine.Contains ("-selection clipboard -o", StringComparison.Ordinal))
99+
{
100+
return (1, string.Empty);
101+
}
102+
LastGetCommandLine = commandLine;
103+
int redirectIndex = commandLine.IndexOf (" > ", StringComparison.Ordinal);
104+
string tempFileName = commandLine [(redirectIndex + 3)..];
105+
106+
File.WriteAllText (tempFileName, _clipboardText);
107+
108+
return (0, string.Empty);
109+
}
110+
111+
public (int exitCode, string result) Process (string cmd, string arguments, string? input = null, bool waitForOutput = true)
112+
{
113+
throw new NotSupportedException ();
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)