/* * * * * * This is a collection of Win API helpers. Mainly dealing with window message hooks * and file drag&drop support for Windows standalone Unity applications. * * 2019.11.28 - Changed the "UnityDragAndDropHook" class to a static class. This * has been done for IL2CPP support. IL2CPP can not marshall instance method * callbacks passed to native code. So the callbacks must be static methods. * Therefore all fields involved also need to be static. * * The MIT License (MIT) * * Copyright (c) 2018 Markus Göbel (Bunny83) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * * * * */ using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace B83.Win32 { public enum HookType : int { WH_JOURNALRECORD = 0, WH_JOURNALPLAYBACK = 1, WH_KEYBOARD = 2, WH_GETMESSAGE = 3, WH_CALLWNDPROC = 4, WH_CBT = 5, WH_SYSMSGFILTER = 6, WH_MOUSE = 7, WH_HARDWARE = 8, WH_DEBUG = 9, WH_SHELL = 10, WH_FOREGROUNDIDLE = 11, WH_CALLWNDPROCRET = 12, WH_KEYBOARD_LL = 13, WH_MOUSE_LL = 14 } // windows messages public enum WM : uint { NULL = 0x0000, CREATE = 0x0001, DESTROY = 0x0002, MOVE = 0x0003, SIZE = 0x0005, ACTIVATE = 0x0006, SETFOCUS = 0x0007, KILLFOCUS = 0x0008, ENABLE = 0x000A, SETREDRAW = 0x000B, SETTEXT = 0x000C, GETTEXT = 0x000D, GETTEXTLENGTH = 0x000E, PAINT = 0x000F, CLOSE = 0x0010, QUERYENDSESSION = 0x0011, QUERYOPEN = 0x0013, ENDSESSION = 0x0016, QUIT = 0x0012, ERASEBKGND = 0x0014, SYSCOLORCHANGE = 0x0015, SHOWWINDOW = 0x0018, WININICHANGE = 0x001A, SETTINGCHANGE = WININICHANGE, DEVMODECHANGE = 0x001B, ACTIVATEAPP = 0x001C, FONTCHANGE = 0x001D, TIMECHANGE = 0x001E, CANCELMODE = 0x001F, SETCURSOR = 0x0020, MOUSEACTIVATE = 0x0021, CHILDACTIVATE = 0x0022, QUEUESYNC = 0x0023, GETMINMAXINFO = 0x0024, PAINTICON = 0x0026, ICONERASEBKGND = 0x0027, NEXTDLGCTL = 0x0028, SPOOLERSTATUS = 0x002A, DRAWITEM = 0x002B, MEASUREITEM = 0x002C, DELETEITEM = 0x002D, VKEYTOITEM = 0x002E, CHARTOITEM = 0x002F, SETFONT = 0x0030, GETFONT = 0x0031, SETHOTKEY = 0x0032, GETHOTKEY = 0x0033, QUERYDRAGICON = 0x0037, COMPAREITEM = 0x0039, GETOBJECT = 0x003D, COMPACTING = 0x0041, [Obsolete] COMMNOTIFY = 0x0044, WINDOWPOSCHANGING = 0x0046, WINDOWPOSCHANGED = 0x0047, [Obsolete] POWER = 0x0048, COPYDATA = 0x004A, CANCELJOURNAL = 0x004B, NOTIFY = 0x004E, INPUTLANGCHANGEREQUEST = 0x0050, INPUTLANGCHANGE = 0x0051, TCARD = 0x0052, HELP = 0x0053, USERCHANGED = 0x0054, NOTIFYFORMAT = 0x0055, CONTEXTMENU = 0x007B, STYLECHANGING = 0x007C, STYLECHANGED = 0x007D, DISPLAYCHANGE = 0x007E, GETICON = 0x007F, SETICON = 0x0080, NCCREATE = 0x0081, NCDESTROY = 0x0082, NCCALCSIZE = 0x0083, NCHITTEST = 0x0084, NCPAINT = 0x0085, NCACTIVATE = 0x0086, GETDLGCODE = 0x0087, SYNCPAINT = 0x0088, NCMOUSEMOVE = 0x00A0, NCLBUTTONDOWN = 0x00A1, NCLBUTTONUP = 0x00A2, NCLBUTTONDBLCLK = 0x00A3, NCRBUTTONDOWN = 0x00A4, NCRBUTTONUP = 0x00A5, NCRBUTTONDBLCLK = 0x00A6, NCMBUTTONDOWN = 0x00A7, NCMBUTTONUP = 0x00A8, NCMBUTTONDBLCLK = 0x00A9, NCXBUTTONDOWN = 0x00AB, NCXBUTTONUP = 0x00AC, NCXBUTTONDBLCLK = 0x00AD, INPUT_DEVICE_CHANGE = 0x00FE, INPUT = 0x00FF, KEYFIRST = 0x0100, KEYDOWN = 0x0100, KEYUP = 0x0101, CHAR = 0x0102, DEADCHAR = 0x0103, SYSKEYDOWN = 0x0104, SYSKEYUP = 0x0105, SYSCHAR = 0x0106, SYSDEADCHAR = 0x0107, UNICHAR = 0x0109, KEYLAST = 0x0108, IME_STARTCOMPOSITION = 0x010D, IME_ENDCOMPOSITION = 0x010E, IME_COMPOSITION = 0x010F, IME_KEYLAST = 0x010F, INITDIALOG = 0x0110, COMMAND = 0x0111, SYSCOMMAND = 0x0112, TIMER = 0x0113, HSCROLL = 0x0114, VSCROLL = 0x0115, INITMENU = 0x0116, INITMENUPOPUP = 0x0117, MENUSELECT = 0x011F, MENUCHAR = 0x0120, ENTERIDLE = 0x0121, MENURBUTTONUP = 0x0122, MENUDRAG = 0x0123, MENUGETOBJECT = 0x0124, UNINITMENUPOPUP = 0x0125, MENUCOMMAND = 0x0126, CHANGEUISTATE = 0x0127, UPDATEUISTATE = 0x0128, QUERYUISTATE = 0x0129, CTLCOLORMSGBOX = 0x0132, CTLCOLOREDIT = 0x0133, CTLCOLORLISTBOX = 0x0134, CTLCOLORBTN = 0x0135, CTLCOLORDLG = 0x0136, CTLCOLORSCROLLBAR = 0x0137, CTLCOLORSTATIC = 0x0138, MOUSEFIRST = 0x0200, MOUSEMOVE = 0x0200, LBUTTONDOWN = 0x0201, LBUTTONUP = 0x0202, LBUTTONDBLCLK = 0x0203, RBUTTONDOWN = 0x0204, RBUTTONUP = 0x0205, RBUTTONDBLCLK = 0x0206, MBUTTONDOWN = 0x0207, MBUTTONUP = 0x0208, MBUTTONDBLCLK = 0x0209, MOUSEWHEEL = 0x020A, XBUTTONDOWN = 0x020B, XBUTTONUP = 0x020C, XBUTTONDBLCLK = 0x020D, MOUSEHWHEEL = 0x020E, MOUSELAST = 0x020E, PARENTNOTIFY = 0x0210, ENTERMENULOOP = 0x0211, EXITMENULOOP = 0x0212, NEXTMENU = 0x0213, SIZING = 0x0214, CAPTURECHANGED = 0x0215, MOVING = 0x0216, POWERBROADCAST = 0x0218, DEVICECHANGE = 0x0219, MDICREATE = 0x0220, MDIDESTROY = 0x0221, MDIACTIVATE = 0x0222, MDIRESTORE = 0x0223, MDINEXT = 0x0224, MDIMAXIMIZE = 0x0225, MDITILE = 0x0226, MDICASCADE = 0x0227, MDIICONARRANGE = 0x0228, MDIGETACTIVE = 0x0229, MDISETMENU = 0x0230, ENTERSIZEMOVE = 0x0231, EXITSIZEMOVE = 0x0232, DROPFILES = 0x0233, MDIREFRESHMENU = 0x0234, IME_SETCONTEXT = 0x0281, IME_NOTIFY = 0x0282, IME_CONTROL = 0x0283, IME_COMPOSITIONFULL = 0x0284, IME_SELECT = 0x0285, IME_CHAR = 0x0286, IME_REQUEST = 0x0288, IME_KEYDOWN = 0x0290, IME_KEYUP = 0x0291, MOUSEHOVER = 0x02A1, MOUSELEAVE = 0x02A3, NCMOUSEHOVER = 0x02A0, NCMOUSELEAVE = 0x02A2, WTSSESSION_CHANGE = 0x02B1, TABLET_FIRST = 0x02c0, TABLET_LAST = 0x02df, CUT = 0x0300, COPY = 0x0301, PASTE = 0x0302, CLEAR = 0x0303, UNDO = 0x0304, RENDERFORMAT = 0x0305, RENDERALLFORMATS = 0x0306, DESTROYCLIPBOARD = 0x0307, DRAWCLIPBOARD = 0x0308, PAINTCLIPBOARD = 0x0309, VSCROLLCLIPBOARD = 0x030A, SIZECLIPBOARD = 0x030B, ASKCBFORMATNAME = 0x030C, CHANGECBCHAIN = 0x030D, HSCROLLCLIPBOARD = 0x030E, QUERYNEWPALETTE = 0x030F, PALETTEISCHANGING = 0x0310, PALETTECHANGED = 0x0311, HOTKEY = 0x0312, PRINT = 0x0317, PRINTCLIENT = 0x0318, APPCOMMAND = 0x0319, THEMECHANGED = 0x031A, CLIPBOARDUPDATE = 0x031D, DWMCOMPOSITIONCHANGED = 0x031E, DWMNCRENDERINGCHANGED = 0x031F, DWMCOLORIZATIONCOLORCHANGED = 0x0320, DWMWINDOWMAXIMIZEDCHANGE = 0x0321, GETTITLEBARINFOEX = 0x033F, HANDHELDFIRST = 0x0358, HANDHELDLAST = 0x035F, AFXFIRST = 0x0360, AFXLAST = 0x037F, PENWINFIRST = 0x0380, PENWINLAST = 0x038F, APP = 0x8000, USER = 0x0400, CPL_LAUNCH = USER + 0x1000, CPL_LAUNCHED = USER + 0x1001, SYSTIMER = 0x118, } // WH_CALLWNDPROC [StructLayout(LayoutKind.Sequential)] public struct CWPSTRUCT { public IntPtr lParam; public IntPtr wParam; public WM message; public IntPtr hwnd; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int x, y; public POINT(int aX, int aY) { x = aX; y = aY; } public override string ToString() { return "(" + x + ", " + y + ")"; } } //WH_GETMESSAGE [StructLayout(LayoutKind.Sequential)] public struct MSG { public IntPtr hwnd; public WM message; public IntPtr wParam; public IntPtr lParam; public ushort time; public POINT pt; } [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left, Top, Right, Bottom; public RECT(int left, int top, int right, int bottom) { Left = left; Top = top; Right = right; Bottom = bottom; } public override string ToString() { return "(" + Left + ", " + Top + ", " + Right + ", " + Bottom + ")"; } } public delegate IntPtr HookProc(int code, IntPtr wParam, ref MSG lParam); public delegate bool EnumThreadDelegate(IntPtr Hwnd, IntPtr lParam); #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN public static class Window { [DllImport("user32.dll")] public static extern bool EnumThreadWindows(uint dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern int GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount); public static string GetClassName(IntPtr hWnd) { var sb = new System.Text.StringBuilder(256); int count = GetClassName(hWnd, sb, 256); return sb.ToString(0, count); } [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern int GetWindowTextLength(IntPtr hWnd); [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount); public static string GetWindowText(IntPtr hWnd) { int length = GetWindowTextLength(hWnd) + 2; var sb = new System.Text.StringBuilder(length); int count = GetWindowText(hWnd, sb, length); return sb.ToString(0, count); } } public static class WinAPI { [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId(); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", SetLastError = true)] public static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll")] public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref MSG lParam); [DllImport("shell32.dll")] public static extern void DragAcceptFiles(IntPtr hwnd, bool fAccept); [DllImport("shell32.dll", CharSet = CharSet.Unicode)] public static extern uint DragQueryFile(IntPtr hDrop, uint iFile, System.Text.StringBuilder lpszFile, uint cch); [DllImport("shell32.dll")] public static extern void DragFinish(IntPtr hDrop); [DllImport("shell32.dll")] public static extern void DragQueryPoint(IntPtr hDrop, out POINT pos); } #endif public static class UnityDragAndDropHook { public delegate void DroppedFilesEvent(List aPathNames, POINT aDropPoint); #pragma warning disable CS0067 public static event DroppedFilesEvent OnDroppedFiles; #pragma warning restore CS0067 #if UNITY_STANDALONE_WIN && !UNITY_EDITOR_WIN private static uint threadId; private static IntPtr mainWindow = IntPtr.Zero; private static IntPtr m_Hook; private static string m_ClassName = "UnityWndClass"; // attribute required for IL2CPP, also has to be a static method [AOT.MonoPInvokeCallback(typeof(EnumThreadDelegate))] private static bool EnumCallback(IntPtr W, IntPtr _) { if (Window.IsWindowVisible(W) && (mainWindow == IntPtr.Zero || (m_ClassName != null && Window.GetClassName(W) == m_ClassName))) { mainWindow = W; } return true; } public static void InstallHook() { threadId = WinAPI.GetCurrentThreadId(); if (threadId > 0) Window.EnumThreadWindows(threadId, EnumCallback, IntPtr.Zero); var hModule = WinAPI.GetModuleHandle(null); m_Hook = WinAPI.SetWindowsHookEx(HookType.WH_GETMESSAGE, Callback, hModule, threadId); // Allow dragging of files onto the main window. generates the WM_DROPFILES message WinAPI.DragAcceptFiles(mainWindow, true); } public static void UninstallHook() { WinAPI.UnhookWindowsHookEx(m_Hook); WinAPI.DragAcceptFiles(mainWindow, false); m_Hook = IntPtr.Zero; } // attribute required for IL2CPP, also has to be a static method [AOT.MonoPInvokeCallback(typeof(HookProc))] private static IntPtr Callback(int code, IntPtr wParam, ref MSG lParam) { if (code == 0 && lParam.message == WM.DROPFILES) { POINT pos; WinAPI.DragQueryPoint(lParam.wParam, out pos); // 0xFFFFFFFF as index makes the method return the number of files uint n = WinAPI.DragQueryFile(lParam.wParam, 0xFFFFFFFF, null, 0); var sb = new System.Text.StringBuilder(1024); List result = new List(); for (uint i = 0; i < n; i++) { int len = (int)WinAPI.DragQueryFile(lParam.wParam, i, sb, 1024); result.Add(sb.ToString(0, len)); sb.Length = 0; } WinAPI.DragFinish(lParam.wParam); if (OnDroppedFiles != null) OnDroppedFiles(result, pos); } return WinAPI.CallNextHookEx(m_Hook, code, wParam, ref lParam); } #else public static void InstallHook() { } public static void UninstallHook() { } #endif } }