2
0

UnixOutput.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. using System.Runtime.InteropServices;
  2. using Microsoft.Win32.SafeHandles;
  3. namespace Terminal.Gui.Drivers;
  4. internal class UnixOutput : OutputBase, IConsoleOutput
  5. {
  6. [StructLayout (LayoutKind.Sequential)]
  7. private struct WinSize
  8. {
  9. public ushort ws_row;
  10. public ushort ws_col;
  11. public ushort ws_xpixel;
  12. public ushort ws_ypixel;
  13. }
  14. private static readonly uint TIOCGWINSZ =
  15. RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ||
  16. RuntimeInformation.IsOSPlatform (OSPlatform.FreeBSD)
  17. ? 0x40087468u // Darwin/BSD
  18. : 0x5413u; // Linux
  19. [DllImport ("libc", SetLastError = true)]
  20. private static extern int ioctl (int fd, uint request, out WinSize ws);
  21. // File descriptor for stdout
  22. private const int STDOUT_FILENO = 1;
  23. [DllImport ("libc")]
  24. private static extern int write (int fd, byte [] buf, int n);
  25. [DllImport ("libc", SetLastError = true)]
  26. private static extern int dup (int fd);
  27. /// <inheritdoc />
  28. protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
  29. {
  30. if (Application.Force16Colors)
  31. {
  32. output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
  33. output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
  34. }
  35. else
  36. {
  37. EscSeqUtils.CSI_AppendForegroundColorRGB (
  38. output,
  39. attr.Foreground.R,
  40. attr.Foreground.G,
  41. attr.Foreground.B
  42. );
  43. EscSeqUtils.CSI_AppendBackgroundColorRGB (
  44. output,
  45. attr.Background.R,
  46. attr.Background.G,
  47. attr.Background.B
  48. );
  49. EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
  50. }
  51. }
  52. /// <inheritdoc />
  53. protected override void Write (StringBuilder output)
  54. {
  55. byte [] utf8 = Encoding.UTF8.GetBytes (output.ToString ());
  56. // Write to stdout (fd 1)
  57. write (STDOUT_FILENO, utf8, utf8.Length);
  58. }
  59. private Point? _lastCursorPosition;
  60. /// <inheritdoc />
  61. protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
  62. {
  63. if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == screenPositionX && _lastCursorPosition.Value.Y == screenPositionY)
  64. {
  65. return true;
  66. }
  67. _lastCursorPosition = new (screenPositionX, screenPositionY);
  68. using var writer = CreateUnixStdoutWriter ();
  69. // + 1 is needed because Unix is based on 1 instead of 0 and
  70. EscSeqUtils.CSI_WriteCursorPosition (writer, screenPositionY + 1, screenPositionX + 1);
  71. return true;
  72. }
  73. private TextWriter CreateUnixStdoutWriter ()
  74. {
  75. // duplicate stdout so we don’t mess with Console.Out’s FD
  76. int fdCopy = dup (STDOUT_FILENO);
  77. if (fdCopy == -1)
  78. {
  79. throw new IOException ("Failed to dup STDOUT_FILENO");
  80. }
  81. // wrap the raw fd into a SafeFileHandle
  82. var handle = new SafeFileHandle (fdCopy, ownsHandle: true);
  83. // create FileStream from the safe handle
  84. var stream = new FileStream (handle, FileAccess.Write);
  85. return new StreamWriter (stream)
  86. {
  87. AutoFlush = true
  88. };
  89. }
  90. /// <inheritdoc />
  91. public void Write (ReadOnlySpan<char> text)
  92. {
  93. if (!ConsoleDriver.RunningUnitTests)
  94. {
  95. byte [] utf8 = Encoding.UTF8.GetBytes (text.ToArray ());
  96. // Write to stdout (fd 1)
  97. write (STDOUT_FILENO, utf8, utf8.Length);
  98. }
  99. }
  100. /// <inheritdoc />
  101. public Size GetSize ()
  102. {
  103. if (ConsoleDriver.RunningUnitTests)
  104. {
  105. // For unit tests, we return a default size.
  106. return Size.Empty;
  107. }
  108. if (ioctl (1, TIOCGWINSZ, out WinSize ws) == 0)
  109. {
  110. if (ws.ws_col > 0 && ws.ws_row > 0)
  111. {
  112. return new (ws.ws_col, ws.ws_row);
  113. }
  114. }
  115. return Size.Empty; // fallback
  116. }
  117. private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
  118. /// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
  119. public override void SetCursorVisibility (CursorVisibility visibility)
  120. {
  121. if (visibility != CursorVisibility.Invisible)
  122. {
  123. if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
  124. {
  125. _currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
  126. Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle));
  127. }
  128. Write (EscSeqUtils.CSI_ShowCursor);
  129. }
  130. else
  131. {
  132. Write (EscSeqUtils.CSI_HideCursor);
  133. }
  134. }
  135. /// <inheritdoc />
  136. public void SetCursorPosition (int col, int row)
  137. {
  138. SetCursorPositionImpl (col, row);
  139. }
  140. /// <inheritdoc />
  141. public void SetSize (int width, int height)
  142. {
  143. // Do nothing
  144. }
  145. /// <inheritdoc />
  146. public void Dispose ()
  147. {
  148. }
  149. }