The Xlib and ncurses libraries query the LC_CTYPE locale value to guess the usable character set. If the calling program did not call setlocale(), that character set will be severely limited. Extensive Unicode support is a reasonable libcaca user expectation. The locale is restored as soon as possible, once the window or terminal have been initialised. Unfortunately, the effect of setlocale() is process- wide, and may affect other threads. This is now documented. Note also that both Xlib and ncurses ignore the effects of uselocale() which would have been the thread-safe solution to this problem.tags/v0.99.beta20
@@ -26,6 +26,7 @@ INSTALL | |||||
caca-config | caca-config | ||||
# Testsuite binaries | # Testsuite binaries | ||||
caca/t/bench | caca/t/bench | ||||
caca/t/bug-setlocale | |||||
caca/t/caca-test | caca/t/caca-test | ||||
caca/t/simple | caca/t/simple | ||||
caca/t/*.log | caca/t/*.log | ||||
@@ -56,6 +56,12 @@ static int caca_plugin_install(caca_display_t *, char const *); | |||||
* retrieved using caca_get_canvas() and it is automatically destroyed when | * retrieved using caca_get_canvas() and it is automatically destroyed when | ||||
* caca_free_display() is called. | * caca_free_display() is called. | ||||
* | * | ||||
* Note that in order to achieve maximum Unicode compatibility, the driver | |||||
* initialisation code may temporarily change the program’s global LC_CTYPE | |||||
* locale using setlocale(). It is advised not to call LC_CTYPE-dependent | |||||
* functions from other threads during the call to caca_create_display(). | |||||
* The locale settings are restored when the function returns. | |||||
* | |||||
* See also caca_create_display_with_driver(). | * See also caca_create_display_with_driver(). | ||||
* | * | ||||
* If an error occurs, NULL is returned and \b errno is set accordingly: | * If an error occurs, NULL is returned and \b errno is set accordingly: | ||||
@@ -228,6 +228,9 @@ static int ncurses_init_graphics(caca_display_t *dp) | |||||
COLOR_WHITE + 8 | COLOR_WHITE + 8 | ||||
}; | }; | ||||
#if defined HAVE_LOCALE_H | |||||
char const *old_locale; | |||||
#endif | |||||
mmask_t newmask; | mmask_t newmask; | ||||
int fg, bg, max; | int fg, bg, max; | ||||
@@ -242,13 +245,16 @@ static int ncurses_init_graphics(caca_display_t *dp) | |||||
signal(SIGWINCH, sigwinch_handler); | signal(SIGWINCH, sigwinch_handler); | ||||
#endif | #endif | ||||
#if defined HAVE_LOCALE_H | |||||
setlocale(LC_ALL, ""); | |||||
#endif | |||||
_caca_set_term_title("caca for ncurses"); | _caca_set_term_title("caca for ncurses"); | ||||
#if defined HAVE_LOCALE_H | |||||
old_locale = setlocale(LC_CTYPE, ""); | |||||
#endif | |||||
initscr(); | initscr(); | ||||
#if defined HAVE_LOCALE_H | |||||
setlocale(LC_CTYPE, old_locale); | |||||
#endif | |||||
keypad(stdscr, TRUE); | keypad(stdscr, TRUE); | ||||
nonl(); | nonl(); | ||||
raw(); | raw(); | ||||
@@ -106,13 +106,22 @@ static int x11_init_graphics(caca_display_t *dp) | |||||
dp->resize.allow = 0; | dp->resize.allow = 0; | ||||
#if defined HAVE_LOCALE_H | #if defined HAVE_LOCALE_H | ||||
setlocale(LC_ALL, ""); | |||||
/* FIXME: some better code here would be: | |||||
* locale_t old_locale = uselocale(newlocale(LC_CTYPE_MASK, | |||||
* "", (locale_t)0); | |||||
* … but XOpenDisplay only works properly with setlocale(), | |||||
* not uselocale(). */ | |||||
char const *old_locale = setlocale(LC_CTYPE, ""); | |||||
#endif | #endif | ||||
dp->drv.p->dpy = XOpenDisplay(NULL); | dp->drv.p->dpy = XOpenDisplay(NULL); | ||||
if(dp->drv.p->dpy == NULL) | if(dp->drv.p->dpy == NULL) | ||||
return -1; | return -1; | ||||
#if defined HAVE_LOCALE_H | |||||
setlocale(LC_CTYPE, old_locale); | |||||
#endif | |||||
#if defined HAVE_GETENV | #if defined HAVE_GETENV | ||||
fonts[0] = getenv("CACA_FONT"); | fonts[0] = getenv("CACA_FONT"); | ||||
if(fonts[0] && *fonts[0]) | if(fonts[0] && *fonts[0]) | ||||
@@ -10,7 +10,7 @@ endif | |||||
EXTRA_DIST = check-copyright check-doxygen check-source check-win32 | EXTRA_DIST = check-copyright check-doxygen check-source check-win32 | ||||
noinst_PROGRAMS = simple bench $(cppunit_tests) | |||||
noinst_PROGRAMS = simple bench bug-setlocale $(cppunit_tests) | |||||
TESTS = simple check-copyright check-source check-win32 \ | TESTS = simple check-copyright check-source check-win32 \ | ||||
$(doxygen_tests) $(cppunit_tests) | $(doxygen_tests) $(cppunit_tests) | ||||
@@ -21,6 +21,9 @@ simple_LDADD = ../libcaca.la | |||||
bench_SOURCES = bench.c | bench_SOURCES = bench.c | ||||
bench_LDADD = ../libcaca.la | bench_LDADD = ../libcaca.la | ||||
bug_setlocale_SOURCES = bug-setlocale.c | |||||
bug_setlocale_LDADD = ../libcaca.la | |||||
caca_test_SOURCES = caca-test.cpp canvas.cpp dirty.cpp driver.cpp export.cpp | caca_test_SOURCES = caca-test.cpp canvas.cpp dirty.cpp driver.cpp export.cpp | ||||
caca_test_CXXFLAGS = $(CPPUNIT_CFLAGS) | caca_test_CXXFLAGS = $(CPPUNIT_CFLAGS) | ||||
caca_test_LDADD = ../libcaca.la $(CPPUNIT_LIBS) | caca_test_LDADD = ../libcaca.la $(CPPUNIT_LIBS) | ||||
@@ -0,0 +1,63 @@ | |||||
/* | |||||
* bug-setlocale: unit test for wrong setlocale() calls | |||||
* Copyright (c) 2016 Sam Hocevar <sam@hocevar.net> | |||||
* All Rights Reserved | |||||
* | |||||
* This program is free software. It comes without any warranty, to | |||||
* the extent permitted by applicable law. You can redistribute it | |||||
* and/or modify it under the terms of the Do What the Fuck You Want | |||||
* to Public License, Version 2, as published by Sam Hocevar. See | |||||
* http://www.wtfpl.net/ for more details. | |||||
*/ | |||||
#include "config.h" | |||||
#if !defined(__KERNEL__) | |||||
# include <stdio.h> | |||||
# include <stdlib.h> | |||||
# include <string.h> | |||||
#endif | |||||
#include "caca.h" | |||||
#define TEST(x) \ | |||||
do \ | |||||
{ \ | |||||
tests++; \ | |||||
if((x)) \ | |||||
passed++; \ | |||||
else \ | |||||
fprintf(stderr, "test #%i failed: %s\n", (tests), #x); \ | |||||
} \ | |||||
while(0) | |||||
int main(int argc, char *argv[]) | |||||
{ | |||||
char buf[10]; | |||||
char const * const * list; | |||||
list = caca_get_display_driver_list(); | |||||
int i, tests = 0, passed = 0; | |||||
snprintf(buf, 10, "%.1f", 0.0f); | |||||
TEST(buf[1] == '.'); | |||||
for (i = 0; list[i]; i += 2) | |||||
{ | |||||
if (!strcmp(list[i], "x11") || !strcmp(list[i], "ncurses")) | |||||
{ | |||||
caca_display_t *dp = caca_create_display_with_driver(NULL, list[i]); | |||||
snprintf(buf, 10, "%.1f", 0.0f); | |||||
TEST(buf[1] == '.'); | |||||
caca_free_display(dp); | |||||
} | |||||
} | |||||
fprintf(stderr, "%i tests, %i errors\n", tests, tests - passed); | |||||
return 0; | |||||
} | |||||
@@ -26,7 +26,7 @@ | |||||
if((x)) \ | if((x)) \ | ||||
passed++; \ | passed++; \ | ||||
else \ | else \ | ||||
fprintf(stderr, "test #%i failed\n", (tests)); \ | |||||
fprintf(stderr, "test #%i failed: %s\n", (tests), #x); \ | |||||
} \ | } \ | ||||
while(0) | while(0) | ||||