├── README ├── dmenu-pango-imlib ├── PKGBUILD └── README ├── linopen ├── PKGBUILD ├── README ├── linopen.conf └── open ├── lolimpd ├── PKGBUILD ├── README ├── lolimpd.c └── lolimpdnu └── xcmenu-git └── PKGBUILD /README: -------------------------------------------------------------------------------- 1 | Various PKGBUILDS 2 | -------------------------------------------------------------------------------- /dmenu-pango-imlib/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Jari Vetoniemi 2 | # dmenu with pango and imlib support 3 | 4 | _gitname=dmenu-pango-imlib 5 | pkgname=dmenu-pango-imlib-git 6 | pkgver=4.5.3 7 | pkgrel=1 8 | pkgdesc="Dynamic X menu - with pango and imlib support" 9 | url="https://github.com/Cloudef/dmenu-pango-imlib" 10 | arch=('i686' 'x86_64') 11 | license=('MIT') 12 | depends=('sh' 'libxinerama' 'libxft' 'pango' 'imlib2') 13 | conflicts=('dmenu' 'dmenu-xft') 14 | provides=('dmenu' 'dmenu-pango-imlib') 15 | source=(git://github.com/Cloudef/dmenu-pango-imlib) 16 | md5sums=(SKIP) 17 | 18 | pkgver() { cd "$_gitname" && git describe | sed 's/^v//; s/-/./g'; } 19 | 20 | build() { 21 | cd "$_gitname" 22 | make 23 | } 24 | 25 | package() { 26 | cd "$_gitname" 27 | make DESTDIR=$pkgdir PREFIX=/usr install 28 | install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" 29 | } 30 | 31 | # vim: set ts=8 sw=4 tw=0 : 32 | -------------------------------------------------------------------------------- /dmenu-pango-imlib/README: -------------------------------------------------------------------------------- 1 | dmenu with pango && imlib support 2 | https://github.com/Cloudef/dmenu-pango-imlib 3 | -------------------------------------------------------------------------------- /linopen/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Cloudef 2 | # Intelligent and suckless replacement for xdg-open 3 | 4 | pkgname=linopen 5 | pkgver=1.6 6 | pkgrel=1 7 | pkgdesc='Intelligent and suckless replacement for xdg-open' 8 | arch=('any') 9 | url='http://cloudef.eu' 10 | license=('WTFPL') 11 | backup=('etc/linopen.conf') 12 | source=('open' 'linopen.conf') 13 | 14 | # Set this 1 to symlink /usr/bin/open to /usr/bin/xdg-open 15 | # and conflict with xdg-utils 16 | PROVIDE_XDG_OPEN=1 17 | 18 | if [ $PROVIDE_XDG_OPEN -eq 1 ]; then 19 | provides=('xdg-utils') 20 | conflicts=('xdg-utils') 21 | fi 22 | 23 | package() { 24 | install -Dm755 "$srcdir/open" "${pkgdir}/usr/bin/open" 25 | install -Dm644 "$srcdir/linopen.conf" "${pkgdir}/etc/linopen.conf" 26 | 27 | if [ $PROVIDE_XDG_OPEN -eq 1 ]; then 28 | ln -s "/usr/bin/open" "$pkgdir/usr/bin/xdg-open" 29 | fi 30 | } 31 | 32 | md5sums=('15a6b084258db3a5d15eda3e65295445' 33 | '37eef2ae627bb018b0638d6fefde7780') 34 | 35 | # vim: set ts=8 sw=3 tw=0 : 36 | -------------------------------------------------------------------------------- /linopen/README: -------------------------------------------------------------------------------- 1 | Intelligent and suckless xdg-open replacement. 2 | Inspired from mimi: https://github.com/taylorchu/mimi 3 | -------------------------------------------------------------------------------- /linopen/linopen.conf: -------------------------------------------------------------------------------- 1 | # 2 | # linopen configuration 3 | # enviroiment variables can be used 4 | # 5 | 6 | # Specify your terminal emulator here 7 | # for terminal support. 8 | terminal=xterm 9 | 10 | # Specify all programs you want to 11 | # open in terminal like this: 12 | interm=vim 13 | 14 | # There are 4 ways to match filetypes. 15 | # The following examples are in the order 16 | # which linopen chooses the program as well. 17 | 18 | # 1. File extension 19 | # .png:sxiv 20 | # .mp4:mplayer 21 | # .txt:vim 22 | 23 | # 2. Mime type 24 | # image/png:sxiv 25 | # video/mp4:mplayer 26 | # text/plain:vim 27 | 28 | # 3. Mime category 29 | image:sxiv 30 | video:mplayer 31 | audio:mplayer->interm # you can also specify the interm rule explictly after '->' 32 | text:vim 33 | 34 | # 4. Regexp 35 | # Match some protocols by default 36 | ?'^http:\/\/':$BROWSER 37 | ?'^https:\/\/':$BROWSER 38 | ?'^www.':$BROWSER 39 | ?'^dvd:\/\/':mplayer 40 | ?'^cdda:\/\/':mplayer->interm 41 | 42 | # Directory rule for directories 43 | # ideally you want to use file manager 44 | # here if you are a GUI user. 45 | directory:echo 46 | 47 | # Default rule just echoes back whatever 48 | # was feed. If you are using DE you could 49 | # just map a file manager here and it would 50 | # integrate with your system. 51 | default:echo 52 | -------------------------------------------------------------------------------- /linopen/open: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # replaces xdg-open 3 | # inspired from mimi: https://github.com/taylorchu/mimi 4 | 5 | _LINOPEN_CFGRC="$HOME/.linopenrc" 6 | _LINOPEN_CFGSYS="/etc/linopen.conf" 7 | _LINOPEN_CFGARG= 8 | 9 | # helpers 10 | err() { echo "$@"; exit 1; } 11 | usage() { echo "usage: $(basename $0) [-c ] [file]"; } 12 | 13 | # pipe configuration 14 | getconfig() { 15 | # '#' are comments in configuration :) 16 | [[ -f "$_LINOPEN_CFGARG" ]] && { egrep -v "^(#|$)" "$_LINOPEN_CFGARG"; return; } 17 | [[ -f "$_LINOPEN_CFGRC" ]] && { egrep -v "^(#|$)" "$_LINOPEN_CFGRC"; return; } 18 | [[ -f "$_LINOPEN_CFGSYS" ]] && { egrep -v "^(#|$)" "$_LINOPEN_CFGSYS"; return; } 19 | } 20 | 21 | # match regexp 22 | # $1 = filename 23 | match_regexp() { 24 | open_with='' 25 | getconfig | grep "^?" | while read cf; do 26 | exp="$(echo "$cf" | sed "s/?'\(.*\)':.*/\1/")" 27 | [[ -n "$(echo "$@" | grep "$exp")" ]] && { 28 | open_with="$(echo "$cf" | sed "s/?'.*':\(.*\)/\1/")"; 29 | } 30 | # exit the loop once a match is encountered 31 | [[ ! "${open_with}" = '' ]] && echo "${open_with}" && exit 0 32 | done 33 | } 34 | 35 | # get terminal emulator from configuration 36 | get_term() { 37 | # inside echo (strip whitespace) 38 | echo $(getconfig | grep -w "^terminal" | head -1 | cut -d = -f 2 | sed 's/[#].*//') 39 | } 40 | 41 | # check, if program needs terminal 42 | # $1 = program 43 | needs_term() { 44 | [[ -n "$(getconfig | grep -w "^interm=$@")" ]] && 45 | return 0 || return 1 46 | } 47 | 48 | # check, if we need fork for terminal program 49 | needs_fork() { 50 | # is either ran from shell or from script 51 | if [[ "$(ps -o stat= -p $PPID)" == *S* ]]; then 52 | # + == not backgrounded 53 | [[ "$(ps -o stat= -p $$)" == *+* ]] && 54 | return 1 || return 0 55 | fi 56 | } 57 | 58 | # launch file with correct program 59 | # $1 = filename 60 | # $2 = forced program 61 | launch() { 62 | local program="$2" 63 | local interm=0 64 | 65 | # is directory? 66 | [[ -d "$1" ]] && { 67 | program="$(getconfig | grep "^directory:" | cut -d : -f 2)" 68 | } 69 | 70 | # if not directory, and file doesn't exist 71 | # try matching regexp 72 | [[ ! -n "$program" ]] && [[ ! -f "$1" ]] && 73 | regexp="$(match_regexp "$1")" 74 | 75 | # if file not found and no matching regexp 76 | [[ ! -f "$1" ]] && [[ ! -d "$1" ]] && [[ ! -n "$regexp" ]] && { 77 | err "file does not exist: $1"; 78 | } || { 79 | [[ -n "$program" ]] || program="$regexp" 80 | } 81 | 82 | # test against extension 83 | [[ -n "$program" ]] || { 84 | local ext=${1##*.}; 85 | program="$(getconfig | grep "^.$ext:" | cut -d : -f 2)" 86 | } 87 | 88 | # test against whole mime type 89 | [[ -n "$program" ]] || program="$(getconfig | \ 90 | grep "^$(file -L -b --mime-type "$1"):" | head -1 | cut -d : -f 2)" 91 | 92 | # test against video/, text/ (first part of mime type) 93 | [[ -n "$program" ]] || program="$(getconfig | \ 94 | grep "^$(file -L -b --mime-type "$1" | sed 's/\(.*\)\/.*/\1:/')" | \ 95 | head -1 | cut -d : -f 2)" 96 | 97 | # test against regexp 98 | [[ -n "$program" ]] || program="$(match_regexp "$1")" 99 | 100 | # test against default as last try 101 | [[ -n "$program" ]] || program="$(getconfig | grep "^default:" | \ 102 | head -1 | cut -d : -f 2)" 103 | 104 | # check arguments 105 | [[ -n "$(echo "$program" | grep "\->interm")" ]] && interm=1 106 | 107 | # sed out the arguments || comments 108 | program="$(echo "$program" | sed 's/->.*//;s/[#].*//')" 109 | program="$(echo $program)" # strip leading&&trailing whitespace 110 | 111 | # check if program is enviroiment variable 112 | [[ -n "$(echo "$program" | grep '^\$')" ]] && { 113 | program="$(echo "$program" | sed 's/^\$//')" 114 | program="$(env | grep "$program" | head -1 | cut -d = -f 2)" 115 | } 116 | 117 | # no program found 118 | [[ -n "$program" ]] || 119 | err "could not find program for '$(basename $1)', check your configuration" 120 | 121 | # check if we need term or fork 122 | if [[ $interm -eq 1 ]] || needs_term "$program"; then 123 | if needs_fork; then 124 | # open in new terminal 125 | "$(get_term)" -e "$program" "$1" & 126 | else 127 | # open in current terminal 128 | $program "$1" 129 | fi 130 | else 131 | # echo program is exception here 132 | [[ "$program" == "echo" ]] && { echo "$1"; } || { 133 | # open in background (redirects everything to /dev/null) 134 | $program "$1" &> /dev/null & 135 | } 136 | fi 137 | } 138 | 139 | # handle file 140 | # $1 = filename 141 | handle() { 142 | local filename="$@" 143 | [[ -n "$(echo $filename | grep "^file://")" ]] && { 144 | filename="${filename##file://}" 145 | } 146 | launch "$filename" 147 | } 148 | 149 | main() { 150 | # print usage if no arguments 151 | [[ -n "$@" ]] || { usage; exit 1; } 152 | 153 | # check configuration argument 154 | [[ "$1" == "-c" ]] && { 155 | shift 1; _LINOPEN_CFGARG="$1"; 156 | [[ -f "$_LINOPEN_CFGARG" ]] || 157 | err "no configuration exists: $_LINOPEN_CFGARG" 158 | shift 1 159 | } 160 | 161 | # check that everything is ok 162 | [[ -n "$(getconfig)" ]] || 163 | err "no configuration exists: /etc/linopen.conf || ~/.linopenrc or the file is empty" 164 | [[ -n "$(getconfig | grep "^default:")" ]] || 165 | err "rule must exist in configuration: 'default:'" 166 | 167 | # handle arguments 168 | while [[ -n "$1" ]]; do 169 | handle "$1" 170 | shift || break 171 | done 172 | } 173 | main "$@" 174 | 175 | # vim: set ts=8 sw=3 tw=0 : 176 | -------------------------------------------------------------------------------- /lolimpd/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Cloudef 2 | # Dmenu client for MPD 3 | 4 | pkgname=lolimpd 5 | pkgver=1.9 6 | pkgrel=1 7 | pkgdesc='Dmenu client for MPD' 8 | arch=('i686' 'x86_64') 9 | url='http://cloudef.eu' 10 | license=('WTFPL') 11 | depends=('mpd' 'dmenu-pango-imlib') 12 | optdepends=('dmenu') 13 | makedepends=('gcc') 14 | source=('lolimpd.c' 'lolimpdnu') 15 | 16 | # Debug build? 17 | DEBUG=0 18 | 19 | package() { 20 | [[ $DEBUG -eq 0 ]] || gcc -g "$srcdir/lolimpd.c" -lmpdclient -o "$srcdir/lolimpd" 21 | [[ $DEBUG -eq 0 ]] && gcc -DNDEBUG -s -Os "$srcdir/lolimpd.c" -lmpdclient -o "$srcdir/lolimpd" 22 | install -Dm775 "$srcdir/lolimpdnu" "${pkgdir}/usr/bin/lolimpdnu" 23 | install -Dm755 "$srcdir/lolimpd" "${pkgdir}/usr/bin/lolimpd" 24 | } 25 | md5sums=('e1ed314d5692ea73d5ec6ae456de9cb3' 26 | 'd505ebd4ec6316eca6621f7e0893a30a') 27 | 28 | # vim: set ts=8 sw=3 tw=0 : 29 | -------------------------------------------------------------------------------- /lolimpd/README: -------------------------------------------------------------------------------- 1 | Dmenu client for MPD 2 | 3 | Provides lolimpd CLI program that can act as simple mpc replacement. 4 | It's main function is to be backend for the dmenu frontend however. 5 | 6 | Before compiling lolimpd, you should change MUSIC_DIR (line 17) to be same as the database location in your mpd configuration. 7 | It's possible to leave it empty, in case 'lolimpd add' and local cover art support is not needed. 8 | 9 | Usage: 10 | 11 | lolimpd - print current playing song (--with-cover argument to include cover art) 12 | 13 | lolimpd add - add inside the MUSIC_DIR defined in lolimpc.c 14 | tries to detect and load playlists/songs automatically from directory 15 | sorts files that were not playlist automatically 16 | 17 | lolimpd ls - list all songs in playlist (--with-cover argument to include cover art) 18 | lolimpd clear - clear playlist 19 | lolimpd play - start playing current song 20 | lolimpd play - tries to search the song using dmenu like matching and start playing it 21 | lolimpd stop - stop playback 22 | lolimpd pause - pause playback 23 | lolimpd toggle - pause/play toggle 24 | lolimpd next/prev - next/prev song in queue 25 | lolimpd repeat - toggle playlist repeat 26 | lolimpd single - toggle single play 27 | lolimpd consume - toggle consume mode 28 | lolimpd crossfade - similar to mpc crossfade 29 | 30 | 31 | lolimpdnu is the lolimpd frontend using dmenu. 32 | 33 | By default lolimpdnu is designed to be used with single big playlist containing all songs. 34 | Incase no big playlists are being used or multiple playlists are swapped the NOCACHE parameter at line 16 should be set to true. 35 | This disables lolimpdnu song cache for faster startup. 36 | 37 | For cover art and selection index store support dmenu-pango-imlib should be used. 38 | Incase no coverart or selection index store support is not needed, set the HAS_IMLIB_DMENU to false at line 8. 39 | 40 | Usage: 41 | 42 | lolimpdnu - lists songs using dmenu. optional song filter can be specified 43 | lolimpdnu -c - clear lolimpdnu cache file if used. use when the playlist has changed 44 | lolimpdnu -g - generate thumbnails for all album cover arts (needs dmenu-pango-imlib) 45 | 46 | -------------------------------------------------------------------------------- /lolimpd/lolimpd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* really dirty code :) 14 | * it's funny how most useful tools are always like that. 15 | * will cleanup when I feel like it. */ 16 | 17 | #define MPD_TIMEOUT 3000 18 | #define MPD_OUTPUT_BUFFER 16384 19 | #define MUSIC_DIR "/mnt/東方/music" 20 | #define SEPERATOR " >> " 21 | #define ARG_WITH_COVER "--with-cover" 22 | 23 | #define _D "\1-\2!\1-\5" 24 | #define ERR_SNTX _D" \3%d \2[\4%s \5:: \4%s\2]\5:" 25 | #ifndef NDEBUG 26 | #define OUT(x,...) \ 27 | _prnt(stderr, _D" "x, ##__VA_ARGS__); 28 | #else 29 | #define OUT(x,...) ; 30 | #endif 31 | #define ERR(x,...) \ 32 | _prnt(stderr, ERR_SNTX" "x, __LINE__, __FILE__, __func__, ##__VA_ARGS__); 33 | #define MEMERR(x) \ 34 | ERR("Could not allocate '%zu bytes' for \"%s\"", sizeof(x), __STRING(x)); 35 | #define MPDERR() \ 36 | if (mpd && mpd->connection && \ 37 | mpd_connection_get_error(mpd->connection) != MPD_ERROR_SUCCESS) \ 38 | ERR("MPD error: (%d) %s", mpd_connection_get_error(mpd->connection), \ 39 | mpd_connection_get_error_message(mpd->connection)); 40 | 41 | /* files are not loaded, if playlist found from same directory */ 42 | static const char *fileFormats[] = { 43 | ".flac", ".tta", ".ogg", ".mp3", ".m4a", ".wav", NULL 44 | }; 45 | 46 | /* playlist we want to load */ 47 | static const char *playlistFormats[] = { 48 | ".cue", ".m3u", ".pls", NULL 49 | }; 50 | 51 | /* mpd server definition */ 52 | typedef struct mpdserver { 53 | const unsigned int *version; 54 | } mpdserver; 55 | 56 | /* mpd queue definition */ 57 | typedef struct mpdqueue { 58 | unsigned int version; 59 | } mpdqueue; 60 | 61 | enum { 62 | PLAY_REPEAT = 0x1, 63 | PLAY_RANDOM = 0x2, 64 | PLAY_SINGLE = 0x4, 65 | PLAY_CONSUME = 0x8 66 | }; 67 | 68 | /* mpd state definition */ 69 | typedef struct mpdstate { 70 | unsigned int id; 71 | unsigned int queuever; 72 | unsigned int queuelen; 73 | unsigned int crossfade; 74 | unsigned int playmode; 75 | int volume; 76 | int song; 77 | enum mpd_state state; 78 | } mpdstate; 79 | 80 | /* mpd client definition */ 81 | typedef struct mpdclient { 82 | const char *host; 83 | unsigned int port; 84 | mpdstate state; 85 | mpdqueue queue; 86 | mpdserver server; 87 | struct mpd_connection *connection; 88 | struct mpd_status *status; 89 | } mpdclient; 90 | static mpdclient *mpd = NULL; 91 | 92 | /* colors */ 93 | static const char *colors[] = { 94 | "\33[31m", /* red */ 95 | "\33[32m", /* green */ 96 | "\33[34m", /* blue */ 97 | "\33[33m", /* yellow */ 98 | "\33[37m", /* white */ 99 | "\33[0m", /* normal */ 100 | }; 101 | 102 | typedef int (*mpdoptfunc)(int argc, char **argv); 103 | 104 | typedef struct mpdopt { 105 | const char *arg; 106 | char argc; 107 | mpdoptfunc func; 108 | } mpdopt; 109 | 110 | #define REGISTER_OPT(x) static int x(int argc, char **argv) 111 | REGISTER_OPT(opt_add); 112 | REGISTER_OPT(opt_clear); 113 | REGISTER_OPT(opt_ls); 114 | REGISTER_OPT(opt_index); 115 | REGISTER_OPT(opt_play); 116 | REGISTER_OPT(opt_stop); 117 | REGISTER_OPT(opt_pause); 118 | REGISTER_OPT(opt_toggle); 119 | REGISTER_OPT(opt_next); 120 | REGISTER_OPT(opt_prev); 121 | REGISTER_OPT(opt_repeat); 122 | REGISTER_OPT(opt_random); 123 | REGISTER_OPT(opt_single); 124 | REGISTER_OPT(opt_consume); 125 | REGISTER_OPT(opt_crossfade); 126 | #undef REGISTER_OPT 127 | 128 | static const mpdopt opts[] = { 129 | { "add", 1, opt_add }, 130 | { "clear", 0, opt_clear }, 131 | { "ls", 0, opt_ls }, 132 | { "index", 0, opt_index }, 133 | { "play", 0, opt_play }, 134 | { "stop", 0, opt_stop }, 135 | { "pause", 0, opt_pause }, 136 | { "toggle", 0, opt_toggle }, 137 | { "next", 0, opt_next }, 138 | { "prev", 0, opt_prev}, 139 | { "repeat", 0, opt_repeat }, 140 | { "random", 0, opt_random }, 141 | { "single", 0, opt_single }, 142 | { "consume", 0, opt_consume }, 143 | { "crossfade", 1, opt_crossfade }, 144 | { NULL, 0, NULL }, 145 | }; 146 | 147 | enum { 148 | RETURN_OK, 149 | RETURN_FAIL, 150 | }; 151 | 152 | /* uppercase strcmp */ 153 | int _strupcmp(const char *hay, const char *needle) 154 | { 155 | size_t i, len; 156 | if ((len = strlen(hay)) != strlen(needle)) return 1; 157 | for (i = 0; i != len; ++i) 158 | if (toupper(hay[i]) != toupper(needle[i])) return 1; 159 | return 0; 160 | } 161 | 162 | /* uppercase strstr */ 163 | char* _strupstr(const char *hay, const char *needle) 164 | { 165 | size_t i, r, p, len, len2; 166 | p = 0; r = 0; 167 | if (!_strupcmp(hay, needle)) return (char*)hay; 168 | if ((len = strlen(hay)) < (len2 = strlen(needle))) return NULL; 169 | for (i = 0; i != len; ++i) { 170 | if (p == len2) return (char*)&hay[r]; /* THIS IS IT! */ 171 | if (toupper(hay[i]) == toupper(needle[p++])) { 172 | if (!r) r = i; /* could this be.. */ 173 | } else { if (r) i = r; r = 0; p = 0; } /* ..nope, damn it! */ 174 | } 175 | if (p == len2) return (char*)&hay[r]; /* THIS IS IT! */ 176 | return NULL; 177 | } 178 | 179 | /* colored print */ 180 | static void _cprnt(FILE *out, char *buffer) { 181 | size_t i, len = strlen(buffer); 182 | for (i = 0; i != len; ++i) { 183 | if (buffer[i] == '\1') fprintf(out, "%s", colors[0]); 184 | else if (buffer[i] == '\2') fprintf(out, "%s", colors[1]); 185 | else if (buffer[i] == '\3') fprintf(out, "%s", colors[2]); 186 | else if (buffer[i] == '\4') fprintf(out, "%s", colors[3]); 187 | else if (buffer[i] == '\5') fprintf(out, "%s", colors[4]); 188 | else fprintf(out, "%c", buffer[i]); 189 | } 190 | fprintf(out, "%s\n", colors[5]); 191 | fflush(out); 192 | } 193 | 194 | /* printf wrapper */ 195 | static void _prnt(FILE *out, const char *fmt, ...) { 196 | va_list args; 197 | char buffer[LINE_MAX]; 198 | 199 | memset(buffer, 0, LINE_MAX); 200 | va_start(args, fmt); 201 | vsnprintf(buffer, LINE_MAX-1, fmt, args); 202 | va_end(args); 203 | _cprnt(out, buffer); 204 | } 205 | 206 | /* get mpd status */ 207 | static struct mpd_status * get_status(void) { 208 | struct mpd_status *status; 209 | assert(mpd->connection); 210 | 211 | if (!(status = mpd_recv_status(mpd->connection))) 212 | goto mpd_error; 213 | if (mpd->status) mpd_status_free(mpd->status); 214 | return mpd->status = status; 215 | 216 | mpd_error: 217 | MPDERR(); 218 | return NULL; 219 | } 220 | 221 | /* fetch cover art */ 222 | static char* fetch_cover(const char *dir) { 223 | struct dirent **names; size_t len, n, i; 224 | char rdir[PATH_MAX], fcover[256], *cover; 225 | 226 | memset(rdir, 0, sizeof(rdir)); 227 | memset(fcover, 0, sizeof(fcover)); 228 | snprintf(rdir, sizeof(rdir)-1, "%s/%s", MUSIC_DIR, dir); 229 | if (!(n = scandir(rdir, &names, 0, alphasort)) || n == -1) 230 | return NULL; 231 | for (i = 0; i != n; ++i) { 232 | if (names[i]->d_type != DT_REG) continue; 233 | if (!_strupstr(names[i]->d_name, ".jpg") && 234 | !_strupstr(names[i]->d_name, ".png")) continue; 235 | 236 | strncpy(fcover, names[i]->d_name, sizeof(fcover)-1); 237 | break; 238 | } 239 | for (i = 0; i != n; ++i) free(names[i]); 240 | free(names); 241 | 242 | if (!strlen(fcover)) 243 | return NULL; 244 | 245 | len = strlen(rdir)+1+strlen(fcover)+2; 246 | if (!(cover = calloc(1, len))) 247 | return NULL; 248 | 249 | snprintf(cover, len-1, "%s/%s", rdir, fcover); 250 | return cover; 251 | } 252 | 253 | /* get cover art for song */ 254 | static char* get_cover_art(const struct mpd_song *song) { 255 | char *uric, *ret; const char *uri, *urid; 256 | if (!song || !(uri = mpd_song_get_uri(song)) || !((uric = strdup(uri)))) 257 | return NULL; 258 | urid = dirname(uric); 259 | ret = fetch_cover(urid); 260 | free(uric); 261 | return ret; 262 | } 263 | 264 | /* add song to queue */ 265 | static int print_song(const struct mpd_song *song, const char *sep, int printimg) { 266 | if (!song) return RETURN_FAIL; 267 | char *basec = NULL, *based = NULL, *cover = NULL; 268 | static char *lalbum = NULL; 269 | static char *lcover = NULL; 270 | const char *disc = mpd_song_get_tag(song, MPD_TAG_DISC, 0); 271 | const char *track = mpd_song_get_tag(song, MPD_TAG_TRACK, 0); 272 | const char *comment = mpd_song_get_tag(song, MPD_TAG_COMMENT, 0); 273 | const char *genre = mpd_song_get_tag(song, MPD_TAG_GENRE, 0); 274 | const char *album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0); 275 | const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); 276 | const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); 277 | if (!title) title = mpd_song_get_tag(song, MPD_TAG_NAME, 0); 278 | if (!artist) artist = mpd_song_get_tag(song, MPD_TAG_ALBUM_ARTIST, 0); 279 | if (!artist) artist = mpd_song_get_tag(song, MPD_TAG_COMPOSER, 0); 280 | if (!artist) artist = mpd_song_get_tag(song, MPD_TAG_PERFORMER, 0); 281 | if (!album && (based = strdup(mpd_song_get_uri(song)))) album = basename(dirname(based)); 282 | if (!title && (basec = strdup(mpd_song_get_uri(song)))) title = basename(basec); 283 | 284 | /* fallbacks */ 285 | if (!artist) artist = "noartist"; 286 | if (!album) album = "noalbum"; 287 | if (!title) title = "notitle"; 288 | 289 | #if 0 290 | OUT("URI: %s", mpd_song_get_uri(song)); 291 | OUT("Song [%d]: d:%d s:%d e:%d m:%lu q:%d", 292 | mpd_song_get_id(song), 293 | mpd_song_get_duration(song), 294 | mpd_song_get_start(song), 295 | mpd_song_get_end(song), 296 | mpd_song_get_last_modified(song), 297 | mpd_song_get_pos(song)); 298 | OUT("D[%s] T[%s]", disc, track); 299 | OUT("GENRE: %s", genre); 300 | OUT("ARTIST: %s", artist); 301 | OUT("ALBUM: %s", album); 302 | OUT("TITLE: %s", title); 303 | #else 304 | if (artist && album && title) { 305 | if (printimg) { 306 | if (lcover && !strcmp(lalbum, album)) { 307 | cover = strdup(lcover); 308 | } else cover = get_cover_art(song); 309 | if (cover) printf("IMG:%s\t", cover); 310 | } 311 | printf("%s%s%s%s%s\n", artist, sep, album, sep, title); 312 | } 313 | #endif 314 | 315 | /* remember last album && cover */ 316 | if (lalbum) free(lalbum); 317 | if (lcover) free(lcover); 318 | if (album) lalbum = strdup(album); 319 | else lalbum = NULL; 320 | if (cover) { 321 | lcover = strdup(cover); 322 | free(cover); 323 | } else lcover = NULL; 324 | 325 | if (based) free(based); 326 | if (basec) free(basec); 327 | return RETURN_OK; 328 | } 329 | 330 | /* match song from queue */ 331 | static int match_song(const struct mpd_song *song, const char *needle, const char *sep, int *exact) { 332 | if (exact) *exact = 0; 333 | if (!song) return RETURN_FAIL; 334 | char *basec = NULL, *based = NULL; 335 | const char *disc = mpd_song_get_tag(song, MPD_TAG_DISC, 0); 336 | const char *track = mpd_song_get_tag(song, MPD_TAG_TRACK, 0); 337 | const char *comment = mpd_song_get_tag(song, MPD_TAG_COMMENT, 0); 338 | const char *genre = mpd_song_get_tag(song, MPD_TAG_GENRE, 0); 339 | const char *album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0); 340 | const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); 341 | const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); 342 | if (!title) title = mpd_song_get_tag(song, MPD_TAG_NAME, 0); 343 | if (!artist) artist = mpd_song_get_tag(song, MPD_TAG_ALBUM_ARTIST, 0); 344 | if (!artist) artist = mpd_song_get_tag(song, MPD_TAG_COMPOSER, 0); 345 | if (!artist) artist = mpd_song_get_tag(song, MPD_TAG_PERFORMER, 0); 346 | if (!album && (based = strdup(mpd_song_get_uri(song)))) album = basename(dirname(based)); 347 | if (!title && (basec = strdup(mpd_song_get_uri(song)))) title = basename(basec); 348 | 349 | /* fallbacks */ 350 | if (!artist) artist = "noartist"; 351 | if (!album) album = "noalbum"; 352 | if (!title) title = "notitle"; 353 | 354 | if (!album || !artist || !title) { 355 | if (based) free(based); 356 | if (basec) free(basec); 357 | return RETURN_FAIL; 358 | } 359 | 360 | char found = 0, *whole = NULL; 361 | size_t lsep = strlen(sep); 362 | size_t len = strlen(artist)+lsep+strlen(album)+lsep+strlen(title)+2; 363 | if ((whole = malloc(len))) { 364 | snprintf(whole, len-1, "%s%s%s%s%s", artist, sep, album, sep, title); 365 | if (based) free(based); 366 | if (basec) free(basec); 367 | if (!strcmp(needle, whole)) { 368 | found = 1; 369 | if (exact) *exact = 1; 370 | } else if (_strupstr(whole, needle)) 371 | found = 1; 372 | 373 | if (!found) { 374 | int tokc = 0; 375 | char *cpy = strdup(needle); 376 | if (cpy) { 377 | char *tok = strtok(cpy, " "); 378 | while (tok) { 379 | if (!strcmp(tok, whole)) { 380 | found++; 381 | } else if (_strupstr(whole, tok)) 382 | found++; 383 | tok = strtok(NULL, " "); 384 | ++tokc; 385 | } 386 | if (tokc != found) found = 0; 387 | free(cpy); 388 | } 389 | } 390 | 391 | free(whole); 392 | if (found) return RETURN_OK; 393 | } 394 | 395 | return RETURN_FAIL; 396 | } 397 | 398 | /* now playing */ 399 | static void now_playing(int printimg) { 400 | char *cover; 401 | struct mpd_song *song = mpd_run_current_song(mpd->connection); 402 | if (!song) return; 403 | print_song(song, SEPERATOR, 0); 404 | if (printimg && (cover = get_cover_art(song))) { 405 | printf("%s\n", cover); 406 | free(cover); 407 | } 408 | mpd_song_free(song); 409 | } 410 | 411 | /* list queue */ 412 | static int list_queue(int printimg) { 413 | unsigned int pos, end, mid; 414 | struct mpd_entity *entity; 415 | assert(mpd && mpd->connection); 416 | 417 | for (mid = pos = 0, end = MPD_OUTPUT_BUFFER; 418 | mpd_send_list_queue_range_meta(mpd->connection, pos, end); 419 | pos = end, end *= 2) { 420 | while ((entity = mpd_recv_entity(mpd->connection))) { 421 | if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { 422 | print_song(mpd_entity_get_song(entity), SEPERATOR, printimg); 423 | mid = mpd_song_get_id(mpd_entity_get_song(entity)); 424 | } 425 | mpd_entity_free(entity); 426 | } 427 | if (end > mid) break; 428 | } 429 | 430 | if (!mpd_response_finish(mpd->connection)) 431 | MPDERR(); 432 | 433 | mpd->queue.version = mpd_status_get_queue_version(mpd->status); 434 | return RETURN_OK; 435 | } 436 | 437 | /* search queue */ 438 | static struct mpd_song* search_queue(const char *needle) { 439 | int exact = 0; 440 | unsigned int pos, end, mid; 441 | struct mpd_song *song = NULL; 442 | struct mpd_entity *entity; 443 | assert(mpd && mpd->connection); 444 | 445 | for (mid = pos = 0, end = MPD_OUTPUT_BUFFER; !song && 446 | mpd_send_list_queue_range_meta(mpd->connection, pos, end); 447 | pos = end, end *= 2) { 448 | while (!song && (entity = mpd_recv_entity(mpd->connection))) { 449 | if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { 450 | if (!exact && match_song(mpd_entity_get_song(entity), needle, SEPERATOR, &exact) == RETURN_OK) { 451 | if (song) mpd_song_free(song); 452 | song = mpd_song_dup(mpd_entity_get_song(entity)); 453 | } 454 | mid = mpd_song_get_id(mpd_entity_get_song(entity)); 455 | } 456 | mpd_entity_free(entity); 457 | } 458 | if (end > mid) break; 459 | } 460 | 461 | if (!mpd_response_finish(mpd->connection)) 462 | MPDERR(); 463 | 464 | mpd->queue.version = mpd_status_get_queue_version(mpd->status); 465 | return song; 466 | } 467 | 468 | /* update status */ 469 | static void update_status(void) { 470 | assert(mpd && mpd->connection); 471 | if (mpd->status) mpd_status_free(mpd->status); 472 | if (!(mpd->status = mpd_run_status(mpd->connection))) 473 | MPDERR(); 474 | 475 | mpd->state.id = mpd_status_get_update_id(mpd->status); 476 | mpd->state.volume = mpd_status_get_volume(mpd->status); 477 | mpd->state.crossfade = mpd_status_get_crossfade(mpd->status); 478 | mpd->state.queuever = mpd_status_get_queue_version(mpd->status); 479 | mpd->state.queuelen = mpd_status_get_queue_length(mpd->status); 480 | mpd->state.song = mpd_status_get_song_id(mpd->status); 481 | mpd->state.state = mpd_status_get_state(mpd->status); 482 | 483 | if (mpd_status_get_repeat(mpd->status)) 484 | mpd->state.playmode |= PLAY_REPEAT; 485 | if (mpd_status_get_random(mpd->status)) 486 | mpd->state.playmode |= PLAY_RANDOM; 487 | if (mpd_status_get_single(mpd->status)) 488 | mpd->state.playmode |= PLAY_SINGLE; 489 | if (mpd_status_get_consume(mpd->status)) 490 | mpd->state.playmode |= PLAY_CONSUME; 491 | 492 | OUT("State [%d]: V:%d CF:%d, Q:%d QL:%d S:%d SP:%d ST:%s", mpd->state.id, 493 | mpd->state.volume, mpd->state.crossfade, 494 | mpd->state.queuever, mpd->state.queuelen, mpd->state.song, 495 | mpd_status_get_song_pos(mpd->status), 496 | mpd->state.state==MPD_STATE_STOP?"STOP": 497 | mpd->state.state==MPD_STATE_PLAY?"PLAY": 498 | mpd->state.state==MPD_STATE_PAUSE?"PAUSE":"UNKNOWN"); 499 | OUT("Playmode: REPT:%d RAND:%d SING:%d CONS:%d", 500 | mpd->state.playmode & PLAY_REPEAT, 501 | mpd->state.playmode & PLAY_RANDOM, 502 | mpd->state.playmode & PLAY_SINGLE, 503 | mpd->state.playmode & PLAY_CONSUME); 504 | } 505 | 506 | /* quit mpd */ 507 | static void quit_mpd(void) { 508 | assert(mpd); 509 | if (mpd->connection) mpd_connection_free(mpd->connection); 510 | if (mpd->status) mpd_status_free(mpd->status); 511 | free(mpd); mpd = NULL; 512 | OUT("Closed mpd connection"); 513 | } 514 | 515 | /* init and connect mpd */ 516 | static int init_mpd(void) { 517 | unsigned int mpd_port = 6600; 518 | const char *host = getenv("MPD_HOST"); 519 | const char *port = getenv("MPD_PORT"); 520 | const char *pass = getenv("MPD_PASSWORD"); 521 | 522 | if (!host) host = "localhost"; 523 | if (port) mpd_port = strtol(port, (char**) NULL, 10); 524 | 525 | if (mpd) quit_mpd(); 526 | if (!(mpd = calloc(1, sizeof(mpdclient)))) 527 | goto alloc_fail; 528 | 529 | if (!(mpd->connection = mpd_connection_new(host, mpd_port, MPD_TIMEOUT)) || 530 | mpd_connection_get_error(mpd->connection)) 531 | goto connect_fail; 532 | 533 | if (pass && !mpd_run_password(mpd->connection, pass)) 534 | goto connect_fail; 535 | 536 | mpd->server.version = mpd_connection_get_server_version(mpd->connection); 537 | mpd->host = host; mpd->port = mpd_port; 538 | OUT("Connected to '%s:%d' - Server version [%d.%d.%d]", host, mpd_port, 539 | mpd->server.version[0], mpd->server.version[1], 540 | mpd->server.version[2]); 541 | 542 | update_status(); 543 | return RETURN_OK; 544 | 545 | alloc_fail: 546 | MEMERR(mpdclient); 547 | connect_fail: 548 | ERR("Connection to MPD server '%s:%d' failed", host, mpd_port); 549 | MPDERR(); 550 | return RETURN_FAIL; 551 | } 552 | 553 | enum { 554 | ADD_MODE_SEARCH, 555 | ADD_MODE_PLAYLIST, 556 | ADD_MODE_FILE, 557 | }; 558 | 559 | int song_compare(const void *a, const void *b) 560 | { 561 | int ret = INT_MAX; 562 | struct mpd_song *sa = mpd_run_get_queue_song_id(mpd->connection, *(int*)a); 563 | struct mpd_song *sb = mpd_run_get_queue_song_id(mpd->connection, *(int*)b); 564 | if (sa && sb) { 565 | const char *cta = mpd_song_get_tag(sa, MPD_TAG_TRACK, 0); 566 | const char *ctb = mpd_song_get_tag(sb, MPD_TAG_TRACK, 0); 567 | int ta = INT_MAX, tb = INT_MAX; 568 | if (cta) sscanf(cta, "%d/%*", &ta); 569 | if (ctb) sscanf(ctb, "%d/%*", &tb); 570 | ret = ta - tb; 571 | } 572 | if (sa) mpd_song_free(sa); 573 | if (sb) mpd_song_free(sb); 574 | return ret; 575 | } 576 | 577 | static void add_from(const char *path, int add_mode, int *found_playlist, int *found_song_id) 578 | { 579 | DIR *dp; 580 | struct dirent *ep; 581 | char *sub_path; 582 | const char *ext; 583 | struct mpd_song *song; 584 | unsigned int start_pos, small_pos; 585 | int sub_path_size, did_add_file = 0, contains_playlist = 0, i, id = -1; 586 | int *song_list = NULL, *old_song_list, song_list_count = 0, song_list_size = 0; 587 | const int song_list_alloc = 32; 588 | 589 | if (found_song_id) *found_song_id = -1; 590 | 591 | if ((dp = opendir(path))) { 592 | /* crawl subdirectories and check if current directory contains playlist */ 593 | while ((ep = readdir(dp))) { 594 | if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue; 595 | if (ep->d_type != DT_DIR && ep->d_type != DT_REG) continue; 596 | sub_path_size = strlen(path)+1+strlen(ep->d_name)+1; 597 | if (!(sub_path = malloc(sub_path_size+1))) continue; 598 | snprintf(sub_path, sub_path_size, "%s/%s", path, ep->d_name); 599 | add_from(sub_path, ADD_MODE_SEARCH, &contains_playlist, NULL); 600 | free(sub_path); 601 | } 602 | closedir(dp); 603 | 604 | /* add the files from directory */ 605 | if (!(dp = opendir(path))) return; 606 | while ((ep = readdir(dp))) { 607 | if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue; 608 | if (ep->d_type != DT_REG) continue; 609 | sub_path_size = strlen(path)+1+strlen(ep->d_name)+1; 610 | if (!(sub_path = malloc(sub_path_size+1))) continue; 611 | snprintf(sub_path, sub_path_size, "%s/%s", path, ep->d_name); 612 | add_from(sub_path, (contains_playlist?ADD_MODE_PLAYLIST:ADD_MODE_FILE), NULL, &id); 613 | free(sub_path); 614 | if (id == -1) continue; 615 | 616 | /* add song to sorter array */ 617 | if (song_list_size <= song_list_count+1) { 618 | old_song_list = song_list; 619 | song_list_size += song_list_alloc; 620 | if (!(song_list = malloc(song_list_size * sizeof(int)))) { 621 | song_list = old_song_list; 622 | continue; 623 | } 624 | if (old_song_list) memcpy(song_list, old_song_list, (song_list_size - song_list_alloc) * sizeof(int)); 625 | else memset(song_list, 0, song_list_size * sizeof(int)); 626 | free(old_song_list); 627 | old_song_list = NULL; 628 | } 629 | song_list[song_list_count++] = id; 630 | } 631 | closedir(dp); 632 | 633 | /* sort songs */ 634 | if (song_list) { 635 | qsort(song_list, song_list_count, sizeof(int), song_compare); 636 | 637 | /* get song with smallest position */ 638 | start_pos = UINT_MAX; 639 | for (i = 0; i != song_list_count; ++i) { 640 | song = mpd_run_get_queue_song_id(mpd->connection, song_list[i]); 641 | if (!song) { 642 | MPDERR(); 643 | continue; 644 | } 645 | small_pos = mpd_song_get_pos(song); 646 | if (small_pos < start_pos) start_pos = small_pos; 647 | mpd_song_free(song); 648 | } 649 | 650 | /* do the moving */ 651 | for (i = 0; start_pos != UINT_MAX && i != song_list_count; ++i) { 652 | if (!mpd_run_move_id(mpd->connection, song_list[i], start_pos+i)) 653 | MPDERR(); 654 | } 655 | } 656 | if (song_list) free(song_list); 657 | } else { 658 | /* something is wrong, if this doesn't pass */ 659 | if (strlen(path) < 1+strlen(MUSIC_DIR)) return; 660 | 661 | /* try add as an playlist */ 662 | for (i = 0; 663 | (add_mode == ADD_MODE_SEARCH || add_mode == ADD_MODE_PLAYLIST) && 664 | !did_add_file && playlistFormats[i]; ++i) 665 | { 666 | ext = playlistFormats[i]; 667 | if (strlen(path) < strlen(ext)) continue; 668 | if (strcmp(path+strlen(path)-strlen(ext), ext)) continue; 669 | if (found_playlist) *found_playlist = 1; 670 | 671 | if (add_mode == ADD_MODE_PLAYLIST) { 672 | printf(">> adding playlist: %s\n", path+1+strlen(MUSIC_DIR)); 673 | if (!mpd_run_load(mpd->connection, path+1+strlen(MUSIC_DIR))) 674 | MPDERR(); 675 | } 676 | did_add_file = 1; 677 | } 678 | 679 | /* try add as an file */ 680 | for (i = 0; add_mode == ADD_MODE_FILE && !did_add_file && fileFormats[i]; ++i) { 681 | ext = fileFormats[i]; 682 | if (strlen(path) < strlen(ext)) continue; 683 | if (strcmp(path+strlen(path)-strlen(ext), ext)) continue; 684 | 685 | printf(">> adding file: %s\n", path+1+strlen(MUSIC_DIR)); 686 | if ((id = mpd_run_add_id(mpd->connection, path+1+strlen(MUSIC_DIR))) == -1) 687 | MPDERR(); 688 | if (found_song_id) *found_song_id = id; 689 | did_add_file = 1; 690 | } 691 | } 692 | } 693 | 694 | #define FUNC_OPT(x) static int x(int argc, char **argv) 695 | FUNC_OPT(opt_add) { 696 | unsigned int id; 697 | char path[PATH_MAX]; 698 | 699 | OUT("add"); 700 | if (!strcmp(argv[0], ".") || !strcmp(argv[0], "..")) { 701 | snprintf(path, PATH_MAX-1, "%s", MUSIC_DIR); 702 | } else { 703 | snprintf(path, PATH_MAX-1, "%s/%s", MUSIC_DIR, argv[0]); 704 | } 705 | 706 | if (access(path, R_OK) != 0) 707 | goto access_fail; 708 | 709 | if (!(id = mpd_run_update(mpd->connection, (!strcmp(path, MUSIC_DIR)?NULL:argv[0])))) { 710 | MPDERR(); 711 | goto fail; 712 | } 713 | 714 | while (1) { 715 | unsigned int current_id; 716 | struct mpd_status *status; 717 | enum mpd_idle idle = mpd_run_idle_mask(mpd->connection, MPD_IDLE_UPDATE); 718 | if (idle == 0) { 719 | MPDERR(); 720 | goto fail; 721 | } 722 | 723 | /* determine the current "update id" */ 724 | if (!(status = mpd_run_status(mpd->connection))) { 725 | MPDERR(); 726 | goto fail; 727 | } 728 | 729 | current_id = mpd_status_get_update_id(status); 730 | mpd_status_free(status); 731 | 732 | /* is our last queued update finished now? */ 733 | if (current_id == 0 || current_id > id || (id > 1 << 30 && id < 1000)) /* wraparound */ 734 | break; 735 | } 736 | 737 | add_from(path, 0, NULL, NULL); 738 | return EXIT_SUCCESS; 739 | 740 | access_fail: 741 | ERR("Cannot access file/directory: %s", path); 742 | goto fail; 743 | fail: 744 | return EXIT_SUCCESS; 745 | } 746 | 747 | FUNC_OPT(opt_clear) { 748 | OUT("clear"); 749 | if (!mpd_run_clear(mpd->connection)) 750 | MPDERR(); 751 | return EXIT_SUCCESS; 752 | } 753 | 754 | FUNC_OPT(opt_ls) { 755 | OUT("ls"); 756 | list_queue((argc && !strcmp(argv[0], ARG_WITH_COVER))?1:0); 757 | return EXIT_SUCCESS; 758 | } 759 | 760 | FUNC_OPT(opt_index) { 761 | OUT("index"); 762 | struct mpd_song *song = mpd_run_current_song(mpd->connection); 763 | printf("%u\n", (song?mpd_song_get_pos(song)+1:1)); 764 | if (song) mpd_song_free(song); 765 | return EXIT_SUCCESS; 766 | } 767 | 768 | FUNC_OPT(opt_play) { 769 | struct mpd_song *song = NULL; 770 | size_t len = 0, i = 0; 771 | char *search; 772 | 773 | if (!argc) mpd_send_play(mpd->connection); 774 | else { 775 | for (i = 0; i != argc; ++i) { 776 | if (i) len += 1; 777 | len += strlen(argv[i]); 778 | } len += 2; 779 | 780 | if (!(search = calloc(1, len))) 781 | return EXIT_FAILURE; 782 | 783 | for (i = 0; i != argc; ++i) { 784 | if (i) search = strncat(search, " ", len); 785 | search = strncat(search, argv[i], len); 786 | } 787 | 788 | OUT("play: %s", search); 789 | if ((song = search_queue(search))) { 790 | print_song(song, SEPERATOR, 0); 791 | if (!mpd_send_play_id(mpd->connection, mpd_song_get_id(song))) 792 | MPDERR(); 793 | mpd_song_free(song); 794 | } else { 795 | printf("no match for: %s\n", search); 796 | } 797 | free(search); 798 | } 799 | 800 | return EXIT_SUCCESS; 801 | 802 | fail: 803 | MPDERR(); 804 | return EXIT_FAILURE; 805 | } 806 | 807 | FUNC_OPT(opt_stop) { 808 | OUT("stop"); 809 | mpd_send_stop(mpd->connection); 810 | return EXIT_SUCCESS; 811 | } 812 | 813 | FUNC_OPT(opt_pause) { 814 | OUT("pause"); 815 | mpd_send_pause(mpd->connection, !argc?1:strtol(argv[0], NULL, 10)); 816 | return EXIT_SUCCESS; 817 | } 818 | 819 | FUNC_OPT(opt_toggle) { 820 | OUT("toggle"); 821 | mpd_send_toggle_pause(mpd->connection); 822 | return EXIT_SUCCESS; 823 | } 824 | 825 | FUNC_OPT(opt_next) { 826 | OUT("next"); 827 | mpd_send_next(mpd->connection); 828 | return EXIT_SUCCESS; 829 | } 830 | 831 | FUNC_OPT(opt_prev) { 832 | OUT("previous"); 833 | mpd_send_previous(mpd->connection); 834 | return EXIT_SUCCESS; 835 | } 836 | 837 | FUNC_OPT(opt_repeat) { 838 | OUT("repeat"); 839 | mpd_send_repeat(mpd->connection, !argc?1:strtol(argv[0], NULL, 10)); 840 | return EXIT_SUCCESS; 841 | } 842 | 843 | FUNC_OPT(opt_random) { 844 | OUT("random"); 845 | mpd_send_random(mpd->connection, !argc?1:strtol(argv[0], NULL, 10)); 846 | return EXIT_SUCCESS; 847 | } 848 | 849 | FUNC_OPT(opt_single) { 850 | OUT("single"); 851 | mpd_send_single(mpd->connection, !argc?1:strtol(argv[0], NULL, 10)); 852 | return EXIT_SUCCESS; 853 | } 854 | 855 | FUNC_OPT(opt_consume) { 856 | OUT("consume"); 857 | mpd_send_consume(mpd->connection, !argc?1:strtol(argv[0], NULL, 10)); 858 | return EXIT_SUCCESS; 859 | } 860 | 861 | FUNC_OPT(opt_crossfade) { 862 | assert(argc); 863 | OUT("crossfade"); 864 | mpd_send_crossfade(mpd->connection, strtol(argv[0], NULL, 10)); 865 | return EXIT_SUCCESS; 866 | } 867 | #undef FUNC_OPT 868 | 869 | static void usage(char *name) { 870 | int o; 871 | printf("usage: %s [", basename(name)); 872 | for (o = 0; opts[o].arg; ++o) 873 | printf("%s%s", opts[o].arg, opts[o+1].arg?"|":""); 874 | printf("]\n"); 875 | printf(" - `%s "ARG_WITH_COVER"` to print path to cover art for playing song\n", basename(name)); 876 | printf(" - `%s ls "ARG_WITH_COVER"` to print paths to cover art as well\n", basename(name)); 877 | exit(EXIT_FAILURE); 878 | } 879 | 880 | static void usage2(char *name, const mpdopt *opt) 881 | { 882 | printf("%s: %s takes %d argument(s)\n", basename(name), opt->arg, opt->argc); 883 | exit(EXIT_FAILURE); 884 | } 885 | 886 | int main(int argc, char **argv) { 887 | int o; 888 | 889 | if (argc >= 2 && strcmp(argv[1], ARG_WITH_COVER)) { 890 | for (o = 0; opts[o].arg && strcmp(argv[1], opts[o].arg); ++o); 891 | if (!opts[o].arg) usage(argv[0]); 892 | if (opts[o].argc > argc-2) usage2(argv[0], &opts[o]); 893 | OUT("%s: %d/%d", opts[o].arg, argc-2, opts[o].argc); 894 | } 895 | 896 | if (init_mpd() != RETURN_OK) 897 | goto fail; 898 | 899 | if (argc >= 2 && strcmp(argv[1], ARG_WITH_COVER)) { 900 | for (o = 0; opts[o].arg && strcmp(argv[1], opts[o].arg); ++o); 901 | if (opts[o].func) opts[o].func(argc-2, argv+2); 902 | } else now_playing((argc>=2 && !strcmp(argv[1], ARG_WITH_COVER))); 903 | 904 | quit_mpd(); 905 | return EXIT_SUCCESS; 906 | 907 | fail: 908 | return EXIT_FAILURE; 909 | } 910 | -------------------------------------------------------------------------------- /lolimpd/lolimpdnu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # dmenu access to lolimpd 3 | 4 | # lolimpd binary name 5 | LOLIMPD="lolimpd" 6 | 7 | # dmenu with imlib support available? 8 | HAS_IMLIB_DMENU=1 9 | 10 | # cache file 11 | CACHE="/tmp/lolimpdnu.cache" 12 | 13 | # removes the cache file after song was selected 14 | # set this to 1, if you use multiple playlists. 15 | # if the playlists are big though, lolimpdnu will be slow. 16 | NOCACHE=0 17 | 18 | # read user options 19 | [[ -f "$HOME/.dmenurc" ]] && { 20 | . "$HOME/.dmenurc" 21 | DMENU="$LOLIMPDNU_DMENU" 22 | } 23 | [[ -n "$DMENU" ]] || { 24 | DMENU="dmenu -i -l 5" 25 | } 26 | 27 | # lolis live here 28 | main() { 29 | local song= 30 | local g= 31 | local index= 32 | local filter= 33 | 34 | # options 35 | [[ "$1" == "-c" ]] && { rm "$CACHE"; shift 1; } 36 | [[ "$1" == "-g" ]] && { g="-g"; shift 1; } 37 | [[ "$1" == "-h" ]] && { 38 | echo "usage: lolimpdnu [cgh] " 39 | echo -e "\t-c\tClear song list cache. Use this, if you add/remove songs from/to mpd." 40 | echo -e "\t-g\tGenerate tumbnail cache for all songs. (Needs dmenu with imlib support)" 41 | echo -e "\t-h\tShow this help." 42 | return; 43 | } 44 | 45 | # filter output 46 | # useful when your IME keybindings are blocked by dmenu 47 | filter="$@" 48 | 49 | # generate cache, if one doesn't exist 50 | [[ $HAS_IMLIB_DMENU -eq 1 ]] && { 51 | [[ -f "$CACHE" ]] || "$LOLIMPD" ls --with-cover > "$CACHE" 52 | } 53 | [[ $HAS_IMLIB_DMENU -eq 0 ]] && { 54 | [[ -f "$CACHE" ]] || "$LOLIMPD" ls > "$CACHE" 55 | } 56 | 57 | # imlib specific 58 | [[ $HAS_IMLIB_DMENU -eq 1 ]] && { 59 | # search for current song 60 | [[ -n "$filter" ]] && { 61 | index=$(grep "$filter" "$CACHE" | grep -Fon "$("$LOLIMPD")" | cut -f1 -d: | head -n1) 62 | } 63 | [[ -n "$filter" ]] || { 64 | index=$("$LOLIMPD" index) 65 | } 66 | [[ -n "$index" ]] || index=1 67 | 68 | # select song with dmenu, starting from currently playing song 69 | [[ -n "$filter" ]] && song="$(grep "$filter" "$CACHE" | $DMENU $g -si $index -p "lolimpd")" 70 | [[ -n "$filter" ]] || song="$(cat "$CACHE" | $DMENU $g -si $index -p "lolimpd")" 71 | } 72 | 73 | # default dmenu 74 | [[ $HAS_IMLIB_DMENU -eq 0 ]] && { 75 | [[ -n "$filter" ]] && song="$(grep "$filter" "$CACHE" | $DMENU -p "lolimpd")" 76 | [[ -n "$filter" ]] || song="$(cat "$CACHE" | $DMENU -p "lolimpd")" 77 | } 78 | 79 | # remove cache if asked 80 | [[ $NOCACHE -eq 0 ]] || rm "$CACHE" 81 | 82 | # play song if selected 83 | [[ -n "$song" ]] || return 84 | "$LOLIMPD" play "$song" 85 | } 86 | main "$@" 87 | -------------------------------------------------------------------------------- /xcmenu-git/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Cloudef 2 | # Lightweight clipboard manager for X 3 | 4 | _gitname=xcmenu 5 | pkgname=xcmenu-git 6 | pkgver=20131104.4 7 | pkgrel=1 8 | pkgdesc='Lightweight clipboard manager for X' 9 | arch=('i686' 'x86_64') 10 | url='http://cloudef.eu' 11 | license=('WTFPL') 12 | depends=('libxcb' 'zlib') 13 | optdepends=('dmenu') 14 | makedepends=('gcc') 15 | replaces=('loliclip') 16 | source=(git://github.com/Cloudef/xcmenu) 17 | md5sums=(SKIP) 18 | 19 | pkgver() { 20 | cd "$srcdir/$_gitname" 21 | echo "$(git log -1 --format="%cd" --date=short | sed 's|-||g').$(git rev-list --count master)" 22 | } 23 | 24 | build() { 25 | cd "$srcdir/$_gitname" 26 | 27 | # Allow custom FLAGS 28 | sed -i config.mk -e 's|^CFLAGS\s*=|CFLAGS +=|' -e 's|^LDFLAGS\s*=|LDFLAGS +=|' 29 | 30 | # custom config 31 | if [[ -e "$startdir/config.h" ]]; then 32 | msg "using custom config.h"; 33 | cp "$startdir/config.h" . 34 | fi 35 | 36 | make 37 | } 38 | 39 | package() { 40 | cd "$srcdir/$_gitname" 41 | make DESTDIR=$pkgdir PREFIX=/usr install 42 | install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" 43 | } 44 | 45 | # vim: set ts=8 sw=3 tw=0 : 46 | --------------------------------------------------------------------------------