1 /* XOSD
2 
3 Copyright (c) 2001 Andre Renaud (andre@ignavus.net)
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 
20 #include <ctype.h>
21 #include <gtk/gtk.h>
22 
23 #include "bmp_osd.h"
24 
25 #include <xmms/plugin.h>
26 #include <xmms/xmmsctrl.h>
27 #include <xmms/configfile.h>
28 
29 static void init(void);
30 static void cleanup(void);
31 static gint timeout_func(gpointer);
32 
33 GeneralPlugin gp = {
34   .handle = NULL,
35   .filename = NULL,
36   .xmms_session = -1,
37   .description = "On Screen Display " XOSD_VERSION,
38   .init = init,
39   .about = NULL,
40   .configure = configure,
41   .cleanup = cleanup,
42 };
43 
44 
45 struct state
46 {
47   gboolean playing;
48   gboolean paused;
49   gboolean shuffle;
50   gboolean repeat;
51   int pos;
52   int volume;
53   int balance;
54   gchar *title;
55 };
56 static struct state previous;
57 struct show show;
58 
59 xosd *osd = NULL;
60 static guint timeout_tag;
61 
62 gchar *font;
63 gchar *colour;
64 
65 gint timeout;
66 gint offset;
67 gint h_offset;
68 gint shadow_offset;
69 gint outline_offset;
70 gint pos;
71 gint align;
72 
73 /*
74  * Return plugin structure.
75  */
76 GeneralPlugin *
get_gplugin_info(void)77 get_gplugin_info(void)
78 {
79   return &gp;
80 }
81 
82 
83 /*
84  * Initialize plugin.
85  */
86 static void
init(void)87 init(void)
88 {
89   DEBUG("init");
90 
91   if (osd) {
92     DEBUG("uniniting osd");
93     xosd_destroy(osd);
94     osd = NULL;
95   }
96 
97   read_config();
98 
99 #if 0
100   /* Deadlocks, because it is to early. */
101   previous.playing = xmms_remote_is_playing(gp.xmms_session);
102   previous.paused = xmms_remote_is_paused(gp.xmms_session);
103   previous.shuffle = xmms_remote_is_shuffle(gp.xmms_session);
104   previous.repeat = xmms_remote_is_repeat(gp.xmms_session);
105   previous.pos = xmms_remote_get_playlist_pos(gp.xmms_session);
106   previous.volume = xmms_remote_get_main_volume(gp.xmms_session);
107   previous.balance = (xmms_remote_get_balance(gp.xmms_session) + 100) / 2;
108   previous.title = NULL;
109 #else
110   memset(&previous, 0, sizeof(struct state));
111 #endif
112 
113   DEBUG("calling osd init function");
114 
115   osd = xosd_create(2);
116   apply_config();
117   DEBUG("osd initialized");
118   if (osd)
119     timeout_tag = gtk_timeout_add(100, timeout_func, NULL);
120 }
121 
122 /*
123  * Free memory and release resources.
124  */
125 static void
cleanup(void)126 cleanup(void)
127 {
128   DEBUG("cleanup");
129   if (osd && timeout_tag)
130     gtk_timeout_remove(timeout_tag);
131   timeout_tag = 0;
132 
133   if (font) {
134     g_free(font);
135     font = NULL;
136   }
137 
138   if (colour) {
139     g_free(colour);
140     colour = NULL;
141   }
142 
143   if (previous.title) {
144     g_free(previous.title);
145     previous.title = NULL;
146   }
147 
148   if (osd) {
149     DEBUG("hide");
150     xosd_hide(osd);
151     DEBUG("uninit");
152     xosd_destroy(osd);
153     DEBUG("done with osd");
154     osd = NULL;
155   }
156 }
157 
158 /*
159  * Read configuration and initialize variables.
160  */
161 void
read_config(void)162 read_config(void)
163 {
164   ConfigFile *cfgfile;
165   DEBUG("read_config");
166 
167   show.volume = 1;
168   show.balance = 1;
169   show.pause = 1;
170   show.trackname = 1;
171   show.stop = 1;
172   show.repeat = 1;
173   show.shuffle = 1;
174 
175   g_free(colour);
176   g_free(font);
177   colour = NULL;
178   font = NULL;
179   timeout = 3;
180   offset = 50;
181   h_offset = 0;
182   shadow_offset = 1;
183   outline_offset = 0;
184   pos = XOSD_bottom;
185   align = XOSD_left;
186 
187   DEBUG("reading configuration data");
188   if ((cfgfile = xmms_cfg_open_default_file()) != NULL) {
189     xmms_cfg_read_string(cfgfile, "osd", "font", &font);
190     xmms_cfg_read_string(cfgfile, "osd", "colour", &colour);
191     xmms_cfg_read_int(cfgfile, "osd", "timeout", &timeout);
192     xmms_cfg_read_int(cfgfile, "osd", "offset", &offset);
193     xmms_cfg_read_int(cfgfile, "osd", "h_offset", &h_offset);
194     xmms_cfg_read_int(cfgfile, "osd", "shadow_offset", &shadow_offset);
195     xmms_cfg_read_int(cfgfile, "osd", "outline_offset", &outline_offset);
196     xmms_cfg_read_int(cfgfile, "osd", "pos", &pos);
197     xmms_cfg_read_int(cfgfile, "osd", "align", &align);
198     xmms_cfg_read_int(cfgfile, "osd", "show_volume", &show.volume);
199     xmms_cfg_read_int(cfgfile, "osd", "show_balance", &show.balance);
200     xmms_cfg_read_int(cfgfile, "osd", "show_pause", &show.pause);
201     xmms_cfg_read_int(cfgfile, "osd", "show_trackname", &show.trackname);
202     xmms_cfg_read_int(cfgfile, "osd", "show_stop", &show.stop);
203     xmms_cfg_read_int(cfgfile, "osd", "show_repeat", &show.repeat);
204     xmms_cfg_read_int(cfgfile, "osd", "show_shuffle", &show.shuffle);
205     xmms_cfg_free(cfgfile);
206   }
207 
208   DEBUG("getting default font");
209   if (font == NULL)
210     font = g_strdup(osd_default_font);
211 
212   DEBUG("default colour");
213   if (colour == NULL)
214     colour = g_strdup("green");
215 
216   DEBUG("done");
217 }
218 
219 /*
220  * Write configuration.
221  */
222 void
write_config(void)223 write_config(void)
224 {
225   ConfigFile *cfgfile;
226   DEBUG("write_config");
227 
228   if ((cfgfile = xmms_cfg_open_default_file()) != NULL) {
229     xmms_cfg_write_string(cfgfile, "osd", "colour", colour);
230     xmms_cfg_write_string(cfgfile, "osd", "font", font);
231     xmms_cfg_write_int(cfgfile, "osd", "timeout", timeout);
232     xmms_cfg_write_int(cfgfile, "osd", "offset", offset);
233     xmms_cfg_write_int(cfgfile, "osd", "h_offset", h_offset);
234     xmms_cfg_write_int(cfgfile, "osd", "shadow_offset", shadow_offset);
235     xmms_cfg_write_int(cfgfile, "osd", "outline_offset", outline_offset);
236     xmms_cfg_write_int(cfgfile, "osd", "pos", pos);
237     xmms_cfg_write_int(cfgfile, "osd", "align", align);
238 
239     xmms_cfg_write_int(cfgfile, "osd", "show_volume", show.volume);
240     xmms_cfg_write_int(cfgfile, "osd", "show_balance", show.balance);
241     xmms_cfg_write_int(cfgfile, "osd", "show_pause", show.pause);
242     xmms_cfg_write_int(cfgfile, "osd", "show_trackname", show.trackname);
243     xmms_cfg_write_int(cfgfile, "osd", "show_stop", show.stop);
244     xmms_cfg_write_int(cfgfile, "osd", "show_repeat", show.repeat);
245     xmms_cfg_write_int(cfgfile, "osd", "show_shuffle", show.shuffle);
246 
247     xmms_cfg_write_default_file(cfgfile);
248     xmms_cfg_free(cfgfile);
249   }
250 
251   DEBUG("done");
252 }
253 
254 void
apply_config(void)255 apply_config(void)
256 {
257   DEBUG("apply_config");
258   if (osd) {
259     if (xosd_set_font(osd, font) == -1)
260       DEBUG("invalid font %s", font);
261 
262     xosd_set_colour(osd, colour);
263     xosd_set_timeout(osd, timeout);
264     xosd_set_shadow_offset(osd, shadow_offset);
265     xosd_set_outline_offset(osd, outline_offset);
266     xosd_set_pos(osd, pos);
267     xosd_set_align(osd, align);
268     xosd_set_vertical_offset(osd, offset);
269     xosd_set_horizontal_offset(osd, h_offset);
270   }
271   DEBUG("done");
272 }
273 
274 /*
275  * Convert hexcode to ASCII.
276  */
277 static void
replace_hexcodes(gchar * text)278 replace_hexcodes(gchar * text)
279 {
280   gchar *head, *tail;
281   int conv_underscore, c;
282   ConfigFile *cfgfile;
283   DEBUG("replace_hexcodes");
284 
285   if ((cfgfile = xmms_cfg_open_default_file()) != NULL) {
286     xmms_cfg_read_boolean(cfgfile, "xmms", "convert_underscore",
287                           &conv_underscore);
288     xmms_cfg_free(cfgfile);
289   }
290 
291   for (head = tail = text; *head; head++, tail++) {
292     /* replace underscors with spaces if necessary */
293     if (conv_underscore && *head == '_') {
294       *tail = ' ';
295       continue;
296     }
297     /* replace hex with character if necessary */
298     if (*head == '%' && isxdigit(head[1]) && isxdigit(head[2])) {
299       sscanf(head + 1, "%2x", &c);
300       *tail = (char) c;
301       head += 2;
302       continue;
303     }
304     *tail = *head;
305   }
306   *tail = '\0';
307 }
308 
309 /*
310  * Callback funtion to handle delayed display.
311  */
312 static gint
timeout_func(gpointer data)313 timeout_func(gpointer data)
314 {
315   struct state current;
316   char *text = NULL;
317   gboolean songchange, showtext, withtime = FALSE;
318   gint playlist_length;
319 
320   DEBUG("timeout func");
321 
322   if (!osd)
323     return FALSE;
324 
325   GDK_THREADS_ENTER();
326 
327   current.playing = xmms_remote_is_playing(gp.xmms_session);
328   current.paused = xmms_remote_is_paused(gp.xmms_session);
329   current.shuffle = xmms_remote_is_shuffle(gp.xmms_session);
330   current.repeat = xmms_remote_is_repeat(gp.xmms_session);
331   current.pos = xmms_remote_get_playlist_pos(gp.xmms_session);
332   current.volume = xmms_remote_get_main_volume(gp.xmms_session);
333   current.balance = (xmms_remote_get_balance(gp.xmms_session) + 100) / 2;
334 
335   /* Get the current title only if the playlist is not empty. Otherwise
336    * it'll crash. Don't forget to free the title! */
337   playlist_length = xmms_remote_get_playlist_length(gp.xmms_session);
338   current.title = playlist_length
339     ? xmms_remote_get_playlist_title(gp.xmms_session, current.pos) : NULL;
340 
341   /* Check for song change. Deleting a song from the playlist only changed the
342    * name, but not the position, so compare also by (still hexencoded) name. */
343   songchange =
344     (previous.pos != current.pos) ||
345     (previous.title == NULL && current.title != NULL) ||
346     (previous.title != NULL && current.title == NULL) ||
347     (previous.title != NULL && current.title != NULL &&
348      (g_strcasecmp(previous.title, current.title) != 0));
349 
350   /* Possible show something when either song or state changed. */
351   showtext = songchange ||
352     ((current.playing != previous.playing) ||
353      (current.paused != previous.paused));
354 
355   /* Determine right text depending on state and state/title change.
356    *    +---+          +---+
357    *    |   |          |   |
358    *    +->PLAY<---->STOP<-+
359    *        ^          ^
360    *        |          |
361    *        +-->PAUSE--+
362    */
363   if (!current.playing) {       /* {PLAY,PAUSE,STOP} -> STOP */
364     text = "Stopped";
365     showtext &= show.stop;
366   } else if (current.paused) {  /* PLAY -> PAUSE */
367     text = "Paused";
368     showtext &= show.pause;
369     withtime = TRUE;
370   } else if (previous.paused && !current.paused && !songchange) {       /* PAUSE -SameSong-> PLAY */
371     text = "Unpaused";
372     showtext &= show.pause;
373     withtime = TRUE;
374   } else {                      /* {PLAY,STOP} -> PLAY <-OtherSong- PAUSE */
375     text = "Playing";
376     showtext &= show.trackname;
377   }
378 
379   /* Decide what to display, in decreasing priority. */
380   if (showtext) {
381     xosd_display(osd, 0, XOSD_string, text);
382     if (show.trackname && (current.title != NULL)) {
383       int len;
384       char *title;
385       gint playlist_time;
386 
387       len = 13 + strlen(current.title) + (withtime ? 11 : 0);
388       title = malloc(len);
389       playlist_time =
390         withtime ? xmms_remote_get_output_time(gp.xmms_session) : 0;
391       snprintf(title, len,
392                withtime ? "%i/%i: %s (%i:%02i)" : "%i/%i: %s",
393                current.pos + 1, playlist_length, current.title,
394                playlist_time / 1000 / 60, playlist_time / 1000 % 60);
395       replace_hexcodes(title);
396       xosd_display(osd, 1, XOSD_string, title);
397       free(title);
398     } else
399       xosd_display(osd, 1, XOSD_string, "");
400   } else if (current.volume != previous.volume && show.volume) {
401     DEBUG("V: %d->%d\n", previous.volume, current.volume);
402     /* xmms returns -1 during a title change. skip this and try again later. */
403     if ((previous.volume == -1) || (current.volume == -1))
404       goto skip;
405     xosd_display(osd, 0, XOSD_string, "Volume");
406     xosd_display(osd, 1, XOSD_percentage, current.volume);
407   } else if (current.balance != previous.balance && show.balance) {
408     DEBUG("B: %d->%d\n", previous.balance, current.balance);
409     /* FIXME: Same as above might happen, but with what values? */
410     xosd_display(osd, 0, XOSD_string, "Balance");
411     xosd_display(osd, 1, XOSD_slider, current.balance);
412   } else if (current.repeat != previous.repeat && show.repeat) {
413     xosd_display(osd, 0, XOSD_string, "Repeat");
414     xosd_display(osd, 1, XOSD_string, current.repeat ? "On" : "Off");
415   } else if (current.shuffle != previous.shuffle && show.shuffle) {
416     xosd_display(osd, 0, XOSD_string, "Shuffle");
417     xosd_display(osd, 1, XOSD_string, current.shuffle ? "On" : "Off");
418   }
419 
420 skip:
421   /* copy current state (including title) for future comparison. Free old
422    * title first. */
423   if (previous.title)
424     g_free(previous.title);
425   previous = current;
426 
427   GDK_THREADS_LEAVE();
428 
429 
430   return TRUE;
431 }
432 
433 /* vim: tabstop=8 shiftwidth=8 noexpandtab
434  */
435