[vlc-commits] [Git][videolan/vlc][master] 11 commits: macosx: Remove removeAndCleanUpStatesInRange

Steve Lhomme (@robUx4) gitlab at videolan.org
Thu Jun 4 19:47:43 UTC 2026



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
1bd955db by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Remove removeAndCleanUpStatesInRange

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
a5444827 by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Cache child nodes of input node

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
7aeae469 by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Call didFinishGeneratingChildNodesForNodeHandler during child node generation early return

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
1b46f999 by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Do not manually clear out child nodes in media source

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
e329ad93 by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Check children count before attempting to generate child nodes

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
4cde2b20 by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Cancel prior observed dispatch source before creating new one

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
9af1e6fc by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Use obj-c input node (and clear cached children) type as parameter for generateChildNodesForDirectoryNode:

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
7fc886fb by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Remove unnecessary node generation within setNodeToDisplay

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
a004bb45 by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Append current library state when clicking breadcrumb

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
a4245e73 by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Replace progressive path control mechanism with snapshots

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
e6582167 by Claudio Cambra at 2026-06-04T18:58:47+00:00
macosx: Provide some visual feedback for nodes with failed child generation

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -


10 changed files:

- modules/gui/macosx/library/VLCInputItem.h
- modules/gui/macosx/library/VLCInputItem.m
- modules/gui/macosx/library/VLCInputNodePathControl.h
- modules/gui/macosx/library/VLCInputNodePathControl.m
- modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewNavigationStack.m
- modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewNavigationState.h
- modules/gui/macosx/library/media-source/VLCMediaSource.h
- modules/gui/macosx/library/media-source/VLCMediaSource.m
- modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.m
- modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m


Changes:

=====================================
modules/gui/macosx/library/VLCInputItem.h
=====================================
@@ -102,6 +102,8 @@ NSDictionary<NSString *, id> *commonInputItemData(NSArray<VLCInputItem*> * const
 @property (readonly) int numberOfChildren;
 @property (readonly, nullable) NSArray <VLCInputNode *> *children;
 
+- (void)clearChildrenCache;
+
 @end
 
 NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/library/VLCInputItem.m
=====================================
@@ -654,6 +654,12 @@ NSString * const value_##prop =
 }
 
 
+ at interface VLCInputNode ()
+{
+    NSArray<VLCInputNode *> *_cachedChildren;
+}
+ at end
+
 @implementation VLCInputNode
 
 - (instancetype)initWithInputNode:(struct input_item_node_t *)p_inputNode
@@ -679,24 +685,40 @@ NSString * const value_##prop =
     return [NSString stringWithFormat:@"%@: node: %p input name: %@, number of children: %i", NSStringFromClass([self class]),_vlcInputItemNode, inputItemName, self.numberOfChildren];
 }
 
+- (void)clearChildrenCache
+{
+    _cachedChildren = nil;
+}
+
 - (int)numberOfChildren
 {
+    if (_cachedChildren) {
+        return (int)_cachedChildren.count;
+    }
     return _vlcInputItemNode ? _vlcInputItemNode->i_children : 0;
 }
 
 - (nullable NSArray<VLCInputNode *> *)children
 {
+    if (_cachedChildren) {
+        return _cachedChildren;
+    }
+
     if (_vlcInputItemNode == NULL) {
         return nil;
     }
     NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:_vlcInputItemNode->i_children];
     for (int i = 0; i < _vlcInputItemNode->i_children; i++) {
+        if (_vlcInputItemNode->pp_children == NULL) {
+            break;
+        }
         VLCInputNode *inputNode = [[VLCInputNode alloc] initWithInputNode:_vlcInputItemNode->pp_children[i]];
         if (inputNode) {
             [mutableArray addObject:inputNode];
         }
     }
-    return [mutableArray copy];
+    _cachedChildren = [mutableArray copy];
+    return _cachedChildren;
 }
 
 @end


=====================================
modules/gui/macosx/library/VLCInputNodePathControl.h
=====================================
@@ -29,11 +29,17 @@ NS_ASSUME_NONNULL_BEGIN
 @interface VLCInputNodePathControl : NSPathControl
 
 @property (readonly) NSMutableDictionary *inputNodePathControlItems;
+// The path items in display order, resolved back to their
+// VLCInputNodePathControlItem instances. Used to snapshot the full breadcrumb
+// for a navigation state so it can be restored later.
+ at property (readonly) NSArray<VLCInputNodePathControlItem *> *orderedInputNodePathControlItems;
 
 - (void)appendInputNodePathControlItem:(VLCInputNodePathControlItem *)inputNodePathControlItem;
-- (void)removeLastInputNodePathControlItem;
 - (void)clearInputNodePathControlItems;
 - (void)clearPathControlItemsAheadOf:(NSPathControlItem *)item;
