Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DrawListTextureWrap #2036

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Dalamud/Dalamud.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\LineBreak.txt" LogicalName="LineBreak.txt" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.DrawToPremul.ps.bin" />
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.DrawToPremul.vs.bin" />
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.MakeStraight.ps.bin" />
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.MakeStraight.vs.bin" />
</ItemGroup>

<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
<ItemGroup>
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
Expand Down
282 changes: 282 additions & 0 deletions Dalamud/Interface/Internal/StaThreadService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

using Dalamud.Utility;

using TerraFX.Interop.Windows;

using static TerraFX.Interop.Windows.Windows;

namespace Dalamud.Interface.Internal;

/// <summary>Dedicated thread for OLE operations, and possibly more native thread-serialized operations.</summary>
[ServiceManager.EarlyLoadedService]
internal partial class StaThreadService : IInternalDisposableService
{
private readonly CancellationTokenSource cancellationTokenSource = new();
private readonly Thread thread;
private readonly ThreadBoundTaskScheduler taskScheduler;
private readonly TaskFactory taskFactory;

private readonly TaskCompletionSource<HWND> messageReceiverHwndTask =
new(TaskCreationOptions.RunContinuationsAsynchronously);

[ServiceManager.ServiceConstructor]
private StaThreadService()
{
try
{
this.thread = new(this.OleThreadBody);
this.thread.SetApartmentState(ApartmentState.STA);

this.taskScheduler = new(this.thread);
this.taskScheduler.TaskQueued += this.TaskSchedulerOnTaskQueued;
this.taskFactory = new(
this.cancellationTokenSource.Token,
TaskCreationOptions.None,
TaskContinuationOptions.None,
this.taskScheduler);

this.thread.Start();
this.messageReceiverHwndTask.Task.Wait();
}
catch (Exception e)
{
this.cancellationTokenSource.Cancel();
this.messageReceiverHwndTask.SetException(e);
throw;
}
}

/// <summary>Gets all the available clipboard formats.</summary>
public IReadOnlySet<uint> AvailableClipboardFormats { get; private set; } = ImmutableSortedSet<uint>.Empty;

/// <summary>Places a pointer to a specific data object onto the clipboard. This makes the data object accessible
/// to the <see cref="OleGetClipboard(IDataObject**)"/> function.</summary>
/// <param name="pdo">Pointer to the <see cref="IDataObject"/> interface on the data object from which the data to
/// be placed on the clipboard can be obtained. This parameter can be NULL; in which case the clipboard is emptied.
/// </param>
/// <returns>This function returns <see cref="S.S_OK"/> on success.</returns>
[LibraryImport("ole32.dll")]
public static unsafe partial int OleSetClipboard(IDataObject* pdo);

/// <inheritdoc cref="OleSetClipboard(IDataObject*)"/>
public static unsafe void OleSetClipboard(ComPtr<IDataObject> pdo) =>
Marshal.ThrowExceptionForHR(OleSetClipboard(pdo.Get()));

/// <summary>Retrieves a data object that you can use to access the contents of the clipboard.</summary>
/// <param name="pdo">Address of <see cref="IDataObject"/> pointer variable that receives the interface pointer to
/// the clipboard data object.</param>
/// <returns>This function returns <see cref="S.S_OK"/> on success.</returns>
[LibraryImport("ole32.dll")]
public static unsafe partial int OleGetClipboard(IDataObject** pdo);

/// <inheritdoc cref="OleGetClipboard(IDataObject**)"/>
public static unsafe ComPtr<IDataObject> OleGetClipboard()
{
var pdo = default(ComPtr<IDataObject>);
Marshal.ThrowExceptionForHR(OleGetClipboard(pdo.GetAddressOf()));
return pdo;
}

/// <summary>Calls the appropriate method or function to release the specified storage medium.</summary>
/// <param name="stgm">Address of <see cref="STGMEDIUM"/> to release.</param>
[LibraryImport("ole32.dll")]
public static unsafe partial void ReleaseStgMedium(STGMEDIUM* stgm);

/// <inheritdoc cref="ReleaseStgMedium(STGMEDIUM*)"/>
public static unsafe void ReleaseStgMedium(ref STGMEDIUM stgm)
{
fixed (STGMEDIUM* pstgm = &stgm)
ReleaseStgMedium(pstgm);
}

/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
this.cancellationTokenSource.Cancel();
if (this.messageReceiverHwndTask.Task.IsCompletedSuccessfully)
SendMessageW(this.messageReceiverHwndTask.Task.Result, WM.WM_CLOSE, 0, 0);

this.thread.Join();
}

/// <summary>Runs a given delegate in the messaging thread.</summary>
/// <param name="action">Delegate to run.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <returns>A <see cref="Task"/> representating the state of the operation.</returns>
public async Task Run(Action action, CancellationToken cancellationToken = default)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
this.cancellationTokenSource.Token,
cancellationToken);
await this.taskFactory.StartNew(action, cancellationToken).ConfigureAwait(true);
}

