gnome-shell/src/shell-doc-system.c
Owen W. Taylor 0d32017ffc Don't switch to a workspace when dragging it to launch on that workspace
With workspace thumbnails, we don't switch workspaces when dragging windows
between workspaces or adding new workspaces, so we also shouldn't switch
on launch.

 * Add workspace parameters to shell_doc_system_open(),
   shell_app_activate, shell_app_open_new_window()

 * Pass a 'params' object when activating items in the overview with
   two currently defined parameters: workspace and timestamp. (timestamp
   is only implemented where it is easy and doesn't require interface
   changes - using the global current timestamp for the shell is almost
   always right or at least good enough.)

https://bugzilla.gnome.org/show_bug.cgi?id=640996
2011-02-08 19:43:21 -05:00

374 lines
11 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include "config.h"
#include "shell-doc-system.h"
#include "shell-global.h"
/**
* SECTION:shell-doc-system
* @short_description: Track recently used documents
*
* Wraps #GtkRecentManager, caching recently used document information, and adds
* APIs for asynchronous queries.
*/
enum {
CHANGED,
DELETED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
struct _ShellDocSystemPrivate {
GtkRecentManager *manager;
GHashTable *infos_by_uri;
GSList *infos_by_timestamp;
guint idle_recent_changed_id;
GHashTable *deleted_infos;
guint idle_emit_deleted_id;
};
G_DEFINE_TYPE(ShellDocSystem, shell_doc_system, G_TYPE_OBJECT);
/**
* shell_doc_system_get_all:
* @system: A #ShellDocSystem
*
* Returns the currently cached set of recent files. Recent files are read initially
* from the underlying #GtkRecentManager, and updated when it changes.
* This function does not perform I/O.
*
* Returns: (transfer none) (element-type GtkRecentInfo): Cached recent file infos
*/
GSList *
shell_doc_system_get_all (ShellDocSystem *self)
{
return self->priv->infos_by_timestamp;
}
/**
* @self: A #ShellDocSystem
* @uri: Url
*
* Returns: (transfer none): Recent file info corresponding to given @uri
*/
GtkRecentInfo *
shell_doc_system_lookup_by_uri (ShellDocSystem *self,
const char *uri)
{
return g_hash_table_lookup (self->priv->infos_by_uri, uri);
}
static gboolean
shell_doc_system_idle_emit_deleted (gpointer data)
{
ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
GHashTableIter iter;
gpointer key, value;
self->priv->idle_emit_deleted_id = 0;
g_hash_table_iter_init (&iter, self->priv->deleted_infos);
while (g_hash_table_iter_next (&iter, &key, &value))
{
GtkRecentInfo *info = key;
g_signal_emit (self, signals[DELETED], 0, info);
}
g_signal_emit (self, signals[CHANGED], 0);
return FALSE;
}
typedef struct {
ShellDocSystem *self;
GtkRecentInfo *info;
} ShellDocSystemRecentQueryData;
static void
on_recent_file_query_result (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
ShellDocSystemRecentQueryData *data = user_data;
ShellDocSystem *self = data->self;
GError *error = NULL;
GFileInfo *fileinfo;
fileinfo = g_file_query_info_finish (G_FILE (source), result, &error);
if (fileinfo)
g_object_unref (fileinfo);
/* This is a strict error check; we don't want to cause recent files to
* vanish for anything potentially transient.
*/
if (error != NULL && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)
{
self->priv->infos_by_timestamp = g_slist_remove (self->priv->infos_by_timestamp, data->info);
g_hash_table_remove (self->priv->infos_by_uri, gtk_recent_info_get_uri (data->info));
g_hash_table_insert (self->priv->deleted_infos, gtk_recent_info_ref (data->info), NULL);
if (self->priv->idle_emit_deleted_id == 0)
self->priv->idle_emit_deleted_id = g_timeout_add (0, shell_doc_system_idle_emit_deleted, self);
}
g_clear_error (&error);
gtk_recent_info_unref (data->info);
g_free (data);
}
/**
* shell_doc_system_queue_existence_check:
* @system: A #ShellDocSystem
* @n_items: Count of items to check for existence, starting from most recent
*
* Asynchronously start a check of a number of recent file for existence;
* any deleted files will be emitted from the #ShellDocSystem::deleted
* signal. Note that this function ignores non-local files; they
* will simply always appear to exist (until they are removed from
* the recent file list manually).
*
* The intent of this function is to be called after a #ShellDocSystem::changed
* signal has been emitted, and a display has shown a subset of those files.
*/
void
shell_doc_system_queue_existence_check (ShellDocSystem *self,
guint n_items)
{
GSList *iter;
guint i;
for (i = 0, iter = self->priv->infos_by_timestamp; i < n_items && iter; i++, iter = iter->next)
{
GtkRecentInfo *info = iter->data;
const char *uri;
GFile *file;
ShellDocSystemRecentQueryData *data;
if (!gtk_recent_info_is_local (info))
continue;
data = g_new0 (ShellDocSystemRecentQueryData, 1);
data->self = self;
data->info = gtk_recent_info_ref (info);
uri = gtk_recent_info_get_uri (info);
file = g_file_new_for_uri (uri);
g_file_query_info_async (file, "standard::type", G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT, NULL, on_recent_file_query_result, data);
g_object_unref (file);
}
}
static int
sort_infos_by_timestamp_descending (gconstpointer a,
gconstpointer b)
{
GtkRecentInfo *info_a = (GtkRecentInfo*)a;
GtkRecentInfo *info_b = (GtkRecentInfo*)b;
time_t modified_a, modified_b;
modified_a = gtk_recent_info_get_modified (info_a);
modified_b = gtk_recent_info_get_modified (info_b);
return modified_b - modified_a;
}
static gboolean
idle_handle_recent_changed (gpointer data)
{
ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
GList *items, *iter;
self->priv->idle_recent_changed_id = 0;
g_hash_table_remove_all (self->priv->deleted_infos);
g_hash_table_remove_all (self->priv->infos_by_uri);
g_slist_free (self->priv->infos_by_timestamp);
self->priv->infos_by_timestamp = NULL;
items = gtk_recent_manager_get_items (self->priv->manager);
for (iter = items; iter; iter = iter->next)
{
GtkRecentInfo *info = iter->data;
const char *uri = gtk_recent_info_get_uri (info);
/* uri is owned by the info */
g_hash_table_insert (self->priv->infos_by_uri, (char*) uri, info);
self->priv->infos_by_timestamp = g_slist_prepend (self->priv->infos_by_timestamp, info);
}
g_list_free (items);
self->priv->infos_by_timestamp = g_slist_sort (self->priv->infos_by_timestamp, sort_infos_by_timestamp_descending);
g_signal_emit (self, signals[CHANGED], 0);
return FALSE;
}
static void
shell_doc_system_on_recent_changed (GtkRecentManager *manager,
ShellDocSystem *self)
{
if (self->priv->idle_recent_changed_id != 0)
return;
self->priv->idle_recent_changed_id = g_timeout_add (0, idle_handle_recent_changed, self);
}
/**
* shell_doc_system_open:
* @system: A #ShellDocSystem
* @info: A #GtkRecentInfo
* @workspace: Open on this workspace, or -1 for default
*
* Launch the default application associated with the mime type of
* @info, using its uri.
*/
void
shell_doc_system_open (ShellDocSystem *system,
GtkRecentInfo *info,
int workspace)
{
GFile *file;
GAppInfo *app_info;
gboolean needs_uri;
GAppLaunchContext *context;
context = shell_global_create_app_launch_context (shell_global_get ());
if (workspace != -1)
gdk_app_launch_context_set_desktop ((GdkAppLaunchContext *)context, workspace);
file = g_file_new_for_uri (gtk_recent_info_get_uri (info));
needs_uri = g_file_get_path (file) == NULL;
g_object_unref (file);
app_info = g_app_info_get_default_for_type (gtk_recent_info_get_mime_type (info), needs_uri);
if (app_info != NULL)
{
GList *uris;
uris = g_list_prepend (NULL, (gpointer)gtk_recent_info_get_uri (info));
g_app_info_launch_uris (app_info, uris, context, NULL);
g_list_free (uris);
}
else
{
char *app_name;
#if GTK_MINOR_VERSION >= 18
const char *app_exec;
#else
char *app_exec;
#endif
char *app_exec_quoted;
guint count;
time_t time;
app_name = gtk_recent_info_last_application (info);
if (gtk_recent_info_get_application_info (info, app_name, &app_exec, &count, &time))
{
GRegex *regex;
/* TODO: Change this once better support for creating
GAppInfo is added to GtkRecentInfo, as right now
this relies on the fact that the file uri is
already a part of appExec, so we don't supply any
files to app_info.launch().
The 'command line' passed to
create_from_command_line is allowed to contain
'%<something>' macros that are expanded to file
name / icon name, etc, so we need to escape % as %%
*/
regex = g_regex_new ("%", 0, 0, NULL);
app_exec_quoted = g_regex_replace (regex, app_exec, -1, 0, "%%", 0, NULL);
g_regex_unref (regex);
app_info = g_app_info_create_from_commandline (app_exec_quoted, NULL, 0, NULL);
g_free (app_exec_quoted);
/* The point of passing an app launch context to
launch() is mostly to get startup notification and
associated benefits like the app appearing on the
right desktop; but it doesn't really work for now
because with the way we create the appInfo we
aren't reading the application's desktop file, and
thus don't find the StartupNotify=true in it. So,
despite passing the app launch context, no startup
notification occurs.
*/
g_app_info_launch (app_info, NULL, context, NULL);
}
g_free (app_name);
}
g_object_unref (context);
}
static void
shell_doc_system_class_init(ShellDocSystemClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *)klass;
signals[CHANGED] =
g_signal_new ("changed",
SHELL_TYPE_DOC_SYSTEM,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[DELETED] =
g_signal_new ("deleted",
SHELL_TYPE_DOC_SYSTEM,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1, GTK_TYPE_RECENT_INFO);
g_type_class_add_private (gobject_class, sizeof (ShellDocSystemPrivate));
}
static void
shell_doc_system_init (ShellDocSystem *self)
{
ShellDocSystemPrivate *priv;
self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
SHELL_TYPE_DOC_SYSTEM,
ShellDocSystemPrivate);
self->priv->manager = gtk_recent_manager_get_default ();
self->priv->deleted_infos = g_hash_table_new_full (NULL, NULL, (GDestroyNotify)gtk_recent_info_unref, NULL);
self->priv->infos_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)gtk_recent_info_unref);
g_signal_connect (self->priv->manager, "changed", G_CALLBACK(shell_doc_system_on_recent_changed), self);
shell_doc_system_on_recent_changed (self->priv->manager, self);
}
/**
* shell_doc_system_get_default:
*
* Return Value: (transfer none): The global #ShellDocSystem singleton
*/
ShellDocSystem *
shell_doc_system_get_default ()
{
static ShellDocSystem *instance = NULL;
if (instance == NULL)
instance = g_object_new (SHELL_TYPE_DOC_SYSTEM, NULL);
return instance;
}