TtyHandler.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // Sections of this code have been abridged from https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TermInfoReader.cs under the MIT license
  2. using System;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. namespace BepInEx.Unix
  7. {
  8. internal class TtyInfo
  9. {
  10. public string TerminalType { get; set; } = "default";
  11. public int MaxColors { get; set; }
  12. public string[] ForegroundColorStrings { get; set; }
  13. public static TtyInfo Default { get; } = new TtyInfo
  14. {
  15. MaxColors = 0
  16. };
  17. public string GetAnsiCode(ConsoleColor color)
  18. {
  19. if (MaxColors <= 0 || ForegroundColorStrings == null)
  20. return string.Empty;
  21. int index = (int)color % MaxColors;
  22. return ForegroundColorStrings[index];
  23. }
  24. }
  25. internal static class TtyHandler
  26. {
  27. private static readonly string[] ncursesLocations = new[]
  28. {
  29. "/usr/share/terminfo",
  30. "/etc/terminfo",
  31. "/usr/lib/terminfo",
  32. "/lib/terminfo"
  33. };
  34. private static string TryTermInfoDir(string dir, string term)
  35. {
  36. string infoFilePath = $"{dir}/{(int)term[0]:x}/{term}";
  37. if (File.Exists(infoFilePath))
  38. return infoFilePath;
  39. infoFilePath = Utility.CombinePaths(dir, term.Substring(0, 1), term);
  40. if (File.Exists(infoFilePath))
  41. return infoFilePath;
  42. return null;
  43. }
  44. private static string FindTermInfoPath(string term)
  45. {
  46. if (string.IsNullOrEmpty(term))
  47. return null;
  48. string termInfoVar = Environment.GetEnvironmentVariable("TERMINFO");
  49. if (termInfoVar != null && Directory.Exists(termInfoVar))
  50. {
  51. string text = TryTermInfoDir(termInfoVar, term);
  52. if (text != null)
  53. {
  54. return text;
  55. }
  56. }
  57. foreach (string location in ncursesLocations)
  58. {
  59. if (Directory.Exists(location))
  60. {
  61. string text = TryTermInfoDir(location, term);
  62. if (text != null)
  63. return text;
  64. }
  65. }
  66. return null;
  67. }
  68. public static TtyInfo GetTtyInfo(string terminal = null)
  69. {
  70. terminal = terminal ?? Environment.GetEnvironmentVariable("TERM");
  71. var path = FindTermInfoPath(terminal);
  72. if (path == null)
  73. return TtyInfo.Default;
  74. byte[] buffer = File.ReadAllBytes(path);
  75. var info = TtyInfoParser.Parse(buffer);
  76. info.TerminalType = terminal;
  77. return info;
  78. }
  79. }
  80. internal static class TtyInfoParser
  81. {
  82. private static readonly int[] ansiColorMapping =
  83. {
  84. 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15
  85. };
  86. public static TtyInfo Parse(byte[] buffer)
  87. {
  88. int intSize;
  89. int magic = GetInt16(buffer, 0);
  90. switch (magic)
  91. {
  92. case 0x11a:
  93. intSize = 2;
  94. break;
  95. case 0x21E:
  96. intSize = 4;
  97. break;
  98. default:
  99. // Unknown ttyinfo format
  100. return TtyInfo.Default;
  101. }
  102. int boolFieldLength = GetInt16(buffer, 4);
  103. int intFieldLength = GetInt16(buffer, 6);
  104. int strOffsetFieldLength = GetInt16(buffer, 8);
  105. // Normally i'd put a more complete implementation here, but I only need to parse this info to get the max color count
  106. // Feel free to implement the rest of this using these sources:
  107. // https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TermInfoReader.cs
  108. // https://invisible-island.net/ncurses/man/term.5.html
  109. // https://invisible-island.net/ncurses/man/terminfo.5.html
  110. int baseOffset = 12 + GetString(buffer, 12).Length + 1; // Skip the terminal name
  111. baseOffset += boolFieldLength; // Length of bool field section
  112. baseOffset += baseOffset % 2; // Correct for boundary
  113. int colorOffset =
  114. baseOffset
  115. + (intSize * (int)TermInfoNumbers.MaxColors); // Finally the offset for the max color integer
  116. //int stringOffset = baseOffset + (intSize * intFieldLength);
  117. //int foregoundColorOffset =
  118. // stringOffset
  119. // + (2 * (int)TermInfoStrings.SetAForeground);
  120. //foregoundColorOffset = stringOffset
  121. // + (2 * strOffsetFieldLength)
  122. // + GetInt16(buffer, foregoundColorOffset);
  123. var info = new TtyInfo();
  124. info.MaxColors = GetInteger(intSize, buffer, colorOffset);
  125. //string setForegroundTemplate = GetString(buffer, foregoundColorOffset);
  126. //info.ForegroundColorStrings = ansiColorMapping.Select(x => setForegroundTemplate.Replace("%p1%", x.ToString())).ToArray();
  127. info.ForegroundColorStrings = ansiColorMapping.Select(x => $"\u001B[{(x > 7 ? 82 + x : 30 + x)}m").ToArray();
  128. return info;
  129. }
  130. private static int GetInt32(byte[] buffer, int offset)
  131. {
  132. return buffer[offset]
  133. | (buffer[offset + 1] << 8)
  134. | (buffer[offset + 2] << 16)
  135. | (buffer[offset + 3] << 24);
  136. }
  137. private static short GetInt16(byte[] buffer, int offset)
  138. {
  139. return (short)(buffer[offset]
  140. | (buffer[offset + 1] << 8));
  141. }
  142. private static int GetInteger(int intSize, byte[] buffer, int offset)
  143. {
  144. return intSize == 2
  145. ? GetInt16(buffer, offset)
  146. : GetInt32(buffer, offset);
  147. }
  148. private static string GetString(byte[] buffer, int offset)
  149. {
  150. int length = 0;
  151. while (buffer[offset + length] != 0x00)
  152. length++;
  153. return Encoding.ASCII.GetString(buffer, offset, length);
  154. }
  155. internal enum TermInfoNumbers
  156. {
  157. MaxColors = 13
  158. }
  159. internal enum TermInfoStrings
  160. {
  161. SetAForeground = 359
  162. }
  163. }
  164. }