Skip to content

Commit 32c7147

Browse files
HJLeeestephentoubMichalPetrykaadamsitnikdanmoseley
authored
Implement Console.SetWindowSize() for linux (#75824)
Co-authored-by: Stephen Toub <stoub@microsoft.com> Co-authored-by: Michał Petryka <35800402+MichalPetryka@users.noreply.github.com> Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com> Co-authored-by: Dan Moseley <danmose@microsoft.com>
1 parent 86611ab commit 32c7147

File tree

15 files changed

+257
-43
lines changed

15 files changed

+257
-43
lines changed

src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetWindowWidth.cs

+3
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@ internal struct WinSize
1919

2020
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetWindowSize", SetLastError = true)]
2121
internal static partial int GetWindowSize(out WinSize winSize);
22+
23+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_SetWindowSize", SetLastError = true)]
24+
internal static partial int SetWindowSize(in WinSize winSize);
2225
}
2326
}

src/libraries/System.Console/ref/System.Console.cs

+14-3
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,18 @@ public static partial class Console
6969
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
7070
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
7171
public static bool TreatControlCAsInput { get { throw null; } set { } }
72-
public static int WindowHeight { [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android"), System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser"), System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios"), System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] get { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] set { } }
72+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
73+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
74+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
75+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
76+
public static int WindowHeight { get { throw null; } set { } }
7377
public static int WindowLeft { get { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] set { } }
7478
public static int WindowTop { get { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] set { } }
75-
public static int WindowWidth { [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android"), System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser"), System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios"), System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] get { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] set { } }
79+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
80+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
81+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
82+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
83+
public static int WindowWidth { get { throw null; } set { } }
7684
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
7785
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
7886
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
@@ -147,7 +155,10 @@ public static void SetIn(System.IO.TextReader newIn) { }
147155
public static void SetOut(System.IO.TextWriter newOut) { }
148156
[System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")]
149157
public static void SetWindowPosition(int left, int top) { }
150-
[System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")]
158+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
159+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
160+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
161+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
151162
public static void SetWindowSize(int width, int height) { }
152163
public static void Write(bool value) { }
153164
public static void Write(char value) { }

src/libraries/System.Console/src/Resources/Strings.resx

+3
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@
194194
<data name="InvalidOperation_ConsoleReadKeyOnFile" xml:space="preserve">
195195
<value>Cannot read keys when either application does not have a console or when console input has been redirected. Try Console.Read.</value>
196196
</data>
197+
<data name="InvalidOperation_SetWindowSize" xml:space="preserve">
198+
<value>Cannot set window size when console output has been redirected.</value>
199+
</data>
197200
<data name="PersistedFiles_NoHomeDirectory" xml:space="preserve">
198201
<value>The home directory of the current user could not be determined.</value>
199202
</data>

src/libraries/System.Console/src/System/Console.cs

+55-13
Original file line numberDiff line numberDiff line change
@@ -392,26 +392,50 @@ public static int WindowTop
392392
set { ConsolePal.WindowTop = value; }
393393
}
394394

395+
[UnsupportedOSPlatform("android")]
396+
[UnsupportedOSPlatform("browser")]
397+
[UnsupportedOSPlatform("ios")]
398+
[UnsupportedOSPlatform("tvos")]
395399
public static int WindowWidth
396400
{
397-
[UnsupportedOSPlatform("android")]
398-
[UnsupportedOSPlatform("browser")]
399-
[UnsupportedOSPlatform("ios")]
400-
[UnsupportedOSPlatform("tvos")]
401401
get { return ConsolePal.WindowWidth; }
402-
[SupportedOSPlatform("windows")]
403-
set { ConsolePal.WindowWidth = value; }
402+
set
403+
{
404+
if (Console.IsOutputRedirected)
405+
{
406+
throw new IOException(SR.InvalidOperation_SetWindowSize);
407+
}
408+
409+
if (value <= 0)
410+
{
411+
throw new ArgumentOutOfRangeException(nameof(value), value, SR.ArgumentOutOfRange_NeedPosNum);
412+
}
413+
414+
ConsolePal.WindowWidth = value;
415+
}
404416
}
405417

418+
[UnsupportedOSPlatform("android")]
419+
[UnsupportedOSPlatform("browser")]
420+
[UnsupportedOSPlatform("ios")]
421+
[UnsupportedOSPlatform("tvos")]
406422
public static int WindowHeight
407423
{
408-
[UnsupportedOSPlatform("android")]
409-
[UnsupportedOSPlatform("browser")]
410-
[UnsupportedOSPlatform("ios")]
411-
[UnsupportedOSPlatform("tvos")]
412424
get { return ConsolePal.WindowHeight; }
413-
[SupportedOSPlatform("windows")]
414-
set { ConsolePal.WindowHeight = value; }
425+
set
426+
{
427+
if (Console.IsOutputRedirected)
428+
{
429+
throw new IOException(SR.InvalidOperation_SetWindowSize);
430+
}
431+
432+
if (value <= 0)
433+
{
434+
throw new ArgumentOutOfRangeException(nameof(value), value, SR.ArgumentOutOfRange_NeedPosNum);
435+
}
436+
437+
ConsolePal.WindowHeight = value;
438+
}
415439
}
416440

417441
[SupportedOSPlatform("windows")]
@@ -420,9 +444,27 @@ public static void SetWindowPosition(int left, int top)
420444
ConsolePal.SetWindowPosition(left, top);
421445
}
422446

423-
[SupportedOSPlatform("windows")]
447+
[UnsupportedOSPlatform("android")]
448+
[UnsupportedOSPlatform("browser")]
449+
[UnsupportedOSPlatform("ios")]
450+
[UnsupportedOSPlatform("tvos")]
424451
public static void SetWindowSize(int width, int height)
425452
{
453+
if (Console.IsOutputRedirected)
454+
{
455+
throw new IOException(SR.InvalidOperation_SetWindowSize);
456+
}
457+
458+
if (width <= 0)
459+
{
460+
throw new ArgumentOutOfRangeException(nameof(width), width, SR.ArgumentOutOfRange_NeedPosNum);
461+
}
462+
463+
if (height <= 0)
464+
{
465+
throw new ArgumentOutOfRangeException(nameof(height), height, SR.ArgumentOutOfRange_NeedPosNum);
466+
}
467+
426468
ConsolePal.SetWindowSize(width, height);
427469
}
428470

src/libraries/System.Console/src/System/ConsolePal.Unix.cs

+20-3
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ public static int WindowWidth
346346
GetWindowSize(out int width, out _);
347347
return width;
348348
}
349-
set { throw new PlatformNotSupportedException(); }
349+
set => SetWindowSize(value, WindowHeight);
350350
}
351351

