1 /*
2  * Copyright (C) 2007 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
3  *
4  * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <string.h>
26 #include <stdlib.h>
27 #include <libsoup/soup.h>
28 
29 #include "icons.h"
30 
31 #define PREFERED_DEPTH  32
32 #define PREFERED_WIDTH  22
33 #define PREFERED_HEIGHT 22
34 
35 GdkPixbuf *icons[ICON_LAST];
36 
37 /* For async downloads of icons */
38 static SoupSession *download_session;
39 static GList       *pending_gets;
40 
41 typedef struct {
42         GUPnPDeviceInfo *info;
43 
44         DeviceIconAvailableCallback callback;
45 
46         SoupMessage     *message;
47         gchar           *mime_type;
48         gint             width;
49         gint             height;
50 } GetIconURLData;
51 
52 static void
get_icon_url_data_free(GetIconURLData * data)53 get_icon_url_data_free (GetIconURLData *data)
54 {
55         g_object_unref (data->info);
56 
57         g_free (data->mime_type);
58         g_slice_free (GetIconURLData, data);
59 }
60 
61 static GdkPixbuf *
get_icon_from_message(SoupMessage * msg,GetIconURLData * data,GError ** error)62 get_icon_from_message (SoupMessage    *msg,
63                        GetIconURLData *data,
64                        GError        **error)
65 {
66         GdkPixbufLoader *loader;
67         GdkPixbuf       *pixbuf;
68 
69         loader = gdk_pixbuf_loader_new_with_mime_type (data->mime_type, error);
70         if (loader == NULL)
71                 return NULL;
72 
73         gdk_pixbuf_loader_write (loader,
74                                  (guchar *) msg->response_body->data,
75                                  msg->response_body->length,
76                                  error);
77         pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
78         if (pixbuf) {
79                 gfloat aspect_ratio;
80                 gint   height;
81 
82                 /* Preserve the aspect-ratio of the original image */
83                 aspect_ratio = (gfloat) data->width / data->height;
84                 height = (gint) (PREFERED_WIDTH / aspect_ratio);
85                 pixbuf = gdk_pixbuf_scale_simple (pixbuf,
86                                                   PREFERED_WIDTH,
87                                                   height,
88                                                   GDK_INTERP_HYPER);
89         }
90 
91         gdk_pixbuf_loader_close (loader, NULL);
92         g_object_unref (loader);
93 
94         return pixbuf;
95 }
96 
97 /**
98  * Icon downloaded.
99  **/
100 static void
got_icon_url(SoupSession * session,SoupMessage * msg,GetIconURLData * data)101 got_icon_url (SoupSession    *session,
102               SoupMessage    *msg,
103               GetIconURLData *data)
104 {
105         GdkPixbuf *pixbuf = NULL;
106 
107         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
108                 GError *error = NULL;
109 
110                 pixbuf = get_icon_from_message (msg, data, &error);
111 
112                 if (error) {
113                         g_warning ("Failed to create icon for '%s': %s",
114                                    gupnp_device_info_get_udn (data->info),
115                                    error->message);
116                         g_error_free (error);
117                 } else if (!pixbuf) {
118                         g_warning ("Failed to create icon for '%s'",
119                                    gupnp_device_info_get_udn (data->info));
120                 }
121         }
122 
123         data->callback (data->info, pixbuf);
124 
125         pending_gets = g_list_remove (pending_gets, data);
126         get_icon_url_data_free (data);
127 }
128 
129 static gboolean
on_icon_schedule_error(gpointer user_data)130 on_icon_schedule_error (gpointer user_data)
131 {
132         GetIconURLData *data = (GetIconURLData *) user_data;
133 
134         data->callback (data->info, NULL);
135         g_object_unref (data->info);
136         g_slice_free (GetIconURLData, data);
137 
138         return FALSE;
139 }
140 
141 void
schedule_icon_update(GUPnPDeviceInfo * info,DeviceIconAvailableCallback callback)142 schedule_icon_update (GUPnPDeviceInfo            *info,
143                       DeviceIconAvailableCallback callback)
144 {
145         GetIconURLData *data;
146         char           *icon_url;
147 
148         data = g_slice_new0 (GetIconURLData);
149         data->info = g_object_ref (info);
150         data->callback = callback;
151 
152         icon_url = gupnp_device_info_get_icon_url
153                         (info,
154                          NULL,
155                          PREFERED_DEPTH,
156                          PREFERED_WIDTH,
157                          PREFERED_HEIGHT,
158                          TRUE,
159                          &data->mime_type,
160                          NULL,
161                          &data->width,
162                          &data->height);
163         if (icon_url == NULL) {
164                 g_free (data->mime_type);
165 
166                 g_idle_add (on_icon_schedule_error, data);
167                 return;
168         }
169 
170         char *new_uri = gupnp_context_rewrite_uri (gupnp_device_info_get_context (info), icon_url);
171         g_free (icon_url);
172 
173         data->message = soup_message_new (SOUP_METHOD_GET, new_uri);
174 
175         if (data->message == NULL) {
176                 g_warning ("Invalid URL icon for device '%s': %s",
177                            gupnp_device_info_get_udn (info),
178                            new_uri);
179 
180                 g_free (new_uri);
181                 g_free (data->mime_type);
182                 g_idle_add (on_icon_schedule_error, data);
183 
184                 return;
185         }
186 
187         pending_gets = g_list_prepend (pending_gets, data);
188         soup_session_queue_message (download_session,
189                                     data->message,
190                                     (SoupSessionCallback) got_icon_url,
191                                     data);
192 
193         g_free (new_uri);
194 }
195 
196 void
unschedule_icon_update(GUPnPDeviceInfo * info)197 unschedule_icon_update (GUPnPDeviceInfo *info)
198 {
199         GList *gets;
200 
201         for (gets = pending_gets; gets; gets = gets->next) {
202                 GetIconURLData *data;
203                 const char *udn1;
204                 const char *udn2;
205 
206                 data = gets->data;
207                 udn1 = gupnp_device_info_get_udn (info);
208                 udn2 = gupnp_device_info_get_udn (data->info);
209 
210                 if (udn1 && udn2 && strcmp (udn1, udn2) == 0) {
211                         soup_session_cancel_message (download_session,
212                                                      data->message,
213                                                      SOUP_STATUS_CANCELLED);
214                         break;
215                 }
216         }
217 }
218 
219 GdkPixbuf *
get_icon_by_id(IconID icon_id)220 get_icon_by_id (IconID icon_id)
221 {
222         g_return_val_if_fail (icon_id > ICON_FIRST && icon_id < ICON_LAST, NULL);
223 
224         return icons[icon_id];
225 }
226 
227 static GdkPixbuf *
get_pixbuf_from_theme(const char * icon_name)228 get_pixbuf_from_theme (const char *icon_name)
229 {
230         GdkScreen    *screen;
231         GtkIconTheme *theme;
232         GdkPixbuf    *pixbuf;
233         GError       *error;
234 
235         screen = gdk_screen_get_default ();
236         theme = gtk_icon_theme_get_for_screen (screen);
237 
238         error = NULL;
239         pixbuf = gtk_icon_theme_load_icon (theme,
240                                            icon_name,
241                                            PREFERED_WIDTH,
242                                            PREFERED_HEIGHT,
243                                            &error);
244         if (pixbuf == NULL) {
245                 g_warning ("Failed to load icon %s: %s",
246                            icon_name,
247                            error->message);
248                 g_error_free (error);
249 
250                 error = NULL;
251                 pixbuf = gtk_icon_theme_load_icon (theme,
252                                                    "image-missing",
253                                                    PREFERED_WIDTH,
254                                                    PREFERED_HEIGHT,
255                                                    &error);
256                 if (pixbuf == NULL) {
257                     g_critical ("Failed to load fallback icon: %s",
258                                 error->message);
259                     g_error_free (error);
260                 }
261         }
262 
263         return pixbuf;
264 }
265 
266 GdkPixbuf *
load_pixbuf_file(const char * file_name)267 load_pixbuf_file (const char *file_name)
268 {
269         GdkPixbuf *pixbuf;
270         char *path;
271 
272         path = g_build_path ("/", "/org/gupnp/Tools/Common", file_name, NULL);
273         pixbuf = gdk_pixbuf_new_from_resource (path, NULL);
274         if (pixbuf == NULL)
275                 g_critical ("failed to get image %s\n", path);
276 
277         g_free (path);
278 
279         return pixbuf;
280 }
281 
282 extern void gupnp_tools_common_unregister_resource (void);
283 extern void gupnp_tools_common_register_resource (void);
284 
285 void
init_icons(void)286 init_icons (void)
287 {
288         int   i, j;
289         const char *file_names[] = {
290                 "pixmaps/upnp-device.png",          /* ICON_DEVICE         */
291                 "pixmaps/upnp-service.png",         /* ICON_SERVICE        */
292                 "pixmaps/upnp-state-variable.png",  /* ICON_VARIABLE       */
293                 "pixmaps/upnp-action-arg-in.png",   /* ICON_ACTION_ARG_IN  */
294                 "pixmaps/upnp-action-arg-out.png",  /* ICON_ACTION_ARG_OUT */
295                 "pixmaps/media-renderer.png"        /* ICON_MEDIA_RENDERER */
296         };
297 
298         const char *theme_names[] = {
299                 "image-missing",               /* ICON_MISSING    */
300                 "network-workgroup",           /* ICON_NETWORK    */
301                 "system-run",                  /* ICON_ACTION     */
302                 "folder",                      /* ICON_VARIABLES  */
303                 "text-x-generic",              /* ICON_FILE       */
304                 "folder-remote",               /* ICON_CONTAINER  */
305                 "audio-x-generic",             /* ICON_AUDIO_ITEM */
306                 "video-x-generic",             /* ICON_VIDEO_ITEM */
307                 "image-x-generic",             /* ICON_IMAGE_ITEM */
308                 "text-x-generic",              /* ICON_TEXT_ITEM */
309         };
310         gupnp_tools_common_register_resource ();
311 
312         for (i = 0; i < ICON_MISSING; i++) {
313                 icons[i] = load_pixbuf_file (file_names[i]);
314         }
315 
316         for (j = 0; i < ICON_LAST; i++, j++) {
317                 icons[i] = get_pixbuf_from_theme (theme_names[j]);
318         }
319 
320 
321         download_session = soup_session_new ();
322         g_assert (download_session != NULL);
323 
324         pending_gets = NULL;
325 }
326 
327 void
deinit_icons(void)328 deinit_icons (void)
329 {
330         int i;
331 
332         while (pending_gets) {
333                 GetIconURLData *data;
334 
335                 data = pending_gets->data;
336 
337                 soup_session_cancel_message (download_session,
338                                              data->message,
339                                              SOUP_STATUS_CANCELLED);
340         }
341 
342         g_object_unref (download_session);
343 
344         for (i = 0; i < ICON_LAST; i++) {
345                 g_object_unref (icons[i]);
346         }
347         gupnp_tools_common_unregister_resource ();
348 }
349 
350