c4be345295821437e8cc1558fcca7773650743d5
[vlc.git] / modules / gui / macosx / intf.m
1 /*****************************************************************************
2  * intf.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2002-2015 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Derk-Jan Hartman <hartman at videolan.org>
8  *          Felix Paul K├╝hne <fkuehne at videolan dot org>
9  *          Pierre d'Herbemont <pdherbemont # videolan org>
10  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #import "intf.h"
35
36 #include <stdlib.h>                                      /* malloc(), free() */
37 #include <string.h>
38 #include <vlc_common.h>
39 #include <vlc_atomic.h>
40 #include <vlc_keys.h>
41 #include <vlc_dialog.h>
42 #include <vlc_url.h>
43 #include <vlc_variables.h>
44
45 #include <unistd.h> /* execl() */
46
47 #import "CompatibilityFixes.h"
48 #import "InputManager.h"
49 #import "MainMenu.h"
50 #import "VideoView.h"
51 #import "prefs.h"
52 #import "playlist.h"
53 #import "playlistinfo.h"
54 #import "open.h"
55 #import "bookmarks.h"
56 #import "coredialogs.h"
57 #import "simple_prefs.h"
58 #import "CoreInteraction.h"
59 #import "TrackSynchronization.h"
60 #import "ExtensionsManager.h"
61 #import "BWQuincyManager.h"
62 #import "ResumeDialogController.h"
63 #import "DebugMessageVisualizer.h"
64 #import "ConvertAndSave.h"
65
66 #import "VideoEffects.h"
67 #import "AudioEffects.h"
68 #import "intf-prefs.h"
69
70 #ifdef HAVE_SPARKLE
71 #import <Sparkle/Sparkle.h>                 /* we're the update delegate */
72 #endif
73
74 #pragma mark -
75 #pragma mark VLC Interface Object Callbacks
76
77 /*****************************************************************************
78  * OpenIntf: initialize interface
79  *****************************************************************************/
80 int OpenIntf (vlc_object_t *p_this)
81 {
82     @autoreleasepool {
83         intf_thread_t *p_intf = (intf_thread_t*) p_this;
84         msg_Dbg(p_intf, "Starting macosx interface");
85
86         [VLCApplication sharedApplication];
87         [VLCMain createInstanceWithIntf: p_intf];
88
89         [NSBundle loadNibNamed:@"MainMenu" owner:[[VLCMain sharedInstance] mainMenu]];
90         [[[VLCMain sharedInstance] mainWindow] makeKeyAndOrderFront:nil];
91
92         return VLC_SUCCESS;
93     }
94 }
95
96 void CloseIntf (vlc_object_t *p_this)
97 {
98     @autoreleasepool {
99         msg_Dbg(p_this, "Closing macosx interface");
100         [[VLCMain sharedInstance] applicationWillTerminate:nil];
101         [VLCMain killInstance];
102     }
103 }
104
105 #pragma mark -
106 #pragma mark Variables Callback
107
108 /*****************************************************************************
109  * ShowController: Callback triggered by the show-intf playlist variable
110  * through the ShowIntf-control-intf, to let us show the controller-win;
111  * usually when in fullscreen-mode
112  *****************************************************************************/
113 static int ShowController(vlc_object_t *p_this, const char *psz_variable,
114                      vlc_value_t old_val, vlc_value_t new_val, void *param)
115 {
116     @autoreleasepool {
117         dispatch_async(dispatch_get_main_queue(), ^{
118
119             intf_thread_t * p_intf = VLCIntf;
120             if (p_intf) {
121                 playlist_t * p_playlist = pl_Get(p_intf);
122                 BOOL b_fullscreen = var_GetBool(p_playlist, "fullscreen");
123                 if (b_fullscreen)
124                     [[VLCMain sharedInstance] showFullscreenController];
125
126                 else if (!strcmp(psz_variable, "intf-show"))
127                     [[[VLCMain sharedInstance] mainWindow] makeKeyAndOrderFront:nil];
128             }
129
130         });
131
132         return VLC_SUCCESS;
133     }
134 }
135
136 #pragma mark -
137 #pragma mark Private
138
139 @interface VLCMain () <BWQuincyManagerDelegate>
140 {
141     intf_thread_t *p_intf;
142     BOOL launched;
143     int items_at_launch;
144
145     BOOL nib_about_loaded;      /* about nibfile */
146     BOOL nib_coredialogs_loaded; /* CoreDialogs nibfile */
147     BOOL b_active_videoplayback;
148
149     NSWindowController *_mainWindowController;
150
151     VLCMainMenu *_mainmenu;
152     VLCPrefs *_prefs;
153     VLCSimplePrefs *_sprefs;
154     VLCOpen *_open;
155     VLCCoreDialogProvider *_coredialogs;
156     VLCBookmarks *_bookmarks;
157     VLCCoreInteraction *_coreinteraction;
158     ResumeDialogController *_resume_dialog;
159     VLCInputManager *_input_manager;
160     VLCPlaylist *_playlist;
161     VLCDebugMessageVisualizer *_messagePanelController;
162     VLCTrackSynchronization *_trackSyncPanel;
163     VLCAudioEffects *_audioEffectsPanel;
164     VLCVideoEffects *_videoEffectsPanel;
165     VLCConvertAndSave *_convertAndSaveWindow;
166
167     bool b_intf_terminating; /* Makes sure applicationWillTerminate will be called only once */
168 }
169
170 @end
171
172 /*****************************************************************************
173  * VLCMain implementation
174  *****************************************************************************/
175 @implementation VLCMain
176
177 #pragma mark -
178 #pragma mark Initialization
179
180 static VLCMain *sharedInstance = nil;
181
182 + (VLCMain *)sharedInstance
183 {
184     return sharedInstance;
185 }
186
187 + (VLCMain *)createInstanceWithIntf:(intf_thread_t *)intf;
188 {
189     static dispatch_once_t pred;
190
191     dispatch_once(&pred, ^{
192         sharedInstance = [[VLCMain alloc] initWithIntf:intf];
193     });
194
195     return sharedInstance;
196 }
197
198 + (void)killInstance
199 {
200     sharedInstance = nil;
201 }
202
203 - (id)initWithIntf:(intf_thread_t *)intf;
204 {
205     self = [super init];
206     if (self) {
207         p_intf = intf;
208
209         [VLCApplication sharedApplication].delegate = self;
210
211         _mainmenu = [[VLCMainMenu alloc] init];
212         _voutController = [[VLCVoutWindowController alloc] init];
213         _playlist = [[VLCPlaylist alloc] init];
214
215         _mainWindowController = [[NSWindowController alloc] initWithWindowNibName:@"MainWindow"];
216
217         var_Create(p_intf, "intf-change", VLC_VAR_BOOL);
218
219         _input_manager = [[VLCInputManager alloc] initWithMain:self];
220
221         var_AddCallback(p_intf->p_libvlc, "intf-toggle-fscontrol", ShowController, (__bridge void *)self);
222         var_AddCallback(p_intf->p_libvlc, "intf-show", ShowController, (__bridge void *)self);
223
224         playlist_t *p_playlist = pl_Get(p_intf);
225         if ([NSApp currentSystemPresentationOptions] & NSApplicationPresentationFullScreen)
226             var_SetBool(p_playlist, "fullscreen", YES);
227
228         _nativeFullscreenMode = var_InheritBool(p_intf, "macosx-nativefullscreenmode");
229
230         if (var_InheritInteger(p_intf, "macosx-icon-change")) {
231             /* After day 354 of the year, the usual VLC cone is replaced by another cone
232              * wearing a Father Xmas hat.
233              * Note: this icon doesn't represent an endorsement of The Coca-Cola Company.
234              */
235             NSCalendar *gregorian =
236             [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
237             NSUInteger dayOfYear = [gregorian ordinalityOfUnit:NSDayCalendarUnit inUnit:NSYearCalendarUnit forDate:[NSDate date]];
238
239             if (dayOfYear >= 354)
240                 [[VLCApplication sharedApplication] setApplicationIconImage: [NSImage imageNamed:@"vlc-xmas"]];
241         }
242
243         /* announce our launch to a potential eyetv plugin */
244         [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"VLCOSXGUIInit"
245                                                                        object: @"VLCEyeTVSupport"
246                                                                      userInfo: NULL
247                                                            deliverImmediately: YES];
248
249     }
250
251     return self;
252 }
253
254 - (intf_thread_t *)intf
255 {
256     return p_intf;
257 }
258
259 - (void)applicationWillFinishLaunching:(NSNotification *)aNotification
260 {
261     _coreinteraction = [VLCCoreInteraction sharedInstance];
262
263     playlist_t * p_playlist = pl_Get(VLCIntf);
264     PL_LOCK;
265     items_at_launch = p_playlist->p_local_category->i_children;
266     PL_UNLOCK;
267
268 #ifdef HAVE_SPARKLE
269     [[SUUpdater sharedUpdater] setDelegate:self];
270 #endif
271 }
272
273 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
274 {
275     launched = YES;
276
277     if (!p_intf)
278         return;
279
280     NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] valueForKey: @"CFBundleVersion"];
281     NSRange endRande = [appVersion rangeOfString:@"-"];
282     if (endRande.location != NSNotFound)
283         appVersion = [appVersion substringToIndex:endRande.location];
284
285     BWQuincyManager *quincyManager = [BWQuincyManager sharedQuincyManager];
286     [quincyManager setApplicationVersion:appVersion];
287     [quincyManager setSubmissionURL:@"http://crash.videolan.org/crash_v200.php"];
288     [quincyManager setDelegate:self];
289     [quincyManager setCompanyName:@"VideoLAN"];
290
291     [_coreinteraction updateCurrentlyUsedHotkeys];
292
293     [self removeOldPreferences];
294
295     /* Handle sleep notification */
296     [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(computerWillSleep:)
297            name:NSWorkspaceWillSleepNotification object:nil];
298
299     /* update the main window */
300     [[self mainWindow] updateWindow];
301     [[self mainWindow] updateTimeSlider];
302     [[self mainWindow] updateVolumeSlider];
303
304     /* Hack: Playlist is started before the interface.
305      * Thus, call additional updaters as we might miss these events if posted before
306      * the callbacks are registered.
307      */
308     [_input_manager inputThreadChanged];
309     [_playlist playbackModeUpdated];
310
311     // respect playlist-autostart
312     // note that PLAYLIST_PLAY will not stop any playback if already started
313     playlist_t * p_playlist = pl_Get(VLCIntf);
314     PL_LOCK;
315     BOOL kidsAround = p_playlist->p_local_category->i_children != 0;
316     if (kidsAround && var_GetBool(p_playlist, "playlist-autostart"))
317         playlist_Control(p_playlist, PLAYLIST_PLAY, true);
318     PL_UNLOCK;
319 }
320
321 #pragma mark -
322 #pragma mark Termination
323
324 - (BOOL)isTerminating
325 {
326     return b_intf_terminating;
327 }
328
329 - (void)applicationWillTerminate:(NSNotification *)notification
330 {
331     if (b_intf_terminating)
332         return;
333     b_intf_terminating = true;
334
335     [_input_manager resumeItunesPlayback:nil];
336
337     if (notification == nil)
338         [[NSNotificationCenter defaultCenter] postNotificationName: NSApplicationWillTerminateNotification object: nil];
339
340     playlist_t * p_playlist = pl_Get(p_intf);
341
342     /* save current video and audio profiles */
343     [[self videoEffectsPanel] saveCurrentProfile];
344     [[self audioEffectsPanel] saveCurrentProfile];
345
346     /* Save some interface state in configuration, at module quit */
347     config_PutInt(p_intf, "random", var_GetBool(p_playlist, "random"));
348     config_PutInt(p_intf, "loop", var_GetBool(p_playlist, "loop"));
349     config_PutInt(p_intf, "repeat", var_GetBool(p_playlist, "repeat"));
350
351     msg_Dbg(p_intf, "Terminating");
352
353     var_DelCallback(p_intf->p_libvlc, "intf-toggle-fscontrol", ShowController, (__bridge void *)self);
354     var_DelCallback(p_intf->p_libvlc, "intf-show", ShowController, (__bridge void *)self);
355
356     [[NSNotificationCenter defaultCenter] removeObserver: self];
357
358     [_voutController.lock lock];
359     // closes all open vouts
360     _voutController = nil;
361     [_voutController.lock unlock];
362
363     /* unsubscribe from libvlc's debug messages */
364     vlc_LogSet(p_intf->p_libvlc, NULL, NULL);
365
366     /* write cached user defaults to disk */
367     [[NSUserDefaults standardUserDefaults] synchronize];
368 }
369
370 #pragma mark -
371 #pragma mark Sparkle delegate
372
373 #ifdef HAVE_SPARKLE
374 /* received directly before the update gets installed, so let's shut down a bit */
375 - (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
376 {
377     [NSApp activateIgnoringOtherApps:YES];
378     [_coreinteraction stopListeningWithAppleRemote];
379     [_coreinteraction stop];
380 }
381
382 /* don't be enthusiastic about an update if we currently play a video */
383 - (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
384 {
385     if ([self activeVideoPlayback])
386         return NO;
387
388     return YES;
389 }
390 #endif
391
392 #pragma mark -
393 #pragma mark Other notification
394
395 /* Listen to the remote in exclusive mode, only when VLC is the active
396    application */
397 - (void)applicationDidBecomeActive:(NSNotification *)aNotification
398 {
399     if (!p_intf)
400         return;
401     if (var_InheritBool(p_intf, "macosx-appleremote") == YES)
402         [_coreinteraction startListeningWithAppleRemote];
403 }
404 - (void)applicationDidResignActive:(NSNotification *)aNotification
405 {
406     if (!p_intf)
407         return;
408     [_coreinteraction stopListeningWithAppleRemote];
409 }
410
411 /* Triggered when the computer goes to sleep */
412 - (void)computerWillSleep: (NSNotification *)notification
413 {
414     [_coreinteraction pause];
415 }
416
417 #pragma mark -
418 #pragma mark File opening over dock icon
419
420 - (void)application:(NSApplication *)o_app openFiles:(NSArray *)o_names
421 {
422     // Only add items here which are getting dropped to to the application icon
423     // or are given at startup. If a file is passed via command line, libvlccore
424     // will add the item, but cocoa also calls this function. In this case, the
425     // invocation is ignored here.
426     if (launched == NO) {
427         if (items_at_launch) {
428             int items = [o_names count];
429             if (items > items_at_launch)
430                 items_at_launch = 0;
431             else
432                 items_at_launch -= items;
433             return;
434         }
435     }
436
437     char *psz_uri = vlc_path2uri([[o_names firstObject] UTF8String], NULL);
438
439     // try to add file as subtitle
440     if ([o_names count] == 1 && psz_uri) {
441         input_thread_t * p_input = pl_CurrentInput(VLCIntf);
442         if (p_input) {
443             int i_result = input_AddSubtitleOSD(p_input, [[o_names firstObject] UTF8String], true, true);
444             vlc_object_release(p_input);
445             if (i_result == VLC_SUCCESS) {
446                 free(psz_uri);
447                 return;
448             }
449         }
450     }
451     free(psz_uri);
452
453     NSArray *o_sorted_names = [o_names sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
454     NSMutableArray *o_result = [NSMutableArray arrayWithCapacity: [o_sorted_names count]];
455     for (NSUInteger i = 0; i < [o_sorted_names count]; i++) {
456         psz_uri = vlc_path2uri([[o_sorted_names objectAtIndex:i] UTF8String], "file");
457         if (!psz_uri)
458             continue;
459
460         NSDictionary *o_dic = [NSDictionary dictionaryWithObject:toNSStr(psz_uri) forKey:@"ITEM_URL"];
461         free(psz_uri);
462         [o_result addObject: o_dic];
463     }
464
465     [[[VLCMain sharedInstance] playlist] addPlaylistItems:o_result];
466 }
467
468 /* When user click in the Dock icon our double click in the finder */
469 - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)hasVisibleWindows
470 {
471     if (!hasVisibleWindows)
472         [[self mainWindow] makeKeyAndOrderFront:self];
473
474     return YES;
475 }
476
477 - (void)showFullscreenController
478 {
479     // defer selector here (possibly another time) to ensure that keyWindow is set properly
480     // (needed for NSApplicationDidBecomeActiveNotification)
481     [[self mainWindow] performSelectorOnMainThread:@selector(showFullscreenController) withObject:nil waitUntilDone:NO];
482 }
483
484 - (void)setActiveVideoPlayback:(BOOL)b_value
485 {
486     assert([NSThread isMainThread]);
487
488     b_active_videoplayback = b_value;
489     if ([self mainWindow]) {
490         [[self mainWindow] setVideoplayEnabled];
491     }
492
493     // update sleep blockers
494     [_input_manager playbackStatusUpdated];
495 }
496
497 #pragma mark -
498 #pragma mark Other objects getters
499
500 - (VLCMainMenu *)mainMenu
501 {
502     return _mainmenu;
503 }
504
505 - (VLCMainWindow *)mainWindow
506 {
507     return (VLCMainWindow *)[_mainWindowController window];
508 }
509
510 - (VLCInputManager *)inputManager
511 {
512     return _input_manager;
513 }
514
515 - (VLCDebugMessageVisualizer *)debugMsgPanel
516 {
517     if (!_messagePanelController)
518         _messagePanelController = [[VLCDebugMessageVisualizer alloc] init];
519
520     return _messagePanelController;
521 }
522
523 - (VLCTrackSynchronization *)trackSyncPanel
524 {
525     if (!_trackSyncPanel)
526         _trackSyncPanel = [[VLCTrackSynchronization alloc] init];
527
528     return _trackSyncPanel;
529 }
530
531 - (VLCAudioEffects *)audioEffectsPanel
532 {
533     if (!_audioEffectsPanel)
534         _audioEffectsPanel = [[VLCAudioEffects alloc] init];
535
536     return _audioEffectsPanel;
537 }
538
539 - (VLCVideoEffects *)videoEffectsPanel
540 {
541     if (!_videoEffectsPanel)
542         _videoEffectsPanel = [[VLCVideoEffects alloc] init];
543
544     return _videoEffectsPanel;
545 }
546
547 - (VLCBookmarks *)bookmarks
548 {
549     if (!_bookmarks)
550         _bookmarks = [[VLCBookmarks alloc] init];
551
552     return _bookmarks;
553 }
554
555 - (VLCOpen *)open
556 {
557     if (!_open)
558         _open = [[VLCOpen alloc] init];
559
560     return _open;
561 }
562
563 - (VLCConvertAndSave *)convertAndSaveWindow
564 {
565     if (_convertAndSaveWindow == nil)
566         _convertAndSaveWindow = [[VLCConvertAndSave alloc] init];
567
568     return _convertAndSaveWindow;
569 }
570
571 - (VLCSimplePrefs *)simplePreferences
572 {
573     if (!_sprefs)
574         _sprefs = [[VLCSimplePrefs alloc] init];
575
576     return _sprefs;
577 }
578
579 - (VLCPrefs *)preferences
580 {
581     if (!_prefs)
582         _prefs = [[VLCPrefs alloc] init];
583
584     return _prefs;
585 }
586
587 - (VLCPlaylist *)playlist
588 {
589     return _playlist;
590 }
591
592 - (VLCCoreDialogProvider *)coreDialogProvider
593 {
594     _coredialogs = [VLCCoreDialogProvider sharedInstance];
595     if (!nib_coredialogs_loaded) {
596         nib_coredialogs_loaded = [NSBundle loadNibNamed:@"CoreDialogs" owner: _coredialogs];
597     }
598
599     return _coredialogs;
600 }
601
602 - (ResumeDialogController *)resumeDialog
603 {
604     if (!_resume_dialog)
605         _resume_dialog = [[ResumeDialogController alloc] init];
606
607     return _resume_dialog;
608 }
609
610 - (BOOL)activeVideoPlayback
611 {
612     return b_active_videoplayback;
613 }
614
615 @end
616
617 /*****************************************************************************
618  * VLCApplication interface
619  *****************************************************************************/
620
621 @implementation VLCApplication
622 // when user selects the quit menu from dock it sends a terminate:
623 // but we need to send a stop: to properly exits libvlc.
624 // However, we are not able to change the action-method sent by this standard menu item.
625 // thus we override terminate: to send a stop:
626 // see [af97f24d528acab89969d6541d83f17ce1ecd580] that introduced the removal of setjmp() and longjmp()
627 - (void)terminate:(id)sender
628 {
629     [self activateIgnoringOtherApps:YES];
630     [self stop:sender];
631 }
632
633 @end