352352
public static int WindowHeight
@@ -356,7 +356,7 @@ public static int WindowHeight
356356
GetWindowSize(out _, out int height);
357357
return height;
358358
}
359-
set { throw new PlatformNotSupportedException(); }
359+
set => SetWindowSize(WindowWidth, value);
360360
}
361361

362362
private static void GetWindowSize(out int width, out int height)
@@ -392,7 +392,24 @@ public static void SetWindowPosition(int left, int top)
392392

393393
public static void SetWindowSize(int width, int height)
394394
{
395-
throw new PlatformNotSupportedException();
395+
lock (Console.Out)
396+
{
397+
Interop.Sys.WinSize winsize = default;
398+
winsize.Row = (ushort)height;
399+
winsize.Col = (ushort)width;
400+
if (Interop.Sys.SetWindowSize(in winsize) == 0)
401+
{
402+
s_windowWidth = winsize.Col;
403+
s_windowHeight = winsize.Row;
404+
}
405+
else
406+
{
407+
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
408+
throw errorInfo.Error == Interop.Error.ENOTSUP ?
409+
new PlatformNotSupportedException() :
410+
Interop.GetIOException(errorInfo);
411+
}
412+
}
396413
}
397414

398415
public static bool CursorVisible

src/libraries/System.Console/src/System/ConsolePal.Windows.cs

