chiark / gitweb /
report volume at log start
[disorder] / disobedience / disobedience.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2006, 2007 Richard Kettlewell
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, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20
21 #include "disobedience.h"
22
23 #include <getopt.h>
24 #include <locale.h>
25 #include <pcre.h>
26
27 /* Apologies for the numerous de-consting casts, but GLib et al do not seem to
28  * have heard of const. */
29
30 #include "style.h"                      /* generated style */
31
32 /* Variables --------------------------------------------------------------- */
33
34 GMainLoop *mainloop;                    /* event loop */
35 GtkWidget *toplevel;                    /* top level window */
36 GtkWidget *report_label;                /* label for progress indicator */
37 GtkWidget *tabs;                        /* main tabs */
38
39 disorder_eclient *client;               /* main client */
40
41 unsigned long last_state;               /* last reported state */
42 int playing;                            /* true if playing some track */
43 int volume_l, volume_r;                 /* volume */
44 double goesupto = 10;                   /* volume upper bound */
45 int choosealpha;                        /* break up choose by letter */
46
47 /** @brief True if a NOP is in flight */
48 static int nop_in_flight;
49
50 /* Window creation --------------------------------------------------------- */
51
52 /* Note that all the client operations kicked off from here will only complete
53  * after we've entered the event loop. */
54
55 static gboolean delete_event(GtkWidget attribute((unused)) *widget,
56                              GdkEvent attribute((unused)) *event,
57                              gpointer attribute((unused)) data) {
58   D(("delete_event"));
59   exit(0);                              /* die immediately */
60 }
61
62 /* Called when the current tab is switched. */
63 static void tab_switched(GtkNotebook attribute((unused)) *notebook,
64                          GtkNotebookPage attribute((unused)) *page,
65                          guint page_num,
66                          gpointer attribute((unused)) user_data) {
67   menu_update(page_num);
68 }
69
70 static GtkWidget *report_box(void) {
71   GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
72
73   report_label = gtk_label_new("");
74   gtk_label_set_ellipsize(GTK_LABEL(report_label), PANGO_ELLIPSIZE_END);
75   gtk_misc_set_alignment(GTK_MISC(report_label), 0, 0);
76   gtk_container_add(GTK_CONTAINER(vbox), gtk_hseparator_new());
77   gtk_container_add(GTK_CONTAINER(vbox), report_label);
78   return vbox;
79 }
80
81 static GtkWidget *notebook(void) {
82   tabs = gtk_notebook_new();
83   g_signal_connect(tabs, "switch-page", G_CALLBACK(tab_switched), 0);
84   gtk_notebook_append_page(GTK_NOTEBOOK(tabs), queue_widget(),
85                            gtk_label_new("Queue"));
86   gtk_notebook_append_page(GTK_NOTEBOOK(tabs), recent_widget(),
87                            gtk_label_new("Recent"));
88   gtk_notebook_append_page(GTK_NOTEBOOK(tabs), choose_widget(),
89                            gtk_label_new("Choose"));
90   return tabs;
91 }
92
93 static void make_toplevel_window(void) {
94   GtkWidget *const vbox = gtk_vbox_new(FALSE, 1);
95   GtkWidget *const rb = report_box();
96
97   D(("top_window"));
98   toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
99   /* default size is too small */
100   gtk_window_set_default_size(GTK_WINDOW(toplevel), 640, 480);
101   /* terminate on close */
102   g_signal_connect(G_OBJECT(toplevel), "delete_event",
103                    G_CALLBACK(delete_event), NULL);
104   /* lay out the window */
105   gtk_window_set_title(GTK_WINDOW(toplevel), "Disobedience");
106   gtk_container_add(GTK_CONTAINER(toplevel), vbox);
107   /* lay out the vbox */
108   gtk_box_pack_start(GTK_BOX(vbox),
109                      menubar(toplevel),
110                      FALSE,             /* expand */
111                      FALSE,             /* fill */
112                      0);
113   gtk_box_pack_start(GTK_BOX(vbox),
114                      control_widget(),
115                      FALSE,             /* expand */
116                      FALSE,             /* fill */
117                      0);
118   gtk_container_add(GTK_CONTAINER(vbox), notebook());
119   gtk_box_pack_end(GTK_BOX(vbox),
120                    rb,
121                    FALSE,             /* expand */
122                    FALSE,             /* fill */
123                    0);
124   gtk_widget_set_name(toplevel, "disobedience");
125 }
126
127 #if MDEBUG
128 static int widget_count, container_count;
129
130 static void count_callback(GtkWidget *w,
131                            gpointer attribute((unused)) data) {
132   ++widget_count;
133   if(GTK_IS_CONTAINER(w)) {
134     ++container_count;
135     gtk_container_foreach(GTK_CONTAINER(w), count_callback, 0);
136   }
137 }
138
139 static void count_widgets(void) {
140   widget_count = 0;
141   container_count = 1;
142   if(toplevel)
143     gtk_container_foreach(GTK_CONTAINER(toplevel), count_callback, 0);
144   fprintf(stderr, "widget count: %8d  container count: %8d\n",
145           widget_count, container_count);
146 }
147 #endif
148
149 #if MTRACK
150 const char *mtag = "init";
151 static hash *mtrack_hash;
152
153 static int *mthfind(const char *tag) {
154   static const int zero = 0;
155   int *cp = hash_find(mtrack_hash, tag);
156   if(!cp) {
157     hash_add(mtrack_hash, tag, &zero, HASH_INSERT);
158     cp = hash_find(mtrack_hash, tag);
159   }
160   return cp;
161 }
162
163 static void *trap_malloc(size_t n) {
164   void *ptr = malloc(n + sizeof(char *));
165
166   *(const char **)ptr = mtag;
167   ++*mthfind(mtag);
168   return (char *)ptr + sizeof(char *);
169 }
170
171 static void trap_free(void *ptr) {
172   const char *tag;
173   if(!ptr)
174     return;
175   ptr = (char *)ptr - sizeof(char *);
176   tag = *(const char **)ptr;
177   --*mthfind(tag);
178   free(ptr);
179 }
180
181 static void *trap_realloc(void *ptr, size_t n) {
182   if(!ptr)
183     return trap_malloc(n);
184   if(!n) {
185     trap_free(ptr);
186     return 0;
187   }
188   ptr = (char *)ptr - sizeof(char *);
189   ptr = realloc(ptr, n + sizeof(char *));
190   *(const char **)ptr = mtag;
191   return (char *)ptr + sizeof(char *);
192 }
193
194 static int report_tags_callback(const char *key, void *value,
195                                 void attribute((unused)) *u) {
196   fprintf(stderr, "%16s: %d\n", key, *(int *)value);
197   return 0;
198 }
199
200 static void report_tags(void) {
201   hash_foreach(mtrack_hash, report_tags_callback, 0);
202   fprintf(stderr, "\n");
203 }
204
205 static const GMemVTable glib_memvtable = {
206   trap_malloc,
207   trap_realloc,
208   trap_free,
209   0,
210   0,
211   0
212 };
213 #endif
214
215 /* Called once every 10 minutes */
216 static gboolean periodic(gpointer attribute((unused)) data) {
217   D(("periodic"));
218   /* Expire cached data */
219   cache_expire();
220   /* Update everything to be sure that the connection to the server hasn't
221    * mysteriously gone stale on us. */
222   all_update();
223 #if MDEBUG
224   count_widgets();
225   fprintf(stderr, "cache size: %zu\n", cache_count());
226 #endif
227 #if MTRACK
228   report_tags();
229 #endif
230   return TRUE;                          /* don't remove me */
231 }
232
233 static gboolean heartbeat(gpointer attribute((unused)) data) {
234   static struct timeval last;
235   struct timeval now;
236   double delta;
237
238   xgettimeofday(&now, 0);
239   if(last.tv_sec) {
240     delta = (now.tv_sec + now.tv_sec / 1.0E6) 
241       - (last.tv_sec + last.tv_sec / 1.0E6);
242     if(delta >= 1.0625)
243       fprintf(stderr, "%f: %fs between 1s heartbeats\n", 
244               now.tv_sec + now.tv_sec / 1.0E6,
245               delta);
246   }
247   last = now;
248   return TRUE;
249 }
250
251 /** @brief Called when a NOP completes */
252 static void nop_completed(void attribute((unused)) *v) {
253   nop_in_flight = 0;
254 }
255
256 /** @brief Called from time to time to arrange for a NOP to be sent
257  *
258  * At most one NOP remains in flight at any moment.  If the client is not
259  * currently connected then no NOP is sent.
260  */
261 static gboolean maybe_send_nop(gpointer attribute((unused)) data) {
262   if(!nop_in_flight && (disorder_eclient_state(client) & DISORDER_CONNECTED)) {
263     nop_in_flight = 1;
264     disorder_eclient_nop(client, nop_completed, 0);
265   }
266   return TRUE;                          /* keep call me please */
267 }
268
269 /* main -------------------------------------------------------------------- */
270
271 static const struct option options[] = {
272   { "help", no_argument, 0, 'h' },
273   { "version", no_argument, 0, 'V' },
274   { "config", required_argument, 0, 'c' },
275   { "tufnel", no_argument, 0, 't' },
276   { "choosealpha", no_argument, 0, 'C' },
277   { "debug", no_argument, 0, 'd' },
278   { 0, 0, 0, 0 }
279 };
280
281 /* display usage message and terminate */
282 static void help(void) {
283   xprintf("Disobedience - GUI client for DisOrder\n"
284           "\n"
285           "Usage:\n"
286           "  disobedience [OPTIONS]\n"
287           "Options:\n"
288           "  --help, -h              Display usage message\n"
289           "  --version, -V           Display version number\n"
290           "  --config PATH, -c PATH  Set configuration file\n"
291           "  --debug, -d             Turn on debugging\n"
292           "\n"
293           "Also GTK+ options will work.\n");
294   xfclose(stdout);
295   exit(0);
296 }
297
298 /* display version number and terminate */
299 static void version(void) {
300   xprintf("disorder version %s\n", disorder_version_string);
301   xfclose(stdout);
302   exit(0);
303 }
304
305 int main(int argc, char **argv) {
306   int n;
307   disorder_eclient *logclient;
308
309   mem_init();
310   /* garbage-collect PCRE's memory */
311   pcre_malloc = xmalloc;
312   pcre_free = xfree;
313 #if MTRACK
314   mtrack_hash = hash_new(sizeof (int));
315   g_mem_set_vtable((GMemVTable *)&glib_memvtable);
316 #endif
317   if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
318   gtk_init(&argc, &argv);
319   gtk_rc_parse_string(style);
320   while((n = getopt_long(argc, argv, "hVc:dtH", options, 0)) >= 0) {
321     switch(n) {
322     case 'h': help();
323     case 'V': version();
324     case 'c': configfile = optarg; break;
325     case 'd': debugging = 1; break;
326     case 't': goesupto = 11; break;
327     case 'C': choosealpha = 1; break;   /* not well tested any more */
328     default: fatal(0, "invalid option");
329     }
330   }
331   signal(SIGPIPE, SIG_IGN);
332   /* create the event loop */
333   D(("create main loop"));
334   mainloop = g_main_loop_new(0, 0);
335   if(config_read()) fatal(0, "cannot read configuration");
336   /* create the clients */
337   if(!(client = gtkclient())
338      || !(logclient = gtkclient()))
339     return 1;                           /* already reported an error */
340   disorder_eclient_log(logclient, &log_callbacks, 0);
341   /* periodic operations (e.g. expiring the cache) */
342 #if MDEBUG || MTRACK
343   g_timeout_add(5000/*milliseconds*/, periodic, 0);
344 #else
345   g_timeout_add(600000/*milliseconds*/, periodic, 0);
346 #endif
347   /* The point of this is to try and get a handle on mysterious
348    * unresponsiveness.  It's not very useful in production use. */
349   if(0)
350     g_timeout_add(1000/*milliseconds*/, heartbeat, 0);
351   make_toplevel_window();
352   /* reset styles now everything has its name */
353   gtk_rc_reset_styles(gtk_settings_get_for_screen(gdk_screen_get_default()));
354   gtk_widget_show_all(toplevel);
355   /* set initial control button visibility/usability */
356   control_update();
357   /* issue a NOP every so often */
358   g_timeout_add_full(G_PRIORITY_LOW,
359                      1000/*interval, ms*/,
360                      maybe_send_nop,
361                      0/*data*/,
362                      0/*notify*/);
363   D(("enter main loop"));
364   MTAG("misc");
365   g_main_loop_run(mainloop);
366   return 0;
367 }
368
369 /*
370 Local Variables:
371 c-basic-offset:2
372 comment-column:40
373 fill-column:79
374 indent-tabs-mode:nil
375 End:
376 */