[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