[vlc-commits] [Git][videolan/vlc][master] macosx: support drag and drop for input items into play queue

Steve Lhomme (@robUx4) gitlab at videolan.org
Tue Aug 18 13:15:51 UTC 2026



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
b81c96e4 by Serhii Bykov at 2026-08-18T13:00:18+00:00
macosx: support drag and drop for input items into play queue

- - - - -


8 changed files:

- modules/gui/macosx/UI/VLCLibraryWindow.xib
- modules/gui/macosx/extensions/NSPasteboardItem+VLCAdditions.h
- modules/gui/macosx/extensions/NSPasteboardItem+VLCAdditions.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/playqueue/VLCPlayQueueDataSource.m
- modules/gui/macosx/views/VLCFileDragRecognisingView.m


Changes:

=====================================
modules/gui/macosx/UI/VLCLibraryWindow.xib
=====================================
@@ -1028,7 +1028,7 @@
                         <rect key="frame" x="0.0" y="0.0" width="528" height="394"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
-                            <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnReordering="NO" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" headerView="4cS-pi-5KZ" viewBased="YES" id="vpJ-Oz-Ebz" customClass="VLCLibraryTableView">
+                            <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnReordering="NO" columnSelection="YES" multipleSelection="YES" autosaveColumns="NO" headerView="4cS-pi-5KZ" viewBased="YES" id="vpJ-Oz-Ebz" customClass="VLCLibraryTableView">
                                 <rect key="frame" x="0.0" y="0.0" width="718" height="366"/>
                                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                 <size key="intercellSpacing" width="3" height="2"/>


=====================================
modules/gui/macosx/extensions/NSPasteboardItem+VLCAdditions.h
=====================================
@@ -25,10 +25,12 @@
 NS_ASSUME_NONNULL_BEGIN
 
 @protocol VLCMediaLibraryItemProtocol;
+ at class VLCInputItem;
 
 @interface NSPasteboardItem (VLCAdditions)
 
 + (instancetype)pasteboardItemWithLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem;
++ (nullable instancetype)pasteboardItemWithInputItem:(VLCInputItem *)inputItem;
 
 @end
 


=====================================
modules/gui/macosx/extensions/NSPasteboardItem+VLCAdditions.m
=====================================
@@ -22,6 +22,7 @@
 
 #import "NSPasteboardItem+VLCAdditions.h"
 
+#import "library/VLCInputItem.h"
 #import "library/VLCLibraryDataTypes.h"
 
 @implementation NSPasteboardItem (VLCAdditions)
@@ -47,4 +48,30 @@
     return pboardItem;
 }
 
++ (nullable instancetype)pasteboardItemWithInputItem:(VLCInputItem *)inputItem
+{
+    if (inputItem == nil || inputItem.MRL.length == 0) {
+        return nil;
+    }
+
+    NSURL * const itemURL = [NSURL URLWithString:inputItem.MRL];
+    if (itemURL == nil) {
+        return nil;
+    }
+
+    NSPasteboardItem * const pboardItem = [[NSPasteboardItem alloc] init];
+    [pboardItem setString:itemURL.absoluteString forType:NSPasteboardTypeString];
+    [pboardItem setString:itemURL.absoluteString
+                  forType:itemURL.isFileURL ? NSPasteboardTypeFileURL : NSPasteboardTypeURL];
+
+    if (itemURL.isFileURL) {
+        NSString * const localPath = itemURL.path;
+        if (localPath.length > 0) {
+            [pboardItem setPropertyList:@[localPath] forType:NSFilenamesPboardType];
+        }
+    }
+
+    return pboardItem;
+}
+
 @end


=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.h
=====================================
@@ -32,6 +32,7 @@ typedef NS_ENUM(NSInteger, VLCMediaSourceMode) {
 };
 
 @class VLCInputNodePathControl;
+ at class VLCInputItem;
 @class VLCLibraryMediaSourceViewNavigationStack;
 @class VLCMediaSourceDataSource;
 
