You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines
6.2 KiB

  1. #nullable enable
  2. using System;
  3. using System.Runtime.InteropServices;
  4. namespace IPA.Logging.Printers
  5. {
  6. /// <summary>
  7. /// Prints a pretty message to the console.
  8. /// </summary>
  9. public class ColoredConsolePrinter : LogPrinter
  10. {
  11. private Logger.LogLevel filter = Logger.LogLevel.All;
  12. /// <summary>
  13. /// A filter for this specific printer.
  14. /// </summary>
  15. /// <value>the filter to apply to this printer</value>
  16. public override Logger.LogLevel Filter { get => filter; set => filter = value; }
  17. /// <summary>
  18. /// The color to print messages as.
  19. /// </summary>
  20. /// <value>the color to print this message as</value>
  21. // Initializer calls this function because Unity's .NET 3.5 doesn't have the color properties on Console
  22. public ConsoleColor Color { get; set; } = GetConsoleColor(WinConsole.OutHandle);
  23. private static ConsoleColor GetDarkenedColor(ConsoleColor color)
  24. => color switch
  25. {
  26. ConsoleColor.Gray => ConsoleColor.DarkGray,
  27. ConsoleColor.Blue => ConsoleColor.DarkBlue,
  28. ConsoleColor.Green => ConsoleColor.DarkGreen,
  29. ConsoleColor.Cyan => ConsoleColor.DarkCyan,
  30. ConsoleColor.Red => ConsoleColor.DarkRed,
  31. ConsoleColor.Magenta => ConsoleColor.DarkMagenta,
  32. ConsoleColor.Yellow => ConsoleColor.DarkYellow,
  33. ConsoleColor.White => ConsoleColor.Gray,
  34. _ => color,
  35. };
  36. private readonly bool darkenSetManually;
  37. private readonly bool darkenMessages;
  38. public ColoredConsolePrinter() : this(Config.SelfConfig.Debug_.DarkenMessages_)
  39. {
  40. darkenSetManually = false;
  41. }
  42. public ColoredConsolePrinter(bool darkenMessages)
  43. {
  44. darkenSetManually = true;
  45. this.darkenMessages = darkenMessages;
  46. }
  47. /// <summary>
  48. /// Prints an entry to the console window.
  49. /// </summary>
  50. /// <param name="level">the <see cref="Logger.Level"/> of the message</param>
  51. /// <param name="time">the <see cref="DateTime"/> the message was recorded at</param>
  52. /// <param name="logName">the name of the log that sent the message</param>
  53. /// <param name="message">the message to print</param>
  54. public override void Print(Logger.Level level, DateTime time, string logName, string message)
  55. {
  56. if (((byte)level & (byte)StandardLogger.PrintFilter) == 0) return;
  57. EnsureDefaultsPopulated(WinConsole.OutHandle);
  58. SetColor(Color, WinConsole.OutHandle);
  59. var prefixStr = "";
  60. var suffixStr = "";
  61. if ((darkenSetManually && darkenMessages) || (!darkenSetManually && Config.SelfConfig.Debug_.DarkenMessages_))
  62. {
  63. var darkened = GetDarkenedColor(Color);
  64. if (darkened != Color)
  65. {
  66. prefixStr = StdoutInterceptor.ConsoleColorToForegroundSet(darkened);
  67. suffixStr = StdoutInterceptor.ConsoleColorToForegroundSet(Color);
  68. }
  69. }
  70. foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
  71. WinConsole.ConOut.WriteLine(Logger.LogFormat, prefixStr + line + suffixStr, logName, time, level.ToString().ToUpperInvariant());
  72. ResetColor(WinConsole.OutHandle);
  73. }
  74. private static bool _haveReadDefaultColors;
  75. private static short _defaultColors;
  76. private static void EnsureDefaultsPopulated(IntPtr handle, bool force = false)
  77. {
  78. if (!_haveReadDefaultColors | force)
  79. {
  80. _ = GetConsoleScreenBufferInfo(handle, out var info);
  81. _defaultColors = (short)(info.Attribute & ~15);
  82. _haveReadDefaultColors = true;
  83. }
  84. }
  85. private static void ResetColor(IntPtr handle)
  86. {
  87. _ = GetConsoleScreenBufferInfo(handle, out var info);
  88. var otherAttrs = (short)(info.Attribute & ~15);
  89. _ = SetConsoleTextAttribute(handle, (short)(otherAttrs | _defaultColors));
  90. }
  91. private static void SetColor(ConsoleColor col, IntPtr handle)
  92. {
  93. _ = GetConsoleScreenBufferInfo(handle, out var info);
  94. var attr = GetAttrForeground(info.Attribute, col);
  95. _ = SetConsoleTextAttribute(handle, attr);
  96. }
  97. private static short GetAttrForeground(int attr, ConsoleColor color)
  98. {
  99. attr &= ~15;
  100. return (short)(attr | (int)color);
  101. }
  102. private static ConsoleColor GetConsoleColor(IntPtr handle)
  103. {
  104. _ = GetConsoleScreenBufferInfo(handle, out var info);
  105. return (ConsoleColor)(info.Attribute & 15);
  106. }
  107. // ReSharper disable NotAccessedField.Local
  108. #pragma warning disable 649
  109. private struct Coordinate
  110. {
  111. public short X;
  112. public short Y;
  113. }
  114. private struct SmallRect
  115. {
  116. public short Left;
  117. public short Top;
  118. public short Right;
  119. public short Bottom;
  120. }
  121. private struct ConsoleScreenBufferInfo
  122. {
  123. public Coordinate Size;
  124. public Coordinate CursorPosition;
  125. public short Attribute;
  126. public SmallRect Window;
  127. public Coordinate MaxWindowSize;
  128. }
  129. #pragma warning restore 649
  130. // ReSharper restore NotAccessedField.Local
  131. [DllImport("kernel32.dll", EntryPoint = "GetConsoleScreenBufferInfo", SetLastError = true, CharSet = CharSet.Unicode)]
  132. private static extern bool GetConsoleScreenBufferInfo(IntPtr handle, out ConsoleScreenBufferInfo info);
  133. [DllImport("kernel32.dll", EntryPoint = "SetConsoleTextAttribute", SetLastError = true, CharSet = CharSet.Unicode)]
  134. private static extern bool SetConsoleTextAttribute(IntPtr handle, short attribute);
  135. }
  136. }