From 0dd01042ccf298f5dd507c5151923d605ab5319e Mon Sep 17 00:00:00 2001 From: foudfou Date: Thu, 29 May 2014 23:14:49 +0200 Subject: [PATCH] * Improve start_hidden on winnt: window not shown at all. * Fix minimize on WM_SYSCOMMAND. Before this improvement, we noticed windows showed at startup despite start_hidden, shortly after XP boot up. Was this due to the use of hooks ? --- src/modules/ctypes/ctypes-utils.jsm | 1 + src/modules/ctypes/winnt/user32.jsm | 41 +++++- src/modules/ctypes/winnt/win32.jsm | 119 ++++++++-------- src/modules/winnt/FiretrayPopupMenu.jsm | 9 +- src/modules/winnt/FiretrayWindow.jsm | 180 +++++++++++------------- 5 files changed, 191 insertions(+), 159 deletions(-) diff --git a/src/modules/ctypes/ctypes-utils.jsm b/src/modules/ctypes/ctypes-utils.jsm index b9a8c66..4142aaf 100644 --- a/src/modules/ctypes/ctypes-utils.jsm +++ b/src/modules/ctypes/ctypes-utils.jsm @@ -50,6 +50,7 @@ const WinABI = is64bit ? ctypes.default_abi : ctypes.winapi_abi; const WinCbABI = is64bit ? ctypes.default_abi : ctypes.stdcall_abi; let log = firetray.Logging.getLogger("firetray.ctypes-utils"); +log.info("is64bit="+is64bit); /** * Loads a library using ctypes and exports an object on to the specified diff --git a/src/modules/ctypes/winnt/user32.jsm b/src/modules/ctypes/winnt/user32.jsm index 103a68f..054f262 100644 --- a/src/modules/ctypes/winnt/user32.jsm +++ b/src/modules/ctypes/winnt/user32.jsm @@ -32,6 +32,7 @@ function user32_defines(lib) { lib.lazy_bind("GetWindowTextW", ctypes.int, win32.HWND, win32.LPTSTR, ctypes.int); lib.lazy_bind("FindWindowW", win32.HWND, win32.LPCTSTR, win32.LPCTSTR); + lib.lazy_bind("PostMessageW", win32.BOOL, win32.HWND, win32.UINT, win32.WPARAM, win32.LPARAM); lib.lazy_bind("SendMessageW", win32.LRESULT, win32.HWND, win32.UINT, win32.WPARAM, win32.LPARAM); this.WM_GETICON = 0x007F; this.WM_SETICON = 0x0080; @@ -95,8 +96,7 @@ function user32_defines(lib) { WinCbABI, win32.LRESULT, [win32.HWND, win32.UINT, win32.WPARAM, win32.LPARAM]).ptr; - // lib.lazy_bind("CallWindowProcW", win32.LRESULT, this.WNDPROC, win32.HWND, win32.UINT, win32.WPARAM, win32.LPARAM); - lib.lazy_bind("CallWindowProcW", win32.LRESULT, ctypes.voidptr_t, win32.HWND, win32.UINT, win32.WPARAM, win32.LPARAM); + lib.lazy_bind("CallWindowProcW", win32.LRESULT, this.WNDPROC, win32.HWND, win32.UINT, win32.WPARAM, win32.LPARAM); lib.lazy_bind("DefWindowProcW", win32.LRESULT, win32.HWND, win32.UINT, win32.WPARAM, win32.LPARAM); this.WNDCLASSEXW = ctypes.StructType("WNDCLASSEXW", [ @@ -186,6 +186,16 @@ function user32_defines(lib) { { "hwnd": win32.HWND } ]); + this.MSG = ctypes.StructType("MSG", [ + { "hwnd": win32.HWND }, + { "message": win32.UINT }, + { "wParam": win32.WPARAM }, + { "lParam": win32.LPARAM }, + { "time": win32.DWORD }, + { "pt": win32.POINT } + ]); + this.PMSG = this.MSG.ptr; + this.HOOKPROC = ctypes.FunctionType( WinCbABI, win32.LRESULT, [ctypes.int, win32.WPARAM, win32.LPARAM]).ptr; @@ -375,6 +385,33 @@ function user32_defines(lib) { lib.lazy_bind("GetWindowPlacement", win32.BOOL, win32.HWND, this.WINDOWPLACEMENT.ptr); lib.lazy_bind("SetWindowPlacement", win32.BOOL, win32.HWND, this.WINDOWPLACEMENT.ptr); + this.WINDOWPOS = ctypes.StructType("WINDOWPOS", [ + { "hwnd": win32.HWND }, + { "hwndInsertAfter": win32.HWND }, + { "x": ctypes.int }, + { "y": ctypes.int }, + { "cx": ctypes.int }, + { "cy": ctypes.int }, + { "flags": win32.UINT } + ]); + this.PWINDOWPOS = this.WINDOWPOS; + + this.SWP_NOSIZE = 0x0001; + this.SWP_NOMOVE = 0x0002; + this.SWP_NOZORDER = 0x0004; + this.SWP_NOREDRAW = 0x0008; + this.SWP_NOACTIVATE = 0x0010; + this.SWP_FRAMECHANGED = 0x0020; /* The frame changed: send WM_NCCALCSIZ= */ + this.SWP_SHOWWINDOW = 0x0040; + this.SWP_HIDEWINDOW = 0x0080; + this.SWP_NOCOPYBITS = 0x0100; + this.SWP_NOOWNERZORDER = 0x0200; /* Don't do owner Z orderin= */ + this.SWP_NOSENDCHANGING = 0x0400; /* Don't send WM_WINDOWPOSCHANGIN= */ + this.SWP_DRAWFRAME = this.SWP_FRAMECHANGED; + this.SWP_NOREPOSITION = this.SWP_NOOWNERZORDER; + this.SWP_DEFERERASE = 0x2000; + this.SWP_ASYNCWINDOWPOS = 0x4000; + } new ctypes_library(USER32_LIBNAME, USER32_ABIS, user32_defines, this); diff --git a/src/modules/ctypes/winnt/win32.jsm b/src/modules/ctypes/winnt/win32.jsm index 15b57d6..ca5e17b 100644 --- a/src/modules/ctypes/winnt/win32.jsm +++ b/src/modules/ctypes/winnt/win32.jsm @@ -97,67 +97,70 @@ var win32 = new function() { this.ERROR_RESOURCE_TYPE_NOT_FOUND = 1813; // WinUser.h - this.WM_NULL = 0x0000; - this.WM_CREATE = 0x0001; - this.WM_DESTROY = 0x0002; - this.WM_MOVE = 0x0003; - this.WM_SIZE = 0x0005; - this.WM_ACTIVATE = 0x0006; - this.WA_INACTIVE = 0; - this.WA_ACTIVE = 1; - this.WA_CLICKACTIVE = 2; - this.WM_SETFOCUS = 0x0007; - this.WM_KILLFOCUS = 0x0008; - this.WM_ENABLE = 0x000A; - this.WM_SETREDRAW = 0x000B; - this.WM_SETTEXT = 0x000C; - this.WM_GETTEXT = 0x000D; - this.WM_GETTEXTLENGTH = 0x000E; - this.WM_PAINT = 0x000F; - this.WM_CLOSE = 0x0010; - this.WM_QUIT = 0x0012; - this.WM_ERASEBKGND = 0x0014; - this.WM_SYSCOLORCHANGE = 0x0015; - this.WM_SHOWWINDOW = 0x0018; - this.WM_WININICHANGE = 0x001A; - this.WM_SETTINGCHANGE = this.WM_WININICHANGE; - this.WM_DEVMODECHANGE = 0x001B; - this.WM_ACTIVATEAPP = 0x001C; - this.WM_FONTCHANGE = 0x001D; - this.WM_TIMECHANGE = 0x001E; - this.WM_CANCELMODE = 0x001F; - this.WM_SETCURSOR = 0x0020; - this.WM_MOUSEACTIVATE = 0x0021; - this.WM_CHILDACTIVATE = 0x0022; - this.WM_QUEUESYNC = 0x0023; - this.WM_COMMAND = 0x0111; - this.WM_SYSCOMMAND = 0x0112; - this.WM_HSCROLL = 0x0114; - this.WM_VSCROLL = 0x0115; - this.WM_MOUSEWHEEL = 0x020A; + this.WM_NULL = 0x0000; + this.WM_CREATE = 0x0001; + this.WM_DESTROY = 0x0002; + this.WM_MOVE = 0x0003; + this.WM_SIZE = 0x0005; + this.WM_ACTIVATE = 0x0006; + this.WA_INACTIVE = 0; + this.WA_ACTIVE = 1; + this.WA_CLICKACTIVE = 2; + this.WM_SETFOCUS = 0x0007; + this.WM_KILLFOCUS = 0x0008; + this.WM_ENABLE = 0x000A; + this.WM_SETREDRAW = 0x000B; + this.WM_SETTEXT = 0x000C; + this.WM_GETTEXT = 0x000D; + this.WM_GETTEXTLENGTH = 0x000E; + this.WM_PAINT = 0x000F; + this.WM_CLOSE = 0x0010; + this.WM_QUIT = 0x0012; + this.WM_ERASEBKGND = 0x0014; + this.WM_SYSCOLORCHANGE = 0x0015; + this.WM_SHOWWINDOW = 0x0018; + this.WM_WININICHANGE = 0x001A; + this.WM_SETTINGCHANGE = this.WM_WININICHANGE; + this.WM_DEVMODECHANGE = 0x001B; + this.WM_ACTIVATEAPP = 0x001C; + this.WM_FONTCHANGE = 0x001D; + this.WM_TIMECHANGE = 0x001E; + this.WM_CANCELMODE = 0x001F; + this.WM_SETCURSOR = 0x0020; + this.WM_MOUSEACTIVATE = 0x0021; + this.WM_CHILDACTIVATE = 0x0022; + this.WM_QUEUESYNC = 0x0023; + this.WM_WINDOWPOSCHANGING = 0x0046; + this.WM_WINDOWPOSCHANGED = 0x0047; + this.WM_INITDIALOG = 0x0110; + this.WM_COMMAND = 0x0111; + this.WM_SYSCOMMAND = 0x0112; + this.WM_HSCROLL = 0x0114; + this.WM_VSCROLL = 0x0115; + this.WM_MOUSEWHEEL = 0x020A; - this.WM_USER = 0x0400; - this.WM_APP = 0x8000; + this.WM_USER = 0x0400; + this.WM_APP = 0x8000; - this.WM_CONTEXTMENU = 0x007B; + this.WM_CONTEXTMENU = 0x007B; - this.WM_MOUSEFIRST = 0x0200; - this.WM_MOUSEMOVE = 0x0200; - this.WM_LBUTTONDOWN = 0x0201; - this.WM_LBUTTONUP = 0x0202; - this.WM_LBUTTONDBLCLK = 0x0203; - this.WM_RBUTTONDOWN = 0x0204; - this.WM_RBUTTONUP = 0x0205; - this.WM_RBUTTONDBLCLK = 0x0206; - this.WM_MBUTTONDOWN = 0x0207; - this.WM_MBUTTONUP = 0x0208; - this.WM_MBUTTONDBLCLK = 0x0209; - this.WM_MOUSEWHEEL = 0x020A; - this.WM_XBUTTONDOWN = 0x020B; - this.WM_XBUTTONUP = 0x020C; - this.WM_XBUTTONDBLCLK = 0x020D; - this.WM_MOUSELAST = 0x020D; - this.WM_MOUSELAST = 0x020A; + this.WM_MOUSEFIRST = 0x0200; + this.WM_MOUSEMOVE = 0x0200; + this.WM_LBUTTONDOWN = 0x0201; + this.WM_LBUTTONUP = 0x0202; + this.WM_LBUTTONDBLCLK = 0x0203; + this.WM_RBUTTONDOWN = 0x0204; + this.WM_RBUTTONUP = 0x0205; + this.WM_RBUTTONDBLCLK = 0x0206; + this.WM_MBUTTONDOWN = 0x0207; + this.WM_MBUTTONUP = 0x0208; + this.WM_MBUTTONDBLCLK = 0x0209; + this.WM_MOUSEWHEEL = 0x020A; + this.WM_XBUTTONDOWN = 0x020B; + this.WM_XBUTTONUP = 0x020C; + this.WM_XBUTTONDBLCLK = 0x020D; + this.WM_MOUSELAST = 0x020D; + this.WM_MOUSELAST = 0x020A; this.SC_MINIMIZE = 0xF020; this.SC_CLOSE = 0xF060; diff --git a/src/modules/winnt/FiretrayPopupMenu.jsm b/src/modules/winnt/FiretrayPopupMenu.jsm index 0b49b48..43113c4 100644 --- a/src/modules/winnt/FiretrayPopupMenu.jsm +++ b/src/modules/winnt/FiretrayPopupMenu.jsm @@ -104,9 +104,12 @@ firetray.PopupMenu = { 0, null); }, - // FIXME: need to handle hides_single_window=false - addWindowItemAndSeparatorMaybe: function(wid) { - if (firetray.Handler.visibleWindowsCount === firetray.Handler.windowsCount) + /** + * @param force: useful to start_hidden, when window is not shown + */ + addWindowItemAndSeparatorMaybe: function(wid, force) { + if (typeof force === "undefined") force = false; + if (force || firetray.Handler.visibleWindowsCount === firetray.Handler.windowsCount) this.insertSeparator(); this.addWindowItem(wid); }, diff --git a/src/modules/winnt/FiretrayWindow.jsm b/src/modules/winnt/FiretrayWindow.jsm index d4999c7..4ac20d4 100644 --- a/src/modules/winnt/FiretrayWindow.jsm +++ b/src/modules/winnt/FiretrayWindow.jsm @@ -26,10 +26,9 @@ const FIRETRAY_XWINDOW_MAXIMIZED = 1 << 1; // We need to keep long-living references to wndProcs callbacks. As they also // happen to be ctypes pointers, we store them into real ctypes arrays. -firetray.Handler.wndProcs = new ctypesMap(user32.WNDPROC); -firetray.Handler.wndProcsOrig = new ctypesMap(user32.WNDPROC); -firetray.Handler.procHooks = new ctypesMap(win32.HHOOK); -firetray.Handler.procHooksRegistred = new ctypesMap(win32.HHOOK); +firetray.Handler.wndProcs = new ctypesMap(win32.LONG_PTR); +firetray.Handler.wndProcsOrig = new ctypesMap(win32.LONG_PTR); +firetray.Handler.wndProcsStartup = new ctypesMap(win32.LONG_PTR); firetray.Window = new FiretrayWindow(); @@ -64,84 +63,69 @@ firetray.Window.wndProc = function(hWnd, uMsg, wParam, lParam) { // filterWindow log.debug("wndProc CALLED with WM_SYSCOMMAND wParam="+wParam); if (wParam === win32.SC_MINIMIZE) { log.debug("GOT ICONIFIED"); - if (firetray.Handler.hideOnMinimizeMaybe(wid)) { + if (firetray.Handler.onMinimize(wid)) { return 0; // processed => preventDefault } } } let procPrev = firetray.Handler.wndProcsOrig.get(wid); - return user32.CallWindowProcW(procPrev, hWnd, uMsg, wParam, lParam); // or DefWindowProcW + return user32.CallWindowProcW(user32.WNDPROC(procPrev), hWnd, uMsg, wParam, lParam); // or DefWindowProcW }; -// We could chain wndProcs, but adding a hook looks simpler. -firetray.Window.showCount = 0; -firetray.Window.startupHook = function(nCode, wParam, lParam) { // WH_CALLWNDPROC, WH_GETMESSAGE - // log.debug("startupHook CALLED: nCode="+nCode+", wParam="+wParam+", lParam="+lParam); - if (nCode < 0) return user32.CallNextHookEx(null, nCode, wParam, lParam); // user32.HC_ACTION +/* + * We get the best effect by intercepting WM_WINDOWPOSCHANGING/SWP_SHOWWINDOW. + * Here, we subclass only once either with a startup wndProc, if + * start_hidden, or just our default wndProc. None of the following works: + * - a WH_CALLWNDPROC hook doesn't catch SWP_SHOWWINDOW + * - chaining WNDPROCs crashes the app (UserCallWinProcCheckWow or ffi_call) + */ +firetray.Window.startupShowCount = 0; +firetray.Window.wndProcStartup = function(hWnd, uMsg, wParam, lParam) { + let wid = firetray.Win32.hwndToHexStr(hWnd); - let cwpstruct = ctypes.cast(win32.LPARAM(lParam), user32.CWPSTRUCT.ptr).contents; - let uMsg = cwpstruct.message; - let hwnd = cwpstruct.hwnd; - let wid = firetray.Win32.hwndToHexStr(hwnd); - let wparam = cwpstruct.wParam; - let lparam = cwpstruct.lParam; + if (uMsg === win32.WM_WINDOWPOSCHANGING) { + let posStruct = ctypes.cast(win32.LPARAM(lParam), user32.WINDOWPOS.ptr).contents; + let isShowing = ((posStruct.flags & user32.SWP_SHOWWINDOW) != 0); + if (isShowing) { + log.debug("wndProcStartup CALLED with WM_WINDOWPOSCHANGING/SWP_SHOWWINDOW"); + firetray.Window.startupShowCount += 1; - if (uMsg === win32.WM_SHOWWINDOW && wparam == 1 && lparam == 0) { // shown and ShowWindow called - log.debug("startupHook CALLED with WM_SHOWWINDOW wparam="+wparam+" lparam="+lparam); - firetray.Window.showCount += 1; - - if (firetray.Utils.prefService.getBoolPref('start_hidden')) { - log.debug("start_hidden"); - - /* Compared to ShowWindow, SetWindowPlacement seems to bypass window - animations. http://stackoverflow.com/a/6087214 */ - let placement = new user32.WINDOWPLACEMENT; - let ret = user32.GetWindowPlacement(hwnd, placement.address()); - log.debug(" GetWindowPlacement="+ret+" winLastError="+ctypes.winLastError); - log.debug(" PLACEMENT="+placement); - - if (firetray.Window.showCount < 2) { - // we can't prevent ShowWindow, so we mitigate the effect by minimizing - // it before. This is why we'll have to restore it when unhidden. - placement.showCmd = user32.SW_SHOWMINNOACTIVE; - ret = user32.SetWindowPlacement(hwnd, placement.address()); - log.debug(" SetWindowPlacement="+ret+" winLastError="+ctypes.winLastError); - - firetray.Utils.timer( - FIRETRAY_DELAY_NOWAIT_MILLISECONDS, - Ci.nsITimer.TYPE_ONE_SHOT, function(){firetray.Handler.hideWindow(wid);} - ); // looks like CData (hwnd) cannot be closured - - } else { // restore - firetray.Window.detachHook(wid); - - placement.showCmd = user32.SW_RESTORE; - user32.SetWindowPlacement(hwnd, placement.address()); + if (firetray.Window.startupShowCount < 2) { // hide + log.debug("start_hidden"); + // Modifying a JS posStruct field does really modify the WINDOWPOS C + // struct behind lParam ! + posStruct.flags &= ~user32.SWP_SHOWWINDOW; + let force = true; + firetray.Handler.addPopupMenuWindowItemAndSeparatorMaybe(wid, force); + } + else { // restore + firetray.Window.attachWndProc({ + wid: wid, hwnd: hWnd, + jsProc: firetray.Window.wndProc, + mapNew: firetray.Handler.wndProcs, + mapBak: null + }); + firetray.Handler.wndProcsStartup.remove(wid); } - - } else { - firetray.Window.detachHook(wid); } - } - return user32.CallNextHookEx(null, nCode, wParam, lParam); + let procPrev = firetray.Handler.wndProcsOrig.get(wid); + return user32.CallWindowProcW(user32.WNDPROC(procPrev), hWnd, uMsg, wParam, lParam); }; -firetray.Window.attachWndProc = function(wid, hwnd) { +// procInfo = {wid, hwnd, jsProc, mapNew, mapBak} +firetray.Window.attachWndProc = function(procInfo) { try { - let wndProc = user32.WNDPROC(firetray.Window.wndProc); + let wndProc = ctypes.cast(user32.WNDPROC(procInfo.jsProc), win32.LONG_PTR); log.debug("proc="+wndProc); - firetray.Handler.wndProcs.insert(wid, wndProc); - let procPrev = user32.WNDPROC( - user32.SetWindowLongW(hwnd, user32.GWLP_WNDPROC, - ctypes.cast(wndProc, win32.LONG_PTR)) - ); + procInfo.mapNew.insert(procInfo.wid, wndProc); + let procPrev = user32.SetWindowLongW(procInfo.hwnd, user32.GWLP_WNDPROC, wndProc); log.debug("procPrev="+procPrev+" winLastError="+ctypes.winLastError); /* we can't store WNDPROC callbacks (JS ctypes objects) with SetPropW(), as we need long-living refs. */ - firetray.Handler.wndProcsOrig.insert(wid, procPrev); + if (procInfo.mapBak) procInfo.mapBak.insert(procInfo.wid, procPrev); } catch (x) { if (x.name === "RangeError") { // instanceof not working :-( @@ -157,40 +141,17 @@ firetray.Window.attachWndProc = function(wid, hwnd) { } }; -firetray.Window.detachWndProc = function(wid) { - let procPrev = firetray.Handler.wndProcsOrig.get(wid); +// procInfo = {wid, mapNew, mapBak} +firetray.Window.detachWndProc = function(procInfo) { + let wid = procInfo.wid; + let procPrev = procInfo.mapBak.get(wid); let hwnd = firetray.Win32.hexStrToHwnd(wid); log.debug("hwnd="+hwnd); - let proc = user32.WNDPROC( - user32.SetWindowLongW(hwnd, user32.GWLP_WNDPROC, - ctypes.cast(procPrev, win32.LONG_PTR)) - ); - firetray.js.assert(firetray.js.strEquals(proc, firetray.Handler.wndProcs.get(wid)), + let proc = user32.SetWindowLongW(hwnd, user32.GWLP_WNDPROC, procPrev); + firetray.js.assert(firetray.js.strEquals(proc, procInfo.mapNew.get(wid)), "Wrong WndProc replaced."); - firetray.Handler.wndProcs.remove(wid); - firetray.Handler.wndProcsOrig.remove(wid); -}; - -firetray.Window.attachHook = function(wid) { // detaches itself alone - let startupHook = user32.HOOKPROC(firetray.Window.startupHook); - log.debug("callhk="+startupHook); - firetray.Handler.procHooks.insert(wid, startupHook); - // Global hooks must reside in a dll (hence hInst). This is important for - // the scope of variables. - let hhook = user32.SetWindowsHookExW( - user32.WH_CALLWNDPROC, startupHook, null, kernel32.GetCurrentThreadId()); - log.debug(" hhook="+hhook+" winLastError="+ctypes.winLastError); - firetray.Handler.procHooksRegistred.insert(wid, hhook); -}; - -firetray.Window.detachHook = function(wid) { // detaches itself alone - let hook = firetray.Handler.procHooksRegistred.get(wid); - if (!user32.UnhookWindowsHookEx(hook)) { - log.error("UnhookWindowsHookEx for window "+wid+" failed: winLastError="+ctypes.winLastError); - return; - } - firetray.Handler.procHooks.remove(wid); - firetray.Handler.procHooksRegistred.remove(wid); + procInfo.mapNew.remove(wid); + procInfo.mapBak.remove(wid); }; @@ -230,10 +191,19 @@ firetray.Handler.registerWindow = function(win) { }); log.debug("window "+wid+" registered"); - firetray.Window.attachWndProc(wid, hwnd); - if (!firetray.Handler.appStarted) { - firetray.Window.attachHook(wid); + let proc, map; + if (!firetray.Handler.appStarted && + firetray.Utils.prefService.getBoolPref('start_hidden')) { + proc = firetray.Window.wndProcStartup; map = firetray.Handler.wndProcsStartup; + } else { + proc = firetray.Window.wndProc; map = firetray.Handler.wndProcs; } + firetray.Window.attachWndProc({ + wid: wid, hwnd: hwnd, + jsProc: proc, + mapNew: map, + mapBak: firetray.Handler.wndProcsOrig + }); firetray.Win32.acceptAllMessages(hwnd); @@ -250,7 +220,25 @@ firetray.Handler.unregisterWindow = function(win) { return false; } - firetray.Window.detachWndProc(wid); + try { + firetray.Window.detachWndProc({ + wid: wid, + mapNew: firetray.Handler.wndProcsStartup, + mapBak: firetray.Handler.wndProcsOrig + }); + log.debug("Window never shown."); + } catch (x) { + if (x.name === "RangeError") { + firetray.Window.detachWndProc({ + wid: wid, + mapNew: firetray.Handler.wndProcs, + mapBak: firetray.Handler.wndProcsOrig + }); + } else { + log.error(x); + Cu.reportError(x); + } + } if (!delete firetray.Handler.windows[wid]) throw new DeleteError();