diff options
Diffstat (limited to 'libtopmenu-common')
-rw-r--r-- | libtopmenu-common/Makefile.am | 17 | ||||
-rw-r--r-- | libtopmenu-common/topmenu-background.c | 365 | ||||
-rw-r--r-- | libtopmenu-common/topmenu-background.h | 74 |
3 files changed, 456 insertions, 0 deletions
diff --git a/libtopmenu-common/Makefile.am b/libtopmenu-common/Makefile.am new file mode 100644 index 0000000..7316058 --- /dev/null +++ b/libtopmenu-common/Makefile.am @@ -0,0 +1,17 @@ +if GTK3 + +lib_LTLIBRARIES = libtopmenu-common-gtk3.la +libtopmenu_common_gtk3_la_SOURCES = topmenu-background.c topmenu-background.h +libtopmenu_common_gtk3_la_CPPFLAGS = $(GTK_CFLAGS) -DG_LOG_DOMAIN=\"topmenu-common\" +libtopmenu_common_gtk3_la_LIBADD = $(GTK_LIBS) + +else + +lib_LTLIBRARIES = libtopmenu-common-gtk2.la +libtopmenu_common_gtk2_la_SOURCES = topmenu-background.c topmenu-background.h +libtopmenu_common_gtk2_la_CPPFLAGS = $(GTK_CFLAGS) -DG_LOG_DOMAIN=\"topmenu-common\" +libtopmenu_common_gtk2_la_LIBADD = $(GTK_LIBS) + +endif + +include_HEADERS = topmenu-background.h diff --git a/libtopmenu-common/topmenu-background.c b/libtopmenu-common/topmenu-background.c new file mode 100644 index 0000000..99d36e9 --- /dev/null +++ b/libtopmenu-common/topmenu-background.c @@ -0,0 +1,365 @@ +/* + * Copyright 2014-2015 Javier S. Pedro <dev.git@javispedro.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <cairo-xlib.h> +#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]; + +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) +{ + if (self->priv->has_color + && self->priv->red == red && self->priv->green == green + && self->priv->blue == blue && self->priv->alpha == alpha) { + return; // Nothing to do + } + + 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"); + 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; + g_debug("received color %.2f,%.2f,%.2f,%.2f", red, green, blue, alpha); + topmenu_background_set_from_color(self, red, green, blue, alpha); + } else if (event->data.l[2] != None) { + g_debug("received pixmap 0x%lx", event->data.l[2]); + topmenu_background_set_from_drawable(self, event->data.l[2]); + } else { + g_debug("received nothing"); + 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 + g_debug("background set not yet supported on Gtk+ 3"); +#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); + } + } + + gtk_widget_queue_draw(widget); +#endif +} + +void topmenu_background_connect(TopMenuBackground *self, GtkWidget *widget) +{ + g_signal_connect(self, "changed", + G_CALLBACK(topmenu_background_apply), widget); +} + +void topmenu_background_disconnect(TopMenuBackground *self, GtkWidget *widget) +{ + g_signal_handlers_disconnect_by_func(self, + G_CALLBACK(topmenu_background_apply), widget); +} diff --git a/libtopmenu-common/topmenu-background.h b/libtopmenu-common/topmenu-background.h new file mode 100644 index 0000000..aca72f8 --- /dev/null +++ b/libtopmenu-common/topmenu-background.h @@ -0,0 +1,74 @@ +/* + * Copyright 2014-2015 Javier S. Pedro <dev.git@javispedro.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _TOPMENU_BACKGROUND_H_ +#define _TOPMENU_BACKGROUND_H_ + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +G_BEGIN_DECLS + +#define TOPMENU_TYPE_BACKGROUND topmenu_background_get_type() +#define TOPMENU_BACKGROUND(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TOPMENU_TYPE_BACKGROUND, TopMenuBackground)) +#define TOPMENU_IS_BACKGROUND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TOPMENU_TYPE_BACKGROUND)) +#define TOPMENU_BACKGROUND_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), TOPMENU_TYPE_BACKGROUND, TopMenuBackgroundClass)) +#define TOPMENU_IS_BACKGROUND_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE((c), TOPMENU_TYPE_BACKGROUND)) +#define TOPMENU_BACKGROUND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TOPMENU_TYPE_BACKGROUND, TopMenuBackgroundClass)) + +typedef struct _TopMenuBackground TopMenuBackground; +typedef struct _TopMenuBackgroundClass TopMenuBackgroundClass; +typedef struct _TopMenuBackgroundPrivate TopMenuBackgroundPrivate; + +struct _TopMenuBackground +{ + GObject parent_instance; + TopMenuBackgroundPrivate *priv; +}; + +struct _TopMenuBackgroundClass +{ + GObjectClass parent_class; +}; + +GType topmenu_background_get_type(void) G_GNUC_CONST; + +TopMenuBackground * topmenu_background_new(void); + +void topmenu_background_set_from_color(TopMenuBackground *self, double red, double green, double blue, double alpha); +void topmenu_background_set_from_drawable(TopMenuBackground *self, Drawable drawable); +void topmenu_background_set_from_cairo_pattern(TopMenuBackground *self, cairo_pattern_t *pattern); +void topmenu_background_clear(TopMenuBackground *self); + +gboolean topmenu_background_get_color(TopMenuBackground *self, double *red, double *green, double *blue, double *alpha); +gboolean topmenu_background_get_pixmap(TopMenuBackground *self, Pixmap *pixmap); + +cairo_pattern_t * topmenu_background_get_cairo_pattern(TopMenuBackground *self); + +void topmenu_background_fill_client_message(TopMenuBackground *self, XClientMessageEvent *event); +void topmenu_background_set_from_client_message(TopMenuBackground *self, XClientMessageEvent *event); + +void topmenu_background_apply(TopMenuBackground *self, GtkWidget *widget); + +void topmenu_background_connect(TopMenuBackground *self, GtkWidget *widget); +void topmenu_background_disconnect(TopMenuBackground *self, GtkWidget *widget); + +G_END_DECLS + +#endif |