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