Пример #1
0
static void
_stage_added (ClutterStageManager *manager,
              ClutterStage        *stage,
              gpointer             data)
{
  Window xid = clutter_x11_get_stage_window (stage);

  if (xid != None)
    {
      XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
                    xid,
                    StructureNotifyMask |
                    ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
                    FocusChangeMask |
                    ExposureMask |
                    KeyPressMask | KeyReleaseMask |
                    EnterWindowMask | LeaveWindowMask |
                    PropertyChangeMask);

      gdk_window_add_filter (NULL,
                             gdk_to_clutter_event_pump__,
                             GINT_TO_POINTER (xid));
    }
  else
    {
      g_signal_connect_after (stage, "realize",
                              G_CALLBACK (_stage_realized),
                              NULL);
    }
}
Пример #2
0
/**
 * shell_global_grab_keyboard:
 * @global: a #ShellGlobal
 *
 * Grab the keyboard to the stage window. The stage will receive
 * all keyboard events until shell_global_ungrab_keyboard() is called.
 * This is appropriate to do when the desktop goes into a special
 * mode where no normal global key shortcuts or application keyboard
 * processing should happen.
 */
gboolean
shell_global_grab_keyboard (ShellGlobal *global)
{
  MetaScreen *screen = mutter_plugin_get_screen (global->plugin);
  MetaDisplay *display = meta_screen_get_display (screen);
  Display *xdisplay = meta_display_get_xdisplay (display);
  ClutterStage *stage = CLUTTER_STAGE (mutter_plugin_get_stage (global->plugin));
  Window stagewin = clutter_x11_get_stage_window (stage);

  /* FIXME: we need to coordinate with the rest of Metacity or we
   * may grab the keyboard away from other portions of Metacity
   * and leave Metacity in a confused state. An X client is allowed
   * to overgrab itself, though not allowed to grab they keyboard
   * away from another applications.
   */
  if (global->keyboard_grabbed)
    return FALSE;

  if (XGrabKeyboard (xdisplay, stagewin,
                     False, /* owner_events - steal events from the rest of metacity */
                     GrabModeAsync, GrabModeAsync,
                     CurrentTime) != Success)
    return FALSE; /* probably AlreadyGrabbed, some other app has a keyboard grab */

  global->keyboard_grabbed = TRUE;

  return TRUE;
}
Пример #3
0
void
meta_compositor_sync_screen_size (MetaCompositor  *compositor,
                                  MetaScreen	  *screen,
                                  guint		   width,
                                  guint		   height)
{
    MetaDisplay    *display = meta_screen_get_display (screen);
    MetaCompScreen *info    = meta_screen_get_compositor_data (screen);
    Display        *xdisplay;
    Window          xwin;

    DEBUG_TRACE ("meta_compositor_sync_screen_size\n");
    g_return_if_fail (info);

    xdisplay = meta_display_get_xdisplay (display);
    xwin = clutter_x11_get_stage_window (CLUTTER_STAGE (info->stage));

    XResizeWindow (xdisplay, xwin, width, height);

    meta_background_actor_screen_size_changed (screen);

    meta_verbose ("Changed size for stage on screen %d to %dx%d\n",
                  meta_screen_get_screen_number (screen),
                  width, height);
}
Пример #4
0
void
meta_set_stage_input_region (MetaScreen   *screen,
                             XserverRegion region)
{
  /* As a wayland compositor we can simply ignore all this trickery
   * for setting an input region on the stage for capturing events in
   * clutter since all input comes to us first and we get to choose
   * who else sees them.
   */
  if (!meta_is_wayland_compositor ())
    {
      MetaDisplay *display = screen->display;
      MetaCompositor *compositor = display->compositor;
      Display *xdpy = meta_display_get_xdisplay (display);
      Window xstage = clutter_x11_get_stage_window (CLUTTER_STAGE (compositor->stage));

      XFixesSetWindowShapeRegion (xdpy, xstage, ShapeInput, 0, 0, region);

      /* It's generally a good heuristic that when a crossing event is generated because
       * we reshape the overlay, we don't want it to affect focus-follows-mouse focus -
       * it's not the user doing something, it's the environment changing under the user.
       */
      meta_display_add_ignored_crossing_serial (display, XNextRequest (xdpy));
      XFixesSetWindowShapeRegion (xdpy, compositor->output, ShapeInput, 0, 0, region);
    }
}
/* Class functions */
gboolean scim_bridge_client_imcontext_filter_key_event (ClutterIMContext *context, ClutterKeyEvent *event)
{
    scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_filter_key_event ()");

    ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);

    if (scim_bridge_client_is_messenger_opened () && imcontext != NULL ) {
        if (context->actor != NULL) {
	    ClutterActor *stage = clutter_actor_get_stage (context->actor);
	    Window current_window, root, parent, *childs;
	    unsigned int nchild;
	    XWindowAttributes winattr;
	    Display *xdpy;
            int new_window_x;
            int new_window_y;

            clutter_actor_get_transformed_position (context->actor, &new_window_x, &new_window_y);
	    xdpy = clutter_x11_get_default_display ();
	    current_window = clutter_x11_get_stage_window(CLUTTER_STAGE(stage));

	    while(1) {
                XGetWindowAttributes (xdpy, current_window, &winattr);
                new_window_x += winattr.x;
                new_window_y += winattr.y;

                XQueryTree(xdpy, current_window, &root, &parent, &childs, &nchild);
                current_window = parent;
                if (root == parent)
                    break;
            }

            if (imcontext->window_x != new_window_x || imcontext->window_y != new_window_y) {
                imcontext->window_x = new_window_x;
                imcontext->window_y = new_window_y;

                scim_bridge_pdebugln (1,
                    "The cursor location is changed: x = %d + %d\ty = %d + %d",
                    imcontext->window_x, imcontext->cursor_x, imcontext->window_y, imcontext->cursor_y);

                if (set_cursor_location (imcontext, new_window_x, new_window_y, imcontext->cursor_x, imcontext->cursor_y)) {
                    scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_filter_key_event ()");
                    return clutter_im_context_filter_keypress (fallback_imcontext, event);
                }
            }
        }

        boolean consumed = FALSE;
        if (filter_key_event (imcontext, event, &consumed)) {
            scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_filter_key_event ()");
        } else if (consumed) {
            return TRUE;
        }
    }

    if (imcontext == NULL || !imcontext->enabled) {
        return clutter_im_context_filter_keypress (fallback_imcontext, event);
    }

    return FALSE;
}
Пример #6
0
/**
 * shell_global_begin_modal:
 * @global: a #ShellGlobal
 *
 * Grabs the keyboard and mouse to the stage window. The stage will
 * receive all keyboard and mouse events until shell_global_end_modal()
 * is called. This is used to implement "modes" for the shell, such as the
 * overview mode or the "looking glass" debug overlay, that block
 * application and normal key shortcuts.
 *
 * Returns value: %TRUE if we succesfully entered the mode. %FALSE if we couldn't
 *  enter the mode. Failure may occur because an application has the pointer
 *  or keyboard grabbed, because Mutter is in a mode itself like moving a
 *  window or alt-Tab window selection, or because shell_global_begin_modal()
 *  was previouly called.
 */