-5
Original file line numberDiff line numberDiff line change
@@ -948,11 +948,6 @@ public static unsafe void SetWindowPosition(int left, int top)
948948

949949
public static unsafe void SetWindowSize(int width, int height)
950950
{
951-
if (width <= 0)
952-
throw new ArgumentOutOfRangeException(nameof(width), width, SR.ArgumentOutOfRange_NeedPosNum);
953-
if (height <= 0)
954-
throw new ArgumentOutOfRangeException(nameof(height), height, SR.ArgumentOutOfRange_NeedPosNum);
955-
956951
// Get the position of the current console window
957952
Interop.Kernel32.CONSOLE_SCREEN_BUFFER_INFO csbi = GetBufferInfo();
958953

src/libraries/System.Console/tests/ManualTests/ManualTests.cs

+33
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.Diagnostics;
66
using System.Threading.Tasks;
77
using System.IO;
8+
using System.Runtime.InteropServices;
89
using System.Text;
10+
using System.Threading;
911
using Xunit;
1012

1113
namespace System
@@ -321,6 +323,37 @@ public static void EncodingTest()
321323
AssertUserExpectedResults("Pi and Sigma or question marks");
322324
}
323325

326+
[ConditionalFact(nameof(ManualTestsEnabled))]
327+
[SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")]
328+
public static void ResizeTest()
329+
{
330+
bool wasResized = false;
331+
332+
using (ManualResetEvent manualResetEvent = new(false))
333+
using (PosixSignalRegistration.Create(PosixSignal.SIGWINCH,
334+
ctx =>
335+
{
336+
wasResized = true;
337+
Assert.Equal(PosixSignal.SIGWINCH, ctx.Signal);
338+
manualResetEvent.Set();
339+
}))
340+
{
341+
int widthBefore = Console.WindowWidth;
342+
int heightBefore = Console.WindowHeight;
343+
344+
Assert.False(wasResized);
345+
346+
Console.SetWindowSize(widthBefore / 2, heightBefore / 2);
347+
348+
Assert.True(manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(50)));
349+
Assert.True(wasResized);
350+
Assert.Equal(widthBefore / 2, Console.WindowWidth );
351+
Assert.Equal(heightBefore / 2, Console.WindowHeight );
352+
353+
Console.SetWindowSize(widthBefore, heightBefore);
354+
}
355+
}
356+
324357
private static void AssertUserExpectedResults(string expected)
325358
{
326359
Console.Write($"Did you see {expected}? [y/n] ");

src/libraries/System.Console/tests/WindowAndCursorProps.cs

+12-12
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static void SetBufferSize_Unix_ThrowsPlatformNotSupportedException()
4646
}
4747

4848
[Theory]
49-
[PlatformSpecific(TestPlatforms.Windows)]
49+
[PlatformSpecific((TestPlatforms.Windows) | (TestPlatforms.AnyUnix & ~TestPlatforms.Browser & ~TestPlatforms.iOS & ~TestPlatforms.MacCatalyst & ~TestPlatforms.tvOS))]
5050
[InlineData(0)]
5151
[InlineData(-1)]
5252
public static void WindowWidth_SetInvalid_ThrowsArgumentOutOfRangeException(int value)
@@ -71,14 +71,14 @@ public static void WindowWidth_GetUnix_Success()
7171
}
7272

7373
[Fact]
74-
[PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] // Expected behavior specific to Unix
74+
[PlatformSpecific(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS)] // Expected behavior specific to Unix
7575
public static void WindowWidth_SetUnix_ThrowsPlatformNotSupportedException()
7676
{
7777
Assert.Throws<PlatformNotSupportedException>(() => Console.WindowWidth = 100);
7878
}
7979

8080
[Theory]
81-
[PlatformSpecific(TestPlatforms.Windows)] // Expected behavior specific to Windows
81+
[PlatformSpecific((TestPlatforms.Windows) | (TestPlatforms.AnyUnix & ~TestPlatforms.Browser & ~TestPlatforms.iOS & ~TestPlatforms.MacCatalyst & ~TestPlatforms.tvOS))]
8282
[InlineData(0)]
8383
[InlineData(-1)]
8484
public static void WindowHeight_SetInvalid_ThrowsArgumentOutOfRangeException(int value)
@@ -102,13 +102,6 @@ public static void WindowHeight_GetUnix_Success()
102102
Helpers.RunInRedirectedOutput((data) => Console.WriteLine(Console.WindowHeight));
103103
}
104104

