#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN using System; using System.Runtime.InteropServices; /// /// 데스크톱 오버레이에 필요한 Win32 선언 모음. /// 선언 자체는 무해하며, 실제 호출부에서 에디터 여부를 가드한다. /// internal static class Win32 { public const int GWL_STYLE = -16; public const int GWL_EXSTYLE = -20; public const uint WS_POPUP = 0x80000000; public const uint WS_VISIBLE = 0x10000000; public const uint WS_EX_TOOLWINDOW = 0x00000080; // 작업표시줄/Alt+Tab 에서 숨김 public const uint WS_EX_APPWINDOW = 0x00040000; public const uint WS_EX_TRANSPARENT = 0x00000020; // 마우스 히트테스트 통과 public const uint WS_EX_NOACTIVATE = 0x08000000; // 클릭해도 포커스를 뺏지 않음 public const uint WS_EX_LAYERED = 0x00080000; // 최상위 창의 클릭 통과에 필요 public const uint LWA_COLORKEY = 0x00000001; public const uint LWA_ALPHA = 0x00000002; public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); public const uint SWP_NOSIZE = 0x0001; public const uint SWP_NOMOVE = 0x0002; public const uint SWP_NOACTIVATE = 0x0010; public const uint SWP_FRAMECHANGED = 0x0020; public const uint SWP_SHOWWINDOW = 0x0040; public const int SM_CXSCREEN = 0; // 주 모니터 너비 public const int SM_CYSCREEN = 1; // 주 모니터 높이 public const int SM_XVIRTUALSCREEN = 76; public const int SM_YVIRTUALSCREEN = 77; public const int SM_CXVIRTUALSCREEN = 78; public const int SM_CYVIRTUALSCREEN = 79; // DwmGetWindowAttribute 속성 번호 public const int DWMWA_EXTENDED_FRAME_BOUNDS = 9; // 보이는 실제 프레임(투명 여백 제외) public const int DWMWA_CLOAKED = 14; // UWP 등 "숨겨졌지만 살아있는" 창 public const uint SPI_GETWORKAREA = 0x0030; public const int SW_HIDE = 0; public const int SW_SHOW = 5; public const int VK_LBUTTON = 0x01; public const int VK_RBUTTON = 0x02; public const int VK_ESCAPE = 0x1B; public const int VK_SHIFT = 0x10; public const int VK_CONTROL = 0x11; public const int VK_MENU = 0x12; // Alt public const int VK_1 = 0x31; public const int VK_2 = 0x32; public const int VK_3 = 0x33; public const int VK_N = 0x4E; [StructLayout(LayoutKind.Sequential)] public struct MARGINS { public int cxLeftWidth, cxRightWidth, cyTopHeight, cyBottomHeight; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int x, y; } [StructLayout(LayoutKind.Sequential)] public struct RECT { public int left, top, right, bottom; public int Width => right - left; public int Height => bottom - top; } [DllImport("user32.dll")] public static extern IntPtr GetActiveWindow(); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); // 지금 활성화된 창 [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern IntPtr FindWindowW(string lpClassName, string lpWindowName); [DllImport("user32.dll")] public static extern uint GetWindowLongW(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] public static extern uint SetWindowLongW(IntPtr hWnd, int nIndex, uint dwNewLong); [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); [DllImport("user32.dll")] public static extern int GetSystemMetrics(int nIndex); [DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT lpPoint); [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] public static extern short GetAsyncKeyState(int vKey); // --- 창 열거 --- public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr hWnd); // 최소화 여부 [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowTextLengthW(IntPtr hWnd); [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowTextW(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] public static extern bool SystemParametersInfoW(uint uiAction, uint uiParam, out RECT pvParam, uint fWinIni); /// DWMWA_CLOAKED 등 int 값을 받는 속성용. [DllImport("dwmapi.dll")] public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out int pvAttribute, int cbAttribute); /// DWMWA_EXTENDED_FRAME_BOUNDS 처럼 RECT 를 받는 속성용. [DllImport("dwmapi.dll")] public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out RECT pvAttribute, int cbAttribute); [DllImport("user32.dll")] public static extern bool SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags); [DllImport("dwmapi.dll")] public static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset); // --- 포커스 --- [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] public static extern IntPtr SetFocus(IntPtr hWnd); [DllImport("user32.dll")] public static extern bool IsWindow(IntPtr hWnd); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll")] public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId(); public static bool IsKeyDown(int vKey) => (GetAsyncKeyState(vKey) & 0x8000) != 0; /// /// 창을 확실히 활성화한다. /// /// SetForegroundWindow 는 그냥 부르면 자주 무시된다. 윈도우는 포그라운드를 /// 가로채는 것을 막으려고 "마지막 입력을 받은 프로세스"만 허용하는데, 우리 창은 /// WS_EX_NOACTIVATE 라 그 판정에서 밀리는 경우가 있다. 현재 포그라운드 창의 /// 입력 큐에 잠깐 붙었다 떼면 같은 스레드로 취급돼 통과한다. /// public static void ForceForeground(IntPtr hwnd) { if (hwnd == IntPtr.Zero) return; IntPtr foreground = GetForegroundWindow(); if (foreground == hwnd) { SetFocus(hwnd); return; } uint currentThread = GetCurrentThreadId(); uint foregroundThread = foreground != IntPtr.Zero ? GetWindowThreadProcessId(foreground, out _) : currentThread; bool attached = foregroundThread != 0 && foregroundThread != currentThread && AttachThreadInput(currentThread, foregroundThread, true); SetForegroundWindow(hwnd); SetFocus(hwnd); if (attached) AttachThreadInput(currentThread, foregroundThread, false); } } #endif