Logo Search packages:      
Sourcecode: heliodor version File versions

main.c

/*
 * Copyright © 2006 Novell, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Author: David Reveman <davidr@novell.com> (gwd)
 * Beryl compatibility fixes and metacity theme restriction by Quinn Storm <quinn@novell.com>
 *   Misc fixes by others.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <beryl-decoration.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/extensions/Xrender.h>
#include <X11/Xregion.h>

#ifndef GTK_DISABLE_DEPRECATED
#define GTK_DISABLE_DEPRECATED
#endif

#include <gtk/gtk.h>
#include <gtk/gtkwindow.h>
#include <gdk/gdkx.h>

#include <gconf/gconf-client.h>

#define WNCK_I_KNOW_THIS_IS_UNSTABLE
#include <libwnck/libwnck.h>
#include <libwnck/window-action-menu.h>

#include <cairo.h>
#include <cairo-xlib.h>

#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 1, 0)
#define CAIRO_EXTEND_PAD CAIRO_EXTEND_NONE
#endif

#include <pango/pango-context.h>
#include <pango/pangocairo.h>

#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

#include <metacity-private/theme.h>

#define METACITY_GCONF_DIR "/apps/metacity/general"

#define COMPIZ_USE_SYSTEM_FONT_KEY            \
    METACITY_GCONF_DIR "/titlebar_uses_system_font"

#define COMPIZ_TITLEBAR_FONT_KEY    \
    METACITY_GCONF_DIR "/titlebar_font"

#define COMPIZ_DOUBLE_CLICK_TITLEBAR_KEY         \
    METACITY_GCONF_DIR "/action_double_click_titlebar"

#define COMPIZ_GCONF_DIR1 "/apps/compiz/plugins/decoration/allscreens/options"

#define COMPIZ_SHADOW_RADIUS_KEY \
    COMPIZ_GCONF_DIR1 "/shadow_radius"

#define COMPIZ_SHADOW_OPACITY_KEY \
    COMPIZ_GCONF_DIR1 "/shadow_opacity"

#define COMPIZ_SHADOW_COLOR_KEY \
    COMPIZ_GCONF_DIR1 "/shadow_color"

#define COMPIZ_SHADOW_OFFSET_X_KEY \
    COMPIZ_GCONF_DIR1 "/shadow_offset_x"

#define COMPIZ_SHADOW_OFFSET_Y_KEY \
    COMPIZ_GCONF_DIR1 "/shadow_offset_y"

#define META_AUDIBLE_BELL_KEY        \
    METACITY_GCONF_DIR "/audible_bell"

#define META_VISUAL_BELL_KEY        \
    METACITY_GCONF_DIR "/visual_bell"

#define META_VISUAL_BELL_TYPE_KEY      \
    METACITY_GCONF_DIR "/visual_bell_type"

#define META_THEME_KEY        \
    METACITY_GCONF_DIR "/theme"

#define COMPIZ_GCONF_DIR2 "/apps/compiz/general/allscreens/options"

#define COMPIZ_AUDIBLE_BELL_KEY           \
    COMPIZ_GCONF_DIR2 "/audible_bell"

#define COMPIZ_GCONF_DIR3 "/apps/compiz/plugins/fade/screen0/options"

#define COMPIZ_VISUAL_BELL_KEY           \
    COMPIZ_GCONF_DIR3 "/visual_bell"

#define COMPIZ_FULLSCREEN_VISUAL_BELL_KEY \
    COMPIZ_GCONF_DIR3 "/fullscreen_visual_bell"

#define GCONF_DIR "/apps/gwd"

#define META_THEME_OPACITY_KEY              \
    GCONF_DIR "/metacity_theme_opacity"

#define META_THEME_SHADE_OPACITY_KEY            \
    GCONF_DIR "/metacity_theme_shade_opacity"

#define META_THEME_ACTIVE_OPACITY_KEY            \
    GCONF_DIR "/metacity_theme_active_opacity"

#define META_THEME_ACTIVE_SHADE_OPACITY_KEY          \
    GCONF_DIR "/metacity_theme_active_shade_opacity"


#define STROKE_ALPHA 0.6

#define ICON_SPACE 20

#define DOUBLE_CLICK_DISTANCE 8.0

#define WM_MOVERESIZE_SIZE_TOPLEFT      0
#define WM_MOVERESIZE_SIZE_TOP          1
#define WM_MOVERESIZE_SIZE_TOPRIGHT     2
#define WM_MOVERESIZE_SIZE_RIGHT        3
#define WM_MOVERESIZE_SIZE_BOTTOMRIGHT  4
#define WM_MOVERESIZE_SIZE_BOTTOM       5
#define WM_MOVERESIZE_SIZE_BOTTOMLEFT   6
#define WM_MOVERESIZE_SIZE_LEFT         7
#define WM_MOVERESIZE_MOVE              8
#define WM_MOVERESIZE_SIZE_KEYBOARD     9
#define WM_MOVERESIZE_MOVE_KEYBOARD    10

#define SHADOW_RADIUS      8.0
#define SHADOW_OPACITY     0.5
#define SHADOW_OFFSET_X    1
#define SHADOW_OFFSET_Y    1
#define SHADOW_COLOR_RED   0x0000
#define SHADOW_COLOR_GREEN 0x0000
#define SHADOW_COLOR_BLUE  0x0000

#define META_OPACITY              0.75
#define META_SHADE_OPACITY        TRUE
#define META_ACTIVE_OPACITY       1.0
#define META_ACTIVE_SHADE_OPACITY TRUE

#define MWM_HINTS_DECORATIONS (1L << 1)

#define MWM_DECOR_ALL      (1L << 0)
#define MWM_DECOR_BORDER   (1L << 1)
#define MWM_DECOR_HANDLE   (1L << 2)
#define MWM_DECOR_TITLE    (1L << 3)
#define MWM_DECOR_MENU     (1L << 4)
#define MWM_DECOR_MINIMIZE (1L << 5)
#define MWM_DECOR_MAXIMIZE (1L << 6)

#define PROP_MOTIF_WM_HINT_ELEMENTS 3


typedef struct {
    unsigned long flags;
    unsigned long functions;
    unsigned long decorations;
} MwmHints;

enum {
    DOUBLE_CLICK_SHADE,
    DOUBLE_CLICK_MAXIMIZE
};

int double_click_action = DOUBLE_CLICK_SHADE;

static gboolean minimal = FALSE;

static double decoration_alpha = 0.5;

#define SWITCHER_SPACE 40

static decor_extents_t _shadow_extents      = { 0, 0, 0, 0 };
static decor_extents_t _win_extents         = { 6, 6, 4, 6 };
static decor_extents_t _max_win_extents     = { 6, 6, 4, 6 };
static decor_extents_t _default_win_extents = { 6, 6, 4, 6 };
static decor_extents_t _switcher_extents    = { 6, 6, 6, 6 + SWITCHER_SPACE };

static int titlebar_height = 17;
static int max_titlebar_height = 17;

static decor_context_t window_context = {
    { 0, 0, 0, 0 },
    6, 6, 4, 6,
    0, 0, 0, 0
};

static decor_context_t max_window_context = {
    { 0, 0, 0, 0 },
    6, 6, 4, 6,
    0, 0, 0, 0
};

static decor_context_t switcher_context = {
    { 0, 0, 0, 0 },
    6, 6, 6, 6 + SWITCHER_SPACE,
    0, 0, 0, 0
};

static decor_context_t shadow_context = {
    { 0, 0, 0, 0 },
    0, 0, 0, 0,
    0, 0, 0, 0,
};

static gdouble shadow_radius   = SHADOW_RADIUS;
static gdouble shadow_opacity  = SHADOW_OPACITY;
static gushort shadow_color[3] = {
    SHADOW_COLOR_RED,
    SHADOW_COLOR_GREEN,
    SHADOW_COLOR_BLUE
};
static gint    shadow_offset_x = SHADOW_OFFSET_X;
static gint    shadow_offset_y = SHADOW_OFFSET_Y;

static double   meta_opacity              = META_OPACITY;
static gboolean meta_shade_opacity        = META_SHADE_OPACITY;
static double   meta_active_opacity       = META_ACTIVE_OPACITY;
static gboolean meta_active_shade_opacity = META_ACTIVE_SHADE_OPACITY;

static decor_shadow_t *no_border_shadow = NULL;
static decor_shadow_t *border_shadow = NULL;
static decor_shadow_t *max_border_shadow = NULL;
static decor_shadow_t *switcher_shadow = NULL;

static GdkPixmap *decor_normal_pixmap = NULL;
static GdkPixmap *decor_active_pixmap = NULL;

static Atom frame_window_atom;
static Atom win_decor_atom;
static Atom wm_move_resize_atom;
static Atom restack_window_atom;
static Atom select_window_atom;
static Atom mwm_hints_atom;

static Atom toolkit_action_atom;
static Atom toolkit_action_main_menu_atom;
static Atom toolkit_action_run_dialog_atom;
static Atom toolkit_action_window_menu_atom;
static Atom toolkit_action_force_quit_dialog_atom;

static Atom panel_action_atom;
static Atom panel_action_main_menu_atom;
static Atom panel_action_run_dialog_atom;

static Time dm_sn_timestamp;

#define C(name) { 0, XC_ ## name }

static struct _cursor {
    Cursor   cursor;
    unsigned int shape;
} cursor[3][3] = {
    { C (top_left_corner),    C (top_side),    C (top_right_corner)    },
    { C (left_side),          C (left_ptr),    C (right_side)            },
    { C (bottom_left_corner), C (bottom_side), C (bottom_right_corner) }
};

typedef struct _decor_color {
    double r;
    double g;
    double b;
} decor_color_t;

#define IN_EVENT_WINDOW      (1 << 0)
#define PRESSED_EVENT_WINDOW (1 << 1)

typedef struct _decor {
    Window        event_windows[3][3];
    Window        button_windows[3];
    guint         button_states[3];
    GdkPixmap           *pixmap;
    GdkPixmap           *buffer_pixmap;
    GdkGC         *gc;
    decor_layout_t    border_layout;
    decor_context_t   *context;
    decor_shadow_t    *shadow;
    Picture       picture;
    gint          button_width;
    gint          width;
    gint          height;
    gboolean            decorated;
    gboolean            active;
    PangoLayout         *layout;
    gchar         *name;
    cairo_pattern_t   *icon;
    GdkPixmap           *icon_pixmap;
    GdkPixbuf           *icon_pixbuf;
    WnckWindowState   state;
    WnckWindowActions actions;
    XID                 prop_xid;
    GtkWidget           *force_quit_dialog;
    void          (*draw) (struct _decor *d);
} decor_t;

void     (*theme_draw_window_decoration)    (decor_t *d);
gboolean (*theme_calc_decoration_size)      (decor_t *d,
                                   int     client_width,
                                   int     client_height,
                                   int     text_width,
                                   int     *width,
                                   int     *height);
void     (*theme_update_border_extents)     (gint    text_height);
void     (*theme_get_event_window_position) (decor_t *d,
                                   gint    i,
                                   gint    j,
                                   gint    width,
                                   gint    height,
                                   gint    *x,
                                   gint    *y,
                                   gint    *w,
                                   gint    *h);
void     (*theme_get_button_position)       (decor_t *d,
                                   gint    i,
                                   gint    width,
                                   gint    height,
                                   gint    *x,
                                   gint    *y,
                                   gint    *w,
                                   gint    *h);

typedef void (*event_callback) (WnckWindow *win, XEvent *event);

static char *program_name;

static GtkWidget     *style_window;

static GHashTable    *frame_table;
static GtkWidget     *action_menu = NULL;
static gboolean      action_menu_mapped = FALSE;
static decor_color_t _title_color[2];
static PangoContext  *pango_context;
static gint      double_click_timeout = 250;

static GtkWidget     *tip_window;
static GtkWidget     *tip_label;
static GTimeVal        tooltip_last_popdown = { -1, -1 };
static gint      tooltip_timer_tag = 0;

static GSList *draw_list = NULL;
static guint  draw_idle_id = 0;

static PangoFontDescription *titlebar_font = NULL;
static gboolean             use_system_font = FALSE;
static gint           text_height;

static GdkPixmap *switcher_pixmap = NULL;
static GdkPixmap *switcher_buffer_pixmap = NULL;
static gint      switcher_width;
static gint      switcher_height;

static XRenderPictFormat *xformat;

static void
decor_update_switcher_property (decor_t *d)
{
    long     data[256];
    Display  *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
    gint     nQuad;
    decor_quad_t quads[N_QUADS_MAX];

    nQuad = decor_set_lSrStSbX_window_quads (quads, &switcher_context,
                                   &d->border_layout,
                                   d->border_layout.top.x2 -
                                   d->border_layout.top.x1 -
                                   switcher_context.extents.left -
                                   switcher_context.extents.right -
                                   32);

    decor_quads_to_property (data, GDK_PIXMAP_XID (d->pixmap),
                       &_switcher_extents, &_switcher_extents,
                       0, 0, quads, nQuad);

    gdk_error_trap_push ();
    XChangeProperty (xdisplay, d->prop_xid,
                 win_decor_atom,
                 XA_INTEGER,
                 32, PropModeReplace, (guchar *) data,
                 BASE_PROP_SIZE + QUAD_PROP_SIZE * nQuad);
    XSync (xdisplay, FALSE);
    gdk_error_trap_pop ();
}

static void
gdk_cairo_set_source_color_alpha (cairo_t  *cr,
                          GdkColor *color,
                          double   alpha)
{
    cairo_set_source_rgba (cr,
                     color->red   / 65535.0,
                     color->green / 65535.0,
                     color->blue  / 65535.0,
                     alpha);
}

static GdkPixmap *
create_pixmap (int w,
             int h)
{
    GdkPixmap     *pixmap;
    GdkVisual     *visual;
    GdkColormap *colormap;

    visual = gdk_visual_get_best_with_depth (32);
    if (!visual)
      return NULL;

    if (w == 0 || h ==0)
      abort ();

    pixmap = gdk_pixmap_new (NULL, w, h, 32);
    if (!pixmap)
      return NULL;

    colormap = gdk_colormap_new (visual, FALSE);
    if (!colormap)
    {
      gdk_pixmap_unref (pixmap);
      return NULL;
    }

    gdk_drawable_set_colormap (GDK_DRAWABLE (pixmap), colormap);
    gdk_colormap_unref (colormap);

    return pixmap;
}

#define CORNER_TOPLEFT     (1 << 0)
#define CORNER_TOPRIGHT    (1 << 1)
#define CORNER_BOTTOMRIGHT (1 << 2)
#define CORNER_BOTTOMLEFT  (1 << 3)

static void
rounded_rectangle (cairo_t *cr,
               double  x,
               double  y,
               double  w,
               double  h,
               double  radius,
               int         corner)
{
    if (corner & CORNER_TOPLEFT)
      cairo_move_to (cr, x + radius, y);
    else
      cairo_move_to (cr, x, y);

    if (corner & CORNER_TOPRIGHT)
      cairo_arc (cr, x + w - radius, y + radius, radius,
               M_PI * 1.5, M_PI * 2.0);
    else
      cairo_line_to (cr, x + w, y);

    if (corner & CORNER_BOTTOMRIGHT)
      cairo_arc (cr, x + w - radius, y + h - radius, radius,
               0.0, M_PI * 0.5);
    else
      cairo_line_to (cr, x + w, y + h);

    if (corner & CORNER_BOTTOMLEFT)
      cairo_arc (cr, x + radius, y + h - radius, radius,
               M_PI * 0.5, M_PI);
    else
      cairo_line_to (cr, x, y + h);

    if (corner & CORNER_TOPLEFT)
      cairo_arc (cr, x + radius, y + radius, radius, M_PI, M_PI * 1.5);
    else
      cairo_line_to (cr, x, y);
}

#define SHADE_LEFT   (1 << 0)
#define SHADE_RIGHT  (1 << 1)
#define SHADE_TOP    (1 << 2)
#define SHADE_BOTTOM (1 << 3)

static void
fill_rounded_rectangle (cairo_t       *cr,
                  double        x,
                  double        y,
                  double        w,
                  double        h,
                  double            radius,
                  int         corner,
                  decor_color_t *c0,
                  double        alpha0,
                  decor_color_t *c1,
                  double            alpha1,
                  int         gravity)
{
    cairo_pattern_t *pattern;

    rounded_rectangle (cr, x, y, w, h, radius, corner);

    if (gravity & SHADE_RIGHT)
    {
      x = x + w;
      w = -w;
    }
    else if (!(gravity & SHADE_LEFT))
    {
      x = w = 0;
    }

    if (gravity & SHADE_BOTTOM)
    {
      y = y + h;
      h = -h;
    }
    else if (!(gravity & SHADE_TOP))
    {
      y = h = 0;
    }

    if (w && h)
    {
      cairo_matrix_t matrix;

      pattern = cairo_pattern_create_radial (0.0, 0.0, 0.0, 0.0, 0.0, w);

      cairo_matrix_init_scale (&matrix, 1.0, w / h);
      cairo_matrix_translate (&matrix, -(x + w), -(y + h));

      cairo_pattern_set_matrix (pattern, &matrix);
    }
    else
    {
      pattern = cairo_pattern_create_linear (x + w, y + h, x, y);
    }

    cairo_pattern_add_color_stop_rgba (pattern, 0.0, c0->r, c0->g, c0->b,
                               alpha0);

    cairo_pattern_add_color_stop_rgba (pattern, 1.0, c1->r, c1->g, c1->b,
                               alpha1);

    cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);

    cairo_set_source (cr, pattern);
    cairo_fill (cr);
    cairo_pattern_destroy (pattern);
}

static void
draw_shadow_background (decor_t           *d,
                  cairo_t           *cr,
                  decor_shadow_t  *s,
                  decor_context_t *c)
{
    Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

    if (!s || !d->picture)
    {
      cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
      cairo_paint (cr);
    }
    else
    {
      decor_fill_picture_extents_with_shadow (xdisplay,
                                    s, c,
                                    d->picture,
                                    &d->border_layout);
    }
}

static void
decor_update_meta_window_property (decor_t        *d,
                           MetaTheme        *theme,
                           MetaFrameFlags flags)
{
    long        data[256];
    Display     *xdisplay =
      GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
    gint        nQuad;
    decor_extents_t extents, max_extents;
    decor_quad_t    quads[N_QUADS_MAX];
    gint            lh, rh;

    if (d->border_layout.rotation)
      lh = d->border_layout.left.x2 - d->border_layout.left.x1;
    else
      lh = d->border_layout.left.y2 - d->border_layout.left.y1;

    if (d->border_layout.rotation)
      rh = d->border_layout.right.x2 - d->border_layout.right.x1;
    else
      rh = d->border_layout.right.y2 - d->border_layout.right.y1;

    nQuad = decor_set_lXrXtXbX_window_quads (quads, d->context,
                                   &d->border_layout,
                                   lh / 2,
                                   rh / 2,
                                   d->border_layout.top.x2 -
                                   d->border_layout.top.x1 -
                                   d->context->left_space -
                                   d->context->right_space -
                                   d->button_width - 1,
                                   (d->border_layout.bottom.x2 -
                                    d->border_layout.bottom.x1 -
                                    d->context->left_space -
                                    d->context->right_space) / 2);

    extents = _win_extents;
    max_extents = _max_win_extents;

    extents.top += titlebar_height;
    max_extents.top += max_titlebar_height;

    decor_quads_to_property (data, GDK_PIXMAP_XID (d->pixmap),
                       &extents, &max_extents,
                       ICON_SPACE + d->button_width,
                       0,
                       quads, nQuad);

    gdk_error_trap_push ();
    XChangeProperty (xdisplay, d->prop_xid,
                 win_decor_atom,
                 XA_INTEGER,
                 32, PropModeReplace, (guchar *) data,
                 BASE_PROP_SIZE + QUAD_PROP_SIZE * nQuad);
                 XSync (xdisplay, FALSE);
    gdk_error_trap_pop ();
}

static void
meta_get_corner_radius (const MetaFrameGeometry *fgeom,
                  int                 *top_left_radius,
                  int               *top_right_radius,
                  int                 *bottom_left_radius,
                  int               *bottom_right_radius)
{

#ifdef HAVE_METACITY_2_17_0
    *top_left_radius     = fgeom->top_left_corner_rounded_radius;
    *top_right_radius    = fgeom->top_right_corner_rounded_radius;
    *bottom_left_radius  = fgeom->bottom_left_corner_rounded_radius;
    *bottom_right_radius = fgeom->bottom_right_corner_rounded_radius;
#else
    *top_left_radius     = fgeom->top_left_corner_rounded ? 5 : 0;
    *top_right_radius    = fgeom->top_right_corner_rounded ? 5 : 0;
    *bottom_left_radius  = fgeom->bottom_left_corner_rounded ? 5 : 0;
    *bottom_right_radius = fgeom->bottom_right_corner_rounded ? 5 : 0;
#endif

}

static int
radius_to_width (int radius,
             int i)
{
    int r2 = radius * radius - (radius - i) * (radius - i);

    return 1 + (radius - floor (sqrt (r2) + 0.5));
}

static Region
meta_get_top_border_region (const MetaFrameGeometry *fgeom,
                      int                     width)
{
    Region     corners_xregion, border_xregion;
    XRectangle xrect;
    int            top_left_radius;
    int            top_right_radius;
    int            bottom_left_radius;
    int            bottom_right_radius;
    int            w, i;

    corners_xregion = XCreateRegion ();

    meta_get_corner_radius (fgeom,
                      &top_left_radius,
                      &top_right_radius,
                      &bottom_left_radius,
                      &bottom_right_radius);

    if (top_left_radius)
    {
      for (i = 0; i < top_left_radius; i++)
      {
          w = radius_to_width (top_left_radius, i);

          xrect.x  = 0;
          xrect.y  = i;
          xrect.width  = w;
          xrect.height = 1;

          XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion);
      }
    }

    if (top_right_radius)
    {
      for (i = 0; i < top_right_radius; i++)
      {
          w = radius_to_width (top_right_radius, i);

          xrect.x  = width - w;
          xrect.y  = i;
          xrect.width  = w;
          xrect.height = 1;

          XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion);
      }
    }

    border_xregion = XCreateRegion ();

    xrect.x = 0;
    xrect.y = 0;
    xrect.width = width;
    xrect.height = fgeom->top_height;

    XUnionRectWithRegion (&xrect, border_xregion, border_xregion);

    XSubtractRegion (border_xregion, corners_xregion, border_xregion);

    XDestroyRegion (corners_xregion);

    return border_xregion;
}

static Region
meta_get_bottom_border_region (const MetaFrameGeometry *fgeom,
                         int                width)
{
    Region     corners_xregion, border_xregion;
    XRectangle xrect;
    int            top_left_radius;
    int            top_right_radius;
    int            bottom_left_radius;
    int            bottom_right_radius;
    int            w, i;

    corners_xregion = XCreateRegion ();

    meta_get_corner_radius (fgeom,
                      &top_left_radius,
                      &top_right_radius,
                      &bottom_left_radius,
                      &bottom_right_radius);

    if (bottom_left_radius)
    {
      for (i = 0; i < bottom_left_radius; i++)
      {
          w = radius_to_width (bottom_left_radius, i);

          xrect.x  = 0;
          xrect.y  = fgeom->bottom_height - i;
          xrect.width  = w;
          xrect.height = 1;

          XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion);
      }
    }

    if (bottom_right_radius)
    {
      for (i = 0; i < bottom_right_radius; i++)
      {
          w = radius_to_width (bottom_right_radius, i);

          xrect.x  = width - w;
          xrect.y  = fgeom->bottom_height - i;
          xrect.width  = w;
          xrect.height = 1;

          XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion);
      }
    }

    border_xregion = XCreateRegion ();

    xrect.x = 0;
    xrect.y = 0;
    xrect.width = width;
    xrect.height = fgeom->bottom_height;

    XUnionRectWithRegion (&xrect, border_xregion, border_xregion);

    XSubtractRegion (border_xregion, corners_xregion, border_xregion);

    XDestroyRegion (corners_xregion);

    return border_xregion;
}

static Region
meta_get_left_border_region (const MetaFrameGeometry *fgeom,
                       int               height)
{
    Region     border_xregion;
    XRectangle xrect;

    border_xregion = XCreateRegion ();

    xrect.x  = 0;
    xrect.y  = 0;
    xrect.width  = fgeom->left_width;
    xrect.height = height - fgeom->top_height - fgeom->bottom_height;

    XUnionRectWithRegion (&xrect, border_xregion, border_xregion);

    return border_xregion;
}

static Region
meta_get_right_border_region (const MetaFrameGeometry *fgeom,
                        int               height)
{
    Region     border_xregion;
    XRectangle xrect;

    border_xregion = XCreateRegion ();

    xrect.x  = 0;
    xrect.y  = 0;
    xrect.width  = fgeom->right_width;
    xrect.height = height - fgeom->top_height - fgeom->bottom_height;

    XUnionRectWithRegion (&xrect, border_xregion, border_xregion);

    return border_xregion;
}

static MetaButtonState
meta_button_state (int state)
{
    if (state & IN_EVENT_WINDOW)
    {
      if (state & PRESSED_EVENT_WINDOW)
          return META_BUTTON_STATE_PRESSED;

      return META_BUTTON_STATE_PRELIGHT;
    }

    return META_BUTTON_STATE_NORMAL;
}
static MetaButtonState
meta_button_state_for_button_type (decor_t        *d,
                           MetaButtonType type)
{
    switch (type) {
    case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
    case META_BUTTON_TYPE_MINIMIZE:
      return meta_button_state (d->button_states[2]);
    case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
    case META_BUTTON_TYPE_MAXIMIZE:
      return meta_button_state (d->button_states[1]);
    case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
    case META_BUTTON_TYPE_CLOSE:
      return meta_button_state (d->button_states[0]);
    case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
    case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
    case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
    case META_BUTTON_TYPE_MENU:
    default:
      break;
    }

    return META_BUTTON_STATE_NORMAL;
}

static void
meta_get_decoration_geometry (decor_t           *d,
                        MetaTheme           *theme,
                        MetaFrameFlags    *flags,
                        MetaFrameGeometry *fgeom,
                        MetaButtonLayout  *button_layout,
                        GdkRectangle      *clip)
{
    gint left_width, right_width, top_height, bottom_height;
    gint i;

    button_layout->left_buttons[0] = META_BUTTON_FUNCTION_MENU;

    for (i = 1; i < MAX_BUTTONS_PER_CORNER; i++)
      button_layout->left_buttons[i] = META_BUTTON_FUNCTION_LAST;

    button_layout->right_buttons[0] = META_BUTTON_FUNCTION_MINIMIZE;
    button_layout->right_buttons[1] = META_BUTTON_FUNCTION_MAXIMIZE;
    button_layout->right_buttons[2] = META_BUTTON_FUNCTION_CLOSE;

    for (i = 3; i < MAX_BUTTONS_PER_CORNER; i++)
      button_layout->right_buttons[i] = META_BUTTON_FUNCTION_LAST;

    *flags = 0;

    if (d->actions & WNCK_WINDOW_ACTION_CLOSE)
      *flags |= META_FRAME_ALLOWS_DELETE;

    if (d->actions & WNCK_WINDOW_ACTION_MINIMIZE)
      *flags |= META_FRAME_ALLOWS_MINIMIZE;

    if (d->actions & WNCK_WINDOW_ACTION_MAXIMIZE)
      *flags |= META_FRAME_ALLOWS_MAXIMIZE;

    *flags |= META_FRAME_ALLOWS_MENU;
    *flags |= META_FRAME_ALLOWS_VERTICAL_RESIZE;
    *flags |= META_FRAME_ALLOWS_HORIZONTAL_RESIZE;
    *flags |= META_FRAME_ALLOWS_MOVE;

    if (d->actions & WNCK_WINDOW_ACTION_MAXIMIZE)
      *flags |= META_FRAME_ALLOWS_MAXIMIZE;

    if (d->active)
      *flags |= META_FRAME_HAS_FOCUS;

#define META_MAXIMIZED (WNCK_WINDOW_STATE_MAXIMIZED_HORIZONTALLY | \
                  WNCK_WINDOW_STATE_MAXIMIZED_VERTICALLY)

    if ((d->state & META_MAXIMIZED) == META_MAXIMIZED)
      *flags |= META_FRAME_MAXIMIZED;

    meta_theme_get_frame_borders (theme,
                          META_FRAME_TYPE_NORMAL,
                          text_height,
                          *flags,
                          &top_height,
                          &bottom_height,
                          &left_width,
                          &right_width);

    clip->x = d->context->left_space - left_width;
    clip->y = d->context->top_space - top_height;

    clip->width = d->border_layout.top.x2 - d->border_layout.top.x1;
    clip->width -= d->context->right_space + d->context->left_space;

    if (d->border_layout.rotation)
      clip->height = d->border_layout.left.x2 - d->border_layout.left.x1;
    else
      clip->height = d->border_layout.left.y2 - d->border_layout.left.y1;

    meta_theme_calc_geometry (theme,
                        META_FRAME_TYPE_NORMAL,
                        text_height,
                        *flags,
                        clip->width,
                        clip->height,
                        button_layout,
                        fgeom);

    clip->width  += left_width + right_width;
    clip->height += top_height + bottom_height;
}

static void
meta_draw_window_decoration (decor_t *d)
{
    Display       *xdisplay =
      GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
    GdkPixmap           *pixmap;
    Picture       src;
    MetaButtonState   button_states[META_BUTTON_TYPE_LAST];
    MetaButtonLayout  button_layout;
    MetaFrameGeometry fgeom;
    MetaFrameFlags    flags;
    MetaTheme           *theme;
    GtkStyle            *style;
    cairo_t       *cr;
    gint          size, i;
    GdkRectangle      clip, rect;
    GdkDrawable       *drawable;
    Region        region;
    double        alpha = (d->active) ? meta_active_opacity : meta_opacity;
    gboolean            shade_alpha = (d->active) ? meta_active_shade_opacity :
      meta_shade_opacity;
    MetaFrameStyle    *frame_style;
    GdkColor            bg_color;
    double        bg_alpha;

    if (!d->pixmap || !d->picture)
      return;

    if (decoration_alpha == 1.0)
      alpha = 1.0;

    style = gtk_widget_get_style (style_window);

    drawable = d->buffer_pixmap ? d->buffer_pixmap : d->pixmap;

    cr = gdk_cairo_create (GDK_DRAWABLE (drawable));

    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);

    theme = meta_theme_get_current ();

    meta_get_decoration_geometry (d, theme, &flags, &fgeom, &button_layout,
                          &clip);

    /* we only have to redraw the shadow background when decoration
       changed size */
    if (d->prop_xid || !d->buffer_pixmap)
      draw_shadow_background (d, cr, d->shadow, d->context);

    for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
      button_states[i] = meta_button_state_for_button_type (d, i);

    frame_style = meta_theme_get_frame_style (theme,
                                    META_FRAME_TYPE_NORMAL,
                                    flags);

    bg_color = style->bg[GTK_STATE_NORMAL];
    bg_alpha = 1.0;

