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