minor
[monky] / src / xmms.c
1 /* -------------------------------------------------------------------------
2  * xmms.c:  conky support for XMMS-related projects
3  *
4  * Copyright (C) 2005  Philip Kovacs kovacsp3@comcast.net
5  * 
6  * $Id$
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  * --------------------------------------------------------------------------- */
22
23 #include <pthread.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "config.h"
29 #include "conky.h"
30 #include "xmms.h"
31
32 #if defined(XMMS) || defined(BMP) || defined(AUDACIOUS)
33 #include <glib.h>
34 #include <dlfcn.h>
35 #endif
36
37 #if defined(INFOPIPE)
38 #include <sys/select.h>
39 #include <sys/time.h>
40 #include <sys/types.h>
41 #include <fcntl.h>
42
43 #define INFOPIPE_NAMED_PIPE "/tmp/xmms-info"
44
45 /* 14 keys comprise the output of the infopipe plugin. */
46 enum _infopipe_keys {
47         INFOPIPE_PROTOCOL=0,
48         INFOPIPE_VERSION,
49         INFOPIPE_STATUS,
50         INFOPIPE_PLAYLIST_TUNES,
51         INFOPIPE_PLAYLIST_CURRTUNE,
52         INFOPIPE_USEC_POSITION,
53         INFOPIPE_POSITION,
54         INFOPIPE_USEC_TIME,
55         INFOPIPE_TIME,
56         INFOPIPE_BITRATE,
57         INFOPIPE_FREQUENCY,
58         INFOPIPE_CHANNELS,
59         INFOPIPE_TITLE,
60         INFOPIPE_FILE
61 };
62 #endif
63
64 static char *xmms_project_name[] = {
65         "none",
66         "xmms",
67         "bmp",
68         "audacious",
69         "infopipe"
70 };
71
72
73 /* access to this item array is synchronized with mutexes */
74 static xmms_t g_items;
75
76 /* ------------------------------------
77  * Conky update function for XMMS data.
78  * ------------------------------------ */
79 void update_xmms(void)
80 {
81     /* 
82       The worker thread is updating the g_items array asynchronously to the main 
83       conky thread.  We merely copy the g_items array into the main thread's info
84       structure when the main thread's update cycle fires.   Note that using the
85       mutexes here makes it easier since we won't have to do any sync in conky.c.
86     */
87     pthread_mutex_lock(&info.xmms.item_mutex);
88     memcpy(&info.xmms.items,g_items,sizeof(g_items));
89     pthread_mutex_unlock(&info.xmms.item_mutex);
90 }
91
92
93 /* ------------------------------------------------------------
94  * Create a worker thread for xmms-related media player status.
95  *
96  * Returns 0 on success, -1 on error. 
97  * ------------------------------------------------------------*/
98 int create_xmms_thread(void)
99 {
100     /* Was an an available project requested? */
101     if (!TEST_XMMS_PROJECT_AVAILABLE(info.xmms.project_mask, info.xmms.current_project)) {
102         ERR("xmms_player '%s' not configured", xmms_project_name[info.xmms.current_project]);
103         return(-1);
104     }
105     
106     /* The project should not be PROJECT_NONE */
107     if (info.xmms.current_project==PROJECT_NONE)
108         return(-1);
109
110     /* Is a worker is thread already running? */
111     if (info.xmms.thread)
112         return(-1);
113
114     /* Joinable thread for xmms activity */
115     pthread_attr_init(&info.xmms.thread_attr);
116     pthread_attr_setdetachstate(&info.xmms.thread_attr, PTHREAD_CREATE_JOINABLE);
117     /* Init mutexes */
118     pthread_mutex_init(&info.xmms.item_mutex, NULL);
119     pthread_mutex_init(&info.xmms.runnable_mutex, NULL);
120     /* Init runnable condition for worker thread */
121     pthread_mutex_lock(&info.xmms.runnable_mutex);
122     info.xmms.runnable=1;
123     pthread_mutex_unlock(&info.xmms.runnable_mutex);
124 #if defined(XMMS) || defined(BMP) || defined(AUDACIOUS)     
125     if (info.xmms.current_project==PROJECT_XMMS || 
126         info.xmms.current_project==PROJECT_BMP || 
127         info.xmms.current_project==PROJECT_AUDACIOUS) {
128         if (pthread_create(&info.xmms.thread, &info.xmms.thread_attr, xmms_thread_func_dynamic, NULL))
129             return(-1);
130     }
131 #endif
132 #if defined(INFOPIPE)
133     if (info.xmms.current_project==PROJECT_INFOPIPE) {
134         if (pthread_create(&info.xmms.thread, &info.xmms.thread_attr, xmms_thread_func_infopipe, NULL))
135             return(-1);
136     }
137 #endif
138
139     return 0;
140 }
141
142 /* ------------------------------------------------
143  * Destroy xmms-related media player status thread.
144  *
145  * Returns 0 on success, -1 on error.
146  * ------------------------------------------------ */
147 int destroy_xmms_thread(void)
148 {
149     /* Is a worker is thread running? If not, no error. */
150     if (!info.xmms.thread)
151         return(0);
152
153     /* Signal xmms worker thread to terminate */
154     pthread_mutex_lock(&info.xmms.runnable_mutex);
155     info.xmms.runnable=0;
156     pthread_mutex_unlock(&info.xmms.runnable_mutex);
157     /* Destroy thread attribute and wait for thread */
158     pthread_attr_destroy(&info.xmms.thread_attr);
159     if (pthread_join(info.xmms.thread, NULL))
160         return(-1);
161     /* Destroy mutexes */
162     pthread_mutex_destroy(&info.xmms.item_mutex);
163     pthread_mutex_destroy(&info.xmms.runnable_mutex);
164
165     info.xmms.thread=(pthread_t)0;
166     return 0;
167 }
168
169 #if defined(XMMS) || defined(BMP) || defined(AUDACIOUS)
170 void check_dlerror(void)
171 {
172     static const char *error;
173
174     if ((error = dlerror()) != NULL) {
175         ERR("error grabbing function symbol");
176         pthread_exit(NULL);
177     }
178 }
179
180 /* ------------------------------------------------------------
181  * Worker thread function for XMMS/BMP/Audacious data sampling.
182  * ------------------------------------------------------------ */ 
183 void *xmms_thread_func_dynamic(void *pvoid)
184 {
185     void *handle,*glib_v1_2_handle;
186     int runnable;
187     static xmms_t items;
188     gint session,playpos,frames,length;
189     gint rate,freq,chans;
190     gchar *psong,*pfilename;
191
192     /* Function pointers for the functions we load dynamically */
193     void (*g_free_v1_2)(gpointer mem);
194     gboolean (*xmms_remote_is_running)(gint session);
195     gboolean (*xmms_remote_is_paused)(gint session);
196     gboolean (*xmms_remote_is_playing)(gint session);
197     gint (*xmms_remote_get_playlist_pos)(gint session);
198     gchar *(*xmms_remote_get_playlist_title)(gint session, gint pos);
199     gint (*xmms_remote_get_playlist_time)(gint session, gint pos);
200     gint (*xmms_remote_get_output_time)(gint session);
201     void (*xmms_remote_get_info)(gint session, gint *rate, gint *freq, gint *chans);
202     gchar *(*xmms_remote_get_playlist_file)(gint session, gint pos);
203     gint (*xmms_remote_get_playlist_length)(gint session);
204
205     pvoid=(void *)pvoid;  /* avoid warning */
206     session=0;
207     psong=NULL;
208     pfilename=NULL;
209     handle=NULL;
210     glib_v1_2_handle=NULL;
211     g_free_v1_2=NULL;
212
213     /* Conky will likely be linked to libglib-2.0.so and not libglib-1.2.so.0.  If conky is receiving
214      * gchar * data from xmms, these strings need to be freed using g_free() from libglib-1.2.so.0.
215      * This macro selects the g_free() from the correct library. */
216     #define G_FREE(mem) (info.xmms.current_project==PROJECT_XMMS ? (*g_free_v1_2)(mem) : g_free(mem))
217
218     switch(info.xmms.current_project) {
219
220     case (PROJECT_XMMS) :
221             /* make an effort to find the glib 1.2 shared lib */
222             if ( ((glib_v1_2_handle = dlopen("libglib-1.2.so.0", RTLD_LAZY))==NULL) &&
223                  ((glib_v1_2_handle = dlopen("libglib12.so", RTLD_LAZY))==NULL) &&
224                  ((glib_v1_2_handle = dlopen("libglib.so", RTLD_LAZY))==NULL) )
225             {
226                 ERR("unable to find glib 1.2 shared object lib!");
227                 pthread_exit(NULL);
228             }
229             g_free_v1_2=dlsym(glib_v1_2_handle, "g_free");
230             check_dlerror();
231
232             handle = dlopen("libxmms.so", RTLD_LAZY);
233             if (!handle) {
234                 ERR("unable to open libxmms.so");
235                 pthread_exit(NULL);
236             }
237             break;
238                     
239     case (PROJECT_BMP) :
240             handle = dlopen("libbeep.so", RTLD_LAZY);
241             if (!handle) {
242                  ERR("unable to open libbeep.so");
243                  pthread_exit(NULL);
244             }
245             break;
246
247     case (PROJECT_AUDACIOUS) :
248             handle = dlopen("libaudacious.so", RTLD_LAZY);
249             if (!handle) {
250                  ERR("unable to open libaudacious.so");
251                  pthread_exit(NULL);
252             }
253             break;
254
255     case (PROJECT_NONE) :
256     default :
257          pthread_exit(NULL);
258     }
259
260     /* Grab the function pointers from the library */
261     xmms_remote_is_running = dlsym(handle, "xmms_remote_is_running");
262     check_dlerror();
263
264     xmms_remote_is_paused = dlsym(handle, "xmms_remote_is_paused");
265     check_dlerror();
266
267     xmms_remote_is_playing = dlsym(handle, "xmms_remote_is_playing");
268     check_dlerror();
269
270     xmms_remote_get_playlist_pos = dlsym(handle, "xmms_remote_get_playlist_pos");
271     check_dlerror();
272
273     xmms_remote_get_playlist_title = dlsym(handle, "xmms_remote_get_playlist_title");
274     check_dlerror();
275
276     xmms_remote_get_playlist_time = dlsym(handle, "xmms_remote_get_playlist_time");
277     check_dlerror();
278
279     xmms_remote_get_output_time = dlsym(handle, "xmms_remote_get_output_time");
280     check_dlerror();
281
282     xmms_remote_get_info = dlsym(handle, "xmms_remote_get_info");
283
284     xmms_remote_get_playlist_file = dlsym(handle, "xmms_remote_get_playlist_file");
285     check_dlerror();
286
287     xmms_remote_get_playlist_length = dlsym(handle, "xmms_remote_get_playlist_length");
288     check_dlerror();
289
290     /* Grab the runnable signal.  Should be non-zero here or we do nothing. */
291     pthread_mutex_lock(&info.xmms.runnable_mutex);
292     runnable=info.xmms.runnable;
293     pthread_mutex_unlock(&info.xmms.runnable_mutex );
294
295     /* Loop until the main thread sets the runnable signal to 0. */
296     while(runnable) {
297
298         for (;;) {  /* convenience loop so we can break below */
299
300             if (!(*xmms_remote_is_running)(session)) {
301                 memset(&items,0,sizeof(items));
302                 strcpy(items[XMMS_STATUS],"Not running");
303                 break;
304             }
305
306             /* Player status */
307             if ((*xmms_remote_is_paused)(session))
308                 strcpy(items[XMMS_STATUS],"Paused");
309             else if ((*xmms_remote_is_playing)(session))
310                  strcpy(items[XMMS_STATUS],"Playing");
311             else
312                  strcpy(items[XMMS_STATUS],"Stopped");
313
314             /* Current song title */
315             playpos = (*xmms_remote_get_playlist_pos)(session);
316             psong = (*xmms_remote_get_playlist_title)(session, playpos);
317             if (psong) {
318                 strncpy(items[XMMS_TITLE],psong,sizeof(items[XMMS_TITLE])-1);
319                 G_FREE(psong);
320                 psong=NULL;
321             }
322
323             /* Current song length as MM:SS */
324             frames = (*xmms_remote_get_playlist_time)(session,playpos);
325             length = frames / 1000;
326             snprintf(items[XMMS_LENGTH],sizeof(items[XMMS_LENGTH])-1,
327                      "%d:%.2d", length / 60, length % 60);
328
329             /* Current song length in seconds */
330             snprintf(items[XMMS_LENGTH_SECONDS],sizeof(items[XMMS_LENGTH_SECONDS])-1,
331                      "%d", length);
332
333             /* Current song position as MM:SS */
334             frames = (*xmms_remote_get_output_time)(session);
335             length = frames / 1000;
336             snprintf(items[XMMS_POSITION],sizeof(items[XMMS_POSITION])-1,
337                      "%d:%.2d", length / 60, length % 60);
338
339             /* Current song position in seconds */
340             snprintf(items[XMMS_POSITION_SECONDS],sizeof(items[XMMS_POSITION_SECONDS])-1,
341                      "%d", length);
342
343             /* Current song bitrate */
344             (*xmms_remote_get_info)(session, &rate, &freq, &chans);
345             snprintf(items[XMMS_BITRATE],sizeof(items[XMMS_BITRATE])-1, "%d", rate);
346
347             /* Current song frequency */
348             snprintf(items[XMMS_FREQUENCY],sizeof(items[XMMS_FREQUENCY])-1, "%d", freq);
349
350             /* Current song channels */
351             snprintf(items[XMMS_CHANNELS],sizeof(items[XMMS_CHANNELS])-1, "%d", chans);
352
353             /* Current song filename */
354             pfilename = (*xmms_remote_get_playlist_file)(session,playpos);
355             if (pfilename) {
356                 strncpy(items[XMMS_FILENAME],pfilename,sizeof(items[XMMS_FILENAME])-1);
357                 G_FREE(pfilename);
358                 pfilename=NULL;
359             }
360
361             /* Length of the Playlist (number of songs) */
362             length = (*xmms_remote_get_playlist_length)(session);
363             snprintf(items[XMMS_PLAYLIST_LENGTH],sizeof(items[XMMS_PLAYLIST_LENGTH])-1, "%d", length);
364
365             /* Playlist position (index of song) */
366             snprintf(items[XMMS_PLAYLIST_POSITION],sizeof(items[XMMS_PLAYLIST_POSITION])-1, "%d", playpos+1);
367
368             break;
369         }
370
371         /* Deliver the refreshed items array to g_items. */
372         pthread_mutex_lock(&info.xmms.item_mutex);
373         memcpy(&g_items,items,sizeof(items));
374         pthread_mutex_unlock(&info.xmms.item_mutex);
375
376         /* Grab the runnable signal for next loop. */
377         pthread_mutex_lock(&info.xmms.runnable_mutex);
378         runnable=info.xmms.runnable;
379         pthread_mutex_unlock(&info.xmms.runnable_mutex);
380
381         sleep(1);
382     }
383
384     if (handle)
385         dlclose(handle);
386     if (glib_v1_2_handle)
387         dlclose(glib_v1_2_handle);
388
389     pthread_exit(NULL);
390 }
391 #endif
392
393 #if defined(INFOPIPE)
394 /* --------------------------------------------------
395  * Worker thread function for InfoPipe data sampling.
396  * -------------------------------------------------- */ 
397 void *xmms_thread_func_infopipe(void *pvoid)
398 {
399     int i,rc,fd,runnable;
400     fd_set readset;
401     struct timeval tm;
402     static char buf[2048],line[128];
403     static xmms_t items;
404     char *pbuf,c;
405
406     pvoid=(void*)pvoid; /* avoid warning */
407
408     /* Grab the runnable signal.  Should be non-zero here or we do nothing. */
409     pthread_mutex_lock(&info.xmms.runnable_mutex);
410     runnable=info.xmms.runnable;
411     pthread_mutex_unlock(&info.xmms.runnable_mutex );
412
413     /* Loop until the main thread sets the runnable signal to 0. */
414     while(runnable) {
415
416         for (;;) {  /* convenience loop so we can break below */
417
418             memset(buf,0,sizeof(buf));
419
420             if ((fd=open(INFOPIPE_NAMED_PIPE, O_RDONLY | O_NONBLOCK)) < 0) {
421                 /* InfoPipe is not running */
422                 memset(items,0,sizeof(items));
423                 strcpy(items[XMMS_STATUS],"Not running");
424                 break;
425             }
426
427             FD_ZERO(&readset);
428             FD_SET(fd,&readset);
429
430             /* On Linux, select() reduces the timer by the amount of time not slept,
431              * so we must reset the timer with each loop. */
432             tm.tv_sec=1;
433             tm.tv_usec=0;
434             rc=select(fd+1,&readset,NULL,NULL,&tm);
435
436             if (rc == -1) {
437                 /* -- debug -- 
438                 perror("infopipe select()"); 
439                 */
440             }
441             else if (rc && FD_ISSET(fd,&readset)) {  /* ready to read */
442
443                 if (read(fd,buf,sizeof(buf)) > 0) { /* buf has data */
444                     
445                     pbuf=buf;
446                     for (i=0;i<14;i++) {
447                         /* 14 lines of key: value pairs presented in a known order */
448                         memset(line,0,sizeof(line));
449                         if ( sscanf(pbuf,"%*[^:]: %[^\n]",line) == EOF )
450                             break;
451                         while((c = *pbuf++) && (c != '\n'));
452
453                         switch(i) {
454                         case INFOPIPE_PROTOCOL:
455                                 break;
456                         case INFOPIPE_VERSION:
457                                 break;
458                         case INFOPIPE_STATUS:
459                                 strncpy(items[XMMS_STATUS],line,sizeof(items[XMMS_STATUS])-1);
460                                 break;
461                         case INFOPIPE_PLAYLIST_TUNES:
462                                 strncpy(items[XMMS_PLAYLIST_LENGTH],line,sizeof(items[XMMS_PLAYLIST_LENGTH])-1);
463                                 break;
464                         case INFOPIPE_PLAYLIST_CURRTUNE:
465                                 strncpy(items[XMMS_PLAYLIST_POSITION],line,sizeof(items[XMMS_PLAYLIST_POSITION])-1);
466                                 break;
467                         case INFOPIPE_USEC_POSITION:
468                                 snprintf(items[XMMS_POSITION_SECONDS],sizeof(items[XMMS_POSITION_SECONDS])-1,
469                                          "%d", atoi(line) / 1000);
470                                 break;
471                         case INFOPIPE_POSITION:
472                                 strncpy(items[XMMS_POSITION],line,sizeof(items[XMMS_POSITION])-1);
473                                 break;
474                         case INFOPIPE_USEC_TIME:
475                                 snprintf(items[XMMS_LENGTH_SECONDS],sizeof(items[XMMS_LENGTH_SECONDS])-1,
476                                          "%d", atoi(line) / 1000);
477                                 break;
478                         case INFOPIPE_TIME:
479                                 strncpy(items[XMMS_LENGTH],line,sizeof(items[XMMS_LENGTH])-1);
480                                 break;
481                         case INFOPIPE_BITRATE:
482                                 strncpy(items[XMMS_BITRATE],line,sizeof(items[XMMS_BITRATE])-1);
483                                 break;
484                         case INFOPIPE_FREQUENCY:
485                                 strncpy(items[XMMS_FREQUENCY],line,sizeof(items[XMMS_FREQUENCY])-1);
486                                 break;
487                         case INFOPIPE_CHANNELS:
488                                 strncpy(items[XMMS_CHANNELS],line,sizeof(items[XMMS_CHANNELS])-1);
489                                 break;
490                         case INFOPIPE_TITLE:
491                                 strncpy(items[XMMS_TITLE],line,sizeof(items[XMMS_TITLE])-1);
492                                 break;
493                         case INFOPIPE_FILE:
494                                 strncpy(items[XMMS_FILENAME],line,sizeof(items[XMMS_FILENAME])-1);
495                                 break;
496                         default:
497                             break;     
498                         }
499                     }
500
501                     /* -- debug --
502                     for(i=0;i<14;i++)
503                         printf("%s\n",items[i]);
504                     */
505                 } 
506             }
507             else {
508                 /* -- debug --
509                 printf("no infopipe data\n"); 
510                 */
511             }
512
513             close(fd);
514
515             break;
516         }
517
518         /* Deliver the refreshed items array to g_items. */
519         pthread_mutex_lock(&info.xmms.item_mutex);
520         memcpy(&g_items,items,sizeof(items));
521         pthread_mutex_unlock(&info.xmms.item_mutex);
522
523         /* Grab the runnable signal for next loop. */
524         pthread_mutex_lock(&info.xmms.runnable_mutex);
525         runnable=info.xmms.runnable;
526         pthread_mutex_unlock(&info.xmms.runnable_mutex);
527
528         sleep(1);
529     }
530
531     pthread_exit(NULL);
532 }
533
534 #endif