@@ -64,6 +65,7 @@ extern NSString * const VLCMediaSourceBaseDataSourceNodeChanged;
 - (void)pathControlAction:(id)sender;
 
 - (void)browseFolderByMrl:(NSString *)mrl;
+- (nullable id<NSPasteboardWriting>)pasteboardWriterForInputItem:(nullable VLCInputItem *)inputItem;
 
 @end
 


=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.m
=====================================
@@ -30,6 +30,7 @@
 #import "VLCMediaSourceProvider.h"
 
 #import "extensions/NSImage+VLCAdditions.h"
+#import "extensions/NSPasteboardItem+VLCAdditions.h"
 #import "extensions/NSString+Helpers.h"
 #import "extensions/NSTableCellView+VLCAdditions.h"
 #import "extensions/NSWindow+VLCAdditions.h"
@@ -47,6 +48,7 @@
 
 #import "main/VLCMain.h"
 
+#import "views/VLCFileDragRecognisingView.h"
 #import "views/VLCImageView.h"
 #import "views/VLCUIUnits.h"
 
@@ -154,6 +156,9 @@ NSString * const VLCMediaSourceTableTagsColumnIdentifier = @"VLCMediaSourceTable
 
     self.tableView.dataSource = self;
     self.tableView.delegate = self;
+    [self.tableView registerForDraggedTypes:@[NSFilenamesPboardType]];
+    [self.tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
+    [self.tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:YES];
 
     NSNib * const tableCellViewNib = [[NSNib alloc] initWithNibNamed:NSStringFromClass(VLCLibraryTableCellView.class) bundle:nil];
     [self.tableView registerNib:tableCellViewNib forIdentifier:VLCLibraryTableCellViewIdentifier];
@@ -537,6 +542,46 @@ referenceSizeForHeaderInSection:(NSInteger)section
     [self reloadData];
 }
 
