[vlc-commits] [Git][videolan/vlc][master] 4 commits: macosx: add dialog to manually open servers for browsing

Felix Paul Kühne (@fkuehne) gitlab at videolan.org
Wed Apr 29 12:39:02 UTC 2026



Felix Paul Kühne pushed to branch master at VideoLAN / VLC


Commits:
3de84566 by Felix Paul Kühne at 2026-04-29T13:54:06+02:00
macosx: add dialog to manually open servers for browsing

- - - - -
b9344055 by Felix Paul Kühne at 2026-04-29T13:54:06+02:00
macosx: fix refresh of media sources

Previously, there was no reload after preparse.

- - - - -
e1c09d34 by Felix Paul Kühne at 2026-04-29T13:54:06+02:00
macosx: support browsing arbitrary MRLs

- - - - -
03f30a16 by Felix Paul Kühne at 2026-04-29T13:54:06+02:00
macosx: VLCInputNodePathControlItem: add description method

- - - - -


18 changed files:

- modules/gui/macosx/Makefile.am
- modules/gui/macosx/UI/MainMenu.xib
- modules/gui/macosx/library/VLCInputNodePathControlItem.m
- modules/gui/macosx/library/VLCLibraryWindow.h
- modules/gui/macosx/library/VLCLibraryWindow.m
- modules/gui/macosx/library/VLCLibraryWindowNavigationSidebarViewController.m
- modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewController.h
- modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewController.m
- modules/gui/macosx/library/media-source/VLCMediaSource.h
- modules/gui/macosx/library/media-source/VLCMediaSource.m
- modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.h
- modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.m
- modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m
- modules/gui/macosx/menus/VLCMainMenu.h
- modules/gui/macosx/menus/VLCMainMenu.m
- + modules/gui/macosx/windows/VLCConnectToServerDialog.h
- + modules/gui/macosx/windows/VLCConnectToServerDialog.m
- po/POTFILES.in


Changes:

=====================================
modules/gui/macosx/Makefile.am
=====================================
@@ -481,6 +481,8 @@ libmacosx_plugin_la_SOURCES = \
 	gui/macosx/views/VLCVolumeSliderCell.m \
 	gui/macosx/windows/VLCAboutWindowController.h \
 	gui/macosx/windows/VLCAboutWindowController.m \
+	gui/macosx/windows/VLCConnectToServerDialog.h \
+	gui/macosx/windows/VLCConnectToServerDialog.m \
 	gui/macosx/windows/VLCDetachedAudioWindow.h \
 	gui/macosx/windows/VLCDetachedAudioWindow.m \
 	gui/macosx/windows/VLCErrorWindowController.h \


=====================================
modules/gui/macosx/UI/MainMenu.xib
=====================================
@@ -77,6 +77,7 @@
                 <outlet property="next" destination="5152" id="uop-ZB-n1Q"/>
                 <outlet property="normal_window" destination="1170" id="T6u-xA-21q"/>
                 <outlet property="openSubtitleFile" destination="5515" id="XNQ-8J-eQH"/>
+                <outlet property="connect_to_server" destination="cts0" id="cts-01-out"/>
                 <outlet property="open_capture" destination="3292" id="wtp-L5-xZc"/>
                 <outlet property="open_disc" destination="413" id="ak8-1y-ze4"/>
                 <outlet property="open_file" destination="816" id="iH5-bK-QzN"/>
@@ -292,6 +293,11 @@
                                     <action selector="intfOpenCapture:" target="-2" id="aLY-nh-hau"/>
                                 </connections>
                             </menuItem>
+                            <menuItem title="Connect to Server..." keyEquivalent="k" id="cts0">
+                                <connections>
+                                    <action selector="intfConnectToServer:" target="-2" id="cts-02-act"/>
+                                </connections>
+                            </menuItem>
                             <menuItem isSeparatorItem="YES" id="2307">
                                 <modifierMask key="keyEquivalentModifierMask" command="YES"/>
                             </menuItem>


=====================================
modules/gui/macosx/library/VLCInputNodePathControlItem.m
=====================================
@@ -61,4 +61,13 @@
     return self;
 }
 
