/* -*- 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 */ #include #include "eel-app-launch-context.h" #include #include #include #ifdef HAVE_STARTUP_NOTIFICATION #define SN_API_NOT_YET_FROZEN #include #include #include #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; }