- 클립보드 히스토리, 클립보드 변환, 순차 붙여넣기 실행 경로에 공통 포커스 복원 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
This commit is contained in:
126
src/AxCopilot/Services/ForegroundPasteHelper.cs
Normal file
126
src/AxCopilot/Services/ForegroundPasteHelper.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user