Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/tutorius/overlayer.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/tutorius/overlayer.py')
-rw-r--r--src/tutorius/overlayer.py707
1 files changed, 707 insertions, 0 deletions
diff --git a/src/tutorius/overlayer.py b/src/tutorius/overlayer.py
new file mode 100644
index 0000000..f0d251e
--- /dev/null
+++ b/src/tutorius/overlayer.py
@@ -0,0 +1,707 @@
+"""
+This module manages drawing of overlayed widgets. The class responsible for
+drawing management (Overlayer) and basic overlayable widgets are defined here.
+"""
+# Copyright (C) 2009, Tutorius.org
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import pygtk
+pygtk.require('2.0')
+
+import gobject
+import gtk
+import cairo
+import pangocairo
+from math import pi
+
+from sugar import profile
+
+# for easy profile access from cairo
+color = profile.get_color().get_stroke_color()
+xo_line_color = (int(color[1:3], 16)/255.0,
+ int(color[3:5], 16)/255.0,
+ int(color[5:7], 16)/255.0)
+color = profile.get_color().get_fill_color()
+xo_fill_color = (int(color[1:3], 16)/255.0,
+ int(color[3:5], 16)/255.0,
+ int(color[5:7], 16)/255.0)
+del color
+
+# This is the CanvasDrawable protocol. Any widget wishing to be drawn on the
+# overlay must implement it. See TextBubble for a sample implementation.
+#class CanvasDrawable(object):
+# """Defines the CanvasDrawable protocol"""
+# no_expose = None
+# def draw_with_context(self, context):
+# """
+# Draws the cairo widget with the passed cairo context.
+# This will be called if the widget is child of an overlayer.
+# """
+# pass
+
+class Overlayer(gtk.Layout):
+ """
+ This guy manages drawing of overlayed widgets. Those can be standard GTK
+ widgets or special "cairoDrawable" widgets which support the defined
+ interface (see the put method).
+
+ @param overlayed widget to be overlayed. Will be resized to full size.
+ """
+
+ _instance = None
+ def __new__(klass, *args):
+ """
+ As the overlay runs in a separate window, bound to the activity's main
+ window, it doesn't make sense to have multiple instances.
+ For this reason, __new__ will enforce the singleton pattern and
+ return the same instance.
+
+ @param klass: the type to instanciate
+ @returns: the instance of Overlayer for this process.
+ """
+ return Overlayer._instance or gtk.Layout.__new__(klass)
+
+ def __init__(self, overlayed=None):
+ if Overlayer._instance:
+ return
+ Overlayer._instance = self
+ super(Overlayer, self).__init__()
+
+ _set_wm_compositing(True)
+
+ self._overlayed = overlayed
+ self._win = gtk.Window(gtk.WINDOW_POPUP)
+ self._win.set_property('skip-taskbar-hint', True)
+ self._win.set_property('skip-pager-hint', True)
+ self._win.set_accept_focus(False)
+ self._win.set_decorated(False)
+ self._win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NOTIFICATION)
+ self._win.set_keep_above(True)
+ self._win.set_app_paintable(True)
+
+ self._win.connect("screen-changed", self.__init_realized)
+ self._win.connect("expose-event", self.__expose_overlay)
+ self.__visible = True
+ gobject.timeout_add_seconds(5, self.__keep_visible)
+
+ # ensure colormap is set before realization, so the window gets
+ # realized with transparency enabled.
+ self.__init_realized(self._win)
+ self._win.show_all()
+
+ self.__render_handle = None
+
+ def __keep_visible(self, *args):
+ """
+ This is a hack to ensure the overlay window is always visible.
+ Ideally, it would be bound to the window, manager raising itself
+ with its parent window. Right now, it still is in the same process
+ as the activity, with the side-effect of disappearing along with any
+ popup that pops down, including menus and tooltips.
+
+ set self.__visible to false to stop this behaviour.
+ """
+ # TODO: use Activity WM tracking to ensure overlayers don't "fight"
+ # for the focus. We should also evaluate the benefit of completely
+ # extracting the overlayer from the activities and putting it as part of
+ # the tutorius service
+ # standalone service.
+ if self.__visible:
+ self._win.present()
+ gobject.timeout_add_seconds(5, self.__keep_visible)
+
+
+
+ def put(self, child, x, y, draggable=False, position_cb=None):
+ """
+ Adds a child widget to be overlayed. This can be, overlay widgets or
+ normal GTK widgets (though normal widgets will alwas appear under
+ cairo widgets due to the rendering chain).
+
+ @param child the child to add
+ @param x the horizontal coordinate for positionning
+ @param y the vertical coordinate for positionning
+ @param draggable: whether or not this widget's should have the
+ mouse-drag functionality enabled.
+ @param position_cb: a callback for obtaining position changes
+ if draggable was activated. Callbacks signature should be
+ cb(x, y).
+ @returns: a handle, used when calling Overlayer.remove(handle)
+ """
+ if hasattr(child, "draw_with_context"):
+ # if the widget has the CanvasDrawable protocol, use it.
+ child.no_expose = True
+
+ wrapper = DragWrapper(child, draggable, position_cb)
+ super(Overlayer, self).put(wrapper.container, x, y)
+ self.show_all()
+
+ # be sure to redraw or the overlay may not show
+ self.queue_draw()
+
+ return wrapper
+
+ def remove(self, handle):
+ """
+ Remove the widget with handle from the overlay.
+
+ @param handle: the handle of the widget, as returned by put()
+ """
+ super(Overlayer, self).remove(handle.container)
+
+ def __init_realized(self, widget):
+ """
+ Initializer to set once widget is realized.
+ Since an expose event is signaled only to realized widgets, we set this
+ callback for the first expose run. It should also be called after
+ beign reparented to ensure the window used for drawing is set up.
+ """
+ screen = widget.get_screen()
+ colormap = screen.get_rgba_colormap()
+ if not colormap:
+ colormap = screen.get_rgb_colormap()
+
+ widget.set_colormap(colormap)
+
+ self._win.set_size_request(screen.get_width(), screen.get_height())
+
+ return True
+
+ def __expose_overlay(self, widget, event):
+ """expose event handler to draw the thing."""
+ screen = widget.get_screen()
+ colormap = screen.get_rgba_colormap()
+ widget.set_colormap(colormap)
+ width, height = widget.get_size()
+
+ ctx = widget.window.cairo_create()
+ # clear the window
+ ctx.set_source_rgba(1.0, 1.0, 1.0, 0.0)
+ ctx.set_operator(cairo.OPERATOR_SOURCE)
+ ctx.paint()
+
+ ctx.set_source_rgba(1.0, 0.2, 0.2, 0.7)
+ ctx.rectangle(10.0, 10.0, 200.0, 150.0)
+ ctx.fill()
+ ctx.move_to(20,20)
+ ctx.set_source_rgba(0.0, 0.2, 0.2, 0.7)
+ layout = ctx.create_layout()
+ layout.set_markup(self._overlayed.get_title())
+ ctx.show_layout(layout)
+
+
+ ##draw no more than our expose event intersects our child
+ #region = gtk.gdk.region_rectangle(child.allocation)
+ #rect = gtk.gdk.region_rectangle(event.area)
+ #region.intersect(rect)
+ #ctx.region (region)
+ #ctx.clip()
+
+ ctx.set_operator(cairo.OPERATOR_OVER)
+
+ #draw overlay
+ for child in self.get_children():
+ drawn_child = child.get_children()[0]
+ if hasattr(drawn_child, "draw_with_context"):
+ drawn_child.draw_with_context(ctx)
+
+ # let events pass through
+ mask = gtk.gdk.Pixmap(None, width, height, 1)
+ mask_ctx = mask.cairo_create()
+ mask_ctx.set_operator(cairo.OPERATOR_CLEAR)
+ mask_ctx.paint()
+ self._win.input_shape_combine_mask(mask, 0, 0)
+
+ return False
+
+class TextBubble(gtk.Widget):
+ """
+ A CanvasDrawableWidget drawing a round textbox and a tail pointing
+ to a specified widget.
+ """
+ def __init__(self, text, speaker=None, tailpos=(0,0)):
+ """
+ Creates a new cairo rendered text bubble.
+
+ @param text the text to render in the bubble
+ @param speaker the widget to compute the tail position from
+ @param tailpos (optional) position relative to the bubble to use as
+ the tail position, if no speaker
+ """
+ gtk.Widget.__init__(self)
+
+ # FIXME: ensure previous call does not interfere with widget stacking,
+ # as using a gtk.Layout and stacking widgets may reveal a screwed up
+ # order with the cairo widget on top.
+ self.__label = None
+
+ self.label = text
+ self.speaker = speaker
+ self.tailpos = tailpos
+ self.line_width = 5
+ self.padding = 20
+
+ self._no_expose = False
+ self.__exposer = None
+ self._text_layout = None
+
+ def draw_with_context(self, context):
+ """
+ Draw using the passed cairo context instead of creating a new cairo
+ context. This eases blending between multiple cairo-rendered
+ widgets.
+ """
+ context.translate(self.allocation.x, self.allocation.y)
+ width = self.allocation.width
+ height = self.allocation.height
+ xradius = width/2
+ yradius = height/2
+ width -= self.line_width
+ height -= self.line_width
+ #
+ # TODO fetch speaker coordinates
+
+ # draw bubble tail if present
+ if self.tailpos != (0,0):
+ context.move_to(xradius-width/4, yradius)
+ context.line_to(self.tailpos[0], self.tailpos[1])
+ context.line_to(xradius+width/4, yradius)
+ context.set_line_width(self.line_width)
+ context.set_source_rgb(*xo_line_color)
+ context.stroke_preserve()
+
+ # bubble border
+ context.move_to(width-self.padding, 0.0)
+ context.line_to(self.padding, 0.0)
+ context.arc_negative(self.padding, self.padding, self.padding,
+ 3*pi/2, pi)
+ context.line_to(0.0, height-self.padding)
+ context.arc_negative(self.padding, height-self.padding, self.padding,
+ pi, pi/2)
+ context.line_to(width-self.padding, height)
+ context.arc_negative(width-self.padding, height-self.padding,
+ self.padding, pi/2, 0)
+ context.line_to(width, self.padding)
+ context.arc_negative(width-self.padding, self.padding, self.padding,
+ 0.0, -pi/2)
+ context.set_line_width(self.line_width)
+ context.set_source_rgb(*xo_line_color)
+ context.stroke_preserve()
+ context.set_source_rgb(*xo_fill_color)
+ context.fill()
+
+ # bubble painting. Redrawing the inside after the tail will combine
+ if self.tailpos != (0,0):
+ context.move_to(xradius-width/4, yradius)
+ context.line_to(self.tailpos[0], self.tailpos[1])
+ context.line_to(xradius+width/4, yradius)
+ context.set_line_width(self.line_width)
+ context.set_source_rgb(*xo_fill_color)
+ context.fill()
+
+ context.set_source_rgb(1.0, 1.0, 1.0)
+ pangoctx = pangocairo.CairoContext(context)
+ self._text_layout.set_markup(self.__label)
+ text_size = self._text_layout.get_pixel_size()
+ pangoctx.move_to(
+ int((self.allocation.width-text_size[0])/2),
+ int((self.allocation.height-text_size[1])/2))
+ pangoctx.show_layout(self._text_layout)
+
+ # work done. Be kind to next cairo widgets and reset matrix.
+ context.identity_matrix()
+
+ def do_realize(self):
+ """ Setup gdk window creation. """
+ self.set_flags(gtk.REALIZED | gtk.NO_WINDOW)
+ self.window = self.get_parent_window()
+ if not self._no_expose:
+ self.__exposer = self.connect_after("expose-event", \
+ self.__on_expose)
+
+ def __on_expose(self, widget, event):
+ """Redraw event callback."""
+ ctx = self.window.cairo_create()
+
+ self.draw_with_context(ctx)
+
+ return True
+
+ def _set_label(self, value):
+ """Sets the label and flags the widget to be redrawn."""
+ self.__label = "<b>%s</b>"%value
+ if not self.parent:
+ return
+ ctx = self.parent.window.cairo_create()
+ pangoctx = pangocairo.CairoContext(ctx)
+ self._text_layout = pangoctx.create_layout()
+ self._text_layout.set_markup(value)
+ del pangoctx, ctx#, surf
+
+ def do_size_request(self, requisition):
+ """Fill requisition with size occupied by the widget."""
+ ctx = self.parent.window.cairo_create()
+ pangoctx = pangocairo.CairoContext(ctx)
+ self._text_layout = pangoctx.create_layout()
+ self._text_layout.set_markup(self.__label)
+
+ width, height = self._text_layout.get_pixel_size()
+ requisition.width = int(width+2*self.padding)
+ requisition.height = int(height+2*self.padding)
+
+ def do_size_allocate(self, allocation):
+ """Save zone allocated to the widget."""
+ self.allocation = allocation
+
+ def _get_label(self):
+ """Getter method for the label property"""
+ return self.__label[3:-4]
+
+ def _set_no_expose(self, value):
+ """setter for no_expose property"""
+ self._no_expose = value
+ if not (self.flags() and gtk.REALIZED):
+ return
+
+ if self.__exposer and value:
+ self.parent.disconnect(self.__exposer)
+ self.__exposer = None
+ elif (not self.__exposer) and (not value):
+ self.__exposer = self.parent.connect_after("expose-event",
+ self.__on_expose)
+
+ def _get_no_expose(self):
+ """getter for no_expose property"""
+ return self._no_expose
+
+ no_expose = property(fset=_set_no_expose, fget=_get_no_expose,
+ doc="Whether the widget should handle exposition events or not.")
+
+ label = property(fget=_get_label, fset=_set_label,
+ doc="Text label which is to be painted on the top of the widget")
+
+gobject.type_register(TextBubble)
+
+class Rectangle(gtk.Widget):
+ """
+ A CanvasDrawableWidget drawing a rectangle over a specified widget.
+ """
+ def __init__(self, widget, color):
+ """
+ Creates a new Rectangle
+
+ @param widget the widget to cover
+ @param color the color of the rectangle, as a 4-tuple RGBA
+ """
+ gtk.Widget.__init__(self)
+
+ self.covered = widget
+ self.color = color
+
+ self.__exposer = self.connect("expose-event", self.__on_expose)
+
+ def draw_with_context(self, context):
+ """
+ Draw using the passed cairo context instead of creating a new cairo
+ context. This eases blending between multiple cairo-rendered
+ widgets.
+ """
+ if self.covered is None:
+ # nothing to hide, no coordinates, no drawing
+ return
+ mask_alloc = self.covered.get_allocation()
+ x, y = self.covered.translate_coordinates(self.parent, 0, 0)
+
+ context.rectangle(x, y, mask_alloc.width, mask_alloc.height)
+ context.set_source_rgba(*self.color)
+ context.fill()
+
+ def do_realize(self):
+ """ Setup gdk window creation. """
+ self.set_flags(gtk.REALIZED | gtk.NO_WINDOW)
+
+ self.window = self.get_parent_window()
+ if not isinstance(self.parent, Overlayer):
+ assert False, "%s should not realize" % type(self).__name__
+ print "Danger, Will Robinson! Rectangle parent is not Overlayer"
+
+ def __on_expose(self, widget, event):
+ """Redraw event callback."""
+ assert False, "%s wasn't meant to be exposed by gtk" % \
+ type(self).__name__
+ ctx = self.window.cairo_create()
+
+ self.draw_with_context(ctx)
+
+ return True
+
+ def do_size_request(self, requisition):
+ """Fill requisition with size occupied by the masked widget."""
+ # This is a bit pointless, as this will always ignore allocation and
+ # be rendered directly on overlay, but for sanity, let's put some values
+ # in there.
+ if not self.covered:
+ requisition.width = 0
+ requisition.height = 0
+ return
+
+ masked_alloc = self.covered.get_allocation()
+ requisition.width = masked_alloc.width
+ requisition.height = masked_alloc.height
+
+ def do_size_allocate(self, allocation):
+ """Save zone allocated to the widget."""
+ self.allocation = allocation
+
+ def _set_no_expose(self, value):
+ """setter for no_expose property"""
+ if self.__exposer and value:
+ self.disconnect(self.__exposer)
+ self.__exposer = None
+ elif (not self.__exposer) and (not value):
+ self.__exposer = self.connect("expose-event", self.__on_expose)
+
+ def _get_no_expose(self):
+ """getter for no_expose property"""
+ return not self.__exposer
+
+ no_expose = property(fset=_set_no_expose, fget=_get_no_expose,
+ doc="Whether the widget should handle exposition events or not.")
+gobject.type_register(Rectangle)
+
+class Mask(gtk.EventBox):
+ """
+ A CanvasDrawableWidget drawing a rectangle over a specified widget.
+ """
+ def __init__(self, catch_events=False, pass_thru=()):
+ """
+ Creates a new Rectangle
+
+ @param catch_events whether the Mask should catch events
+ @param pass_thru the widgets that "punch holes" through this Mask.
+ Events will pass through to those widgets.
+ """
+ gtk.EventBox.__init__(self)
+ self.no_expose = True # ignored
+ self._catch_events = False
+ self.catch_events = catch_events
+ self.pass_thru = list(pass_thru)
+
+ def __del__(self):
+ for widget in self.pass_thru:
+ widget.drag_unhighlight()
+
+ def mask(self, widget):
+ """
+ Remove the widget from the unmasked list.
+ @param widget a widget to remask
+ """
+ assert widget in self.pass_thru, \
+ "trying to mask already masked widget"
+ self.pass_thru.remove(widget)
+ widget.drag_unhighlight()
+
+ def unmask(self, widget):
+ """
+ Add to the unmasked list the widget passed.
+ A hole will be punched through the mask at that widget's position.
+ @param widget a widget to unmask
+ """
+ if widget not in self.pass_thru:
+ widget.drag_highlight()
+ self.pass_thru.append(widget)
+
+
+ def set_catch_events(self, do_catch):
+ """Sets whether the mask catches events of widgets under it"""
+ if bool(self._catch_events) ^ bool(do_catch):
+ if do_catch:
+ self._catch_events = True
+ self.grab_add()
+ else:
+ self.grab_remove()
+ self._catch_events = False
+
+ def get_catch_events(self):
+ """Gets whether the mask catches events of widgets under it"""
+ return bool(self._catch_handle)
+
+ catch_events = property(fset=set_catch_events, fget=get_catch_events)
+
+ def draw_with_context(self, context):
+ """
+ Draw using the passed cairo context instead of creating a new cairo
+ context. This eases blending between multiple cairo-rendered
+ widgets.
+ """
+ # Fill parent container
+ mask_alloc = self.parent.get_allocation()
+ oldrule = context.get_fill_rule()
+ context.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
+ x, y = self.translate_coordinates(self.parent, 0, 0)
+
+ context.rectangle(x, y, mask_alloc.width, mask_alloc.height)
+ for hole in self.pass_thru:
+ alloc = hole.get_allocation()
+ x, y = hole.translate_coordinates(self.parent, 0, 0)
+ context.rectangle(x, y, alloc.width, alloc.height)
+ context.set_source_rgba(0, 0, 0, 0.7)
+ context.fill()
+ context.set_fill_rule(oldrule)
+
+ def do_size_request(self, requisition):
+ """Fill requisition with size occupied by the masked widget."""
+ # This is required for the event box to span across all the parent.
+ alloc = self.parent.get_allocation()
+ requisition.width = alloc.width
+ requisition.height = alloc.height
+
+ def do_size_allocate(self, allocation):
+ """Save zone allocated to the widget."""
+ self.allocation = allocation
+
+gobject.type_register(Mask)
+
+class DragWrapper(object):
+ """Wrapper to allow gtk widgets to be dragged around"""
+ def __init__(self, widget, draggable=False, callback=None):
+ """
+ Creates a wrapper to allow gtk widgets to be mouse dragged, if the
+ parent container supports the move() method, like a gtk.Layout.
+
+ Note: initial position attribute is none, until the widget is moved.
+
+ @param widget the widget to enhance with drag capability
+ @param draggable wether to enable the drag functionality now
+ @param callback: a callback for obtaining position changes
+ if draggable was activated. Callbacks signature should be
+ cb(x, y).
+ """
+ self._widget = widget
+ self.container = gtk.EventBox()
+ self.container.show()
+ self.container.set_visible_window(False)
+ self.container.add(widget)
+
+ self._drag_on = False # whether dragging is enabled
+ self._rel_pos = (0,0) # mouse pos relative to widget
+ self._handles = [] # event handlers
+ self._dragging = False # whether a drag is in progress
+ self.position = None # position of the widget
+ self._callback = callback
+
+ self.draggable = draggable
+
+ def _pressed_cb(self, widget, evt):
+ """Callback for start of drag event"""
+ self.container.grab_add()
+ self._dragging = True
+ self._rel_pos = evt.get_coords()
+
+ def _moved_cb(self, widget, evt):
+ """Callback for mouse drag events"""
+ if not self._dragging:
+ return
+
+ # Focus on a widget before dragging another would
+ # create addititonal move event, making the widget jump unexpectedly.
+ # Solution found was to process those focus events before dragging.
+ if gtk.events_pending():
+ return
+
+ xrel, yrel = self._rel_pos
+ xparent, yparent = evt.get_coords()
+ xparent, yparent = widget.translate_coordinates(widget.parent,
+ xparent, yparent)
+ self.position = (xparent-xrel, yparent-yrel)
+ self._widget.parent.move(self.container, *self.position)
+ self._widget.parent.queue_draw()
+
+ def _released_cb(self, *args):
+ """Callback for end of drag (mouse release)."""
+ self.container.grab_remove()
+ self._dragging = False
+ if self._callback:
+ callback(*self.position)
+
+ def _drag_end(self, *args):
+ """Callback for end of drag (stolen focus)."""
+ self._dragging = False
+
+ def set_draggable(self, value):
+ """Setter for the draggable property"""
+ if bool(value) ^ bool(self._drag_on):
+ if value:
+ size = self._widget.size_request()
+ self.container.set_size_request(*size)
+ self._handles.append(self.container.connect(
+ "button-press-event", self._pressed_cb))
+ self._handles.append(self.container.connect(
+ "button-release-event", self._released_cb))
+ self._handles.append(self.container.connect(
+ "motion-notify-event", self._moved_cb))
+ self._handles.append(self.container.connect(
+ "grab-broken-event", self._drag_end))
+ else:
+ while self._handles:
+ handle = self._handles.pop()
+ self.container.disconnect(handle)
+ self._drag_on = value
+
+ def get_draggable(self):
+ """Getter for the draggable property"""
+ return self._drag_on
+
+ draggable = property(fset=set_draggable, fget=get_draggable, \
+ doc="Property to enable the draggable behaviour of the widget")
+
+ def set_widget(self, widget):
+ """Setter for the widget property"""
+ if self._dragging or self._drag_on:
+ raise Exception("Can't change widget while dragging is enabled.")
+
+ assert hasattr(widget, "parent"), "wrapped widget should have a parent"
+ parent = widget.parent
+ assert hasattr(parent, "move"), "container of widget need move method"
+ self._widget = widget
+
+ def get_widget(self):
+ """Getter for the widget property"""
+ return self._widget
+
+ widget = property(fset=set_widget, fget=get_widget)
+
+
+GCONF_COMPOSITING_KEY = '/apps/metacity/general/compositing_manager'
+
+def _set_wm_compositing(compositing):
+ """
+ Utility method to reset metacity's compostiting.
+ Compositing allows windows to blend together and avoids flickers
+ when widgets redraw (like the frame animation).
+
+ @type compositing: bool
+ @param compositing: Whether to enable metacity compositing feature.
+ @returns: the new value for compositing
+ """
+ import gconf
+ client = gconf.client_get_default()
+ if not client.get_bool(GCONF_COMPOSITING_KEY):
+ client.set_bool(GCONF_COMPOSITING_KEY, compositing)
+ return client.get_bool(GCONF_COMPOSITING_KEY)
+
+
+# vim:set ts=4 sts=4 sw=4 et: