From 5dbc254039d1fd79ad587c6c98e9e8428495a0de Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 21 Jan 2015 00:43:40 +0100 Subject: right button menu and other features --- libmdock/app-id.c | 16 +- libmdock/app-id.h | 2 + libmdock/com.javispedro.mdock.widget.gschema.xml | 4 + libmdock/matcher.c | 21 +- libmdock/mdock-item.c | 425 ++++++++++++++++++++++- libmdock/mdock-item.h | 28 +- libmdock/mdock-widget.c | 195 +++++++++-- test/mdock-standalone.c | 3 + 8 files changed, 645 insertions(+), 49 deletions(-) diff --git a/libmdock/app-id.c b/libmdock/app-id.c index b54ed6c..6ea456a 100644 --- a/libmdock/app-id.c +++ b/libmdock/app-id.c @@ -49,10 +49,15 @@ void app_id_destroy(AppId *appid) g_slice_free(AppId, appid); } +static inline guint str_hash0(const gchar *s) +{ + return s ? g_str_hash(s) : 0; +} + guint app_id_hash(gconstpointer appid_pointer) { const AppId *appid = appid_pointer; - return g_str_hash(appid->host) + appid->uid + g_str_hash(appid->executable) + g_str_hash(appid->wm_class_class) + g_str_hash(appid->wm_class_name); + return str_hash0(appid->host) + appid->uid + str_hash0(appid->executable) + str_hash0(appid->wm_class_class) + str_hash0(appid->wm_class_name); } gboolean app_id_equal(gconstpointer ap, gconstpointer bp) @@ -155,3 +160,12 @@ AppId * app_id_from_window(WnckWindow *window) return appid; } + +gboolean app_id_is_local_user(AppId *appid) +{ + if (appid->host && !is_local_host(appid->host)) { + return FALSE; + } + + return appid->uid == getuid(); +} diff --git a/libmdock/app-id.h b/libmdock/app-id.h index df69ab6..d2abe92 100644 --- a/libmdock/app-id.h +++ b/libmdock/app-id.h @@ -41,4 +41,6 @@ gboolean app_id_equal(gconstpointer ap, gconstpointer bp); AppId * app_id_from_window(WnckWindow *window); +gboolean app_id_is_local_user(AppId *appid); + #endif /* _MDOCK_APPID_H_ */ diff --git a/libmdock/com.javispedro.mdock.widget.gschema.xml b/libmdock/com.javispedro.mdock.widget.gschema.xml index 9b05fa1..c48dee4 100644 --- a/libmdock/com.javispedro.mdock.widget.gschema.xml +++ b/libmdock/com.javispedro.mdock.widget.gschema.xml @@ -5,5 +5,9 @@ [] Items in the dock + + 48 + Icon size + diff --git a/libmdock/matcher.c b/libmdock/matcher.c index 5c1bd8b..df8e8b6 100644 --- a/libmdock/matcher.c +++ b/libmdock/matcher.c @@ -152,13 +152,16 @@ static void add_desktop_info(GDesktopAppInfo *info) const gchar *wmclass = g_desktop_app_info_get_startup_wm_class(info); if (wmclass && wmclass[0]) { - add_desktop_item_by_wmclass(wmclass, info, 128000); + gchar *wmclass_lower = g_ascii_strdown(wmclass, -1); + add_desktop_item_by_wmclass(wmclass_lower, info, 128000); } if (executable && executable[0]) { gchar *basename = g_path_get_basename(executable); - add_desktop_item_by_wmclass(basename, info, -32000); + gchar *basename_lower = g_ascii_strdown(basename, -1); + add_desktop_item_by_wmclass(basename_lower, info, -32000); g_free(basename); + g_free(basename_lower); } g_free(executable); @@ -172,7 +175,7 @@ static void refresh_appinfo() g_hash_table_remove_all(info_by_wmclass); GList *list = g_app_info_get_all(); - for (GList *l = g_list_first(list); l; l = g_list_next(l)) { + for (GList *l = list; l; l = g_list_next(l)) { add_desktop_info(G_DESKTOP_APP_INFO(l->data)); } @@ -208,6 +211,8 @@ static gboolean init_matcher(void) g_free, (GDestroyNotify)desktop_item_list_destroy); refresh_appinfo(); + + init_done = TRUE; } return TRUE; @@ -220,7 +225,6 @@ static GSList * matches_by_executable(const gchar *exec) if (elist) { return g_slist_copy(elist->list); } else { - g_debug("Executable %s not matched", exec); return NULL; } } @@ -239,23 +243,30 @@ static GSList * matches_by_wmclass(const gchar *wmclass) const gchar * match_appid_to_desktopid(AppId *appid) { if (!init_matcher()) return NULL; + if (!app_id_is_local_user(appid)) return NULL; GSList *list = NULL; if (appid->executable) { list = g_slist_concat(list, matches_by_executable(appid->executable)); } - if (appid->wm_class_name) { + if (appid->wm_class_class) { + list = g_slist_concat(list, matches_by_wmclass(appid->wm_class_class)); + } + if (appid->wm_class_name && (g_strcmp0(appid->wm_class_class, appid->wm_class_name) != 0)) { list = g_slist_concat(list, matches_by_wmclass(appid->wm_class_name)); } if (list) { list = g_slist_sort(list, desktop_item_compare); +#if 1 + g_debug("for appid with wmclass %s", appid->wm_class_class); for (GSList *l = list; l; l = l->next) { DesktopItem *item = l->data; g_debug(" match %d %s", item->priority, item->desktop_file); } +#endif DesktopItem *best = list->data; g_slist_free(list); diff --git a/libmdock/mdock-item.c b/libmdock/mdock-item.c index 726644f..43dcdca 100644 --- a/libmdock/mdock-item.c +++ b/libmdock/mdock-item.c @@ -17,40 +17,437 @@ * along with MDock. If not, see . */ +#include "mdock-enums.h" #include "mdock-item.h" #define MDOCK_ITEM_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), MDOCK_TYPE_ITEM, MDockItemPrivate)) struct _MDockItemPrivate { + MDockItemType type; + GDesktopAppInfo *appinfo; + guint icon_size; + GdkPixbuf *icon; + GList *windows; + + GtkMenu *menu; + GtkImageMenuItem *menu_close_all; + GtkImageMenuItem *menu_pin; + GtkImageMenuItem *menu_launch; }; -G_DEFINE_TYPE (MDockItem, mdock_item, GTK_TYPE_BOX) +G_DEFINE_TYPE (MDockItem, mdock_item, GTK_TYPE_WIDGET) -static void -mdock_item_finalize (GObject *object) +enum { + PROP_0, + PROP_TYPE, + PROP_DESKTOP_APP_INFO, + PROP_ICON_SIZE, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL }; + +static GdkPixbuf *mdock_item_load_icon(MDockItem *self, guint icon_size) { - G_OBJECT_CLASS (mdock_item_parent_class)->finalize (object); + if (self->priv->appinfo) { + GtkIconTheme *theme = gtk_icon_theme_get_default(); + GIcon *icon = g_app_info_get_icon(G_APP_INFO(self->priv->appinfo)); + if (icon) { + GtkIconInfo *info = gtk_icon_theme_lookup_by_gicon(theme, icon, icon_size, + GTK_ICON_LOOKUP_GENERIC_FALLBACK | GTK_ICON_LOOKUP_FORCE_SIZE); + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL); + gtk_icon_info_free(info); + if (pixbuf) { + return pixbuf; + } + } + } + + if (self->priv->windows) { + GdkPixbuf *pixbuf = wnck_window_get_icon(self->priv->windows->data); + if (pixbuf) { + return gdk_pixbuf_scale_simple(pixbuf, self->priv->icon_size, icon_size, GDK_INTERP_BILINEAR); + } + } + + return NULL; } -static void -mdock_item_class_init (MDockItemClass *klass) +static const gchar *mdock_item_get_display_name(MDockItem *self) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); + if (self->priv->appinfo) { + return g_app_info_get_display_name(G_APP_INFO(self->priv->appinfo)); + } else if (self->priv->windows) { + WnckApplication *app = wnck_window_get_application(self->priv->windows->data); + return wnck_application_get_name(app); + } else { + return NULL; + } +} - object_class->finalize = mdock_item_finalize; +static void mdock_item_update_icon(MDockItem *self) +{ + g_clear_object(&self->priv->icon); + self->priv->icon = mdock_item_load_icon(self, self->priv->icon_size); + gtk_widget_queue_draw(GTK_WIDGET(self)); +} - g_type_class_add_private (object_class, sizeof (MDockItemPrivate)); +static void mdock_item_update_tooltip(MDockItem *self) +{ + gtk_widget_set_tooltip_text(GTK_WIDGET(self), mdock_item_get_display_name(self)); } -static void -mdock_item_init (MDockItem *self) +static void mdock_item_update_menu(MDockItem *self) +{ + gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_close_all), self->priv->windows != NULL); + gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_pin), self->priv->appinfo != NULL); + gtk_widget_set_visible(GTK_WIDGET(self->priv->menu_launch), self->priv->appinfo != NULL); + if (!gtk_image_menu_item_get_image(self->priv->menu_launch) && self->priv->icon) { + gint width, height; + if (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height)) { + GdkPixbuf *pixbuf = mdock_item_load_icon(self, MIN(width, height)); + if (pixbuf) { + GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf); + gtk_image_menu_item_set_image(self->priv->menu_launch, image); + g_object_unref(pixbuf); + } + } + } +} + +static void mdock_item_realize(GtkWidget *widget) +{ + GdkWindowAttr attributes; + guint attributes_mask; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK; + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask); + + gdk_window_set_user_data(widget->window, widget); + gtk_widget_set_realized(widget, TRUE); + + widget->style = gtk_style_attach(widget->style, widget->window); + gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL); +} + +static void mdock_item_size_request(GtkWidget *widget, GtkRequisition *requisition) +{ + MDockItem *self = MDOCK_ITEM(widget); + requisition->width = requisition->height = self->priv->icon_size; +} + +static void mdock_item_size_allocate(GtkWidget *widget, GtkAllocation *allocation) +{ + MDockItem *self = MDOCK_ITEM(widget); + allocation->width = allocation->height = self->priv->icon_size; + GTK_WIDGET_CLASS(mdock_item_parent_class)->size_allocate(widget, allocation); +} + +static gboolean mdock_item_button_press(GtkWidget *widget, GdkEventButton *event) +{ + MDockItem *self = MDOCK_ITEM(widget); + switch (event->button) { + case 1: + if (self->priv->windows) { + // This item has one window at least + if (self->priv->windows->next) { + // There is more than one window + gboolean found_window = FALSE; + for (GList *l = self->priv->windows; l; l = g_list_next(l)) { + WnckWindow *window = WNCK_WINDOW(l->data); + if (wnck_window_is_most_recently_activated(window) + || wnck_window_transient_is_most_recently_activated(window)) { + // This is the currently focused window, let's focus the next in the list + GList *n = l->next ? g_list_next(l) : self->priv->windows; + window = WNCK_WINDOW(n->data); + wnck_window_activate_transient(window, event->time); + found_window = TRUE; + break; + } + } + if (!found_window) { + WnckWindow *window = WNCK_WINDOW(self->priv->windows->data); + wnck_window_activate_transient(window, event->time); + } + } else { + // There is only one window in this group + WnckWindow *window = WNCK_WINDOW(self->priv->windows->data); + if (wnck_window_is_minimized(window)) { + wnck_window_unminimize(window, event->time); + } else if (wnck_window_is_most_recently_activated(window) + || wnck_window_transient_is_most_recently_activated(window)) { + wnck_window_minimize(window); + } else { + wnck_window_activate_transient(window, event->time); + } + } + } else if (self->priv->appinfo) { + // No windows, but we have an application to launch! + GError *error = NULL; + if (!g_app_info_launch(G_APP_INFO(self->priv->appinfo), NULL, NULL, &error)) { + GtkWidget *msg; + + g_warning("Cannot launch '%s': %s", + g_app_info_get_display_name(G_APP_INFO(self->priv->appinfo)), + error->message); + + msg = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(widget)), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_OK, + "Cannot launch '%s': %s", + g_app_info_get_display_name(G_APP_INFO(self->priv->appinfo)), + error->message); + + g_signal_connect_swapped(msg, "response", + G_CALLBACK(gtk_widget_destroy), msg); + + gtk_widget_show_all(msg); + g_error_free(error); + } + } else { + g_warning("A MDockItem has no windows and no appinfo to launch"); + } + + break; + + case 3: + gtk_menu_popup(self->priv->menu, + NULL, NULL, NULL, NULL, + event->button, event->time); + break; + } + + return TRUE; +} + +static gboolean mdock_item_expose(GtkWidget *widget, GdkEventExpose *event) +{ + MDockItem *self = MDOCK_ITEM(widget); + cairo_t *cr = gdk_cairo_create(widget->window); + + if (self->priv->icon) { + gdk_cairo_set_source_pixbuf(cr, self->priv->icon, 0, 0); + cairo_paint(cr); + } + + cairo_destroy(cr); + + return FALSE; +} + +static void mdock_item_set_property(GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + MDockItem *self = MDOCK_ITEM(object); + switch (property_id) { + case PROP_TYPE: + self->priv->type = g_value_get_enum(value); + mdock_item_update_icon(self); + break; + case PROP_DESKTOP_APP_INFO: + self->priv->appinfo = g_value_dup_object(value); + mdock_item_update_icon(self); + mdock_item_update_tooltip(self); + break; + case PROP_ICON_SIZE: + self->priv->icon_size = g_value_get_uint(value); + gtk_widget_queue_resize(GTK_WIDGET(self)); + mdock_item_update_icon(self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void mdock_item_get_property(GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + MDockItem *self = MDOCK_ITEM(object); + switch (property_id) { + case PROP_TYPE: + g_value_set_enum(value, self->priv->type); + break; + case PROP_DESKTOP_APP_INFO: + g_value_set_object(value, self->priv->appinfo); + break; + case PROP_ICON_SIZE: + g_value_set_uint(value, self->priv->icon_size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void mdock_item_dispose(GObject *object) +{ + MDockItem *self = MDOCK_ITEM(object); + g_clear_object(&self->priv->appinfo); + g_clear_object(&self->priv->icon); + g_clear_object(&self->priv->menu); + g_clear_object(&self->priv->menu_close_all); + g_clear_object(&self->priv->menu_pin); + g_clear_object(&self->priv->menu_launch); + G_OBJECT_CLASS(mdock_item_parent_class)->dispose(object); +} + +static void mdock_item_finalize(GObject *object) +{ + MDockItem *self = MDOCK_ITEM(object); + g_list_free(self->priv->windows); + G_OBJECT_CLASS(mdock_item_parent_class)->finalize(object); +} + +static void mdock_item_init(MDockItem *self) { self->priv = MDOCK_ITEM_GET_PRIVATE (self); + self->priv->type = MDOCK_ITEM_TYPE_APPLICATION; + self->priv->icon_size = 48; + + gtk_widget_add_events(GTK_WIDGET(self), GDK_BUTTON_PRESS_MASK); + + self->priv->menu = g_object_ref_sink(gtk_menu_new()); + + self->priv->menu_close_all = g_object_ref_sink(gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL)); + gtk_menu_shell_append(GTK_MENU_SHELL(self->priv->menu), + GTK_WIDGET(self->priv->menu_close_all)); + g_signal_connect_swapped(self->priv->menu_close_all, "activate", + G_CALLBACK(mdock_item_close_all_windows), self); + + self->priv->menu_pin = g_object_ref_sink(gtk_image_menu_item_new_with_mnemonic("_Pin")); + gtk_menu_shell_append(GTK_MENU_SHELL(self->priv->menu), + GTK_WIDGET(self->priv->menu_pin)); + + self->priv->menu_launch = g_object_ref_sink(gtk_image_menu_item_new_with_mnemonic("_New instance")); + gtk_menu_shell_append(GTK_MENU_SHELL(self->priv->menu), + GTK_WIDGET(self->priv->menu_launch)); +} + +static void +mdock_item_class_init(MDockItemClass *klass) +{ + GObjectClass *obj_class = G_OBJECT_CLASS (klass); + obj_class->set_property = mdock_item_set_property; + obj_class->get_property = mdock_item_get_property; + obj_class->dispose = mdock_item_dispose; + obj_class->finalize = mdock_item_finalize; + + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + widget_class->realize = mdock_item_realize; + widget_class->size_request = mdock_item_size_request; + widget_class->size_allocate = mdock_item_size_allocate; + widget_class->button_press_event = mdock_item_button_press; + widget_class->expose_event = mdock_item_expose; + + obj_properties[PROP_TYPE] = g_param_spec_enum("type", + "Type of this dock item", + "Set the type of this dock item", + M_TYPE_DOCK_ITEM_TYPE, + MDOCK_ITEM_TYPE_APPLICATION, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DESKTOP_APP_INFO] = g_param_spec_object("desktop-app-info", + "The GDesktopAppInfo this application or launcher refers to", + "Set the GDesktopAppInfo this application or launcher refers to", + G_TYPE_DESKTOP_APP_INFO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ICON_SIZE] = g_param_spec_uint("icon-size", + "Icon size", + "Set the icon size", + 16, 256, + 48, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(obj_class, N_PROPERTIES, obj_properties); + + g_type_class_add_private (obj_class, sizeof (MDockItemPrivate)); +} + +MDockItem *mdock_item_new(MDockItemType type) +{ + return g_object_new(MDOCK_TYPE_ITEM, "type", type, NULL); +} + +MDockItemType mdock_item_get_item_type(MDockItem *self) +{ + return self->priv->type; +} + +void mdock_item_set_item_type(MDockItem *self, MDockItemType type) +{ + g_object_set(self, "type", type, NULL); +} + +GDesktopAppInfo *mdock_item_get_desktop_app_info(MDockItem *self) +{ + return g_object_ref(self->priv->appinfo); +} + +void mdock_item_set_desktop_app_info(MDockItem *self, GDesktopAppInfo *app_info) +{ + g_object_set(self, "desktop-app-info", app_info, NULL); +} + +void mdock_item_add_window(MDockItem *self, WnckWindow *window) +{ + self->priv->windows = g_list_append(self->priv->windows, window); + if (!self->priv->icon) { + mdock_item_update_icon(self); + } + if (!gtk_widget_get_has_tooltip(GTK_WIDGET(self))) { + mdock_item_update_tooltip(self); + } + mdock_item_update_menu(self); +} + +void mdock_item_remove_window(MDockItem *self, WnckWindow *window) +{ + self->priv->windows = g_list_remove(self->priv->windows, window); + mdock_item_update_menu(self); +} + +void mdock_item_set_last_active_window(MDockItem *self, WnckWindow *window) +{ +#if 0 /* Reorder the list of windows; put most recently used windows first. */ + GList *l = g_list_find(self->priv->windows, window); + g_return_if_fail(l != NULL); + + if (self->priv->windows == l) { + return; + } + + self->priv->windows = g_list_remove_link(self->priv->windows, l); + self->priv->windows = g_list_concat(l, self->priv->windows); +#endif +} + +gint mdock_item_get_num_windows(MDockItem *self) +{ + return g_list_length(self->priv->windows); } -MDockItem * -mdock_item_new () +void mdock_item_close_all_windows(MDockItem *self) { - return g_object_new (MDOCK_TYPE_ITEM, NULL); + for (GList *l = self->priv->windows; l; l = g_list_next(l)) { + WnckWindow *window = WNCK_WINDOW(l->data); + wnck_window_close(window, GDK_CURRENT_TIME); + } } diff --git a/libmdock/mdock-item.h b/libmdock/mdock-item.h index c934cf4..619f2c8 100644 --- a/libmdock/mdock-item.h +++ b/libmdock/mdock-item.h @@ -21,6 +21,9 @@ #define __MDOCK_ITEM_H__ #include +#include +#define WNCK_I_KNOW_THIS_IS_UNSTABLE 1 +#include G_BEGIN_DECLS @@ -38,21 +41,36 @@ typedef struct _MDockItemPrivate MDockItemPrivate; typedef enum { MDOCK_ITEM_TYPE_APPLICATION, - MDOCK_ITEM_TYPE_LAUNCHER + MDOCK_ITEM_TYPE_LAUNCHER, + MDOCK_ITEM_TYPE_SEPARATOR, + MDOCK_ITEM_TYPE_MINIMIZED_WINDOW } MDockItemType; struct _MDockItem { - GtkBin parent; + GtkWidget parent; MDockItemPrivate *priv; }; struct _MDockItemClass { - GtkBinClass parent_class; + GtkWidgetClass parent_class; }; -GType mdock_item_get_type (void) G_GNUC_CONST; -MDockItem *mdock_item_new (void); +GType mdock_item_get_type(void) G_GNUC_CONST; +MDockItem *mdock_item_new(MDockItemType type); + +MDockItemType mdock_item_get_item_type(MDockItem *self); +void mdock_item_set_item_type(MDockItem *self, MDockItemType type); + +GDesktopAppInfo *mdock_item_get_desktop_app_info(MDockItem *self); +void mdock_item_set_desktop_app_info(MDockItem *self, GDesktopAppInfo *app_info); + +void mdock_item_add_window(MDockItem *self, WnckWindow *window); +void mdock_item_remove_window(MDockItem *self, WnckWindow *window); +void mdock_item_set_last_active_window(MDockItem *self, WnckWindow *window); +gint mdock_item_get_num_windows(MDockItem *self); + +void mdock_item_close_all_windows(MDockItem *self); G_END_DECLS diff --git a/libmdock/mdock-widget.c b/libmdock/mdock-widget.c index defca10..a5d1307 100644 --- a/libmdock/mdock-widget.c +++ b/libmdock/mdock-widget.c @@ -28,22 +28,61 @@ struct _MDockWidgetPrivate { + gchar *settings_path; + GSettings *settings; WnckScreen *wnck_screen; - GSequence *groups; + GSequence *items; + GHashTable *appid_to_item; + GHashTable *desktopid_to_item; + GHashTable *window_to_item; }; G_DEFINE_TYPE(MDockWidget, mdock_widget, GTK_TYPE_BOX) #define MDOCK_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), MDOCK_TYPE_WIDGET, MDockWidgetPrivate)) +enum { + PROP_0, + PROP_SETTINGS_PATH, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL }; + static void handle_active_window_changed(MDockWidget *self, WnckWindow *previous_window, WnckScreen *screen) { + WnckWindow *window = wnck_screen_get_active_window(screen); + GSequenceIter *iter = g_hash_table_lookup(self->priv->window_to_item, window); + if (iter) { + MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); + mdock_item_set_last_active_window(item, window); + } +} +static gboolean filter_deleted_group(gpointer key, gpointer value, gpointer user_data) +{ + return value == user_data; } static void handle_window_closed(MDockWidget *self, WnckWindow *window, WnckScreen *screen) { + GSequenceIter *iter = g_hash_table_lookup(self->priv->window_to_item, window); + if (!iter) { + // We never managed this window + return; + } + + MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); + mdock_item_remove_window(item, window); + g_hash_table_remove(self->priv->window_to_item, window); + if (mdock_item_get_num_windows(item) == 0) { + // Removing window! + g_hash_table_foreach_remove(self->priv->appid_to_item, filter_deleted_group, iter); + g_hash_table_foreach_remove(self->priv->desktopid_to_item, filter_deleted_group, iter); + g_sequence_remove(iter); + gtk_widget_destroy(GTK_WIDGET(item)); + } } static void handle_window_opened(MDockWidget *self, WnckWindow *window, WnckScreen *screen) @@ -51,16 +90,90 @@ static void handle_window_opened(MDockWidget *self, WnckWindow *window, WnckScre if (wnck_window_is_skip_tasklist(window)) { return; } - g_debug("Window opened: %s", wnck_window_get_name(window)); + AppId *appid = app_id_from_window(window); - g_debug("%s %d %s %s %s", appid->host, appid->uid, appid->executable, appid->wm_class_class, appid->wm_class_name); + GSequenceIter *iter = g_hash_table_lookup(self->priv->appid_to_item, appid); + if (iter) { + MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); + mdock_item_add_window(item, window); + app_id_destroy(appid); + g_hash_table_insert(self->priv->window_to_item, window, iter); + return; + } + const gchar *desktopid = match_appid_to_desktopid(appid); - g_debug("desktopid: %s", desktopid); + if (desktopid) { + iter = g_hash_table_lookup(self->priv->desktopid_to_item, desktopid); + if (iter) { + MDockItem *item = MDOCK_ITEM(g_sequence_get(iter)); + mdock_item_add_window(item, window); + g_hash_table_insert(self->priv->appid_to_item, appid, iter); // takes ownership of appid + g_hash_table_insert(self->priv->window_to_item, window, iter); + return; + } + } + + MDockItem *item = mdock_item_new(MDOCK_ITEM_TYPE_APPLICATION); + iter = g_sequence_append(self->priv->items, item); + if (desktopid) { + GDesktopAppInfo *app_info = g_desktop_app_info_new(desktopid); + mdock_item_set_desktop_app_info(item, app_info); + g_object_unref(app_info); + } + + g_settings_bind(self->priv->settings, "icon-size", + item, "icon-size", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + mdock_item_add_window(item, window); + + g_hash_table_insert(self->priv->appid_to_item, appid, iter); // takes ownership of appid + if (desktopid) { + g_hash_table_insert(self->priv->desktopid_to_item, g_strdup(desktopid), iter); + } + g_hash_table_insert(self->priv->window_to_item, window, iter); + + gtk_widget_show(GTK_WIDGET(item)); + gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(item), TRUE, FALSE, 0); +} + +static void mdock_widget_constructed(GObject *obj) +{ + MDockWidget *self = MDOCK_WIDGET(obj); + + g_debug("Constructing with path: %s", self->priv->settings_path); + self->priv->settings = g_settings_new_with_path("com.javispedro.mdock.widget", + self->priv->settings_path); + + self->priv->wnck_screen = wnck_screen_get_default(); + g_signal_connect_object(self->priv->wnck_screen, "active-window-changed", + G_CALLBACK(handle_active_window_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object(self->priv->wnck_screen, "window-closed", + G_CALLBACK(handle_window_closed), self, G_CONNECT_SWAPPED); + g_signal_connect_object(self->priv->wnck_screen, "window-opened", + G_CALLBACK(handle_window_opened), self, G_CONNECT_SWAPPED); + + self->priv->items = g_sequence_new(NULL); + self->priv->appid_to_item = g_hash_table_new_full(app_id_hash, app_id_equal, + (GDestroyNotify)app_id_destroy, NULL); + self->priv->desktopid_to_item = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + self->priv->window_to_item = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, NULL); + + GList *windows = wnck_screen_get_windows_stacked(self->priv->wnck_screen); + for (GList *l = windows; l; l = g_list_next(l)) { + WnckWindow *window = l->data; + handle_window_opened(self, window, self->priv->wnck_screen); + } } static void mdock_widget_dispose(GObject *obj) { MDockWidget *self = MDOCK_WIDGET(obj); + if (self->priv->settings) { + g_clear_object(&self->priv->settings); + } if (self->priv->wnck_screen) { g_signal_handlers_disconnect_by_data(self->priv->wnck_screen, self); self->priv->wnck_screen = NULL; @@ -68,38 +181,72 @@ static void mdock_widget_dispose(GObject *obj) G_OBJECT_CLASS(mdock_widget_parent_class)->dispose(obj); } -static void mdock_widget_class_init(MDockWidgetClass *klass) +static void mdock_widget_finalize(GObject *obj) { - // GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + MDockWidget *self = MDOCK_WIDGET(obj); + g_free(self->priv->settings_path); + g_sequence_free(self->priv->items); + g_hash_table_destroy(self->priv->appid_to_item); + g_hash_table_destroy(self->priv->desktopid_to_item); + g_hash_table_destroy(self->priv->window_to_item); + G_OBJECT_CLASS(mdock_widget_parent_class)->dispose(obj); +} - GObjectClass *obj_class = G_OBJECT_CLASS(klass); - obj_class->dispose = mdock_widget_dispose; +static void mdock_widget_set_property(GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + MDockWidget *self = MDOCK_WIDGET(object); + switch (property_id) { + case PROP_SETTINGS_PATH: + self->priv->settings_path = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} - g_type_class_add_private(klass, sizeof(MDockWidgetPrivate)); +static void mdock_widget_get_property(GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + MDockWidget *self = MDOCK_WIDGET(object); + switch (property_id) { + case PROP_SETTINGS_PATH: + g_value_set_string(value, self->priv->settings_path); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } } static void mdock_widget_init(MDockWidget *self) { self->priv = MDOCK_WIDGET_GET_PRIVATE(self); +} - self->priv->wnck_screen = wnck_screen_get_default(); - g_signal_connect_object(self->priv->wnck_screen, "active-window-changed", - G_CALLBACK(handle_active_window_changed), self, G_CONNECT_SWAPPED); - g_signal_connect_object(self->priv->wnck_screen, "window-closed", - G_CALLBACK(handle_window_closed), self, G_CONNECT_SWAPPED); - g_signal_connect_object(self->priv->wnck_screen, "window-opened", - G_CALLBACK(handle_window_opened), self, G_CONNECT_SWAPPED); +static void mdock_widget_class_init(MDockWidgetClass *klass) +{ + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->constructed = mdock_widget_constructed; + obj_class->dispose = mdock_widget_dispose; + obj_class->finalize = mdock_widget_finalize; + obj_class->set_property = mdock_widget_set_property; + obj_class->get_property = mdock_widget_get_property; - self->priv->groups = g_sequence_new(NULL); + obj_properties[PROP_SETTINGS_PATH] = g_param_spec_string("settings-path", + "Path to GSettings for this widget", + "Set the path to GSettings for this widget", + "/com/javispedro/mdock/standalone/", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_debug("widget init"); + g_object_class_install_properties(obj_class, N_PROPERTIES, obj_properties); - GList *windows = wnck_screen_get_windows_stacked(self->priv->wnck_screen); - for (GList *l = g_list_first(windows); l; l = g_list_next(l)) { - g_debug("list"); - WnckWindow *window = l->data; - handle_window_opened(self, window, self->priv->wnck_screen); - } + g_type_class_add_private(klass, sizeof(MDockWidgetPrivate)); } GtkWidget *mdock_widget_new(void) diff --git a/test/mdock-standalone.c b/test/mdock-standalone.c index fee91a6..d977dea 100644 --- a/test/mdock-standalone.c +++ b/test/mdock-standalone.c @@ -29,6 +29,7 @@ static void construct_main_window() mainwin = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); mdock = MDOCK_WIDGET(mdock_widget_new()); gtk_container_add(GTK_CONTAINER(mainwin), GTK_WIDGET(mdock)); + gtk_window_set_title(mainwin, "MDock standalone"); } int main(int argc, char **argv) @@ -39,6 +40,8 @@ int main(int argc, char **argv) g_signal_connect(mainwin, "destroy", G_CALLBACK(gtk_main_quit), NULL); + gtk_window_set_keep_above(mainwin, TRUE); + gtk_window_set_skip_taskbar_hint(mainwin, TRUE); gtk_widget_show_all(GTK_WIDGET(mainwin)); gtk_main(); -- cgit v1.2.3