darwinvlc/macosx: rework binary, start main loop in darwinvlc
[vlc.git] / bin / darwinvlc.m
1 /*****************************************************************************
2  * darwinvlc.m: OS X specific main executable for VLC media player
3  *****************************************************************************
4  * Copyright (C) 2013-2015 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul K├╝hne <fkuehne at videolan dot org>
8  *          David Fuhrmann <dfuhrmann at videolan dot org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <vlc/vlc.h>
30 #include <stdlib.h>
31 #include <locale.h>
32 #include <signal.h>
33 #include <string.h>
34
35 #import <CoreFoundation/CoreFoundation.h>
36 #import <Cocoa/Cocoa.h>
37
38
39 /**
40  * Handler called when VLC asks to terminate the program.
41  */
42 static void vlc_terminate(void *data)
43 {
44     (void)data;
45
46     dispatch_async(dispatch_get_main_queue(), ^{
47         /*
48          * Stop the main loop. When using the CoreFoundation mainloop, simply
49          * CFRunLoopStop can be used.
50          *
51          * But this does not work when having an interface.
52          * In this case, [NSApp stop:nil] needs to be used, but the used flag is only
53          * evaluated at the end of main loop event processing. This is always true
54          * in the case of code inside a action method. But here, this is
55          * not true and thus we need to send an dummy event to make sure the stop
56          * flag is actually processed by the main loop.
57          */
58         if (NSApp == nil) {
59             CFRunLoopStop(CFRunLoopGetCurrent());
60         } else {
61
62             [NSApp stop:nil];
63             NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined
64                                                 location:NSMakePoint(0,0)
65                                            modifierFlags:0
66                                                timestamp:0.0
67                                             windowNumber:0
68                                                  context:nil
69                                                  subtype:0
70                                                    data1:0
71                                                    data2:0];
72             [NSApp postEvent:event atStart:YES];
73         }
74
75     });
76 }
77
78 /*****************************************************************************
79  * main: parse command line, start interface and spawn threads.
80  *****************************************************************************/
81 int main(int i_argc, const char *ppsz_argv[])
82 {
83     /* The so-called POSIX-compliant MacOS X reportedly processes SIGPIPE even
84      * if it is blocked in all thread.
85      * Note: this is NOT an excuse for not protecting against SIGPIPE. If
86      * LibVLC runs outside of VLC, we cannot rely on this code snippet. */
87     signal(SIGPIPE, SIG_IGN);
88     /* Restore SIGCHLD in case our parent process ignores it. */
89     signal(SIGCHLD, SIG_DFL);
90
91 #ifndef NDEBUG
92     /* Activate malloc checking routines to detect heap corruptions. */
93     setenv("MALLOC_CHECK_", "2", 1);
94 #endif
95
96 #ifdef TOP_BUILDDIR
97     setenv("VLC_PLUGIN_PATH", TOP_BUILDDIR"/modules", 1);
98     setenv("VLC_DATA_PATH", TOP_SRCDIR"/share", 1);
99 #endif
100
101 #ifndef ALLOW_RUN_AS_ROOT
102     if (geteuid() == 0)
103     {
104         fprintf(stderr, "VLC is not supposed to be run as root. Sorry.\n"
105         "If you need to use real-time priorities and/or privileged TCP ports\n"
106         "you can use %s-wrapper (make sure it is Set-UID root and\n"
107         "cannot be run by non-trusted users first).\n", ppsz_argv[0]);
108         return 1;
109     }
110 #endif
111
112     setlocale(LC_ALL, "");
113
114     if (isatty(STDERR_FILENO))
115         /* This message clutters error logs. It is printed only on a TTY.
116          * Fortunately, LibVLC prints version info with -vv anyway. */
117         fprintf(stderr, "VLC media player %s (revision %s)\n",
118                  libvlc_get_version(), libvlc_get_changeset());
119
120     sigset_t set;
121
122     sigemptyset(&set);
123     /*
124      * The darwin version of VLC used GCD to dequeue interesting signals.
125      * For this to work, those signals must be blocked.
126      *
127      * There are two advantages over traditional signal handlers:
128      *  - handling is done on a separate thread: no need to worry about async-safety,
129      *  - EINTR is not generated: other threads need not handle that error.
130      * That being said, some LibVLC programs do not use sigwait(). Therefore
131      * EINTR must still be handled cleanly, notably from poll() calls.
132      *
133      * Signals that request a clean shutdown.
134      * We have to handle SIGTERM cleanly because of daemon mode. */
135     sigaddset(&set, SIGINT);
136     sigaddset(&set, SIGTERM);
137
138     /* SIGPIPE can happen and would crash the process. On modern systems,
139      * the MSG_NOSIGNAL flag protects socket write operations against SIGPIPE.
140      * But we still need to block SIGPIPE when:
141      *  - writing to pipes,
142      *  - using write() instead of send() for code not specific to sockets.
143      * LibVLC code assumes that SIGPIPE is blocked. Other LibVLC applications
144      * shall block it (or handle it somehow) too.
145      */
146     sigaddset(&set, SIGPIPE);
147
148     /* SIGCHLD must be dequeued to clean up zombie child processes.
149      * Furthermore the handler must not be set to SIG_IGN (see above).
150      * We cannot pragmatically handle EINTR, short reads and short writes
151      * in every code paths (including underlying libraries). So we just
152      * block SIGCHLD in all threads, and dequeue it below. */
153     sigaddset(&set, SIGCHLD);
154
155     /* Block all these signals */
156     pthread_sigmask(SIG_SETMASK, &set, NULL);
157
158     /* Handle signals with GCD */
159     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
160     dispatch_source_t sigIntSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGINT, 0, queue);
161     dispatch_source_t sigTermSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, queue);
162     dispatch_source_t sigChldSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGCHLD, 0, queue);
163
164     if (!sigIntSource || !sigTermSource || !sigChldSource)
165         abort();
166
167     dispatch_source_set_event_handler(sigIntSource, ^{
168         vlc_terminate(nil);
169     });
170     dispatch_source_set_event_handler(sigTermSource, ^{
171         vlc_terminate(nil);
172     });
173
174     dispatch_source_set_event_handler(sigChldSource, ^{
175         int status;
176         while(waitpid(-1, &status, WNOHANG) > 0)
177             ;
178     });
179
180     dispatch_resume(sigIntSource);
181     dispatch_resume(sigTermSource);
182     dispatch_resume(sigChldSource);
183
184
185     /* Handle parameters */
186     const char *argv[i_argc + 2];
187     int argc = 0;
188
189     argv[argc++] = "--no-ignore-config";
190     argv[argc++] = "--media-library";
191
192     /* overwrite system language on Mac */
193     char *lang = NULL;
194
195     for (int i = 0; i < i_argc; i++) {
196         if (!strncmp(ppsz_argv[i], "--language", 10)) {
197             lang = strstr(ppsz_argv[i], "=");
198             ppsz_argv++, i_argc--;
199             continue;
200         }
201     }
202     if (lang && strncmp( lang, "auto", 4 )) {
203         char tmp[11];
204         snprintf(tmp, 11, "LANG%s", lang);
205         putenv(tmp);
206     }
207
208     if (!lang) {
209         CFStringRef language;
210         language = (CFStringRef)CFPreferencesCopyAppValue(CFSTR("language"),
211                                                           kCFPreferencesCurrentApplication);
212         if (language) {
213             CFIndex length = CFStringGetLength(language) + 1;
214             if (length > 0) {
215                 CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8);
216                 lang = (char *)malloc(maxSize);
217                 CFStringGetCString(language, lang, maxSize - 1, kCFStringEncodingUTF8);
218             }
219             if (strncmp( lang, "auto", 4 )) {
220                 char tmp[11];
221                 snprintf(tmp, 11, "LANG=%s", lang);
222                 putenv(tmp);
223             }
224             CFRelease(language);
225         }
226     }
227
228     ppsz_argv++; i_argc--; /* skip executable path */
229
230     /* When VLC.app is run by double clicking in Mac OS X, the 2nd arg
231      * is the PSN - process serial number (a unique PID-ish thingie)
232      * still ok for real Darwin & when run from command line
233      * for example -psn_0_9306113 */
234     if (i_argc >= 1 && !strncmp(*ppsz_argv, "-psn" , 4))
235         ppsz_argv++, i_argc--;
236
237     memcpy (argv + argc, ppsz_argv, i_argc * sizeof(*argv));
238     argc += i_argc;
239     argv[argc] = NULL;
240
241     /* Initialize libvlc */
242     libvlc_instance_t *vlc = libvlc_new(argc, argv);
243     if (vlc == NULL)
244         return 1;
245
246     int ret = 1;
247     libvlc_set_exit_handler(vlc, vlc_terminate, NULL);
248     libvlc_set_app_id(vlc, "org.VideoLAN.VLC", PACKAGE_VERSION, PACKAGE_NAME);
249     libvlc_set_user_agent(vlc, "VLC media player", "VLC/"PACKAGE_VERSION);
250
251     libvlc_add_intf(vlc, "hotkeys,none");
252
253     if (libvlc_add_intf(vlc, NULL))
254         goto out;
255     libvlc_playlist_play(vlc, -1, 0, NULL);
256
257     /*
258      * Run the main loop. If the mac interface is not initialized, only the CoreFoundation
259      * runloop is used. Otherwise, [NSApp run] needs to be called, which setups more stuff
260      * before actually starting the loop.
261      */
262     @autoreleasepool {
263         if(NSApp == nil) {
264             CFRunLoopRun();
265
266         } else {
267             [NSApp run];
268         }
269     }
270
271     ret = 0;
272     /* Cleanup */
273 out:
274     dispatch_release(sigIntSource);
275     dispatch_release(sigTermSource);
276     dispatch_release(sigChldSource);
277
278     libvlc_release(vlc);
279
280     return ret;
281 }