#ifdef HAVE_METACITY_2_17_0
    if (frame_style->window_background_color)
    {
      meta_color_spec_render (frame_style->window_background_color,
                        GTK_WIDGET (style_window),
                        &bg_color);

      bg_alpha = frame_style->window_background_alpha / 255.0;
    }
#endif

    cairo_destroy (cr);

    rect.x     = 0;
    rect.y     = 0;
    rect.width = clip.width;

    size = MAX (fgeom.top_height, fgeom.bottom_height);

    if (rect.width && size)
    {
      pixmap = create_pixmap (rect.width, size);

      cr = gdk_cairo_create (GDK_DRAWABLE (pixmap));
      gdk_cairo_set_source_color_alpha (cr, &bg_color, bg_alpha);
      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);

      src = XRenderCreatePicture (xdisplay,
                            GDK_PIXMAP_XID (pixmap),
                            xformat, 0, NULL);

      if (fgeom.top_height)
      {
          rect.height = fgeom.top_height;

          cairo_paint (cr);

          meta_theme_draw_frame (theme,
                           style_window,
                           pixmap,
                           &rect,
                           0, 0,
                           META_FRAME_TYPE_NORMAL,
                           flags,
                           clip.width - fgeom.left_width -
                           fgeom.right_width,
                           clip.height - fgeom.top_height -
                           fgeom.bottom_height,
                           d->layout,
                           text_height,
                           &button_layout,
                           button_states,
                           d->icon_pixbuf,
                           NULL);

          region = meta_get_top_border_region (&fgeom, clip.width);

          decor_blend_top_border_picture (xdisplay,
                                  d->context,
                                  src,
                                  0, 0,
                                  d->picture,
                                  &d->border_layout,
                                  region,
                                  alpha * 0xffff,
                                  shade_alpha);

          XDestroyRegion (region);
      }

      if (fgeom.bottom_height)
      {
          rect.height = fgeom.bottom_height;

          cairo_paint (cr);

          meta_theme_draw_frame (theme,
                           style_window,
                           pixmap,
                           &rect,
                           0,
                           -(clip.height - fgeom.bottom_height),
                           META_FRAME_TYPE_NORMAL,
                           flags,
                           clip.width - fgeom.left_width -
                           fgeom.right_width,
                           clip.height - fgeom.top_height -
                           fgeom.bottom_height,
                           d->layout,
                           text_height,
                           &button_layout,
                           button_states,
                           d->icon_pixbuf,
                           NULL);

          region = meta_get_bottom_border_region (&fgeom, clip.width);

          decor_blend_bottom_border_picture (xdisplay,
                                     d->context,
                                     src,
                                     0, 0,
                                     d->picture,
                                     &d->border_layout,
                                     region,
                                     alpha * 0xffff,
                                     shade_alpha);

          XDestroyRegion (region);
      }

      cairo_destroy (cr);

      gdk_pixmap_unref (pixmap);

      XRenderFreePicture (xdisplay, src);
    }

    rect.height = clip.height - fgeom.top_height - fgeom.bottom_height;

    size = MAX (fgeom.left_width, fgeom.right_width);

    if (size && rect.height)
    {
      pixmap = create_pixmap (size, rect.height);

      cr = gdk_cairo_create (GDK_DRAWABLE (pixmap));
      gdk_cairo_set_source_color_alpha (cr, &bg_color, bg_alpha);
      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);

      src = XRenderCreatePicture (xdisplay,
                            GDK_PIXMAP_XID (pixmap),
                            xformat, 0, NULL);

      if (fgeom.left_width)
      {
          rect.width = fgeom.left_width;

          cairo_paint (cr);

          meta_theme_draw_frame (theme,
                           style_window,
                           pixmap,
                           &rect,
                           0,
                           -fgeom.top_height,
                           META_FRAME_TYPE_NORMAL,
                           flags,
                           clip.width - fgeom.left_width -
                           fgeom.right_width,
                           clip.height - fgeom.top_height -
                           fgeom.bottom_height,
                           d->layout,
                           text_height,
                           &button_layout,
                           button_states,
                           d->icon_pixbuf,
                           NULL);

          region = meta_get_left_border_region (&fgeom, clip.height);

          decor_blend_left_border_picture (xdisplay,
                                   d->context,
                                   src,
                                   0, 0,
                                   d->picture,
                                   &d->border_layout,
                                   region,
                                   alpha * 0xffff,
                                   shade_alpha);

          XDestroyRegion (region);
      }

      if (fgeom.right_width)
      {
          rect.width = fgeom.right_width;

          cairo_paint (cr);

          meta_theme_draw_frame (theme,
                           style_window,
                           pixmap,
                           &rect,
                           -(clip.width - fgeom.right_width),
                           -fgeom.top_height,
                           META_FRAME_TYPE_NORMAL,
                           flags,
                           clip.width - fgeom.left_width -
                           fgeom.right_width,
                           clip.height - fgeom.top_height -
                           fgeom.bottom_height,
                           d->layout,
                           text_height,
                           &button_layout,
                           button_states,
                           d->icon_pixbuf,
                           NULL);

          region = meta_get_right_border_region (&fgeom, clip.height);

          decor_blend_right_border_picture (xdisplay,
                                    d->context,
                                    src,
                                    0, 0,
                                    d->picture,
                                    &d->border_layout,
                                    region,
                                    alpha * 0xffff,
                                    shade_alpha);

          XDestroyRegion (region);
      }

      cairo_destroy (cr);

      gdk_pixmap_unref (pixmap);

      XRenderFreePicture (xdisplay, src);
    }

    if (d->buffer_pixmap)
      gdk_draw_drawable  (d->pixmap,
                      d->gc,
                      d->buffer_pixmap,
                      0,
                      0,
                      0,
                      0,
                      d->width,
                      d->height);

    if (d->prop_xid)
    {
      decor_update_meta_window_property (d, theme, flags);
      d->prop_xid = 0;
    }
}

