1 /*
2 * MOC - music on console
3 * Copyright (C) 2005,2006 Damian Pietras <daper@daper.net>
4 *
5 * This program 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
8 * (at your option) any later version.
9 *
10 */
11
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15
16 /* _XOPEN_SOURCE is known to break compilation under OpenBSD. */
17 #ifndef OPENBSD
18 # define _XOPEN_SOURCE 500 /* for wcswidth() */
19 #endif
20
21 #include <stdio.h>
22 #include <stdarg.h>
23
24 #ifdef HAVE_ICONV
25 # include <iconv.h>
26 #endif
27 #ifdef HAVE_NL_TYPES_H
28 # include <nl_types.h>
29 #endif
30 #ifdef HAVE_LANGINFO_H
31 # include <langinfo.h>
32 #endif
33
34 #ifdef HAVE_NCURSESW_H
35 # include <ncursesw/curses.h>
36 #elif HAVE_NCURSES_H
37 # include <ncurses.h>
38 #else
39 # include <curses.h>
40 #endif
41 #include <assert.h>
42 #include <string.h>
43 #include <errno.h>
44 #include <wchar.h>
45
46 #include "common.h"
47 #include "log.h"
48 #include "options.h"
49 #include "utf8.h"
50 #include "rcc.h"
51
52 static char *terminal_charset = NULL;
53 static int using_utf8 = 0;
54
55 static iconv_t iconv_desc = (iconv_t)(-1);
56 static iconv_t files_iconv_desc = (iconv_t)(-1);
57 static iconv_t xterm_iconv_desc = (iconv_t)(-1);
58
59
60 /* Return a malloc()ed string converted using iconv().
61 * If for_file_name is not 0, use the conversion defined for file names.
62 * For NULL returns NULL. */
iconv_str(const iconv_t desc,const char * str)63 char *iconv_str (const iconv_t desc, const char *str)
64 {
65 char buf[512];
66 #ifdef FREEBSD
67 const char *inbuf;
68 #else
69 char *inbuf;
70 #endif
71 char *outbuf;
72 char *str_copy;
73 size_t inbytesleft, outbytesleft;
74 char *converted;
75
76 if (!str)
77 return NULL;
78 if (desc == (iconv_t)(-1))
79 return xstrdup (str);
80
81 inbuf = str_copy = xstrdup (str);
82 outbuf = buf;
83 inbytesleft = strlen(inbuf);
84 outbytesleft = sizeof(buf) - 1;
85
86 iconv (desc, NULL, NULL, NULL, NULL);
87
88 while (inbytesleft) {
89 if (iconv(desc, &inbuf, &inbytesleft, &outbuf,
90 &outbytesleft)
91 == (size_t)(-1)) {
92 if (errno == EILSEQ) {
93 inbuf++;
94 inbytesleft--;
95 if (!--outbytesleft) {
96 *outbuf = 0;
97 break;
98 }
99 *(outbuf++) = '#';
100 }
101 else if (errno == EINVAL) {
102 *(outbuf++) = '#';
103 *outbuf = 0;
104 break;
105 }
106 else if (errno == E2BIG) {
107 outbuf[sizeof(buf)-1] = 0;
108 break;
109 }
110 }
111 }
112
113 *outbuf = 0;
114 converted = xstrdup (buf);
115 free (str_copy);
116
117 return converted;
118 }
119
files_iconv_str(const char * str)120 char *files_iconv_str (const char *str)
121 {
122 return iconv_str (files_iconv_desc, str);
123 }
124
xterm_iconv_str(const char * str)125 char *xterm_iconv_str (const char *str)
126 {
127 return iconv_str (xterm_iconv_desc, str);
128 }
129
xwaddstr(WINDOW * win,const char * str)130 int xwaddstr (WINDOW *win, const char *str)
131 {
132 int res;
133
134 if (using_utf8)
135 res = waddstr (win, str);
136 else {
137 char *lstr = iconv_str (iconv_desc, str);
138
139 res = waddstr (win, lstr);
140 free (lstr);
141 }
142
143 return res;
144 }
145
146 /* Convert multi-byte sequence to wide characters. Change invalid UTF-8
147 * sequences to '?'. 'dest' can be NULL as in mbstowcs().
148 * If 'invalid_char' is not NULL it will be set to 1 if an invalid character
149 * appears in the string, otherwise 0. */
xmbstowcs(wchar_t * dest,const char * src,size_t len,int * invalid_char)150 static size_t xmbstowcs (wchar_t *dest, const char *src, size_t len,
151 int *invalid_char)
152 {
153 mbstate_t ps;
154 size_t count = 0;
155
156 assert (src != NULL);
157 assert (!dest || len > 0);
158
159 memset (&ps, 0, sizeof(ps));
160
161 if (dest)
162 memset (dest, 0, len * sizeof(wchar_t));
163
164 if (invalid_char)
165 *invalid_char = 0;
166
167 while (src && (len || !dest)) {
168 size_t res;
169
170 res = mbsrtowcs (dest, &src, len, &ps);
171 if (res != (size_t)-1) {
172 count += res;
173 src = NULL;
174 }
175 else {
176 size_t converted;
177
178 src++;
179 if (dest) {
180 converted = wcslen (dest);
181 dest += converted;
182 count += converted;
183 len -= converted;
184
185 if (len > 1) {
186 *dest = L'?';
187 dest++;
188 *dest = L'\0';
189 len--;
190 }
191 else
192 *(dest - 1) = L'\0';
193 }
194 else
195 count++;
196 memset (&ps, 0, sizeof(ps));
197
198 if (invalid_char)
199 *invalid_char = 1;
200 }
201 }
202
203 return count;
204 }
205
xwaddnstr(WINDOW * win,const char * str,const int n)206 int xwaddnstr (WINDOW *win, const char *str, const int n)
207 {
208 int res, width, inv_char;
209 wchar_t *ucs;
210 char *mstr, *lstr;
211 size_t size, num_chars;
212
213 assert (n > 0);
214 assert (str != NULL);
215
216 mstr = iconv_str (iconv_desc, str);
217
218 size = xmbstowcs (NULL, mstr, -1, NULL) + 1;
219 ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);
220 xmbstowcs (ucs, mstr, size, &inv_char);
221 width = wcswidth (ucs, WIDTH_MAX);
222
223 if (width == -1) {
224 size_t clidx;
225 for (clidx = 0; clidx < size - 1; clidx++) {
226 if (wcwidth (ucs[clidx]) == -1)
227 ucs[clidx] = L'?';
228 }
229 width = wcswidth (ucs, WIDTH_MAX);
230 inv_char = 1;
231 }
232
233 if (width > n) {
234 while (width > n)
235 width -= wcwidth (ucs[--size]);
236 ucs[size] = L'\0';
237 }
238
239 num_chars = wcstombs (NULL, ucs, 0);
240 lstr = (char *)xmalloc (num_chars + 1);
241
242 if (inv_char)
243 wcstombs (lstr, ucs, num_chars + 1);
244 else
245 snprintf (lstr, num_chars + 1, "%s", mstr);
246
247 res = waddstr (win, lstr);
248
249 free (ucs);
250 free (lstr);
251 free (mstr);
252 return res;
253 }
254
xmvwaddstr(WINDOW * win,const int y,const int x,const char * str)255 int xmvwaddstr (WINDOW *win, const int y, const int x, const char *str)
256 {
257 int res;
258
259 if (using_utf8)
260 res = mvwaddstr (win, y, x, str);
261 else {
262 char *lstr = iconv_str (iconv_desc, str);
263
264 res = mvwaddstr (win, y, x, lstr);
265 free (lstr);
266 }
267
268 return res;
269 }
270
xmvwaddnstr(WINDOW * win,const int y,const int x,const char * str,const int n)271 int xmvwaddnstr (WINDOW *win, const int y, const int x, const char *str,
272 const int n)
273 {
274 int res;
275
276 if (using_utf8)
277 res = mvwaddnstr (win, y, x, str, n);
278 else {
279 char *lstr = iconv_str (iconv_desc, str);
280
281 res = mvwaddnstr (win, y, x, lstr, n);
282 free (lstr);
283 }
284
285 return res;
286 }
287
xwprintw(WINDOW * win,const char * fmt,...)288 int xwprintw (WINDOW *win, const char *fmt, ...)
289 {
290 va_list va;
291 int res;
292 char *buf;
293
294 va_start (va, fmt);
295 buf = format_msg_va (fmt, va);
296 va_end (va);
297
298 if (using_utf8)
299 res = waddstr (win, buf);
300 else {
301 char *lstr = iconv_str (iconv_desc, buf);
302
303 res = waddstr (win, lstr);
304 free (lstr);
305 }
306
307 free (buf);
308
309 return res;
310 }
311
iconv_cleanup()312 static void iconv_cleanup ()
313 {
314 if (iconv_desc != (iconv_t)(-1)
315 && iconv_close(iconv_desc) == -1)
316 logit ("iconv_close() failed: %s", strerror(errno));
317 }
318
utf8_init()319 void utf8_init ()
320 {
321 #ifdef HAVE_NL_LANGINFO_CODESET
322 #ifdef HAVE_NL_LANGINFO
323 terminal_charset = xstrdup (nl_langinfo(CODESET));
324 assert (terminal_charset != NULL);
325
326 if (!strcmp(terminal_charset, "UTF-8")) {
327 #ifdef HAVE_NCURSESW
328 logit ("Using UTF8 output");
329 using_utf8 = 1;
330 #else /* HAVE_NCURSESW */
331 terminal_charset = xstrdup ("US-ASCII");
332 logit ("Using US-ASCII conversion - compiled without libncursesw");
333 #endif /* HAVE_NCURSESW */
334 }
335 else
336 logit ("Terminal character set: %s", terminal_charset);
337 #else /* HAVE_NL_LANGINFO */
338 terminal_charset = xstrdup ("US-ASCII");
339 logit ("Assuming US-ASCII terminal character set");
340 #endif /* HAVE_NL_LANGINFO */
341 #endif /* HAVE_NL_LANGINFO_CODESET */
342
343 if (!using_utf8 && terminal_charset) {
344 iconv_desc = iconv_open (terminal_charset, "UTF-8");
345 if (iconv_desc == (iconv_t)(-1))
346 logit ("iconv_open() failed: %s", strerror(errno));
347 }
348
349 if (options_get_int ("FileNamesIconv"))
350 files_iconv_desc = iconv_open ("UTF-8", "");
351
352 if (options_get_int ("NonUTFXterm"))
353 xterm_iconv_desc = iconv_open ("", "UTF-8");
354 }
355
utf8_cleanup()356 void utf8_cleanup ()
357 {
358 if (terminal_charset)
359 free (terminal_charset);
360 iconv_cleanup ();
361 }
362
363 /* Return the number of columns the string occupies when displayed. */
strwidth(const char * s)364 size_t strwidth (const char *s)
365 {
366 wchar_t *ucs;
367 size_t size;
368 size_t width;
369
370 assert (s != NULL);
371
372 size = xmbstowcs (NULL, s, -1, NULL) + 1;
373 ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);
374 xmbstowcs (ucs, s, size, NULL);
375 width = wcswidth (ucs, WIDTH_MAX);
376 free (ucs);
377
378 return width;
379 }
380
381 /* Return a malloc()ed string containing the tail of 'str' up to a
382 * maximum of 'len' characters (in columns occupied on the screen). */
xstrtail(const char * str,const int len)383 char *xstrtail (const char *str, const int len)
384 {
385 wchar_t *ucs;
386 wchar_t *ucs_tail;
387 size_t size;
388 int width;
389 char *tail;
390
391 assert (str != NULL);
392 assert (len > 0);
393
394 size = xmbstowcs(NULL, str, -1, NULL) + 1;
395 ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);
396 xmbstowcs (ucs, str, size, NULL);
397 ucs_tail = ucs;
398
399 width = wcswidth (ucs, WIDTH_MAX);
400 assert (width >= 0);
401
402 while (width > len)
403 width -= wcwidth (*ucs_tail++);
404
405 size = wcstombs (NULL, ucs_tail, 0) + 1;
406 tail = (char *)xmalloc (size);
407 wcstombs (tail, ucs_tail, size);
408
409 free (ucs);
410
411 return tail;
412 }
413