From 496bed4c69118c945940d41b6fb19cf2ec14e637 Mon Sep 17 00:00:00 2001 From: grittyfrog <148605153+grittyfrog@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:30:11 +1100 Subject: [PATCH] Fix multi-line copy/paste between ImGui and XIV (#1525) --- .../Internal/ImGuiClipboardConfig.cs | 80 +++++++++++++++++++ .../Interface/Internal/InterfaceManager.cs | 2 + 2 files changed, 82 insertions(+) create mode 100644 Dalamud/Interface/Internal/ImGuiClipboardConfig.cs diff --git a/Dalamud/Interface/Internal/ImGuiClipboardConfig.cs b/Dalamud/Interface/Internal/ImGuiClipboardConfig.cs new file mode 100644 index 000000000..b3302add4 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiClipboardConfig.cs @@ -0,0 +1,80 @@ +using System.Runtime.InteropServices; +using ImGuiNET; + +namespace Dalamud.Interface.Internal; + +/// +/// Configures the ImGui clipboard behaviour to work nicely with XIV. +/// +/// +/// +/// XIV uses '\r' for line endings and will truncate all text after a '\n' character. +/// This means that copy/pasting multi-line text from ImGui to XIV will only copy the first line. +/// +/// +/// ImGui uses '\n' for line endings and will ignore '\r' entirely. +/// This means that copy/pasting multi-line text from XIV to ImGui will copy all the text +/// without line breaks. +/// +/// +/// To fix this we normalize all clipboard line endings entering/exiting ImGui to '\r\n' which +/// works for both ImGui and XIV. +/// +/// +internal static class ImGuiClipboardConfig +{ + private delegate void SetClipboardTextDelegate(IntPtr userData, string text); + private delegate string GetClipboardTextDelegate(); + + private static SetClipboardTextDelegate? _setTextOriginal = null; + private static GetClipboardTextDelegate? _getTextOriginal = null; + + // These must exist as variables to prevent them from being GC'd + private static SetClipboardTextDelegate? _setText = null; + private static GetClipboardTextDelegate? _getText = null; + + public static void Apply() + { + var io = ImGui.GetIO(); + if (_setTextOriginal == null) + { + _setTextOriginal = + Marshal.GetDelegateForFunctionPointer(io.SetClipboardTextFn); + } + + if (_getTextOriginal == null) + { + _getTextOriginal = + Marshal.GetDelegateForFunctionPointer(io.GetClipboardTextFn); + } + + _setText = new SetClipboardTextDelegate(SetClipboardText); + _getText = new GetClipboardTextDelegate(GetClipboardText); + + io.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(_setText); + io.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(_getText); + } + + public static void Unapply() + { + var io = ImGui.GetIO(); + if (_setTextOriginal != null) + { + io.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(_setTextOriginal); + } + if (_getTextOriginal != null) + { + io.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(_getTextOriginal); + } + } + + private static void SetClipboardText(IntPtr userData, string text) + { + _setTextOriginal!(userData, text.ReplaceLineEndings("\r\n")); + } + + private static string GetClipboardText() + { + return _getTextOriginal!().ReplaceLineEndings("\r\n"); + } +} diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 1b12fd853..7d164c01f 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -240,6 +240,7 @@ internal class InterfaceManager : IDisposable, IServiceType this.processMessageHook?.Dispose(); }).Wait(); + ImGuiClipboardConfig.Unapply(); this.scene?.Dispose(); } @@ -628,6 +629,7 @@ internal class InterfaceManager : IDisposable, IServiceType ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; this.SetupFonts(); + ImGuiClipboardConfig.Apply(); if (!configuration.IsDocking) {