/*
* nautilus-previewer: nautilus previewer DBus wrapper
*
* Copyright (C) 2011, Red Hat, Inc.
*
* Nautilus is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* Nautilus 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*
* Author: Cosimo Cecchi
*
*/
#define G_LOG_DOMAIN "nautilus-previewer"
#include "config.h"
#include "nautilus-previewer.h"
#include "nautilus-file.h"
#include "nautilus-files-view.h"
#include "nautilus-window.h"
#include "nautilus-window-slot.h"
#include
#ifdef GDK_WINDOWING_WAYLAND
#include
#endif
#ifdef GDK_WINDOWING_X11
#include
#endif
#define PREVIEWER2_DBUS_IFACE "org.gnome.NautilusPreviewer2"
#define PREVIEWER_DBUS_NAME "org.gnome.NautilusPreviewer"
#define PREVIEWER_DBUS_PATH "/org/gnome/NautilusPreviewer"
static const char *previewer_dbus_name = PREVIEWER_DBUS_NAME PROFILE;
static const char *previewer_dbus_path = PREVIEWER_DBUS_PATH PROFILE;
static gboolean tried_alternative_previewer_dbus_name = FALSE;
static gboolean previewer_ready = FALSE;
static gboolean fetching_bus = FALSE;
static GDBusProxy *previewer_proxy = NULL;
enum
{
DBUS_SUBSCRIPTION_NAVIGATE_TO,
DBUS_SUBSCRIPTION_RENAME,
DBUS_SUBSCRIPTION_SELECTION,
DBUS_SUBSCRIPTION_TRASH,
NUM_DBUS_SUBSCRIPTIONS
};
static guint dbus_subscriptions[NUM_DBUS_SUBSCRIPTIONS];
static GCancellable *cancellable = NULL;
static NautilusWindowSlot *current_slot = NULL; /* weak ref */
static GtkRoot *current_window = NULL; /* weak ref */
static gchar *exported_window_handle = NULL;
static void real_call_show_file (const gchar *uri,
const gchar *window_handle,
gboolean close_if_already_visible,
const char *activation_token);
static void previewer_call_close (void);
static void create_new_bus (void);
#ifdef GDK_WINDOWING_WAYLAND
typedef struct
{
gchar *uri;
gboolean close_if_already_visible;
char *activation_token;
} PreviewExportData;
static void
preview_export_data_free (gpointer _data)
{
PreviewExportData *data = _data;
g_free (data->uri);
g_free (data->activation_token);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (PreviewExportData, preview_export_data_free)
static void
wayland_window_handle_exported (GdkToplevel *toplevel,
const char *wayland_handle_str,
gpointer user_data)
{
PreviewExportData *data = user_data;
g_autofree char *wayland_handle = g_strdup_printf ("wayland:%s", wayland_handle_str);
real_call_show_file (data->uri, wayland_handle, data->close_if_already_visible, data->activation_token);
}
#endif
static void
clear_exported_window_handle (void)
{
if (exported_window_handle == NULL)
{
return;
}
#ifdef GDK_WINDOWING_WAYLAND
if (current_window != NULL &&
GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (current_window))))
{
GdkSurface *gdk_surface = gtk_native_get_surface (GTK_NATIVE (current_window));
if (GDK_IS_WAYLAND_TOPLEVEL (gdk_surface) &&
g_str_has_prefix (exported_window_handle, "wayland:"))
{
gdk_wayland_toplevel_drop_exported_handle (GDK_WAYLAND_TOPLEVEL (gdk_surface),
exported_window_handle + strlen ("wayland:"));
}
}
#endif
g_free (exported_window_handle);
exported_window_handle = NULL;
}
static gboolean
is_uri_selected (NautilusFilesView *files_view,
const char *uri)
{
g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (files_view);
if (g_list_length (selection) != 1)
{
return FALSE;
}
g_autoptr (NautilusFile) file = nautilus_file_get_by_uri (uri);
return file == selection->data;
}
typedef void (*PreviewerEventCallback) (NautilusFilesView *files_view,
GVariant *parameters);
static void
previewer_navigate_to_event (NautilusFilesView *files_view,
GVariant *parameters)
{
const char *uri;
g_variant_get (parameters, "(s)", &uri);
g_autoptr (NautilusFile) file = nautilus_file_get_by_uri (uri);
if (!nautilus_file_is_directory (file) || !is_uri_selected (files_view, uri))
{
g_warning ("Ignoring previewer NavigateTo request for uri %s", uri);
return;
}
nautilus_files_view_activate_file (files_view, file, NAUTILUS_OPEN_FLAG_NORMAL);
}
static void
previewer_rename_event (NautilusFilesView *files_view,
GVariant *parameters)
{
const char *uri;
g_variant_get (parameters, "(s)", &uri);
if (!is_uri_selected (files_view, uri))
{
g_warning ("Ignoring previewer Rename request for uri %s", uri);
return;
}
gtk_widget_activate_action (GTK_WIDGET (files_view), "view.rename", NULL);
}
static void
previewer_selection_event (NautilusFilesView *files_view,
GVariant *parameters)
{
GtkDirectionType direction;
g_variant_get (parameters, "(u)", &direction);
nautilus_files_view_preview_selection_event (files_view, direction);
}
static void
previewer_trash_event (NautilusFilesView *files_view,
GVariant *parameters)
{
const char *uri;
g_variant_get (parameters, "(s)", &uri);
if (!is_uri_selected (files_view, uri))
{
g_warning ("Ignoring previewer Trash request for uri %s", uri);
return;
}
gtk_widget_activate_action (GTK_WIDGET (files_view), "view.move-to-trash", NULL);
}
static void
handle_previewer_event (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
PreviewerEventCallback callback = user_data;
NautilusFilesView *files_view = (current_slot != NULL) ?
nautilus_window_slot_get_current_view (current_slot) : NULL;
if (files_view == NULL)
{
return;
}
callback (files_view, parameters);
}
static guint
setup_dbus_connection (GDBusConnection *connection,
const char *event_name,
PreviewerEventCallback callback)
{
return g_dbus_connection_signal_subscribe (connection,
previewer_dbus_name,
PREVIEWER2_DBUS_IFACE,
event_name,
previewer_dbus_path,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
handle_previewer_event,
callback,
NULL);
}
static void
switch_to_alternative_previewer_dbus_name (void)
{
tried_alternative_previewer_dbus_name = TRUE;
if (g_str_has_suffix (previewer_dbus_name, "Devel"))
{
previewer_dbus_name = PREVIEWER_DBUS_NAME;
previewer_dbus_path = PREVIEWER_DBUS_PATH;
}
else
{
previewer_dbus_name = PREVIEWER_DBUS_NAME "Devel";
previewer_dbus_path = PREVIEWER_DBUS_PATH "Devel";
}
}
static void
on_ping_finished (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr (GError) error = NULL;
g_autoptr (GVariant) variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error);
if (error == NULL)
{
GDBusConnection *connection = g_dbus_proxy_get_connection (previewer_proxy);
previewer_ready = TRUE;
fetching_bus = FALSE;
dbus_subscriptions[DBUS_SUBSCRIPTION_NAVIGATE_TO] =
setup_dbus_connection (connection, "NavigateTo", previewer_navigate_to_event);
dbus_subscriptions[DBUS_SUBSCRIPTION_RENAME] =
setup_dbus_connection (connection, "Rename", previewer_rename_event);
dbus_subscriptions[DBUS_SUBSCRIPTION_SELECTION] =
setup_dbus_connection (connection, "SelectionEvent", previewer_selection_event);
dbus_subscriptions[DBUS_SUBSCRIPTION_TRASH] =
setup_dbus_connection (connection, "Trash", previewer_trash_event);
}
else if (!tried_alternative_previewer_dbus_name)
{
switch_to_alternative_previewer_dbus_name ();
create_new_bus ();
}
else
{
fetching_bus = FALSE;
g_debug ("Unable to create NautilusPreviewer2 proxy: %s", error->message);
}
}
static void
on_bus_ready (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr (GError) error = NULL;
g_clear_object (&previewer_proxy);
previewer_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
if (error == NULL)
{
g_dbus_proxy_call (previewer_proxy,
"org.freedesktop.DBus.Peer.Ping", NULL,
G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
cancellable, on_ping_finished, NULL);
}
else if (!tried_alternative_previewer_dbus_name)
{
switch_to_alternative_previewer_dbus_name ();
create_new_bus ();
}
else
{
fetching_bus = FALSE;
g_debug ("Unable to create NautilusPreviewer2 proxy: %s", error->message);
}
}
static void
create_new_bus (void)
{
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION,
NULL,
previewer_dbus_name,
previewer_dbus_path,
PREVIEWER2_DBUS_IFACE,
cancellable,
on_bus_ready,
NULL);
}
static gboolean
ensure_previewer_proxy (void)
{
if (previewer_ready)
{
return TRUE;
}
if (fetching_bus)
{
return FALSE;
}
fetching_bus = TRUE;
create_new_bus ();
return FALSE;
}
static void
previewer2_method_ready_cb (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
GDBusProxy *proxy = G_DBUS_PROXY (source);
g_autoptr (GError) error = NULL;
g_dbus_proxy_call_finish (proxy, res, &error);
if (error != NULL)
{
g_debug ("Unable to call method on NautilusPreviewer: %s", error->message);
}
}
void
nautilus_previewer_call_show_file (const gchar *uri,
NautilusWindowSlot *slot,
gboolean close_if_already_visible)
{
g_set_weak_pointer (¤t_slot, slot);
GtkRoot *window = gtk_widget_get_root (GTK_WIDGET (slot));
g_autoptr (GdkAppLaunchContext) launch_context = gdk_display_get_app_launch_context (
gtk_root_get_display (window));
g_autofree char *activation_token = g_app_launch_context_get_startup_notify_id (
G_APP_LAUNCH_CONTEXT (launch_context),
NULL,
NULL);
/* Reuse existing handle if called again for the same window. */
if (current_window == window &&
exported_window_handle != NULL)
{
real_call_show_file (uri, exported_window_handle, close_if_already_visible, activation_token);
return;
}
/* Otherwise, obtain a new window handle. */
clear_exported_window_handle ();
g_set_weak_pointer (¤t_window, window);
g_signal_connect_object (g_application_get_default (), "last-window-closed",
G_CALLBACK (previewer_call_close), window, 0);
GdkSurface *gdk_surface = gtk_native_get_surface (GTK_NATIVE (window));
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
{
guint xid = (guint) gdk_x11_surface_get_xid (gdk_surface);
g_autofree char *window_handle = g_strdup_printf ("x11:%x", xid);
real_call_show_file (uri, window_handle, close_if_already_visible, activation_token);
return;
}
#endif
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
{
g_autoptr (PreviewExportData) data = g_new0 (PreviewExportData, 1);
data->uri = g_strdup (uri);
data->close_if_already_visible = close_if_already_visible;
data->activation_token = g_strdup (activation_token);
if (gdk_wayland_toplevel_export_handle (GDK_WAYLAND_TOPLEVEL (gdk_surface),
wayland_window_handle_exported,
data,
preview_export_data_free))
{
/* Don't let autoptr free data successfully taken by the call. */
(void) g_steal_pointer (&data);
return;
}
}
#endif
g_warning ("Couldn't export handle, unsupported windowing system");
/* Let's use a fallback, so at least a preview will be displayed */
real_call_show_file (uri, "x11:0", close_if_already_visible, activation_token);
}
static void
real_call_show_file (const gchar *uri,
const gchar *window_handle,
gboolean close_if_already_visible,
const char *activation_token)
{
g_set_str (&exported_window_handle, window_handle);
if (!ensure_previewer_proxy ())
{
return;
}
if (activation_token == NULL)
{
activation_token = "";
}
GVariant *parameters = g_variant_new (
"(ssbs)",
uri, window_handle, close_if_already_visible, activation_token);
g_dbus_proxy_call (previewer_proxy,
"ShowFile",
parameters,
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
previewer2_method_ready_cb,
NULL);
}
static void
previewer_call_close (void)
{
if (!ensure_previewer_proxy ())
{
return;
}
/* don't autostart the previewer if it's not running */
g_dbus_proxy_call (previewer_proxy,
"Close",
NULL,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
cancellable,
previewer2_method_ready_cb,
NULL);
}
void
nautilus_previewer_setup (void)
{
ensure_previewer_proxy ();
}
void
nautilus_previewer_teardown (GDBusConnection *connection)
{
for (guint i = 0; i < NUM_DBUS_SUBSCRIPTIONS; i += 1)
{
if (dbus_subscriptions[i] != 0)
{
g_dbus_connection_signal_unsubscribe (connection, dbus_subscriptions[i]);
}
}
g_cancellable_cancel (cancellable);
g_clear_object (&cancellable);
g_clear_object (&previewer_proxy);
clear_exported_window_handle ();
g_clear_weak_pointer (¤t_window);
g_clear_weak_pointer (¤t_slot);
}
gboolean
nautilus_previewer_is_visible (void)
{
if (!ensure_previewer_proxy ())
{
return FALSE;
}
g_autoptr (GVariant) variant = g_dbus_proxy_get_cached_property (previewer_proxy, "Visible");
if (variant)
{
return g_variant_get_boolean (variant);
}
return FALSE;
}