Files
HRM-System/node_modules/@react-grab/cli/dist/read-clipboard.ps1

112 lines
4.0 KiB
PowerShell

# Windows clipboard reader for `react-grab watch`. Reads CF_UNICODETEXT and the
# registered "Chromium Web Custom MIME Data Format" (a base::Pickle of web custom
# data) via Win32, in a single OpenClipboard so the two reads are consistent.
# Emits { changeCount, text, pickleBase64 } as JSON; the CLI decodes the pickle
# (shared with macOS/Linux). GetClipboardSequenceNumber gives a cheap monotonic
# change token for idle polling.
$ErrorActionPreference = "Stop"
$source = @"
using System;
using System.Runtime.InteropServices;
public static class RgClip {
[DllImport("user32.dll", SetLastError = true)]
public static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool CloseClipboard();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetClipboardData(uint uFormat);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint RegisterClipboardFormat(string lpszFormat);
[DllImport("user32.dll")]
public static extern uint GetClipboardSequenceNumber();
[DllImport("user32.dll", SetLastError = true)]
public static extern bool IsClipboardFormatAvailable(uint format);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern UIntPtr GlobalSize(IntPtr hMem);
private const uint CF_UNICODETEXT = 13;
private static byte[] ReadLocked(uint format) {
if (!IsClipboardFormatAvailable(format)) return null;
IntPtr handle = GetClipboardData(format);
if (handle == IntPtr.Zero) return null;
IntPtr pointer = GlobalLock(handle);
if (pointer == IntPtr.Zero) return null;
try {
ulong size = GlobalSize(handle).ToUInt64();
if (size == 0 || size > int.MaxValue) return null;
byte[] bytes = new byte[(int)size];
Marshal.Copy(pointer, bytes, 0, (int)size);
return bytes;
} finally {
GlobalUnlock(handle);
}
}
public static byte[][] ReadAll(uint customFormat) {
byte[][] result = new byte[2][];
if (!OpenClipboard(IntPtr.Zero)) return result;
try {
result[0] = ReadLocked(CF_UNICODETEXT);
result[1] = ReadLocked(customFormat);
} finally {
CloseClipboard();
}
return result;
}
}
"@
# Add-Type recompiles on every process start, so the reader is compiled to a
# cached assembly once and merely loaded on subsequent polls.
$cacheDir = Join-Path $env:TEMP "react-grab-watch"
# Key the cached DLL by a hash of the source so a changed reader recompiles
# instead of loading a stale assembly.
$sourceHashBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($source))
$sourceHash = ([System.BitConverter]::ToString($sourceHashBytes) -replace "-", "").Substring(0, 16)
$cachedDll = Join-Path $cacheDir "RgClipReader-$sourceHash.dll"
$loaded = $false
if (Test-Path $cachedDll) {
try { Add-Type -Path $cachedDll | Out-Null; $loaded = $true } catch {}
}
if (-not $loaded) {
try {
New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null
Add-Type -TypeDefinition $source -Language CSharp -OutputAssembly $cachedDll -ErrorAction Stop | Out-Null
Add-Type -Path $cachedDll | Out-Null
$loaded = $true
} catch {}
}
if (-not $loaded) {
Add-Type -TypeDefinition $source -Language CSharp | Out-Null
}
$changeCount = [RgClip]::GetClipboardSequenceNumber()
$customFormat = [RgClip]::RegisterClipboardFormat("Chromium Web Custom MIME Data Format")
$blobs = [RgClip]::ReadAll($customFormat)
$text = $null
if ($null -ne $blobs[0]) {
$text = [System.Text.Encoding]::Unicode.GetString($blobs[0]).TrimEnd([char]0)
}
$pickleBase64 = $null
if ($null -ne $blobs[1]) {
$pickleBase64 = [System.Convert]::ToBase64String($blobs[1])
}
$payload = [ordered]@{
changeCount = [int64]$changeCount
text = $text
pickleBase64 = $pickleBase64
}
$payload | ConvertTo-Json -Compress