/*
* nautilus-progress-persistence-handler.c: file operation progress 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 "nautilus-progress-info.h"
#include "nautilus-progress-info-manager.h"
struct _NautilusProgressPersistenceHandler
{
GObject parent_instance;
NautilusProgressInfoManager *manager;
NautilusApplication *app;
guint active_infos;
};
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, 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
* - when one file operation finishes, if it's not the last one, we only update the
* resident notification's message
* - in the same case, if it's the last one, we close the resident notification,
* 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->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
progress_persistence_handler_update_notification (NautilusProgressPersistenceHandler *self)
{
GNotification *notification;
gchar *body;
if (!server_has_persistence ())
{
return;
}
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->active_infos),
self->active_infos);
g_notification_set_body (notification, body);
nautilus_application_send_notification (self->app,
"progress", notification);
g_object_unref (notification);
g_free (body);
}
void
nautilus_progress_persistence_handler_make_persistent (NautilusProgressPersistenceHandler *self)
{
GList *windows;
windows = nautilus_application_get_windows (self->app);
if (self->active_infos > 0 && windows == NULL)
{
progress_persistence_handler_update_notification (self);
}
}
static void
progress_persistence_handler_show_complete_notification (NautilusProgressPersistenceHandler *self)
{
GNotification *complete_notification;
if (!server_has_persistence ())
{
return;
}
complete_notification = g_notification_new (_("File Operations"));
g_notification_set_body (complete_notification,
_("All file operations have been completed"));
nautilus_application_send_notification (self->app,
"transfer-complete",
complete_notification);
g_object_unref (complete_notification);
}
static void
progress_persistence_handler_hide_notification (NautilusProgressPersistenceHandler *self)
{
if (!server_has_persistence ())
{
return;
}
nautilus_application_withdraw_notification (self->app,
"progress");
}
static void
progress_info_finished_cb (NautilusProgressInfo *info,
NautilusProgressPersistenceHandler *self)
{
GtkWindow *last_active_window;
self->active_infos--;
last_active_window = gtk_application_get_active_window (GTK_APPLICATION (self->app));
if (self->active_infos > 0)
{
if (last_active_window == NULL)
{
progress_persistence_handler_update_notification (self);
}
}
else
{
if ((last_active_window == NULL) || !gtk_window_is_active (last_active_window))
{
progress_persistence_handler_hide_notification (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->active_infos++;
windows = nautilus_application_get_windows (self->app);
if (windows == NULL)
{
progress_persistence_handler_update_notification (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 ());
/* Release reference aquired when the signal was connected. */
g_object_unref (info);
}
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 ());
/* Hold a ref to keep the info alive until we get the ::finished signal. */
g_object_ref (info);
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->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->manager = nautilus_progress_info_manager_dup_singleton ();
g_signal_connect (self->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;
}
NautilusProgressPersistenceHandler *
nautilus_progress_persistence_handler_new (GObject *app)
{
NautilusProgressPersistenceHandler *self;
self = g_object_new (NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER, NULL);
self->app = NAUTILUS_APPLICATION (app);
g_action_map_add_action_entries (G_ACTION_MAP (self->app),
progress_persistence_entries, G_N_ELEMENTS (progress_persistence_entries),
self);
return self;
}