#define SWITCHER_ALPHA 0xa0a0

static void
draw_switcher_background (decor_t *d)
{
    Display   *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
    cairo_t   *cr;
    GtkStyle        *style;
    decor_color_t color;
    double    alpha = SWITCHER_ALPHA / 65535.0;
    double    x1, y1, x2, y2, h;
    int             top;
    unsigned long pixel;
    ushort    a = SWITCHER_ALPHA;

    if (!d->buffer_pixmap)
      return;

    style = gtk_widget_get_style (style_window);

    color.r = style->bg[GTK_STATE_NORMAL].red   / 65535.0;
    color.g = style->bg[GTK_STATE_NORMAL].green / 65535.0;
    color.b = style->bg[GTK_STATE_NORMAL].blue  / 65535.0;

    cr = gdk_cairo_create (GDK_DRAWABLE (d->buffer_pixmap));

    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);

    top = _switcher_extents.top;

    x1 = switcher_context.left_space - _switcher_extents.left;
    y1 = switcher_context.top_space - _switcher_extents.top;
    x2 = d->width - switcher_context.right_space + _switcher_extents.right;
    y2 = d->height - switcher_context.bottom_space + _switcher_extents.bottom;

    h = y2 - y1 - _switcher_extents.top - _switcher_extents.top;

    cairo_set_line_width (cr, 1.0);

    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);

    draw_shadow_background (d, cr, switcher_shadow, &switcher_context);

    fill_rounded_rectangle (cr,
                      x1 + 0.5,
                      y1 + 0.5,
                      _switcher_extents.left - 0.5,
                      top - 0.5,
                      5.0, CORNER_TOPLEFT,
                      &color, alpha, &color, alpha * 0.75,
                      SHADE_TOP | SHADE_LEFT);

    fill_rounded_rectangle (cr,
                      x1 + _switcher_extents.left,
                      y1 + 0.5,
                      x2 - x1 - _switcher_extents.left -
                      _switcher_extents.right,
                      top - 0.5,
                      5.0, 0,
                      &color, alpha, &color, alpha * 0.75,
                      SHADE_TOP);

    fill_rounded_rectangle (cr,
                      x2 - _switcher_extents.right,
                      y1 + 0.5,
                      _switcher_extents.right - 0.5,
                      top - 0.5,
                      5.0, CORNER_TOPRIGHT,
                      &color, alpha, &color, alpha * 0.75,
                      SHADE_TOP | SHADE_RIGHT);

    fill_rounded_rectangle (cr,
                      x1 + 0.5,
                      y1 + top,
                      _switcher_extents.left - 0.5,
                      h,
                      5.0, 0,
                      &color, alpha, &color, alpha * 0.75,
                      SHADE_LEFT);

    fill_rounded_rectangle (cr,
                      x2 - _switcher_extents.right,
                      y1 + top,
                      _switcher_extents.right - 0.5,
                      h,
                      5.0, 0,
                      &color, alpha, &color, alpha * 0.75,
                      SHADE_RIGHT);

    fill_rounded_rectangle (cr,
                      x1 + 0.5,
                      y2 - _switcher_extents.top,
                      _switcher_extents.left - 0.5,
                      _switcher_extents.top - 0.5,
                      5.0, CORNER_BOTTOMLEFT,
                      &color, alpha, &color, alpha * 0.75,
                      SHADE_BOTTOM | SHADE_LEFT);

    fill_rounded_rectangle (cr,
                      x1 + _switcher_extents.left,
                      y2 - _switcher_extents.top,
                      x2 - x1 - _switcher_extents.left -
                      _switcher_extents.right,
                      _switcher_extents.top - 0.5,
                      5.0, 0,
                      &color, alpha, &color, alpha * 0.75,
                      SHADE_BOTTOM);

    fill_rounded_rectangle (cr,
                      x2 - _switcher_extents.right,
                      y2 - _switcher_extents.top,
                      _switcher_extents.right - 0.5,
                      _switcher_extents.top - 0.5,
                      5.0, CORNER_BOTTOMRIGHT,
                      &color, alpha, &color, alpha * 0.75,
                      SHADE_BOTTOM | SHADE_RIGHT);

    cairo_rectangle (cr, x1 + _switcher_extents.left,
                 y1 + top,
                 x2 - x1 - _switcher_extents.left - _switcher_extents.right,
                 h);
    gdk_cairo_set_source_color_alpha (cr,
                              &style->bg[GTK_STATE_NORMAL],
                              alpha);
    cairo_fill (cr);

    cairo_set_operator (cr, CAIRO_OPERATOR_OVER);

    rounded_rectangle (cr,
                   x1 + 0.5, y1 + 0.5,
                   x2 - x1 - 1.0, y2 - y1 - 1.0,
                   5.0,
                   CORNER_TOPLEFT | CORNER_TOPRIGHT | CORNER_BOTTOMLEFT |
                   CORNER_BOTTOMRIGHT);

    cairo_clip (cr);

    cairo_translate (cr, 1.0, 1.0);

    rounded_rectangle (cr,
                   x1 + 0.5, y1 + 0.5,
                   x2 - x1 - 1.0, y2 - y1 - 1.0,
                   5.0,
                   CORNER_TOPLEFT | CORNER_TOPRIGHT | CORNER_BOTTOMLEFT |
                   CORNER_BOTTOMRIGHT);

    cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.4);
    cairo_stroke (cr);

    cairo_translate (cr, -2.0, -2.0);

    rounded_rectangle (cr,
                   x1 + 0.5, y1 + 0.5,
                   x2 - x1 - 1.0, y2 - y1 - 1.0,
                   5.0,
                   CORNER_TOPLEFT | CORNER_TOPRIGHT | CORNER_BOTTOMLEFT |
                   CORNER_BOTTOMRIGHT);

    cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.1);
    cairo_stroke (cr);

    cairo_translate (cr, 1.0, 1.0);

    cairo_reset_clip (cr);

    rounded_rectangle (cr,
                   x1 + 0.5, y1 + 0.5,
                   x2 - x1 - 1.0, y2 - y1 - 1.0,
                   5.0,
                   CORNER_TOPLEFT | CORNER_TOPRIGHT | CORNER_BOTTOMLEFT |
                   CORNER_BOTTOMRIGHT);

    gdk_cairo_set_source_color_alpha (cr,
                              &style->fg[GTK_STATE_NORMAL],
                              alpha);

    cairo_stroke (cr);

    cairo_destroy (cr);

    gdk_draw_drawable (d->pixmap,
                   d->gc,
                   d->buffer_pixmap,
                   0,
                   0,
                   0,
                   0,
                   d->width,
                   d->height);

    pixel = ((((a * style->bg[GTK_STATE_NORMAL].red  ) >> 24) & 0x0000ff) |
           (((a * style->bg[GTK_STATE_NORMAL].green) >> 16) & 0x00ff00) |
           (((a * style->bg[GTK_STATE_NORMAL].blue ) >>  8) & 0xff0000) |
           (((a & 0xff00) << 16)));

    decor_update_switcher_property (d);

    gdk_error_trap_push ();
    XSetWindowBackground (xdisplay, d->prop_xid, pixel);
    XClearWindow (xdisplay, d->prop_xid);
    XSync (xdisplay, FALSE);
    gdk_error_trap_pop ();

    d->prop_xid = 0;
}

static void
draw_switcher_foreground (decor_t *d)
{
    cairo_t   *cr;
    GtkStyle        *style;
    decor_color_t color;
    double    alpha = SWITCHER_ALPHA / 65535.0;

    if (!d->pixmap || !d->buffer_pixmap)
      return;

    style = gtk_widget_get_style (style_window);

    color.r = style->bg[GTK_STATE_NORMAL].red   / 65535.0;
    color.g = style->bg[GTK_STATE_NORMAL].green / 65535.0;
    color.b = style->bg[GTK_STATE_NORMAL].blue  / 65535.0;

    cr = gdk_cairo_create (GDK_DRAWABLE (d->buffer_pixmap));

    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);

    cairo_rectangle (cr, switcher_context.left_space,
                 d->height - switcher_context.bottom_space,
                 d->width - switcher_context.left_space -
                 switcher_context.right_space,
                 SWITCHER_SPACE);

    gdk_cairo_set_source_color_alpha (cr,
                              &style->bg[GTK_STATE_NORMAL],
                              alpha);
    cairo_fill (cr);

    if (d->layout)
    {
      int w;

      cairo_set_operator (cr, CAIRO_OPERATOR_OVER);

      gdk_cairo_set_source_color_alpha (cr,
                                &style->fg[GTK_STATE_NORMAL],
                                1.0);

      pango_layout_get_pixel_size (d->layout, &w, NULL);

      cairo_move_to (cr, d->width / 2 - w / 2,
                   d->height - switcher_context.bottom_space +
                   SWITCHER_SPACE / 2 - text_height / 2);

      pango_cairo_show_layout (cr, d->layout);
    }

    cairo_destroy (cr);

    gdk_draw_drawable  (d->pixmap,
                  d->gc,
                  d->buffer_pixmap,
                  0,
                  0,
                  0,
                  0,
                  d->width,
                  d->height);
}

static void
draw_switcher_decoration (decor_t *d)
{
    if (d->prop_xid)
      draw_switcher_background (d);

    draw_switcher_foreground (d);
}

static gboolean
draw_decor_list (void *data)
{
    GSList  *list;
    decor_t *d;

    draw_idle_id = 0;

    for (list = draw_list; list; list = list->next)
    {
      d = (decor_t *) list->data;
      (*d->draw) (d);
    }

    g_slist_free (draw_list);
    draw_list = NULL;

    return FALSE;
}

static void
queue_decor_draw (decor_t *d)
{
    if (g_slist_find (draw_list, d))
      return;

    draw_list = g_slist_append (draw_list, d);

    if (!draw_idle_id)
      draw_idle_id = g_idle_add (draw_decor_list, NULL);
}

static GdkPixmap *
pixmap_new_from_pixbuf (GdkPixbuf *pixbuf)
{
    GdkPixmap *pixmap;
    guint     width, height;
    cairo_t   *cr;

    width  = gdk_pixbuf_get_width (pixbuf);
    height = gdk_pixbuf_get_height (pixbuf);

    pixmap = create_pixmap (width, height);
    if (!pixmap)
      return NULL;

    cr = (cairo_t *) gdk_cairo_create (GDK_DRAWABLE (pixmap));
    gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
    cairo_paint (cr);
    cairo_destroy (cr);

    return pixmap;
}

static void
update_default_decorations (GdkScreen *screen)
{
    long        data[256];
    Window      xroot;
    GdkDisplay        *gdkdisplay = gdk_display_get_default ();
    Display     *xdisplay = gdk_x11_display_get_xdisplay (gdkdisplay);
    Atom        bareAtom, normalAtom, activeAtom;
    decor_t     d;
    gint        nQuad;
    decor_quad_t    quads[N_QUADS_MAX];
    decor_extents_t extents = _win_extents;

    xroot = RootWindowOfScreen (gdk_x11_screen_get_xscreen (screen));

    bareAtom   = XInternAtom (xdisplay, "_NET_WINDOW_DECOR_BARE", FALSE);
    normalAtom = XInternAtom (xdisplay, "_NET_WINDOW_DECOR_NORMAL", FALSE);
    activeAtom = XInternAtom (xdisplay, "_NET_WINDOW_DECOR_ACTIVE", FALSE);

    if (no_border_shadow)
    {
      decor_layout_t layout;

      decor_get_default_layout (&shadow_context, 1, 1, &layout);

      nQuad = decor_set_lSrStSbS_window_quads (quads, &shadow_context,
                                     &layout);

      decor_quads_to_property (data, no_border_shadow->pixmap,
                         &_shadow_extents, &_shadow_extents,
                         0, 0, quads, nQuad);

      XChangeProperty (xdisplay, xroot,
                   bareAtom,
                   XA_INTEGER,
                   32, PropModeReplace, (guchar *) data,
                   BASE_PROP_SIZE + QUAD_PROP_SIZE * nQuad);

      if (minimal)
      {
          XChangeProperty (xdisplay, xroot,
                       normalAtom,
                       XA_INTEGER,
                       32, PropModeReplace, (guchar *) data,
                       BASE_PROP_SIZE + QUAD_PROP_SIZE * nQuad);
          XChangeProperty (xdisplay, xroot,
                       activeAtom,
                       XA_INTEGER,
                       32, PropModeReplace, (guchar *) data,
                       BASE_PROP_SIZE + QUAD_PROP_SIZE * nQuad);
      }
    }
    else
    {
      XDeleteProperty (xdisplay, xroot, bareAtom);

      if (minimal)
      {
          XDeleteProperty (xdisplay, xroot, normalAtom);
          XDeleteProperty (xdisplay, xroot, activeAtom);
      }
    }

    if (minimal)
      return;

    memset (&d, 0, sizeof (d));

    d.context = &window_context;
    d.shadow  = border_shadow;

    decor_get_default_layout (d.context, 1, 1, &d.border_layout);

    d.width  = d.border_layout.width;
    d.height = d.border_layout.height;

    extents.top += titlebar_height;

    d.draw = theme_draw_window_decoration;

    if (decor_normal_pixmap)
      gdk_pixmap_unref (decor_normal_pixmap);

    nQuad = decor_set_lSrStSbS_window_quads (quads, d.context,
                                   &d.border_layout);

    decor_normal_pixmap = create_pixmap (d.width, d.height);
    if (decor_normal_pixmap)
    {
      d.pixmap  = decor_normal_pixmap;
      d.active  = FALSE;
      d.picture = XRenderCreatePicture (xdisplay,
                                GDK_PIXMAP_XID (d.pixmap),
                                xformat, 0, NULL);

      (*d.draw) (&d);

      XRenderFreePicture (xdisplay, d.picture);

      decor_quads_to_property (data, GDK_PIXMAP_XID (d.pixmap),
                         &extents, &extents, 0, 0, quads, nQuad);

      XChangeProperty (xdisplay, xroot,
                   normalAtom,
                   XA_INTEGER,
                   32, PropModeReplace, (guchar *) data,
                   BASE_PROP_SIZE + QUAD_PROP_SIZE * nQuad);
    }

    if (decor_active_pixmap)
      gdk_pixmap_unref (decor_active_pixmap);

    decor_active_pixmap = create_pixmap (d.width, d.height);
    if (decor_active_pixmap)
    {
      d.pixmap  = decor_active_pixmap;
      d.active  = TRUE;
      d.picture = XRenderCreatePicture (xdisplay,
                                GDK_PIXMAP_XID (d.pixmap),
                                xformat, 0, NULL);

      (*d.draw) (&d);

      XRenderFreePicture (xdisplay, d.picture);

      decor_quads_to_property (data, GDK_PIXMAP_XID (d.pixmap),
                         &extents, &extents, 0, 0, quads, nQuad);

      XChangeProperty (xdisplay, xroot,
                   activeAtom,
                   XA_INTEGER,
                   32, PropModeReplace, (guchar *) data,
                   BASE_PROP_SIZE + QUAD_PROP_SIZE * nQuad);
    }
}

