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 <[email protected]>

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

Signed-off-by: Claudio Cambra <[email protected]>

- - - - -
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 <[email protected]>

- - - - -
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 <[email protected]>

- - - - -
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 <[email protected]>

- - - - -
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 <[email protected]>

- - - - -
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 <[email protected]>

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

Signed-off-by: Claudio Cambra <[email protected]>

- - - - -
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 <[email protected]>

- - - - -
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 <[email protected]>

- - - - -
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 <[email protected]>

- - - - -


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 =
 }
 
 
+@interface VLCInputNode ()
+{
+    NSArray<VLCInputNode *> *_cachedChildren;
+}
+@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.
+@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;
+@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.
+@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


_______________________________________________
vlc-commits mailing list
[email protected]
https://mailman.videolan.org/listinfo/vlc-commits

Reply via email to