2 * Devious: UPnP Control Point for Maemo 5
4 * Copyright (C) 2009 Kyle Cronan
6 * Author: Kyle Cronan <kyle@pbx.org>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; version 3 of the License, or (at your option)
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16 * Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this library; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
30 #include <gdk-pixbuf/gdk-pixbuf.h>
32 #include <hildon/hildon.h>
33 #include <libgupnp/gupnp-control-point.h>
34 #include <libgupnp-av/gupnp-av.h>
35 #include <libsoup/soup.h>
40 void add_content(GUPnPDIDLLiteParser *didl_parser, xmlNode *object_node,
43 struct browse_data *data = (struct browse_data *)user_data;
45 char *title = gupnp_didl_lite_object_get_title(object_node);
46 char *id = gupnp_didl_lite_object_get_id(object_node);
47 gboolean container = gupnp_didl_lite_object_is_container(object_node);
49 GdkPixbuf *icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
50 (container ? "general_folder"
51 : "general_audio_file"),
52 HILDON_ICON_PIXEL_SIZE_FINGER,
55 gtk_list_store_append(data->list, &iter);
56 gtk_list_store_set(data->list, &iter,
57 COL_ICON, icon, COL_LABEL, title,
58 COL_ID, id, COL_CONTENT, data->content_dir,
59 COL_CONTAINER, container, -1);
62 struct browse_data *browse_data_new(GUPnPServiceProxy *content_dir,
63 const char *id, guint32 starting_index,
64 GtkListStore *list, struct proxy_set *set)
66 struct browse_data *data;
68 data = g_slice_new(struct browse_data);
69 data->content_dir = g_object_ref(content_dir);
70 data->id = g_strdup(id);
71 data->starting_index = starting_index;
78 void browse_data_free(struct browse_data *data)
81 g_object_unref(data->content_dir);
82 g_slice_free(struct browse_data, data);
85 void browse_cb(GUPnPServiceProxy *content_dir,
86 GUPnPServiceProxyAction *action, gpointer user_data)
88 struct browse_data *data;
90 guint32 number_returned;
91 guint32 total_matches;
94 data = (struct browse_data *)user_data;
98 gupnp_service_proxy_end_action(content_dir, action, &error,
99 "Result", G_TYPE_STRING, &didl_xml,
100 "NumberReturned", G_TYPE_UINT,
102 "TotalMatches", G_TYPE_UINT, &total_matches,
106 GError *error = NULL;
108 if (!gupnp_didl_lite_parser_parse_didl(data->set->app->didl_parser,
109 didl_xml, add_content, data,
111 g_warning("%s\n", error->message);
116 data->starting_index += number_returned;
118 /* See if we have more objects to get */
119 remaining = total_matches - data->starting_index;
120 /* Keep browsing till we get each and every object */
121 if (remaining != 0) browse(content_dir, data->id, data->starting_index,
122 MIN(remaining, MAX_BROWSE),
123 data->list, data->set);
125 GUPnPServiceInfo *info;
127 info = GUPNP_SERVICE_INFO(content_dir);
128 g_warning("Failed to browse '%s': %s\n",
129 gupnp_service_info_get_location(info),
135 browse_data_free(data);
138 void browse(GUPnPServiceProxy *content_dir, const char *container_id,
139 guint32 starting_index, guint32 requested_count,
140 GtkListStore *list, struct proxy_set *proxy_set)
142 struct browse_data *data;
143 data = browse_data_new(content_dir, container_id, starting_index, list,
146 gupnp_service_proxy_begin_action(content_dir, "Browse", browse_cb, data,
147 "ObjectID", G_TYPE_STRING, container_id,
148 "BrowseFlag", G_TYPE_STRING,
149 "BrowseDirectChildren",
150 "Filter", G_TYPE_STRING, "*",
151 "StartingIndex", G_TYPE_UINT,
153 "RequestedCount", G_TYPE_UINT,
155 "SortCriteria", G_TYPE_STRING, "",
159 void update_container(GUPnPServiceProxy *content_dir,
160 const char *container_id)
163 // GtkTreeModel *model;
164 // GtkTreeIter container_iter;
167 void on_container_update_ids(GUPnPServiceProxy *content_dir,
168 const char *variable, GValue *value,
171 char **tokens = g_strsplit(g_value_get_string(value), ",", 0);
173 for (i=0; tokens[i] != NULL && tokens[i+1] != NULL; i+=2) {
174 update_container(content_dir, tokens[i]);
179 void set_panarea_padding(GtkWidget *child, gpointer data)
181 void set_child_padding(GtkWidget *child, gpointer user_data)
183 GtkBox *box = GTK_BOX(user_data);
184 gboolean expand, fill;
188 gtk_box_query_child_packing(box, child, &expand, &fill, &pad, &pack);
189 gtk_box_set_child_packing(box, child, expand, fill, 0, pack);
192 if (GTK_IS_CONTAINER(child))
193 gtk_container_forall(GTK_CONTAINER(child), set_child_padding, child);
196 GtkWidget *new_selector(GtkListStore *list)
198 GtkWidget *selector = hildon_touch_selector_new();
200 HildonTouchSelectorColumn *column =
201 hildon_touch_selector_append_column(HILDON_TOUCH_SELECTOR(selector),
202 GTK_TREE_MODEL(list), NULL, NULL);
203 g_object_unref(list);
204 hildon_touch_selector_column_set_text_column(column, 1);
206 hildon_touch_selector_set_hildon_ui_mode(HILDON_TOUCH_SELECTOR(selector),
207 HILDON_UI_MODE_NORMAL);
208 GtkCellRenderer *renderer;
209 renderer = gtk_cell_renderer_pixbuf_new();
210 g_object_set(renderer, "xalign", ICON_XALIGN, NULL);
211 gtk_cell_renderer_set_fixed_size(renderer, ICON_WIDTH, ROW_HEIGHT);
212 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), renderer, FALSE);
213 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(column), renderer,
216 renderer = gtk_cell_renderer_text_new();
217 g_object_set(renderer, "xalign", 0.0, NULL);
218 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), renderer, TRUE);
219 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(column), renderer,
222 gtk_container_forall(GTK_CONTAINER(selector), set_panarea_padding, NULL);
224 gtk_widget_show_all(selector);
228 void transport_uri(GUPnPServiceProxy *av_transport,
229 GUPnPServiceProxyAction *action, gpointer user_data)
231 GError *error = NULL;
232 if (gupnp_service_proxy_end_action(av_transport, action, &error, NULL)) {
233 /* TODO: do something with duration? */
235 g_warning("Failed to set URI");
240 gboolean mime_type_is_a(const char *mime_type1, const char *mime_type2)
244 char *content_type1 = g_content_type_from_mime_type(mime_type1);
245 char *content_type2 = g_content_type_from_mime_type(mime_type2);
246 if (content_type1 == NULL || content_type2 == NULL) {
247 /* Uknown content type, just do a simple comarison */
248 ret = g_ascii_strcasecmp(mime_type1, mime_type2) == 0;
250 ret = g_content_type_is_a(content_type1, content_type2);
253 g_free(content_type1);
254 g_free(content_type2);
259 gboolean is_transport_compat(const gchar *renderer_protocol,
260 const gchar *renderer_host,
261 const gchar *item_protocol,
262 const gchar *item_host)
264 if (g_ascii_strcasecmp(renderer_protocol, item_protocol) != 0 &&
265 g_ascii_strcasecmp(renderer_protocol, "*") != 0) {
267 } else if (g_ascii_strcasecmp("INTERNAL", renderer_protocol) == 0 &&
268 g_ascii_strcasecmp(renderer_host, item_host) != 0) {
269 /* Host must be the same in case of INTERNAL protocol */
276 gboolean is_content_format_compat(const gchar *renderer_content_format,
277 const gchar *item_content_format)
279 if(g_ascii_strcasecmp(renderer_content_format, "*") != 0 &&
280 !mime_type_is_a(item_content_format, renderer_content_format)) {
287 gchar *get_dlna_pn(gchar **additional_info_fields)
291 for (i = 0; additional_info_fields[i]; i++) {
292 pn = g_strstr_len(additional_info_fields[i],
293 strlen(additional_info_fields[i]), "DLNA.ORG_PN=");
295 pn += 12; /* end of "DLNA.ORG_PN=" */
303 gboolean is_additional_info_compat(const gchar *renderer_additional_info,
304 const gchar *item_additional_info)
306 gboolean ret = FALSE;
308 if (g_ascii_strcasecmp(renderer_additional_info, "*") == 0) {
312 char **renderer_tokens = g_strsplit(renderer_additional_info, ";", -1);
313 if (renderer_tokens == NULL) {
317 char **item_tokens = g_strsplit(item_additional_info, ";", -1);
318 if (item_tokens == NULL) {
322 char *renderer_pn = get_dlna_pn(renderer_tokens);
323 char *item_pn = get_dlna_pn(item_tokens);
324 if (renderer_pn == NULL || item_pn == NULL) {
328 if (g_ascii_strcasecmp(renderer_pn, item_pn) == 0) {
333 g_strfreev(item_tokens);
335 g_strfreev(renderer_tokens);
340 gboolean is_protocol_info_compat(xmlNode *res_node,
341 const gchar *renderer_protocol)
343 gchar *item_protocol;
344 gchar **item_proto_tokens;
345 gchar **renderer_proto_tokens;
346 gboolean ret = FALSE;
348 item_protocol = gupnp_didl_lite_property_get_attribute(res_node,
350 if (!item_protocol) return FALSE;
352 item_proto_tokens = g_strsplit(item_protocol, ":", 4);
353 renderer_proto_tokens = g_strsplit(renderer_protocol, ":", 4);
355 if (!item_proto_tokens[0] || !item_proto_tokens[1] ||
356 !item_proto_tokens[2] || !item_proto_tokens[3] ||
357 !renderer_proto_tokens[0] || !renderer_proto_tokens[1] ||
358 !renderer_proto_tokens[2] || !renderer_proto_tokens[3])
361 if (is_transport_compat(renderer_proto_tokens[0], renderer_proto_tokens[2],
362 item_proto_tokens[0], item_proto_tokens[1]) &&
363 is_content_format_compat(renderer_proto_tokens[2],
364 item_proto_tokens[2]) &&
365 is_additional_info_compat(renderer_proto_tokens[3],
366 item_proto_tokens[3]))
370 g_free(item_protocol);
371 g_strfreev(renderer_proto_tokens);
372 g_strfreev(item_proto_tokens);
377 char *find_compat_uri_from_metadata(const char *metadata, char **duration,
378 char **art_uri, struct proxy *renderer)
381 void on_didl_item_available(GUPnPDIDLLiteParser *didl_parser,
382 xmlNode *item_node, gpointer user_data)
385 gupnp_didl_lite_object_get_property(item_node, "res");
386 if (!resources) return;
389 for (i=0; renderer->protocols[i] && uri == NULL; i++) {
390 GList *res, *compat_res = NULL;
392 for (res = resources; res != NULL; res = res->next) {
393 res_node = (xmlNode *)res->data;
396 for (j=0; renderer->protocols[j]; j++) {
397 if (is_protocol_info_compat(res_node,
398 renderer->protocols[j])) {
404 if (!compat_res) continue;
406 res_node = (xmlNode *)compat_res->data;
407 uri = gupnp_didl_lite_property_get_value(res_node);
408 *duration = gupnp_didl_lite_property_get_attribute(res_node,
411 gupnp_didl_lite_object_get_property(item_node, ALBUM_ART);
412 if (album_art) *art_uri =
413 gupnp_didl_lite_property_get_value((xmlNode *)album_art->data);
414 else *art_uri = NULL;
416 g_list_free(resources);
419 GError *error = NULL;
420 /* Assumption: metadata only contains a single didl object */
421 gupnp_didl_lite_parser_parse_didl(renderer->set->app->didl_parser, metadata,
422 on_didl_item_available, NULL, &error);
424 g_warning("%s\n", error->message);
431 struct proxy *current_renderer(struct proxy_set *proxy_set)
433 int i = hildon_picker_button_get_active(proxy_set->app->renderer_picker);
435 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(proxy_set->renderer_list),
438 gtk_tree_model_get(GTK_TREE_MODEL(proxy_set->renderer_list), &iter,
441 return g_hash_table_lookup(proxy_set->renderers, udn);
444 void set_image(SoupSession *session, SoupMessage *msg, gpointer user_data)
446 struct selection_data *data = (struct selection_data *)user_data;
448 if (!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) return;
450 GInputStream *stream =
451 g_memory_input_stream_new_from_data(msg->response_body->data,
452 msg->response_body->length, NULL);
454 GError *error = NULL;
456 gdk_pixbuf_new_from_stream_at_scale(stream, PLAY_WINDOW_IMAGE_SIZE,
457 PLAY_WINDOW_IMAGE_SIZE,
459 g_object_unref(G_OBJECT(stream));
461 if (!error) gtk_image_set_from_pixbuf(GTK_IMAGE(data->art_image), pixbuf);
464 void set_av_transport_uri(GUPnPServiceProxy *content_dir,
465 GUPnPServiceProxyAction *action, gpointer user_data)
467 GError *error = NULL;
469 gupnp_service_proxy_end_action(content_dir, action, &error,
470 "Result", G_TYPE_STRING, &metadata, NULL);
471 if (!metadata) return;
473 g_warning("Failed to get metadata for content");
477 struct selection_data *data = (struct selection_data *)user_data;
478 struct proxy *renderer = current_renderer(data->set);
480 char *uri = find_compat_uri_from_metadata(metadata, &data->duration,
481 &data->art_uri, renderer);
483 g_warning("no compatible URI found.");
488 SoupMessage *msg = soup_message_new("GET", data->art_uri);
489 soup_session_queue_message(data->set->app->soup, msg, set_image, data);
492 GUPnPServiceProxy *av_transport = GUPNP_SERVICE_PROXY(
493 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(renderer->proxy),
496 gupnp_service_proxy_begin_action(av_transport, "SetAVTransportURI",
498 "InstanceID", G_TYPE_UINT, 0,
499 "CurrentURI", G_TYPE_STRING, uri,
500 "CurrentURIMetaData", G_TYPE_STRING,
505 void av_transport_action_cb(GUPnPServiceProxy *av_transport,
506 GUPnPServiceProxyAction *action, gpointer data)
508 const char *action_name = (const char *)data;
509 GError *error = NULL;
511 if (!gupnp_service_proxy_end_action(av_transport, action, &error, NULL)) {
512 g_warning("Failed to send action '%s': %s",
513 action_name, error->message);
518 void g_value_free(gpointer data)
520 g_value_unset((GValue *)data);
521 g_slice_free(GValue, data);
524 GHashTable *create_av_transport_args_hash(char **additional_args)
526 GHashTable *args = g_hash_table_new_full(g_str_hash, g_str_equal,
529 GValue *instance_id = g_slice_alloc0(sizeof(GValue));
530 g_value_init(instance_id, G_TYPE_UINT);
531 g_value_set_uint(instance_id, 0);
533 g_hash_table_insert(args, "InstanceID", instance_id);
535 if (additional_args) {
537 for (i=0; additional_args[i]; i += 2) {
538 GValue *value = g_slice_alloc0(sizeof(GValue));
539 g_value_init(value, G_TYPE_STRING);
540 g_value_set_string(value, additional_args[i + 1]);
541 g_hash_table_insert(args, additional_args[i], value);
547 void av_transport_send_action(GUPnPServiceProxy *av_transport, char *action,
548 char *additional_args[])
550 GHashTable *args = create_av_transport_args_hash(additional_args);
552 gupnp_service_proxy_begin_action_hash(av_transport, action,
553 av_transport_action_cb,
556 g_hash_table_unref(args);
559 void transport_selection(struct proxy *server, struct selection_data *data)
561 GtkTreeModel *model = gtk_tree_row_reference_get_model(data->row);
562 GtkTreePath *path = gtk_tree_row_reference_get_path(data->row);
565 gtk_tree_model_get_iter(model, &iter, path);
569 GUPnPServiceProxy *content;
570 gtk_tree_model_get(model, &iter, COL_LABEL, &label,
571 COL_CONTENT, &content, COL_ID, &id, -1);
573 gupnp_service_proxy_begin_action(content, "Browse",
574 set_av_transport_uri, data,
575 "ObjectID", G_TYPE_STRING, id,
576 "BrowseFlag", G_TYPE_STRING,
578 "Filter", G_TYPE_STRING, "*",
579 "StartingIndex", G_TYPE_UINT, 0,
580 "RequestedCount", G_TYPE_UINT, 0,
581 "SortCriteria", G_TYPE_STRING, "",
585 void play_button(GtkWidget *button, gpointer data)
587 struct proxy_set *proxy_set = (struct proxy_set *)data;
588 struct proxy *renderer = current_renderer(proxy_set);
589 GUPnPServiceProxy *av_transport = GUPNP_SERVICE_PROXY(
590 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(renderer->proxy),
592 char *args[] = {"Speed", "1", NULL};
593 av_transport_send_action(av_transport, "Play", args);
596 struct selection_data *selection_data_new(struct proxy *server,
600 struct selection_data *data =
601 (struct selection_data *)g_slice_alloc0(sizeof(struct selection_data));
602 data->row = gtk_tree_row_reference_new(model, path);
603 data->set = server->set;
607 void selection_data_free(struct selection_data *data)
609 gtk_tree_row_reference_free(data->row);
610 g_slice_free(struct selection_data, data);
613 void play_window_destroy(GtkWidget *window, gpointer user_data)
615 struct selection_data *data = (struct selection_data *)user_data;
616 selection_data_free(data);
619 GtkWidget *play_window_image(struct selection_data *data)
621 GError *error = NULL;
622 GdkPixbuf *icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
623 "mediaplayer_default_album",
624 PLAY_WINDOW_IMAGE_SIZE,
627 data->art_image = gtk_image_new_from_pixbuf(icon);
629 return data->art_image;
632 void mp_callback(GtkWidget *event_box, GdkEventButton *event,
635 struct toggle_data *data = (struct toggle_data *)user_data;
637 if (event->type == GDK_BUTTON_PRESS) {
638 if (data->toggle) data->state ^= 1;
639 gtk_image_set_from_pixbuf(GTK_IMAGE(data->image),
640 data->pixbufs[data->toggle ? data->state : 1]);
642 if (!data->toggle) gtk_image_set_from_pixbuf(GTK_IMAGE(data->image),
644 if (data->callback) (data->callback)(event_box, data->user_data);
648 GtkWidget *mp_button(char *file, char *onpress_file, gboolean toggle,
649 void (*callback)(GtkWidget *, gpointer),
653 struct toggle_data *data = g_slice_alloc0(sizeof(struct toggle_data));
654 data->toggle = toggle;
655 data->user_data = user_data;
656 data->callback = callback;
658 GError *error = NULL;
659 strcpy(fname, MP_ICON_PATH);
661 strcat(fname, ".png");
662 data->pixbufs[0] = gdk_pixbuf_new_from_file(fname, &error);
664 strcpy(fname, MP_ICON_PATH);
665 strcat(fname, onpress_file);
666 strcat(fname, ".png");
667 data->pixbufs[1] = gdk_pixbuf_new_from_file(fname, &error);
669 data->image = GTK_IMAGE(gtk_image_new_from_pixbuf(data->pixbufs[0]));
671 GtkWidget *event_box = gtk_event_box_new();
672 gtk_container_add(GTK_CONTAINER(event_box), GTK_WIDGET(data->image));
674 g_signal_connect(G_OBJECT(event_box), "button_press_event",
675 G_CALLBACK(mp_callback), data);
676 g_signal_connect(G_OBJECT(event_box), "button_release_event",
677 G_CALLBACK(mp_callback), data);
681 GtkWidget *play_window(struct proxy *server, struct selection_data *data)
683 GtkWidget *window = hildon_stackable_window_new();
684 gtk_window_set_title(GTK_WINDOW(window), PLAY_WINDOW_TITLE);
686 GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
687 gtk_container_add(GTK_CONTAINER(window), vbox);
689 GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
690 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
692 GtkWidget *image = play_window_image(data);
693 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 20);
695 GtkWidget *inner_box = gtk_vbox_new(FALSE, 0);
696 gtk_box_pack_start(GTK_BOX(hbox), inner_box, TRUE, TRUE, 6);
698 GtkWidget *button_box = gtk_hbutton_box_new();
699 gtk_box_pack_start(GTK_BOX(vbox), button_box, FALSE, FALSE, 6);
701 GtkWidget *back = mp_button("Back", "BackPressed", FALSE, NULL, NULL);
702 gtk_box_pack_start(GTK_BOX(button_box), back, FALSE, FALSE, 0);
704 GtkWidget *play = mp_button("Play", "Pause", TRUE, play_button,
706 gtk_box_pack_start(GTK_BOX(button_box), play, FALSE, FALSE, 0);
708 GtkWidget *forward = mp_button("Forward", "ForwardPressed", FALSE,
710 gtk_box_pack_start(GTK_BOX(button_box), forward, FALSE, FALSE, 0);
712 GtkWidget *shuffle = mp_button("Shuffle", "ShufflePressed", TRUE, NULL, NULL);
713 gtk_box_pack_start(GTK_BOX(button_box), shuffle, FALSE, FALSE, 0);
715 GtkWidget *repeat = mp_button("Repeat", "RepeatPressed", TRUE, NULL, NULL);
716 gtk_box_pack_start(GTK_BOX(button_box), repeat, FALSE, FALSE, 0);
718 g_signal_connect(window, "destroy", G_CALLBACK(play_window_destroy), data);
719 gtk_widget_show_all(GTK_WIDGET(vbox));
724 void content_select(HildonTouchSelector *selector, gint column, gpointer data)
727 path = hildon_touch_selector_get_last_activated_row(selector, column);
730 GtkTreeModel *model = hildon_touch_selector_get_model(selector, column);
732 gtk_tree_model_get_iter(model, &iter, path);
734 GUPnPServiceProxy *content;
737 gtk_tree_model_get(model, &iter, COL_CONTENT, &content, COL_ID, &id,
738 COL_CONTAINER, &container,
739 COL_LABEL, &label, -1);
741 struct proxy *server = (struct proxy *)data;
745 GtkListStore *view_list;
746 window = content_window(server, label, &view_list);
747 browse(content, id, 0, MAX_BROWSE, view_list, server->set);
748 gupnp_service_proxy_add_notify(content, "ContainerUpdateIDs",
749 G_TYPE_STRING, on_container_update_ids,
751 gupnp_service_proxy_set_subscribed(content, TRUE);
753 struct selection_data *data = selection_data_new(server, model, path);
754 transport_selection(server, data);
755 window = play_window(server, data);
758 HildonWindowStack *stack = hildon_window_stack_get_default();
759 hildon_window_stack_push_1(stack, HILDON_STACKABLE_WINDOW(window));
762 GtkWidget *content_window(struct proxy *server, char *title,
763 GtkListStore **view_list)
765 GtkWidget *window = hildon_stackable_window_new();
766 gtk_window_set_title(GTK_WINDOW(window), title);
768 GtkListStore *list = gtk_list_store_new(NUM_COLS, GDK_TYPE_PIXBUF,
769 G_TYPE_STRING, G_TYPE_STRING,
770 G_TYPE_POINTER, G_TYPE_BOOLEAN);
772 GtkWidget *selector = new_selector(list);
774 g_signal_connect(G_OBJECT(selector), "changed",
775 G_CALLBACK(content_select), server);
777 gtk_container_add(GTK_CONTAINER(window), selector);
783 void server_select(HildonTouchSelector *selector, gint column, gpointer data)
786 path = hildon_touch_selector_get_last_activated_row(selector, column);
789 GtkTreeModel *model = hildon_touch_selector_get_model(selector, column);
791 gtk_tree_model_get_iter(model, &iter, path);
794 gtk_tree_model_get(model, &iter, COL_ID, &udn, -1);
796 GHashTable *servers = (GHashTable *)data;
797 struct proxy *server = g_hash_table_lookup(servers, udn);
799 GUPnPServiceProxy *content_dir = GUPNP_SERVICE_PROXY(
800 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(server->proxy),
803 GtkListStore *view_list;
804 GtkWidget *window = content_window(server, server->name, &view_list);
806 browse(content_dir, "0", 0, MAX_BROWSE, view_list, server->set);
808 gupnp_service_proxy_add_notify(content_dir, "ContainerUpdateIDs",
809 G_TYPE_STRING, on_container_update_ids,
811 gupnp_service_proxy_set_subscribed(content_dir, TRUE);
813 /* TODO: GList *child_devices = gupnp_device_info_list_devices(GUPNP_DEVICE_INFO(server->proxy)); */
815 HildonWindowStack *stack = hildon_window_stack_get_default();
816 hildon_window_stack_push_1(stack, HILDON_STACKABLE_WINDOW(window));
819 GtkWidget *target_selector(struct proxy_set *proxy_set)
822 selector = hildon_touch_selector_new();
824 HildonTouchSelectorColumn *column =
825 hildon_touch_selector_append_column(HILDON_TOUCH_SELECTOR(selector),
826 GTK_TREE_MODEL(proxy_set->renderer_list), NULL, NULL);
827 g_object_unref(proxy_set->renderer_list);
828 hildon_touch_selector_column_set_text_column(column, 0);
830 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
831 g_object_set(renderer, "xalign", 0.0, NULL);
832 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), renderer, TRUE);
833 gtk_cell_renderer_set_fixed_size(renderer, -1, ROW_HEIGHT);
834 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(column), renderer,
837 gtk_widget_show_all(selector);
841 GtkWidget *main_menu(struct proxy_set *proxy_set)
843 GtkWidget *menu = hildon_app_menu_new();
845 button = hildon_picker_button_new(HILDON_SIZE_AUTO,
846 HILDON_BUTTON_ARRANGEMENT_VERTICAL);
847 hildon_button_set_title(HILDON_BUTTON(button), CHOOSE_TARGET);
849 GtkWidget *selector = target_selector(proxy_set);
850 hildon_picker_button_set_selector(HILDON_PICKER_BUTTON(button),
851 HILDON_TOUCH_SELECTOR(selector));
852 proxy_set->app->renderer_picker = HILDON_PICKER_BUTTON(button);
854 hildon_app_menu_append(HILDON_APP_MENU(menu), GTK_BUTTON(button));
856 gtk_widget_show_all(menu);
860 GtkWidget *main_window(HildonProgram *program, struct proxy_set *proxy_set)
862 GtkWidget *window = hildon_stackable_window_new();
863 hildon_program_add_window(program, HILDON_WINDOW(window));
865 gtk_window_set_title(GTK_WINDOW(window), APPLICATION_NAME);
867 proxy_set->server_list = gtk_list_store_new(NUM_COLS, GDK_TYPE_PIXBUF,
868 G_TYPE_STRING, G_TYPE_STRING,
869 G_TYPE_POINTER, G_TYPE_BOOLEAN);
871 GtkWidget *selector = new_selector(proxy_set->server_list);
873 g_signal_connect(G_OBJECT(selector), "changed",
874 G_CALLBACK(server_select), proxy_set->servers);
876 gtk_container_add(GTK_CONTAINER(window), selector);
878 GtkWidget *menu = main_menu(proxy_set);
879 hildon_window_set_app_menu(HILDON_WINDOW(window), HILDON_APP_MENU(menu));
884 void protocol_info(GUPnPServiceProxy *cm, GUPnPServiceProxyAction *action,
887 gchar *sink_protocols;
888 GError *error = NULL;
889 const gchar *udn = gupnp_service_info_get_udn(GUPNP_SERVICE_INFO(cm));
891 if (!gupnp_service_proxy_end_action(cm, action, &error, "Sink",
892 G_TYPE_STRING, &sink_protocols,
894 g_warning("Failed to get sink protocol info from "
895 "media renderer '%s':%s\n",
896 udn, error->message);
901 struct proxy *server = (struct proxy *)user_data;
902 server->protocols = g_strsplit(sink_protocols, ",", 0);
905 void add_renderer(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
907 const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
910 struct proxy *server = g_hash_table_lookup(proxy_set->renderers, udn);
913 char *name = gupnp_device_info_get_friendly_name(GUPNP_DEVICE_INFO(proxy));
916 server = (struct proxy *)g_slice_alloc(sizeof(struct proxy));
917 server->proxy = proxy;
919 server->set = proxy_set;
922 gtk_list_store_append(proxy_set->renderer_list, &iter);
923 gtk_list_store_set(proxy_set->renderer_list, &iter,
924 0, server->name, 1, udn, -1);
926 /* TODO: default to saved value */
927 hildon_picker_button_set_active(proxy_set->app->renderer_picker, 0);
929 GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->renderer_list);
930 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
931 server->row = gtk_tree_row_reference_new(model, path);
933 g_hash_table_replace(proxy_set->renderers, (char *)udn, server);
935 GUPnPServiceProxy *cm = GUPNP_SERVICE_PROXY(
936 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(proxy),
937 CONNECTION_MANAGER));
938 gupnp_service_proxy_begin_action(cm, "GetProtocolInfo", protocol_info,
942 void add_server(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
944 const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
947 struct proxy *server = g_hash_table_lookup(proxy_set->servers, udn);
950 char *name = gupnp_device_info_get_friendly_name(GUPNP_DEVICE_INFO(proxy));
951 GUPnPServiceInfo *content_dir =
952 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(proxy), CONTENT_DIR);
954 if (!name || !content_dir) return;
956 server = (struct proxy *)g_slice_alloc(sizeof(struct proxy));
957 server->proxy = proxy;
959 server->set = proxy_set;
961 GError *error = NULL;
962 GdkPixbuf *icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
963 "control_bluetooth_lan",
964 HILDON_ICON_PIXEL_SIZE_FINGER,
967 gtk_list_store_append(proxy_set->server_list, &iter);
968 gtk_list_store_set(proxy_set->server_list, &iter,
969 COL_ICON, icon, COL_LABEL, server->name, COL_ID, udn,
970 COL_CONTENT, NULL, COL_CONTAINER, TRUE, -1);
972 GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->server_list);
973 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
974 server->row = gtk_tree_row_reference_new(model, path);
976 g_hash_table_replace(proxy_set->servers, (char *)udn, server);
979 void remove_renderer(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
981 const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
982 struct proxy *server = g_hash_table_lookup(proxy_set->renderers, udn);
985 GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->renderer_list);
987 gtk_tree_model_get_iter(model, &iter,
988 gtk_tree_row_reference_get_path(server->row));
989 gtk_list_store_remove(proxy_set->renderer_list, &iter);
990 g_hash_table_remove(proxy_set->renderers, udn);
993 /* TODO: change current selection if necessary */
996 void remove_server(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
998 const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
999 struct proxy *server = g_hash_table_lookup(proxy_set->servers, udn);
1000 if (!server) return;
1002 GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->server_list);
1004 gtk_tree_model_get_iter(model, &iter,
1005 gtk_tree_row_reference_get_path(server->row));
1006 gtk_list_store_remove(proxy_set->server_list, &iter);
1007 g_hash_table_remove(proxy_set->servers, udn);
1010 /* TODO: bring user back to server menu if necessary */
1013 void device_proxy_available(GUPnPControlPoint *cp,
1014 GUPnPDeviceProxy *proxy, gpointer user_data)
1016 struct proxy_set *proxy_set = (struct proxy_set *)user_data;
1018 type = gupnp_device_info_get_device_type(GUPNP_DEVICE_INFO(proxy));
1020 if (g_pattern_match_simple(MEDIA_RENDERER, type)) {
1021 add_renderer(proxy, proxy_set);
1022 } else if (g_pattern_match_simple(MEDIA_SERVER, type)) {
1023 add_server(proxy, proxy_set);
1027 void device_proxy_unavailable(GUPnPControlPoint *cp,
1028 GUPnPDeviceProxy *proxy, gpointer user_data)
1030 struct proxy_set *proxy_set = (struct proxy_set *)user_data;
1032 type = gupnp_device_info_get_device_type(GUPNP_DEVICE_INFO(proxy));
1034 if (g_pattern_match_simple(MEDIA_RENDERER, type)) {
1035 remove_renderer(proxy, proxy_set);
1036 } else if (g_pattern_match_simple(MEDIA_SERVER, type)) {
1037 remove_server(proxy, proxy_set);
1041 void init_upnp(struct proxy_set *proxy_set)
1043 GError *error = NULL;
1047 GUPnPContext *context = gupnp_context_new(NULL, NULL, 0, &error);
1049 g_printerr("Error creating the GUPnP context: %s\n", error->message);
1050 g_error_free(error);
1054 GUPnPControlPoint *cp = gupnp_control_point_new(context, "ssdp:all");
1056 g_signal_connect(cp, "device-proxy-available",
1057 G_CALLBACK(device_proxy_available), proxy_set);
1058 g_signal_connect(cp, "device-proxy-unavailable",
1059 G_CALLBACK(device_proxy_unavailable), proxy_set);
1061 gssdp_resource_browser_set_active(GSSDP_RESOURCE_BROWSER(cp), TRUE);
1064 void init_application(struct application *app)
1066 struct proxy_set *set = &app->set;
1067 set->renderers = g_hash_table_new(g_str_hash, g_str_equal);
1068 set->servers = g_hash_table_new(g_str_hash, g_str_equal);
1070 set->renderer_list = gtk_list_store_new(2, G_TYPE_STRING,
1072 set->server_list = gtk_list_store_new(NUM_COLS, GDK_TYPE_PIXBUF,
1073 G_TYPE_STRING, G_TYPE_STRING,
1074 G_TYPE_POINTER, G_TYPE_BOOLEAN);
1077 app->didl_parser = gupnp_didl_lite_parser_new();
1078 app->soup = soup_session_async_new();
1081 int main(int argc, char *argv[])
1083 g_thread_init(NULL);
1085 hildon_gtk_init(&argc, &argv);
1087 HildonProgram *program = hildon_program_get_instance();
1088 g_set_application_name(APPLICATION_NAME);
1090 struct application app;
1091 init_application(&app);
1093 GtkWidget *window = main_window(program, &app.set);
1094 g_signal_connect(G_OBJECT(window), "destroy",
1095 G_CALLBACK(gtk_main_quit), NULL);
1097 init_upnp(&app.set);
1099 gtk_widget_show(window);