105-
[Fact]
106-
[PlatformSpecific(TestPlatforms.AnyUnix)] // Expected behavior specific to Unix
107-
public static void WindowHeight_SetUnix_ThrowsPlatformNotSupportedException()
108-
{
109-
Assert.Throws<PlatformNotSupportedException>(() => Console.WindowHeight = 100);
110-
}
111-
112105
[Fact]
113106
[PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser & ~TestPlatforms.iOS & ~TestPlatforms.MacCatalyst & ~TestPlatforms.tvOS)] // Expected behavior specific to Unix
114107
public static void LargestWindowWidth_UnixGet_ReturnsExpected()
@@ -117,6 +110,13 @@ public static void LargestWindowWidth_UnixGet_ReturnsExpected()
117110
Helpers.RunInRedirectedOutput((data) => Assert.Equal(Console.WindowWidth, Console.LargestWindowWidth));
118111
}
119112

113+
[Fact]
114+
[PlatformSpecific(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS)] // Expected behavior specific to Unix
115+
public static void WindowHeight_SetUnix_ThrowsPlatformNotSupportedException()
116+
{
117+
Assert.Throws<PlatformNotSupportedException>(() => Console.WindowHeight = 100);
118+
}
119+
120120
[Fact]
121121
[PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser & ~TestPlatforms.iOS & ~TestPlatforms.MacCatalyst & ~TestPlatforms.tvOS)] // Expected behavior specific to Unix
122122
public static void LargestWindowHeight_UnixGet_ReturnsExpected()
@@ -559,7 +559,7 @@ public void SetWindowPosition_Unix_ThrowsPlatformNotSupportedException()
559559
Assert.Throws<PlatformNotSupportedException>(() => Console.SetWindowPosition(50, 50));
560560
}
561561

562-
[PlatformSpecific(TestPlatforms.Windows)]
562+
[PlatformSpecific((TestPlatforms.Windows) | (TestPlatforms.AnyUnix & ~TestPlatforms.Browser & ~TestPlatforms.iOS & ~TestPlatforms.MacCatalyst & ~TestPlatforms.tvOS))]
563563
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoNorServerCore))]
564564
public void SetWindowSize_GetWindowSize_ReturnsExpected()
565565
{
@@ -587,7 +587,7 @@ public void SetWindowSize_GetWindowSize_ReturnsExpected()
587587
}
588588

589589
[Fact]
590-
[PlatformSpecific(TestPlatforms.AnyUnix)]
590+
[PlatformSpecific(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS)]
591591
public void SetWindowSize_Unix_ThrowsPlatformNotSupportedException()
592592
{
593593
Assert.Throws<PlatformNotSupportedException>(() => Console.SetWindowSize(50, 50));

src/libraries/System.Private.CoreLib/src/System/ArgumentOutOfRangeException.cs

-6
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@ public ArgumentOutOfRangeException(string? message, Exception? innerException)
4848
HResult = HResults.COR_E_ARGUMENTOUTOFRANGE;
4949
}
5050

51-
// We will not use this in the classlibs, but we'll provide it for
52-
// anyone that's really interested so they don't have to stick a bunch
53-
// of printf's in their code.
5451
public ArgumentOutOfRangeException(string? paramName, object? actualValue, string? message)
5552
: base(message, paramName)
5653
{
@@ -87,9 +84,6 @@ public override string Message
8784
}
8885

8986
// Gets the value of the argument that caused the exception.
90-
// Note - we don't set this anywhere in the class libraries in
91-
// version 1, but it might come in handy for other developers who
92-
// want to avoid sticking printf's in their code.
9387
public virtual object? ActualValue => _actualValue;
9488
}
9589
}

0 commit comments

Comments
 (0)