static gboolean
get_window_prop (Window xwindow,
             Atom   atom,
             Window *val)
{
    Atom   type;
    int        format;
    gulong nitems;
    gulong bytes_after;
    Window *w;
    int    err, result;

    *val = 0;

    gdk_error_trap_push ();

    type = None;
    result = XGetWindowProperty (gdk_display,
                         xwindow,
                         atom,
                         0, G_MAXLONG,
                         False, XA_WINDOW, &type, &format, &nitems,
                         &bytes_after, (void*) &w);
    err = gdk_error_trap_pop ();
    if (err != Success || result != Success)
      return FALSE;

    if (type != XA_WINDOW)
    {
      XFree (w);
      return FALSE;
    }

    *val = *w;
    XFree (w);

    return TRUE;
}

static unsigned int
get_mwm_prop (Window xwindow)
{
    Display   *xdisplay;
    Atom      actual;
    int             err, result, format;
    unsigned long n, left;
    MwmHints        *mwm_hints;
    unsigned int  decor = MWM_DECOR_ALL;

    xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

    gdk_error_trap_push ();

    result = XGetWindowProperty (xdisplay, xwindow, mwm_hints_atom,
                         0L, 20L, FALSE, mwm_hints_atom,
                         &actual, &format, &n, &left,
                         (unsigned char **) &mwm_hints);

    err = gdk_error_trap_pop ();
    if (err != Success || result != Success)
      return decor;

    if (n && mwm_hints)
    {
      if (n >= PROP_MOTIF_WM_HINT_ELEMENTS)
      {
          if (mwm_hints->flags & MWM_HINTS_DECORATIONS)
            decor = mwm_hints->decorations;
      }

      XFree (mwm_hints);
    }

    return decor;
}

#define TOP_RESIZE_HEIGHT 2
static void
meta_get_event_window_position (decor_t *d,
                        gint    i,
                        gint    j,
                        gint  width,
                        gint  height,
                        gint    *x,
                        gint    *y,
                        gint    *w,
                        gint    *h)
{
    MetaButtonLayout  button_layout;
    MetaFrameGeometry fgeom;
    MetaFrameFlags    flags;
    MetaTheme           *theme;
    GdkRectangle      clip;

    theme = meta_theme_get_current ();

    meta_get_decoration_geometry (d, theme, &flags, &fgeom, &button_layout,
                          &clip);

    width  += fgeom.right_width + fgeom.left_width;
    height += fgeom.top_height  + fgeom.bottom_height;

    switch (i) {
    case 2: /* bottom */
      switch (j) {
      case 2: /* bottom right */
          *x = width - fgeom.right_width;
          *y = height - fgeom.bottom_height;
          *w = fgeom.right_width;
          *h = fgeom.bottom_height;
          break;
      case 1: /* bottom */
          *x = fgeom.left_width;
          *y = height - fgeom.bottom_height;
          *w = width - fgeom.left_width - fgeom.right_width;
          *h = fgeom.bottom_height;
          break;
      case 0: /* bottom left */
      default:
          *x = 0;
          *y = height - fgeom.bottom_height;
          *w = fgeom.left_width;
          *h = fgeom.bottom_height;
          break;
      }
      break;
    case 1: /* middle */
      switch (j) {
      case 2: /* right */
          *x = width - fgeom.right_width;
          *y = fgeom.top_height;
          *w = fgeom.right_width;
          *h = height - fgeom.top_height - fgeom.bottom_height;
          break;
      case 1: /* middle */
          *x = fgeom.left_width;
          *y = fgeom.title_rect.y + TOP_RESIZE_HEIGHT;
          *w = width - fgeom.left_width - fgeom.right_width;
          *h = height - fgeom.top_titlebar_edge - fgeom.bottom_height;
          break;
      case 0: /* left */
      default:
          *x = 0;
          *y = fgeom.top_height;
          *w = fgeom.left_width;
          *h = height - fgeom.top_height - fgeom.bottom_height;
          break;
      }
      break;
    case 0: /* top */
    default:
      switch (j) {
      case 2: /* top right */
          *x = width - fgeom.right_width;
          *y = 0;
          *w = fgeom.right_width;
          *h = fgeom.top_height;
          break;
      case 1: /* top */
          *x = fgeom.left_width;
          *y = 0;
          *w = width - fgeom.left_width - fgeom.right_width;
          *h = fgeom.title_rect.y + TOP_RESIZE_HEIGHT;
          break;
      case 0: /* top left */
      default:
          *x = 0;
          *y = 0;
          *w = fgeom.left_width;
          *h = fgeom.top_height;
          break;
      }
    }
}

static void
meta_get_button_position (decor_t *d,
                    gint    i,
                    gint        width,
                    gint        height,
                    gint    *x,
                    gint    *y,
                    gint    *w,
                    gint    *h)
{
    MetaButtonLayout  button_layout;
    MetaFrameGeometry fgeom;
    MetaFrameFlags    flags;
    MetaTheme           *theme;
    GdkRectangle      clip;

#ifdef HAVE_METACITY_2_15_21
    MetaButtonSpace   *space;
#else
    GdkRectangle      *space;
#endif

    theme = meta_theme_get_current ();

    meta_get_decoration_geometry (d, theme, &flags, &fgeom, &button_layout,
                          &clip);

    switch (i) {
    case 2:
      space = &fgeom.min_rect;
      break;
    case 1:
      space = &fgeom.max_rect;
      break;
    case 0:
    default:
      space = &fgeom.close_rect;
      break;
    }

#ifdef HAVE_METACITY_2_15_21
    *x = space->clickable.x;
    *y = space->clickable.y;
    *w = space->clickable.width;
    *h = space->clickable.height;
#else
    *x = space->x;
    *y = space->y;
    *w = space->width;
    *h = space->height;
#endif

}


static void
update_event_windows (WnckWindow *win)
{
    Display *xdisplay;
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");
    gint    x0, y0, width, height, x, y, w, h;
    gint    i, j, k, l;

    xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

    wnck_window_get_geometry (win, &x0, &y0, &width, &height);

    if (d->state & WNCK_WINDOW_STATE_SHADED)
    {
      height = 0;
      k = l = 1;
    }
    else
    {
      k = 0;
      l = 2;
    }

    gdk_error_trap_push ();

    for (i = 0; i < 3; i++)
    {
      static guint event_window_actions[3][3] = {
          {
            WNCK_WINDOW_ACTION_RESIZE,
            WNCK_WINDOW_ACTION_RESIZE,
            WNCK_WINDOW_ACTION_RESIZE
          }, {
            WNCK_WINDOW_ACTION_RESIZE,
            WNCK_WINDOW_ACTION_MOVE,
            WNCK_WINDOW_ACTION_RESIZE
          }, {
            WNCK_WINDOW_ACTION_RESIZE,
            WNCK_WINDOW_ACTION_RESIZE,
            WNCK_WINDOW_ACTION_RESIZE
          }
      };

      for (j = 0; j < 3; j++)
      {
          if (d->actions & event_window_actions[i][j] && i >= k && i <= l)
          {
            (*theme_get_event_window_position) (d, i, j, width, height,
                                        &x, &y, &w, &h);

            XMapWindow (xdisplay, d->event_windows[i][j]);
            XMoveResizeWindow (xdisplay, d->event_windows[i][j],
                           x, y, w, h);
          }
          else
          {
            XUnmapWindow (xdisplay, d->event_windows[i][j]);
          }
      }
    }

    for (i = 0; i < 3; i++)
    {
      static guint button_actions[3] = {
          WNCK_WINDOW_ACTION_CLOSE,
          WNCK_WINDOW_ACTION_MAXIMIZE,
          WNCK_WINDOW_ACTION_MINIMIZE
      };

      if (d->actions & button_actions[i])
      {
          (*theme_get_button_position) (d, i, width, height, &x, &y, &w, &h);

          XMapWindow (xdisplay, d->button_windows[i]);
          XMoveResizeWindow (xdisplay, d->button_windows[i], x, y, w, h);
      }
      else
          XUnmapWindow (xdisplay, d->button_windows[i]);
    }

    XSync (xdisplay, FALSE);
    gdk_error_trap_pop ();
}

#if HAVE_WNCK_WINDOW_HAS_NAME
static const char *
wnck_window_get_real_name (WnckWindow *win)
{
    return wnck_window_has_name (win) ? wnck_window_get_name (win) : NULL;
}
#define wnck_window_get_name wnck_window_get_real_name
#endif

static gint
max_window_name_width (WnckWindow *win)
{
    decor_t     *d = g_object_get_data (G_OBJECT (win), "decor");
    const gchar *name;
    gint    w;

    if (!d->layout)
    {
      d->layout = pango_layout_new (pango_context);
      if (!d->layout)
          return 0;

      pango_layout_set_wrap (d->layout, PANGO_WRAP_CHAR);
    }

    name = wnck_window_get_name (win);
    if (!name)
      return 0;

    pango_layout_set_width (d->layout, -1);
    pango_layout_set_text (d->layout, name, strlen (name));
    pango_layout_get_pixel_size (d->layout, &w, NULL);

    if (d->name)
      pango_layout_set_text (d->layout, d->name, strlen (d->name));

    return w + 6;
}

static void
update_window_decoration_name (WnckWindow *win)
{
    decor_t     *d = g_object_get_data (G_OBJECT (win), "decor");
    const gchar       *name;
    glong       name_length;
    PangoLayoutLine *line;

    if (d->name)
    {
      g_free (d->name);
      d->name = NULL;
    }

    name = wnck_window_get_name (win);
    if (name && (name_length = strlen (name)))
    {
      gint w, n_line;

      if (theme_draw_window_decoration != meta_draw_window_decoration)
      {
            w = SHRT_MAX;
      }
      else
      {
            int width;
            wnck_window_get_geometry(win,0,0,&width,0);
            w= width - ICON_SPACE -2 - d->button_width;
            if (w<1)
                  w=1;
      }


      pango_layout_set_width (d->layout, w * PANGO_SCALE);
      pango_layout_set_text (d->layout, name, name_length);

      n_line = pango_layout_get_line_count (d->layout);

      line = pango_layout_get_line (d->layout, 0);

      name_length = line->length;
      if (pango_layout_get_line_count (d->layout) > 1)
      {
          if (name_length < 4)
          {
            pango_layout_set_text (d->layout, NULL, 0);
            return;
          }

          d->name = g_strndup (name, name_length);
          strcpy (d->name + name_length - 3, "...");
      }
      else
          d->name = g_strndup (name, name_length);

      pango_layout_set_text (d->layout, d->name, name_length);
    }
}

static void
update_window_decoration_icon (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    if (d->icon)
    {
      cairo_pattern_destroy (d->icon);
      d->icon = NULL;
    }

    if (d->icon_pixmap)
    {
      gdk_pixmap_unref (d->icon_pixmap);
      d->icon_pixmap = NULL;
    }

    if (d->icon_pixbuf)
      gdk_pixbuf_unref (d->icon_pixbuf);

    d->icon_pixbuf = wnck_window_get_mini_icon (win);
    if (d->icon_pixbuf)
    {
      cairo_t     *cr;

      gdk_pixbuf_ref (d->icon_pixbuf);

      d->icon_pixmap = pixmap_new_from_pixbuf (d->icon_pixbuf);
      cr = gdk_cairo_create (GDK_DRAWABLE (d->icon_pixmap));
      d->icon = cairo_pattern_create_for_surface (cairo_get_target (cr));
      cairo_destroy (cr);
    }
}

static void
update_window_decoration_state (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    d->state = wnck_window_get_state (win);
}

static void
update_window_decoration_actions (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    d->actions = wnck_window_get_actions (win);
}

static void
meta_calc_button_size (decor_t *d)
{
    gint i, min_x, x, y, w, h, width;

    width = d->border_layout.top.x2 - d->border_layout.top.x1 -
      d->context->left_space - d->context->right_space;
    min_x = width;

    for (i = 0; i < 3; i++)
    {
      static guint button_actions[3] = {
          WNCK_WINDOW_ACTION_CLOSE,
          WNCK_WINDOW_ACTION_MAXIMIZE,
          WNCK_WINDOW_ACTION_MINIMIZE
      };

      if (d->actions & button_actions[i])
      {
          meta_get_button_position (d,
                              i,
                              width,
                              256,
                              &x, &y, &w, &h);

          if (x < min_x)
            min_x = x;
      }
    }

    d->button_width = width - min_x;
}

static gboolean
meta_calc_decoration_size (decor_t *d,
                     gint    w,
                     gint    h,
                     gint    name_width,
                     gint    *width,
                     gint    *height)
{
    decor_layout_t  layout;
    decor_context_t *context;
    decor_shadow_t  *shadow;

    if ((d->state & META_MAXIMIZED) == META_MAXIMIZED)
    {
      context = &max_window_context;
      shadow  = max_border_shadow;
    }
    else
    {
      context = &window_context;
      shadow  = border_shadow;
    }

    decor_get_best_layout (context, w, h, &layout);

    if (context != d->context ||
      memcmp (&layout, &d->border_layout, sizeof (layout)))
    {
      *width  = layout.width;
      *height = layout.height;

      d->border_layout = layout;
      d->context       = context;
      d->shadow        = shadow;

      meta_calc_button_size (d);

      return TRUE;
    }


    return FALSE;
}

static gboolean
update_window_decoration_size (WnckWindow *win)
{
    decor_t   *d = g_object_get_data (G_OBJECT (win), "decor");
    GdkPixmap *pixmap, *buffer_pixmap = NULL;
    Picture   picture;
    gint      width, height;
    gint      w, h, name_width;
    Display   *xdisplay;

    xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

    wnck_window_get_geometry (win, NULL, NULL, &w, &h);

    name_width = max_window_name_width (win);

    if (!(*theme_calc_decoration_size) (d, w, h, name_width, &width, &height))
    {
      update_window_decoration_name (win);
      return FALSE;
    }

    pixmap = create_pixmap (width, height);
    if (!pixmap)
      return FALSE;

    buffer_pixmap = create_pixmap (width, height);
    if (!buffer_pixmap)
    {
      gdk_pixmap_unref (pixmap);
      return FALSE;
    }

    picture = XRenderCreatePicture (xdisplay, GDK_PIXMAP_XID (buffer_pixmap),
                            xformat, 0, NULL);

    if (d->pixmap)
      gdk_pixmap_unref (d->pixmap);

    if (d->buffer_pixmap)
      gdk_pixmap_unref (d->buffer_pixmap);

    if (d->gc)
      gdk_gc_unref (d->gc);

    if (d->picture)
      XRenderFreePicture (xdisplay, d->picture);

    d->pixmap          = pixmap;
    d->buffer_pixmap = buffer_pixmap;
    d->gc        = gdk_gc_new (pixmap);

    d->picture = picture;

    d->width  = width;
    d->height = height;

    d->prop_xid = wnck_window_get_xid (win);

    update_window_decoration_name (win);

    queue_decor_draw (d);

    return TRUE;
}

static void
add_frame_window (WnckWindow *win,
              Window     frame)
{
    Display        *xdisplay;
    XSetWindowAttributes attr;
    gulong         xid = wnck_window_get_xid (win);
    decor_t        *d = g_object_get_data (G_OBJECT (win), "decor");
    gint           i, j;

    xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

    attr.event_mask = ButtonPressMask | EnterWindowMask | LeaveWindowMask;
    attr.override_redirect = TRUE;

    gdk_error_trap_push ();

    for (i = 0; i < 3; i++)
    {
      for (j = 0; j < 3; j++)
      {
          d->event_windows[i][j] =
            XCreateWindow (xdisplay,
                         frame,
                         0, 0, 1, 1, 0,
                         CopyFromParent, CopyFromParent, CopyFromParent,
                         CWOverrideRedirect | CWEventMask, &attr);

          if (cursor[i][j].cursor)
            XDefineCursor (xdisplay, d->event_windows[i][j],
                         cursor[i][j].cursor);
      }
    }

    attr.event_mask |= ButtonReleaseMask;

    for (i = 0; i < 3; i++)
    {
      d->button_windows[i] =
          XCreateWindow (xdisplay,
                     frame,
                     0, 0, 1, 1, 0,
                     CopyFromParent, CopyFromParent, CopyFromParent,
                     CWOverrideRedirect | CWEventMask, &attr);

      d->button_states[i] = 0;
    }

    XSync (xdisplay, FALSE);
    if (!gdk_error_trap_pop ())
    {
      if (get_mwm_prop (xid) & (MWM_DECOR_ALL | MWM_DECOR_TITLE))
          d->decorated = TRUE;

      for (i = 0; i < 3; i++)
          for (j = 0; j < 3; j++)
            g_hash_table_insert (frame_table,
                             GINT_TO_POINTER (d->event_windows[i][j]),
                             GINT_TO_POINTER (xid));

      for (i = 0; i < 3; i++)
          g_hash_table_insert (frame_table,
                         GINT_TO_POINTER (d->button_windows[i]),
                         GINT_TO_POINTER (xid));


      update_window_decoration_state (win);
      update_window_decoration_actions (win);
      update_window_decoration_icon (win);
      update_window_decoration_size (win);

      update_event_windows (win);
    }
    else
    {
      memset (d->event_windows, 0, sizeof (d->event_windows));
    }
}