+- (NSString *)description
+{
+    if (_inputNode != nil && _inputNode.inputItem != nil) {
+        return [NSString stringWithFormat:@"path: %@", _inputNode.inputItem.path];
+    } else {
+        return @"no input node";
+    }
+}
+
 @end


=====================================
modules/gui/macosx/library/VLCLibraryWindow.h
=====================================
@@ -143,7 +143,7 @@ extern NSString * const VLCLibraryWindowEmbeddedVideoPlaybackActiveKey;
                               usingConstraints:(NSArray<NSLayoutConstraint *> *)constraints
                              displayingMessage:(NSString *)message;
 - (void)displayNoResultsMessage;
-- (void)goToLocalFolderMrl:(NSString *)mrl;
+- (void)browseFolderByMrl:(NSString *)mrl;
 
 - (IBAction)goToBrowseSection:(id)sender;
 - (IBAction)sortLibrary:(id)sender;


=====================================
modules/gui/macosx/library/VLCLibraryWindow.m
=====================================
@@ -344,10 +344,10 @@ static int ShowController(vlc_object_t * __unused p_this,
     }
 }
 
-- (void)goToLocalFolderMrl:(NSString *)mrl
+- (void)browseFolderByMrl:(NSString *)mrl
 {
     [self goToBrowseSection:self];
-    [(VLCLibraryMediaSourceViewController *)self.librarySegmentViewController presentLocalFolderMrl:mrl];
+    [(VLCLibraryMediaSourceViewController *)self.librarySegmentViewController browseFolderByMrl:mrl];
 }
 
 - (IBAction)sortLibrary:(id)sender


=====================================
modules/gui/macosx/library/VLCLibraryWindowNavigationSidebarViewController.m
=====================================
@@ -417,7 +417,7 @@ static NSString * const VLCLibrarySegmentCellIdentifier = @"VLCLibrarySegmentCel
         VLCLibrarySegmentBookmarkedLocation * const bookmarkedLocation =
             (VLCLibrarySegmentBookmarkedLocation *)representedObject;
         self.libraryWindow.librarySegmentType = bookmarkedLocation.segmentType;
-        [self.libraryWindow goToLocalFolderMrl:bookmarkedLocation.mrl];
+        [self.libraryWindow browseFolderByMrl:bookmarkedLocation.mrl];
     } else if ([representedObject isKindOfClass:VLCMediaLibraryGroup.class]) {
         [self.libraryWindow presentLibraryItem:(VLCMediaLibraryGroup *)representedObject];
     }


=====================================
modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewController.h
=====================================
@@ -55,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 - (void)presentBrowseView;
 - (void)presentStreamsView;
-- (void)presentLocalFolderMrl:(NSString *)mrl;
+- (void)browseFolderByMrl:(NSString *)mrl;
 
 @end
 


=====================================
modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewController.m
=====================================
@@ -232,10 +232,10 @@
     [_baseDataSource reloadViews];
 }
 
-- (void)presentLocalFolderMrl:(NSString *)mrl
+- (void)browseFolderByMrl:(NSString *)mrl
 {
     [self presentBrowseView];
-    [self.baseDataSource presentLocalFolderMrl:mrl];
+    [self.baseDataSource browseFolderByMrl:mrl];
 }
 
 @end


=====================================
modules/gui/macosx/library/media-source/VLCMediaSource.h
=====================================
@@ -42,7 +42,7 @@ extern NSString *VLCMediaSourcePreparsingEnded;
                   andPreparser:(vlc_preparser_t *)p_preparser
                         forCategory:(enum services_discovery_category_e)category;
 - (instancetype)initMyFoldersMediaSourceWithPreparser:(vlc_preparser_t *)p_preparser;
-- (instancetype)initWithLocalFolderMrl:(NSString *)mrl
+- (instancetype)initWithFolderMrl:(NSString *)mrl
                      andPreparser:(vlc_preparser_t *)p_preparser;
 
 - (nullable NSError *)preparseInputNodeWithinTree:(VLCInputNode *)inputNode;


