]> git.sesse.net Git - vlc/blob - modules/gui/macosx/vout.m
* Option for Desktop Background mode for VLC OSX.
[vlc] / modules / gui / macosx / vout.m
1 /*****************************************************************************
2  * vout.m: MacOS X video output module
3  *****************************************************************************
4  * Copyright (C) 2001-2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Colin Delacroix <colin@zoy.org>
8  *          Florian G. Pflug <fgp@phlo.org>
9  *          Jon Lech Johansen <jon-vl@nanocrew.net>
10  *          Derk-Jan Hartman <hartman at videolan dot org>
11  *          Eric Petit <titer@m0k.org>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  * 
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
26  *****************************************************************************/
27
28 /*****************************************************************************
29  * Preamble
30  *****************************************************************************/
31 #include <errno.h>                                                 /* ENOMEM */
32 #include <stdlib.h>                                                /* free() */
33 #include <string.h>                                            /* strerror() */
34
35 /* BeginFullScreen, EndFullScreen */
36 #include <QuickTime/QuickTime.h>
37
38 #include <vlc_keys.h>
39
40 #include "intf.h"
41 #include "vout.h"
42
43
44 /*****************************************************************************
45  * DeviceCallback: Callback triggered when the video-device variable is changed
46  *****************************************************************************/
47 int DeviceCallback( vlc_object_t *p_this, const char *psz_variable,
48                      vlc_value_t old_val, vlc_value_t new_val, void *param )
49 {
50     vlc_value_t val;
51     vout_thread_t *p_vout = (vout_thread_t *)p_this;
52     
53     msg_Dbg( p_vout, "set %d", new_val.i_int );
54     var_Create( p_vout->p_vlc, "video-device", VLC_VAR_INTEGER );
55     var_Set( p_vout->p_vlc, "video-device", new_val );
56     
57     val.b_bool = VLC_TRUE;
58     var_Set( p_vout, "intf-change", val );
59     return VLC_SUCCESS;
60 }
61
62
63 /*****************************************************************************
64  * VLCWindow implementation
65  *****************************************************************************/
66 @implementation VLCWindow
67
68 - (id) initWithVout: (vout_thread_t *) vout view: (NSView *) view
69                      frame: (NSRect *) frame
70 {
71     p_vout  = vout;
72     o_view  = view;
73     s_frame = frame;
74
75     [self performSelectorOnMainThread: @selector(initReal:)
76         withObject: NULL waitUntilDone: YES];
77
78     if( !b_init_ok )
79     {
80         return NULL;
81     }
82     
83     return self;
84 }
85
86 - (id) initReal: (id) sender
87 {
88     NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
89     NSArray *o_screens = [NSScreen screens];
90     NSScreen *o_screen;
91     vlc_bool_t b_menubar_screen = VLC_FALSE;
92     int i_timeout, i_device;
93     vlc_value_t value_drawable;
94
95     b_init_ok = VLC_FALSE;
96
97     var_Get( p_vout->p_vlc, "drawable", &value_drawable );
98
99     /* We only wait for NSApp to initialise if we're not embedded (as in the
100      * case of the Mozilla plugin).  We can tell whether we're embedded or not
101      * by examining the "drawable" value: if it's zero, we're running in the
102      * main Mac intf; if it's non-zero, we're embedded. */
103     if( value_drawable.i_int == 0 )
104     {
105         /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
106         for( i_timeout = 20 ; i_timeout-- ; )
107         {
108             if( NSApp == NULL )
109             {
110                 msleep( INTF_IDLE_SLEEP );
111             }
112         }
113
114         if( NSApp == NULL )
115         {
116             /* No MacOS X intf, unable to communicate with MT */
117             msg_Err( p_vout, "no MacOS X interface present" );
118             return NULL;
119         }
120     }
121     
122     if( [o_screens count] <= 0 )
123     {
124         msg_Err( p_vout, "no OSX screens available" );
125         return NULL;
126     }
127
128     /* p_real_vout: the vout we have to use to check for video-on-top
129        and a few other things. If we are the QuickTime output, it's us.
130        It we are the OpenGL provider, it is our parent. */
131     if( p_vout->i_object_type == VLC_OBJECT_OPENGL )
132     {
133         p_real_vout = (vout_thread_t *) p_vout->p_parent;
134     }
135     else
136     {
137         p_real_vout = p_vout;
138     }
139
140     p_fullscreen_state = NULL;
141     i_time_mouse_last_moved = mdate();
142
143     var_Create( p_vout, "macosx-vdev", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
144     var_Create( p_vout, "macosx-fill", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
145     var_Create( p_vout, "macosx-stretch", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
146     var_Create( p_vout, "macosx-opaqueness", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
147     var_Create( p_vout, "macosx-background", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
148
149     /* Get the pref value when this is the first time, otherwise retrieve the device from the top level video-device var */
150     if( var_Type( p_real_vout->p_vlc, "video-device" ) == 0 )
151     {
152         i_device = var_GetInteger( p_vout, "macosx-vdev" );
153     }
154     else
155     {
156         i_device = var_GetInteger( p_real_vout->p_vlc, "video-device" );
157     }
158
159     /* Setup the menuitem for the multiple displays. */
160     if( var_Type( p_real_vout, "video-device" ) == 0 )
161     {
162         int i = 1;
163         vlc_value_t val2, text;
164         NSScreen * o_screen;
165
166         var_Create( p_real_vout, "video-device", VLC_VAR_INTEGER |
167                                             VLC_VAR_HASCHOICE );
168         text.psz_string = _("Video Device");
169         var_Change( p_real_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
170
171         NSEnumerator * o_enumerator = [o_screens objectEnumerator];
172
173         val2.i_int = 0;
174         text.psz_string = _("Default");
175         var_Change( p_real_vout, "video-device",
176                         VLC_VAR_ADDCHOICE, &val2, &text );
177         var_Set( p_real_vout, "video-device", val2 );
178
179         while( (o_screen = [o_enumerator nextObject]) != NULL )
180         {
181             char psz_temp[255];
182             NSRect s_rect = [o_screen frame];
183
184             snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1,
185                       "%s %d (%dx%d)", _("Screen"), i,
186                       (int)s_rect.size.width, (int)s_rect.size.height );
187
188             text.psz_string = psz_temp;
189             val2.i_int = i;
190             var_Change( p_real_vout, "video-device",
191                         VLC_VAR_ADDCHOICE, &val2, &text );
192             if( i == i_device )
193             {
194                 var_Set( p_real_vout, "video-device", val2 );
195             }
196             i++;
197         }
198
199         var_AddCallback( p_real_vout, "video-device", DeviceCallback,
200                          NULL );
201
202         val2.b_bool = VLC_TRUE;
203         var_Set( p_real_vout, "intf-change", val2 );
204     }
205
206     /* Find out on which screen to open the window */
207     if( i_device <= 0 || i_device > (int)[o_screens count] )
208     {
209          /* No preference specified. Use the main screen */
210         o_screen = [NSScreen mainScreen];
211         if( o_screen == [o_screens objectAtIndex: 0] )
212             b_menubar_screen = VLC_TRUE;
213     }
214     else
215     {
216         i_device--;
217         o_screen = [o_screens objectAtIndex: i_device];
218         b_menubar_screen = ( i_device == 0 );
219     }
220
221     if( p_vout->b_fullscreen )
222     {
223         NSRect screen_rect = [o_screen frame];
224         screen_rect.origin.x = screen_rect.origin.y = 0;
225
226         /* Creates a window with size: screen_rect on o_screen */
227         [self initWithContentRect: screen_rect
228               styleMask: NSBorderlessWindowMask
229               backing: NSBackingStoreBuffered
230               defer: YES screen: o_screen];
231
232         if( b_menubar_screen )
233         {
234             BeginFullScreen( &p_fullscreen_state, NULL, 0, 0,
235                              NULL, NULL, fullScreenAllowEvents );
236         }
237     }
238     else if( var_GetBool( p_real_vout, "macosx-background" ) )
239     {
240         NSRect screen_rect = [o_screen frame];
241         screen_rect.origin.x = screen_rect.origin.y = 0;
242
243         /* Creates a window with size: screen_rect on o_screen */
244         [self initWithContentRect: screen_rect
245               styleMask: NSBorderlessWindowMask
246               backing: NSBackingStoreBuffered
247               defer: YES screen: o_screen];
248
249         [self setLevel: CGWindowLevelForKey(kCGDesktopWindowLevelKey)];
250     }
251     else
252     {
253         unsigned int i_stylemask = NSTitledWindowMask |
254                                    NSMiniaturizableWindowMask |
255                                    NSClosableWindowMask |
256                                    NSResizableWindowMask;
257
258         NSRect s_rect;
259         if( !s_frame )
260         {
261             s_rect.size.width  = p_vout->i_window_width;
262             s_rect.size.height = p_vout->i_window_height;
263         }
264         else
265         {
266             s_rect = *s_frame;
267         }
268        
269         [self initWithContentRect: s_rect
270               styleMask: i_stylemask
271               backing: NSBackingStoreBuffered
272               defer: YES screen: o_screen];
273
274         [self setAlphaValue: var_GetFloat( p_vout, "macosx-opaqueness" )];
275
276         if( var_GetBool( p_real_vout, "video-on-top" ) )
277         {
278             [self setLevel: NSStatusWindowLevel];
279         }
280
281         if( !s_frame )
282         {
283             [self center];
284         }
285     }
286
287     [self updateTitle];
288     [self makeKeyAndOrderFront: nil];
289     [self setReleasedWhenClosed: YES];
290
291     /* We'll catch mouse events */
292     [self setAcceptsMouseMovedEvents: YES];
293     [self makeFirstResponder: self];
294
295     /* Add the view. It's automatically resized to fit the window */
296     [self setContentView: o_view];
297     
298     [o_pool release];
299
300     b_init_ok = VLC_TRUE;
301     return self;
302 }
303
304 - (void) close
305 {
306     /* XXX waitUntilDone = NO to avoid a possible deadlock when hitting
307        Command-Q */
308     [self setAcceptsMouseMovedEvents: NO];
309     [self setContentView: NULL];
310     [self performSelectorOnMainThread: @selector(closeReal:)
311         withObject: NULL waitUntilDone: NO];
312 }
313
314 - (id) closeReal: (id) sender
315 {
316     [super close];
317     if( p_fullscreen_state )
318     {
319         EndFullScreen( p_fullscreen_state, 0 );
320     }
321     return NULL;
322 }
323
324 - (void)setOnTop:(BOOL)b_on_top
325 {
326     if( b_on_top )
327     {
328         [self setLevel: NSStatusWindowLevel];
329     }
330     else
331     {
332         [self setLevel: NSNormalWindowLevel];
333     }
334 }
335
336 - (void)hideMouse:(BOOL)b_hide
337 {
338     BOOL b_inside;
339     NSPoint ml;
340     NSView *o_contents = [self contentView];
341     
342     ml = [self convertScreenToBase:[NSEvent mouseLocation]];
343     ml = [o_contents convertPoint:ml fromView:nil];
344     b_inside = [o_contents mouse: ml inRect: [o_contents bounds]];
345     
346     if( b_hide && b_inside )
347     {
348         [NSCursor setHiddenUntilMouseMoves: YES];
349     }
350     else if( !b_hide )
351     {
352         [NSCursor setHiddenUntilMouseMoves: NO];
353     }
354 }
355
356 - (void)manage
357 {
358     if( p_fullscreen_state )
359     {
360         if( mdate() - i_time_mouse_last_moved > 3000000 )
361         {
362             [self hideMouse: YES];
363         }
364     }
365     else
366     {
367         [self hideMouse: NO];
368     }
369
370     /* Disable screensaver */
371     UpdateSystemActivity( UsrActivity );
372 }
373
374 - (void)scaleWindowWithFactor: (float)factor
375 {
376     NSSize newsize;
377     int i_corrected_height, i_corrected_width;
378     NSPoint topleftbase;
379     NSPoint topleftscreen;
380     
381     if ( !p_vout->b_fullscreen )
382     {
383         topleftbase.x = 0;
384         topleftbase.y = [self frame].size.height;
385         topleftscreen = [self convertBaseToScreen: topleftbase];
386         
387         if( p_vout->render.i_height * p_vout->render.i_aspect > 
388                         p_vout->render.i_width * VOUT_ASPECT_FACTOR )
389         {
390             i_corrected_width = p_vout->render.i_height * p_vout->render.i_aspect /
391                                             VOUT_ASPECT_FACTOR;
392             newsize.width = (int) ( i_corrected_width * factor );
393             newsize.height = (int) ( p_vout->render.i_height * factor );
394         }
395         else
396         {
397             i_corrected_height = p_vout->render.i_width * VOUT_ASPECT_FACTOR /
398                                             p_vout->render.i_aspect;
399             newsize.width = (int) ( p_vout->render.i_width * factor );
400             newsize.height = (int) ( i_corrected_height * factor );
401         }
402     
403         [self setContentSize: newsize];
404         
405         [self setFrameTopLeftPoint: topleftscreen];
406         p_vout->i_changes |= VOUT_SIZE_CHANGE;
407     }
408 }
409
410 - (void)toggleFloatOnTop
411 {
412     vlc_value_t val;
413
414     if( var_Get( p_real_vout, "video-on-top", &val )>=0 && val.b_bool)
415     {
416         val.b_bool = VLC_FALSE;
417     }
418     else
419     {
420         val.b_bool = VLC_TRUE;
421     }
422     var_Set( p_real_vout, "video-on-top", val );
423 }
424
425 - (void)toggleFullscreen
426 {
427     vlc_value_t val;
428     var_Get( p_real_vout, "fullscreen", &val );
429     val.b_bool = !val.b_bool;
430     var_Set( p_real_vout, "fullscreen", val );
431 }
432
433 - (BOOL)isFullscreen
434 {
435     vlc_value_t val;
436     var_Get( p_real_vout, "fullscreen", &val );
437     return( val.b_bool );
438 }
439
440 - (void)snapshot
441 {
442     vout_Control( p_real_vout, VOUT_SNAPSHOT );
443 }
444
445 - (BOOL)canBecomeKeyWindow
446 {
447     return YES;
448 }
449
450 /* Sometimes crashes VLC....
451 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
452 {
453         return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event];
454 }*/
455
456 - (void)keyDown:(NSEvent *)o_event
457 {
458     unichar key = 0;
459     vlc_value_t val;
460     unsigned int i_pressed_modifiers = 0;
461     val.i_int = 0;
462     
463     i_pressed_modifiers = [o_event modifierFlags];
464
465     if( i_pressed_modifiers & NSShiftKeyMask )
466         val.i_int |= KEY_MODIFIER_SHIFT;
467     if( i_pressed_modifiers & NSControlKeyMask )
468         val.i_int |= KEY_MODIFIER_CTRL;
469     if( i_pressed_modifiers & NSAlternateKeyMask )
470         val.i_int |= KEY_MODIFIER_ALT;
471     if( i_pressed_modifiers & NSCommandKeyMask )
472         val.i_int |= KEY_MODIFIER_COMMAND;
473
474     key = [[o_event charactersIgnoringModifiers] characterAtIndex: 0];
475
476     if( key )
477     {
478         /* Escape should always get you out of fullscreen */
479         if( key == (unichar) 0x1b )
480         {
481              if( [self isFullscreen] )
482              {
483                  [self toggleFullscreen];
484              }
485         }
486         else if ( key == ' ' )
487         {
488             vlc_value_t val;
489             val.i_int = config_GetInt( p_vout, "key-play-pause" );
490             var_Set( p_vout->p_vlc, "key-pressed", val );
491         }
492         else
493         {
494             val.i_int |= CocoaKeyToVLC( key );
495             var_Set( p_vout->p_vlc, "key-pressed", val );
496         }
497     }
498     else
499     {
500         [super keyDown: o_event];
501     }
502 }
503
504 - (void)updateTitle
505 {
506     NSMutableString * o_title = NULL, * o_mrl = NULL;
507     input_thread_t * p_input;
508     
509     if( p_vout == NULL )
510     {
511         return;
512     }
513     
514     p_input = vlc_object_find( p_vout, VLC_OBJECT_INPUT, FIND_PARENT );
515     
516     if( p_input == NULL )
517     {
518         return;
519     }
520
521     if( p_input->input.p_item->psz_name != NULL )
522         o_title = [NSMutableString stringWithUTF8String:
523             p_input->input.p_item->psz_name];
524     if( p_input->input.p_item->psz_uri != NULL )
525         o_mrl = [NSMutableString stringWithUTF8String:
526             p_input->input.p_item->psz_uri];
527     if( o_title == nil )
528         o_title = o_mrl;
529
530     if( o_mrl != nil )
531     {
532         if( p_input->input.p_access && !strcmp( p_input->input.p_access->p_module->psz_shortname, "File" ) )
533         {
534             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
535             if( prefix_range.location != NSNotFound )
536                 [o_mrl deleteCharactersInRange: prefix_range];
537             [self setRepresentedFilename: o_mrl];
538         }
539         [self setTitle: o_title];
540     }
541     else
542     {
543         [self setTitle: [NSString stringWithCString: VOUT_TITLE]];
544     }
545     vlc_object_release( p_input );
546 }
547
548 /* This is actually the same as VLCControls::stop. */
549 - (BOOL)windowShouldClose:(id)sender
550 {
551     playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
552                                                        FIND_ANYWHERE );
553     if( p_playlist == NULL )      
554     {
555         return NO;
556     }
557
558     playlist_Stop( p_playlist );
559     vlc_object_release( p_playlist );
560
561     /* The window will be closed by the intf later. */
562     return NO;
563 }
564
565 - (BOOL)acceptsFirstResponder
566 {
567     return YES;
568 }
569
570 - (BOOL)becomeFirstResponder
571 {
572     return YES;
573 }
574
575 - (BOOL)resignFirstResponder
576 {
577     /* We need to stay the first responder or we'll miss some
578        events */
579     return NO;
580 }
581
582 - (void)mouseDown:(NSEvent *)o_event
583 {
584     vlc_value_t val;
585
586     switch( [o_event type] )
587     {
588         case NSLeftMouseDown:
589         {
590             var_Get( p_vout, "mouse-button-down", &val );
591             val.i_int |= 1;
592             var_Set( p_vout, "mouse-button-down", val );
593         }
594         break;
595
596         default:
597             [super mouseDown: o_event];
598         break;
599     }
600 }
601
602 - (void)otherMouseDown:(NSEvent *)o_event
603 {
604     vlc_value_t val;
605
606     switch( [o_event type] )
607     {
608         case NSOtherMouseDown:
609         {
610             var_Get( p_vout, "mouse-button-down", &val );
611             val.i_int |= 2;
612             var_Set( p_vout, "mouse-button-down", val );
613         }
614         break;
615
616         default:
617             [super mouseDown: o_event];
618         break;
619     }
620 }
621
622 - (void)rightMouseDown:(NSEvent *)o_event
623 {
624     vlc_value_t val;
625
626     switch( [o_event type] )
627     {
628         case NSRightMouseDown:
629         {
630             var_Get( p_vout, "mouse-button-down", &val );
631             val.i_int |= 4;
632             var_Set( p_vout, "mouse-button-down", val );
633         }
634         break;
635
636         default:
637             [super mouseDown: o_event];
638         break;
639     }
640 }
641
642 - (void)mouseUp:(NSEvent *)o_event
643 {
644     vlc_value_t val;
645
646     switch( [o_event type] )
647     {
648         case NSLeftMouseUp:
649         {
650             vlc_value_t b_val;
651             b_val.b_bool = VLC_TRUE;
652             var_Set( p_vout, "mouse-clicked", b_val );
653
654             var_Get( p_vout, "mouse-button-down", &val );
655             val.i_int &= ~1;
656             var_Set( p_vout, "mouse-button-down", val );
657         }
658         break;
659
660         default:
661             [super mouseUp: o_event];
662         break;
663     }
664 }
665
666 - (void)otherMouseUp:(NSEvent *)o_event
667 {
668     vlc_value_t val;
669
670     switch( [o_event type] )
671     {
672         case NSOtherMouseUp:
673         {
674             var_Get( p_vout, "mouse-button-down", &val );
675             val.i_int &= ~2;
676             var_Set( p_vout, "mouse-button-down", val );
677         }
678         break;
679
680         default:
681             [super mouseUp: o_event];
682         break;
683     }
684 }
685
686 - (void)rightMouseUp:(NSEvent *)o_event
687 {
688     vlc_value_t val;
689
690     switch( [o_event type] )
691     {
692         case NSRightMouseUp:
693         {
694             var_Get( p_vout, "mouse-button-down", &val );
695             val.i_int &= ~4;
696             var_Set( p_vout, "mouse-button-down", val );
697         }
698         break;
699
700         default:
701             [super mouseUp: o_event];
702         break;
703     }
704 }
705
706 - (void)mouseDragged:(NSEvent *)o_event
707 {
708     [self mouseMoved: o_event];
709 }
710
711 - (void)otherMouseDragged:(NSEvent *)o_event
712 {
713     [self mouseMoved: o_event];
714 }
715
716 - (void)rightMouseDragged:(NSEvent *)o_event
717 {
718     [self mouseMoved: o_event];
719 }
720
721 - (void)mouseMoved:(NSEvent *)o_event
722 {   
723     NSPoint ml;
724     NSRect s_rect;
725     BOOL b_inside;
726
727     i_time_mouse_last_moved = mdate();
728
729     s_rect = [o_view bounds];
730     ml = [o_view convertPoint: [o_event locationInWindow] fromView: nil];
731     b_inside = [o_view mouse: ml inRect: s_rect];
732
733     if( b_inside )
734     {
735         vlc_value_t val;
736         unsigned int i_width, i_height, i_x, i_y;
737
738         vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
739                                    (unsigned int)s_rect.size.height,
740                                    &i_x, &i_y, &i_width, &i_height );
741
742         val.i_int = ( ((int)ml.x) - i_x ) *  
743                     p_vout->render.i_width / i_width;
744         var_Set( p_vout, "mouse-x", val );
745
746         if( [[o_view className] isEqualToString: @"VLCGLView"] )
747         {
748             val.i_int = ( ((int)(s_rect.size.height - ml.y)) - i_y ) *
749                         p_vout->render.i_height / i_height;
750         }
751         else
752         {
753             val.i_int = ( ((int)ml.y) - i_y ) * 
754                         p_vout->render.i_height / i_height;
755         }
756         var_Set( p_vout, "mouse-y", val );
757             
758         val.b_bool = VLC_TRUE;
759         var_Set( p_vout, "mouse-moved", val ); 
760     }
761
762     [super mouseMoved: o_event];
763 }
764
765 @end