summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2015-01-21 00:43:40 +0100
committerJavier <dev.git@javispedro.com>2015-01-21 00:43:40 +0100
commit5dbc254039d1fd79ad587c6c98e9e8428495a0de (patch)
treef5e66c95acbf4b692960701cfac7c89d75ee3f1c
parent8069ebf9a157ce0952fd9f3ed6a4edcdce071911 (diff)
downloadmdock-5dbc254039d1fd79ad587c6c98e9e8428495a0de.tar.gz
mdock-5dbc254039d1fd79ad587c6c98e9e8428495a0de.zip
right button menu and other features
-rw-r--r--libmdock/app-id.c16
-rw-r--r--libmdock/app-id.h2
-rw-r--r--libmdock/com.javispedro.mdock.widget.gschema.xml4
-rw-r--r--libmdock/matcher.c21
-rw-r--r--libmdock/mdock-item.c425
-rw-r--r--libmdock/mdock-item.h28
-rw-r--r--libmdock/mdock-widget.c195
-rw-r--r--test/mdock-standalone.c3
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 @@
<default>[]</default>
<summary>Items in the dock</summary>
</key>
+ <key name="icon-size" type="u">
+ <default>48</default>
+ <summary>Icon size</summary>
+ </key>
</schema>
</schemalist>
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 <http://www.gnu.org/licenses/>.
*/
+#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 <gtk/gtk.h>
+#include <gio/gdesktopappinfo.h>
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE 1
+#include <libwnck/libwnck.h>
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();