/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ #include "config.h" #include "shell-app-private.h" #include "shell-global.h" #include "st.h" #include /** * SECTION:shell-app * @short_description: Object representing an application * * This object wraps a #ShellAppInfo, providing methods and signals * primarily useful for running applications. */ struct _ShellApp { GObject parent; ShellAppInfo *info; guint workspace_switch_id; gboolean window_sort_stale; GSList *windows; }; G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT); enum { WINDOWS_CHANGED, LAST_SIGNAL }; static guint shell_app_signals[LAST_SIGNAL] = { 0 }; const char * shell_app_get_id (ShellApp *app) { return shell_app_info_get_id (app->info); } /** * shell_app_create_icon_texture: * * Look up the icon for this application, and create a #ClutterTexture * for it at the given size. * * Return value: (transfer none): A floating #ClutterActor */ ClutterActor * shell_app_create_icon_texture (ShellApp *app, float size) { return shell_app_info_create_icon_texture (app->info, size); } typedef struct { ShellApp *app; int size; } CreateFadedIconData; static CoglHandle shell_app_create_faded_icon_cpu (StTextureCache *cache, const char *key, void *datap, GError **error) { CreateFadedIconData *data = datap; ShellApp *app; GdkPixbuf *pixbuf; int size; CoglHandle texture; gint width, height, rowstride; guint8 n_channels; gint fade_start; gint fade_range; guint i, j; guint pixbuf_byte_size; guint8 *orig_pixels; guint8 *pixels; GIcon *icon; GtkIconInfo *info; app = data->app; size = data->size; icon = shell_app_info_get_icon (app->info); if (icon == NULL) return COGL_INVALID_HANDLE; info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (), icon, (int) (size + 0.5), GTK_ICON_LOOKUP_FORCE_SIZE); g_object_unref (icon); if (info == NULL) return COGL_INVALID_HANDLE; pixbuf = gtk_icon_info_load_icon (info, NULL); gtk_icon_info_free (info); if (pixbuf == NULL) return COGL_INVALID_HANDLE; width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); n_channels = gdk_pixbuf_get_n_channels (pixbuf); orig_pixels = gdk_pixbuf_get_pixels (pixbuf); pixbuf_byte_size = (height - 1) * rowstride + + width * ((n_channels * gdk_pixbuf_get_bits_per_sample (pixbuf) + 7) / 8); pixels = g_malloc0 (rowstride * height); memcpy (pixels, orig_pixels, pixbuf_byte_size); fade_start = width / 2; fade_range = width - fade_start; for (i = fade_start; i < width; i++) { for (j = 0; j < height; j++) { guchar *pixel = &pixels[j * rowstride + i * n_channels]; float fade = 1.0 - ((float) i - fade_start) / fade_range; pixel[0] = 0.5 + pixel[0] * fade; pixel[1] = 0.5 + pixel[1] * fade; pixel[2] = 0.5 + pixel[2] * fade; pixel[3] = 0.5 + pixel[3] * fade; } } texture = cogl_texture_new_from_data (width, height, COGL_TEXTURE_NONE, gdk_pixbuf_get_has_alpha (pixbuf) ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888, COGL_PIXEL_FORMAT_ANY, rowstride, pixels); g_free (pixels); g_object_unref (pixbuf); return texture; } /** * shell_app_get_faded_icon: * @app: A #ShellApp * @size: Size in pixels * * Return an actor with a horizontally faded look. * * Return value: (transfer none): A floating #ClutterActor, or %NULL if no icon */ ClutterActor * shell_app_get_faded_icon (ShellApp *app, float size) { MetaWindow *window; CoglHandle texture; ClutterActor *result; char *cache_key; CreateFadedIconData data; /* Punt for WINDOW types for now...easier to reuse the property tracking bits, * and this helps us visually distinguish app-tracked from not. */ window = shell_app_info_get_source_window (app->info); if (window) { return st_texture_cache_bind_pixbuf_property (st_texture_cache_get_default (), G_OBJECT (window), "icon"); } cache_key = g_strdup_printf ("faded-icon:%s,size=%f", shell_app_get_id (app), size); data.app = app; data.size = (int) (0.5 + size); texture = st_texture_cache_load (st_texture_cache_get_default (), cache_key, ST_TEXTURE_CACHE_POLICY_FOREVER, shell_app_create_faded_icon_cpu, &data, NULL); g_free (cache_key); if (texture != COGL_INVALID_HANDLE) { result = clutter_texture_new (); clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (result), texture); } else { result = clutter_texture_new (); g_object_set (result, "opacity", 0, "width", size, "height", size, NULL); } return result; } char * shell_app_get_name (ShellApp *app) { return shell_app_info_get_name (app->info); } char * shell_app_get_description (ShellApp *app) { return shell_app_info_get_description (app->info); } gboolean shell_app_is_transient (ShellApp *app) { return shell_app_info_is_transient (app->info); } gboolean shell_app_launch (ShellApp *self, GError **error) { return shell_app_info_launch (self->info, error); } /** * _shell_app_get_info: * * Returns: (transfer none): Associated app info */ ShellAppInfo * _shell_app_get_info (ShellApp *app) { return app->info; } typedef struct { ShellApp *app; MetaWorkspace *active_workspace; } CompareWindowsData; static int shell_app_compare_windows (gconstpointer a, gconstpointer b, gpointer datap) { MetaWindow *win_a = (gpointer)a; MetaWindow *win_b = (gpointer)b; CompareWindowsData *data = datap; gboolean ws_a, ws_b; gboolean vis_a, vis_b; ws_a = meta_window_get_workspace (win_a) == data->active_workspace; ws_b = meta_window_get_workspace (win_b) == data->active_workspace; if (ws_a && !ws_b) return -1; else if (!ws_a && ws_b) return 1; vis_a = meta_window_showing_on_its_workspace (win_a); vis_b = meta_window_showing_on_its_workspace (win_b); if (vis_a && !vis_b) return -1; else if (!vis_a && vis_b) return 1; return meta_window_get_user_time (win_b) - meta_window_get_user_time (win_a); } /** * shell_app_get_windows: * @app: * * Get the toplevel, interesting windows which are associated with this * application. The returned list will be sorted first by whether * they're on the active workspace, then by whether they're visible, * and finally by the time the user last interacted with them. * * Returns: (transfer none) (element-type MetaWindow): List of windows */ GSList * shell_app_get_windows (ShellApp *app) { if (app->window_sort_stale) { CompareWindowsData data; data.app = app; data.active_workspace = meta_screen_get_active_workspace (shell_global_get_screen (shell_global_get ())); app->windows = g_slist_sort_with_data (app->windows, shell_app_compare_windows, &data); app->window_sort_stale = FALSE; } return app->windows; } guint shell_app_get_n_windows (ShellApp *app) { return g_slist_length (app->windows); } static gboolean shell_app_has_visible_windows (ShellApp *app) { GSList *iter; for (iter = app->windows; iter; iter = iter->next) { MetaWindow *window = iter->data; if (meta_window_showing_on_its_workspace (window)) return TRUE; } return FALSE; } gboolean shell_app_is_on_workspace (ShellApp *app, MetaWorkspace *workspace) { GSList *iter; for (iter = app->windows; iter; iter = iter->next) { if (meta_window_get_workspace (iter->data) == workspace) return TRUE; } return FALSE; } /** * shell_app_compare: * @app: * @other: A #ShellApp * * Compare one #ShellApp instance to another, in the following way: * - If one of them has visible windows and the other does not, the one * with visible windows is first. * - If one has no windows at all (i.e. it's not running) and the other * does, the one with windows is first. * - Finally, the application which the user interacted with most recently * compares earlier. */ int shell_app_compare (ShellApp *app, ShellApp *other) { gboolean vis_app, vis_other; GSList *windows_app, *windows_other; vis_app = shell_app_has_visible_windows (app); vis_other = shell_app_has_visible_windows (other); if (vis_app && !vis_other) return -1; else if (!vis_app && vis_other) return 1; if (app->windows && !other->windows) return -1; else if (!app->windows && other->windows) return 1; windows_app = shell_app_get_windows (app); windows_other = shell_app_get_windows (other); return meta_window_get_user_time (windows_other->data) - meta_window_get_user_time (windows_app->data); } ShellApp * _shell_app_new_for_window (MetaWindow *window) { ShellApp *app; app = g_object_new (SHELL_TYPE_APP, NULL); app->info = shell_app_system_create_from_window (shell_app_system_get_default (), window); _shell_app_system_register_app (shell_app_system_get_default (), app); _shell_app_add_window (app, window); return app; } ShellApp * _shell_app_new (ShellAppInfo *info) { ShellApp *app; app = g_object_new (SHELL_TYPE_APP, NULL); app->info = shell_app_info_ref (info); _shell_app_system_register_app (shell_app_system_get_default (), app); return app; } static void shell_app_on_unmanaged (MetaWindow *window, ShellApp *app) { _shell_app_remove_window (app, window); } static void shell_app_on_user_time_changed (MetaWindow *window, GParamSpec *pspec, ShellApp *app) { /* Ideally we don't want to emit windows-changed if the sort order * isn't actually changing. This check catches most of those. */ if (window != app->windows->data) { app->window_sort_stale = TRUE; g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0); } } static void shell_app_on_ws_switch (MetaScreen *screen, int from, int to, MetaMotionDirection direction, gpointer data) { ShellApp *self = SHELL_APP (data); self->window_sort_stale = TRUE; g_signal_emit (self, shell_app_signals[WINDOWS_CHANGED], 0); } void _shell_app_add_window (ShellApp *app, MetaWindow *window) { if (g_slist_find (app->windows, window)) return; app->windows = g_slist_prepend (app->windows, g_object_ref (window)); g_signal_connect (window, "unmanaged", G_CALLBACK(shell_app_on_unmanaged), app); g_signal_connect (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app); app->window_sort_stale = TRUE; g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0); if (app->workspace_switch_id == 0) { MetaScreen *screen = shell_global_get_screen (shell_global_get ()); app->workspace_switch_id = g_signal_connect (screen, "workspace-switched", G_CALLBACK(shell_app_on_ws_switch), app); } } static void disconnect_workspace_switch (ShellApp *app) { MetaScreen *screen; if (app->workspace_switch_id == 0) return; screen = shell_global_get_screen (shell_global_get ()); g_signal_handler_disconnect (screen, app->workspace_switch_id); app->workspace_switch_id = 0; } void _shell_app_remove_window (ShellApp *app, MetaWindow *window) { if (!g_slist_find (app->windows, window)) return; g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_unmanaged), app); g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_user_time_changed), app); g_object_unref (window); app->windows = g_slist_remove (app->windows, window); g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0); if (app->windows == NULL) disconnect_workspace_switch (app); } static void shell_app_init (ShellApp *self) { } static void shell_app_dispose (GObject *object) { ShellApp *app = SHELL_APP (object); if (app->info) { shell_app_info_unref (app->info); app->info = NULL; } while (app->windows) _shell_app_remove_window (app, app->windows->data); disconnect_workspace_switch (app); G_OBJECT_CLASS(shell_app_parent_class)->dispose (object); } static void shell_app_class_init(ShellAppClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = shell_app_dispose; shell_app_signals[WINDOWS_CHANGED] = g_signal_new ("windows-changed", SHELL_TYPE_APP, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); }