=====================================
modules/gui/macosx/library/media-source/VLCMediaSource.m
=====================================
@@ -97,6 +97,7 @@ static const struct vlc_media_tree_callbacks treeCallbacks = {
 
 static const char *const localDevicesDescription = "My Machine";
 static const char *const myFoldersDescription = "My Folders";
+static const char *const remoteBrowseDescription = "Remote Browse";
 
 #pragma mark - VLCMediaSource methods
 @implementation VLCMediaSource
@@ -215,20 +216,38 @@ static const char *const myFoldersDescription = "My Folders";
     return self;
 }
 
-- (instancetype)initWithLocalFolderMrl:(NSString *)mrl
-                          andPreparser:(vlc_preparser_t *)p_preparser
+- (instancetype)initWithFolderMrl:(NSString *)mrl
+                     andPreparser:(vlc_preparser_t *)p_preparser
 {
     self = [super init];
     if (self) {
         _p_preparser = p_preparser;
 
+        NSURL * const directoryUrl = [NSURL URLWithString:mrl];
+        const BOOL isFileUrl = directoryUrl.isFileURL;
+
          _p_mediaSource = malloc(sizeof(vlc_media_source_t));
         if (!_p_mediaSource) {
             return self;
         }
 
-        _p_mediaSource->description = myFoldersDescription;
-        _p_mediaSource->tree = calloc(1, sizeof(vlc_media_tree_t));
+        if (isFileUrl) {
+            _category = SD_CAT_MYCOMPUTER;
+            _p_mediaSource->description = myFoldersDescription;
+            _p_mediaSource->tree = calloc(1, sizeof(vlc_media_tree_t));
+
+            BOOL mrlTargetIsDirectory = NO;
+            const BOOL mrlTargetExists = [NSFileManager.defaultManager
+                                          fileExistsAtPath:directoryUrl.path
+                                               isDirectory:&mrlTargetIsDirectory];
+            if (!mrlTargetExists || !mrlTargetIsDirectory) {
+                return nil;
+            }
+        } else {
+            _category = SD_CAT_LAN;
+            _p_mediaSource->description = remoteBrowseDescription;
+            _p_mediaSource->tree = vlc_media_tree_New();
+        }
 
         if (_p_mediaSource->tree == NULL) {
             free(_p_mediaSource);
@@ -236,26 +255,26 @@ static const char *const myFoldersDescription = "My Folders";
             return self;
         }
 
-        _category = SD_CAT_MYCOMPUTER;
-
-        NSFileManager * const fileManager = NSFileManager.defaultManager;
-        NSURL * const directoryUrl = [NSURL URLWithString:mrl];
-        BOOL mrlTargetIsDirectory = NO;
-        const BOOL mrlTargetExists = [fileManager fileExistsAtPath:directoryUrl.path
-                                                       isDirectory:&mrlTargetIsDirectory];
-        if (!mrlTargetExists || !mrlTargetIsDirectory) {
-            return nil;
-        }
-
         const char * const directoryPath = mrl.UTF8String;
         const char * const directoryDesc = mrl.lastPathComponent.UTF8String;
         input_item_t * const directoryItem = input_item_NewExt(directoryPath,
                                                                directoryDesc,
                                                                0,
                                                                ITEM_TYPE_DIRECTORY,
-                                                               ITEM_LOCAL);
-        input_item_node_t * const directoryNode = input_item_node_Create(directoryItem);
-        _p_mediaSource->tree->root = *directoryNode;
+                                                               isFileUrl ? ITEM_LOCAL : ITEM_NET);
+
+        if (isFileUrl) {
+            input_item_node_t * const directoryNode = input_item_node_Create(directoryItem);
+            _p_mediaSource->tree->root = *directoryNode;
+        } else {
+            input_item_node_t * const directoryNode = input_item_node_Create(directoryItem);
+            input_item_node_AppendNode(&_p_mediaSource->tree->root, directoryNode);
+            input_item_Release(directoryItem);
+            _p_treeListenerID = vlc_media_tree_AddListener(_p_mediaSource->tree,
+                                                           &treeCallbacks,
+                                                           (__bridge void *)self,
+                                                           NO);
+        }
     }
     return self;
 }
@@ -282,6 +301,11 @@ static const char *const myFoldersDescription = "My Folders";
             free(_p_mediaSource->tree);
             free(_p_mediaSource);
             _p_mediaSource = NULL;
