melder_console.cpp 5.0 KB

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