diff --git a/.gitignore b/.gitignore index 3cfb2e8..84d6204 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ INSTALL caca-config # Testsuite binaries caca/t/bench +caca/t/bug-setlocale caca/t/caca-test caca/t/simple caca/t/*.log diff --git a/caca/caca.c b/caca/caca.c index 2a453ff..a4dbb5e 100644 --- a/caca/caca.c +++ b/caca/caca.c @@ -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 * 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(). * * If an error occurs, NULL is returned and \b errno is set accordingly: diff --git a/caca/driver/ncurses.c b/caca/driver/ncurses.c index 7171e91..832f911 100644 --- a/caca/driver/ncurses.c +++ b/caca/driver/ncurses.c @@ -228,6 +228,9 @@ static int ncurses_init_graphics(caca_display_t *dp) COLOR_WHITE + 8 }; +#if defined HAVE_LOCALE_H + char const *old_locale; +#endif mmask_t newmask; int fg, bg, max; @@ -242,13 +245,16 @@ static int ncurses_init_graphics(caca_display_t *dp) signal(SIGWINCH, sigwinch_handler); #endif -#if defined HAVE_LOCALE_H - setlocale(LC_ALL, ""); -#endif - _caca_set_term_title("caca for ncurses"); +#if defined HAVE_LOCALE_H + old_locale = setlocale(LC_CTYPE, ""); +#endif initscr(); +#if defined HAVE_LOCALE_H + setlocale(LC_CTYPE, old_locale); +#endif + keypad(stdscr, TRUE); nonl(); raw(); diff --git a/caca/driver/x11.c b/caca/driver/x11.c index 899a6c9..e3ef434 100644 --- a/caca/driver/x11.c +++ b/caca/driver/x11.c @@ -106,13 +106,22 @@ static int x11_init_graphics(caca_display_t *dp) dp->resize.allow = 0; #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 dp->drv.p->dpy = XOpenDisplay(NULL); if(dp->drv.p->dpy == NULL) return -1; +#if defined HAVE_LOCALE_H + setlocale(LC_CTYPE, old_locale); +#endif + #if defined HAVE_GETENV fonts[0] = getenv("CACA_FONT"); if(fonts[0] && *fonts[0]) diff --git a/caca/t/Makefile.am b/caca/t/Makefile.am index ea78dac..a4aee7b 100644 --- a/caca/t/Makefile.am +++ b/caca/t/Makefile.am @@ -10,7 +10,7 @@ endif 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 \ $(doxygen_tests) $(cppunit_tests) @@ -21,6 +21,9 @@ simple_LDADD = ../libcaca.la bench_SOURCES = bench.c 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_CXXFLAGS = $(CPPUNIT_CFLAGS) caca_test_LDADD = ../libcaca.la $(CPPUNIT_LIBS) diff --git a/caca/t/bug-setlocale.c b/caca/t/bug-setlocale.c new file mode 100644 index 0000000..c469c13 --- /dev/null +++ b/caca/t/bug-setlocale.c @@ -0,0 +1,63 @@ +/* + * bug-setlocale: unit test for wrong setlocale() calls + * Copyright (c) 2016 Sam Hocevar + * 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 +# include +# include +#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; +} + diff --git a/caca/t/simple.c b/caca/t/simple.c index 6320609..b8666ce 100644 --- a/caca/t/simple.c +++ b/caca/t/simple.c @@ -26,7 +26,7 @@ if((x)) \ passed++; \ else \ - fprintf(stderr, "test #%i failed\n", (tests)); \ + fprintf(stderr, "test #%i failed: %s\n", (tests), #x); \ } \ while(0)