From c76cbbd5183e745368e5f7cccfa9e0d9d44d6e11 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 23 May 2023 10:33:53 +0200 Subject: [PATCH 01/11] dragdrop --- Dalamud.CorePlugin/PluginImpl.cs | 3 +- Dalamud/Dalamud.csproj | 1 + Dalamud/Interface/DragDrop/DragDropInterop.cs | 74 ++++++++++++ Dalamud/Interface/DragDrop/DragDropManager.cs | 106 ++++++++++++++++++ Dalamud/Interface/DragDrop/DragDropTarget.cs | 23 ++++ .../Interface/DragDrop/IDragDropManager.cs | 43 +++++++ Dalamud/Interface/UiBuilder.cs | 9 ++ 7 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 Dalamud/Interface/DragDrop/DragDropInterop.cs create mode 100644 Dalamud/Interface/DragDrop/DragDropManager.cs create mode 100644 Dalamud/Interface/DragDrop/DragDropTarget.cs create mode 100644 Dalamud/Interface/DragDrop/IDragDropManager.cs 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; From 96bb94b9d5334bed5e53748ce2daacfebe515eda Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 25 May 2023 18:49:52 +0200 Subject: [PATCH 02/11] Improve Drag & Drop interface. --- Dalamud.CorePlugin/PluginImpl.cs | 3 +- Dalamud/Interface/DragDrop/DragDropInterop.cs | 7 +- Dalamud/Interface/DragDrop/DragDropManager.cs | 74 ++++-- Dalamud/Interface/DragDrop/DragDropTarget.cs | 215 ++++++++++++++++++ .../Interface/DragDrop/IDragDropManager.cs | 5 +- Dalamud/Interface/UiBuilder.cs | 1 + 6 files changed, 283 insertions(+), 22 deletions(-) diff --git a/Dalamud.CorePlugin/PluginImpl.cs b/Dalamud.CorePlugin/PluginImpl.cs index 206c578c2..d352ad2c8 100644 --- a/Dalamud.CorePlugin/PluginImpl.cs +++ b/Dalamud.CorePlugin/PluginImpl.cs @@ -1,13 +1,12 @@ 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/Interface/DragDrop/DragDropInterop.cs b/Dalamud/Interface/DragDrop/DragDropInterop.cs index d25400834..b3befac07 100644 --- a/Dalamud/Interface/DragDrop/DragDropInterop.cs +++ b/Dalamud/Interface/DragDrop/DragDropInterop.cs @@ -1,10 +1,15 @@ using System; using System.Runtime.InteropServices; using System.Text; -using System.Windows.Forms; +using Microsoft.VisualStudio.OLE.Interop; + +// ReSharper disable UnusedMember.Local +// ReSharper disable IdentifierTypo +// ReSharper disable InconsistentNaming namespace Dalamud.Interface.DragDrop; +/// Implements interop enums and function calls to interact with external drag and drop. internal partial class DragDropManager { private static class DragDropInterop diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index ab838dc45..c27149153 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -1,18 +1,47 @@ -using ImGuiNET; using System; using System.Collections.Generic; using System.Runtime.InteropServices; + +using ImGuiNET; using Serilog; namespace Dalamud.Interface.DragDrop; +/// +/// A manager that keeps state of external windows drag and drop events, +/// and can be used to create ImGui drag and drop sources and targets for those external events. +/// internal partial class DragDropManager : IDisposable, IDragDropManager { private readonly UiBuilder uiBuilder; + private int lastDropFrame = -2; + private int lastTooltipFrame = -1; + + /// Initializes a new instance of the class. + /// The parent instance. public DragDropManager(UiBuilder uiBuilder) => this.uiBuilder = uiBuilder; + /// Gets a value indicating whether external drag and drop is available at all. + public bool ServiceAvailable { get; private set; } + + /// Gets a value indicating whether a valid external drag and drop is currently active and hovering over any FFXIV-related viewport. + public bool IsDragging { get; private set; } + + /// Gets a value indicating whether there are any files or directories currently being dragged. + public bool HasPaths { get; private set; } + + /// Gets the list of file paths currently being dragged from an external application over any FFXIV-related viewport. + public IReadOnlyList Files { get; private set; } = Array.Empty(); + + /// Gets a set of all extensions available in the paths currently being dragged from an external application over any FFXIV-related viewport. + public IReadOnlySet Extensions { get; private set; } = new HashSet(); + + /// Gets the list of directory paths currently being dragged from an external application over any FFXIV-related viewport. + public IReadOnlyList Directories { get; private set; } = Array.Empty(); + + /// Enable external drag and drop. public void Enable() { if (this.ServiceAvailable) @@ -32,31 +61,42 @@ internal partial class DragDropManager : IDisposable, IDragDropManager } } - public void Dispose() + /// Disable external drag and drop. + public void Disable() { if (!this.ServiceAvailable) { return; } + try + { + DragDropInterop.RevokeDragDrop(this.uiBuilder.WindowHandlePtr); + } + catch (Exception ex) + { + Log.Error($"Could not disable windows drag and drop utility:\n{ex}"); + } + this.ServiceAvailable = false; } - 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 Dispose() + => this.Disable(); /// public void CreateImGuiSource(string label, Func validityCheck, Func tooltipBuilder) { - if (!this.IsDragging && !this.IsDropping()) return; - if (!validityCheck(this) || !ImGui.BeginDragDropSource(ImGuiDragDropFlags.SourceExtern)) return; + if (!this.HasPaths && !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)) @@ -72,7 +112,10 @@ internal partial class DragDropManager : IDisposable, IDragDropManager { files = Array.Empty(); directories = Array.Empty(); - if (!this.IsDragging || !ImGui.BeginDragDropTarget()) return false; + if (!this.IsDragging || !ImGui.BeginDragDropTarget()) + { + return false; + } unsafe { @@ -89,9 +132,6 @@ internal partial class DragDropManager : IDisposable, IDragDropManager return false; } - private int lastDropFrame = -2; - private int lastTooltipFrame = -1; - private bool CheckTooltipFrame(out int frame) { frame = ImGui.GetFrameCount(); diff --git a/Dalamud/Interface/DragDrop/DragDropTarget.cs b/Dalamud/Interface/DragDrop/DragDropTarget.cs index cff97a987..79a20cff0 100644 --- a/Dalamud/Interface/DragDrop/DragDropTarget.cs +++ b/Dalamud/Interface/DragDrop/DragDropTarget.cs @@ -1,23 +1,238 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using Dalamud.Utility; +using ImGuiNET; using Microsoft.VisualStudio.OLE.Interop; +using Serilog; namespace Dalamud.Interface.DragDrop; +/// Implements the IDropTarget interface to interact with external drag and dropping. internal partial class DragDropManager : IDropTarget { + /// Create the drag and drop formats we accept. + private static readonly FORMATETC[] FormatEtc = + { + new() + { + cfFormat = (ushort)DragDropInterop.ClipboardFormat.CF_HDROP, + ptd = nint.Zero, + dwAspect = (uint)DragDropInterop.DVAspect.DVASPECT_CONTENT, + lindex = -1, + tymed = (uint)DragDropInterop.TYMED.TYMED_HGLOBAL, + }, + }; + + /// + /// Invoked whenever a drag and drop process drags files into any FFXIV-related viewport. + /// + /// The drag and drop data. + /// The mouse button used to drag as well as key modifiers. + /// The global cursor position. + /// Effects that can be used with this drag and drop process. public void DragEnter(IDataObject pDataObj, uint grfKeyState, POINTL pt, ref uint pdwEffect) { + this.IsDragging = true; + UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true); + + if (pDataObj.QueryGetData(FormatEtc) != 0) + { + pdwEffect = 0; + } + else + { + pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; + (this.Files, this.Directories) = this.GetPaths(pDataObj); + this.HasPaths = this.Files.Count + this.Directories.Count > 0; + this.Extensions = this.Files.Select(Path.GetExtension).Where(p => !p.IsNullOrEmpty()).Distinct().ToHashSet(); + } } + /// Invoked every windows update-frame as long as the drag and drop process keeps hovering over an FFXIV-related viewport. + /// The mouse button used to drag as well as key modifiers. + /// The global cursor position. + /// Effects that can be used with this drag and drop process. + /// Can be invoked more often than once a XIV frame, can also be less often (?). public void DragOver(uint grfKeyState, POINTL pt, ref uint pdwEffect) { + UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, false); + pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; } + /// Invoked whenever a drag and drop process that hovered over any FFXIV-related viewport leaves all FFXIV-related viewports. public void DragLeave() { + this.IsDragging = false; + this.Files = Array.Empty(); + this.Directories = Array.Empty(); + this.Extensions = new HashSet(); } + /// Invoked whenever a drag process ends by dropping over any FFXIV-related viewport. + /// The drag and drop data. + /// The mouse button used to drag as well as key modifiers. + /// The global cursor position. + /// Effects that can be used with this drag and drop process. public void Drop(IDataObject pDataObj, uint grfKeyState, POINTL pt, ref uint pdwEffect) { + MouseDrop((DragDropInterop.ModifierKeys)grfKeyState); + this.lastDropFrame = ImGui.GetFrameCount(); + this.IsDragging = false; + if (this.Files.Count > 0 || this.Directories.Count > 0) + { + pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; + } + else + { + pdwEffect = 0; + } + } + + private static void UpdateIo(DragDropInterop.ModifierKeys keys, bool entering) + { + var io = ImGui.GetIO(); + void UpdateMouse(int mouseIdx) + { + if (entering) + { + io.MouseDownDuration[mouseIdx] = 1f; + } + + io.MouseDown[mouseIdx] = true; + io.AddMouseButtonEvent(mouseIdx, true); + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_LBUTTON)) + { + UpdateMouse(0); + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_RBUTTON)) + { + UpdateMouse(1); + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_MBUTTON)) + { + UpdateMouse(2); + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_CONTROL)) + { + io.KeyCtrl = true; + io.AddKeyEvent(ImGuiKey.LeftCtrl, true); + } + else + { + io.KeyCtrl = false; + io.AddKeyEvent(ImGuiKey.LeftCtrl, false); + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_ALT)) + { + io.KeyAlt = true; + io.AddKeyEvent(ImGuiKey.LeftAlt, true); + } + else + { + io.KeyAlt = false; + io.AddKeyEvent(ImGuiKey.LeftAlt, false); + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_SHIFT)) + { + io.KeyShift = true; + io.AddKeyEvent(ImGuiKey.LeftShift, true); + } + else + { + io.KeyShift = false; + io.AddKeyEvent(ImGuiKey.LeftShift, false); + } + } + + private static void MouseDrop(DragDropInterop.ModifierKeys keys) + { + var io = ImGui.GetIO(); + void UpdateMouse(int mouseIdx) + { + io.AddMouseButtonEvent(mouseIdx, false); + io.MouseDown[mouseIdx] = false; + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_LBUTTON)) + { + UpdateMouse(0); + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_RBUTTON)) + { + UpdateMouse(1); + } + + if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_MBUTTON)) + { + UpdateMouse(2); + } + } + + private (string[] Files, string[] Directories) GetPaths(IDataObject data) + { + if (!this.IsDragging) + { + return (Array.Empty(), Array.Empty()); + } + + try + { + var stgMedium = new STGMEDIUM[] + { + default, + }; + data.GetData(FormatEtc, stgMedium); + var numFiles = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, uint.MaxValue, new StringBuilder(), 0); + var files = new string[numFiles]; + var sb = new StringBuilder(1024); + var directoryCount = 0; + var fileCount = 0; + for (var i = 0u; i < numFiles; ++i) + { + sb.Clear(); + var ret = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, i, sb, sb.Capacity); + if (ret >= sb.Capacity) + { + sb.Capacity = ret + 1; + ret = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, i, sb, sb.Capacity); + } + + if (ret > 0 && ret < sb.Capacity) + { + var s = sb.ToString(); + if (Directory.Exists(s)) + { + files[^(++directoryCount)] = s; + } + else + { + files[fileCount++] = s; + } + } + } + + var fileArray = fileCount > 0 ? files.Take(fileCount).ToArray() : Array.Empty(); + var directoryArray = directoryCount > 0 ? files.TakeLast(directoryCount).Reverse().ToArray() : Array.Empty(); + + return (fileArray, directoryArray); + } + catch (Exception ex) + { + Log.Error($"Error obtaining data from drag & drop:\n{ex}"); + } + + return (Array.Empty(), Array.Empty()); } } diff --git a/Dalamud/Interface/DragDrop/IDragDropManager.cs b/Dalamud/Interface/DragDrop/IDragDropManager.cs index 2a0e7bdd9..736c8af24 100644 --- a/Dalamud/Interface/DragDrop/IDragDropManager.cs +++ b/Dalamud/Interface/DragDrop/IDragDropManager.cs @@ -37,7 +37,8 @@ public interface IDragDropManager /// 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. + /// On success, contains the list of file paths dropped onto the target. + /// On success, contains the list of directory paths dropped onto the target. /// True if items were dropped onto the target this frame, false otherwise. - public bool CreateImGuiTarget(string label, out IReadOnlyList files); + public bool CreateImGuiTarget(string label, out IReadOnlyList files, out IReadOnlyList directories); } diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 79541648b..a421d17ba 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -50,6 +50,7 @@ public sealed class UiBuilder : IDisposable this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch); this.namespaceName = namespaceName; this.dragDropManager = new DragDropManager(this); + this.dragDropManager.Enable(); this.interfaceManager.Draw += this.OnDraw; this.interfaceManager.BuildFonts += this.OnBuildFonts; From 0c53741b1de2bebfd3b928251f17148711d446d8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 25 May 2023 21:16:52 +0200 Subject: [PATCH 03/11] Remove OLE interop dependency. --- Dalamud/Dalamud.cs | 4 ++ Dalamud/Dalamud.csproj | 1 - Dalamud/Interface/DragDrop/DragDropInterop.cs | 27 ++++++++++- Dalamud/Interface/DragDrop/DragDropManager.cs | 30 +++++++----- Dalamud/Interface/DragDrop/DragDropTarget.cs | 48 +++++++++++-------- Dalamud/Interface/UiBuilder.cs | 5 +- 6 files changed, 76 insertions(+), 39 deletions(-) diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 73914d2a7..142f653ef 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Game.Gui.Internal; +using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal; using Dalamud.Plugin.Internal; using Dalamud.Utility; @@ -135,6 +136,9 @@ internal sealed class Dalamud : IServiceType // will not receive any windows messages Service.GetNullable()?.Dispose(); + // this must be done before unloading interface manager, since it relies on the window handle members. + Service.GetNullable()?.Dispose(); + // this must be done before unloading plugins, or it can cause a race condition // due to rendering happening on another thread, where a plugin might receive // a render call after it has been disposed, which can crash if it attempts to diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 0f259ee31..61730b5ca 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -82,7 +82,6 @@ - diff --git a/Dalamud/Interface/DragDrop/DragDropInterop.cs b/Dalamud/Interface/DragDrop/DragDropInterop.cs index b3befac07..4a14d5e37 100644 --- a/Dalamud/Interface/DragDrop/DragDropInterop.cs +++ b/Dalamud/Interface/DragDrop/DragDropInterop.cs @@ -1,9 +1,9 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Text; -using Microsoft.VisualStudio.OLE.Interop; - // ReSharper disable UnusedMember.Local // ReSharper disable IdentifierTypo // ReSharper disable InconsistentNaming @@ -12,6 +12,29 @@ namespace Dalamud.Interface.DragDrop; /// Implements interop enums and function calls to interact with external drag and drop. internal partial class DragDropManager { + internal struct POINTL + { + [ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")] + public int x; + [ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")] + public int y; + } + + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("00000122-0000-0000-C000-000000000046")] + [ComImport] + public interface IDropTarget + { + [MethodImpl(MethodImplOptions.InternalCall)] + void DragEnter([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect); + [MethodImpl(MethodImplOptions.InternalCall)] + void DragOver([ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect); + [MethodImpl(MethodImplOptions.InternalCall)] + void DragLeave(); + [MethodImpl(MethodImplOptions.InternalCall)] + void Drop([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect); + } + private static class DragDropInterop { [Flags] diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index c27149153..2b1f79ed1 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; - +using System.Threading.Tasks; +using Dalamud.Interface.Internal; using ImGuiNET; using Serilog; @@ -11,17 +12,22 @@ namespace Dalamud.Interface.DragDrop; /// A manager that keeps state of external windows drag and drop events, /// and can be used to create ImGui drag and drop sources and targets for those external events. /// -internal partial class DragDropManager : IDisposable, IDragDropManager +[ServiceManager.EarlyLoadedService] +internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType { - private readonly UiBuilder uiBuilder; - + private InterfaceManager? interfaceManager; private int lastDropFrame = -2; private int lastTooltipFrame = -1; - /// Initializes a new instance of the class. - /// The parent instance. - public DragDropManager(UiBuilder uiBuilder) - => this.uiBuilder = uiBuilder; + [ServiceManager.ServiceConstructor] + private DragDropManager() + { + Service.GetAsync().ContinueWith(task => + { + this.interfaceManager = task.Result.Manager; + this.Enable(); + }); + } /// Gets a value indicating whether external drag and drop is available at all. public bool ServiceAvailable { get; private set; } @@ -44,14 +50,15 @@ internal partial class DragDropManager : IDisposable, IDragDropManager /// Enable external drag and drop. public void Enable() { - if (this.ServiceAvailable) + if (this.ServiceAvailable || this.interfaceManager == null) { return; } try { - var ret2 = DragDropInterop.RegisterDragDrop(this.uiBuilder.WindowHandlePtr, this); + var ret2 = DragDropInterop.RegisterDragDrop(this.interfaceManager.WindowHandlePtr, this); + Log.Information($"[DragDrop] Registered window {this.interfaceManager.WindowHandlePtr} for external drag and drop operations. ({ret2})"); Marshal.ThrowExceptionForHR(ret2); this.ServiceAvailable = true; } @@ -71,7 +78,8 @@ internal partial class DragDropManager : IDisposable, IDragDropManager try { - DragDropInterop.RevokeDragDrop(this.uiBuilder.WindowHandlePtr); + DragDropInterop.RevokeDragDrop(this.interfaceManager!.WindowHandlePtr); + Log.Information($"[DragDrop] Disabled external drag and drop operations for window {this.interfaceManager.WindowHandlePtr}."); } catch (Exception ex) { diff --git a/Dalamud/Interface/DragDrop/DragDropTarget.cs b/Dalamud/Interface/DragDrop/DragDropTarget.cs index 79a20cff0..a592991dd 100644 --- a/Dalamud/Interface/DragDrop/DragDropTarget.cs +++ b/Dalamud/Interface/DragDrop/DragDropTarget.cs @@ -2,30 +2,30 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices.ComTypes; using System.Text; using Dalamud.Utility; using ImGuiNET; -using Microsoft.VisualStudio.OLE.Interop; using Serilog; namespace Dalamud.Interface.DragDrop; /// Implements the IDropTarget interface to interact with external drag and dropping. -internal partial class DragDropManager : IDropTarget +internal partial class DragDropManager : DragDropManager.IDropTarget { + private int lastUpdateFrame = -1; + /// Create the drag and drop formats we accept. - private static readonly FORMATETC[] FormatEtc = - { + private static FORMATETC FormatEtc = new() { - cfFormat = (ushort)DragDropInterop.ClipboardFormat.CF_HDROP, + cfFormat = (short)DragDropInterop.ClipboardFormat.CF_HDROP, ptd = nint.Zero, - dwAspect = (uint)DragDropInterop.DVAspect.DVASPECT_CONTENT, + dwAspect = DVASPECT.DVASPECT_CONTENT, lindex = -1, - tymed = (uint)DragDropInterop.TYMED.TYMED_HGLOBAL, - }, - }; + tymed = TYMED.TYMED_HGLOBAL, + }; /// /// Invoked whenever a drag and drop process drags files into any FFXIV-related viewport. @@ -39,7 +39,7 @@ internal partial class DragDropManager : IDropTarget this.IsDragging = true; UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true); - if (pDataObj.QueryGetData(FormatEtc) != 0) + if (pDataObj.QueryGetData(ref FormatEtc) != 0) { pdwEffect = 0; } @@ -50,17 +50,24 @@ internal partial class DragDropManager : IDropTarget this.HasPaths = this.Files.Count + this.Directories.Count > 0; this.Extensions = this.Files.Select(Path.GetExtension).Where(p => !p.IsNullOrEmpty()).Distinct().ToHashSet(); } + Log.Debug("[DragDrop] Entering external Drag and Drop with {KeyState} at {PtX}, {PtY} and with {N} files.", (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y, this.Files.Count + this.Directories.Count); } /// Invoked every windows update-frame as long as the drag and drop process keeps hovering over an FFXIV-related viewport. /// The mouse button used to drag as well as key modifiers. /// The global cursor position. /// Effects that can be used with this drag and drop process. - /// Can be invoked more often than once a XIV frame, can also be less often (?). + /// Can be invoked more often than once a XIV frame, so we are keeping track of frames to skip unnecessary updates. public void DragOver(uint grfKeyState, POINTL pt, ref uint pdwEffect) { - UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, false); - pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; + var frame = ImGui.GetFrameCount(); + if (frame != this.lastUpdateFrame) + { + this.lastUpdateFrame = frame; + UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, false); + pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; + Log.Verbose("[DragDrop] External Drag and Drop with {KeyState} at {PtX}, {PtY}.", (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y); + } } /// Invoked whenever a drag and drop process that hovered over any FFXIV-related viewport leaves all FFXIV-related viewports. @@ -70,6 +77,7 @@ internal partial class DragDropManager : IDropTarget this.Files = Array.Empty(); this.Directories = Array.Empty(); this.Extensions = new HashSet(); + Log.Debug("[DragDrop] Leaving external Drag and Drop."); } /// Invoked whenever a drag process ends by dropping over any FFXIV-related viewport. @@ -90,6 +98,8 @@ internal partial class DragDropManager : IDropTarget { pdwEffect = 0; } + + Log.Debug("[DragDrop] Dropping {N} files with {KeyState} at {PtX}, {PtY}.", this.Files.Count + this.Directories.Count, (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y); } private static void UpdateIo(DragDropInterop.ModifierKeys keys, bool entering) @@ -189,12 +199,8 @@ internal partial class DragDropManager : IDropTarget try { - var stgMedium = new STGMEDIUM[] - { - default, - }; - data.GetData(FormatEtc, stgMedium); - var numFiles = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, uint.MaxValue, new StringBuilder(), 0); + data.GetData(ref FormatEtc, out var stgMedium); + var numFiles = DragDropInterop.DragQueryFile(stgMedium.unionmember, uint.MaxValue, new StringBuilder(), 0); var files = new string[numFiles]; var sb = new StringBuilder(1024); var directoryCount = 0; @@ -202,11 +208,11 @@ internal partial class DragDropManager : IDropTarget for (var i = 0u; i < numFiles; ++i) { sb.Clear(); - var ret = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, i, sb, sb.Capacity); + var ret = DragDropInterop.DragQueryFile(stgMedium.unionmember, i, sb, sb.Capacity); if (ret >= sb.Capacity) { sb.Capacity = ret + 1; - ret = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, i, sb, sb.Capacity); + ret = DragDropInterop.DragQueryFile(stgMedium.unionmember, i, sb, sb.Capacity); } if (ret > 0 && ret < sb.Capacity) diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index a421d17ba..5bd317af5 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -31,7 +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 readonly DragDropManager dragDropManager = Service.Get(); private bool hasErrorWindow = false; private bool lastFrameUiHideState = false; @@ -49,8 +49,6 @@ 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.dragDropManager.Enable(); this.interfaceManager.Draw += this.OnDraw; this.interfaceManager.BuildFonts += this.OnBuildFonts; @@ -406,7 +404,6 @@ 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; From ca69aae50039e34df2fcf9a856947bc347f0f1a0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 25 May 2023 21:35:01 +0200 Subject: [PATCH 04/11] Remove some warnings. --- Dalamud/Interface/DragDrop/DragDropInterop.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dalamud/Interface/DragDrop/DragDropInterop.cs b/Dalamud/Interface/DragDrop/DragDropInterop.cs index 4a14d5e37..8bf04f160 100644 --- a/Dalamud/Interface/DragDrop/DragDropInterop.cs +++ b/Dalamud/Interface/DragDrop/DragDropInterop.cs @@ -9,6 +9,7 @@ using System.Text; // ReSharper disable InconsistentNaming namespace Dalamud.Interface.DragDrop; +#pragma warning disable SA1600 // Elements should be documented /// Implements interop enums and function calls to interact with external drag and drop. internal partial class DragDropManager { @@ -27,10 +28,13 @@ internal partial class DragDropManager { [MethodImpl(MethodImplOptions.InternalCall)] void DragEnter([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect); + [MethodImpl(MethodImplOptions.InternalCall)] void DragOver([ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect); + [MethodImpl(MethodImplOptions.InternalCall)] void DragLeave(); + [MethodImpl(MethodImplOptions.InternalCall)] void Drop([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect); } @@ -100,3 +104,4 @@ internal partial class DragDropManager public static extern int DragQueryFile(IntPtr hDrop, uint iFile, StringBuilder lpszFile, int cch); } } +#pragma warning restore SA1600 // Elements should be documented From 4b3253b9e73be4ea08d72cc50fdd89c32d12861b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 25 May 2023 22:01:12 +0200 Subject: [PATCH 05/11] Fix some problems. --- Dalamud/Interface/DragDrop/DragDropManager.cs | 15 ++++++++------- Dalamud/Interface/DragDrop/DragDropTarget.cs | 16 ++++++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index 2b1f79ed1..0bb7eac12 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -35,16 +35,17 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService /// Gets a value indicating whether a valid external drag and drop is currently active and hovering over any FFXIV-related viewport. public bool IsDragging { get; private set; } - /// Gets a value indicating whether there are any files or directories currently being dragged. - public bool HasPaths { get; private set; } + /// Gets a value indicating whether there are any files or directories currently being dragged, or stored from the last drop. + public bool HasPaths + => this.Files.Count + this.Directories.Count > 0; - /// Gets the list of file paths currently being dragged from an external application over any FFXIV-related viewport. + /// Gets the list of file paths currently being dragged from an external application over any FFXIV-related viewport, or stored from the last drop. public IReadOnlyList Files { get; private set; } = Array.Empty(); - /// Gets a set of all extensions available in the paths currently being dragged from an external application over any FFXIV-related viewport. + /// Gets a set of all extensions available in the paths currently being dragged from an external application over any FFXIV-related viewport or stored from the last drop. public IReadOnlySet Extensions { get; private set; } = new HashSet(); - /// Gets the list of directory paths currently being dragged from an external application over any FFXIV-related viewport. + /// Gets the list of directory paths currently being dragged from an external application over any FFXIV-related viewport or stored from the last drop. public IReadOnlyList Directories { get; private set; } = Array.Empty(); /// Enable external drag and drop. @@ -96,7 +97,7 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService /// public void CreateImGuiSource(string label, Func validityCheck, Func tooltipBuilder) { - if (!this.HasPaths && !this.IsDropping()) + if (!this.IsDragging && !this.IsDropping()) { return; } @@ -120,7 +121,7 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService { files = Array.Empty(); directories = Array.Empty(); - if (!this.IsDragging || !ImGui.BeginDragDropTarget()) + if (!this.HasPaths || !ImGui.BeginDragDropTarget()) { return false; } diff --git a/Dalamud/Interface/DragDrop/DragDropTarget.cs b/Dalamud/Interface/DragDrop/DragDropTarget.cs index a592991dd..b6bb9d237 100644 --- a/Dalamud/Interface/DragDrop/DragDropTarget.cs +++ b/Dalamud/Interface/DragDrop/DragDropTarget.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices.ComTypes; using System.Text; @@ -15,6 +16,7 @@ namespace Dalamud.Interface.DragDrop; internal partial class DragDropManager : DragDropManager.IDropTarget { private int lastUpdateFrame = -1; + private DragDropInterop.ModifierKeys lastKeyState = DragDropInterop.ModifierKeys.MK_NONE; /// Create the drag and drop formats we accept. private static FORMATETC FormatEtc = @@ -37,7 +39,7 @@ internal partial class DragDropManager : DragDropManager.IDropTarget public void DragEnter(IDataObject pDataObj, uint grfKeyState, POINTL pt, ref uint pdwEffect) { this.IsDragging = true; - UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true); + this.lastKeyState = UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true); if (pDataObj.QueryGetData(ref FormatEtc) != 0) { @@ -47,9 +49,9 @@ internal partial class DragDropManager : DragDropManager.IDropTarget { pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; (this.Files, this.Directories) = this.GetPaths(pDataObj); - this.HasPaths = this.Files.Count + this.Directories.Count > 0; this.Extensions = this.Files.Select(Path.GetExtension).Where(p => !p.IsNullOrEmpty()).Distinct().ToHashSet(); } + Log.Debug("[DragDrop] Entering external Drag and Drop with {KeyState} at {PtX}, {PtY} and with {N} files.", (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y, this.Files.Count + this.Directories.Count); } @@ -64,7 +66,7 @@ internal partial class DragDropManager : DragDropManager.IDropTarget if (frame != this.lastUpdateFrame) { this.lastUpdateFrame = frame; - UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, false); + this.lastKeyState = UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, false); pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; Log.Verbose("[DragDrop] External Drag and Drop with {KeyState} at {PtX}, {PtY}.", (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y); } @@ -87,10 +89,10 @@ internal partial class DragDropManager : DragDropManager.IDropTarget /// Effects that can be used with this drag and drop process. public void Drop(IDataObject pDataObj, uint grfKeyState, POINTL pt, ref uint pdwEffect) { - MouseDrop((DragDropInterop.ModifierKeys)grfKeyState); + MouseDrop(this.lastKeyState); this.lastDropFrame = ImGui.GetFrameCount(); this.IsDragging = false; - if (this.Files.Count > 0 || this.Directories.Count > 0) + if (this.HasPaths) { pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; } @@ -102,7 +104,7 @@ internal partial class DragDropManager : DragDropManager.IDropTarget Log.Debug("[DragDrop] Dropping {N} files with {KeyState} at {PtX}, {PtY}.", this.Files.Count + this.Directories.Count, (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y); } - private static void UpdateIo(DragDropInterop.ModifierKeys keys, bool entering) + private static DragDropInterop.ModifierKeys UpdateIo(DragDropInterop.ModifierKeys keys, bool entering) { var io = ImGui.GetIO(); void UpdateMouse(int mouseIdx) @@ -163,6 +165,8 @@ internal partial class DragDropManager : DragDropManager.IDropTarget io.KeyShift = false; io.AddKeyEvent(ImGuiKey.LeftShift, false); } + + return keys; } private static void MouseDrop(DragDropInterop.ModifierKeys keys) From 07a92ba0258080d2b4c649b3e193176eeacc73a2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 3 Jun 2023 00:01:01 +0200 Subject: [PATCH 06/11] Let DI handle lifetime and make Plugin Service. --- Dalamud/Dalamud.cs | 3 -- Dalamud/Interface/DragDrop/DragDropManager.cs | 30 +++++++++---------- Dalamud/Interface/UiBuilder.cs | 6 ---- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index d6cf6a107..4e491f12a 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -151,9 +151,6 @@ internal sealed class Dalamud : IServiceType // will not receive any windows messages Service.GetNullable()?.Dispose(); - // this must be done before unloading interface manager, since it relies on the window handle members. - Service.GetNullable()?.Dispose(); - // this must be done before unloading plugins, or it can cause a race condition // due to rendering happening on another thread, where a plugin might receive // a render call after it has been disposed, which can crash if it attempts to diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index 0bb7eac12..8a4a5ca51 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Threading.Tasks; + using Dalamud.Interface.Internal; +using Dalamud.IoC; +using Dalamud.IoC.Internal; using ImGuiNET; using Serilog; @@ -12,22 +14,19 @@ namespace Dalamud.Interface.DragDrop; /// A manager that keeps state of external windows drag and drop events, /// and can be used to create ImGui drag and drop sources and targets for those external events. /// +[PluginInterface] [ServiceManager.EarlyLoadedService] +[InherentDependency] internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType { - private InterfaceManager? interfaceManager; + private readonly InterfaceManager interfaceManager = Service.Get(); private int lastDropFrame = -2; private int lastTooltipFrame = -1; + [ServiceManager.ServiceConstructor] private DragDropManager() - { - Service.GetAsync().ContinueWith(task => - { - this.interfaceManager = task.Result.Manager; - this.Enable(); - }); - } + => this.Enable(); /// Gets a value indicating whether external drag and drop is available at all. public bool ServiceAvailable { get; private set; } @@ -51,16 +50,16 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService /// Enable external drag and drop. public void Enable() { - if (this.ServiceAvailable || this.interfaceManager == null) + if (this.ServiceAvailable) { return; } try { - var ret2 = DragDropInterop.RegisterDragDrop(this.interfaceManager.WindowHandlePtr, this); - Log.Information($"[DragDrop] Registered window {this.interfaceManager.WindowHandlePtr} for external drag and drop operations. ({ret2})"); - Marshal.ThrowExceptionForHR(ret2); + var ret = DragDropInterop.RegisterDragDrop(this.interfaceManager.WindowHandlePtr, this); + Log.Information($"[DragDrop] Registered window {this.interfaceManager.WindowHandlePtr} for external drag and drop operations. ({ret})"); + Marshal.ThrowExceptionForHR(ret); this.ServiceAvailable = true; } catch (Exception ex) @@ -79,8 +78,9 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService try { - DragDropInterop.RevokeDragDrop(this.interfaceManager!.WindowHandlePtr); - Log.Information($"[DragDrop] Disabled external drag and drop operations for window {this.interfaceManager.WindowHandlePtr}."); + var ret = DragDropInterop.RevokeDragDrop(this.interfaceManager!.WindowHandlePtr); + Log.Information($"[DragDrop] Disabled external drag and drop operations for window {this.interfaceManager.WindowHandlePtr}. ({ret})"); + Marshal.ThrowExceptionForHR(ret); } catch (Exception ex) { diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 5a757e0e6..53f223ef2 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -31,7 +31,6 @@ 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 = Service.Get(); [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -102,11 +101,6 @@ 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. /// From b99d62e45005ba07b870a08aa1de9a2e383be1a7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 13 Jun 2023 20:55:22 +0200 Subject: [PATCH 07/11] Some finetuning. --- Dalamud/Interface/DragDrop/DragDropInterop.cs | 16 ++++++++-------- Dalamud/Interface/DragDrop/DragDropTarget.cs | 7 +++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Dalamud/Interface/DragDrop/DragDropInterop.cs b/Dalamud/Interface/DragDrop/DragDropInterop.cs index 8bf04f160..28a2644a5 100644 --- a/Dalamud/Interface/DragDrop/DragDropInterop.cs +++ b/Dalamud/Interface/DragDrop/DragDropInterop.cs @@ -13,14 +13,6 @@ namespace Dalamud.Interface.DragDrop; /// Implements interop enums and function calls to interact with external drag and drop. internal partial class DragDropManager { - internal struct POINTL - { - [ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")] - public int x; - [ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")] - public int y; - } - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("00000122-0000-0000-C000-000000000046")] [ComImport] @@ -39,6 +31,14 @@ internal partial class DragDropManager void Drop([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect); } + internal struct POINTL + { + [ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")] + public int x; + [ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")] + public int y; + } + private static class DragDropInterop { [Flags] diff --git a/Dalamud/Interface/DragDrop/DragDropTarget.cs b/Dalamud/Interface/DragDrop/DragDropTarget.cs index b6bb9d237..05e5599f9 100644 --- a/Dalamud/Interface/DragDrop/DragDropTarget.cs +++ b/Dalamud/Interface/DragDrop/DragDropTarget.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices.ComTypes; using System.Text; @@ -19,7 +18,7 @@ internal partial class DragDropManager : DragDropManager.IDropTarget private DragDropInterop.ModifierKeys lastKeyState = DragDropInterop.ModifierKeys.MK_NONE; /// Create the drag and drop formats we accept. - private static FORMATETC FormatEtc = + private FORMATETC formatEtc = new() { cfFormat = (short)DragDropInterop.ClipboardFormat.CF_HDROP, @@ -41,7 +40,7 @@ internal partial class DragDropManager : DragDropManager.IDropTarget this.IsDragging = true; this.lastKeyState = UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true); - if (pDataObj.QueryGetData(ref FormatEtc) != 0) + if (pDataObj.QueryGetData(ref this.formatEtc) != 0) { pdwEffect = 0; } @@ -203,7 +202,7 @@ internal partial class DragDropManager : DragDropManager.IDropTarget try { - data.GetData(ref FormatEtc, out var stgMedium); + data.GetData(ref this.formatEtc, out var stgMedium); var numFiles = DragDropInterop.DragQueryFile(stgMedium.unionmember, uint.MaxValue, new StringBuilder(), 0); var files = new string[numFiles]; var sb = new StringBuilder(1024); From 20186afa490344267be267cf0c1aa9e63d737d8b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 13 Jun 2023 20:55:37 +0200 Subject: [PATCH 08/11] Resolve as service. --- Dalamud/Interface/DragDrop/DragDropManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index 8a4a5ca51..9134d5315 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -17,6 +17,7 @@ namespace Dalamud.Interface.DragDrop; [PluginInterface] [ServiceManager.EarlyLoadedService] [InherentDependency] +[ResolveVia] internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType { private readonly InterfaceManager interfaceManager = Service.Get(); From 90722fcfbe2c613b9313487ed0c35fa88b47ae6f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 13 Jun 2023 21:22:12 +0200 Subject: [PATCH 09/11] Change to actively inject WithScene. --- Dalamud/Interface/DragDrop/DragDropManager.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index 9134d5315..4363cfabc 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -16,11 +16,11 @@ namespace Dalamud.Interface.DragDrop; /// [PluginInterface] [ServiceManager.EarlyLoadedService] -[InherentDependency] [ResolveVia] internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType { - private readonly InterfaceManager interfaceManager = Service.Get(); + [ServiceManager.ServiceDependency] + private readonly InterfaceManager.InterfaceManagerWithScene interfaceManager = Service.Get(); private int lastDropFrame = -2; private int lastTooltipFrame = -1; @@ -58,14 +58,14 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService try { - var ret = DragDropInterop.RegisterDragDrop(this.interfaceManager.WindowHandlePtr, this); - Log.Information($"[DragDrop] Registered window {this.interfaceManager.WindowHandlePtr} for external drag and drop operations. ({ret})"); + var ret = DragDropInterop.RegisterDragDrop(this.interfaceManager.Manager.WindowHandlePtr, this); + Log.Information($"[DragDrop] Registered window 0x{this.interfaceManager.Manager.WindowHandlePtr:X} for external drag and drop operations. ({ret})"); Marshal.ThrowExceptionForHR(ret); this.ServiceAvailable = true; } catch (Exception ex) { - Log.Error($"Could not create windows drag and drop utility:\n{ex}"); + Log.Error($"Could not create windows drag and drop utility for window 0x{this.interfaceManager.Manager.WindowHandlePtr:X}:\n{ex}"); } } @@ -79,13 +79,13 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService try { - var ret = DragDropInterop.RevokeDragDrop(this.interfaceManager!.WindowHandlePtr); - Log.Information($"[DragDrop] Disabled external drag and drop operations for window {this.interfaceManager.WindowHandlePtr}. ({ret})"); + var ret = DragDropInterop.RevokeDragDrop(this.interfaceManager.Manager.WindowHandlePtr); + Log.Information($"[DragDrop] Disabled external drag and drop operations for window 0x{this.interfaceManager.Manager.WindowHandlePtr:X}. ({ret})"); Marshal.ThrowExceptionForHR(ret); } catch (Exception ex) { - Log.Error($"Could not disable windows drag and drop utility:\n{ex}"); + Log.Error($"Could not disable windows drag and drop utility for window 0x{this.interfaceManager.Manager.WindowHandlePtr:X}:\n{ex}"); } this.ServiceAvailable = false; From d7ce12a2ea6991cbae7a234cc2d5b851b7ab0aa9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 13 Jun 2023 21:23:14 +0200 Subject: [PATCH 10/11] Whitespace... --- Dalamud/Interface/DragDrop/DragDropManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index 4363cfabc..34f1296e1 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -21,10 +21,10 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService { [ServiceManager.ServiceDependency] private readonly InterfaceManager.InterfaceManagerWithScene interfaceManager = Service.Get(); + private int lastDropFrame = -2; private int lastTooltipFrame = -1; - [ServiceManager.ServiceConstructor] private DragDropManager() => this.Enable(); From c0715515745bf026dc353e4d405b9de4ffa49d3e Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 8 Jul 2023 12:39:02 +0200 Subject: [PATCH 11/11] remove unneeded includes --- Dalamud/Dalamud.cs | 1 - Dalamud/Interface/UiBuilder.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 4e491f12a..c38594771 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Game.Gui.Internal; -using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal; using Dalamud.Plugin.Internal; using Dalamud.Utility; diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 53f223ef2..eca0f64a0 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -7,7 +7,6 @@ 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;