Initial commit to new repository
This commit is contained in:
108
src/AxCopilot/Services/FileDialogWatcher.cs
Normal file
108
src/AxCopilot/Services/FileDialogWatcher.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace AxCopilot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Windows 열기/저장 대화상자(#32770)를 감지하여 이벤트를 발생시킵니다.
|
||||
/// SetWinEventHook으로 HWND 생성/소멸을 모니터링합니다.
|
||||
/// </summary>
|
||||
public class FileDialogWatcher : IDisposable
|
||||
{
|
||||
// ─── P/Invoke ────────────────────────────────────────────────────────────
|
||||
|
||||
private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
|
||||
IntPtr hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax,
|
||||
IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc,
|
||||
uint idProcess, uint idThread, uint dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
private const uint EVENT_OBJECT_SHOW = 0x8002;
|
||||
private const uint EVENT_OBJECT_CREATE = 0x8000;
|
||||
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
|
||||
private const uint WINEVENT_SKIPOWNPROCESS = 0x0002;
|
||||
|
||||
// ─── 상태 ────────────────────────────────────────────────────────────────
|
||||
|
||||
private IntPtr _hook;
|
||||
private WinEventDelegate? _delegate; // prevent GC
|
||||
private bool _disposed;
|
||||
private readonly Dispatcher _dispatcher;
|
||||
|
||||
/// <summary>열기/저장 대화상자가 감지되면 발생합니다. IntPtr = 대화상자 HWND.</summary>
|
||||
public event EventHandler<IntPtr>? FileDialogOpened;
|
||||
|
||||
public FileDialogWatcher()
|
||||
{
|
||||
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_hook != IntPtr.Zero) return;
|
||||
_delegate = OnWinEvent;
|
||||
_hook = SetWinEventHook(
|
||||
EVENT_OBJECT_SHOW, EVENT_OBJECT_SHOW,
|
||||
IntPtr.Zero, _delegate,
|
||||
0, 0,
|
||||
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_hook != IntPtr.Zero)
|
||||
{
|
||||
UnhookWinEvent(_hook);
|
||||
_hook = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWinEvent(IntPtr hWinEventHook, uint eventType,
|
||||
IntPtr hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime)
|
||||
{
|
||||
if (hwnd == IntPtr.Zero || idObject != 0) return;
|
||||
if (!IsWindowVisible(hwnd)) return;
|
||||
|
||||
// 클래스명 #32770 = 공통 대화상자
|
||||
var sb = new StringBuilder(256);
|
||||
GetClassName(hwnd, sb, 256);
|
||||
var className = sb.ToString();
|
||||
|
||||
if (className != "#32770") return;
|
||||
|
||||
// 창 제목으로 열기/저장 대화상자인지 확인
|
||||
var titleSb = new StringBuilder(256);
|
||||
GetWindowText(hwnd, titleSb, 256);
|
||||
var title = titleSb.ToString();
|
||||
|
||||
if (title.Contains("열기") || title.Contains("Open") ||
|
||||
title.Contains("저장") || title.Contains("Save") ||
|
||||
title.Contains("다른 이름") || title.Contains("Browse") ||
|
||||
title.Contains("폴더") || title.Contains("Folder"))
|
||||
{
|
||||
_dispatcher.BeginInvoke(() => FileDialogOpened?.Invoke(this, hwnd));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user