diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml
index a97953233..274bf6706 100644
--- a/.github/workflows/api-docs.yml
+++ b/.github/workflows/api-docs.yml
@@ -4,6 +4,8 @@ on:
push:
# only publish v2 (main or develop); v2 is published via the Terminal.GuiV2Docs repo
branches: [main, develop]
+ paths:
+ - docfx/**
permissions:
id-token: write
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 78c4dc0fb..5103f7bf0 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -25,7 +25,7 @@ on:
jobs:
CodeQL-Build:
-
+ if: github.repository == 'gui-cs/Terminal.Gui'|| github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 6e2fd86ac..e7dad3b01 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -29,6 +29,7 @@ jobs:
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
+ if: github.repository == 'gui-cs/Terminal.Gui'|| github.event_name == 'schedule'
permissions:
actions: read
contents: read
diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml
index f5e5ee42c..940d19604 100644
--- a/.github/workflows/dotnet-core.yml
+++ b/.github/workflows/dotnet-core.yml
@@ -13,7 +13,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
-
+ timeout-minutes: 10
steps:
- uses: actions/checkout@v4
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
index 0247b5671..fb5fa1c8c 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
@@ -752,9 +752,9 @@ namespace Terminal.Gui {
contents = new int [Rows, Cols, 3];
for (int row = 0; row < Rows; row++) {
for (int col = 0; col < Cols; col++) {
- //Curses.move (row, col);
- //Curses.attrset (Colors.TopLevel.Normal);
- //Curses.addch ((int)(uint)' ');
+ Curses.move (row, col);
+ Curses.attrset (Colors.TopLevel.Normal);
+ Curses.addch ((int)(uint)' ');
contents [row, col, 0] = ' ';
contents [row, col, 1] = Colors.TopLevel.Normal;
contents [row, col, 2] = 0;
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
index 40d51be4f..48c375a6a 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
@@ -256,7 +256,7 @@ namespace Unix.Terminal {
/// to avoid the dependency on libc-dev Linux.
///
static class CoreCLR {
-#if NET7_0
+#if NET6_0_OR_GREATER
// Custom resolver to support true single-file apps
// (those which run directly from bundle; in-memory).
// -1 on Unix means self-referencing binary (libcoreclr.so)
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
index 811426f44..1e02a03ae 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
@@ -113,7 +113,9 @@ namespace Terminal.Gui {
ConsoleDriver consoleDriver;
volatile ConsoleKeyInfo [] cki = null;
static volatile bool isEscSeq;
- bool stopTasks;
+
+ internal CancellationTokenSource TokenSource = new CancellationTokenSource ();
+
#if PROCESS_REQUEST
bool neededProcessRequest;
#endif
@@ -125,21 +127,13 @@ namespace Terminal.Gui {
throw new ArgumentNullException ("Console driver instance must be provided.");
}
this.consoleDriver = consoleDriver;
- Task.Run (ProcessInputResultQueue);
- Task.Run (CheckWinChange);
- }
-
- internal void StopTasks ()
- {
- stopTasks = true;
+ Task.Run (ProcessInputResultQueue, TokenSource.Token);
+ Task.Run (CheckWinChange, TokenSource.Token);
}
public InputResult? ReadConsoleInput ()
{
- while (true) {
- if (stopTasks) {
- return null;
- }
+ while (!TokenSource.IsCancellationRequested) {
waitForStart.Set ();
winChange.Set ();
@@ -154,11 +148,13 @@ namespace Terminal.Gui {
return inputResultQueue.Dequeue ();
}
}
+
+ return null;
}
void ProcessInputResultQueue ()
{
- while (true) {
+ while (!TokenSource.IsCancellationRequested) {
waitForStart.Wait ();
waitForStart.Reset ();
@@ -176,8 +172,23 @@ namespace Terminal.Gui {
ConsoleModifiers mod = 0;
ConsoleKeyInfo newConsoleKeyInfo = default;
- while (true) {
- ConsoleKeyInfo consoleKeyInfo = Console.ReadKey (true);
+ while (!TokenSource.IsCancellationRequested) {
+ ConsoleKeyInfo consoleKeyInfo = default;
+
+ try {
+ if (Console.KeyAvailable) {
+ consoleKeyInfo = Console.ReadKey (true);
+ } else {
+ Task.Delay (100, TokenSource.Token).Wait (TokenSource.Token);
+ if (Console.KeyAvailable) {
+ consoleKeyInfo = Console.ReadKey (true);
+ }
+ }
+ } catch (OperationCanceledException) {
+
+ return;
+ }
+
if ((consoleKeyInfo.KeyChar == (char)Key.Esc && !isEscSeq)
|| (consoleKeyInfo.KeyChar != (char)Key.Esc && isEscSeq)) {
if (cki == null && consoleKeyInfo.KeyChar != (char)Key.Esc && isEscSeq) {
@@ -201,18 +212,19 @@ namespace Terminal.Gui {
}
break;
} else {
- GetConsoleInputType (consoleKeyInfo);
- break;
+ if (consoleKeyInfo != default) {
+ GetConsoleInputType (consoleKeyInfo);
+ break;
+ }
}
+
+ TokenSource.Token.ThrowIfCancellationRequested ();
}
}
void CheckWinChange ()
{
- while (true) {
- if (stopTasks) {
- return;
- }
+ while (!TokenSource.IsCancellationRequested) {
winChange.Wait ();
winChange.Reset ();
WaitWinChange ();
@@ -222,13 +234,16 @@ namespace Terminal.Gui {
void WaitWinChange ()
{
- while (true) {
- // Wait for a while then check if screen has changed sizes
- Task.Delay (500).Wait ();
+ while (!TokenSource.IsCancellationRequested) {
+ try {
+ // Wait for a while then check if screen has changed sizes
+ Task.Delay (500, TokenSource.Token).Wait (TokenSource.Token);
+
+ } catch (OperationCanceledException) {
- if (stopTasks) {
return;
}
+
int buffHeight, buffWidth;
if (((NetDriver)consoleDriver).IsWinPlatform) {
buffHeight = Math.Max (Console.BufferHeight, 0);
@@ -691,7 +706,7 @@ namespace Terminal.Gui {
public override void End ()
{
- mainLoop.netEvents.StopTasks ();
+ mainLoop.Dispose ();
if (IsWinPlatform) {
NetWinConsole.Cleanup ();
@@ -1019,8 +1034,28 @@ namespace Terminal.Gui {
public override void Suspend ()
{
- }
+ if (Environment.OSVersion.Platform != PlatformID.Unix) {
+ return;
+ }
+ StopReportingMouseMoves ();
+ Console.ResetColor ();
+ Console.Clear ();
+
+ //Disable alternative screen buffer.
+ Console.Out.Write ("\x1b[?1049l");
+
+ //Set cursor key to cursor.
+ Console.Out.Write ("\x1b[?25h");
+
+ Platform.Suspend ();
+
+ //Enable alternative screen buffer.
+ Console.Out.Write ("\x1b[?1049h");
+
+ Application.Refresh ();
+ StartReportingMouseMoves ();
+ }
public override void SetAttribute (Attribute c)
{
@@ -1343,7 +1378,11 @@ namespace Terminal.Gui {
public override bool SetCursorVisibility (CursorVisibility visibility)
{
savedCursorVisibility = visibility;
- return Console.CursorVisible = visibility == CursorVisibility.Default;
+ Console.Out.Write (visibility == CursorVisibility.Default
+ ? "\x1b[?25h"
+ : "\x1b[?25l");
+
+ return visibility == CursorVisibility.Default;
}
///
@@ -1423,7 +1462,7 @@ namespace Terminal.Gui {
///
/// This implementation is used for NetDriver.
///
- internal class NetMainLoop : IMainLoopDriver {
+ internal class NetMainLoop : IMainLoopDriver, IDisposable {
ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
Queue inputResult = new Queue ();
@@ -1453,27 +1492,25 @@ namespace Terminal.Gui {
void NetInputHandler ()
{
- while (true) {
+ while (!tokenSource.IsCancellationRequested) {
waitForProbe.Wait ();
waitForProbe.Reset ();
if (inputResult.Count == 0) {
inputResult.Enqueue (netEvents.ReadConsoleInput ());
}
- try {
- while (inputResult.Peek () == null) {
- inputResult.Dequeue ();
- }
- if (inputResult.Count > 0) {
- keyReady.Set ();
- }
- } catch (InvalidOperationException) { }
+ while (inputResult.Count > 0 && inputResult.Peek () == null) {
+ inputResult.Dequeue ();
+ }
+ if (inputResult.Count > 0) {
+ keyReady.Set ();
+ }
}
}
void IMainLoopDriver.Setup (MainLoop mainLoop)
{
this.mainLoop = mainLoop;
- Task.Run (NetInputHandler);
+ Task.Run (NetInputHandler, tokenSource.Token);
}
void IMainLoopDriver.Wakeup ()
@@ -1503,8 +1540,7 @@ namespace Terminal.Gui {
return inputResult.Count > 0 || CheckTimers (wait, out _);
}
- tokenSource.Dispose ();
- tokenSource = new CancellationTokenSource ();
+ tokenSource.Token.ThrowIfCancellationRequested ();
return true;
}
@@ -1537,5 +1573,11 @@ namespace Terminal.Gui {
ProcessInput?.Invoke (inputResult.Dequeue ().Value);
}
}
+
+ public void Dispose ()
+ {
+ tokenSource.Cancel ();
+ netEvents.TokenSource.Cancel ();
+ }
}
}
\ No newline at end of file
diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs
index 52343079b..c7195e5d7 100644
--- a/Terminal.Gui/Core/Application.cs
+++ b/Terminal.Gui/Core/Application.cs
@@ -1037,6 +1037,7 @@ namespace Terminal.Gui {
toplevel.LayoutSubviews ();
toplevel.PositionToplevels ();
toplevel.WillPresent ();
+ EnsuresTopOnFront ();
if (refreshDriver) {
MdiTop?.OnChildLoaded (toplevel);
toplevel.OnLoaded ();
@@ -1133,9 +1134,9 @@ namespace Terminal.Gui {
// BUGBUG: MdiTop is not cleared here, but it should be?
- MainLoop = null;
Driver?.End ();
Driver = null;
+ MainLoop = null;
Iteration = null;
RootMouseEvent = null;
RootKeyEvent = null;
diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs
index 5ea7785d1..25d765759 100644
--- a/Terminal.Gui/Core/View.cs
+++ b/Terminal.Gui/Core/View.cs
@@ -394,6 +394,10 @@ namespace Terminal.Gui {
}
}
}
+
+ if (SuperView is Toplevel && Application.Current?.Focused != SuperView) {
+ Application.EnsuresTopOnFront ();
+ }
}
OnCanFocusChanged ();
SetNeedsDisplay ();
diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs
index 8fbe05642..a63ebb873 100644
--- a/Terminal.Gui/Core/Window.cs
+++ b/Terminal.Gui/Core/Window.cs
@@ -104,7 +104,7 @@ namespace Terminal.Gui {
public override void OnCanFocusChanged ()
{
- if (MostFocused == null && CanFocus && Visible) {
+ if (HasFocus && MostFocused == null && CanFocus && Visible) {
EnsureFocus ();
}
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index 9162889d1..bd919e2aa 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -20,7 +20,7 @@
portable
- net472;netstandard2.0;netstandard2.1;net7.0;net8.0
+ net472;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0
Terminal.Gui
Terminal.Gui
true
diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs
index e0d4edbda..ae6528d94 100644
--- a/Terminal.Gui/Views/ListView.cs
+++ b/Terminal.Gui/Views/ListView.cs
@@ -601,10 +601,12 @@ namespace Terminal.Gui {
///
public virtual bool MoveEnd ()
{
- if (source.Count > 0 && selected != source.Count - 1) {
+ if (source?.Count > 0 && selected != source.Count - 1) {
selected = source.Count - 1;
if (top + selected > Frame.Height - 1) {
- top = selected;
+ top = selected < Frame.Height - 1
+ ? Math.Max (Frame.Height - selected + 1, 0)
+ : Math.Max (selected - Frame.Height + 1, 0);
}
OnSelectedChanged ();
SetNeedsDisplay ();
@@ -749,6 +751,11 @@ namespace Terminal.Gui {
public void EnsureSelectedItemVisible ()
{
SuperView?.LayoutSubviews ();
+ // If last item is selected and is removed, ensures a valid selected item
+ if (Source != null && selected > Source.Count - 1) {
+ SelectedItem = Source.Count - 1;
+ SetNeedsDisplay ();
+ }
if (selected < top) {
top = selected;
} else if (Frame.Height > 0 && selected >= top + Frame.Height) {
@@ -831,11 +838,30 @@ namespace Terminal.Gui {
}
///
- public int Count => src != null ? src.Count : 0;
+ public int Count {
+ get {
+ CheckAndResizeMarksIfRequired ();
+ return src?.Count ?? 0;
+ }
+ }
///
public int Length => len;
+ void CheckAndResizeMarksIfRequired ()
+ {
+ if (src != null && count != src.Count) {
+ count = src.Count;
+ BitArray newMarks = new BitArray (count);
+ for (var i = 0; i < Math.Min (marks.Length, newMarks.Length); i++) {
+ newMarks [i] = marks [i];
+ }
+ marks = newMarks;
+
+ len = GetMaxLengthItem ();
+ }
+ }
+
int GetMaxLengthItem ()
{
if (src == null || src?.Count == 0) {
@@ -896,7 +922,7 @@ namespace Terminal.Gui {
///
public bool IsMarked (int item)
{
- if (item >= 0 && item < count)
+ if (item >= 0 && item < Count)
return marks [item];
return false;
}
@@ -904,7 +930,7 @@ namespace Terminal.Gui {
///
public void SetMark (int item, bool value)
{
- if (item >= 0 && item < count)
+ if (item >= 0 && item < Count)
marks [item] = value;
}
diff --git a/UICatalog/UICatalog.csproj b/UICatalog/UICatalog.csproj
index 6a82bb666..89dcd4c0c 100644
--- a/UICatalog/UICatalog.csproj
+++ b/UICatalog/UICatalog.csproj
@@ -20,9 +20,9 @@
-
+
-
+
diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs
index d49250eab..2550a1bb3 100644
--- a/UnitTests/Application/ApplicationTests.cs
+++ b/UnitTests/Application/ApplicationTests.cs
@@ -771,28 +771,28 @@ namespace Terminal.Gui.ApplicationTests {
Assert.True (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.False (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ()));
Assert.True (win.CanFocus);
Assert.False (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ()));
Assert.True (win.CanFocus);
Assert.True (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.False (win2.HasFocus);
- Assert.Equal ("win", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Pressed });
Assert.True (win.CanFocus);
Assert.False (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Released });
Assert.Null (Toplevel.dragPosition);
}
@@ -816,35 +816,35 @@ namespace Terminal.Gui.ApplicationTests {
Assert.True (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.False (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
win.CanFocus = false;
Assert.False (win.CanFocus);
Assert.False (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ()));
Assert.True (win2.CanFocus);
Assert.False (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ()));
Assert.False (win.CanFocus);
Assert.False (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
win.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Pressed });
Assert.False (win.CanFocus);
Assert.False (win.HasFocus);
Assert.True (win2.CanFocus);
Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+ Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Released });
Assert.Null (Toplevel.dragPosition);
}
diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj
index 7c4b4705a..a5f4cdc1a 100644
--- a/UnitTests/UnitTests.csproj
+++ b/UnitTests/UnitTests.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/UnitTests/Views/ListViewTests.cs b/UnitTests/Views/ListViewTests.cs
index f705dab25..1f6589204 100644
--- a/UnitTests/Views/ListViewTests.cs
+++ b/UnitTests/Views/ListViewTests.cs
@@ -300,16 +300,16 @@ namespace Terminal.Gui.ViewTests {
Assert.Equal (19, lv.SelectedItem);
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌──────────┐
+│Line10 │
+│Line11 │
+│Line12 │
+│Line13 │
+│Line14 │
+│Line15 │
+│Line16 │
+│Line17 │
+│Line18 │
│Line19 │
-│ │
-│ │
-│ │
-│ │
-│ │
-│ │
-│ │
-│ │
-│ │
└──────────┘", output);
Assert.True (lv.ScrollUp (20));