1 /* melder_console.cpp
2  *
3  * Copyright (C) 1992-2018 Paul Boersma
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or (at
8  * your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this work. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "melder.h"
20 #ifdef _WIN32
21 	#include <windows.h>
22 	#include <fcntl.h>
23 	#include <io.h>
24 #endif
25 
26 /*
27 	MelderConsole is the interface through which Melder_casual writes to
28 	the console (on stderr), and through which Melder_info writes to the
29 	console (on stdout) if it does not write to the GUI.
30 */
31 
32 namespace MelderConsole {// reopen
33 	/*
34 		On Windows, the console ("command prompt") understands UTF-16,
35 		independently of the selected code page, so we use UTF-16 by default.
36 		We could use UTF-8 as well, but this will work only if the code page
37 		is 65001 (i.e. one would have to type "chcp 65001" into the console).
38 		In redirection cases (pipes or files), the encoding will often have
39 		to be different.
40 
41 		On Unix-like platforms (MacOS and Linux), the console understands UTF-8,
42 		so we use UTF-8 by default. Other programs are usually fine handling UTF-8,
43 		so we are probably good even in the context of pipes or redirection.
44 	*/
45 	MelderConsole::Encoding encoding =
46 		#if defined (_WIN32)
47 			MelderConsole::Encoding::UTF16;
48 		#else
49 			MelderConsole::Encoding::UTF8;
50 		#endif
51 }
52 
setEncoding(MelderConsole::Encoding newEncoding)53 void MelderConsole::setEncoding (MelderConsole::Encoding newEncoding) {
54 	MelderConsole :: encoding = newEncoding;
55 }
56 
57 /*
58 	stdout and stderr should be kept distinct. For instance, if you do
59 		praat test.praat > out.txt
60 	the output of Melder_info should go to the file `out.txt`,
61 	whereas the output of Melder_casual should go to the console or terminal.
62 
63 	On MacOS and Linux this requirement is satisfied by default,
64 	but on Windows satisfying this requirement involves some work.
65 */
66 
ensureThatStdoutAndStderrAreInitialized()67 static void ensureThatStdoutAndStderrAreInitialized () {
68 	#if defined (_WIN32)
69 		/*
70 			Stdout and stderr are initialized automatically if we are redirected to a pipe or file.
71 			Stdout and stderr are not initialized, however, if Praat is started from the console,
72 			neither in GUI mode nor in console mode; in these latter cases,
73 			we manually attach stdout and stderr to the calling console.
74 		*/
75 		auto ensureThatStreamIsInitialized = [] (FILE *stream, int handle) {
76 			bool streamHasBeenInitialized = ( _fileno (stream) >= 0 );
77 			if (! streamHasBeenInitialized) {
78 				/*
79 					Don't change the following four lines into
80 						freopen ("CONOUT$", "w", stream);
81 					because if you did that, the distinction between stdout and stderr would be lost.
82 				*/
83 				HANDLE osfHandle = GetStdHandle (handle);
84 				if (osfHandle) {
85 					int fileDescriptor = _open_osfhandle ((intptr_t) osfHandle, _O_TEXT);
86 					Melder_assert (fileDescriptor != 0);
87 					FILE *f = _fdopen (fileDescriptor, "w");
88 					if (! f)
89 						return;   // this can happen under Cygwin
90 					*stream = *f;
91 				}
92 			}
93 		};
94 		ensureThatStreamIsInitialized (stdout, STD_OUTPUT_HANDLE);
95 		ensureThatStreamIsInitialized (stderr, STD_ERROR_HANDLE);
96 	#endif
97 }
98 
write(conststring32 message,bool useStderr)99 void MelderConsole::write (conststring32 message, bool useStderr) {
100 	if (! message)
101 		return;
102 	ensureThatStdoutAndStderrAreInitialized ();
103 	FILE *f = useStderr ? stderr : stdout;
104 	if (MelderConsole :: encoding == Encoding::UTF16) {
105 		#if defined (_WIN32)
106 			fflush (f);
107 			int savedMode = _setmode (_fileno (f), _O_U16TEXT);   // without line-break translation
108 		#endif
109 		fwprintf (f, L"%ls", Melder_peek32to16 (message));   // with line-break translation (Windows)
110 		fflush (f);
111 		#if defined (_WIN32)
112 			_setmode (_fileno (f), savedMode);
113 		#endif
114 	} else if (MelderConsole :: encoding == Encoding::UTF8) {
115 		for (const char32 *p = & message [0]; *p != U'\0'; p ++) {
116 			char32 kar = *p;
117 			if (kar <= 0x00'007F) {
118 				fputc ((int) kar, f);   // because fputc wants an int instead of a uint8 (guarded conversion)
119 			} else if (kar <= 0x00'07FF) {
120 				fputc (0xC0 | (kar >> 6), f);
121 				fputc (0x80 | (kar & 0x00'003F), f);
122 			} else if (kar <= 0x00'FFFF) {
123 				fputc (0xE0 | (kar >> 12), f);
124 				fputc (0x80 | ((kar >> 6) & 0x00'003F), f);
125 				fputc (0x80 | (kar & 0x00'003F), f);
126 			} else {
127 				fputc (0xF0 | (kar >> 18), f);
128 				fputc (0x80 | ((kar >> 12) & 0x00'003F), f);
129 				fputc (0x80 | ((kar >> 6) & 0x00'003F), f);
130 				fputc (0x80 | (kar & 0x00'003F), f);
131 			}
132 		}
133 		fflush (f);
134 	} else if (MelderConsole :: encoding == Encoding::ANSI) {
135 		integer n = str32len (message);
136 		for (integer i = 0; i < n; i ++) {
137 			/*
138 				We convert Unicode to ISO 8859-1 by simple truncation. This loses information.
139 			*/
140 			unsigned int kar = message [i] & 0x00'00FF;
141 			fputc ((int) kar, f);
142 		}
143 		fflush (f);
144 	} else {
145 		// should not happen
146 	}
147 }
148 
149 /* End of file melder_console.cpp */
150