+// Replaces the entire breadcrumb with the given ordered items, rebuilding the
+// backing lookup table. Used when restoring a navigation state.
+- (void)setPathWithInputNodePathControlItems:(NSArray<VLCInputNodePathControlItem *> *)items;
 
 @end
 


=====================================
modules/gui/macosx/library/VLCInputNodePathControl.m
=====================================
@@ -44,21 +44,6 @@
     self.pathItems = pathItems;
 }
 
-- (void)removeLastInputNodePathControlItem
-{
-    if (self.pathItems.count == 0) {
-        _inputNodePathControlItems = NSMutableDictionary.dictionary;
-        return;
-    }
-
-    NSMutableArray * const pathItems = self.pathItems.mutableCopy;
-    NSPathControlItem * const lastItem = pathItems.lastObject;
-
-    [pathItems removeLastObject];
-    self.pathItems = pathItems;
-    [self.inputNodePathControlItems removeObjectForKey:lastItem.image.accessibilityDescription];
-}
-
 - (void)clearInputNodePathControlItems
 {
     _inputNodePathControlItems = NSMutableDictionary.dictionary;
@@ -92,4 +77,26 @@
     [self.inputNodePathControlItems removeObjectsForKeys:itemIdsToRemove];
 }
 
+- (NSArray<VLCInputNodePathControlItem *> *)orderedInputNodePathControlItems
+{
+    NSMutableArray<VLCInputNodePathControlItem *> * const ordered =
+        [NSMutableArray arrayWithCapacity:self.pathItems.count];
+    for (NSPathControlItem * const item in self.pathItems) {
+        VLCInputNodePathControlItem * const inputNodeItem =
+            [self.inputNodePathControlItems objectForKey:item.image.accessibilityDescription];
+        if (inputNodeItem != nil) {
+            [ordered addObject:inputNodeItem];
+        }
+    }
+    return ordered.copy;
+}
+
+- (void)setPathWithInputNodePathControlItems:(NSArray<VLCInputNodePathControlItem *> *)items
+{
+    [self clearInputNodePathControlItems];
+    for (VLCInputNodePathControlItem * const item in items) {
+        [self appendInputNodePathControlItem:item];
+    }
+}
+
 @end


=====================================
modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewNavigationStack.m
=====================================
@@ -164,10 +164,6 @@
     _currentPosition =
         [[VLCLibraryMediaSourceViewNavigationCurrentStackPosition alloc] initWithStackIndex:newPositionIndex andState:_navigationStates[newPositionIndex]];
 
-    VLCInputNode *node = _currentPosition.navigationState.currentNodeDisplayed;
-    VLCInputNodePathControlItem *nodePathItem = [[VLCInputNodePathControlItem alloc] initWithInputNode:node];
-    [self.libraryWindow.mediaSourcePathControl appendInputNodePathControlItem:nodePathItem];
-
     [self setMediaSourceViewToState:_currentPosition.navigationState];
 }
 
@@ -181,8 +177,6 @@
     _currentPosition =
         [[VLCLibraryMediaSourceViewNavigationCurrentStackPosition alloc] initWithStackIndex:newPositionIndex andState:_navigationStates[newPositionIndex]];
 
-    [self.libraryWindow.mediaSourcePathControl removeLastInputNodePathControlItem];
-
     [self setMediaSourceViewToState:_currentPosition.navigationState];
 }
 
@@ -196,12 +190,13 @@
         NSUInteger firstIndexToRemove = _currentPosition.navigationStackIndex + 1;
         // -1 to account for the array count
         NSRange rangeToRemove = NSMakeRange(firstIndexToRemove, (_navigationStates.count - 1) - _currentPosition.navigationStackIndex);
-        [self removeAndCleanUpStatesInRange:rangeToRemove];
         [_navigationStates removeObjectsInRange:rangeToRemove];
     }
 
     VLCLibraryMediaSourceViewNavigationState * const navigationState =
         [[VLCLibraryMediaSourceViewNavigationState alloc] initFromMediaSourceDataSource:self.baseDataSource.childDataSource];
+    navigationState.pathControlItems =
+        self.libraryWindow.mediaSourcePathControl.orderedInputNodePathControlItems;
     _currentPosition =
         [[VLCLibraryMediaSourceViewNavigationCurrentStackPosition alloc] initWithStackIndex:_navigationStates.count andState:navigationState];
     [_navigationStates addObject:navigationState];
@@ -209,22 +204,6 @@
     [self updateDelegateNavigationButtons];
 }
 