+        } else if (_p_mediaSource->description == remoteBrowseDescription) {
+            _p_mediaSource->description = NULL;
+            vlc_media_tree_Release(_p_mediaSource->tree);
+            free(_p_mediaSource);
+            _p_mediaSource = NULL;
         } else {
             vlc_media_source_Release(_p_mediaSource);
         }


=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.h
=====================================
@@ -62,7 +62,7 @@ extern NSString * const VLCMediaSourceBaseDataSourceNodeChanged;
 - (void)homeButtonAction:(id)sender;
 - (void)pathControlAction:(id)sender;
 
-- (void)presentLocalFolderMrl:(NSString *)mrl;
+- (void)browseFolderByMrl:(NSString *)mrl;
 
 @end
 


=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.m
=====================================
@@ -650,13 +650,13 @@ referenceSizeForHeaderInSection:(NSInteger)section
         _lanDeviceSnapshot = [self buildLANDeviceSnapshot];
     }
     if (self.viewMode == VLCLibraryGridViewModeSegment) {
-        if (self.collectionView.dataSource == self) {
-            const NSInteger index = [_mediaSources indexOfObject:aNotification.object];
-            if (self.collectionView.numberOfSections > index) {
-                [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:index]];
-            } else {
-                [self.collectionView reloadData];
-            }
+        const NSInteger index = [_mediaSources indexOfObject:aNotification.object];
+        const BOOL inSync =
+            self.collectionView.numberOfSections == (NSInteger)_mediaSources.count;
+        if (self.collectionView.dataSource == self
+            && index != NSNotFound
+            && inSync) {
+            [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:index]];
         } else {
             [self.collectionView reloadData];
         }
@@ -680,16 +680,23 @@ referenceSizeForHeaderInSection:(NSInteger)section
                                                       object:self];
 }
 
-- (void)presentLocalFolderMrl:(NSString *)mrl
+- (void)browseFolderByMrl:(NSString *)mrl
 {
     vlc_preparser_t *p_preparser = getNetworkPreparser();
     VLCMediaSource * const mediaSource =
-        [[VLCMediaSource alloc] initWithLocalFolderMrl:mrl andPreparser:p_preparser];
+        [[VLCMediaSource alloc] initWithFolderMrl:mrl andPreparser:p_preparser];
     if (mediaSource == nil) {
         NSLog(@"Could not create valid media source for mrl: %@", mrl);
         return;
     }
-    [self configureChildDataSourceWithNode:mediaSource.rootNode andMediaSource:mediaSource];
+    VLCInputNode * const entryNode = mediaSource.category == SD_CAT_LAN
+        ? mediaSource.rootNode.children.firstObject
+        : mediaSource.rootNode;
+    if (entryNode == nil) {
+        NSLog(@"No entry node for mrl: %@", mrl);
+        return;
+    }
+    [self configureChildDataSourceWithNode:entryNode andMediaSource:mediaSource];
     [self reloadData];
 }
 


=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m
=====================================
@@ -65,11 +65,43 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
 - (instancetype)initWithParentBaseDataSource:(VLCMediaSourceBaseDataSource *)parentBaseDataSource
 {
     self = [super init];
-    if (self)
+    if (self) {
         self.parentBaseDataSource = parentBaseDataSource;
+        NSNotificationCenter * const notificationCenter = NSNotificationCenter.defaultCenter;
+        [notificationCenter addObserver:self
+                               selector:@selector(mediaSourceChildrenChanged:)
+                                   name:VLCMediaSourceChildrenReset
+                                 object:nil];
+        [notificationCenter addObserver:self
+                               selector:@selector(mediaSourceChildrenChanged:)
+                                   name:VLCMediaSourceChildrenAdded
+                                 object:nil];
+        [notificationCenter addObserver:self
+                               selector:@selector(mediaSourceChildrenChanged:)
+                                   name:VLCMediaSourceChildrenRemoved
+                                 object:nil];
+        [notificationCenter addObserver:self
+                               selector:@selector(mediaSourceChildrenChanged:)
+                                   name:VLCMediaSourcePreparsingEnded
+                                 object:nil];
+    }
     return self;
 }
 
