using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
namespace Terminal.Gui.Drivers;
internal class UnixOutput : OutputBase, IOutput
{
[StructLayout (LayoutKind.Sequential)]
private struct WinSize
{
public ushort ws_row;
public ushort ws_col;
public ushort ws_xpixel;
public ushort ws_ypixel;
}
private static readonly uint TIOCGWINSZ =
RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ||
RuntimeInformation.IsOSPlatform (OSPlatform.FreeBSD)
? 0x40087468u // Darwin/BSD
: 0x5413u; // Linux
[DllImport ("libc", SetLastError = true)]
private static extern int ioctl (int fd, uint request, out WinSize ws);
// File descriptor for stdout
private const int STDOUT_FILENO = 1;
[DllImport ("libc")]
private static extern int write (int fd, byte [] buf, int n);
[DllImport ("libc", SetLastError = true)]
private static extern int dup (int fd);
///
protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
{
if (Force16Colors)
{
output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
}
else
{
EscSeqUtils.CSI_AppendForegroundColorRGB (
output,
attr.Foreground.R,
attr.Foreground.G,
attr.Foreground.B
);
EscSeqUtils.CSI_AppendBackgroundColorRGB (
output,
attr.Background.R,
attr.Background.G,
attr.Background.B
);
EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
}
}
///
protected override void Write (StringBuilder output)
{
try
{
byte [] utf8 = Encoding.UTF8.GetBytes (output.ToString ());
// Write to stdout (fd 1)
write (STDOUT_FILENO, utf8, utf8.Length);
}
catch
{
// ignore for unit tests
}
}
private Point? _lastCursorPosition;
///
protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
{
if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == screenPositionX && _lastCursorPosition.Value.Y == screenPositionY)
{
return true;
}
_lastCursorPosition = new (screenPositionX, screenPositionY);
try
{
using TextWriter? writer = CreateUnixStdoutWriter ();
// + 1 is needed because Unix is based on 1 instead of 0 and
EscSeqUtils.CSI_WriteCursorPosition (writer!, screenPositionY + 1, screenPositionX + 1);
}
catch
{
// ignore
}
return true;
}
private TextWriter? CreateUnixStdoutWriter ()
{
// duplicate stdout so we don't mess with Console.Out's FD
int fdCopy = dup (STDOUT_FILENO);
if (fdCopy == -1)
{
// Log but don't throw - we're likely running without a TTY (CI/CD, tests, etc.)
var errno = Marshal.GetLastWin32Error ();
Logging.Warning ($"Failed to dup STDOUT_FILENO, errno={errno}. Running without TTY support.");
return null; // Return null instead of throwing
}
try
{
// wrap the raw fd into a SafeFileHandle
SafeFileHandle handle = new SafeFileHandle (fdCopy, ownsHandle: true);
// create FileStream from the safe handle
FileStream stream = new FileStream (handle, FileAccess.Write);
return new StreamWriter (stream)
{
AutoFlush = true
};
}
catch (Exception ex)
{
Logging.Warning ($"Failed to create TextWriter from dup'd STDOUT: {ex.Message}");
return null;
}
}
///
public void Write (ReadOnlySpan text)
{
try
{
byte [] utf8 = Encoding.UTF8.GetBytes (text.ToArray ());
// Write to stdout (fd 1)
write (STDOUT_FILENO, utf8, utf8.Length);
}
catch
{
// ignore for unit tests
}
}
///
public Size GetSize ()
{
try
{
if (ioctl (1, TIOCGWINSZ, out WinSize ws) == 0)
{
if (ws.ws_col > 0 && ws.ws_row > 0)
{
return new (ws.ws_col, ws.ws_row);
}
}
}
catch
{
// ignore
}
return new (80, 25); // fallback
}
private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
///
public override void SetCursorVisibility (CursorVisibility visibility)
{
try
{
if (visibility != CursorVisibility.Invisible)
{
if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
{
_currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle));
}
Write (EscSeqUtils.CSI_ShowCursor);
}
else
{
Write (EscSeqUtils.CSI_HideCursor);
}
}
catch
{
// ignore
}
}
///
public Point GetCursorPosition ()
{
return _lastCursorPosition ?? Point.Empty;
}
///
public void SetCursorPosition (int col, int row)
{
SetCursorPositionImpl (col, row);
}
///
public void SetSize (int width, int height)
{
// Do nothing
}
///
public void Dispose ()
{
}
}