/*
- * Devious: UPNP Control Point for Maemo 5
+ * Devious: UPnP Control Point for Maemo 5
*
* Copyright (C) 2009 Kyle Cronan
*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <gio/gio.h>
#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gtk/gtk.h>
#include <hildon/hildon.h>
#include <libgupnp/gupnp-control-point.h>
#include <libgupnp-av/gupnp-av.h>
+#include <libsoup/soup.h>
#include "devious.h"
struct browse_data *browse_data_new(GUPnPServiceProxy *content_dir,
const char *id, guint32 starting_index,
- GtkListStore *list)
+ GtkListStore *list, struct proxy_set *set)
{
struct browse_data *data;
data->id = g_strdup(id);
data->starting_index = starting_index;
data->list = list;
+ data->set = set;
return data;
}
guint32 remaining;
GError *error = NULL;
- if (!gupnp_didl_lite_parser_parse_didl(data->didl_parser, didl_xml,
- add_content, data, &error)) {
+ if (!gupnp_didl_lite_parser_parse_didl(data->set->app->didl_parser,
+ didl_xml, add_content, data,
+ &error)) {
g_warning("%s\n", error->message);
g_error_free(error);
}
/* Keep browsing till we get each and every object */
if (remaining != 0) browse(content_dir, data->id, data->starting_index,
MIN(remaining, MAX_BROWSE),
- data->list, data->didl_parser);
+ data->list, data->set);
} else if (error) {
GUPnPServiceInfo *info;
void browse(GUPnPServiceProxy *content_dir, const char *container_id,
guint32 starting_index, guint32 requested_count,
- GtkListStore *list, GUPnPDIDLLiteParser *didl_parser)
+ GtkListStore *list, struct proxy_set *proxy_set)
{
struct browse_data *data;
- data = browse_data_new(content_dir, container_id, starting_index, list);
+ data = browse_data_new(content_dir, container_id, starting_index, list,
+ proxy_set);
gupnp_service_proxy_begin_action(content_dir, "Browse", browse_cb, data,
"ObjectID", G_TYPE_STRING, container_id,
}
char *find_compat_uri_from_metadata(const char *metadata, char **duration,
- struct proxy *renderer)
+ char **art_uri, struct proxy *renderer)
{
char *uri = NULL;
void on_didl_item_available(GUPnPDIDLLiteParser *didl_parser,
uri = gupnp_didl_lite_property_get_value(res_node);
*duration = gupnp_didl_lite_property_get_attribute(res_node,
"duration");
+ GList *album_art =
+ gupnp_didl_lite_object_get_property(item_node, ALBUM_ART);
+ if (album_art) *art_uri =
+ gupnp_didl_lite_property_get_value((xmlNode *)album_art->data);
+ else *art_uri = NULL;
}
g_list_free(resources);
}
GError *error = NULL;
/* Assumption: metadata only contains a single didl object */
- gupnp_didl_lite_parser_parse_didl(renderer->set->didl_parser, metadata,
+ gupnp_didl_lite_parser_parse_didl(renderer->set->app->didl_parser, metadata,
on_didl_item_available, NULL, &error);
if (error) {
g_warning("%s\n", error->message);
struct proxy *current_renderer(struct proxy_set *proxy_set)
{
- int i = hildon_picker_button_get_active(proxy_set->renderer_picker);
+ int i = hildon_picker_button_get_active(proxy_set->app->renderer_picker);
GtkTreeIter iter;
gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(proxy_set->renderer_list),
&iter, NULL, i);
return g_hash_table_lookup(proxy_set->renderers, udn);
}
+void set_image(SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+ struct selection_data *data = (struct selection_data *)user_data;
+
+ if (!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) return;
+
+ GInputStream *stream =
+ g_memory_input_stream_new_from_data(msg->response_body->data,
+ msg->response_body->length, NULL);
+
+ GError *error = NULL;
+ GdkPixbuf *pixbuf =
+ gdk_pixbuf_new_from_stream_at_scale(stream, PLAY_WINDOW_IMAGE_SIZE,
+ PLAY_WINDOW_IMAGE_SIZE,
+ TRUE, NULL, &error);
+ g_object_unref(G_OBJECT(stream));
+
+ if (!error) gtk_image_set_from_pixbuf(GTK_IMAGE(data->art_image), pixbuf);
+}
+
void set_av_transport_uri(GUPnPServiceProxy *content_dir,
GUPnPServiceProxyAction *action, gpointer user_data)
{
g_error_free(error);
}
- struct proxy_set *proxy_set = (struct proxy_set *)user_data;
- struct proxy *renderer = current_renderer(proxy_set);
+ struct selection_data *data = (struct selection_data *)user_data;
+ struct proxy *renderer = current_renderer(data->set);
- char *duration;
- char *uri = find_compat_uri_from_metadata(metadata, &duration, renderer);
+ char *uri = find_compat_uri_from_metadata(metadata, &data->duration,
+ &data->art_uri, renderer);
if (!uri) {
g_warning("no compatible URI found.");
return;
}
+
+ if (data->art_uri) {
+ SoupMessage *msg = soup_message_new("GET", data->art_uri);
+ soup_session_queue_message(data->set->app->soup, msg, set_image, data);
+ }
GUPnPServiceProxy *av_transport = GUPNP_SERVICE_PROXY(
gupnp_device_info_get_service(GUPNP_DEVICE_INFO(renderer->proxy),
g_hash_table_unref(args);
}
-void transport_selection(struct proxy *server, GtkTreeRowReference *row)
+void transport_selection(struct proxy *server, struct selection_data *data)
{
- server->current_selection = row;
-
- GtkTreeModel *model = gtk_tree_row_reference_get_model(row);
- GtkTreePath *path = gtk_tree_row_reference_get_path(row);
+ GtkTreeModel *model = gtk_tree_row_reference_get_model(data->row);
+ GtkTreePath *path = gtk_tree_row_reference_get_path(data->row);
GtkTreeIter iter;
gtk_tree_model_get_iter(model, &iter, path);
COL_CONTENT, &content, COL_ID, &id, -1);
gupnp_service_proxy_begin_action(content, "Browse",
- set_av_transport_uri, server->set,
+ set_av_transport_uri, data,
"ObjectID", G_TYPE_STRING, id,
"BrowseFlag", G_TYPE_STRING,
"BrowseMetadata",
av_transport_send_action(av_transport, "Play", args);
}
-GtkWidget *play_window(struct proxy *server, GtkTreeRowReference *row)
+struct selection_data *selection_data_new(struct proxy *server,
+ GtkTreeModel *model,
+ GtkTreePath *path)
+{
+ struct selection_data *data =
+ (struct selection_data *)g_slice_alloc0(sizeof(struct selection_data));
+ data->row = gtk_tree_row_reference_new(model, path);
+ data->set = server->set;
+ return data;
+}
+
+void selection_data_free(struct selection_data *data)
+{
+ gtk_tree_row_reference_free(data->row);
+ g_slice_free(struct selection_data, data);
+}
+
+void play_window_destroy(GtkWidget *window, gpointer user_data)
+{
+ struct selection_data *data = (struct selection_data *)user_data;
+ selection_data_free(data);
+}
+
+GtkWidget *play_window_image(struct selection_data *data)
+{
+ GError *error = NULL;
+ GdkPixbuf *icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
+ "mediaplayer_default_album",
+ PLAY_WINDOW_IMAGE_SIZE,
+ 0, &error);
+
+ data->art_image = gtk_image_new_from_pixbuf(icon);
+
+ return data->art_image;
+}
+
+void mp_callback(GtkWidget *event_box, GdkEventButton *event,
+ gpointer user_data)
+{
+ struct toggle_data *data = (struct toggle_data *)user_data;
+
+ if (event->type == GDK_BUTTON_PRESS) {
+ if (data->toggle) data->state ^= 1;
+ gtk_image_set_from_pixbuf(GTK_IMAGE(data->image),
+ data->pixbufs[data->toggle ? data->state : 1]);
+ } else {
+ if (!data->toggle) gtk_image_set_from_pixbuf(GTK_IMAGE(data->image),
+ data->pixbufs[0]);
+ if (data->callback) (data->callback)(event_box, data->user_data);
+ }
+}
+
+GtkWidget *mp_button(char *file, char *onpress_file, gboolean toggle,
+ void (*callback)(GtkWidget *, gpointer),
+ gpointer user_data)
+{
+ char fname[256];
+ struct toggle_data *data = g_slice_alloc0(sizeof(struct toggle_data));
+ data->toggle = toggle;
+ data->user_data = user_data;
+ data->callback = callback;
+
+ GError *error = NULL;
+ strcpy(fname, MP_ICON_PATH);
+ strcat(fname, file);
+ strcat(fname, ".png");
+ data->pixbufs[0] = gdk_pixbuf_new_from_file(fname, &error);
+
+ strcpy(fname, MP_ICON_PATH);
+ strcat(fname, onpress_file);
+ strcat(fname, ".png");
+ data->pixbufs[1] = gdk_pixbuf_new_from_file(fname, &error);
+
+ data->image = GTK_IMAGE(gtk_image_new_from_pixbuf(data->pixbufs[0]));
+
+ GtkWidget *event_box = gtk_event_box_new();
+ gtk_container_add(GTK_CONTAINER(event_box), GTK_WIDGET(data->image));
+
+ g_signal_connect(G_OBJECT(event_box), "button_press_event",
+ G_CALLBACK(mp_callback), data);
+ g_signal_connect(G_OBJECT(event_box), "button_release_event",
+ G_CALLBACK(mp_callback), data);
+ return event_box;
+}
+
+GtkWidget *play_window(struct proxy *server, struct selection_data *data)
{
GtkWidget *window = hildon_stackable_window_new();
gtk_window_set_title(GTK_WINDOW(window), PLAY_WINDOW_TITLE);
gtk_container_add(GTK_CONTAINER(window), vbox);
GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
- gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 6);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
- GtkWidget *image = gtk_image_new();
- gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 6);
+ GtkWidget *image = play_window_image(data);
+ gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 20);
GtkWidget *inner_box = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox), inner_box, TRUE, TRUE, 6);
GtkWidget *button_box = gtk_hbutton_box_new();
gtk_box_pack_start(GTK_BOX(vbox), button_box, FALSE, FALSE, 6);
- GtkWidget *button =
- hildon_button_new_with_text(HILDON_SIZE_FINGER_HEIGHT,
- HILDON_BUTTON_ARRANGEMENT_VERTICAL,
- "Play", "");
+ GtkWidget *back = mp_button("Back", "BackPressed", FALSE, NULL, NULL);
+ gtk_box_pack_start(GTK_BOX(button_box), back, FALSE, FALSE, 0);
- g_signal_connect(button, "clicked", G_CALLBACK(play_button), server->set);
+ GtkWidget *play = mp_button("Play", "Pause", TRUE, play_button,
+ data->set);
+ gtk_box_pack_start(GTK_BOX(button_box), play, FALSE, FALSE, 0);
- gtk_box_pack_start(GTK_BOX(button_box), button, FALSE, FALSE, 0);
+ GtkWidget *forward = mp_button("Forward", "ForwardPressed", FALSE,
+ NULL, NULL);
+ gtk_box_pack_start(GTK_BOX(button_box), forward, FALSE, FALSE, 0);
+
+ GtkWidget *shuffle = mp_button("Shuffle", "ShufflePressed", TRUE, NULL, NULL);
+ gtk_box_pack_start(GTK_BOX(button_box), shuffle, FALSE, FALSE, 0);
+
+ GtkWidget *repeat = mp_button("Repeat", "RepeatPressed", TRUE, NULL, NULL);
+ gtk_box_pack_start(GTK_BOX(button_box), repeat, FALSE, FALSE, 0);
+
+ g_signal_connect(window, "destroy", G_CALLBACK(play_window_destroy), data);
gtk_widget_show_all(GTK_WIDGET(vbox));
return window;
if (container) {
GtkListStore *view_list;
window = content_window(server, label, &view_list);
- browse(content, id, 0, MAX_BROWSE, view_list, server->set->didl_parser);
+ browse(content, id, 0, MAX_BROWSE, view_list, server->set);
gupnp_service_proxy_add_notify(content, "ContainerUpdateIDs",
G_TYPE_STRING, on_container_update_ids,
NULL);
gupnp_service_proxy_set_subscribed(content, TRUE);
} else {
- GtkTreeRowReference *row = gtk_tree_row_reference_new(model, path);
- transport_selection(server, row);
- window = play_window(server, row);
+ struct selection_data *data = selection_data_new(server, model, path);
+ transport_selection(server, data);
+ window = play_window(server, data);
}
HildonWindowStack *stack = hildon_window_stack_get_default();
GtkListStore *view_list;
GtkWidget *window = content_window(server, server->name, &view_list);
- browse(content_dir, "0", 0, MAX_BROWSE, view_list,
- server->set->didl_parser);
+ browse(content_dir, "0", 0, MAX_BROWSE, view_list, server->set);
gupnp_service_proxy_add_notify(content_dir, "ContainerUpdateIDs",
G_TYPE_STRING, on_container_update_ids,
GtkWidget *selector = target_selector(proxy_set);
hildon_picker_button_set_selector(HILDON_PICKER_BUTTON(button),
HILDON_TOUCH_SELECTOR(selector));
- proxy_set->renderer_picker = HILDON_PICKER_BUTTON(button);
+ proxy_set->app->renderer_picker = HILDON_PICKER_BUTTON(button);
hildon_app_menu_append(HILDON_APP_MENU(menu), GTK_BUTTON(button));
gtk_window_set_title(GTK_WINDOW(window), APPLICATION_NAME);
- GError *error = NULL;
- proxy_set->icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
- "control_bluetooth_lan",
- HILDON_ICON_PIXEL_SIZE_FINGER,
- 0, &error);
proxy_set->server_list = gtk_list_store_new(NUM_COLS, GDK_TYPE_PIXBUF,
G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_POINTER, G_TYPE_BOOLEAN);
char *name = gupnp_device_info_get_friendly_name(GUPNP_DEVICE_INFO(proxy));
if (!name) return;
- server = (struct proxy *)g_malloc(sizeof(struct proxy));
+ server = (struct proxy *)g_slice_alloc(sizeof(struct proxy));
server->proxy = proxy;
server->name = name;
server->set = proxy_set;
0, server->name, 1, udn, -1);
/* TODO: default to saved value */
- hildon_picker_button_set_active(proxy_set->renderer_picker, 0);
+ hildon_picker_button_set_active(proxy_set->app->renderer_picker, 0);
GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->renderer_list);
GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
if (!name || !content_dir) return;
- server = (struct proxy *)g_malloc(sizeof(struct proxy));
+ server = (struct proxy *)g_slice_alloc(sizeof(struct proxy));
server->proxy = proxy;
server->name = name;
server->set = proxy_set;
+ GError *error = NULL;
+ GdkPixbuf *icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
+ "control_bluetooth_lan",
+ HILDON_ICON_PIXEL_SIZE_FINGER,
+ 0, &error);
GtkTreeIter iter;
gtk_list_store_append(proxy_set->server_list, &iter);
gtk_list_store_set(proxy_set->server_list, &iter,
- COL_ICON, proxy_set->icon, COL_LABEL, server->name, COL_ID, udn,
- COL_CONTENT, NULL, COL_CONTAINER, TRUE, -1);
+ COL_ICON, icon, COL_LABEL, server->name, COL_ID, udn,
+ COL_CONTENT, NULL, COL_CONTAINER, TRUE, -1);
GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->server_list);
GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
gssdp_resource_browser_set_active(GSSDP_RESOURCE_BROWSER(cp), TRUE);
}
+void init_application(struct application *app)
+{
+ struct proxy_set *set = &app->set;
+ set->renderers = g_hash_table_new(g_str_hash, g_str_equal);
+ set->servers = g_hash_table_new(g_str_hash, g_str_equal);
+
+ set->renderer_list = gtk_list_store_new(2, G_TYPE_STRING,
+ G_TYPE_STRING);
+ set->server_list = gtk_list_store_new(NUM_COLS, GDK_TYPE_PIXBUF,
+ G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_POINTER, G_TYPE_BOOLEAN);
+ set->app = app;
+
+ app->didl_parser = gupnp_didl_lite_parser_new();
+ app->soup = soup_session_async_new();
+}
+
int main(int argc, char *argv[])
{
g_thread_init(NULL);
HildonProgram *program = hildon_program_get_instance();
g_set_application_name(APPLICATION_NAME);
- struct proxy_set proxy_set;
- proxy_set.renderers = g_hash_table_new(g_str_hash, g_str_equal);
- proxy_set.servers = g_hash_table_new(g_str_hash, g_str_equal);
+ struct application app;
+ init_application(&app);
- proxy_set.renderer_list = gtk_list_store_new(2, G_TYPE_STRING,
- G_TYPE_STRING);
- proxy_set.didl_parser = gupnp_didl_lite_parser_new();
- GtkWidget *window = main_window(program, &proxy_set);
+ GtkWidget *window = main_window(program, &app.set);
g_signal_connect(G_OBJECT(window), "destroy",
G_CALLBACK(gtk_main_quit), NULL);
- init_upnp(&proxy_set);
+ init_upnp(&app.set);
gtk_widget_show(window);
gtk_main();