+- (void)dealloc
+{
+    [NSNotificationCenter.defaultCenter removeObserver:self];
+}
+
+- (void)mediaSourceChildrenChanged:(NSNotification *)notification
+{
+    if (notification.object != self.displayedMediaSource)
+        return;
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self reloadData];
+    });
+}
+
 - (dispatch_source_t)observeLocalUrl:(NSURL *)url
                       forVnodeEvents:(dispatch_source_vnode_flags_t)eventsFlags
                     withEventHandler:(dispatch_block_t)eventHandlerBlock
@@ -107,6 +139,12 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
     NSParameterAssert(self.parentBaseDataSource);
     if (self.parentBaseDataSource.mediaSourceMode == VLCMediaSourceModeLAN) {
         NSURL * const nodeUrl = [NSURL URLWithString:nodeToDisplay.inputItem.MRL];
+
+        if (!nodeUrl.isFileURL) {
+            [self reloadData];
+            return;
+        }
+
         NSError * const error =
             [self.displayedMediaSource generateChildNodesForDirectoryNode:inputNode
                                                                   withUrl:nodeUrl];
@@ -121,7 +159,7 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
 
         const __weak typeof(self) weakSelf = self;
         self.observedPathDispatchSource = [self observeLocalUrl:nodeUrl
-                                                forVnodeEvents:DISPATCH_VNODE_WRITE | 
+                                                forVnodeEvents:DISPATCH_VNODE_WRITE |
                                                                DISPATCH_VNODE_DELETE |
                                                                DISPATCH_VNODE_RENAME
                                             withEventHandler:^{


=====================================
modules/gui/macosx/menus/VLCMainMenu.h
=====================================
@@ -50,6 +50,7 @@
 @property (readwrite, weak) IBOutlet NSMenuItem *open_disc;
 @property (readwrite, weak) IBOutlet NSMenuItem *open_net;
 @property (readwrite, weak) IBOutlet NSMenuItem *open_capture;
+ at property (readwrite, weak) IBOutlet NSMenuItem *connect_to_server;
 @property (readwrite, weak) IBOutlet NSMenuItem *open_recent;
 @property (readwrite, weak) IBOutlet NSMenuItem *close_window;
 @property (readwrite, weak) IBOutlet NSMenuItem *convertandsave;
@@ -237,6 +238,7 @@
 - (IBAction)intfOpenDisc:(id)sender;
 - (IBAction)intfOpenNet:(id)sender;
 - (IBAction)intfOpenCapture:(id)sender;
+- (IBAction)intfConnectToServer:(id)sender;
 - (IBAction)savePlaylist:(id)sender;
 - (IBAction)savePlayQueueToLibrary:(id)sender;
 


=====================================
modules/gui/macosx/menus/VLCMainMenu.m
=====================================
@@ -50,6 +50,7 @@
 #import "preferences/VLCSimplePrefsController.h"
 
 #import "windows/VLCAboutWindowController.h"
+#import "windows/VLCConnectToServerDialog.h"
 #import "windows/VLCDetachedAudioWindow.h"
 #import "windows/VLCOpenWindowController.h"
 #import "windows/VLCErrorWindowController.h"
@@ -350,6 +351,7 @@ typedef NS_ENUM(NSInteger, VLCObjectType) {
     [_open_disc setTitle: _NS("Open Disc...")];
     [_open_net setTitle: _NS("Open Stream...")];
     [_open_capture setTitle: _NS("Open Capture Device...")];
+    [_connect_to_server setTitle: _NS("Connect to Server...")];
     [_open_recent setTitle: _NS("Open Recent")];
     [_close_window setTitle: _NS("Close Window")];
     [_convertandsave setTitle: _NS("Convert / Stream...")];
@@ -1336,6 +1338,12 @@ typedef NS_ENUM(NSInteger, VLCObjectType) {
     [[VLCMain.sharedInstance open] openCapture];
 }
 
+- (IBAction)intfConnectToServer:(id)sender
+{
+    VLCConnectToServerDialog * const dialog = [[VLCConnectToServerDialog alloc] init];
+    [dialog show];
+}
+
 - (IBAction)savePlaylist:(id)sender
 {
     static dispatch_once_t once;


=====================================
modules/gui/macosx/windows/VLCConnectToServerDialog.h
=====================================
@@ -0,0 +1,33 @@
+/*****************************************************************************
+ * VLCConnectToServerDialog.h: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2026 VLC authors and VideoLAN
+ *
+ * Authors: Felix Paul Kühne <fkuehne -at- videolan.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import <Cocoa/Cocoa.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+ at interface VLCConnectToServerDialog : NSObject
+
+- (void)show;
+
+ at end
+
+NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/windows/VLCConnectToServerDialog.m
=====================================
@@ -0,0 +1,197 @@
+/*****************************************************************************
+ * VLCConnectToServerDialog.m: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2026 VLC authors and VideoLAN
+ *
+ * Authors: Felix Paul Kühne <fkuehne -at- videolan.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import "VLCConnectToServerDialog.h"
+
+#import "extensions/NSString+Helpers.h"
+#import "library/VLCLibraryWindow.h"
+#import "main/VLCMain.h"
+
+#import <vlc_modules.h>
+
+static NSString * const VLCConnectToServerRecentsDefaultsKey = @"VLCConnectToServerRecents";
+static const NSUInteger VLCConnectToServerMaxRecents = 8;
+
+ at interface VLCConnectToServerDialog () <NSComboBoxDataSource, NSComboBoxDelegate, NSControlTextEditingDelegate>
+{
+    NSComboBox *_addressField;
+    NSButton *_connectButton;
+    NSMutableArray<NSString *> *_recents;
+}
+ at end
+
+ at implementation VLCConnectToServerDialog
+
+- (instancetype)init
+{
+    self = [super init];
+    if (self) {
+        NSArray<NSString *> * const stored =
+            [NSUserDefaults.standardUserDefaults stringArrayForKey:VLCConnectToServerRecentsDefaultsKey];
+        _recents = stored ? [stored mutableCopy] : [NSMutableArray array];
+    }
+    return self;
+}
+
+- (NSView *)buildAccessoryView
+{
+    NSView * const content = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 420, 56)];
+
+    _addressField = [[NSComboBox alloc] initWithFrame:NSZeroRect];
+    _addressField.translatesAutoresizingMaskIntoConstraints = NO;
+    _addressField.placeholderString = @"smb://user@server.example.com/";
+    _addressField.usesDataSource = YES;
+    _addressField.dataSource = self;
+    _addressField.delegate = self;
+    _addressField.completes = YES;
+    [content addSubview:_addressField];
+
+    NSTextField * const hint = [NSTextField labelWithString:[self supportedSchemesHint]];
+    hint.translatesAutoresizingMaskIntoConstraints = NO;
+    hint.textColor = NSColor.secondaryLabelColor;
+    hint.font = [NSFont systemFontOfSize:NSFont.smallSystemFontSize];
+    [content addSubview:hint];
+
+    [NSLayoutConstraint activateConstraints:@[
+        [_addressField.topAnchor constraintEqualToAnchor:content.topAnchor],
+        [_addressField.leadingAnchor constraintEqualToAnchor:content.leadingAnchor],
+        [_addressField.trailingAnchor constraintEqualToAnchor:content.trailingAnchor],
+
+        [hint.topAnchor constraintEqualToAnchor:_addressField.bottomAnchor constant:6],
+        [hint.leadingAnchor constraintEqualToAnchor:content.leadingAnchor],
+        [hint.trailingAnchor constraintEqualToAnchor:content.trailingAnchor],
+        [hint.bottomAnchor constraintEqualToAnchor:content.bottomAnchor],
+    ]];
+
+    return content;
+}
+
+- (NSString *)supportedSchemesHint
+{
+    NSMutableArray<NSString *> * const schemes = [NSMutableArray array];
+    if (module_exists("webdav"))
+        [schemes addObjectsFromArray:@[@"webdav", @"webdavs"]];
+    if (module_exists("smb2"))
+        [schemes addObject:@"smb"];
+    if (module_exists("ftp"))
+        [schemes addObjectsFromArray:@[@"ftp", @"ftps", @"ftpes"]];
+    if (module_exists("sftp"))
+        [schemes addObject:@"sftp"];
+    if (module_exists("nfs"))
+        [schemes addObject:@"nfs"];
+
+    return [NSString stringWithFormat:_NS("Supported protocols: %@"),
+            [schemes componentsJoinedByString:@", "]];
+}
+
+- (void)show
+{
+    NSAlert * const alert = [[NSAlert alloc] init];
+    alert.messageText = _NS("Connect to Server");
+    alert.informativeText = _NS("Enter a server address to browse.");
+    _connectButton = [alert addButtonWithTitle:_NS("Connect")];
+    _connectButton.enabled = NO;
+    [alert addButtonWithTitle:_NS("Cancel")];
+    alert.accessoryView = [self buildAccessoryView];
+    alert.window.initialFirstResponder = _addressField;
+
+    if ([alert runModal] != NSAlertFirstButtonReturn)
+        return;
+
+    NSString * const mrl = [self currentMrl];
+    if (mrl == nil)
+        return;
+
+    [self rememberRecent:mrl];
+    [VLCMain.sharedInstance.libraryWindow browseFolderByMrl:mrl];
+}
+
+#pragma mark - combo box data source (recent servers)
+
+- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)comboBox
+{
+    return _recents.count;
+}
+
+- (id)comboBox:(NSComboBox *)comboBox objectValueForItemAtIndex:(NSInteger)index
+{
+    return _recents[index];
+}
+
+- (NSString *)comboBox:(NSComboBox *)comboBox completedString:(NSString *)string
+{
+    for (NSString * const entry in _recents) {
+        if ([entry.lowercaseString hasPrefix:string.lowercaseString])
+            return entry;
+    }
+    return nil;
+}
+
+#pragma mark - validation
+
+- (void)controlTextDidChange:(NSNotification *)notification
+{
+    [self updateConnectEnabled];
+}
+
+- (void)comboBoxSelectionDidChange:(NSNotification *)notification
+{
+    NSComboBox * const box = notification.object;
+    const NSInteger index = box.indexOfSelectedItem;
+    if (index >= 0 && index < (NSInteger)_recents.count)
+        box.stringValue = _recents[index];
+    [self updateConnectEnabled];
+}
+
+- (void)updateConnectEnabled
+{
+    _connectButton.enabled = [self currentMrl] != nil;
+}
+
+- (nullable NSString *)currentMrl
+{
+    NSString * const input = [_addressField.stringValue stringByTrimmingCharactersInSet:
+                              NSCharacterSet.whitespaceAndNewlineCharacterSet];
+    if (input.length == 0)
+        return nil;
+
+    NSURL * const url = [NSURL URLWithString:input];
+    if (url.scheme.length == 0 || url.host.length == 0)
+        return nil;
+
+    return input;
+}
+
+#pragma mark - recent list
+
+- (void)rememberRecent:(NSString *)mrl
+{
+    [_recents removeObject:mrl];
+    [_recents insertObject:mrl atIndex:0];
+    while (_recents.count > VLCConnectToServerMaxRecents)
+        [_recents removeLastObject];
+
+    [NSUserDefaults.standardUserDefaults setObject:_recents
+                                            forKey:VLCConnectToServerRecentsDefaultsKey];
+}
+
+ at end


=====================================
po/POTFILES.in
=====================================
@@ -604,6 +604,8 @@ modules/gui/macosx/views/VLCVolumeSliderCell.h
 modules/gui/macosx/views/VLCVolumeSliderCell.m
 modules/gui/macosx/windows/VLCAboutWindowController.h
 modules/gui/macosx/windows/VLCAboutWindowController.m
+modules/gui/macosx/windows/VLCConnectToServerDialog.h
+modules/gui/macosx/windows/VLCConnectToServerDialog.m
 modules/gui/macosx/windows/VLCDetachedAudioWindow.h
 modules/gui/macosx/windows/VLCDetachedAudioWindow.m
 modules/gui/macosx/windows/VLCErrorWindowController.h



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/0eb9ea48f22f849d529b9aaf9a74d457e54300e5...03f30a16fb3b2aad444db158d72ae2db176158c6

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/0eb9ea48f22f849d529b9aaf9a74d457e54300e5...03f30a16fb3b2aad444db158d72ae2db176158c6
You're receiving this email because of your account on code.videolan.org.




More information about the vlc-commits mailing list