e83f1ca4ed309b93ab4ce7903973fff4a1ee592a
[devious] / devious.c
1 /*
2  * Devious: UPnP Control Point for Maemo 5
3  *
4  * Copyright (C) 2009 Kyle Cronan
5  *
6  * Author: Kyle Cronan <kyle@pbx.org>
7  *
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)
11  * any later version.
12  *
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.
17  *
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
21  *
22  */
23
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <gio/gio.h>
29 #include <glib.h>
30 #include <gdk-pixbuf/gdk-pixbuf.h>
31 #include <gtk/gtk.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>
36
37 #include "devious.h"
38
39
40 void add_content(GUPnPDIDLLiteParser *didl_parser, xmlNode *object_node,
41                  gpointer user_data)
42 {
43     struct browse_data *data = (struct browse_data *)user_data;
44     
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);
48     GError *error = NULL;
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,
53                                                0, &error);
54     GtkTreeIter iter;
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);
60 }
61
62 struct browse_data *browse_data_new(GUPnPServiceProxy *content_dir,
63                                     const char *id, guint32 starting_index,
64                                     GtkListStore *list, struct proxy_set *set)
65 {
66     struct browse_data *data;
67
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;
72     data->list = list;
73     data->set = set;
74
75     return data;
76 }
77
78 void browse_data_free(struct browse_data *data)
79 {
80     g_free(data->id);
81     g_object_unref(data->content_dir);
82     g_slice_free(struct browse_data, data);
83 }
84
85 void browse_cb(GUPnPServiceProxy *content_dir,
86                GUPnPServiceProxyAction *action, gpointer user_data)
87 {
88     struct browse_data *data;
89     char *didl_xml;
90     guint32 number_returned;
91     guint32 total_matches;
92     GError *error;
93
94     data = (struct browse_data *)user_data;
95     didl_xml = NULL;
96     error = NULL;
97
98     gupnp_service_proxy_end_action(content_dir, action, &error,
99                                    "Result", G_TYPE_STRING, &didl_xml,
100                                    "NumberReturned", G_TYPE_UINT,
101                                        &number_returned,
102                                    "TotalMatches", G_TYPE_UINT, &total_matches,
103                                    NULL);
104     if (didl_xml) {
105         guint32 remaining;
106         GError *error = NULL;
107
108         if (!gupnp_didl_lite_parser_parse_didl(data->set->app->didl_parser,
109                                                didl_xml, add_content, data,
110                                                &error)) {
111             g_warning("%s\n", error->message);
112             g_error_free(error);
113         }
114         g_free(didl_xml);
115
116         data->starting_index += number_returned;
117
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);
124     } else if (error) {
125         GUPnPServiceInfo *info;
126
127         info = GUPNP_SERVICE_INFO(content_dir);
128         g_warning("Failed to browse '%s': %s\n",
129                   gupnp_service_info_get_location(info),
130                   error->message);
131
132         g_error_free(error);
133     }
134
135     browse_data_free(data);
136 }
137
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)
141 {
142     struct browse_data *data;
143     data = browse_data_new(content_dir, container_id, starting_index, list,
144                            proxy_set);
145
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,
152                                          starting_index,
153                                      "RequestedCount", G_TYPE_UINT,
154                                          requested_count,
155                                      "SortCriteria", G_TYPE_STRING, "",
156                                      NULL);
157 }
158
159 void update_container(GUPnPServiceProxy *content_dir,
160                       const char *container_id)
161 {
162 //    TODO
163 //    GtkTreeModel *model;
164 //    GtkTreeIter container_iter;
165 }
166
167 void on_container_update_ids(GUPnPServiceProxy *content_dir,
168                              const char *variable, GValue *value,
169                              gpointer user_data)
170 {
171     char **tokens = g_strsplit(g_value_get_string(value), ",", 0);
172     guint i;
173     for (i=0; tokens[i] != NULL && tokens[i+1] != NULL; i+=2) {
174         update_container(content_dir, tokens[i]);
175     }
176     g_strfreev(tokens);
177 }
178
179 void set_panarea_padding(GtkWidget *child, gpointer data)
180 {
181     void set_child_padding(GtkWidget *child, gpointer user_data)
182     {
183         GtkBox *box = GTK_BOX(user_data);
184         gboolean expand, fill;
185         guint pad;
186         GtkPackType pack;
187
188         gtk_box_query_child_packing(box, child, &expand, &fill, &pad, &pack);
189         gtk_box_set_child_packing(box, child, expand, fill, 0, pack);
190     }
191
192     if (GTK_IS_CONTAINER(child))
193         gtk_container_forall(GTK_CONTAINER(child), set_child_padding, child);
194 }
195
196 GtkWidget *new_selector(GtkListStore *list)
197 {
198     GtkWidget *selector = hildon_touch_selector_new();
199
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);
205
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,
214                                    "pixbuf", 0, NULL);
215
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,
220                                    "text", 1, NULL);
221
222     gtk_container_forall(GTK_CONTAINER(selector), set_panarea_padding, NULL);
223
224     gtk_widget_show_all(selector);
225     return selector;
226 }
227
228 void transport_uri(GUPnPServiceProxy *av_transport,
229                    GUPnPServiceProxyAction *action, gpointer user_data)
230 {
231     GError *error = NULL;
232     if (gupnp_service_proxy_end_action(av_transport, action, &error, NULL)) {
233         /* TODO: do something with duration? */
234     } else {
235         g_warning("Failed to set URI");
236         g_error_free(error);
237     }
238 }
239
240 gboolean mime_type_is_a(const char *mime_type1, const char *mime_type2)
241 {
242     gboolean ret;
243
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;
249     } else {
250         ret = g_content_type_is_a(content_type1, content_type2);
251     }
252
253     g_free(content_type1);
254     g_free(content_type2);
255
256     return ret;
257 }
258
259 gboolean is_transport_compat(const gchar *renderer_protocol,
260                              const gchar *renderer_host,
261                              const gchar *item_protocol,
262                              const gchar *item_host)
263 {
264     if (g_ascii_strcasecmp(renderer_protocol, item_protocol) != 0 &&
265         g_ascii_strcasecmp(renderer_protocol, "*") != 0) {
266         return FALSE;
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 */
270         return FALSE;
271     } else {
272         return TRUE;
273     }
274 }
275
276 gboolean is_content_format_compat(const gchar *renderer_content_format,
277                                   const gchar *item_content_format)
278 {
279     if(g_ascii_strcasecmp(renderer_content_format, "*") != 0 &&
280         !mime_type_is_a(item_content_format, renderer_content_format)) {
281         return FALSE;
282     } else {
283         return TRUE;
284     }
285 }
286
287 gchar *get_dlna_pn(gchar **additional_info_fields)
288 {
289     gchar *pn = NULL;
290     gint i;
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=");
294         if (pn) {
295             pn += 12; /* end of "DLNA.ORG_PN=" */
296             break;
297         }
298     }
299
300     return pn;
301 }
302
303 gboolean is_additional_info_compat(const gchar *renderer_additional_info,
304                                    const gchar *item_additional_info)
305 {
306     gboolean ret = FALSE;
307
308     if (g_ascii_strcasecmp(renderer_additional_info, "*") == 0) {
309         return TRUE;
310     }
311
312     char **renderer_tokens = g_strsplit(renderer_additional_info, ";", -1);
313     if (renderer_tokens == NULL) {
314         return FALSE;
315     }
316
317     char **item_tokens = g_strsplit(item_additional_info, ";", -1);
318     if (item_tokens == NULL) {
319         goto no_item_tokens;
320     }
321
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) {
325         goto no_renderer_pn;
326     }
327
328     if (g_ascii_strcasecmp(renderer_pn, item_pn) == 0) {
329         ret = TRUE;
330     }
331
332 no_renderer_pn:
333     g_strfreev(item_tokens);
334 no_item_tokens:
335     g_strfreev(renderer_tokens);
336
337     return ret;
338 }
339
340 gboolean is_protocol_info_compat(xmlNode *res_node,
341                                  const gchar *renderer_protocol)
342 {
343     gchar *item_protocol;
344     gchar **item_proto_tokens;
345     gchar **renderer_proto_tokens;
346     gboolean ret = FALSE;
347
348     item_protocol = gupnp_didl_lite_property_get_attribute(res_node,
349                                                            "protocolInfo");
350     if (!item_protocol) return FALSE;
351
352     item_proto_tokens = g_strsplit(item_protocol, ":", 4);
353     renderer_proto_tokens = g_strsplit(renderer_protocol, ":", 4);
354
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])
359         goto return_point;
360
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]))
367         ret = TRUE;
368
369 return_point:
370     g_free(item_protocol);
371     g_strfreev(renderer_proto_tokens);
372     g_strfreev(item_proto_tokens);
373
374     return ret;
375 }
376
377 char *find_compat_uri_from_metadata(const char *metadata, char **duration,
378                                     char **art_uri, struct proxy *renderer)
379 {
380     char *uri = NULL;
381     void on_didl_item_available(GUPnPDIDLLiteParser *didl_parser,
382                                 xmlNode *item_node, gpointer user_data)
383     {
384         GList *resources =
385             gupnp_didl_lite_object_get_property(item_node, "res");
386         if (!resources) return;
387
388         int i;
389         for (i=0; renderer->protocols[i] && uri == NULL; i++) {
390             GList *res, *compat_res = NULL;
391             xmlNode *res_node;
392             for (res = resources; res != NULL; res = res->next) {
393                 res_node = (xmlNode *)res->data;
394
395                 int j;
396                 for (j=0; renderer->protocols[j]; j++) {
397                     if (is_protocol_info_compat(res_node,
398                                                 renderer->protocols[j])) {
399                         compat_res = res;
400                         break;
401                     }
402                 }
403             }
404             if (!compat_res) continue;
405
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,
409                                                                "duration");
410             GList *album_art =
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;
415         }
416         g_list_free(resources);
417     }
418
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);
423     if (error) {
424         g_warning("%s\n", error->message);
425         g_error_free(error);
426     }
427     
428     return uri;
429 }
430
431 struct proxy *current_renderer(struct proxy_set *proxy_set)
432 {
433     int i = hildon_picker_button_get_active(proxy_set->app->renderer_picker);
434     GtkTreeIter iter;
435     gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(proxy_set->renderer_list),
436                                   &iter, NULL, i);
437     char *udn;
438     gtk_tree_model_get(GTK_TREE_MODEL(proxy_set->renderer_list), &iter,
439                        1, &udn, -1);
440     
441     return g_hash_table_lookup(proxy_set->renderers, udn);
442 }
443
444 void set_image(SoupSession *session, SoupMessage *msg, gpointer user_data)
445 {
446     struct selection_data *data = (struct selection_data *)user_data;
447
448     if (!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) return;
449
450     GInputStream *stream =
451         g_memory_input_stream_new_from_data(msg->response_body->data,
452                                             msg->response_body->length, NULL);
453     
454     GError *error = NULL;
455     GdkPixbuf *pixbuf =
456         gdk_pixbuf_new_from_stream_at_scale(stream, PLAY_WINDOW_IMAGE_SIZE,
457                                                     PLAY_WINDOW_IMAGE_SIZE,
458                                             TRUE, NULL, &error);
459     g_object_unref(G_OBJECT(stream));
460
461     if (!error) gtk_image_set_from_pixbuf(GTK_IMAGE(data->art_image), pixbuf);
462 }
463
464 void set_av_transport_uri(GUPnPServiceProxy *content_dir,
465                           GUPnPServiceProxyAction *action, gpointer user_data)
466 {
467     GError *error = NULL;
468     char *metadata;
469     gupnp_service_proxy_end_action(content_dir, action, &error,
470                                    "Result", G_TYPE_STRING, &metadata, NULL);
471     if (!metadata) return;
472     if (error) {
473         g_warning("Failed to get metadata for content");
474         g_error_free(error);
475     }
476
477     struct selection_data *data = (struct selection_data *)user_data;
478     struct proxy *renderer = current_renderer(data->set);
479
480     char *uri = find_compat_uri_from_metadata(metadata, &data->duration,
481                                               &data->art_uri, renderer);
482     if (!uri) {
483         g_warning("no compatible URI found.");
484         return;
485     }
486     
487     if (data->art_uri) {
488         SoupMessage *msg = soup_message_new("GET", data->art_uri);
489         soup_session_queue_message(data->set->app->soup, msg, set_image, data);
490     }
491
492     GUPnPServiceProxy *av_transport = GUPNP_SERVICE_PROXY(
493         gupnp_device_info_get_service(GUPNP_DEVICE_INFO(renderer->proxy),
494                                       AV_TRANSPORT));
495
496     gupnp_service_proxy_begin_action(av_transport, "SetAVTransportURI",
497                                          transport_uri, NULL,
498                                      "InstanceID", G_TYPE_UINT, 0,
499                                      "CurrentURI", G_TYPE_STRING, uri,
500                                      "CurrentURIMetaData", G_TYPE_STRING,
501                                          metadata,
502                                      NULL);
503 }
504
505 void av_transport_action_cb(GUPnPServiceProxy *av_transport,
506                             GUPnPServiceProxyAction *action, gpointer data)
507 {
508     const char *action_name = (const char *)data;
509     GError *error = NULL;
510
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);
514         g_error_free(error);
515     }
516 }
517
518 void g_value_free(gpointer data)
519 {
520     g_value_unset((GValue *)data);
521     g_slice_free(GValue, data);
522 }
523
524 GHashTable *create_av_transport_args_hash(char **additional_args)
525 {
526     GHashTable *args = g_hash_table_new_full(g_str_hash, g_str_equal,
527                                              NULL, g_value_free);
528
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);
532
533     g_hash_table_insert(args, "InstanceID", instance_id);
534
535     if (additional_args) {
536         int i;
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);
542         }
543     }
544     return args;
545 }
546
547 void av_transport_send_action(GUPnPServiceProxy *av_transport, char *action,
548                               char *additional_args[])
549 {
550     GHashTable *args = create_av_transport_args_hash(additional_args);
551
552     gupnp_service_proxy_begin_action_hash(av_transport, action,
553                                           av_transport_action_cb,
554                                           (char *)action,
555                                           args);
556     g_hash_table_unref(args);
557 }
558
559 void transport_selection(struct proxy *server, struct selection_data *data)
560 {
561     GtkTreeModel *model = gtk_tree_row_reference_get_model(data->row);
562     GtkTreePath *path = gtk_tree_row_reference_get_path(data->row);
563
564     GtkTreeIter iter;
565     gtk_tree_model_get_iter(model, &iter, path);
566
567     char *label;
568     char *id;
569     GUPnPServiceProxy *content;
570     gtk_tree_model_get(model, &iter, COL_LABEL, &label,
571                                      COL_CONTENT, &content, COL_ID, &id, -1);
572     
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,
577                                          "BrowseMetadata",
578                                      "Filter", G_TYPE_STRING, "*",
579                                      "StartingIndex", G_TYPE_UINT, 0,
580                                      "RequestedCount", G_TYPE_UINT, 0,
581                                      "SortCriteria", G_TYPE_STRING, "",
582                                      NULL);
583 }
584
585 void play_button(GtkWidget *button, gpointer data)
586 {
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),
591                                       AV_TRANSPORT));
592     char *args[] = {"Speed", "1", NULL};
593     av_transport_send_action(av_transport, "Play", args);
594 }
595
596 struct selection_data *selection_data_new(struct proxy *server,
597                                           GtkTreeModel *model,
598                                           GtkTreePath *path)
599 {
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;
604     return data;
605 }
606
607 void selection_data_free(struct selection_data *data)
608 {
609     gtk_tree_row_reference_free(data->row);
610     g_slice_free(struct selection_data, data);
611 }
612
613 void play_window_destroy(GtkWidget *window, gpointer user_data)
614 {
615     struct selection_data *data = (struct selection_data *)user_data;
616     selection_data_free(data);
617 }
618
619 GtkWidget *play_window_image(struct selection_data *data)
620 {
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,
625                                                0, &error);
626
627     data->art_image = gtk_image_new_from_pixbuf(icon);
628
629     return data->art_image;
630 }
631
632 void mp_callback(GtkWidget *event_box, GdkEventButton *event,
633                  gpointer user_data)
634 {
635     struct toggle_data *data = (struct toggle_data *)user_data;
636     
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]);
641     } else {
642         if (!data->toggle) gtk_image_set_from_pixbuf(GTK_IMAGE(data->image),
643                                                      data->pixbufs[0]);
644         if (data->callback) (data->callback)(event_box, data->user_data);
645     }
646 }
647
648 GtkWidget *mp_button(char *file, char *onpress_file, gboolean toggle,
649                      void (*callback)(GtkWidget *, gpointer),
650                      gpointer user_data)
651 {
652     char fname[256];
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;
657     
658     GError *error = NULL;
659     strcpy(fname, MP_ICON_PATH);
660     strcat(fname, file);
661     strcat(fname, ".png");
662     data->pixbufs[0] = gdk_pixbuf_new_from_file(fname, &error);
663
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);
668     
669     data->image = GTK_IMAGE(gtk_image_new_from_pixbuf(data->pixbufs[0]));
670
671     GtkWidget *event_box = gtk_event_box_new();
672     gtk_container_add(GTK_CONTAINER(event_box), GTK_WIDGET(data->image));
673     
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);
678     return event_box;
679 }
680
681 GtkWidget *play_window(struct proxy *server, struct selection_data *data)
682 {
683     GtkWidget *window = hildon_stackable_window_new();
684     gtk_window_set_title(GTK_WINDOW(window), PLAY_WINDOW_TITLE);
685     
686     GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
687     gtk_container_add(GTK_CONTAINER(window), vbox);
688     
689     GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
690     gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
691     
692     GtkWidget *image = play_window_image(data);
693     gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 20);
694     
695     GtkWidget *inner_box = gtk_vbox_new(FALSE, 0);
696     gtk_box_pack_start(GTK_BOX(hbox), inner_box, TRUE, TRUE, 6);
697
698     GtkWidget *button_box = gtk_hbutton_box_new();
699     gtk_box_pack_start(GTK_BOX(vbox), button_box, FALSE, FALSE, 6);
700     
701     GtkWidget *back = mp_button("Back", "BackPressed", FALSE, NULL, NULL);
702     gtk_box_pack_start(GTK_BOX(button_box), back, FALSE, FALSE, 0);
703     
704     GtkWidget *play = mp_button("Play", "Pause", TRUE, play_button,
705                                 data->set);
706     gtk_box_pack_start(GTK_BOX(button_box), play, FALSE, FALSE, 0);
707     
708     GtkWidget *forward = mp_button("Forward", "ForwardPressed", FALSE,
709                                    NULL, NULL);
710     gtk_box_pack_start(GTK_BOX(button_box), forward, FALSE, FALSE, 0);
711     
712     GtkWidget *shuffle = mp_button("Shuffle", "ShufflePressed", TRUE, NULL, NULL);
713     gtk_box_pack_start(GTK_BOX(button_box), shuffle, FALSE, FALSE, 0);
714     
715     GtkWidget *repeat = mp_button("Repeat", "RepeatPressed", TRUE, NULL, NULL);
716     gtk_box_pack_start(GTK_BOX(button_box), repeat, FALSE, FALSE, 0);
717     
718     g_signal_connect(window, "destroy", G_CALLBACK(play_window_destroy), data);
719     gtk_widget_show_all(GTK_WIDGET(vbox));
720     
721     return window;
722 }
723
724 void content_select(HildonTouchSelector *selector, gint column, gpointer data)
725 {
726     GtkTreePath *path;
727     path = hildon_touch_selector_get_last_activated_row(selector, column);
728     if (!path) return;
729
730     GtkTreeModel *model = hildon_touch_selector_get_model(selector, column);
731     GtkTreeIter iter;
732     gtk_tree_model_get_iter(model, &iter, path);
733
734     GUPnPServiceProxy *content;
735     char *id, *label;
736     gboolean container;
737     gtk_tree_model_get(model, &iter, COL_CONTENT, &content, COL_ID, &id,
738                                      COL_CONTAINER, &container,
739                                      COL_LABEL, &label, -1);
740
741     struct proxy *server = (struct proxy *)data;
742     GtkWidget *window;
743     
744     if (container) {
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,
750                                        NULL);
751         gupnp_service_proxy_set_subscribed(content, TRUE);
752     } else {
753         struct selection_data *data = selection_data_new(server, model, path);
754         transport_selection(server, data);
755         window = play_window(server, data);
756     }
757     
758     HildonWindowStack *stack = hildon_window_stack_get_default();
759     hildon_window_stack_push_1(stack, HILDON_STACKABLE_WINDOW(window));
760 }
761
762 GtkWidget *content_window(struct proxy *server, char *title,
763                           GtkListStore **view_list)
764 {
765     GtkWidget *window = hildon_stackable_window_new();
766     gtk_window_set_title(GTK_WINDOW(window), title);
767
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);
771
772     GtkWidget *selector = new_selector(list);
773
774     g_signal_connect(G_OBJECT(selector), "changed",
775                      G_CALLBACK(content_select), server);
776
777     gtk_container_add(GTK_CONTAINER(window), selector);
778
779     *view_list = list;
780     return window;
781 }
782
783 void server_select(HildonTouchSelector *selector, gint column, gpointer data)
784 {
785     GtkTreePath *path;
786     path = hildon_touch_selector_get_last_activated_row(selector, column);
787     if (!path) return;
788
789     GtkTreeModel *model = hildon_touch_selector_get_model(selector, column);
790     GtkTreeIter iter;
791     gtk_tree_model_get_iter(model, &iter, path);
792
793     char *udn;
794     gtk_tree_model_get(model, &iter, COL_ID, &udn, -1);
795
796     GHashTable *servers = (GHashTable *)data;
797     struct proxy *server = g_hash_table_lookup(servers, udn);
798
799     GUPnPServiceProxy *content_dir = GUPNP_SERVICE_PROXY(
800         gupnp_device_info_get_service(GUPNP_DEVICE_INFO(server->proxy),
801                                       CONTENT_DIR));
802
803     GtkListStore *view_list;
804     GtkWidget *window = content_window(server, server->name, &view_list);
805
806     browse(content_dir, "0", 0, MAX_BROWSE, view_list, server->set);
807
808     gupnp_service_proxy_add_notify(content_dir, "ContainerUpdateIDs",
809                                    G_TYPE_STRING, on_container_update_ids,
810                                    NULL);
811     gupnp_service_proxy_set_subscribed(content_dir, TRUE);
812
813     /* TODO: GList *child_devices = gupnp_device_info_list_devices(GUPNP_DEVICE_INFO(server->proxy)); */
814
815     HildonWindowStack *stack = hildon_window_stack_get_default();
816     hildon_window_stack_push_1(stack, HILDON_STACKABLE_WINDOW(window));
817 }
818
819 GtkWidget *target_selector(struct proxy_set *proxy_set)
820 {
821     GtkWidget *selector;
822     selector = hildon_touch_selector_new();
823
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);
829
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,
835                                    "text", 0, NULL);
836
837     gtk_widget_show_all(selector);
838     return selector;
839 }
840
841 GtkWidget *main_menu(struct proxy_set *proxy_set)
842 {
843     GtkWidget *menu = hildon_app_menu_new();
844     GtkWidget *button;
845     button = hildon_picker_button_new(HILDON_SIZE_AUTO,
846                                       HILDON_BUTTON_ARRANGEMENT_VERTICAL);
847     hildon_button_set_title(HILDON_BUTTON(button), CHOOSE_TARGET);
848     
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);
853     
854     hildon_app_menu_append(HILDON_APP_MENU(menu), GTK_BUTTON(button));
855
856     gtk_widget_show_all(menu);
857     return menu;
858 }
859
860 GtkWidget *main_window(HildonProgram *program, struct proxy_set *proxy_set)
861 {
862     GtkWidget *window = hildon_stackable_window_new();
863     hildon_program_add_window(program, HILDON_WINDOW(window));
864
865     gtk_window_set_title(GTK_WINDOW(window), APPLICATION_NAME);
866
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);
870
871     GtkWidget *selector = new_selector(proxy_set->server_list);
872
873     g_signal_connect(G_OBJECT(selector), "changed",
874                      G_CALLBACK(server_select), proxy_set->servers);
875
876     gtk_container_add(GTK_CONTAINER(window), selector);
877
878     GtkWidget *menu = main_menu(proxy_set);
879     hildon_window_set_app_menu(HILDON_WINDOW(window), HILDON_APP_MENU(menu));
880     
881     return window;
882 }
883
884 void protocol_info(GUPnPServiceProxy *cm, GUPnPServiceProxyAction *action,
885                    gpointer user_data)
886 {
887     gchar *sink_protocols;
888     GError *error = NULL;
889     const gchar *udn = gupnp_service_info_get_udn(GUPNP_SERVICE_INFO(cm));
890
891     if (!gupnp_service_proxy_end_action(cm, action, &error, "Sink",
892                                         G_TYPE_STRING, &sink_protocols,
893                                         NULL)) {
894         g_warning("Failed to get sink protocol info from "
895                        "media renderer '%s':%s\n",
896                    udn, error->message);
897         g_error_free(error);
898         return;
899     }
900
901     struct proxy *server = (struct proxy *)user_data;
902     server->protocols = g_strsplit(sink_protocols, ",", 0);
903 }
904
905 void add_renderer(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
906 {
907     const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
908     if (!udn) return;
909     
910     struct proxy *server = g_hash_table_lookup(proxy_set->renderers, udn);
911     if (server) return;
912     
913     char *name = gupnp_device_info_get_friendly_name(GUPNP_DEVICE_INFO(proxy));
914     if (!name) return;
915     
916     server = (struct proxy *)g_slice_alloc(sizeof(struct proxy));
917     server->proxy = proxy;
918     server->name = name;
919     server->set = proxy_set;
920
921     GtkTreeIter iter;
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);
925     
926     /* TODO: default to saved value */
927     hildon_picker_button_set_active(proxy_set->app->renderer_picker, 0);
928
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);
932
933     g_hash_table_replace(proxy_set->renderers, (char *)udn, server);
934
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,
939                                      server, NULL);
940 }
941
942 void add_server(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
943 {
944     const char *udn = gupnp_device_info_get_udn(GUPNP_DEVICE_INFO(proxy));
945     if (!udn) return;
946     
947     struct proxy *server = g_hash_table_lookup(proxy_set->servers, udn);
948     if (server) return;
949
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);
953     
954     if (!name || !content_dir) return;
955
956     server = (struct proxy *)g_slice_alloc(sizeof(struct proxy));
957     server->proxy = proxy;
958     server->name = name;
959     server->set = proxy_set;
960     
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,
965                                                0, &error);
966     GtkTreeIter iter;
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);
971
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);
975
976     g_hash_table_replace(proxy_set->servers, (char *)udn, server);
977 }
978
979 void remove_renderer(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
980 {
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);
983     if (!server) return;
984
985     GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->renderer_list);
986     GtkTreeIter iter;
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);
991     free(server);
992     
993     /* TODO: change current selection if necessary */
994 }
995
996 void remove_server(GUPnPDeviceProxy *proxy, struct proxy_set *proxy_set)
997 {
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;
1001
1002     GtkTreeModel *model = GTK_TREE_MODEL(proxy_set->server_list);
1003     GtkTreeIter iter;
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);
1008     free(server);
1009     
1010     /* TODO: bring user back to server menu if necessary */
1011 }
1012
1013 void device_proxy_available(GUPnPControlPoint *cp,
1014                             GUPnPDeviceProxy *proxy, gpointer user_data)
1015 {
1016     struct proxy_set *proxy_set = (struct proxy_set *)user_data;
1017     const char *type;
1018     type = gupnp_device_info_get_device_type(GUPNP_DEVICE_INFO(proxy));
1019
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);
1024     }
1025 }
1026
1027 void device_proxy_unavailable(GUPnPControlPoint *cp,
1028                               GUPnPDeviceProxy *proxy, gpointer user_data)
1029 {
1030     struct proxy_set *proxy_set = (struct proxy_set *)user_data;
1031     const char *type;
1032     type = gupnp_device_info_get_device_type(GUPNP_DEVICE_INFO(proxy));
1033
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);
1038     }
1039 }
1040
1041 void init_upnp(struct proxy_set *proxy_set)
1042 {
1043     GError *error = NULL;
1044
1045     g_type_init();
1046
1047     GUPnPContext *context = gupnp_context_new(NULL, NULL, 0, &error);
1048     if (error) {
1049         g_printerr("Error creating the GUPnP context: %s\n", error->message);
1050         g_error_free(error);
1051         exit(1);
1052     }
1053
1054     GUPnPControlPoint *cp = gupnp_control_point_new(context, "ssdp:all");
1055
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);
1060
1061     gssdp_resource_browser_set_active(GSSDP_RESOURCE_BROWSER(cp), TRUE);
1062 }
1063
1064 void init_application(struct application *app)
1065 {
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);
1069
1070     set->renderer_list = gtk_list_store_new(2, G_TYPE_STRING,
1071                                                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);
1075     set->app = app;
1076
1077     app->didl_parser = gupnp_didl_lite_parser_new();
1078     app->soup = soup_session_async_new();
1079 }
1080
1081 int main(int argc, char *argv[])
1082 {
1083     g_thread_init(NULL);
1084
1085     hildon_gtk_init(&argc, &argv);
1086
1087     HildonProgram *program = hildon_program_get_instance();
1088     g_set_application_name(APPLICATION_NAME);
1089
1090     struct application app;
1091     init_application(&app);
1092
1093     GtkWidget *window = main_window(program, &app.set);
1094     g_signal_connect(G_OBJECT(window), "destroy",
1095                      G_CALLBACK(gtk_main_quit), NULL);
1096     
1097     init_upnp(&app.set);
1098
1099     gtk_widget_show(window);
1100     gtk_main();
1101
1102     return 0;
1103 }