--- /dev/null
+/* This file is part of SDL_haa - SDL addon for Hildon Animation Actors
+ * Copyright (C) 2010 Javier S. Pedro
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA or see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <SDL.h>
+#include <SDL_syswm.h>
+
+#ifdef HAVE_XSHM
+#include <X11/extensions/XShm.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#endif
+
+#include "SDL_haa.h"
+#include "atoms.inc"
+
+typedef struct HAA_ActorPriv {
+ HAA_Actor p;
+
+ Window window, parent;
+ Visual *visual;
+ Colormap colormap;
+ XImage *image;
+ GC gc;
+#ifdef HAVE_XSHM
+ XShmSegmentInfo shminfo;
+#endif
+ unsigned char ready;
+ struct HAA_ActorPriv *prev, *next;
+} HAA_ActorPriv;
+
+static Bool initialized = False;
+
+static Display *display;
+static Window parent_window;
+static HAA_ActorPriv *first = NULL, *last = NULL;
+
+#ifdef HAVE_XSHM
+static int shm_major, shm_minor;
+static Bool shm_pixmaps;
+static Bool have_shm;
+#else
+static const Bool have_shm = False;
+#endif
+
+int HAA_Init(Uint32 flags)
+{
+ SDL_SysWMinfo info;
+
+ SDL_VERSION(&info.version);
+ if (SDL_GetWMInfo(&info) != 1) {
+ SDL_SetError("SDL_haa is incompatible with this SDL version");
+ return -1;
+ }
+
+ display = info.info.x11.display;
+ if (flags & SDL_FULLSCREEN) {
+ parent_window = info.info.x11.fswindow;
+ } else {
+ parent_window = info.info.x11.wmwindow;
+ }
+ first = last = NULL;
+
+ if (!initialized) {
+ XInternAtoms(display, (char**)atom_names, ATOM_COUNT, True, atom_values);
+ initialized = True;
+
+#ifdef HAVE_XSHM
+ have_shm = XShmQueryVersion(display, &shm_major, &shm_minor, &shm_pixmaps);
+#endif
+
+ SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
+ }
+
+ return 0;
+}
+
+void HAA_Quit()
+{
+
+}
+
+static HAA_ActorPriv* find_actor_for_window(Window w)
+{
+ HAA_ActorPriv* a;
+
+ for (a = first; a; a = a->next) {
+ if (a->window == w) {
+ return a;
+ }
+ }
+
+ return NULL;
+}
+
+static void actor_send_message(HAA_ActorPriv* actor, Atom message_type,
+ Uint32 l0, Uint32 l1, Uint32 l2, Uint32 l3, Uint32 l4)
+{
+ Window window = actor->window;
+ XEvent event = { 0 };
+
+ event.xclient.type = ClientMessage;
+ event.xclient.window = window;
+ event.xclient.message_type = message_type;
+ event.xclient.format = 32;
+ event.xclient.data.l[0] = l0;
+ event.xclient.data.l[1] = l1;
+ event.xclient.data.l[2] = l2;
+ event.xclient.data.l[3] = l3;
+ event.xclient.data.l[4] = l4;
+
+ XSendEvent(display, window, True,
+ StructureNotifyMask,
+ (XEvent *)&event);
+}
+
+static void HAA_ReparentAllTo(Window new_parent)
+{
+ if (new_parent != parent_window) {
+ HAA_ActorPriv* a;
+ /* video mode has changed */
+ parent_window = new_parent;
+
+ /* unmap all actors that were already ready */
+ for (a = first; a; a = a->next) {
+ a->parent = parent_window;
+ if (a->ready) {
+ XUnmapWindow(display, a->window);
+ }
+ }
+
+ XSync(display, False);
+
+ /* now remap and reconfigure all actors */
+ for (a = first; a; a = a->next) {
+ if (a->ready) {
+ XMapWindow(display, a->window);
+ a->ready = 0;
+ a->p.pending |= HAA_PENDING_EVERYTHING;
+ } else {
+ a->p.pending |= HAA_PENDING_PARENT | HAA_PENDING_SHOW;
+ }
+ }
+
+ XFlush(display);
+ }
+}
+
+static void HAA_AutoReparent()
+{
+ SDL_SysWMinfo info;
+ SDL_Surface *screen = SDL_GetVideoSurface();
+ Window new_parent;
+
+ if (!screen) return;
+
+ SDL_VERSION(&info.version);
+ int res = SDL_GetWMInfo(&info);
+ assert(res == 1);
+
+ if (screen->flags & SDL_FULLSCREEN) {
+ new_parent = info.info.x11.fswindow;
+ } else {
+ new_parent = info.info.x11.wmwindow;
+ }
+
+ HAA_ReparentAllTo(new_parent);
+}
+
+static void HAA_Pending(HAA_ActorPriv* actor)
+{
+ const Uint16 pending = actor->p.pending;
+
+ if (!actor->ready) return; //Enqueue and wait
+
+ if (pending & HAA_PENDING_ANCHOR) {
+ actor_send_message(actor,
+ ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ANCHOR),
+ actor->p.gravity, actor->p.anchor_x, actor->p.anchor_y, 0, 0);
+ }
+ if (pending & HAA_PENDING_POSITION) {
+ actor_send_message(actor,
+ ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_POSITION),
+ actor->p.position_x, actor->p.position_y, actor->p.depth, 0, 0);
+ }
+
+ if (pending & HAA_PENDING_ROTATION_X) {
+ actor_send_message(actor,
+ ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
+ HAA_X_AXIS,
+ actor->p.x_rotation_angle,
+ 0, actor->p.x_rotation_y, actor->p.x_rotation_z);
+ }
+ if (pending & HAA_PENDING_ROTATION_Y) {
+ actor_send_message(actor,
+ ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
+ HAA_Y_AXIS,
+ actor->p.y_rotation_angle,
+ actor->p.y_rotation_x, 0, actor->p.y_rotation_z);
+ }
+ if (pending & HAA_PENDING_ROTATION_Z) {
+ actor_send_message(actor,
+ ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
+ HAA_Z_AXIS,
+ actor->p.x_rotation_angle,
+ actor->p.z_rotation_x, actor->p.z_rotation_y, 0);
+ }
+
+ if (pending & HAA_PENDING_SCALE) {
+ actor_send_message(actor,
+ ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_SCALE),
+ actor->p.scale_x, actor->p.scale_y, 0, 0, 0);
+ }
+
+ if (pending & HAA_PENDING_PARENT) {
+ actor_send_message(actor,
+ ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_PARENT),
+ actor->parent, 0, 0, 0, 0);
+ }
+ if (pending & HAA_PENDING_SHOW) {
+ actor_send_message(actor,
+ ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_SHOW),
+ actor->p.visible, actor->p.opacity, 0, 0, 0);
+ }
+
+ actor->p.pending = HAA_PENDING_NOTHING;
+}
+
+static void sdl_expose()
+{
+ SDL_Event events[32];
+
+ /* Pull out all old refresh events */
+ SDL_PeepEvents(events, sizeof(events)/sizeof(events[0]),
+ SDL_GETEVENT, SDL_VIDEOEXPOSEMASK);
+
+ /* Post the event, if desired */
+ if ( SDL_EventState(SDL_VIDEOEXPOSE, SDL_QUERY) == SDL_ENABLE ) {
+ SDL_Event event;
+ event.type = SDL_VIDEOEXPOSE;
+ SDL_PushEvent(&event);
+ }
+}
+
+static void actor_update_ready(HAA_ActorPriv* actor)
+{
+ Window window = actor->window;
+ int status;
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop = NULL;
+
+ status = XGetWindowProperty(display, window,
+ ATOM(_HILDON_ANIMATION_CLIENT_READY), 0, 32,
+ False, XA_ATOM,
+ &actual_type, &actual_format,
+ &nitems, &bytes_after, &prop);
+
+ if (prop) {
+ XFree(prop);
+ }
+
+ if (status != Success || actual_type != XA_ATOM ||
+ actual_format != 32 || nitems != 1) {
+ actor->ready = 0;
+ return;
+ }
+
+ if (actor->ready) {
+ /* Ready flag already set, which means hildon-desktop just restarted */
+ XUnmapWindow(display, window);
+ XSync(display, False);
+ XMapWindow(display, window);
+ XSync(display, False);
+ SDL_Delay(500); /* Give the WM some time to relax. */
+
+ /* Next Flip will resend every setting */
+ actor->p.pending = HAA_PENDING_EVERYTHING;
+ return;
+ }
+
+ actor->ready = 1;
+
+ /* Send all pending messages now */
+ HAA_Pending(actor);
+
+ /* Force a redraw */
+ sdl_expose();
+}
+
+int HAA_FilterEvent(const SDL_Event *event)
+{
+ switch (event->type) {
+
+ case SDL_SYSWMEVENT: {
+ const XEvent *e = &event->syswm.msg->event.xevent;
+ switch(e->type) {
+ case PropertyNotify:
+ if (e->xproperty.atom == ATOM(_HILDON_ANIMATION_CLIENT_READY)) {
+ HAA_ActorPriv* actor =
+ find_actor_for_window(e->xproperty.window);
+ if (actor) {
+ actor_update_ready(actor);
+ return 0; // Handled
+ }
+ }
+ break;
+ }
+ }
+ break;
+
+ case SDL_KEYUP: {
+ /* For reasons unknown, we seem to receive this on fullscreen events */
+ const SDL_keysym *key = &event->key.keysym;
+ if (key->scancode == 0 && key->sym == SDLK_NUMLOCK &&
+ key->mod == KMOD_NUM) {
+ /* Either task switched or fullscreen invoked */
+ HAA_AutoReparent();
+ }
+ }
+ break;
+
+ }
+
+ return 1;
+}
+
+HAA_Actor* HAA_CreateActor(Uint32 flags,
+ int width, int height, int bitsPerPixel)
+{
+ HAA_ActorPriv *actor = malloc(sizeof(HAA_ActorPriv));
+ if (!actor) {
+ SDL_Error(SDL_ENOMEM);
+ return NULL;
+ }
+
+ /* Default actor settings */
+ actor->p.position_x = 0;
+ actor->p.position_y = 0;
+ actor->p.depth = 0;
+ actor->p.visible = 0;
+ actor->p.opacity = 255;
+ actor->parent = parent_window;
+ actor->p.scale_x = 1 << 16;
+ actor->p.scale_y = 1 << 16;
+ actor->p.gravity = HAA_GRAVITY_NONE;
+ actor->p.anchor_x = 0;
+ actor->p.anchor_y = 0;
+ actor->p.x_rotation_angle = 0;
+ actor->p.x_rotation_y = 0;
+ actor->p.x_rotation_z = 0;
+ actor->p.y_rotation_angle = 0;
+ actor->p.y_rotation_x = 0;
+ actor->p.y_rotation_z = 0;
+ actor->p.z_rotation_angle = 0;
+ actor->p.z_rotation_x = 0;
+ actor->p.z_rotation_y = 0;
+ actor->p.pending =
+ HAA_PENDING_POSITION | HAA_PENDING_SCALE | HAA_PENDING_PARENT;
+
+ /* Select the X11 visual */
+ int screen = DefaultScreen(display);
+ Window root = RootWindow(display, screen);
+ XVisualInfo vinfo;
+ XImage *image;
+ void* pixels = NULL;
+ if (!XMatchVisualInfo(display, screen, bitsPerPixel, TrueColor, &vinfo)) {
+ /* Not matched; Use the default visual instead */
+ int numVisuals;
+ XVisualInfo *xvi;
+
+ vinfo.screen = screen;
+ xvi = XGetVisualInfo(display, VisualScreenMask, &vinfo, &numVisuals);
+ assert(xvi);
+
+ vinfo = *xvi;
+ XFree(xvi);
+ }
+ actor->visual = vinfo.visual;
+
+ if (vinfo.visual != DefaultVisual(display, screen)) {
+ /* Allocate a private color map. */
+ actor->colormap = XCreateColormap(display, root,
+ vinfo.visual, AllocNone);
+ } else {
+ actor->colormap = 0;
+ }
+
+ /* Create X11 window for actor */
+ XSetWindowAttributes attr;
+ unsigned long attrmask = CWBorderPixel | CWBackPixel | CWBitGravity;
+ attr.background_pixel = BlackPixel(display, screen);
+ attr.border_pixel = attr.background_pixel;
+ attr.bit_gravity = ForgetGravity;
+ if (actor->colormap) {
+ attr.colormap = actor->colormap;
+ attrmask |= CWColormap;
+ }
+
+ Window window = actor->window = XCreateWindow(display, root,
+ 0, 0, width, height, 0, vinfo.depth,
+ InputOutput, vinfo.visual,
+ attrmask, &attr);
+
+ XStoreName(display, window, "sdl_haa window");
+
+ Atom atom = ATOM(_HILDON_WM_WINDOW_TYPE_ANIMATION_ACTOR);
+ XChangeProperty(display, window, ATOM(_NET_WM_WINDOW_TYPE),
+ XA_ATOM, 32, PropModeReplace,
+ (unsigned char *) &atom, 1);
+
+ /* Setup the X Image */
+ if (have_shm) {
+ image = actor->image = XShmCreateImage(display, vinfo.visual,
+ vinfo.depth, ZPixmap, NULL, &actor->shminfo, width, height);
+ if (!image) {
+ SDL_SetError("Cannot create XSHM image");
+ goto cleanup_window;
+ }
+
+ actor->shminfo.shmid = shmget(IPC_PRIVATE,
+ image->bytes_per_line * image->height, IPC_CREAT|0777);
+ if (actor->shminfo.shmid < 0) {
+ SDL_SetError("Failed to get shared memory");
+ XDestroyImage(image);
+ goto cleanup_window;
+ }
+
+ actor->shminfo.shmaddr = shmat(actor->shminfo.shmid, NULL, 0);
+ if (!actor->shminfo.shmaddr) {
+ SDL_SetError("Failed to attach shared memory");
+ XDestroyImage(image);
+ shmctl(actor->shminfo.shmid, IPC_RMID, 0);
+ goto cleanup_window;
+ }
+
+ actor->shminfo.readOnly = True;
+ if (!XShmAttach(display, &actor->shminfo)) {
+ SDL_SetError("Failed to attach shared memory image");
+ XDestroyImage(image);
+ shmdt(actor->shminfo.shmaddr);
+ shmctl(actor->shminfo.shmid, IPC_RMID, 0);
+ goto cleanup_window;
+ }
+
+ /* Ensure attachment is done */
+ XSync(display, False);
+
+ /* Nobody else needs it now */
+ shmctl(actor->shminfo.shmid, IPC_RMID, 0);
+
+ pixels = actor->shminfo.shmaddr;
+ image->data = (char*) pixels;
+ } else {
+ pixels = malloc(width * height * (vinfo.depth / 8));
+ if (!pixels) {
+ SDL_SetError("Cannot allocate image");
+ goto cleanup_window;
+ }
+ image = actor->image = XCreateImage(display, vinfo.visual,
+ vinfo.depth, ZPixmap, 0, (char*) pixels, width, height, 8, 0);
+ if (!image) {
+ SDL_SetError("Cannot create X image");
+ goto cleanup_window;
+ }
+ }
+
+ /* Guess alpha mask */
+ Uint32 Amask = 0;
+ if (image->depth == 32) {
+ Amask = ~(vinfo.red_mask | vinfo.green_mask | vinfo.blue_mask);
+ }
+
+ /* Create GC */
+ GC gc = actor->gc = XCreateGC(display, window, 0, NULL);
+ XSetForeground(display, gc, 0xFFFFFFFFU);
+
+ /** Create SDL texture for actor */
+ actor->p.surface = SDL_CreateRGBSurfaceFrom(pixels,
+ image->width, image->height, image->depth, image->bytes_per_line,
+ vinfo.red_mask, vinfo.green_mask, vinfo.blue_mask, Amask);
+
+ if (!actor->p.surface) {
+ /* SDL Error already set */
+ goto cleanup_gc;
+ }
+
+ /* Map X11 window */
+ XSelectInput(display, window, PropertyChangeMask);
+ XMapWindow(display, window);
+
+ /* Add to actor linked list */
+ if (first == NULL) {
+ assert(last == NULL);
+ actor->next = actor->prev = NULL;
+ first = last = actor;
+ } else {
+ last->next = actor;
+ actor->prev = last;
+ actor->next = NULL;
+ last = actor;
+ }
+
+ XSync(display, False);
+ return (HAA_Actor*) actor;
+
+cleanup_gc:
+ XFreeGC(display, gc);
+cleanup_image:
+ if (have_shm) {
+ XShmDetach(display, &actor->shminfo);
+ XDestroyImage(image);
+ shmdt(actor->shminfo.shmaddr);
+ } else {
+ XDestroyImage(image);
+ }
+cleanup_window:
+ XDestroyWindow(display, window);
+ if (actor->colormap) XFreeColormap(display, actor->colormap);
+cleanup_actor:
+ free(actor);
+
+ XSync(display, True);
+ return NULL;
+}
+
+void HAA_FreeActor(HAA_Actor* a)
+{
+ HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
+ if (!a) return;
+
+ XFreeGC(display, actor->gc);
+ if (have_shm) {
+ XShmDetach(display, &actor->shminfo);
+ XDestroyImage(actor->image);
+ shmdt(actor->shminfo.shmaddr);
+ XDestroyWindow(display, actor->window);
+ } else {
+ XDestroyImage(actor->image);
+ }
+ if (actor->colormap)
+ XFreeColormap(display, actor->colormap);
+ SDL_FreeSurface(actor->p.surface);
+
+ if (first == actor && last == actor) {
+ assert(!actor->next && !actor->prev);
+ first = NULL;
+ last = NULL;
+ } else if (last == actor) {
+ assert(!actor->next && actor->prev);
+ last = actor->prev;
+ last->next = NULL;
+ } else if (first == actor) {
+ assert(actor->next && !actor->prev);
+ first = actor->next;
+ first->prev = NULL;
+ } else {
+ assert(actor->next && actor->prev);
+ actor->prev->next = actor->next;
+ actor->next->prev = actor->prev;
+ }
+
+ free(actor);
+
+ XFlush(display);
+}
+
+int HAA_Commit(HAA_Actor* a)
+{
+ HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
+
+ HAA_Pending(actor);
+ XSync(display, False);
+
+ return 0;
+}
+
+int HAA_Flip(HAA_Actor* a)
+{
+ HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
+ Window window = actor->window;
+ GC gc = actor->gc;
+ XImage *image = actor->image;
+
+ if (have_shm) {
+ XShmPutImage(display, window, gc, image,
+ 0, 0, 0, 0, image->width, image->height, False);
+ } else {
+ XPutImage(display, window, gc, image,
+ 0, 0, 0, 0, image->width, image->height);
+ }
+
+ HAA_Pending(actor);
+ XSync(display, False);
+
+ return 0;
+}
+