static gboolean
update_switcher_window (WnckWindow *win,
                  Window     selected)
{
    decor_t    *d = g_object_get_data (G_OBJECT (win), "decor");
    GdkPixmap  *pixmap, *buffer_pixmap = NULL;
    gint       height, width = 0;
    WnckWindow *selected_win;
    Display    *xdisplay;

    xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

    wnck_window_get_geometry (win, NULL, NULL, &width, NULL);

    decor_get_default_layout (&switcher_context, width, 1, &d->border_layout);

    width  = d->border_layout.width;
    height = d->border_layout.height;

    d->decorated = FALSE;
    d->draw  = draw_switcher_decoration;

    if (!d->pixmap && switcher_pixmap)
    {
      gdk_pixmap_ref (switcher_pixmap);
      d->pixmap = switcher_pixmap;
    }

    if (!d->buffer_pixmap && switcher_buffer_pixmap)
    {
      gdk_pixmap_ref (switcher_buffer_pixmap);
      d->buffer_pixmap = switcher_buffer_pixmap;
    }

    if (!d->width)
      d->width = switcher_width;

    if (!d->height)
      d->height = switcher_height;

    selected_win = wnck_window_get (selected);
    if (selected_win)
    {
      glong       name_length;
      PangoLayoutLine *line;
      const gchar *name;

      if (d->name)
      {
          g_free (d->name);
          d->name = NULL;
      }

      name = wnck_window_get_name (selected_win);
      if (name && (name_length = strlen (name)))
      {
          gint n_line;

          if (!d->layout)
          {
            d->layout = pango_layout_new (pango_context);
            if (d->layout)
                pango_layout_set_wrap (d->layout, PANGO_WRAP_CHAR);
          }

          if (d->layout)
          {
            int tw;

            tw = width - switcher_context.left_space -
                switcher_context.right_space - 64;
            pango_layout_set_width (d->layout, tw * PANGO_SCALE);
            pango_layout_set_text (d->layout, name, name_length);

            n_line = pango_layout_get_line_count (d->layout);

            line = pango_layout_get_line (d->layout, 0);

            name_length = line->length;
            if (pango_layout_get_line_count (d->layout) > 1)
            {
                if (name_length < 4)
                {
                  g_object_unref (G_OBJECT (d->layout));
                  d->layout = NULL;
                }
                else
                {
                  d->name = g_strndup (name, name_length);
                  strcpy (d->name + name_length - 3, "...");
                }
            }
            else
                d->name = g_strndup (name, name_length);

            if (d->layout)
                pango_layout_set_text (d->layout, d->name, name_length);
          }
      }
      else if (d->layout)
      {
          g_object_unref (G_OBJECT (d->layout));
          d->layout = NULL;
      }
    }

    if (width == d->width && height == d->height)
    {
      if (!d->gc)
          d->gc = gdk_gc_new (d->pixmap);

      if (!d->picture)
          d->picture =
            XRenderCreatePicture (xdisplay,
                              GDK_PIXMAP_XID (d->buffer_pixmap),
                              xformat, 0, NULL);

      queue_decor_draw (d);
      return FALSE;
    }

    pixmap = create_pixmap (width, height);
    if (!pixmap)
      return FALSE;

    buffer_pixmap = create_pixmap (width, height);
    if (!buffer_pixmap)
    {
      gdk_pixmap_unref (pixmap);
      return FALSE;
    }

    if (switcher_pixmap)
      gdk_pixmap_unref (switcher_pixmap);

    if (switcher_buffer_pixmap)
      gdk_pixmap_unref (switcher_buffer_pixmap);

    if (d->pixmap)
      gdk_pixmap_unref (d->pixmap);

    if (d->buffer_pixmap)
      gdk_pixmap_unref (d->buffer_pixmap);

    if (d->gc)
      gdk_gc_unref (d->gc);

    if (d->picture)
      XRenderFreePicture (xdisplay, d->picture);

    switcher_pixmap        = pixmap;
    switcher_buffer_pixmap = buffer_pixmap;

    switcher_width  = width;
    switcher_height = height;

    gdk_pixmap_ref (pixmap);
    gdk_pixmap_ref (buffer_pixmap);

    d->pixmap          = pixmap;
    d->buffer_pixmap = buffer_pixmap;
    d->gc        = gdk_gc_new (pixmap);

    d->picture = XRenderCreatePicture (xdisplay, GDK_PIXMAP_XID (buffer_pixmap),
                               xformat, 0, NULL);

    d->width  = width;
    d->height = height;

    d->prop_xid = wnck_window_get_xid (win);

    queue_decor_draw (d);

    return TRUE;
}

static void
remove_frame_window (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");
    Display *xdisplay;

    xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

    if (d->pixmap)
    {
      gdk_pixmap_unref (d->pixmap);
      d->pixmap = NULL;
    }

    if (d->buffer_pixmap)
    {
      gdk_pixmap_unref (d->buffer_pixmap);
      d->buffer_pixmap = NULL;
    }

    if (d->gc)
    {
      gdk_gc_unref (d->gc);
      d->gc = NULL;
    }

    if (d->picture)
    {
      XRenderFreePicture (xdisplay, d->picture);
      d->picture = 0;
    }

    if (d->name)
    {
      g_free (d->name);
      d->name = NULL;
    }

    if (d->layout)
    {
      g_object_unref (G_OBJECT (d->layout));
      d->layout = NULL;
    }

    if (d->icon)
    {
      cairo_pattern_destroy (d->icon);
      d->icon = NULL;
    }

    if (d->icon_pixmap)
    {
      gdk_pixmap_unref (d->icon_pixmap);
      d->icon_pixmap = NULL;
    }

    if (d->icon_pixbuf)
    {
      gdk_pixbuf_unref (d->icon_pixbuf);
      d->icon_pixbuf = NULL;
    }

    if (d->force_quit_dialog)
    {
      GtkWidget *dialog = d->force_quit_dialog;

      d->force_quit_dialog = NULL;
      gtk_widget_destroy (dialog);
    }

    d->width  = 0;
    d->height = 0;

    d->decorated = FALSE;

    d->state   = 0;
    d->actions = 0;

    d->context = NULL;
    d->shadow  = NULL;

    draw_list = g_slist_remove (draw_list, d);
}

static void
window_name_changed (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    if (d->decorated)
    {
      if (!update_window_decoration_size (win))
          queue_decor_draw (d);
    }
}

static void
window_geometry_changed (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    if (d->decorated)
    {
      update_window_decoration_size (win);
      update_event_windows (win);
    }
}

static void
window_icon_changed (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    if (d->decorated)
    {
      update_window_decoration_icon (win);
      queue_decor_draw (d);
    }
}

static void
window_state_changed (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    if (d->decorated)
    {
      update_window_decoration_state (win);
      if (update_window_decoration_size(win))
            update_event_windows (win);
      else
            queue_decor_draw(d);
    }
}

static void
window_actions_changed (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    if (d->decorated)
    {
      update_window_decoration_actions (win);
      if (update_window_decoration_size (win))
          update_event_windows (win);
      else
          queue_decor_draw (d);
    }
}

static void
connect_window (WnckWindow *win)
{
    g_signal_connect_object (win, "name_changed",
                       G_CALLBACK (window_name_changed),
                       0, 0);
    g_signal_connect_object (win, "geometry_changed",
                       G_CALLBACK (window_geometry_changed),
                       0, 0);
    g_signal_connect_object (win, "icon_changed",
                       G_CALLBACK (window_icon_changed),
                       0, 0);
    g_signal_connect_object (win, "state_changed",
                       G_CALLBACK (window_state_changed),
                       0, 0);
    g_signal_connect_object (win, "actions_changed",
                       G_CALLBACK (window_actions_changed),
                       0, 0);
}

static void
active_window_changed (WnckScreen *screen)
{
    WnckWindow *win;
    decor_t    *d;

    win = wnck_screen_get_previously_active_window (screen);
    if (win)
    {
      d = g_object_get_data (G_OBJECT (win), "decor");
      if (d->pixmap)
      {
          d->active = wnck_window_is_active (win);
          queue_decor_draw (d);
      }
    }

    win = wnck_screen_get_active_window (screen);
    if (win)
    {
      d = g_object_get_data (G_OBJECT (win), "decor");
      if (d->pixmap)
      {
          d->active = wnck_window_is_active (win);
          queue_decor_draw (d);
      }
    }
}

static void
window_opened (WnckScreen *screen,
             WnckWindow *win)
{
    decor_t *d;
    Window  window;
    gulong  xid;

    d = calloc (1, sizeof (decor_t));
    if (!d)
      return;

    d->active = wnck_window_is_active (win);

    d->draw = theme_draw_window_decoration;

    g_object_set_data (G_OBJECT (win), "decor", d);

    connect_window (win);

    xid = wnck_window_get_xid (win);

    if (get_window_prop (xid, select_window_atom, &window))
    {
      d->prop_xid = wnck_window_get_xid (win);
      update_switcher_window (win, window);
    }
    else if (get_window_prop (xid, frame_window_atom, &window))
    {
      add_frame_window (win, window);
    }
}

static void
window_closed (WnckScreen *screen,
             WnckWindow *win)
{
    Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    remove_frame_window (win);

    gdk_error_trap_push ();
    XDeleteProperty (xdisplay, wnck_window_get_xid (win), win_decor_atom);
    XSync (xdisplay, FALSE);
    gdk_error_trap_pop ();

    g_free (d);
}

static void
connect_screen (WnckScreen *screen)
{
    GList *windows;

    g_signal_connect_object (G_OBJECT (screen), "active_window_changed",
                       G_CALLBACK (active_window_changed),
                       0, 0);
    g_signal_connect_object (G_OBJECT (screen), "window_opened",
                       G_CALLBACK (window_opened),
                       0, 0);
    g_signal_connect_object (G_OBJECT (screen), "window_closed",
                       G_CALLBACK (window_closed),
                       0, 0);

    windows = wnck_screen_get_windows (screen);
    while (windows != NULL)
    {
      window_opened (screen, windows->data);
      windows = windows->next;
    }
}

static void
move_resize_window (WnckWindow *win,
                int            direction,
                XEvent     *xevent)
{
    Display    *xdisplay;
    GdkDisplay *gdkdisplay;
    GdkScreen  *screen;
    Window     xroot;
    XEvent     ev;

    gdkdisplay = gdk_display_get_default ();
    xdisplay   = GDK_DISPLAY_XDISPLAY (gdkdisplay);
    screen     = gdk_display_get_default_screen (gdkdisplay);
    xroot      = RootWindowOfScreen (gdk_x11_screen_get_xscreen (screen));

    if (action_menu_mapped)
    {
      gtk_object_destroy (GTK_OBJECT (action_menu));
      action_menu_mapped = FALSE;
      action_menu = NULL;
      return;
    }

    ev.xclient.type    = ClientMessage;
    ev.xclient.display = xdisplay;

    ev.xclient.serial     = 0;
    ev.xclient.send_event = TRUE;

    ev.xclient.window       = wnck_window_get_xid (win);
    ev.xclient.message_type = wm_move_resize_atom;
    ev.xclient.format       = 32;

    ev.xclient.data.l[0] = xevent->xbutton.x_root;
    ev.xclient.data.l[1] = xevent->xbutton.y_root;
    ev.xclient.data.l[2] = direction;
    ev.xclient.data.l[3] = xevent->xbutton.button;
    ev.xclient.data.l[4] = 1;

    XUngrabPointer (xdisplay, xevent->xbutton.time);
    XUngrabKeyboard (xdisplay, xevent->xbutton.time);

    XSendEvent (xdisplay, xroot, FALSE,
            SubstructureRedirectMask | SubstructureNotifyMask,
            &ev);

    XSync (xdisplay, FALSE);
}

static void
restack_window (WnckWindow *win,
            int      stack_mode)
{
    Display    *xdisplay;
    GdkDisplay *gdkdisplay;
    GdkScreen  *screen;
    Window     xroot;
    XEvent     ev;

    gdkdisplay = gdk_display_get_default ();
    xdisplay   = GDK_DISPLAY_XDISPLAY (gdkdisplay);
    screen     = gdk_display_get_default_screen (gdkdisplay);
    xroot      = RootWindowOfScreen (gdk_x11_screen_get_xscreen (screen));

    if (action_menu_mapped)
    {
      gtk_object_destroy (GTK_OBJECT (action_menu));
      action_menu_mapped = FALSE;
      action_menu = NULL;
      return;
    }

    ev.xclient.type    = ClientMessage;
    ev.xclient.display = xdisplay;

    ev.xclient.serial     = 0;
    ev.xclient.send_event = TRUE;

    ev.xclient.window       = wnck_window_get_xid (win);
    ev.xclient.message_type = restack_window_atom;
    ev.xclient.format       = 32;

    ev.xclient.data.l[0] = 2;
    ev.xclient.data.l[1] = None;
    ev.xclient.data.l[2] = stack_mode;
    ev.xclient.data.l[3] = 0;
    ev.xclient.data.l[4] = 0;

    XSendEvent (xdisplay, xroot, FALSE,
            SubstructureRedirectMask | SubstructureNotifyMask,
            &ev);

    XSync (xdisplay, FALSE);
}

/* stolen from gtktooltip.c */

#define DEFAULT_DELAY 500           /* Default delay in ms */
#define STICKY_DELAY 0              /* Delay before popping up next tip
                             * if we're sticky
                             */
#define STICKY_REVERT_DELAY 1000    /* Delay before sticky tooltips revert
                             * to normal
                             */

static void
show_tooltip (const char *text)
{
    GdkDisplay     *gdkdisplay;
    GtkRequisition requisition;
    gint       x, y, w, h;
    GdkScreen        *screen;
    gint       monitor_num;
    GdkRectangle   monitor;

    gdkdisplay = gdk_display_get_default ();

    gtk_label_set_text (GTK_LABEL (tip_label), text);

    gtk_widget_size_request (tip_window, &requisition);

    w = requisition.width;
    h = requisition.height;

    gdk_display_get_pointer (gdkdisplay, &screen, &x, &y, NULL);

    x -= (w / 2 + 4);

    monitor_num = gdk_screen_get_monitor_at_point (screen, x, y);
    gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);

    if ((x + w) > monitor.x + monitor.width)
      x -= (x + w) - (monitor.x + monitor.width);
    else if (x < monitor.x)
      x = monitor.x;

    if ((y + h + 16) > monitor.y + monitor.height)
      y = y - h - 16;
    else
      y = y + 16;

    gtk_window_move (GTK_WINDOW (tip_window), x, y);
    gtk_widget_show (tip_window);
}

static void
hide_tooltip (void)
{
    if (GTK_WIDGET_VISIBLE (tip_window))
      g_get_current_time (&tooltip_last_popdown);

    gtk_widget_hide (tip_window);

    if (tooltip_timer_tag)
    {
      g_source_remove (tooltip_timer_tag);
      tooltip_timer_tag = 0;
    }
}

static gboolean
tooltip_recently_shown (void)
{
    GTimeVal now;
    glong    msec;

    g_get_current_time (&now);

    msec = (now.tv_sec - tooltip_last_popdown.tv_sec) * 1000 +
      (now.tv_usec - tooltip_last_popdown.tv_usec) / 1000;

    return (msec < STICKY_REVERT_DELAY);
}

static gint
tooltip_timeout (gpointer data)
{
    tooltip_timer_tag = 0;

    show_tooltip ((const char *) data);

    return FALSE;
}

static void
tooltip_start_delay (const char *text)
{
    guint delay = DEFAULT_DELAY;

    if (tooltip_timer_tag)
      return;

    if (tooltip_recently_shown ())
      delay = STICKY_DELAY;

    tooltip_timer_tag = g_timeout_add (delay,
                               tooltip_timeout,
                               (gpointer) text);
}

static gint
tooltip_paint_window (GtkWidget *tooltip)
{
    GtkRequisition req;

    gtk_widget_size_request (tip_window, &req);
    gtk_paint_flat_box (tip_window->style, tip_window->window,
                  GTK_STATE_NORMAL, GTK_SHADOW_OUT,
                  NULL, GTK_WIDGET (tip_window), "tooltip",
                  0, 0, req.width, req.height);

    return FALSE;
}

