[818/906] window: add send_message_async vmethod
[platform/upstream/gstreamer.git] / gst-libs / gst / gl / cocoa / gstglwindow_cocoa.m
1 /*
2  * GStreamer
3  * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it un der the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <Cocoa/Cocoa.h>
26
27 #include "gstgl_cocoa_private.h"
28
29 /* =============================================================*/
30 /*                                                              */
31 /*               GstGLNSWindow declaration                      */
32 /*                                                              */
33 /* =============================================================*/
34
35 @interface GstGLNSWindow: NSWindow {
36   BOOL m_isClosed;
37   GstGLWindowCocoa *m_cocoa;
38 }
39 - (id)initWithContentRect:(NSRect)contentRect
40     styleMask: (unsigned int) styleMask
41     backing: (NSBackingStoreType) bufferingType
42     defer: (BOOL) flag screen: (NSScreen *) aScreen
43     gstWin: (GstGLWindowCocoa *) window;
44 - (void) setClosed;
45 - (BOOL) isClosed;
46 - (BOOL) canBecomeMainWindow;
47 - (BOOL) canBecomeKeyWindow;
48 @end
49
50 /* =============================================================*/
51 /*                                                              */
52 /*                      GstGLWindow                             */
53 /*                                                              */
54 /* =============================================================*/
55
56 #ifndef GNUSTEP
57 static BOOL GSRegisterCurrentThread(void) { return TRUE; };
58 static void GSUnregisterCurrentThread(void) {};
59 #endif
60
61 #define GST_GL_WINDOW_COCOA_GET_PRIVATE(o)  \
62   (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_GL_TYPE_WINDOW_COCOA, GstGLWindowCocoaPrivate))
63
64 #define GST_CAT_DEFAULT gst_gl_window_cocoa_debug
65 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
66
67 #define DEBUG_INIT \
68   GST_DEBUG_CATEGORY_GET (GST_CAT_DEFAULT, "glwindow");
69 #define gst_gl_window_cocoa_parent_class parent_class
70 G_DEFINE_TYPE_WITH_CODE (GstGLWindowCocoa, gst_gl_window_cocoa, GST_GL_TYPE_WINDOW, DEBUG_INIT);
71
72 static guintptr gst_gl_window_cocoa_get_window_handle (GstGLWindow * window);
73 static void gst_gl_window_cocoa_set_window_handle (GstGLWindow * window,
74     guintptr handle);
75 static void gst_gl_window_cocoa_draw (GstGLWindow * window, guint width, guint height);
76 static void gst_gl_window_cocoa_run (GstGLWindow * window);
77 static void gst_gl_window_cocoa_quit (GstGLWindow * window);
78 static void gst_gl_window_cocoa_send_message_async (GstGLWindow * window,
79     GstGLWindowCB callback, gpointer data, GDestroyNotify destroy);
80
81 struct _GstGLWindowCocoaPrivate
82 {
83   GstGLNSWindow *internal_win_id;
84   gboolean visible;
85   NSWindow *parent;
86   NSThread *thread;
87   gboolean running;
88 };
89
90 static void
91 gst_gl_window_cocoa_class_init (GstGLWindowCocoaClass * klass)
92 {
93   GstGLWindowClass *window_class;
94   
95 #ifndef GNUSTEP
96   NSAutoreleasePool* pool = nil;
97 #endif
98
99   window_class = (GstGLWindowClass *) klass;
100
101   g_type_class_add_private (klass, sizeof (GstGLWindowCocoaPrivate));
102
103   window_class->get_window_handle =
104       GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_get_window_handle);
105   window_class->set_window_handle =
106       GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_set_window_handle);
107   window_class->draw_unlocked = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_draw);
108   window_class->draw = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_draw);
109   window_class->run = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_run);
110   window_class->quit = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_quit);
111   window_class->send_message_async =
112       GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_send_message_async);
113
114 #ifndef GNUSTEP
115   pool = [[NSAutoreleasePool alloc] init];
116   [NSApplication sharedApplication];
117
118   [pool release];
119 #endif
120 }
121
122 static void
123 gst_gl_window_cocoa_init (GstGLWindowCocoa * window)
124 {
125   window->priv = GST_GL_WINDOW_COCOA_GET_PRIVATE (window);
126
127   gst_gl_window_set_need_lock (GST_GL_WINDOW (window), FALSE);
128 }
129
130 /* Must be called in the gl thread */
131 GstGLWindowCocoa *
132 gst_gl_window_cocoa_new (void)
133 {
134   GstGLWindowCocoa *window = g_object_new (GST_GL_TYPE_WINDOW_COCOA, NULL);
135
136   return window;
137 }
138
139 gboolean
140 gst_gl_window_cocoa_create_window (GstGLWindowCocoa *window_cocoa)
141 {
142   GstGLWindow *window = GST_GL_WINDOW (window_cocoa);
143   GstGLContext *context = gst_gl_window_get_context (window);
144   GstGLContextCocoa *context_cocoa = GST_GL_CONTEXT_COCOA (context);
145   GstGLWindowCocoaPrivate *priv = window_cocoa->priv;
146   NSRect rect = context_cocoa->priv->rect;
147
148   priv->internal_win_id =[[GstGLNSWindow alloc] initWithContentRect:rect styleMask: 
149     (NSTitledWindowMask | NSClosableWindowMask |
150     NSResizableWindowMask | NSMiniaturizableWindowMask)
151     backing: NSBackingStoreBuffered defer: NO screen: nil gstWin: window_cocoa];
152
153   GST_DEBUG ("NSWindow id: %lud\n", (guintptr) priv->internal_win_id);
154
155   priv->thread = [NSThread currentThread];
156
157   [NSApp setDelegate: priv->internal_win_id];
158
159   gst_object_unref (context);
160
161   return TRUE;
162 }
163
164 static guintptr
165 gst_gl_window_cocoa_get_window_handle (GstGLWindow *window)
166 {
167   return (guintptr) GST_GL_WINDOW_COCOA (window)->priv->internal_win_id;
168 }
169
170 static void
171 gst_gl_window_cocoa_set_window_handle (GstGLWindow * window, guintptr handle)
172 {
173   GstGLWindowCocoa *window_cocoa;
174   GstGLWindowCocoaPrivate *priv;
175
176   window_cocoa = GST_GL_WINDOW_COCOA (window);
177   priv = window_cocoa->priv;
178
179   priv->parent = (NSWindow*) handle;
180   if (priv->internal_win_id) {
181     GstGLContextCocoa *context = (GstGLContextCocoa *) gst_gl_window_get_context (window);
182     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
183     AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc] init:window_cocoa];
184
185
186     GSRegisterCurrentThread();
187
188     if (context) {
189       g_source_remove (context->priv->source_id);
190       gst_object_unref (context);
191     }
192
193     [app_thread_performer performSelectorOnMainThread:@selector(setWindow) 
194         withObject:0 waitUntilDone:YES];
195
196     [pool release];
197   }
198 }
199
200 /* Thread safe */
201 static void
202 gst_gl_window_cocoa_draw (GstGLWindow * window, guint width, guint height)
203 {
204   GstGLWindowCocoa *window_cocoa;
205   GstGLWindowCocoaPrivate *priv;
206   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
207   AppThreadPerformer* app_thread_performer;
208
209   window_cocoa = GST_GL_WINDOW_COCOA (window);
210   priv = window_cocoa->priv;
211
212   GSRegisterCurrentThread();
213
214   app_thread_performer = [[AppThreadPerformer alloc] init:window_cocoa];
215   [app_thread_performer performSelector:@selector(updateWindow) 
216       onThread:priv->thread withObject:nil waitUntilDone:YES];
217
218   if (!priv->parent && !priv->visible) {
219     static gint x = 0;
220     static gint y = 0;
221
222     NSRect mainRect = [[NSScreen mainScreen] visibleFrame];
223     NSRect windowRect = [priv->internal_win_id frame];
224
225     GST_DEBUG ("main screen rect: %d %d %d %d\n", (int) mainRect.origin.x,
226         (int) mainRect.origin.y, (int) mainRect.size.width,
227         (int) mainRect.size.height);
228
229     windowRect.origin.x += x;
230     windowRect.origin.y += mainRect.size.height > y ? (mainRect.size.height - y) * 0.5 : y;
231     windowRect.size.width = width;
232     windowRect.size.height = height;
233
234     GST_DEBUG ("window rect: %d %d %d %d\n", (int) windowRect.origin.x,
235         (int) windowRect.origin.y, (int) windowRect.size.width,
236         (int) windowRect.size.height);
237
238     x += 20;
239     y += 20;
240
241 #ifndef GNUSTEP
242     [priv->internal_win_id setFrame:windowRect display:NO];
243     GST_DEBUG ("make the window available\n");
244     [priv->internal_win_id makeMainWindow];
245 #endif
246     [app_thread_performer performSelector:@selector(orderFront)
247         onThread:priv->thread withObject:nil waitUntilDone:YES];
248
249     /*[priv->internal_win_id setViewsNeedDisplay:YES]; */
250     priv->visible = TRUE;
251   }
252
253   [pool release];
254 }
255
256 static void
257 gst_gl_window_cocoa_run (GstGLWindow * window)
258 {
259   GstGLWindowCocoa *window_cocoa;
260   GstGLWindowCocoaPrivate *priv;
261   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
262   NSRunLoop *run_loop = [NSRunLoop currentRunLoop];
263
264   window_cocoa = GST_GL_WINDOW_COCOA (window);
265   priv = window_cocoa->priv;
266
267   [run_loop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
268
269   GST_DEBUG ("begin loop\n");
270
271   if (priv->internal_win_id != nil) {
272     priv->running = TRUE;
273     while (priv->running)
274       [run_loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
275
276     [priv->internal_win_id release];
277     priv->internal_win_id = nil;
278   }
279
280   [pool release];
281
282   GST_DEBUG ("end loop\n");
283 }
284
285 /* Thread safe */
286 static void
287 gst_gl_window_cocoa_quit (GstGLWindow * window)
288 {
289   GstGLWindowCocoa *window_cocoa;
290   GstGLWindowCocoaPrivate *priv;
291
292   window_cocoa = GST_GL_WINDOW_COCOA (window);
293   priv = window_cocoa->priv;
294
295   if (window) {
296     if (GSRegisterCurrentThread() || 1) {
297       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
298       
299       AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc]
300           initWithAll:window_cocoa callback:NULL userData:NULL];
301       [app_thread_performer performSelector:@selector(stopApp)
302           onThread:priv->thread withObject:nil waitUntilDone:YES];
303
304       [pool release];
305
306       GSUnregisterCurrentThread();
307     }
308     else
309       GST_DEBUG ("failed to register current thread, application thread is lost\n");
310   }
311 }
312
313 /* Thread safe */
314 static void
315 gst_gl_window_cocoa_send_message_async (GstGLWindow * window,
316     GstGLWindowCB callback, gpointer data, GDestroyNotify destroy)
317 {
318   GstGLWindowCocoa *window_cocoa;
319   GstGLWindowCocoaPrivate *priv;
320
321   window_cocoa = GST_GL_WINDOW_COCOA (window);
322   priv = window_cocoa->priv;
323
324   GSRegisterCurrentThread ();
325
326   if (window) {
327     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
328
329     AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc]
330         initWithAll:window_cocoa callback:callback userData:data];
331
332     [app_thread_performer performSelector:@selector(sendToApp) onThread:priv->thread
333         withObject:nil waitUntilDone:NO];
334
335     [pool release];
336   }
337 }
338
339 /* =============================================================*/
340 /*                                                              */
341 /*                    GstGLNSWindow implementation              */
342 /*                                                              */
343 /* =============================================================*/
344
345 @implementation GstGLNSWindow
346
347 - (id) initWithContentRect: (NSRect) contentRect
348         styleMask: (unsigned int) styleMask
349     backing: (NSBackingStoreType) bufferingType
350     defer: (BOOL) flag screen: (NSScreen *) aScreen
351     gstWin: (GstGLWindowCocoa *) cocoa {
352
353   m_isClosed = NO;
354   m_cocoa = cocoa;
355
356   self = [super initWithContentRect: contentRect
357         styleMask: styleMask backing: bufferingType
358         defer: flag screen:aScreen];
359
360   [self setReleasedWhenClosed:NO];
361
362   GST_DEBUG ("initializing GstGLNSWindow\n");
363
364   [self setTitle:@"OpenGL renderer"];
365
366   [self setBackgroundColor:[NSColor clearColor]];
367
368   [self orderOut:m_cocoa->priv->internal_win_id];
369
370   if (m_cocoa->priv->parent) {
371     NSWindow *window = m_cocoa->priv->parent;
372     [window setContentView: [m_cocoa->priv->internal_win_id contentView]];
373   }
374
375   return self;
376 }
377
378 - (void) setClosed {
379   m_isClosed = YES;
380 }
381
382 - (BOOL) isClosed {
383   return m_isClosed;
384 }
385
386 - (BOOL) canBecomeMainWindow {
387   return YES;
388 }
389
390 - (BOOL) canBecomeKeyWindow {
391   return YES;
392 }
393
394 /* Called in the main thread which is never the gl thread */
395 - (BOOL) windowShouldClose:(id)sender {
396     
397   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
398   AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc] 
399     init:m_cocoa];
400     
401   GST_DEBUG ("user clicked the close button\n");
402   
403   [app_thread_performer performSelector:@selector(closeWindow) onThread:m_cocoa->priv->thread
404     withObject:nil waitUntilDone:YES];
405   
406   [pool release];
407   
408   return YES;
409 }
410
411 - (void) applicationDidFinishLaunching: (NSNotification *) not {
412 }
413
414 - (void) applicationWillFinishLaunching: (NSNotification *) not {
415 }
416
417 - (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
418   /* the application is manually stopped by calling stopApp on the AppThreadPerformer */
419   return NO;
420 }
421
422 - (void) applicationWillTerminate:(NSNotification *)aNotification {
423 #ifdef GNUSTEP
424   /* fixes segfault with gst-launch-1.0 -e ... and sending SIGINT (Ctrl-C)
425    * which causes GNUstep to run a signal handler in the main thread.
426    * However that thread has never been 'registered' with GNUstep so
427    * the autorelease magic of objective-c causes a segfault from accessing
428    * a null NSThread object somewhere deep in GNUstep.
429    *
430    * I put it here because this is the first time we can register the thread.
431    */
432   GSRegisterCurrentThread();
433 #endif
434 }
435
436 @end
437
438
439 /* =============================================================*/
440 /*                                                              */
441 /*                GstGLNSOpenGLView implementation              */
442 /*                                                              */
443 /* =============================================================*/
444
445 @implementation GstGLNSOpenGLView
446
447 - (id)initWithFrame:(GstGLWindowCocoa *)window rect:(NSRect)contentRect pixelFormat:(NSOpenGLPixelFormat *)fmt {
448
449   self = [super initWithFrame: contentRect pixelFormat: fmt];
450
451   m_cocoa = window;
452   m_resizeCount = 0;
453
454 #ifndef GNUSTEP
455   [self setWantsLayer:NO];
456 #endif
457
458   return self;
459 }
460
461 - (void)reshape {
462   GstGLWindow *window;
463
464   window = GST_GL_WINDOW (m_cocoa);
465
466   if (m_resizeCount % 5 == 0) {
467     m_resizeCount = 0;
468     if (window->resize) {
469
470       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
471       NSRect bounds = [self bounds];
472       AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc]
473         initWithSize:m_cocoa callback:window->resize userData:window->resize_data 
474         toSize:bounds.size];
475
476       [app_thread_performer performSelector:@selector(resizeWindow) onThread:m_cocoa->priv->thread 
477         withObject:nil waitUntilDone:YES];
478
479       [pool release];
480     }
481   }
482   m_resizeCount++;
483 }
484
485 - (void) update {
486 }
487
488 @end
489
490 /* =============================================================*/
491 /*                                                              */
492 /*               AppThreadPerformer implementation              */
493 /*                                                              */
494 /* =============================================================*/
495
496 @implementation AppThreadPerformer
497
498 - (id) init: (GstGLWindowCocoa *) window {
499   m_cocoa = window;
500   m_callback = NULL;
501   m_callback2 = NULL;
502   m_data = NULL;
503   m_width = 0;
504   m_height = 0;
505   return self;
506 }
507
508 - (id) initWithCallback:(GstGLWindowCocoa *)window callback:(GstGLWindowCB)callback userData:(gpointer)data {
509   m_cocoa = window;
510   m_callback = callback;
511   m_callback2 = NULL;
512   m_data = data;
513   m_width = 0;
514   m_height = 0;
515   return self;
516 }
517
518 - (id) initWithSize: (GstGLWindowCocoa *) window
519     callback:(GstGLWindowResizeCB)callback userData:(gpointer)data
520   toSize:(NSSize)size {
521   m_cocoa = window;
522   m_callback = NULL;
523   m_callback2 = callback;
524   m_data = data;
525   m_width = size.width;
526   m_height = size.height;
527   return self;
528 }
529
530 - (id) initWithAll: (GstGLWindowCocoa *) window
531     callback:(GstGLWindowCB) callback userData: (gpointer) data {
532   m_cocoa = window;
533   m_callback = callback;
534   m_callback2 = NULL;
535   m_data = data;
536   m_width = 0;
537   m_height = 0;
538   return self;
539 }
540
541 - (void) updateWindow {
542   if (m_cocoa->priv->running) {
543
544     if (![m_cocoa->priv->internal_win_id isClosed]) {
545       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
546
547       /* draw opengl scene in the back buffer */
548       GST_GL_WINDOW (m_cocoa)->draw (GST_GL_WINDOW (m_cocoa)->draw_data);
549       /* Copy the back buffer to the front buffer */
550       [[[m_cocoa->priv->internal_win_id contentView] openGLContext] flushBuffer];
551
552       [pool release];
553     }
554   }
555 }
556
557 - (void) resizeWindow {
558   if (m_cocoa->priv->running && ![m_cocoa->priv->internal_win_id isClosed]) {
559     m_callback2 (m_data, m_width, m_height);
560     [[[m_cocoa->priv->internal_win_id contentView] openGLContext] update];
561       GST_GL_WINDOW (m_cocoa)->draw (GST_GL_WINDOW (m_cocoa)->draw_data);
562     [[[m_cocoa->priv->internal_win_id contentView] openGLContext] flushBuffer];
563   }
564 }
565
566 - (void) sendToApp {
567   if (m_callback)
568     m_callback (m_data);
569 }
570
571 - (void) setWindow {
572   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
573   NSWindow *window = m_cocoa->priv->parent;
574   
575   [m_cocoa->priv->internal_win_id orderOut:m_cocoa->priv->internal_win_id];
576   
577   [window setContentView: [m_cocoa->priv->internal_win_id contentView]];
578
579   [pool release];
580 }
581
582 - (void) stopApp {
583 #ifdef GNUSTEP
584   NSAutoreleasePool *pool = nil;
585 #endif
586
587   m_cocoa->priv->running = FALSE;
588   if (m_callback)
589     m_callback (m_data);
590
591 #ifdef GNUSTEP
592   pool = [[NSAutoreleasePool alloc] init];
593   if ([NSApp isRunning])
594     [NSApp stop:self];
595   [pool release];
596 #endif
597 }
598
599 - (void) closeWindow {
600   GstGLWindow *window;
601
602   window = GST_GL_WINDOW (m_cocoa);
603
604   [m_cocoa->priv->internal_win_id setClosed];
605   if (window->close) {
606     window->close (window->close_data);
607   }
608 }
609
610 - (void) orderFront {
611   [m_cocoa->priv->internal_win_id orderFront:m_cocoa->priv->internal_win_id];
612 }
613
614 @end