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