/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/* caja-program-choosing.c - functions for selecting and activating
 				 programs for opening/viewing particular files.

   Copyright (C) 2000 Eazel, Inc.

   The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
   Boston, MA 02110-1301, USA.

   Author: John Sullivan <sullivan@eazel.com>
*/

#include <config.h>
#include "caja-program-choosing.h"

#include "caja-mime-actions.h"
#include "caja-global-preferences.h"
#include "caja-icon-info.h"
#include "caja-recent.h"
#include "caja-desktop-icon-file.h"
#include <eel/eel-mate-extensions.h>
#include <eel/eel-stock-dialogs.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gio/gdesktopappinfo.h>
#include <stdlib.h>

#include <gdk/gdk.h>
#include <gdk/gdkx.h>

/**
 * application_cannot_open_location
 *
 * Handle the case where an application has been selected to be launched,
 * and it cannot handle the current uri scheme.  This can happen
 * because the default application for a file type may not be able
 * to handle some kinds of locations.   We want to tell users that their
 * default application doesn't work here, rather than switching off to
 * a different one without them noticing.
 *
 * @application: The application that was to be launched.
 * @file: The file whose location was passed as a parameter to the application
 * @parent_window: A window to use as the parent for any error dialogs.
 *  */
static void
application_cannot_open_location (GAppInfo *application,
                                  CajaFile *file,
                                  const char *uri_scheme,
                                  GtkWindow *parent_window)
{
#ifdef NEW_MIME_COMPLETE
    GtkDialog *message_dialog;
    LaunchParameters *launch_parameters;
    char *prompt;
    char *message;
    char *file_name;
    int response;

    file_name = caja_file_get_display_name (file);

    if (caja_mime_has_any_applications_for_file (file))
    {
        if (application != NULL)
        {
            prompt = _("Open Failed, would you like to choose another application?");
            message = g_strdup_printf (_("\"%s\" cannot open \"%s\" because \"%s\" cannot access files at \"%s\" "
                                         "locations."),
                                       g_app_info_get_display_name (application), file_name,
                                       g_app_info_get_display_name (application), uri_scheme);
        }
        else
        {
            prompt = _("Open Failed, would you like to choose another action?");
            message = g_strdup_printf (_("The default action cannot open \"%s\" because it cannot access files at \"%s\" "
                                         "locations."),
                                       file_name, uri_scheme);
        }

        message_dialog = eel_show_yes_no_dialog (prompt,
                         message,
                         GTK_STOCK_OK,
                         GTK_STOCK_CANCEL,
                         parent_window);
        response = gtk_dialog_run (message_dialog);
        gtk_widget_destroy (GTK_WIDGET (message_dialog));

        if (response == GTK_RESPONSE_YES)
        {
            launch_parameters = launch_parameters_new (file, parent_window);
            caja_choose_application_for_file
            (file,
             parent_window,
             launch_application_callback,
             launch_parameters);

        }
        g_free (message);
    }
    else
    {
        if (application != NULL)
        {
            prompt = g_strdup_printf (_("\"%s\" cannot open \"%s\" because \"%s\" cannot access files at \"%s\" "
                                        "locations."), g_app_info_get_display_name (application), file_name,
                                      g_app_info_get_display_name (application), uri_scheme);
            message = _("No other applications are available to view this file. "
                        "If you copy this file onto your computer, you may be able to open "
                        "it.");
        }
        else
        {
            prompt = g_strdup_printf (_("The default action cannot open \"%s\" because it cannot access files at \"%s\" "
                                        "locations."), file_name, uri_scheme);
            message = _("No other actions are available to view this file. "
                        "If you copy this file onto your computer, you may be able to open "
                        "it.");
        }

        eel_show_info_dialog (prompt, message, parent_window);
        g_free (prompt);
    }

    g_free (file_name);
#endif
}

/**
 * caja_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
caja_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, caja_file_get_activation_uri (l->data));
    }
    uris = g_list_reverse (uris);
    caja_launch_application_by_uri (application, uris,
                                    parent_window);
    g_list_free_full (uris, g_free);
}

/*
 * Set the DISPLAY variable, to be use by g_spawn_async.
 */
static void
set_environment (gpointer display)
{
    g_setenv("DISPLAY", display, TRUE);
}

static void
dummy_child_watch (GPid     pid,
                   gint     status,
                   gpointer user_data)
{
  /* Nothing, this is just to ensure we don't double fork
   * and break pkexec:
   * https://bugzilla.gnome.org/show_bug.cgi?id=675789
   */
}

static void
gather_pid_callback (GDesktopAppInfo *appinfo,
                     GPid            pid,
                     gpointer        data)
{
    g_child_watch_add(pid, dummy_child_watch, NULL);
}

void
caja_launch_application_by_uri (GAppInfo *application,
                                GList *uris,
                                GtkWindow *parent_window)
{
    char            *uri, *uri_scheme;
    GList           *locations, *l;
    GFile *location;
    CajaFile    *file;
    gboolean        result;
    GError *error;
#if GTK_CHECK_VERSION (3, 0, 0)
    GdkDisplay *display;
#endif
    GdkAppLaunchContext *launch_context;
    CajaIconInfo *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);

#if GTK_CHECK_VERSION (3, 0, 0)
    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));
    }
#else
    launch_context = gdk_app_launch_context_new ();
    if (parent_window)
        gdk_app_launch_context_set_screen (launch_context,
                                           gtk_window_get_screen (parent_window));
