/*
* Copyright © 2012 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranties of
* MERCHANTABILITY, SATISFACTORY QUALITY, 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, see .
*
* Author: Ted Gould
*/
#define G_LOG_DOMAIN "hudapplicationlist"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "window-info.h"
#include "application-list.h"
#include "application-source.h"
#include "source.h"
#include "window-stack-iface.h"
typedef struct _HudApplicationListPrivate HudApplicationListPrivate;
struct _HudApplicationListPrivate {
HudApplicationSource * last_focused_main_stage_source;
DBusWindowStack * window_stack;
gulong matcher_app_sig;
gulong matcher_view_open_sig;
gulong matcher_view_close_sig;
GHashTable * applications;
HudSource * used_source;
};
#define HUD_APPLICATION_LIST_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), HUD_TYPE_APPLICATION_LIST, HudApplicationListPrivate))
static void hud_application_list_class_init (HudApplicationListClass * klass);
static void hud_application_list_init (HudApplicationList * self);
static void hud_application_list_constructed (GObject * object);
static void matching_setup (HudApplicationList * self);
static void hud_application_list_dispose (GObject * object);
static void hud_application_list_finalize (GObject * object);
static void source_iface_init (HudSourceInterface * iface);
static void window_changed (DBusWindowStack * matcher,
guint window_id,
const gchar * app_id,
guint stage,
gpointer user_data);
static void view_opened (DBusWindowStack * matcher,
guint window_id,
const gchar * app_id,
gpointer user_data);
static void view_closed (DBusWindowStack * matcher,
guint window_id,
const gchar * app_id,
gpointer user_data);
static void source_use (HudSource * hud_source);
static void source_unuse (HudSource * hud_source);
static void source_search (HudSource * hud_source,
HudTokenList * search_string,
void (*append_func) (HudResult * result, gpointer user_data),
gpointer user_data);
static void source_list_applications (HudSource * hud_source,
HudTokenList * search_string,
void (*append_func) (const gchar *application_id, const gchar *application_icon, HudSourceItemType type, gpointer user_data),
gpointer user_data);
static HudSource * source_get (HudSource * hud_source,
const gchar * application_id);
static GList * source_get_items (HudSource * list);
static void application_source_changed (HudSource * source,
gpointer user_data);
static gboolean hud_application_list_name_in_ignore_list (HudWindowInfo *window);
G_DEFINE_TYPE_WITH_CODE (HudApplicationList, hud_application_list, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (HUD_TYPE_SOURCE, source_iface_init))
/* Class Init */
static void
hud_application_list_class_init (HudApplicationListClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (HudApplicationListPrivate));
object_class->constructed = hud_application_list_constructed;
object_class->dispose = hud_application_list_dispose;
object_class->finalize = hud_application_list_finalize;
klass->matching_setup = matching_setup;
return;
}
/* Intialized the source interface */
static void
source_iface_init (HudSourceInterface *iface)
{
iface->use = source_use;
iface->unuse = source_unuse;
iface->search = source_search;
iface->list_applications = source_list_applications;
iface->get = source_get;
iface->get_items = source_get_items;
return;
}
/* Instance Init */
static void
hud_application_list_init (HudApplicationList *self)
{
self->priv = HUD_APPLICATION_LIST_GET_PRIVATE(self);
self->priv->applications = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
return;
}
/* Final build steps */
static void
hud_application_list_constructed (GObject * object)
{
HudApplicationList * self = HUD_APPLICATION_LIST(object);
HudApplicationListClass * aclass = HUD_APPLICATION_LIST_GET_CLASS(self);
if (aclass->matching_setup != NULL) {
aclass->matching_setup(self);
}
return;
}
static void
matching_setup (HudApplicationList * self)
{
GError *error = NULL;
self->priv->window_stack = dbus_window_stack_proxy_new_for_bus_sync(
G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
"com.canonical.Unity.WindowStack",
"/com/canonical/Unity/WindowStack",
NULL, &error);
if(self->priv->window_stack == NULL) {
g_warning("Could not construct window stack proxy: %s", error->message);
g_error_free(error);
return;
}
g_debug("connecting to window stack signals");
self->priv->matcher_app_sig = g_signal_connect(self->priv->window_stack,
"focused-window-changed",
G_CALLBACK(window_changed), self);
self->priv->matcher_view_open_sig = g_signal_connect(self->priv->window_stack,
"window-created",
G_CALLBACK(view_opened), self);
self->priv->matcher_view_close_sig = g_signal_connect(self->priv->window_stack,
"window-destroyed",
G_CALLBACK(view_closed), self);
g_debug("connected to window stack signals");
GVariant *stack_variant = NULL;
error = NULL;
if (!dbus_window_stack_call_get_window_stack_sync(self->priv->window_stack,
&stack_variant, NULL, &error)) {
g_warning("Could not get window stack: %s", error->message);
g_error_free(error);
return;
}
GVariantIter iter;
g_variant_iter_init(&iter, stack_variant);
GVariant *window_info = NULL;
while ((window_info = g_variant_iter_next_value(&iter))) {
GVariantIter window_info_iter;
g_variant_iter_init(&window_info_iter, window_info);
GVariant *window_id_variant = g_variant_iter_next_value(&window_info_iter);
GVariant *app_id_variant = g_variant_iter_next_value(&window_info_iter);
GVariant *focused_variant = g_variant_iter_next_value(&window_info_iter);
GVariant *stage_variant = g_variant_iter_next_value(&window_info_iter);
view_opened(self->priv->window_stack,
g_variant_get_uint32(window_id_variant),
g_variant_get_string(app_id_variant, NULL),
/*g_variant_get_uint32(stage_variant),*/self);
if (g_variant_get_boolean(focused_variant)) {
HudApplicationSource *source = g_hash_table_lookup(
self->priv->applications,
g_variant_get_string(app_id_variant, NULL));
if (source
&& !hud_application_list_name_in_ignore_list(
hud_application_source_get_application_info(
source))) {
window_changed(self->priv->window_stack,
g_variant_get_uint32(window_id_variant),
g_variant_get_string(app_id_variant, NULL),
g_variant_get_uint32(stage_variant), self);
}
}
g_variant_unref (window_id_variant);
g_variant_unref (app_id_variant);
g_variant_unref (focused_variant);
g_variant_unref (stage_variant);
g_variant_unref (window_info);
}
g_variant_unref(stack_variant);
}
/* Clean up references */
static void
hud_application_list_dispose (GObject *object)
{
HudApplicationList * self = HUD_APPLICATION_LIST(object);
g_debug("Application List Dispose Start");
if (self->priv->used_source != NULL) {
hud_source_unuse(self->priv->used_source);
self->priv->used_source = NULL;
}
if (self->priv->matcher_app_sig != 0 && self->priv->window_stack != NULL) {
g_signal_handler_disconnect(self->priv->window_stack, self->priv->matcher_app_sig);
}
self->priv->matcher_app_sig = 0;
if (self->priv->matcher_view_open_sig != 0 && self->priv->window_stack != NULL) {
g_signal_handler_disconnect(self->priv->window_stack, self->priv->matcher_view_open_sig);
}
self->priv->matcher_view_open_sig = 0;
if (self->priv->matcher_view_close_sig != 0 && self->priv->window_stack != NULL) {
g_signal_handler_disconnect(self->priv->window_stack, self->priv->matcher_view_close_sig);
}
self->priv->matcher_view_close_sig = 0;
g_debug("Unrefing window stack");
g_clear_object(&self->priv->window_stack);
g_debug("Unref'd window stack");
g_hash_table_remove_all(self->priv->applications);
g_debug("Application List Dispose Recurse");
G_OBJECT_CLASS (hud_application_list_parent_class)->dispose (object);
g_debug("Application List Dispose Stop");
return;
}
/* Free memory */
static void
hud_application_list_finalize (GObject *object)
{
HudApplicationList * self = HUD_APPLICATION_LIST(object);
g_debug("Application List Finalize Start");
g_clear_pointer(&self->priv->applications, g_hash_table_unref);
g_debug("Application List Finalize Recurse");
G_OBJECT_CLASS (hud_application_list_parent_class)->finalize (object);
g_debug("Application List Finalize Stop");
return;
}
static HudApplicationSource *
application_info_to_source (HudApplicationList * list, HudApplicationInfo * bapp)
{
const gchar * id = hud_window_info_get_app_id(bapp);
HudApplicationSource * source = g_hash_table_lookup(list->priv->applications, id);
if (source == NULL) {
source = hud_application_source_new_for_app(bapp);
g_signal_connect(source, "changed", G_CALLBACK(application_source_changed), list);
g_hash_table_insert(list->priv->applications, g_strdup(id), source);
id = NULL; /* We used the malloc in the table */
hud_source_changed(HUD_SOURCE(list));
}
return source;
}
static gboolean
hud_application_list_name_in_ignore_list (HudWindowInfo *window)
{
static const gchar * const ignored_names[] = {
"Hud Prototype Test",
"Hud",
"DNDCollectionWindow",
"launcher",
"dash",
"Dash",
"panel",
"hud",
"unity-2d-shell",
"unity-dash",
"unity-panel",
"unity-launcher",
"XdndCollectionWindowImp",
};
gboolean ignored = FALSE;
gint i;
gchar *window_name = hud_window_info_get_utf8_prop(window, "WM_NAME");
g_debug ("checking window name '%s'", window_name);
/* it's possible to get NULL here */
if (window_name == NULL)
return FALSE;
for (i = 0; i < G_N_ELEMENTS (ignored_names); i++)
if (g_str_equal (ignored_names[i], window_name))
{
g_debug ("window name '%s' blocked", window_name);
ignored = TRUE;
break;
}
g_free(window_name);
return ignored;
}
/* Called each time the focused application changes */
static void
window_changed (DBusWindowStack *window_stack, guint window_id, const gchar *app_id, guint stack, gpointer user_data)
{
g_debug("window_changed(%d, %s, %d)", window_id, app_id, stack);
HudApplicationList * list = HUD_APPLICATION_LIST(user_data);
HudWindowInfo *window = hud_window_info_new(list->priv->window_stack, window_id, app_id, stack);
if (hud_application_list_name_in_ignore_list (window)) {
g_object_unref(window);
return;
}
/* Clear the last source, as we've obviously changed */
list->priv->last_focused_main_stage_source = NULL;
HudApplicationSource *source = application_info_to_source(list, window);
g_debug("looking up application source for: %s", app_id);
/* If we weren't able to lookup the app, let's try to find a source
for the window. */
if (source == NULL) {
g_debug("no applicationn source was found");
guint xid = hud_window_info_get_window_id(window);
GList * sources = g_hash_table_get_values(list->priv->applications);
GList * lsource = NULL;
for (lsource = sources; lsource != NULL; lsource = g_list_next(lsource)) {
HudApplicationSource * appsource = HUD_APPLICATION_SOURCE(lsource->data);
if (appsource == NULL) continue;
if (hud_application_source_has_xid(appsource, xid)) {
source = appsource;
break;
}
}
}
if (source == NULL) {
g_warning("Unable to find source for window");
return;
}
list->priv->last_focused_main_stage_source = source;
hud_application_source_focus(source, window, window);
hud_source_changed(HUD_SOURCE(list));
g_object_unref(window);
return;
}
/* A new view has been opened */
static void
view_opened (DBusWindowStack * window_stack, guint window_id, const gchar *app_id, gpointer user_data)
{
g_debug("view_opened(%d, %s)", window_id, app_id);
HudApplicationList * list = HUD_APPLICATION_LIST(user_data);
HudWindowInfo *window = hud_window_info_new(list->priv->window_stack,
window_id, app_id, HUD_WINDOW_INFO_STAGE_MAIN);
HudApplicationSource * source = application_info_to_source(list, window);
if (source == NULL) {
g_object_unref(window);
return;
}
hud_application_source_add_window(source, window);
g_object_unref(window);
return;
}
/* A view has been closed */
static void
view_closed (DBusWindowStack * window_stack, guint window_id, const gchar *app_id, gpointer user_data)
{
g_debug("view_closed(%d, %s)", window_id, app_id);
HudApplicationList * list = HUD_APPLICATION_LIST(user_data);
HudSource *source = source_get(HUD_SOURCE(user_data), app_id);
if (source == NULL) {
g_warning("Window closed for unknown app: %s", app_id);
return;
}
HudApplicationSource *appsource = HUD_APPLICATION_SOURCE(source);
if (hud_application_source_has_xid(appsource, window_id)) {
hud_application_source_window_closed(appsource, window_id);
}
/* If the application source has become empty it means that
* the corresponding app has terminated and it's time to do the
* cleanup.
*/
if (hud_application_source_is_empty (appsource)) {
if ((gpointer)appsource == (gpointer)list->priv->used_source) {
hud_source_unuse(HUD_SOURCE(appsource));
list->priv->used_source = NULL;
}
g_debug("Removing application %s", app_id);
g_hash_table_remove(list->priv->applications, app_id);
}
hud_source_changed(HUD_SOURCE(list));
}
/* Source interface using this source */
static void
source_use (HudSource *hud_source)
{
g_return_if_fail(HUD_IS_APPLICATION_LIST(hud_source));
HudApplicationList * list = HUD_APPLICATION_LIST(hud_source);
HudApplicationSource * source = NULL;
/* First see if we've already got it */
source = list->priv->last_focused_main_stage_source;
if (source == NULL) {
g_warning("Unable to find source for window");
return;
}
if (list->priv->used_source != NULL) {
hud_source_unuse(list->priv->used_source);
}
list->priv->used_source = HUD_SOURCE(source);
hud_source_use(HUD_SOURCE(source));
return;
}
/* Source interface unusing this source */
static void
source_unuse (HudSource *hud_source)
{
g_return_if_fail(HUD_IS_APPLICATION_LIST(hud_source));
HudApplicationList * list = HUD_APPLICATION_LIST(hud_source);
g_return_if_fail(list->priv->used_source != NULL);
hud_source_unuse(list->priv->used_source);
list->priv->used_source = NULL;
return;
}
/* Search this source */
static void
source_search (HudSource * hud_source,
HudTokenList * search_string,
void (*append_func) (HudResult * result, gpointer user_data),
gpointer user_data)
{
g_return_if_fail(HUD_IS_APPLICATION_LIST(hud_source));
HudApplicationList * list = HUD_APPLICATION_LIST(hud_source);
g_return_if_fail(list->priv->used_source != NULL);
hud_source_search(list->priv->used_source, search_string, append_func, user_data);
return;
}
static void
source_list_applications (HudSource * hud_source,
HudTokenList * search_string,
void (*append_func) (const gchar *application_id, const gchar *application_icon, HudSourceItemType type, gpointer user_data),
gpointer user_data)
{
g_return_if_fail(HUD_IS_APPLICATION_LIST(hud_source));
HudApplicationList * list = HUD_APPLICATION_LIST(hud_source);
GList * sources = g_hash_table_get_values(list->priv->applications);
GList * lsource = NULL;
for (lsource = sources; lsource != NULL; lsource = g_list_next(lsource)) {
HudApplicationSource * appsource = HUD_APPLICATION_SOURCE(lsource->data);
if (appsource == NULL || HUD_SOURCE(appsource) == list->priv->used_source) continue;
hud_source_list_applications(HUD_SOURCE(appsource), search_string, append_func, user_data);
}
if (list->priv->used_source != NULL) {
hud_source_list_applications(list->priv->used_source, search_string, append_func, user_data);
}
}
static HudSource *
source_get (HudSource * hud_source,
const gchar * application_id)
{
g_return_val_if_fail(HUD_IS_APPLICATION_LIST(hud_source), NULL);
g_return_val_if_fail(application_id != NULL, NULL);
HudApplicationList * list = HUD_APPLICATION_LIST(hud_source);
return g_hash_table_lookup(list->priv->applications, application_id);
}
/* An application has signaled that it's items have changed */
static void
application_source_changed (HudSource * source, gpointer user_data)
{
HudApplicationList * list = HUD_APPLICATION_LIST(user_data);
hud_source_changed(HUD_SOURCE(list));
return;
}
/**
* hud_application_list_new:
*
* Create a new application list.
*
* Return Value: (transfer full): New #HudApplicationList
*/
HudApplicationList *
hud_application_list_new (void)
{
return g_object_new(HUD_TYPE_APPLICATION_LIST,
NULL);
}
/**
* hud_application_list_get_source:
* @list: A #HudApplicationList object
* @id: Application ID to find
*
* Looks for a source in the application list database or if it
* doesn't exist, it creates it.
*
* Return value: (transfer none): An #HudApplicationSource matching @id
*/
HudApplicationSource *
hud_application_list_get_source (HudApplicationList * list, const gchar * id)
{
g_return_val_if_fail(HUD_IS_APPLICATION_LIST(list), NULL);
g_return_val_if_fail(id != NULL, NULL);
HudApplicationSource * source = HUD_APPLICATION_SOURCE(source_get(HUD_SOURCE(list), id));
if (source == NULL) {
source = hud_application_source_new_for_id(id);
g_signal_connect(source, "changed", G_CALLBACK(application_source_changed), list);
g_hash_table_insert(list->priv->applications, g_strdup(id), source);
}
return source;
}
/**
* hud_application_list_get_focused_app:
* @list: A #HudApplicationList object
*
* Gets the focused app source
*
* Return value: (transfer none): The current #HudApplicationSource
*/
HudSource *
hud_application_list_get_focused_app (HudApplicationList * list)
{
g_return_val_if_fail(HUD_IS_APPLICATION_LIST(list), NULL);
HudApplicationListClass * aclass = HUD_APPLICATION_LIST_GET_CLASS(list);
if (G_UNLIKELY(aclass->get_focused_app != NULL)) {
return aclass->get_focused_app(list);
}
return HUD_SOURCE(list->priv->last_focused_main_stage_source);
}
/**
* hud_application_list_get_side_stage_focused_app:
* @list: A #HudApplicationList object
*
* Gets the side stage focused app source
*
* Return value: (transfer none): The current #HudApplicationSource
*/
HudSource *
hud_application_list_get_side_stage_focused_app (HudApplicationList * list)
{
g_return_val_if_fail(HUD_IS_APPLICATION_LIST(list), NULL);
return NULL;
}
/**
* hud_application_list_get_apps:
* @list: A #HudApplicationList object
*
* Gets a list of applications
*
* Return value: A list of #HudApplicationSource objects
*/
GList *
hud_application_list_get_apps (HudApplicationList * list)
{
g_return_val_if_fail(HUD_IS_APPLICATION_LIST(list), NULL);
return g_hash_table_get_values(list->priv->applications);
}
/**
* hud_application_list_get_active_collector:
*
* Returns the active collector if there is one
*
* Returns: (transfer none): A #HudCollector or NULL if none
*/
GList *
source_get_items (HudSource * source)
{
g_return_val_if_fail(HUD_IS_APPLICATION_LIST(source), NULL);
HudApplicationList *list = HUD_APPLICATION_LIST(source);
g_return_val_if_fail(list->priv->used_source != NULL, NULL);
return hud_source_get_items(list->priv->used_source);
}