* 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 ?
This commit is contained in:
foudfou 2014-05-29 23:14:49 +02:00
parent 7aca24c88a
commit 0dd01042cc
5 changed files with 191 additions and 159 deletions

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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);
},

View File

@ -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();