+- (id<NSPasteboardWriting>)tableView:(NSTableView *)tableView pasteboardWriterForRow:(NSInteger)row
+{
+    VLCInputItem * const inputItem = _mediaSourceMode == VLCMediaSourceModeLAN
+        ? _lanDeviceSnapshot[row].inputNode.inputItem
+        : _mediaSources[row].rootNode.inputItem;
+    return [self pasteboardWriterForInputItem:inputItem];
+}
+
+- (NSDragOperation)tableView:(NSTableView *)tableView
+                validateDrop:(id<NSDraggingInfo>)info
+                 proposedRow:(NSInteger)row
+       proposedDropOperation:(NSTableViewDropOperation)dropOperation
+{
+    const id propertyList = [info.draggingPasteboard propertyListForType:NSFilenamesPboardType];
+    if (propertyList == nil) {
+        return NSDragOperationNone;
+    }
+
+    [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
+    return NSDragOperationCopy;
+}
+
+- (BOOL)tableView:(NSTableView *)tableView
+       acceptDrop:(id<NSDraggingInfo>)info
+              row:(NSInteger)row
+    dropOperation:(NSTableViewDropOperation)dropOperation
+{
+    return [VLCFileDragRecognisingView
+        handlePasteboardFromDragSessionAsPlayQueueItems:info.draggingPasteboard];
+}
+
+- (nullable id<NSPasteboardWriting>)pasteboardWriterForInputItem:(nullable VLCInputItem *)inputItem
+{
+    if (inputItem == nil || inputItem.inputType == ITEM_TYPE_DIRECTORY) {
+        return nil;
+    }
+
+    return [NSPasteboardItem pasteboardItemWithInputItem:inputItem];
+}
+
 #pragma mark - LAN device snapshot
 
 - (NSArray<VLCLANDeviceRecord *> *)buildMediaSourceSnapshot


=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m
=====================================
@@ -26,6 +26,7 @@
 #import "VLCMediaSource.h"
 #import "VLCMediaSourceBaseDataSource.h"
 
+#import "extensions/NSPasteboardItem+VLCAdditions.h"
 #import "extensions/NSString+Helpers.h"
 #import "extensions/NSTableCellView+VLCAdditions.h"
 
@@ -42,6 +43,7 @@
 
 #import "playqueue/VLCPlayQueueController.h"
 
+#import "views/VLCFileDragRecognisingView.h"
 #import "views/VLCImageView.h"
 #import "views/VLCUIUnits.h"
 
@@ -249,6 +251,9 @@ static NSValue * _Nullable inputItemIdentifier(VLCInputItem * _Nullable const in
 {
     [self.tableView setDoubleAction:@selector(tableViewAction:)];
     [self.tableView setTarget:self];
+    [self.tableView registerForDraggedTypes:@[NSFilenamesPboardType]];
+    [self.tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
+    [self.tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:YES];
 }
 
 - (nullable VLCInputNode *)inputNodeForIndexPath:(NSIndexPath *)indexPath
@@ -516,6 +521,35 @@ static NSValue * _Nullable inputItemIdentifier(VLCInputItem * _Nullable const in
     }
 }
 
+- (id<NSPasteboardWriting>)tableView:(NSTableView *)tableView pasteboardWriterForRow:(NSInteger)row
+{
+    VLCInputItem * const inputItem = [self mediaSourceInputItemAtRow:row];
+    return [self.parentBaseDataSource pasteboardWriterForInputItem:inputItem];
+}
+
+- (NSDragOperation)tableView:(NSTableView *)tableView
+                validateDrop:(id<NSDraggingInfo>)info
+                 proposedRow:(NSInteger)row
+       proposedDropOperation:(NSTableViewDropOperation)dropOperation
+{
+    const id propertyList = [info.draggingPasteboard propertyListForType:NSFilenamesPboardType];
+    if (propertyList == nil) {
+        return NSDragOperationNone;
+    }
+
+    [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
+    return NSDragOperationCopy;
+}
+
+- (BOOL)tableView:(NSTableView *)tableView
+       acceptDrop:(id<NSDraggingInfo>)info
+              row:(NSInteger)row
+    dropOperation:(NSTableViewDropOperation)dropOperation
+{
+    return [VLCFileDragRecognisingView
+        handlePasteboardFromDragSessionAsPlayQueueItems:info.draggingPasteboard];
+}
+
 - (nullable VLCInputNode *)mediaSourceInputNodeAtRow:(NSInteger)tableViewRow
 {
     if (_nodeToDisplay == nil) {


=====================================
modules/gui/macosx/playqueue/VLCPlayQueueDataSource.m
=====================================
@@ -29,10 +29,8 @@
 #import "playqueue/VLCPlayQueueTableCellView.h"
 #import "playqueue/VLCPlayQueueItem.h"
 #import "playqueue/VLCPlayQueueModel.h"
-#import "views/VLCDragDropView.h"
 #import "library/VLCLibraryDataTypes.h"
-#import "library/VLCInputItem.h"
-#import "windows/VLCOpenInputMetadata.h"
+#import "views/VLCDragDropView.h"
 
 static NSString *VLCPlayQueueCellIdentifier = @"VLCPlayQueueCellIdentifier";
 
@@ -59,7 +57,15 @@ static NSString *VLCPlayQueueCellIdentifier = @"VLCPlayQueueCellIdentifier";
 
 - (void)prepareForUse
 {
-    [_tableView registerForDraggedTypes:@[VLCMediaLibraryMediaItemPasteboardType, VLCMediaLibraryMediaItemUTI, VLCPlaylistItemPasteboardType, NSFilenamesPboardType]];
+    [_tableView registerForDraggedTypes:@[
+        VLCMediaLibraryMediaItemPasteboardType,
+        VLCMediaLibraryMediaItemUTI,
+        VLCPlaylistItemPasteboardType,
+        NSPasteboardTypeURL,
+        NSPasteboardTypeFileURL,
+        NSPasteboardTypeString,
+        NSFilenamesPboardType
+    ]];
 }
 
 - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
@@ -140,73 +146,8 @@ static NSString *VLCPlayQueueCellIdentifier = @"VLCPlayQueueCellIdentifier";
         [_playQueueController moveItemWithID:uniqueID toPosition:row];
         return YES;
     }
-
-    /* Collect library media items from all pasteboard items.
-     * Table view drags create one NSPasteboardItem per selected row, so we
-     * must iterate all of them to capture every dragged item. */
-    NSMutableArray<VLCMediaLibraryMediaItem *> * const allMediaItems = [NSMutableArray array];
-
-    for (NSPasteboardItem * const pboardItem in info.draggingPasteboard.pasteboardItems) {
-        NSData *itemData = [pboardItem dataForType:VLCMediaLibraryMediaItemPasteboardType];
-        if (!itemData) {
-            itemData = [pboardItem dataForType:VLCMediaLibraryMediaItemUTI];
-        }
-        if (!itemData) {
-            continue;
-        }
-
-        /* It is a media library item, so unarchive it and add it to the playlist */
-        NSError *unarchiveError = nil;
-        NSArray<VLCMediaLibraryMediaItem *> * const items =
-            [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSArray class], [VLCMediaLibraryMediaItem class], nil]
-                                                fromData:itemData
-                                                   error:&unarchiveError];
-        if (unarchiveError != nil) {
-            msg_Err(getIntf(), "Failed to unarchive MediaLibrary Item: %s",
-                    unarchiveError.localizedDescription.UTF8String);
-            continue;
-        }
-
-        if (items) {
-            [allMediaItems addObjectsFromArray:items];
-        }
-    }
-
-    if (allMediaItems.count > 0) {
-        NSInteger insertionIndex = (NSInteger)row;
-        for (VLCMediaLibraryMediaItem * const mediaItem in allMediaItems) {
-            [_playQueueController addInputItem:mediaItem.inputItem.vlcInputItem
-                                    atPosition:insertionIndex
-                                 startPlayback:NO];
-            insertionIndex++;
-        }
-        return YES;
-    }
-
-    /* Not a library item — check if it is a file handle from the Finder */
-    const id propertyList = [info.draggingPasteboard propertyListForType:NSFilenamesPboardType];
-    if (propertyList == nil) {
-        return NO;
-    }
-
-    const NSUInteger mediaCount = [propertyList count];
-    if (mediaCount > 0) {
-        NSMutableArray * const metadataArray = [NSMutableArray arrayWithCapacity:mediaCount];
-        for (NSString * const mediaPath in propertyList) {
-            VLCOpenInputMetadata *inputMetadata;
-            NSURL * const url = [NSURL fileURLWithPath:mediaPath isDirectory:NO];
-            if (!url) {
-                continue;
-            }
-            inputMetadata = [[VLCOpenInputMetadata alloc] init];
-            inputMetadata.MRLString = url.absoluteString;
-            [metadataArray addObject:inputMetadata];
-        }
-        [_playQueueController addPlayQueueItems:metadataArray];
-
-        return YES;
-    }
-    return NO;
+    return [VLCFileDragRecognisingView
+        handlePasteboardFromDragSessionAsPlayQueueItems:info.draggingPasteboard];
 }
 
 - (NSArray<NSTableViewRowAction *> *)tableView:(NSTableView *)tableView


=====================================
modules/gui/macosx/views/VLCFileDragRecognisingView.m
=====================================
@@ -22,6 +22,8 @@
 
 #import "VLCFileDragRecognisingView.h"
 
+#import "library/VLCInputItem.h"
+#import "library/VLCLibraryDataTypes.h"
 #import "main/VLCMain.h"
 #import "playqueue/VLCPlayQueueController.h"
 #import "windows/VLCOpenInputMetadata.h"
@@ -30,12 +32,66 @@
 
 + (BOOL)handlePasteboardFromDragSessionAsPlayQueueItems:(NSPasteboard *)pasteboard
 {
+    NSMutableArray<VLCMediaLibraryMediaItem *> * const allMediaItems = [NSMutableArray array];
+    for (NSPasteboardItem * const pboardItem in pasteboard.pasteboardItems) {
+        NSData *itemData = [pboardItem dataForType:VLCMediaLibraryMediaItemPasteboardType];
+        if (!itemData) {
+            itemData = [pboardItem dataForType:VLCMediaLibraryMediaItemUTI];
+        }
+        if (!itemData) {
+            continue;
+        }
+
+        NSError *unarchiveError = nil;
+        NSArray<VLCMediaLibraryMediaItem *> * const items =
+            [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSArray class], [VLCMediaLibraryMediaItem class], nil]
+                                                fromData:itemData
+                                                   error:&unarchiveError];
+        if (unarchiveError != nil) {
+            msg_Err(getIntf(), "Failed to unarchive MediaLibrary Item: %s",
+                    unarchiveError.localizedDescription.UTF8String);
+            continue;
+        }
+
+        if (items) {
+            [allMediaItems addObjectsFromArray:items];
+        }
+    }
+
+    if (allMediaItems.count > 0) {
+        for (VLCMediaLibraryMediaItem * const mediaItem in allMediaItems) {
+            [VLCMain.sharedInstance.playQueueController addInputItem:mediaItem.inputItem.vlcInputItem
+                                                          atPosition:-1
+                                                       startPlayback:NO];
+        }
+        return YES;
+    }
+
+    NSMutableArray<VLCOpenInputMetadata *> * const URLMetadataArray = [NSMutableArray array];
+    for (NSPasteboardItem * const pboardItem in pasteboard.pasteboardItems) {
+        NSString * const URLString = [pboardItem stringForType:NSPasteboardTypeURL] ?:
+                                     [pboardItem stringForType:NSPasteboardTypeFileURL] ?:
+                                     [pboardItem stringForType:NSPasteboardTypeString];
+        if (URLString.length <= 0) {
+            continue;
+        }
+
+        VLCOpenInputMetadata * const inputMetadata = [[VLCOpenInputMetadata alloc] init];
+        inputMetadata.MRLString = URLString;
+        [URLMetadataArray addObject:inputMetadata];
+    }
+
+    if (URLMetadataArray.count > 0) {
+        [VLCMain.sharedInstance.playQueueController addPlayQueueItems:URLMetadataArray];
+        return YES;
+    }
+
     const id propertyList = [pasteboard propertyListForType:NSFilenamesPboardType];
     if (propertyList == nil) {
         return NO;
     }
 
-    NSArray * const values = [propertyList sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
+    NSArray * const values = propertyList;
     const NSUInteger valueCount = values.count;
     if (valueCount <= 0) {
         return NO;
@@ -93,12 +149,21 @@
 
 - (void)setupDragRecognition
 {
-    [self registerForDraggedTypes:@[NSFilenamesPboardType]];
+    [self registerForDraggedTypes:@[
+        VLCMediaLibraryMediaItemPasteboardType,
+        VLCMediaLibraryMediaItemUTI,
+        NSPasteboardTypeURL,
+        NSPasteboardTypeFileURL,
+        NSPasteboardTypeString,
+        NSFilenamesPboardType
+    ]];
 }
 
 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
 {
-    if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric) {
+    const NSDragOperation sourceOperationMask = [sender draggingSourceOperationMask];
+    if ((sourceOperationMask & NSDragOperationCopy) == NSDragOperationCopy ||
+        (sourceOperationMask & NSDragOperationGeneric) == NSDragOperationGeneric) {
         return NSDragOperationCopy;
     }
     return NSDragOperationNone;



View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/b81c96e4f8abd8e57802944c7dee62d8e079c4e2

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/b81c96e4f8abd8e57802944c7dee62d8e079c4e2
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