/* * Copyright 2014-2015 Javier S. Pedro * * This file is part of TopMenu. * * TopMenu is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * TopMenu 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with TopMenu. If not, see . */ #include #include #include "topmenu-background.h" struct _TopMenuBackgroundPrivate { double red, green, blue, alpha; Pixmap pixmap; gint width, height, depth; gboolean has_color; }; enum { SIGNAL_CHANGED, N_SIGNALS }; G_DEFINE_TYPE(TopMenuBackground, topmenu_background, G_TYPE_OBJECT) #define TOPMENU_BACKGROUND_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), TOPMENU_TYPE_BACKGROUND, TopMenuBackgroundPrivate)) static guint signals[N_SIGNALS]; #if GTK_MAJOR_VERSION == 3 #define TOPMENU_BACKGROUND_CSS_STYLE_CLASS "topmenu-background" static GtkCssProvider *topmenu_provider; #endif static inline GdkDisplay * topmenu_background_get_display(TopMenuBackground *self) { return gdk_display_get_default(); } static gboolean topmenu_background_reset(TopMenuBackground *self) { gboolean had_contents = self->priv->has_color || self->priv->pixmap; self->priv->has_color = FALSE; if (self->priv->pixmap) { GdkDisplay *display = topmenu_background_get_display(self); XFreePixmap(GDK_DISPLAY_XDISPLAY(display), self->priv->pixmap); self->priv->pixmap = None; } return had_contents; } static void topmenu_background_finalize(GObject *obj) { TopMenuBackground *self = TOPMENU_BACKGROUND(obj); topmenu_background_clear(self); G_OBJECT_CLASS(topmenu_background_parent_class)->finalize(obj); } static void topmenu_background_class_init(TopMenuBackgroundClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); obj_class->finalize = topmenu_background_finalize; signals[SIGNAL_CHANGED] = g_signal_newv("changed", G_TYPE_FROM_CLASS(obj_class), G_SIGNAL_NO_RECURSE, NULL, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); g_type_class_add_private(klass, sizeof(TopMenuBackgroundPrivate)); } static void topmenu_background_init(TopMenuBackground *self) { self->priv = TOPMENU_BACKGROUND_GET_PRIVATE(self); } TopMenuBackground *topmenu_background_new(void) { return TOPMENU_BACKGROUND(g_object_new(TOPMENU_TYPE_BACKGROUND, NULL)); } void topmenu_background_set_from_color(TopMenuBackground *self, double red, double green, double blue, double alpha) { topmenu_background_reset(self); self->priv->has_color = TRUE; self->priv->red = red; self->priv->green = green; self->priv->blue = blue; self->priv->alpha = alpha; g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } void topmenu_background_set_from_drawable(TopMenuBackground *self, Drawable drawable) { gboolean had_contents = topmenu_background_reset(self); g_return_if_fail(drawable); GdkDisplay *display = topmenu_background_get_display(self); GdkScreen *screen = gdk_display_get_default_screen(display); Display *xdpy = GDK_DISPLAY_XDISPLAY(display); int err; gdk_error_trap_push(); Window root; int x, y; unsigned int width, height, border_width, depth; if (!XGetGeometry(xdpy, drawable, &root, &x, &y, &width, &height, &border_width, &depth)) { g_debug("could not get geometry of drawable: 0x%lx", drawable); goto err_out; } self->priv->width = width; self->priv->height = height; self->priv->depth = depth; cairo_surface_t *orig_surface = cairo_xlib_surface_create(xdpy, drawable, GDK_VISUAL_XVISUAL(gdk_screen_get_system_visual(screen)), width, height); if (!orig_surface) { g_debug("could not create orig surface"); goto err_out; } self->priv->pixmap = XCreatePixmap(xdpy, drawable, width, height, depth); cairo_surface_t *new_surface = cairo_xlib_surface_create(xdpy, self->priv->pixmap, GDK_VISUAL_XVISUAL(gdk_screen_get_system_visual(screen)), width, height); if (!new_surface) { g_debug("could not create new surface"); XFreePixmap(xdpy, self->priv->pixmap); goto err_out; } cairo_t *cr = cairo_create(new_surface); cairo_set_source_surface(cr, orig_surface, 0, 0); cairo_paint(cr); cairo_destroy(cr); cairo_surface_destroy(new_surface); cairo_surface_destroy(orig_surface); err_out: err = gdk_error_trap_pop(); if (err) g_debug("X error: %d", err); if (had_contents || self->priv->pixmap) { g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } } void topmenu_background_set_from_cairo_pattern(TopMenuBackground *self, cairo_pattern_t *pattern) { if (!pattern) { topmenu_background_clear(self); return; } gboolean had_contents = topmenu_background_reset(self); cairo_surface_t *surface; double red, green, blue, alpha; if (cairo_pattern_get_rgba(pattern, &red, &green, &blue, &alpha) == CAIRO_STATUS_SUCCESS) { topmenu_background_set_from_color(self, red, green, blue, alpha); } else if (cairo_pattern_get_surface(pattern, &surface) == CAIRO_STATUS_SUCCESS) { cairo_surface_type_t type = cairo_surface_get_type(surface); if (type == CAIRO_SURFACE_TYPE_XLIB) { Drawable drawable = cairo_xlib_surface_get_drawable(surface); topmenu_background_set_from_drawable(self, drawable); return; } else { g_warning("no idea how to handle this cairo pattern type"); } } else { g_warning("no idea how to handle this cairo surface type"); } if (had_contents) { g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } } void topmenu_background_clear(TopMenuBackground *self) { if (topmenu_background_reset(self)) { g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } } gboolean topmenu_background_get_color(TopMenuBackground *self, double *red, double *green, double *blue, double *alpha) { if (self->priv->has_color) { if (red) *red = self->priv->red; if (green) *green = self->priv->green; if (blue) *blue = self->priv->blue; if (alpha) *alpha = self->priv->alpha; return TRUE; } else { return FALSE; } } gboolean topmenu_background_get_pixmap(TopMenuBackground *self, Pixmap *pixmap) { if (self->priv->pixmap) { if (pixmap) *pixmap = self->priv->pixmap; return TRUE; } else { return FALSE; } } cairo_pattern_t * topmenu_background_get_cairo_pattern(TopMenuBackground *self) { if (self->priv->has_color) { return cairo_pattern_create_rgba(self->priv->red, self->priv->green, self->priv->blue, self->priv->alpha); } else if (self->priv->pixmap) { GdkDisplay *display = topmenu_background_get_display(self); GdkScreen *screen = gdk_display_get_default_screen(display); Display *xdpy = GDK_DISPLAY_XDISPLAY(display); cairo_surface_t * surface = cairo_xlib_surface_create(xdpy, self->priv->pixmap, GDK_VISUAL_XVISUAL(gdk_screen_get_system_visual(screen)), self->priv->width, self->priv->height); cairo_pattern_t * pattern = cairo_pattern_create_for_surface(surface); cairo_surface_destroy(surface); return pattern; } else { return NULL; } } void topmenu_background_fill_client_message(TopMenuBackground *self, XClientMessageEvent *event) { event->format = 32; event->data.l[0] = self->priv->has_color ? 1 : 0; if (self->priv->has_color) { // Encode color as a 32-bit RGBA value guint32 rgba = (lround(self->priv->red * 255.0) & 0xFF) << 24 | (lround(self->priv->green * 255.0) & 0xFF) << 16 | (lround(self->priv->blue * 255.0) & 0xFF) << 8 | (lround(self->priv->alpha * 255.0) & 0xFF) << 0; event->data.l[1] = rgba; } else { event->data.l[1] = 0; } event->data.l[2] = self->priv->pixmap; event->data.l[3] = 0; event->data.l[4] = 0; } void topmenu_background_set_from_client_message(TopMenuBackground *self, XClientMessageEvent *event) { g_return_if_fail(event->format == 32); g_warn_if_fail(event->data.l[3] == 0); if (event->data.l[0] & 1) { const guint32 rgba = event->data.l[1]; double red = ((rgba >> 24) & 0xFF) / 255.0, green = ((rgba >> 16) & 0xFF) / 255.0, blue = ((rgba >> 8) & 0xFF) / 255.0, alpha = ((rgba >> 0) & 0xFF) / 255.0; topmenu_background_set_from_color(self, red, green, blue, alpha); } else if (event->data.l[2] != None) { topmenu_background_set_from_drawable(self, event->data.l[2]); } else { topmenu_background_clear(self); } } #if GTK_MAJOR_VERSION == 2 static GdkPixmap * get_pixmap(GdkDisplay *display, Pixmap pixmap) { gdk_error_trap_push(); GdkPixmap *gp = gdk_pixmap_lookup_for_display(display, pixmap); if (!gp) { gp = gdk_pixmap_foreign_new_for_display(display, pixmap); } gdk_error_trap_pop(); return gp; } #endif void topmenu_background_apply(TopMenuBackground *self, GtkWidget *widget) { #if GTK_MAJOR_VERSION == 3 if (!topmenu_provider) { topmenu_provider = gtk_css_provider_new(); gtk_css_provider_load_from_data(topmenu_provider, "." TOPMENU_BACKGROUND_CSS_STYLE_CLASS " {\n" " background-color: rgba (0, 0, 0, 0);\n" " background-image: none;\n" "}", -1, NULL); } GtkStyleContext *context = gtk_widget_get_style_context(widget); GdkWindow *widget_window = gtk_widget_get_window(widget); double red, green, blue, alpha; Pixmap pixmap; if (topmenu_background_get_color(self, &red, &green, &blue, &alpha)) { gtk_style_context_add_class(context, TOPMENU_BACKGROUND_CSS_STYLE_CLASS); if (widget_window) { GdkRGBA color = {red, green, blue, alpha}; gdk_window_set_background_rgba(widget_window, &color); g_debug("do color"); } } else if (topmenu_background_get_pixmap(self, &pixmap)) { gtk_style_context_add_class(context, TOPMENU_BACKGROUND_CSS_STYLE_CLASS); if (widget_window) { cairo_pattern_t * pattern = topmenu_background_get_cairo_pattern(self); gdk_window_set_background_pattern(widget_window, pattern); cairo_pattern_destroy(pattern); } } else { gtk_style_context_remove_class(context, TOPMENU_BACKGROUND_CSS_STYLE_CLASS); } gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(topmenu_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); #elif GTK_MAJOR_VERSION == 2 // Clear current background GtkRcStyle *rc_style; gtk_widget_set_style(widget, NULL); rc_style = gtk_rc_style_new(); gtk_widget_modify_style(widget, rc_style); g_object_unref(rc_style); double red, green, blue, alpha; Pixmap pixmap; if (topmenu_background_get_color(self, &red, &green, &blue, &alpha)) { if (alpha >= 1.0) { // Opaque GdkColor color = { 0, red * 65535U, green * 65535U, blue * 65535U }; gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &color); } else { // TODO Alpha color g_warning("Alpha colors not yet supported (alpha=%f)", alpha); } } else if (topmenu_background_get_pixmap(self, &pixmap)) { GdkDisplay *display = gtk_widget_get_display(widget); GdkPixmap *gpixmap = get_pixmap(display, pixmap); if (gpixmap) { if (!gdk_drawable_get_colormap(gpixmap)) { gdk_drawable_set_colormap(GDK_DRAWABLE (gpixmap), gtk_widget_get_colormap(widget)); } GtkStyle *style = gtk_style_copy(gtk_widget_get_style(widget)); g_clear_object(&style->bg_pixmap[GTK_STATE_NORMAL]); style->bg_pixmap[GTK_STATE_NORMAL] = g_object_ref_sink(gpixmap); gtk_widget_set_style(widget, style); g_object_unref(style); } } #endif gtk_widget_queue_draw(widget); } void topmenu_background_unapply(TopMenuBackground *self, GtkWidget *widget) { #if GTK_MAJOR_VERSION == 3 GtkStyleContext *context = gtk_widget_get_style_context(widget); gtk_style_context_remove_class(context, TOPMENU_BACKGROUND_CSS_STYLE_CLASS); gtk_style_context_remove_provider(context, GTK_STYLE_PROVIDER(topmenu_provider)); #elif GTK_MAJOR_VERSION == 2 // Clear current background GtkRcStyle *rc_style; gtk_widget_set_style(widget, NULL); rc_style = gtk_rc_style_new(); gtk_widget_modify_style(widget, rc_style); g_object_unref(rc_style); #endif gtk_widget_queue_draw(widget); } void topmenu_background_connect(TopMenuBackground *self, GtkWidget *widget) { g_signal_connect(self, "changed", G_CALLBACK(topmenu_background_apply), widget); if (gtk_widget_get_realized(widget)) { topmenu_background_apply(self, widget); } } void topmenu_background_disconnect(TopMenuBackground *self, GtkWidget *widget) { g_signal_handlers_disconnect_by_func(self, G_CALLBACK(topmenu_background_apply), widget); }