diff --git a/src/XTerm.NET.Tests/Esc.cs b/src/XTerm.NET.Tests/Esc.cs
index 0cc6cf34..be503816 100644
--- a/src/XTerm.NET.Tests/Esc.cs
+++ b/src/XTerm.NET.Tests/Esc.cs
@@ -24,6 +24,11 @@ public static class Esc
public const string ClearScreen = "\u001b[2J";
public const string ClearLine = "\u001b[2K";
+ // mouse reporting (DECSET)
+ public const string EnableMouseTracking = "\u001b[?1000h";
+ public const string EnableSgrMouse = "\u001b[?1006h";
+ public const string EnableSgrPixelsMouse = "\u001b[?1016h";
+
/// A control sequence: ESC [ followed by the body.
public static string Csi(string body)
{
@@ -43,6 +48,19 @@ public static string SetCursorPosition(int x, int y)
return $"\u001b[{y + 1};{x + 1}H";
}
+ ///
+ /// An SGR mouse report (1006, or 1016 in pixels), taking a zero-based cell or pixel.
+ ///
+ ///
+ /// One-based on the wire like CUP, and converted here for the same reason. The terminator
+ /// is the only thing telling a press from a release, so it comes from a flag rather than a
+ /// hand-typed M or m.
+ ///
+ public static string SgrMouseReport(int button, int x, int y, bool press = true)
+ {
+ return $"\u001b[<{button};{x + 1};{y + 1}{(press ? 'M' : 'm')}";
+ }
+
///
/// An application programming command, which is how the Kitty graphics protocol arrives.
///
diff --git a/src/XTerm.NET.Tests/MouseTrackingTests.cs b/src/XTerm.NET.Tests/MouseTrackingTests.cs
index 71a721c6..069863bf 100644
--- a/src/XTerm.NET.Tests/MouseTrackingTests.cs
+++ b/src/XTerm.NET.Tests/MouseTrackingTests.cs
@@ -296,6 +296,48 @@ public void MouseEvent_SGRFormat_Release_UsesLowercaseM()
#endregion
+ #region SGR-Pixels Format
+
+ [Fact]
+ public void MouseEvent_SGRPixels_ReportsOneBasedPixelPosition()
+ {
+ var terminal = CreateTerminal();
+ terminal.Write(Esc.EnableMouseTracking + Esc.EnableSgrPixelsMouse);
+
+ var down = terminal.GenerateMouseEvent(MouseButton.Left, 5, 10, 57, 213, MouseEventType.Down);
+ var up = terminal.GenerateMouseEvent(MouseButton.Left, 5, 10, 57, 213, MouseEventType.Up);
+
+ Assert.Equal(MouseEncoding.SGRPixels, terminal.MouseEncoding);
+ Assert.Equal(Esc.SgrMouseReport(0, 57, 213), down);
+ Assert.Equal(Esc.SgrMouseReport(0, 57, 213, press: false), up);
+ }
+
+ [Fact]
+ public void MouseEvent_SGRPixels_FromCellOnlyHost_ReportsCellCorner()
+ {
+ // A host that never learned the pixel overload still answers in pixels, at the clicked
+ // cell's top-left, so dividing by the CSI 16 t cell size lands back on that cell.
+ var terminal = new Terminal(new TerminalOptions { Cols = 80, Rows = 24, CellWidthPixels = 9, CellHeightPixels = 18 });
+ terminal.Write(Esc.EnableMouseTracking + Esc.EnableSgrPixelsMouse);
+
+ var sequence = terminal.GenerateMouseEvent(MouseButton.Left, 5, 10, MouseEventType.Down);
+
+ Assert.Equal(Esc.SgrMouseReport(0, 5 * 9, 10 * 18), sequence);
+ }
+
+ [Fact]
+ public void MouseEvent_PixelOverload_UnderSGR_StillReportsCells()
+ {
+ var terminal = CreateTerminal();
+ terminal.Write(Esc.EnableMouseTracking + Esc.EnableSgrMouse);
+
+ var sequence = terminal.GenerateMouseEvent(MouseButton.Left, 5, 10, 57, 213, MouseEventType.Down);
+
+ Assert.Equal(Esc.SgrMouseReport(0, 5, 10), sequence);
+ }
+
+ #endregion
+
#region Modifiers
[Fact]
diff --git a/src/XTerm.NET.Tests/RequestModeTests.cs b/src/XTerm.NET.Tests/RequestModeTests.cs
index e2280a6d..e67b7be1 100644
--- a/src/XTerm.NET.Tests/RequestModeTests.cs
+++ b/src/XTerm.NET.Tests/RequestModeTests.cs
@@ -107,6 +107,7 @@ public void Reports_each_mode_as_set_after_setting_it_and_reset_after_resetting_
[InlineData(6)] // origin
[InlineData(1000)] // VT200 mouse
[InlineData(1006)] // SGR mouse encoding
+ [InlineData(1016)] // SGR-Pixels mouse encoding
[InlineData(1049)] // alternate buffer
[InlineData(2004)] // bracketed paste
[InlineData(2026)] // synchronized output
@@ -201,6 +202,30 @@ public void Reports_only_the_mouse_encoding_currently_selected()
replies);
}
+ ///
+ /// SGR-Pixels is one more choice in that same selection. An application probing for it with
+ /// DECRQM before switching has to hear "supported" -- a 0 reply sends it back to cell reports --
+ /// and once it is on, 1006 is not, because the reports no longer carry cells.
+ ///
+ [Fact]
+ public void Reports_sgr_pixels_as_a_mouse_encoding_selection()
+ {
+ var terminal = Fresh();
+
+ terminal.Write(Set(1016));
+ var replies = Replies(terminal);
+ terminal.Write(Query(1016));
+ terminal.Write(Query(1006));
+
+ Assert.Equal(new[] { Report(1016, true), Report(1006, false) }, replies);
+
+ replies.Clear();
+ terminal.Write(Reset(1016));
+ terminal.Write(Query(1016));
+
+ Assert.Equal(new[] { Report(1016, false) }, replies);
+ }
+
///
/// The three alternate-buffer modes differ only in the cursor and erase work they do on the way
/// in and out; there is one buffer, so they read alike. An application that entered with 1049
@@ -239,7 +264,6 @@ public void Reports_the_alternate_buffer_alike_for_all_three_modes_that_switch_i
[Theory]
[InlineData(1035)] // NumLock modifiers
[InlineData(1001)] // highlight mouse tracking, not implemented
- [InlineData(1016)] // pixel-position mouse, not implemented
[InlineData(64738)] // not a mode at all
public void Reports_not_recognised_for_modes_it_keeps_no_state_for(int mode)
{
diff --git a/src/XTerm.NET/Input/MouseInput.cs b/src/XTerm.NET/Input/MouseInput.cs
index ec117915..ff91b71d 100644
--- a/src/XTerm.NET/Input/MouseInput.cs
+++ b/src/XTerm.NET/Input/MouseInput.cs
@@ -47,4 +47,5 @@ public enum MouseEncoding
Utf8, // UTF-8 encoding (CSI ? 1005 h)
SGR, // SGR format (CSI ? 1006 h) - recommended
URXVT, // URXVT format (CSI ? 1015 h)
+ SGRPixels, // SGR format with pixel coordinates (CSI ? 1016 h)
}
diff --git a/src/XTerm.NET/Input/MouseTracker.cs b/src/XTerm.NET/Input/MouseTracker.cs
index f8f2adb9..7e0c095b 100644
--- a/src/XTerm.NET/Input/MouseTracker.cs
+++ b/src/XTerm.NET/Input/MouseTracker.cs
@@ -27,6 +27,25 @@ public MouseTracker(Terminal terminal)
/// Generates a mouse event sequence.
///
public string GenerateMouseEvent(MouseButton button, int x, int y, MouseEventType eventType, KeyModifiers modifiers = KeyModifiers.None)
+ {
+ // A host that only knows the cell still gets an answer in pixel mode: the cell's top-left
+ // corner, from the same cell metrics a CSI 16 t query reports. An application dividing by
+ // those metrics recovers exactly the cell that was clicked -- which is all this host knows.
+ return GenerateMouseEvent(button, x, y,
+ x * Math.Max(1, _terminal.Options.CellWidthPixels),
+ y * Math.Max(1, _terminal.Options.CellHeightPixels),
+ eventType, modifiers);
+ }
+
+ ///
+ /// Generates a mouse event sequence for a host that knows the pointer's pixel position.
+ ///
+ ///
+ /// and are 0-based offsets from the top-left
+ /// of the cell area, in the same device pixels as CellWidthPixels. They are used only
+ /// under SGR-Pixels (DECSET 1016); every other encoding reports the cell.
+ ///
+ public string GenerateMouseEvent(MouseButton button, int x, int y, int pixelX, int pixelY, MouseEventType eventType, KeyModifiers modifiers = KeyModifiers.None)
{
// Check if this mode supports this event type
if (!ShouldReportEvent(button, eventType))
@@ -39,6 +58,9 @@ public string GenerateMouseEvent(MouseButton button, int x, int y, MouseEventTyp
return Encoding switch
{
MouseEncoding.SGR => GenerateSGRSequence(button, x, y, eventType, modifiers),
+ // 1016 is 1006 with the coordinates swapped for pixels -- same button byte, same M/m
+ // terminator, and 1-based like xterm's (EmitMousePosition adds one in both modes).
+ MouseEncoding.SGRPixels => GenerateSGRSequence(button, Math.Max(0, pixelX), Math.Max(0, pixelY), eventType, modifiers),
MouseEncoding.URXVT => GenerateURXVTSequence(button, x, y, eventType, modifiers),
MouseEncoding.Utf8 => GenerateUTF8Sequence(button, x, y, eventType, modifiers),
_ => GenerateDefaultSequence(button, x, y, eventType, modifiers)
diff --git a/src/XTerm.NET/InputHandler.Modes.cs b/src/XTerm.NET/InputHandler.Modes.cs
index c7db77ba..7cd70839 100644
--- a/src/XTerm.NET/InputHandler.Modes.cs
+++ b/src/XTerm.NET/InputHandler.Modes.cs
@@ -168,6 +168,9 @@ private bool TryGetPrivateModeState(int mode, out bool set)
case (int)TerminalMode.MouseReportUrxvt:
set = mouseTracker.Encoding == MouseEncoding.URXVT;
return true;
+ case (int)TerminalMode.MouseReportPixel:
+ set = mouseTracker.Encoding == MouseEncoding.SGRPixels;
+ return true;
case (int)TerminalMode.SendFocusEvents:
set = _terminal.SendFocusEvents;
return true;
@@ -449,6 +452,7 @@ private void ResetCSIMode(int mode, bool isPrivate)
case TerminalMode.MouseReportUtf8:
case TerminalMode.MouseReportSgr:
case TerminalMode.MouseReportUrxvt:
+ case TerminalMode.MouseReportPixel:
_terminal.GetMouseTracker().Encoding = MouseEncoding.Default;
break;
diff --git a/src/XTerm.NET/InputHandler.cs b/src/XTerm.NET/InputHandler.cs
index 650861f1..b25ce1b6 100644
--- a/src/XTerm.NET/InputHandler.cs
+++ b/src/XTerm.NET/InputHandler.cs
@@ -2004,6 +2004,10 @@ private void SetCSIMode(int mode, bool isPrivate)
_terminal.GetMouseTracker().Encoding = MouseEncoding.URXVT;
break;
+ case TerminalMode.MouseReportPixel:
+ _terminal.GetMouseTracker().Encoding = MouseEncoding.SGRPixels;
+ break;
+
case TerminalMode.EightBitInput:
_terminal.EightBitInput = true;
break;
diff --git a/src/XTerm.NET/Terminal.cs b/src/XTerm.NET/Terminal.cs
index 66693d99..14831a6c 100644
--- a/src/XTerm.NET/Terminal.cs
+++ b/src/XTerm.NET/Terminal.cs
@@ -1728,6 +1728,23 @@ public string GenerateMouseEvent(MouseButton button, int x, int y, MouseEventTyp
return _mouseTracker.GenerateMouseEvent(button, x, y, eventType, modifiers);
}
+ ///
+ /// Generates an escape sequence for a mouse event, with the pointer's pixel position for
+ /// SGR-Pixels reporting (DECSET 1016).
+ ///
+ /// The mouse button
+ /// The column position (0-based)
+ /// The row position (0-based)
+ /// Horizontal offset from the left of the cell area, in device pixels (0-based)
+ /// Vertical offset from the top of the cell area, in device pixels (0-based)
+ /// The type of mouse event
+ /// Modifier keys held during the event
+ /// The escape sequence string to send to the application
+ public string GenerateMouseEvent(MouseButton button, int x, int y, int pixelX, int pixelY, MouseEventType eventType, KeyModifiers modifiers = KeyModifiers.None)
+ {
+ return _mouseTracker.GenerateMouseEvent(button, x, y, pixelX, pixelY, eventType, modifiers);
+ }
+
///
/// Generates an escape sequence for a focus event (focus in/out).
///
diff --git a/src/XTerm.NET/XTerm.NET.csproj b/src/XTerm.NET/XTerm.NET.csproj
index 85515d3e..4d5934c7 100644
--- a/src/XTerm.NET/XTerm.NET.csproj
+++ b/src/XTerm.NET/XTerm.NET.csproj
@@ -7,8 +7,8 @@
XTerm
- 2.0.3
- 2.0.3.0
+ 2.0.4
+ 2.0.4.0
Dotnet port of xterm.js, giving you ability to host console applications, with Sixel and Kitty graphics.
Iciclecreek
Tom Laird-McConnell