aboutsummaryrefslogtreecommitdiff
path: root/libtopmenu-common/topmenu-background.c
diff options
context:
space:
mode:
Diffstat (limited to 'libtopmenu-common/topmenu-background.c')
-rw-r--r--libtopmenu-common/topmenu-background.c365
1 files changed, 365 insertions, 0 deletions
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);
+}