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