/* 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 */