From bfa76a2882917179511a137cb111d662d2351377 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 1 Nov 2023 15:16:21 +0000 Subject: [PATCH 1/3] Fixes #2865. Unknown character sequence while resizing terminal. --- .../CursesDriver/CursesDriver.cs | 79 ++++++++++++-- .../ConsoleDrivers/CursesDriver/binding.cs | 44 ++++++-- .../ConsoleDrivers/CursesDriver/constants.cs | 1 + UICatalog/Scenarios/MainLoopTimeouts.cs | 101 ++++++++++++++++++ 4 files changed, 206 insertions(+), 19 deletions(-) create mode 100644 UICatalog/Scenarios/MainLoopTimeouts.cs diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 8a9f0d2a6b..8b94b116b1 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -341,6 +341,7 @@ void ProcessInput () Key k = Key.Null; if (code == Curses.KEY_CODE_YES) { + var lastWch = wch; while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) { ProcessWinChange (); code = Curses.get_wch (out wch); @@ -379,6 +380,24 @@ void ProcessInput () } else if (wch >= 325 && wch <= 327) { // Shift+Alt+(F1 - F3) wch -= 60; k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch); + } else { + code = Curses.get_wch (out wch); + if (code == 0) { + switch (wch) { + case 16: + keyModifiers.Shift = true; + break; + default: + if (lastWch == Curses.KeyResize && wch == 91) { + // Returns this keys to the std input + Curses.ungetch (91); + Curses.ungetch (27); + return; + } else { + throw new Exception (); + } + } + } } keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); keyHandler (new KeyEvent (k, MapKeyModifiers (k))); @@ -388,8 +407,6 @@ void ProcessInput () // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey if (wch == 27) { - Curses.timeout (10); - code = Curses.get_wch (out int wch2); if (code == Curses.KEY_CODE_YES) { @@ -425,6 +442,56 @@ void ProcessInput () } else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) { keyModifiers.Shift = true; keyModifiers.Alt = true; + } else if (wch2 == Curses.KeySS3) { + while (code > -1) { + code = Curses.get_wch (out wch2); + if (code == 0) { + switch (wch2) { + case 16: + keyModifiers.Shift = true; + break; + case 108: + k = (Key)'+'; + break; + case 109: + k = (Key)'-'; + break; + case 112: + k = Key.InsertChar; + break; + case 113: + k = Key.End; + break; + case 114: + k = Key.CursorDown; + break; + case 115: + k = Key.PageDown; + break; + case 116: + k = Key.CursorLeft; + break; + case 117: + k = Key.Clear; + break; + case 118: + k = Key.CursorRight; + break; + case 119: + k = Key.Home; + break; + case 120: + k = Key.CursorUp; + break; + case 121: + k = Key.PageUp; + break; + default: + k = (Key)wch2; + break; + } + } + } } else if (wch2 < 256) { k = (Key)wch2; keyModifiers.Alt = true; @@ -559,7 +626,6 @@ void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos) public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) { // Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called - Curses.timeout (0); this.keyHandler = keyHandler; this.keyDownHandler = keyDownHandler; this.keyUpHandler = keyUpHandler; @@ -572,9 +638,7 @@ public override void PrepareToRun (MainLoop mainLoop, Action keyHandle return true; }); - mLoop.WinChanged += () => { - ProcessInput (); - }; + mLoop.WinChanged += () => ProcessWinChange (); } public override void Init (Action terminalResized) @@ -585,6 +649,7 @@ public override void Init (Action terminalResized) try { window = Curses.initscr (); Curses.set_escdelay (10); + Curses.nodelay (window.Handle, true); } catch (Exception e) { throw new Exception ($"Curses failed to initialize, the exception is: {e.Message}"); } @@ -673,13 +738,11 @@ public override void Init (Action terminalResized) ResizeScreen (); UpdateOffScreen (); - } public override void ResizeScreen () { Clip = new Rect (0, 0, Cols, Rows); - Curses.refresh (); } public override void UpdateOffScreen () diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs index f50f646387..c71da22083 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs @@ -49,13 +49,13 @@ namespace Unix.Terminal { #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public partial class Curses { - //[StructLayout (LayoutKind.Sequential)] - //public struct winsize { - // public ushort ws_row; - // public ushort ws_col; - // public ushort ws_xpixel; /* unused */ - // public ushort ws_ypixel; /* unused */ - //}; + [StructLayout (LayoutKind.Sequential)] + public struct winsize { + public ushort ws_row; + public ushort ws_col; + public ushort ws_xpixel; /* unused */ + public ushort ws_ypixel; /* unused */ + }; [StructLayout (LayoutKind.Sequential)] public struct MouseEvent { @@ -78,8 +78,8 @@ public struct MouseEvent { [DllImport ("libc")] public extern static int setlocale (int cate, [MarshalAs (UnmanagedType.LPStr)] string locale); - //[DllImport ("libc")] - //public extern static int ioctl (int fd, int cmd, out winsize argp); + [DllImport ("libc")] + public extern static int ioctl (int fd, int cmd, out winsize argp); static void LoadMethods () { @@ -227,8 +227,26 @@ internal static IntPtr console_sharp_get_curscr () internal static void console_sharp_get_dims (out int lines, out int cols) { - lines = Marshal.ReadInt32 (lines_ptr); - cols = Marshal.ReadInt32 (cols_ptr); + if (UnmanagedLibrary.IsMacOSPlatform) { + int cmd = TIOCGWINSZ_MAC; + + if (ioctl (1, cmd, out winsize ws) == 0) { + lines = ws.ws_row; + cols = ws.ws_col; + + if (lines == Lines && cols == Cols) { + return; + } + + resizeterm (lines, cols); + } else { + lines = Lines; + cols = Cols; + } + } else { + lines = Marshal.ReadInt32 (lines_ptr); + cols = Marshal.ReadInt32 (cols_ptr); + } //int cmd; //if (UnmanagedLibrary.IsMacOSPlatform) { @@ -347,6 +365,7 @@ static public int IsAlt (int key) static public int savetty () => methods.savetty (); static public int resetty () => methods.resetty (); static public int set_escdelay (int size) => methods.set_escdelay (size); + static public int nodelay (IntPtr win, bool bf) => methods.nodelay (win, bf); } #pragma warning disable RCS1102 // Make class static. @@ -424,6 +443,7 @@ internal class Delegates { public delegate int savetty (); public delegate int resetty (); public delegate int set_escdelay (int size); + public delegate int nodelay (IntPtr win, bool bf); } internal class NativeMethods { @@ -498,6 +518,7 @@ internal class NativeMethods { public readonly Delegates.savetty savetty; public readonly Delegates.resetty resetty; public readonly Delegates.set_escdelay set_escdelay; + public readonly Delegates.nodelay nodelay; public UnmanagedLibrary UnmanagedLibrary; public NativeMethods (UnmanagedLibrary lib) @@ -574,6 +595,7 @@ public NativeMethods (UnmanagedLibrary lib) savetty = lib.GetNativeMethodDelegate ("savetty"); resetty = lib.GetNativeMethodDelegate ("resetty"); set_escdelay = lib.GetNativeMethodDelegate ("set_escdelay"); + nodelay = lib.GetNativeMethodDelegate ("nodelay"); } } #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs index fb9bc326bb..e2171bdf4e 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs @@ -106,6 +106,7 @@ public enum Event : long { public const int KeyHome = unchecked((int)0x106); public const int KeyMouse = unchecked((int)0x199); public const int KeyCSI = unchecked((int)0x5b); + public const int KeySS3 = unchecked((int)0x4f); public const int KeyEnd = unchecked((int)0x168); public const int KeyDeleteChar = unchecked((int)0x14a); public const int KeyInsertChar = unchecked((int)0x14b); diff --git a/UICatalog/Scenarios/MainLoopTimeouts.cs b/UICatalog/Scenarios/MainLoopTimeouts.cs new file mode 100644 index 0000000000..c0b015ba4d --- /dev/null +++ b/UICatalog/Scenarios/MainLoopTimeouts.cs @@ -0,0 +1,101 @@ +#pragma warning disable format + +#pragma warning restore format +using System; +using System.Collections.Generic; +using Terminal.Gui; + +namespace UICatalog.Scenarios { + [ScenarioMetadata (Name: "MainLoopTimeouts", Description: "MainLoop Timeouts")] + [ScenarioCategory ("Tests")] + public class MainLoopTimeouts : Scenario { + static readonly List GlobalList = new () { "1" }; + static readonly ListView GlobalListView = new () { Width = Dim.Fill (), Height = Dim.Fill () }; + + static Label CounterLabel; + static Label BlinkingLabel; + + static int Counter = 0; + + static object _listToken = null; + static object _blinkToken = null; + static object _countToken = null; + + public override void Init (ColorScheme colorScheme) + { + Application.Init (); + + var startButton = new Button ("Start"); + var stopButton = new Button ("Stop") { Y = 1 }; + var container = new View () { X = Pos.Center (), Y = Pos.Center (), Width = 8, Height = 8, ColorScheme = Colors.Error }; + + CounterLabel = new Label ("0") { X = Pos.X (container), Y = Pos.Y (container) - 2 }; + BlinkingLabel = new Label ("Blink") { X = Pos.X (container), Y = Pos.Bottom (container) + 1 }; + + startButton.Clicked += Start; + stopButton.Clicked += Stop; + + GlobalListView.SetSource (GlobalList); + container.Add (GlobalListView); + + Application.Top.Add (container, CounterLabel, BlinkingLabel); + Application.Top.Add (startButton, stopButton); + Application.Run (); + Application.Shutdown (); + } + + public override void Run () + { + } + + private static void Start () + { + _listToken = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), Add); + _blinkToken = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (1000), Blink); + _countToken = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (1000), Count); + } + + private static void Stop () + { + Application.MainLoop.RemoveTimeout (_listToken); + Application.MainLoop.RemoveTimeout (_blinkToken); + Application.MainLoop.RemoveTimeout (_countToken); + } + + private static bool Add (MainLoop mainLoop) + { + Application.MainLoop.Invoke (() => { + GlobalList.Add (new Random ().Next (100).ToString ()); + GlobalListView.MoveDown (); + }); + + return true; + } + + private static bool Blink (MainLoop mainLoop) + { + Application.MainLoop.Invoke (() => { + if (BlinkingLabel.Visible) { + BlinkingLabel.Visible = false; + System.Diagnostics.Debug.WriteLine (BlinkingLabel.Visible); + } else { + BlinkingLabel.Visible = true; + System.Diagnostics.Debug.WriteLine (BlinkingLabel.Visible); + } + + }); + + return true; + } + + private static bool Count (MainLoop mainLoop) + { + Application.MainLoop.Invoke (() => { + Counter++; + CounterLabel.Text = Counter.ToString (); + }); + + return true; + } + } +} \ No newline at end of file From 2411b8a32e2c8b3895792dee5c7bc522ff608d04 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 2 Nov 2023 13:57:02 +0000 Subject: [PATCH 2/3] Added USE_IOCTL definition for toggle. --- .../ConsoleDrivers/CursesDriver/binding.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs index c71da22083..68fd7bdd1e 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs @@ -41,14 +41,18 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // + +//#define USE_IOCTL + using System; using System.Runtime.InteropServices; using Terminal.Gui; namespace Unix.Terminal { #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - public partial class Curses { +#if USE_IOCTL + [StructLayout (LayoutKind.Sequential)] public struct winsize { public ushort ws_row; @@ -56,7 +60,7 @@ public struct winsize { public ushort ws_xpixel; /* unused */ public ushort ws_ypixel; /* unused */ }; - +#endif [StructLayout (LayoutKind.Sequential)] public struct MouseEvent { public short ID; @@ -77,10 +81,11 @@ public struct MouseEvent { [DllImport ("libc")] public extern static int setlocale (int cate, [MarshalAs (UnmanagedType.LPStr)] string locale); +#if USE_IOCTL [DllImport ("libc")] public extern static int ioctl (int fd, int cmd, out winsize argp); - +#endif static void LoadMethods () { var libs = UnmanagedLibrary.IsMacOSPlatform ? new string [] { "libncurses.dylib" } : new string [] { "libncursesw.so.6", "libncursesw.so.5" }; @@ -227,6 +232,8 @@ internal static IntPtr console_sharp_get_curscr () internal static void console_sharp_get_dims (out int lines, out int cols) { +#if USE_IOCTL + if (UnmanagedLibrary.IsMacOSPlatform) { int cmd = TIOCGWINSZ_MAC; @@ -247,7 +254,10 @@ internal static void console_sharp_get_dims (out int lines, out int cols) lines = Marshal.ReadInt32 (lines_ptr); cols = Marshal.ReadInt32 (cols_ptr); } - +#else + lines = Marshal.ReadInt32 (lines_ptr); + cols = Marshal.ReadInt32 (cols_ptr); +#endif //int cmd; //if (UnmanagedLibrary.IsMacOSPlatform) { // cmd = TIOCGWINSZ_MAC; From 164f1b70042a6fb000d36564da4a726f2b1b4ff5 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 6 Nov 2023 22:28:56 +0000 Subject: [PATCH 3/3] Explaining that is a CSI (Esc[) = 27;91. --- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 8b94b116b1..b908708d69 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -384,14 +384,15 @@ void ProcessInput () code = Curses.get_wch (out wch); if (code == 0) { switch (wch) { + // Shift code. case 16: keyModifiers.Shift = true; break; default: if (lastWch == Curses.KeyResize && wch == 91) { - // Returns this keys to the std input - Curses.ungetch (91); - Curses.ungetch (27); + // Returns this keys to the std input which is a CSI (\x1b[). + Curses.ungetch (91); // [ + Curses.ungetch (27); // Esc return; } else { throw new Exception ();