1 /++
2 Helpers to set up a terminal environment.
3 4 See_Also:
5 [kameloso.terminal.colours]
6 7 Copyright: [JR](https://github.com/zorael)
8 License: [Boost Software License 1.0](https://www.boost.org/users/license.html)
9 10 Authors:
11 [JR](https://github.com/zorael)
12 +/13 modulekameloso.terminal;
14 15 private:
16 17 importstd.typecons : Flag, No, Yes;
18 19 public:
20 21 @safe:
22 23 /// Special terminal control characters.24 enumTerminalToken25 {
26 /// Character that preludes a terminal colouring code.27 format = '\033',
28 29 /// Terminal bell/beep.30 bell = '\007',
31 }
32 33 34 version(Windows)
35 {
36 // Taken from LDC: https://github.com/ldc-developers/ldc/pull/3086/commits/9626213a37 // https://github.com/ldc-developers/ldc/pull/3086/commits/9626213a38 39 importcore.sys.windows.wincon : SetConsoleCP, SetConsoleMode, SetConsoleOutputCP;
40 41 /// Original codepage at program start.42 private__gshareduintoriginalCP;
43 44 /// Original output codepage at program start.45 private__gshareduintoriginalOutputCP;
46 47 /// Original console mode at program start.48 private__gshareduintoriginalConsoleMode;
49 50 /++
51 Sets the console codepage to display UTF-8 characters (åäö, 高所恐怖症, ...)
52 and the console mode to display terminal colours.
53 +/54 voidsetConsoleModeAndCodepage() @system55 {
56 importcore.stdc.stdlib : atexit;
57 importcore.sys.windows.winbase : GetStdHandle, INVALID_HANDLE_VALUE, STD_OUTPUT_HANDLE;
58 importcore.sys.windows.wincon : ENABLE_VIRTUAL_TERMINAL_PROCESSING,
59 GetConsoleCP, GetConsoleMode, GetConsoleOutputCP;
60 importcore.sys.windows.winnls : CP_UTF8;
61 62 originalCP = GetConsoleCP();
63 originalOutputCP = GetConsoleOutputCP();
64 65 cast(void)SetConsoleCP(CP_UTF8);
66 cast(void)SetConsoleOutputCP(CP_UTF8);
67 68 autostdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
69 assert((stdoutHandle != INVALID_HANDLE_VALUE), "Failed to get standard output handle");
70 71 immutablegetModeRetval = GetConsoleMode(stdoutHandle, &originalConsoleMode);
72 73 if (getModeRetval != 0)
74 {
75 // The console is a real terminal, not a pager (or Cygwin mintty)76 cast(void)SetConsoleMode(stdoutHandle, originalConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
77 }
78 79 // atexit handlers are also called when exiting via exit() etc.;80 // that's the reason this isn't a RAII struct.81 atexit(&resetConsoleModeAndCodepage);
82 }
83 84 /++
85 Resets the console codepage and console mode to the values they had at
86 program start.
87 +/88 extern(C)
89 privatevoidresetConsoleModeAndCodepage() @system90 {
91 importcore.sys.windows.winbase : GetStdHandle, INVALID_HANDLE_VALUE, STD_OUTPUT_HANDLE;
92 93 autostdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
94 assert((stdoutHandle != INVALID_HANDLE_VALUE), "Failed to get standard output handle");
95 96 cast(void)SetConsoleCP(originalCP);
97 cast(void)SetConsoleOutputCP(originalOutputCP);
98 cast(void)SetConsoleMode(stdoutHandle, originalConsoleMode);
99 }
100 }
101 102 103 version(Posix)
104 {
105 // isTTY106 /++
107 Determines whether or not the program is being run in a terminal (virtual TTY).
108 109 "isatty() returns 1 if fd is an open file descriptor referring to a
110 terminal; otherwise 0 is returned, and errno is set to indicate the error."
111 112 Returns:
113 `true` if the current environment appears to be a terminal;
114 `false` if not (e.g. pager or certain IDEs with terminal windows).
115 +/116 boolisTTY() //@safe117 {
118 importcore.sys.posix.unistd : STDOUT_FILENO, isatty;
119 return (isatty(STDOUT_FILENO) == 1);
120 }
121 }
122 elseversion(Windows)
123 {
124 /// Ditto125 boolisTTY() @system126 {
127 importcore.sys.windows.winbase : FILE_TYPE_PIPE, GetFileType, GetStdHandle, STD_OUTPUT_HANDLE;
128 autohandle = GetStdHandle(STD_OUTPUT_HANDLE);
129 return (GetFileType(handle) != FILE_TYPE_PIPE);
130 }
131 }
132 else133 {
134 staticassert(0, "Unsupported platform, please file a bug.");
135 }
136 137 138 // isTerminal139 /++
140 Determines whether or not the program is being run in a terminal, be it a
141 real TTY or a whitelisted pseudo-TTY such as those employed in IDE terminal
142 emulators.
143 144 Returns:
145 `true` if the environment is either a real TTY or one of a few whitelisted
146 pseudo-TTYs; `false` if not.
147 +/148 boolisTerminal() @system149 {
150 importkameloso.platform : currentPlatform;
151 152 if (isTTY) returntrue;
153 154 switch (currentPlatform)
155 {
156 case"Msys":
157 case"Cygwin":
158 case"vscode":
159 returntrue;
160 161 default:
162 returnfalse;
163 }
164 }
165 166 167 // applyMonochromeAndFlushOverrides168 /++
169 Override [kameloso.pods.CoreSettings.monochrome|CoreSettings.monochrome] and
170 potentially [kameloso.pods.CoreSettings.flush|CoreSettings.flush] if the
171 terminal seems to not truly be a terminal (such as a pager, or a non-whitelisted
172 IDE terminal emulator).
173 174 The idea is to generally override monochrome to true if it's a pager, but
175 keep monochrome and override flush to true if it's a whitelisted environment.
176 177 Params:
178 monochrome = Reference to monochrome setting bool.
179 flush = Reference to flush setting bool.
180 +/181 voidapplyMonochromeAndFlushOverrides(refboolmonochrome, refboolflush) @system182 {
183 importkameloso.platform : currentPlatform;
184 185 if (!isTTY)
186 {
187 switch (currentPlatform)
188 {
189 case"Msys":
190 // Requires manual flushing despite setvbuf191 // No need to set monochrome though192 flush = true;
193 break;
194 195 case"Cygwin":
196 case"vscode":
197 // Probably no longer needs modifications198 break;
199 200 default:
201 // Non-whitelisted non-TTY; set monochrome202 monochrome = true;
203 break;
204 }
205 }
206 }
207 208 209 // ensureAppropriateBuffering210 /++
211 Ensures select non-TTY environments (like Cygwin) are line-buffered.
212 +/213 voidensureAppropriateBuffering() @system214 {
215 importkameloso.constants : BufferSize;
216 importkameloso.platform : currentPlatform;
217 importstd.stdio : stdout;
218 importcore.stdc.stdio : _IOLBF;
219 220 if (!isTTY)
221 {
222 switch (currentPlatform)
223 {
224 case"Msys":
225 case"Cygwin":
226 case"vscode":
227 /+
228 Some terminal environments require us to flush standard out after
229 writing to it, as they are likely pagers and not TTYs behind the
230 scene. Whitelist some and set standard out to be line-buffered
231 for those.
232 +/233 stdout.setvbuf(BufferSize.vbufStdout, _IOLBF);
234 break;
235 236 default:
237 // Non-whitelisted non-TTY (a pager), leave as-is.238 break;
239 }
240 }
241 }
242 243 244 // setTitle245 /++
246 Sets the terminal title to a given string. Supposedly.
247 248 Example:
249 ---
250 setTitle("kameloso IRC bot");
251 ---
252 253 Params:
254 title = String to set the title to.
255 +/256 voidsetTitle(conststringtitle) @system257 {
258 version(Posix)
259 {
260 importstd.stdio : stdout, write;
261 262 write("\033]0;", title, "\007");
263 stdout.flush();
264 }
265 elseversion(Windows)
266 {
267 importstd.string : toStringz;
268 importcore.sys.windows.wincon : SetConsoleTitleA;
269 270 SetConsoleTitleA(title.toStringz);
271 }
272 else273 {
274 staticassert(0, "Unsupported platform, please file a bug.");
275 }
276 }