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"
27 #define _POSIX_C_SOURCE 2
29 #define _DEFAULT_SOURCE
36 #include <sys/types.h>
46 # include <langinfo.h>
49 #include "libmpdclient.h"
54 # include <sys/ioctl.h>
58 #if defined SIGWINCH && defined TIOCGWINSZ
59 # define HAVE_RESIZE 1
61 # define HAVE_RESIZE 0
62 # warn No SIGWINCH support.
66 /******************** Misc **************************************************/
68 # define __attribute__(x)
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;
117 parseArguments(argc
, argv
);
119 registerSignalHandlers();
121 atexit(disconnectFromMPD
);
125 timeout
= timeout
<= 30 ? timeout
* 2 : 60;
126 connected
= connectToMPD(timeout
> 10 ? 10 : timeout
);
131 connected
= getSong();
141 /******************** Global data *******************************************/
143 mpd_Connection
*conn
;
144 mpd_InfoEntity
*info
;
157 const wchar_t *format
;
160 size_t line_len
, line_capacity
;
163 const char *password
;
166 unsigned short background
;
168 volatile sig_atomic_t gotSignal
;
169 volatile sig_atomic_t width
;
172 static int done(void) {
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]"
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",
210 static void parseArguments(int argc
, char **argv
) {
211 const char *hostarg
= 0, *portarg
= 0, *format
;
216 argv0
= strrchr(argv
[0], '/');
217 argv0
= argv0
&& argv0
[1] ? argv0
+ 1 : *argv
;
221 if (argc
> 1 && !strcmp(argv
[1], "--help")) {
226 format
= DEFAULT_FORMAT
;
229 while ((opt
= getopt(argc
, argv
, "-hbBc:f:"))!=-1) {
232 case 'b': D
.background
= 1; break;
233 case 'B': D
.background
= 2; break;
237 unsigned long c
= strtoul(optarg
, &end
, 0);
238 die_on(c
< 3 || c
> UINT_MAX
|| *end
,
239 "invalid terminal width: %s", optarg
);
251 if (!hostarg
) { hostarg
= optarg
; break; }
252 if (!portarg
) { portarg
= optarg
; break; }
253 die_on(1, "invalid argument: %s", optarg
);
257 die_on(1, "invalid option: %c", optopt
);
262 /* Host, password and port */
264 hostarg
= getenv("MPD_HOST");
267 hostarg
= DEFAULT_HOST
;
269 end
= strchr(hostarg
, '@');
271 /* If '@' has been found we got host from either command line
272 * or environment and in both cases the string is
275 D
.password
= hostarg
;
281 portarg
= getenv("MPD_PORT");
284 end
= strchr(hostarg
, ':');
291 D
.port
= DEFAULT_PORT
;
293 unsigned long p
= strtoul(portarg
, &end
, 0);
294 die_on(p
<= 0 || p
> 0xffff || *end
, "invalid port: %s", portarg
);
300 D
.format
= wideFromMulti(format
);
305 if (!D
.cur
.columns
) {
306 handleResize(SIGWINCH
);
307 D
.cur
.columns
= D
.width
;
310 if (!D
.cur
.columns
) {
311 end
= getenv("COLUMNS");
313 unsigned long c
= strtoul(end
, &end
, 0);
314 if (c
>= 3 && c
<= UINT_MAX
&& !*end
) {
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) {
329 pdie_on(pid
< 0, "fork");
331 printf("[%ld]\n", (long)pid
);
338 static void registerSignalHandlers(void) {
340 signal(SIGWINCH
, handleResize
);
343 signal(SIGHUP
, handleSignal
);
346 signal(SIGINT
, handleSignal
);
349 signal(SIGQUIT
, handleSignal
);
352 signal(SIGTERM
, handleSignal
);
357 static void handleSignal(int sig
) {
362 signal(sig
, handleSignal
);
366 static void handleResize(int sig
) {
368 if (ioctl(1, TIOCGWINSZ
, &size
) >= 0 && size
.ws_col
) {
369 D
.width
= size
.ws_col
;
371 signal(sig
, handleResize
);
376 /******************** Connection handling ***********************************/
377 static int connectToMPD(unsigned timeout
) {
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
);
386 mpd_finishCommand(D
.conn
);
393 static void disconnectFromMPD(void) {
395 mpd_closeConnection(D
.conn
);
399 mpd_freeInfoEntity(D
.info
);
407 /******************** Song ingo retrival ************************************/
408 static int getSong(void) {
410 mpd_InfoEntity
*info
;
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
);
421 mpd_freeStatus(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
) {
438 mpd_freeInfoEntity(D
.info
);
443 mpd_sendCurrentSongCommand(D
.conn
);
447 info
= mpd_getNextInfoEntity(D
.conn
);
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
);
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;
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
)) {
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
);
502 if (doDisplay
|| D
.background
) {
506 } while (!done() && --secs
);
511 static void calculateHilightPos(void) {
513 D
.cur
.hilightPos
= D
.cur
.len
514 ? (wchar_t)D
.cur
.pos
* D
.cur
.columns
/ D
.cur
.len
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
;
532 hilightLeft
= D
.cur
.error
? 0 : D
.cur
.hilightPos
;
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;
552 outs((const wchar_t[]){L
' '}, 1);
553 if (!--cols
|| cols
== 1) {
557 if (D
.line_len
< cols
) {
558 outs(D
.line
, D
.line_len
);
560 outputScrolled(cols
);
563 if (hilightLeft
&& hilightLeft
< cols
) {
564 printf("%*s", (int)hilightLeft
, "");
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
;
585 outs(D
.line
+ scroll
, len
);
590 unsigned skip
= 0, len
= separator_len
;
591 if (scroll
> D
.line_len
) {
592 skip
= D
.cur
.scroll
- D
.line_len
;
600 outs(separator
+ skip
, len
);
605 outs(D
.line
, 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
);
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) {
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);
652 D
.line_len
= doFormat(D
.format
, 0, 0);
657 static const wchar_t *skipBraces(const wchar_t *p
) {
678 static const wchar_t *skipFormatting(const wchar_t *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
;
714 while (*p
&& *p
!= L
'%') {
719 *last
= *p
? p
+ 1 : p
;
723 ((len == sizeof #str - 1) && \
724 !memcmp(L"" #str, name, (sizeof #str - 1) * sizeof *name))
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);
746 } else if (!song
->file
) {
748 } else if (EQ(file
)) {
749 value
= strrchr(song
->file
, '/');
750 value
= value
? value
+ 1 : song
->file
;
751 } else if (EQ(filenoext
) || EQ(pathnoext
)) {
753 value
= strchr(song
->file
, '/');
754 value
= value
? value
+ 1 : song
->file
;
755 dot
= strchr(value
, '.');
756 value
= *name
== L
'p' ? song
->file
: value
;
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
;
768 return value
? appendUTFStr(off
, value
) : off
;
772 static size_t doFormat(const wchar_t *p
, size_t offset
, const wchar_t **last
) {
784 case L
'#': /* Escape */
786 off
= appendW(off
, p
, 1);
796 p
= skipFormatting(p
+ 1);
802 p
= skipFormatting(p
+ 1);
809 case L
'[': { /* Open group */
810 size_t o
= doFormat(p
+ 1, off
, &p
);
811 found
= found
|| o
!= off
;
816 case L
']': /* Close group */
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
;
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
);
840 static void ensureCapacity(size_t capacity
)
842 size_t c
= D
.line_capacity
;
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
);
857 while (c
< capacity
) {
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");
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
);
882 /******************** Terminal handling **************************************/
883 static void termDone(void) {
892 static void termInit(void) {
896 fputs("\33[?25l", stdout
);
900 static void termBeginLine(void) {
901 /* Begining of line */
903 fputs("\0337\33[1;1f\r", stdout
);
910 fputs("\33[30;1m", stdout
); /* dark grey */
911 } else if (hilightLeft
) {
912 fputs("\33[37;1;44m", stdout
); /* hilighted */
914 termNormal(); /* normal */
918 static void termNormal(void) {
919 fputs("\33[0m", stdout
); /* normal */
922 static void termEndLine(void) {
924 fputs("\33[0m\33[K\r", stdout
);
926 /* Get back to saved position */
928 fputs("\0338", stdout
);
937 /******************** Misc **************************************************/
939 static void _die(int perr
, const char *fmt
, ...)
941 fputs(argv0
, stderr
);
946 vfprintf(stderr
, fmt
, ap
);
950 fprintf(stderr
, ": %s", strerror(errno
));
958 /******************** Character encoding stuff ******************************/
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
),
970 # ifndef __STDC_ISO_10646__
971 # error Unsupported wchar_t encoding, requires ISO 10646 (Unicode)
973 #define initIconv() do ; while (0)
974 #define iconvDo(cd, in, inleft, chsize, outFunc, data) 0
978 static void initCodesets(void) {
979 setlocale(LC_CTYPE
, "");
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
;
1000 /* UTF-8 -> wchar_t */
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");
1010 /* Get current codeset */
1011 codeset
= nl_langinfo(CODESET
);
1012 if (!codeset
|| !*codeset
) {
1016 /* multibyte -> wchar_t */
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);
1026 iconv_wchar2str
= iconv_open(codeset
, "WCHAR_T");
1030 memcpy(buffer
, codeset
, len
);
1034 strcat(buffer
+ len
, *it
);
1035 iconv_wchar2str
= iconv_open(buffer
, "WCHAR_T");
1036 } while (iconv_wchar2str
== (iconv_t
)-1 && *++it
);
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
),
1047 if (cd
== (iconv_t
)-1) {
1052 iconv(cd
, 0, 0, 0, 0);
1054 size_t outleft
= sizeof buffer
;
1056 size_t ret
= iconv(cd
, &in
, &inleft
, &out
, &outleft
);
1058 if (out
!= buffer
) {
1059 outFunc(buffer
, out
- buffer
, data
);
1065 } else if (ret
!= (size_t)-1) {
1066 /* The whole string has been converted now reset the
1069 } else if (errno
== EILSEQ
) {
1070 /* Invalid sequence, skip character (should never
1074 } else if (errno
== EINVAL
) {
1075 /* Partial sequence at the end, break (should never
1088 static void _outsIconvFunc(const char *buffer
, size_t len
, 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
);
1101 printf("%.*s", ret
, buf
);
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
);
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
)) {
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;
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. */
1145 unsigned char ch
= *str
++;
1149 } else if (ch
< 0x80) {
1152 } else if ((ch
& 0xC0) == 0x80) {
1154 val
= (val
<< 6) | (ch
& 0x3f);
1155 if (!--seq
&& (val
< 0xD800 || val
> 0xDFFF)
1156 && val
<= (uint_least32_t)WCHAR_MAX
- WCHAR_MIN
) {
1159 } else if (ch
== 0xC0 || ch
== 0xC1 || ch
>= 0xF5) {
1161 } else if ((ch
& 0xE0) == 0xC0) {
1164 } else if ((ch
& 0xF0) == 0xE0) {
1167 } else if ((ch
& 0xF0) == 0xF0) {
1173 return out
- D
.line
;
1175 /* We should never be here */
1176 die_on(1, "internall error (%d)", __LINE__
);
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
);
1195 if (iconvDo(iconv_str2wchar
, (char *)str
, len
, 1,
1196 wideFromMultiIconvFunc
, &wb
)) {
1200 memset(&ps
, 0, sizeof ps
);
1205 size_t ret
= mbrtowc(wb
.buf
+ wb
.len
, str
, len
, &ps
);
1207 if (ret
== (size_t)-1) {
1208 /* EILSEQ, skip a single byte */
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 */
1215 } else if (++wb
.len
>= wb
.capacity
) {
1216 /* Got a wide char; store it and consume ret bytes */
1221 wb
.buf
= realloc(wb
.buf
, wb
.capacity
* sizeof *wb
.buf
);
1222 pdie_on(!wb
.buf
, "malloc");
1228 if (wb
.capacity
== wb
.len
+ 1) {
1231 wchar_t *ret
= realloc(wb
.buf
, (wb
.len
+ 1) * sizeof *wb
.buf
);
1232 return ret
? ret
: wb
.buf
;
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;
1248 } while (cap
< total
);
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
);
1257 wb
->buf
[wb
->len
] = 0;