/src/gstreamer/subprojects/glib-2.86.3/glib/gprint.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright © 2025 Luca Bacci |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public |
17 | | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | | * |
19 | | * Author: Luca Bacci <luca.bacci@outlook.com> |
20 | | */ |
21 | | |
22 | | #include "config.h" |
23 | | |
24 | | #include <stdlib.h> |
25 | | #include <stdio.h> |
26 | | |
27 | | #include "gtypes.h" |
28 | | #include "gunicodeprivate.h" |
29 | | #include "gprintprivate.h" |
30 | | #include "gprintfint.h" |
31 | | |
32 | | #ifndef _WIN32 |
33 | | #include <unistd.h> |
34 | | #else |
35 | | #include "gwin32private.h" |
36 | | #include <io.h> |
37 | | #endif |
38 | | |
39 | 0 | #define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \ |
40 | 0 | (wc == 0x7f) || \ |
41 | 0 | (wc >= 0x80 && wc < 0xa0))) |
42 | | |
43 | | char * |
44 | | g_print_convert (const char *string, |
45 | | const char *charset) |
46 | 0 | { |
47 | 0 | if (!g_utf8_validate (string, -1, NULL)) |
48 | 0 | { |
49 | 0 | GString *gstring = g_string_new ("[Invalid UTF-8] "); |
50 | 0 | guchar *p; |
51 | |
|
52 | 0 | for (p = (guchar *)string; *p; p++) |
53 | 0 | { |
54 | 0 | if (CHAR_IS_SAFE(*p) && |
55 | 0 | !(*p == '\r' && *(p + 1) != '\n') && |
56 | 0 | *p < 0x80) |
57 | 0 | g_string_append_c (gstring, *p); |
58 | 0 | else |
59 | 0 | g_string_append_printf (gstring, "\\x%02x", (guint)(guchar)*p); |
60 | 0 | } |
61 | |
|
62 | 0 | return g_string_free (gstring, FALSE); |
63 | 0 | } |
64 | 0 | else |
65 | 0 | { |
66 | 0 | GError *err = NULL; |
67 | |
|
68 | 0 | gchar *result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, &err); |
69 | 0 | if (result) |
70 | 0 | return result; |
71 | 0 | else |
72 | 0 | { |
73 | | /* Not thread-safe, but doesn't matter if we print the warning twice |
74 | | */ |
75 | 0 | static gboolean warned = FALSE; |
76 | 0 | if (!warned) |
77 | 0 | { |
78 | 0 | warned = TRUE; |
79 | 0 | _g_fprintf (stderr, "GLib: Cannot convert message: %s\n", err->message); |
80 | 0 | } |
81 | 0 | g_error_free (err); |
82 | |
|
83 | 0 | return g_strdup (string); |
84 | 0 | } |
85 | 0 | } |
86 | 0 | } |
87 | | |
88 | | #ifdef _WIN32 |
89 | | |
90 | | static int |
91 | | print_console_nolock (const char *string, |
92 | | FILE *stream) |
93 | | { |
94 | | HANDLE handle = (HANDLE) _get_osfhandle (_fileno (stream)); |
95 | | size_t size = strlen (string); |
96 | | DWORD written = 0; |
97 | | |
98 | | if (size > INT_MAX) |
99 | | return 0; |
100 | | |
101 | | /* WriteFile are WriteConsole are limited to DWORD lengths, |
102 | | * but int and DWORD should are of the same size, so we don't |
103 | | * care. |
104 | | */ |
105 | | G_STATIC_ASSERT (INT_MAX <= MAXDWORD); |
106 | | |
107 | | /* We might also check if the source string is ASCII */ |
108 | | if (GetConsoleOutputCP () == CP_UTF8) |
109 | | { |
110 | | /* If the output codepage is UTF-8, we can just call WriteFile, |
111 | | * avoiding a conversion to UTF-16 (which probably will be done |
112 | | * by ConDrv). |
113 | | */ |
114 | | /* Note: we cannot use fputs() here. When outputting to the |
115 | | * console, the UCRT converts the passed string to the console |
116 | | * charset, which is UTF-8, but interprets the string in the |
117 | | * LC_CTYPE charset, which can be anything. |
118 | | */ |
119 | | |
120 | | if (!WriteFile (handle, string, size, &written, NULL)) |
121 | | WIN32_API_FAILED ("WriteFile"); |
122 | | } |
123 | | else |
124 | | { |
125 | | /* Convert to UTF-16 and output using WriteConsole */ |
126 | | |
127 | | /* Note: we can't use fputws() with mode _O_U16TEXT because: |
128 | | * |
129 | | * - file descriptors cannot be locked, unlike FILE streams, so |
130 | | * we cannot set a custom mode on the file descriptor. |
131 | | * - the fputws() implementation is not very good: it outputs codeunit |
132 | | * by codeunit in a loop, so it's slow [1] and breaks UTF-16 surrogate |
133 | | * pairs [2]. |
134 | | * |
135 | | * [1] https://github.com/microsoft/terminal/issues/18124#issuecomment-2451987873 |
136 | | * [2] https://developercommunity.visualstudio.com/t/wprintf-with-_setmode-_O_U16TEXT-or-_O_U/10447076 |
137 | | */ |
138 | | |
139 | | wchar_t buffer[1024]; |
140 | | wchar_t *utf16 = NULL; |
141 | | size_t utf16_len = 0; |
142 | | DWORD utf16_written = 0; |
143 | | |
144 | | g_utf8_to_utf16_make_valid (string, |
145 | | buffer, G_N_ELEMENTS (buffer), |
146 | | &utf16, &utf16_len); |
147 | | |
148 | | /* The length of the UTF-16 string (in count of gunichar2) cannot be |
149 | | * greater than the length of the UTF-8 string (in count of bytes). |
150 | | * So utf16_len <= size <= INT_MAX <= MAXDWORD. |
151 | | */ |
152 | | g_assert (utf16_len <= size); |
153 | | |
154 | | if (!WriteConsole (handle, utf16, utf16_len, &utf16_written, NULL)) |
155 | | WIN32_API_FAILED ("WriteConsole"); |
156 | | |
157 | | if (utf16_written < utf16_len) |
158 | | { |
159 | | written = g_utf8_to_utf16_make_valid_backtrack (string, utf16_written); |
160 | | } |
161 | | else |
162 | | { |
163 | | written = size; |
164 | | } |
165 | | |
166 | | if (utf16 != buffer) |
167 | | g_free (utf16); |
168 | | } |
169 | | |
170 | | if (written > INT_MAX) |
171 | | written = INT_MAX; |
172 | | |
173 | | return (int) written; |
174 | | } |
175 | | |
176 | | static int |
177 | | print_console (const char *string, |
178 | | FILE *stream) |
179 | | { |
180 | | int ret; |
181 | | |
182 | | /* Locking the stream is not important, but leads |
183 | | * to nicer output in case of concurrent writes. |
184 | | */ |
185 | | _lock_file (stream); |
186 | | |
187 | | #if defined (_MSC_VER) || defined (_UCRT) |
188 | | _fflush_nolock (stream); |
189 | | #else |
190 | | fflush (stream); |
191 | | #endif |
192 | | |
193 | | ret = print_console_nolock (string, stream); |
194 | | |
195 | | _unlock_file (stream); |
196 | | |
197 | | return ret; |
198 | | } |
199 | | |
200 | | #endif /* _WIN32 */ |
201 | | |
202 | | static int |
203 | | print_string (char const *string, |
204 | | FILE *stream) |
205 | 2 | { |
206 | 2 | size_t written = fwrite (string, 1, strlen (string), stream); |
207 | | |
208 | 2 | return MIN (written, INT_MAX); |
209 | 2 | } |
210 | | |
211 | | int |
212 | | g_fputs (char const *string, |
213 | | FILE *stream) |
214 | 2 | { |
215 | 2 | int ret; |
216 | | |
217 | | #ifdef _WIN32 |
218 | | if (g_win32_file_stream_is_console_output (stream)) |
219 | | { |
220 | | ret = print_console (string, stream); |
221 | | |
222 | | if (string[ret] != '\0') |
223 | | ret += print_string (&string[ret], stream); |
224 | | } |
225 | | else |
226 | | { |
227 | | ret = print_string (string, stream); |
228 | | } |
229 | | #else |
230 | 2 | const char *charset; |
231 | | |
232 | 2 | if (isatty (fileno (stream)) && |
233 | 0 | !g_get_charset (&charset)) |
234 | 0 | { |
235 | 0 | char *converted = g_print_convert (string, charset); |
236 | 0 | ret = print_string (converted, stream); |
237 | 0 | g_free (converted); |
238 | 0 | } |
239 | 2 | else |
240 | 2 | { |
241 | 2 | ret = print_string (string, stream); |
242 | 2 | } |
243 | 2 | #endif |
244 | | |
245 | 2 | return ret; |
246 | 2 | } |