diff options
-rw-r--r-- | lib/egg/eel-app-launch-context.c | 614 | ||||
-rw-r--r-- | lib/egg/eel-app-launch-context.h | 77 |
2 files changed, 691 insertions, 0 deletions
diff --git a/lib/egg/eel-app-launch-context.c b/lib/egg/eel-app-launch-context.c new file mode 100644 index 000000000..54f6a66a0 --- /dev/null +++ b/lib/egg/eel-app-launch-context.c @@ -0,0 +1,614 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* eel-mount-operation.c - Gtk+ implementation for GAppLaunchContext + + Copyright (C) 2007 Red Hat, Inc. + + The Gnome 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 Gnome 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 Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Alexander Larsson <alexl@redhat.com> +*/ + +#include <config.h> +#include "eel-app-launch-context.h" + +#include <gio/gio.h> +#include <glib/gurifuncs.h> +#include <string.h> + +#ifdef HAVE_STARTUP_NOTIFICATION +#define SN_API_NOT_YET_FROZEN +#include <libsn/sn.h> +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <gtk/gtkmain.h> +#endif + +G_DEFINE_TYPE (EelAppLaunchContext, eel_app_launch_context, G_TYPE_APP_LAUNCH_CONTEXT); + +struct EelAppLaunchContextPrivate { + GdkDisplay *display; + GdkScreen *screen; + guint32 timestamp; + GIcon *icon; + char *icon_name; +}; + +static void +eel_app_launch_context_finalize (GObject *object) +{ + EelAppLaunchContext *context; + EelAppLaunchContextPrivate *priv; + + context = EEL_APP_LAUNCH_CONTEXT (object); + + priv = context->priv; + + if (priv->display) { + g_object_unref (priv->display); + } + if (priv->screen) { + g_object_unref (priv->screen); + } + if (priv->icon) { + g_object_unref (priv->screen); + } + g_free (priv->icon_name); + + (*G_OBJECT_CLASS (eel_app_launch_context_parent_class)->finalize) (object); +} + +static char * +get_display (GAppLaunchContext *context, + GAppInfo *info, + GList *files) +{ + GdkDisplay *display; + EelAppLaunchContextPrivate *priv; + + priv = EEL_APP_LAUNCH_CONTEXT (context)->priv; + + if (priv->screen) { + return gdk_screen_make_display_name (priv->screen); + } + + if (priv->display) { + display = priv->display; + } else { + display = gdk_display_get_default (); + } + + return g_strdup (gdk_display_get_name (display)); +} + +#ifdef HAVE_STARTUP_NOTIFICATION +static void +sn_error_trap_push (SnDisplay *display, + Display *xdisplay) +{ + gdk_error_trap_push (); +} + +static void +sn_error_trap_pop (SnDisplay *display, + Display *xdisplay) +{ + gdk_error_trap_pop (); +} + +static char * +get_display_name (GFile *file) +{ + GFileInfo *info; + char *name, *tmp; + + /* This does sync i/o, which isn't ideal. + * It should probably use the NautilusFile machinery + */ + + name = NULL; + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, + 0, NULL, NULL); + if (info) { + name = g_strdup (g_file_info_get_display_name (info)); + g_object_unref (info); + } + + if (name == NULL) { + name = g_file_get_basename (file); + if (!g_utf8_validate (name, -1, NULL)) { + tmp = name; + name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); + g_free (tmp); + } + } + return name; +} + +static GIcon * +get_icon (GFile *file) +{ + GFileInfo *info; + GIcon *icon; + + icon = NULL; + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_ICON, + 0, NULL, NULL); + if (info) { + icon = g_file_info_get_icon (info); + if (icon) { + g_object_ref (icon); + } + g_object_unref (info); + } + + return icon; + +} + +static char * +gicon_to_string (GIcon *icon) +{ + GFile *file; + const char * const *names; + + if (G_IS_FILE_ICON (icon)) { + file = g_file_icon_get_file (G_FILE_ICON (icon)); + if (file) { + return g_file_get_path (file); + } + } else if (G_IS_THEMED_ICON (icon)) { + names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + if (names) { + return g_strdup (names[0]); + } + } + + return NULL; +} + +/* FIXME: This is the wrong way to do this; there should be some event + * (e.g. button press) available with a good time. A function like + * this should not be needed. + */ +static Time +slowly_and_stupidly_obtain_timestamp (Display *xdisplay) +{ + Window xwindow; + XEvent event; + + { + XSetWindowAttributes attrs; + Atom atom_name; + Atom atom_type; + char* name; + + attrs.override_redirect = True; + attrs.event_mask = PropertyChangeMask | StructureNotifyMask; + + xwindow = + XCreateWindow (xdisplay, + RootWindow (xdisplay, 0), + -100, -100, 1, 1, + 0, + CopyFromParent, + CopyFromParent, + (Visual *)CopyFromParent, + CWOverrideRedirect | CWEventMask, + &attrs); + + atom_name = XInternAtom (xdisplay, "WM_NAME", TRUE); + g_assert (atom_name != None); + atom_type = XInternAtom (xdisplay, "STRING", TRUE); + g_assert (atom_type != None); + + name = "Fake Window"; + XChangeProperty (xdisplay, + xwindow, atom_name, + atom_type, + 8, PropModeReplace, name, strlen (name)); + } + + XWindowEvent (xdisplay, + xwindow, + PropertyChangeMask, + &event); + + XDestroyWindow(xdisplay, xwindow); + + return event.xproperty.time; +} + +/* This should be fairly long, as it's confusing to users if a startup + * ends when it shouldn't (it appears that the startup failed, and + * they have to relaunch the app). Also the timeout only matters when + * there are bugs and apps don't end their own startup sequence. + * + * This timeout is a "last resort" timeout that ignores whether the + * startup sequence has shown activity or not. Metacity and the + * tasklist have smarter, and correspondingly able-to-be-shorter + * timeouts. The reason our timeout is dumb is that we don't monitor + * the sequence (don't use an SnMonitorContext) + */ +#define STARTUP_TIMEOUT_LENGTH (30 /* seconds */ * 1000) + +typedef struct +{ + GSList *contexts; + guint timeout_id; +} StartupTimeoutData; + +static void +free_startup_timeout (void *data) +{ + StartupTimeoutData *std; + + std = data; + + g_slist_foreach (std->contexts, + (GFunc) sn_launcher_context_unref, + NULL); + g_slist_free (std->contexts); + + if (std->timeout_id != 0) { + g_source_remove (std->timeout_id); + std->timeout_id = 0; + } + + g_free (std); +} + +static gboolean +startup_timeout (void *data) +{ + StartupTimeoutData *std; + GSList *tmp; + GTimeVal now; + int min_timeout; + + std = data; + + min_timeout = STARTUP_TIMEOUT_LENGTH; + + g_get_current_time (&now); + + tmp = std->contexts; + while (tmp != NULL) { + SnLauncherContext *sn_context; + GSList *next; + long tv_sec, tv_usec; + double elapsed; + + sn_context = tmp->data; + next = tmp->next; + + sn_launcher_context_get_last_active_time (sn_context, + &tv_sec, &tv_usec); + + elapsed = + ((((double)now.tv_sec - tv_sec) * G_USEC_PER_SEC + + (now.tv_usec - tv_usec))) / 1000.0; + + if (elapsed >= STARTUP_TIMEOUT_LENGTH) { + std->contexts = g_slist_remove (std->contexts, + sn_context); + sn_launcher_context_complete (sn_context); + sn_launcher_context_unref (sn_context); + } else { + min_timeout = MIN (min_timeout, (STARTUP_TIMEOUT_LENGTH - elapsed)); + } + + tmp = next; + } + + if (std->contexts == NULL) { + std->timeout_id = 0; + } else { + std->timeout_id = g_timeout_add (min_timeout, + startup_timeout, + std); + } + + /* always remove this one, but we may have reinstalled another one. */ + return FALSE; +} + +static void +add_startup_timeout (GdkScreen *screen, + SnLauncherContext *sn_context) +{ + StartupTimeoutData *data; + + data = g_object_get_data (G_OBJECT (screen), "appinfo-startup-data"); + if (data == NULL) { + data = g_new (StartupTimeoutData, 1); + data->contexts = NULL; + data->timeout_id = 0; + + g_object_set_data_full (G_OBJECT (screen), "appinfo-startup-data", + data, free_startup_timeout); + } + + sn_launcher_context_ref (sn_context); + data->contexts = g_slist_prepend (data->contexts, sn_context); + + if (data->timeout_id == 0) { + data->timeout_id = g_timeout_add (STARTUP_TIMEOUT_LENGTH, + startup_timeout, + data); + } +} + +#endif + + +static char * +get_startup_notify_id (GAppLaunchContext *context, + GAppInfo *info, + GList *files) +{ +#ifdef HAVE_STARTUP_NOTIFICATION + EelAppLaunchContextPrivate *priv; + SnLauncherContext *sn_context; + SnDisplay *sn_display; + GdkDisplay *display; + GdkScreen *screen; + int files_count; + char *name, *description, *icon_name; + const char *binary_name; + GIcon *icon; + guint32 timestamp; + char *id; + + priv = EEL_APP_LAUNCH_CONTEXT (context)->priv; + + if (priv->screen) { + screen = priv->screen; + display = gdk_screen_get_display (priv->screen); + } else if (priv->display) { + display = priv->display; + screen = gdk_display_get_default_screen (display); + } else { + display = gdk_display_get_default (); + screen = gdk_display_get_default_screen (display); + } + + sn_display = sn_display_new (gdk_display, + sn_error_trap_push, + sn_error_trap_pop); + + + sn_context = sn_launcher_context_new (sn_display, + gdk_screen_get_number (screen)); + + + files_count = g_list_length (files); + if (files_count == 1) { + name = get_display_name (files->data); + description = g_strdup_printf (_("Opening %s"), name); + } else { + name = NULL; + description = g_strdup_printf (ngettext ("Opening %d Item", + "Opening %d Items", + files_count), + files_count); + } + + if (name != NULL) { + sn_launcher_context_set_name (sn_context, name); + g_free (name); + } + + if (description != NULL) { + sn_launcher_context_set_description (sn_context, description); + g_free (description); + } + + icon_name = NULL; + if (priv->icon_name) { + icon_name = g_strdup (priv->icon_name); + } else { + icon = NULL; + + if (priv->icon != NULL) { + icon = g_object_ref (priv->icon); + } else if (files_count == 1) { + icon = get_icon (files->data); + } + + if (icon == NULL) { + icon = g_app_info_get_icon (info); + g_object_ref (icon); + } + + if (icon) { + icon_name = gicon_to_string (icon); + } + g_object_unref (icon); + } + + if (icon_name) { + sn_launcher_context_set_icon_name (sn_context, icon_name); + g_free (icon_name); + } + + + binary_name = g_app_info_get_executable (info); + sn_launcher_context_set_binary_name (sn_context, binary_name); + + timestamp = priv->timestamp; + if (timestamp == GDK_CURRENT_TIME) { + timestamp = gtk_get_current_event_time (); + } + if (timestamp == GDK_CURRENT_TIME) { + timestamp = slowly_and_stupidly_obtain_timestamp (GDK_SCREEN_XDISPLAY (screen)); + } + + sn_launcher_context_initiate (sn_context, + g_get_application_name () ? g_get_application_name () : "unknown", + binary_name, + timestamp); + + id = g_strdup (sn_launcher_context_get_startup_id (sn_context)); + + add_startup_timeout (screen, sn_context); + + sn_launcher_context_unref (sn_context); + + sn_display_unref (sn_display); + + return id; +#else + return NULL; +#endif +} + +static void +launch_failed (GAppLaunchContext *context, + const char *startup_notify_id) +{ +#ifdef HAVE_STARTUP_NOTIFICATION + EelAppLaunchContextPrivate *priv; + GdkScreen *screen; + StartupTimeoutData *data; + SnLauncherContext *sn_context; + GSList *l; + + priv = EEL_APP_LAUNCH_CONTEXT (context)->priv; + + if (priv->screen) { + screen = priv->screen; + } else if (priv->display) { + screen = gdk_display_get_default_screen (priv->display); + } else { + screen = gdk_display_get_default_screen (gdk_display_get_default ()); + } + + data = g_object_get_data (G_OBJECT (screen), "appinfo-startup-data"); + + if (data) { + for (l = data->contexts; l != NULL; l = l->next) { + sn_context = l->data; + if (strcmp (startup_notify_id, sn_launcher_context_get_startup_id (sn_context)) == 0) { + data->contexts = g_slist_remove (data->contexts, + sn_context); + sn_launcher_context_complete (sn_context); + sn_launcher_context_unref (sn_context); + break; + } + } + + if (data->contexts == NULL) { + g_source_remove (data->timeout_id); + data->timeout_id = 0; + } + } + +#endif + +} + + +static void +eel_app_launch_context_class_init (EelAppLaunchContextClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GAppLaunchContextClass *context_class = G_APP_LAUNCH_CONTEXT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (EelAppLaunchContextPrivate)); + + gobject_class->finalize = eel_app_launch_context_finalize; + + context_class->get_display = get_display; + context_class->get_startup_notify_id = get_startup_notify_id; + context_class->launch_failed = launch_failed; +} + +static void +eel_app_launch_context_init (EelAppLaunchContext *context) +{ + context->priv = G_TYPE_INSTANCE_GET_PRIVATE (context, + EEL_TYPE_APP_LAUNCH_CONTEXT, + EelAppLaunchContextPrivate); +} + +void +eel_app_launch_context_set_display (EelAppLaunchContext *context, + GdkDisplay *display) +{ + if (context->priv->display) { + g_object_unref (context->priv->display); + context->priv->display = NULL; + } + + if (display) { + context->priv->display = g_object_ref (display); + } +} + +void +eel_app_launch_context_set_screen (EelAppLaunchContext *context, + GdkScreen *screen) +{ + if (context->priv->screen) { + g_object_unref (context->priv->screen); + context->priv->screen = NULL; + } + + if (screen) { + context->priv->screen = g_object_ref (screen); + } +} + +void +eel_app_launch_context_set_timestamp (EelAppLaunchContext *context, + guint32 timestamp) +{ + context->priv->timestamp = timestamp; +} + +void +eel_app_launch_context_set_icon (EelAppLaunchContext *context, + GIcon *icon) +{ + if (context->priv->icon) { + g_object_unref (context->priv->icon); + context->priv->icon = NULL; + } + + if (icon) { + context->priv->icon = g_object_ref (icon); + } +} + +void +eel_app_launch_context_set_icon_name (EelAppLaunchContext *context, + const char *icon_name) +{ + g_free (context->priv->icon_name); + context->priv->icon_name = g_strdup (icon_name); +} + +EelAppLaunchContext * +eel_app_launch_context_new (void) +{ + EelAppLaunchContext *context; + + context = g_object_new (eel_app_launch_context_get_type (), NULL); + return context; +} diff --git a/lib/egg/eel-app-launch-context.h b/lib/egg/eel-app-launch-context.h new file mode 100644 index 000000000..ace6925d3 --- /dev/null +++ b/lib/egg/eel-app-launch-context.h @@ -0,0 +1,77 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* eel-app-launch-context.h - Gtk+ implementation for GAppLaunchContext + + Copyright (C) 2007 Red Hat, Inc. + + The Gnome 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 Gnome 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 Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Alexander Larsson <alexl@redhat.com> +*/ + +#ifndef EEL_APP_LAUCH_CONTEXT_H +#define EEL_APP_LAUCH_CONTEXT_H + +#include <glib.h> +#include <gio/gio.h> +#include <gtk/gtkwidget.h> + +G_BEGIN_DECLS + +#define EEL_TYPE_APP_LAUNCH_CONTEXT (eel_app_launch_context_get_type ()) +#define EEL_APP_LAUNCH_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EEL_TYPE_APP_LAUNCH_CONTEXT, EelAppLaunchContext)) +#define EEL_APP_LAUNCH_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EEL_TYPE_APP_LAUNCH_CONTEXT, EelAppLaunchContextClass)) +#define EEL_IS_APP_LAUNCH_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EEL_TYPE_APP_LAUNCH_CONTEXT)) +#define EEL_IS_APP_LAUNCH_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EEL_TYPE_APP_LAUNCH_CONTEXT)) +#define EEL_APP_LAUNCH_CONTEXT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EEL_TYPE_APP_LAUNCH_CONTEXT, EelAppLaunchContextClass)) + +typedef struct EelAppLaunchContext EelAppLaunchContext; +typedef struct EelAppLaunchContextClass EelAppLaunchContextClass; +typedef struct EelAppLaunchContextPrivate EelAppLaunchContextPrivate; + +struct EelAppLaunchContext +{ + GAppLaunchContext parent_instance; + + EelAppLaunchContextPrivate *priv; +}; + +struct EelAppLaunchContextClass +{ + GAppLaunchContextClass parent_class; + + +}; + +GType eel_app_launch_context_get_type (void); + +EelAppLaunchContext *eel_app_launch_context_new (void); +void eel_app_launch_context_set_display (EelAppLaunchContext *context, + GdkDisplay *display); +void eel_app_launch_context_set_screen (EelAppLaunchContext *context, + GdkScreen *screen); +void eel_app_launch_context_set_timestamp (EelAppLaunchContext *context, + guint32 timestamp); +void eel_app_launch_context_set_icon (EelAppLaunchContext *context, + GIcon *icon); +void eel_app_launch_context_set_icon_name (EelAppLaunchContext *context, + const char *icon_name); + +G_END_DECLS + +#endif /* EEL_APP_LAUNCH_CONTEXT_H */ + + |