Some checks failed
Release Gate / gate (push) Has been cancelled
- 클립보드 히스토리, 클립보드 변환, 순차 붙여넣기 실행 경로에 공통 포커스 복원 helper를 추가했습니다. - 이전 활성 창 복원, 최소 대기, Ctrl+V 주입 순서를 하나로 맞춰 포커스 누락으로 내용이 원래 창에 들어가지 않던 문제를 완화했습니다. - 관련 변경 이력을 README와 DEVELOPMENT 문서에 반영했습니다. 검증: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\ - 경고 0 / 오류 0
127 lines
4.6 KiB
C#
127 lines
4.6 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace AxCopilot.Services;
|
|
|
|
internal static class ForegroundPasteHelper
|
|
{
|
|
private const int SW_RESTORE = 9;
|
|
private const uint INPUT_KEYBOARD = 1;
|
|
private const uint KEYEVENTF_KEYUP = 0x0002;
|
|
private const ushort VK_CONTROL = 0x11;
|
|
private const ushort VK_V = 0x56;
|
|
|
|
public static async Task<bool> RestoreWindowAsync(
|
|
IntPtr hwnd,
|
|
CancellationToken ct,
|
|
int initialDelayMs = 220,
|
|
int settleDelayMs = 60,
|
|
int maxAttempts = 4)
|
|
{
|
|
if (hwnd == IntPtr.Zero || !IsWindow(hwnd))
|
|
return false;
|
|
|
|
await Task.Delay(initialDelayMs, ct);
|
|
|
|
if (IsIconic(hwnd))
|
|
ShowWindow(hwnd, SW_RESTORE);
|
|
|
|
for (var attempt = 0; attempt < maxAttempts; attempt++)
|
|
{
|
|
uint currentThread = GetCurrentThreadId();
|
|
uint targetThread = GetWindowThreadProcessId(hwnd, out _);
|
|
var foreground = GetForegroundWindow();
|
|
uint foregroundThread = foreground != IntPtr.Zero
|
|
? GetWindowThreadProcessId(foreground, out _)
|
|
: 0;
|
|
|
|
var attachedTarget = false;
|
|
var attachedForeground = false;
|
|
|
|
try
|
|
{
|
|
if (targetThread != 0 && targetThread != currentThread)
|
|
{
|
|
AttachThreadInput(currentThread, targetThread, true);
|
|
attachedTarget = true;
|
|
}
|
|
|
|
if (foregroundThread != 0 && foregroundThread != currentThread && foregroundThread != targetThread)
|
|
{
|
|
AttachThreadInput(currentThread, foregroundThread, true);
|
|
attachedForeground = true;
|
|
}
|
|
|
|
BringWindowToTop(hwnd);
|
|
SetForegroundWindow(hwnd);
|
|
}
|
|
finally
|
|
{
|
|
if (attachedForeground)
|
|
AttachThreadInput(currentThread, foregroundThread, false);
|
|
|
|
if (attachedTarget)
|
|
AttachThreadInput(currentThread, targetThread, false);
|
|
}
|
|
|
|
await Task.Delay(settleDelayMs, ct);
|
|
|
|
if (GetForegroundWindow() == hwnd)
|
|
return true;
|
|
}
|
|
|
|
return GetForegroundWindow() == hwnd;
|
|
}
|
|
|
|
public static async Task<bool> PasteClipboardAsync(
|
|
IntPtr hwnd,
|
|
CancellationToken ct,
|
|
int initialDelayMs = 220)
|
|
{
|
|
var restored = await RestoreWindowAsync(hwnd, ct, initialDelayMs);
|
|
if (!restored)
|
|
return false;
|
|
|
|
await Task.Delay(40, ct);
|
|
SendCtrlV();
|
|
return true;
|
|
}
|
|
|
|
private static void SendCtrlV()
|
|
{
|
|
var inputs = new INPUT[4];
|
|
inputs[0] = new INPUT { Type = INPUT_KEYBOARD, Ki = new KEYBDINPUT { wVk = VK_CONTROL } };
|
|
inputs[1] = new INPUT { Type = INPUT_KEYBOARD, Ki = new KEYBDINPUT { wVk = VK_V } };
|
|
inputs[2] = new INPUT { Type = INPUT_KEYBOARD, Ki = new KEYBDINPUT { wVk = VK_V, dwFlags = KEYEVENTF_KEYUP } };
|
|
inputs[3] = new INPUT { Type = INPUT_KEYBOARD, Ki = new KEYBDINPUT { wVk = VK_CONTROL, dwFlags = KEYEVENTF_KEYUP } };
|
|
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<INPUT>());
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Explicit, Size = 40)]
|
|
private struct INPUT
|
|
{
|
|
[FieldOffset(0)] public uint Type;
|
|
[FieldOffset(8)] public KEYBDINPUT Ki;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct KEYBDINPUT
|
|
{
|
|
public ushort wVk;
|
|
public ushort wScan;
|
|
public uint dwFlags;
|
|
public uint time;
|
|
public IntPtr dwExtraInfo;
|
|
}
|
|
|
|
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
[DllImport("user32.dll")] private static extern bool BringWindowToTop(IntPtr hWnd);
|
|
[DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow();
|
|
[DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
|
|
[DllImport("user32.dll")] private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, [MarshalAs(UnmanagedType.Bool)] bool fAttach);
|
|
[DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
[DllImport("user32.dll")] private static extern bool IsWindow(IntPtr hWnd);
|
|
[DllImport("user32.dll")] private static extern bool IsIconic(IntPtr hWnd);
|
|
[DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId();
|
|
[DllImport("user32.dll")] private static extern uint SendInput(uint nInputs, [MarshalAs(UnmanagedType.LPArray)] INPUT[] pInputs, int cbSize);
|
|
}
|