gboolean
shell_global_begin_modal (ShellGlobal *global,
                          guint32      timestamp)
{
  ClutterStage *stage = CLUTTER_STAGE (mutter_plugin_get_stage (global->plugin));
  Window stagewin = clutter_x11_get_stage_window (stage);

  return mutter_plugin_begin_modal (global->plugin, stagewin, None, 0, timestamp);
}
Пример #7
0
static void
_set_cursor_location_internal(FcitxIMContext *fcitxcontext)
{
    ClutterIMContext* context = CLUTTER_IM_CONTEXT(fcitxcontext);
    ClutterActor *stage = clutter_actor_get_stage (context->actor);
    Window current_window, root, parent, *childs;
    unsigned int nchild;
    XWindowAttributes winattr;
    Display *xdpy;
    float fx, fy;
    gint x, y;

    if (!stage)
        return;

    clutter_actor_get_transformed_position (context->actor, &fx, &fy);
    x = fx;
    y = fy;

    xdpy = clutter_x11_get_default_display ();
    current_window = clutter_x11_get_stage_window(CLUTTER_STAGE(stage));

    if (!xdpy || !current_window)
        return;

    while(1) {
        XGetWindowAttributes (xdpy, current_window, &winattr);
        x += winattr.x;
        y += winattr.y;

        XQueryTree(xdpy, current_window, &root, &parent, &childs, &nchild);
        current_window = parent;
        if (root == parent)
        break;
    }

    if (fcitxcontext->area.x != x || fcitxcontext->area.y != y) {
        fcitxcontext->area.x = x;
        fcitxcontext->area.y = y;
    }

    if (context->actor == NULL ||
        !IsFcitxIMClientValid(fcitxcontext->client)) {
        return;
    }

    ClutterIMRectangle area = fcitxcontext->area;
    if (area.x == -1 && area.y == -1 && area.width == 0 && area.height == 0) {
        area.y = 0;
        area.x = 0;
    }

    FcitxIMClientSetCursorLocation(fcitxcontext->client, area.x, area.y + area.height);
    return;
}
Пример #8
0
static void
mpl_panel_clutter_ensure_window (MplPanelClutter *panel)
{
  MplPanelClutterPrivate *priv = panel->priv;
  Window                  xwin = None;
  Display                *xdpy;
  gint32                  myint;
  Atom                    atom1, atom2;

  if (priv->xwindow)
    return;

  xdpy = clutter_x11_get_default_display ();

  clutter_actor_realize (priv->stage);

  priv->xwindow = xwin =
    clutter_x11_get_stage_window (CLUTTER_STAGE (priv->stage));

  g_object_set (panel, "xid", xwin, NULL);

  if (priv->needs_gdk_pump)
    mpl_panel_clutter_setup_events_with_gtk_for_xid (xwin);

  if (!mpl_utils_panel_in_standalone_mode ())
    {
      MPL_X_ERROR_TRAP ();

      /*
       * Make dock, sticky and position at the correct place.
       */
      atom1 = XInternAtom(xdpy, "_NET_WM_WINDOW_TYPE", False);
      atom2 = XInternAtom(xdpy, "_NET_WM_WINDOW_TYPE_DOCK", False);

      XChangeProperty (xdpy,
                       xwin,
                       atom1,
                       XA_ATOM, 32,
                       PropModeReplace, (unsigned char *) &atom2, 1);

      atom1 = XInternAtom(xdpy, "_NET_WM_DESKTOP", False);
      myint = -1;

      XChangeProperty (xdpy,
                       xwin,
                       atom1,
                       XA_CARDINAL, 32,
                       PropModeReplace, (unsigned char *) &myint, 1);

      XSync (xdpy, False);

      MPL_X_ERROR_UNTRAP ();
    }
}
Пример #9
0
static void
do_set_stage_input_region (MetaScreen   *screen,
                           XserverRegion region)
{
    MetaCompScreen *info = meta_screen_get_compositor_data (screen);
    MetaDisplay *display = meta_screen_get_display (screen);
    Display        *xdpy = meta_display_get_xdisplay (display);
    Window        xstage = clutter_x11_get_stage_window (CLUTTER_STAGE (info->stage));

    XFixesSetWindowShapeRegion (xdpy, xstage, ShapeInput, 0, 0, region);

    /* It's generally a good heuristic that when a crossing event is generated because
     * we reshape the overlay, we don't want it to affect focus-follows-mouse focus -
     * it's not the user doing something, it's the environment changing under the user.
     */
    meta_display_add_ignored_crossing_serial (display, XNextRequest (xdpy));
    XFixesSetWindowShapeRegion (xdpy, info->output, ShapeInput, 0, 0, region);
}
static ClutterX11FilterReturn
xfixes_cursor_event_filter (XEvent        *xev,
                            ClutterEvent  *cev,
                            gpointer       data)
{
  ShellXFixesCursor *xfixes_cursor = data;

  if (xev->xany.window != clutter_x11_get_stage_window (xfixes_cursor->stage))
    return CLUTTER_X11_FILTER_CONTINUE;

  if (xev->xany.type == xfixes_cursor->xfixes_event_base + XFixesCursorNotify)
    {
      XFixesCursorNotifyEvent *notify_event = (XFixesCursorNotifyEvent *)xev;
      if (notify_event->subtype == XFixesDisplayCursorNotify)
        xfixes_cursor_reset_image (xfixes_cursor);
    }
    return CLUTTER_X11_FILTER_CONTINUE;
}
Пример #11
0
/* This is an IBus workaround. The flow of events with IBus is that every time
 * it gets gets a key event, it:
 *
 *  Sends it to the daemon via D-Bus asynchronously
 *  When it gets an reply, synthesizes a new GdkEvent and puts it into the
 *   GDK event queue with gdk_event_put(), including
 *   IBUS_FORWARD_MASK = 1 << 25 in the state to prevent a loop.
 *
 * (Normally, IBus uses the GTK+ key snooper mechanism to get the key
 * events early, but since our key events aren't visible to GTK+ key snoopers,
 * IBus will instead get the events via the standard
 * GtkIMContext.filter_keypress() mechanism.)
 *
 * There are a number of potential problems here; probably the worst
 * problem is that IBus doesn't forward the timestamp with the event
 * so that every key event that gets delivered ends up with
 * GDK_CURRENT_TIME.  This creates some very subtle bugs; for example
 * if you have IBus running and a keystroke is used to trigger
 * launching an application, focus stealing prevention won't work
 * right. http://code.google.com/p/ibus/issues/detail?id=1184
 *
 * In any case, our normal flow of key events is:
 *
 *  GDK filter function => clutter_x11_handle_event => clutter actor
 *
 * So, if we see a key event that gets delivered via the GDK event handler
 * function - then we know it must be one of these synthesized events, and
 * we should push it back to clutter.
 *
 * To summarize, the full key event flow with IBus is:
 *
 *   GDK filter function
 *     => Mutter
 *     => gnome_cinnamon_plugin_xevent_filter()
 *     => clutter_x11_handle_event()
 *     => clutter event delivery to actor
 *     => gtk_im_context_filter_event()
 *     => sent to IBus daemon
 *     => response received from IBus daemon
 *     => gdk_event_put()
 *     => GDK event handler
 *     => <this function>
 *     => clutter_event_put()
 *     => clutter event delivery to actor
 *
 * Anything else we see here we just pass on to the normal GDK event handler
 * gtk_main_do_event().
 */
static void
gnome_cinnamon_gdk_event_handler (GdkEvent *event_gdk,
                               gpointer  data)
{
  if (event_gdk->type == GDK_KEY_PRESS || event_gdk->type == GDK_KEY_RELEASE)
    {
      ClutterActor *stage;
      Window stage_xwindow;

      stage = clutter_stage_get_default ();
      stage_xwindow = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));

      if (GDK_WINDOW_XID (event_gdk->key.window) == stage_xwindow)
        {
          ClutterDeviceManager *device_manager = clutter_device_manager_get_default ();
          ClutterInputDevice *keyboard = clutter_device_manager_get_core_device (device_manager,
                                                                                 CLUTTER_KEYBOARD_DEVICE);

          ClutterEvent *event_clutter = clutter_event_new ((event_gdk->type == GDK_KEY_PRESS) ?
                                                           CLUTTER_KEY_PRESS : CLUTTER_KEY_RELEASE);
          event_clutter->key.time = event_gdk->key.time;
          event_clutter->key.flags = CLUTTER_EVENT_NONE;
          event_clutter->key.stage = CLUTTER_STAGE (stage);
          event_clutter->key.source = NULL;

          /* This depends on ClutterModifierType and GdkModifierType being
           * identical, which they are currently. (They both match the X
           * modifier state in the low 16-bits and have the same extensions.) */
          event_clutter->key.modifier_state = event_gdk->key.state;

          event_clutter->key.keyval = event_gdk->key.keyval;
          event_clutter->key.hardware_keycode = event_gdk->key.hardware_keycode;
          event_clutter->key.unicode_value = gdk_keyval_to_unicode (event_clutter->key.keyval);
          event_clutter->key.device = keyboard;

          clutter_event_put (event_clutter);
          clutter_event_free (event_clutter);

          return;
        }
    }

  gtk_main_do_event (event_gdk);
}
Пример #12
0
static gboolean
handle_xembed_event (ClutterBackendX11 *backend_x11,
                     XEvent            *xevent)
{
  ClutterActor *stage;

  stage = clutter_stage_get_default ();

  switch (xevent->xclient.data.l[1])
    {
    case XEMBED_EMBEDDED_NOTIFY:
      CLUTTER_NOTE (EVENT, "got XEMBED_EMBEDDED_NOTIFY from %lx",
                    xevent->xclient.data.l[3]);

      ParentEmbedderWin = xevent->xclient.data.l[3];

      clutter_actor_realize (stage);
      clutter_actor_show (stage);

      xembed_set_info (backend_x11,
                       clutter_x11_get_stage_window (CLUTTER_STAGE (stage)),
                       XEMBED_MAPPED);
      break;
    case XEMBED_WINDOW_ACTIVATE:
      CLUTTER_NOTE (EVENT, "got XEMBED_WINDOW_ACTIVATE");
      break;
    case XEMBED_WINDOW_DEACTIVATE:
      CLUTTER_NOTE (EVENT, "got XEMBED_WINDOW_DEACTIVATE");
      break;
    case XEMBED_FOCUS_IN:
      CLUTTER_NOTE (EVENT, "got XEMBED_FOCUS_IN");
      if (ParentEmbedderWin)
        xembed_send_message (backend_x11, ParentEmbedderWin,
                             XEMBED_FOCUS_NEXT,
                             0, 0, 0);
      break;
    default:
      CLUTTER_NOTE (EVENT, "got unknown XEMBED message");
      break;
    }

  /* do not propagate the XEMBED events to the stage */
  return FALSE;
}
Пример #13
0
static void
set_above_and_fullscreen (void)
{
  Display *dpy = clutter_x11_get_default_display ();
  Window win = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
  char *atom_names[2] = {
    "_NET_WM_STATE_FULLSCREEN",
    "_NET_WM_STATE_ABOVE",
  };
  Atom states[G_N_ELEMENTS (atom_names)];

  XInternAtoms (dpy, atom_names, G_N_ELEMENTS (atom_names),
                False, states);

  XChangeProperty (dpy, win,
                   XInternAtom (dpy, "_NET_WM_STATE", False),
                   XA_ATOM, 32, PropModeReplace,
                   (unsigned char *) states, G_N_ELEMENTS (atom_names));
}
Пример #14
0
/* Returns a new reference to window */
static GdkWindow *
window_for_actor (ClutterActor *actor)
{
  GdkDisplay *display = gdk_display_get_default ();
  ClutterActor *stage;
  Window xwindow;
  GdkWindow *window;

  stage = clutter_actor_get_stage (actor);
  xwindow = clutter_x11_get_stage_window ((ClutterStage *)stage);

  window = gdk_x11_window_lookup_for_display (display, xwindow);
  if (window)
    g_object_ref (window);
  else
    window = gdk_x11_window_foreign_new_for_display (display, xwindow);

  return window;
}
Пример #15
0
void
cinnamon_tray_manager_manage_stage (CinnamonTrayManager *manager,
                                 ClutterStage     *stage,
                                 StWidget         *theme_widget)
{
  Window stage_xwindow;
  GdkWindow *stage_window;
  GdkDisplay *display;
  GdkScreen *screen;

  g_return_if_fail (manager->priv->stage == NULL);

  manager->priv->stage = g_object_ref (stage);

  stage_xwindow = clutter_x11_get_stage_window (stage);

  /* This is a pretty ugly way to get the GdkScreen for the stage; it
   *  will normally go through the foreign_new() case with a
   *  round-trip to the X server, it might be nicer to pass the screen
   *  in in some way. (The Clutter/Muffin combo is currently incapable
   *  of multi-screen operation, so alternatively we could just assume
   *  that clutter_x11_get_default_screen() gives us the right
   *  screen.) We assume, in any case, that we are using the default
   *  GDK display.
   */
  display = gdk_display_get_default();
  stage_window = gdk_x11_window_lookup_for_display (display, stage_xwindow);
  if (stage_window)
    g_object_ref (stage_window);
  else
    stage_window = gdk_x11_window_foreign_new_for_display (display, stage_xwindow);

  screen = gdk_window_get_screen (stage_window);

  g_object_unref (stage_window);

  na_tray_manager_manage_screen (manager->priv->na_manager, screen);

  g_signal_connect (theme_widget, "style-changed",
                    G_CALLBACK (cinnamon_tray_manager_style_changed), manager);
  cinnamon_tray_manager_style_changed (theme_widget, manager);
}
static void
shell_embedded_window_set_property (GObject         *object,
                                    guint            prop_id,
                                    const GValue    *value,
                                    GParamSpec      *pspec)
{
  ShellEmbeddedWindow *window = SHELL_EMBEDDED_WINDOW (object);

  switch (prop_id)
    {
    case PROP_STAGE:
      window->priv->stage_xwindow =
        clutter_x11_get_stage_window (g_value_get_object (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}
Пример #17
0
gboolean
meta_stage_is_focused (MetaScreen *screen)
{
  ClutterStage *stage;
  Window window;

  if (meta_is_wayland_compositor ())
    return TRUE;

  stage = CLUTTER_STAGE (meta_get_stage_for_screen (screen));
  if (!stage)
    return FALSE;

  window = clutter_x11_get_stage_window (stage);

  if (window == None)
    return FALSE;

  return (screen->display->focus_xwindow == window);
}
Пример #18
0
static void
setup_standalone (DawatiStatusPanel *status_panel)
{
    ClutterActor *stage, *status;
    Window xwin;

    status = make_status (status_panel);
    clutter_actor_set_size (status, 1000, 600);

    stage = clutter_stage_new ();
    clutter_actor_set_size (stage, 1000, 600);
    clutter_container_add_actor (CLUTTER_CONTAINER (stage), status);

    clutter_actor_realize (stage);
    xwin = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));

    mpl_panel_clutter_setup_events_with_gtk_for_xid (xwin);

    clutter_actor_show (stage);
}
Пример #19
0
void
meta_focus_stage_window (MetaScreen *screen,
                         guint32     timestamp)
{
  ClutterStage *stage;
  Window window;

  stage = CLUTTER_STAGE (meta_get_stage_for_screen (screen));
  if (!stage)
    return;

  window = clutter_x11_get_stage_window (stage);

  if (window == None)
    return;

  meta_display_set_input_focus_xwindow (screen->display,
                                        screen,
                                        window,
                                        timestamp);
}
static void
xfixes_cursor_hide (ShellXFixesCursor *xfixes_cursor)
{
  int minor, major;
  Display *xdisplay;
  Window xwindow;

  if (xfixes_cursor->is_showing == FALSE)
      return;

  if (!xfixes_cursor->have_xfixes || !xfixes_cursor->stage)
      return;

  xdisplay = clutter_x11_get_default_display ();
  xwindow = clutter_x11_get_stage_window (xfixes_cursor->stage);
  XFixesQueryVersion (xdisplay, &major, &minor);
  if (major >= 4)
    {
      XFixesHideCursor (xdisplay, xwindow);
      xfixes_cursor->is_showing = FALSE;
    }
}
ScreenSaver *
screensaver_new (ClutterStage * stage)
{
  ScreenSaver *screensaver;

  screensaver = g_new0 (ScreenSaver, 1);

  screensaver->disabled = FALSE;
  screensaver->stage = stage;

#if HAVE_X11
  screensaver->display = clutter_x11_get_default_display ();
  screensaver->window = clutter_x11_get_stage_window (stage);

  screensaver_init_x11 (screensaver);
#endif

#ifdef ENABLE_DBUS
  screensaver_init_dbus (screensaver);
#endif

  return screensaver;
}
static void
xfixes_cursor_set_stage (ShellXFixesCursor *xfixes_cursor,
                         ClutterStage  *stage)
{
  if (xfixes_cursor->stage == stage)
    return;

  if (xfixes_cursor->stage)
    {
      g_signal_handlers_disconnect_by_func (xfixes_cursor->stage,
                                            (void *)xfixes_cursor_on_stage_destroy,
                                            xfixes_cursor);

      clutter_x11_remove_filter (xfixes_cursor_event_filter, xfixes_cursor);
    }
  xfixes_cursor->stage = stage;
  if (xfixes_cursor->stage)
    {
      int error_base;

      xfixes_cursor->stage = stage;
      g_signal_connect (xfixes_cursor->stage, "destroy",
                        G_CALLBACK (xfixes_cursor_on_stage_destroy), xfixes_cursor);

      clutter_x11_add_filter (xfixes_cursor_event_filter, xfixes_cursor);

      xfixes_cursor->have_xfixes = XFixesQueryExtension (clutter_x11_get_default_display (),
                                                         &xfixes_cursor->xfixes_event_base,
                                                         &error_base);
      if (xfixes_cursor->have_xfixes)
        XFixesSelectCursorInput (clutter_x11_get_default_display (),
                                 clutter_x11_get_stage_window (stage),
                                 XFixesDisplayCursorNotifyMask);

      xfixes_cursor_reset_image (xfixes_cursor);
    }
}
Пример #23
0
void
meta_compositor_sync_screen_size (MetaCompositor  *compositor,
				  guint		   width,
				  guint		   height)
{
  MetaDisplay *display = compositor->display;

  if (meta_is_wayland_compositor ())
    {
      /* FIXME: when we support a sliced stage, this is the place to do it
         But! This is not the place to apply KMS config, here we only
         notify Clutter/Cogl/GL that the framebuffer sizes changed.

         And because for now clutter does not do sliced, we use one
         framebuffer the size of the whole screen, and when running on
         bare metal MetaMonitorManager will do the necessary tricks to
         show the right portions on the right screens.
      */

      clutter_actor_set_size (compositor->stage, width, height);
    }
  else
    {
      Display        *xdisplay;
      Window          xwin;

      xdisplay = meta_display_get_xdisplay (display);
      xwin = clutter_x11_get_stage_window (CLUTTER_STAGE (compositor->stage));

      XResizeWindow (xdisplay, xwin, width, height);
    }

  meta_verbose ("Changed size for stage on screen %d to %dx%d\n",
                meta_screen_get_screen_number (display->screen),
                width, height);
}
Пример #24
0
int
main (int argc, char *argv[])
{
  GLenum err = 0;
#ifdef WIN32
  HGLRC clutter_gl_context = 0;
  HDC clutter_dc = 0;
#else
  Display *clutter_display = NULL;
  Window clutter_win = 0;
  GLXContext clutter_gl_context = NULL;
#endif
  GstPipeline *pipeline = NULL;
  GstBus *bus = NULL;
  GstElement *glupload = NULL;
  GstState state = 0;
  ClutterActor *stage = NULL;
  ClutterActor *clutter_texture = NULL;
  GAsyncQueue *queue_input_buf = NULL;
  GAsyncQueue *queue_output_buf = NULL;
  GstElement *fakesink = NULL;

  /* init gstreamer then clutter */

  gst_init (&argc, &argv);
  clutter_threads_init ();
  clutter_init (&argc, &argv);
  clutter_threads_enter ();
  g_print ("clutter version: %s\n", CLUTTER_VERSION_S);

  /* init glew */

  err = glewInit ();
  if (err != GLEW_OK)
    g_debug ("failed to init GLEW: %s", glewGetErrorString (err));

  /* avoid to dispatch unecesary events */

  clutter_ungrab_keyboard ();
  clutter_ungrab_pointer ();

  /* retrieve and turn off clutter opengl context */

  stage = clutter_stage_get_default ();

  /* retrieve and turn off clutter opengl context */

#ifdef WIN32
  clutter_gl_context = wglGetCurrentContext ();
  clutter_dc = wglGetCurrentDC ();
  wglMakeCurrent (0, 0);
#else
  clutter_display = clutter_x11_get_default_display ();
  clutter_win = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
  clutter_gl_context = glXGetCurrentContext ();
  glXMakeCurrent (clutter_display, None, 0);
#endif

  /* setup gstreamer pipeline */

  pipeline =
      GST_PIPELINE (gst_parse_launch
      ("videotestsrc ! video/x-raw-yuv, width=320, height=240, framerate=(fraction)30/1 ! "
          "glupload ! gleffects effect=5 ! glfiltercube ! fakesink sync=1",
          NULL));

  /* setup bus */

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  gst_bus_add_signal_watch (bus);
  g_signal_connect (bus, "message::error", G_CALLBACK (end_stream_cb), NULL);
  g_signal_connect (bus, "message::warning", G_CALLBACK (end_stream_cb), NULL);
  g_signal_connect (bus, "message::eos", G_CALLBACK (end_stream_cb), NULL);
  gst_object_unref (bus);

  /* clutter_gl_context is an external OpenGL context with which gst-plugins-gl want to share textures */

  glupload = gst_bin_get_by_name (GST_BIN (pipeline), "glupload0");
  g_object_set (G_OBJECT (glupload), "external-opengl-context",
      clutter_gl_context, NULL);
  g_object_unref (glupload);

  /* NULL to PAUSED state pipeline to make sure the gst opengl context is created and
   * shared with the clutter one */

  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED);
  state = GST_STATE_PAUSED;
  if (gst_element_get_state (GST_ELEMENT (pipeline), &state, NULL,
          GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
    g_debug ("failed to pause pipeline\n");
    return -1;
  }

  /* turn on back clutter opengl context */

#ifdef WIN32
  wglMakeCurrent (clutter_dc, clutter_gl_context);
#else
  glXMakeCurrent (clutter_display, clutter_win, clutter_gl_context);
#endif

  /* clutter stage */

  clutter_actor_set_size (stage, 640, 480);
  clutter_actor_set_position (stage, 0, 0);
  clutter_stage_set_title (CLUTTER_STAGE (stage), "clutter and gst-plugins-gl");
  clutter_texture = setup_stage (CLUTTER_STAGE (stage));

  /* append a gst-gl texture to this queue when you do not need it no more */

  queue_input_buf = g_async_queue_new ();
  queue_output_buf = g_async_queue_new ();
  g_object_set_data (G_OBJECT (clutter_texture), "queue_input_buf",
      queue_input_buf);
  g_object_set_data (G_OBJECT (clutter_texture), "queue_output_buf",
      queue_output_buf);

  /* set a callback to retrieve the gst gl textures */

  fakesink = gst_bin_get_by_name (GST_BIN (pipeline), "fakesink0");
  g_object_set (G_OBJECT (fakesink), "signal-handoffs", TRUE, NULL);
  g_signal_connect (fakesink, "handoff", G_CALLBACK (on_gst_buffer),
      clutter_texture);
  g_object_unref (fakesink);

  /* play gst */

  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);

  /* main loop */

  clutter_main ();

  /* before to deinitialize the gst-gl-opengl context,
   * no shared context (here the clutter one) must be current
   */
#ifdef WIN32
  wglMakeCurrent (0, 0);
#else
  glXMakeCurrent (clutter_display, None, 0);
#endif

  clutter_threads_leave ();

  /* stop and clean up the pipeline */

  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
  g_object_unref (pipeline);

  /* make sure there is no pending gst gl buffer in the communication queues 
   * between clutter and gst-gl
   */

  while (g_async_queue_length (queue_input_buf) > 0) {
    GstBuffer *buf = g_async_queue_pop (queue_input_buf);
    gst_buffer_unref (buf);
  }

  while (g_async_queue_length (queue_output_buf) > 0) {
    GstBuffer *buf = g_async_queue_pop (queue_output_buf);
    gst_buffer_unref (buf);
  }

  return 0;
}
Пример #25
0
void
meta_compositor_manage_screen (MetaCompositor *compositor,
                               MetaScreen     *screen)
{
    MetaCompScreen *info;
    MetaDisplay    *display       = meta_screen_get_display (screen);
    Display        *xdisplay      = meta_display_get_xdisplay (display);
    int             screen_number = meta_screen_get_screen_number (screen);
    Window          xroot         = meta_screen_get_xroot (screen);
    Window          xwin;
    gint            width, height;
    XWindowAttributes attr;
    long            event_mask;
    guint           n_retries;
    guint           max_retries;

    /* Check if the screen is already managed */
    if (meta_screen_get_compositor_data (screen))
        return;

    if (meta_get_replace_current_wm ())
        max_retries = 5;
    else
        max_retries = 1;

    n_retries = 0;

    /* Some compositors (like old versions of Muffin) might not properly unredirect
     * subwindows before destroying the WM selection window; so we wait a while
     * for such a compositor to exit before giving up.
     */
    while (TRUE)
    {
        meta_error_trap_push_with_return (display);
        XCompositeRedirectSubwindows (xdisplay, xroot, CompositeRedirectManual);
        XSync (xdisplay, FALSE);

        if (!meta_error_trap_pop_with_return (display))
            break;

        if (n_retries == max_retries)
        {
            /* This probably means that a non-WM compositor like xcompmgr is running;
             * we have no way to get it to exit */
            meta_fatal (_("Another compositing manager is already running on screen %i on display \"%s\"."),
                        screen_number, display->name);
        }

        n_retries++;
        g_usleep (G_USEC_PER_SEC);
    }

    info = g_new0 (MetaCompScreen, 1);
    /*
     * We use an empty input region for Clutter as a default because that allows
     * the user to interact with all the windows displayed on the screen.
     * We have to initialize info->pending_input_region to an empty region explicitly,
     * because None value is used to mean that the whole screen is an input region.
     */
    info->pending_input_region = XFixesCreateRegion (xdisplay, NULL, 0);

    info->screen = screen;

    meta_screen_set_compositor_data (screen, info);

    info->output = None;
    info->windows = NULL;

    meta_screen_set_cm_selection (screen);

    info->stage = clutter_stage_new ();

    meta_screen_get_size (screen, &width, &height);
    clutter_actor_realize (info->stage);

    xwin = clutter_x11_get_stage_window (CLUTTER_STAGE (info->stage));

    XResizeWindow (xdisplay, xwin, width, height);

    event_mask = FocusChangeMask |
                 ExposureMask |
                 EnterWindowMask | LeaveWindowMask |
                 PointerMotionMask |
                 PropertyChangeMask |
                 ButtonPressMask | ButtonReleaseMask |
                 KeyPressMask | KeyReleaseMask |
                 StructureNotifyMask;

    if (XGetWindowAttributes (xdisplay, xwin, &attr))
    {
        event_mask |= attr.your_event_mask;
    }

    XSelectInput (xdisplay, xwin, event_mask);

    info->window_group = meta_window_group_new (screen);
    info->background_actor = meta_background_actor_new_for_screen (screen);
    info->bottom_window_group = clutter_group_new();
    info->overlay_group = clutter_group_new ();
    info->top_window_group = meta_window_group_new (screen);
    info->hidden_group = clutter_group_new ();

    clutter_container_add (CLUTTER_CONTAINER (info->window_group),
                           info->background_actor,
                           NULL);

    clutter_container_add (CLUTTER_CONTAINER (info->stage),
                           info->window_group,
                           info->overlay_group,
                           info->hidden_group,
                           NULL);

    clutter_actor_hide (info->hidden_group);

    info->plugin_mgr =
        meta_plugin_manager_get (screen);
    meta_plugin_manager_initialize (info->plugin_mgr);

    /*
     * Delay the creation of the overlay window as long as we can, to avoid
     * blanking out the screen. This means that during the plugin loading, the
     * overlay window is not accessible; if the plugin needs to access it
     * directly, it should hook into the "show" signal on stage, and do
     * its stuff there.
     */
    info->output = get_output_window (screen);
    XReparentWindow (xdisplay, xwin, info->output, 0, 0);

    /* Make sure there isn't any left-over output shape on the
     * overlay window by setting the whole screen to be an
     * output region.
     *
     * Note: there doesn't seem to be any real chance of that
     *  because the X server will destroy the overlay window
     *  when the last client using it exits.
     */
    XFixesSetWindowShapeRegion (xdisplay, info->output, ShapeBounding, 0, 0, None);

    do_set_stage_input_region (screen, info->pending_input_region);
    if (info->pending_input_region != None)
    {
        XFixesDestroyRegion (xdisplay, info->pending_input_region);
        info->pending_input_region = None;
    }

    clutter_actor_show (info->overlay_group);
    clutter_actor_show (info->stage);
}
Пример #26
0
Window
meta_backend_x11_get_xwindow (MetaBackendX11 *x11)
{
  ClutterActor *stage = meta_backend_get_stage (META_BACKEND (x11));
  return clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
}
Пример #27
0
void
meta_compositor_manage (MetaCompositor *compositor)
{
  MetaDisplay *display = compositor->display;
  Display *xdisplay = display->xdisplay;
  MetaScreen *screen = display->screen;
  Window xwin = 0;
  gint width, height;

  meta_screen_set_cm_selection (display->screen);

  if (meta_is_wayland_compositor ())
    {
      MetaWaylandCompositor *wayland_compositor = meta_wayland_compositor_get_default ();

      compositor->stage = meta_stage_new ();

      wayland_compositor->stage = compositor->stage;

      meta_screen_get_size (screen, &width, &height);
      clutter_actor_set_size (compositor->stage, width, height);
      clutter_actor_show (compositor->stage);
    }
  else
    {
      compositor->stage = clutter_stage_new ();

      meta_screen_get_size (screen, &width, &height);
      clutter_actor_realize (compositor->stage);

      xwin = clutter_x11_get_stage_window (CLUTTER_STAGE (compositor->stage));

      XResizeWindow (xdisplay, xwin, width, height);

        {
          MetaBackendX11 *backend = META_BACKEND_X11 (meta_get_backend ());
          Display *backend_xdisplay = meta_backend_x11_get_xdisplay (backend);
          unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 };
          XIEventMask mask = { XIAllMasterDevices, sizeof (mask_bits), mask_bits };

          XISetMask (mask.mask, XI_KeyPress);
          XISetMask (mask.mask, XI_KeyRelease);
          XISetMask (mask.mask, XI_ButtonPress);
          XISetMask (mask.mask, XI_ButtonRelease);
          XISetMask (mask.mask, XI_Enter);
          XISetMask (mask.mask, XI_Leave);
          XISetMask (mask.mask, XI_FocusIn);
          XISetMask (mask.mask, XI_FocusOut);
          XISetMask (mask.mask, XI_Motion);
          XIClearMask (mask.mask, XI_TouchBegin);
          XIClearMask (mask.mask, XI_TouchEnd);
          XIClearMask (mask.mask, XI_TouchUpdate);
          XISelectEvents (backend_xdisplay, xwin, &mask, 1);
        }
    }

  /* We use connect_after() here to accomodate code in GNOME Shell that,
   * when benchmarking drawing performance, connects to ::after-paint
   * and calls glFinish(). The timing information from that will be
   * more accurate if we hold off until that completes before we signal
   * apps to begin drawing the next frame. If there are no other
   * connections to ::after-paint, connect() vs. connect_after() doesn't
   * matter.
   */
  g_signal_connect_after (CLUTTER_STAGE (compositor->stage), "after-paint",
                          G_CALLBACK (after_stage_paint), compositor);

  clutter_stage_set_sync_delay (CLUTTER_STAGE (compositor->stage), META_SYNC_DELAY);

  compositor->window_group = meta_window_group_new (screen);
  compositor->top_window_group = meta_window_group_new (screen);

  clutter_actor_add_child (compositor->stage, compositor->window_group);
  clutter_actor_add_child (compositor->stage, compositor->top_window_group);

  if (meta_is_wayland_compositor ())
    {
      /* NB: When running as a wayland compositor we don't need an X
       * composite overlay window, and we don't need to play any input
       * region tricks to redirect events into clutter. */
      compositor->output = None;
    }
  else
    {
      compositor->output = screen->composite_overlay_window;

      XReparentWindow (xdisplay, xwin, compositor->output, 0, 0);

      meta_empty_stage_input_region (screen);

      /* Make sure there isn't any left-over output shape on the
       * overlay window by setting the whole screen to be an
       * output region.
       *
       * Note: there doesn't seem to be any real chance of that
       *  because the X server will destroy the overlay window
       *  when the last client using it exits.
       */
      XFixesSetWindowShapeRegion (xdisplay, compositor->output, ShapeBounding, 0, 0, None);

      /* Map overlay window before redirecting windows offscreen so we catch their
       * contents until we show the stage.
       */
      XMapWindow (xdisplay, compositor->output);
    }

  redirect_windows (display->screen);

  compositor->plugin_mgr = meta_plugin_manager_new (compositor);
}
int
main (int argc, char *argv[])
{
  GstPipeline *pipeline;
  GstBus *bus;

  GstElement *srcbin;
  GstElement *tee;
  GstElement *queue[N_ACTORS], *sink[N_ACTORS];
  GstElement *upload[N_ACTORS];
/*
  GstElement *effect[N_ACTORS];
*/
  ClutterActor *stage;
  GstGLClutterActor *actor[N_ACTORS];
  Display *disp;
  Window stage_win;
  const gchar *desc;
  gint i;
  gint ok = FALSE;
  ClutterInitError clutter_err = CLUTTER_INIT_ERROR_UNKNOWN;

  clutter_err = clutter_init (&argc, &argv);
  if (clutter_err != CLUTTER_INIT_SUCCESS)
    g_warning ("Failed to initalize clutter: %d\n", clutter_err);

  gst_init (&argc, &argv);

  disp = clutter_x11_get_default_display ();
  if (!clutter_x11_has_composite_extension ()) {
    g_error ("XComposite extension missing");
  }

  stage = clutter_stage_get_default ();
  clutter_actor_set_size (CLUTTER_ACTOR (stage),
      W * COLS + (COLS - 1), H * ROWS + (ROWS - 1));

  stage_win = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
  XCompositeRedirectSubwindows (disp, stage_win, CompositeRedirectManual);

  for (i = 0; i < N_ACTORS; i++) {
    actor[i] = g_new0 (GstGLClutterActor, 1);
    actor[i]->stage = stage;
    actor[i]->win = XCreateSimpleWindow (disp, stage_win, 0, 0, W, H, 0, 0, 0);
    XMapRaised (disp, actor[i]->win);
    XSync (disp, FALSE);
  }
/*
  desc = g_strdup_printf ("v4l2src ! "
                          "video/x-raw, width=640, height=480, framerate=30/1 ! "
                          "videoscale !"
                          "video/x-raw, width=%d, height=%d ! "
                          "identity", W, H);
*/
  desc = g_strdup_printf ("videotestsrc ! "
      "video/x-raw, format=RGB, width=%d, height=%d !" "identity", W, H);
  pipeline = GST_PIPELINE (gst_pipeline_new (NULL));

  srcbin = gst_parse_bin_from_description (desc, TRUE, NULL);
  if (!srcbin)
    g_error ("Source bin creation failed");

  tee = gst_element_factory_make ("tee", NULL);

  gst_bin_add_many (GST_BIN (pipeline), srcbin, tee, NULL);

  for (i = 0; i < N_ACTORS; i++) {
    queue[i] = gst_element_factory_make ("queue", NULL);
    upload[i] = gst_element_factory_make ("glupload", NULL);
/*      effect[i] = gst_element_factory_make ("gleffects", NULL); */
    sink[i] = gst_element_factory_make ("glimagesink", NULL);
/*    gst_bin_add_many (GST_BIN (pipeline),
        queue[i], upload[i], effect[i], sink[i], NULL); */
    gst_bin_add_many (GST_BIN (pipeline), queue[i], upload[i], sink[i], NULL);
  }

  gst_element_link_many (srcbin, tee, NULL);

  for (i = 0; i < N_ACTORS; i++) {
    ok |=
//        gst_element_link_many (tee, queue[i], upload[i], effect[i], sink[i],
        gst_element_link_many (tee, queue[i], upload[i], sink[i], NULL);
  }

  if (!ok)
    g_error ("Failed to link one or more elements");

/*
  for (i = 0; i < N_ACTORS; i++) {
    g_message ("setting effect %d on %s", i + 1,
        gst_element_get_name (effect[i]));
    g_object_set (G_OBJECT (effect[i]), "effect", i + 1, NULL);
  }
*/

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));

  gst_bus_set_sync_handler (bus, (GstBusSyncHandler) create_window, actor,
      NULL);
  gst_object_unref (bus);

  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);

  clutter_actor_show_all (stage);

  clutter_main ();

  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
  gst_object_unref (pipeline);

  return 0;
}
Пример #29
0
gboolean toon_clutter_stage_set_window_icon(ClutterStage *stage, const gchar *path, GError ** /*error*/ )
{
    (void) stage;
    (void) path;
#if 0
    // TODO: assign some GError* to error in case of failure.
    Display *dpy = clutter_x11_get_default_display();
    Window win = clutter_x11_get_stage_window(stage);
    static Atom net_wm_icon = None;

    if (! net_wm_icon)
        net_wm_icon = XInternAtom(dpy, "_NET_WM_ICON", False);

    GdkPixbuf *pixbuf = NULL;
    GError *pixbuf_error = NULL;

    gint pixels_w = 32;
    gint pixels_h = 32;
    //g_info("Loading image at size of %dx%d\n", pixels_w, pixels_h);
    pixbuf = gdk_pixbuf_new_from_file_at_scale(path, pixels_w, pixels_h, FALSE, &pixbuf_error);
    if (! pixbuf)
    {
        g_error("Error loading image %s: %s", path, pixbuf_error->message);
        g_error_free(pixbuf_error);
        pixbuf_error = NULL;
        g_object_unref(pixbuf);
        return FALSE;
    }
    int n_channels = gdk_pixbuf_get_n_channels(pixbuf);
    if (n_channels != 4)
    {
        g_critical("Image has no alpha channel and we require one.");
        g_object_unref(pixbuf);
        return FALSE;
    }
    g_assert(gdk_pixbuf_get_colorspace(pixbuf) == GDK_COLORSPACE_RGB);
    g_assert(gdk_pixbuf_get_bits_per_sample(pixbuf) == 8);
    g_assert(gdk_pixbuf_get_has_alpha(pixbuf));
    g_assert(n_channels == 4);

    gulong bufsize = pixels_w * pixels_h * sizeof(char) * 4;
    guchar *data = (guchar *) g_malloc(bufsize + (sizeof(gulong) * 2));
    ((gulong *)data)[0] = pixels_w;
    ((gulong *)data)[1] = pixels_h;
    guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
    memcpy((void *) &(((gulong *)data)[2]), (void *) pixels, bufsize);

    ///* For some inexplicable reason XChangeProperty always takes
    // * an array of longs when the format == 32 even on 64-bit
    // * architectures where sizeof(long) != 32. Therefore we need
    // * to pointlessly pad each 32-bit value with an extra 4
    // * bytes so that libX11 can remove them again to send the
    // * request. We can do this in-place if we start from the
    // * end 
    // */
    //if (sizeof(gulong) != 4)
    //{
    //    const guint32 *src = (guint32 *) (data + 2) + pixels_w * pixels_h;
    //    gulong *dst = data + 2 + pixels_w * pixels_h;
 
    //    while (dst > data + 2)
    //    {
    //        *(--dst) = *(--src);
    //    }
    //}

    XChangeProperty(
        dpy, // X11 display
        win, // X11 window
        net_wm_icon, // name of property
        XA_CARDINAL, // type of property
        32, // format
        PropModeReplace, // mode
        (unsigned char *) data, // data (we must cast it to unsigned char* if format is 32)
        pixels_w * pixels_h + 2); // nelements

    g_free(data);
    g_object_unref(pixbuf);
#endif
    return TRUE;
}
Пример #30
0
int
main (int argc, char *argv[])
{
  ClutterTimeline *timeline;
  ClutterAlpha     *alpha;
  ClutterBehaviour *scaler_1, *scaler_2;
  ClutterActor    *stage;
  ClutterColor     stage_color = { 0x61, 0x64, 0x8c, 0xff };
  SuperOH         *oh;
  gint             i;
  GError          *error;

#ifdef REQ_NON_COMPOSITION
  Window           xwin;
  Atom             non_comp_atom;
  Display         *dpy;
  int              one = 1;
#endif

  error = NULL;

  clutter_init_with_args (&argc, &argv,
                          NULL,
                          super_oh_entries,
                          NULL,
                          &error);
  if (error)
    {
      g_warning ("Unable to initialise Clutter:\n%s",
                 error->message);
      g_error_free (error);

      exit (1);
    }

  stage = clutter_stage_get_default ();
  clutter_actor_set_size (stage, 800, 600);

  clutter_stage_set_title (CLUTTER_STAGE (stage), "Actors Test");
  clutter_stage_set_color (CLUTTER_STAGE (stage),
		           &stage_color);

#ifdef REQ_NON_COMPOSITION
  /* request non-composited mode */
  clutter_stage_fullscreen (stage);
  xwin = clutter_x11_get_stage_window (stage);
  dpy = XOpenDisplay(NULL);
  non_comp_atom = XInternAtom(dpy, "_HILDON_NON_COMPOSITED_WINDOW", False);
  XChangeProperty (dpy, xwin, non_comp_atom,
                   XA_INTEGER, 32, PropModeReplace,
                   (unsigned char *) &one, 1);
  printf ("stage win is %lx\n", xwin);
  XSync (dpy, False);
#endif

  oh = g_new(SuperOH, 1);

  /* Create a timeline to manage animation */
  timeline = clutter_timeline_new (360, 60); /* num frames, fps */
  g_object_set (timeline, "loop", TRUE, NULL);   /* have it loop */

  /* fire a callback for frame change */
  g_signal_connect (timeline, "new-frame", G_CALLBACK (frame_cb), oh);

  /* Set up some behaviours to handle scaling  */
  alpha = clutter_alpha_new_full (timeline, CLUTTER_ALPHA_SINE, NULL, NULL);

  scaler_1 = clutter_behaviour_scale_new (alpha,
					  0.5, 0.5,
					  1.0, 1.0);

  scaler_2 = clutter_behaviour_scale_new (alpha,
					  1.0, 1.0,
					  0.5, 0.5);

  /* create a new group to hold multiple actors in a group */
  oh->group = clutter_group_new();

  oh->hand = g_new (ClutterActor*, n_hands);
  for (i = 0; i < n_hands; i++)
    {
      gint x, y, w, h;
      gint radius = get_radius ();

      /* Create a texture from file, then clone in to same resources */
      if (i == 0)
	{
	  if ((oh->hand[i] = clutter_texture_new_from_file ("redhand.png",
							    &error)) == NULL)
	    {
	      g_error ("image load failed: %s", error->message);
	      exit (1);
	    }
	}
      else
	oh->hand[i] = clutter_clone_texture_new (CLUTTER_TEXTURE(oh->hand[0]));

      /* Place around a circle */
      w = clutter_actor_get_width (oh->hand[0]);
      h = clutter_actor_get_height (oh->hand[0]);

      x = CLUTTER_STAGE_WIDTH () / 2
	+ radius
	* cos (i * M_PI / (n_hands / 2))
	- w / 2;

      y = CLUTTER_STAGE_HEIGHT () / 2
	+ radius
	* sin (i * M_PI / (n_hands / 2))
	- h / 2;

      clutter_actor_set_position (oh->hand[i], x, y);

      clutter_actor_move_anchor_point_from_gravity (oh->hand[i],
						   CLUTTER_GRAVITY_CENTER);

      /* Add to our group group */
      clutter_container_add_actor (CLUTTER_CONTAINER (oh->group), oh->hand[i]);

#if 1 /* FIXME: disabled as causes drift? - see comment above */
      if (i % 2)
	clutter_behaviour_apply (scaler_1, oh->hand[i]);
      else
	clutter_behaviour_apply (scaler_2, oh->hand[i]);
#endif
    }

  /* Add the group to the stage */
  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
                               CLUTTER_ACTOR (oh->group));

  /* Show everying ( and map window ) */
  clutter_actor_show (stage);


  g_signal_connect (stage, "button-press-event",
		    G_CALLBACK (input_cb),
		    oh);
  g_signal_connect (stage, "key-release-event",
		    G_CALLBACK (input_cb),
		    oh);

  /* and start it */
  clutter_timeline_start (timeline);

  clutter_main ();

  g_free (oh->hand);
  g_free (oh);

  return 0;
}