Fix various compiler warnings
[tinyapps.git] / mpd-show.c
blobb7d30e2539d6f8430f79350fae335bd2b476fe12
1 /*
2 * Prints song MPD's curently playing.
3 * Copyright 2005-2011 by Michal Nazarewicz (mina86/AT/mina86.com)
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 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 * This is part of Tiny Applications Collection
19 * -> http://tinyapps.sourceforge.net/
23 #define APP_VERSION "0.19"
25 #define HAVE_ICONV 1
27 #define _POSIX_C_SOURCE 2
28 #define _BSD_SOURCE
29 #define _DEFAULT_SOURCE
31 #include <errno.h>
32 #include <stdint.h>
33 #include <wchar.h>
34 #include <unistd.h>
35 #include <sys/time.h>
36 #include <sys/types.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <stdlib.h>
40 #include <signal.h>
41 #include <limits.h>
42 #include <locale.h>
44 #if HAVE_ICONV
45 # include <iconv.h>
46 # include <langinfo.h>
47 #endif
49 #include "libmpdclient.h"
51 #ifdef SIGWINCH
52 # include <termios.h>
53 # ifndef TIOCGWINSZ
54 # include <sys/ioctl.h>
55 # endif
56 #endif
58 #if defined SIGWINCH && defined TIOCGWINSZ
59 # define HAVE_RESIZE 1
60 #else
61 # define HAVE_RESIZE 0
62 # warn No SIGWINCH support.
63 #endif
66 /******************** Misc **************************************************/
67 #if __GNUC__ <= 2
68 # define __attribute__(x)
69 #endif
71 const char *argv0 = "mpd-show";
73 static void _die(int perr, const char *fmt, ...)
74 __attribute__((noreturn, format(printf, 2, 3), cold));
75 #define die_on(cond, ...) do { if (cond) _die(0, __VA_ARGS__); } while (0)
76 #define pdie_on(cond, ...) do { if (cond) _die(1, __VA_ARGS__); } while (0)
79 /********** Defaults **********/
80 #define DEFAULT_HOST "localhost"
81 #define DEFAULT_PORT 6600
82 #define DEFAULT_COLUMNS 80
83 #define DEFAULT_FORMAT \
84 "[[[%artist% <&%album%> ]|%artist% - |<%album%> ]" \
85 "&[[%track%. &%title%]|%title%]]" \
86 "|[%track%. &%title%]|%title%|%filenoext%"
89 /******************** Terminal **********************************************/
90 static void termInit(void);
91 static void termBeginLine(void);
92 static void termNormal(void);
93 static void termEndLine(void);
96 /******************** Main functions ****************************************/
97 static void parseArguments(int argc, char **argv)
98 __attribute__((nonnull));
99 static void daemonize(void);
100 static void registerSignalHandlers(void);
102 static int connectToMPD(unsigned timeout);
103 static void disconnectFromMPD(void);
104 static int getSong(void);
105 static void display(unsigned secs);
107 static void initCodesets(void);
109 static int done(void);
110 static int error(void) __attribute__((pure));
113 int main(int argc, char **argv) {
114 unsigned timeout = 1, connected = 0;
116 initCodesets();
117 parseArguments(argc, argv);
118 daemonize();
119 registerSignalHandlers();
120 termInit();
121 atexit(disconnectFromMPD);
123 do {
124 if (!connected) {
125 timeout = timeout <= 30 ? timeout * 2 : 60;
126 connected = connectToMPD(timeout > 10 ? 10 : timeout);
129 if (connected) {
130 timeout = 1;
131 connected = getSong();
134 display(timeout);
135 } while (!done());
137 return 0;
141 /******************** Global data *******************************************/
142 struct {
143 mpd_Connection *conn;
144 mpd_InfoEntity *info;
146 struct state {
147 int error;
148 int state;
149 int songid;
150 int pos;
151 int len;
152 unsigned hilightPos;
153 unsigned scroll;
154 unsigned columns;
155 } cur, old;
157 const wchar_t *format;
159 wchar_t *line;
160 size_t line_len, line_capacity;
162 const char *host;
163 const char *password;
164 unsigned short port;
166 unsigned short background;
168 volatile sig_atomic_t gotSignal;
169 volatile sig_atomic_t width;
170 } D;
172 static int done(void) {
173 return D.gotSignal;
176 static int error(void) {
177 return !!(D.conn->error);
181 /******************** Initialisation ****************************************/
182 static void handleSignal(int sig) __attribute__((cold));
183 static void handleResize(int sig) __attribute__((cold));
185 static const wchar_t *wideFromMulti(const char *str) __attribute__((nonnull));
187 static void usage(void) __attribute__((noreturn));
188 static void usage(void)
190 printf("mpd-show " APP_VERSION " (c) 2005-2011 by Michal Nazarewicz (mina86/AT/mina86.com)\n"
191 "usage: %s [ <options> ] [ <host>[:<port> | <port> ]]\n"
192 " -b -B runs in background mode; -B also forks into background\n"
193 " -c<col> assumes <col>-char wide term [$COLUMNS or %d]"
194 #if HAVE_RESIZE
195 " (obsolete)"
196 #endif
197 "\n"
198 " -f<fmt> uses <fmt> for displaying song (see mpc(1)); supports following tags:\n",
199 argv0, DEFAULT_COLUMNS);
200 printf(" album, artist, comment, composer, date, dir, disc, file, filenoext,\n"
201 " genre, name, path, pathnoext, time, title, track and Y pseudo tag.\n"
202 " <host> host MPD is listening on optionally prefixed with '<password>@'\n"
203 " [$MPD_HOST or " DEFAULT_HOST "]\n"
204 " <port> port MPD is listening on [$MPD_PORT or %u]\n",
205 DEFAULT_PORT);
206 exit(0);
210 static void parseArguments(int argc, char **argv) {
211 const char *hostarg = 0, *portarg = 0, *format;
212 char *end;
213 int opt;
215 /* Program name */
216 argv0 = strrchr(argv[0], '/');
217 argv0 = argv0 && argv0[1] ? argv0 + 1 : *argv;
220 /* Help */
221 if (argc > 1 && !strcmp(argv[1], "--help")) {
222 usage();
225 /* Some defaults */
226 format = DEFAULT_FORMAT;
228 /* Get opts */
229 while ((opt = getopt(argc, argv, "-hbBc:f:"))!=-1) {
230 switch (opt) {
231 case 'h': usage();
232 case 'b': D.background = 1; break;
233 case 'B': D.background = 2; break;
235 /* Columns */
236 case 'c': {
237 unsigned long c = strtoul(optarg, &end, 0);
238 die_on(c < 3 || c > UINT_MAX || *end,
239 "invalid terminal width: %s", optarg);
240 D.cur.columns = c;
241 break;
244 /* Format */
245 case 'f':
246 format = optarg;
247 break;
249 /* An argument */
250 case 1:
251 if (!hostarg) { hostarg = optarg; break; }
252 if (!portarg) { portarg = optarg; break; }
253 die_on(1, "invalid argument: %s", optarg);
255 /* An error */
256 default:
257 die_on(1, "invalid option: %c", optopt);
262 /* Host, password and port */
263 if (!hostarg) {
264 hostarg = getenv("MPD_HOST");
266 if (!hostarg) {
267 hostarg = DEFAULT_HOST;
269 end = strchr(hostarg, '@');
270 if (end) {
271 /* If '@' has been found we got host from either command line
272 * or environment and in both cases the string is
273 * modifiable. */
274 *end = 0;
275 D.password = hostarg;
276 hostarg = end + 1;
278 D.host = hostarg;
280 if (!portarg) {
281 portarg = getenv("MPD_PORT");
283 if (!portarg) {
284 end = strchr(hostarg, ':');
285 if (end) {
286 *end = 0;
287 portarg = end + 1;
290 if (!portarg) {
291 D.port = DEFAULT_PORT;
292 } else {
293 unsigned long p = strtoul(portarg, &end, 0);
294 die_on(p <= 0 || p > 0xffff || *end, "invalid port: %s", portarg);
295 D.port = p;
299 /* Format */
300 D.format = wideFromMulti(format);
303 /* Columns */
304 #if HAVE_RESIZE
305 if (!D.cur.columns) {
306 handleResize(SIGWINCH);
307 D.cur.columns = D.width;
309 #endif
310 if (!D.cur.columns) {
311 end = getenv("COLUMNS");
312 if (end) {
313 unsigned long c = strtoul(end, &end, 0);
314 if (c >= 3 && c <= UINT_MAX && !*end) {
315 D.cur.columns = c;
319 if (!D.cur.columns) {
320 D.cur.columns = DEFAULT_COLUMNS;
322 D.width = D.cur.columns;
326 static void daemonize(void) {
327 if (D.background > 1) {
328 pid_t pid = fork();
329 pdie_on(pid < 0, "fork");
330 if (pid) {
331 printf("[%ld]\n", (long)pid);
332 _exit(0);
338 static void registerSignalHandlers(void) {
339 #if HAVE_RESIZE
340 signal(SIGWINCH, handleResize);
341 #endif
342 #ifdef SIGHUP
343 signal(SIGHUP , handleSignal);
344 #endif
345 #ifdef SIGINT
346 signal(SIGINT , handleSignal);
347 #endif
348 #ifdef SIGQUIT
349 signal(SIGQUIT , handleSignal);
350 #endif
351 #ifdef SIGTERM
352 signal(SIGTERM , handleSignal);
353 #endif
357 static void handleSignal(int sig) {
358 if (D.gotSignal) {
359 exit(1);
361 D.gotSignal = 1;
362 signal(sig, handleSignal);
365 #if HAVE_RESIZE
366 static void handleResize(int sig) {
367 struct winsize size;
368 if (ioctl(1, TIOCGWINSZ, &size) >= 0 && size.ws_col) {
369 D.width = size.ws_col;
371 signal(sig, handleResize);
373 #endif
376 /******************** Connection handling ***********************************/
377 static int connectToMPD(unsigned timeout) {
378 disconnectFromMPD();
380 D.conn = mpd_newConnection(D.host, D.port, timeout);
381 pdie_on(!D.conn, "connect");
383 if (D.password && !error()) {
384 mpd_sendPasswordCommand(D.conn, D.password);
385 if (!error()) {
386 mpd_finishCommand(D.conn);
390 return !error();
393 static void disconnectFromMPD(void) {
394 if (D.conn) {
395 mpd_closeConnection(D.conn);
396 D.conn = 0;
398 if (D.info) {
399 mpd_freeInfoEntity(D.info);
400 D.info = 0;
402 D.cur.songid = -1;
403 D.old.songid = -1;
407 /******************** Song ingo retrival ************************************/
408 static int getSong(void) {
409 mpd_Status *status;
410 mpd_InfoEntity *info;
412 /* Get status */
413 mpd_sendStatusCommand(D.conn);
414 if (error()) return 0;
415 status = mpd_getStatus(D.conn);
416 if (error()) return 0;
417 pdie_on(!status, "get status");
419 mpd_nextListOkCommand(D.conn);
420 if (error()) {
421 mpd_freeStatus(status);
422 return 0;
425 /* Copy status */
426 D.cur.state = status->state;
427 D.cur.songid = status->songid;
428 D.cur.pos = status->elapsedTime;
429 D.cur.len = status->totalTime;
430 mpd_freeStatus(status);
432 /* Same song, return */
433 if (D.cur.songid == D.old.songid) {
434 return 1;
437 if (D.info) {
438 mpd_freeInfoEntity(D.info);
439 D.info = 0;
442 /* Get song */
443 mpd_sendCurrentSongCommand(D.conn);
444 if (error()) {
445 return 0;
447 info = mpd_getNextInfoEntity(D.conn);
448 if (error()) {
449 return 0;
452 if (!info) {
453 return
454 D.cur.state == MPD_STATUS_STATE_PLAY ||
455 D.cur.state == MPD_STATUS_STATE_PAUSE;
458 if (info->type != MPD_INFO_ENTITY_TYPE_SONG) {
459 mpd_freeInfoEntity(info);
460 return 0;
463 D.info = info;
464 return 1;
468 /******************** Displaying data ***************************************/
469 static void formatLine(void);
470 static void calculateHilightPos(void);
471 static void output(void);
474 static void display(unsigned secs) {
475 static int first_time = 1;
477 do {
478 int doDisplay = 0;
480 D.cur.error = D.conn->error;
481 D.cur.columns = D.width;
483 #define CHANGED(field) (D.cur.field != D.old.field)
485 if (first_time || CHANGED(error) || CHANGED(songid)) {
486 first_time = 0;
487 formatLine();
488 doDisplay = 1;
491 doDisplay = doDisplay || CHANGED(columns);
492 if (doDisplay || CHANGED(pos) || CHANGED(len)) {
493 calculateHilightPos();
494 doDisplay = doDisplay || CHANGED(hilightPos);
497 doDisplay = doDisplay || CHANGED(state) || CHANGED(scroll);
499 #undef CHANGED
501 D.old = D.cur;
502 if (doDisplay || D.background) {
503 output();
505 usleep(1000000);
506 } while (!done() && --secs);
511 static void calculateHilightPos(void) {
512 if (!D.cur.error) {
513 D.cur.hilightPos = D.cur.len
514 ? (wchar_t)D.cur.pos * D.cur.columns / D.cur.len
515 : (unsigned)0;
520 /******************** Outputting data ***************************************/
521 static unsigned hilightLeft;
523 static void outs(const wchar_t *str, size_t len) __attribute__((nonnull));
525 static void outputScrolled(unsigned cols);
528 static void output(void) {
529 unsigned cols = D.cur.columns;
530 wchar_t tmp;
532 hilightLeft = D.cur.error ? 0 : D.cur.hilightPos;
534 termBeginLine();
536 /* State */
537 if (D.cur.error) {
538 tmp = '!';
539 } else {
540 switch (D.cur.state) {
541 case MPD_STATUS_STATE_STOP: tmp = L'\u25A0'; break;
542 case MPD_STATUS_STATE_PLAY: tmp = L'\u25BA'; break;
543 case MPD_STATUS_STATE_PAUSE: tmp = L' '; break;
544 default: tmp = L'?'; break;
547 outs(&tmp, 1);
548 if (!--cols) {
549 goto end;
552 outs((const wchar_t[]){L' '}, 1);
553 if (!--cols || cols == 1) {
554 goto end;
557 if (D.line_len < cols) {
558 outs(D.line, D.line_len);
559 } else {
560 outputScrolled(cols);
563 if (hilightLeft && hilightLeft < cols) {
564 printf("%*s", (int)hilightLeft, "");
565 termNormal();
568 end:
569 termEndLine();
573 static void outputScrolled(unsigned cols) {
574 static const wchar_t separator[7] = L" * * * ";
575 static const size_t separator_len =
576 sizeof separator / sizeof *separator;
578 const size_t scroll = D.cur.scroll;
580 if (scroll < D.line_len) {
581 unsigned len = D.line_len - D.cur.scroll;
582 if (len >= cols) {
583 len = cols - 1;
585 outs(D.line + scroll, len);
586 cols -= len;
589 if (cols != 1) {
590 unsigned skip = 0, len = separator_len;
591 if (scroll > D.line_len) {
592 skip = D.cur.scroll - D.line_len;
593 len -= skip;
596 if (len >= cols) {
597 len = cols - 1;
600 outs(separator + skip, len);
601 cols -= len;
604 if (cols != 1) {
605 outs(D.line, cols - 1);
606 cols = 1;
609 D.cur.scroll = (scroll + 1) % (D.line_len + separator_len);
613 static void _outs(const wchar_t *str, size_t len) __attribute__((nonnull));
615 static void outs(const wchar_t *str, size_t len) {
616 if (hilightLeft && len >= hilightLeft) {
617 _outs(str, hilightLeft);
618 len -= hilightLeft;
619 str += hilightLeft;
620 hilightLeft = 0;
621 termNormal();
624 if (len) {
625 _outs(str, len);
626 hilightLeft -= len;
631 /******************** Formatting line ***************************************/
632 static void ensureCapacity(size_t capacity);
634 static size_t appendUTF(size_t offset, const char *str, size_t len)
635 __attribute__((nonnull));
636 static size_t appendUTFStr(size_t offset, const char *str)
637 __attribute__((nonnull));
638 static size_t appendW(size_t offset, const wchar_t *str, size_t len)
639 __attribute__((nonnull));
641 static size_t doFormat(const wchar_t *p, size_t offset, const wchar_t **last)
642 __attribute__((nonnull(1)));
645 static void formatLine(void) {
646 if (D.cur.error) {
647 appendW(0, L"[", 1);
648 D.line_len = appendW(appendUTFStr(1, D.conn->errorStr), L"]", 1);
649 } else if (!D.info) {
650 D.line_len = appendW(0, L"[no song]", 10);
651 } else {
652 D.line_len = doFormat(D.format, 0, 0);
657 static const wchar_t *skipBraces(const wchar_t *p) {
658 unsigned stack = 1;
659 for(;; ++p) {
660 switch (*p) {
661 case L'[':
662 ++stack;
663 break;
665 case L']':
666 --stack;
667 if (!stack == 0) {
668 return p;
670 break;
672 case 0:
673 return p;
678 static const wchar_t *skipFormatting(const wchar_t *p) {
679 for (;; ++p) {
680 switch (*p) {
681 case L'[':
682 p = skipBraces(p);
683 if (!*p) {
684 return p;
686 break;
688 case L'#':
689 if (p[1]) {
690 ++p;
692 break;
694 case L']':
695 ++p;
696 /* FALL THROUGH */
698 case L'&':
699 case L'|':
700 case 0:
701 return p;
707 static size_t doFormatTag(const wchar_t *p, size_t off, const wchar_t **last) {
708 const mpd_Song *const song = D.info->info.song;
709 const wchar_t *const name = p;
710 const char *value;
711 size_t len;
713 /* Tag */
714 while (*p && *p != L'%') {
715 ++p;
717 len = p - name;
718 if (last) {
719 *last = *p ? p + 1 : p;
722 #define EQ(str) \
723 ((len == sizeof #str - 1) && \
724 !memcmp(L"" #str, name, (sizeof #str - 1) * sizeof *name))
726 value = 0;
727 if (EQ(Y)) return off - 1; /* special case */
728 else if (EQ(artist )) value = song->artist;
729 else if (EQ(title )) value = song->title;
730 else if (EQ(album )) value = song->album;
731 else if (EQ(track )) value = song->track;
732 else if (EQ(path )) value = song->file;
733 else if (EQ(name )) value = song->name;
734 else if (EQ(date )) value = song->date;
735 else if (EQ(genre )) value = song->genre;
736 else if (EQ(composer)) value = song->composer;
737 else if (EQ(disc )) value = song->disc;
738 else if (EQ(comment )) value = song->comment;
739 else if (EQ(time )) {
740 if (song->time != MPD_SONG_NO_TIME) {
741 static char buffer[16];
742 snprintf(buffer, sizeof buffer, "%2d:%02d",
743 song->time / 60, song->time % 60);
744 value = buffer;
746 } else if (!song->file) {
747 /* nop */
748 } else if (EQ(file )) {
749 value = strrchr(song->file, '/');
750 value = value ? value + 1 : song->file;
751 } else if (EQ(filenoext) || EQ(pathnoext)) {
752 char *dot;
753 value = strchr(song->file, '/');
754 value = value ? value + 1 : song->file;
755 dot = strchr(value, '.');
756 value = *name == L'p' ? song->file : value;
758 if (dot) {
759 return appendUTF(off, value, dot - value);
761 } else if (EQ(dir)) {
762 value = strrchr(song->file, '/');
763 return value ? appendUTF(off, song->file, value - song->file) : off;
766 #undef EQ
768 return value ? appendUTFStr(off, value) : off;
772 static size_t doFormat(const wchar_t *p, size_t offset, const wchar_t **last) {
773 size_t off = offset;
774 int found = 0;
776 for(;;) {
777 switch (*p) {
778 case 0 : /* NUL */
779 if (last) {
780 *last = p;
782 return off;
784 case L'#': /* Escape */
785 if (*++p) {
786 off = appendW(off, p, 1);
788 ++p;
789 break;
791 case L'|': /* OR */
792 if (!found) {
793 ++p;
794 off = offset;
795 } else {
796 p = skipFormatting(p + 1);
798 break;
800 case L'&': /* AND */
801 if (!found) {
802 p = skipFormatting(p + 1);
803 } else {
804 ++p;
805 found = 0;
807 break;
809 case L'[': { /* Open group */
810 size_t o = doFormat(p + 1, off, &p);
811 found = found || o != off;
812 off = o;
814 break;
816 case L']': /* Close group */
817 if (last) {
818 *last = p + 1;
820 return found ? off : offset;
822 case L'%': { /* Tag */
823 size_t o = doFormatTag(p + 1, off, &p);
824 found = found || o != off;
825 off = o + 1 == off /* special case */ ? off : o;
827 break;
829 default : { /* Copy variable chars */
830 const wchar_t *ch = p;
831 while (*++p && !wcschr(L"#%|&[]", *p)) { /* nop */ }
832 off = appendW(off, ch, p - ch);
834 break;
840 static void ensureCapacity(size_t capacity)
842 size_t c = D.line_capacity;
844 if (c >= capacity) {
845 return;
848 /* Check for overflow */
849 die_on(capacity * (size_t)2 < capacity
850 || capacity * sizeof *D.line < capacity
851 || capacity * (size_t)2 * sizeof *D.line < capacity,
852 "requested too much memory: %zu", capacity);
854 if (!c) {
855 c = 32;
857 while (c < capacity) {
858 c *= 2;
861 /* Don't care about loosing track of memory if realloc(3) fails,
862 * we're going to die anyways. */
863 D.line = realloc(D.line, c * sizeof *D.line);
864 pdie_on(!D.line, "malloc");
865 D.line_capacity = c;
869 static size_t appendUTFStr(size_t offset, const char *str) {
870 return appendUTF(offset, str, strlen(str));
874 static size_t appendW(size_t offset, const wchar_t *str, size_t len)
876 ensureCapacity(offset + len);
877 memcpy(D.line + offset, str, len * sizeof *str);
878 return offset + len;
882 /******************** Terminal handling **************************************/
883 static void termDone(void) {
884 /* Show cursor */
885 if (!D.background) {
886 puts("\33[?25h");
888 /* Reset colors */
889 puts("\33[0m\n");
892 static void termInit(void) {
893 atexit(termDone);
894 if (!D.background) {
895 /* Hide cursor */
896 fputs("\33[?25l", stdout);
900 static void termBeginLine(void) {
901 /* Begining of line */
902 if (D.background) {
903 fputs("\0337\33[1;1f\r", stdout);
904 } else {
905 putchar('\r');
908 /* Set color */
909 if (D.cur.error) {
910 fputs("\33[30;1m", stdout); /* dark grey */
911 } else if (hilightLeft) {
912 fputs("\33[37;1;44m", stdout); /* hilighted */
913 } else {
914 termNormal(); /* normal */
918 static void termNormal(void) {
919 fputs("\33[0m", stdout); /* normal */
922 static void termEndLine(void) {
923 /* Clear line */
924 fputs("\33[0m\33[K\r", stdout);
926 /* Get back to saved position */
927 if (D.background) {
928 fputs("\0338", stdout);
931 /* and flush */
932 fflush(stdout);
937 /******************** Misc **************************************************/
939 static void _die(int perr, const char *fmt, ...)
941 fputs(argv0, stderr);
942 if (fmt) {
943 va_list ap;
944 va_start(ap, fmt);
945 fputs(": ", stderr);
946 vfprintf(stderr, fmt, ap);
947 va_end(ap);
949 if (perr) {
950 fprintf(stderr, ": %s", strerror(errno));
952 putc('\n', stderr);
953 exit(1);
958 /******************** Character encoding stuff ******************************/
960 #if HAVE_ICONV
961 static iconv_t iconv_utf2wchar = (iconv_t)-1;
962 static iconv_t iconv_str2wchar = (iconv_t)-1;
963 static iconv_t iconv_wchar2str = (iconv_t)-1;
965 static void initIconv(void);
966 static int iconvDo(iconv_t cd, char *in, size_t inleft, size_t chsize,
967 void (*outFunc)(const char *buf, size_t len, void *data),
968 void *data);
969 #else
970 # ifndef __STDC_ISO_10646__
971 # error Unsupported wchar_t encoding, requires ISO 10646 (Unicode)
972 # endif
973 #define initIconv() do ; while (0)
974 #define iconvDo(cd, in, inleft, chsize, outFunc, data) 0
975 #endif
978 static void initCodesets(void) {
979 setlocale(LC_CTYPE, "");
980 initIconv();
986 #if HAVE_ICONV
987 static void initIconv(void) {
988 static const char *const wchar_codes[] = {
989 "WCHAR_T//TRANSLIT", "WCHAR_T//IGNORE", "WCHAR_T", 0
991 static const char *const code_suffix[] = {
992 "//TRANSLIT", "//IGNORE", "", 0
995 const char *const *it;
996 const char *codeset;
997 char *buffer;
998 size_t len;
1000 /* UTF-8 -> wchar_t */
1001 it = wchar_codes;
1002 do {
1003 iconv_utf2wchar = iconv_open(*it, "UTF-8");
1004 } while (iconv_utf2wchar == (iconv_t)-1 && *++it);
1005 #ifndef __STDC_ISO_10646__
1006 die_on(iconv_utf2wchar == (iconv_t)-1,
1007 "cannot handle UTF-8->wchar_t conversion; no iconv() support nor wchar_t uses unicode");
1008 #endif
1010 /* Get current codeset */
1011 codeset = nl_langinfo(CODESET);
1012 if (!codeset || !*codeset) {
1013 return;
1016 /* multibyte -> wchar_t */
1017 it = wchar_codes;
1018 do {
1019 iconv_str2wchar = iconv_open(*it, codeset);
1020 } while (iconv_str2wchar == (iconv_t)-1 && *++it);
1022 /* wchar_t -> multibyte */
1023 len = strlen(codeset);
1024 buffer = malloc(len + 11);
1025 if (!buffer) {
1026 iconv_wchar2str = iconv_open(codeset, "WCHAR_T");
1027 return;
1030 memcpy(buffer, codeset, len);
1032 it = code_suffix;
1033 do {
1034 strcat(buffer + len, *it);
1035 iconv_wchar2str = iconv_open(buffer, "WCHAR_T");
1036 } while (iconv_wchar2str == (iconv_t)-1 && *++it);
1038 free(buffer);
1042 static int iconvDo(iconv_t cd, char *in, size_t inleft, size_t chsize,
1043 void (*outFunc)(const char *buf, size_t len, void *data),
1044 void *data) {
1045 char buffer[256];
1047 if (cd == (iconv_t)-1) {
1048 return 0;
1051 inleft *= chsize;
1052 iconv(cd, 0, 0, 0, 0);
1053 for(;;) {
1054 size_t outleft = sizeof buffer;
1055 char *out = buffer;
1056 size_t ret = iconv(cd, &in, &inleft, &out, &outleft);
1058 if (out != buffer) {
1059 outFunc(buffer, out - buffer, data);
1062 if (!in) {
1063 /* We are done */
1064 break;
1065 } else if (ret != (size_t)-1) {
1066 /* The whole string has been converted now reset the
1067 * state */
1068 in = 0;
1069 } else if (errno == EILSEQ) {
1070 /* Invalid sequence, skip character (should never
1071 * happen) */
1072 in += chsize;
1073 inleft -= chsize;
1074 } else if (errno == EINVAL) {
1075 /* Partial sequence at the end, break (should never
1076 * happen) */
1077 break;
1081 return 1;
1083 #endif
1088 static void _outsIconvFunc(const char *buffer, size_t len, void *ignore) {
1089 (void)ignore;
1090 printf("%.*s", (int)len, buffer);
1093 static void _outs(const wchar_t *str, size_t len) {
1094 if (!iconvDo(iconv_wchar2str, (void *)str, len, sizeof *str,
1095 _outsIconvFunc, 0)) {
1096 char buf[MB_CUR_MAX];
1097 int ret = wctomb(NULL, 0);
1098 for (; len; --len, ++str) {
1099 ret = wctomb(buf, *str);
1100 if (ret > 0) {
1101 printf("%.*s", ret, buf);
1102 } else {
1103 putchar('?');
1110 static void appendIconvFunc(const char *buffer, size_t len, void *_off) {
1111 size_t *offsetp = _off;
1113 len /= sizeof *D.line;
1114 ensureCapacity(*offsetp + len);
1115 memcpy(D.line + *offsetp, buffer, len * sizeof *D.line);
1116 *offsetp += len;
1119 static size_t appendUTFFallback(size_t offset, const char *str, size_t len);
1121 static size_t appendUTF(size_t offset, const char *str, size_t len) {
1122 size_t off = offset;
1124 if (iconvDo(iconv_utf2wchar, (char *)str, len, 1, appendIconvFunc, &off)) {
1125 return off;
1127 return appendUTFFallback(offset, str, len);
1130 static size_t appendUTFFallback(size_t offset, const char *str, size_t len) {
1131 #ifdef __STDC_ISO_10646__
1132 uint_least32_t val = 0;
1133 unsigned seq = 0;
1134 wchar_t *out;
1136 ensureCapacity(offset + len);
1137 out = D.line + offset;
1139 /* http://en.wikipedia.org/wiki/UTF-8#Description */
1140 /* Invalid sequences are simply ignored. */
1141 /* This code does not check for 3- and 4-byte long sequences which
1142 * could be encoded using fewer bytes. Those are treated as valid
1143 * characters even though they shouldn't be. */
1144 while (len--) {
1145 unsigned char ch = *str++;
1147 if (!ch) {
1148 seq = 0;
1149 } else if (ch < 0x80) {
1150 seq = 0;
1151 *out++ = ch;
1152 } else if ((ch & 0xC0) == 0x80) {
1153 if (!seq) continue;
1154 val = (val << 6) | (ch & 0x3f);
1155 if (!--seq && (val < 0xD800 || val > 0xDFFF)
1156 && val <= (uint_least32_t)WCHAR_MAX - WCHAR_MIN) {
1157 *out = val;
1159 } else if (ch == 0xC0 || ch == 0xC1 || ch >= 0xF5) {
1160 seq = 0;
1161 } else if ((ch & 0xE0) == 0xC0) {
1162 seq = 1;
1163 val = ch & ~0x1F;
1164 } else if ((ch & 0xF0) == 0xE0) {
1165 seq = 2;
1166 val = ch & 0xF;
1167 } else if ((ch & 0xF0) == 0xF0) {
1168 seq = 3;
1169 val = ch & 0xF;
1173 return out - D.line;
1174 #else
1175 /* We should never be here */
1176 die_on(1, "internall error (%d)", __LINE__);
1177 #endif
1181 struct wbuffer {
1182 size_t len;
1183 size_t capacity;
1184 wchar_t *buf;
1187 static void
1188 wideFromMultiIconvFunc(const char *buffer, size_t len, void *_data);
1190 static const wchar_t *wideFromMulti(const char *str) {
1191 struct wbuffer wb = { 0, 0, 0 };
1192 size_t len = strlen(str);
1193 mbstate_t ps;
1195 if (iconvDo(iconv_str2wchar, (char *)str, len, 1,
1196 wideFromMultiIconvFunc, &wb)) {
1197 goto done;
1200 memset(&ps, 0, sizeof ps);
1201 wb.capacity = 32;
1202 goto realloc;
1204 while (len) {
1205 size_t ret = mbrtowc(wb.buf + wb.len, str, len, &ps);
1207 if (ret == (size_t)-1) {
1208 /* EILSEQ, skip a single byte */
1209 str += 1;
1210 len -= 1;
1211 } else if (!ret || (size_t)-2) {
1212 /* Reached NUL or an incomplete multibyte sequence at
1213 the end which we’re treating as end of string */
1214 break;
1215 } else if (++wb.len >= wb.capacity) {
1216 /* Got a wide char; store it and consume ret bytes */
1217 str += ret;
1218 len -= ret;
1219 wb.capacity *= 2;
1220 realloc:
1221 wb.buf = realloc(wb.buf, wb.capacity * sizeof *wb.buf);
1222 pdie_on(!wb.buf, "malloc");
1226 done:
1227 wb.buf[wb.len] = 0;
1228 if (wb.capacity == wb.len + 1) {
1229 return wb.buf;
1230 } else {
1231 wchar_t *ret = realloc(wb.buf, (wb.len + 1) * sizeof *wb.buf);
1232 return ret ? ret : wb.buf;
1237 static void
1238 wideFromMultiIconvFunc(const char *buffer, size_t len, void *_data) {
1239 struct wbuffer *wb = _data;
1241 len /= sizeof *wb->buf;
1243 if (wb->len + len >= wb->capacity) {
1244 size_t cap = wb->capacity ? wb->capacity : 16;
1245 size_t total = wb->len + len + 1;
1246 do {
1247 cap *= 2;
1248 } while (cap < total);
1250 wb->capacity = cap;
1251 wb->buf = realloc(wb->buf, cap * sizeof *wb->buf);
1252 pdie_on(!wb->buf, "malloc");
1255 memcpy(wb->buf + wb->len, buffer, len * sizeof *wb->buf);
1256 wb->len += len;
1257 wb->buf[wb->len] = 0;