static gboolean
create_tooltip_window (void)
{
    tip_window = gtk_window_new (GTK_WINDOW_POPUP);

    gtk_widget_set_app_paintable (tip_window, TRUE);
    gtk_window_set_resizable (GTK_WINDOW (tip_window), FALSE);
    gtk_widget_set_name (tip_window, "gtk-tooltips");
    gtk_container_set_border_width (GTK_CONTAINER (tip_window), 4);

#if GTK_CHECK_VERSION (2, 10, 0)
    if (!gtk_check_version (2, 10, 0))
      gtk_window_set_type_hint (GTK_WINDOW (tip_window),
                          GDK_WINDOW_TYPE_HINT_TOOLTIP);
#endif

    g_signal_connect_swapped (tip_window,
                        "expose_event",
                        G_CALLBACK (tooltip_paint_window),
                        0);

    tip_label = gtk_label_new (NULL);
    gtk_label_set_line_wrap (GTK_LABEL (tip_label), TRUE);
    gtk_misc_set_alignment (GTK_MISC (tip_label), 0.5, 0.5);
    gtk_widget_show (tip_label);

    gtk_container_add (GTK_CONTAINER (tip_window), tip_label);

    gtk_widget_ensure_style (tip_window);

    return TRUE;
}

static void
handle_tooltip_event (WnckWindow *win,
                  XEvent     *xevent,
                  guint  state,
                  const char *tip)
{
    switch (xevent->type) {
    case ButtonPress:
      hide_tooltip ();
      break;
    case ButtonRelease:
      break;
    case EnterNotify:
      if (!(state & PRESSED_EVENT_WINDOW))
      {
          if (wnck_window_is_active (win))
            tooltip_start_delay (tip);
      }
      break;
    case LeaveNotify:
      hide_tooltip ();
      break;
    }
}

static void
close_button_event (WnckWindow *win,
                XEvent     *xevent)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");
    guint   state = d->button_states[0];

    handle_tooltip_event (win, xevent, state, "Close Window");

    switch (xevent->type) {
    case ButtonPress:
      if (xevent->xbutton.button == 1)
          d->button_states[0] |= PRESSED_EVENT_WINDOW;
      break;
    case ButtonRelease:
      if (xevent->xbutton.button == 1)
      {
          if (d->button_states[0] == (PRESSED_EVENT_WINDOW | IN_EVENT_WINDOW))
            wnck_window_close (win, xevent->xbutton.time);

          d->button_states[0] &= ~PRESSED_EVENT_WINDOW;
      }
      break;
    case EnterNotify:
      d->button_states[0] |= IN_EVENT_WINDOW;
      break;
    case LeaveNotify:
      d->button_states[0] &= ~IN_EVENT_WINDOW;
      break;
    }

    if (state != d->button_states[0])
      queue_decor_draw (d);
}

static void
max_button_event (WnckWindow *win,
              XEvent     *xevent)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");
    guint   state = d->button_states[1];

    if (wnck_window_is_maximized (win))
      handle_tooltip_event (win, xevent, state, "Unmaximize Window");
    else
      handle_tooltip_event (win, xevent, state, "Maximize Window");

    switch (xevent->type) {
    case ButtonPress:
      if (xevent->xbutton.button == 1)
          d->button_states[1] |= PRESSED_EVENT_WINDOW;
      break;
    case ButtonRelease:
      if (xevent->xbutton.button == 1)
      {
          if (d->button_states[1] == (PRESSED_EVENT_WINDOW | IN_EVENT_WINDOW))
          {
            if (wnck_window_is_maximized (win))
                wnck_window_unmaximize (win);
            else
                wnck_window_maximize (win);
          }

          d->button_states[1] &= ~PRESSED_EVENT_WINDOW;
      }
      break;
    case EnterNotify:
      d->button_states[1] |= IN_EVENT_WINDOW;
      break;
    case LeaveNotify:
      d->button_states[1] &= ~IN_EVENT_WINDOW;
      break;
    }

    if (state != d->button_states[1])
      queue_decor_draw (d);
}

static void
min_button_event (WnckWindow *win,
              XEvent     *xevent)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");
    guint   state = d->button_states[2];

    handle_tooltip_event (win, xevent, state, "Minimize Window");

    switch (xevent->type) {
    case ButtonPress:
      if (xevent->xbutton.button == 1)
          d->button_states[2] |= PRESSED_EVENT_WINDOW;
      break;
    case ButtonRelease:
      if (xevent->xbutton.button == 1)
      {
          if (d->button_states[2] == (PRESSED_EVENT_WINDOW | IN_EVENT_WINDOW))
            wnck_window_minimize (win);

          d->button_states[2] &= ~PRESSED_EVENT_WINDOW;
      }
      break;
    case EnterNotify:
      d->button_states[2] |= IN_EVENT_WINDOW;
      if (wnck_window_is_active (win))
          tooltip_start_delay ("Minimize Window");
      break;
    case LeaveNotify:
      d->button_states[2] &= ~IN_EVENT_WINDOW;
      break;
    }

    if (state != d->button_states[2])
      queue_decor_draw (d);
}

static void
top_left_event (WnckWindow *win,
            XEvent     *xevent)
{
    if (xevent->xbutton.button == 1)
      move_resize_window (win, WM_MOVERESIZE_SIZE_TOPLEFT, xevent);
}

static void
top_event (WnckWindow *win,
         XEvent     *xevent)
{
    if (xevent->xbutton.button == 1)
      move_resize_window (win, WM_MOVERESIZE_SIZE_TOP, xevent);
}

static void
top_right_event (WnckWindow *win,
             XEvent     *xevent)
{
    if (xevent->xbutton.button == 1)
      move_resize_window (win, WM_MOVERESIZE_SIZE_TOPRIGHT, xevent);
}

static void
left_event (WnckWindow *win,
          XEvent     *xevent)
{
    if (xevent->xbutton.button == 1)
      move_resize_window (win, WM_MOVERESIZE_SIZE_LEFT, xevent);
}

static void
action_menu_unmap (GObject *object)
{
    action_menu_mapped = FALSE;
}

static void
position_action_menu (GtkMenu  *menu,
                  gint     *x,
                  gint     *y,
                  gboolean *push_in,
                  gpointer user_data)
{
    WnckWindow *win = (WnckWindow *) user_data;

    wnck_window_get_geometry (win, x, y, NULL, NULL);

    *push_in = TRUE;
}

static void
action_menu_map (WnckWindow *win,
             long     button,
             Time     time)
{
    GdkDisplay *gdkdisplay;
    GdkScreen  *screen;

    gdkdisplay = gdk_display_get_default ();
    screen     = gdk_display_get_default_screen (gdkdisplay);

    if (action_menu)
    {
      if (action_menu_mapped)
      {
          gtk_widget_destroy (action_menu);
          action_menu_mapped = FALSE;
          action_menu = NULL;
          return;
      }
      else
          gtk_widget_destroy (action_menu);
    }

    switch (wnck_window_get_window_type (win)) {
    case WNCK_WINDOW_DESKTOP:
    case WNCK_WINDOW_DOCK:
      /* don't allow window action */
      return;
    case WNCK_WINDOW_NORMAL:
    case WNCK_WINDOW_DIALOG:
    case WNCK_WINDOW_MODAL_DIALOG:
    case WNCK_WINDOW_TOOLBAR:
    case WNCK_WINDOW_MENU:
    case WNCK_WINDOW_UTILITY:
    case WNCK_WINDOW_SPLASHSCREEN:
      /* allow window action menu */
      break;
    }

    action_menu = wnck_create_window_action_menu (win);

    gtk_menu_set_screen (GTK_MENU (action_menu), screen);

    g_signal_connect_object (G_OBJECT (action_menu), "unmap",
                       G_CALLBACK (action_menu_unmap),
                       0, 0);

    gtk_widget_show (action_menu);

    if (button)
      gtk_menu_popup (GTK_MENU (action_menu),
                  NULL, NULL,
                  NULL, NULL,
                  button,
                  time);
    else
      gtk_menu_popup (GTK_MENU (action_menu),
                  NULL, NULL,
                  position_action_menu, (gpointer) win,
                  button,
                  time);

    action_menu_mapped = TRUE;
}

static double
square (double x)
{
    return x * x;
}

static double
dist (double x1, double y1,
      double x2, double y2)
{
    return sqrt (square (x1 - x2) + square (y1 - y2));
}

static void
title_event (WnckWindow *win,
           XEvent     *xevent)
{
    static int      last_button_num = 0;
    static Window last_button_xwindow = None;
    static Time     last_button_time = 0;
    static int      last_button_x = 0;
    static int      last_button_y = 0;

    if (xevent->type != ButtonPress)
      return;

    if (xevent->xbutton.button == 1)
    {
      if (xevent->xbutton.button == last_button_num                 &&
          xevent->xbutton.window == last_button_xwindow             &&
          xevent->xbutton.time < last_button_time + double_click_timeout &&
          dist (xevent->xbutton.x, xevent->xbutton.y,
              last_button_x, last_button_y) < DOUBLE_CLICK_DISTANCE)
      {
          switch (double_click_action) {
          case DOUBLE_CLICK_SHADE:
            if (wnck_window_is_shaded (win))
                wnck_window_unshade (win);
            else
                wnck_window_shade (win);
            break;
          case DOUBLE_CLICK_MAXIMIZE:
            if (wnck_window_is_maximized (win))
                wnck_window_unmaximize (win);
            else
                wnck_window_maximize (win);
          default:
            break;
          }

          last_button_num     = 0;
          last_button_xwindow = None;
          last_button_time    = 0;
          last_button_x = 0;
          last_button_y = 0;
      }
      else
      {
          last_button_num     = xevent->xbutton.button;
          last_button_xwindow = xevent->xbutton.window;
          last_button_time    = xevent->xbutton.time;
          last_button_x = xevent->xbutton.x;
          last_button_y = xevent->xbutton.y;

          restack_window (win, Above);

          move_resize_window (win, WM_MOVERESIZE_MOVE, xevent);
      }
    }
    else if (xevent->xbutton.button == 2)
    {
      restack_window (win, Below);
    }
    else if (xevent->xbutton.button == 3)
    {
      action_menu_map (win,
                   xevent->xbutton.button,
                   xevent->xbutton.time);
    }
      else if (xevent->xbutton.button == 4)
      {
            if(!wnck_window_is_shaded(win))
                  wnck_window_shade(win);
      }
      else if (xevent->xbutton.button == 5)
      {
            if(wnck_window_is_shaded(win))
                  wnck_window_unshade(win);
      }
}

static void
right_event (WnckWindow *win,
           XEvent     *xevent)
{
    if (xevent->xbutton.button == 1)
      move_resize_window (win, WM_MOVERESIZE_SIZE_RIGHT, xevent);
}

static void
bottom_left_event (WnckWindow *win,
               XEvent     *xevent)
{
    if (xevent->xbutton.button == 1)
      move_resize_window (win, WM_MOVERESIZE_SIZE_BOTTOMLEFT, xevent);
}

static void
bottom_event (WnckWindow *win,
            XEvent     *xevent)
{
    if (xevent->xbutton.button == 1)
      move_resize_window (win, WM_MOVERESIZE_SIZE_BOTTOM, xevent);
}

static void
bottom_right_event (WnckWindow *win,
                XEvent     *xevent)
{
    if (xevent->xbutton.button == 1)
      move_resize_window (win, WM_MOVERESIZE_SIZE_BOTTOMRIGHT, xevent);
}

static void
panel_action (Display *xdisplay,
            Window  root,
            Atom    panel_action,
            Time    event_time)
{
    XEvent ev;

    ev.type           = ClientMessage;
    ev.xclient.window       = root;
    ev.xclient.message_type = panel_action_atom;
    ev.xclient.format       = 32;
    ev.xclient.data.l[0]    = panel_action;
    ev.xclient.data.l[1]    = event_time;
    ev.xclient.data.l[2]    = 0;
    ev.xclient.data.l[3]    = 0;
    ev.xclient.data.l[4]    = 0;

    XSendEvent (xdisplay, root, FALSE, StructureNotifyMask, &ev);
}

static void
force_quit_dialog_realize (GtkWidget *dialog,
                     void      *data)
{
    WnckWindow *win = data;

    gdk_error_trap_push ();
    XSetTransientForHint (gdk_display,
                    GDK_WINDOW_XID (dialog->window),
                    wnck_window_get_xid (win));
    XSync (gdk_display, FALSE);
    gdk_error_trap_pop ();
}

static char *
get_client_machine (Window xwindow)
{
    Atom   atom, type;
    gulong nitems, bytes_after;
    gchar  *str = NULL;
    int    format, result;
    char   *retval;

    atom = XInternAtom (gdk_display, "WM_CLIENT_MACHINE", FALSE);

    gdk_error_trap_push ();

    result = XGetWindowProperty (gdk_display,
                         xwindow, atom,
                         0, G_MAXLONG,
                         FALSE, XA_STRING, &type, &format, &nitems,
                         &bytes_after, (guchar **) &str);

    gdk_error_trap_pop ();

    if (result != Success)
      return NULL;

    if (type != XA_STRING)
    {
      XFree (str);
      return NULL;
    }

    retval = g_strdup (str);

    XFree (str);

    return retval;
}

static void
kill_window (WnckWindow *win)
{
    WnckApplication *app;

    app = wnck_window_get_application (win);
    if (app)
    {
      gchar buf[257], *client_machine;
      int   pid;

      pid = wnck_application_get_pid (app);
      client_machine = get_client_machine (wnck_application_get_xid (app));

      if (client_machine && pid > 0)
      {
          if (gethostname (buf, sizeof (buf) - 1) == 0)
          {
            if (strcmp (buf, client_machine) == 0)
                kill (pid, 9);
          }
      }

      if (client_machine)
          g_free (client_machine);
    }

    gdk_error_trap_push ();
    XKillClient (gdk_display, wnck_window_get_xid (win));
    XSync (gdk_display, FALSE);
    gdk_error_trap_pop ();
}

static void
force_quit_dialog_response (GtkWidget *dialog,
                      gint      response,
                      void      *data)
{
    WnckWindow *win = data;
    decor_t    *d = g_object_get_data (G_OBJECT (win), "decor");

    if (response == GTK_RESPONSE_ACCEPT)
      kill_window (win);

    if (d->force_quit_dialog)
    {
      d->force_quit_dialog = NULL;
      gtk_widget_destroy (dialog);
    }
}

static void
show_force_quit_dialog (WnckWindow *win,
                  Time        timestamp)
{
    decor_t   *d = g_object_get_data (G_OBJECT (win), "decor");
    GtkWidget *dialog;
    gchar     *str, *tmp;

    if (d->force_quit_dialog)
      return;

    tmp = g_markup_escape_text (wnck_window_get_name (win), -1);
    str = g_strdup_printf ("The window \"%s\" is not responding.", tmp);

    g_free (tmp);

    dialog = gtk_message_dialog_new (NULL, 0,
                             GTK_MESSAGE_WARNING,
                             GTK_BUTTONS_NONE,
                             "<b>%s</b>\n\n%s",
                             str,
                             "Forcing this application to "
                             "quit will cause you to lose any "
                             "unsaved changes.");
    g_free (str);

    gtk_window_set_icon_name (GTK_WINDOW (dialog), "force-quit");

    gtk_label_set_use_markup (GTK_LABEL (GTK_MESSAGE_DIALOG (dialog)->label),
                        TRUE);
    gtk_label_set_line_wrap (GTK_LABEL (GTK_MESSAGE_DIALOG (dialog)->label),
                       TRUE);

    gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                      GTK_STOCK_CANCEL,
                      GTK_RESPONSE_REJECT,
                      "_Force Quit",
                      GTK_RESPONSE_ACCEPT,
                      NULL);

    gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT);

    g_signal_connect (G_OBJECT (dialog), "realize",
                  G_CALLBACK (force_quit_dialog_realize),
                  win);

    g_signal_connect (G_OBJECT (dialog), "response",
                  G_CALLBACK (force_quit_dialog_response),
                  win);

    gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);

    gtk_widget_realize (dialog);

    gdk_x11_window_set_user_time (dialog->window, timestamp);

    gtk_widget_show (dialog);

    d->force_quit_dialog = dialog;
}

static void
hide_force_quit_dialog (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    if (d->force_quit_dialog)
    {
      gtk_widget_destroy (d->force_quit_dialog);
      d->force_quit_dialog = NULL;
    }
}

