const EVENT_OBJECT_SHOW = 0x8002; const EVENT_OBJECT_HIDE = 0x8003; const WINEVENT_OUTOFCONTEXT = 0; const WINEVENT_SKIPOWNPROCESS = 2; const QS_ALLINPUT = 0x04FF; const INFINITE = 0xFFFFFFFF; const WAIT_OBJECT_0 = 0; const WAIT_TIMEOUT = 258; const PM_NOREMOVE = 0; function DialogWatcher(titleText, onDialogStart, onDialogEnd) { this.titleText = titleText; this.onDialogStart = onDialogStart; this.onDialogEnd = onDialogEnd; } DialogWatcher.prototype.init = function() { this.hwnd = undefined; if (!this.user32) { this.user32 = ctypes.open("user32.dll"); } if (!this.findWindow) { this.findWindow = user32.declare("FindWindowW", ctypes.winapi_abi, ctypes.uintptr_t, ctypes.char16_t.ptr, ctypes.char16_t.ptr); } if (!this.winEventProcType) { this.winEventProcType = ctypes.FunctionType(ctypes.stdcall_abi, ctypes.void_t, [ctypes.uintptr_t, ctypes.uint32_t, ctypes.uintptr_t, ctypes.long, ctypes.long, ctypes.uint32_t, ctypes.uint32_t]).ptr; } if (!this.setWinEventHook) { this.setWinEventHook = user32.declare("SetWinEventHook", ctypes.winapi_abi, ctypes.uintptr_t, ctypes.uint32_t, ctypes.uint32_t, ctypes.uintptr_t, this.winEventProcType, ctypes.uint32_t, ctypes.uint32_t, ctypes.uint32_t); } if (!this.unhookWinEvent) { this.unhookWinEvent = user32.declare("UnhookWinEvent", ctypes.winapi_abi, ctypes.int, ctypes.uintptr_t); } if (!this.pointType) { this.pointType = ctypes.StructType("tagPOINT", [ { "x": ctypes.long }, { "y": ctypes.long } ] ); } if (!this.msgType) { this.msgType = ctypes.StructType("tagMSG", [ { "hwnd": ctypes.uintptr_t }, { "message": ctypes.uint32_t }, { "wParam": ctypes.uintptr_t }, { "lParam": ctypes.intptr_t }, { "time": ctypes.uint32_t }, { "pt": this.pointType } ] ); } if (!this.peekMessage) { this.peekMessage = user32.declare("PeekMessageW", ctypes.winapi_abi, ctypes.int, this.msgType.ptr, ctypes.uintptr_t, ctypes.uint32_t, ctypes.uint32_t, ctypes.uint32_t); } if (!this.msgWaitForMultipleObjects) { this.msgWaitForMultipleObjects = user32.declare("MsgWaitForMultipleObjects", ctypes.winapi_abi, ctypes.uint32_t, ctypes.uint32_t, ctypes.uintptr_t.ptr, ctypes.int, ctypes.uint32_t, ctypes.uint32_t); } if (!this.getWindowTextW) { this.getWindowTextW = user32.declare("GetWindowTextW", ctypes.winapi_abi, ctypes.int, ctypes.uintptr_t, ctypes.char16_t.ptr, ctypes.int); } if (!this.messageBox) { // Handy for debugging this code this.messageBox = user32.declare("MessageBoxW", ctypes.winapi_abi, ctypes.int, ctypes.uintptr_t, ctypes.char16_t.ptr, ctypes.char16_t.ptr, ctypes.uint32_t); } }; DialogWatcher.prototype.getWindowText = function(hwnd) { var bufType = ctypes.ArrayType(ctypes.char16_t); var buffer = new bufType(256); if (this.getWindowTextW(hwnd, buffer, buffer.length)) { return buffer.readString(); } }; DialogWatcher.prototype.processWindowEvents = function(timeout) { var onWinEvent = function(self, hook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) { var nhwnd = Number(hwnd) if (event == EVENT_OBJECT_SHOW) { if (nhwnd == self.hwnd) { // We've already picked up this event via FindWindow return; } var windowText = self.getWindowText(hwnd); if (windowText == self.titleText && self.onDialogStart) { self.hwnd = nhwnd; self.onDialogStart(nhwnd); } } else if (event == EVENT_OBJECT_HIDE && nhwnd == self.hwnd && self.onDialogEnd) { self.onDialogEnd(); self.hwnd = null; } }; var self = this; var callback = this.winEventProcType(function(hook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) { onWinEvent(self, hook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime); } ); var hook = this.setWinEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE, 0, callback, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); if (!hook) { return; } // Check if the window is already showing var hwnd = this.findWindow(null, this.titleText); if (hwnd && hwnd > 0) { this.hwnd = Number(hwnd); if (this.onDialogStart) { this.onDialogStart(this.hwnd); } } if (!timeout) { timeout = INFINITE; } var waitStatus = WAIT_OBJECT_0; var expectingStart = this.onDialogStart && this.hwnd === undefined; var startWaitTime = Date.now(); while (this.hwnd === undefined || this.onDialogEnd && this.hwnd) { waitStatus = this.msgWaitForMultipleObjects(0, null, 0, expectingStart ? INFINITE : timeout, 0); if (waitStatus == WAIT_OBJECT_0) { var msg = new this.msgType; this.peekMessage(msg.address(), 0, 0, 0, PM_NOREMOVE); } if (waitStatus == WAIT_TIMEOUT || (Date.now() - startWaitTime) >= timeout) { break; } } this.unhookWinEvent(hook); // Returns true if the hook was successful, something was found, and we never timed out return this.hwnd !== undefined && waitStatus == WAIT_OBJECT_0; };