From: kyle Date: Thu, 3 Dec 2009 07:58:35 +0000 (-0600) Subject: album art support and other work on play window X-Git-Url: http://git.maemo.org/git/?p=devious;a=commitdiff_plain;h=HEAD album art support and other work on play window --- diff --git a/devious.c b/devious.c index a219344..e83f1ca 100644 --- a/devious.c +++ b/devious.c @@ -1,5 +1,5 @@ /* - * Devious: UPNP Control Point for Maemo 5 + * Devious: UPnP Control Point for Maemo 5 * * Copyright (C) 2009 Kyle Cronan * @@ -25,11 +25,14 @@ #include #include #include +#include #include +#include #include #include #include #include +#include #include "devious.h" @@ -58,7 +61,7 @@ void add_content(GUPnPDIDLLiteParser *didl_parser, xmlNode *object_node, 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; @@ -67,6 +70,7 @@ struct browse_data *browse_data_new(GUPnPServiceProxy *content_dir, data->id = g_strdup(id); data->starting_index = starting_index; data->list = list; + data->set = set; return data; } @@ -101,8 +105,9 @@ void browse_cb(GUPnPServiceProxy *content_dir, 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); } @@ -115,7 +120,7 @@ void browse_cb(GUPnPServiceProxy *content_dir, /* 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; @@ -132,10 +137,11 @@ void browse_cb(GUPnPServiceProxy *content_dir, 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, @@ -369,7 +375,7 @@ return_point: } 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, @@ -401,13 +407,18 @@ char *find_compat_uri_from_metadata(const char *metadata, char **duration, 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); @@ -419,7 +430,7 @@ char *find_compat_uri_from_metadata(const char *metadata, char **duration, 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); @@ -430,6 +441,26 @@ struct proxy *current_renderer(struct proxy_set *proxy_set) 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) { @@ -443,15 +474,20 @@ void set_av_transport_uri(GUPnPServiceProxy *content_dir, 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), @@ -520,12 +556,10 @@ void av_transport_send_action(GUPnPServiceProxy *av_transport, char *action, 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); @@ -537,7 +571,7 @@ void transport_selection(struct proxy *server, GtkTreeRowReference *row) 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", @@ -559,7 +593,92 @@ void play_button(GtkWidget *button, gpointer data) 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); @@ -568,10 +687,10 @@ GtkWidget *play_window(struct proxy *server, GtkTreeRowReference *row) 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); @@ -579,14 +698,24 @@ GtkWidget *play_window(struct proxy *server, GtkTreeRowReference *row) 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; @@ -615,15 +744,15 @@ void content_select(HildonTouchSelector *selector, gint column, gpointer data) 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(); @@ -674,8 +803,7 @@ void server_select(HildonTouchSelector *selector, gint column, gpointer data) 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, @@ -721,7 +849,7 @@ GtkWidget *main_menu(struct proxy_set *proxy_set) 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)); @@ -736,11 +864,6 @@ GtkWidget *main_window(HildonProgram *program, struct proxy_set *proxy_set) 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); @@ -790,7 +913,7 @@ void add_renderer(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set) 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; @@ -801,7 +924,7 @@ void add_renderer(GUPnPDeviceProxy *proxy, struct proxy_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); @@ -830,16 +953,21 @@ void add_server(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set) 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); @@ -933,6 +1061,23 @@ void init_upnp(struct proxy_set *proxy_set) 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); @@ -942,18 +1087,14 @@ int main(int argc, char *argv[]) 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(); diff --git a/devious.h b/devious.h index a880add..c8d93f1 100644 --- a/devious.h +++ b/devious.h @@ -28,40 +28,64 @@ #define PLAY_WINDOW_TITLE "Now playing" #define CHOOSE_TARGET "Choose target" #define ROW_HEIGHT 75 +#define MP_ICON_PATH "/usr/share/themes/default/mediaplayer/" #define ICON_WIDTH 90 #define ICON_XALIGN 0.6 +#define PLAY_WINDOW_IMAGE_SIZE 295 #define MEDIA_RENDERER "urn:schemas-upnp-org:device:MediaRenderer:*" #define MEDIA_SERVER "urn:schemas-upnp-org:device:MediaServer:*" #define CONTENT_DIR "urn:schemas-upnp-org:service:ContentDirectory" #define CONNECTION_MANAGER "urn:schemas-upnp-org:service:ConnectionManager" #define AV_TRANSPORT "urn:schemas-upnp-org:service:AVTransport" +#define ALBUM_ART "albumArtURI" #define MAX_BROWSE 64 -struct proxy { - GtkTreeRowReference *row; - GUPnPDeviceProxy *proxy; - char *name; - struct proxy_set *set; - char **protocols; - GtkTreeRowReference *current_selection; -}; - struct proxy_set { GHashTable *renderers; GHashTable *servers; GtkListStore *renderer_list; GtkListStore *server_list; - GdkPixbuf *icon; + struct application *app; +}; + +struct application { + struct proxy_set set; + SoupSession *soup; HildonPickerButton *renderer_picker; GUPnPDIDLLiteParser *didl_parser; }; +struct proxy { + GtkTreeRowReference *row; + GUPnPDeviceProxy *proxy; + char *name; + char **protocols; + struct proxy_set *set; +}; + struct browse_data { GUPnPServiceProxy *content_dir; gchar *id; guint32 starting_index; GtkListStore *list; - GUPnPDIDLLiteParser *didl_parser; + struct proxy_set *set; +}; + +struct selection_data { + GtkTreeRowReference *row; + char *duration; + char *art_uri; + GtkWidget *art_image; + struct proxy_set *set; +}; + +struct toggle_data { + GdkPixbuf *pixbufs[2]; + GtkImage *image; + gboolean toggle; + gboolean state; + void (*callback)(GtkWidget *, gpointer); + gpointer user_data; }; enum { @@ -76,7 +100,7 @@ enum { 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); void set_panarea_padding(GtkWidget *child, gpointer data);