static GdkFilterReturn
event_filter_func (GdkXEvent *gdkxevent,
               GdkEvent  *event,
               gpointer  data)
{
    Display    *xdisplay;
    GdkDisplay *gdkdisplay;
    XEvent     *xevent = gdkxevent;
    gulong     xid = 0;

    gdkdisplay = gdk_display_get_default ();
    xdisplay   = GDK_DISPLAY_XDISPLAY (gdkdisplay);

    switch (xevent->type) {
    case ButtonPress:
    case ButtonRelease:
      xid = (gulong)
          g_hash_table_lookup (frame_table,
                         GINT_TO_POINTER (xevent->xbutton.window));
      break;
    case EnterNotify:
    case LeaveNotify:
      xid = (gulong)
          g_hash_table_lookup (frame_table,
                         GINT_TO_POINTER (xevent->xcrossing.window));
      break;
    case MotionNotify:
      xid = (gulong)
          g_hash_table_lookup (frame_table,
                         GINT_TO_POINTER (xevent->xmotion.window));
      break;
    case PropertyNotify:
      if (xevent->xproperty.atom == frame_window_atom)
      {
          WnckWindow *win;

          xid = xevent->xproperty.window;

          win = wnck_window_get (xid);
          if (win)
          {
            Window frame, window;

            if (!get_window_prop (xid, select_window_atom, &window))
            {
                if (get_window_prop (xid, frame_window_atom, &frame))
                  add_frame_window (win, frame);
                else
                  remove_frame_window (win);
            }
          }
      }
      else if (xevent->xproperty.atom == mwm_hints_atom)
      {
          WnckWindow *win;

          xid = xevent->xproperty.window;

          win = wnck_window_get (xid);
          if (win)
          {
            decor_t  *d = g_object_get_data (G_OBJECT (win), "decor");
            gboolean decorated = FALSE;

            if (get_mwm_prop (xid) & (MWM_DECOR_ALL | MWM_DECOR_TITLE))
                decorated = TRUE;

            if (decorated != d->decorated)
            {
                d->decorated = decorated;
                if (decorated)
                {
                  d->context = NULL;
                  d->width = d->height = 0;

                  update_window_decoration_size (win);
                  update_event_windows (win);
                }
                else
                {
                  gdk_error_trap_push ();
                  XDeleteProperty (xdisplay, xid, win_decor_atom);
                  XSync (xdisplay, FALSE);
                  gdk_error_trap_pop ();
                }
            }
          }
      }
      else if (xevent->xproperty.atom == select_window_atom)
      {
          WnckWindow *win;

          xid = xevent->xproperty.window;

          win = wnck_window_get (xid);
          if (win)
          {
            Window select;

            if (get_window_prop (xid, select_window_atom, &select))
                update_switcher_window (win, select);
          }
      }
      break;
    case DestroyNotify:
      g_hash_table_remove (frame_table,
                       GINT_TO_POINTER (xevent->xproperty.window));
      break;
    case ClientMessage:
      if (xevent->xclient.message_type == toolkit_action_atom)
      {
          long action;

          action = xevent->xclient.data.l[0];
          if (action == toolkit_action_main_menu_atom)
          {
            panel_action (xdisplay, xevent->xclient.window,
                        panel_action_main_menu_atom,
                        xevent->xclient.data.l[1]);
          }
          else if (action == toolkit_action_run_dialog_atom)
          {
            panel_action (xdisplay, xevent->xclient.window,
                        panel_action_run_dialog_atom,
                        xevent->xclient.data.l[1]);
          }
          else if (action == toolkit_action_window_menu_atom)
          {
            WnckWindow *win;

            win = wnck_window_get (xevent->xclient.window);
            if (win)
            {
                action_menu_map (win,
                             xevent->xclient.data.l[2],
                             xevent->xclient.data.l[1]);
            }
          }
          else if (action == toolkit_action_force_quit_dialog_atom)
          {
            WnckWindow *win;

            win = wnck_window_get (xevent->xclient.window);
            if (win)
            {
                if (xevent->xclient.data.l[2])
                  show_force_quit_dialog (win,
                                    xevent->xclient.data.l[1]);
                else
                  hide_force_quit_dialog (win);
            }
          }
      }
    default:
      break;
    }

    if (xid)
    {
      WnckWindow *win;

      win = wnck_window_get (xid);
      if (win)
      {
          static event_callback callback[3][3] = {
            { top_left_event,    top_event,    top_right_event    },
            { left_event,          title_event,  right_event            },
            { bottom_left_event, bottom_event, bottom_right_event }
          };
          static event_callback button_callback[3] = {
            close_button_event,
            max_button_event,
            min_button_event
          };
          decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

          if (d->decorated)
          {
            gint i, j;

            for (i = 0; i < 3; i++)
                for (j = 0; j < 3; j++)
                  if (d->event_windows[i][j] == xevent->xany.window)
                      (*callback[i][j]) (win, xevent);

            for (i = 0; i < 3; i++)
                if (d->button_windows[i] == xevent->xany.window)
                  (*button_callback[i]) (win, xevent);
          }
      }
    }

    return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn
selection_event_filter_func (GdkXEvent *gdkxevent,
                       GdkEvent  *event,
                       gpointer  data)
{
    Display    *xdisplay;
    GdkDisplay *gdkdisplay;
    XEvent     *xevent = gdkxevent;
    int            status;

    gdkdisplay = gdk_display_get_default ();
    xdisplay   = GDK_DISPLAY_XDISPLAY (gdkdisplay);

    switch (xevent->type) {
    case SelectionRequest:
      decor_handle_selection_request (xdisplay, xevent, dm_sn_timestamp);
      break;
    case SelectionClear:
      status = decor_handle_selection_clear (xdisplay, xevent, 0);
      if (status == DECOR_SELECTION_GIVE_UP)
          exit (0);
    default:
      break;
    }

    return GDK_FILTER_CONTINUE;
}


/* from clearlooks theme */
static void
rgb_to_hls (gdouble *r,
          gdouble *g,
          gdouble *b)
{
    gdouble min;
    gdouble max;
    gdouble red;
    gdouble green;
    gdouble blue;
    gdouble h, l, s;
    gdouble delta;

    red = *r;
    green = *g;
    blue = *b;

    if (red > green)
    {
      if (red > blue)
          max = red;
      else
          max = blue;

      if (green < blue)
          min = green;
      else
          min = blue;
    }
    else
    {
      if (green > blue)
          max = green;
      else
          max = blue;

      if (red < blue)
          min = red;
      else
          min = blue;
    }

    l = (max + min) / 2;
    s = 0;
    h = 0;

    if (max != min)
    {
      if (l <= 0.5)
          s = (max - min) / (max + min);
      else
          s = (max - min) / (2 - max - min);

      delta = max -min;
      if (red == max)
          h = (green - blue) / delta;
      else if (green == max)
          h = 2 + (blue - red) / delta;
      else if (blue == max)
          h = 4 + (red - green) / delta;

      h *= 60;
      if (h < 0.0)
          h += 360;
    }

    *r = h;
    *g = l;
    *b = s;
}

static void
hls_to_rgb (gdouble *h,
          gdouble *l,
          gdouble *s)
{
    gdouble hue;
    gdouble lightness;
    gdouble saturation;
    gdouble m1, m2;
    gdouble r, g, b;

    lightness = *l;
    saturation = *s;

    if (lightness <= 0.5)
      m2 = lightness * (1 + saturation);
    else
      m2 = lightness + saturation - lightness * saturation;

    m1 = 2 * lightness - m2;

    if (saturation == 0)
    {
      *h = lightness;
      *l = lightness;
      *s = lightness;
    }
    else
    {
      hue = *h + 120;
      while (hue > 360)
          hue -= 360;
      while (hue < 0)
          hue += 360;

      if (hue < 60)
          r = m1 + (m2 - m1) * hue / 60;
      else if (hue < 180)
          r = m2;
      else if (hue < 240)
          r = m1 + (m2 - m1) * (240 - hue) / 60;
      else
          r = m1;

      hue = *h;
      while (hue > 360)
          hue -= 360;
      while (hue < 0)
          hue += 360;

      if (hue < 60)
          g = m1 + (m2 - m1) * hue / 60;
      else if (hue < 180)
          g = m2;
      else if (hue < 240)
          g = m1 + (m2 - m1) * (240 - hue) / 60;
      else
          g = m1;

      hue = *h - 120;
      while (hue > 360)
          hue -= 360;
      while (hue < 0)
          hue += 360;

      if (hue < 60)
          b = m1 + (m2 - m1) * hue / 60;
      else if (hue < 180)
          b = m2;
      else if (hue < 240)
          b = m1 + (m2 - m1) * (240 - hue) / 60;
      else
          b = m1;

      *h = r;
      *l = g;
      *s = b;
    }
}

static void
shade (const decor_color_t *a,
       decor_color_t       *b,
       float               k)
{
    double red;
    double green;
    double blue;

    red   = a->r;
    green = a->g;
    blue  = a->b;

    rgb_to_hls (&red, &green, &blue);

    green *= k;
    if (green > 1.0)
      green = 1.0;
    else if (green < 0.0)
      green = 0.0;

    blue *= k;
    if (blue > 1.0)
      blue = 1.0;
    else if (blue < 0.0)
      blue = 0.0;

    hls_to_rgb (&red, &green, &blue);

    b->r = red;
    b->g = green;
    b->b = blue;
}

static void
update_style (GtkWidget *widget)
{
    GtkStyle      *style;
    decor_color_t spot_color;

    style = gtk_widget_get_style (widget);
    gtk_style_attach (style, widget->window);

    spot_color.r = style->bg[GTK_STATE_SELECTED].red   / 65535.0;
    spot_color.g = style->bg[GTK_STATE_SELECTED].green / 65535.0;
    spot_color.b = style->bg[GTK_STATE_SELECTED].blue  / 65535.0;

    shade (&spot_color, &_title_color[0], 1.05);
    shade (&_title_color[0], &_title_color[1], 0.85);
}

/* to save some memory, value is specific to current decorations */
#define TRANSLUCENT_CORNER_SIZE 3

static void
draw_border_shape (Display       *xdisplay,
               Pixmap      pixmap,
               Picture     picture,
               int               width,
               int               height,
               decor_context_t *c,
               void              *closure)
{
    static XRenderColor white = { 0xffff, 0xffff, 0xffff, 0xffff };
    GdkScreen           *screen;
    GdkColormap         *colormap;
    decor_t       d;
    double        save_decoration_alpha;

    memset (&d, 0, sizeof (d));

    d.pixmap  = gdk_pixmap_foreign_new_for_display (gdk_display_get_default (),
                                        pixmap);
    d.width   = width;
    d.height  = height;
    d.active  = TRUE;
    d.draw    = theme_draw_window_decoration;
    d.picture = picture;
    d.context = c;

    /* we use closure argument if maximized */
    if (closure)
      d.state |=
          WNCK_WINDOW_STATE_MAXIMIZED_HORIZONTALLY |
          WNCK_WINDOW_STATE_MAXIMIZED_VERTICALLY;

    decor_get_default_layout (c, 1, 1, &d.border_layout);

    screen   = gdk_display_get_default_screen (gdk_display_get_default ());
    colormap = gdk_screen_get_rgba_colormap (screen);

    gdk_drawable_set_colormap (d.pixmap, colormap);

    /* create shadow from opaque decoration */
    save_decoration_alpha = decoration_alpha;
    decoration_alpha = 1.0;

    (*d.draw) (&d);

    decoration_alpha = save_decoration_alpha;

    XRenderFillRectangle (xdisplay, PictOpSrc, picture, &white,
                    c->left_space,
                    c->top_space,
                    width - c->left_space - c->right_space,
                    height - c->top_space - c->bottom_space);

    gdk_pixmap_unref (d.pixmap);
}

static int
update_shadow (void)
{
    decor_shadow_options_t opt;
    Display          *xdisplay = gdk_display;
    GdkDisplay             *display = gdk_display_get_default ();
    GdkScreen              *screen = gdk_display_get_default_screen (display);

    opt.shadow_radius  = shadow_radius;
    opt.shadow_opacity = shadow_opacity;

    memcpy (opt.shadow_color, shadow_color, sizeof (shadow_color));

    opt.shadow_offset_x = shadow_offset_x;
    opt.shadow_offset_y = shadow_offset_y;

    if (no_border_shadow)
    {
      decor_shadow_destroy (xdisplay, no_border_shadow);
      no_border_shadow = NULL;
    }

    no_border_shadow = decor_shadow_create (xdisplay,
                                  gdk_x11_screen_get_xscreen (screen),
                                  1, 1,
                                  0,
                                  0,
                                  0,
                                  0,
                                  0, 0, 0, 0,
                                  &opt,
                                  &shadow_context,
                                  decor_draw_simple,
                                  0);

    if (border_shadow)
    {
      decor_shadow_destroy (xdisplay, border_shadow);
      border_shadow = NULL;
    }

    border_shadow = decor_shadow_create (xdisplay,
                               gdk_x11_screen_get_xscreen (screen),
                               1, 1,
                               _win_extents.left,
                               _win_extents.right,
                               _win_extents.top + titlebar_height,
                               _win_extents.bottom,
                               _win_extents.left -
                               _win_extents.left -
                               TRANSLUCENT_CORNER_SIZE,
                               _win_extents.right -
                               TRANSLUCENT_CORNER_SIZE,
                               _win_extents.top + titlebar_height -
                               TRANSLUCENT_CORNER_SIZE,
                               _win_extents.bottom -
                               TRANSLUCENT_CORNER_SIZE,
                               &opt,
                               &window_context,
                               draw_border_shape,
                               0);

    if (max_border_shadow)
    {
      decor_shadow_destroy (xdisplay, max_border_shadow);
      max_border_shadow = NULL;
    }

    max_border_shadow =
      decor_shadow_create (xdisplay,
                       gdk_x11_screen_get_xscreen (screen),
                       1, 1,
                       _max_win_extents.left,
                       _max_win_extents.right,
                       _max_win_extents.top + max_titlebar_height,
                       _max_win_extents.bottom,
                       _max_win_extents.left -
                       _max_win_extents.left - TRANSLUCENT_CORNER_SIZE,
                       _max_win_extents.right - TRANSLUCENT_CORNER_SIZE,
                       _max_win_extents.top + max_titlebar_height -
                       TRANSLUCENT_CORNER_SIZE,
                       _max_win_extents.bottom - TRANSLUCENT_CORNER_SIZE,
                       &opt,
                       &max_window_context,
                       draw_border_shape,
                       (void *) 1);

    if (switcher_shadow)
    {
      decor_shadow_destroy (xdisplay, switcher_shadow);
      switcher_shadow = NULL;
    }

    switcher_shadow = decor_shadow_create (xdisplay,
                                 gdk_x11_screen_get_xscreen (screen),
                                 1, 1,
                                 _switcher_extents.left,
                                 _switcher_extents.right,
                                 _switcher_extents.top,
                                 _switcher_extents.bottom,
                                 _switcher_extents.left -
                                 TRANSLUCENT_CORNER_SIZE,
                                 _switcher_extents.right -
                                 TRANSLUCENT_CORNER_SIZE,
                                 _switcher_extents.top -
                                 TRANSLUCENT_CORNER_SIZE,
                                 _switcher_extents.bottom -
                                 TRANSLUCENT_CORNER_SIZE,
                                 &opt,
                                 &switcher_context,
                                 decor_draw_simple,
                                 0);

    return 1;
}

static void
update_window_decoration (WnckWindow *win)
{
    decor_t *d = g_object_get_data (G_OBJECT (win), "decor");

    if (d->decorated)
    {
      /* force size update */
      d->context = NULL;
      d->width = d->height = 0;

      update_window_decoration_size (win);
      update_event_windows (win);
    }
    else
    {
      Window xid = wnck_window_get_xid (win);
      Window select;

      if (get_window_prop (xid, select_window_atom, &select))
      {
          /* force size update */
          d->context = NULL;
          d->width = d->height = 0;
          switcher_width = switcher_height = 0;

          update_switcher_window (win, select);
      }
    }
}

static void
style_changed (GtkWidget *widget)
{
    GdkDisplay *gdkdisplay;
    GdkScreen  *gdkscreen;
    WnckScreen *screen;
    GList      *windows;

    gdkdisplay = gdk_display_get_default ();
    gdkscreen  = gdk_display_get_default_screen (gdkdisplay);
    screen     = wnck_screen_get_default ();

    update_style (widget);

    update_default_decorations (gdkscreen);

    if (minimal)
      return;

    windows = wnck_screen_get_windows (screen);
    while (windows != NULL)
    {
      update_window_decoration (WNCK_WINDOW (windows->data));
      windows = windows->next;
    }
}

static const PangoFontDescription *
get_titlebar_font (void)
{
    if (use_system_font)
    {
      return NULL;
    }
    else
      return titlebar_font;
}

static void
titlebar_font_changed (GConfClient *client)
{
    gchar *str;

    str = gconf_client_get_string (client,
                           COMPIZ_TITLEBAR_FONT_KEY,
                           NULL);
    if (!str)
      str = g_strdup ("Sans Bold 12");

    if (titlebar_font)
      pango_font_description_free (titlebar_font);

    titlebar_font = pango_font_description_from_string (str);

    g_free (str);
}

static void
double_click_titlebar_changed (GConfClient *client)
{
    gchar *action;

    double_click_action = DOUBLE_CLICK_MAXIMIZE;

    action = gconf_client_get_string (client,
                              COMPIZ_DOUBLE_CLICK_TITLEBAR_KEY,
                              NULL);
    if (action)
    {
      if (strcmp (action, "toggle_shade") == 0)
          double_click_action = DOUBLE_CLICK_SHADE;
      else if (strcmp (action, "toggle_maximize") == 0)
          double_click_action = DOUBLE_CLICK_MAXIMIZE;

      g_free (action);
    }
}

static void
meta_update_border_extents (gint text_height)
{
    MetaTheme *theme;
    gint      top_height, bottom_height, left_width, right_width;

    theme = meta_theme_get_current ();

    meta_theme_get_frame_borders (theme,
                          META_FRAME_TYPE_NORMAL,
                          text_height, 0,
                          &top_height,
                          &bottom_height,
                          &left_width,
                          &right_width);

    _win_extents.top    = _default_win_extents.top;
    _win_extents.bottom = bottom_height;
    _win_extents.left   = left_width;
    _win_extents.right  = right_width;

    titlebar_height = top_height - _win_extents.top;

    meta_theme_get_frame_borders (theme,
                          META_FRAME_TYPE_NORMAL,
                          text_height, META_FRAME_MAXIMIZED,
                          &top_height,
                          &bottom_height,
                          &left_width,
                          &right_width);

    _max_win_extents.top    = _default_win_extents.top;
    _max_win_extents.bottom = bottom_height;
    _max_win_extents.left   = left_width;
    _max_win_extents.right  = right_width;

    max_titlebar_height = top_height - _max_win_extents.top;
}

static void
update_titlebar_font (void)
{
    const PangoFontDescription *font_desc;
    PangoFontMetrics           *metrics;
    PangoLanguage        *lang;

    font_desc = get_titlebar_font ();
    if (!font_desc)
    {
      GtkStyle *default_style;

      default_style = gtk_widget_get_default_style ();
      font_desc = default_style->font_desc;
    }

    pango_context_set_font_description (pango_context, font_desc);

    lang    = pango_context_get_language (pango_context);
    metrics = pango_context_get_metrics (pango_context, font_desc, lang);

    text_height = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) +
                        pango_font_metrics_get_descent (metrics));

    pango_font_metrics_unref (metrics);
}

