* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file disobedience/client.c
+ * @brief GLIB integration for @ref lib/eclient.c client
+ */
#include "disobedience.h"
-/* GSource subclass for disorder_eclient */
+/** @brief GSource subclass for disorder_eclient */
struct eclient_source {
GSource gsource;
disorder_eclient *client;
GPollFD pollfd;
};
-/* Called before FDs are polled to choose a timeout. We ask for a 3s
- * timeout and every 10s or so we force a dispatch. */
+/** @brief Called before FDs are polled to choose a timeout.
+ *
+ * We ask for a 3s timeout and every 10s or so we force a dispatch.
+ */
static gboolean gtkclient_prepare(GSource *source,
gint *timeout) {
const struct eclient_source *esource = (struct eclient_source *)source;
return FALSE; /* please poll */
}
-/* Check whether we're ready to dispatch. */
+/** @brief Check whether we're ready to dispatch */
static gboolean gtkclient_check(GSource *source) {
const struct eclient_source *esource = (struct eclient_source *)source;
D(("gtkclient_check fd=%d events=%x revents=%x",
return esource->pollfd.fd != -1 && esource->pollfd.revents != 0;
}
-/* Dispatch */
+/** @brief Called after poll() (or otherwise) to dispatch an event */
static gboolean gtkclient_dispatch(GSource *source,
GSourceFunc attribute((unused)) callback,
gpointer attribute((unused)) user_data) {
return TRUE; /* ??? not documented */
}
-/* Table of callbacks for GSource subclass */
+/** @brief Table of callbacks for GSource subclass */
static const GSourceFuncs sourcefuncs = {
gtkclient_prepare,
gtkclient_check,
0,
};
-/* Tell the mainloop what we need */
+/** @brief Tell the mainloop what we need */
static void gtkclient_poll(void *u,
disorder_eclient attribute((unused)) *c,
int fd, unsigned mode) {
}
}
-/* Report a communication-level error. It will be automatically retried. */
+/** @brief Report a communication-level error
+ *
+ * Any operations still outstanding are automatically replied by the underlying
+ * @ref lib/eclient.c code.
+ */
static void gtkclient_comms_error(void attribute((unused)) *u,
const char *msg) {
D(("gtkclient_comms_error %s", msg));
gtk_label_set_text(GTK_LABEL(report_label), msg);
}
-/* Report a protocol error. It will not be retried. We offer a callback to
- * the submitter of the original command and if none is supplied we pop up an
- * error box. */
+/** @brief Report a protocol-level error
+ *
+ * The error will not be retried. We offer a callback to the submitter of the
+ * original command and if none is supplied we pop up an error box.
+ */
static void gtkclient_protocol_error(void attribute((unused)) *u,
void *v,
int code,
popup_protocol_error(code, msg);
}
+/** @brief Report callback from eclient */
static void gtkclient_report(void attribute((unused)) *u,
const char *msg) {
if(!msg)
menu_update(-1);
}
+/** @brief Repoort an unhandled protocol-level error to the user */
void popup_protocol_error(int attribute((unused)) code,
const char *msg) {
gtk_label_set_text(GTK_LABEL(report_label), msg);
popup_error(msg);
}
-/* Table of eclient callbacks */
+/** @brief Table of eclient callbacks */
static const disorder_eclient_callbacks gtkclient_callbacks = {
gtkclient_comms_error,
gtkclient_protocol_error,
gtkclient_report
};
-/* Create an eclient using the GLib main loop */
+/** @brief Create a @ref disorder_ecliient using the GLib main loop */
disorder_eclient *gtkclient(void) {
GSource *source;
struct eclient_source *esource;
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file disobedience/control.c
+ * @brief Volume control and buttons
+ */
#include "disobedience.h"
/* Control bar ------------------------------------------------------------- */
+/** @brief Guard against feedback loop in volume control */
static int suppress_set_volume;
-/* Guard against feedback loop in volume control */
-static struct icon {
+/** @brief Definition of an icon
+ *
+ * The design here is rather mad: rather than changing the image displayed by
+ * icons according to their state, we flip the visibility of pairs of icons.
+ */
+struct icon {
+ /** @brief Filename for image */
const char *icon;
+
+ /** @brief Text for tooltip */
const char *tip;
+
+ /** @brief Called when button is clicked (activated) */
void (*clicked)(GtkButton *button, gpointer userdata);
+
+ /** @brief Called to update button when state may have changed */
void (*update)(const struct icon *i);
+
+ /** @brief @ref eclient.h function to call */
int (*action)(disorder_eclient *c,
disorder_eclient_no_response *completed,
void *v);
+
+ /** @brief Pointer to button */
GtkWidget *button;
-} icons[] = {
+};
+
+/** @brief Table of all icons */
+static struct icon icons[] = {
{ "pause.png", "Pause playing track", clicked_icon, update_pause,
disorder_eclient_pause, 0 },
{ "play.png", "Resume playing track", clicked_icon, update_play,
{ "notescross.png", "Disable play", clicked_icon, update_disable,
disorder_eclient_disable, 0 },
};
+
+/** @brief Count of icons */
#define NICONS (int)(sizeof icons / sizeof *icons)
-GtkAdjustment *volume_adj, *balance_adj;
+static GtkAdjustment *volume_adj;
+static GtkAdjustment *balance_adj;
/** @brief Called whenever last_state changes in any way */
static void control_monitor(void attribute((unused)) *u) {
icons[n].update(&icons[n]);
}
-/* Create the control bar */
+/** @brief Create the control bar */
GtkWidget *control_widget(void) {
GtkWidget *hbox = gtk_hbox_new(FALSE, 1), *vbox;
GtkWidget *content;
icon->action(client, 0, 0);
}
+/** @brief Called when the volume has been adjusted */
static void volume_adjusted(GtkAdjustment attribute((unused)) *a,
gpointer attribute((unused)) user_data) {
double v = gtk_adjustment_get_value(volume_adj) / goesupto;
0);
}
-/* Called to format the volume value */
+/** @brief Formats the volume value */
static gchar *format_volume(GtkScale attribute((unused)) *scale,
gdouble value) {
char s[32];
return g_strdup(s);
}
-/* Called to format the balance value. */
+/** @brief Formats the balance value */
static gchar *format_balance(GtkScale attribute((unused)) *scale,
gdouble value) {
char s[32];
* Thanks to Clive and Andrew.
*/
+/** @brief Return the greater of @p x and @p y */
static double max(double x, double y) {
return x > y ? x : y;
}
+/** @brief Compute the left channel volume */
static double left(double v, double b) {
if(b > 0) /* volume = right */
return v * (1 - b);
return v;
}
+/** @brief Compute the right channel volume */
static double right(double v, double b) {
if(b > 0) /* volume = right */
return v;
return v * (1 + b);
}
+/** @brief Compute the overall volume */
static double volume(double l, double r) {
return max(l, r);
}
+/** @brief Compute the balance */
static double balance(double l, double r) {
if(l > r)
return r / l - 1;
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file disobedience/disobedience.c
+ * @brief Main Disobedience program
+ */
#include "disobedience.h"
/* Variables --------------------------------------------------------------- */
-GMainLoop *mainloop; /* event loop */
-GtkWidget *toplevel; /* top level window */
-GtkWidget *report_label; /* label for progress indicator */
-GtkWidget *tabs; /* main tabs */
+/** @brief Event loop */
+GMainLoop *mainloop;
+
+/** @brief Top-level window */
+GtkWidget *toplevel;
+
+/** @brief Label for progress indicator */
+GtkWidget *report_label;
+
+/** @brief Main tab group */
+GtkWidget *tabs;
+
+/** @brief Main client */
+disorder_eclient *client;
-disorder_eclient *client; /* main client */
+/** @brief Last reported state
+ *
+ * This is updated by log_state().
+ */
+unsigned long last_state;
+
+/** @brief True if some track is playing
+ *
+ * This ought to be removed in favour of last_state & DISORDER_PLAYING
+ */
+int playing;
+
+/** @brief Left channel volume */
+int volume_l;
+
+/** @brief Right channel volume */
+int volume_r;
-unsigned long last_state; /* last reported state */
-int playing; /* true if playing some track */
-int volume_l, volume_r; /* volume */
double goesupto = 10; /* volume upper bound */
-int choosealpha; /* break up choose by letter */
+
+/** @brief Break up choose tab by initial letter */
+int choosealpha;
/** @brief True if a NOP is in flight */
static int nop_in_flight;
/* Note that all the client operations kicked off from here will only complete
* after we've entered the event loop. */
+/** @brief Called when main window is deleted
+ *
+ * Terminates the program.
+ */
static gboolean delete_event(GtkWidget attribute((unused)) *widget,
GdkEvent attribute((unused)) *event,
gpointer attribute((unused)) data) {
exit(0); /* die immediately */
}
-/* Called when the current tab is switched. */
+/** @brief Called when the current tab is switched
+ *
+ * Updates the menu settings to correspond to the new page.
+ */
static void tab_switched(GtkNotebook attribute((unused)) *notebook,
GtkNotebookPage attribute((unused)) *page,
guint page_num,
menu_update(page_num);
}
+/** @brief Create the report box */
static GtkWidget *report_box(void) {
GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
return vbox;
}
+/** @brief Create and populate the main tab group */
static GtkWidget *notebook(void) {
tabs = gtk_notebook_new();
g_signal_connect(tabs, "switch-page", G_CALLBACK(tab_switched), 0);
return tabs;
}
+/** @brief Create and populate the main window */
static void make_toplevel_window(void) {
GtkWidget *const vbox = gtk_vbox_new(FALSE, 1);
GtkWidget *const rb = report_box();
};
#endif
-/* Called once every 10 minutes */
+/** @brief Called once every 10 minutes */
static gboolean periodic(gpointer attribute((unused)) data) {
D(("periodic"));
/* Expire cached data */
return TRUE; /* don't remove me */
}
+/** @brief Called from time to time
+ *
+ * Used for debugging purposes
+ */
static gboolean heartbeat(gpointer attribute((unused)) data) {
static struct timeval last;
struct timeval now;
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file disobedience/log.c
+ * @brief State monitoring
+ *
+ * Disobedience relies on the server to tell when essentially anything changes,
+ * even if it initiated the change itself. It uses the @c log command to
+ * achieve this.
+ */
#include "disobedience.h"
log_volume
};
+/** @brief State monitor
+ *
+ * We keep a linked list of everything that is interested in state changes.
+ */
struct monitor {
+ /** @brief Next monitor */
struct monitor *next;
+
+ /** @brief State bits of interest */
unsigned long mask;
+
+ /** @brief Function to call if any of @c mask change */
monitor_callback *callback;
+
+ /** @brief User data for callback */
void *u;
};
+/** @brief List of monitors */
static struct monitor *monitors;
+/** @brief Update everything */
void all_update(void) {
playing_update();
queue_update();
volume_update();
}
+/** @brief Called when the client connects
+ *
+ * Depending on server and network state the TCP connection to the server may
+ * go up or down many times during the lifetime of Disobedience. This function
+ * is called whenever it connects.
+ *
+ * The intent is to use the monitor logic to achieve this in future.
+ */
static void log_connected(void attribute((unused)) *v) {
/* Don't know what we might have missed while disconnected so update
* everything. We get this at startup too and this is how we do the initial
all_update();
}
+/** @brief Called when the current track finishes playing */
static void log_completed(void attribute((unused)) *v,
const char attribute((unused)) *track) {
playing = 0;
playing_update();
}
+/** @brief Called when the current track fails */
static void log_failed(void attribute((unused)) *v,
const char attribute((unused)) *track,
const char attribute((unused)) *status) {
playing_update();
}
+/** @brief Called when some track is moved within the queue */
static void log_moved(void attribute((unused)) *v,
const char attribute((unused)) *user) {
- queue_update();
+ queue_update();
}
static void log_playing(void attribute((unused)) *v,
* here */
}
+/** @brief Called when a track is added to the queue */
static void log_queue(void attribute((unused)) *v,
struct queue_entry attribute((unused)) *q) {
queue_update();
}
+/** @brief Called when a track is added to the recently-played list */
static void log_recent_added(void attribute((unused)) *v,
struct queue_entry attribute((unused)) *q) {
recent_update();
}
+/** @brief Called when a track is removed from the recently-played list
+ *
+ * We do nothing here - log_recent_added() suffices.
+ */
static void log_recent_removed(void attribute((unused)) *v,
const char attribute((unused)) *id) {
/* nothing - log_recent_added() will trigger the relevant update */
}
+/** @brief Called when a track is removed from the queue */
static void log_removed(void attribute((unused)) *v,
const char attribute((unused)) *id,
const char attribute((unused)) *user) {
queue_update();
}
+/** @brief Called when the current track is scratched */
static void log_scratched(void attribute((unused)) *v,
const char attribute((unused)) *track,
const char attribute((unused)) *user) {
playing_update();
}
+/** @brief Called when a state change occurs */
static void log_state(void attribute((unused)) *v,
unsigned long state) {
const struct monitor *m;
playing_update();
}
+/** @brief Called when volume changes */
static void log_volume(void attribute((unused)) *v,
int l, int r) {
if(volume_l != l || volume_r != r) {
}
}
+/** @brief Add a monitor to the list */
void register_monitor(monitor_callback *callback,
void *u,
unsigned long mask) {