diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 8a9f0d2a6b..b908708d69 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,25 @@ 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) { + // Shift code. + case 16: + keyModifiers.Shift = true; + break; + default: + if (lastWch == Curses.KeyResize && wch == 91) { + // Returns this keys to the std input which is a CSI (\x1b[). + Curses.ungetch (91); // [ + Curses.ungetch (27); // Esc + return; + } else { + throw new Exception (); + } + } + } } keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); keyHandler (new KeyEvent (k, MapKeyModifiers (k))); @@ -388,8 +408,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 +443,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 +627,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 +639,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 +650,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 +739,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..68fd7bdd1e 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs @@ -41,22 +41,26 @@ // 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 { - //[StructLayout (LayoutKind.Sequential)] - //public struct winsize { - // public ushort ws_row; - // public ushort ws_col; - // public ushort ws_xpixel; /* unused */ - // public ushort ws_ypixel; /* unused */ - //}; +#if USE_IOCTL + [StructLayout (LayoutKind.Sequential)] + public struct winsize { + public ushort ws_row; + public ushort ws_col; + 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); - + [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,9 +232,32 @@ 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; + + 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); + } +#else lines = Marshal.ReadInt32 (lines_ptr); cols = Marshal.ReadInt32 (cols_ptr); - +#endif //int cmd; //if (UnmanagedLibrary.IsMacOSPlatform) { // cmd = TIOCGWINSZ_MAC; @@ -347,6 +375,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 +453,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 +528,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 +605,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