static gboolean
shadow_settings_changed (GConfClient *client)
{
      return FALSE;
    double   radius, opacity;
    int      offset;
    gchar    *color;
    gboolean changed = FALSE;

    radius = gconf_client_get_float (client,
                             COMPIZ_SHADOW_RADIUS_KEY,
                             NULL);
    radius = MAX (0.0, MIN (radius, 48.0));
    if (shadow_radius != radius)
    {
      shadow_radius = radius;
      changed = TRUE;
    }

    opacity = gconf_client_get_float (client,
                              COMPIZ_SHADOW_OPACITY_KEY,
                              NULL);
    opacity = MAX (0.0, MIN (opacity, 6.0));
    if (shadow_opacity != opacity)
    {
      shadow_opacity = opacity;
      changed = TRUE;
    }

    color = gconf_client_get_string (client,
                             COMPIZ_SHADOW_COLOR_KEY,
                             NULL);
    if (color)
    {
      int c[4];

      if (sscanf (color, "#%2x%2x%2x%2x", &c[0], &c[1], &c[2], &c[3]) == 4)
      {
          shadow_color[0] = c[0] << 8 | c[0];
          shadow_color[1] = c[1] << 8 | c[1];
          shadow_color[2] = c[2] << 8 | c[2];
          changed = TRUE;
      }

      g_free (color);
    }

    offset = gconf_client_get_int (client,
                           COMPIZ_SHADOW_OFFSET_X_KEY,
                           NULL);
    offset = MAX (-16, MIN (offset, 16));
    if (shadow_offset_x != offset)
    {
      shadow_offset_x = offset;
      changed = TRUE;
    }

    offset = gconf_client_get_int (client,
                           COMPIZ_SHADOW_OFFSET_Y_KEY,
                           NULL);
    offset = MAX (-16, MIN (offset, 16));
    if (shadow_offset_y != offset)
    {
      shadow_offset_y = offset;
      changed = TRUE;
    }

    return changed;
}

static void
bell_settings_changed (GConfClient *client)
{
    gboolean audible, visual, fullscreen;
    gchar    *type;

    audible = gconf_client_get_bool (client,
                             META_AUDIBLE_BELL_KEY,
                             NULL);

    visual = gconf_client_get_bool (client,
                            META_VISUAL_BELL_KEY,
                            NULL);

    type = gconf_client_get_string (client,
                            META_VISUAL_BELL_TYPE_KEY,
                            NULL);

    if (type && strcmp (type, "fullscreen") == 0)
      fullscreen = TRUE;
    else
      fullscreen = FALSE;

    g_free (type);

    gconf_client_set_bool (client,
                     COMPIZ_AUDIBLE_BELL_KEY,
                     audible,
                     NULL);

    gconf_client_set_bool (client,
                     COMPIZ_VISUAL_BELL_KEY,
                     visual,
                     NULL);

    gconf_client_set_bool (client,
                     COMPIZ_FULLSCREEN_VISUAL_BELL_KEY,
                     fullscreen,
                     NULL);
}

static gboolean
theme_changed (GConfClient *client)
{

      gchar *theme;

      theme = gconf_client_get_string (client,
                               META_THEME_KEY,
                               NULL);

      if (theme)
      {
          meta_theme_set_current (theme, TRUE);

          g_free (theme);
      }

      theme_draw_window_decoration  = meta_draw_window_decoration;
      theme_calc_decoration_size    = meta_calc_decoration_size;
      theme_update_border_extents   = meta_update_border_extents;
      theme_get_event_window_position = meta_get_event_window_position;
      theme_get_button_position     = meta_get_button_position;
    return TRUE;

}

static gboolean
theme_opacity_changed (GConfClient *client)
{
      return FALSE;

    gboolean shade_opacity, changed = FALSE;
    gdouble  opacity;

    opacity = gconf_client_get_float (client,
                            META_THEME_OPACITY_KEY,
                            NULL);

    if (opacity != meta_opacity)
    {
      meta_opacity = opacity;
      changed = TRUE;
    }

    if (opacity < 1.0)
    {
      shade_opacity = gconf_client_get_bool (client,
                                   META_THEME_SHADE_OPACITY_KEY,
                                   NULL);

      if (shade_opacity != meta_shade_opacity)
      {
          meta_shade_opacity = shade_opacity;
          changed = TRUE;
      }
    }

    opacity = gconf_client_get_float (client,
                            META_THEME_ACTIVE_OPACITY_KEY,
                            NULL);

    if (opacity != meta_active_opacity)
    {
      meta_active_opacity = opacity;
      changed = TRUE;
    }

    if (opacity < 1.0)
    {
      shade_opacity =
          gconf_client_get_bool (client,
                           META_THEME_ACTIVE_SHADE_OPACITY_KEY,
                           NULL);

      if (shade_opacity != meta_active_shade_opacity)
      {
          meta_active_shade_opacity = shade_opacity;
          changed = TRUE;
      }
    }

    return changed;

}

static void
value_changed (GConfClient *client,
             const gchar *key,
             GConfValue  *value,
             void        *data)
{
    gboolean changed = FALSE;

    if (strcmp (key, COMPIZ_USE_SYSTEM_FONT_KEY) == 0)
    {
      if (gconf_client_get_bool (client,
                           COMPIZ_USE_SYSTEM_FONT_KEY,
                           NULL) != use_system_font)
      {
          use_system_font = !use_system_font;
          changed = TRUE;
      }
    }
    else if (strcmp (key, COMPIZ_TITLEBAR_FONT_KEY) == 0)
    {
      titlebar_font_changed (client);
      changed = !use_system_font;
    }
    else if (strcmp (key, COMPIZ_DOUBLE_CLICK_TITLEBAR_KEY) == 0)
    {
      double_click_titlebar_changed (client);
    }
    else if (strcmp (key, COMPIZ_SHADOW_RADIUS_KEY)   == 0 ||
           strcmp (key, COMPIZ_SHADOW_OPACITY_KEY)  == 0 ||
           strcmp (key, COMPIZ_SHADOW_OFFSET_X_KEY) == 0 ||
           strcmp (key, COMPIZ_SHADOW_OFFSET_Y_KEY) == 0 ||
           strcmp (key, COMPIZ_SHADOW_COLOR_KEY) == 0)
    {
      if (shadow_settings_changed (client))
          changed = TRUE;
    }
    else if (strcmp (key, META_AUDIBLE_BELL_KEY)     == 0 ||
           strcmp (key, META_VISUAL_BELL_KEY)      == 0 ||
           strcmp (key, META_VISUAL_BELL_TYPE_KEY) == 0)
    {
      bell_settings_changed (client);
    }
    else if (
           strcmp (key, META_THEME_KEY) == 0)
    {
      if (theme_changed (client))
          changed = TRUE;
    }
    else if (strcmp (key, META_THEME_OPACITY_KEY)            == 0 ||
           strcmp (key, META_THEME_SHADE_OPACITY_KEY)        == 0 ||
           strcmp (key, META_THEME_ACTIVE_OPACITY_KEY)       == 0 ||
           strcmp (key, META_THEME_ACTIVE_SHADE_OPACITY_KEY) == 0)
    {
      if (theme_opacity_changed (client))
          changed = TRUE;
    }

    if (changed)
    {
      GdkDisplay *gdkdisplay;
      GdkScreen  *gdkscreen;
      WnckScreen *screen = data;
      GList    *windows;

      gdkdisplay = gdk_display_get_default ();
      gdkscreen  = gdk_display_get_default_screen (gdkdisplay);

      update_titlebar_font ();
      (*theme_update_border_extents) (text_height);
      update_shadow ();

      update_default_decorations (gdkscreen);

      if (minimal)
          return;

      windows = wnck_screen_get_windows (screen);
      while (windows != NULL)
      {
          decor_t *d = g_object_get_data (G_OBJECT (windows->data), "decor");

          if (d->decorated)
          {

            if (
                d->draw == meta_draw_window_decoration)
                d->draw = theme_draw_window_decoration;

          }

          update_window_decoration (WNCK_WINDOW (windows->data));
          windows = windows->next;
      }
    }
}

static gboolean
init_settings (WnckScreen *screen)
{
    GtkSettings   *settings;
    GConfClient   *gconf;
    GdkScreen   *gdkscreen;
    GdkColormap *colormap;

    gconf = gconf_client_get_default ();

    gconf_client_add_dir (gconf,
                    GCONF_DIR,
                    GCONF_CLIENT_PRELOAD_ONELEVEL,
                    NULL);

    gconf_client_add_dir (gconf,
                    METACITY_GCONF_DIR,
                    GCONF_CLIENT_PRELOAD_ONELEVEL,
                    NULL);

    gconf_client_add_dir (gconf,
                    COMPIZ_GCONF_DIR1,
                    GCONF_CLIENT_PRELOAD_ONELEVEL,
                    NULL);

    gconf_client_add_dir (gconf,
                    COMPIZ_GCONF_DIR2,
                    GCONF_CLIENT_PRELOAD_ONELEVEL,
                    NULL);

    gconf_client_add_dir (gconf,
                    COMPIZ_GCONF_DIR3,
                    GCONF_CLIENT_PRELOAD_ONELEVEL,
                    NULL);

    g_signal_connect (G_OBJECT (gconf),
                  "value_changed",
                  G_CALLBACK (value_changed),
                  screen);

    style_window = gtk_window_new (GTK_WINDOW_POPUP);

    gdkscreen = gdk_display_get_default_screen (gdk_display_get_default ());
    colormap = gdk_screen_get_rgba_colormap (gdkscreen);
    if (colormap)
      gtk_widget_set_colormap (style_window, colormap);

    gtk_widget_realize (style_window);

    g_signal_connect_object (style_window, "style-set",
                       G_CALLBACK (style_changed),
                       0, 0);

    settings = gtk_widget_get_settings (style_window);

    g_object_get (G_OBJECT (settings), "gtk-double-click-time",
              &double_click_timeout, NULL);

    pango_context = gtk_widget_create_pango_context (style_window);

    use_system_font = gconf_client_get_bool (gconf,
                                   COMPIZ_USE_SYSTEM_FONT_KEY,
                                   NULL);

    theme_changed (gconf);
    theme_opacity_changed (gconf);
    update_style (style_window);
    titlebar_font_changed (gconf);
    update_titlebar_font ();
    double_click_titlebar_changed (gconf);
    shadow_settings_changed (gconf);
    bell_settings_changed (gconf);
    (*theme_update_border_extents) (text_height);
    update_shadow ();

    return TRUE;
}

int
main (int argc, char *argv[])
{
    GdkDisplay *gdkdisplay;
    Display    *xdisplay;
    GdkScreen  *gdkscreen;
    WnckScreen *screen;
    gint       i, j, status;
    gboolean   replace = FALSE;

    program_name = argv[0];

    gtk_init (&argc, &argv);

    for (i = 0; i < argc; i++)
    {
      if (strcmp (argv[i], "--minimal") == 0)
      {
          minimal = TRUE;
      }
      else  if (strcmp (argv[i], "--replace") == 0)
      {
          replace = TRUE;
      }
      else  if (strcmp (argv[i], "--help") == 0)
      {
          fprintf (stderr, "%s [--minimal] [--replace] [--help]\n",
                 program_name);
          return 0;
      }
    }

    gdkdisplay = gdk_display_get_default ();
    xdisplay   = gdk_x11_display_get_xdisplay (gdkdisplay);
    gdkscreen  = gdk_display_get_default_screen (gdkdisplay);

    frame_window_atom   = XInternAtom (xdisplay, "_NET_FRAME_WINDOW", FALSE);
    win_decor_atom      = XInternAtom (xdisplay, "_NET_WINDOW_DECOR", FALSE);
    wm_move_resize_atom = XInternAtom (xdisplay, "_NET_WM_MOVERESIZE", FALSE);
    restack_window_atom = XInternAtom (xdisplay, "_NET_RESTACK_WINDOW", FALSE);
    select_window_atom  = XInternAtom (xdisplay, "_SWITCH_SELECT_WINDOW",
                               FALSE);
    mwm_hints_atom      = XInternAtom (xdisplay, "_MOTIF_WM_HINTS", FALSE);

    toolkit_action_atom               =
      XInternAtom (xdisplay, "_COMPIZ_TOOLKIT_ACTION", FALSE);
    toolkit_action_main_menu_atom     =
      XInternAtom (xdisplay, "_COMPIZ_TOOLKIT_ACTION_MAIN_MENU", FALSE);
    toolkit_action_run_dialog_atom    =
      XInternAtom (xdisplay, "_COMPIZ_TOOLKIT_ACTION_RUN_DIALOG", FALSE);
    toolkit_action_window_menu_atom   =
      XInternAtom (xdisplay, "_COMPIZ_TOOLKIT_ACTION_WINDOW_MENU", FALSE);
    toolkit_action_force_quit_dialog_atom =
      XInternAtom (xdisplay, "_COMPIZ_TOOLKIT_ACTION_FORCE_QUIT_DIALOG",
                 FALSE);

    panel_action_atom          =
      XInternAtom (xdisplay, "_GNOME_PANEL_ACTION", FALSE);
    panel_action_main_menu_atom  =
      XInternAtom (xdisplay, "_GNOME_PANEL_ACTION_MAIN_MENU", FALSE);
    panel_action_run_dialog_atom =
      XInternAtom (xdisplay, "_GNOME_PANEL_ACTION_RUN_DIALOG", FALSE);

    status = decor_acquire_dm_session (xdisplay, DefaultScreen(xdisplay), "gwd",
                                                         replace, &dm_sn_timestamp);
    if (status != DECOR_ACQUIRE_STATUS_SUCCESS)
    {
      if (status == DECOR_ACQUIRE_STATUS_OTHER_DM_RUNNING)
      {
          fprintf (stderr,
                 "%s: Could not acquire decoration manager "
                 "selection on screen %d display \"%s\"\n",
                 program_name, DefaultScreen(xdisplay), DisplayString (xdisplay));
      }
      else if (status == DECOR_ACQUIRE_STATUS_OTHER_DM_RUNNING)
      {
          fprintf (stderr,
                 "%s: Screen %d on display \"%s\" already "
                 "has a decoration manager; try using the "
                 "--replace option to replace the current "
                 "decoration manager.\n",
                 program_name, DefaultScreen(xdisplay), DisplayString (xdisplay));
      }

      return 1;
    }

    for (i = 0; i < 3; i++)
    {
      for (j = 0; j < 3; j++)
      {
          if (cursor[i][j].shape != XC_left_ptr)
            cursor[i][j].cursor =
                XCreateFontCursor (xdisplay, cursor[i][j].shape);
      }
    }

    xformat = XRenderFindStandardFormat (xdisplay, PictStandardARGB32);

    frame_table = g_hash_table_new (NULL, NULL);

    if (!create_tooltip_window ())
    {
      fprintf (stderr, "%s, Couldn't create tooltip window\n", argv[0]);
      return 1;
    }

    screen = wnck_screen_get_default ();

    gdk_window_add_filter (NULL,
                     selection_event_filter_func,
                     NULL);

    if (!minimal)
    {
      gdk_window_add_filter (NULL,
                         event_filter_func,
                         NULL);

      connect_screen (screen);
    }

    if (!init_settings (screen))
    {
      fprintf (stderr, "%s: Failed to get necessary gtk settings\n", argv[0]);
      return 1;
    }

    decor_set_dm_check_hint (xdisplay, DefaultScreen(xdisplay));

    update_default_decorations (gdkscreen);

    gtk_main ();

    return 0;
}

Generated by  Doxygen 1.6.0   Back to index