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 <hildon/hildon.h>
31 #include <libgupnp/gupnp-control-point.h>
32 #include <libgupnp-av/gupnp-av.h>
37 void add_content(GUPnPDIDLLiteParser *didl_parser, xmlNode *object_node,
40 struct browse_data *data = (struct browse_data *)user_data;
42 char *title = gupnp_didl_lite_object_get_title(object_node);
43 char *id = gupnp_didl_lite_object_get_id(object_node);
44 gboolean container = gupnp_didl_lite_object_is_container(object_node);
46 GdkPixbuf *icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
47 (container ? "general_folder"
48 : "general_audio_file"),
49 HILDON_ICON_PIXEL_SIZE_FINGER,
52 gtk_list_store_append(data->list, &iter);
53 gtk_list_store_set(data->list, &iter,
54 COL_ICON, icon, COL_LABEL, title,
55 COL_ID, id, COL_CONTENT, data->content_dir,
56 COL_CONTAINER, container, -1);
59 struct browse_data *browse_data_new(GUPnPServiceProxy *content_dir,
60 const char *id, guint32 starting_index,
63 struct browse_data *data;
65 data = g_slice_new(struct browse_data);
66 data->content_dir = g_object_ref(content_dir);
67 data->id = g_strdup(id);
68 data->starting_index = starting_index;
74 void browse_data_free(struct browse_data *data)
77 g_object_unref(data->content_dir);
78 g_slice_free(struct browse_data, data);
81 void browse_cb(GUPnPServiceProxy *content_dir,
82 GUPnPServiceProxyAction *action, gpointer user_data)
84 struct browse_data *data;
86 guint32 number_returned;
87 guint32 total_matches;
90 data = (struct browse_data *)user_data;
94 gupnp_service_proxy_end_action(content_dir, action, &error,
95 "Result", G_TYPE_STRING, &didl_xml,
96 "NumberReturned", G_TYPE_UINT,
98 "TotalMatches", G_TYPE_UINT, &total_matches,
102 GError *error = NULL;
104 if (!gupnp_didl_lite_parser_parse_didl(data->didl_parser, didl_xml,
105 add_content, data, &error)) {
106 g_warning("%s\n", error->message);
111 data->starting_index += number_returned;
113 /* See if we have more objects to get */
114 remaining = total_matches - data->starting_index;
115 /* Keep browsing till we get each and every object */
116 if (remaining != 0) browse(content_dir, data->id, data->starting_index,
117 MIN(remaining, MAX_BROWSE),
118 data->list, data->didl_parser);
120 GUPnPServiceInfo *info;
122 info = GUPNP_SERVICE_INFO(content_dir);
123 g_warning("Failed to browse '%s': %s\n",
124 gupnp_service_info_get_location(info),
130 browse_data_free(data);
133 void browse(GUPnPServiceProxy *content_dir, const char *container_id,
134 guint32 starting_index, guint32 requested_count,
135 GtkListStore *list, GUPnPDIDLLiteParser *didl_parser)
137 struct browse_data *data;
138 data = browse_data_new(content_dir, container_id, starting_index, list);
140 gupnp_service_proxy_begin_action(content_dir, "Browse", browse_cb, data,
141 "ObjectID", G_TYPE_STRING, container_id,
142 "BrowseFlag", G_TYPE_STRING,
143 "BrowseDirectChildren",
144 "Filter", G_TYPE_STRING, "*",
145 "StartingIndex", G_TYPE_UINT,
147 "RequestedCount", G_TYPE_UINT,
149 "SortCriteria", G_TYPE_STRING, "",
153 void update_container(GUPnPServiceProxy *content_dir,
154 const char *container_id)
157 // GtkTreeModel *model;
158 // GtkTreeIter container_iter;
161 void on_container_update_ids(GUPnPServiceProxy *content_dir,
162 const char *variable, GValue *value,
165 char **tokens = g_strsplit(g_value_get_string(value), ",", 0);
167 for (i=0; tokens[i] != NULL && tokens[i+1] != NULL; i+=2) {
168 update_container(content_dir, tokens[i]);
173 void set_panarea_padding(GtkWidget *child, gpointer data)
175 void set_child_padding(GtkWidget *child, gpointer user_data)
177 GtkBox *box = GTK_BOX(user_data);
178 gboolean expand, fill;
182 gtk_box_query_child_packing(box, child, &expand, &fill, &pad, &pack);
183 gtk_box_set_child_packing(box, child, expand, fill, 0, pack);
186 if (GTK_IS_CONTAINER(child))
187 gtk_container_forall(GTK_CONTAINER(child), set_child_padding, child);
190 GtkWidget *new_selector(GtkListStore *list)
192 GtkWidget *selector = hildon_touch_selector_new();
194 HildonTouchSelectorColumn *column =
195 hildon_touch_selector_append_column(HILDON_TOUCH_SELECTOR(selector),
196 GTK_TREE_MODEL(list), NULL, NULL);
197 g_object_unref(list);
198 hildon_touch_selector_column_set_text_column(column, 1);
200 hildon_touch_selector_set_hildon_ui_mode(HILDON_TOUCH_SELECTOR(selector),
201 HILDON_UI_MODE_NORMAL);
202 GtkCellRenderer *renderer;
203 renderer = gtk_cell_renderer_pixbuf_new();
204 g_object_set(renderer, "xalign", ICON_XALIGN, NULL);
205 gtk_cell_renderer_set_fixed_size(renderer, ICON_WIDTH, ROW_HEIGHT);
206 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), renderer, FALSE);
207 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(column), renderer,
210 renderer = gtk_cell_renderer_text_new();
211 g_object_set(renderer, "xalign", 0.0, NULL);
212 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), renderer, TRUE);
213 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(column), renderer,
216 gtk_container_forall(GTK_CONTAINER(selector), set_panarea_padding, NULL);
218 gtk_widget_show_all(selector);
222 void transport_uri(GUPnPServiceProxy *av_transport,
223 GUPnPServiceProxyAction *action, gpointer user_data)
225 GError *error = NULL;
226 if (gupnp_service_proxy_end_action(av_transport, action, &error, NULL)) {
227 /* TODO: do something with duration? */
229 g_warning("Failed to set URI");
234 gboolean mime_type_is_a(const char *mime_type1, const char *mime_type2)
238 char *content_type1 = g_content_type_from_mime_type(mime_type1);
239 char *content_type2 = g_content_type_from_mime_type(mime_type2);
240 if (content_type1 == NULL || content_type2 == NULL) {
241 /* Uknown content type, just do a simple comarison */
242 ret = g_ascii_strcasecmp(mime_type1, mime_type2) == 0;
244 ret = g_content_type_is_a(content_type1, content_type2);
247 g_free(content_type1);
248 g_free(content_type2);
253 gboolean is_transport_compat(const gchar *renderer_protocol,
254 const gchar *renderer_host,
255 const gchar *item_protocol,
256 const gchar *item_host)
258 if (g_ascii_strcasecmp(renderer_protocol, item_protocol) != 0 &&
259 g_ascii_strcasecmp(renderer_protocol, "*") != 0) {
261 } else if (g_ascii_strcasecmp("INTERNAL", renderer_protocol) == 0 &&
262 g_ascii_strcasecmp(renderer_host, item_host) != 0) {
263 /* Host must be the same in case of INTERNAL protocol */
270 gboolean is_content_format_compat(const gchar *renderer_content_format,
271 const gchar *item_content_format)
273 if(g_ascii_strcasecmp(renderer_content_format, "*") != 0 &&
274 !mime_type_is_a(item_content_format, renderer_content_format)) {
281 gchar *get_dlna_pn(gchar **additional_info_fields)
285 for (i = 0; additional_info_fields[i]; i++) {
286 pn = g_strstr_len(additional_info_fields[i],
287 strlen(additional_info_fields[i]), "DLNA.ORG_PN=");
289 pn += 12; /* end of "DLNA.ORG_PN=" */
297 gboolean is_additional_info_compat(const gchar *renderer_additional_info,
298 const gchar *item_additional_info)
300 gboolean ret = FALSE;
302 if (g_ascii_strcasecmp(renderer_additional_info, "*") == 0) {
306 char **renderer_tokens = g_strsplit(renderer_additional_info, ";", -1);
307 if (renderer_tokens == NULL) {
311 char **item_tokens = g_strsplit(item_additional_info, ";", -1);
312 if (item_tokens == NULL) {
316 char *renderer_pn = get_dlna_pn(renderer_tokens);
317 char *item_pn = get_dlna_pn(item_tokens);
318 if (renderer_pn == NULL || item_pn == NULL) {
322 if (g_ascii_strcasecmp(renderer_pn, item_pn) == 0) {
327 g_strfreev(item_tokens);
329 g_strfreev(renderer_tokens);
334 gboolean is_protocol_info_compat(xmlNode *res_node,
335 const gchar *renderer_protocol)
337 gchar *item_protocol;
338 gchar **item_proto_tokens;
339 gchar **renderer_proto_tokens;
340 gboolean ret = FALSE;
342 item_protocol = gupnp_didl_lite_property_get_attribute(res_node,
344 if (!item_protocol) return FALSE;
346 item_proto_tokens = g_strsplit(item_protocol, ":", 4);
347 renderer_proto_tokens = g_strsplit(renderer_protocol, ":", 4);
349 if (!item_proto_tokens[0] || !item_proto_tokens[1] ||
350 !item_proto_tokens[2] || !item_proto_tokens[3] ||
351 !renderer_proto_tokens[0] || !renderer_proto_tokens[1] ||
352 !renderer_proto_tokens[2] || !renderer_proto_tokens[3])
355 if (is_transport_compat(renderer_proto_tokens[0], renderer_proto_tokens[2],
356 item_proto_tokens[0], item_proto_tokens[1]) &&
357 is_content_format_compat(renderer_proto_tokens[2],
358 item_proto_tokens[2]) &&
359 is_additional_info_compat(renderer_proto_tokens[3],
360 item_proto_tokens[3]))
364 g_free(item_protocol);
365 g_strfreev(renderer_proto_tokens);
366 g_strfreev(item_proto_tokens);
371 char *find_compat_uri_from_metadata(const char *metadata, char **duration,
372 struct proxy *renderer)
375 void on_didl_item_available(GUPnPDIDLLiteParser *didl_parser,
376 xmlNode *item_node, gpointer user_data)
379 gupnp_didl_lite_object_get_property(item_node, "res");
380 if (!resources) return;
383 for (i=0; renderer->protocols[i] && uri == NULL; i++) {
384 GList *res, *compat_res = NULL;
386 for (res = resources; res != NULL; res = res->next) {
387 res_node = (xmlNode *)res->data;
390 for (j=0; renderer->protocols[j]; j++) {
391 if (is_protocol_info_compat(res_node,
392 renderer->protocols[j])) {
398 if (!compat_res) continue;
400 res_node = (xmlNode *)compat_res->data;
401 uri = gupnp_didl_lite_property_get_value(res_node);
402 *duration = gupnp_didl_lite_property_get_attribute(res_node,
405 g_list_free(resources);
408 GError *error = NULL;
409 /* Assumption: metadata only contains a single didl object */
410 gupnp_didl_lite_parser_parse_didl(renderer->set->didl_parser, metadata,
411 on_didl_item_available, NULL, &error);
413 g_warning("%s\n", error->message);
420 struct proxy *current_renderer(struct proxy_set *proxy_set)
422 int i = hildon_picker_button_get_active(proxy_set->renderer_picker);
424 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(proxy_set->renderer_list),
427 gtk_tree_model_get(GTK_TREE_MODEL(proxy_set->renderer_list), &iter,
430 return g_hash_table_lookup(proxy_set->renderers, udn);
433 void set_av_transport_uri(GUPnPServiceProxy *content_dir,
434 GUPnPServiceProxyAction *action, gpointer user_data)
436 GError *error = NULL;
438 gupnp_service_proxy_end_action(content_dir, action, &error,
439 "Result", G_TYPE_STRING, &metadata, NULL);
440 if (!metadata) return;
442 g_warning("Failed to get metadata for content");
446 struct proxy_set *proxy_set = (struct proxy_set *)user_data;
447 struct proxy *renderer = current_renderer(proxy_set);
450 char *uri = find_compat_uri_from_metadata(metadata, &duration, renderer);
452 g_warning("no compatible URI found.");
456 GUPnPServiceProxy *av_transport = GUPNP_SERVICE_PROXY(
457 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(renderer->proxy),
460 gupnp_service_proxy_begin_action(av_transport, "SetAVTransportURI",
462 "InstanceID", G_TYPE_UINT, 0,
463 "CurrentURI", G_TYPE_STRING, uri,
464 "CurrentURIMetaData", G_TYPE_STRING,
469 void av_transport_action_cb(GUPnPServiceProxy *av_transport,
470 GUPnPServiceProxyAction *action, gpointer data)
472 const char *action_name = (const char *)data;
473 GError *error = NULL;
475 if (!gupnp_service_proxy_end_action(av_transport, action, &error, NULL)) {
476 g_warning("Failed to send action '%s': %s",
477 action_name, error->message);
482 void g_value_free(gpointer data)
484 g_value_unset((GValue *)data);
485 g_slice_free(GValue, data);
488 GHashTable *create_av_transport_args_hash(char **additional_args)
490 GHashTable *args = g_hash_table_new_full(g_str_hash, g_str_equal,
493 GValue *instance_id = g_slice_alloc0(sizeof(GValue));
494 g_value_init(instance_id, G_TYPE_UINT);
495 g_value_set_uint(instance_id, 0);
497 g_hash_table_insert(args, "InstanceID", instance_id);
499 if (additional_args) {
501 for (i=0; additional_args[i]; i += 2) {
502 GValue *value = g_slice_alloc0(sizeof(GValue));
503 g_value_init(value, G_TYPE_STRING);
504 g_value_set_string(value, additional_args[i + 1]);
505 g_hash_table_insert(args, additional_args[i], value);
511 void av_transport_send_action(GUPnPServiceProxy *av_transport, char *action,
512 char *additional_args[])
514 GHashTable *args = create_av_transport_args_hash(additional_args);
516 gupnp_service_proxy_begin_action_hash(av_transport, action,
517 av_transport_action_cb,
520 g_hash_table_unref(args);
523 void transport_selection(struct proxy *server, GtkTreeRowReference *row)
525 server->current_selection = row;
527 GtkTreeModel *model = gtk_tree_row_reference_get_model(row);
528 GtkTreePath *path = gtk_tree_row_reference_get_path(row);
531 gtk_tree_model_get_iter(model, &iter, path);
535 GUPnPServiceProxy *content;
536 gtk_tree_model_get(model, &iter, COL_LABEL, &label,
537 COL_CONTENT, &content, COL_ID, &id, -1);
539 gupnp_service_proxy_begin_action(content, "Browse",
540 set_av_transport_uri, server->set,
541 "ObjectID", G_TYPE_STRING, id,
542 "BrowseFlag", G_TYPE_STRING,
544 "Filter", G_TYPE_STRING, "*",
545 "StartingIndex", G_TYPE_UINT, 0,
546 "RequestedCount", G_TYPE_UINT, 0,
547 "SortCriteria", G_TYPE_STRING, "",
551 void play_button(GtkWidget *button, gpointer data)
553 struct proxy_set *proxy_set = (struct proxy_set *)data;
554 struct proxy *renderer = current_renderer(proxy_set);
555 GUPnPServiceProxy *av_transport = GUPNP_SERVICE_PROXY(
556 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(renderer->proxy),
558 char *args[] = {"Speed", "1", NULL};
559 av_transport_send_action(av_transport, "Play", args);
562 GtkWidget *play_window(struct proxy *server, GtkTreeRowReference *row)
564 GtkWidget *window = hildon_stackable_window_new();
565 gtk_window_set_title(GTK_WINDOW(window), PLAY_WINDOW_TITLE);
567 GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
568 gtk_container_add(GTK_CONTAINER(window), vbox);
570 GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
571 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 6);
573 GtkWidget *image = gtk_image_new();
574 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 6);
576 GtkWidget *inner_box = gtk_vbox_new(FALSE, 0);
577 gtk_box_pack_start(GTK_BOX(hbox), inner_box, TRUE, TRUE, 6);
579 GtkWidget *button_box = gtk_hbutton_box_new();
580 gtk_box_pack_start(GTK_BOX(vbox), button_box, FALSE, FALSE, 6);
583 hildon_button_new_with_text(HILDON_SIZE_FINGER_HEIGHT,
584 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
587 g_signal_connect(button, "clicked", G_CALLBACK(play_button), server->set);
589 gtk_box_pack_start(GTK_BOX(button_box), button, FALSE, FALSE, 0);
590 gtk_widget_show_all(GTK_WIDGET(vbox));
595 void content_select(HildonTouchSelector *selector, gint column, gpointer data)
598 path = hildon_touch_selector_get_last_activated_row(selector, column);
601 GtkTreeModel *model = hildon_touch_selector_get_model(selector, column);
603 gtk_tree_model_get_iter(model, &iter, path);
605 GUPnPServiceProxy *content;
608 gtk_tree_model_get(model, &iter, COL_CONTENT, &content, COL_ID, &id,
609 COL_CONTAINER, &container,
610 COL_LABEL, &label, -1);
612 struct proxy *server = (struct proxy *)data;
616 GtkListStore *view_list;
617 window = content_window(server, label, &view_list);
618 browse(content, id, 0, MAX_BROWSE, view_list, server->set->didl_parser);
619 gupnp_service_proxy_add_notify(content, "ContainerUpdateIDs",
620 G_TYPE_STRING, on_container_update_ids,
622 gupnp_service_proxy_set_subscribed(content, TRUE);
624 GtkTreeRowReference *row = gtk_tree_row_reference_new(model, path);
625 transport_selection(server, row);
626 window = play_window(server, row);
629 HildonWindowStack *stack = hildon_window_stack_get_default();
630 hildon_window_stack_push_1(stack, HILDON_STACKABLE_WINDOW(window));
633 GtkWidget *content_window(struct proxy *server, char *title,
634 GtkListStore **view_list)
636 GtkWidget *window = hildon_stackable_window_new();
637 gtk_window_set_title(GTK_WINDOW(window), title);
639 GtkListStore *list = gtk_list_store_new(NUM_COLS, GDK_TYPE_PIXBUF,
640 G_TYPE_STRING, G_TYPE_STRING,
641 G_TYPE_POINTER, G_TYPE_BOOLEAN);
643 GtkWidget *selector = new_selector(list);
645 g_signal_connect(G_OBJECT(selector), "changed",
646 G_CALLBACK(content_select), server);
648 gtk_container_add(GTK_CONTAINER(window), selector);
654 void server_select(HildonTouchSelector *selector, gint column, gpointer data)
657 path = hildon_touch_selector_get_last_activated_row(selector, column);
660 GtkTreeModel *model = hildon_touch_selector_get_model(selector, column);
662 gtk_tree_model_get_iter(model, &iter, path);
665 gtk_tree_model_get(model, &iter, COL_ID, &udn, -1);
667 GHashTable *servers = (GHashTable *)data;
668 struct proxy *server = g_hash_table_lookup(servers, udn);
670 GUPnPServiceProxy *content_dir = GUPNP_SERVICE_PROXY(
671 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(server->proxy),
674 GtkListStore *view_list;
675 GtkWidget *window = content_window(server, server->name, &view_list);
677 browse(content_dir, "0", 0, MAX_BROWSE, view_list,
678 server->set->didl_parser);
680 gupnp_service_proxy_add_notify(content_dir, "ContainerUpdateIDs",
681 G_TYPE_STRING, on_container_update_ids,
683 gupnp_service_proxy_set_subscribed(content_dir, TRUE);
685 /* TODO: GList *child_devices = gupnp_device_info_list_devices(GUPNP_DEVICE_INFO(server->proxy)); */
687 HildonWindowStack *stack = hildon_window_stack_get_default();
688 hildon_window_stack_push_1(stack, HILDON_STACKABLE_WINDOW(window));
691 GtkWidget *target_selector(struct proxy_set *proxy_set)
694 selector = hildon_touch_selector_new();
696 HildonTouchSelectorColumn *column =
697 hildon_touch_selector_append_column(HILDON_TOUCH_SELECTOR(selector),
698 GTK_TREE_MODEL(proxy_set->renderer_list), NULL, NULL);
699 g_object_unref(proxy_set->renderer_list);
700 hildon_touch_selector_column_set_text_column(column, 0);
702 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
703 g_object_set(renderer, "xalign", 0.0, NULL);
704 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), renderer, TRUE);
705 gtk_cell_renderer_set_fixed_size(renderer, -1, ROW_HEIGHT);
706 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(column), renderer,
709 gtk_widget_show_all(selector);
713 GtkWidget *main_menu(struct proxy_set *proxy_set)
715 GtkWidget *menu = hildon_app_menu_new();
717 button = hildon_picker_button_new(HILDON_SIZE_AUTO,
718 HILDON_BUTTON_ARRANGEMENT_VERTICAL);
719 hildon_button_set_title(HILDON_BUTTON(button), CHOOSE_TARGET);
721 GtkWidget *selector = target_selector(proxy_set);
722 hildon_picker_button_set_selector(HILDON_PICKER_BUTTON(button),
723 HILDON_TOUCH_SELECTOR(selector));
724 proxy_set->renderer_picker = HILDON_PICKER_BUTTON(button);
726 hildon_app_menu_append(HILDON_APP_MENU(menu), GTK_BUTTON(button));
728 gtk_widget_show_all(menu);
732 GtkWidget *main_window(HildonProgram *program, struct proxy_set *proxy_set)
734 GtkWidget *window = hildon_stackable_window_new();
735 hildon_program_add_window(program, HILDON_WINDOW(window));
737 gtk_window_set_title(GTK_WINDOW(window), APPLICATION_NAME);
739 GError *error = NULL;
740 proxy_set->icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
741 "control_bluetooth_lan",
742 HILDON_ICON_PIXEL_SIZE_FINGER,
744 proxy_set->server_list = gtk_list_store_new(NUM_COLS, GDK_TYPE_PIXBUF,
745 G_TYPE_STRING, G_TYPE_STRING,
746 G_TYPE_POINTER, G_TYPE_BOOLEAN);
748 GtkWidget *selector = new_selector(proxy_set->server_list);
750 g_signal_connect(G_OBJECT(selector), "changed",
751 G_CALLBACK(server_select), proxy_set->servers);
753 gtk_container_add(GTK_CONTAINER(window), selector);
755 GtkWidget *menu = main_menu(proxy_set);
756 hildon_window_set_app_menu(HILDON_WINDOW(window), HILDON_APP_MENU(menu));
761 void protocol_info(GUPnPServiceProxy *cm, GUPnPServiceProxyAction *action,
764 gchar *sink_protocols;
765 GError *error = NULL;
766 const gchar *udn = gupnp_service_info_get_udn(GUPNP_SERVICE_INFO(cm));
768 if (!gupnp_service_proxy_end_action(cm, action, &error, "Sink",
769 G_TYPE_STRING, &sink_protocols,
771 g_warning("Failed to get sink protocol info from "
772 "media renderer '%s':%s\n",
773 udn, error->message);
778 struct proxy *server = (struct proxy *)user_data;
779 server->protocols = g_strsplit(sink_protocols, ",", 0);
782 void add_renderer(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
784 const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
787 struct proxy *server = g_hash_table_lookup(proxy_set->renderers, udn);
790 char *name = gupnp_device_info_get_friendly_name(GUPNP_DEVICE_INFO(proxy));
793 server = (struct proxy *)g_malloc(sizeof(struct proxy));
794 server->proxy = proxy;
796 server->set = proxy_set;
799 gtk_list_store_append(proxy_set->renderer_list, &iter);
800 gtk_list_store_set(proxy_set->renderer_list, &iter,
801 0, server->name, 1, udn, -1);
803 /* TODO: default to saved value */
804 hildon_picker_button_set_active(proxy_set->renderer_picker, 0);
806 GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->renderer_list);
807 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
808 server->row = gtk_tree_row_reference_new(model, path);
810 g_hash_table_replace(proxy_set->renderers, (char *)udn, server);
812 GUPnPServiceProxy *cm = GUPNP_SERVICE_PROXY(
813 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(proxy),
814 CONNECTION_MANAGER));
815 gupnp_service_proxy_begin_action(cm, "GetProtocolInfo", protocol_info,
819 void add_server(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
821 const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
824 struct proxy *server = g_hash_table_lookup(proxy_set->servers, udn);
827 char *name = gupnp_device_info_get_friendly_name(GUPNP_DEVICE_INFO(proxy));
828 GUPnPServiceInfo *content_dir =
829 gupnp_device_info_get_service(GUPNP_DEVICE_INFO(proxy), CONTENT_DIR);
831 if (!name || !content_dir) return;
833 server = (struct proxy *)g_malloc(sizeof(struct proxy));
834 server->proxy = proxy;
836 server->set = proxy_set;
839 gtk_list_store_append(proxy_set->server_list, &iter);
840 gtk_list_store_set(proxy_set->server_list, &iter,
841 COL_ICON, proxy_set->icon, COL_LABEL, server->name, COL_ID, udn,
842 COL_CONTENT, NULL, COL_CONTAINER, TRUE, -1);
844 GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->server_list);
845 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
846 server->row = gtk_tree_row_reference_new(model, path);
848 g_hash_table_replace(proxy_set->servers, (char *)udn, server);
851 void remove_renderer(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
853 const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
854 struct proxy *server = g_hash_table_lookup(proxy_set->renderers, udn);
857 GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->renderer_list);
859 gtk_tree_model_get_iter(model, &iter,
860 gtk_tree_row_reference_get_path(server->row));
861 gtk_list_store_remove(proxy_set->renderer_list, &iter);
862 g_hash_table_remove(proxy_set->renderers, udn);
865 /* TODO: change current selection if necessary */
868 void remove_server(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
870 const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
871 struct proxy *server = g_hash_table_lookup(proxy_set->servers, udn);
874 GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->server_list);
876 gtk_tree_model_get_iter(model, &iter,
877 gtk_tree_row_reference_get_path(server->row));
878 gtk_list_store_remove(proxy_set->server_list, &iter);
879 g_hash_table_remove(proxy_set->servers, udn);
882 /* TODO: bring user back to server menu if necessary */
885 void device_proxy_available(GUPnPControlPoint *cp,
886 GUPnPDeviceProxy *proxy, gpointer user_data)
888 struct proxy_set *proxy_set = (struct proxy_set *)user_data;
890 type = gupnp_device_info_get_device_type(GUPNP_DEVICE_INFO(proxy));
892 if (g_pattern_match_simple(MEDIA_RENDERER, type)) {
893 add_renderer(proxy, proxy_set);
894 } else if (g_pattern_match_simple(MEDIA_SERVER, type)) {
895 add_server(proxy, proxy_set);
899 void device_proxy_unavailable(GUPnPControlPoint *cp,
900 GUPnPDeviceProxy *proxy, gpointer user_data)
902 struct proxy_set *proxy_set = (struct proxy_set *)user_data;
904 type = gupnp_device_info_get_device_type(GUPNP_DEVICE_INFO(proxy));
906 if (g_pattern_match_simple(MEDIA_RENDERER, type)) {
907 remove_renderer(proxy, proxy_set);
908 } else if (g_pattern_match_simple(MEDIA_SERVER, type)) {
909 remove_server(proxy, proxy_set);
913 void init_upnp(struct proxy_set *proxy_set)
915 GError *error = NULL;
919 GUPnPContext *context = gupnp_context_new(NULL, NULL, 0, &error);
921 g_printerr("Error creating the GUPnP context: %s\n", error->message);
926 GUPnPControlPoint *cp = gupnp_control_point_new(context, "ssdp:all");
928 g_signal_connect(cp, "device-proxy-available",
929 G_CALLBACK(device_proxy_available), proxy_set);
930 g_signal_connect(cp, "device-proxy-unavailable",
931 G_CALLBACK(device_proxy_unavailable), proxy_set);
933 gssdp_resource_browser_set_active(GSSDP_RESOURCE_BROWSER(cp), TRUE);
936 int main(int argc, char *argv[])
940 hildon_gtk_init(&argc, &argv);
942 HildonProgram *program = hildon_program_get_instance();
943 g_set_application_name(APPLICATION_NAME);
945 struct proxy_set proxy_set;
946 proxy_set.renderers = g_hash_table_new(g_str_hash, g_str_equal);
947 proxy_set.servers = g_hash_table_new(g_str_hash, g_str_equal);
949 proxy_set.renderer_list = gtk_list_store_new(2, G_TYPE_STRING,
951 proxy_set.didl_parser = gupnp_didl_lite_parser_new();
952 GtkWidget *window = main_window(program, &proxy_set);
953 g_signal_connect(G_OBJECT(window), "destroy",
954 G_CALLBACK(gtk_main_quit), NULL);
956 init_upnp(&proxy_set);
958 gtk_widget_show(window);