1 /* This file is part of SDL_haa - SDL addon for Hildon Animation Actors
2 * Copyright (C) 2010 Javier S. Pedro
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA or see <http://www.gnu.org/licenses/>.
26 #include <X11/Xutil.h>
27 #include <X11/Xatom.h>
29 #include <SDL_syswm.h>
32 #include <X11/extensions/XShm.h>
40 typedef struct HAA_ActorPriv {
43 Window window, parent;
49 XShmSegmentInfo shminfo;
52 struct HAA_ActorPriv *prev, *next;
55 static Display *display;
56 static Window parent_window;
57 static HAA_ActorPriv *first = NULL, *last = NULL;
59 /* Queued reparents. */
60 static Uint32 queued_reparent_time;
61 static Bool queued_reparent_fs;
64 static int shm_major, shm_minor;
65 static Bool shm_pixmaps;
68 static const Bool have_shm = False;
71 int HAA_Init(Uint32 flags)
75 SDL_VERSION(&info.version);
76 if (SDL_GetWMInfo(&info) != 1) {
77 SDL_SetError("SDL_haa is incompatible with this SDL version");
81 display = info.info.x11.display;
83 queued_reparent_time = 0;
86 XInternAtoms(display, (char**)atom_names, ATOM_COUNT, True, atom_values);
89 have_shm = XShmQueryVersion(display, &shm_major, &shm_minor, &shm_pixmaps);
92 /* This might add some noise to your event queue, but we need them. */
93 SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
100 /* Nothing to do for now */
103 static HAA_ActorPriv* find_actor_for_window(Window w)
107 for (a = first; a; a = a->next) {
108 if (a->window == w) {
116 static void actor_send_message(HAA_ActorPriv* actor, Atom message_type,
117 Uint32 l0, Uint32 l1, Uint32 l2, Uint32 l3, Uint32 l4)
119 Window window = actor->window;
120 XEvent event = { 0 };
122 event.xclient.type = ClientMessage;
123 event.xclient.window = window;
124 event.xclient.message_type = message_type;
125 event.xclient.format = 32;
126 event.xclient.data.l[0] = l0;
127 event.xclient.data.l[1] = l1;
128 event.xclient.data.l[2] = l2;
129 event.xclient.data.l[3] = l3;
130 event.xclient.data.l[4] = l4;
132 XSendEvent(display, window, True,
137 static void reparent_all_to(Window new_parent)
140 /* video mode has changed */
141 parent_window = new_parent;
143 /* if we don't have any actors, no need to reparent them */
145 assert(last == NULL);
149 /* unmap all actors that were already ready */
150 for (a = first; a; a = a->next) {
151 a->parent = parent_window;
153 XUnmapWindow(display, a->window);
157 XSync(display, False);
159 /* now remap and reconfigure all actors */
160 for (a = first; a; a = a->next) {
162 XMapWindow(display, a->window);
164 a->p.pending |= HAA_PENDING_EVERYTHING;
166 a->p.pending |= HAA_PENDING_PARENT | HAA_PENDING_SHOW;
173 static int auto_reparent_all_to(Bool fullscreen)
177 XWindowAttributes attr;
181 SDL_VERSION(&info.version);
182 res = SDL_GetWMInfo(&info);
185 /* Delete any pending reparent */
186 queued_reparent_time = 0;
189 new_parent = info.info.x11.fswindow;
191 new_parent = info.info.x11.wmwindow;
194 XGetWindowAttributes(display, new_parent, &attr);
195 is_mapped = attr.map_state == IsViewable;
197 /* Do we really need to reparent? */
198 if (new_parent != parent_window) {
201 reparent_all_to(new_parent);
206 } else if (!is_mapped) {
207 /* We don't actually need to reparent,
208 * but we found that our current parent has been unmapped! */
209 parent_window = 0; // Make current one it invalid
210 return 1; // Signal failure
212 /* We don't need to reparent and everything is OK. */
217 static void handle_queued_reparent()
219 if (queued_reparent_time) {
220 /* A reparent is pending. */
221 Uint32 now = SDL_GetTicks();
222 if (now > queued_reparent_time) {
223 /* Try to do the queued reparent now. */
224 int res = auto_reparent_all_to(queued_reparent_fs);
226 /* Failed to reparent? Try again in 200 ms. */
227 queued_reparent_time = now + 200;
233 static void queue_auto_reparent_to(Bool fullscreen, Uint32 delay)
235 queued_reparent_time = SDL_GetTicks() + delay;
236 queued_reparent_fs = fullscreen;
239 int HAA_SetVideoMode()
241 SDL_Surface *screen = SDL_GetVideoSurface();
244 SDL_SetError("Failed to get current video surface");
248 auto_reparent_all_to(screen->flags & SDL_FULLSCREEN ? True : False);
253 static void HAA_Pending(HAA_ActorPriv* actor)
255 const Uint16 pending = actor->p.pending;
257 if (!actor->ready) return; //Enqueue and wait
259 if (pending & HAA_PENDING_ANCHOR) {
260 actor_send_message(actor,
261 ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ANCHOR),
262 actor->p.gravity, actor->p.anchor_x, actor->p.anchor_y, 0, 0);
264 if (pending & HAA_PENDING_POSITION) {
265 actor_send_message(actor,
266 ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_POSITION),
267 actor->p.position_x, actor->p.position_y, actor->p.depth, 0, 0);
270 if (pending & HAA_PENDING_ROTATION_X) {
271 actor_send_message(actor,
272 ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
274 actor->p.x_rotation_angle,
275 0, actor->p.x_rotation_y, actor->p.x_rotation_z);
277 if (pending & HAA_PENDING_ROTATION_Y) {
278 actor_send_message(actor,
279 ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
281 actor->p.y_rotation_angle,
282 actor->p.y_rotation_x, 0, actor->p.y_rotation_z);
284 if (pending & HAA_PENDING_ROTATION_Z) {
285 actor_send_message(actor,
286 ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
288 actor->p.z_rotation_angle,
289 actor->p.z_rotation_x, actor->p.z_rotation_y, 0);
292 if (pending & HAA_PENDING_SCALE) {
293 actor_send_message(actor,
294 ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_SCALE),
295 actor->p.scale_x, actor->p.scale_y, 0, 0, 0);
298 if (pending & HAA_PENDING_PARENT) {
299 actor_send_message(actor,
300 ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_PARENT),
301 actor->parent, 0, 0, 0, 0);
303 if (pending & HAA_PENDING_SHOW) {
304 actor_send_message(actor,
305 ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_SHOW),
306 actor->p.visible, actor->p.opacity, 0, 0, 0);
309 actor->p.pending = HAA_PENDING_NOTHING;
312 /** Push a SDL_VIDEOEXPOSE event to the queue */
313 static void sdl_expose()
315 SDL_Event events[32];
317 /* Pull out all old refresh events */
318 SDL_PeepEvents(events, sizeof(events)/sizeof(events[0]),
319 SDL_GETEVENT, SDL_VIDEOEXPOSEMASK);
321 /* Post the event, if desired */
322 if ( SDL_EventState(SDL_VIDEOEXPOSE, SDL_QUERY) == SDL_ENABLE ) {
324 event.type = SDL_VIDEOEXPOSE;
325 SDL_PushEvent(&event);
329 /** Called when the client ready notification is received. */
330 static void actor_update_ready(HAA_ActorPriv* actor)
332 Window window = actor->window;
336 unsigned long nitems, bytes_after;
337 unsigned char *prop = NULL;
339 status = XGetWindowProperty(display, window,
340 ATOM(_HILDON_ANIMATION_CLIENT_READY), 0, 32,
342 &actual_type, &actual_format,
343 &nitems, &bytes_after, &prop);
349 if (status != Success || actual_type != XA_ATOM ||
350 actual_format != 32 || nitems != 1) {
356 /* Ready flag already set, which means hildon-desktop just restarted */
357 XUnmapWindow(display, window);
358 XSync(display, False);
359 XMapWindow(display, window);
360 XSync(display, False);
361 SDL_Delay(500); /* Give the WM some time to relax. */
363 /* Next Flip will resend every setting */
364 actor->p.pending = HAA_PENDING_EVERYTHING;
370 /* Send all pending messages now */
377 int HAA_FilterEvent(const SDL_Event *event)
379 handle_queued_reparent();
381 if (event->type == SDL_SYSWMEVENT) {
382 const XEvent *e = &event->syswm.msg->event.xevent;
383 if (e->type == PropertyNotify) {
384 if (e->xproperty.atom == ATOM(_HILDON_ANIMATION_CLIENT_READY)) {
385 HAA_ActorPriv* actor =
386 find_actor_for_window(e->xproperty.window);
388 actor_update_ready(actor);
393 } else if (event->type == SDL_ACTIVEEVENT) {
394 /* We know that after an input focus loss, the fullscreen window
395 * will be unampped. We get no warnings about when this happens.
396 * So we take a preventive approach and automatically reparent to the
397 * windowed window when any out of focus event happens.
398 * Of course, we have then to reparent to the fullscreen window when
399 * the focus comes back. But we have a problem: there's a 1.5 second
400 * delay between getting focus and SDL actually mapping the fullscreen
401 * window, and we cannot reparent back to the fullscreen window while
404 if (event->active.state == SDL_APPINPUTFOCUS) {
405 SDL_Surface *screen = SDL_GetVideoSurface();
406 if (screen && screen->flags & SDL_FULLSCREEN) {
407 if (event->active.gain) {
408 /* Gaining fullscreen focus:
409 * Wait for 1.5 seconds before reparenting to fullscreen.
411 queue_auto_reparent_to(True, 1500);
413 /* Losing fullscreen focus:
414 * Windowed mode window is always mapped; can reparent now.
416 auto_reparent_all_to(False);
422 return 1; // Unhandled event
425 HAA_Actor* HAA_CreateActor(Uint32 flags,
426 int width, int height, int bitsPerPixel)
428 HAA_ActorPriv *actor = malloc(sizeof(HAA_ActorPriv));
430 SDL_Error(SDL_ENOMEM);
434 /* Refresh the parent_window if needed. */
435 int res = HAA_SetVideoMode();
441 /* Default actor settings */
442 actor->p.position_x = 0;
443 actor->p.position_y = 0;
445 actor->p.visible = 0;
446 actor->p.opacity = 255;
447 actor->parent = parent_window;
448 actor->p.scale_x = 1 << 16;
449 actor->p.scale_y = 1 << 16;
450 actor->p.gravity = HAA_GRAVITY_NONE;
451 actor->p.anchor_x = 0;
452 actor->p.anchor_y = 0;
453 actor->p.x_rotation_angle = 0;
454 actor->p.x_rotation_y = 0;
455 actor->p.x_rotation_z = 0;
456 actor->p.y_rotation_angle = 0;
457 actor->p.y_rotation_x = 0;
458 actor->p.y_rotation_z = 0;
459 actor->p.z_rotation_angle = 0;
460 actor->p.z_rotation_x = 0;
461 actor->p.z_rotation_y = 0;
463 HAA_PENDING_POSITION | HAA_PENDING_SCALE | HAA_PENDING_PARENT;
465 /* Select the X11 visual */
466 int screen = DefaultScreen(display);
467 Window root = RootWindow(display, screen);
471 if (!XMatchVisualInfo(display, screen, bitsPerPixel, TrueColor, &vinfo)) {
472 /* Not matched; Use the default visual instead */
476 vinfo.screen = screen;
477 xvi = XGetVisualInfo(display, VisualScreenMask, &vinfo, &numVisuals);
483 actor->visual = vinfo.visual;
485 if (vinfo.visual != DefaultVisual(display, screen)) {
486 /* Allocate a private color map. */
487 actor->colormap = XCreateColormap(display, root,
488 vinfo.visual, AllocNone);
493 /* Create X11 window for actor */
494 XSetWindowAttributes attr;
495 unsigned long attrmask = CWBorderPixel | CWBackPixel | CWBitGravity;
496 attr.background_pixel = BlackPixel(display, screen);
497 attr.border_pixel = attr.background_pixel;
498 attr.bit_gravity = ForgetGravity;
499 if (actor->colormap) {
500 attr.colormap = actor->colormap;
501 attrmask |= CWColormap;
504 Window window = actor->window = XCreateWindow(display, root,
505 0, 0, width, height, 0, vinfo.depth,
506 InputOutput, vinfo.visual,
509 XStoreName(display, window, "sdl_haa window");
511 Atom atom = ATOM(_HILDON_WM_WINDOW_TYPE_ANIMATION_ACTOR);
512 XChangeProperty(display, window, ATOM(_NET_WM_WINDOW_TYPE),
513 XA_ATOM, 32, PropModeReplace,
514 (unsigned char *) &atom, 1);
516 /* Setup the X Image */
518 image = actor->image = XShmCreateImage(display, vinfo.visual,
519 vinfo.depth, ZPixmap, NULL, &actor->shminfo, width, height);
521 SDL_SetError("Cannot create XSHM image");
525 actor->shminfo.shmid = shmget(IPC_PRIVATE,
526 image->bytes_per_line * image->height, IPC_CREAT|0777);
527 if (actor->shminfo.shmid < 0) {
528 SDL_SetError("Failed to get shared memory");
529 XDestroyImage(image);
533 actor->shminfo.shmaddr = shmat(actor->shminfo.shmid, NULL, 0);
534 if (!actor->shminfo.shmaddr) {
535 SDL_SetError("Failed to attach shared memory");
536 XDestroyImage(image);
537 shmctl(actor->shminfo.shmid, IPC_RMID, 0);
541 actor->shminfo.readOnly = True;
542 if (!XShmAttach(display, &actor->shminfo)) {
543 SDL_SetError("Failed to attach shared memory image");
544 XDestroyImage(image);
545 shmdt(actor->shminfo.shmaddr);
546 shmctl(actor->shminfo.shmid, IPC_RMID, 0);
550 /* Ensure attachment is done */
551 XSync(display, False);
553 /* Nobody else needs it now */
554 shmctl(actor->shminfo.shmid, IPC_RMID, 0);
556 pixels = actor->shminfo.shmaddr;
557 image->data = (char*) pixels;
559 pixels = malloc(width * height * (vinfo.depth / 8));
561 SDL_SetError("Cannot allocate image");
564 image = actor->image = XCreateImage(display, vinfo.visual,
565 vinfo.depth, ZPixmap, 0, (char*) pixels, width, height, 8, 0);
567 SDL_SetError("Cannot create X image");
572 /* Guess alpha mask */
574 if (image->depth == 32) {
575 Amask = ~(vinfo.red_mask | vinfo.green_mask | vinfo.blue_mask);
579 GC gc = actor->gc = XCreateGC(display, window, 0, NULL);
580 XSetForeground(display, gc, 0xFFFFFFFFU);
582 /** Create SDL texture for actor */
583 actor->p.surface = SDL_CreateRGBSurfaceFrom(pixels,
584 image->width, image->height, image->depth, image->bytes_per_line,
585 vinfo.red_mask, vinfo.green_mask, vinfo.blue_mask, Amask);
587 if (!actor->p.surface) {
588 /* SDL Error already set */
593 XSelectInput(display, window, PropertyChangeMask);
594 XMapWindow(display, window);
596 /* Add to actor linked list */
598 assert(last == NULL);
599 actor->next = actor->prev = NULL;
600 first = last = actor;
608 XSync(display, False);
609 return (HAA_Actor*) actor;
612 XFreeGC(display, gc);
615 XShmDetach(display, &actor->shminfo);
616 XDestroyImage(image);
617 shmdt(actor->shminfo.shmaddr);
619 XDestroyImage(image);
622 XDestroyWindow(display, window);
623 if (actor->colormap) XFreeColormap(display, actor->colormap);
627 XSync(display, True);
631 void HAA_FreeActor(HAA_Actor* a)
633 HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
636 XFreeGC(display, actor->gc);
638 XShmDetach(display, &actor->shminfo);
639 XDestroyImage(actor->image);
640 shmdt(actor->shminfo.shmaddr);
641 XDestroyWindow(display, actor->window);
643 XDestroyImage(actor->image);
646 XFreeColormap(display, actor->colormap);
647 SDL_FreeSurface(actor->p.surface);
649 /* Remove actor from global linked list */
650 if (first == actor && last == actor) {
651 assert(!actor->next && !actor->prev);
654 } else if (last == actor) {
655 assert(!actor->next && actor->prev);
658 } else if (first == actor) {
659 assert(actor->next && !actor->prev);
663 assert(actor->next && actor->prev);
664 actor->prev->next = actor->next;
665 actor->next->prev = actor->prev;
673 int HAA_Commit(HAA_Actor* a)
675 HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
678 XSync(display, False);
683 int HAA_Flip(HAA_Actor* a)
685 HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
686 Window window = actor->window;
688 XImage *image = actor->image;
691 XShmPutImage(display, window, gc, image,
692 0, 0, 0, 0, image->width, image->height, False);
694 XPutImage(display, window, gc, image,
695 0, 0, 0, 0, image->width, image->height);
699 XSync(display, False);