1 /* Eye Of Gnome - General Utilities
2  *
3  * Copyright (C) 2006 The Free Software Foundation
4  *
5  * Author: Lucas Rocha <lucasr@gnome.org>
6  *
7  * Based on code by:
8  *	- Jens Finke <jens@gnome.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <sys/time.h>
30 #ifdef HAVE_STRPTIME
31 #define _XOPEN_SOURCE
32 #endif /* HAVE_STRPTIME */
33 
34 #include <time.h>
35 
36 #include "eog-util.h"
37 #include "eog-debug.h"
38 
39 #include <errno.h>
40 #include <string.h>
41 #include <glib.h>
42 #include <glib/gprintf.h>
43 #include <gtk/gtk.h>
44 #include <gio/gio.h>
45 #include <glib/gi18n.h>
46 #ifdef HAVE_LIBPORTAL
47 #include <libportal/portal.h>
48 #include <libportal/portal-gtk3.h>
49 #endif
50 
51 void
eog_util_show_help(const gchar * section,GtkWindow * parent)52 eog_util_show_help (const gchar *section, GtkWindow *parent)
53 {
54 	GError *error = NULL;
55 	gchar *uri = NULL;
56 
57 	if (section)
58 		uri = g_strdup_printf ("help:eog/%s", section);
59 
60 	gtk_show_uri_on_window (parent, ((uri != NULL) ? uri : "help:eog"),
61                                 gtk_get_current_event_time (), &error);
62 
63 	g_free (uri);
64 
65 	if (error) {
66 		GtkWidget *dialog;
67 
68 		dialog = gtk_message_dialog_new (parent,
69 						 0,
70 						 GTK_MESSAGE_ERROR,
71 						 GTK_BUTTONS_OK,
72 						 _("Could not display help for Image Viewer"));
73 
74 		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
75 							  "%s", error->message);
76 
77 		g_signal_connect_swapped (dialog, "response",
78 					  G_CALLBACK (gtk_widget_destroy),
79 					  dialog);
80 		gtk_widget_show (dialog);
81 
82 		g_error_free (error);
83 	}
84 }
85 
86 gchar *
eog_util_make_valid_utf8(const gchar * str)87 eog_util_make_valid_utf8 (const gchar *str)
88 {
89 	GString *string;
90 	const char *remainder, *invalid;
91 	int remaining_bytes, valid_bytes;
92 
93 	string = NULL;
94 	remainder = str;
95 	remaining_bytes = strlen (str);
96 
97 	while (remaining_bytes != 0) {
98 		if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
99 			break;
100 		}
101 
102 		valid_bytes = invalid - remainder;
103 
104 		if (string == NULL) {
105 			string = g_string_sized_new (remaining_bytes);
106 		}
107 
108 		g_string_append_len (string, remainder, valid_bytes);
109 		g_string_append_c (string, '?');
110 
111 		remaining_bytes -= valid_bytes + 1;
112 		remainder = invalid + 1;
113 	}
114 
115 	if (string == NULL) {
116 		return g_strdup (str);
117 	}
118 
119 	g_string_append (string, remainder);
120 	g_string_append (string, _(" (invalid Unicode)"));
121 
122 	g_assert (g_utf8_validate (string->str, -1, NULL));
123 
124 	return g_string_free (string, FALSE);
125 }
126 
127 GSList*
eog_util_parse_uri_string_list_to_file_list(const gchar * uri_list)128 eog_util_parse_uri_string_list_to_file_list (const gchar *uri_list)
129 {
130 	GSList* file_list = NULL;
131 	gsize i = 0;
132 	gchar **uris;
133 
134 	uris = g_uri_list_extract_uris (uri_list);
135 
136 	while (uris[i] != NULL) {
137 		file_list = g_slist_append (file_list, g_file_new_for_uri (uris[i]));
138 		i++;
139 	}
140 
141 	g_strfreev (uris);
142 
143 	return file_list;
144 }
145 
146 GSList*
eog_util_string_list_to_file_list(GSList * string_list)147 eog_util_string_list_to_file_list (GSList *string_list)
148 {
149 	GSList *it = NULL;
150 	GSList *file_list = NULL;
151 
152 	for (it = string_list; it != NULL; it = it->next) {
153 		char *uri_str;
154 
155 		uri_str = (gchar *) it->data;
156 
157 		file_list = g_slist_prepend (file_list,
158 					     g_file_new_for_uri (uri_str));
159 	}
160 
161 	return g_slist_reverse (file_list);
162 }
163 
164 GSList*
eog_util_strings_to_file_list(gchar ** strings)165 eog_util_strings_to_file_list (gchar **strings)
166 {
167 	int i;
168  	GSList *file_list = NULL;
169 
170 	for (i = 0; strings[i]; i++) {
171  		file_list = g_slist_prepend (file_list,
172 					      g_file_new_for_uri (strings[i]));
173  	}
174 
175  	return g_slist_reverse (file_list);
176 }
177 
178 GSList*
eog_util_string_array_to_list(const gchar ** files,gboolean create_uri)179 eog_util_string_array_to_list (const gchar **files, gboolean create_uri)
180 {
181 	gint i;
182 	GSList *list = NULL;
183 
184 	if (files == NULL) return list;
185 
186 	for (i = 0; files[i]; i++) {
187 		char *str;
188 
189 		if (create_uri) {
190 			GFile *file;
191 
192 			file = g_file_new_for_commandline_arg (files[i]);
193 			str = g_file_get_uri (file);
194 
195 			g_object_unref (file);
196 		} else {
197 			str = g_strdup (files[i]);
198 		}
199 
200 		if (str) {
201 			list = g_slist_prepend (list, g_strdup (str));
202 			g_free (str);
203 		}
204 	}
205 
206 	return g_slist_reverse (list);
207 }
208 
209 gchar **
eog_util_string_array_make_absolute(gchar ** files)210 eog_util_string_array_make_absolute (gchar **files)
211 {
212 	int i;
213 	int size;
214 	gchar **abs_files;
215 	GFile *file;
216 
217 	if (files == NULL)
218 		return NULL;
219 
220 	size = g_strv_length (files);
221 
222 	/* Ensure new list is NULL-terminated */
223 	abs_files = g_new0 (gchar *, size+1);
224 
225 	for (i = 0; i < size; i++) {
226 		file = g_file_new_for_commandline_arg (files[i]);
227 		abs_files[i] = g_file_get_uri (file);
228 
229 		g_object_unref (file);
230 	}
231 
232 	return abs_files;
233 }
234 
235 static gchar *dot_dir = NULL;
236 static void migrate_config_folder (const gchar* new_folder);
237 
238 static gboolean
ensure_dir_exists(const char * dir)239 ensure_dir_exists (const char *dir)
240 {
241 	if (g_file_test (dir, G_FILE_TEST_IS_DIR))
242 		return TRUE;
243 
244 	if (g_mkdir_with_parents (dir, 0700) == 0) {
245 		/* If the folder is created try migrating from the old folder */
246 		migrate_config_folder (dir);
247 		return TRUE;
248 	}
249 
250 	if (errno == EEXIST)
251 		return g_file_test (dir, G_FILE_TEST_IS_DIR);
252 
253 	g_warning ("Failed to create directory %s: %s", dir, strerror (errno));
254 	return FALSE;
255 }
256 
257 const gchar *
eog_util_dot_dir(void)258 eog_util_dot_dir (void)
259 {
260 	if (dot_dir == NULL) {
261 		gboolean exists;
262 
263 		dot_dir = g_build_filename (g_get_user_config_dir (),
264 					    "eog", NULL);
265 
266 		exists = ensure_dir_exists (dot_dir);
267 		if (G_UNLIKELY (!exists)) {
268 			static gboolean printed_warning = FALSE;
269 
270 			if (!printed_warning) {
271 				g_warning ("EOG could not save some of your preferences in its settings directory due to a file with the same name (%s) blocking its creation. Please remove that file, or move it away.", dot_dir);
272 				printed_warning = TRUE;
273 			}
274 			g_free (dot_dir);
275 			dot_dir = NULL;
276 			return NULL;
277 		}
278 	}
279 
280 	return dot_dir;
281 }
282 
migrate_config_file(const gchar * old_filename,const gchar * new_filename)283 static void migrate_config_file (const gchar *old_filename, const gchar* new_filename)
284 {
285 	GFile *old_file, *new_file;
286 	GError *error = NULL;
287 
288 	if (!g_file_test (old_filename, G_FILE_TEST_IS_REGULAR))
289 		return;
290 
291 	old_file = g_file_new_for_path (old_filename);
292 	new_file = g_file_new_for_path (new_filename);
293 
294 	if (!g_file_move (old_file, new_file, G_FILE_COPY_NONE, NULL, NULL, NULL, &error)) {
295 		g_warning ("Could not migrate config file %s: %s\n", old_filename, error->message);
296 		g_error_free (error);
297 	}
298 	g_object_unref (new_file);
299 	g_object_unref (old_file);
300 }
301 
migrate_config_folder(const gchar * new_dir)302 static void migrate_config_folder (const gchar* new_dir)
303 {
304 	gchar* old_dir = g_build_filename (g_get_home_dir (), ".gnome2",
305 					   "eog", NULL);
306 	gchar* old_filename = NULL;
307 	gchar* new_filename = NULL;
308 	GError *error = NULL;
309 	GFile *dir_file = NULL;
310 	gsize i;
311 	static const gchar *old_files[] = { "eog-print-settings.ini",
312 					    NULL };
313 
314 	if(!g_file_test (old_dir, G_FILE_TEST_IS_DIR)) {
315 		/* Nothing to migrate */
316 		g_free (old_dir);
317 		return;
318 	}
319 
320 	eog_debug (DEBUG_PREFERENCES);
321 
322 	for (i = 0; old_files[i] != NULL; i++) {
323 		old_filename = g_build_filename (old_dir,
324 						old_files[i], NULL);
325 		new_filename = g_build_filename (new_dir,
326 						old_files[i], NULL);
327 
328 		migrate_config_file (old_filename, new_filename);
329 
330 		g_free (new_filename);
331 		g_free (old_filename);
332 	}
333 
334 	/* Migrate accels file */
335 	old_filename = g_build_filename (g_get_home_dir (), ".gnome2",
336 					 "accels", "eog", NULL);
337 	/* move file to ~/.config/eog/accels if its not already there */
338 	new_filename = g_build_filename (new_dir, "accels", NULL);
339 
340 	migrate_config_file (old_filename, new_filename);
341 
342 	g_free (new_filename);
343 	g_free (old_filename);
344 
345 	dir_file = g_file_new_for_path (old_dir);
346 	if (!g_file_delete (dir_file, NULL, &error)) {
347 		g_warning ("An error occurred while deleting the old config folder %s: %s\n", old_dir, error->message);
348 		g_error_free (error);
349 	}
350 	g_object_unref (dir_file);
351 	g_free(old_dir);
352 }
353 
354 /* Based on eel_filename_strip_extension() */
355 
356 /**
357  * eog_util_filename_get_extension:
358  * @filename: a filename
359  *
360  * Returns a reasonably good guess of the file extension of @filename.
361  *
362  * Returns: a newly allocated string with the file extension of @filename.
363  **/
364 char *
eog_util_filename_get_extension(const char * filename)365 eog_util_filename_get_extension (const char * filename)
366 {
367 	char *begin, *begin2;
368 
369 	if (filename == NULL) {
370 		return NULL;
371 	}
372 
373 	begin = strrchr (filename, '.');
374 
375 	if (begin && begin != filename) {
376 		if (strcmp (begin, ".gz") == 0 ||
377 		    strcmp (begin, ".bz2") == 0 ||
378 		    strcmp (begin, ".sit") == 0 ||
379 		    strcmp (begin, ".Z") == 0) {
380 			begin2 = begin - 1;
381 			while (begin2 > filename &&
382 			       *begin2 != '.') {
383 				begin2--;
384 			}
385 			if (begin2 != filename) {
386 				begin = begin2;
387 			}
388 		}
389 		begin ++;
390 	} else {
391 		return NULL;
392 	}
393 
394 	return g_strdup (begin);
395 }
396 
397 
398 /**
399  * eog_util_file_is_persistent:
400  * @file: a #GFile
401  *
402  * Checks whether @file is a non-removable local mount.
403  *
404  * Returns: %TRUE if @file is in a non-removable mount,
405  * %FALSE otherwise or when it is remote.
406  **/
407 gboolean
eog_util_file_is_persistent(GFile * file)408 eog_util_file_is_persistent (GFile *file)
409 {
410 	GMount *mount;
411 
412 	if (!g_file_is_native (file))
413 		return FALSE;
414 
415 	mount = g_file_find_enclosing_mount (file, NULL, NULL);
416 	if (mount) {
417 		if (g_mount_can_unmount (mount)) {
418 			return FALSE;
419 		}
420 	}
421 
422 	return TRUE;
423 }
424 
425 static void
_eog_util_show_file_in_filemanager_fallback(GFile * file,GtkWindow * toplevel)426 _eog_util_show_file_in_filemanager_fallback (GFile *file, GtkWindow *toplevel)
427 {
428 	gchar *uri = NULL;
429 	GError *error = NULL;
430 	guint32 timestamp = gtk_get_current_event_time ();
431 
432 	if (g_file_query_file_type (file, 0, NULL) == G_FILE_TYPE_DIRECTORY) {
433 		uri = g_file_get_uri (file);
434 	} else {
435 		/* If input file is not a directory we must open it's
436 		   folder/parent to avoid opening the file itself     */
437 		GFile *parent_file;
438 
439 		parent_file = g_file_get_parent (file);
440 		if (G_LIKELY (parent_file))
441 			uri = g_file_get_uri (parent_file);
442 		g_object_unref (parent_file);
443 	}
444 
445 	if (uri && !gtk_show_uri_on_window (toplevel, uri, timestamp, &error)) {
446 		g_warning ("Couldn't show containing folder \"%s\": %s", uri,
447 			   error->message);
448 		g_error_free (error);
449 	}
450 
451 	g_free (uri);
452 }
453 
454 void
eog_util_show_file_in_filemanager(GFile * file,GtkWindow * toplevel)455 eog_util_show_file_in_filemanager (GFile *file, GtkWindow *toplevel)
456 {
457 	GDBusProxy *proxy;
458 	gboolean done = FALSE;
459 
460 	g_return_if_fail (file != NULL);
461 
462 	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
463 				G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
464 				G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
465 				NULL, "org.freedesktop.FileManager1",
466 				"/org/freedesktop/FileManager1",
467 				"org.freedesktop.FileManager1",
468 				NULL, NULL);
469 
470 	if (proxy) {
471 		gchar *uri = g_file_get_uri (file);
472 		gchar *startup_id;
473 		GVariant *params, *result;
474 		GVariantBuilder builder;
475 
476 		g_variant_builder_init (&builder,
477 					G_VARIANT_TYPE ("as"));
478 		g_variant_builder_add (&builder, "s", uri);
479 
480 		/* This seems to be the expected format, as other values
481 		   cause the filemanager window not to get focus. */
482 		startup_id = g_strdup_printf("_TIME%u",
483 					     gtk_get_current_event_time());
484 
485 		/* params is floating! */
486 		params = g_variant_new ("(ass)", &builder, startup_id);
487 
488 		g_free (startup_id);
489 		g_variant_builder_clear (&builder);
490 
491 		/* Floating params-GVariant is consumed here */
492 		result = g_dbus_proxy_call_sync (proxy, "ShowItems",
493 						 params, G_DBUS_CALL_FLAGS_NONE,
494 						 -1, NULL, NULL);
495 
496 		/* Receiving a non-NULL result counts as a successful call. */
497 		if (G_LIKELY (result != NULL)) {
498 			done = TRUE;
499 			g_variant_unref (result);
500 		}
501 
502 		g_free (uri);
503 		g_object_unref (proxy);
504 	}
505 
506 	/* Fallback to gtk_show_uri() if launch over DBus is not possible */
507 	if (!done)
508 		_eog_util_show_file_in_filemanager_fallback (file, toplevel);
509 }
510 
511 /* Portal */
512 
513 #ifdef HAVE_LIBPORTAL
514 gboolean
eog_util_is_running_inside_flatpak(void)515 eog_util_is_running_inside_flatpak (void)
516 {
517 	return g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
518 }
519 
520 static void
open_with_flatpak_portal_cb(GObject * source,GAsyncResult * result,gpointer user_data)521 open_with_flatpak_portal_cb (GObject *source, GAsyncResult *result, gpointer user_data)
522 {
523 	XdpPortal *portal = XDP_PORTAL (source);
524 	g_autoptr(GError) error = NULL;
525 
526 	if (!xdp_portal_open_uri_finish (portal, result, &error)
527 	    && !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
528 	{
529 		g_warning ("Failed to open file via portal: %s", error->message);
530 	}
531 }
532 
533 void
eog_util_open_file_with_flatpak_portal(GFile * file,GtkWindow * window)534 eog_util_open_file_with_flatpak_portal (GFile *file, GtkWindow *window)
535 {
536 	g_autoptr(XdpPortal) portal = NULL;
537 	g_autofree gchar *uri = NULL;
538 	XdpParent *parent = NULL;
539 
540 	portal = xdp_portal_new ();
541 	parent = xdp_parent_new_gtk (window);
542 	uri = g_file_get_uri (file);
543 
544 	xdp_portal_open_uri (portal,
545 			     parent,
546 			     uri,
547 			     XDP_OPEN_URI_FLAG_ASK,
548 			     NULL,
549 			     open_with_flatpak_portal_cb,
550 			     NULL);
551 	xdp_parent_free (parent);
552 }
553 
554 static void
set_wallpaper_with_portal_cb(GObject * source,GAsyncResult * result,gpointer user_data)555 set_wallpaper_with_portal_cb (GObject *source, GAsyncResult *result, gpointer user_data)
556 {
557         XdpPortal *portal = XDP_PORTAL (source);
558         g_autoptr(GError) error = NULL;
559 
560         if (!xdp_portal_set_wallpaper_finish (portal, result, &error)
561             && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
562         {
563                 g_warning ("Failed to set wallpaper via portal: %s", error->message);
564         }
565 }
566 
567 void
eog_util_set_wallpaper_with_portal(GFile * file,GtkWindow * window)568 eog_util_set_wallpaper_with_portal (GFile *file, GtkWindow *window)
569 {
570         g_autoptr(XdpPortal) portal = NULL;
571         g_autofree gchar *uri = NULL;
572         XdpParent *parent = NULL;
573 
574         portal = xdp_portal_new ();
575         parent = xdp_parent_new_gtk (window);
576         uri = g_file_get_uri (file);
577 
578         xdp_portal_set_wallpaper (portal,
579                                   parent,
580                                   uri,
581                                   XDP_WALLPAPER_FLAG_BACKGROUND | XDP_WALLPAPER_FLAG_PREVIEW,
582                                   NULL,
583                                   set_wallpaper_with_portal_cb,
584                                   NULL);
585         xdp_parent_free (parent);
586 }
587 #endif
588