/* nautilus-program-choosing.c - functions for selecting and activating * programs for opening/viewing particular files. * * Copyright (C) 2000 Eazel, Inc. * * The Gnome Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * The Gnome Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with the Gnome Library; see the file COPYING.LIB. If not, * see . * * Author: John Sullivan */ #include #include "nautilus-program-choosing.h" #include "nautilus-global-preferences.h" #include "nautilus-icon-info.h" #include "nautilus-recent.h" #include #include #include #include #include #include #include #include void nautilus_launch_application_for_mount (GAppInfo *app_info, GMount *mount, GtkWindow *parent_window) { GFile *root; NautilusFile *file; GList *files; root = g_mount_get_root (mount); file = nautilus_file_get (root); g_object_unref (root); files = g_list_append (NULL, file); nautilus_launch_application (app_info, files, parent_window); g_list_free_full (files, (GDestroyNotify) nautilus_file_unref); } /** * nautilus_launch_application: * * Fork off a process to launch an application with a given file as a * parameter. Provide a parent window for error dialogs. * * @application: The application to be launched. * @uris: The files whose locations should be passed as a parameter to the application. * @parent_window: A window to use as the parent for any error dialogs. */ void nautilus_launch_application (GAppInfo *application, GList *files, GtkWindow *parent_window) { GList *uris, *l; uris = NULL; for (l = files; l != NULL; l = l->next) { uris = g_list_prepend (uris, nautilus_file_get_activation_uri (l->data)); } uris = g_list_reverse (uris); nautilus_launch_application_by_uri (application, uris, parent_window); g_list_free_full (uris, g_free); } static GdkAppLaunchContext * get_launch_context (GtkWindow *parent_window) { GdkDisplay *display; GdkAppLaunchContext *launch_context; if (parent_window != NULL) { display = gtk_widget_get_display (GTK_WIDGET (parent_window)); } else { display = gdk_display_get_default (); } launch_context = gdk_display_get_app_launch_context (display); if (parent_window != NULL) { gdk_app_launch_context_set_screen (launch_context, gtk_window_get_screen (parent_window)); } return launch_context; } void nautilus_launch_application_by_uri (GAppInfo *application, GList *uris, GtkWindow *parent_window) { char *uri; GList *locations, *l; GFile *location; NautilusFile *file; gboolean result; GError *error; g_autoptr (GdkAppLaunchContext) launch_context = NULL; NautilusIconInfo *icon; int count, total; g_assert (uris != NULL); /* count the number of uris with local paths */ count = 0; total = g_list_length (uris); locations = NULL; for (l = uris; l != NULL; l = l->next) { uri = l->data; location = g_file_new_for_uri (uri); if (g_file_is_native (location)) { count++; } locations = g_list_prepend (locations, location); } locations = g_list_reverse (locations); launch_context = get_launch_context (parent_window); file = nautilus_file_get_by_uri (uris->data); icon = nautilus_file_get_icon (file, 48, gtk_widget_get_scale_factor (GTK_WIDGET (parent_window)), 0); nautilus_file_unref (file); if (icon) { gdk_app_launch_context_set_icon_name (launch_context, nautilus_icon_info_get_used_name (icon)); g_object_unref (icon); } error = NULL; if (count == total) { /* All files are local, so we can use g_app_info_launch () with * the file list we constructed before. */ result = g_app_info_launch (application, locations, G_APP_LAUNCH_CONTEXT (launch_context), &error); } else { /* Some files are non local, better use g_app_info_launch_uris (). */ result = g_app_info_launch_uris (application, uris, G_APP_LAUNCH_CONTEXT (launch_context), &error); } if (result) { for (l = uris; l != NULL; l = l->next) { file = nautilus_file_get_by_uri (l->data); nautilus_recent_add_file (file, application); nautilus_file_unref (file); } } g_list_free_full (locations, g_object_unref); } static void launch_application_from_command_internal (const gchar *full_command, GdkScreen *screen, gboolean use_terminal) { GAppInfoCreateFlags flags; g_autoptr (GError) error = NULL; g_autoptr (GAppInfo) app = NULL; flags = G_APP_INFO_CREATE_NONE; if (use_terminal) { flags = G_APP_INFO_CREATE_NEEDS_TERMINAL; } app = g_app_info_create_from_commandline (full_command, NULL, flags, &error); if (app != NULL && !(use_terminal && screen == NULL)) { GdkDisplay *display; g_autoptr (GdkAppLaunchContext) context = NULL; display = gdk_screen_get_display (screen); context = gdk_display_get_app_launch_context (display); gdk_app_launch_context_set_screen (context, screen); g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (context), &error); } if (error != NULL) { g_message ("Could not start application: %s", error->message); } } /** * nautilus_launch_application_from_command: * * Fork off a process to launch an application with a given uri as * a parameter. * * @command_string: The application to be launched, with any desired * command-line options. * @...: Passed as parameters to the application after quoting each of them. */ void nautilus_launch_application_from_command (GdkScreen *screen, const char *command_string, gboolean use_terminal, ...) { char *full_command, *tmp; char *quoted_parameter; char *parameter; va_list ap; full_command = g_strdup (command_string); va_start (ap, use_terminal); while ((parameter = va_arg (ap, char *)) != NULL) { quoted_parameter = g_shell_quote (parameter); tmp = g_strconcat (full_command, " ", quoted_parameter, NULL); g_free (quoted_parameter); g_free (full_command); full_command = tmp; } va_end (ap); launch_application_from_command_internal (full_command, screen, use_terminal); g_free (full_command); } /** * nautilus_launch_application_from_command: * * Fork off a process to launch an application with a given uri as * a parameter. * * @command_string: The application to be launched, with any desired * command-line options. * @parameters: Passed as parameters to the application after quoting each of them. */ void nautilus_launch_application_from_command_array (GdkScreen *screen, const char *command_string, gboolean use_terminal, const char * const *parameters) { char *full_command, *tmp; char *quoted_parameter; const char * const *p; full_command = g_strdup (command_string); if (parameters != NULL) { for (p = parameters; *p != NULL; p++) { quoted_parameter = g_shell_quote (*p); tmp = g_strconcat (full_command, " ", quoted_parameter, NULL); g_free (quoted_parameter); g_free (full_command); full_command = tmp; } } launch_application_from_command_internal (full_command, screen, use_terminal); g_free (full_command); } void nautilus_launch_desktop_file (GdkScreen *screen, const char *desktop_file_uri, const GList *parameter_uris, GtkWindow *parent_window) { GError *error; char *message, *desktop_file_path; const GList *p; GList *files; int total, count; GFile *file, *desktop_file; GDesktopAppInfo *app_info; GdkAppLaunchContext *context; /* Don't allow command execution from remote locations * to partially mitigate the security * risk of executing arbitrary commands. */ desktop_file = g_file_new_for_uri (desktop_file_uri); desktop_file_path = g_file_get_path (desktop_file); if (!g_file_is_native (desktop_file)) { g_free (desktop_file_path); g_object_unref (desktop_file); eel_show_error_dialog (_("Sorry, but you cannot execute commands from " "a remote site."), _("This is disabled due to security considerations."), parent_window); return; } g_object_unref (desktop_file); app_info = g_desktop_app_info_new_from_filename (desktop_file_path); g_free (desktop_file_path); if (app_info == NULL) { eel_show_error_dialog (_("There was an error launching the application."), NULL, parent_window); return; } /* count the number of uris with local paths */ count = 0; total = g_list_length ((GList *) parameter_uris); files = NULL; for (p = parameter_uris; p != NULL; p = p->next) { file = g_file_new_for_uri ((const char *) p->data); if (g_file_is_native (file)) { count++; } files = g_list_prepend (files, file); } /* check if this app only supports local files */ if (g_app_info_supports_files (G_APP_INFO (app_info)) && !g_app_info_supports_uris (G_APP_INFO (app_info)) && parameter_uris != NULL) { if (count == 0) { /* all files are non-local */ eel_show_error_dialog (_("This drop target only supports local files."), _("To open non-local files copy them to a local folder and then" " drop them again."), parent_window); g_list_free_full (files, g_object_unref); g_object_unref (app_info); return; } else if (count != total) { /* some files are non-local */ eel_show_warning_dialog (_("This drop target only supports local files."), _("To open non-local files copy them to a local folder and then" " drop them again. The local files you dropped have already been opened."), parent_window); } } error = NULL; context = gdk_display_get_app_launch_context (gtk_widget_get_display (GTK_WIDGET (parent_window))); /* TODO: Ideally we should accept a timestamp here instead of using GDK_CURRENT_TIME */ gdk_app_launch_context_set_timestamp (context, GDK_CURRENT_TIME); gdk_app_launch_context_set_screen (context, gtk_window_get_screen (parent_window)); if (count == total) { /* All files are local, so we can use g_app_info_launch () with * the file list we constructed before. */ g_app_info_launch (G_APP_INFO (app_info), files, G_APP_LAUNCH_CONTEXT (context), &error); } else { /* Some files are non local, better use g_app_info_launch_uris (). */ g_app_info_launch_uris (G_APP_INFO (app_info), (GList *) parameter_uris, G_APP_LAUNCH_CONTEXT (context), &error); } if (error != NULL) { message = g_strconcat (_("Details: "), error->message, NULL); eel_show_error_dialog (_("There was an error launching the application."), message, parent_window); g_error_free (error); g_free (message); } g_list_free_full (files, g_object_unref); g_object_unref (context); g_object_unref (app_info); } /* HAX * * TODO: remove everything below once it’s doable from GTK+. * * Context: https://bugzilla.gnome.org/show_bug.cgi?id=781132 and * https://bugzilla.gnome.org/show_bug.cgi?id=779312 * * In a sandboxed environment, this is needed to able to get the actual * result of the operation, since gtk_show_uri_on_window () neither blocks * nor returns a useful value. */ #ifdef GDK_WINDOWING_WAYLAND #include #endif typedef void (*GtkWindowHandleExported) (GtkWindow *window, const char *handle, gpointer user_data); #ifdef GDK_WINDOWING_WAYLAND typedef struct { GtkWindow *window; GtkWindowHandleExported callback; gpointer user_data; } WaylandWindowHandleExportedData; static void wayland_window_handle_exported (GdkWindow *window, const char *wayland_handle_str, gpointer user_data) { WaylandWindowHandleExportedData *data = user_data; char *handle_str; handle_str = g_strdup_printf ("wayland:%s", wayland_handle_str); data->callback (data->window, handle_str, data->user_data); g_free (handle_str); } #endif static gboolean window_export_handle (GtkWindow *window, GtkWindowHandleExported callback, gpointer user_data) { #ifdef GDK_WINDOWING_X11 if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window)))) { GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); char *handle_str; guint32 xid = (guint32) gdk_x11_window_get_xid (gdk_window); handle_str = g_strdup_printf ("x11:%x", xid); callback (window, handle_str, user_data); return TRUE; } #endif #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window)))) { GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); WaylandWindowHandleExportedData *data; data = g_new0 (WaylandWindowHandleExportedData, 1); data->window = window; data->callback = callback; data->user_data = user_data; if (!gdk_wayland_window_export_handle (gdk_window, wayland_window_handle_exported, data, g_free)) { g_free (data); return FALSE; } else { return TRUE; } } #endif g_warning ("Couldn't export handle, unsupported windowing system"); return FALSE; } static void gtk_window_unexport_handle (GtkWindow *window) { #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window)))) { GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); gdk_wayland_window_unexport_handle (gdk_window); } #endif } static void on_launch_default_for_uri (GObject *source, GAsyncResult *result, gpointer data) { GTask *task; GtkWindow *window; gboolean success; GError *error = NULL; task = data; window = g_task_get_source_object (task); success = g_app_info_launch_default_for_uri_finish (result, &error); if (window) { gtk_window_unexport_handle (window); } if (success) { g_task_return_boolean (task, success); } else { g_task_return_error (task, error); } /* Reffed in the call to window_export_handle */ g_object_unref (task); } static void on_window_handle_export (GtkWindow *window, const char *handle_str, gpointer user_data) { GTask *task = user_data; GAppLaunchContext *context = g_task_get_task_data (task); const char *uri; uri = g_object_get_data (G_OBJECT (context), "uri"); g_app_launch_context_setenv (context, "PARENT_WINDOW_ID", handle_str); g_app_info_launch_default_for_uri_async (uri, context, g_task_get_cancellable (task), on_launch_default_for_uri, task); } static void launch_default_for_uri_thread_func (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { GAppLaunchContext *launch_context; const char *uri; gboolean success; GError *error = NULL; launch_context = task_data; uri = g_object_get_data (G_OBJECT (launch_context), "uri"); success = g_app_info_launch_default_for_uri (uri, launch_context, &error); if (success) { g_task_return_boolean (task, success); } else { g_task_return_error (task, error); } } void nautilus_launch_default_for_uri_async (const char *uri, GtkWindow *parent_window, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { g_autoptr (GdkAppLaunchContext) launch_context = NULL; g_autoptr (GTask) task = NULL; g_return_if_fail (uri != NULL); launch_context = get_launch_context (parent_window); task = g_task_new (parent_window, cancellable, callback, callback_data); gdk_app_launch_context_set_timestamp (launch_context, GDK_CURRENT_TIME); g_object_set_data_full (G_OBJECT (launch_context), "uri", g_strdup (uri), g_free); g_task_set_task_data (task, g_object_ref (launch_context), g_object_unref); if (parent_window != NULL) { gboolean handle_exported; handle_exported = window_export_handle (parent_window, on_window_handle_export, g_object_ref (task)); if (handle_exported) { /* Launching will now be handled from the callback */ return; } } g_task_run_in_thread (task, launch_default_for_uri_thread_func); } gboolean nautilus_launch_default_for_uri_finish (GAsyncResult *result, GError **error) { g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } /* END OF HAX */