macOS: Move NSSound category to its own file
[vlc.git] / modules / gui / macosx / VLCCoreInteraction.m
1 /*****************************************************************************
2  * CoreInteraction.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2011-2015 Felix Paul Kühne
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #import "VLCCoreInteraction.h"
25 #import "VLCMain.h"
26 #import "VLCOpenWindowController.h"
27 #import "VLCPlaylist.h"
28 #import <math.h>
29 #import <vlc_playlist.h>
30 #import <vlc_input.h>
31 #import <vlc_keys.h>
32 #import <vlc_vout.h>
33 #import <vlc_vout_osd.h>
34 #import <vlc/vlc.h>
35 #import <vlc_strings.h>
36 #import <vlc_url.h>
37 #import <vlc_modules.h>
38 #import <vlc_charset.h>
39 #include <vlc_plugin.h>
40 #import "SPMediaKeyTap.h"
41 #import "AppleRemote.h"
42 #import "VLCInputManager.h"
43
44 #import "NSSound+VLCAdditions.h"
45
46 static int BossCallback(vlc_object_t *p_this, const char *psz_var,
47                         vlc_value_t oldval, vlc_value_t new_val, void *param)
48 {
49     @autoreleasepool {
50         dispatch_async(dispatch_get_main_queue(), ^{
51             [[VLCCoreInteraction sharedInstance] pause];
52             [[NSApplication sharedApplication] hide:nil];
53         });
54
55         return VLC_SUCCESS;
56     }
57 }
58
59 @interface VLCCoreInteraction ()
60 {
61     int i_currentPlaybackRate;
62     mtime_t timeA, timeB;
63
64     float f_maxVolume;
65
66     /* media key support */
67     BOOL b_mediaKeySupport;
68     BOOL b_mediakeyJustJumped;
69     SPMediaKeyTap *_mediaKeyController;
70     BOOL b_mediaKeyTrapEnabled;
71
72     AppleRemote *_remote;
73     BOOL b_remote_button_hold; /* true as long as the user holds the left,right,plus or minus on the remote control */
74
75     NSArray *_usedHotkeys;
76 }
77 @end
78
79 @implementation VLCCoreInteraction
80
81 #pragma mark - Initialization
82
83 + (VLCCoreInteraction *)sharedInstance
84 {
85     static VLCCoreInteraction *sharedInstance = nil;
86     static dispatch_once_t pred;
87
88     dispatch_once(&pred, ^{
89         sharedInstance = [VLCCoreInteraction new];
90     });
91
92     return sharedInstance;
93 }
94
95 - (instancetype)init
96 {
97     self = [super init];
98     if (self) {
99         intf_thread_t *p_intf = getIntf();
100
101         /* init media key support */
102         b_mediaKeySupport = var_InheritBool(p_intf, "macosx-mediakeys");
103         if (b_mediaKeySupport) {
104             _mediaKeyController = [[SPMediaKeyTap alloc] initWithDelegate:self];
105             [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
106                                                                      [SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey,
107                                                                      nil]];
108         }
109         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(coreChangedMediaKeySupportSetting:) name:VLCMediaKeySupportSettingChangedNotification object: nil];
110
111         /* init Apple Remote support */
112         _remote = [[AppleRemote alloc] init];
113         [_remote setClickCountEnabledButtons: kRemoteButtonPlay];
114         [_remote setDelegate: self];
115
116         var_AddCallback(p_intf->obj.libvlc, "intf-boss", BossCallback, (__bridge void *)self);
117     }
118     return self;
119 }
120
121 - (void)dealloc
122 {
123     intf_thread_t *p_intf = getIntf();
124     var_DelCallback(p_intf->obj.libvlc, "intf-boss", BossCallback, (__bridge void *)self);
125     [[NSNotificationCenter defaultCenter] removeObserver: self];
126 }
127
128
129 #pragma mark - Playback Controls
130
131 - (void)playOrPause
132 {
133     input_thread_t *p_input = pl_CurrentInput(getIntf());
134     playlist_t *p_playlist = pl_Get(getIntf());
135
136     if (p_input) {
137         playlist_TogglePause(p_playlist);
138         vlc_object_release(p_input);
139
140     } else {
141         PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
142         if ([[[VLCMain sharedInstance] playlist] isSelectionEmpty] && (root == ROOT_TYPE_PLAYLIST || root == ROOT_TYPE_MEDIALIBRARY))
143             [[[VLCMain sharedInstance] open] openFileGeneric];
144         else
145             [[[VLCMain sharedInstance] playlist] playItem:nil];
146     }
147 }
148
149 - (void)pause
150 {
151     playlist_t *p_playlist = pl_Get(getIntf());
152
153     playlist_Pause(p_playlist);
154 }
155
156 - (void)stop
157 {
158     playlist_Stop(pl_Get(getIntf()));
159 }
160
161 - (void)faster
162 {
163     var_TriggerCallback(pl_Get(getIntf()), "rate-faster");
164 }
165
166 - (void)slower
167 {
168     var_TriggerCallback(pl_Get(getIntf()), "rate-slower");
169 }
170
171 - (void)normalSpeed
172 {
173     var_SetFloat(pl_Get(getIntf()), "rate", 1.);
174 }
175
176 - (void)toggleRecord
177 {
178     intf_thread_t *p_intf = getIntf();
179     if (!p_intf)
180         return;
181
182     input_thread_t * p_input;
183     p_input = pl_CurrentInput(p_intf);
184     if (p_input) {
185         var_ToggleBool(p_input, "record");
186         vlc_object_release(p_input);
187     }
188 }
189
190 - (void)setPlaybackRate:(int)i_value
191 {
192     playlist_t * p_playlist = pl_Get(getIntf());
193
194     double speed = pow(2, (double)i_value / 17);
195     int rate = INPUT_RATE_DEFAULT / speed;
196     if (i_currentPlaybackRate != rate)
197         var_SetFloat(p_playlist, "rate", (float)INPUT_RATE_DEFAULT / (float)rate);
198     i_currentPlaybackRate = rate;
199 }
200
201 - (int)playbackRate
202 {
203     float f_rate;
204
205     intf_thread_t *p_intf = getIntf();
206     if (!p_intf)
207         return 0;
208
209     input_thread_t * p_input;
210     p_input = pl_CurrentInput(p_intf);
211     if (p_input) {
212         f_rate = var_GetFloat(p_input, "rate");
213         vlc_object_release(p_input);
214     }
215     else
216     {
217         playlist_t * p_playlist = pl_Get(getIntf());
218         f_rate = var_GetFloat(p_playlist, "rate");
219     }
220
221     double value = 17 * log(f_rate) / log(2.);
222     int returnValue = (int) ((value > 0) ? value + .5 : value - .5);
223
224     if (returnValue < -34)
225         returnValue = -34;
226     else if (returnValue > 34)
227         returnValue = 34;
228
229     i_currentPlaybackRate = returnValue;
230     return returnValue;
231 }
232
233 - (void)previous
234 {
235     playlist_Prev(pl_Get(getIntf()));
236 }
237
238 - (void)next
239 {
240     playlist_Next(pl_Get(getIntf()));
241 }
242
243 - (int)durationOfCurrentPlaylistItem
244 {
245     intf_thread_t *p_intf = getIntf();
246     if (!p_intf)
247         return 0;
248
249     input_thread_t * p_input = pl_CurrentInput(p_intf);
250     int64_t i_duration = -1;
251     if (!p_input)
252         return i_duration;
253
254     input_Control(p_input, INPUT_GET_LENGTH, &i_duration);
255     vlc_object_release(p_input);
256
257     return (int)(i_duration / 1000000);
258 }
259
260 - (NSURL*)URLOfCurrentPlaylistItem
261 {
262     intf_thread_t *p_intf = getIntf();
263     if (!p_intf)
264         return nil;
265
266     input_thread_t *p_input = pl_CurrentInput(p_intf);
267     if (!p_input)
268         return nil;
269
270     input_item_t *p_item = input_GetItem(p_input);
271     if (!p_item) {
272         vlc_object_release(p_input);
273         return nil;
274     }
275
276     char *psz_uri = input_item_GetURI(p_item);
277     if (!psz_uri) {
278         vlc_object_release(p_input);
279         return nil;
280     }
281
282     NSURL *o_url;
283     o_url = [NSURL URLWithString:toNSStr(psz_uri)];
284     vlc_object_release(p_input);
285
286     return o_url;
287 }
288
289 - (NSString*)nameOfCurrentPlaylistItem
290 {
291     intf_thread_t *p_intf = getIntf();
292     if (!p_intf)
293         return nil;
294
295     input_thread_t *p_input = pl_CurrentInput(p_intf);
296     if (!p_input)
297         return nil;
298
299     input_item_t *p_item = input_GetItem(p_input);
300     if (!p_item) {
301         vlc_object_release(p_input);
302         return nil;
303     }
304
305     char *psz_uri = input_item_GetURI(p_item);
306     if (!psz_uri) {
307         vlc_object_release(p_input);
308         return nil;
309     }
310
311     NSString *o_name = @"";
312     char *format = var_InheritString(getIntf(), "input-title-format");
313     if (format) {
314         char *formated = vlc_strfinput(p_input, format);
315         free(format);
316         o_name = toNSStr(formated);
317         free(formated);
318     }
319
320     NSURL * o_url = [NSURL URLWithString:toNSStr(psz_uri)];
321     free(psz_uri);
322
323     if ([o_name isEqualToString:@""]) {
324         if ([o_url isFileURL])
325             o_name = [[NSFileManager defaultManager] displayNameAtPath:[o_url path]];
326         else
327             o_name = [o_url absoluteString];
328     }
329     vlc_object_release(p_input);
330     return o_name;
331 }
332
333 - (void)forward
334 {
335     //LEGACY SUPPORT
336     [self forwardShort];
337 }
338
339 - (void)backward
340 {
341     //LEGACY SUPPORT
342     [self backwardShort];
343 }
344
345 - (void)jumpWithValue:(char *)p_value forward:(BOOL)b_value
346 {
347     input_thread_t *p_input = pl_CurrentInput(getIntf());
348     if (!p_input)
349         return;
350
351     int i_interval = var_InheritInteger( p_input, p_value );
352     if (i_interval > 0) {
353         mtime_t val = CLOCK_FREQ * i_interval;
354         if (!b_value)
355             val = val * -1;
356         var_SetInteger( p_input, "time-offset", val );
357     }
358     vlc_object_release(p_input);
359 }
360
361 - (void)forwardExtraShort
362 {
363     [self jumpWithValue:"extrashort-jump-size" forward:YES];
364 }
365
366 - (void)backwardExtraShort
367 {
368     [self jumpWithValue:"extrashort-jump-size" forward:NO];
369 }
370
371 - (void)forwardShort
372 {
373     [self jumpWithValue:"short-jump-size" forward:YES];
374 }
375
376 - (void)backwardShort
377 {
378     [self jumpWithValue:"short-jump-size" forward:NO];
379 }
380
381 - (void)forwardMedium
382 {
383     [self jumpWithValue:"medium-jump-size" forward:YES];
384 }
385
386 - (void)backwardMedium
387 {
388     [self jumpWithValue:"medium-jump-size" forward:NO];
389 }
390
391 - (void)forwardLong
392 {
393     [self jumpWithValue:"long-jump-size" forward:YES];
394 }
395
396 - (void)backwardLong
397 {
398     [self jumpWithValue:"long-jump-size" forward:NO];
399 }
400
401 - (void)shuffle
402 {
403     intf_thread_t *p_intf = getIntf();
404     if (!p_intf)
405         return;
406
407     vlc_value_t val;
408     playlist_t * p_playlist = pl_Get(p_intf);
409     vout_thread_t *p_vout = getVout();
410
411     var_Get(p_playlist, "random", &val);
412     val.b_bool = !val.b_bool;
413     var_Set(p_playlist, "random", val);
414     if (val.b_bool) {
415         if (p_vout) {
416             vout_OSDMessage(p_vout, SPU_DEFAULT_CHANNEL, "%s", _("Random On"));
417             vlc_object_release(p_vout);
418         }
419         config_PutInt(p_playlist, "random", 1);
420     }
421     else
422     {
423         if (p_vout) {
424             vout_OSDMessage(p_vout, SPU_DEFAULT_CHANNEL, "%s", _("Random Off"));
425             vlc_object_release(p_vout);
426         }
427         config_PutInt(p_playlist, "random", 0);
428     }
429 }
430
431 - (void)repeatAll
432 {
433     intf_thread_t *p_intf = getIntf();
434     if (!p_intf)
435         return;
436
437     playlist_t * p_playlist = pl_Get(p_intf);
438
439     var_SetBool(p_playlist, "repeat", NO);
440     var_SetBool(p_playlist, "loop", YES);
441     config_PutInt(p_playlist, "repeat", NO);
442     config_PutInt(p_playlist, "loop", YES);
443
444     vout_thread_t *p_vout = getVout();
445     if (p_vout) {
446         vout_OSDMessage(p_vout, SPU_DEFAULT_CHANNEL, "%s", _("Repeat All"));
447         vlc_object_release(p_vout);
448     }
449 }
450
451 - (void)repeatOne
452 {
453     intf_thread_t *p_intf = getIntf();
454     if (!p_intf)
455         return;
456
457     playlist_t * p_playlist = pl_Get(p_intf);
458
459     var_SetBool(p_playlist, "repeat", YES);
460     var_SetBool(p_playlist, "loop", NO);
461     config_PutInt(p_playlist, "repeat", YES);
462     config_PutInt(p_playlist, "loop", NO);
463
464     vout_thread_t *p_vout = getVout();
465     if (p_vout) {
466         vout_OSDMessage(p_vout, SPU_DEFAULT_CHANNEL, "%s", _("Repeat One"));
467         vlc_object_release(p_vout);
468     }
469 }
470
471 - (void)repeatOff
472 {
473     intf_thread_t *p_intf = getIntf();
474     if (!p_intf)
475         return;
476
477     playlist_t * p_playlist = pl_Get(p_intf);
478
479     var_SetBool(p_playlist, "repeat", NO);
480     var_SetBool(p_playlist, "loop", NO);
481     config_PutInt(p_playlist, "repeat", NO);
482     config_PutInt(p_playlist, "loop", NO);
483
484     vout_thread_t *p_vout = getVout();
485     if (p_vout) {
486         vout_OSDMessage(p_vout, SPU_DEFAULT_CHANNEL, "%s", _("Repeat Off"));
487         vlc_object_release(p_vout);
488     }
489 }
490
491 - (void)setAtoB
492 {
493     if (!timeA) {
494         input_thread_t * p_input = pl_CurrentInput(getIntf());
495         if (p_input) {
496             timeA = var_GetInteger(p_input, "time");
497             vlc_object_release(p_input);
498         }
499     } else if (!timeB) {
500         input_thread_t * p_input = pl_CurrentInput(getIntf());
501         if (p_input) {
502             timeB = var_GetInteger(p_input, "time");
503             vlc_object_release(p_input);
504         }
505     } else
506         [self resetAtoB];
507 }
508
509 - (void)resetAtoB
510 {
511     timeA = 0;
512     timeB = 0;
513 }
514
515 - (void)updateAtoB
516 {
517     if (timeB) {
518         input_thread_t * p_input = pl_CurrentInput(getIntf());
519         if (p_input) {
520             mtime_t currentTime = var_GetInteger(p_input, "time");
521             if ( currentTime >= timeB || currentTime < timeA)
522                 var_SetInteger(p_input, "time", timeA);
523             vlc_object_release(p_input);
524         }
525     }
526 }
527
528 - (void)volumeUp
529 {
530     intf_thread_t *p_intf = getIntf();
531     if (!p_intf)
532         return;
533
534     playlist_VolumeUp(pl_Get(p_intf), 1, NULL);
535 }
536
537 - (void)volumeDown
538 {
539     intf_thread_t *p_intf = getIntf();
540     if (!p_intf)
541         return;
542
543     playlist_VolumeDown(pl_Get(p_intf), 1, NULL);
544 }
545
546 - (void)toggleMute
547 {
548     intf_thread_t *p_intf = getIntf();
549     if (!p_intf)
550         return;
551
552     playlist_MuteToggle(pl_Get(p_intf));
553 }
554
555 - (BOOL)mute
556 {
557     intf_thread_t *p_intf = getIntf();
558     if (!p_intf)
559         return NO;
560
561     BOOL b_is_muted = NO;
562     b_is_muted = playlist_MuteGet(pl_Get(p_intf)) > 0;
563
564     return b_is_muted;
565 }
566
567 - (int)volume
568 {
569     intf_thread_t *p_intf = getIntf();
570     if (!p_intf)
571         return 0;
572
573     float volume = playlist_VolumeGet(pl_Get(p_intf));
574
575     return lroundf(volume * AOUT_VOLUME_DEFAULT);
576 }
577
578 - (void)setVolume: (int)i_value
579 {
580     intf_thread_t *p_intf = getIntf();
581     if (!p_intf)
582         return;
583
584     if (i_value >= self.maxVolume)
585         i_value = self.maxVolume;
586
587     float f_value = i_value / (float)AOUT_VOLUME_DEFAULT;
588
589     playlist_VolumeSet(pl_Get(p_intf), f_value);
590 }
591
592 - (float)maxVolume
593 {
594     if (f_maxVolume == 0.) {
595         f_maxVolume = (float)var_InheritInteger(getIntf(), "macosx-max-volume") / 100. * AOUT_VOLUME_DEFAULT;
596     }
597
598     return f_maxVolume;
599 }
600
601 - (void)addSubtitlesToCurrentInput:(NSArray *)paths
602 {
603     input_thread_t * p_input = pl_CurrentInput(getIntf());
604     if (!p_input)
605         return;
606
607     NSUInteger count = [paths count];
608
609     for (int i = 0; i < count ; i++) {
610         const char *path = [[[paths objectAtIndex:i] path] UTF8String];
611         msg_Dbg(getIntf(), "loading subs from %s", path);
612
613         int i_result = input_AddSubtitleOSD(p_input, path, true, true);
614         if (i_result != VLC_SUCCESS)
615             msg_Warn(getIntf(), "unable to load subtitles from '%s'", path);
616     }
617     vlc_object_release(p_input);
618 }
619
620 - (void)showPosition
621 {
622     input_thread_t *p_input = pl_CurrentInput(getIntf());
623     if (p_input != NULL) {
624         vout_thread_t *p_vout = input_GetVout(p_input);
625         if (p_vout != NULL) {
626             var_SetInteger(getIntf()->obj.libvlc, "key-action", ACTIONID_POSITION);
627             vlc_object_release(p_vout);
628         }
629         vlc_object_release(p_input);
630     }
631 }
632
633 #pragma mark - Drop support for files into the video, controls bar or drop box
634
635 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
636 {
637     NSArray *items = [[[VLCMain sharedInstance] playlist] createItemsFromExternalPasteboard:[sender draggingPasteboard]];
638
639     if (items.count == 0)
640         return NO;
641
642     [[[VLCMain sharedInstance] playlist] addPlaylistItems:items tryAsSubtitle:YES];
643     return YES;
644 }
645
646 #pragma mark - video output stuff
647
648 - (void)setAspectRatioIsLocked:(BOOL)b_value
649 {
650     config_PutInt(getIntf(), "macosx-lock-aspect-ratio", b_value);
651 }
652
653 - (BOOL)aspectRatioIsLocked
654 {
655     return config_GetInt(getIntf(), "macosx-lock-aspect-ratio");
656 }
657
658 - (void)toggleFullscreen
659 {
660     intf_thread_t *p_intf = getIntf();
661     if (!p_intf)
662         return;
663
664     vout_thread_t *p_vout = getVoutForActiveWindow();
665     if (p_vout) {
666         BOOL b_fs = var_ToggleBool(p_vout, "fullscreen");
667         var_SetBool(pl_Get(p_intf), "fullscreen", b_fs);
668         vlc_object_release(p_vout);
669     } else { // e.g. lion fullscreen toggle
670         BOOL b_fs = var_ToggleBool(pl_Get(p_intf), "fullscreen");
671         [[[VLCMain sharedInstance] voutController] setFullscreen:b_fs forWindow:nil withAnimation:YES];
672     }
673 }
674
675 #pragma mark - uncommon stuff
676
677 - (BOOL)fixPreferences
678 {
679     NSMutableString * o_workString;
680     NSRange returnedRange;
681     NSRange fullRange;
682     BOOL b_needsRestart = NO;
683
684     #define fixpref(pref) \
685     o_workString = [[NSMutableString alloc] initWithFormat:@"%s", config_GetPsz(getIntf(), pref)]; \
686     if ([o_workString length] > 0) \
687     { \
688         returnedRange = [o_workString rangeOfString:@"macosx" options: NSCaseInsensitiveSearch]; \
689         if (returnedRange.location != NSNotFound) \
690         { \
691             if ([o_workString isEqualToString:@"macosx"]) \
692                 [o_workString setString:@""]; \
693             fullRange = NSMakeRange(0, [o_workString length]); \
694             [o_workString replaceOccurrencesOfString:@":macosx" withString:@"" options: NSCaseInsensitiveSearch range: fullRange]; \
695             fullRange = NSMakeRange(0, [o_workString length]); \
696             [o_workString replaceOccurrencesOfString:@"macosx:" withString:@"" options: NSCaseInsensitiveSearch range: fullRange]; \
697             \
698             config_PutPsz(getIntf(), pref, [o_workString UTF8String]); \
699             b_needsRestart = YES; \
700         } \
701     }
702
703     fixpref("control");
704     fixpref("extraintf");
705     #undef fixpref
706
707     return b_needsRestart;
708 }
709
710 #pragma mark - video filter handling
711
712 - (const char *)getFilterType:(const char *)psz_name
713 {
714     module_t *p_obj = module_find(psz_name);
715     if (!p_obj) {
716         return NULL;
717     }
718
719     if (module_provides(p_obj, "video splitter")) {
720         return "video-splitter";
721     } else if (module_provides(p_obj, "video filter")) {
722         return "video-filter";
723     } else if (module_provides(p_obj, "sub source")) {
724         return "sub-source";
725     } else if (module_provides(p_obj, "sub filter")) {
726         return "sub-filter";
727     } else {
728         msg_Err(getIntf(), "Unknown video filter type.");
729         return NULL;
730     }
731 }
732
733 - (void)setVideoFilter: (const char *)psz_name on:(BOOL)b_on
734 {
735     intf_thread_t *p_intf = getIntf();
736     if (!p_intf)
737         return;
738     char *psz_string, *psz_parser;
739
740     const char *psz_filter_type = [self getFilterType:psz_name];
741     if (!psz_filter_type) {
742         msg_Err(p_intf, "Unable to find filter module \"%s\".", psz_name);
743         return;
744     }
745
746     msg_Dbg(p_intf, "will set filter '%s'", psz_name);
747
748
749     psz_string = config_GetPsz(p_intf, psz_filter_type);
750
751     if (b_on) {
752         if (psz_string == NULL) {
753             psz_string = strdup(psz_name);
754         } else if (strstr(psz_string, psz_name) == NULL) {
755             char *psz_tmp = strdup([[NSString stringWithFormat: @"%s:%s", psz_string, psz_name] UTF8String]);
756             free(psz_string);
757             psz_string = psz_tmp;
758         }
759     } else {
760         if (!psz_string)
761             return;
762
763         psz_parser = strstr(psz_string, psz_name);
764         if (psz_parser) {
765             if (*(psz_parser + strlen(psz_name)) == ':') {
766                 memmove(psz_parser, psz_parser + strlen(psz_name) + 1,
767                         strlen(psz_parser + strlen(psz_name) + 1) + 1);
768             } else {
769                 *psz_parser = '\0';
770             }
771
772             /* Remove trailing : : */
773             if (strlen(psz_string) > 0 && *(psz_string + strlen(psz_string) -1) == ':')
774                 *(psz_string + strlen(psz_string) -1) = '\0';
775         } else {
776             free(psz_string);
777             return;
778         }
779     }
780     config_PutPsz(p_intf, psz_filter_type, psz_string);
781
782     /* Try to set on the fly */
783     if (!strcmp(psz_filter_type, "video-splitter")) {
784         playlist_t *p_playlist = pl_Get(p_intf);
785         var_SetString(p_playlist, psz_filter_type, psz_string);
786     } else {
787         vout_thread_t *p_vout = getVout();
788         if (p_vout) {
789             var_SetString(p_vout, psz_filter_type, psz_string);
790             vlc_object_release(p_vout);
791         }
792     }
793
794     free(psz_string);
795 }
796
797 - (void)restartFilterIfNeeded: (const char *)psz_filter option: (const char *)psz_name
798 {
799     vout_thread_t *p_vout = getVout();
800     intf_thread_t *p_intf = getIntf();
801     if (!p_intf)
802         return;
803
804     if (p_vout == NULL)
805         return;
806     else
807         vlc_object_release(p_vout);
808
809     vlc_object_t *p_filter = vlc_object_find_name(pl_Get(p_intf), psz_filter);
810     if (p_filter) {
811
812         /* we cannot rely on the p_filter existence.
813          This filter might be just
814          disabled, but the object still exists. Therefore, the string
815          is checked, additionally.
816          */
817         const char *psz_filter_type = [self getFilterType:psz_filter];
818         if (!psz_filter_type) {
819             msg_Err(p_intf, "Unable to find filter module \"%s\".", psz_name);
820             goto out;
821         }
822
823         char *psz_string = config_GetPsz(p_intf, psz_filter_type);
824         if (!psz_string) {
825             goto out;
826         }
827         if (strstr(psz_string, psz_filter) == NULL) {
828             free(psz_string);
829             goto out;
830         }
831         free(psz_string);
832
833         int i_type;
834         i_type = var_Type(p_filter, psz_name);
835         if (i_type == 0)
836             i_type = config_GetType(p_intf, psz_name);
837
838         if (!(i_type & VLC_VAR_ISCOMMAND)) {
839             msg_Warn(p_intf, "Brute-restarting filter '%s', because the last changed option isn't a command", psz_name);
840
841             [self setVideoFilter: psz_filter on: NO];
842             [self setVideoFilter: psz_filter on: YES];
843         } else
844             msg_Dbg(p_intf, "restart not needed");
845
846         out:
847         vlc_object_release(p_filter);
848     }
849 }
850
851 - (void)setVideoFilterProperty: (const char *)psz_name forFilter: (const char *)psz_filter integer: (int)i_value
852 {
853     vout_thread_t *p_vout = getVout();
854     vlc_object_t *p_filter;
855     intf_thread_t *p_intf = getIntf();
856     if (!p_intf)
857         return;
858
859     config_PutInt(p_intf, psz_name, i_value);
860
861     if (p_vout) {
862         p_filter = vlc_object_find_name(pl_Get(p_intf), psz_filter);
863
864         if (!p_filter) {
865             msg_Warn(p_intf, "filter '%s' isn't enabled", psz_filter);
866             vlc_object_release(p_vout);
867             return;
868         }
869         var_SetInteger(p_filter, psz_name, i_value);
870         vlc_object_release(p_vout);
871         vlc_object_release(p_filter);
872
873         [self restartFilterIfNeeded: psz_filter option: psz_name];
874     }
875 }
876
877 - (void)setVideoFilterProperty: (const char *)psz_name forFilter: (const char *)psz_filter float: (float)f_value
878 {
879     vout_thread_t *p_vout = getVout();
880     vlc_object_t *p_filter;
881     intf_thread_t *p_intf = getIntf();
882     if (!p_intf)
883         return;
884
885     config_PutFloat(p_intf, psz_name, f_value);
886
887     if (p_vout) {
888         p_filter = vlc_object_find_name(pl_Get(p_intf), psz_filter);
889
890         if (!p_filter) {
891             msg_Warn(p_intf, "filter '%s' isn't enabled", psz_filter);
892             vlc_object_release(p_vout);
893             return;
894         }
895         var_SetFloat(p_filter, psz_name, f_value);
896         vlc_object_release(p_vout);
897         vlc_object_release(p_filter);
898
899         [self restartFilterIfNeeded: psz_filter option: psz_name];
900     }
901 }
902
903 - (void)setVideoFilterProperty: (const char *)psz_name forFilter: (const char *)psz_filter string: (const char *)psz_value
904 {
905     char *psz_new_value = strdup(psz_value);
906     vout_thread_t *p_vout = getVout();
907     vlc_object_t *p_filter;
908     intf_thread_t *p_intf = getIntf();
909     if (!p_intf)
910         return;
911
912     config_PutPsz(p_intf, psz_name, EnsureUTF8(psz_new_value));
913
914     if (p_vout) {
915         p_filter = vlc_object_find_name(pl_Get(p_intf), psz_filter);
916
917         if (!p_filter) {
918             msg_Warn(p_intf, "filter '%s' isn't enabled", psz_filter);
919             vlc_object_release(p_vout);
920             return;
921         }
922         var_SetString(p_filter, psz_name, EnsureUTF8(psz_new_value));
923         vlc_object_release(p_vout);
924         vlc_object_release(p_filter);
925
926         [self restartFilterIfNeeded: psz_filter option: psz_name];
927     }
928
929     free(psz_new_value);
930 }
931
932 - (void)setVideoFilterProperty: (const char *)psz_name forFilter: (const char *)psz_filter boolean: (BOOL)b_value
933 {
934     vout_thread_t *p_vout = getVout();
935     vlc_object_t *p_filter;
936     intf_thread_t *p_intf = getIntf();
937     if (!p_intf)
938         return;
939
940     config_PutInt(p_intf, psz_name, b_value);
941
942     if (p_vout) {
943         p_filter = vlc_object_find_name(pl_Get(p_intf), psz_filter);
944
945         if (!p_filter) {
946             msg_Warn(p_intf, "filter '%s' isn't enabled", psz_filter);
947             vlc_object_release(p_vout);
948             return;
949         }
950         var_SetBool(p_filter, psz_name, b_value);
951         vlc_object_release(p_vout);
952         vlc_object_release(p_filter);
953     }
954 }
955
956 #pragma mark -
957 #pragma mark Media Key support
958
959 - (void)resetMediaKeyJump
960 {
961     b_mediakeyJustJumped = NO;
962 }
963
964 - (void)coreChangedMediaKeySupportSetting: (NSNotification *)o_notification
965 {
966     intf_thread_t *p_intf = getIntf();
967     if (!p_intf)
968         return;
969
970     b_mediaKeySupport = var_InheritBool(p_intf, "macosx-mediakeys");
971     if (b_mediaKeySupport && !_mediaKeyController)
972         _mediaKeyController = [[SPMediaKeyTap alloc] initWithDelegate:self];
973
974     VLCMain *main = [VLCMain sharedInstance];
975     if (b_mediaKeySupport && ([[[main playlist] model] hasChildren] ||
976                               [[main inputManager] hasInput])) {
977         if (!b_mediaKeyTrapEnabled) {
978             b_mediaKeyTrapEnabled = YES;
979             msg_Dbg(p_intf, "Enable media key support");
980             [_mediaKeyController startWatchingMediaKeys];
981         }
982     } else {
983         if (b_mediaKeyTrapEnabled) {
984             b_mediaKeyTrapEnabled = NO;
985             msg_Dbg(p_intf, "Disable media key support");
986             [_mediaKeyController stopWatchingMediaKeys];
987         }
988     }
989 }
990
991 -(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event
992 {
993     if (b_mediaKeySupport) {
994         assert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys);
995
996         int keyCode = (([event data1] & 0xFFFF0000) >> 16);
997         int keyFlags = ([event data1] & 0x0000FFFF);
998         int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
999         int keyRepeat = (keyFlags & 0x1);
1000
1001         if (keyCode == NX_KEYTYPE_PLAY && keyState == 0)
1002             [self playOrPause];
1003
1004         if ((keyCode == NX_KEYTYPE_FAST || keyCode == NX_KEYTYPE_NEXT) && !b_mediakeyJustJumped) {
1005             if (keyState == 0 && keyRepeat == 0)
1006                 [self next];
1007             else if (keyRepeat == 1) {
1008                 [self forwardShort];
1009                 b_mediakeyJustJumped = YES;
1010                 [self performSelector:@selector(resetMediaKeyJump)
1011                            withObject: NULL
1012                            afterDelay:0.25];
1013             }
1014         }
1015
1016         if ((keyCode == NX_KEYTYPE_REWIND || keyCode == NX_KEYTYPE_PREVIOUS) && !b_mediakeyJustJumped) {
1017             if (keyState == 0 && keyRepeat == 0)
1018                 [self previous];
1019             else if (keyRepeat == 1) {
1020                 [self backwardShort];
1021                 b_mediakeyJustJumped = YES;
1022                 [self performSelector:@selector(resetMediaKeyJump)
1023                            withObject: NULL
1024                            afterDelay:0.25];
1025             }
1026         }
1027     }
1028 }
1029
1030 #pragma mark -
1031 #pragma mark Apple Remote Control
1032
1033 - (void)startListeningWithAppleRemote
1034 {
1035     [_remote startListening: self];
1036 }
1037
1038 - (void)stopListeningWithAppleRemote
1039 {
1040     [_remote stopListening:self];
1041 }
1042
1043 #pragma mark - menu navigation
1044 - (void)menuFocusActivate
1045 {
1046     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
1047     if (p_input_thread == NULL)
1048         return;
1049
1050     input_Control(p_input_thread, INPUT_NAV_ACTIVATE, NULL );
1051     vlc_object_release(p_input_thread);
1052 }
1053
1054 - (void)moveMenuFocusLeft
1055 {
1056     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
1057     if (p_input_thread == NULL)
1058         return;
1059
1060     input_Control(p_input_thread, INPUT_NAV_LEFT, NULL );
1061     vlc_object_release(p_input_thread);
1062 }
1063
1064 - (void)moveMenuFocusRight
1065 {
1066     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
1067     if (p_input_thread == NULL)
1068         return;
1069
1070     input_Control(p_input_thread, INPUT_NAV_RIGHT, NULL );
1071     vlc_object_release(p_input_thread);
1072 }
1073
1074 - (void)moveMenuFocusUp
1075 {
1076     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
1077     if (p_input_thread == NULL)
1078         return;
1079
1080     input_Control(p_input_thread, INPUT_NAV_UP, NULL );
1081     vlc_object_release(p_input_thread);
1082 }
1083
1084 - (void)moveMenuFocusDown
1085 {
1086     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
1087     if (p_input_thread == NULL)
1088         return;
1089
1090     input_Control(p_input_thread, INPUT_NAV_DOWN, NULL );
1091     vlc_object_release(p_input_thread);
1092 }
1093
1094 /* Helper method for the remote control interface in order to trigger forward/backward and volume
1095  increase/decrease as long as the user holds the left/right, plus/minus button */
1096 - (void) executeHoldActionForRemoteButton: (NSNumber*) buttonIdentifierNumber
1097 {
1098     intf_thread_t *p_intf = getIntf();
1099     if (!p_intf)
1100         return;
1101
1102     if (b_remote_button_hold) {
1103         switch([buttonIdentifierNumber intValue]) {
1104             case kRemoteButtonRight_Hold:
1105                 [self forward];
1106                 break;
1107             case kRemoteButtonLeft_Hold:
1108                 [self backward];
1109                 break;
1110             case kRemoteButtonVolume_Plus_Hold:
1111                 if (p_intf)
1112                     var_SetInteger(p_intf->obj.libvlc, "key-action", ACTIONID_VOL_UP);
1113                 break;
1114             case kRemoteButtonVolume_Minus_Hold:
1115                 if (p_intf)
1116                     var_SetInteger(p_intf->obj.libvlc, "key-action", ACTIONID_VOL_DOWN);
1117                 break;
1118         }
1119         if (b_remote_button_hold) {
1120             /* trigger event */
1121             [self performSelector:@selector(executeHoldActionForRemoteButton:)
1122                        withObject:buttonIdentifierNumber
1123                        afterDelay:0.25];
1124         }
1125     }
1126 }
1127
1128 /* Apple Remote callback */
1129 - (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier
1130                pressedDown: (BOOL) pressedDown
1131                 clickCount: (unsigned int) count
1132 {
1133     intf_thread_t *p_intf = getIntf();
1134     if (!p_intf)
1135         return;
1136
1137     switch(buttonIdentifier) {
1138         case k2009RemoteButtonFullscreen:
1139             [self toggleFullscreen];
1140             break;
1141         case k2009RemoteButtonPlay:
1142             [self playOrPause];
1143             break;
1144         case kRemoteButtonPlay:
1145             if (count >= 2)
1146                 [self toggleFullscreen];
1147             else
1148                 [self playOrPause];
1149             break;
1150         case kRemoteButtonVolume_Plus:
1151             if (config_GetInt(getIntf(), "macosx-appleremote-sysvol"))
1152                 [NSSound increaseSystemVolume];
1153             else
1154                 if (p_intf)
1155                     var_SetInteger(p_intf->obj.libvlc, "key-action", ACTIONID_VOL_UP);
1156             break;
1157         case kRemoteButtonVolume_Minus:
1158             if (config_GetInt(getIntf(), "macosx-appleremote-sysvol"))
1159                 [NSSound decreaseSystemVolume];
1160             else
1161                 if (p_intf)
1162                     var_SetInteger(p_intf->obj.libvlc, "key-action", ACTIONID_VOL_DOWN);
1163             break;
1164         case kRemoteButtonRight:
1165             if (config_GetInt(getIntf(), "macosx-appleremote-prevnext"))
1166                 [self forward];
1167             else
1168                 [self next];
1169             break;
1170         case kRemoteButtonLeft:
1171             if (config_GetInt(getIntf(), "macosx-appleremote-prevnext"))
1172                 [self backward];
1173             else
1174                 [self previous];
1175             break;
1176         case kRemoteButtonRight_Hold:
1177         case kRemoteButtonLeft_Hold:
1178         case kRemoteButtonVolume_Plus_Hold:
1179         case kRemoteButtonVolume_Minus_Hold:
1180             /* simulate an event as long as the user holds the button */
1181             b_remote_button_hold = pressedDown;
1182             if (pressedDown) {
1183                 NSNumber* buttonIdentifierNumber = [NSNumber numberWithInt:buttonIdentifier];
1184                 [self performSelector:@selector(executeHoldActionForRemoteButton:)
1185                            withObject:buttonIdentifierNumber];
1186             }
1187             break;
1188         case kRemoteButtonMenu:
1189             [self showPosition];
1190             break;
1191         case kRemoteButtonPlay_Sleep:
1192         {
1193             NSAppleScript * script = [[NSAppleScript alloc] initWithSource:@"tell application \"System Events\" to sleep"];
1194             [script executeAndReturnError:nil];
1195             break;
1196         }
1197         default:
1198             /* Add here whatever you want other buttons to do */
1199             break;
1200     }
1201 }
1202
1203 #pragma mark -
1204 #pragma mark Key Shortcuts
1205
1206 /*****************************************************************************
1207  * hasDefinedShortcutKey: Check to see if the key press is a defined VLC
1208  * shortcut key.  If it is, pass it off to VLC for handling and return YES,
1209  * otherwise ignore it and return NO (where it will get handled by Cocoa).
1210  *****************************************************************************/
1211
1212 - (BOOL)keyEvent:(NSEvent *)o_event
1213 {
1214     BOOL eventHandled = NO;
1215     NSString * characters = [o_event charactersIgnoringModifiers];
1216     if ([characters length] > 0) {
1217         unichar key = [characters characterAtIndex: 0];
1218
1219         if (key) {
1220             input_thread_t * p_input = pl_CurrentInput(getIntf());
1221             if (p_input != NULL) {
1222                 vout_thread_t *p_vout = input_GetVout(p_input);
1223
1224                 if (p_vout != NULL) {
1225                     /* Escape */
1226                     if (key == (unichar) 0x1b) {
1227                         if (var_GetBool(p_vout, "fullscreen")) {
1228                             [self toggleFullscreen];
1229                             eventHandled = YES;
1230                         }
1231                     }
1232                     vlc_object_release(p_vout);
1233                 }
1234                 vlc_object_release(p_input);
1235             }
1236         }
1237     }
1238     return eventHandled;
1239 }
1240
1241 - (BOOL)hasDefinedShortcutKey:(NSEvent *)o_event force:(BOOL)b_force
1242 {
1243     intf_thread_t *p_intf = getIntf();
1244     if (!p_intf)
1245         return NO;
1246
1247     unichar key = 0;
1248     vlc_value_t val;
1249     unsigned int i_pressed_modifiers = 0;
1250
1251     val.i_int = 0;
1252     i_pressed_modifiers = [o_event modifierFlags];
1253
1254     if (i_pressed_modifiers & NSControlKeyMask)
1255         val.i_int |= KEY_MODIFIER_CTRL;
1256
1257     if (i_pressed_modifiers & NSAlternateKeyMask)
1258         val.i_int |= KEY_MODIFIER_ALT;
1259
1260     if (i_pressed_modifiers & NSShiftKeyMask)
1261         val.i_int |= KEY_MODIFIER_SHIFT;
1262
1263     if (i_pressed_modifiers & NSCommandKeyMask)
1264         val.i_int |= KEY_MODIFIER_COMMAND;
1265
1266     NSString * characters = [o_event charactersIgnoringModifiers];
1267     if ([characters length] > 0) {
1268         key = [[characters lowercaseString] characterAtIndex: 0];
1269
1270         /* handle Lion's default key combo for fullscreen-toggle in addition to our own hotkeys */
1271         if (key == 'f' && i_pressed_modifiers & NSControlKeyMask && i_pressed_modifiers & NSCommandKeyMask) {
1272             [self toggleFullscreen];
1273             return YES;
1274         }
1275
1276         if (!b_force) {
1277             switch(key) {
1278                 case NSDeleteCharacter:
1279                 case NSDeleteFunctionKey:
1280                 case NSDeleteCharFunctionKey:
1281                 case NSBackspaceCharacter:
1282                 case NSUpArrowFunctionKey:
1283                 case NSDownArrowFunctionKey:
1284                 case NSEnterCharacter:
1285                 case NSCarriageReturnCharacter:
1286                     return NO;
1287             }
1288         }
1289
1290         val.i_int |= CocoaKeyToVLC(key);
1291
1292         BOOL b_found_key = NO;
1293         for (NSUInteger i = 0; i < [_usedHotkeys count]; i++) {
1294             NSString *str = [_usedHotkeys objectAtIndex:i];
1295             unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa: str];
1296
1297             if ([[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: str]] &&
1298                 (i_keyModifiers & NSShiftKeyMask)     == (i_pressed_modifiers & NSShiftKeyMask) &&
1299                 (i_keyModifiers & NSControlKeyMask)   == (i_pressed_modifiers & NSControlKeyMask) &&
1300                 (i_keyModifiers & NSAlternateKeyMask) == (i_pressed_modifiers & NSAlternateKeyMask) &&
1301                 (i_keyModifiers & NSCommandKeyMask)   == (i_pressed_modifiers & NSCommandKeyMask)) {
1302                 b_found_key = YES;
1303                 break;
1304             }
1305         }
1306
1307         if (b_found_key) {
1308             var_SetInteger(p_intf->obj.libvlc, "key-pressed", val.i_int);
1309             return YES;
1310         }
1311     }
1312
1313     return NO;
1314 }
1315
1316 - (void)updateCurrentlyUsedHotkeys
1317 {
1318     NSMutableArray *mutArray = [[NSMutableArray alloc] init];
1319     /* Get the main Module */
1320     module_t *p_main = module_get_main();
1321     assert(p_main);
1322     unsigned confsize;
1323     module_config_t *p_config;
1324
1325     p_config = module_config_get (p_main, &confsize);
1326
1327     for (size_t i = 0; i < confsize; i++) {
1328         module_config_t *p_item = p_config + i;
1329
1330         if (CONFIG_ITEM(p_item->i_type) && p_item->psz_name != NULL
1331             && !strncmp(p_item->psz_name , "key-", 4)
1332             && !EMPTY_STR(p_item->psz_text)) {
1333             if (p_item->value.psz)
1334                 [mutArray addObject:toNSStr(p_item->value.psz)];
1335         }
1336     }
1337     module_config_free (p_config);
1338
1339     _usedHotkeys = [[NSArray alloc] initWithArray:mutArray copyItems:YES];
1340 }
1341
1342 @end