gui/macosx: use input vars instead of controls
[vlc.git] / modules / gui / macosx / VLCCoreInteraction.m
blob904183594d1f934d36764ce0930a9e51fd5ebd1b
1 /*****************************************************************************
2  * CoreInteraction.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2011-2018 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  *****************************************************************************/
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_actions.h>
32 #import <vlc_aout.h>
33 #import <vlc_vout.h>
34 #import <vlc_vout_osd.h>
35 #import <vlc/vlc.h>
36 #import <vlc_strings.h>
37 #import <vlc_url.h>
38 #import <vlc_modules.h>
39 #import <vlc_charset.h>
40 #include <vlc_plugin.h>
41 #import "SPMediaKeyTap.h"
42 #import "AppleRemote.h"
43 #import "VLCInputManager.h"
45 #import "NSSound+VLCAdditions.h"
47 static int BossCallback(vlc_object_t *p_this, const char *psz_var,
48                         vlc_value_t oldval, vlc_value_t new_val, void *param)
50     @autoreleasepool {
51         dispatch_async(dispatch_get_main_queue(), ^{
52             [[VLCCoreInteraction sharedInstance] pause];
53             [[NSApplication sharedApplication] hide:nil];
54         });
56         return VLC_SUCCESS;
57     }
60 @interface VLCCoreInteraction ()
62     int i_currentPlaybackRate;
63     vlc_tick_t timeA, timeB;
65     float f_maxVolume;
67     /* media key support */
68     BOOL b_mediaKeySupport;
69     BOOL b_mediakeyJustJumped;
70     SPMediaKeyTap *_mediaKeyController;
71     BOOL b_mediaKeyTrapEnabled;
73     AppleRemote *_remote;
74     BOOL b_remote_button_hold; /* true as long as the user holds the left,right,plus or minus on the remote control */
76     NSArray *_usedHotkeys;
78 @end
80 @implementation VLCCoreInteraction
82 #pragma mark - Initialization
84 + (VLCCoreInteraction *)sharedInstance
86     static VLCCoreInteraction *sharedInstance = nil;
87     static dispatch_once_t pred;
89     dispatch_once(&pred, ^{
90         sharedInstance = [VLCCoreInteraction new];
91     });
93     return sharedInstance;
96 - (instancetype)init
98     self = [super init];
99     if (self) {
100         intf_thread_t *p_intf = getIntf();
102         /* init media key support */
103         b_mediaKeySupport = var_InheritBool(p_intf, "macosx-mediakeys");
104         if (b_mediaKeySupport) {
105             _mediaKeyController = [[SPMediaKeyTap alloc] initWithDelegate:self];
106             [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
107                                                                      [SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey,
108                                                                      nil]];
109         }
110         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(coreChangedMediaKeySupportSetting:) name:VLCMediaKeySupportSettingChangedNotification object: nil];
112         /* init Apple Remote support */
113         _remote = [[AppleRemote alloc] init];
114         [_remote setClickCountEnabledButtons: kRemoteButtonPlay];
115         [_remote setDelegate: self];
117         var_AddCallback(pl_Get(p_intf), "intf-boss", BossCallback, (__bridge void *)self);
118     }
119     return self;
122 - (void)dealloc
124     intf_thread_t *p_intf = getIntf();
125     var_DelCallback(pl_Get(p_intf), "intf-boss", BossCallback, (__bridge void *)self);
126     [[NSNotificationCenter defaultCenter] removeObserver: self];
130 #pragma mark - Playback Controls
132 - (void)play
134     playlist_t *p_playlist = pl_Get(getIntf());
136     playlist_Play(p_playlist);
139 - (void)playOrPause
141     input_thread_t *p_input = pl_CurrentInput(getIntf());
142     playlist_t *p_playlist = pl_Get(getIntf());
144     if (p_input) {
145         playlist_TogglePause(p_playlist);
146         vlc_object_release(p_input);
148     } else {
149         PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
150         if ([[[VLCMain sharedInstance] playlist] isSelectionEmpty] && (root == ROOT_TYPE_PLAYLIST || root == ROOT_TYPE_MEDIALIBRARY))
151             [[[VLCMain sharedInstance] open] openFileGeneric];
152         else
153             [[[VLCMain sharedInstance] playlist] playItem:nil];
154     }
157 - (void)pause
159     playlist_t *p_playlist = pl_Get(getIntf());
161     playlist_Pause(p_playlist);
164 - (void)stop
166     playlist_Stop(pl_Get(getIntf()));
169 - (void)faster
171     var_TriggerCallback(pl_Get(getIntf()), "rate-faster");
174 - (void)slower
176     var_TriggerCallback(pl_Get(getIntf()), "rate-slower");
179 - (void)normalSpeed
181     var_SetFloat(pl_Get(getIntf()), "rate", 1.);
184 - (void)toggleRecord
186     intf_thread_t *p_intf = getIntf();
187     if (!p_intf)
188         return;
190     input_thread_t * p_input;
191     p_input = pl_CurrentInput(p_intf);
192     if (p_input) {
193         var_ToggleBool(p_input, "record");
194         vlc_object_release(p_input);
195     }
198 - (void)setPlaybackRate:(int)i_value
200     playlist_t * p_playlist = pl_Get(getIntf());
202     double speed = pow(2, (double)i_value / 17);
203     int rate = INPUT_RATE_DEFAULT / speed;
204     if (i_currentPlaybackRate != rate)
205         var_SetFloat(p_playlist, "rate", (float)INPUT_RATE_DEFAULT / (float)rate);
206     i_currentPlaybackRate = rate;
209 - (int)playbackRate
211     float f_rate;
213     intf_thread_t *p_intf = getIntf();
214     if (!p_intf)
215         return 0;
217     input_thread_t * p_input;
218     p_input = pl_CurrentInput(p_intf);
219     if (p_input) {
220         f_rate = var_GetFloat(p_input, "rate");
221         vlc_object_release(p_input);
222     }
223     else
224     {
225         playlist_t * p_playlist = pl_Get(getIntf());
226         f_rate = var_GetFloat(p_playlist, "rate");
227     }
229     double value = 17 * log(f_rate) / log(2.);
230     int returnValue = (int) ((value > 0) ? value + .5 : value - .5);
232     if (returnValue < -34)
233         returnValue = -34;
234     else if (returnValue > 34)
235         returnValue = 34;
237     i_currentPlaybackRate = returnValue;
238     return returnValue;
241 - (void)previous
243     playlist_Prev(pl_Get(getIntf()));
246 - (void)next
248     playlist_Next(pl_Get(getIntf()));
251 - (NSInteger)durationOfCurrentPlaylistItem
253     intf_thread_t *p_intf = getIntf();
254     if (!p_intf)
255         return 0;
257     input_thread_t * p_input = pl_CurrentInput(p_intf);
258     int64_t i_duration = -1;
259     if (!p_input)
260         return i_duration;
262     i_duration = var_GetInteger(p_input, "length");
263     vlc_object_release(p_input);
265     return (i_duration / 1000000);
268 - (NSURL*)URLOfCurrentPlaylistItem
270     intf_thread_t *p_intf = getIntf();
271     if (!p_intf)
272         return nil;
274     input_thread_t *p_input = pl_CurrentInput(p_intf);
275     if (!p_input)
276         return nil;
278     input_item_t *p_item = input_GetItem(p_input);
279     if (!p_item) {
280         vlc_object_release(p_input);
281         return nil;
282     }
284     char *psz_uri = input_item_GetURI(p_item);
285     if (!psz_uri) {
286         vlc_object_release(p_input);
287         return nil;
288     }
290     NSURL *o_url;
291     o_url = [NSURL URLWithString:toNSStr(psz_uri)];
292     free(psz_uri);
293     vlc_object_release(p_input);
295     return o_url;
298 - (NSString*)nameOfCurrentPlaylistItem
300     intf_thread_t *p_intf = getIntf();
301     if (!p_intf)
302         return nil;
304     input_thread_t *p_input = pl_CurrentInput(p_intf);
305     if (!p_input)
306         return nil;
308     input_item_t *p_item = input_GetItem(p_input);
309     if (!p_item) {
310         vlc_object_release(p_input);
311         return nil;
312     }
314     char *psz_uri = input_item_GetURI(p_item);
315     if (!psz_uri) {
316         vlc_object_release(p_input);
317         return nil;
318     }
320     NSString *o_name = @"";
321     char *format = var_InheritString(getIntf(), "input-title-format");
322     if (format) {
323         char *formated = vlc_strfinput(p_input, format);
324         free(format);
325         o_name = toNSStr(formated);
326         free(formated);
327     }
329     NSURL * o_url = [NSURL URLWithString:toNSStr(psz_uri)];
330     free(psz_uri);
332     if ([o_name isEqualToString:@""]) {
333         if ([o_url isFileURL])
334             o_name = [[NSFileManager defaultManager] displayNameAtPath:[o_url path]];
335         else
336             o_name = [o_url absoluteString];
337     }
338     vlc_object_release(p_input);
339     return o_name;
342 - (void)forward
344     //LEGACY SUPPORT
345     [self forwardShort];
348 - (void)backward
350     //LEGACY SUPPORT
351     [self backwardShort];
354 - (void)jumpWithValue:(char *)p_value forward:(BOOL)b_value
356     input_thread_t *p_input = pl_CurrentInput(getIntf());
357     if (!p_input)
358         return;
360     int64_t i_interval = var_InheritInteger( p_input, p_value );
361     if (i_interval > 0) {
362         vlc_tick_t val = vlc_tick_from_sec( i_interval );
363         if (!b_value)
364             val = val * -1;
365         var_SetInteger( p_input, "time-offset", val );
366     }
367     vlc_object_release(p_input);
370 - (void)forwardExtraShort
372     [self jumpWithValue:"extrashort-jump-size" forward:YES];
375 - (void)backwardExtraShort
377     [self jumpWithValue:"extrashort-jump-size" forward:NO];
380 - (void)forwardShort
382     [self jumpWithValue:"short-jump-size" forward:YES];
385 - (void)backwardShort
387     [self jumpWithValue:"short-jump-size" forward:NO];
390 - (void)forwardMedium
392     [self jumpWithValue:"medium-jump-size" forward:YES];
395 - (void)backwardMedium
397     [self jumpWithValue:"medium-jump-size" forward:NO];
400 - (void)forwardLong
402     [self jumpWithValue:"long-jump-size" forward:YES];
405 - (void)backwardLong
407     [self jumpWithValue:"long-jump-size" forward:NO];
410 - (void)shuffle
412     intf_thread_t *p_intf = getIntf();
413     if (!p_intf)
414         return;
416     vlc_value_t val;
417     playlist_t * p_playlist = pl_Get(p_intf);
418     vout_thread_t *p_vout = getVout();
420     var_Get(p_playlist, "random", &val);
421     val.b_bool = !val.b_bool;
422     var_Set(p_playlist, "random", val);
423     if (val.b_bool) {
424         if (p_vout) {
425             vout_OSDMessage(p_vout, VOUT_SPU_CHANNEL_OSD, "%s", _("Random On"));
426             vlc_object_release(p_vout);
427         }
428         config_PutInt("random", 1);
429     }
430     else
431     {
432         if (p_vout) {
433             vout_OSDMessage(p_vout, VOUT_SPU_CHANNEL_OSD, "%s", _("Random Off"));
434             vlc_object_release(p_vout);
435         }
436         config_PutInt("random", 0);
437     }
440 - (void)repeatAll
442     intf_thread_t *p_intf = getIntf();
443     if (!p_intf)
444         return;
446     playlist_t * p_playlist = pl_Get(p_intf);
448     var_SetBool(p_playlist, "repeat", NO);
449     var_SetBool(p_playlist, "loop", YES);
450     config_PutInt("repeat", NO);
451     config_PutInt("loop", YES);
453     vout_thread_t *p_vout = getVout();
454     if (p_vout) {
455         vout_OSDMessage(p_vout, VOUT_SPU_CHANNEL_OSD, "%s", _("Repeat All"));
456         vlc_object_release(p_vout);
457     }
460 - (void)repeatOne
462     intf_thread_t *p_intf = getIntf();
463     if (!p_intf)
464         return;
466     playlist_t * p_playlist = pl_Get(p_intf);
468     var_SetBool(p_playlist, "repeat", YES);
469     var_SetBool(p_playlist, "loop", NO);
470     config_PutInt("repeat", YES);
471     config_PutInt("loop", NO);
473     vout_thread_t *p_vout = getVout();
474     if (p_vout) {
475         vout_OSDMessage(p_vout, VOUT_SPU_CHANNEL_OSD, "%s", _("Repeat One"));
476         vlc_object_release(p_vout);
477     }
480 - (void)repeatOff
482     intf_thread_t *p_intf = getIntf();
483     if (!p_intf)
484         return;
486     playlist_t * p_playlist = pl_Get(p_intf);
488     var_SetBool(p_playlist, "repeat", NO);
489     var_SetBool(p_playlist, "loop", NO);
490     config_PutInt("repeat", NO);
491     config_PutInt("loop", NO);
493     vout_thread_t *p_vout = getVout();
494     if (p_vout) {
495         vout_OSDMessage(p_vout, VOUT_SPU_CHANNEL_OSD, "%s", _("Repeat Off"));
496         vlc_object_release(p_vout);
497     }
500 - (void)setAtoB
502     if (!timeA) {
503         input_thread_t * p_input = pl_CurrentInput(getIntf());
504         if (p_input) {
505             msg_Dbg(getIntf(), "Setting A value");
507             timeA = var_GetInteger(p_input, "time");
508             vlc_object_release(p_input);
509         }
510     } else if (!timeB) {
511         input_thread_t * p_input = pl_CurrentInput(getIntf());
512         if (p_input) {
513             msg_Dbg(getIntf(), "Setting B value");
515             timeB = var_GetInteger(p_input, "time");
516             vlc_object_release(p_input);
517         }
518     } else
519         [self resetAtoB];
522 - (void)resetAtoB
524     msg_Dbg(getIntf(), "Resetting A to B values");
525     timeA = 0;
526     timeB = 0;
529 - (void)updateAtoB
531     if (timeB) {
532         input_thread_t * p_input = pl_CurrentInput(getIntf());
533         if (p_input) {
534             vlc_tick_t currentTime = var_GetInteger(p_input, "time");
535             if ( currentTime >= timeB || currentTime < timeA)
536                 var_SetInteger(p_input, "time", timeA);
537             vlc_object_release(p_input);
538         }
539     }
542 - (void)jumpToTime:(vlc_tick_t)time
544     input_thread_t * p_input = pl_CurrentInput(getIntf());
545     if (p_input) {
546         var_SetInteger(p_input, "time", time);
547         vlc_object_release(p_input);
548     }
551 - (void)volumeUp
553     intf_thread_t *p_intf = getIntf();
554     if (!p_intf)
555         return;
557     playlist_VolumeUp(pl_Get(p_intf), 1, NULL);
560 - (void)volumeDown
562     intf_thread_t *p_intf = getIntf();
563     if (!p_intf)
564         return;
566     playlist_VolumeDown(pl_Get(p_intf), 1, NULL);
569 - (void)toggleMute
571     intf_thread_t *p_intf = getIntf();
572     if (!p_intf)
573         return;
575     playlist_MuteToggle(pl_Get(p_intf));
578 - (BOOL)mute
580     intf_thread_t *p_intf = getIntf();
581     if (!p_intf)
582         return NO;
584     BOOL b_is_muted = NO;
585     b_is_muted = playlist_MuteGet(pl_Get(p_intf)) > 0;
587     return b_is_muted;
590 - (int)volume
592     intf_thread_t *p_intf = getIntf();
593     if (!p_intf)
594         return 0;
596     float volume = playlist_VolumeGet(pl_Get(p_intf));
598     return (int)lroundf(volume * AOUT_VOLUME_DEFAULT);
601 - (void)setVolume: (int)i_value
603     intf_thread_t *p_intf = getIntf();
604     if (!p_intf)
605         return;
607     if (i_value >= self.maxVolume)
608         i_value = self.maxVolume;
610     float f_value = i_value / (float)AOUT_VOLUME_DEFAULT;
612     playlist_VolumeSet(pl_Get(p_intf), f_value);
615 - (float)maxVolume
617     if (f_maxVolume == 0.) {
618         f_maxVolume = (float)var_InheritInteger(getIntf(), "macosx-max-volume") / 100. * AOUT_VOLUME_DEFAULT;
619     }
621     return f_maxVolume;
624 - (void)addSubtitlesToCurrentInput:(NSArray *)paths
626     input_thread_t * p_input = pl_CurrentInput(getIntf());
627     if (!p_input)
628         return;
630     NSUInteger count = [paths count];
632     for (int i = 0; i < count ; i++) {
633         char *mrl = vlc_path2uri([[[paths objectAtIndex:i] path] UTF8String], NULL);
634         if (!mrl)
635             continue;
636         msg_Dbg(getIntf(), "loading subs from %s", mrl);
638         int i_result = input_AddSlave(p_input, SLAVE_TYPE_SPU, mrl, true, true, true);
639         if (i_result != VLC_SUCCESS)
640             msg_Warn(getIntf(), "unable to load subtitles from '%s'", mrl);
641         free(mrl);
642     }
643     vlc_object_release(p_input);
646 - (void)showPosition
648     input_thread_t *p_input = pl_CurrentInput(getIntf());
649     if (p_input != NULL) {
650         vout_thread_t *p_vout = input_GetVout(p_input);
651         if (p_vout != NULL) {
652             var_SetInteger(getIntf()->obj.libvlc, "key-action", ACTIONID_POSITION);
653             vlc_object_release(p_vout);
654         }
655         vlc_object_release(p_input);
656     }
659 #pragma mark - Drop support for files into the video, controls bar or drop box
661 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
663     NSArray *items = [[[VLCMain sharedInstance] playlist] createItemsFromExternalPasteboard:[sender draggingPasteboard]];
665     if (items.count == 0)
666         return NO;
668     [[[VLCMain sharedInstance] playlist] addPlaylistItems:items tryAsSubtitle:YES];
669     return YES;
672 #pragma mark - video output stuff
674 - (void)setAspectRatioIsLocked:(BOOL)b_value
676     config_PutInt("macosx-lock-aspect-ratio", b_value);
679 - (BOOL)aspectRatioIsLocked
681     return config_GetInt("macosx-lock-aspect-ratio");
684 - (void)toggleFullscreen
686     intf_thread_t *p_intf = getIntf();
687     if (!p_intf)
688         return;
690     vout_thread_t *p_vout = getVoutForActiveWindow();
691     if (p_vout) {
692         BOOL b_fs = var_ToggleBool(p_vout, "fullscreen");
693         var_SetBool(pl_Get(p_intf), "fullscreen", b_fs);
694         vlc_object_release(p_vout);
695     } else { // e.g. lion fullscreen toggle
696         BOOL b_fs = var_ToggleBool(pl_Get(p_intf), "fullscreen");
697         [[[VLCMain sharedInstance] voutProvider] setFullscreen:b_fs forWindow:nil withAnimation:YES];
698     }
701 #pragma mark - uncommon stuff
703 - (BOOL)fixIntfSettings
705     NSMutableString * o_workString;
706     NSRange returnedRange;
707     NSRange fullRange;
708     BOOL b_needsRestart = NO;
710     #define fixpref(pref) \
711     o_workString = [[NSMutableString alloc] initWithFormat:@"%s", config_GetPsz(pref)]; \
712     if ([o_workString length] > 0) \
713     { \
714         returnedRange = [o_workString rangeOfString:@"macosx" options: NSCaseInsensitiveSearch]; \
715         if (returnedRange.location != NSNotFound) \
716         { \
717             if ([o_workString isEqualToString:@"macosx"]) \
718                 [o_workString setString:@""]; \
719             fullRange = NSMakeRange(0, [o_workString length]); \
720             [o_workString replaceOccurrencesOfString:@":macosx" withString:@"" options: NSCaseInsensitiveSearch range: fullRange]; \
721             fullRange = NSMakeRange(0, [o_workString length]); \
722             [o_workString replaceOccurrencesOfString:@"macosx:" withString:@"" options: NSCaseInsensitiveSearch range: fullRange]; \
723             \
724             config_PutPsz(pref, [o_workString UTF8String]); \
725             b_needsRestart = YES; \
726         } \
727     }
729     fixpref("control");
730     fixpref("extraintf");
731     #undef fixpref
733     return b_needsRestart;
736 #pragma mark - video filter handling
738 - (const char *)getFilterType:(const char *)psz_name
740     module_t *p_obj = module_find(psz_name);
741     if (!p_obj) {
742         return NULL;
743     }
745     if (module_provides(p_obj, "video splitter")) {
746         return "video-splitter";
747     } else if (module_provides(p_obj, "video filter")) {
748         return "video-filter";
749     } else if (module_provides(p_obj, "sub source")) {
750         return "sub-source";
751     } else if (module_provides(p_obj, "sub filter")) {
752         return "sub-filter";
753     } else {
754         msg_Err(getIntf(), "Unknown video filter type.");
755         return NULL;
756     }
759 - (void)setVideoFilter: (const char *)psz_name on:(BOOL)b_on
761     intf_thread_t *p_intf = getIntf();
762     if (!p_intf)
763         return;
764     playlist_t *p_playlist = pl_Get(p_intf);
765     char *psz_string, *psz_parser;
767     const char *psz_filter_type = [self getFilterType:psz_name];
768     if (!psz_filter_type) {
769         msg_Err(p_intf, "Unable to find filter module \"%s\".", psz_name);
770         return;
771     }
773     msg_Dbg(p_intf, "will turn filter '%s' %s", psz_name, b_on ? "on" : "off");
775     psz_string = var_InheritString(p_playlist, psz_filter_type);
777     if (b_on) {
778         if (psz_string == NULL) {
779             psz_string = strdup(psz_name);
780         } else if (strstr(psz_string, psz_name) == NULL) {
781             char *psz_tmp = strdup([[NSString stringWithFormat: @"%s:%s", psz_string, psz_name] UTF8String]);
782             free(psz_string);
783             psz_string = psz_tmp;
784         }
785     } else {
786         if (!psz_string)
787             return;
789         psz_parser = strstr(psz_string, psz_name);
790         if (psz_parser) {
791             if (*(psz_parser + strlen(psz_name)) == ':') {
792                 memmove(psz_parser, psz_parser + strlen(psz_name) + 1,
793                         strlen(psz_parser + strlen(psz_name) + 1) + 1);
794             } else {
795                 *psz_parser = '\0';
796             }
798             /* Remove trailing : : */
799             if (strlen(psz_string) > 0 && *(psz_string + strlen(psz_string) -1) == ':')
800                 *(psz_string + strlen(psz_string) -1) = '\0';
801         } else {
802             free(psz_string);
803             return;
804         }
805     }
806     var_SetString(p_playlist, psz_filter_type, psz_string);
808     /* Try to set non splitter filters on the fly */
809     if (strcmp(psz_filter_type, "video-splitter")) {
810         NSArray<NSValue *> *vouts = getVouts();
811         if (vouts)
812             for (NSValue * val in vouts) {
813                 vout_thread_t *p_vout = [val pointerValue];
814                 var_SetString(p_vout, psz_filter_type, psz_string);
815                 vlc_object_release(p_vout);
816             }
817     }
819     free(psz_string);
822 - (void)setVideoFilterProperty: (char const *)psz_property
823                      forFilter: (char const *)psz_filter
824                      withValue: (vlc_value_t)value
826     NSArray<NSValue *> *vouts = getVouts();
827     intf_thread_t *p_intf = getIntf();
828     if (!p_intf)
829         return;
831     playlist_t *p_playlist = pl_Get(p_intf);
833     int i_type = 0;
834     bool b_is_command = false;
835     char const *psz_filter_type = [self getFilterType: psz_filter];
836     if (!psz_filter_type) {
837         msg_Err(p_intf, "Unable to find filter module \"%s\".", psz_filter);
838         return;
839     }
841     if (vouts && [vouts count])
842     {
843         i_type = var_Type((vout_thread_t *)[[vouts firstObject] pointerValue], psz_property);
844         b_is_command = i_type & VLC_VAR_ISCOMMAND;
845     }
846     if (!i_type)
847         i_type = config_GetType(psz_property);
849     i_type &= VLC_VAR_CLASS;
850     if (i_type == VLC_VAR_BOOL)
851         var_SetBool(p_playlist, psz_property, value.b_bool);
852     else if (i_type == VLC_VAR_INTEGER)
853         var_SetInteger(p_playlist, psz_property, value.i_int);
854     else if (i_type == VLC_VAR_FLOAT)
855         var_SetFloat(p_playlist, psz_property, value.f_float);
856     else if (i_type == VLC_VAR_STRING)
857         var_SetString(p_playlist, psz_property, EnsureUTF8(value.psz_string));
858     else
859     {
860         msg_Err(p_intf,
861                 "Module %s's %s variable is of an unsupported type ( %d )",
862                 psz_filter, psz_property, i_type);
863         b_is_command = false;
864     }
866     if (b_is_command)
867         if (vouts)
868             for (NSValue *ptr in vouts)
869             {
870                 vout_thread_t *p_vout = [ptr pointerValue];
871                 var_SetChecked(p_vout, psz_property, i_type, value);
872 #ifndef NDEBUG
873                 int i_cur_type = var_Type(p_vout, psz_property);
874                 assert((i_cur_type & VLC_VAR_CLASS) == i_type);
875                 assert(i_cur_type & VLC_VAR_ISCOMMAND);
876 #endif
877             }
879     if (vouts)
880         for (NSValue *ptr in vouts)
881             vlc_object_release((vout_thread_t *)[ptr pointerValue]);
884 #pragma mark -
885 #pragma mark Media Key support
887 - (void)resetMediaKeyJump
889     b_mediakeyJustJumped = NO;
892 - (void)coreChangedMediaKeySupportSetting: (NSNotification *)o_notification
894     intf_thread_t *p_intf = getIntf();
895     if (!p_intf)
896         return;
898     b_mediaKeySupport = var_InheritBool(p_intf, "macosx-mediakeys");
899     if (b_mediaKeySupport && !_mediaKeyController)
900         _mediaKeyController = [[SPMediaKeyTap alloc] initWithDelegate:self];
902     VLCMain *main = [VLCMain sharedInstance];
903     if (b_mediaKeySupport && ([[[main playlist] model] hasChildren] ||
904                               [[main inputManager] hasInput])) {
905         if (!b_mediaKeyTrapEnabled) {
906             b_mediaKeyTrapEnabled = YES;
907             msg_Dbg(p_intf, "Enable media key support");
908             [_mediaKeyController startWatchingMediaKeys];
909         }
910     } else {
911         if (b_mediaKeyTrapEnabled) {
912             b_mediaKeyTrapEnabled = NO;
913             msg_Dbg(p_intf, "Disable media key support");
914             [_mediaKeyController stopWatchingMediaKeys];
915         }
916     }
919 -(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event
921     if (b_mediaKeySupport) {
922         assert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys);
924         int keyCode = (([event data1] & 0xFFFF0000) >> 16);
925         int keyFlags = ([event data1] & 0x0000FFFF);
926         int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
927         int keyRepeat = (keyFlags & 0x1);
929         if (keyCode == NX_KEYTYPE_PLAY && keyState == 0)
930             [self playOrPause];
932         if ((keyCode == NX_KEYTYPE_FAST || keyCode == NX_KEYTYPE_NEXT) && !b_mediakeyJustJumped) {
933             if (keyState == 0 && keyRepeat == 0)
934                 [self next];
935             else if (keyRepeat == 1) {
936                 [self forwardShort];
937                 b_mediakeyJustJumped = YES;
938                 [self performSelector:@selector(resetMediaKeyJump)
939                            withObject: NULL
940                            afterDelay:0.25];
941             }
942         }
944         if ((keyCode == NX_KEYTYPE_REWIND || keyCode == NX_KEYTYPE_PREVIOUS) && !b_mediakeyJustJumped) {
945             if (keyState == 0 && keyRepeat == 0)
946                 [self previous];
947             else if (keyRepeat == 1) {
948                 [self backwardShort];
949                 b_mediakeyJustJumped = YES;
950                 [self performSelector:@selector(resetMediaKeyJump)
951                            withObject: NULL
952                            afterDelay:0.25];
953             }
954         }
955     }
958 #pragma mark -
959 #pragma mark Apple Remote Control
961 - (void)startListeningWithAppleRemote
963     [_remote startListening: self];
966 - (void)stopListeningWithAppleRemote
968     [_remote stopListening:self];
971 #pragma mark - menu navigation
972 - (void)menuFocusActivate
974     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
975     if (p_input_thread == NULL)
976         return;
978     input_Control(p_input_thread, INPUT_NAV_ACTIVATE, NULL );
979     vlc_object_release(p_input_thread);
982 - (void)moveMenuFocusLeft
984     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
985     if (p_input_thread == NULL)
986         return;
988     input_Control(p_input_thread, INPUT_NAV_LEFT, NULL );
989     vlc_object_release(p_input_thread);
992 - (void)moveMenuFocusRight
994     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
995     if (p_input_thread == NULL)
996         return;
998     input_Control(p_input_thread, INPUT_NAV_RIGHT, NULL );
999     vlc_object_release(p_input_thread);
1002 - (void)moveMenuFocusUp
1004     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
1005     if (p_input_thread == NULL)
1006         return;
1008     input_Control(p_input_thread, INPUT_NAV_UP, NULL );
1009     vlc_object_release(p_input_thread);
1012 - (void)moveMenuFocusDown
1014     input_thread_t *p_input_thread = pl_CurrentInput(getIntf());
1015     if (p_input_thread == NULL)
1016         return;
1018     input_Control(p_input_thread, INPUT_NAV_DOWN, NULL );
1019     vlc_object_release(p_input_thread);
1022 /* Helper method for the remote control interface in order to trigger forward/backward and volume
1023  increase/decrease as long as the user holds the left/right, plus/minus button */
1024 - (void) executeHoldActionForRemoteButton: (NSNumber*) buttonIdentifierNumber
1026     intf_thread_t *p_intf = getIntf();
1027     if (!p_intf)
1028         return;
1030     if (b_remote_button_hold) {
1031         switch([buttonIdentifierNumber intValue]) {
1032             case kRemoteButtonRight_Hold:
1033                 [self forward];
1034                 break;
1035             case kRemoteButtonLeft_Hold:
1036                 [self backward];
1037                 break;
1038             case kRemoteButtonVolume_Plus_Hold:
1039                 if (p_intf)
1040                     var_SetInteger(p_intf->obj.libvlc, "key-action", ACTIONID_VOL_UP);
1041                 break;
1042             case kRemoteButtonVolume_Minus_Hold:
1043                 if (p_intf)
1044                     var_SetInteger(p_intf->obj.libvlc, "key-action", ACTIONID_VOL_DOWN);
1045                 break;
1046         }
1047         if (b_remote_button_hold) {
1048             /* trigger event */
1049             [self performSelector:@selector(executeHoldActionForRemoteButton:)
1050                        withObject:buttonIdentifierNumber
1051                        afterDelay:0.25];
1052         }
1053     }
1056 /* Apple Remote callback */
1057 - (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier
1058                pressedDown: (BOOL) pressedDown
1059                 clickCount: (unsigned int) count
1061     intf_thread_t *p_intf = getIntf();
1062     if (!p_intf)
1063         return;
1065     switch(buttonIdentifier) {
1066         case k2009RemoteButtonFullscreen:
1067             [self toggleFullscreen];
1068             break;
1069         case k2009RemoteButtonPlay:
1070             [self playOrPause];
1071             break;
1072         case kRemoteButtonPlay:
1073             if (count >= 2)
1074                 [self toggleFullscreen];
1075             else
1076                 [self playOrPause];
1077             break;
1078         case kRemoteButtonVolume_Plus:
1079             if (config_GetInt("macosx-appleremote-sysvol"))
1080                 [NSSound increaseSystemVolume];
1081             else
1082                 if (p_intf)
1083                     var_SetInteger(p_intf->obj.libvlc, "key-action", ACTIONID_VOL_UP);
1084             break;
1085         case kRemoteButtonVolume_Minus:
1086             if (config_GetInt("macosx-appleremote-sysvol"))
1087                 [NSSound decreaseSystemVolume];
1088             else
1089                 if (p_intf)
1090                     var_SetInteger(p_intf->obj.libvlc, "key-action", ACTIONID_VOL_DOWN);
1091             break;
1092         case kRemoteButtonRight:
1093             if (config_GetInt("macosx-appleremote-prevnext"))
1094                 [self forward];
1095             else
1096                 [self next];
1097             break;
1098         case kRemoteButtonLeft:
1099             if (config_GetInt("macosx-appleremote-prevnext"))
1100                 [self backward];
1101             else
1102                 [self previous];
1103             break;
1104         case kRemoteButtonRight_Hold:
1105         case kRemoteButtonLeft_Hold:
1106         case kRemoteButtonVolume_Plus_Hold:
1107         case kRemoteButtonVolume_Minus_Hold:
1108             /* simulate an event as long as the user holds the button */
1109             b_remote_button_hold = pressedDown;
1110             if (pressedDown) {
1111                 NSNumber* buttonIdentifierNumber = [NSNumber numberWithInt:buttonIdentifier];
1112                 [self performSelector:@selector(executeHoldActionForRemoteButton:)
1113                            withObject:buttonIdentifierNumber];
1114             }
1115             break;
1116         case kRemoteButtonMenu:
1117             [self showPosition];
1118             break;
1119         case kRemoteButtonPlay_Sleep:
1120         {
1121             NSAppleScript * script = [[NSAppleScript alloc] initWithSource:@"tell application \"System Events\" to sleep"];
1122             [script executeAndReturnError:nil];
1123             break;
1124         }
1125         default:
1126             /* Add here whatever you want other buttons to do */
1127             break;
1128     }
1131 #pragma mark -
1132 #pragma mark Key Shortcuts
1134 /*****************************************************************************
1135  * hasDefinedShortcutKey: Check to see if the key press is a defined VLC
1136  * shortcut key.  If it is, pass it off to VLC for handling and return YES,
1137  * otherwise ignore it and return NO (where it will get handled by Cocoa).
1138  *****************************************************************************/
1140 - (BOOL)keyEvent:(NSEvent *)o_event
1142     BOOL eventHandled = NO;
1143     NSString * characters = [o_event charactersIgnoringModifiers];
1144     if ([characters length] > 0) {
1145         unichar key = [characters characterAtIndex: 0];
1147         if (key) {
1148             input_thread_t * p_input = pl_CurrentInput(getIntf());
1149             if (p_input != NULL) {
1150                 vout_thread_t *p_vout = input_GetVout(p_input);
1152                 if (p_vout != NULL) {
1153                     /* Escape */
1154                     if (key == (unichar) 0x1b) {
1155                         if (var_GetBool(p_vout, "fullscreen")) {
1156                             [self toggleFullscreen];
1157                             eventHandled = YES;
1158                         }
1159                     }
1160                     vlc_object_release(p_vout);
1161                 }
1162                 vlc_object_release(p_input);
1163             }
1164         }
1165     }
1166     return eventHandled;
1169 - (BOOL)hasDefinedShortcutKey:(NSEvent *)o_event force:(BOOL)b_force
1171     intf_thread_t *p_intf = getIntf();
1172     if (!p_intf)
1173         return NO;
1175     unichar key = 0;
1176     vlc_value_t val;
1177     unsigned int i_pressed_modifiers = 0;
1179     val.i_int = 0;
1180     i_pressed_modifiers = [o_event modifierFlags];
1182     if (i_pressed_modifiers & NSControlKeyMask)
1183         val.i_int |= KEY_MODIFIER_CTRL;
1185     if (i_pressed_modifiers & NSAlternateKeyMask)
1186         val.i_int |= KEY_MODIFIER_ALT;
1188     if (i_pressed_modifiers & NSShiftKeyMask)
1189         val.i_int |= KEY_MODIFIER_SHIFT;
1191     if (i_pressed_modifiers & NSCommandKeyMask)
1192         val.i_int |= KEY_MODIFIER_COMMAND;
1194     NSString * characters = [o_event charactersIgnoringModifiers];
1195     if ([characters length] > 0) {
1196         key = [[characters lowercaseString] characterAtIndex: 0];
1198         /* handle Lion's default key combo for fullscreen-toggle in addition to our own hotkeys */
1199         if (key == 'f' && i_pressed_modifiers & NSControlKeyMask && i_pressed_modifiers & NSCommandKeyMask) {
1200             [self toggleFullscreen];
1201             return YES;
1202         }
1204         if (!b_force) {
1205             switch(key) {
1206                 case NSDeleteCharacter:
1207                 case NSDeleteFunctionKey:
1208                 case NSDeleteCharFunctionKey:
1209                 case NSBackspaceCharacter:
1210                 case NSUpArrowFunctionKey:
1211                 case NSDownArrowFunctionKey:
1212                 case NSEnterCharacter:
1213                 case NSCarriageReturnCharacter:
1214                     return NO;
1215             }
1216         }
1218         val.i_int |= CocoaKeyToVLC(key);
1220         BOOL b_found_key = NO;
1221         for (NSUInteger i = 0; i < [_usedHotkeys count]; i++) {
1222             NSString *str = [_usedHotkeys objectAtIndex:i];
1223             unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa: str];
1225             if ([[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: str]] &&
1226                 (i_keyModifiers & NSShiftKeyMask)     == (i_pressed_modifiers & NSShiftKeyMask) &&
1227                 (i_keyModifiers & NSControlKeyMask)   == (i_pressed_modifiers & NSControlKeyMask) &&
1228                 (i_keyModifiers & NSAlternateKeyMask) == (i_pressed_modifiers & NSAlternateKeyMask) &&
1229                 (i_keyModifiers & NSCommandKeyMask)   == (i_pressed_modifiers & NSCommandKeyMask)) {
1230                 b_found_key = YES;
1231                 break;
1232             }
1233         }
1235         if (b_found_key) {
1236             var_SetInteger(p_intf->obj.libvlc, "key-pressed", val.i_int);
1237             return YES;
1238         }
1239     }
1241     return NO;
1244 - (void)updateCurrentlyUsedHotkeys
1246     NSMutableArray *mutArray = [[NSMutableArray alloc] init];
1247     /* Get the main Module */
1248     module_t *p_main = module_get_main();
1249     assert(p_main);
1250     unsigned confsize;
1251     module_config_t *p_config;
1253     p_config = module_config_get (p_main, &confsize);
1255     for (size_t i = 0; i < confsize; i++) {
1256         module_config_t *p_item = p_config + i;
1258         if (CONFIG_ITEM(p_item->i_type) && p_item->psz_name != NULL
1259             && !strncmp(p_item->psz_name , "key-", 4)
1260             && !EMPTY_STR(p_item->psz_text)) {
1261             if (p_item->value.psz)
1262                 [mutArray addObject:toNSStr(p_item->value.psz)];
1263         }
1264     }
1265     module_config_free (p_config);
1267     _usedHotkeys = [[NSArray alloc] initWithArray:mutArray copyItems:YES];
1270 @end