/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 1999 Free Software Foundation, Inc. * Copyright (C) 2007, 2009 Vincent Untz. * Copyright (C) 2008 Lucas Rocha. * Copyright (C) 2008 William Jon McCann * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include "gsm-util.h" #include "gsp-app.h" #include "gsp-app-manager.h" static GspAppManager *manager = NULL; typedef struct { char *dir; int index; GFileMonitor *monitor; } GspXdgDir; struct _GspAppManagerPrivate { GSList *apps; GSList *dirs; }; #define GSP_APP_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSP_TYPE_APP_MANAGER, GspAppManagerPrivate)) enum { ADDED, REMOVED, LAST_SIGNAL }; static guint gsp_app_manager_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (GspAppManager, gsp_app_manager, G_TYPE_OBJECT) static void gsp_app_manager_dispose (GObject *object); static void gsp_app_manager_finalize (GObject *object); static void _gsp_app_manager_app_unref (GspApp *app, GspAppManager *manager); static void _gsp_app_manager_app_removed (GspAppManager *manager, GspApp *app); static GspXdgDir * _gsp_xdg_dir_new (const char *dir, int index) { GspXdgDir *xdgdir; xdgdir = g_slice_new (GspXdgDir); xdgdir->dir = g_strdup (dir); xdgdir->index = index; xdgdir->monitor = NULL; return xdgdir; } static void _gsp_xdg_dir_free (GspXdgDir *xdgdir) { if (xdgdir->dir) { g_free (xdgdir->dir); xdgdir->dir = NULL; } if (xdgdir->monitor) { g_file_monitor_cancel (xdgdir->monitor); g_object_unref (xdgdir->monitor); xdgdir->monitor = NULL; } g_slice_free (GspXdgDir, xdgdir); } static void gsp_app_manager_class_init (GspAppManagerClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); gobject_class->dispose = gsp_app_manager_dispose; gobject_class->finalize = gsp_app_manager_finalize; gsp_app_manager_signals[ADDED] = g_signal_new ("added", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GspAppManagerClass, added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); gsp_app_manager_signals[REMOVED] = g_signal_new ("removed", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GspAppManagerClass, removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); g_type_class_add_private (class, sizeof (GspAppManagerPrivate)); } static void gsp_app_manager_init (GspAppManager *manager) { manager->priv = GSP_APP_MANAGER_GET_PRIVATE (manager); memset (manager->priv, 0, sizeof (GspAppManagerPrivate)); } static void gsp_app_manager_dispose (GObject *object) { GspAppManager *manager; g_return_if_fail (object != NULL); g_return_if_fail (GSP_IS_APP_MANAGER (object)); manager = GSP_APP_MANAGER (object); /* we unref GspApp objects in dispose since they might need to * reference us during their dispose/finalize */ g_slist_foreach (manager->priv->apps, (GFunc) _gsp_app_manager_app_unref, manager); g_slist_free (manager->priv->apps); manager->priv->apps = NULL; G_OBJECT_CLASS (gsp_app_manager_parent_class)->dispose (object); } static void gsp_app_manager_finalize (GObject *object) { GspAppManager *manager; g_return_if_fail (object != NULL); g_return_if_fail (GSP_IS_APP_MANAGER (object)); manager = GSP_APP_MANAGER (object); g_slist_foreach (manager->priv->dirs, (GFunc) _gsp_xdg_dir_free, NULL); g_slist_free (manager->priv->dirs); manager->priv->dirs = NULL; G_OBJECT_CLASS (gsp_app_manager_parent_class)->finalize (object); manager = NULL; } static void _gsp_app_manager_emit_added (GspAppManager *manager, GspApp *app) { g_signal_emit (G_OBJECT (manager), gsp_app_manager_signals[ADDED], 0, app); } static void _gsp_app_manager_emit_removed (GspAppManager *manager, GspApp *app) { g_signal_emit (G_OBJECT (manager), gsp_app_manager_signals[REMOVED], 0, app); } /* * Directories */ static int gsp_app_manager_get_dir_index (GspAppManager *manager, const char *dir) { GSList *l; GspXdgDir *xdgdir; g_return_val_if_fail (GSP_IS_APP_MANAGER (manager), -1); g_return_val_if_fail (dir != NULL, -1); for (l = manager->priv->dirs; l != NULL; l = l->next) { xdgdir = l->data; if (strcmp (dir, xdgdir->dir) == 0) { return xdgdir->index; } } return -1; } const char * gsp_app_manager_get_dir (GspAppManager *manager, unsigned int index) { GSList *l; GspXdgDir *xdgdir; g_return_val_if_fail (GSP_IS_APP_MANAGER (manager), NULL); for (l = manager->priv->dirs; l != NULL; l = l->next) { xdgdir = l->data; if (index == xdgdir->index) { return xdgdir->dir; } } return NULL; } static int _gsp_app_manager_find_dir_with_basename (GspAppManager *manager, const char *basename, int minimum_index) { GSList *l; GspXdgDir *xdgdir; char *path; GKeyFile *keyfile; int result = -1; path = NULL; keyfile = g_key_file_new (); for (l = manager->priv->dirs; l != NULL; l = l->next) { xdgdir = l->data; if (xdgdir->index <= minimum_index) { continue; } g_free (path); path = g_build_filename (xdgdir->dir, basename, NULL); if (!g_file_test (path, G_FILE_TEST_EXISTS)) { continue; } if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) { continue; } /* the file exists and is readable */ if (result == -1) { result = xdgdir->index; } else { result = MIN (result, xdgdir->index); } } g_key_file_free (keyfile); g_free (path); return result; } static void _gsp_app_manager_handle_delete (GspAppManager *manager, GspApp *app, const char *basename, int index) { unsigned int position; unsigned int system_position; position = gsp_app_get_xdg_position (app); system_position = gsp_app_get_xdg_system_position (app); if (system_position < index) { /* it got deleted, but we don't even care about it */ return; } if (index < position) { /* it got deleted, but in a position earlier than the current * one. This happens when the user file was changed and became * identical to the system file; in this case, the user file is * simply removed. */ g_assert (index == 0); return; } if (position == index && (system_position == index || system_position == G_MAXUINT)) { /* the file used by the user was deleted, and there's no other * file in system directories. So it really got deleted. */ _gsp_app_manager_app_removed (manager, app); return; } if (system_position == index) { /* then we know that position != index; we just hae to tell * GspApp if there's still a system directory containing this * basename */ int new_system; new_system = _gsp_app_manager_find_dir_with_basename (manager, basename, index); if (new_system < 0) { gsp_app_set_xdg_system_position (app, G_MAXUINT); } else { gsp_app_set_xdg_system_position (app, new_system); } return; } if (position == index) { /* then we know that system_position != G_MAXUINT; we need to * tell GspApp to change position to system_position */ const char *dir; dir = gsp_app_manager_get_dir (manager, system_position); if (dir) { char *path; path = g_build_filename (dir, basename, NULL); gsp_app_reload_at (app, path, (unsigned int) system_position); g_free (path); } else { _gsp_app_manager_app_removed (manager, app); } return; } g_assert_not_reached (); } static gboolean gsp_app_manager_xdg_dir_monitor (GFileMonitor *monitor, GFile *child, GFile *other_file, GFileMonitorEvent flags, gpointer data) { GspAppManager *manager; GspApp *old_app; GspApp *app; GFile *parent; char *basename; char *dir; char *path; int index; manager = GSP_APP_MANAGER (data); basename = g_file_get_basename (child); if (!g_str_has_suffix (basename, ".desktop")) { /* not a desktop file, we can ignore */ g_free (basename); return TRUE; } old_app = gsp_app_manager_find_app_with_basename (manager, basename); parent = g_file_get_parent (child); dir = g_file_get_path (parent); g_object_unref (parent); index = gsp_app_manager_get_dir_index (manager, dir); if (index < 0) { /* not a directory we know; should never happen, though */ g_free (dir); return TRUE; } path = g_file_get_path (child); switch (flags) { case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_CREATED: /* we just do as if it was a new file: GspApp is clever enough * to do the right thing */ app = gsp_app_new (path, (unsigned int) index); /* we didn't have this app before, so add it */ if (old_app == NULL && app != NULL) { gsp_app_manager_add (manager, app); g_object_unref (app); } /* else: it was just updated, GspApp took care of * sending the event */ break; case G_FILE_MONITOR_EVENT_DELETED: if (!old_app) { /* it got deleted, but we don't know about it, so * nothing to do */ break; } _gsp_app_manager_handle_delete (manager, old_app, basename, index); break; default: break; } g_free (path); g_free (dir); g_free (basename); return TRUE; } /* * Initialization */ static void _gsp_app_manager_fill_from_dir (GspAppManager *manager, GspXdgDir *xdgdir) { GFile *file; GDir *dir; const char *name; file = g_file_new_for_path (xdgdir->dir); xdgdir->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); g_object_unref (file); if (xdgdir->monitor) { g_signal_connect (xdgdir->monitor, "changed", G_CALLBACK (gsp_app_manager_xdg_dir_monitor), manager); } dir = g_dir_open (xdgdir->dir, 0, NULL); if (!dir) { return; } while ((name = g_dir_read_name (dir))) { GspApp *app; char *desktop_file_path; if (!g_str_has_suffix (name, ".desktop")) { continue; } desktop_file_path = g_build_filename (xdgdir->dir, name, NULL); app = gsp_app_new (desktop_file_path, xdgdir->index); if (app != NULL) { gsp_app_manager_add (manager, app); g_object_unref (app); } g_free (desktop_file_path); } g_dir_close (dir); } void gsp_app_manager_fill (GspAppManager *manager) { char **autostart_dirs; int i; if (manager->priv->apps != NULL) return; autostart_dirs = gsm_util_get_autostart_dirs (); /* we always assume that the first directory is the user one */ g_assert (g_str_has_prefix (autostart_dirs[0], g_get_user_config_dir ())); for (i = 0; autostart_dirs[i] != NULL; i++) { GspXdgDir *xdgdir; if (gsp_app_manager_get_dir_index (manager, autostart_dirs[i]) >= 0) { continue; } xdgdir = _gsp_xdg_dir_new (autostart_dirs[i], i); manager->priv->dirs = g_slist_prepend (manager->priv->dirs, xdgdir); _gsp_app_manager_fill_from_dir (manager, xdgdir); } g_strfreev (autostart_dirs); } /* * App handling */ static void _gsp_app_manager_app_unref (GspApp *app, GspAppManager *manager) { g_signal_handlers_disconnect_by_func (app, _gsp_app_manager_app_removed, manager); g_object_unref (app); } static void _gsp_app_manager_app_removed (GspAppManager *manager, GspApp *app) { _gsp_app_manager_emit_removed (manager, app); manager->priv->apps = g_slist_remove (manager->priv->apps, app); _gsp_app_manager_app_unref (app, manager); } void gsp_app_manager_add (GspAppManager *manager, GspApp *app) { g_return_if_fail (GSP_IS_APP_MANAGER (manager)); g_return_if_fail (GSP_IS_APP (app)); manager->priv->apps = g_slist_prepend (manager->priv->apps, g_object_ref (app)); g_signal_connect_swapped (app, "removed", G_CALLBACK (_gsp_app_manager_app_removed), manager); _gsp_app_manager_emit_added (manager, app); } GspApp * gsp_app_manager_find_app_with_basename (GspAppManager *manager, const char *basename) { GSList *l; GspApp *app; g_return_val_if_fail (GSP_IS_APP_MANAGER (manager), NULL); g_return_val_if_fail (basename != NULL, NULL); for (l = manager->priv->apps; l != NULL; l = l->next) { app = GSP_APP (l->data); if (strcmp (basename, gsp_app_get_basename (app)) == 0) return app; } return NULL; } /* * Singleton */ GspAppManager * gsp_app_manager_get (void) { if (manager == NULL) { manager = g_object_new (GSP_TYPE_APP_MANAGER, NULL); return manager; } else { return g_object_ref (manager); } } GSList * gsp_app_manager_get_apps (GspAppManager *manager) { g_return_val_if_fail (GSP_IS_APP_MANAGER (manager), NULL); return g_slist_copy (manager->priv->apps); }