-- (void)removeAndCleanUpStatesInRange:(NSRange)range
-{
-    NSAssert(range.location + range.length - 1 < _navigationStates.count, @"Invalid range for state removal and cleanup, out of bounds.");
-    
-    for (NSUInteger i = range.location; i < range.length; ++i) {
-        VLCLibraryMediaSourceViewNavigationState *state = [_navigationStates objectAtIndex:i];
-        VLCInputNode *stateNode = state.currentNodeDisplayed;
-        
-        if (stateNode) {
-            [state.currentMediaSource.displayedMediaSource clearChildNodesForNode:stateNode.vlcInputItemNode];
-        }
-        
-        [_navigationStates removeObjectAtIndex:i];
-    }
-}
-
 - (void)updateDelegateNavigationButtons
 {
     if (self.libraryWindow == nil) {
@@ -239,6 +218,7 @@
 {
     [self.baseDataSource setChildDataSource:state.currentMediaSource];
     [self.baseDataSource.childDataSource setNodeToDisplay:state.currentNodeDisplayed];
+    [self.libraryWindow.mediaSourcePathControl setPathWithInputNodePathControlItems:state.pathControlItems];
 
     [self updateDelegateNavigationButtons];
 }


=====================================
modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewNavigationState.h
=====================================
@@ -26,11 +26,15 @@ NS_ASSUME_NONNULL_BEGIN
 
 @class VLCMediaSourceDataSource;
 @class VLCInputNode;
+ at class VLCInputNodePathControlItem;
 
 @interface VLCLibraryMediaSourceViewNavigationState : NSObject
 
 @property (readonly) VLCMediaSourceDataSource *currentMediaSource;
 @property (readwrite) VLCInputNode *currentNodeDisplayed;
+// Snapshot of the full breadcrumb shown while this state was current, so the
+// path control can be rebuilt exactly when navigating back or forward to it.
+ at property (readwrite, nullable) NSArray<VLCInputNodePathControlItem *> *pathControlItems;
 
 - (instancetype)initFromMediaSourceDataSource:(VLCMediaSourceDataSource *)mediaSourceDataSource;
 


=====================================
modules/gui/macosx/library/media-source/VLCMediaSource.h
=====================================
@@ -47,7 +47,7 @@ extern NSString *VLCMediaSourcePreparsingEnded;
 
 - (nullable NSError *)preparseInputNodeWithinTree:(VLCInputNode *)inputNode;
 - (void)clearChildNodesForNode:(input_item_node_t*)inputNode;
-- (nullable NSError *)generateChildNodesForDirectoryNode:(input_item_node_t *)directoryNode
+- (nullable NSError *)generateChildNodesForDirectoryNode:(VLCInputNode *)directoryInputNode
                                                  withUrl:(NSURL *)directoryUrl;
 
 @property (nonatomic, readonly) NSString *mediaSourceDescription;


=====================================
modules/gui/macosx/library/media-source/VLCMediaSource.m
=====================================
@@ -289,16 +289,7 @@ static const char *const remoteBrowseDescription = "Remote Browse";
         if (_p_mediaSource->description == localDevicesDescription || _p_mediaSource->description == myFoldersDescription) {
             _p_mediaSource->description = NULL;
 
-            input_item_node_t **childrenNodes = _p_mediaSource->tree->root.pp_children;
-            if (childrenNodes) {
-                for (int i = 0; i <_p_mediaSource->tree->root.i_children; ++i) {
-                    input_item_node_t *childNode = childrenNodes[i];
-                    input_item_node_RemoveNode(&(_p_mediaSource->tree->root), childNode);
-                    input_item_node_Delete(childNode);
-                }
-            }
-
-            free(_p_mediaSource->tree);
+            vlc_media_tree_Release(_p_mediaSource->tree);
             free(_p_mediaSource);
             _p_mediaSource = NULL;
         } else if (_p_mediaSource->description == remoteBrowseDescription) {
@@ -329,10 +320,8 @@ static const char *const remoteBrowseDescription = "Remote Browse";
 
     if (inputNode.inputItem.inputType == ITEM_TYPE_DIRECTORY &&
         [inputNode.inputItem.MRL hasPrefix:@"file://"]) {
-        input_item_node_t *vlcInputNode = inputNode.vlcInputItemNode;
         NSURL *dirUrl = [NSURL URLWithString:inputNode.inputItem.MRL];
-
-        return [self generateChildNodesForDirectoryNode:vlcInputNode withUrl:dirUrl];
+        return [self generateChildNodesForDirectoryNode:inputNode withUrl:dirUrl];
     }
 
     vlc_media_tree_Preparse(_p_mediaSource->tree, _p_preparser,
@@ -431,10 +420,13 @@ static const char *const remoteBrowseDescription = "Remote Browse";
     });
 }
 
-- (NSError *)generateChildNodesForDirectoryNode:(input_item_node_t *)directoryNode
+- (NSError *)generateChildNodesForDirectoryNode:(VLCInputNode *)directoryInputNode
                                         withUrl:(NSURL *)directoryUrl
 {
-    NSParameterAssert(directoryNode != NULL && directoryUrl != nil);
+    NSParameterAssert(directoryInputNode != NULL && directoryUrl != nil);
+    [directoryInputNode clearChildrenCache];
+    input_item_node_t * const directoryNode = directoryInputNode.vlcInputItemNode;
+
     @synchronized (self) {
         if (self.willStartGeneratingChildNodesForNodeHandler) {
             self.willStartGeneratingChildNodesForNodeHandler(directoryNode);
@@ -459,6 +451,9 @@ static const char *const remoteBrowseDescription = "Remote Browse";
 
         if (error) {
             NSLog(@"Failed to get directories: %@.", error);
+            if (self.didFinishGeneratingChildNodesForNodeHandler) {
+                self.didFinishGeneratingChildNodesForNodeHandler(directoryNode);
+            }
             return error;
         }
 


=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.m
=====================================
@@ -623,8 +623,20 @@ referenceSizeForHeaderInSection:(NSInteger)section
 
     VLCInputNodePathControlItem * const matchingItem = [self.pathControl.inputNodePathControlItems objectForKey:itemNodeMrl];
     if (matchingItem != nil) {
+        VLCInputNode * const currentNode = self.childDataSource.nodeToDisplay;
+        if (currentNode != nil &&
+            [matchingItem.inputNode.inputItem.MRL isEqualToString:currentNode.inputItem.MRL]) {
+            return;
+        }
+
+        // Jumping to an ancestor via the path control is a navigation in its own
+        // right. Record it as a new state (with the trimmed breadcrumb) so that
+        // pressing back returns to the descendant we were previously viewing,
+        // rather than to the parent of the clicked item. The breadcrumb for each
+        // state is restored wholesale on back/forward, so the path stays correct.
         self.childDataSource.nodeToDisplay = matchingItem.inputNode;
         [self.pathControl clearPathControlItemsAheadOf:selectedItem];
+        [self.navigationStack appendCurrentLibraryState];
     } else {
         NSLog(@"Could not find matching item for clicked path item: %@", selectedItem);
     }


=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m
=====================================
@@ -134,8 +134,6 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
     NSAssert(nodeToDisplay, @"Nil node to display, will not set");
     _nodeToDisplay = nodeToDisplay;
 
-    input_item_node_t * const inputNode = nodeToDisplay.vlcInputItemNode;
-
     NSParameterAssert(self.parentBaseDataSource);
     if (self.parentBaseDataSource.mediaSourceMode == VLCMediaSourceModeLAN) {
         NSURL * const nodeUrl = [NSURL URLWithString:nodeToDisplay.inputItem.MRL];
@@ -145,16 +143,9 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
             return;
         }
 
-        NSError * const error =
-            [self.displayedMediaSource generateChildNodesForDirectoryNode:inputNode
-                                                                  withUrl:nodeUrl];
-        if (error) {
-            dispatch_async(dispatch_get_main_queue(), ^{
-                NSAlert * const alert = [NSAlert alertWithError:error];
-                alert.alertStyle = NSAlertStyleCritical;
-                [alert runModal];
-            });
-            return;
+        if (self.observedPathDispatchSource) {
+            dispatch_source_cancel(self.observedPathDispatchSource);
+            self.observedPathDispatchSource = nil;
         }
 
         const __weak typeof(self) weakSelf = self;
@@ -171,7 +162,7 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
                 });
             } else {
                 dispatch_async(dispatch_get_main_queue(), ^{
-                    [weakSelf.displayedMediaSource generateChildNodesForDirectoryNode:inputNode
+                    [weakSelf.displayedMediaSource generateChildNodesForDirectoryNode:nodeToDisplay
                                                                               withUrl:nodeUrl];
                     [weakSelf reloadData];
                 });
@@ -321,12 +312,15 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
             cellView.textField.stringValue = NSTR("Loading…");
             dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
                 NSURL * const inputNodeUrl = [NSURL URLWithString:inputNode.inputItem.MRL];
-                input_item_node_t * const p_inputNode = inputNode.vlcInputItemNode;
                 NSError * const error =
-                    [self.displayedMediaSource generateChildNodesForDirectoryNode:p_inputNode
+                    [self.displayedMediaSource generateChildNodesForDirectoryNode:inputNode
                                                                           withUrl:inputNodeUrl];
-                if (error)
+                if (error) {
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        cellView.textField.stringValue = NSTR("Unavailable");
+                    });
                     return;
+                }
 
                 dispatch_async(dispatch_get_main_queue(), ^{
                     cellView.textField.stringValue =



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/c8d61664c5671e99f644eecf7ad482dc3eb57af4...e65821678553077d4b11fe01b1c4e48f47432799

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/c8d61664c5671e99f644eecf7ad482dc3eb57af4...e65821678553077d4b11fe01b1c4e48f47432799
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list