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