Skip to content

Wayland / X11 drag files support#119949

Open
userhaj wants to merge 7 commits into
godotengine:masterfrom
userhaj:50dndgodot2os
Open

Wayland / X11 drag files support#119949
userhaj wants to merge 7 commits into
godotengine:masterfrom
userhaj:50dndgodot2os

Conversation

@userhaj
Copy link
Copy Markdown

@userhaj userhaj commented Jun 1, 2026

What problem(s) does this PR solve?

Creates Window.drag_files()
Implements new drag_files method in X11 and Wayland display servers

Testing

Drag and drop functionality manually tested with gdscript:

extends Node

var drag_files: PackedStringArray

# First drag drop any files from an OS file browser
func _ready() -> void:
	get_window().files_dropped.connect(files_to_drag)

func files_to_drag(files):
	drag_files = files
	print("Files Dropped:")
	print(files)
	
# Then click and drag anywhere on Godot app to drag away files
func _input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		if event.is_pressed():
			print("GDScript Start Drag")
			get_window().drag_files(drag_files)

Additional information

AI was used to understand dnd and search for dnd documentation

@userhaj userhaj marked this pull request as ready for review June 1, 2026 20:30
@userhaj userhaj requested review from a team as code owners June 1, 2026 20:30
userhaj added 3 commits June 1, 2026 13:52
Window.drag_files() fix non-named argument
window_drag_files documentation added
@userhaj userhaj requested a review from a team as a code owner June 1, 2026 21:29
@scgm0
Copy link
Copy Markdown
Contributor

scgm0 commented Jun 2, 2026

I think there is still a lack of a precise drag-and-drop file feature. Currently, dragging files in is at the entire window level. It would be great if we could set it so that only specific areas (specific UI controls) accept files. Additionally, the ability to drag files out of Godot could also be added to UI controls, which would make things much more convenient.

@bruvzg
Copy link
Copy Markdown
Member

bruvzg commented Jun 2, 2026

A quick implementation of the same method for macOS:

Patch
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index d3fb28f3eb..dc5991d8e0 100644
--- a/platform/macos/godot_content_view.mm
+++ b/platform/macos/godot_content_view.mm
@@ -374,8 +374,12 @@ - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
 
 	return NO;
 }
 
+- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context {
+	return NSDragOperationCopy;
+}
+
 // MARK: Focus
 
 - (BOOL)canBecomeKeyView {
 	DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index f6b9e0327b..cf73aa7a3e 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -393,8 +393,10 @@ public:
 
 	virtual void window_start_drag(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
 	virtual void window_start_resize(DisplayServerEnums::WindowResizeEdge p_edge, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
 
+	virtual void window_drag_files(const PackedStringArray &p_files, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
+
 	virtual void window_set_window_buttons_offset(const Vector2i &p_offset, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
 	virtual Vector3i window_get_safe_title_margins(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
 
 	void cursor_update_shape();
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 003c7cc45b..7d4cb88770 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -2358,8 +2358,33 @@
 	NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown location:((NSWindow *)wd.window_object).mouseLocationOutsideOfEventStream modifierFlags:0 timestamp:[[NSProcessInfo processInfo] systemUptime] windowNumber:((NSWindow *)wd.window_object).windowNumber context:nil eventNumber:0 clickCount:1 pressure:1.0f];
 	[wd.window_object performWindowDragWithEvent:event];
 }
 
+void DisplayServerMacOS::window_drag_files(const PackedStringArray &p_files, DisplayServerEnums::WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+
+	ERR_FAIL_COND(p_files.is_empty());
+	ERR_FAIL_COND(!windows.has(p_window));
+	WindowData &wd = windows[p_window];
+
+	NSPoint local_pos = [wd.window_object convertPointFromScreen:[NSEvent mouseLocation]];
+	NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged location:local_pos modifierFlags:0 timestamp:0 windowNumber:[wd.window_object windowNumber] context:nil eventNumber:0 clickCount:0 pressure:0];
+
+	NSMutableArray *files = [NSMutableArray array];
+	for (const String &file : p_files) {
+		NSString *file_path = [NSString stringWithUTF8String:file.utf8().get_data()];
+		NSURL *file_url = [NSURL fileURLWithPath:file_path];
+		NSImage *file_icon = [[NSWorkspace sharedWorkspace] iconForFile:file_path];
+		NSDraggingItem *drag_item = [[NSDraggingItem alloc] initWithPasteboardWriter:file_url];
+		[drag_item setDraggingFrame:NSMakeRect(local_pos.x - file_icon.size.width / 2, local_pos.y - file_icon.size.height / 2, file_icon.size.width, file_icon.size.height) contents:file_icon];
+		[files addObject:drag_item];
+	}
+
+	NSDraggingSession *dragging_session = [wd.window_view beginDraggingSessionWithItems:files event:event source:wd.window_view];
+	dragging_session.animatesToStartingPositionsOnCancelOrFail = YES;
+	dragging_session.draggingFormation = NSDraggingFormationNone;
+}
+
 void DisplayServerMacOS::window_start_resize(DisplayServerEnums::WindowResizeEdge p_edge, DisplayServerEnums::WindowID p_window) {
 	_THREAD_SAFE_METHOD_
 
 	ERR_FAIL_INDEX(int(p_edge), DisplayServerEnums::WINDOW_EDGE_MAX);
diff --git a/platform/macos/godot_content_view.h b/platform/macos/godot_content_view.h
index d93cb291c1..45faebd53f 100644
--- a/platform/macos/godot_content_view.h
+++ b/platform/macos/godot_content_view.h
@@ -57,9 +57,9 @@
 @end
 
 GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations") // OpenGL is deprecated in macOS 10.14.
 
-@interface GodotContentView : RootView <NSTextInputClient> {
+@interface GodotContentView : RootView <NSTextInputClient, NSDraggingSource> {
 	DisplayServerEnums::WindowID window_id;
 	NSTrackingArea *tracking_area;
 	NSMutableAttributedString *marked_text;
 	bool ime_input_event_in_progress;

Should we allow setting a custom preview icon for dragged files? Not sure if it will be possible on all platforms.

I think there is still a lack of a precise drag-and-drop file feature. urrently, dragging files in is at the entire window level. It would be great if we could set it so that only specific areas (specific UI controls) accept files.

This makes sense to implement, but not directly related to this PR.

@userhaj
Copy link
Copy Markdown
Author

userhaj commented Jun 2, 2026

A quick implementation of the same method for macOS:

@bruvzg Fantastic! This PR is based on trying to close proposal 50, which would imply work on all windowing OSes.

I wanted to get feedback on if the method addition is an acceptable first step.
Any chance you can do Windows too? We need a drop_source_windows IDropSource.

Should we allow setting a custom preview icon for dragged files? Not sure if it will be possible on all platforms.

We should eventually; in Wayland I set the icon to nullptr, but it can be changed to a preview image.

wl_data_device_start_drag(ss->wl_data_device, ss->wl_data_source_selection, window_get_wl_surface(p_window), nullptr, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));

I don't think X11 has this feature; it would need to be coded manually, creating a window and updating it to follow the cursor, or left out.

@userhaj
Copy link
Copy Markdown
Author

userhaj commented Jun 5, 2026

There is an XWayland bug when you drag in X11 the cursor gets stuck on the forbidden icon. https://discuss.kde.org/t/drag-and-drop-incorrect-cursor-on-xwayland/30540/5

This can be replicated in the dolphin file browser by running command:

env QT_QPA_PLATFORM=xcb dolphin

This PR suffers the same exact behavior; even though it's a bug, it is working as x11 -> Wayland works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement drag-and-drop from a Godot project to the OS file manager (filesystem)

4 participants