/// <summary>Runs a given delegate in the messaging thread.</summary>
/// <typeparam name="T">Type of the return value.</typeparam>
/// <param name="func">Delegate to run.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <returns>A <see cref="Task{T}"/> representating the state of the operation.</returns>
public async Task<T> Run<T>(Func<T> func, CancellationToken cancellationToken = default)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
this.cancellationTokenSource.Token,
cancellationToken);
return await this.taskFactory.StartNew(func, cancellationToken).ConfigureAwait(true);
}

/// <summary>Runs a given delegate in the messaging thread.</summary>
/// <param name="func">Delegate to run.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <returns>A <see cref="Task{T}"/> representating the state of the operation.</returns>
public async Task Run(Func<Task> func, CancellationToken cancellationToken = default)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
this.cancellationTokenSource.Token,
cancellationToken);
await await this.taskFactory.StartNew(func, cancellationToken).ConfigureAwait(true);
}

/// <summary>Runs a given delegate in the messaging thread.</summary>
/// <typeparam name="T">Type of the return value.</typeparam>
/// <param name="func">Delegate to run.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <returns>A <see cref="Task{T}"/> representating the state of the operation.</returns>
public async Task<T> Run<T>(Func<Task<T>> func, CancellationToken cancellationToken = default)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
this.cancellationTokenSource.Token,
cancellationToken);
return await await this.taskFactory.StartNew(func, cancellationToken).ConfigureAwait(true);
}

[LibraryImport("ole32.dll")]
private static partial int OleInitialize(nint reserved);

[LibraryImport("ole32.dll")]
private static partial void OleUninitialize();

[LibraryImport("ole32.dll")]
private static partial int OleFlushClipboard();

private void TaskSchedulerOnTaskQueued() =>
PostMessageW(this.messageReceiverHwndTask.Task.Result, WM.WM_NULL, 0, 0);

private void UpdateAvailableClipboardFormats(HWND hWnd)
{
if (!OpenClipboard(hWnd))
{
this.AvailableClipboardFormats = ImmutableSortedSet<uint>.Empty;
return;
}

var formats = new SortedSet<uint>();
for (var cf = EnumClipboardFormats(0); cf != 0; cf = EnumClipboardFormats(cf))
formats.Add(cf);
this.AvailableClipboardFormats = formats;
CloseClipboard();
}

private LRESULT MessageReceiverWndProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam)
{
this.taskScheduler.Run();

switch (uMsg)
{
case WM.WM_CLIPBOARDUPDATE:
this.UpdateAvailableClipboardFormats(hWnd);
break;

case WM.WM_DESTROY:
PostQuitMessage(0);
return 0;
}

return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}

private unsafe void OleThreadBody()
{
var hInstance = (HINSTANCE)Marshal.GetHINSTANCE(typeof(StaThreadService).Module);
ushort wndClassAtom = 0;
var gch = GCHandle.Alloc(this);
try
{
((HRESULT)OleInitialize(0)).ThrowOnError();

fixed (char* name = typeof(StaThreadService).FullName!)
{
var wndClass = new WNDCLASSEXW
{
cbSize = (uint)sizeof(WNDCLASSEXW),
lpfnWndProc = &MessageReceiverWndProcStatic,
hInstance = hInstance,
hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1),
lpszClassName = (ushort*)name,
};

wndClassAtom = RegisterClassExW(&wndClass);
if (wndClassAtom == 0)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

this.messageReceiverHwndTask.SetResult(
CreateWindowExW(
0,
(ushort*)wndClassAtom,
(ushort*)name,
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
default,
default,
hInstance,
(void*)GCHandle.ToIntPtr(gch)));

[UnmanagedCallersOnly]
static LRESULT MessageReceiverWndProcStatic(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam)
{
nint gchn;
if (uMsg == WM.WM_NCCREATE)
{
gchn = (nint)((CREATESTRUCTW*)lParam)->lpCreateParams;
SetWindowLongPtrW(hWnd, GWLP.GWLP_USERDATA, gchn);
}
else
{
gchn = GetWindowLongPtrW(hWnd, GWLP.GWLP_USERDATA);
}

if (gchn == 0)
return DefWindowProcW(hWnd, uMsg, wParam, lParam);

return ((StaThreadService)GCHandle.FromIntPtr(gchn).Target!)
.MessageReceiverWndProc(hWnd, uMsg, wParam, lParam);
}
}

AddClipboardFormatListener(this.messageReceiverHwndTask.Task.Result);
this.UpdateAvailableClipboardFormats(this.messageReceiverHwndTask.Task.Result);

for (MSG msg; GetMessageW(&msg, default, 0, 0);)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
catch (Exception e)
{
gch.Free();
_ = OleFlushClipboard();
OleUninitialize();
if (wndClassAtom != 0)
UnregisterClassW((ushort*)wndClassAtom, hInstance);
this.messageReceiverHwndTask.TrySetException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ public void Draw()

ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));

if (!this.textureManager.HasClipboardImage())
{
ImGuiComponents.DisabledButton("Paste from Clipboard");
}
else if (ImGui.Button("Paste from Clipboard"))
{
this.addedTextures.Add(new(Api10: this.textureManager.CreateFromClipboardAsync()));
}

if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGameIcon)))
{
ImGui.PushID(nameof(this.DrawGetFromGameIcon));
Expand Down
Loading
Loading