26d93df5d52b33901baa89b4fbaabc631c22b09d
[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             glib_v1_2_handle = dlopen("libglib-1.2.so.0", RTLD_LAZY);
222             if (!glib_v1_2_handle) {
223                 ERR("unable to open libglib-1.2.so");
224                 pthread_exit(NULL);
225             }
226             g_free_v1_2=dlsym(glib_v1_2_handle, "g_free");
227             check_dlerror();
228
229             handle = dlopen("libxmms.so", RTLD_LAZY);
230             if (!handle) {
231                 ERR("unable to open libxmms.so");
232                 pthread_exit(NULL);
233             }
234             break;
235                     
236     case (PROJECT_BMP) :
237             handle = dlopen("libbeep.so", RTLD_LAZY);
238             if (!handle) {
239                  ERR("unable to open libbeep.so");
240                  pthread_exit(NULL);
241             }
242             break;
243
244     case (PROJECT_AUDACIOUS) :
245             handle = dlopen("libaudacious.so", RTLD_LAZY);
246             if (!handle) {
247                  ERR("unable to open libaudacious.so");
248                  pthread_exit(NULL);
249             }
250             break;
251
252     case (PROJECT_NONE) :
253     default :
254          pthread_exit(NULL);
255     }
256
257     /* Grab the function pointers from the library */
258     xmms_remote_is_running = dlsym(handle, "xmms_remote_is_running");
259     check_dlerror();
260
261     xmms_remote_is_paused = dlsym(handle, "xmms_remote_is_paused");
262     check_dlerror();
263
264     xmms_remote_is_playing = dlsym(handle, "xmms_remote_is_playing");
265     check_dlerror();
266
267     xmms_remote_get_playlist_pos = dlsym(handle, "xmms_remote_get_playlist_pos");
268     check_dlerror();
269
270     xmms_remote_get_playlist_title = dlsym(handle, "xmms_remote_get_playlist_title");
271     check_dlerror();
272
273     xmms_remote_get_playlist_time = dlsym(handle, "xmms_remote_get_playlist_time");
274     check_dlerror();
275
276     xmms_remote_get_output_time = dlsym(handle, "xmms_remote_get_output_time");
277     check_dlerror();
278
279     xmms_remote_get_info = dlsym(handle, "xmms_remote_get_info");
280
281     xmms_remote_get_playlist_file = dlsym(handle, "xmms_remote_get_playlist_file");
282     check_dlerror();
283
284     xmms_remote_get_playlist_length = dlsym(handle, "xmms_remote_get_playlist_length");
285     check_dlerror();
286
287     /* Grab the runnable signal.  Should be non-zero here or we do nothing. */
288     pthread_mutex_lock(&info.xmms.runnable_mutex);
289     runnable=info.xmms.runnable;
290     pthread_mutex_unlock(&info.xmms.runnable_mutex );
291
292     /* Loop until the main thread sets the runnable signal to 0. */
293     while(runnable) {
294
295         for (;;) {  /* convenience loop so we can break below */
296
297             if (!(*xmms_remote_is_running)(session)) {
298                 memset(&items,0,sizeof(items));
299                 strcpy(items[XMMS_STATUS],"Not running");
300                 break;
301             }
302
303             /* Player status */
304             if ((*xmms_remote_is_paused)(session))
305                 strcpy(items[XMMS_STATUS],"Paused");
306             else if ((*xmms_remote_is_playing)(session))
307                  strcpy(items[XMMS_STATUS],"Playing");
308             else
309                  strcpy(items[XMMS_STATUS],"Stopped");
310
311             /* Current song title */
312             playpos = (*xmms_remote_get_playlist_pos)(session);
313             psong = (*xmms_remote_get_playlist_title)(session, playpos);
314             if (psong) {
315                 strncpy(items[XMMS_TITLE],psong,sizeof(items[XMMS_TITLE])-1);
316                 G_FREE(psong);
317                 psong=NULL;
318             }
319
320             /* Current song length as MM:SS */
321             frames = (*xmms_remote_get_playlist_time)(session,playpos);
322             length = frames / 1000;
323             snprintf(items[XMMS_LENGTH],sizeof(items[XMMS_LENGTH])-1,
324                      "%d:%.2d", length / 60, length % 60);
325
326             /* Current song length in seconds */
327             snprintf(items[XMMS_LENGTH_SECONDS],sizeof(items[XMMS_LENGTH_SECONDS])-1,
328                      "%d", length);
329
330             /* Current song position as MM:SS */
331             frames = (*xmms_remote_get_output_time)(session);
332             length = frames / 1000;
333             snprintf(items[XMMS_POSITION],sizeof(items[XMMS_POSITION])-1,
334                      "%d:%.2d", length / 60, length % 60);
335
336             /* Current song position in seconds */
337             snprintf(items[XMMS_POSITION_SECONDS],sizeof(items[XMMS_POSITION_SECONDS])-1,
338                      "%d", length);
339
340             /* Current song bitrate */
341             (*xmms_remote_get_info)(session, &rate, &freq, &chans);
342             snprintf(items[XMMS_BITRATE],sizeof(items[XMMS_BITRATE])-1, "%d", rate);
343
344             /* Current song frequency */
345             snprintf(items[XMMS_FREQUENCY],sizeof(items[XMMS_FREQUENCY])-1, "%d", freq);
346
347             /* Current song channels */
348             snprintf(items[XMMS_CHANNELS],sizeof(items[XMMS_CHANNELS])-1, "%d", chans);
349
350             /* Current song filename */
351             pfilename = (*xmms_remote_get_playlist_file)(session,playpos);
352             if (pfilename) {
353                 strncpy(items[XMMS_FILENAME],pfilename,sizeof(items[XMMS_FILENAME])-1);
354                 G_FREE(pfilename);
355                 pfilename=NULL;
356             }
357
358             /* Length of the Playlist (number of songs) */
359             length = (*xmms_remote_get_playlist_length)(session);
360             snprintf(items[XMMS_PLAYLIST_LENGTH],sizeof(items[XMMS_PLAYLIST_LENGTH])-1, "%d", length);
361
362             /* Playlist position (index of song) */
363             snprintf(items[XMMS_PLAYLIST_POSITION],sizeof(items[XMMS_PLAYLIST_POSITION])-1, "%d", playpos+1);
364
365             break;
366         }
367
368         /* Deliver the refreshed items array to g_items. */
369         pthread_mutex_lock(&info.xmms.item_mutex);
370         memcpy(&g_items,items,sizeof(items));
371         pthread_mutex_unlock(&info.xmms.item_mutex);
372
373         /* Grab the runnable signal for next loop. */
374         pthread_mutex_lock(&info.xmms.runnable_mutex);
375         runnable=info.xmms.runnable;
376         pthread_mutex_unlock(&info.xmms.runnable_mutex);
377
378         sleep(1);
379     }
380
381     if (handle)
382         dlclose(handle);
383     if (glib_v1_2_handle)
384         dlclose(glib_v1_2_handle);
385
386     pthread_exit(NULL);
387 }
388 #endif
389
390 #if defined(INFOPIPE)
391 /* --------------------------------------------------
392  * Worker thread function for InfoPipe data sampling.
393  * -------------------------------------------------- */ 
394 void *xmms_thread_func_infopipe(void *pvoid)
395 {
396     int i,rc,fd,runnable;
397     fd_set readset;
398     struct timeval tm;
399     static char buf[2048],line[128];
400     static xmms_t items;
401     char *pbuf,c;
402
403     pvoid=(void*)pvoid; /* avoid warning */
404
405     /* Grab the runnable signal.  Should be non-zero here or we do nothing. */
406     pthread_mutex_lock(&info.xmms.runnable_mutex);
407     runnable=info.xmms.runnable;
408     pthread_mutex_unlock(&info.xmms.runnable_mutex );
409
410     /* Loop until the main thread sets the runnable signal to 0. */
411     while(runnable) {
412
413         for (;;) {  /* convenience loop so we can break below */
414
415             memset(buf,0,sizeof(buf));
416
417             if ((fd=open(INFOPIPE_NAMED_PIPE, O_RDONLY | O_NONBLOCK)) < 0) {
418                 /* InfoPipe is not running */
419                 memset(items,0,sizeof(items));
420                 strcpy(items[XMMS_STATUS],"Not running");
421                 break;
422             }
423
424             FD_ZERO(&readset);
425             FD_SET(fd,&readset);
426
427             /* On Linux, select() reduces the timer by the amount of time not slept,
428              * so we must reset the timer with each loop. */
429             tm.tv_sec=1;
430             tm.tv_usec=0;
431             rc=select(fd+1,&readset,NULL,NULL,&tm);
432
433             if (rc == -1) {
434                 /* -- debug -- 
435                 perror("infopipe select()"); 
436                 */
437             }
438             else if (rc && FD_ISSET(fd,&readset)) {  /* ready to read */
439
440                 if (read(fd,buf,sizeof(buf)) > 0) { /* buf has data */
441                     
442                     pbuf=buf;
443                     for (i=0;i<14;i++) {
444                         /* 14 lines of key: value pairs presented in a known order */
445                         memset(line,0,sizeof(line));
446                         if ( sscanf(pbuf,"%*[^:]: %[^\n]",line) == EOF )
447                             break;
448                         while((c = *pbuf++) && (c != '\n'));
449
450                         switch(i) {
451                         case INFOPIPE_PROTOCOL:
452                                 break;
453                         case INFOPIPE_VERSION:
454                                 break;
455                         case INFOPIPE_STATUS:
456                                 strncpy(items[XMMS_STATUS],line,sizeof(items[XMMS_STATUS])-1);
457                                 break;
458                         case INFOPIPE_PLAYLIST_TUNES:
459                                 strncpy(items[XMMS_PLAYLIST_LENGTH],line,sizeof(items[XMMS_PLAYLIST_LENGTH])-1);
460                                 break;
461                         case INFOPIPE_PLAYLIST_CURRTUNE:
462                                 strncpy(items[XMMS_PLAYLIST_POSITION],line,sizeof(items[XMMS_PLAYLIST_POSITION])-1);
463                                 break;
464                         case INFOPIPE_USEC_POSITION:
465                                 snprintf(items[XMMS_POSITION_SECONDS],sizeof(items[XMMS_POSITION_SECONDS])-1,
466                                          "%d", atoi(line) / 1000);
467                                 break;
468                         case INFOPIPE_POSITION:
469                                 strncpy(items[XMMS_POSITION],line,sizeof(items[XMMS_POSITION])-1);
470                                 break;
471                         case INFOPIPE_USEC_TIME:
472                                 snprintf(items[XMMS_LENGTH_SECONDS],sizeof(items[XMMS_LENGTH_SECONDS])-1,
473                                          "%d", atoi(line) / 1000);
474                                 break;
475                         case INFOPIPE_TIME:
476                                 strncpy(items[XMMS_LENGTH],line,sizeof(items[XMMS_LENGTH])-1);
477                                 break;
478                         case INFOPIPE_BITRATE:
479                                 strncpy(items[XMMS_BITRATE],line,sizeof(items[XMMS_BITRATE])-1);
480                                 break;
481                         case INFOPIPE_FREQUENCY:
482                                 strncpy(items[XMMS_FREQUENCY],line,sizeof(items[XMMS_FREQUENCY])-1);
483                                 break;
484                         case INFOPIPE_CHANNELS:
485                                 strncpy(items[XMMS_CHANNELS],line,sizeof(items[XMMS_CHANNELS])-1);
486                                 break;
487                         case INFOPIPE_TITLE:
488                                 strncpy(items[XMMS_TITLE],line,sizeof(items[XMMS_TITLE])-1);
489                                 break;
490                         case INFOPIPE_FILE:
491                                 strncpy(items[XMMS_FILENAME],line,sizeof(items[XMMS_FILENAME])-1);
492                                 break;
493                         default:
494                             break;     
495                         }
496                     }
497
498                     /* -- debug --
499                     for(i=0;i<14;i++)
500                         printf("%s\n",items[i]);
501                     */
502                 } 
503             }
504             else {
505                 /* -- debug --
506                 printf("no infopipe data\n"); 
507                 */
508             }
509
510             close(fd);
511
512             break;
513         }
514
515         /* Deliver the refreshed items array to g_items. */
516         pthread_mutex_lock(&info.xmms.item_mutex);
517         memcpy(&g_items,items,sizeof(items));
518         pthread_mutex_unlock(&info.xmms.item_mutex);
519
520         /* Grab the runnable signal for next loop. */
521         pthread_mutex_lock(&info.xmms.runnable_mutex);
522         runnable=info.xmms.runnable;
523         pthread_mutex_unlock(&info.xmms.runnable_mutex);
524
525         sleep(1);
526     }
527
528     pthread_exit(NULL);
529 }
530
531 #endif