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