/* * Copyright (C) 2022 Alberts Muktupāvels * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, see . */ #include #include #include #include #include #define WINDOW_NAME "Test Icon Cache" #define NET_WM_ICON_NAME "face-cool" #define WM_HINTS_ICON_NAME "face-monkey" typedef struct { GtkWidget *set_button; GtkWidget *nochange_button; GtkWidget *unset_button; GtkWidget *status_label; } RowData; typedef struct { Display *xdisplay; Window xwindow; Pixmap icon_pixmap; Pixmap icon_mask; } IconCacheTest; static void unset_net_wm_icon (IconCacheTest *self) { XDeleteProperty (self->xdisplay, self->xwindow, XInternAtom (self->xdisplay, "_NET_WM_ICON", False)); } static void set_net_wm_icon (IconCacheTest *self) { GdkPixbuf *pixbuf; int width; int height; int size; unsigned long *data; unsigned long *p; pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), NET_WM_ICON_NAME, 24, GTK_ICON_LOOKUP_FORCE_SIZE, NULL); width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); size = 2 + width * height; data = p = g_malloc (size * sizeof (unsigned long)); *p++ = width; *p++ = height; { unsigned char *pixels; int n_channels; int stride; int x; int y; pixels = gdk_pixbuf_get_pixels (pixbuf); n_channels = gdk_pixbuf_get_n_channels (pixbuf); stride = gdk_pixbuf_get_rowstride (pixbuf); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { unsigned char r; unsigned char g; unsigned char b; unsigned char a; r = pixels[y * stride + x * n_channels + 0]; g = pixels[y * stride + x * n_channels + 1]; b = pixels[y * stride + x * n_channels + 2]; if (n_channels >= 4) a = pixels[y * stride + x * n_channels + 3]; else a = 255; *p++ = a << 24 | r << 16 | g << 8 | b ; } } } XChangeProperty (self->xdisplay, self->xwindow, XInternAtom (self->xdisplay, "_NET_WM_ICON", False), XA_CARDINAL, 32, PropModeReplace, (unsigned char *) data, size); g_object_unref (pixbuf); g_free (data); } static void create_icon_pixmaps (IconCacheTest *self) { int screen; GdkPixbuf *pixbuf; cairo_surface_t *surface; cairo_t *cr; screen = DefaultScreen (self->xdisplay); pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), WM_HINTS_ICON_NAME, 24, GTK_ICON_LOOKUP_FORCE_SIZE, NULL); g_assert (self->icon_pixmap == None); self->icon_pixmap = XCreatePixmap (self->xdisplay, self->xwindow, 24, 24, XDefaultDepth (self->xdisplay, screen)); surface = cairo_xlib_surface_create (self->xdisplay, self->icon_pixmap, XDefaultVisual (self->xdisplay, screen), 24, 24); cr = cairo_create (surface); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); if (gdk_pixbuf_get_has_alpha (pixbuf)) { cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA); cairo_paint (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SATURATE); cairo_paint (cr); cairo_pop_group_to_source (cr); } cairo_paint (cr); cairo_destroy (cr); cairo_surface_destroy (surface); if (gdk_pixbuf_get_has_alpha (pixbuf)) { Screen *xscreen; g_assert (self->icon_mask == None); self->icon_mask = XCreatePixmap (self->xdisplay, self->xwindow, 24, 24, 1); xscreen = DefaultScreenOfDisplay (self->xdisplay); surface = cairo_xlib_surface_create_for_bitmap (self->xdisplay, self->icon_mask, xscreen, 24, 24); cr = cairo_create (surface); gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); cairo_destroy (cr); cairo_surface_destroy (surface); } g_object_unref (pixbuf); } static void free_icon_pixmaps (IconCacheTest *self) { if (self->icon_pixmap != None) { XFreePixmap (self->xdisplay, self->icon_pixmap); self->icon_pixmap = None; } if (self->icon_mask != None) { XFreePixmap (self->xdisplay, self->icon_mask); self->icon_mask = None; } } static void unset_wm_hints (IconCacheTest *self) { XWMHints *hints; hints = XGetWMHints (self->xdisplay, self->xwindow); if (hints == NULL) return; free_icon_pixmaps (self); hints->flags |= IconPixmapHint | IconMaskHint; hints->icon_pixmap = None; hints->icon_mask = None; XSetWMHints (self->xdisplay, self->xwindow, hints); XFree (hints); } static void set_wm_hints (IconCacheTest *self) { XWMHints *hints; hints = XGetWMHints (self->xdisplay, self->xwindow); if (hints == NULL) hints = XAllocWMHints (); free_icon_pixmaps (self); create_icon_pixmaps (self); hints->flags |= IconPixmapHint | IconMaskHint; hints->icon_pixmap = self->icon_pixmap; hints->icon_mask = self->icon_mask; XSetWMHints (self->xdisplay, self->xwindow, hints); XFree (hints); } static void unset_net_wm_icon_cb (GtkButton *button, IconCacheTest *self) { RowData *data; unset_net_wm_icon (self); data = g_object_get_data (G_OBJECT (button), "row-data"); gtk_label_set_text (GTK_LABEL (data->status_label), "✘"); gtk_widget_set_sensitive (data->unset_button, FALSE); } static void set_net_wm_icon_cb (GtkButton *button, IconCacheTest *self) { RowData *data; set_net_wm_icon (self); data = g_object_get_data (G_OBJECT (button), "row-data"); gtk_label_set_text (GTK_LABEL (data->status_label), "✔"); gtk_widget_set_sensitive (data->unset_button, TRUE); } static void unset_wm_hints_cb (GtkButton *button, IconCacheTest *self) { RowData *data; unset_wm_hints (self); data = g_object_get_data (G_OBJECT (button), "row-data"); gtk_label_set_text (GTK_LABEL (data->status_label), "✘"); gtk_widget_set_sensitive (data->unset_button, FALSE); } static void set_wm_hints_cb (GtkButton *button, IconCacheTest *self) { RowData *data; set_wm_hints (self); data = g_object_get_data (G_OBJECT (button), "row-data"); gtk_label_set_text (GTK_LABEL (data->status_label), "✔"); gtk_widget_set_sensitive (data->unset_button, TRUE); } static void urgency_cb (GtkButton *button, IconCacheTest *self) { XWMHints *hints; hints = XGetWMHints (self->xdisplay, self->xwindow); if (hints == NULL) hints = XAllocWMHints (); hints->flags ^= XUrgencyHint; XSetWMHints (self->xdisplay, self->xwindow, hints); XFree (hints); } static GtkWidget * append_icon_row (const char *name, const char *icon_name, GCallback set_cb, GCallback nochange_cb, GCallback unset_cb, IconCacheTest *self) { GtkWidget *row; RowData *data; GtkWidget *box; GtkWidget *label; GtkWidget *btn; GtkWidget *img; row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); data = g_new0 (RowData, 1); g_object_set_data_full (G_OBJECT (row), "row-data", data, g_free); label = gtk_label_new (name); gtk_box_pack_start (GTK_BOX (row), label, TRUE, TRUE, 0); gtk_label_set_xalign (GTK_LABEL (label), 1.); gtk_widget_show (label); label = data->status_label = gtk_label_new ("✘"); g_object_set_data (G_OBJECT (label), "row-data", data); gtk_box_pack_end (GTK_BOX (row), label, FALSE, FALSE, 0); gtk_label_set_xalign (GTK_LABEL (label), 1.); gtk_widget_show (label); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_box_pack_end (GTK_BOX (row), box, FALSE, FALSE, 0); gtk_widget_show (box); img = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR); gtk_container_add (GTK_CONTAINER (box), img); gtk_widget_show (img); btn = data->set_button = gtk_button_new_with_label ("set"); g_object_set_data (G_OBJECT (btn), "row-data", data); gtk_container_add (GTK_CONTAINER (box), btn); gtk_widget_show (btn); g_signal_connect (btn, "clicked", set_cb, self); btn = data->nochange_button = gtk_button_new_with_label ("nochange"); g_object_set_data (G_OBJECT (btn), "row-data", data); gtk_container_add (GTK_CONTAINER (box), btn); gtk_widget_show (btn); if (nochange_cb != NULL) g_signal_connect (btn, "clicked", nochange_cb, self); else gtk_widget_set_sensitive (btn, FALSE); btn = data->unset_button = gtk_button_new_with_label ("unset"); g_object_set_data (G_OBJECT (btn), "row-data", data); gtk_container_add (GTK_CONTAINER (box), btn); gtk_widget_set_sensitive (btn, FALSE); gtk_widget_show (btn); g_signal_connect (btn, "clicked", unset_cb, self); return row; } static void icon_changed_cb (WnckWindow *window, GtkImage *image) { GdkPixbuf *icon; icon = wnck_window_get_icon (window); gtk_image_set_from_pixbuf (image, icon); } static void mini_icon_changed_cb (WnckWindow *window, GtkImage *image) { GdkPixbuf *icon; icon = wnck_window_get_mini_icon (window); gtk_image_set_from_pixbuf (image, icon); } static void icon_changed_count_cb (WnckWindow *window, GtkLabel *label) { static int count = 0; char *text; text = g_strdup_printf ("%d", ++count); gtk_label_set_text (label, text); g_free (text); } static void append_result_box (GtkWidget *vbox, WnckWindow *window) { GtkWidget *box1; GtkWidget *box2; GtkWidget *image; GtkWidget *label; box1 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_box_pack_start (GTK_BOX (vbox), box1, TRUE, FALSE, 0); gtk_widget_show (box1); box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_box_pack_end (GTK_BOX (box1), box2, TRUE, FALSE, 0); gtk_widget_show (box2); image = gtk_image_new (); gtk_box_pack_start (GTK_BOX (box2), image, FALSE, FALSE, 0); gtk_widget_show (image); g_signal_connect (window, "icon-changed", G_CALLBACK (icon_changed_cb), image); icon_changed_cb (window, GTK_IMAGE (image)); image = gtk_image_new (); gtk_box_pack_start (GTK_BOX (box2), image, FALSE, FALSE, 0); gtk_widget_show (image); g_signal_connect (window, "icon-changed", G_CALLBACK (mini_icon_changed_cb), image); mini_icon_changed_cb (window, GTK_IMAGE (image)); label = gtk_label_new ("0"); gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0); gtk_widget_show (label); g_signal_connect (window, "icon-changed", G_CALLBACK (icon_changed_count_cb), label); } static void window_opened_cb (WnckScreen *screen, WnckWindow *window, GtkWidget *vbox) { const char *name; name = wnck_window_get_name (window); if (g_strcmp0 (name, WINDOW_NAME) != 0) return; append_result_box (vbox, window); } int main (int argc, char **argv) { WnckScreen *screen; GtkWidget *window; GtkWidget *vbox; GtkWidget *separator; GtkWidget *row; IconCacheTest self; gtk_init (&argc, &argv); screen = wnck_screen_get_default (); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), WINDOW_NAME); g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); g_object_set (G_OBJECT (vbox), "margin", 12, NULL); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); row = append_icon_row ("_NET_WM_ICON:", NET_WM_ICON_NAME, G_CALLBACK (set_net_wm_icon_cb), NULL, G_CALLBACK (unset_net_wm_icon_cb), &self); gtk_box_pack_start (GTK_BOX (vbox), row, FALSE, FALSE, 0); gtk_widget_show (row); row = append_icon_row ("WM_HINTS:", WM_HINTS_ICON_NAME, G_CALLBACK (set_wm_hints_cb), G_CALLBACK (urgency_cb), G_CALLBACK (unset_wm_hints_cb), &self); gtk_box_pack_start (GTK_BOX (vbox), row, FALSE, FALSE, 0); gtk_widget_show (row); separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_container_add (GTK_CONTAINER (vbox), separator); gtk_widget_show (separator); g_signal_connect (screen, "window-opened", G_CALLBACK (window_opened_cb), vbox); gtk_window_present (GTK_WINDOW (window)); self.xdisplay = gdk_x11_display_get_xdisplay (gdk_display_get_default ()); self.xwindow = gdk_x11_window_get_xid (gtk_widget_get_window (window)); self.icon_pixmap = None; self.icon_mask = None; gtk_main (); free_icon_pixmaps (&self); return EXIT_SUCCESS; }