#endif

    file = caja_file_get_by_uri (uris->data);
    icon = caja_file_get_icon (file, 48, 0);
    caja_file_unref (file);
    if (icon)
    {
        gdk_app_launch_context_set_icon_name (launch_context,
                                              caja_icon_info_get_used_name (icon));
        g_object_unref (icon);
    }

    error = NULL;

    result = g_desktop_app_info_launch_uris_as_manager (G_DESKTOP_APP_INFO (application),
                                                        uris,
                                                        G_APP_LAUNCH_CONTEXT (launch_context),
                                                        G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
                                                        NULL, NULL,
                                                        gather_pid_callback, application,
                                                        &error);

    g_object_unref (launch_context);

    if (!result)
    {
        if (error->domain == G_IO_ERROR &&
                error->code == G_IO_ERROR_NOT_SUPPORTED)
        {
            uri_scheme = g_uri_parse_scheme (uris->data);
            application_cannot_open_location (application,
                                              file,
                                              uri_scheme,
                                              parent_window);
            g_free (uri_scheme);
        }
        else
        {
#ifdef NEW_MIME_COMPLETE
            caja_program_chooser_show_invalid_message
            (MATE_VFS_MIME_ACTION_TYPE_APPLICATION, file, parent_window);
#else
            g_warning ("Cannot open app: %s\n", error->message);
#endif
        }
        g_error_free (error);
    }
    else
    {
        for (l = uris; l != NULL; l = l->next)
        {
            file = caja_file_get_by_uri (l->data);
            caja_recent_add_file (file, application);
            caja_file_unref (file);
        }
    }

    g_list_free_full (locations, g_object_unref);
}

/**
 * caja_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
caja_launch_application_from_command (GdkScreen  *screen,
                                      const char *name,
                                      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);

    if (use_terminal)
    {
        eel_mate_open_terminal_on_screen (full_command, screen);
    }
    else
    {
#if GTK_CHECK_VERSION (3, 0, 0)
        GdkAppLaunchContext *launch_context;
        GdkDisplay *display;
        GAppInfo *app_info = NULL;
        app_info = g_app_info_create_from_commandline (full_command,
                                                       NULL,
                                                       G_APP_INFO_CREATE_NONE,
                                                       NULL);
        if (app_info != NULL)
        {
            display = gdk_screen_get_display (screen);
            launch_context = gdk_display_get_app_launch_context (display);
            gdk_app_launch_context_set_screen (launch_context, screen);
            g_app_info_launch (app_info, NULL, G_APP_LAUNCH_CONTEXT (launch_context), NULL);
            g_object_unref (launch_context);
            g_object_unref (app_info);
        }
#else
        GError *error = NULL;
        gchar **argv = NULL;
        char* display;
        GPid  pid;

        if (!g_shell_parse_argv (full_command, NULL, &argv, &error)) {
            g_error_free (error);
            g_free (full_command);
            return;
        }

        display = gdk_screen_make_display_name (screen);

        g_spawn_async (
            NULL, /* working directory */
            argv,
            NULL, /* envp */
            G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
            set_environment,
            display,
            &pid,
            &error);

        if (error != NULL) {
            g_error_free (error);
        } else {
            g_child_watch_add(pid, dummy_child_watch, NULL);
        }
 
        g_free(display);
#endif
    }

    g_free (full_command);
}

/**
 * caja_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
caja_launch_application_from_command_array (GdkScreen  *screen,
        const char *name,
        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;
        }
    }

    if (use_terminal)
    {
        eel_mate_open_terminal_on_screen (full_command, screen);
    }
    else
    {
#if GTK_CHECK_VERSION (3, 0, 0)
        GdkAppLaunchContext *launch_context;
        GdkDisplay *display;
        GAppInfo *app_info = NULL;
        app_info = g_app_info_create_from_commandline (full_command,
                                                       NULL,
                                                       G_APP_INFO_CREATE_NONE,
                                                       NULL);
        if (app_info != NULL)
        {
            display = gdk_screen_get_display (screen);
            launch_context = gdk_display_get_app_launch_context (display);
            gdk_app_launch_context_set_screen (launch_context, screen);
            g_app_info_launch (app_info, NULL, G_APP_LAUNCH_CONTEXT (launch_context), NULL);
            g_object_unref (launch_context);
            g_object_unref (app_info);
        }
#else
        GError *error = NULL;
        gchar **argv = NULL;
        char* display;
        GPid  pid;

        if (!g_shell_parse_argv (full_command, NULL, &argv, &error)) {
            g_error_free (error);
            g_free (full_command);
            return;
        }

        display = gdk_screen_make_display_name (screen);

        g_spawn_async (
            NULL, /* working directory */
            argv,
            NULL, /* envp */
            G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
            set_environment,
            display,
            &pid,
            &error);

        if (error != NULL) {
            g_error_free (error);
        } else {
            g_child_watch_add(pid, dummy_child_watch, NULL);
        }
 
        g_free(display);
#endif
    }

    g_free (full_command);
}

void
caja_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;
#if GTK_CHECK_VERSION (3, 0, 0)
    context = gdk_display_get_app_launch_context (gtk_widget_get_display (GTK_WIDGET (parent_window)));
#else
    context = gdk_app_launch_context_new ();
#endif
    /* 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));
    g_desktop_app_info_launch_uris_as_manager (app_info,
                                               (GList *) parameter_uris,
                                               G_APP_LAUNCH_CONTEXT (context),
                                               G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
                                               NULL, NULL,
                                               gather_pid_callback, app_info,
                                               &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);
}