BUTR/Bannerlord.BLSE

View on GitHub
src/Bannerlord.LauncherEx/Helpers/Input/WindowsClipboard.cs

Summary

Maintainability
A
2 hrs
Test Coverage
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

using Windows.Win32;
using Windows.Win32.Foundation;

namespace Bannerlord.LauncherEx.Helpers;

internal static class WindowsClipboard
{
    private const uint CFUnicodeText = 13;

    public static void SetText(string text)
    {
        TryOpenClipboard();

        InnerSet(text);
    }

    private static unsafe void InnerSet(string text)
    {
        PInvoke.EmptyClipboard();
        var hGlobal = (HGLOBAL) IntPtr.Zero;
        try
        {
            var bytes = (text.Length + 1) * 2;
            hGlobal = (HGLOBAL) Marshal.AllocHGlobal(bytes);
            if (hGlobal == IntPtr.Zero)
            {
                ThrowWin32();
            }

            var target = (HGLOBAL) new IntPtr(PInvoke.GlobalLock(hGlobal));
            if (target == IntPtr.Zero)
            {
                ThrowWin32();
            }

            try
            {
                Marshal.Copy(text.ToCharArray(), 0, target, text.Length);
            }
            finally
            {
                PInvoke.GlobalUnlock(target);
            }

            if (PInvoke.SetClipboardData(CFUnicodeText, new HANDLE(hGlobal)) == IntPtr.Zero)
            {
                ThrowWin32();
            }

            hGlobal = (HGLOBAL) IntPtr.Zero;
        }
        finally
        {
            if (hGlobal != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(hGlobal);
            }

            PInvoke.CloseClipboard();
        }
    }

    private static void TryOpenClipboard()
    {
        var num = 10;
        while (true)
        {
            if (PInvoke.OpenClipboard(HWND.Null))
            {
                break;
            }

            if (--num == 0)
            {
                ThrowWin32();
            }

            Thread.Sleep(100);
        }
    }

    public static string? GetText()
    {
        if (!PInvoke.IsClipboardFormatAvailable(CFUnicodeText))
        {
            return null;
        }
        TryOpenClipboard();

        return InnerGet();
    }

    private static unsafe string? InnerGet()
    {
        var handle = (HGLOBAL) IntPtr.Zero;

        var pointer = IntPtr.Zero;
        try
        {
            handle = (HGLOBAL) (IntPtr) PInvoke.GetClipboardData(CFUnicodeText);
            if (handle == IntPtr.Zero)
            {
                return null;
            }

            pointer = new IntPtr(PInvoke.GlobalLock(handle));
            if (pointer == IntPtr.Zero)
            {
                return null;
            }

            var size = (int) PInvoke.GlobalSize(handle);
            var buff = new byte[size];

            Marshal.Copy(pointer, buff, 0, size);

            return Encoding.Unicode.GetString(buff).TrimEnd('\0');
        }
        finally
        {
            if (pointer != IntPtr.Zero)
            {
                PInvoke.GlobalUnlock(handle);
            }

            PInvoke.CloseClipboard();
        }
    }

    private static void ThrowWin32() => throw new Win32Exception(Marshal.GetLastWin32Error());
}