/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ var EXPORTED_SYMBOLS = [ "firetray" ]; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/ctypes.jsm"); Cu.import("resource://firetray/ctypes/ctypesMap.jsm"); Cu.import("resource://firetray/ctypes/winnt/kernel32.jsm"); Cu.import("resource://firetray/ctypes/winnt/user32.jsm"); Cu.import("resource://firetray/ctypes/winnt/win32.jsm"); Cu.import("resource://firetray/winnt/FiretrayWin32.jsm"); Cu.import("resource://firetray/FiretrayWindow.jsm"); Cu.import("resource://firetray/commons.js"); firetray.Handler.subscribeLibsForClosing([user32, kernel32]); let log = firetray.Logging.getLogger("firetray.Window"); if ("undefined" == typeof(firetray.Handler)) log.error("This module MUST be imported from/after FiretrayHandler !"); const FIRETRAY_XWINDOW_HIDDEN = 1 << 0; // when minimized also 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(win32.LONG_PTR); firetray.Handler.wndProcsOrig = new ctypesMap(win32.LONG_PTR); firetray.Handler.wndProcsStartup = new ctypesMap(win32.LONG_PTR); firetray.Window = new FiretrayWindow(); firetray.Window.init = function() { this.initialized = true; }; firetray.Window.shutdown = function() { this.initialized = false; }; firetray.Window.getVisibility = function(wid) { let hwnd = firetray.Win32.hexStrToHwnd(wid); let style = user32.GetWindowLongW(hwnd, user32.GWL_STYLE); return ((style & user32.WS_VISIBLE) != 0); // user32.IsWindowVisible(hwnd); }; // firetray.Window.{show,hide} useless as we don't need to restore position and size firetray.Window.setVisibility = function(wid, visible) { log.debug("setVisibility="+visible); let hwnd = firetray.Win32.hexStrToHwnd(wid); let ret = user32.ShowWindow(hwnd, visible ? user32.SW_SHOW : user32.SW_HIDE); log.debug(" ShowWindow="+ret+" winLastError="+ctypes.winLastError); }; firetray.Window.wndProc = function(hWnd, uMsg, wParam, lParam) { // filterWindow // log.debug("wndProc CALLED: hWnd="+hWnd+", uMsg=0x"+uMsg.toString(16)+", wParam="+wParam+", lParam="+lParam); let wid = firetray.Win32.hwndToHexStr(hWnd); if (uMsg === win32.WM_SYSCOMMAND) { log.debug("wndProc CALLED with WM_SYSCOMMAND wParam="+wParam); if (wParam === win32.SC_MINIMIZE) { log.debug("GOT ICONIFIED"); if (firetray.Handler.onMinimize(wid)) { return 0; // processed => preventDefault } } } let procPrev = firetray.Handler.wndProcsOrig.get(wid); return user32.CallWindowProcW(user32.WNDPROC(procPrev), hWnd, uMsg, wParam, lParam); // or DefWindowProcW }; /* * 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); 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 (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); } } } let procPrev = firetray.Handler.wndProcsOrig.get(wid); return user32.CallWindowProcW(user32.WNDPROC(procPrev), hWnd, uMsg, wParam, lParam); }; // procInfo = {wid, hwnd, jsProc, mapNew, mapBak} firetray.Window.attachWndProc = function(procInfo) { try { let wndProc = ctypes.cast(user32.WNDPROC(procInfo.jsProc), win32.LONG_PTR); log.debug("proc="+wndProc); 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. */ if (procInfo.mapBak) procInfo.mapBak.insert(procInfo.wid, procPrev); } catch (x) { if (x.name === "RangeError") { // instanceof not working :-( let msg = x+"\n\nYou seem to have more than "+FIRETRAY_WINDOW_COUNT_MAX +" windows open. This breaks FireTray and most probably " +firetray.Handler.appName+"."; log.error(msg); Cu.reportError(msg); }else { log.error(x); Cu.reportError(x); } } }; // 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.SetWindowLongW(hwnd, user32.GWLP_WNDPROC, procPrev); firetray.js.assert(firetray.js.strEquals(proc, procInfo.mapNew.get(wid)), "Wrong WndProc replaced."); procInfo.mapNew.remove(wid); procInfo.mapBak.remove(wid); }; ///////////////////////// firetray.Handler overriding ///////////////////////// /** debug facility */ firetray.Handler.dumpWindows = function() { let dumpStr = ""+firetray.Handler.windowsCount; for (let wid in firetray.Handler.windows) { dumpStr += " "+wid; } log.info(dumpStr); }; firetray.Handler.registerWindow = function(win) { log.debug("register window"); let baseWin = firetray.Handler.getWindowInterface(win, "nsIBaseWindow"); let nativeHandle = baseWin.nativeHandle; let hwnd, wid; if (nativeHandle) { hwnd = firetray.Win32.hexStrToHwnd(nativeHandle); wid = nativeHandle; } else { hwnd = user32.FindWindowW("MozillaWindowClass", win.document.title); wid = firetray.Win32.hwndToHexStr(hwnd); } log.debug("=== hwnd="+hwnd+" wid="+wid+" win.document.title: "+win.document.title); if (this.windows.hasOwnProperty(wid)) { let msg = "Window ("+wid+") already registered."; log.error(msg); Cu.reportError(msg); return false; } this.windows[wid] = {}; this.windows[wid].chromeWin = win; this.windows[wid].baseWin = baseWin; Object.defineProperties(this.windows[wid], { "visible": { get: function(){return firetray.Window.getVisibility(wid);} } }); log.debug("window "+wid+" registered"); 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); log.debug("AFTER"); firetray.Handler.dumpWindows(); return wid; }; firetray.Handler.unregisterWindow = function(win) { log.debug("unregister window"); let wid = firetray.Window.getRegisteredWinIdFromChromeWindow(win); if (!firetray.Handler.windows.hasOwnProperty(wid)) { log.error("can't unregister unknown window "+wid); return false; } 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(); firetray.Handler.dumpWindows(); log.debug("window "+wid+" unregistered"); return true; }; firetray.Handler.showWindow = function(wid) { firetray.Handler.removePopupMenuWindowItemAndSeparatorMaybe(wid); return firetray.Window.setVisibility(wid, true); }; firetray.Handler.hideWindow = function(wid) { firetray.Handler.addPopupMenuWindowItemAndSeparatorMaybe(wid); return firetray.Window.setVisibility(wid, false); }; firetray.Handler.windowGetAttention = function(wid) { // see nsWindow.cpp for (var first in this.windows) break; wid = wid || first; let hwnd = firetray.Win32.hexStrToHwnd(wid); let fgWnd = user32.GetForegroundWindow(); log.debug(hwnd+" === "+fgWnd); if (firetray.js.strEquals(hwnd, fgWnd) || !this.windows[wid].visible) return; let defaultCycleCount = new win32.DWORD; user32.SystemParametersInfoW(user32.SPI_GETFOREGROUNDFLASHCOUNT, 0, defaultCycleCount.address(), 0); log.debug("defaultCycleCount="+defaultCycleCount); let flashInfo = new user32.FLASHWINFO; flashInfo.cbSize = user32.FLASHWINFO.size; flashInfo.hwnd = hwnd; flashInfo.dwFlags = user32.FLASHW_ALL; flashInfo.uCount = defaultCycleCount; flashInfo.dwTimeout = 0; user32.FlashWindowEx(flashInfo.address()); };