1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /*
3 * Copyright (c) 2017 Ali Abdallah <ali@xfce.org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <math.h>
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <glib-object.h>
30
31 #include <gdk-pixbuf/gdk-pixbuf.h>
32
33 #include <gio/gio.h>
34 #include <glib/gstdio.h>
35
36 #include <tumbler/tumbler.h>
37
38 #include <desktop-thumbnailer/desktop-thumbnailer.h>
39
40
41
42 static void desktop_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer,
43 GCancellable *cancellable,
44 TumblerFileInfo *info);
45
46 static void desktop_thumbnailer_get_property(GObject *object,
47 guint prop_id,
48 GValue *value,
49 GParamSpec *pspec);
50
51 static void desktop_thumbnailer_set_property(GObject *object,
52 guint prop_id,
53 const GValue *value,
54 GParamSpec *pspec);
55
56 static void desktop_thumbnailer_finalize(GObject *object);
57
58
59
60 struct _DesktopThumbnailerClass
61 {
62 TumblerAbstractThumbnailerClass __parent__;
63 };
64
65 struct _DesktopThumbnailer
66 {
67 TumblerAbstractThumbnailer __parent__;
68
69 gchar *exec;
70 };
71
72
73
74 G_DEFINE_DYNAMIC_TYPE (DesktopThumbnailer,
75 desktop_thumbnailer,
76 TUMBLER_TYPE_ABSTRACT_THUMBNAILER);
77
78
79 enum
80 {
81 PROP_0,
82 PROP_EXEC
83 };
84
85
86
87 void
desktop_thumbnailer_register(TumblerProviderPlugin * plugin)88 desktop_thumbnailer_register (TumblerProviderPlugin *plugin)
89 {
90 desktop_thumbnailer_register_type (G_TYPE_MODULE (plugin));
91 }
92
93
94
desktop_thumbnailer_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)95 static void desktop_thumbnailer_get_property(GObject *object,
96 guint prop_id,
97 GValue *value,
98 GParamSpec *pspec)
99 {
100 DesktopThumbnailer *thumbnailer = DESKTOP_THUMBNAILER(object);
101
102 switch(prop_id)
103 {
104 case PROP_EXEC:
105 g_value_set_string(value, thumbnailer->exec);
106 break;
107
108 default:
109 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
110 break;
111 }
112 }
113
114
115
desktop_thumbnailer_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)116 static void desktop_thumbnailer_set_property(GObject *object,
117 guint prop_id,
118 const GValue *value,
119 GParamSpec *pspec)
120 {
121 DesktopThumbnailer *thumbnailer = DESKTOP_THUMBNAILER(object);
122
123 switch(prop_id)
124 {
125 case PROP_EXEC:
126 thumbnailer->exec = g_strdup(g_value_get_string(value));
127 break;
128
129 default:
130 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
131 break;
132 }
133 }
134
135
136
137 static void
desktop_thumbnailer_class_init(DesktopThumbnailerClass * klass)138 desktop_thumbnailer_class_init (DesktopThumbnailerClass *klass)
139 {
140 TumblerAbstractThumbnailerClass *abstractthumbnailer_class;
141 GObjectClass *gobject_class;
142
143 gobject_class = G_OBJECT_CLASS(klass);
144
145 gobject_class->get_property = desktop_thumbnailer_get_property;
146 gobject_class->set_property = desktop_thumbnailer_set_property;
147
148 g_object_class_install_property (gobject_class,
149 PROP_EXEC,
150 g_param_spec_string("exec",
151 NULL,
152 NULL,
153 NULL,
154 G_PARAM_CONSTRUCT_ONLY |
155 G_PARAM_READWRITE));
156
157 gobject_class->finalize = desktop_thumbnailer_finalize;
158 abstractthumbnailer_class = TUMBLER_ABSTRACT_THUMBNAILER_CLASS (klass);
159 abstractthumbnailer_class->create = desktop_thumbnailer_create;
160 }
161
162
163
164 static void
desktop_thumbnailer_class_finalize(DesktopThumbnailerClass * klass)165 desktop_thumbnailer_class_finalize (DesktopThumbnailerClass *klass)
166 {
167 }
168
169
170 static void
desktop_thumbnailer_finalize(GObject * object)171 desktop_thumbnailer_finalize(GObject *object)
172 {
173
174 }
175
176 static void
desktop_thumbnailer_init(DesktopThumbnailer * thumbnailer)177 desktop_thumbnailer_init (DesktopThumbnailer *thumbnailer)
178 {
179 }
180
181
182 static void
te_string_append_quoted(GString * string,const gchar * unquoted)183 te_string_append_quoted (GString *string,
184 const gchar *unquoted)
185 {
186 gchar *quoted;
187
188 quoted = g_shell_quote (unquoted);
189 g_string_append (string, quoted);
190 g_free (quoted);
191 }
192
193
194
195 static GdkPixbuf *
desktop_thumbnailer_get_pixbuf(GInputStream * stream,int dest_width,int dest_height,GCancellable * cancellable)196 desktop_thumbnailer_get_pixbuf (GInputStream *stream,
197 int dest_width,
198 int dest_height,
199 GCancellable *cancellable)
200 {
201 GdkPixbuf *source;
202 gdouble hratio;
203 gdouble wratio;
204 gint source_width;
205 gint source_height;
206 GError *error = NULL;
207
208 source = gdk_pixbuf_new_from_stream (stream, cancellable, &error);
209
210 if (!source)
211 {
212 g_clear_error (&error);
213 return NULL;
214 }
215
216 /* determine the source pixbuf dimensions */
217 source_width = gdk_pixbuf_get_width (source);
218 source_height = gdk_pixbuf_get_height (source);
219
220
221 /* return the same pixbuf if no scaling is required */
222 if (source_width <= dest_width && source_height <= dest_height)
223 return source;
224
225 /* determine which axis needs to be scaled down more */
226 wratio = (gdouble) source_width / (gdouble) dest_width;
227 hratio = (gdouble) source_height / (gdouble) dest_height;
228
229 /* adjust the other axis */
230 if (hratio > wratio)
231 dest_width = rint (source_width / hratio);
232 else
233 dest_height = rint (source_height / wratio);
234
235 /* scale the pixbuf down to the desired size */
236 return gdk_pixbuf_scale_simple (source,
237 MAX (dest_width, 1), MAX (dest_height, 1),
238 GDK_INTERP_BILINEAR);
239
240 }
241
242
243
244 static gboolean
desktop_thumbnailer_exec_parse(const gchar * exec,const gchar * file_path,const gchar * file_uri,gint desired_size,const gchar * output_file,gint * argc,gchar *** argv,GError ** error)245 desktop_thumbnailer_exec_parse (const gchar *exec,
246 const gchar *file_path,
247 const gchar *file_uri,
248 gint desired_size,
249 const gchar *output_file,
250 gint *argc,
251 gchar ***argv,
252 GError **error)
253 {
254 const gchar *p;
255 gboolean result = FALSE;
256 GString *command_line = g_string_new (NULL);
257
258 for (p = exec; *p != '\0'; ++p)
259 {
260 if (p[0] == '%' && p[1] != '\0')
261 {
262 switch (*++p)
263 {
264 case 'u':
265 if (G_LIKELY (file_uri != NULL))
266 te_string_append_quoted (command_line, file_uri);
267 break;
268
269 case 'i':
270 if (G_LIKELY (file_path != NULL))
271 te_string_append_quoted (command_line, file_path);
272 break;
273
274 case 's':
275 g_string_append_printf(command_line, "%d", desired_size);
276 break;
277
278 case 'o':
279 if (G_LIKELY (output_file != NULL))
280 te_string_append_quoted (command_line, output_file);
281 break;
282
283 case '%':
284 g_string_append_c (command_line, '%');
285 break;
286 }
287 }
288 else
289 {
290 g_string_append_c (command_line, *p);
291 }
292 }
293
294 result = g_shell_parse_argv (command_line->str, argc, argv, error);
295
296 g_string_free (command_line, TRUE);
297 return result;
298 }
299
300
301
302 static GdkPixbuf *
desktop_thumbnailer_load_thumbnail(DesktopThumbnailer * thumbnailer,const gchar * uri,const gchar * path,gint width,gint height,GCancellable * cancellable)303 desktop_thumbnailer_load_thumbnail (DesktopThumbnailer *thumbnailer,
304 const gchar *uri,
305 const gchar *path,
306 gint width,
307 gint height,
308 GCancellable *cancellable)
309 {
310 GFileIOStream *stream;
311 GFile *tmpfile;
312 gchar *exec;
313 gchar **cmd_argv;
314 gchar *tmpfilepath;
315 gboolean res;
316 gint cmd_argc;
317 gint size;
318 gchar *working_directory = NULL;
319 GdkPixbuf *pixbuf = NULL;
320 GError *error = NULL;
321
322 g_object_get (G_OBJECT (thumbnailer), "exec", &exec, NULL);
323
324 tmpfile = g_file_new_tmp ("tumbler-XXXXXXX.png", &stream, NULL);
325
326 if ( G_LIKELY (tmpfile) )
327 {
328 tmpfilepath = g_file_get_path (tmpfile);
329
330 size = MIN (width, height);
331
332 res = desktop_thumbnailer_exec_parse (exec,
333 path,
334 uri,
335 size,
336 tmpfilepath,
337 &cmd_argc,
338 &cmd_argv,
339 &error);
340
341 if (G_LIKELY (res))
342 {
343 working_directory = g_path_get_dirname (path);
344
345
346 res = g_spawn_sync (working_directory,
347 cmd_argv, NULL,
348 G_SPAWN_SEARCH_PATH,
349 NULL, NULL,
350 NULL, NULL,
351 NULL,
352 &error);
353
354 if (G_LIKELY (res))
355 {
356 pixbuf = desktop_thumbnailer_get_pixbuf (g_io_stream_get_input_stream(G_IO_STREAM(stream)),
357 width,
358 height,
359 cancellable);
360 g_unlink (tmpfilepath);
361 }
362
363 g_free (working_directory);
364 g_strfreev (cmd_argv);
365 }
366 else
367 {
368 g_warning (_("Malformed command line \"%s\": %s"), exec, error->message);
369 g_clear_error (&error);
370 }
371 g_free (tmpfilepath);
372 g_object_unref (tmpfile);
373 g_object_unref (stream);
374 }
375
376 g_free (exec);
377
378 return pixbuf;
379 }
380
381
382
383 static void
desktop_thumbnailer_create(TumblerAbstractThumbnailer * thumbnailer,GCancellable * cancellable,TumblerFileInfo * info)384 desktop_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer,
385 GCancellable *cancellable,
386 TumblerFileInfo *info)
387 {
388
389 TumblerThumbnail *thumbnail;
390 TumblerThumbnailFlavor *flavor;
391 TumblerImageData data;
392 const gchar *uri;
393 gchar *path;
394 GFile *file;
395 gint height;
396 gint width;
397 GError *error = NULL;
398 GdkPixbuf *pixbuf = NULL;
399
400 g_return_if_fail (IS_DESKTOP_THUMBNAILER (thumbnailer));
401 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
402 g_return_if_fail (TUMBLER_IS_FILE_INFO (info));
403
404 /* do nothing if cancelled */
405 if (g_cancellable_is_cancelled (cancellable))
406 return;
407
408 uri = tumbler_file_info_get_uri (info);
409
410 file = g_file_new_for_uri (uri);
411 path = g_file_get_path (file);
412
413 thumbnail = tumbler_file_info_get_thumbnail (info);
414 g_assert (thumbnail != NULL);
415
416 flavor = tumbler_thumbnail_get_flavor (thumbnail);
417 g_assert (flavor != NULL);
418
419 tumbler_thumbnail_flavor_get_size (flavor, &width, &height);
420
421 pixbuf = desktop_thumbnailer_load_thumbnail (DESKTOP_THUMBNAILER(thumbnailer), uri, path, width, height, cancellable);
422
423 if (pixbuf != NULL)
424 {
425 data.data = gdk_pixbuf_get_pixels (pixbuf);
426 data.has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
427 data.bits_per_sample = gdk_pixbuf_get_bits_per_sample (pixbuf);
428 data.width = gdk_pixbuf_get_width (pixbuf);
429 data.height = gdk_pixbuf_get_height (pixbuf);
430 data.rowstride = gdk_pixbuf_get_rowstride (pixbuf);
431 data.colorspace = (TumblerColorspace) gdk_pixbuf_get_colorspace (pixbuf);
432
433 tumbler_thumbnail_save_image_data (thumbnail, &data,
434 tumbler_file_info_get_mtime (info),
435 NULL, &error);
436
437 g_object_unref (pixbuf);
438 }
439
440 if (error != NULL)
441 {
442 g_signal_emit_by_name (thumbnailer, "error", uri, error->code, error->message);
443 g_error_free (error);
444 }
445 else
446 {
447 g_signal_emit_by_name (thumbnailer, "ready", uri);
448 }
449
450 g_free (path);
451
452 g_object_unref (thumbnail);
453 g_object_unref (flavor);
454 g_object_unref (file);
455 }
456