Files
MyCharacterAgent/Assets/02_Scripts/Desktop/Win32.cs
2026-09-01 21:36:23 +09:00

154 lines
7.3 KiB
C#

#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
using System;
using System.Runtime.InteropServices;
/// <summary>
/// 데스크톱 오버레이에 필요한 Win32 선언 모음.
/// 선언 자체는 무해하며, 실제 호출부에서 에디터 여부를 가드한다.
/// </summary>
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);
/// <summary>DWMWA_CLOAKED 등 int 값을 받는 속성용.</summary>
[DllImport("dwmapi.dll")] public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out int pvAttribute, int cbAttribute);
/// <summary>DWMWA_EXTENDED_FRAME_BOUNDS 처럼 RECT 를 받는 속성용.</summary>
[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;
/// <summary>
/// 창을 확실히 활성화한다.
///
/// SetForegroundWindow 는 그냥 부르면 자주 무시된다. 윈도우는 포그라운드를
/// 가로채는 것을 막으려고 "마지막 입력을 받은 프로세스"만 허용하는데, 우리 창은
/// WS_EX_NOACTIVATE 라 그 판정에서 밀리는 경우가 있다. 현재 포그라운드 창의
/// 입력 큐에 잠깐 붙었다 떼면 같은 스레드로 취급돼 통과한다.
/// </summary>
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