From 34ced542636123c2832b341593741d4ee0a413c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberts=20Muktup=C4=81vels?= Date: Fri, 18 Mar 2022 21:13:35 +0200 Subject: tests: add test-icon-cache tool - Set button always sets new icon. This should emit icon-changed signal unless there is higher priority icon already set. - Nochange button is used to cause PropertyNotify event that should not cause changes to icon and/or should not emit icon-changed signal. - Unset button is used to remove icon. This should emit icon-changed signal only if highest priority icon is unset. --- libwnck/meson.build | 1 + libwnck/test-icon-cache.c | 554 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 555 insertions(+) create mode 100644 libwnck/test-icon-cache.c diff --git a/libwnck/meson.build b/libwnck/meson.build index ddbff6e..9bc1739 100644 --- a/libwnck/meson.build +++ b/libwnck/meson.build @@ -161,6 +161,7 @@ test_progs = [ 'test-pager', 'test-urgent', 'test-shutdown', + 'test-icon-cache', ] foreach prog: progs + test_progs diff --git a/libwnck/test-icon-cache.c b/libwnck/test-icon-cache.c new file mode 100644 index 0000000..2f3fc38 --- /dev/null +++ b/libwnck/test-icon-cache.c @@ -0,0 +1,554 @@ +/* + * 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; +} -- cgit v1.2.1