diff --git a/Dalamud.CorePlugin/PluginImpl.cs b/Dalamud.CorePlugin/PluginImpl.cs
index d352ad2c8..206c578c2 100644
--- a/Dalamud.CorePlugin/PluginImpl.cs
+++ b/Dalamud.CorePlugin/PluginImpl.cs
@@ -1,12 +1,13 @@
using System;
using System.IO;
-
+using System.Windows.Forms;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Plugin;
using Dalamud.Utility;
+using PInvoke;
namespace Dalamud.CorePlugin
{
diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj
index 61730b5ca..0f259ee31 100644
--- a/Dalamud/Dalamud.csproj
+++ b/Dalamud/Dalamud.csproj
@@ -82,6 +82,7 @@
+
diff --git a/Dalamud/Interface/DragDrop/DragDropInterop.cs b/Dalamud/Interface/DragDrop/DragDropInterop.cs
new file mode 100644
index 000000000..d25400834
--- /dev/null
+++ b/Dalamud/Interface/DragDrop/DragDropInterop.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Windows.Forms;
+
+namespace Dalamud.Interface.DragDrop;
+
+internal partial class DragDropManager
+{
+ private static class DragDropInterop
+ {
+ [Flags]
+ public enum ModifierKeys
+ {
+ MK_NONE = 0x00,
+ MK_LBUTTON = 0x01,
+ MK_RBUTTON = 0x02,
+ MK_SHIFT = 0x04,
+ MK_CONTROL = 0x08,
+ MK_MBUTTON = 0x10,
+ MK_ALT = 0x20,
+ }
+
+ public enum ClipboardFormat
+ {
+ CF_TEXT = 1,
+ CF_BITMAP = 2,
+ CF_DIB = 3,
+ CF_UNICODETEXT = 13,
+ CF_HDROP = 15,
+ }
+
+ [Flags]
+ public enum DVAspect
+ {
+ DVASPECT_CONTENT = 0x01,
+ DVASPECT_THUMBNAIL = 0x02,
+ DVASPECT_ICON = 0x04,
+ DVASPECT_DOCPRINT = 0x08,
+ }
+
+ [Flags]
+ public enum TYMED
+ {
+ TYMED_NULL = 0x00,
+ TYMED_HGLOBAL = 0x01,
+ TYMED_FILE = 0x02,
+ TYMED_ISTREAM = 0x04,
+ TYMED_ISTORAGE = 0x08,
+ TYMED_GDI = 0x10,
+ TYMED_MFPICT = 0x20,
+ TYMED_ENHMF = 0x40,
+ }
+
+ [Flags]
+ public enum DropEffects : uint
+ {
+ None = 0x00_0000_00,
+ Copy = 0x00_0000_01,
+ Move = 0x00_0000_02,
+ Link = 0x00_0000_04,
+ Scroll = 0x80_0000_00,
+ }
+
+ [DllImport("ole32.dll")]
+ public static extern int RegisterDragDrop(nint hwnd, IDropTarget pDropTarget);
+
+ [DllImport("ole32.dll")]
+ public static extern int RevokeDragDrop(nint hwnd);
+
+ [DllImport("shell32.dll")]
+ public static extern int DragQueryFile(IntPtr hDrop, uint iFile, StringBuilder lpszFile, int cch);
+ }
+}
diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs
new file mode 100644
index 000000000..ab838dc45
--- /dev/null
+++ b/Dalamud/Interface/DragDrop/DragDropManager.cs
@@ -0,0 +1,106 @@
+using ImGuiNET;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using Serilog;
+
+namespace Dalamud.Interface.DragDrop;
+
+internal partial class DragDropManager : IDisposable, IDragDropManager
+{
+ private readonly UiBuilder uiBuilder;
+
+ public DragDropManager(UiBuilder uiBuilder)
+ => this.uiBuilder = uiBuilder;
+
+ public void Enable()
+ {
+ if (this.ServiceAvailable)
+ {
+ return;
+ }
+
+ try
+ {
+ var ret2 = DragDropInterop.RegisterDragDrop(this.uiBuilder.WindowHandlePtr, this);
+ Marshal.ThrowExceptionForHR(ret2);
+ this.ServiceAvailable = true;
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"Could not create windows drag and drop utility:\n{ex}");
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!this.ServiceAvailable)
+ {
+ return;
+ }
+
+
+ }
+
+ public bool ServiceAvailable { get; internal set; }
+
+ public bool IsDragging { get; private set; }
+
+ public IReadOnlyList Files { get; private set; } = Array.Empty();
+
+ public IReadOnlySet Extensions { get; private set; } = new HashSet();
+
+ public IReadOnlyList Directories { get; private set; } = Array.Empty();
+
+ ///
+ public void CreateImGuiSource(string label, Func validityCheck, Func tooltipBuilder)
+ {
+ if (!this.IsDragging && !this.IsDropping()) return;
+ if (!validityCheck(this) || !ImGui.BeginDragDropSource(ImGuiDragDropFlags.SourceExtern)) return;
+
+ ImGui.SetDragDropPayload(label, nint.Zero, 0);
+ if (this.CheckTooltipFrame(out var frame) && tooltipBuilder(this))
+ {
+ this.lastTooltipFrame = frame;
+ }
+
+ ImGui.EndDragDropSource();
+ }
+
+ ///
+ public bool CreateImGuiTarget(string label, out IReadOnlyList files, out IReadOnlyList directories)
+ {
+ files = Array.Empty();
+ directories = Array.Empty();
+ if (!this.IsDragging || !ImGui.BeginDragDropTarget()) return false;
+
+ unsafe
+ {
+ if (ImGui.AcceptDragDropPayload(label, ImGuiDragDropFlags.AcceptBeforeDelivery).NativePtr != null && this.IsDropping())
+ {
+ this.lastDropFrame = -2;
+ files = this.Files;
+ directories = this.Directories;
+ return true;
+ }
+ }
+
+ ImGui.EndDragDropTarget();
+ return false;
+ }
+
+ private int lastDropFrame = -2;
+ private int lastTooltipFrame = -1;
+
+ private bool CheckTooltipFrame(out int frame)
+ {
+ frame = ImGui.GetFrameCount();
+ return this.lastTooltipFrame < frame;
+ }
+
+ private bool IsDropping()
+ {
+ var frame = ImGui.GetFrameCount();
+ return this.lastDropFrame == frame || this.lastDropFrame == frame - 1;
+ }
+}
diff --git a/Dalamud/Interface/DragDrop/DragDropTarget.cs b/Dalamud/Interface/DragDrop/DragDropTarget.cs
new file mode 100644
index 000000000..cff97a987
--- /dev/null
+++ b/Dalamud/Interface/DragDrop/DragDropTarget.cs
@@ -0,0 +1,23 @@
+using System;
+using Microsoft.VisualStudio.OLE.Interop;
+
+namespace Dalamud.Interface.DragDrop;
+
+internal partial class DragDropManager : IDropTarget
+{
+ public void DragEnter(IDataObject pDataObj, uint grfKeyState, POINTL pt, ref uint pdwEffect)
+ {
+ }
+
+ public void DragOver(uint grfKeyState, POINTL pt, ref uint pdwEffect)
+ {
+ }
+
+ public void DragLeave()
+ {
+ }
+
+ public void Drop(IDataObject pDataObj, uint grfKeyState, POINTL pt, ref uint pdwEffect)
+ {
+ }
+}
diff --git a/Dalamud/Interface/DragDrop/IDragDropManager.cs b/Dalamud/Interface/DragDrop/IDragDropManager.cs
new file mode 100644
index 000000000..2a0e7bdd9
--- /dev/null
+++ b/Dalamud/Interface/DragDrop/IDragDropManager.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+
+namespace Dalamud.Interface.DragDrop;
+
+///
+/// A service to handle external drag and drop from WinAPI.
+///
+public interface IDragDropManager
+{
+ /// Gets a value indicating whether Drag and Drop functionality is available at all.
+ public bool ServiceAvailable { get; }
+
+ /// Gets a value indicating whether anything is being dragged from an external application and over any of the games viewports.
+ public bool IsDragging { get; }
+
+ /// Gets the list of files currently being dragged from an external application over any of the games viewports.
+ public IReadOnlyList Files { get; }
+
+ /// Gets the set of file types by extension currently being dragged from an external application over any of the games viewports.
+ public IReadOnlySet Extensions { get; }
+
+ /// Gets the list of directories currently being dragged from an external application over any of the games viewports.
+ public IReadOnlyList Directories { get; }
+
+ /// Create an ImGui drag & drop source that is active only if anything is being dragged from an external source.
+ /// The label used for the drag & drop payload.
+ /// A function returning whether the current status is relevant for this source. Checked before creating the source but only if something is being dragged.
+ public void CreateImGuiSource(string label, Func validityCheck)
+ => this.CreateImGuiSource(label, validityCheck, _ => false);
+
+ /// Create an ImGui drag & drop source that is active only if anything is being dragged from an external source.
+ /// The label used for the drag & drop payload.
+ /// A function returning whether the current status is relevant for this source. Checked before creating the source but only if something is being dragged.
+ /// Executes ImGui functions to build a tooltip. Should return true if it creates any tooltip and false otherwise. If multiple sources are active, only the first non-empty tooltip type drawn in a frame will be used.
+ public void CreateImGuiSource(string label, Func validityCheck, Func tooltipBuilder);
+
+ /// Create an ImGui drag & drop target on the last ImGui object.
+ /// The label used for the drag & drop payload.
+ /// On success, contains the files dropped onto the target.
+ /// True if items were dropped onto the target this frame, false otherwise.
+ public bool CreateImGuiTarget(string label, out IReadOnlyList files);
+}
diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs
index da4f241ac..79541648b 100644
--- a/Dalamud/Interface/UiBuilder.cs
+++ b/Dalamud/Interface/UiBuilder.cs
@@ -7,6 +7,7 @@ using Dalamud.Configuration.Internal;
using Dalamud.Game;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui;
+using Dalamud.Interface.DragDrop;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.ManagedAsserts;
@@ -30,6 +31,7 @@ public sealed class UiBuilder : IDisposable
private readonly string namespaceName;
private readonly InterfaceManager interfaceManager = Service.Get();
private readonly GameFontManager gameFontManager = Service.Get();
+ private readonly DragDropManager dragDropManager;
private bool hasErrorWindow = false;
private bool lastFrameUiHideState = false;
@@ -47,6 +49,7 @@ public sealed class UiBuilder : IDisposable
this.stopwatch = new Stopwatch();
this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch);
this.namespaceName = namespaceName;
+ this.dragDropManager = new DragDropManager(this);
this.interfaceManager.Draw += this.OnDraw;
this.interfaceManager.BuildFonts += this.OnBuildFonts;
@@ -100,6 +103,11 @@ public sealed class UiBuilder : IDisposable
///
public event Action HideUi;
+ ///
+ /// Gets the manager for external, WinAPI-based drag and drop functionality.
+ ///
+ public IDragDropManager DragDropManager => this.dragDropManager;
+
///
/// Gets the default Dalamud font based on Noto Sans CJK Medium in 17pt - supporting all game languages and icons.
///
@@ -397,6 +405,7 @@ public sealed class UiBuilder : IDisposable
///
void IDisposable.Dispose()
{
+ this.dragDropManager.Dispose();
this.interfaceManager.Draw -= this.OnDraw;
this.interfaceManager.BuildFonts -= this.OnBuildFonts;
this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers;