]> git.sesse.net Git - vlc/blob - modules/gui/macosx/vout.m
* Update copyright to 2005 where appropriate.
[vlc] / modules / gui / macosx / vout.m
1 /*****************************************************************************
2  * vout.m: MacOS X video output module
3  *****************************************************************************
4  * Copyright (C) 2001-2005 VideoLAN
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  * VLCWindow implementation
45  *****************************************************************************/
46 @implementation VLCWindow
47
48 - (id)initWithVout:(vout_thread_t *)_p_vout frame:(NSRect *)s_frame
49 {
50     int i_timeout;
51
52     p_vout = _p_vout;
53
54     /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
55     for( i_timeout = 20 ; i_timeout-- ; )
56     {
57         if( NSApp == NULL )
58         {
59             msleep( INTF_IDLE_SLEEP );
60         }
61     }
62
63     if( NSApp == NULL )
64     {
65         /* No MacOS X intf, unable to communicate with MT */
66         msg_Err( p_vout, "no MacOS X interface present" );
67         return NULL;
68     }                                                                           
69
70     /* p_real_vout: the vout we have to use to check for video-on-top
71        and a few other things. If we are the QuickTime output, it's us.
72        It we are the OpenGL provider, it is our parent. */
73     if( p_vout->i_object_type == VLC_OBJECT_OPENGL )
74     {
75         p_real_vout = (vout_thread_t *) p_vout->p_parent;
76     }
77     else
78     {
79         p_real_vout = p_vout;
80     }
81
82     p_fullscreen_state = NULL;
83     i_time_mouse_last_moved = mdate();
84
85     NSScreen * o_screen;
86     vlc_bool_t b_main_screen;
87
88     var_Create( p_vout, "macosx-vdev", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
89     var_Create( p_vout, "macosx-fill", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
90     var_Create( p_vout, "macosx-stretch", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
91     var_Create( p_vout, "macosx-opaqueness", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
92
93     /* Setup the menuitem for the multiple displays. Read the vlc preference (macosx-vdev) for the primary display */
94     NSArray * o_screens = [NSScreen screens];
95     if( [o_screens count] > 0 && var_Type( p_real_vout, "video-device" ) == 0 )
96     {
97         int i = 1;
98         vlc_value_t val, val2, text;
99         NSScreen * o_screen;
100
101         var_Get( p_real_vout, "macosx-vdev", &val );
102
103         var_Create( p_real_vout, "video-device", VLC_VAR_INTEGER |
104                                             VLC_VAR_HASCHOICE );
105         text.psz_string = _("Video device");
106         var_Change( p_real_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
107
108         NSEnumerator * o_enumerator = [o_screens objectEnumerator];
109
110         while( (o_screen = [o_enumerator nextObject]) != NULL )
111         {
112             char psz_temp[255];
113             NSRect s_rect = [o_screen frame];
114
115             snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1,
116                       "%s %d (%dx%d)", _("Screen"), i,
117                       (int)s_rect.size.width, (int)s_rect.size.height );
118
119             text.psz_string = psz_temp;
120             val2.i_int = i;
121             var_Change( p_real_vout, "video-device",
122                         VLC_VAR_ADDCHOICE, &val2, &text );
123
124             if( ( i - 1 ) == val.i_int )
125             {
126                 var_Set( p_real_vout, "video-device", val2 );
127             }
128             i++;
129         }
130
131         var_AddCallback( p_real_vout, "video-device", vout_VarCallback,
132                          NULL );
133
134         val2.b_bool = VLC_TRUE;
135         var_Set( p_real_vout, "intf-change", val2 );
136     }
137
138     /* Find out on which screen to open the window */
139     int i_device = var_GetInteger( p_real_vout, "video-device" );
140     if( i_device < 0 )
141     {
142          /* No preference specified. Use the main screen */
143         o_screen = [NSScreen mainScreen];
144         b_main_screen = 1;
145     }
146     else
147     {
148         NSArray *o_screens = [NSScreen screens];
149         
150         if( [o_screens count] < (unsigned) i_device )
151         {
152             o_screen = [NSScreen mainScreen];
153             b_main_screen = 1;
154         }
155         else
156         {
157             i_device--;
158             o_screen = [o_screens objectAtIndex: i_device];
159             var_SetInteger( p_real_vout, "macosx-vdev", i_device );
160             b_main_screen = ( i_device == 0 );
161         }
162     }
163
164     NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
165
166     if( p_vout->b_fullscreen )
167     {
168         NSRect screen_rect = [o_screen frame];
169         screen_rect.origin.x = screen_rect.origin.y = 0;
170
171         /* Creates a window with size: screen_rect on o_screen */
172         [self initWithContentRect: screen_rect
173               styleMask: NSBorderlessWindowMask
174               backing: NSBackingStoreBuffered
175               defer: YES screen: o_screen];
176
177         if( b_main_screen )
178         {
179             BeginFullScreen( &p_fullscreen_state, NULL, 0, 0,
180                              NULL, NULL, fullScreenAllowEvents );
181         }
182     }
183     else
184     {
185         unsigned int i_stylemask = NSTitledWindowMask |
186                                    NSMiniaturizableWindowMask |
187                                    NSClosableWindowMask |
188                                    NSResizableWindowMask;
189
190         NSRect s_rect;
191         if( !s_frame )
192         {
193             s_rect.size.width  = p_vout->i_window_width;
194             s_rect.size.height = p_vout->i_window_height;
195         }
196         else
197         {
198             s_rect = *s_frame;
199         }
200        
201         [self initWithContentRect: s_rect
202               styleMask: i_stylemask
203               backing: NSBackingStoreBuffered
204               defer: YES screen: o_screen];
205
206         [self setAlphaValue: var_GetFloat( p_vout, "macosx-opaqueness" )];
207
208         if( var_GetBool( p_real_vout, "video-on-top" ) )
209         {
210             [self setLevel: NSStatusWindowLevel];
211         }
212
213         if( !s_frame )
214         {
215             [self center];
216         }
217     }
218
219     [self updateTitle];
220     [self makeKeyAndOrderFront: nil];
221     [self setReleasedWhenClosed: YES];
222
223     /* We'll catch mouse events */
224     [self setAcceptsMouseMovedEvents: YES];
225     [self makeFirstResponder: self];
226     
227     [o_pool release];
228     return self;
229 }
230
231 - (void)close
232 {
233     if( p_fullscreen_state )
234     {
235         EndFullScreen( p_fullscreen_state, 0 );
236     }
237     [super close];
238 }
239
240 - (void)setOnTop:(BOOL)b_on_top
241 {
242     if( b_on_top )
243     {
244         [self setLevel: NSStatusWindowLevel];
245     }
246     else
247     {
248         [self setLevel: NSNormalWindowLevel];
249     }
250 }
251
252 - (void)hideMouse:(BOOL)b_hide
253 {
254     BOOL b_inside;
255     NSPoint ml;
256     NSView *o_contents = [self contentView];
257     
258     ml = [self convertScreenToBase:[NSEvent mouseLocation]];
259     ml = [o_contents convertPoint:ml fromView:nil];
260     b_inside = [o_contents mouse: ml inRect: [o_contents bounds]];
261     
262     if( b_hide && b_inside )
263     {
264         [NSCursor setHiddenUntilMouseMoves: YES];
265     }
266     else if( !b_hide )
267     {
268         [NSCursor setHiddenUntilMouseMoves: NO];
269     }
270 }
271
272 - (void)manage
273 {
274     if( p_fullscreen_state )
275     {
276         if( mdate() - i_time_mouse_last_moved > 3000000 )
277         {
278             [self hideMouse: YES];
279         }
280     }
281     else
282     {
283         [self hideMouse: NO];
284     }
285
286     /* Disable screensaver */
287     UpdateSystemActivity( UsrActivity );
288 }
289
290 - (void)scaleWindowWithFactor: (float)factor
291 {
292     NSSize newsize;
293     int i_corrected_height, i_corrected_width;
294     NSPoint topleftbase;
295     NSPoint topleftscreen;
296     
297     if ( !p_vout->b_fullscreen )
298     {
299         topleftbase.x = 0;
300         topleftbase.y = [self frame].size.height;
301         topleftscreen = [self convertBaseToScreen: topleftbase];
302         
303         if( p_vout->render.i_height * p_vout->render.i_aspect > 
304                         p_vout->render.i_width * VOUT_ASPECT_FACTOR )
305         {
306             i_corrected_width = p_vout->render.i_height * p_vout->render.i_aspect /
307                                             VOUT_ASPECT_FACTOR;
308             newsize.width = (int) ( i_corrected_width * factor );
309             newsize.height = (int) ( p_vout->render.i_height * factor );
310         }
311         else
312         {
313             i_corrected_height = p_vout->render.i_width * VOUT_ASPECT_FACTOR /
314                                             p_vout->render.i_aspect;
315             newsize.width = (int) ( p_vout->render.i_width * factor );
316             newsize.height = (int) ( i_corrected_height * factor );
317         }
318     
319         [self setContentSize: newsize];
320         
321         [self setFrameTopLeftPoint: topleftscreen];
322         p_vout->i_changes |= VOUT_SIZE_CHANGE;
323     }
324 }
325
326 - (void)toggleFloatOnTop
327 {
328     vlc_value_t val;
329
330     if( var_Get( p_vout, "video-on-top", &val )>=0 && val.b_bool)
331     {
332         val.b_bool = VLC_FALSE;
333     }
334     else
335     {
336         val.b_bool = VLC_TRUE;
337     }
338     var_Set( p_vout, "video-on-top", val );
339 }
340
341 - (void)toggleFullscreen
342 {
343     vlc_value_t val;
344     val.b_bool = !p_real_vout->b_fullscreen;
345     var_Set( p_real_vout, "fullscreen", val );
346 }
347
348 - (BOOL)isFullscreen
349 {
350     return( p_vout->b_fullscreen );
351 }
352
353 - (void)snapshot
354 {
355     vout_Control( p_vout, VOUT_SNAPSHOT );
356 }
357
358 - (BOOL)canBecomeKeyWindow
359 {
360     return YES;
361 }
362
363 /* Sometimes crashes VLC....
364 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
365 {
366         return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event];
367 }*/
368
369 - (void)keyDown:(NSEvent *)o_event
370 {
371     unichar key = 0;
372     vlc_value_t val;
373     unsigned int i_pressed_modifiers = 0;
374     val.i_int = 0;
375     
376     i_pressed_modifiers = [o_event modifierFlags];
377
378     if( i_pressed_modifiers & NSShiftKeyMask )
379         val.i_int |= KEY_MODIFIER_SHIFT;
380     if( i_pressed_modifiers & NSControlKeyMask )
381         val.i_int |= KEY_MODIFIER_CTRL;
382     if( i_pressed_modifiers & NSAlternateKeyMask )
383         val.i_int |= KEY_MODIFIER_ALT;
384     if( i_pressed_modifiers & NSCommandKeyMask )
385         val.i_int |= KEY_MODIFIER_COMMAND;
386
387     key = [[o_event charactersIgnoringModifiers] characterAtIndex: 0];
388
389     if( key )
390     {
391         /* Escape should always get you out of fullscreen */
392         if( key == (unichar) 0x1b )
393         {
394              if( [self isFullscreen] )
395              {
396                  [self toggleFullscreen];
397              }
398         }
399         else if ( key == ' ' )
400         {
401             vlc_value_t val;
402             val.i_int = config_GetInt( p_vout, "key-play-pause" );
403             var_Set( p_vout->p_vlc, "key-pressed", val );
404         }
405         else
406         {
407             val.i_int |= CocoaKeyToVLC( key );
408             var_Set( p_vout->p_vlc, "key-pressed", val );
409         }
410     }
411     else
412     {
413         [super keyDown: o_event];
414     }
415 }
416
417 - (void)updateTitle
418 {
419     NSMutableString * o_title,* o_mrl;
420     vlc_bool_t b_file = VLC_FALSE;
421     input_thread_t * p_input;
422     
423     if( p_vout == NULL )
424     {
425         return;
426     }
427     
428     p_input = vlc_object_find( p_vout, VLC_OBJECT_INPUT,
429                                                 FIND_PARENT );
430     
431     if( p_input == NULL )
432     {
433         return;
434     }
435
436     if( ! strcmp( p_input->input.p_access->p_module->psz_shortname, "File" ) )
437         b_file = VLC_TRUE;
438     if( p_input->input.p_item->psz_name != NULL )
439         o_title = [NSMutableString stringWithUTF8String:
440             p_input->input.p_item->psz_name];
441     if( p_input->input.p_item->psz_uri != NULL )
442         o_mrl = [NSMutableString stringWithUTF8String:
443             p_input->input.p_item->psz_uri];
444     if( o_title == nil )
445         o_title = o_mrl;
446
447     vlc_object_release( p_input );
448     if( o_mrl != nil )
449     {
450         if( b_file == VLC_TRUE )
451         {
452             NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
453             if( prefix_range.location != NSNotFound )
454                 [o_mrl deleteCharactersInRange: prefix_range];
455             [self setRepresentedFilename: o_mrl];
456         }
457         [self setTitle: o_title];
458     }
459     else
460     {
461         [self setTitle: [NSString stringWithCString: VOUT_TITLE]];
462     }
463 }
464
465 /* This is actually the same as VLCControls::stop. */
466 - (BOOL)windowShouldClose:(id)sender
467 {
468     playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
469                                                        FIND_ANYWHERE );
470     if( p_playlist == NULL )      
471     {
472         return NO;
473     }
474
475     playlist_Stop( p_playlist );
476     vlc_object_release( p_playlist );
477
478     /* The window will be closed by the intf later. */
479     return NO;
480 }
481
482 - (BOOL)acceptsFirstResponder
483 {
484     return YES;
485 }
486
487 - (BOOL)becomeFirstResponder
488 {
489     return YES;
490 }
491
492 - (BOOL)resignFirstResponder
493 {
494     /* We need to stay the first responder or we'll miss some
495        events */
496     return NO;
497 }
498
499 - (void)mouseDown:(NSEvent *)o_event
500 {
501     vlc_value_t val;
502
503     switch( [o_event type] )
504     {
505         case NSLeftMouseDown:
506         {
507             var_Get( p_vout, "mouse-button-down", &val );
508             val.i_int |= 1;
509             var_Set( p_vout, "mouse-button-down", val );
510         }
511         break;
512
513         default:
514             [super mouseDown: o_event];
515         break;
516     }
517 }
518
519 - (void)otherMouseDown:(NSEvent *)o_event
520 {
521     vlc_value_t val;
522
523     switch( [o_event type] )
524     {
525         case NSOtherMouseDown:
526         {
527             var_Get( p_vout, "mouse-button-down", &val );
528             val.i_int |= 2;
529             var_Set( p_vout, "mouse-button-down", val );
530         }
531         break;
532
533         default:
534             [super mouseDown: o_event];
535         break;
536     }
537 }
538
539 - (void)rightMouseDown:(NSEvent *)o_event
540 {
541     vlc_value_t val;
542
543     switch( [o_event type] )
544     {
545         case NSRightMouseDown:
546         {
547             var_Get( p_vout, "mouse-button-down", &val );
548             val.i_int |= 4;
549             var_Set( p_vout, "mouse-button-down", val );
550         }
551         break;
552
553         default:
554             [super mouseDown: o_event];
555         break;
556     }
557 }
558
559 - (void)mouseUp:(NSEvent *)o_event
560 {
561     vlc_value_t val;
562
563     switch( [o_event type] )
564     {
565         case NSLeftMouseUp:
566         {
567             vlc_value_t b_val;
568             b_val.b_bool = VLC_TRUE;
569             var_Set( p_vout, "mouse-clicked", b_val );
570
571             var_Get( p_vout, "mouse-button-down", &val );
572             val.i_int &= ~1;
573             var_Set( p_vout, "mouse-button-down", val );
574         }
575         break;
576
577         default:
578             [super mouseUp: o_event];
579         break;
580     }
581 }
582
583 - (void)otherMouseUp:(NSEvent *)o_event
584 {
585     vlc_value_t val;
586
587     switch( [o_event type] )
588     {
589         case NSOtherMouseUp:
590         {
591             var_Get( p_vout, "mouse-button-down", &val );
592             val.i_int &= ~2;
593             var_Set( p_vout, "mouse-button-down", val );
594         }
595         break;
596
597         default:
598             [super mouseUp: o_event];
599         break;
600     }
601 }
602
603 - (void)rightMouseUp:(NSEvent *)o_event
604 {
605     vlc_value_t val;
606
607     switch( [o_event type] )
608     {
609         case NSRightMouseUp:
610         {
611             var_Get( p_vout, "mouse-button-down", &val );
612             val.i_int &= ~4;
613             var_Set( p_vout, "mouse-button-down", val );
614         }
615         break;
616
617         default:
618             [super mouseUp: o_event];
619         break;
620     }
621 }
622
623 - (void)mouseDragged:(NSEvent *)o_event
624 {
625     [self mouseMoved: o_event];
626 }
627
628 - (void)otherMouseDragged:(NSEvent *)o_event
629 {
630     [self mouseMoved: o_event];
631 }
632
633 - (void)rightMouseDragged:(NSEvent *)o_event
634 {
635     [self mouseMoved: o_event];
636 }
637
638 - (void)mouseMoved:(NSEvent *)o_event
639 {   
640     NSPoint ml;
641     NSRect s_rect;
642     BOOL b_inside;
643     NSView * o_view;
644
645     i_time_mouse_last_moved = mdate();
646
647     o_view = [self contentView];
648     s_rect = [o_view bounds];
649     ml = [o_view convertPoint: [o_event locationInWindow] fromView: nil];
650     b_inside = [o_view mouse: ml inRect: s_rect];
651
652     if( b_inside )
653     {
654         vlc_value_t val;
655         int i_width, i_height, i_x, i_y;
656
657         vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
658                                    (unsigned int)s_rect.size.height,
659                                    &i_x, &i_y, &i_width, &i_height );
660
661         val.i_int = ( ((int)ml.x) - i_x ) *  
662                     p_vout->render.i_width / i_width;
663         var_Set( p_vout, "mouse-x", val );
664
665         if( [[o_view className] isEqualToString: @"VLCGLView"] )
666         {
667             val.i_int = ( ((int)(s_rect.size.height - ml.y)) - i_y ) *
668                         p_vout->render.i_height / i_height;
669         }
670         else
671         {
672             val.i_int = ( ((int)ml.y) - i_y ) * 
673                         p_vout->render.i_height / i_height;
674         }
675         var_Set( p_vout, "mouse-y", val );
676             
677         val.b_bool = VLC_TRUE;
678         var_Set( p_vout, "mouse-moved", val ); 
679     }
680
681     [super mouseMoved: o_event];
682 }
683
684 @end