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 NSOpenGLPFANoRecovery,
383 NSOpenGLPFADoubleBuffer,
384 NSOpenGLPFAColorSize, 24,
385 NSOpenGLPFAAlphaSize, 8,
386 NSOpenGLPFADepthSize, 24,
391 fmt = [[NSOpenGLPixelFormat alloc]
392 initWithAttributes:attribs];
395 GST_WARNING ("Cannot create NSOpenGLPixelFormat");
399 self = [super initWithFrame: frame pixelFormat:fmt];
402 actualContext = [self openGLContext];
403 [actualContext makeCurrentContext];
404 [actualContext update];
406 /* Black background */
407 glClearColor (0.0, 0.0, 0.0, 0.0);
411 width = frame.size.width;
412 height = frame.size.height;
413 drawingBounds = NSMakeRect(0, 0, width, height);
415 GST_LOG ("Width: %d Height: %d", width, height);
417 trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
418 options: (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow)
422 [self addTrackingArea:trackingArea];
423 mainThread = [NSThread mainThread];
429 - (NSRect) getDrawingBounds {
430 return drawingBounds;
435 gdouble frame_par, view_par;
436 gint view_height, view_width, c_height, c_width, c_x, c_y;
439 GST_LOG ("reshaping");
445 [actualContext makeCurrentContext];
447 bounds = [self bounds];
448 view_width = bounds.size.width;
449 view_height = bounds.size.height;
451 frame_par = (gdouble) width / height;
452 view_par = (gdouble) view_width / view_height;
453 if (!keepAspectRatio)
454 view_par = frame_par;
456 if (frame_par == view_par) {
457 c_height = view_height;
458 c_width = view_width;
461 } else if (frame_par < view_par) {
462 c_height = view_height;
463 c_width = c_height * frame_par;
464 c_x = (view_width - c_width) / 2;
467 c_width = view_width;
468 c_height = c_width / frame_par;
470 c_y = (view_height - c_height) / 2;
473 drawingBounds = NSMakeRect(c_x, c_y, c_width, c_height);
474 glViewport (c_x, c_y, (GLint) c_width, (GLint) c_height);
477 - (void) initTextures {
479 [actualContext makeCurrentContext];
481 /* Free previous texture if any */
483 glDeleteTextures (1, (GLuint *)&pi_texture);
487 data = g_realloc (data, width * height * sizeof(short)); // short or 3byte?
489 data = g_malloc0(width * height * sizeof(short));
491 /* Create textures */
492 glGenTextures (1, (GLuint *)&pi_texture);
494 glEnable (GL_TEXTURE_RECTANGLE_EXT);
495 glEnable (GL_UNPACK_CLIENT_STORAGE_APPLE);
497 glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
498 glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
500 glBindTexture (GL_TEXTURE_RECTANGLE_EXT, pi_texture);
502 /* Use VRAM texturing */
503 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
504 GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_CACHED_APPLE);
506 /* Tell the driver not to make a copy of the texture but to use
508 glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
510 /* Linear interpolation */
511 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
512 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
514 /* I have no idea what this exactly does, but it seems to be
515 necessary for scaling */
516 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
517 GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
518 glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
519 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
520 // glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); WHY ??
522 glTexImage2D (GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA,
524 GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, data);
530 - (void) reloadTexture {
535 GST_LOG ("Reloading Texture");
537 [actualContext makeCurrentContext];
539 glBindTexture (GL_TEXTURE_RECTANGLE_EXT, pi_texture);
540 glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
542 /* glTexSubImage2D is faster than glTexImage2D
543 http://developer.apple.com/samplecode/Sample_Code/Graphics_3D/
544 TextureRange/MainOpenGLView.m.htm */
545 glTexSubImage2D (GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0,
547 GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, data); //FIXME
560 glTexCoord2f (0.0, 0.0);
561 glVertex2f (-f_x, f_y);
563 glTexCoord2f (0.0, (float) height);
564 glVertex2f (-f_x, -f_y);
566 glTexCoord2f ((float) width, (float) height);
567 glVertex2f (f_x, -f_y);
569 glTexCoord2f ((float) width, 0.0);
570 glVertex2f (f_x, f_y);
574 - (void) drawRect:(NSRect) rect {
575 GLint params[] = { 1 };
577 [actualContext makeCurrentContext];
579 CGLSetParameter (CGLGetCurrentContext (), kCGLCPSwapInterval, params);
581 /* Black background */
582 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
585 [actualContext flushBuffer];
590 glBindTexture (GL_TEXTURE_RECTANGLE_EXT, pi_texture); // FIXME
593 [actualContext flushBuffer];
596 - (void) displayTexture {
597 if ([self lockFocusIfCanDraw]) {
599 [self drawRect:[self bounds]];
600 [self reloadTexture];
608 - (char *) getTextureBuffer {
612 - (void) setFullScreen:(BOOL) flag {
613 if (!fullscreen && flag) {
615 /* Create the new pixel format */
616 NSOpenGLPixelFormat *fmt;
617 NSOpenGLPixelFormatAttribute attribs[] = {
618 NSOpenGLPFAAccelerated,
619 NSOpenGLPFANoRecovery,
620 NSOpenGLPFADoubleBuffer,
621 NSOpenGLPFAColorSize, 24,
622 NSOpenGLPFAAlphaSize, 8,
623 NSOpenGLPFADepthSize, 24,
624 NSOpenGLPFAFullScreen,
625 NSOpenGLPFAScreenMask,
626 CGDisplayIDToOpenGLDisplayMask (kCGDirectMainDisplay),
630 fmt = [[NSOpenGLPixelFormat alloc]
631 initWithAttributes:attribs];
634 GST_WARNING ("Cannot create NSOpenGLPixelFormat");
638 /* Create the new OpenGL context */
639 fullScreenContext = [[NSOpenGLContext alloc]
640 initWithFormat: fmt shareContext:nil];
641 if (!fullScreenContext) {
642 GST_WARNING ("Failed to create new NSOpenGLContext");
646 actualContext = fullScreenContext;
648 /* Capture display, switch to fullscreen */
649 if (CGCaptureAllDisplays () != CGDisplayNoErr) {
650 GST_WARNING ("CGCaptureAllDisplays() failed");
653 [fullScreenContext setFullScreen];
654 [fullScreenContext makeCurrentContext];
659 [self setNeedsDisplay:YES];
661 } else if (fullscreen && !flag) {
662 // fullscreen now and needs to go back to normal
665 actualContext = [self openGLContext];
667 [NSOpenGLContext clearCurrentContext];
668 [fullScreenContext clearDrawable];
669 [fullScreenContext release];
670 fullScreenContext = nil;
672 CGReleaseAllDisplays ();
677 [self setNeedsDisplay:YES];
684 - (void) setVideoSize: (int)w : (int)h {
685 GST_LOG ("width:%d, height:%d", w, h);
690 // if (data) g_free(data);
692 // data = g_malloc0 (2 * w * h);
697 - (void) setKeepAspectRatio: (BOOL) flag {
698 keepAspectRatio = flag;
702 - (void) setMainThread: (NSThread *) thread {
706 - (void) haveSuperviewReal:(NSMutableArray *)closure {
707 BOOL haveSuperview = [self superview] != nil;
708 [closure addObject:[NSNumber numberWithBool:haveSuperview]];
711 - (BOOL) haveSuperview {
712 NSMutableArray *closure = [NSMutableArray arrayWithCapacity:1];
713 [self performSelector:@selector(haveSuperviewReal:)
715 withObject:(id)closure waitUntilDone:YES];
717 return [[closure objectAtIndex:0] boolValue];
720 - (void) addToSuperviewReal:(NSView *)superview {
722 [superview addSubview:self];
723 bounds = [superview bounds];
724 [self setFrame:bounds];
725 [self setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
728 - (void) addToSuperview: (NSView *)superview {
729 [self performSelector:@selector(addToSuperviewReal:)
731 withObject:superview waitUntilDone:YES];
734 - (void) removeFromSuperview: (id)unused
736 [self removeFromSuperview];
740 GST_LOG ("dealloc called");
741 if (data) g_free(data);
743 if (fullScreenContext) {
744 [NSOpenGLContext clearCurrentContext];
745 [fullScreenContext clearDrawable];
746 [fullScreenContext release];
747 if (actualContext == fullScreenContext) actualContext = nil;
748 fullScreenContext = nil;
754 - (void)updateTrackingAreas {
755 [self removeTrackingArea:trackingArea];
756 [trackingArea release];
757 trackingArea = [[NSTrackingArea alloc] initWithRect: [self bounds]
758 options: (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow)
759 owner:self userInfo:nil];
760 [self addTrackingArea:trackingArea];
763 - (BOOL)acceptsFirstResponder {
767 - (void) setNavigation:(GstNavigation *)nav
772 - (void)sendMouseEvent:(NSEvent *)event : (const char *)event_name
781 switch ([event type]) {
785 case NSLeftMouseDown:
789 case NSRightMouseDown:
798 location = [self convertPoint:[event locationInWindow] fromView:nil];
804 y = (1 - ((gdouble) y) / [self bounds].size.height) * [self bounds].size.height;
806 gst_navigation_send_mouse_event (navigation, event_name, button, x, y);
809 - (void)sendKeyEvent:(NSEvent *)event : (const char *)event_name
814 gst_navigation_send_key_event(navigation, event_name, gst_keycode_to_keyname([event keyCode]));
817 - (void)sendModifierKeyEvent:(NSEvent *)event
819 NSUInteger flags = [event modifierFlags];
820 const gchar* event_name = flags > savedModifierFlags ? "key-press" : "key-release";
821 savedModifierFlags = flags;
822 [self sendKeyEvent: event: event_name];
825 - (void)keyDown:(NSEvent *) event;
827 [self sendKeyEvent: event: "key-press"];
828 [super keyDown: event];
831 - (void)keyUp:(NSEvent *) event;
833 [self sendKeyEvent: event: "key-release"];
834 [super keyUp: event];
837 - (void)flagsChanged:(NSEvent *) event;
839 [self sendModifierKeyEvent: event];
840 [super flagsChanged: event];
843 - (void)mouseDown:(NSEvent *) event;
845 [self sendMouseEvent:event: "mouse-button-press"];
846 [super mouseDown: event];
849 - (void)mouseUp:(NSEvent *) event;
851 [self sendMouseEvent:event: "mouse-button-release"];
852 [super mouseUp: event];
855 - (void)mouseMoved:(NSEvent *)event;
857 [self sendMouseEvent:event: "mouse-move"];
858 [super mouseMoved: event];
861 - (void)mouseEntered:(NSEvent *)event;
863 [super mouseEntered: event];
866 - (void)mouseExited:(NSEvent *)event;
868 [super mouseExited: event];