Add g_fputs() utility function

Like g_print, expects UTF-8 and outputs in the right charset.
This commit is contained in:
Luca Bacci
2025-04-28 16:56:41 +02:00
parent 7faaac9265
commit f26173963a
2 changed files with 173 additions and 0 deletions

View File

@@ -24,9 +24,18 @@
#include <stdlib.h>
#include <stdio.h>
#include "gtypes.h"
#include "gunicodeprivate.h"
#include "gprintprivate.h"
#include "gprintfint.h"
#ifndef _WIN32
#include <unistd.h>
#else
#include "gwin32private.h"
#include <io.h>
#endif
#define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \
(wc == 0x7f) || \
(wc >= 0x80 && wc < 0xa0)))
@@ -75,3 +84,163 @@ g_print_convert (const char *string,
}
}
}
#ifdef _WIN32
static int
print_console_nolock (const char *string,
FILE *stream)
{
HANDLE handle = (HANDLE) _get_osfhandle (_fileno (stream));
size_t size = strlen (string);
DWORD written = 0;
if (size > INT_MAX)
return 0;
/* WriteFile are WriteConsole are limited to DWORD lengths,
* but int and DWORD should are of the same size, so we don't
* care.
*/
G_STATIC_ASSERT (INT_MAX <= MAXDWORD);
/* We might also check if the source string is ASCII */
if (GetConsoleOutputCP () == CP_UTF8)
{
/* If the output codepage is UTF-8, we can just call WriteFile,
* avoiding a conversion to UTF-16 (which probably will be done
* by ConDrv).
*/
/* Note: we cannot use fputs() here. When outputting to the
* console, the UCRT converts the passed string to the console
* charset, which is UTF-8, but interprets the string in the
* LC_CTYPE charset, which can be anything.
*/
if (!WriteFile (handle, string, size, &written, NULL))
WIN32_API_FAILED ("WriteFile");
}
else
{
/* Convert to UTF-16 and output using WriteConsole */
/* Note: we can't use fputws() with mode _O_U16TEXT because:
*
* - file descriptors cannot be locked, unlike FILE streams, so
* we cannot set a custom mode on the file descriptor.
* - the fputws() implementation is not very good: it outputs codeunit
* by codeunit in a loop, so it's slow [1] and breaks UTF-16 surrogate
* pairs [2].
*
* [1] https://github.com/microsoft/terminal/issues/18124#issuecomment-2451987873
* [2] https://developercommunity.visualstudio.com/t/wprintf-with-_setmode-_O_U16TEXT-or-_O_U/10447076
*/
wchar_t buffer[1024];
wchar_t *utf16 = NULL;
size_t utf16_len = 0;
DWORD utf16_written = 0;
g_utf8_to_utf16_make_valid (string,
buffer, G_N_ELEMENTS (buffer),
&utf16, &utf16_len);
/* The length of the UTF-16 string (in count of gunichar2) cannot be
* greater than the length of the UTF-8 string (in count of bytes).
* So utf16_len <= size <= INT_MAX <= MAXDWORD.
*/
g_assert (utf16_len <= size);
if (!WriteConsole (handle, utf16, utf16_len, &utf16_written, NULL))
WIN32_API_FAILED ("WriteConsole");
if (utf16_written < utf16_len)
{
written = g_utf8_to_utf16_make_valid_backtrack (string, utf16_written);
}
else
{
written = size;
}
if (utf16 != buffer)
g_free (utf16);
}
if (written > INT_MAX)
written = INT_MAX;
return (int) written;
}
static int
print_console (const char *string,
FILE *stream)
{
int ret;
/* Locking the stream is not important, but leads
* to nicer output in case of concurrent writes.
*/
_lock_file (stream);
#if defined (_MSC_VER) || defined (_UCRT)
_fflush_nolock (stream);
#else
fflush (stream);
#endif
ret = print_console_nolock (string, stream);
_unlock_file (stream);
return ret;
}
#endif /* _WIN32 */
static int
print_string (char const *string,
FILE *stream)
{
size_t written = fwrite (string, 1, strlen (string), stream);
return MIN (written, INT_MAX);
}
int
g_fputs (char const *string,
FILE *stream)
{
int ret;
#ifdef _WIN32
if (g_win32_file_stream_is_console_output (stream))
{
ret = print_console (string, stream);
if (string[ret] != '\0')
ret += print_string (&string[ret], stream);
}
else
{
ret = print_string (string, stream);
}
#else
const char *charset;
if (isatty (fileno (stream)) &&
!g_get_charset (&charset))
{
char *converted = g_print_convert (string, charset);
ret = print_string (converted, stream);
g_free (converted);
}
else
{
ret = print_string (string, stream);
}
#endif
return ret;
}

View File

@@ -28,6 +28,10 @@
G_BEGIN_DECLS
int
g_fputs (const char *string,
FILE *stream);
char *
g_print_convert (const char *string,
const char *charset);