41da11dd1a977d23fb99c73b559b5d3e7bf9ebc7
[vlc.git] / modules / gui / macosx / VLCMain.m
1 /*****************************************************************************
2  * VLCMain.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2002-2016 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 "VLCMain.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 #import "CompatibilityFixes.h"
46 #import "VLCInputManager.h"
47 #import "VLCMainMenu.h"
48 #import "VLCVoutView.h"
49 #import "prefs.h"
50 #import "VLCPlaylist.h"
51 #import "VLCPlaylistInfo.h"
52 #import "VLCPlaylistInfo.h"
53 #import "VLCOpenWindowController.h"
54 #import "VLCBookmarksWindowController.h"
55 #import "VLCCoreDialogProvider.h"
56 #import "VLCSimplePrefsController.h"
57 #import "VLCCoreInteraction.h"
58 #import "VLCTrackSynchronizationWindowController.h"
59 #import "VLCExtensionsManager.h"
60 #import "VLCResumeDialogController.h"
61 #import "VLCDebugMessageWindowController.h"
62 #import "VLCConvertAndSaveWindowController.h"
63
64 #import "VLCVideoEffectsWindowController.h"
65 #import "VLCAudioEffectsWindowController.h"
66 #import "VLCMain+OldPrefs.h"
67 #import "VLCApplication.h"
68
69 #ifdef HAVE_SPARKLE
70 #import <Sparkle/Sparkle.h>                 /* we're the update delegate */
71 #endif
72
73 #pragma mark -
74 #pragma mark VLC Interface Object Callbacks
75
76 /*****************************************************************************
77  * OpenIntf: initialize interface
78  *****************************************************************************/
79
80 static intf_thread_t *p_interface_thread;
81
82 intf_thread_t *getIntf()
83 {
84     return p_interface_thread;
85 }
86
87 int OpenIntf (vlc_object_t *p_this)
88 {
89     @autoreleasepool {
90         intf_thread_t *p_intf = (intf_thread_t*) p_this;
91         p_interface_thread = p_intf;
92         msg_Dbg(p_intf, "Starting macosx interface");
93
94         @try {
95             [VLCApplication sharedApplication];
96             [VLCMain sharedInstance];
97
98             [NSBundle loadNibNamed:@"MainMenu" owner:[[VLCMain sharedInstance] mainMenu]];
99             [[[VLCMain sharedInstance] mainWindow] makeKeyAndOrderFront:nil];
100
101             msg_Dbg(p_intf, "Finished loading macosx interface");
102             return VLC_SUCCESS;
103         } @catch (NSException *exception) {
104             msg_Err(p_intf, "Loading the macosx interface failed. Do you have a valid window server?");
105             return VLC_EGENERIC;
106         }
107     }
108 }
109
110 void CloseIntf (vlc_object_t *p_this)
111 {
112     @autoreleasepool {
113         msg_Dbg(p_this, "Closing macosx interface");
114         [[VLCMain sharedInstance] applicationWillTerminate:nil];
115         [VLCMain killInstance];
116
117         p_interface_thread = nil;
118     }
119 }
120
121 #pragma mark -
122 #pragma mark Variables Callback
123
124 /*****************************************************************************
125  * ShowController: Callback triggered by the show-intf playlist variable
126  * through the ShowIntf-control-intf, to let us show the controller-win;
127  * usually when in fullscreen-mode
128  *****************************************************************************/
129 static int ShowController(vlc_object_t *p_this, const char *psz_variable,
130                      vlc_value_t old_val, vlc_value_t new_val, void *param)
131 {
132     @autoreleasepool {
133         dispatch_async(dispatch_get_main_queue(), ^{
134
135             intf_thread_t * p_intf = getIntf();
136             if (p_intf) {
137                 playlist_t * p_playlist = pl_Get(p_intf);
138                 BOOL b_fullscreen = var_GetBool(p_playlist, "fullscreen");
139                 if (b_fullscreen)
140                     [[VLCMain sharedInstance] showFullscreenController];
141
142                 else if (!strcmp(psz_variable, "intf-show"))
143                     [[[VLCMain sharedInstance] mainWindow] makeKeyAndOrderFront:nil];
144             }
145
146         });
147
148         return VLC_SUCCESS;
149     }
150 }
151
152 #pragma mark -
153 #pragma mark Private
154
155 @interface VLCMain ()
156 #ifdef HAVE_SPARKLE
157     <SUUpdaterDelegate>
158 #endif
159 {
160     intf_thread_t *p_intf;
161     BOOL launched;
162     int items_at_launch;
163
164     BOOL b_active_videoplayback;
165
166     NSWindowController *_mainWindowController;
167     VLCMainMenu *_mainmenu;
168     VLCPrefs *_prefs;
169     VLCSimplePrefsController *_sprefs;
170     VLCOpenWindowController *_open;
171     VLCCoreDialogProvider *_coredialogs;
172     VLCBookmarksWindowController *_bookmarks;
173     VLCCoreInteraction *_coreinteraction;
174     VLCResumeDialogController *_resume_dialog;
175     VLCInputManager *_input_manager;
176     VLCPlaylist *_playlist;
177     VLCDebugMessageWindowController *_messagePanelController;
178     VLCStatusBarIcon *_statusBarIcon;
179     VLCTrackSynchronizationWindowController *_trackSyncPanel;
180     VLCAudioEffectsWindowController *_audioEffectsPanel;
181     VLCVideoEffectsWindowController *_videoEffectsPanel;
182     VLCConvertAndSaveWindowController *_convertAndSaveWindow;
183     VLCExtensionsManager *_extensionsManager;
184     VLCInfo *_currentMediaInfoPanel;
185
186     bool b_intf_terminating; /* Makes sure applicationWillTerminate will be called only once */
187 }
188
189 @end
190
191 /*****************************************************************************
192  * VLCMain implementation
193  *****************************************************************************/
194 @implementation VLCMain
195
196 #pragma mark -
197 #pragma mark Initialization
198
199 static VLCMain *sharedInstance = nil;
200
201 + (VLCMain *)sharedInstance;
202 {
203     static dispatch_once_t pred;
204     dispatch_once(&pred, ^{
205         sharedInstance = [[VLCMain alloc] init];
206     });
207
208     return sharedInstance;
209 }
210
211 + (void)killInstance
212 {
213     sharedInstance = nil;
214 }
215
216 - (id)init
217 {
218     self = [super init];
219     if (self) {
220         p_intf = getIntf();
221
222         [VLCApplication sharedApplication].delegate = self;
223
224         _input_manager = [[VLCInputManager alloc] initWithMain:self];
225
226         // first initalize extensions dialog provider, then core dialog
227         // provider which will register both at the core
228         _extensionsManager = [[VLCExtensionsManager alloc] init];
229         _coredialogs = [[VLCCoreDialogProvider alloc] init];
230
231         _mainmenu = [[VLCMainMenu alloc] init];
232         _statusBarIcon = [[VLCStatusBarIcon  alloc] init];
233
234         _voutController = [[VLCVoutWindowController alloc] init];
235         _playlist = [[VLCPlaylist alloc] init];
236
237         _mainWindowController = [[NSWindowController alloc] initWithWindowNibName:@"MainWindow"];
238
239         var_AddCallback(p_intf->obj.libvlc, "intf-toggle-fscontrol", ShowController, (__bridge void *)self);
240         var_AddCallback(p_intf->obj.libvlc, "intf-show", ShowController, (__bridge void *)self);
241
242         playlist_t *p_playlist = pl_Get(p_intf);
243         if ([NSApp currentSystemPresentationOptions] & NSApplicationPresentationFullScreen)
244             var_SetBool(p_playlist, "fullscreen", YES);
245
246         _nativeFullscreenMode = var_InheritBool(p_intf, "macosx-nativefullscreenmode");
247
248         if (var_InheritInteger(p_intf, "macosx-icon-change")) {
249             /* After day 354 of the year, the usual VLC cone is replaced by another cone
250              * wearing a Father Xmas hat.
251              * Note: this icon doesn't represent an endorsement of The Coca-Cola Company.
252              */
253             NSCalendar *gregorian =
254             [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
255             NSUInteger dayOfYear = [gregorian ordinalityOfUnit:NSDayCalendarUnit inUnit:NSYearCalendarUnit forDate:[NSDate date]];
256
257             if (dayOfYear >= 354)
258                 [[VLCApplication sharedApplication] setApplicationIconImage: [NSImage imageNamed:@"VLC-Xmas"]];
259         }
260     }
261
262     return self;
263 }
264
265 - (void)dealloc
266 {
267     msg_Dbg(getIntf(), "Deinitializing VLCMain object");
268 }
269
270 - (void)applicationWillFinishLaunching:(NSNotification *)aNotification
271 {
272     _coreinteraction = [VLCCoreInteraction sharedInstance];
273
274     playlist_t * p_playlist = pl_Get(getIntf());
275     PL_LOCK;
276     items_at_launch = p_playlist->p_playing->i_children;
277     PL_UNLOCK;
278
279 #ifdef HAVE_SPARKLE
280     [[SUUpdater sharedUpdater] setDelegate:self];
281 #endif
282 }
283
284 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
285 {
286     launched = YES;
287
288     if (!p_intf)
289         return;
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     // respect playlist-autostart
305     // note that PLAYLIST_PLAY will not stop any playback if already started
306     playlist_t * p_playlist = pl_Get(getIntf());
307     PL_LOCK;
308     BOOL kidsAround = p_playlist->p_playing->i_children != 0;
309     if (kidsAround && var_GetBool(p_playlist, "playlist-autostart"))
310         playlist_Control(p_playlist, PLAYLIST_PLAY, true);
311     PL_UNLOCK;
312 }
313
314 #pragma mark -
315 #pragma mark Termination
316
317 - (BOOL)isTerminating
318 {
319     return b_intf_terminating;
320 }
321
322 - (void)applicationWillTerminate:(NSNotification *)notification
323 {
324     if (b_intf_terminating)
325         return;
326     b_intf_terminating = true;
327
328     [_input_manager resumeItunesPlayback:nil];
329
330     if (notification == nil)
331         [[NSNotificationCenter defaultCenter] postNotificationName: NSApplicationWillTerminateNotification object: nil];
332
333     playlist_t * p_playlist = pl_Get(p_intf);
334
335     /* save current video and audio profiles */
336     [[self videoEffectsPanel] saveCurrentProfile];
337     [[self audioEffectsPanel] saveCurrentProfile];
338
339     /* Save some interface state in configuration, at module quit */
340     config_PutInt(p_intf, "random", var_GetBool(p_playlist, "random"));
341     config_PutInt(p_intf, "loop", var_GetBool(p_playlist, "loop"));
342     config_PutInt(p_intf, "repeat", var_GetBool(p_playlist, "repeat"));
343
344     msg_Dbg(p_intf, "Terminating");
345
346     var_DelCallback(p_intf->obj.libvlc, "intf-toggle-fscontrol", ShowController, (__bridge void *)self);
347     var_DelCallback(p_intf->obj.libvlc, "intf-show", ShowController, (__bridge void *)self);
348
349     [[NSNotificationCenter defaultCenter] removeObserver: self];
350
351     [_voutController.lock lock];
352     // closes all open vouts
353     _voutController = nil;
354     [_voutController.lock unlock];
355
356     /* write cached user defaults to disk */
357     [[NSUserDefaults standardUserDefaults] synchronize];
358 }
359
360 #pragma mark -
361 #pragma mark Sparkle delegate
362
363 #ifdef HAVE_SPARKLE
364 /* received directly before the update gets installed, so let's shut down a bit */
365 - (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
366 {
367     [NSApp activateIgnoringOtherApps:YES];
368     [_coreinteraction stopListeningWithAppleRemote];
369     [_coreinteraction stop];
370 }
371
372 /* don't be enthusiastic about an update if we currently play a video */
373 - (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
374 {
375     if ([self activeVideoPlayback])
376         return NO;
377
378     return YES;
379 }
380 #endif
381
382 #pragma mark -
383 #pragma mark Other notification
384
385 /* Listen to the remote in exclusive mode, only when VLC is the active
386    application */
387 - (void)applicationDidBecomeActive:(NSNotification *)aNotification
388 {
389     if (!p_intf)
390         return;
391     if (var_InheritBool(p_intf, "macosx-appleremote") == YES)
392         [_coreinteraction startListeningWithAppleRemote];
393 }
394 - (void)applicationDidResignActive:(NSNotification *)aNotification
395 {
396     if (!p_intf)
397         return;
398     [_coreinteraction stopListeningWithAppleRemote];
399 }
400
401 /* Triggered when the computer goes to sleep */
402 - (void)computerWillSleep: (NSNotification *)notification
403 {
404     [_coreinteraction pause];
405 }
406
407 #pragma mark -
408 #pragma mark File opening over dock icon
409
410 - (void)application:(NSApplication *)o_app openFiles:(NSArray *)o_names
411 {
412     // Only add items here which are getting dropped to to the application icon
413     // or are given at startup. If a file is passed via command line, libvlccore
414     // will add the item, but cocoa also calls this function. In this case, the
415     // invocation is ignored here.
416     if (launched == NO) {
417         if (items_at_launch) {
418             int items = [o_names count];
419             if (items > items_at_launch)
420                 items_at_launch = 0;
421             else
422                 items_at_launch -= items;
423             return;
424         }
425     }
426
427     NSArray *o_sorted_names = [o_names sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
428     NSMutableArray *o_result = [NSMutableArray arrayWithCapacity: [o_sorted_names count]];
429     for (NSUInteger i = 0; i < [o_sorted_names count]; i++) {
430         char *psz_uri = vlc_path2uri([[o_sorted_names objectAtIndex:i] UTF8String], "file");
431         if (!psz_uri)
432             continue;
433
434         NSDictionary *o_dic = [NSDictionary dictionaryWithObject:toNSStr(psz_uri) forKey:@"ITEM_URL"];
435         [o_result addObject: o_dic];
436         free(psz_uri);
437     }
438
439     [[[VLCMain sharedInstance] playlist] addPlaylistItems:o_result tryAsSubtitle:YES];
440 }
441
442 /* When user click in the Dock icon our double click in the finder */
443 - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)hasVisibleWindows
444 {
445     if (!hasVisibleWindows)
446         [[self mainWindow] makeKeyAndOrderFront:self];
447
448     return YES;
449 }
450
451 - (void)showFullscreenController
452 {
453     // defer selector here (possibly another time) to ensure that keyWindow is set properly
454     // (needed for NSApplicationDidBecomeActiveNotification)
455     [[self mainWindow] performSelectorOnMainThread:@selector(showFullscreenController) withObject:nil waitUntilDone:NO];
456 }
457
458 - (void)setActiveVideoPlayback:(BOOL)b_value
459 {
460     assert([NSThread isMainThread]);
461
462     b_active_videoplayback = b_value;
463     if ([self mainWindow]) {
464         [[self mainWindow] setVideoplayEnabled];
465     }
466
467     // update sleep blockers
468     [_input_manager playbackStatusUpdated];
469 }
470
471 #pragma mark -
472 #pragma mark Other objects getters
473
474 - (VLCMainMenu *)mainMenu
475 {
476     return _mainmenu;
477 }
478
479 - (VLCStatusBarIcon *)statusBarIcon
480 {
481     return _statusBarIcon;
482 }
483
484 - (VLCMainWindow *)mainWindow
485 {
486     return (VLCMainWindow *)[_mainWindowController window];
487 }
488
489 - (VLCInputManager *)inputManager
490 {
491     return _input_manager;
492 }
493
494 - (VLCExtensionsManager *)extensionsManager
495 {
496     return _extensionsManager;
497 }
498
499 - (VLCDebugMessageWindowController *)debugMsgPanel
500 {
501     if (!_messagePanelController)
502         _messagePanelController = [[VLCDebugMessageWindowController alloc] init];
503
504     return _messagePanelController;
505 }
506
507 - (VLCTrackSynchronizationWindowController *)trackSyncPanel
508 {
509     if (!_trackSyncPanel)
510         _trackSyncPanel = [[VLCTrackSynchronizationWindowController alloc] init];
511
512     return _trackSyncPanel;
513 }
514
515 - (VLCAudioEffectsWindowController *)audioEffectsPanel
516 {
517     if (!_audioEffectsPanel)
518         _audioEffectsPanel = [[VLCAudioEffectsWindowController alloc] init];
519
520     return _audioEffectsPanel;
521 }
522
523 - (VLCVideoEffectsWindowController *)videoEffectsPanel
524 {
525     if (!_videoEffectsPanel)
526         _videoEffectsPanel = [[VLCVideoEffectsWindowController alloc] init];
527
528     return _videoEffectsPanel;
529 }
530
531 - (VLCInfo *)currentMediaInfoPanel;
532 {
533     if (!_currentMediaInfoPanel)
534         _currentMediaInfoPanel = [[VLCInfo alloc] init];
535
536     return _currentMediaInfoPanel;
537 }
538
539 - (VLCBookmarksWindowController *)bookmarks
540 {
541     if (!_bookmarks)
542         _bookmarks = [[VLCBookmarksWindowController alloc] init];
543
544     return _bookmarks;
545 }
546
547 - (VLCOpenWindowController *)open
548 {
549     if (!_open)
550         _open = [[VLCOpenWindowController alloc] init];
551
552     return _open;
553 }
554
555 - (VLCConvertAndSaveWindowController *)convertAndSaveWindow
556 {
557     if (_convertAndSaveWindow == nil)
558         _convertAndSaveWindow = [[VLCConvertAndSaveWindowController alloc] init];
559
560     return _convertAndSaveWindow;
561 }
562
563 - (VLCSimplePrefsController *)simplePreferences
564 {
565     if (!_sprefs)
566         _sprefs = [[VLCSimplePrefsController alloc] init];
567
568     return _sprefs;
569 }
570
571 - (VLCPrefs *)preferences
572 {
573     if (!_prefs)
574         _prefs = [[VLCPrefs alloc] init];
575
576     return _prefs;
577 }
578
579 - (VLCPlaylist *)playlist
580 {
581     return _playlist;
582 }
583
584 - (VLCCoreDialogProvider *)coreDialogProvider
585 {
586     return _coredialogs;
587 }
588
589 - (VLCResumeDialogController *)resumeDialog
590 {
591     if (!_resume_dialog)
592         _resume_dialog = [[VLCResumeDialogController alloc] init];
593
594     return _resume_dialog;
595 }
596
597 - (BOOL)activeVideoPlayback
598 {
599     return b_active_videoplayback;
600 }
601
602 @end