/*
* nautilus-progress-persistence-handler.c: file operation progress systray icon and notification handler.
*
* Copyright (C) 2007, 2011, 2015 Red Hat, Inc.
*
* This program 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.
*
* This program 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 .
*
* Authors: Alexander Larsson
* Cosimo Cecchi
* Carlos Soriano
*
*/
#include
#include "nautilus-progress-persistence-handler.h"
#include "nautilus-application.h"
#include "nautilus-progress-info-widget.h"
#include
#include
#include
struct _NautilusProgressPersistenceHandlerPriv {
NautilusProgressInfoManager *manager;
NautilusApplication *app;
guint active_infos;
GtkStatusIcon *status_icon;
};
G_DEFINE_TYPE (NautilusProgressPersistenceHandler, nautilus_progress_persistence_handler, G_TYPE_OBJECT);
/* Our policy for showing progress notification is the following:
* - file operations that end within two seconds do not get notified in any way
* - if no file operations are running, and one passes the two seconds
* timeout, a window is displayed with the progress
* - if the window is closed, we show a resident notification, or a status icon, depending on
* the capabilities of the notification daemon running in the session
* - if some file operations are running, and another one passes the two seconds
* timeout, and the window is showing, we add it to the window directly
* - in the same case, but when the window is not showing, we update the resident
* notification, changing its message, or the status icon's tooltip
* - when one file operation finishes, if it's not the last one, we only update the
* resident notification's message, or the status icon's tooltip
* - in the same case, if it's the last one, we close the resident notification,
* or the status icon, and trigger a transient one
* - in the same case, but the window was showing, we just hide the window
*/
static gboolean server_has_persistence (void);
static void
show_file_transfers (NautilusProgressPersistenceHandler *self)
{
GFile *home;
home = g_file_new_for_path (g_get_home_dir ());
nautilus_application_open_location (self->priv->app, home, NULL, NULL);
g_object_unref (home);
}
static void
action_show_file_transfers (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
NautilusProgressPersistenceHandler *self;
self = NAUTILUS_PROGRESS_PERSISTENCE_HANDLER (user_data);
show_file_transfers (self);
}
static GActionEntry progress_persistence_entries[] = {
{ "show-file-transfers", action_show_file_transfers, NULL, NULL, NULL }
};
static void
status_icon_activate_cb (GtkStatusIcon *icon,
NautilusProgressPersistenceHandler *self)
{
gtk_status_icon_set_visible (icon, FALSE);
show_file_transfers (self);
}
static void
progress_persistence_handler_ensure_status_icon (NautilusProgressPersistenceHandler *self)
{
GIcon *icon;
GtkStatusIcon *status_icon;
if (self->priv->status_icon != NULL) {
return;
}
icon = g_themed_icon_new_with_default_fallbacks ("system-file-manager-symbolic");
status_icon = gtk_status_icon_new_from_gicon (icon);
g_signal_connect (status_icon, "activate",
(GCallback) status_icon_activate_cb,
self);
gtk_status_icon_set_visible (status_icon, FALSE);
g_object_unref (icon);
self->priv->status_icon = status_icon;
}
static void
progress_persistence_handler_update_notification (NautilusProgressPersistenceHandler *self)
{
GNotification *notification;
gchar *body;
notification = g_notification_new (_("File Operations"));
g_notification_set_default_action (notification, "app.show-file-transfers");
g_notification_add_button (notification, _("Show Details"),
"app.show-file-transfers");
body = g_strdup_printf (ngettext ("%'d file operation active",
"%'d file operations active",
self->priv->active_infos),
self->priv->active_infos);
g_notification_set_body (notification, body);
nautilus_application_send_notification (self->priv->app,
"progress", notification);
g_object_unref (notification);
g_free (body);
}
static void
progress_persistence_handler_update_status_icon (NautilusProgressPersistenceHandler *self)
{
gchar *tooltip;
progress_persistence_handler_ensure_status_icon (self);
tooltip = g_strdup_printf (ngettext ("%'d file operation active",
"%'d file operations active",
self->priv->active_infos),
self->priv->active_infos);
gtk_status_icon_set_tooltip_text (self->priv->status_icon, tooltip);
g_free (tooltip);
gtk_status_icon_set_visible (self->priv->status_icon, TRUE);
}
void
nautilus_progress_persistence_handler_make_persistent (NautilusProgressPersistenceHandler *self)
{
GList *windows;
windows = nautilus_application_get_windows (self->priv->app);
if (self->priv->active_infos > 0 &&
g_list_length (windows) == 0) {
if (server_has_persistence ()) {
progress_persistence_handler_update_notification (self);
} else {
progress_persistence_handler_update_status_icon (self);
}
}
}
static void
progress_persistence_handler_update_notification_or_status (NautilusProgressPersistenceHandler *self)
{
if (server_has_persistence ()) {
progress_persistence_handler_update_notification (self);
} else {
progress_persistence_handler_update_status_icon (self);
}
}
static void
progress_persistence_handler_show_complete_notification (NautilusProgressPersistenceHandler *self)
{
GNotification *complete_notification;
/* don't display the notification if we'd be using a status icon */
if (!server_has_persistence ()) {
return;
}
complete_notification = g_notification_new (_("File Operations"));
g_notification_set_body (complete_notification,
_("All file operations have been successfully completed"));
nautilus_application_send_notification (self->priv->app,
"transfer-complete",
complete_notification);
g_object_unref (complete_notification);
}
static void
progress_persistence_handler_hide_notification_or_status (NautilusProgressPersistenceHandler *self)
{
if (self->priv->status_icon != NULL) {
gtk_status_icon_set_visible (self->priv->status_icon, FALSE);
}
nautilus_application_withdraw_notification (self->priv->app,
"progress");
}
static void
progress_info_finished_cb (NautilusProgressInfo *info,
NautilusProgressPersistenceHandler *self)
{
GList *windows;
self->priv->active_infos--;
windows = nautilus_application_get_windows (self->priv->app);
if (self->priv->active_infos > 0) {
if (g_list_length (windows) == 0) {
progress_persistence_handler_update_notification_or_status (self);
}
} else if (g_list_length (windows) == 0) {
progress_persistence_handler_hide_notification_or_status (self);
progress_persistence_handler_show_complete_notification (self);
}
}
static void
handle_new_progress_info (NautilusProgressPersistenceHandler *self,
NautilusProgressInfo *info)
{
GList *windows;
g_signal_connect (info, "finished",
G_CALLBACK (progress_info_finished_cb), self);
self->priv->active_infos++;
windows = nautilus_application_get_windows (self->priv->app);
if (g_list_length (windows) == 0) {
progress_persistence_handler_update_notification_or_status (self);
}
}
typedef struct {
NautilusProgressInfo *info;
NautilusProgressPersistenceHandler *self;
} TimeoutData;
static void
timeout_data_free (TimeoutData *data)
{
g_clear_object (&data->self);
g_clear_object (&data->info);
g_slice_free (TimeoutData, data);
}
static TimeoutData *
timeout_data_new (NautilusProgressPersistenceHandler *self,
NautilusProgressInfo *info)
{
TimeoutData *retval;
retval = g_slice_new0 (TimeoutData);
retval->self = g_object_ref (self);
retval->info = g_object_ref (info);
return retval;
}
static gboolean
new_op_started_timeout (TimeoutData *data)
{
NautilusProgressInfo *info = data->info;
NautilusProgressPersistenceHandler *self = data->self;
if (nautilus_progress_info_get_is_paused (info)) {
return TRUE;
}
if (!nautilus_progress_info_get_is_finished (info)) {
handle_new_progress_info (self, info);
}
timeout_data_free (data);
return FALSE;
}
static void
release_application (NautilusProgressInfo *info,
NautilusProgressPersistenceHandler *self)
{
/* release the GApplication hold we acquired */
g_application_release (g_application_get_default ());
}
static void
progress_info_started_cb (NautilusProgressInfo *info,
NautilusProgressPersistenceHandler *self)
{
TimeoutData *data;
/* hold GApplication so we never quit while there's an operation pending */
g_application_hold (g_application_get_default ());
g_signal_connect (info, "finished",
G_CALLBACK (release_application), self);
data = timeout_data_new (self, info);
/* timeout for the progress window to appear */
g_timeout_add_seconds (2,
(GSourceFunc) new_op_started_timeout,
data);
}
static void
new_progress_info_cb (NautilusProgressInfoManager *manager,
NautilusProgressInfo *info,
NautilusProgressPersistenceHandler *self)
{
g_signal_connect (info, "started",
G_CALLBACK (progress_info_started_cb), self);
}
static void
nautilus_progress_persistence_handler_dispose (GObject *obj)
{
NautilusProgressPersistenceHandler *self = NAUTILUS_PROGRESS_PERSISTENCE_HANDLER (obj);
g_clear_object (&self->priv->manager);
G_OBJECT_CLASS (nautilus_progress_persistence_handler_parent_class)->dispose (obj);
}
static gboolean
server_has_persistence (void)
{
static gboolean retval = FALSE;
GDBusConnection *conn;
GVariant *result;
char **cap, **caps;
static gboolean initialized = FALSE;
if (initialized) {
return retval;
}
initialized = TRUE;
conn = g_application_get_dbus_connection (g_application_get_default ());
result = g_dbus_connection_call_sync (conn,
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"GetCapabilities",
g_variant_new ("()"),
G_VARIANT_TYPE ("(as)"),
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, NULL);
if (result == NULL)
return FALSE;
g_variant_get (result, "(^a&s)", &caps);
for (cap = caps; *cap != NULL; cap++)
if (g_strcmp0 ("persistence", *cap) == 0)
retval = TRUE;
g_free (caps);
g_variant_unref (result);
return retval;
}
static void
nautilus_progress_persistence_handler_init (NautilusProgressPersistenceHandler *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER,
NautilusProgressPersistenceHandlerPriv);
self->priv->manager = nautilus_progress_info_manager_dup_singleton ();
g_signal_connect (self->priv->manager, "new-progress-info",
G_CALLBACK (new_progress_info_cb), self);
}
static void
nautilus_progress_persistence_handler_class_init (NautilusProgressPersistenceHandlerClass *klass)
{
GObjectClass *oclass;
oclass = G_OBJECT_CLASS (klass);
oclass->dispose = nautilus_progress_persistence_handler_dispose;
g_type_class_add_private (klass, sizeof (NautilusProgressPersistenceHandlerPriv));
}
NautilusProgressPersistenceHandler *
nautilus_progress_persistence_handler_new (GObject *app)
{
NautilusProgressPersistenceHandler *self;
self = g_object_new (NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER, NULL);
self->priv->app = NAUTILUS_APPLICATION (app);
g_action_map_add_action_entries (G_ACTION_MAP (self->priv->app),
progress_persistence_entries, G_N_ELEMENTS (progress_persistence_entries),
self);
return self;
}