325988432413f3808643474b5d7c060d1eecda1e
[sdlhildon] / sdlhaa / src / SDL_haa.c
1 /* This file is part of SDL_haa - SDL addon for Hildon Animation Actors
2  * Copyright (C) 2010 Javier S. Pedro
3  *
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.
8  *
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.
13  *
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/>.
18  */
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <assert.h>
24
25 #include <X11/Xlib.h>
26 #include <X11/Xutil.h>
27 #include <X11/Xatom.h>
28 #include <SDL.h>
29 #include <SDL_syswm.h>
30
31 #ifdef HAVE_XSHM
32 #include <X11/extensions/XShm.h>
33 #include <sys/ipc.h>
34 #include <sys/shm.h>
35 #endif
36
37 #include "SDL_haa.h"
38 #include "atoms.inc"
39
40 typedef struct HAA_ActorPriv {
41         HAA_Actor p;
42
43         Window window, parent;
44         Visual *visual;
45         Colormap colormap;
46         XImage *image;
47         GC gc;
48 #ifdef HAVE_XSHM
49         XShmSegmentInfo shminfo;
50 #endif
51         unsigned char ready;
52         struct HAA_ActorPriv *prev, *next;
53 } HAA_ActorPriv;
54
55 static Display *display;
56 static Window parent_window;
57 static HAA_ActorPriv *first = NULL, *last = NULL;
58
59 /* Queued reparents. */
60 static Uint32 queued_reparent_time;
61 static Bool queued_reparent_fs;
62
63 #ifdef HAVE_XSHM
64 static int shm_major, shm_minor;
65 static Bool shm_pixmaps;
66 static Bool have_shm;
67 #else
68 static const Bool have_shm = False;
69 #endif
70
71 int HAA_Init(Uint32 flags)
72 {
73         SDL_SysWMinfo info;
74
75         SDL_VERSION(&info.version);
76         if (SDL_GetWMInfo(&info) != 1) {
77                 SDL_SetError("SDL_haa is incompatible with this SDL version");
78                 return -1;
79         }
80
81         display = info.info.x11.display;
82         parent_window = 0;
83         queued_reparent_time = 0;
84         first = last = NULL;
85
86         XInternAtoms(display, (char**)atom_names, ATOM_COUNT, True, atom_values);
87
88 #ifdef HAVE_XSHM
89         have_shm = XShmQueryVersion(display, &shm_major, &shm_minor, &shm_pixmaps);
90 #endif
91
92         /* This might add some noise to your event queue, but we need them. */
93         SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
94
95         return 0;
96 }
97
98 void HAA_Quit()
99 {
100         /* Nothing to do for now */
101 }
102
103 static HAA_ActorPriv* find_actor_for_window(Window w)
104 {
105         HAA_ActorPriv* a;
106
107         for (a = first; a; a = a->next) {
108                 if (a->window == w) {
109                         return a;
110                 }
111         }
112
113         return NULL;
114 }
115
116 static void actor_send_message(HAA_ActorPriv* actor, Atom message_type,
117                 Uint32 l0, Uint32 l1, Uint32 l2, Uint32 l3, Uint32 l4)
118 {
119         Window window = actor->window;
120         XEvent event = { 0 };
121
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;
131
132         XSendEvent(display, window, True,
133                 StructureNotifyMask,
134                 (XEvent *)&event);
135 }
136
137 static void reparent_all_to(Window new_parent)
138 {
139         HAA_ActorPriv* a;
140         /* video mode has changed */
141         parent_window = new_parent;
142
143         /* if we don't have any actors, no need to reparent them */
144         if (first == NULL) {
145                 assert(last == NULL);
146                 return;
147         }
148
149         /* unmap all actors that were already ready */
150         for (a = first; a; a = a->next) {
151                 a->parent = parent_window;
152                 if (a->ready) {
153                         XUnmapWindow(display, a->window);
154                 }
155         }
156
157         XSync(display, False);
158
159         /* now remap and reconfigure all actors */
160         for (a = first; a; a = a->next) {
161                 if (a->ready) {
162                         XMapWindow(display, a->window);
163                         a->ready = 0;
164                         a->p.pending |= HAA_PENDING_EVERYTHING;
165                 } else {
166                         a->p.pending |= HAA_PENDING_PARENT | HAA_PENDING_SHOW;
167                 }
168         }
169
170         XFlush(display);
171 }
172
173 static int auto_reparent_all_to(Bool fullscreen)
174 {
175         SDL_SysWMinfo info;
176         Window new_parent;
177         XWindowAttributes attr;
178         Bool is_mapped;
179         int res;
180
181         SDL_VERSION(&info.version);
182         res = SDL_GetWMInfo(&info);
183         assert(res == 1);
184
185         /* Delete any pending reparent */
186         queued_reparent_time = 0;
187
188         if (fullscreen) {
189                 new_parent = info.info.x11.fswindow;
190         } else {
191                 new_parent = info.info.x11.wmwindow;
192         }
193
194         XGetWindowAttributes(display, new_parent, &attr);
195         is_mapped = attr.map_state == IsViewable;
196
197         /* Do we really need to reparent? */
198         if (new_parent != parent_window) {
199                 /* Yes, we do. */
200                 if (is_mapped) {
201                         reparent_all_to(new_parent);
202                         return 0;
203                 } else {
204                         return 1;
205                 }
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
211         } else {
212                 /* We don't need to reparent and everything is OK. */
213                 return 0;
214         }
215 }
216
217 static void handle_queued_reparent()
218 {
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);
225                         if (res != 0) {
226                                 /* Failed to reparent? Try again in 200 ms. */
227                                 queued_reparent_time = now + 200;
228                         }
229                 }
230         }
231 }
232
233 static void queue_auto_reparent_to(Bool fullscreen, Uint32 delay)
234 {
235         queued_reparent_time = SDL_GetTicks() + delay;
236         queued_reparent_fs = fullscreen;
237 }
238
239 int HAA_SetVideoMode()
240 {
241         SDL_Surface *screen = SDL_GetVideoSurface();
242
243         if (!screen) {
244                 SDL_SetError("Failed to get current video surface");
245                 return 1;
246         }
247
248         auto_reparent_all_to(screen->flags & SDL_FULLSCREEN ? True : False);
249
250         return 0;
251 }
252
253 static void HAA_Pending(HAA_ActorPriv* actor)
254 {
255         const Uint16 pending = actor->p.pending;
256
257         if (!actor->ready) return; //Enqueue and wait
258
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);
263         }
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);
268         }
269
270         if (pending & HAA_PENDING_ROTATION_X) {
271                  actor_send_message(actor,
272                         ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
273                         HAA_X_AXIS,
274                         actor->p.x_rotation_angle,
275                         0, actor->p.x_rotation_y, actor->p.x_rotation_z);
276         }
277         if (pending & HAA_PENDING_ROTATION_Y) {
278                  actor_send_message(actor,
279                         ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
280                         HAA_Y_AXIS,
281                         actor->p.y_rotation_angle,
282                         actor->p.y_rotation_x, 0, actor->p.y_rotation_z);
283         }
284         if (pending & HAA_PENDING_ROTATION_Z) {
285                  actor_send_message(actor,
286                         ATOM(_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION),
287                         HAA_Z_AXIS,
288                         actor->p.z_rotation_angle,
289                         actor->p.z_rotation_x, actor->p.z_rotation_y, 0);
290         }
291
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);
296         }
297
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);
302         }
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);
307         }
308
309         actor->p.pending = HAA_PENDING_NOTHING;
310 }
311
312 /** Push a SDL_VIDEOEXPOSE event to the queue */
313 static void sdl_expose()
314 {
315         SDL_Event events[32];
316
317         /* Pull out all old refresh events */
318         SDL_PeepEvents(events, sizeof(events)/sizeof(events[0]),
319                 SDL_GETEVENT, SDL_VIDEOEXPOSEMASK);
320
321         /* Post the event, if desired */
322         if ( SDL_EventState(SDL_VIDEOEXPOSE, SDL_QUERY) == SDL_ENABLE ) {
323                 SDL_Event event;
324                 event.type = SDL_VIDEOEXPOSE;
325                 SDL_PushEvent(&event);
326         }
327 }
328
329 /** Called when the client ready notification is received. */
330 static void actor_update_ready(HAA_ActorPriv* actor)
331 {
332         Window window = actor->window;
333         int status;
334         Atom actual_type;
335         int actual_format;
336         unsigned long nitems, bytes_after;
337         unsigned char *prop = NULL;
338         
339         status = XGetWindowProperty(display, window,
340                 ATOM(_HILDON_ANIMATION_CLIENT_READY), 0, 32,
341                 False, XA_ATOM,
342                 &actual_type, &actual_format,
343                 &nitems, &bytes_after, &prop);
344
345         if (prop) {
346                 XFree(prop);
347         }
348
349         if (status != Success || actual_type != XA_ATOM ||
350                         actual_format != 32 || nitems != 1)  {
351                 actor->ready = 0;
352                 return;
353         }
354
355         if (actor->ready) {
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. */
362
363                 /* Next Flip will resend every setting */
364                 actor->p.pending = HAA_PENDING_EVERYTHING;
365                 return;
366         }
367
368         actor->ready = 1;
369
370         /* Send all pending messages now */
371         HAA_Pending(actor);
372
373         /* Force a redraw */
374         sdl_expose();
375 }
376
377 int HAA_FilterEvent(const SDL_Event *event)
378 {
379         handle_queued_reparent();
380
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);
387                                 if (actor) {
388                                         actor_update_ready(actor);
389                                         return 0; // Handled
390                                 }
391                         }
392                 }
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
402                  * it is unmapped.
403                  */
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.
410                                          */
411                                         queue_auto_reparent_to(True, 1500);
412                                 } else {
413                                         /* Losing fullscreen focus:
414                                          * Windowed mode window is always mapped; can reparent now.
415                                          */
416                                         auto_reparent_all_to(False);
417                                 }
418                         }
419                 }
420         }
421
422         return 1; // Unhandled event
423 }
424
425 HAA_Actor* HAA_CreateActor(Uint32 flags,
426         int width, int height, int bitsPerPixel)
427 {
428         HAA_ActorPriv *actor = malloc(sizeof(HAA_ActorPriv));
429         if (!actor) {
430                 SDL_Error(SDL_ENOMEM);
431                 return NULL;
432         }
433
434         /* Refresh the parent_window if needed. */
435         int res = HAA_SetVideoMode();
436         if (res != 0) {
437                 goto cleanup_actor;
438                 return NULL;
439         }
440
441         /* Default actor settings */
442         actor->p.position_x = 0;
443         actor->p.position_y = 0;
444         actor->p.depth = 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;
462         actor->p.pending =
463                 HAA_PENDING_POSITION | HAA_PENDING_SCALE | HAA_PENDING_PARENT;
464
465         /* Select the X11 visual */
466         int screen = DefaultScreen(display);
467         Window root = RootWindow(display, screen);
468         XVisualInfo vinfo;
469         XImage *image;
470         void* pixels = NULL;
471         if (!XMatchVisualInfo(display, screen, bitsPerPixel, TrueColor, &vinfo)) {
472                 /* Not matched; Use the default visual instead */
473                 int numVisuals;
474                 XVisualInfo *xvi;
475
476                 vinfo.screen = screen;
477                 xvi = XGetVisualInfo(display, VisualScreenMask, &vinfo, &numVisuals);
478                 assert(xvi);
479
480                 vinfo = *xvi;
481                 XFree(xvi);
482         }
483         actor->visual = vinfo.visual;
484         
485         if (vinfo.visual != DefaultVisual(display, screen)) {
486                 /* Allocate a private color map. */
487                 actor->colormap = XCreateColormap(display, root, 
488                         vinfo.visual, AllocNone);
489         } else {
490                 actor->colormap = 0;
491         }
492
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;
502         }
503
504         Window window = actor->window = XCreateWindow(display, root,
505                 0, 0, width, height, 0, vinfo.depth,
506                 InputOutput, vinfo.visual,
507                 attrmask, &attr);
508
509         XStoreName(display, window, "sdl_haa window");
510
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);
515
516         /* Setup the X Image */
517         if (have_shm) {
518                 image = actor->image = XShmCreateImage(display, vinfo.visual,
519                         vinfo.depth, ZPixmap, NULL, &actor->shminfo, width, height);
520                 if (!image) {
521                         SDL_SetError("Cannot create XSHM image");
522                         goto cleanup_window;
523                 }
524
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);
530                         goto cleanup_window;
531                 }
532
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);
538                         goto cleanup_window;
539                 }
540
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);
547                         goto cleanup_window;
548                 }
549
550                 /* Ensure attachment is done */
551                 XSync(display, False);
552
553                 /* Nobody else needs it now */
554                 shmctl(actor->shminfo.shmid, IPC_RMID, 0);
555
556                 pixels = actor->shminfo.shmaddr;
557                 image->data = (char*) pixels;
558         } else {
559                 pixels = malloc(width * height * (vinfo.depth / 8));
560                 if (!pixels) {
561                         SDL_SetError("Cannot allocate image");
562                         goto cleanup_window;
563                 }
564                 image = actor->image = XCreateImage(display, vinfo.visual,
565                         vinfo.depth, ZPixmap, 0, (char*) pixels, width, height, 8, 0);
566                 if (!image) {
567                         SDL_SetError("Cannot create X image");
568                         goto cleanup_window;
569                 }
570         }
571
572         /* Guess alpha mask */
573         Uint32 Amask = 0;
574         if (image->depth == 32) {
575                 Amask = ~(vinfo.red_mask | vinfo.green_mask | vinfo.blue_mask);
576         }
577
578         /* Create GC */
579         GC gc = actor->gc = XCreateGC(display, window, 0, NULL);
580         XSetForeground(display, gc, 0xFFFFFFFFU);
581
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);
586
587         if (!actor->p.surface) {
588                 /* SDL Error already set */
589                 goto cleanup_gc;
590         }
591
592         /* Map X11 window */
593         XSelectInput(display, window, PropertyChangeMask);
594         XMapWindow(display, window);
595
596         /* Add to actor linked list */
597         if (first == NULL) {
598                 assert(last == NULL);
599                 actor->next = actor->prev = NULL;
600                 first = last = actor;
601         } else {
602                 last->next = actor;
603                 actor->prev = last;
604                 actor->next = NULL;
605                 last = actor;
606         }
607
608         XSync(display, False);
609         return (HAA_Actor*) actor;
610
611 cleanup_gc:
612         XFreeGC(display, gc);
613 cleanup_image:
614         if (have_shm) {
615                 XShmDetach(display, &actor->shminfo);
616                 XDestroyImage(image);
617                 shmdt(actor->shminfo.shmaddr);
618         } else {
619                 XDestroyImage(image);
620         }
621 cleanup_window:
622         XDestroyWindow(display, window);
623         if (actor->colormap) XFreeColormap(display, actor->colormap);
624 cleanup_actor:
625         free(actor);
626
627         XSync(display, True);
628         return NULL;
629 }
630         
631 void HAA_FreeActor(HAA_Actor* a)
632 {
633         HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
634         if (!a) return;
635
636         XFreeGC(display, actor->gc);
637         if (have_shm) {
638                 XShmDetach(display, &actor->shminfo);
639                 XDestroyImage(actor->image);
640                 shmdt(actor->shminfo.shmaddr);
641                 XDestroyWindow(display, actor->window);
642         } else {
643                 XDestroyImage(actor->image);
644         }
645         if (actor->colormap)
646                 XFreeColormap(display, actor->colormap);
647         SDL_FreeSurface(actor->p.surface);
648
649         /* Remove actor from global linked list */
650         if (first == actor && last == actor) {
651                 assert(!actor->next && !actor->prev);
652                 first = NULL;
653                 last = NULL;
654         } else if (last == actor) {
655                 assert(!actor->next && actor->prev);
656                 last = actor->prev;
657                 last->next = NULL;
658         } else if (first == actor) {
659                 assert(actor->next && !actor->prev);
660                 first = actor->next;
661                 first->prev = NULL;
662         } else {
663                 assert(actor->next && actor->prev);
664                 actor->prev->next = actor->next;
665                 actor->next->prev = actor->prev;
666         }
667
668         free(actor);
669
670         XFlush(display);
671 }
672
673 int HAA_Commit(HAA_Actor* a)
674 {
675         HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
676
677         HAA_Pending(actor);
678         XSync(display, False);
679
680         return 0;
681 }
682
683 int HAA_Flip(HAA_Actor* a)
684 {
685         HAA_ActorPriv* actor = (HAA_ActorPriv*)a;
686         Window window = actor->window;
687         GC gc = actor->gc;
688         XImage *image = actor->image;
689
690         if (have_shm) {
691                 XShmPutImage(display, window, gc, image, 
692                                                 0, 0, 0, 0, image->width, image->height, False);
693         } else {
694                 XPutImage(display, window, gc, image, 
695                                         0, 0, 0, 0, image->width, image->height);
696         }
697
698         HAA_Pending(actor);
699         XSync(display, False);
700
701         return 0;
702 }
703