diff --git a/Demos/Sharpie.Demos.Bounce/Sharpie.Demos.Bounce.csproj b/Demos/Sharpie.Demos.Bounce/Sharpie.Demos.Bounce.csproj index 1a49ddb..a773ba0 100644 --- a/Demos/Sharpie.Demos.Bounce/Sharpie.Demos.Bounce.csproj +++ b/Demos/Sharpie.Demos.Bounce/Sharpie.Demos.Bounce.csproj @@ -24,7 +24,7 @@ - + diff --git a/Demos/Sharpie.Demos.Events/Sharpie.Demos.Events.csproj b/Demos/Sharpie.Demos.Events/Sharpie.Demos.Events.csproj index 1f4bfa3..6ed9933 100644 --- a/Demos/Sharpie.Demos.Events/Sharpie.Demos.Events.csproj +++ b/Demos/Sharpie.Demos.Events/Sharpie.Demos.Events.csproj @@ -24,7 +24,7 @@ - + diff --git a/Demos/Sharpie.Demos.Font/Sharpie.Demos.Font.csproj b/Demos/Sharpie.Demos.Font/Sharpie.Demos.Font.csproj index 439a882..efd2b4e 100644 --- a/Demos/Sharpie.Demos.Font/Sharpie.Demos.Font.csproj +++ b/Demos/Sharpie.Demos.Font/Sharpie.Demos.Font.csproj @@ -26,7 +26,7 @@ - + diff --git a/Demos/Sharpie.Demos.Slk/Sharpie.Demos.Slk.csproj b/Demos/Sharpie.Demos.Slk/Sharpie.Demos.Slk.csproj index e471be5..f8f34e7 100644 --- a/Demos/Sharpie.Demos.Slk/Sharpie.Demos.Slk.csproj +++ b/Demos/Sharpie.Demos.Slk/Sharpie.Demos.Slk.csproj @@ -24,7 +24,7 @@ - + diff --git a/Demos/Sharpie.Demos.Snake/Sharpie.Demos.Snake.csproj b/Demos/Sharpie.Demos.Snake/Sharpie.Demos.Snake.csproj index a441e4a..b0b4068 100644 --- a/Demos/Sharpie.Demos.Snake/Sharpie.Demos.Snake.csproj +++ b/Demos/Sharpie.Demos.Snake/Sharpie.Demos.Snake.csproj @@ -24,7 +24,7 @@ - + diff --git a/Demos/Sharpie.Demos.Windows/Sharpie.Demos.Windows.csproj b/Demos/Sharpie.Demos.Windows/Sharpie.Demos.Windows.csproj index a7dedbd..e509572 100644 --- a/Demos/Sharpie.Demos.Windows/Sharpie.Demos.Windows.csproj +++ b/Demos/Sharpie.Demos.Windows/Sharpie.Demos.Windows.csproj @@ -26,7 +26,7 @@ - + diff --git a/Sharpie.Tests/CursesBackendTests.cs b/Sharpie.Tests/CursesBackendTests.cs index 6a07730..3df40b4 100644 --- a/Sharpie.Tests/CursesBackendTests.cs +++ b/Sharpie.Tests/CursesBackendTests.cs @@ -49,6 +49,18 @@ private void MockLoadResult(string lib, bool result) }); } + private void VerifyAttempts(params string[] candidates) + { + foreach (var c in candidates) + { + _dotNetSystemAdapterMock.Verify( + s => s.TryLoadNativeLibrary(c, out It.Ref.IsAny), Times.Once); + } + + _dotNetSystemAdapterMock.Verify( + s => s.TryLoadNativeLibrary(It.IsAny(), out It.Ref.IsAny), Times.Exactly(candidates.Length)); + } + [TestInitialize] public void TestInitialize() { @@ -147,35 +159,110 @@ public void NCurses2_CallsNCurses1() out It.Ref.IsAny), Times.Once); } - [TestMethod] - public void NCurses2_ForLinux_HasManyOptionsForNCurses() + [TestMethod, DataRow("linux"), DataRow("freebsd")] + public void NCurses2_ForLinuxOrFreeBsd_HasSpecificOptions(string op) { _dotNetSystemAdapterMock.Setup(s => s.IsLinux) - .Returns(true); + .Returns(op == "linux"); + _dotNetSystemAdapterMock.Setup(s => s.IsFreeBsd) + .Returns(op == "freebsd"); var options = new[] { - "ncursesw", - "libncursesw.so", - "libncursesw.so.5", "libncursesw.so.6", - "ncurses", - "libncurses.so", - "libncurses.so.5", + "libncursesw.so.5", + "libncursesw.so", + "ncursesw", "libncurses.so.6", + "libncurses.so.5", + "libncurses.so", + "ncurses", }; Should.Throw(() => CursesBackend.NCurses(_dotNetSystemAdapterMock.Object)); - foreach (var option in options) + VerifyAttempts(options); + } + + [TestMethod] + public void NCurses2_ForMacOs_TriesToLoadDefault_IfNoHomeBrewFound() + { + _dotNetSystemAdapterMock.Setup(s => s.IsMacOs) + .Returns(true); + + Should.Throw(() => CursesBackend.NCurses(_dotNetSystemAdapterMock.Object)); + + VerifyAttempts("ncurses"); + } + + [TestMethod] + public void NCurses2_ForMacOs_ScansTheLibraryDirectory_IfNoHomeBrewFound() + { + _dotNetSystemAdapterMock.Setup(s => s.IsMacOs) + .Returns(true); + + _dotNetSystemAdapterMock.Setup(s => s.CombinePaths(It.IsAny())) + .Returns((string[] ps) => string.Join("+", ps)); + _dotNetSystemAdapterMock.Setup(s => s.GetEnvironmentVariable("HOMEBREW_PREFIX")).Returns("/h"); + _dotNetSystemAdapterMock.Setup(s => s.DirectoryExists("/h+lib")).Returns(true); + _dotNetSystemAdapterMock.Setup(s => s.EnumerateFiles("/h+lib")).Returns(new[] { - _dotNetSystemAdapterMock.Verify( - s => s.TryLoadNativeLibrary(option, It.IsAny(), It.IsAny(), - out It.Ref.IsAny), Times.Once); - } + "dummy.txt", + "libncurses.10", + "libncurses.dylib", + "libncurses.a.dylib", + "libncurses.1.dylib", + "libncurses.10.dylib" + }); - _dotNetSystemAdapterMock.Verify( - s => s.TryLoadNativeLibrary(It.IsAny(), It.IsAny(), It.IsAny(), - out It.Ref.IsAny), Times.Exactly(options.Length)); + Should.Throw(() => CursesBackend.NCurses(_dotNetSystemAdapterMock.Object)); + + VerifyAttempts("/h+lib+libncurses.1.dylib", "/h+lib+libncurses.10.dylib", "ncurses"); + } + + [TestMethod] + public void NCurses2_ForMacOs_ScansTheCellarDirectories_IfNoHomeBrewFound() + { + _dotNetSystemAdapterMock.Setup(s => s.IsMacOs) + .Returns(true); + + _dotNetSystemAdapterMock.Setup(s => s.CombinePaths(It.IsAny())) + .Returns((string[] ps) => string.Join("+", ps)); + _dotNetSystemAdapterMock.Setup(s => s.GetEnvironmentVariable("HOMEBREW_CELLAR")).Returns("/h"); + _dotNetSystemAdapterMock.Setup(s => s.DirectoryExists("/h+ncurses")).Returns(true); + _dotNetSystemAdapterMock.Setup(s => s.DirectoryExists("/h+ncurses-one+lib")).Returns(true); + _dotNetSystemAdapterMock.Setup(s => s.DirectoryExists("/h+ncurses-2+lib")).Returns(true); + _dotNetSystemAdapterMock.Setup(s => s.EnumerateDirectories("/h+ncurses")).Returns(new[] + { + "/h+ncurses-one", + "/h+ncurses-2" + }); + + _dotNetSystemAdapterMock.Setup(s => s.EnumerateFiles("/h+ncurses-one+lib")).Returns(new[] + { + "dummy.txt", + "libncurses.10", + "libncurses.dylib", + "libncurses.a.dylib", + "libncurses.1.dylib", + "libncurses.10.dylib" + }); + + _dotNetSystemAdapterMock.Setup(s => s.EnumerateFiles("/h+ncurses-2+lib")).Returns(new[] + { + "dummy.txt", + "libncurses.2.dylib", + "libncurses.12.dylib" + }); + + Should.Throw(() => CursesBackend.NCurses(_dotNetSystemAdapterMock.Object)); + + VerifyAttempts( + "/h+ncurses-one+lib+libncurses.1.dylib", + "/h+ncurses-2+lib+libncurses.2.dylib", + "/h+ncurses-one+lib+libncurses.10.dylib", + "/h+ncurses-2+lib+libncurses.12.dylib", + "ncurses"); } + } diff --git a/Sharpie.Tests/CursesMouseEventParserTests.cs b/Sharpie.Tests/CursesMouseEventParserTests.cs new file mode 100644 index 0000000..89b8a12 --- /dev/null +++ b/Sharpie.Tests/CursesMouseEventParserTests.cs @@ -0,0 +1,192 @@ +/* +Copyright (c) 2022, Alexandru Ciobanu +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +namespace Sharpie.Tests; + +[TestClass] +public class CursesMouseEventParserTests +{ + [TestMethod] + public void Get_ReturnsObject_ForAbi1() + { + var r = CursesMouseEventParser.Get(1); + r.ShouldNotBeNull(); + + r.ReportPosition.ShouldBe(8u << 24); + r.All.ShouldBe((8u << 24) - 1); + } + + [TestMethod] + public void Get_ReturnsObject_ForAbi2() + { + var r = CursesMouseEventParser.Get(2); + r.ShouldNotBeNull(); + + r.ReportPosition.ShouldBe(8u << 25); + r.All.ShouldBe((8u << 25) - 1); + } + + [TestMethod] + public void Get_ReturnsAbi1_ForUnknownAbi() + { + CursesMouseEventParser.Get(-1).ShouldBe(CursesMouseEventParser.Get(1)); + } + + [TestMethod, + DataRow(1u << ((1 - 1) * 6), MouseButton.Button1, MouseButtonState.Released), + DataRow(2u << ((1 - 1) * 6), MouseButton.Button1, MouseButtonState.Pressed), + DataRow(4u << ((1 - 1) * 6), MouseButton.Button1, MouseButtonState.Clicked), + DataRow(8u << ((1 - 1) * 6), MouseButton.Button1, MouseButtonState.DoubleClicked), + DataRow(16u << ((1 - 1) * 6), MouseButton.Button1, MouseButtonState.TripleClicked), + + DataRow(1u << ((2 - 1) * 6), MouseButton.Button2, MouseButtonState.Released), + DataRow(2u << ((2 - 1) * 6), MouseButton.Button2, MouseButtonState.Pressed), + DataRow(4u << ((2 - 1) * 6), MouseButton.Button2, MouseButtonState.Clicked), + DataRow(8u << ((2 - 1) * 6), MouseButton.Button2, MouseButtonState.DoubleClicked), + DataRow(16u << ((2 - 1) * 6), MouseButton.Button2, MouseButtonState.TripleClicked), + + DataRow(1u << ((3 - 1) * 6), MouseButton.Button3, MouseButtonState.Released), + DataRow(2u << ((3 - 1) * 6), MouseButton.Button3, MouseButtonState.Pressed), + DataRow(4u << ((3 - 1) * 6), MouseButton.Button3, MouseButtonState.Clicked), + DataRow(8u << ((3 - 1) * 6), MouseButton.Button3, MouseButtonState.DoubleClicked), + DataRow(16u << ((3 - 1) * 6), MouseButton.Button3, MouseButtonState.TripleClicked), + + DataRow(1u << ((4 - 1) * 6), MouseButton.Button4, MouseButtonState.Released), + DataRow(2u << ((4 - 1) * 6), MouseButton.Button4, MouseButtonState.Pressed), + DataRow(4u << ((4 - 1) * 6), MouseButton.Button4, MouseButtonState.Clicked), + DataRow(8u << ((4 - 1) * 6), MouseButton.Button4, MouseButtonState.DoubleClicked), + DataRow(16u << ((4 - 1) * 6), MouseButton.Button4, MouseButtonState.TripleClicked)] + public void Parse_ParsesTheButtonAndState_ForAbi1(uint raw, MouseButton expButton, MouseButtonState expState) + { + var parser = CursesMouseEventParser.Get(1); + var p = parser.Parse(raw); + + p.ShouldBe((expButton, expState, ModifierKey.None)); + } + + + [TestMethod, + DataRow(1u << ((1 - 1) * 5), MouseButton.Button1, MouseButtonState.Released), + DataRow(2u << ((1 - 1) * 5), MouseButton.Button1, MouseButtonState.Pressed), + DataRow(4u << ((1 - 1) * 5), MouseButton.Button1, MouseButtonState.Clicked), + DataRow(8u << ((1 - 1) * 5), MouseButton.Button1, MouseButtonState.DoubleClicked), + DataRow(16u << ((1 - 1) * 5), MouseButton.Button1, MouseButtonState.TripleClicked), + + DataRow(1u << ((2 - 1) * 5), MouseButton.Button2, MouseButtonState.Released), + DataRow(2u << ((2 - 1) * 5), MouseButton.Button2, MouseButtonState.Pressed), + DataRow(4u << ((2 - 1) * 5), MouseButton.Button2, MouseButtonState.Clicked), + DataRow(8u << ((2 - 1) * 5), MouseButton.Button2, MouseButtonState.DoubleClicked), + DataRow(16u << ((2 - 1) * 5), MouseButton.Button2, MouseButtonState.TripleClicked), + + DataRow(1u << ((3 - 1) * 5), MouseButton.Button3, MouseButtonState.Released), + DataRow(2u << ((3 - 1) * 5), MouseButton.Button3, MouseButtonState.Pressed), + DataRow(4u << ((3 - 1) * 5), MouseButton.Button3, MouseButtonState.Clicked), + DataRow(8u << ((3 - 1) * 5), MouseButton.Button3, MouseButtonState.DoubleClicked), + DataRow(16u << ((3 - 1) * 5), MouseButton.Button3, MouseButtonState.TripleClicked), + + DataRow(1u << ((4 - 1) * 5), MouseButton.Button4, MouseButtonState.Released), + DataRow(2u << ((4 - 1) * 5), MouseButton.Button4, MouseButtonState.Pressed), + DataRow(4u << ((4 - 1) * 5), MouseButton.Button4, MouseButtonState.Clicked), + DataRow(8u << ((4 - 1) * 5), MouseButton.Button4, MouseButtonState.DoubleClicked), + DataRow(16u << ((4 - 1) * 5), MouseButton.Button4, MouseButtonState.TripleClicked), + + DataRow(1u << ((5 - 1) * 5), MouseButton.Button5, MouseButtonState.Released), + DataRow(2u << ((5 - 1) * 5), MouseButton.Button5, MouseButtonState.Pressed), + DataRow(4u << ((5 - 1) * 5), MouseButton.Button5, MouseButtonState.Clicked), + DataRow(8u << ((5 - 1) * 5), MouseButton.Button5, MouseButtonState.DoubleClicked), + DataRow(16u << ((5 - 1) * 5), MouseButton.Button5, MouseButtonState.TripleClicked), + ] + public void Parse_ParsesTheButtonAndState_ForAbi2(uint raw, MouseButton expButton, MouseButtonState expState) + { + var parser = CursesMouseEventParser.Get(2); + var p = parser.Parse(raw); + + p.ShouldBe((expButton, expState, ModifierKey.None)); + } + + [TestMethod, + DataRow(0u, ModifierKey.None), + DataRow(1u << 24, ModifierKey.Ctrl), + DataRow(2u << 24, ModifierKey.Shift), + DataRow(4u << 24, ModifierKey.Alt), + DataRow((4u << 24) | (1u << 24), ModifierKey.Alt | ModifierKey.Ctrl), + DataRow((2u << 24) | (1u << 24), ModifierKey.Shift | ModifierKey.Ctrl), + DataRow((4u << 24) | (2u << 24), ModifierKey.Alt | ModifierKey.Shift), + DataRow((1u << 24) | (2u << 24), ModifierKey.Ctrl | ModifierKey.Shift), + DataRow((4u << 24) | (1u << 24) | (2u << 24), + ModifierKey.Alt | ModifierKey.Shift | ModifierKey.Ctrl)] + public void Parse_ParsesTheModifiers_ForAbi1(uint raw, ModifierKey expMod) + { + var parser = CursesMouseEventParser.Get(1); + var p = parser.Parse(raw | 1u); + + p.ShouldBe((MouseButton.Button1, MouseButtonState.Released,expMod)); + } + + + [TestMethod, + DataRow(0u, ModifierKey.None), + DataRow(1u << 25, ModifierKey.Ctrl), + DataRow(2u << 25, ModifierKey.Shift), + DataRow(4u << 25, ModifierKey.Alt), + DataRow((4u << 25) | (1u << 25), ModifierKey.Alt | ModifierKey.Ctrl), + DataRow((2u << 25) | (1u << 25), ModifierKey.Shift | ModifierKey.Ctrl), + DataRow((4u << 25) | (2u << 25), ModifierKey.Alt | ModifierKey.Shift), + DataRow((1u << 25) | (2u << 25), ModifierKey.Ctrl | ModifierKey.Shift), + DataRow((4u << 25) | (1u << 25) | (2u << 25), + ModifierKey.Alt | ModifierKey.Shift | ModifierKey.Ctrl)] + public void Parse_ParsesTheModifiers_ForAbi2(uint raw, ModifierKey expMod) + { + var parser = CursesMouseEventParser.Get(2); + var p = parser.Parse(raw | 1u); + + p.ShouldBe((MouseButton.Button1, MouseButtonState.Released,expMod)); + } + + [TestMethod, + DataRow(0u), DataRow(8u << 24), DataRow(1u << 24)] + public void Parse_ReturnsNullIfNoButtonPresent_ForAbi1(uint raw) + { + var parser = CursesMouseEventParser.Get(1); + var p = parser.Parse(raw); + + p.ShouldBeNull(); + } + + [TestMethod, + DataRow(0u), DataRow(8u << 25), DataRow(1u << 25)] + public void Parse_ReturnsNullIfNoButtonPresent_ForAbi2(uint raw) + { + var parser = CursesMouseEventParser.Get(2); + var p = parser.Parse(raw); + + p.ShouldBeNull(); + } +} diff --git a/Sharpie.Tests/EventPumpTests.cs b/Sharpie.Tests/EventPumpTests.cs index fe178fb..0c83b41 100644 --- a/Sharpie.Tests/EventPumpTests.cs +++ b/Sharpie.Tests/EventPumpTests.cs @@ -112,6 +112,10 @@ public void TestInitialize() _cursesMock.Setup(s => s.initscr()) .Returns(new IntPtr(100)); + _cursesMock.Setup(s => s.mouse_version()) + .Returns(1); + + _terminal = new(_cursesMock.Object, new(UseStandardKeySequenceResolvers: false, ManagedWindows: TestContext.TestName!.Contains("_WhenManaged_"))); @@ -567,7 +571,7 @@ public void Listen1_SkipsInvalidMouseEvents() var dx = skip ? 10 : 0; me = new() { - x = dx + 5, y = dx + 6, buttonState = (uint) CursesMouseEvent.EventType.ReportPosition + x = dx + 5, y = dx + 6, buttonState = 8u << 24 // report position }; var res = skip ? -1 : 0; @@ -582,35 +586,17 @@ public void Listen1_SkipsInvalidMouseEvents() _cursesMock.Verify(v => v.getmouse(out It.Ref.IsAny), Times.Exactly(2)); } - - [TestMethod, Timeout(Timeout)] - public void Listen1_SkipsMouseEvents_WithBadButtons() - { - _pump.UseInternalMouseEventResolver = false; - - var skip = true; - _cursesMock.Setup(s => s.getmouse(out It.Ref.IsAny)) - .Returns((out CursesMouseEvent me) => - { - me = new() { buttonState = skip ? 0 : (uint) CursesMouseEvent.EventType.Button2Released }; - - skip = !skip; - return 0; - }); - - var e = SimulateEventRep(2, (int) CursesKey.Yes, (int) CursesKey.Mouse); - ((MouseActionEvent) e).Button.ShouldBe(MouseButton.Button2); - - _cursesMock.Verify(v => v.getmouse(out It.Ref.IsAny), Times.Exactly(2)); - } - + [TestMethod, Timeout(Timeout)] public void Listen1_ProcessesMouseMoveEvents() { _cursesMock.Setup(s => s.getmouse(out It.Ref.IsAny)) .Returns((out CursesMouseEvent me) => { - me = new() { x = 5, y = 6, buttonState = (uint) CursesMouseEvent.EventType.ReportPosition }; + me = new() + { + x = 5, y = 6, buttonState = 8u << 24 // report position + }; return 0; }); @@ -627,7 +613,10 @@ public void Listen1_ProcessesMouseMoveEvents_AndUsesInternalMouseResolver() _cursesMock.Setup(s => s.getmouse(out It.Ref.IsAny)) .Returns((out CursesMouseEvent me) => { - me = new() { x = 5, y = 6, buttonState = (uint) CursesMouseEvent.EventType.ReportPosition }; + me = new() + { + x = 5, y = 6, buttonState = 8u << 24 // report position + }; return 0; }); @@ -651,8 +640,7 @@ public void Listen1_ProcessesMouseActionEvents() { x = 5, y = 6, - buttonState = (uint) CursesMouseEvent.EventType.Button1Clicked | - (uint) CursesMouseEvent.EventType.Alt + buttonState = (4u << ((1 - 1) * 6)) | (4u << 24) // Button1Clicked + Alt }; return 0; @@ -680,8 +668,7 @@ public void Listen1_ProcessesMouseActionEvents_AndUsesInternalMouseResolver() { x = 5, y = 6, - buttonState = (uint) CursesMouseEvent.EventType.Button1Pressed | - (uint) CursesMouseEvent.EventType.Alt + buttonState = (2u << ((1 - 1) * 6)) | (4u << 24) // Button1Pressed + Alt }; return 0; @@ -837,7 +824,10 @@ public void Listen1_ConsidersBreaksInSequences() _cursesMock.Setup(s => s.getmouse(out It.Ref.IsAny)) .Returns((out CursesMouseEvent me) => { - me = new() { buttonState = (uint) CursesMouseEvent.EventType.ReportPosition }; + me = new() + { + buttonState = 8u << 24 // report position + }; return 0; }); diff --git a/Sharpie.Tests/HelpersTests.cs b/Sharpie.Tests/HelpersTests.cs index 984bf31..8d79d79 100644 --- a/Sharpie.Tests/HelpersTests.cs +++ b/Sharpie.Tests/HelpersTests.cs @@ -282,48 +282,6 @@ public void ConvertKeyPressEvent_ConvertsKnownMappings(uint rawKey, Key expKey, result.modifierKey.ShouldBe(expMod); } - [TestMethod, DataRow(CursesMouseEvent.EventType.Button1Released, MouseButton.Button1, MouseButtonState.Released), - DataRow(CursesMouseEvent.EventType.Button1Pressed, MouseButton.Button1, MouseButtonState.Pressed), - DataRow(CursesMouseEvent.EventType.Button1Clicked, MouseButton.Button1, MouseButtonState.Clicked), - DataRow(CursesMouseEvent.EventType.Button1DoubleClicked, MouseButton.Button1, MouseButtonState.DoubleClicked), - DataRow(CursesMouseEvent.EventType.Button1TripleClicked, MouseButton.Button1, MouseButtonState.TripleClicked), - DataRow(CursesMouseEvent.EventType.Button2Released, MouseButton.Button2, MouseButtonState.Released), - DataRow(CursesMouseEvent.EventType.Button2Pressed, MouseButton.Button2, MouseButtonState.Pressed), - DataRow(CursesMouseEvent.EventType.Button2Clicked, MouseButton.Button2, MouseButtonState.Clicked), - DataRow(CursesMouseEvent.EventType.Button2DoubleClicked, MouseButton.Button2, MouseButtonState.DoubleClicked), - DataRow(CursesMouseEvent.EventType.Button2TripleClicked, MouseButton.Button2, MouseButtonState.TripleClicked), - DataRow(CursesMouseEvent.EventType.Button3Released, MouseButton.Button3, MouseButtonState.Released), - DataRow(CursesMouseEvent.EventType.Button3Pressed, MouseButton.Button3, MouseButtonState.Pressed), - DataRow(CursesMouseEvent.EventType.Button3Clicked, MouseButton.Button3, MouseButtonState.Clicked), - DataRow(CursesMouseEvent.EventType.Button3DoubleClicked, MouseButton.Button3, MouseButtonState.DoubleClicked), - DataRow(CursesMouseEvent.EventType.Button3TripleClicked, MouseButton.Button3, MouseButtonState.TripleClicked), - DataRow(CursesMouseEvent.EventType.Button4Released, MouseButton.Button4, MouseButtonState.Released), - DataRow(CursesMouseEvent.EventType.Button4Pressed, MouseButton.Button4, MouseButtonState.Pressed), - DataRow(CursesMouseEvent.EventType.Button4Clicked, MouseButton.Button4, MouseButtonState.Clicked), - DataRow(CursesMouseEvent.EventType.Button4DoubleClicked, MouseButton.Button4, MouseButtonState.DoubleClicked), - DataRow(CursesMouseEvent.EventType.Button4TripleClicked, MouseButton.Button4, MouseButtonState.TripleClicked)] - public void ConvertMouseActionEvent_ConvertsKnownMappings(int evt, MouseButton expButton, MouseButtonState expState) - { - var result = Helpers.ConvertMouseActionEvent((CursesMouseEvent.EventType) evt); - result.button.ShouldBe(expButton); - result.state.ShouldBe(expState); - } - - [TestMethod, DataRow(0, ModifierKey.None), DataRow(CursesMouseEvent.EventType.Alt, ModifierKey.Alt), - DataRow(CursesMouseEvent.EventType.Ctrl, ModifierKey.Ctrl), - DataRow(CursesMouseEvent.EventType.Shift, ModifierKey.Shift), - DataRow(CursesMouseEvent.EventType.Alt | CursesMouseEvent.EventType.Ctrl, ModifierKey.Alt | ModifierKey.Ctrl), - DataRow(CursesMouseEvent.EventType.Shift | CursesMouseEvent.EventType.Ctrl, ModifierKey.Shift | ModifierKey.Ctrl), - DataRow(CursesMouseEvent.EventType.Alt | CursesMouseEvent.EventType.Shift, ModifierKey.Alt | ModifierKey.Shift), - DataRow(CursesMouseEvent.EventType.Ctrl | CursesMouseEvent.EventType.Shift, ModifierKey.Ctrl | ModifierKey.Shift), - DataRow(CursesMouseEvent.EventType.Alt | CursesMouseEvent.EventType.Ctrl | CursesMouseEvent.EventType.Shift, - ModifierKey.Alt | ModifierKey.Shift | ModifierKey.Ctrl)] - public void ConvertMouseActionEvent_MapsModifiers(int evt, ModifierKey expMod) - { - var result = Helpers.ConvertMouseActionEvent((CursesMouseEvent.EventType) evt); - result.modifierKey.ShouldBe(expMod); - } - [TestMethod] public void EnumerateInHalves_Throws_IfCountIsNegative() { diff --git a/Sharpie.Tests/NCursesBackendTests.cs b/Sharpie.Tests/NCursesBackendTests.cs index 677a649..bf04c27 100644 --- a/Sharpie.Tests/NCursesBackendTests.cs +++ b/Sharpie.Tests/NCursesBackendTests.cs @@ -39,9 +39,7 @@ public class NCursesBackendTests private Mock _dotNetSystemAdapterMock = null!; private Mock _nativeSymbolResolverMock = null!; - private static CursesComplexChar MakeTestComplexChar(uint x) => - new(x, x, x, x, x, - x); + private static CursesComplexChar MakeTestComplexChar(uint x) => new() { _attrAndColorPair = x }; [TestInitialize] public void TestInitialize() @@ -526,6 +524,9 @@ public void initscr_IsRelayedToLibrary(int ret) public void longname_IsRelayedToLibrary(string ret) { var h = Marshal.StringToHGlobalAnsi(ret); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve(s => s(), h); _backend.longname() @@ -536,6 +537,9 @@ public void longname_IsRelayedToLibrary(string ret) public void termname_IsRelayedToLibrary(string ret) { var h = Marshal.StringToHGlobalAnsi(ret); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve(s => s(), h); _backend.termname() @@ -546,6 +550,8 @@ public void termname_IsRelayedToLibrary(string ret) public void curses_version_IsRelayedToLibrary(string ret) { var h = Marshal.StringToHGlobalAnsi(ret); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); _nativeSymbolResolverMock.MockResolve(s => s(), h); _backend.curses_version() @@ -1030,6 +1036,9 @@ public void slk_label_IsRelayedToLibrary(string ret) { const int i = 999; var h = Marshal.StringToHGlobalAnsi(ret); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve(s => s(i), h); _backend.slk_label(i) @@ -1211,6 +1220,10 @@ public void wenclose_IsRelayedToLibrary(bool ret) public void keybound_IsRelayedToLibrary(string ret) { var h = Marshal.StringToHGlobalAnsi(ret); + + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve(s => s('A', 2), h); _backend.keybound('A', 2) @@ -1221,6 +1234,9 @@ public void keybound_IsRelayedToLibrary(string ret) public void keyname_IsRelayedToLibrary(string ret) { var h = Marshal.StringToHGlobalAnsi(ret); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve(s => s('A'), h); _backend.keyname('A') @@ -1231,6 +1247,9 @@ public void keyname_IsRelayedToLibrary(string ret) public void key_name_IsRelayedToLibrary(string ret) { var h = Marshal.StringToHGlobalAnsi(ret); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve(s => s('A'), h); _backend.key_name('A') @@ -1718,6 +1737,9 @@ public void wadd_wchnstr_IsRelayedToLibrary(int ret) [TestMethod, DataRow(0), DataRow(-1)] public void init_color_IsRelayedToLibrary(int ret) { + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve(s => s(1, 2, 3, 4), ret); _backend.init_color(1, 2, 3, 4) @@ -1738,7 +1760,9 @@ public void wunctrl_IsRelayedToLibrary(string ret) { var ch = new CursesComplexChar(); var h = Marshal.StringToHGlobalUni(ret); - + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryUnicodeStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve(s => s(ref ch), h); _backend.wunctrl(ch) @@ -2035,6 +2059,9 @@ public void pair_content_IsRelayedToLibrary(int ret) [TestMethod, DataRow(0), DataRow(-1)] public void color_content_IsRelayedToLibrary(int ret) { + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve() .Setup(s => s(1, out It.Ref.IsAny, out It.Ref.IsAny, out It.Ref.IsAny)) @@ -2055,12 +2082,27 @@ public void color_content_IsRelayedToLibrary(int ret) ((int) blue).ShouldBe(33); } + [TestMethod, DataRow("something", -1), DataRow("6.2.3", 2), DataRow("something6.2.3", 2), + DataRow("something 5.7", -1), DataRow("something 4.7.5", -1), DataRow("something 5.7.12312", 1)] + public void mouse_version_ParsesCursesVersion(string ver, int m) + { + var h = Marshal.StringToHGlobalAnsi(ver); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + _nativeSymbolResolverMock.MockResolve() + .Setup(s => s()) + .Returns(h); + + _backend.mouse_version() + .ShouldBe(m); + } + [TestMethod, DataRow(0), DataRow(-1)] public void mousemask_IsRelayedToLibrary(int ret) { _nativeSymbolResolverMock.MockResolve() - .Setup(s => s(1, out It.Ref.IsAny)) - .Returns((int _, out int om) => + .Setup(s => s(1, out It.Ref.IsAny)) + .Returns((uint _, out uint om) => { om = 11; return ret; @@ -2070,9 +2112,10 @@ public void mousemask_IsRelayedToLibrary(int ret) _backend.mousemask(1, out var old) .ShouldBe(ret); - old.ShouldBe(11); + old.ShouldBe(11u); } + [TestMethod, DataRow(0), DataRow(-1)] public void wattr_get_IsRelayedToLibrary(int ret) { diff --git a/Sharpie.Tests/NativeLibraryWrapperTests.cs b/Sharpie.Tests/NativeLibraryWrapperTests.cs index cb59943..4328aa6 100644 --- a/Sharpie.Tests/NativeLibraryWrapperTests.cs +++ b/Sharpie.Tests/NativeLibraryWrapperTests.cs @@ -79,6 +79,8 @@ public void TryLoad_TriesToLoadByName_IfHasNoDirectory_ThenByPath() [TestMethod] public void TryLoad_TriesToLoadByPath_IfHasDirectory() { + _dotNetSystemAdapterMock.Setup(s => s.GetDirectoryName(It.IsAny())) + .Returns("something"); NativeLibraryWrapper.TryLoad(_dotNetSystemAdapterMock.Object, new[] { "hello/world" }); _dotNetSystemAdapterMock.Verify( diff --git a/Sharpie.Tests/Sharpie.Tests.csproj b/Sharpie.Tests/Sharpie.Tests.csproj index 0fc3058..9879c47 100644 --- a/Sharpie.Tests/Sharpie.Tests.csproj +++ b/Sharpie.Tests/Sharpie.Tests.csproj @@ -22,16 +22,16 @@ - - - - - - + + + + + + - + diff --git a/Sharpie.Tests/TerminalTests.cs b/Sharpie.Tests/TerminalTests.cs index 71ccc09..3c9896e 100644 --- a/Sharpie.Tests/TerminalTests.cs +++ b/Sharpie.Tests/TerminalTests.cs @@ -289,19 +289,24 @@ public void Ctor_Throws_WhenCursesFailsToPreparesCaretMode(bool enabled) .Operation.ShouldBe("curs_set"); } - [TestMethod, DataRow(true), DataRow(false)] - public void Ctor_PreparesUseMouse_ByAskingCurses(bool enabled) + [TestMethod, DataRow(true, 1), DataRow(true, 2), DataRow(false, 0)] + public void Ctor_PreparesUseMouse_ByAskingCurses(bool enabled, int abi) { + _cursesMock.Setup(s => s.mouse_version()) + .Returns(abi); + _terminal = new(_cursesMock.Object, new(UseMouse: enabled, MouseClickInterval: 999)); _terminal.Events.UseInternalMouseEventResolver.ShouldBeFalse(); _cursesMock.Verify(v => v.mouseinterval(999), enabled ? Times.Once : Times.Never); + var parser = CursesMouseEventParser.Get(abi); var expMask = enabled - ? (int) CursesMouseEvent.EventType.ReportPosition | (int) CursesMouseEvent.EventType.All + ? parser.ReportPosition | parser.All : 0; - _cursesMock.Verify(v => v.mousemask(expMask, out It.Ref.IsAny), Times.Once); + _cursesMock.Verify(v => v.mousemask(expMask, out It.Ref.IsAny), Times.Once); + _cursesMock.Verify(v => v.mouse_version(), Times.Exactly(enabled ? 2: 1)); } [TestMethod] @@ -316,7 +321,7 @@ public void Ctor_PreparesUseMouse_WithoutClickInterval_ByAskingCurses() [TestMethod, DataRow(true), DataRow(false), SuppressMessage("ReSharper", "StringLiteralTypo")] public void Ctor_Throws_WhenCursesFailsToPreparesUseMouse_1(bool enabled) { - _cursesMock.Setup(s => s.mousemask(It.IsAny(), out It.Ref.IsAny)) + _cursesMock.Setup(s => s.mousemask(It.IsAny(), out It.Ref.IsAny)) .Returns(-1); Should.Throw(() => new Terminal(_cursesMock.Object, new(UseMouse: enabled))) @@ -880,8 +885,8 @@ public void Dispose_RestoresCursesDefaults() _cursesMock.Setup(s => s.mouseinterval(It.IsAny())) .Returns(199); - _cursesMock.Setup(s => s.mousemask(It.IsAny(), out It.Ref.IsAny)) - .Returns((int _, out int o) => + _cursesMock.Setup(s => s.mousemask(It.IsAny(), out It.Ref.IsAny)) + .Returns((uint _, out uint o) => { o = 888; return 0; @@ -895,20 +900,20 @@ public void Dispose_RestoresCursesDefaults() _terminal.Dispose(); _cursesMock.Verify(v => v.mouseinterval(199), Times.Once); - _cursesMock.Verify(v => v.mousemask(888, out It.Ref.IsAny), Times.Once); + _cursesMock.Verify(v => v.mousemask(888, out It.Ref.IsAny), Times.Once); _cursesMock.Verify(v => v.curs_set(66), Times.Once); } [TestMethod] - public void Dispose_DoeNotThrow_IfCursesFails() + public void Dispose_DoesNotThrow_IfCursesFails() { _terminal = new(_cursesMock.Object, _settings); _cursesMock.Setup(s => s.mouseinterval(It.IsAny())) .Returns(-1); - _cursesMock.Setup(s => s.mousemask(It.IsAny(), out It.Ref.IsAny)) - .Returns((int _, out int o) => + _cursesMock.Setup(s => s.mousemask(It.IsAny(), out It.Ref.IsAny)) + .Returns((uint _, out uint o) => { o = 888; return -1; diff --git a/Sharpie.Tests/UnixNCursesBackendTests.cs b/Sharpie.Tests/UnixNCursesBackendTests.cs index 33dd4a8..c2e2657 100644 --- a/Sharpie.Tests/UnixNCursesBackendTests.cs +++ b/Sharpie.Tests/UnixNCursesBackendTests.cs @@ -30,6 +30,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE namespace Sharpie.Tests; +using System.Runtime.InteropServices; using Nito.Disposables; [TestClass, SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] @@ -107,8 +108,8 @@ public void mousemask_CallsCursesButNotNotConsole_IfCursesFails() .Returns(true); _nativeSymbolResolverMock.MockResolve() - .Setup(s => s(It.IsAny(), out It.Ref.IsAny)) - .Returns((int _, out int o) => + .Setup(s => s(It.IsAny(), out It.Ref.IsAny)) + .Returns((uint _, out uint o) => { o = 999; return -1; @@ -117,25 +118,60 @@ public void mousemask_CallsCursesButNotNotConsole_IfCursesFails() _backend.mousemask(100, out var old) .ShouldBe(-1); - old.ShouldBe(999); + old.ShouldBe(999u); _dotNetSystemAdapterMock.Verify(v => v.OutAndFlush(It.IsAny()), Times.Never); } - [TestMethod, SuppressMessage("ReSharper", "IdentifierTypo"), DataRow(0, "\x1b[?1003l"), - DataRow((int) CursesMouseEvent.EventType.ReportPosition, "\x1b[?1003h"), - DataRow((int) CursesMouseEvent.EventType.All, "\x1b[?1000h")] - public void mousemask_CallsCursesAndConsole_IfCursesSucceeds_WithReportPosition(int mask, string exp) + [TestMethod, SuppressMessage("ReSharper", "IdentifierTypo"), + DataRow(1), DataRow(2)] + public void mousemask_OutsToConsole_WhenReportingPosition(int abi) { - _dotNetSystemAdapterMock.Setup(s => s.IsFreeBsd) - .Returns(true); - _nativeSymbolResolverMock.MockResolve( - s => s(It.IsAny(), out It.Ref.IsAny), 0); + s => s(It.IsAny(), out It.Ref.IsAny), 0); + + var version = abi == 2 ? "version 6.0.0" : "version 1.0.0"; + _nativeSymbolResolverMock.MockResolve( + s => s(), Marshal.StringToHGlobalAnsi(version)); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + + var parser = CursesMouseEventParser.Get(abi); + _backend.mousemask(parser.ReportPosition, out var _) + .ShouldBe(0); - _backend.mousemask(mask, out var _) + _dotNetSystemAdapterMock.Verify(v => v.OutAndFlush("\x1b[?1003h"), Times.Once); + } + + [TestMethod, SuppressMessage("ReSharper", "IdentifierTypo"), + DataRow(1), DataRow(2)] + public void mousemask_OutsToConsole_WhenAll(int abi) + { + _nativeSymbolResolverMock.MockResolve( + s => s(It.IsAny(), out It.Ref.IsAny), 0); + + var version = abi == 2 ? "version 6.0.0" : "version 1.0.0"; + _nativeSymbolResolverMock.MockResolve( + s => s(), Marshal.StringToHGlobalAnsi(version)); + _dotNetSystemAdapterMock.Setup(s => s.NativeLibraryAnsiStrPtrToString(It.IsAny())) + .CallBase(); + var parser = CursesMouseEventParser.Get(abi); + _backend.mousemask(parser.All, out var _) .ShouldBe(0); - _dotNetSystemAdapterMock.Verify(v => v.OutAndFlush(exp), Times.Once); + _dotNetSystemAdapterMock.Verify(v => v.OutAndFlush("\x1b[?1000h"), Times.Once); + } + + [TestMethod, SuppressMessage("ReSharper", "IdentifierTypo")] + public void mousemask_OutsToConsole_WhenNothing() + { + _nativeSymbolResolverMock.MockResolve( + s => s(It.IsAny(), out It.Ref.IsAny), 0); + + _nativeSymbolResolverMock.MockResolve(); + _backend.mousemask(0, out var _) + .ShouldBe(0); + + _dotNetSystemAdapterMock.Verify(v => v.OutAndFlush("\x1b[?1003l"), Times.Once); } } diff --git a/Sharpie/Abstractions/IColorManager.cs b/Sharpie/Abstractions/IColorManager.cs index 4a0e6a8..84f9187 100644 --- a/Sharpie/Abstractions/IColorManager.cs +++ b/Sharpie/Abstractions/IColorManager.cs @@ -211,3 +211,4 @@ public interface IColorManager /// (short red, short green, short blue) BreakdownColor(StandardColor color); } + diff --git a/Sharpie/Abstractions/ICursesBackend.cs b/Sharpie/Abstractions/ICursesBackend.cs index 01790b3..e45f089 100644 --- a/Sharpie/Abstractions/ICursesBackend.cs +++ b/Sharpie/Abstractions/ICursesBackend.cs @@ -431,7 +431,9 @@ int wborder_set(IntPtr window, CursesComplexChar leftSide, CursesComplexChar rig int ungetmouse(CursesMouseEvent @event); - int mousemask(int newMask, out int oldMask); + int mousemask(uint newMask, out uint oldMask); + + int mouse_version(); bool wenclose(IntPtr window, int line, int col); @@ -445,3 +447,4 @@ int wborder_set(IntPtr window, CursesComplexChar leftSide, CursesComplexChar rig bool monitor_pending_resize(Action action, [NotNullWhen(true)] out IDisposable? handle); } + diff --git a/Sharpie/Abstractions/IDotNetSystemAdapter.cs b/Sharpie/Abstractions/IDotNetSystemAdapter.cs index 0501fa1..08e183b 100644 --- a/Sharpie/Abstractions/IDotNetSystemAdapter.cs +++ b/Sharpie/Abstractions/IDotNetSystemAdapter.cs @@ -1,84 +1,85 @@ namespace Sharpie.Abstractions; -using System.Reflection; -using System.Runtime.Versioning; - /// -/// An internal interface used to help test the functionality or native library loader. +/// An internal interface used to help test the functionality or native library loader. /// - internal interface IDotNetSystemAdapter { - private sealed class DotNetSystemAdapter: IDotNetSystemAdapter - { - } - /// - /// The actual instance that connects this interface to the .NET runtime. + /// The actual instance that connects this interface to the .NET runtime. /// - public static IDotNetSystemAdapter Instance = new DotNetSystemAdapter(); - - /// - [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] - bool TryLoadNativeLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, - out IntPtr handle) => - NativeLibrary.TryLoad(libraryName, assembly, searchPath, out handle); - - /// - [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] - bool TryLoadNativeLibrary(string libraryPath, out IntPtr handle) => NativeLibrary.TryLoad(libraryPath, out handle); - - /// - [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] - bool TryGetNativeLibraryExport(IntPtr handle, string name, out IntPtr address) => - NativeLibrary.TryGetExport(handle, name, out address); - - /// - [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] - void FreeNativeLibrary(IntPtr handle) => NativeLibrary.Free(handle); - - /// - [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] - Delegate NativeLibraryFunctionToDelegate(IntPtr ptr, Type t) => Marshal.GetDelegateForFunctionPointer(ptr, t); + public static readonly IDotNetSystemAdapter Instance = new DotNetSystemAdapter(); /// - /// Checks if the operating system is Linux. + /// Checks if the operating system is Linux. /// /// true if the operating system is Linux; false otherwise. [SupportedOSPlatformGuard("linux"), ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] bool IsLinux => OperatingSystem.IsLinux(); /// - /// Checks if the operating system is FreeBSD. + /// Checks if the operating system is FreeBSD. /// /// true if the operating system is FreeBSD; false otherwise. [SupportedOSPlatformGuard("freebsd"), ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] bool IsFreeBsd => OperatingSystem.IsFreeBSD(); /// - /// Checks if the operating system is MacOS. + /// Checks if the operating system is MacOS. /// /// true if the operating system is MacOS; false otherwise. [SupportedOSPlatformGuard("macos"), ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] bool IsMacOs => OperatingSystem.IsMacOS(); /// - /// Checks if the operating system is Unix-like. + /// Checks if the operating system is Unix-like. /// /// true if the operating system is Unix-like; false otherwise. [SupportedOSPlatformGuard("linux"), SupportedOSPlatformGuard("freebsd"), SupportedOSPlatformGuard("macos"), ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] bool IsUnixLike => IsLinux || IsFreeBsd || IsMacOs; + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + bool TryLoadNativeLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, + out IntPtr handle) => + NativeLibrary.TryLoad(libraryName, assembly, searchPath, out handle); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + bool TryLoadNativeLibrary(string libraryPath, out IntPtr handle) => NativeLibrary.TryLoad(libraryPath, out handle); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + bool TryGetNativeLibraryExport(IntPtr handle, string name, out IntPtr address) => + NativeLibrary.TryGetExport(handle, name, out address); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + void FreeNativeLibrary(IntPtr handle) => NativeLibrary.Free(handle); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + Delegate NativeLibraryFunctionToDelegate(IntPtr ptr, Type t) => Marshal.GetDelegateForFunctionPointer(ptr, t); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + string? NativeLibraryUnicodeStrPtrToString(IntPtr ptr) => Marshal.PtrToStringUni(ptr); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + string? NativeLibraryAnsiStrPtrToString(IntPtr ptr) => Marshal.PtrToStringAnsi(ptr); + /// - /// Sets the console title. + /// Sets the console title. /// /// [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] void SetConsoleTitle(string title) => Console.Title = title; /// - /// Writes to out and flushes immediately. + /// Writes to out and flushes immediately. /// /// What to write. [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] @@ -88,9 +89,39 @@ void OutAndFlush(string what) Console.Out.Flush(); } - /// + /// [SupportedOSPlatform("linux"), SupportedOSPlatform("freebsd"), SupportedOSPlatform("macos"), ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] IDisposable MonitorTerminalResizeSignal(Action action) => PosixSignalRegistration.Create(PosixSignal.SIGWINCH, _ => { action(); }); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + string? GetEnvironmentVariable(string name) => Environment.GetEnvironmentVariable(name); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + bool DirectoryExists(string name) => Directory.Exists(name); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + string? GetDirectoryName(string path) => Path.GetDirectoryName(path); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + string CombinePaths(params string[] paths) => Path.Combine(paths); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + IEnumerable EnumerateDirectories(string directory) => Directory.EnumerateDirectories(directory); + + /// + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop method.")] + IEnumerable EnumerateFiles(string directory) => Directory.EnumerateFiles(directory); + + [ExcludeFromCodeCoverage(Justification = ".NET runtime interop implementation.")] + private sealed class DotNetSystemAdapter: IDotNetSystemAdapter + { + } } + diff --git a/Sharpie/Abstractions/IDrawSurface.cs b/Sharpie/Abstractions/IDrawSurface.cs index 8144bd7..a88e9fe 100644 --- a/Sharpie/Abstractions/IDrawSurface.cs +++ b/Sharpie/Abstractions/IDrawSurface.cs @@ -5,6 +5,11 @@ namespace Sharpie.Abstractions; /// public interface IDrawSurface { + /// + /// The total size of the draw surface. + /// + public Size Size { get; } + /// /// Draws a at a using the given style. /// @@ -12,9 +17,5 @@ public interface IDrawSurface /// The rune to draw. /// The cell style. void DrawCell(Point location, Rune rune, Style style); - - /// - /// The total size of the draw surface. - /// - public Size Size { get; } } + diff --git a/Sharpie/Abstractions/IDrawable.cs b/Sharpie/Abstractions/IDrawable.cs index 630c6b9..d99a896 100644 --- a/Sharpie/Abstractions/IDrawable.cs +++ b/Sharpie/Abstractions/IDrawable.cs @@ -20,3 +20,4 @@ public interface IDrawable /// Thrown if is null. void DrawOnto(IDrawSurface destination, Rectangle srcArea, Point destLocation); } + diff --git a/Sharpie/Abstractions/IEventPump.cs b/Sharpie/Abstractions/IEventPump.cs index fe2f4ff..134e136 100644 --- a/Sharpie/Abstractions/IEventPump.cs +++ b/Sharpie/Abstractions/IEventPump.cs @@ -86,3 +86,4 @@ public interface IEventPump /// This operation is not thread safe. bool Uses(ResolveEscapeSequenceFunc resolver); } + diff --git a/Sharpie/Abstractions/IInterval.cs b/Sharpie/Abstractions/IInterval.cs index b008338..9519891 100644 --- a/Sharpie/Abstractions/IInterval.cs +++ b/Sharpie/Abstractions/IInterval.cs @@ -11,3 +11,4 @@ public interface IInterval: IDisposable /// void Stop() => Dispose(); } + diff --git a/Sharpie/Abstractions/INativeSymbolResolver.cs b/Sharpie/Abstractions/INativeSymbolResolver.cs index dfb987d..b8f1b86 100644 --- a/Sharpie/Abstractions/INativeSymbolResolver.cs +++ b/Sharpie/Abstractions/INativeSymbolResolver.cs @@ -1,19 +1,18 @@ namespace Sharpie.Abstractions; -using System.Reflection; - /// -/// Interface implemented by objects that can provide native symbols (such as function) to the callers. -/// The existing implementation is class. +/// Interface implemented by objects that can provide native symbols (such as function) to the callers. +/// The existing implementation is class. /// [PublicAPI] internal interface INativeSymbolResolver { /// - /// Resolves a given function based on its delegate type. + /// Resolves a given function based on its delegate type. /// /// The type of the function. /// The resolved function. /// Thrown if the given function could not be resolved. TDelegate Resolve() where TDelegate: MulticastDelegate; } + diff --git a/Sharpie/Backend/CursesBackend.cs b/Sharpie/Backend/CursesBackend.cs index e8c5361..74e6575 100644 --- a/Sharpie/Backend/CursesBackend.cs +++ b/Sharpie/Backend/CursesBackend.cs @@ -1,18 +1,27 @@ namespace Sharpie.Backend; +using System.Text.RegularExpressions; + /// -/// Provides functionality for obtaining instances. +/// Provides functionality for obtaining instances. /// [PublicAPI] public static class CursesBackend { + private const string NCursesPrefix = "ncurses"; + private const string LibCPrefix = "libc"; + /// - /// Internal method that loads the Curses backend from native libraries (and any other support library that is required). + /// Internal method that loads the Curses backend from native libraries (and any other support library that is + /// required). /// /// Adapter for .NET functionality. /// Function that provides paths/names for the native loader. /// - /// Thrown if or are null. + /// + /// Thrown if or + /// are null. + /// /// Thrown if no suitable library was found. internal static ICursesBackend NCurses(IDotNetSystemAdapter dotNetSystemAdapter, Func> libPathResolver) @@ -20,7 +29,7 @@ internal static ICursesBackend NCurses(IDotNetSystemAdapter dotNetSystemAdapter, Debug.Assert(dotNetSystemAdapter != null); Debug.Assert(libPathResolver != null); - var cw = NativeLibraryWrapper.TryLoad(dotNetSystemAdapter, libPathResolver("ncurses")); + var cw = NativeLibraryWrapper.TryLoad(dotNetSystemAdapter, libPathResolver(NCursesPrefix)); if (cw == null) { throw new CursesInitializationException(); @@ -28,7 +37,7 @@ internal static ICursesBackend NCurses(IDotNetSystemAdapter dotNetSystemAdapter, if (dotNetSystemAdapter.IsUnixLike) { - var lw = NativeLibraryWrapper.TryLoad(dotNetSystemAdapter, libPathResolver("libc")); + var lw = NativeLibraryWrapper.TryLoad(dotNetSystemAdapter, libPathResolver(LibCPrefix)); if (lw == null) { throw new CursesInitializationException(); @@ -41,11 +50,11 @@ internal static ICursesBackend NCurses(IDotNetSystemAdapter dotNetSystemAdapter, } /// - /// Loads the Curses backend from native libraries (and any other support library that is required). + /// Loads the Curses backend from native libraries (and any other support library that is required). /// /// Function that provides paths/names for the native loader. /// - /// Thrown if is null. + /// Thrown if is null. /// Thrown if no suitable library was found. [ExcludeFromCodeCoverage(Justification = "References a singleton .NET object and cannot be tested.")] public static ICursesBackend NCurses(Func> libPathResolver) @@ -57,44 +66,107 @@ public static ICursesBackend NCurses(Func> libPathRe return NCurses(IDotNetSystemAdapter.Instance, libPathResolver); } - /// - /// Internal method that loads the Curses backend from native libraries (and any other support library that is required). - /// This method uses standard known names for the 'ncurses' and potentially 'libc' libraries. + /// Internal method that loads the Curses backend from native libraries (and any other support library that is + /// required). + /// This method uses standard known names for the 'ncurses' and potentially 'libc' libraries. /// /// Adapter for .NET functionality. /// /// Thrown if no suitable library was found. internal static ICursesBackend NCurses(IDotNetSystemAdapter dotNetSystemAdapter) { - return NCurses(dotNetSystemAdapter, s => + return NCurses(dotNetSystemAdapter, lib => { - if ((dotNetSystemAdapter.IsLinux || dotNetSystemAdapter.IsFreeBsd) && s == "ncurses") + return lib switch { - return new[] - { - "ncursesw", - "libncursesw.so", - "libncursesw.so.5", - "libncursesw.so.6", - "ncurses", - "libncurses.so", - "libncurses.so.5", - "libncurses.so.6", - }; - } - - return new[] { s }; + NCursesPrefix when dotNetSystemAdapter.IsLinux || dotNetSystemAdapter.IsFreeBsd => + FindLinuxAndFreeBsdNCursesCandidates(), + NCursesPrefix when dotNetSystemAdapter.IsMacOs => FindMacOsNCursesCandidates(dotNetSystemAdapter), + var _ => new[] { lib } + }; }); } /// - /// Loads the Curses backend from native libraries (and any other support library that is required). - /// This method uses standard known names for the required libraries. + /// Loads the Curses backend from native libraries (and any other support library that is required). + /// This method uses standard known names for the required libraries. /// /// /// Thrown if no suitable library was found. [ExcludeFromCodeCoverage(Justification = "References a singleton .NET object and cannot be tested.")] public static ICursesBackend NCurses() => NCurses(IDotNetSystemAdapter.Instance); + + private static IEnumerable<(string name, int version)> GetCandidatesInDirectory( + IDotNetSystemAdapter dotNetSystemAdapter, string directory, Regex pattern) + { + Debug.Assert(dotNetSystemAdapter != null); + Debug.Assert(pattern != null); + Debug.Assert(!string.IsNullOrEmpty(directory)); + + return dotNetSystemAdapter.EnumerateFiles(directory) + .Select(f => pattern.Match(f)) + .Where(m => m.Success) + .Select(m => (name: dotNetSystemAdapter.CombinePaths(directory, m.Groups[0] + .Value), version: int.Parse(m.Groups[1] + .Value))); + } + + [SupportedOSPlatform("linux"), SupportedOSPlatform("freebsd")] + private static IEnumerable FindLinuxAndFreeBsdNCursesCandidates() + { + return new[] + { + "libncursesw.so.6", + "libncursesw.so.5", + "libncursesw.so", + "ncursesw", + "libncurses.so.6", + "libncurses.so.5", + "libncurses.so", + NCursesPrefix + }; + } + + [SupportedOSPlatform("macos")] + private static IEnumerable FindMacOsNCursesCandidates(IDotNetSystemAdapter dotNetSystemAdapter) + { + Debug.Assert(dotNetSystemAdapter != null); + + var homeBrewPrefix = dotNetSystemAdapter.GetEnvironmentVariable("HOMEBREW_PREFIX"); + var homeBrewCellar = dotNetSystemAdapter.GetEnvironmentVariable("HOMEBREW_CELLAR"); + + var candidates = new List<(string name, int version)>(); + var matchRegEx = new Regex(@"libncurses\.(\d+)\.dylib", RegexOptions.Compiled); + + if (!string.IsNullOrEmpty(homeBrewPrefix)) + { + var libPath = dotNetSystemAdapter.CombinePaths(homeBrewPrefix, "lib"); + if (dotNetSystemAdapter.DirectoryExists(libPath)) + { + candidates.AddRange(GetCandidatesInDirectory(dotNetSystemAdapter, libPath, matchRegEx)); + } + } + + if (!string.IsNullOrEmpty(homeBrewCellar)) + { + var ncursesPath = dotNetSystemAdapter.CombinePaths(homeBrewCellar, NCursesPrefix); + if (dotNetSystemAdapter.DirectoryExists(ncursesPath)) + { + foreach (var v in dotNetSystemAdapter.EnumerateDirectories(ncursesPath)) + { + var libPath = dotNetSystemAdapter.CombinePaths(v, "lib"); + if (dotNetSystemAdapter.DirectoryExists(libPath)) + { + candidates.AddRange(GetCandidatesInDirectory(dotNetSystemAdapter, libPath, matchRegEx)); + } + } + } + } + + return candidates.OrderByDescending(c => c.version) + .Select(c => c.name) + .Concat(new[] { NCursesPrefix }); + } } diff --git a/Sharpie/Backend/CursesComplexChar.cs b/Sharpie/Backend/CursesComplexChar.cs index 4421626..8c924b9 100644 --- a/Sharpie/Backend/CursesComplexChar.cs +++ b/Sharpie/Backend/CursesComplexChar.cs @@ -38,21 +38,10 @@ namespace Sharpie.Backend; SuppressMessage("ReSharper", "PrivateFieldCanBeConvertedToLocalVariable")] public struct CursesComplexChar { - internal CursesComplexChar(uint attrAndColorPair, uint char0, uint char1, uint char2, - uint char3, uint char4) - { - _attrAndColorPair = attrAndColorPair; - _char0 = char0; - _char1 = char1; - _char2 = char2; - _char3 = char3; - _char4 = char4; - } - - [MarshalAs(UnmanagedType.U4)] private readonly uint _attrAndColorPair; - [MarshalAs(UnmanagedType.U4)] private readonly uint _char0; - [MarshalAs(UnmanagedType.U4)] private readonly uint _char1; - [MarshalAs(UnmanagedType.U4)] private readonly uint _char2; - [MarshalAs(UnmanagedType.U4)] private readonly uint _char3; - [MarshalAs(UnmanagedType.U4)] private readonly uint _char4; + [MarshalAs(UnmanagedType.U4)] internal uint _attrAndColorPair; + [MarshalAs(UnmanagedType.U4)] internal uint _char0; + [MarshalAs(UnmanagedType.U4)] internal uint _char1; + [MarshalAs(UnmanagedType.U4)] internal uint _char2; + [MarshalAs(UnmanagedType.U4)] internal uint _char3; + [MarshalAs(UnmanagedType.U4)] internal uint _char4; } diff --git a/Sharpie/Backend/CursesMouseEvent.cs b/Sharpie/Backend/CursesMouseEvent.cs index c841bdf..e414ea1 100644 --- a/Sharpie/Backend/CursesMouseEvent.cs +++ b/Sharpie/Backend/CursesMouseEvent.cs @@ -32,89 +32,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE namespace Sharpie.Backend; /// -/// Internal Curses mouse event. +/// Curses mouse event - encapsulates the data sent by the backend related to mouse events. /// [PublicAPI, StructLayout(LayoutKind.Sequential), ExcludeFromCodeCoverage] public struct CursesMouseEvent { - private const int CursesMouseShift = 6; - - [PublicAPI] - public enum Button - { - None = 0, - Button1 = 1, - Button2 = 2, - Button3 = 3, - Button4 = 4, - Modifiers = 5 - } - - [PublicAPI, Flags] - public enum Action - { - ButtonReleased = 1, - ButtonPressed = ButtonReleased << 1, - ButtonClicked = ButtonPressed << 1, - DoubleClicked = ButtonClicked << 1, - TripleClicked = DoubleClicked << 1, - Reserved = TripleClicked << 1 - } - - [Flags, PublicAPI] - public enum EventType - { - Button1Released = Action.ButtonReleased << ((Button.Button1 - 1) * CursesMouseShift), - - Button1Pressed = Action.ButtonPressed << ((Button.Button1 - 1) * CursesMouseShift), - - Button1Clicked = Action.ButtonClicked << ((Button.Button1 - 1) * CursesMouseShift), - - Button1DoubleClicked = Action.DoubleClicked << ((Button.Button1 - 1) * CursesMouseShift), - - Button1TripleClicked = Action.TripleClicked << ((Button.Button1 - 1) * CursesMouseShift), - - Button2Released = Action.ButtonReleased << ((Button.Button2 - 1) * CursesMouseShift), - - Button2Pressed = Action.ButtonPressed << ((Button.Button2 - 1) * CursesMouseShift), - - Button2Clicked = Action.ButtonClicked << ((Button.Button2 - 1) * CursesMouseShift), - - Button2DoubleClicked = Action.DoubleClicked << ((Button.Button2 - 1) * CursesMouseShift), - - Button2TripleClicked = Action.TripleClicked << ((Button.Button2 - 1) * CursesMouseShift), - - Button3Released = Action.ButtonReleased << ((Button.Button3 - 1) * CursesMouseShift), - - Button3Pressed = Action.ButtonPressed << ((Button.Button3 - 1) * CursesMouseShift), - - Button3Clicked = Action.ButtonClicked << ((Button.Button3 - 1) * CursesMouseShift), - - Button3DoubleClicked = Action.DoubleClicked << ((Button.Button3 - 1) * CursesMouseShift), - - Button3TripleClicked = Action.TripleClicked << ((Button.Button3 - 1) * CursesMouseShift), - - Button4Released = Action.ButtonReleased << ((Button.Button4 - 1) * CursesMouseShift), - - Button4Pressed = Action.ButtonPressed << ((Button.Button4 - 1) * CursesMouseShift), - - Button4Clicked = Action.ButtonClicked << ((Button.Button4 - 1) * CursesMouseShift), - - Button4DoubleClicked = Action.DoubleClicked << ((Button.Button4 - 1) * CursesMouseShift), - - Button4TripleClicked = Action.TripleClicked << ((Button.Button4 - 1) * CursesMouseShift), - - Ctrl = 1 << ((Button.Modifiers - 1) * CursesMouseShift), - Shift = 2 << ((Button.Modifiers - 1) * CursesMouseShift), - Alt = 4 << ((Button.Modifiers - 1) * CursesMouseShift), - - ReportPosition = 8 << ((Button.Modifiers - 1) * CursesMouseShift), - All = ReportPosition - 1 - } - - [MarshalAs(UnmanagedType.I2)] public short id; - [MarshalAs(UnmanagedType.I4)] public int x; - [MarshalAs(UnmanagedType.I4)] public int y; - [MarshalAs(UnmanagedType.I4)] public int z; - [MarshalAs(UnmanagedType.U4)] public uint buttonState; + [MarshalAs(UnmanagedType.I2)] internal short id; + [MarshalAs(UnmanagedType.I4)] internal int x; + [MarshalAs(UnmanagedType.I4)] internal int y; + [MarshalAs(UnmanagedType.I4)] internal int z; + [MarshalAs(UnmanagedType.U4)] internal uint buttonState; } diff --git a/Sharpie/Backend/CursesMouseEventParser.cs b/Sharpie/Backend/CursesMouseEventParser.cs new file mode 100644 index 0000000..41d0f65 --- /dev/null +++ b/Sharpie/Backend/CursesMouseEventParser.cs @@ -0,0 +1,243 @@ +/* +Copyright (c) 2022, Alexandru Ciobanu +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma warning disable CS1591 +namespace Sharpie.Backend; + +/// +/// Provides functionality for parsing Curses mouse events. +/// +internal abstract class CursesMouseEventParser +{ + private static readonly CursesMouseEventParser V1 = new CursesMouseV1EventParser(); + private static readonly CursesMouseEventParser V2 = new CursesMouseV2EventParser(); + + [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] + protected CursesMouseEventParser() + { + Button1Released = CalculateButtonState(Action.Released, Button.Button1); + Button1Pressed = CalculateButtonState(Action.Pressed, Button.Button1); + Button1Clicked = CalculateButtonState(Action.Clicked, Button.Button1); + Button1DoubleClicked = CalculateButtonState(Action.DoubleClicked, Button.Button1); + Button1TripleClicked = CalculateButtonState(Action.TripleClicked, Button.Button1); + + Button2Released = CalculateButtonState(Action.Released, Button.Button2); + Button2Pressed = CalculateButtonState(Action.Pressed, Button.Button2); + Button2Clicked = CalculateButtonState(Action.Clicked, Button.Button2); + Button2DoubleClicked = CalculateButtonState(Action.DoubleClicked, Button.Button2); + Button2TripleClicked = CalculateButtonState(Action.TripleClicked, Button.Button2); + + Button3Released = CalculateButtonState(Action.Released, Button.Button3); + Button3Pressed = CalculateButtonState(Action.Pressed, Button.Button3); + Button3Clicked = CalculateButtonState(Action.Clicked, Button.Button3); + Button3DoubleClicked = CalculateButtonState(Action.DoubleClicked, Button.Button3); + Button3TripleClicked = CalculateButtonState(Action.TripleClicked, Button.Button3); + + Button4Released = CalculateButtonState(Action.Released, Button.Button4); + Button4Pressed = CalculateButtonState(Action.Pressed, Button.Button4); + Button4Clicked = CalculateButtonState(Action.Clicked, Button.Button4); + Button4DoubleClicked = CalculateButtonState(Action.DoubleClicked, Button.Button4); + Button4TripleClicked = CalculateButtonState(Action.TripleClicked, Button.Button4); + + Button5Released = CalculateButtonState(Action.Released, Button.Button5); + Button5Pressed = CalculateButtonState(Action.Pressed, Button.Button5); + Button5Clicked = CalculateButtonState(Action.Clicked, Button.Button5); + Button5DoubleClicked = CalculateButtonState(Action.DoubleClicked, Button.Button5); + Button5TripleClicked = CalculateButtonState(Action.TripleClicked, Button.Button5); + + Ctrl = CalculateModifierState(Modifier.Ctrl); + Shift = CalculateModifierState(Modifier.Shift); + Alt = CalculateModifierState(Modifier.Alt); + ReportPosition = CalculateModifierState(Modifier.ReportPosition); + + All = ReportPosition - 1; + } + + private uint Button1Released { get; } + private uint Button1Pressed { get; } + private uint Button1Clicked { get; } + private uint Button1DoubleClicked { get; } + private uint Button1TripleClicked { get; } + private uint Button2Released { get; } + private uint Button2Pressed { get; } + private uint Button2Clicked { get; } + private uint Button2DoubleClicked { get; } + private uint Button2TripleClicked { get; } + private uint Button3Released { get; } + private uint Button3Pressed { get; } + private uint Button3Clicked { get; } + private uint Button3DoubleClicked { get; } + private uint Button3TripleClicked { get; } + private uint Button4Released { get; } + private uint Button4Pressed { get; } + private uint Button4Clicked { get; } + private uint Button4DoubleClicked { get; } + private uint Button4TripleClicked { get; } + private uint Button5Released { get; } + private uint Button5Pressed { get; } + private uint Button5Clicked { get; } + private uint Button5DoubleClicked { get; } + private uint Button5TripleClicked { get; } + private uint Ctrl { get; } + private uint Shift { get; } + private uint Alt { get; } + public uint ReportPosition { get; } + public uint All { get; } + + protected abstract uint CalculateButtonState(Action action, Button button); + protected abstract uint CalculateModifierState(Modifier modifier); + + /// + /// Converts a Curses mouse action into proper format. + /// + /// The raw Curses mouse event flags. + /// The mouse action attributes. + public (MouseButton button, MouseButtonState state, ModifierKey modifierKey)? Parse(uint flags) + { + var modifierKey = ModifierKey.None; + var button = (MouseButton) 0; + var state = (MouseButtonState) 0; + + bool Has(uint flag) => (flags & flag) == flag; + + void MapMod(uint flag, ModifierKey mod) + { + if (Has(flag)) + { + modifierKey |= mod; + } + } + + bool MapButton(uint flag, MouseButton b, MouseButtonState s) + { + var h = Has(flag); + if (h) + { + button = b; + state = s; + } + + return h; + } + + MapMod(Alt, ModifierKey.Alt); + MapMod(Ctrl, ModifierKey.Ctrl); + MapMod(Shift, ModifierKey.Shift); + + if (MapButton(Button1Released, MouseButton.Button1, MouseButtonState.Released) || + MapButton(Button1Pressed, MouseButton.Button1, MouseButtonState.Pressed) || + MapButton(Button1Clicked, MouseButton.Button1, MouseButtonState.Clicked) || + MapButton(Button1DoubleClicked, MouseButton.Button1, MouseButtonState.DoubleClicked) || + MapButton(Button1TripleClicked, MouseButton.Button1, MouseButtonState.TripleClicked) || + MapButton(Button2Released, MouseButton.Button2, MouseButtonState.Released) || + MapButton(Button2Pressed, MouseButton.Button2, MouseButtonState.Pressed) || + MapButton(Button2Clicked, MouseButton.Button2, MouseButtonState.Clicked) || + MapButton(Button2DoubleClicked, MouseButton.Button2, MouseButtonState.DoubleClicked) || + MapButton(Button2TripleClicked, MouseButton.Button2, MouseButtonState.TripleClicked) || + MapButton(Button3Released, MouseButton.Button3, MouseButtonState.Released) || + MapButton(Button3Pressed, MouseButton.Button3, MouseButtonState.Pressed) || + MapButton(Button3Clicked, MouseButton.Button3, MouseButtonState.Clicked) || + MapButton(Button3DoubleClicked, MouseButton.Button3, MouseButtonState.DoubleClicked) || + MapButton(Button3TripleClicked, MouseButton.Button3, MouseButtonState.TripleClicked) || + MapButton(Button4Released, MouseButton.Button4, MouseButtonState.Released) || + MapButton(Button4Pressed, MouseButton.Button4, MouseButtonState.Pressed) || + MapButton(Button4Clicked, MouseButton.Button4, MouseButtonState.Clicked) || + MapButton(Button4DoubleClicked, MouseButton.Button4, MouseButtonState.DoubleClicked) || + MapButton(Button4TripleClicked, MouseButton.Button4, MouseButtonState.TripleClicked) || + MapButton(Button5Released, MouseButton.Button5, MouseButtonState.Released) || + MapButton(Button5Pressed, MouseButton.Button5, MouseButtonState.Pressed) || + MapButton(Button5Clicked, MouseButton.Button5, MouseButtonState.Clicked) || + MapButton(Button5DoubleClicked, MouseButton.Button5, MouseButtonState.DoubleClicked) || + MapButton(Button5TripleClicked, MouseButton.Button5, MouseButtonState.TripleClicked)) + { + return (button, state, modifierKey); + } + + return null; + } + + /// + /// Gets the mouse event parser based on the provided ABI version. + /// + /// The ABI version. + /// The mouse event parser. + public static CursesMouseEventParser Get(int abiVersion) => + abiVersion switch + { + 2 => V2, + var _ => V1 + }; + + private sealed class CursesMouseV1EventParser: CursesMouseEventParser + { + protected override uint CalculateButtonState(Action action, Button button) => + button switch + { + Button.Button5 => 1u << 31, + var _ => (uint) action << (((int) button - 1) * 6) + }; + + protected override uint CalculateModifierState(Modifier modifier) => (uint) modifier << 24; + } + + private sealed class CursesMouseV2EventParser: CursesMouseEventParser + { + protected override uint CalculateButtonState(Action action, Button button) => + (uint) action << (((int) button - 1) * 5); + + protected override uint CalculateModifierState(Modifier modifier) => (uint) modifier << 25; + } + + protected enum Action + { + Released = 1, + Pressed = Released << 1, + Clicked = Pressed << 1, + DoubleClicked = Clicked << 1, + TripleClicked = DoubleClicked << 1 + } + + protected enum Modifier + { + Ctrl = 1, + Shift = Ctrl << 1, + Alt = Shift << 1, + ReportPosition = Alt << 1 + } + + protected enum Button + { + Button1 = 1, + Button2 = 2, + Button3 = 3, + Button4 = 4, + Button5 = 5 + } +} diff --git a/Sharpie/Backend/LibCFunctionMap.cs b/Sharpie/Backend/LibCFunctionMap.cs index 7043a2c..b4e30ea 100644 --- a/Sharpie/Backend/LibCFunctionMap.cs +++ b/Sharpie/Backend/LibCFunctionMap.cs @@ -31,7 +31,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE namespace Sharpie.Backend; /// -/// Function map for LibC library. +/// Function map for LibC library. /// [SuppressMessage("ReSharper", "IdentifierTypo"), SuppressMessage("ReSharper", "InconsistentNaming")] internal abstract class LibCFunctionMap diff --git a/Sharpie/Backend/NCursesBackend.cs b/Sharpie/Backend/NCursesBackend.cs index 8766046..8bf904d 100644 --- a/Sharpie/Backend/NCursesBackend.cs +++ b/Sharpie/Backend/NCursesBackend.cs @@ -2,11 +2,13 @@ namespace Sharpie.Backend; +using System.Text.RegularExpressions; + [PublicAPI] internal class NCursesBackend: ICursesBackend { - public IDotNetSystemAdapter DotNetSystemAdapter { get; } private readonly INativeSymbolResolver _nCursesSymbolResolver; + private int? _mouseAbiVersion; internal NCursesBackend(IDotNetSystemAdapter dotNetSystemAdapter, INativeSymbolResolver nCursesSymbolResolver) { @@ -17,6 +19,8 @@ internal NCursesBackend(IDotNetSystemAdapter dotNetSystemAdapter, INativeSymbolR _nCursesSymbolResolver = nCursesSymbolResolver; } + public IDotNetSystemAdapter DotNetSystemAdapter { get; } + // ReSharper disable IdentifierTypo // ReSharper disable InconsistentNaming @@ -34,7 +38,8 @@ internal NCursesBackend(IDotNetSystemAdapter dotNetSystemAdapter, INativeSymbolR public bool is_nodelay(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); - public bool is_notimeout(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); + public bool is_notimeout(IntPtr window) => + _nCursesSymbolResolver.Resolve()(window); public bool is_scrollok(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); @@ -96,7 +101,7 @@ public IntPtr derwin(IntPtr window, int lines, int cols, int beginLine, public int flushinp() => _nCursesSymbolResolver.Resolve()(); - public int getattrs(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); + public int getattrs(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); public int getcurx(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); @@ -114,7 +119,8 @@ public IntPtr derwin(IntPtr window, int lines, int cols, int beginLine, public int getpary(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); - public int halfdelay(int tenthsOfSec) => _nCursesSymbolResolver.Resolve()(tenthsOfSec); + public int halfdelay(int tenthsOfSec) => + _nCursesSymbolResolver.Resolve()(tenthsOfSec); public bool has_colors() => _nCursesSymbolResolver.Resolve()(); @@ -125,7 +131,8 @@ public IntPtr derwin(IntPtr window, int lines, int cols, int beginLine, public void idcok(IntPtr window, bool set) => _nCursesSymbolResolver.Resolve()(window, set); - public int idlok(IntPtr window, bool set) => _nCursesSymbolResolver.Resolve()(window, set); + public int idlok(IntPtr window, bool set) => + _nCursesSymbolResolver.Resolve()(window, set); public void immedok(IntPtr window, bool set) => _nCursesSymbolResolver.Resolve()(window, set); @@ -150,7 +157,8 @@ public bool is_wintouched(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); public string? keyname(uint keyCode) => - Marshal.PtrToStringAnsi(_nCursesSymbolResolver.Resolve()(keyCode)); + DotNetSystemAdapter.NativeLibraryAnsiStrPtrToString( + _nCursesSymbolResolver.Resolve()(keyCode)); public int keypad(IntPtr window, bool set) => _nCursesSymbolResolver.Resolve()(window, set); @@ -159,7 +167,8 @@ public int leaveok(IntPtr window, bool set) => _nCursesSymbolResolver.Resolve()(window, set); public string? longname() => - Marshal.PtrToStringAnsi(_nCursesSymbolResolver.Resolve()()); + DotNetSystemAdapter.NativeLibraryAnsiStrPtrToString( + _nCursesSymbolResolver.Resolve()()); public int meta(IntPtr window, bool set) => _nCursesSymbolResolver.Resolve()(window, set); @@ -262,7 +271,8 @@ public int slk_attr_set(uint attrs, short colorPair, IntPtr reserved) => public int slk_init(int format) => _nCursesSymbolResolver.Resolve()(format); public string? slk_label(int labelIndex) => - Marshal.PtrToStringAnsi(_nCursesSymbolResolver.Resolve()(labelIndex)); + DotNetSystemAdapter.NativeLibraryAnsiStrPtrToString( + _nCursesSymbolResolver.Resolve()(labelIndex)); public int slk_noutrefresh() => _nCursesSymbolResolver.Resolve()(); @@ -286,7 +296,8 @@ public int syncok(IntPtr window, bool set) => _nCursesSymbolResolver.Resolve()(window, set); public string? termname() => - Marshal.PtrToStringAnsi(_nCursesSymbolResolver.Resolve()()); + DotNetSystemAdapter.NativeLibraryAnsiStrPtrToString( + _nCursesSymbolResolver.Resolve()()); public int ungetch(uint @char) => _nCursesSymbolResolver.Resolve()(@char); @@ -346,7 +357,7 @@ public int wechochar(IntPtr window, uint charAndAttrs) => public int wgetch(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); - public int wgetnstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder dest, int length) => + public int wgetnstr(IntPtr window, StringBuilder dest, int length) => _nCursesSymbolResolver.Resolve()(window, dest, length); public int whline(IntPtr window, uint @char, int count) => @@ -354,7 +365,7 @@ public int whline(IntPtr window, uint @char, int count) => public int winch(IntPtr window) => _nCursesSymbolResolver.Resolve()(window); - public int winchnstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder dest, int length) => + public int winchnstr(IntPtr window, StringBuilder dest, int length) => _nCursesSymbolResolver.Resolve()(window, dest, length); public int winsch(IntPtr window, uint charAndAttrs) => @@ -402,10 +413,12 @@ public int resizeterm(int lines, int cols) => _nCursesSymbolResolver.Resolve()(lines, cols); public string? keybound(uint keyCode, int count) => - Marshal.PtrToStringAnsi(_nCursesSymbolResolver.Resolve()(keyCode, count)); + DotNetSystemAdapter.NativeLibraryAnsiStrPtrToString( + _nCursesSymbolResolver.Resolve()(keyCode, count)); public string? curses_version() => - Marshal.PtrToStringAnsi(_nCursesSymbolResolver.Resolve()()); + DotNetSystemAdapter.NativeLibraryAnsiStrPtrToString(_nCursesSymbolResolver + .Resolve()()); public int assume_default_colors(int fgColor, int bgColor) => _nCursesSymbolResolver.Resolve()(fgColor, bgColor); @@ -432,7 +445,8 @@ public int getcchar(CursesComplexChar @char, StringBuilder dest, out uint attrs, reserved); public string? key_name(uint @char) => - Marshal.PtrToStringAnsi(_nCursesSymbolResolver.Resolve()(@char)); + DotNetSystemAdapter.NativeLibraryAnsiStrPtrToString( + _nCursesSymbolResolver.Resolve()(@char)); public int killwchar(out uint @char) => _nCursesSymbolResolver.Resolve()(out @char); @@ -456,7 +470,7 @@ public int wadd_wch(IntPtr window, CursesComplexChar @char) => public int wadd_wchnstr(IntPtr window, CursesComplexChar[] str, int count) => _nCursesSymbolResolver.Resolve()(window, str, count); - public int waddnwstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] string text, int length) => + public int waddnwstr(IntPtr window, string text, int length) => _nCursesSymbolResolver.Resolve()(window, text, length); public int wbkgrnd(IntPtr window, CursesComplexChar @char) => @@ -503,7 +517,8 @@ public int wins_wch(IntPtr window, CursesComplexChar @char) => _nCursesSymbolResolver.Resolve()(window, ref @char); public string? wunctrl(CursesComplexChar @char) => - Marshal.PtrToStringUni(_nCursesSymbolResolver.Resolve()(ref @char)); + DotNetSystemAdapter.NativeLibraryUnicodeStrPtrToString( + _nCursesSymbolResolver.Resolve()(ref @char)); public int wvline_set(IntPtr window, CursesComplexChar @char, int count) => _nCursesSymbolResolver.Resolve()(window, ref @char, count); @@ -514,9 +529,39 @@ public int getmouse(out CursesMouseEvent @event) => public int ungetmouse(CursesMouseEvent @event) => _nCursesSymbolResolver.Resolve()(ref @event); - public virtual int mousemask(int newMask, out int oldMask) => + public virtual int mousemask(uint newMask, out uint oldMask) => _nCursesSymbolResolver.Resolve()(newMask, out oldMask); + public virtual int mouse_version() + { + if (_mouseAbiVersion == null) + { + var ver = curses_version(); + var abi = -1; + if (ver != null) + { + var versionParser = new Regex(@".*(\d+)\.(\d+)\.(\d+)"); + var match = versionParser.Match(ver); + if (match.Success) + { + var major = int.Parse(match.Groups[1] + .Value); + + abi = major switch + { + >= 6 => 2, + 5 => 1, + var _ => abi + }; + } + } + + _mouseAbiVersion = abi; + } + + return _mouseAbiVersion.Value; + } + public bool wenclose(IntPtr window, int line, int col) => _nCursesSymbolResolver.Resolve()(window, line, col); diff --git a/Sharpie/Backend/NCursesFunctionMap.cs b/Sharpie/Backend/NCursesFunctionMap.cs index 61ecead..e9203de 100644 --- a/Sharpie/Backend/NCursesFunctionMap.cs +++ b/Sharpie/Backend/NCursesFunctionMap.cs @@ -31,11 +31,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE namespace Sharpie.Backend; /// -/// Function map for NCurses library. +/// Function map for NCurses library. /// -[SuppressMessage("ReSharper", "IdentifierTypo"),SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "IdentifierTypo"), SuppressMessage("ReSharper", "InconsistentNaming")] internal abstract class NCursesFunctionMap { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int assume_default_colors(int fgColor, int bgColor); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int baudrate(); @@ -54,6 +57,9 @@ internal abstract class NCursesFunctionMap [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int color_content(short color, out short red, out short green, out short blue); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate uint COLOR_PAIR(uint attrs); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int copywin(IntPtr fromWindow, IntPtr toWindow, int srcMinLine, int srcMinCol, int destMinLine, int destMinCol, int destMaxLine, int destMaxCol, @@ -62,12 +68,18 @@ public delegate int copywin(IntPtr fromWindow, IntPtr toWindow, int srcMinLine, [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int curs_set(int level); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate IntPtr curses_version(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int def_prog_mode(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int def_shell_mode(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int define_key(string keyName, int keyCode); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int delay_output(int delayMillis); @@ -90,6 +102,9 @@ public delegate IntPtr derwin(IntPtr window, int lines, int cols, int beginLine, [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int endwin(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int erasewchar(out uint @char); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void filter(); @@ -100,388 +115,367 @@ public delegate IntPtr derwin(IntPtr window, int lines, int cols, int beginLine, public delegate int flushinp(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int halfdelay(int tenthsOfSec); + public delegate int getattrs(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool has_colors(); + public delegate int getbegx(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool has_ic(); + public delegate int getbegy(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool has_il(); + public delegate int getcchar(ref CursesComplexChar @char, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder dest, + out uint attrs, out short colorPair, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void idcok(IntPtr window, bool set); + public delegate int getcurx(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int idlok(IntPtr window, bool set); + public delegate int getcury(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void immedok(IntPtr window, bool set); + public delegate int getmaxx(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr initscr(); + public delegate int getmaxy(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int init_color(short color, short red, short green, short blue); + public delegate int getmouse(out CursesMouseEvent @event); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int init_pair(short colorPair, short fgColor, short bgColor); + public delegate int getparx(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int intrflush(IntPtr window, bool set); + public delegate int getpary(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool isendwin(); + public delegate int halfdelay(int tenthsOfSec); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_linetouched(IntPtr window, int line); + public delegate bool has_colors(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_wintouched(IntPtr window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate IntPtr keyname(uint keyCode); + public delegate bool has_ic(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int keypad(IntPtr window, bool set); + public delegate bool has_il(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int leaveok(IntPtr window, bool set); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate IntPtr longname(); + public delegate void idcok(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int meta(IntPtr window, bool set); + public delegate int idlok(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int mvderwin(IntPtr window, int parentLine, int parentCol); + public delegate void immedok(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int mvwin(IntPtr window, int toLine, int toCol); + public delegate int init_color(short color, short red, short green, short blue); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr newpad(int lines, int cols); + public delegate int init_pair(short colorPair, short fgColor, short bgColor); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr newwin(int lines, int cols, int atLine, int atCol); + public delegate IntPtr initscr(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int nl(); + public delegate int intrflush(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int nocbreak(); + public delegate bool is_cleared(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int nodelay(IntPtr window, bool set); + public delegate bool is_idcok(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int noecho(); + public delegate bool is_idlok(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int nonl(); + public delegate bool is_immedok(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void noqiflush(); + public delegate bool is_keypad(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int noraw(); + public delegate bool is_leaveok(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int notimeout(IntPtr window, bool set); + public delegate bool is_linetouched(IntPtr window, int line); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int overlay(IntPtr srcWindow, IntPtr destWindow); + public delegate bool is_nodelay(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int overwrite(IntPtr srcWindow, IntPtr destWindow); + public delegate bool is_notimeout(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int pair_content(short colorPair, out short fgColor, out short bgColor); + public delegate bool is_scrollok(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate uint COLOR_PAIR(uint attrs); + public delegate bool is_syncok(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate uint PAIR_NUMBER(uint colorPair); + public delegate bool is_term_resized(int lines, int cols); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int pechochar(IntPtr pad, uint charAndAttrs); + public delegate bool is_wintouched(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int pnoutrefresh(IntPtr pad, int padMinLine, int padMinCol, int scrMinLine, - int scrMinCol, int scrMaxLine, int scrMaxCol); + public delegate bool isendwin(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int prefresh(IntPtr pad, int padMinLine, int padMinCol, int scrMinLine, - int scrMinCol, int scrMaxLine, int scrMaxCol); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate int key_defined(string keyName); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void qiflush(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate IntPtr key_name(uint @char); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int raw(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate IntPtr keybound(uint keyCode, int count); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int resetty(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate IntPtr keyname(uint keyCode); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int reset_prog_mode(); + public delegate int keyok(int keyCode, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int reset_shell_mode(); + public delegate int keypad(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int ripoffline(int lines, ICursesBackend.ripoffline_callback callback); + public delegate int killwchar(out uint @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int savetty(); + public delegate int leaveok(IntPtr window, bool set); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int scrollok(IntPtr window, bool set); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate IntPtr longname(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_attroff(uint attrs); + public delegate int meta(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_attron(uint attrs); + public delegate int mouseinterval(int millis); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_attrset(uint attrs); + public delegate int mousemask(uint newMask, out uint oldMask); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_attr(); + public delegate int mvderwin(IntPtr window, int parentLine, int parentCol); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_attr_set(uint attrs, short colorPair, IntPtr reserved); + public delegate int mvwin(IntPtr window, int toLine, int toCol); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_clear(); + public delegate IntPtr newpad(int lines, int cols); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_color(short colorPair); + public delegate IntPtr newwin(int lines, int cols, int atLine, int atCol); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_init(int format); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate IntPtr slk_label(int labelIndex); + public delegate int nl(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_noutrefresh(); + public delegate int nocbreak(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_refresh(); + public delegate int nodelay(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_restore(); + public delegate int noecho(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_touch(); + public delegate void nofilter(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int start_color(); + public delegate int nonl(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr subpad(IntPtr pad, int lines, int cols, int atRow, - int atCol); + public delegate void noqiflush(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr subwin(IntPtr window, int lines, int cols, int atLine, - int atCol); + public delegate int noraw(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int syncok(IntPtr window, bool set); + public delegate int notimeout(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr termname(); + public delegate int overlay(IntPtr srcWindow, IntPtr destWindow); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int ungetch(uint @char); + public delegate int overwrite(IntPtr srcWindow, IntPtr destWindow); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void use_env(bool set); + public delegate int pair_content(short colorPair, out short fgColor, out short bgColor); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int waddch(IntPtr window, uint charAndAttrs); + public delegate uint PAIR_NUMBER(uint colorPair); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int waddchnstr(IntPtr window, uint[] charsAndAttrs, int length); + public delegate int pecho_wchar(IntPtr window, ref CursesComplexChar @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wattr_on(IntPtr window, uint attrs, IntPtr reserved); + public delegate int pechochar(IntPtr pad, uint charAndAttrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wattr_off(IntPtr window, uint attrs, IntPtr reserved); + public delegate int pnoutrefresh(IntPtr pad, int padMinLine, int padMinCol, int scrMinLine, + int scrMinCol, int scrMaxLine, int scrMaxCol); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wbkgd(IntPtr window, uint charAndAttrs); + public delegate int prefresh(IntPtr pad, int padMinLine, int padMinCol, int scrMinLine, + int scrMinCol, int scrMaxLine, int scrMaxCol); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void wbkgdset(IntPtr window, uint charAndAttrs); + public delegate void qiflush(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wborder(IntPtr window, uint leftSide, uint rightSide, uint topSide, - uint bottomSide, uint topLeftCorner, uint topRightCorner, uint bottomLeftCorner, - uint bottomRightCorner); + public delegate int raw(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wchgat(IntPtr window, int count, uint attrs, short colorPair, - IntPtr reserved); + public delegate int reset_prog_mode(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wclear(IntPtr window); + public delegate int reset_shell_mode(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wclrtobot(IntPtr window); + public delegate int resetty(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wclrtoeol(IntPtr window); + public delegate int resize_term(int lines, int cols); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wcolor_set(IntPtr window, short colorPair, IntPtr reserved); + public delegate int resizeterm(int lines, int cols); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void wcursyncup(IntPtr window); + public delegate int ripoffline(int lines, ICursesBackend.ripoffline_callback callback); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wdelch(IntPtr window); + public delegate int savetty(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wechochar(IntPtr window, uint charAndAttrs); + public delegate int scrollok(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int werase(IntPtr window); + public delegate int set_tabsize(int size); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wgetch(IntPtr window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate int wgetnstr(IntPtr window, StringBuilder dest, int length); + public delegate int setcchar(out CursesComplexChar @char, [MarshalAs(UnmanagedType.LPWStr)] string text, uint attrs, + short colorPair, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int whline(IntPtr window, uint @char, int count); + public delegate int slk_attr(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int winch(IntPtr window); + public delegate int slk_attr_off(uint attrs, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int winchnstr(IntPtr window, StringBuilder dest, int length); + public delegate int slk_attr_on(uint attrs, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int winsch(IntPtr window, uint charAndAttrs); + public delegate int slk_attr_set(uint attrs, short colorPair, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int winsdelln(IntPtr window, int count); + public delegate int slk_attroff(uint attrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wmove(IntPtr window, int newLine, int newCol); + public delegate int slk_attron(uint attrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wnoutrefresh(IntPtr window); + public delegate int slk_attrset(uint attrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wredrawln(IntPtr window, int startLine, int lineCount); + public delegate int slk_clear(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wrefresh(IntPtr window); + public delegate int slk_color(short colorPair); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wscrl(IntPtr window, int count); + public delegate int slk_init(int format); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wsetscrreg(IntPtr window, int top, int bottom); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate IntPtr slk_label(int labelIndex); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void wsyncdown(IntPtr window); + public delegate int slk_noutrefresh(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void wsyncup(IntPtr window); + public delegate int slk_refresh(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void wtimeout(IntPtr window, int delay); + public delegate int slk_restore(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wtouchln(IntPtr window, int line, int count, int changed); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate int slk_set(int labelIndex, string title, int fmt); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wvline(IntPtr window, uint @char, int count); + public delegate int slk_touch(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_term_resized(int lines, int cols); + public delegate int start_color(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int resize_term(int lines, int cols); + public delegate IntPtr subpad(IntPtr pad, int lines, int cols, int atRow, + int atCol); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int resizeterm(int lines, int cols); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate IntPtr keybound(uint keyCode, int count); + public delegate IntPtr subwin(IntPtr window, int lines, int cols, int atLine, + int atCol); - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate IntPtr curses_version(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int syncok(IntPtr window, bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int assume_default_colors(int fgColor, int bgColor); + public delegate int term_attrs(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int define_key(string keyName, int keyCode); + public delegate IntPtr termname(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate int key_defined(string keyName); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int unget_wch(uint @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int keyok(int keyCode, bool set); + public delegate int ungetch(uint @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int set_tabsize(int size); + public delegate int ungetmouse(ref CursesMouseEvent @event); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int use_default_colors(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wresize(IntPtr window, int lines, int columns); + public delegate void use_env(bool set); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void nofilter(); + public delegate int wadd_wch(IntPtr window, ref CursesComplexChar @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getcchar(ref CursesComplexChar @char, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder dest, - out uint attrs, out short colorPair, IntPtr reserved); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate IntPtr key_name(uint @char); + public delegate int wadd_wchnstr(IntPtr window, CursesComplexChar[] @char, int count); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int killwchar(out uint @char); + public delegate int waddch(IntPtr window, uint charAndAttrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int pecho_wchar(IntPtr window, ref CursesComplexChar @char); + public delegate int waddchnstr(IntPtr window, uint[] charsAndAttrs, int length); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int setcchar(out CursesComplexChar @char, [MarshalAs(UnmanagedType.LPWStr)] string text, uint attrs, - short colorPair, IntPtr reserved); + public delegate int waddnwstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] string text, int length); - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public delegate int slk_set(int labelIndex, string title, int fmt); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int wattr_get(IntPtr window, out uint attrs, out short colorPair, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int term_attrs(); + public delegate int wattr_off(IntPtr window, uint attrs, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int unget_wch(uint @char); + public delegate int wattr_on(IntPtr window, uint attrs, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wadd_wch(IntPtr window, ref CursesComplexChar @char); + public delegate int wattr_set(IntPtr window, uint attrs, short colorPair, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wadd_wchnstr(IntPtr window, CursesComplexChar[] @char, int count); + public delegate int wbkgd(IntPtr window, uint charAndAttrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int waddnwstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] string text, int length); + public delegate void wbkgdset(IntPtr window, uint charAndAttrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int wbkgrnd(IntPtr window, ref CursesComplexChar @char); @@ -489,6 +483,11 @@ public delegate int setcchar(out CursesComplexChar @char, [MarshalAs(UnmanagedTy [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void wbkgrndset(IntPtr window, ref CursesComplexChar @char); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int wborder(IntPtr window, uint leftSide, uint rightSide, uint topSide, + uint bottomSide, uint topLeftCorner, uint topRightCorner, uint bottomLeftCorner, + uint bottomRightCorner); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int wborder_set(IntPtr window, ref CursesComplexChar leftSide, ref CursesComplexChar rightSide, ref CursesComplexChar topSide, ref CursesComplexChar bottomSide, ref CursesComplexChar topLeftCorner, @@ -496,134 +495,135 @@ public delegate int wborder_set(IntPtr window, ref CursesComplexChar leftSide, r ref CursesComplexChar bottomRightCorner); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wecho_wchar(IntPtr window, ref CursesComplexChar @char); + public delegate int wchgat(IntPtr window, int count, uint attrs, short colorPair, + IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wget_wch(IntPtr window, out uint dest); + public delegate int wclear(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wgetbkgrnd(IntPtr window, out CursesComplexChar @char); + public delegate int wclrtobot(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wgetn_wstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder dest, int length); + public delegate int wclrtoeol(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int whline_set(IntPtr window, ref CursesComplexChar @char, int count); + public delegate int wcolor_set(IntPtr window, short colorPair, IntPtr reserved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int win_wch(IntPtr window, out CursesComplexChar @char); + public delegate void wcursyncup(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int win_wchnstr(IntPtr window, CursesComplexChar[] @char, int length); + public delegate int wdelch(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int winnwstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder text, int length); + public delegate int wecho_wchar(IntPtr window, ref CursesComplexChar @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wins_nwstr(IntPtr window, string text, int length); + public delegate int wechochar(IntPtr window, uint charAndAttrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wins_wch(IntPtr window, ref CursesComplexChar @char); + public delegate bool wenclose(IntPtr window, int line, int col); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr wunctrl(ref CursesComplexChar @char); + public delegate int werase(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wvline_set(IntPtr window, ref CursesComplexChar @char, int count); + public delegate int wget_wch(IntPtr window, out uint dest); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int erasewchar(out uint @char); + public delegate int wgetbkgrnd(IntPtr window, out CursesComplexChar @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getattrs(IntPtr window); + public delegate int wgetch(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getcurx(IntPtr window); + public delegate int wgetn_wstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder dest, int length); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getcury(IntPtr window); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate int wgetnstr(IntPtr window, StringBuilder dest, int length); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getbegx(IntPtr window); + public delegate IntPtr wgetparent(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getbegy(IntPtr window); + public delegate int wgetscrreg(IntPtr window, out int top, out int bottom); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getmaxx(IntPtr window); + public delegate int whline(IntPtr window, uint @char, int count); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getmaxy(IntPtr window); + public delegate int whline_set(IntPtr window, ref CursesComplexChar @char, int count); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getparx(IntPtr window); + public delegate int win_wch(IntPtr window, out CursesComplexChar @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getpary(IntPtr window); + public delegate int win_wchnstr(IntPtr window, CursesComplexChar[] @char, int length); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_attr_off(uint attrs, IntPtr reserved); + public delegate int winch(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int slk_attr_on(uint attrs, IntPtr reserved); + public delegate int winchnstr(IntPtr window, StringBuilder dest, int length); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wattr_get(IntPtr window, out uint attrs, out short colorPair, IntPtr reserved); + public delegate int winnwstr(IntPtr window, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder text, int length); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wattr_set(IntPtr window, uint attrs, short colorPair, IntPtr reserved); + public delegate int wins_nwstr(IntPtr window, string text, int length); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_cleared(IntPtr window); + public delegate int wins_wch(IntPtr window, ref CursesComplexChar @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_idcok(IntPtr window); + public delegate int winsch(IntPtr window, uint charAndAttrs); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_idlok(IntPtr window); + public delegate int winsdelln(IntPtr window, int count); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_immedok(IntPtr window); + public delegate bool wmouse_trafo(IntPtr window, ref int line, ref int col, bool toScreen); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_keypad(IntPtr window); + public delegate int wmove(IntPtr window, int newLine, int newCol); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_leaveok(IntPtr window); + public delegate int wnoutrefresh(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_nodelay(IntPtr window); + public delegate int wredrawln(IntPtr window, int startLine, int lineCount); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_notimeout(IntPtr window); + public delegate int wrefresh(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_scrollok(IntPtr window); + public delegate int wresize(IntPtr window, int lines, int columns); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool is_syncok(IntPtr window); + public delegate int wscrl(IntPtr window, int count); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr wgetparent(IntPtr window); + public delegate int wsetscrreg(IntPtr window, int top, int bottom); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int wgetscrreg(IntPtr window, out int top, out int bottom); + public delegate void wsyncdown(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int getmouse(out CursesMouseEvent @event); + public delegate void wsyncup(IntPtr window); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int ungetmouse(ref CursesMouseEvent @event); + public delegate void wtimeout(IntPtr window, int delay); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int mousemask(int newMask, out int oldMask); + public delegate int wtouchln(IntPtr window, int line, int count, int changed); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool wenclose(IntPtr window, int line, int col); + public delegate IntPtr wunctrl(ref CursesComplexChar @char); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int mouseinterval(int millis); + public delegate int wvline(IntPtr window, uint @char, int count); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool wmouse_trafo(IntPtr window, ref int line, ref int col, bool toScreen); + public delegate int wvline_set(IntPtr window, ref CursesComplexChar @char, int count); } diff --git a/Sharpie/Backend/NativeLibraryWrapper.cs b/Sharpie/Backend/NativeLibraryWrapper.cs index de4aa2c..dae88dd 100644 --- a/Sharpie/Backend/NativeLibraryWrapper.cs +++ b/Sharpie/Backend/NativeLibraryWrapper.cs @@ -97,8 +97,9 @@ public TDelegate Resolve() where TDelegate: MulticastDelegate { Debug.Assert(dotNetSystemAdapter != null); - if (string.IsNullOrEmpty(Path.GetDirectoryName(libraryNameOrPath)) && - dotNetSystemAdapter.TryLoadNativeLibrary(libraryNameOrPath, Assembly.GetCallingAssembly(), null, out var libHandle) || + if (string.IsNullOrEmpty(dotNetSystemAdapter.GetDirectoryName(libraryNameOrPath)) && + dotNetSystemAdapter.TryLoadNativeLibrary(libraryNameOrPath, Assembly.GetCallingAssembly(), null, + out var libHandle) || dotNetSystemAdapter.TryLoadNativeLibrary(libraryNameOrPath, out libHandle)) { return new(dotNetSystemAdapter, libHandle); diff --git a/Sharpie/Backend/UnixNCursesBackend.cs b/Sharpie/Backend/UnixNCursesBackend.cs index 120d744..1d8ba0e 100644 --- a/Sharpie/Backend/UnixNCursesBackend.cs +++ b/Sharpie/Backend/UnixNCursesBackend.cs @@ -31,7 +31,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE namespace Sharpie.Backend; /// -/// NCurses-backend with unix-specific extensions. +/// NCurses-backend with unix-specific extensions. /// [SupportedOSPlatform("linux"), SupportedOSPlatform("macos"), SupportedOSPlatform("freebsd")] internal sealed class UnixNCursesBackend: NCursesBackend @@ -39,35 +39,35 @@ internal sealed class UnixNCursesBackend: NCursesBackend private readonly INativeSymbolResolver _libCSymbolResolver; /// - /// Creates a new instance of this class. + /// Creates a new instance of this class. /// /// The .NET system adapter. /// The NCurses symbol resolver. /// The LibC symbol resolver. - public UnixNCursesBackend(IDotNetSystemAdapter dotNetSystemAdapter, - INativeSymbolResolver nCursesSymbolResolver, - INativeSymbolResolver libCSymbolResolver): - base(dotNetSystemAdapter, nCursesSymbolResolver) + public UnixNCursesBackend(IDotNetSystemAdapter dotNetSystemAdapter, INativeSymbolResolver nCursesSymbolResolver, + INativeSymbolResolver libCSymbolResolver): base(dotNetSystemAdapter, nCursesSymbolResolver) { Debug.Assert(dotNetSystemAdapter.IsUnixLike); Debug.Assert(libCSymbolResolver != null); _libCSymbolResolver = libCSymbolResolver; } - + // ReSharper disable IdentifierTypo // ReSharper disable InconsistentNaming - - public override int mousemask(int newMask, out int oldMask) + + public override int mousemask(uint newMask, out uint oldMask) { var result = base.mousemask(newMask, out oldMask); if (!result.Failed()) { + var parser = CursesMouseEventParser.Get(mouse_version()); + var csi = "\x1b[?1003l"; - if ((newMask & (int) CursesMouseEvent.EventType.ReportPosition) != 0) + if ((newMask & parser.ReportPosition) != 0) { csi = "\x1b[?1003h"; - } else if ((newMask & (int) CursesMouseEvent.EventType.All) != 0) + } else if ((newMask & parser.All) != 0) { csi = "\x1b[?1000h"; } @@ -90,7 +90,7 @@ public override bool monitor_pending_resize(Action action, [NotNullWhen(true)] o handle = DotNetSystemAdapter.MonitorTerminalResizeSignal(action); return true; } - + // ReSharper restore InconsistentNaming // ReSharper restore IdentifierTypo } diff --git a/Sharpie/Canvas.cs b/Sharpie/Canvas.cs index 80b9aa5..a6c4017 100644 --- a/Sharpie/Canvas.cs +++ b/Sharpie/Canvas.cs @@ -1,7 +1,8 @@ namespace Sharpie; /// -/// A general-purpose drawing surface that can be latter draw onto any object that implements . +/// A general-purpose drawing surface that can be latter draw onto any object that implements +/// . /// Supports multiple types of drawing operations most commonly used in terminal apps. /// [PublicAPI] @@ -557,9 +558,6 @@ public Canvas(Size size) _cells = new Cell[size.Width, size.Height]; } - /// - void IDrawSurface.DrawCell(Point location, Rune rune, Style style) { SetCell(location.X, location.Y, rune, style); } - /// public Size Size { get; } @@ -595,6 +593,9 @@ public void DrawOnto(IDrawSurface destination, Rectangle srcArea, Point destLoca } } + /// + void IDrawSurface.DrawCell(Point location, Rune rune, Style style) { SetCell(location.X, location.Y, rune, style); } + private bool InArea(int x, int y) => x >= 0 && x < Size.Width && y >= 0 && y < Size.Height; private void SetCell(int x, int y, Rune rune, Style style) @@ -835,7 +836,8 @@ public void Glyph(Point location, GradientGlyphStyle gradientGlyphStyle, int fil } /// - /// Draws a line starting at a given starting at a given point vertically or horizontally using line drawing characters. + /// Draws a line starting at a given starting at a given point vertically or horizontally using line drawing + /// characters. /// /// The start location. /// The line orientation. @@ -920,7 +922,7 @@ public void Line(PointF location, float length, Orientation orientation, LineSty } /// - /// Draws a line between two points in the drawing using block characters. + /// Draws a line between two points in the drawing using block characters. /// /// The starting cell. /// The ending cell. diff --git a/Sharpie/EventPump.cs b/Sharpie/EventPump.cs index 92ef477..3cffca5 100644 --- a/Sharpie/EventPump.cs +++ b/Sharpie/EventPump.cs @@ -10,6 +10,7 @@ namespace Sharpie; public sealed class EventPump: IEventPump { private readonly IList _keySequenceResolvers = new List(); + private CursesMouseEventParser _cursesMouseEventParser; private ConcurrentQueue _delegatedObjects = new(); private MouseEventResolver? _mouseEventResolver; @@ -21,7 +22,11 @@ public sealed class EventPump: IEventPump /// Thrown if is null. /// /// This method is not thread-safe. - internal EventPump(Terminal parent) => Terminal = parent ?? throw new ArgumentNullException(nameof(parent)); + internal EventPump(Terminal parent) + { + Terminal = parent ?? throw new ArgumentNullException(nameof(parent)); + _cursesMouseEventParser = CursesMouseEventParser.Get(Terminal.Curses.mouse_version()); + } /// public Terminal Terminal { get; } @@ -275,17 +280,13 @@ private IEnumerable Listen(IntPtr handle, CancellationToken cancellationT return null; } - if (mouseEvent.buttonState == (uint) CursesMouseEvent.EventType.ReportPosition) - { - return new MouseMoveEvent(new(mouseEvent.x, mouseEvent.y)); - } - var (button, state, mouseMod) = - Helpers.ConvertMouseActionEvent((CursesMouseEvent.EventType) mouseEvent.buttonState); + var parsed = _cursesMouseEventParser.Parse(mouseEvent.buttonState); - return button == 0 - ? null - : new MouseActionEvent(new(mouseEvent.x, mouseEvent.y), button, state, mouseMod); + return parsed == null + ? new MouseMoveEvent(new(mouseEvent.x, mouseEvent.y)) + : new MouseActionEvent(new(mouseEvent.x, mouseEvent.y), parsed.Value.button, parsed.Value.state, + parsed.Value.modifierKey); default: var (key, keyMod) = Helpers.ConvertKeyPressEvent(keyCode); return new KeyEvent(key, new(ControlCharacter.Null), Terminal.Curses.key_name(keyCode), keyMod); diff --git a/Sharpie/Helpers.cs b/Sharpie/Helpers.cs index bae1544..09ad99f 100644 --- a/Sharpie/Helpers.cs +++ b/Sharpie/Helpers.cs @@ -278,118 +278,6 @@ internal static (Key key, ModifierKey modifierKey) ConvertKeyPressEvent(uint key }; } - /// - /// Converts a Curses mouse action into proper format. - /// - /// The Curses mouse event type. - /// The mouse action attributes. - internal static (MouseButton button, MouseButtonState state, ModifierKey modifierKey) ConvertMouseActionEvent( - CursesMouseEvent.EventType type) - { - var modifierKey = ModifierKey.None; - var button = (MouseButton) 0; - var state = (MouseButtonState) 0; - - if (type.HasFlag(CursesMouseEvent.EventType.Alt)) - { - modifierKey |= ModifierKey.Alt; - } - - if (type.HasFlag(CursesMouseEvent.EventType.Ctrl)) - { - modifierKey |= ModifierKey.Ctrl; - } - - if (type.HasFlag(CursesMouseEvent.EventType.Shift)) - { - modifierKey |= ModifierKey.Shift; - } - - if (type.HasFlag(CursesMouseEvent.EventType.Button1Released)) - { - button = MouseButton.Button1; - state = MouseButtonState.Released; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button1Pressed)) - { - button = MouseButton.Button1; - state = MouseButtonState.Pressed; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button1Clicked)) - { - button = MouseButton.Button1; - state = MouseButtonState.Clicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button1DoubleClicked)) - { - button = MouseButton.Button1; - state = MouseButtonState.DoubleClicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button1TripleClicked)) - { - button = MouseButton.Button1; - state = MouseButtonState.TripleClicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button2Released)) - { - button = MouseButton.Button2; - state = MouseButtonState.Released; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button2Pressed)) - { - button = MouseButton.Button2; - state = MouseButtonState.Pressed; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button2Clicked)) - { - button = MouseButton.Button2; - state = MouseButtonState.Clicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button2DoubleClicked)) - { - button = MouseButton.Button2; - state = MouseButtonState.DoubleClicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button2TripleClicked)) - { - button = MouseButton.Button2; - state = MouseButtonState.TripleClicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button3Released)) - { - button = MouseButton.Button3; - state = MouseButtonState.Released; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button3Pressed)) - { - button = MouseButton.Button3; - state = MouseButtonState.Pressed; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button3Clicked)) - { - button = MouseButton.Button3; - state = MouseButtonState.Clicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button3DoubleClicked)) - { - button = MouseButton.Button3; - state = MouseButtonState.DoubleClicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button3TripleClicked)) - { - button = MouseButton.Button3; - state = MouseButtonState.TripleClicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button4Released)) - { - button = MouseButton.Button4; - state = MouseButtonState.Released; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button4Pressed)) - { - button = MouseButton.Button4; - state = MouseButtonState.Pressed; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button4Clicked)) - { - button = MouseButton.Button4; - state = MouseButtonState.Clicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button4DoubleClicked)) - { - button = MouseButton.Button4; - state = MouseButtonState.DoubleClicked; - } else if (type.HasFlag(CursesMouseEvent.EventType.Button4TripleClicked)) - { - button = MouseButton.Button4; - state = MouseButtonState.TripleClicked; - } - - return (button, state, modifierKey); - } - /// /// Enumerates a given interval using 1/2s. /// @@ -455,7 +343,7 @@ internal static (int start, int count) IntersectSegments(int seg1Start, int seg1 } /// - /// Draws a line between two points in the drawing using block characters. + /// Draws a line between two points in the drawing using block characters. /// /// The starting point. /// The ending point. @@ -509,7 +397,7 @@ internal static void TraceLineInHalves(PointF start, PointF end, Action } /// - /// Refreshes a surface using terminal's facility. + /// Refreshes a surface using terminal's facility. /// /// The terminal. /// The surface. @@ -530,7 +418,7 @@ internal static void Refresh(this Terminal t, ISurface s) } /// - /// Intersects the given with the total area of a surface and returns the intersection. + /// Intersects the given with the total area of a surface and returns the intersection. /// /// The size of the destination surface. /// The desired area. @@ -539,7 +427,7 @@ internal static bool AdjustToActualArea(this Size size, ref Rectangle area) => new Rectangle(new(0, 0), size).AdjustToActualArea(ref area); /// - /// Intersects the given with the total area of a surface and returns the intersection. + /// Intersects the given with the total area of a surface and returns the intersection. /// /// The size of the destination surface. /// The desired area. @@ -553,7 +441,7 @@ internal static bool AdjustToActualArea(this Rectangle total, ref Rectangle area } /// - /// Intersects the given with the total area of a surface and returns the intersection. + /// Intersects the given with the total area of a surface and returns the intersection. /// /// The size of the destination surface. /// The desired area. diff --git a/Sharpie/Sharpie.csproj b/Sharpie/Sharpie.csproj index d6334c0..edc52c1 100644 --- a/Sharpie/Sharpie.csproj +++ b/Sharpie/Sharpie.csproj @@ -25,14 +25,14 @@ - - + + - - - + + + @@ -44,7 +44,7 @@ - + diff --git a/Sharpie/Terminal.cs b/Sharpie/Terminal.cs index c5ecf2f..2a7b281 100644 --- a/Sharpie/Terminal.cs +++ b/Sharpie/Terminal.cs @@ -50,7 +50,7 @@ public sealed class Terminal: ITerminal, IDisposable private TerminalSurface? _header; private int? _initialCaretMode; private int? _initialMouseClickDelay; - private int? _initialMouseMask; + private uint? _initialMouseMask; private ManualResetEventSlim? _runCompletedEvent; private Screen _screen; private SoftLabelKeyManager _softLabelKeyManager; @@ -72,7 +72,7 @@ public Terminal(ICursesBackend curses, TerminalOptions options) if (options.MouseClickInterval is < 0) { - throw new ArgumentOutOfRangeException(nameof(options.MouseClickInterval)); + throw new ArgumentOutOfRangeException(nameof(options)); } if (_terminalInstanceActive) @@ -187,8 +187,9 @@ public Terminal(ICursesBackend curses, TerminalOptions options) .Check(nameof(Curses.mouseinterval), "Failed to set the mouse click interval."); - Curses.mousemask((int) CursesMouseEvent.EventType.ReportPosition | (int) CursesMouseEvent.EventType.All, - out var initialMouseMask) + var parser = CursesMouseEventParser.Get(Curses.mouse_version()); + + Curses.mousemask(parser.ReportPosition | parser.All, out var initialMouseMask) .Check(nameof(Curses.mousemask), "Failed to enable the mouse."); _eventPump.UseInternalMouseEventResolver = Options.MouseClickInterval == null; @@ -351,7 +352,7 @@ public void Dispose() /// public VideoAttribute SupportedAttributes => - (VideoAttribute) Curses.term_attrs() + (VideoAttribute) Curses.term_attrs() .Check(nameof(Curses.term_attrs), "Failed to get the terminal attributes"); ///