2 * Copyright (C) 2004 Zaheer Abbas Merali <zaheerabbas at merali dot org>
3 * Copyright (C) 2007 Pioneers of the Inevitable <songbird@songbirdnest.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under 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.
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.
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.
20 * The development of this code was made possible due to the involvement of Pioneers
21 * of the Inevitable, the creators of the Songbird Music player
25 /* inspiration gained from looking at source of osx video out of xine and vlc
26 * and is reflected in the code
30 #include <Cocoa/Cocoa.h>
32 #import "cocoawindow.h"
33 #import "osxvideosink.h"
35 #include <OpenGL/OpenGL.h>
36 #include <OpenGL/gl.h>
37 #include <OpenGL/glext.h>
39 #include <Carbon/Carbon.h>
41 /* Debugging category */
42 #include <gst/gstinfo.h>
44 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 1040
45 #define kVK_ANSI_Keypad0 0x52
46 #define kVK_ANSI_Keypad1 0x53
47 #define kVK_ANSI_Keypad2 0x54
48 #define kVK_ANSI_Keypad3 0x55
49 #define kVK_ANSI_Keypad4 0x56
50 #define kVK_ANSI_Keypad5 0x57
51 #define kVK_ANSI_Keypad6 0x58
52 #define kVK_ANSI_Keypad7 0x59
53 #define kVK_ANSI_Keypad8 0x5b
54 #define kVK_ANSI_Keypad9 0x5c
55 #define kVK_ANSI_KeypadDecimal 0x41
56 #define kVK_ANSI_KeypadDivide 0x4b
57 #define kVK_ANSI_KeypadEnter 0x4c
58 #define kVK_ANSI_KeypadMinus 0x4e
59 #define kVK_ANSI_KeypadMultiply 0x43
60 #define kVK_ANSI_KeypadPlus 0x45
61 #define kVK_Control 0x3b
62 #define kVK_Delete 0x33
63 #define kVK_DownArrow 0x7d
65 #define kVK_Escape 0x35
78 #define kVK_ForwardDelete 0x75
81 #define kVK_LeftArrow 0x7b
82 #define kVK_Option 0x3a
83 #define kVK_PageDown 0x79
84 #define kVK_PageUp 0x74
85 #define kVK_Return 0x24
86 #define kVK_RightArrow 0x7c
87 #define kVK_Shift 0x38
89 #define kVK_UpArrow 0x7e
90 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED <= 1040 */
93 const gchar* gst_keycode_to_keyname(gint16 keycode)
155 case kVK_ANSI_RightBracket:
156 return "bracketright";
161 case kVK_ANSI_LeftBracket:
162 return "bracketleft";
175 case kVK_ANSI_Semicolon:
177 case kVK_ANSI_Backslash:
187 case kVK_ANSI_Period:
191 case kVK_ANSI_KeypadDecimal:
193 case kVK_ANSI_KeypadMultiply:
194 return "KP_Multiply";
195 case kVK_ANSI_KeypadPlus:
197 case kVK_ANSI_KeypadClear:
199 case kVK_ANSI_KeypadDivide:
201 case kVK_ANSI_KeypadEnter:
203 case kVK_ANSI_KeypadMinus:
204 return "KP_Subtract";
205 case kVK_ANSI_KeypadEquals:
207 case kVK_ANSI_Keypad0:
209 case kVK_ANSI_Keypad1:
211 case kVK_ANSI_Keypad2:
213 case kVK_ANSI_Keypad3:
215 case kVK_ANSI_Keypad4:
217 case kVK_ANSI_Keypad5:
219 case kVK_ANSI_Keypad6:
221 case kVK_ANSI_Keypad7:
223 case kVK_ANSI_Keypad8:
225 case kVK_ANSI_Keypad9:
228 /* keycodes for keys that are independent of keyboard layout*/
252 case kVK_RightOption:
254 case kVK_RightControl:
304 case kVK_ForwardDelete:
329 @ implementation GstOSXVideoSinkWindow
331 /* The object has to be released */
332 - (id) initWithContentNSRect: (NSRect) rect
333 styleMask: (unsigned int) styleMask
334 backing: (NSBackingStoreType) bufferingType
336 screen:(NSScreen *) aScreen
338 self = [super initWithContentRect: rect
340 backing: bufferingType
344 GST_DEBUG ("Initializing GstOSXvideoSinkWindow");
346 gstview = [[GstGLView alloc] initWithFrame:rect];
349 [self setContentView:gstview];
350 [self setTitle:@"GStreamer Video Output"];
355 - (void) setContentSize:(NSSize) size {
357 height = size.height;
359 [super setContentSize:size];
362 - (GstGLView *) gstView {
366 - (void) awakeFromNib {
367 [self setAcceptsMouseMovedEvents:YES];
374 // OpenGL implementation
377 @ implementation GstGLView
379 - (id) initWithFrame:(NSRect) frame {
380 NSOpenGLPixelFormat *fmt;
381 NSOpenGLPixelFormatAttribute attribs[] = {
382 NSOpenGLPFAAccelerated,
383 NSOpenGLPFANoRecovery,
384 NSOpenGLPFADoubleBuffer,
385 NSOpenGLPFAColorSize, 24,
386 NSOpenGLPFAAlphaSize, 8,
387 NSOpenGLPFADepthSize, 24,
392 fmt = [[NSOpenGLPixelFormat alloc]
393 initWithAttributes:attribs];
396 GST_WARNING ("Cannot create NSOpenGLPixelFormat");
400 self = [super initWithFrame: frame pixelFormat:fmt];
403 actualContext = [self openGLContext];
404 [actualContext makeCurrentContext];
405 [actualContext update];
407 /* Black background */
408 glClearColor (0.0, 0.0, 0.0, 0.0);
412 width = frame.size.width;
413 height = frame.size.height;
414 drawingBounds = NSMakeRect(0, 0, width, height);
416 GST_LOG ("Width: %d Height: %d", width, height);
418 trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
419 options: (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow)
423 [self addTrackingArea:trackingArea];
424 mainThread = [NSThread mainThread];
430 - (NSRect) getDrawingBounds {
431 return drawingBounds;
436 gdouble frame_par, view_par;
437 gint view_height, view_width, c_height, c_width, c_x, c_y;
440 GST_LOG ("reshaping");
446 [actualContext makeCurrentContext];
448 bounds = [self bounds];
449 view_width = bounds.size.width;
450 view_height = bounds.size.height;
452 frame_par = (gdouble) width / height;
453 view_par = (gdouble) view_width / view_height;
454 if (!keepAspectRatio)
455 view_par = frame_par;
457 if (frame_par == view_par) {
458 c_height = view_height;
459 c_width = view_width;
462 } else if (frame_par < view_par) {
463 c_height = view_height;
464 c_width = c_height * frame_par;
465 c_x = (view_width - c_width) / 2;
468 c_width = view_width;
469 c_height = c_width / frame_par;
471 c_y = (view_height - c_height) / 2;
474 drawingBounds = NSMakeRect(c_x, c_y, c_width, c_height);
475 glViewport (c_x, c_y, (GLint) c_width, (GLint) c_height);
478 - (void) initTextures {
480 [actualContext makeCurrentContext];
482 /* Free previous texture if any */
484 glDeleteTextures (1, (GLuint *)&pi_texture);
488 data = g_realloc (data, width * height * sizeof(short)); // short or 3byte?
490 data = g_malloc0(width * height * sizeof(short));
492 /* Create textures */
493 glGenTextures (1, (GLuint *)&pi_texture);
495 glEnable (GL_TEXTURE_RECTANGLE_EXT);
496 glEnable (GL_UNPACK_CLIENT_STORAGE_APPLE);
498 glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
499 glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
501 glBindTexture (GL_TEXTURE_RECTANGLE_EXT, pi_texture);
503 /* Use VRAM texturing */
504 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
505 GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_CACHED_APPLE);
507 /* Tell the driver not to make a copy of the texture but to use
509 glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
511 /* Linear interpolation */
512 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
513 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
515 /* I have no idea what this exactly does, but it seems to be
516 necessary for scaling */
517 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
518 GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
519 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
520 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
521 // glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); WHY ??
523 glTexImage2D (GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA,
525 GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, data);
531 - (void) reloadTexture {
536 GST_LOG ("Reloading Texture");
538 [actualContext makeCurrentContext];
540 glBindTexture (GL_TEXTURE_RECTANGLE_EXT, pi_texture);
541 glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
543 /* glTexSubImage2D is faster than glTexImage2D
544 http://developer.apple.com/samplecode/Sample_Code/Graphics_3D/
545 TextureRange/MainOpenGLView.m.htm */
546 glTexSubImage2D (GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0,
548 GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, data); //FIXME
561 glTexCoord2f (0.0, 0.0);
562 glVertex2f (-f_x, f_y);
564 glTexCoord2f (0.0, (float) height);
565 glVertex2f (-f_x, -f_y);
567 glTexCoord2f ((float) width, (float) height);
568 glVertex2f (f_x, -f_y);
570 glTexCoord2f ((float) width, 0.0);
571 glVertex2f (f_x, f_y);
575 - (void) drawRect:(NSRect) rect {
576 GLint params[] = { 1 };
578 [actualContext makeCurrentContext];
580 CGLSetParameter (CGLGetCurrentContext (), kCGLCPSwapInterval, params);
582 /* Black background */
583 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
586 [actualContext flushBuffer];
591 glBindTexture (GL_TEXTURE_RECTANGLE_EXT, pi_texture); // FIXME
594 [actualContext flushBuffer];
597 - (void) displayTexture {
598 if ([self lockFocusIfCanDraw]) {
600 [self drawRect:[self bounds]];
601 [self reloadTexture];
609 - (char *) getTextureBuffer {
613 - (void) setFullScreen:(BOOL) flag {
614 if (!fullscreen && flag) {
616 /* Create the new pixel format */
617 NSOpenGLPixelFormat *fmt;
618 NSOpenGLPixelFormatAttribute attribs[] = {
619 NSOpenGLPFAAccelerated,
620 NSOpenGLPFANoRecovery,
621 NSOpenGLPFADoubleBuffer,
622 NSOpenGLPFAColorSize, 24,
623 NSOpenGLPFAAlphaSize, 8,
624 NSOpenGLPFADepthSize, 24,
625 NSOpenGLPFAFullScreen,
626 NSOpenGLPFAScreenMask,
627 CGDisplayIDToOpenGLDisplayMask (kCGDirectMainDisplay),
631 fmt = [[NSOpenGLPixelFormat alloc]
632 initWithAttributes:attribs];
635 GST_WARNING ("Cannot create NSOpenGLPixelFormat");
639 /* Create the new OpenGL context */
640 fullScreenContext = [[NSOpenGLContext alloc]
641 initWithFormat: fmt shareContext:nil];
642 if (!fullScreenContext) {
643 GST_WARNING ("Failed to create new NSOpenGLContext");
647 actualContext = fullScreenContext;
649 /* Capture display, switch to fullscreen */
650 if (CGCaptureAllDisplays () != CGDisplayNoErr) {
651 GST_WARNING ("CGCaptureAllDisplays() failed");
654 [fullScreenContext setFullScreen];
655 [fullScreenContext makeCurrentContext];
660 [self setNeedsDisplay:YES];
662 } else if (fullscreen && !flag) {
663 // fullscreen now and needs to go back to normal
666 actualContext = [self openGLContext];
668 [NSOpenGLContext clearCurrentContext];
669 [fullScreenContext clearDrawable];
670 [fullScreenContext release];
671 fullScreenContext = nil;
673 CGReleaseAllDisplays ();
678 [self setNeedsDisplay:YES];
685 - (void) setVideoSize: (int) w:(int) h {
686 GST_LOG ("width:%d, height:%d", w, h);
691 // if (data) g_free(data);
693 // data = g_malloc0 (2 * w * h);
698 - (void) setKeepAspectRatio: (BOOL) flag {
699 keepAspectRatio = flag;
703 - (void) setMainThread: (NSThread *) thread {
707 - (void) haveSuperviewReal:(NSMutableArray *)closure {
708 BOOL haveSuperview = [self superview] != nil;
709 [closure addObject:[NSNumber numberWithBool:haveSuperview]];
712 - (BOOL) haveSuperview {
713 NSMutableArray *closure = [NSMutableArray arrayWithCapacity:1];
714 [self performSelector:@selector(haveSuperviewReal:)
716 withObject:(id)closure waitUntilDone:YES];
718 return [[closure objectAtIndex:0] boolValue];
721 - (void) addToSuperviewReal:(NSView *)superview {
723 [superview addSubview:self];
724 bounds = [superview bounds];
725 [self setFrame:bounds];
726 [self setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
729 - (void) addToSuperview: (NSView *)superview {
730 [self performSelector:@selector(addToSuperviewReal:)
732 withObject:superview waitUntilDone:YES];
735 - (void) removeFromSuperview: (id)unused
737 [self removeFromSuperview];
741 GST_LOG ("dealloc called");
742 if (data) g_free(data);
744 if (fullScreenContext) {
745 [NSOpenGLContext clearCurrentContext];
746 [fullScreenContext clearDrawable];
747 [fullScreenContext release];
748 if (actualContext == fullScreenContext) actualContext = nil;
749 fullScreenContext = nil;
755 - (void)updateTrackingAreas {
756 [self removeTrackingArea:trackingArea];
757 [trackingArea release];
758 trackingArea = [[NSTrackingArea alloc] initWithRect: [self bounds]
759 options: (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow)
760 owner:self userInfo:nil];
761 [self addTrackingArea:trackingArea];
764 - (BOOL)acceptsFirstResponder {
768 - (void) setNavigation:(GstNavigation *)nav
773 - (void)sendMouseEvent:(NSEvent *)event: (const char *)event_name
782 switch ([event type]) {
786 case NSLeftMouseDown:
790 case NSRightMouseDown:
799 location = [self convertPoint:[event locationInWindow] fromView:nil];
805 y = (1 - ((gdouble) y) / [self bounds].size.height) * [self bounds].size.height;
807 gst_navigation_send_mouse_event (navigation, event_name, button, x, y);
810 - (void)sendKeyEvent:(NSEvent *)event: (const char *)event_name
815 gst_navigation_send_key_event(navigation, event_name, gst_keycode_to_keyname([event keyCode]));
818 - (void)sendModifierKeyEvent:(NSEvent *)event
820 NSUInteger flags = [event modifierFlags];
821 const gchar* event_name = flags > savedModifierFlags ? "key-press" : "key-release";
822 savedModifierFlags = flags;
823 [self sendKeyEvent: event: event_name];
826 - (void)keyDown:(NSEvent *) event;
828 [self sendKeyEvent: event: "key-press"];
829 [super keyDown: event];
832 - (void)keyUp:(NSEvent *) event;
834 [self sendKeyEvent: event: "key-release"];
835 [super keyUp: event];
838 - (void)flagsChanged:(NSEvent *) event;
840 [self sendModifierKeyEvent: event];
841 [super flagsChanged: event];
844 - (void)mouseDown:(NSEvent *) event;
846 [self sendMouseEvent:event: "mouse-button-press"];
847 [super mouseDown: event];
850 - (void)mouseUp:(NSEvent *) event;
852 [self sendMouseEvent:event: "mouse-button-release"];
853 [super mouseUp: event];
856 - (void)mouseMoved:(NSEvent *)event;
858 [self sendMouseEvent:event: "mouse-move"];
859 [super mouseMoved: event];
862 - (void)mouseEntered:(NSEvent *)event;
864 [super mouseEntered: event];
867 - (void)mouseExited:(NSEvent *)event;
869 [super mouseExited: event];