macOS: Explicitly set the App icon
[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             else
260                 [[VLCApplication sharedApplication] setApplicationIconImage: [NSImage imageNamed:@"VLC"]];
261         }
262     }
263
264     return self;
265 }
266
267 - (void)dealloc
268 {
269     msg_Dbg(getIntf(), "Deinitializing VLCMain object");
270 }
271
272 - (void)applicationWillFinishLaunching:(NSNotification *)aNotification
273 {
274     _coreinteraction = [VLCCoreInteraction sharedInstance];
275
276     playlist_t * p_playlist = pl_Get(getIntf());
277     PL_LOCK;
278     items_at_launch = p_playlist->p_playing->i_children;
279     PL_UNLOCK;
280
281 #ifdef HAVE_SPARKLE
282     [[SUUpdater sharedUpdater] setDelegate:self];
283 #endif
284 }
285
286 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
287 {
288     launched = YES;
289
290     if (!p_intf)
291         return;
292
293     [_coreinteraction updateCurrentlyUsedHotkeys];
294
295     [self removeOldPreferences];
296
297     /* Handle sleep notification */
298     [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(computerWillSleep:)
299            name:NSWorkspaceWillSleepNotification object:nil];
300
301     /* update the main window */
302     [[self mainWindow] updateWindow];
303     [[self mainWindow] updateTimeSlider];
304     [[self mainWindow] updateVolumeSlider];
305
306     // respect playlist-autostart
307     // note that PLAYLIST_PLAY will not stop any playback if already started
308     playlist_t * p_playlist = pl_Get(getIntf());
309     PL_LOCK;
310     BOOL kidsAround = p_playlist->p_playing->i_children != 0;
311     if (kidsAround && var_GetBool(p_playlist, "playlist-autostart"))
312         playlist_Control(p_playlist, PLAYLIST_PLAY, true);
313     PL_UNLOCK;
314 }
315
316 #pragma mark -
317 #pragma mark Termination
318
319 - (BOOL)isTerminating
320 {
321     return b_intf_terminating;
322 }
323
324 - (void)applicationWillTerminate:(NSNotification *)notification
325 {
326     if (b_intf_terminating)
327         return;
328     b_intf_terminating = true;
329
330     [_input_manager resumeItunesPlayback:nil];
331
332     if (notification == nil)
333         [[NSNotificationCenter defaultCenter] postNotificationName: NSApplicationWillTerminateNotification object: nil];
334
335     playlist_t * p_playlist = pl_Get(p_intf);
336
337     /* save current video and audio profiles */
338     [[self videoEffectsPanel] saveCurrentProfile];
339     [[self audioEffectsPanel] saveCurrentProfile];
340
341     /* Save some interface state in configuration, at module quit */
342     config_PutInt(p_intf, "random", var_GetBool(p_playlist, "random"));
343     config_PutInt(p_intf, "loop", var_GetBool(p_playlist, "loop"));
344     config_PutInt(p_intf, "repeat", var_GetBool(p_playlist, "repeat"));
345
346     msg_Dbg(p_intf, "Terminating");
347
348     var_DelCallback(p_intf->obj.libvlc, "intf-toggle-fscontrol", ShowController, (__bridge void *)self);
349     var_DelCallback(p_intf->obj.libvlc, "intf-show", ShowController, (__bridge void *)self);
350
351     [[NSNotificationCenter defaultCenter] removeObserver: self];
352
353     [_voutController.lock lock];
354     // closes all open vouts
355     _voutController = nil;
356     [_voutController.lock unlock];
357
358     /* write cached user defaults to disk */
359     [[NSUserDefaults standardUserDefaults] synchronize];
360 }
361
362 #pragma mark -
363 #pragma mark Sparkle delegate
364
365 #ifdef HAVE_SPARKLE
366 /* received directly before the update gets installed, so let's shut down a bit */
367 - (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
368 {
369     [NSApp activateIgnoringOtherApps:YES];
370     [_coreinteraction stopListeningWithAppleRemote];
371     [_coreinteraction stop];
372 }
373
374 /* don't be enthusiastic about an update if we currently play a video */
375 - (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
376 {
377     if ([self activeVideoPlayback])
378         return NO;
379
380     return YES;
381 }
382 #endif
383
384 #pragma mark -
385 #pragma mark Other notification
386
387 /* Listen to the remote in exclusive mode, only when VLC is the active
388    application */
389 - (void)applicationDidBecomeActive:(NSNotification *)aNotification
390 {
391     if (!p_intf)
392         return;
393     if (var_InheritBool(p_intf, "macosx-appleremote") == YES)
394         [_coreinteraction startListeningWithAppleRemote];
395 }
396 - (void)applicationDidResignActive:(NSNotification *)aNotification
397 {
398     if (!p_intf)
399         return;
400     [_coreinteraction stopListeningWithAppleRemote];
401 }
402
403 /* Triggered when the computer goes to sleep */
404 - (void)computerWillSleep: (NSNotification *)notification
405 {
406     [_coreinteraction pause];
407 }
408
409 #pragma mark -
410 #pragma mark File opening over dock icon
411
412 - (void)application:(NSApplication *)o_app openFiles:(NSArray *)o_names
413 {
414     // Only add items here which are getting dropped to to the application icon
415     // or are given at startup. If a file is passed via command line, libvlccore
416     // will add the item, but cocoa also calls this function. In this case, the
417     // invocation is ignored here.
418     if (launched == NO) {
419         if (items_at_launch) {
420             int items = [o_names count];
421             if (items > items_at_launch)
422                 items_at_launch = 0;
423             else
424                 items_at_launch -= items;
425             return;
426         }
427     }
428
429     NSArray *o_sorted_names = [o_names sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
430     NSMutableArray *o_result = [NSMutableArray arrayWithCapacity: [o_sorted_names count]];
431     for (NSUInteger i = 0; i < [o_sorted_names count]; i++) {
432         char *psz_uri = vlc_path2uri([[o_sorted_names objectAtIndex:i] UTF8String], "file");
433         if (!psz_uri)
434             continue;
435
436         NSDictionary *o_dic = [NSDictionary dictionaryWithObject:toNSStr(psz_uri) forKey:@"ITEM_URL"];
437         [o_result addObject: o_dic];
438         free(psz_uri);
439     }
440
441     [[[VLCMain sharedInstance] playlist] addPlaylistItems:o_result tryAsSubtitle:YES];
442 }
443
444 /* When user click in the Dock icon our double click in the finder */
445 - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)hasVisibleWindows
446 {
447     if (!hasVisibleWindows)
448         [[self mainWindow] makeKeyAndOrderFront:self];
449
450     return YES;
451 }
452
453 - (void)showFullscreenController
454 {
455     // defer selector here (possibly another time) to ensure that keyWindow is set properly
456     // (needed for NSApplicationDidBecomeActiveNotification)
457     [[self mainWindow] performSelectorOnMainThread:@selector(showFullscreenController) withObject:nil waitUntilDone:NO];
458 }
459
460 - (void)setActiveVideoPlayback:(BOOL)b_value
461 {
462     assert([NSThread isMainThread]);
463
464     b_active_videoplayback = b_value;
465     if ([self mainWindow]) {
466         [[self mainWindow] setVideoplayEnabled];
467     }
468
469     // update sleep blockers
470     [_input_manager playbackStatusUpdated];
471 }
472
473 #pragma mark -
474 #pragma mark Other objects getters
475
476 - (VLCMainMenu *)mainMenu
477 {
478     return _mainmenu;
479 }
480
481 - (VLCStatusBarIcon *)statusBarIcon
482 {
483     return _statusBarIcon;
484 }
485
486 - (VLCMainWindow *)mainWindow
487 {
488     return (VLCMainWindow *)[_mainWindowController window];
489 }
490
491 - (VLCInputManager *)inputManager
492 {
493     return _input_manager;
494 }
495
496 - (VLCExtensionsManager *)extensionsManager
497 {
498     return _extensionsManager;
499 }
500
501 - (VLCDebugMessageWindowController *)debugMsgPanel
502 {
503     if (!_messagePanelController)
504         _messagePanelController = [[VLCDebugMessageWindowController alloc] init];
505
506     return _messagePanelController;
507 }
508
509 - (VLCTrackSynchronizationWindowController *)trackSyncPanel
510 {
511     if (!_trackSyncPanel)
512         _trackSyncPanel = [[VLCTrackSynchronizationWindowController alloc] init];
513
514     return _trackSyncPanel;
515 }
516
517 - (VLCAudioEffectsWindowController *)audioEffectsPanel
518 {
519     if (!_audioEffectsPanel)
520         _audioEffectsPanel = [[VLCAudioEffectsWindowController alloc] init];
521
522     return _audioEffectsPanel;
523 }
524
525 - (VLCVideoEffectsWindowController *)videoEffectsPanel
526 {
527     if (!_videoEffectsPanel)
528         _videoEffectsPanel = [[VLCVideoEffectsWindowController alloc] init];
529
530     return _videoEffectsPanel;
531 }
532
533 - (VLCInfo *)currentMediaInfoPanel;
534 {
535     if (!_currentMediaInfoPanel)
536         _currentMediaInfoPanel = [[VLCInfo alloc] init];
537
538     return _currentMediaInfoPanel;
539 }
540
541 - (VLCBookmarksWindowController *)bookmarks
542 {
543     if (!_bookmarks)
544         _bookmarks = [[VLCBookmarksWindowController alloc] init];
545
546     return _bookmarks;
547 }
548
549 - (VLCOpenWindowController *)open
550 {
551     if (!_open)
552         _open = [[VLCOpenWindowController alloc] init];
553
554     return _open;
555 }
556
557 - (VLCConvertAndSaveWindowController *)convertAndSaveWindow
558 {
559     if (_convertAndSaveWindow == nil)
560         _convertAndSaveWindow = [[VLCConvertAndSaveWindowController alloc] init];
561
562     return _convertAndSaveWindow;
563 }
564
565 - (VLCSimplePrefsController *)simplePreferences
566 {
567     if (!_sprefs)
568         _sprefs = [[VLCSimplePrefsController alloc] init];
569
570     return _sprefs;
571 }
572
573 - (VLCPrefs *)preferences
574 {
575     if (!_prefs)
576         _prefs = [[VLCPrefs alloc] init];
577
578     return _prefs;
579 }
580
581 - (VLCPlaylist *)playlist
582 {
583     return _playlist;
584 }
585
586 - (VLCCoreDialogProvider *)coreDialogProvider
587 {
588     return _coredialogs;
589 }
590
591 - (VLCResumeDialogController *)resumeDialog
592 {
593     if (!_resume_dialog)
594         _resume_dialog = [[VLCResumeDialogController alloc] init];
595
596     return _resume_dialog;
597 }
598
599 - (BOOL)activeVideoPlayback
600 {
601     return b_active_videoplayback;
602 }
603
604 @end