/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* The tray icon for the main app. We need a hidden proxy window as (1) we want a unique icon, (2) the icon sends notifications to a single window. */ var EXPORTED_SYMBOLS = [ "firetray" ]; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/ctypes.jsm"); Cu.import("resource://firetray/ctypes/ctypesMap.jsm"); Cu.import("resource://firetray/ctypes/winnt/win32.jsm"); Cu.import("resource://firetray/ctypes/winnt/gdi32.jsm"); Cu.import("resource://firetray/ctypes/winnt/kernel32.jsm"); Cu.import("resource://firetray/ctypes/winnt/shell32.jsm"); Cu.import("resource://firetray/ctypes/winnt/user32.jsm"); Cu.import("resource://firetray/winnt/FiretrayWin32.jsm"); Cu.import("resource://firetray/commons.js"); firetray.Handler.subscribeLibsForClosing([gdi32, kernel32, shell32, user32]); let log = firetray.Logging.getLogger("firetray.StatusIcon"); if ("undefined" == typeof(firetray.Handler)) log.error("This module MUST be imported from/after FiretrayHandler !"); const ICON_CHROME_PATH = "chrome://firetray/skin/icons/winnt"; const ICON_CHROME_FILES = { 'blank-icon': { use:'tray', path:ICON_CHROME_PATH+"/blank-icon.bmp" }, 'mail-unread': { use:'tray', path:ICON_CHROME_PATH+"/mail-unread.ico" }, 'prefs': { use:'menu', path:ICON_CHROME_PATH+"/gtk-preferences.bmp" }, 'quit': { use:'menu', path:ICON_CHROME_PATH+"/application-exit.bmp" }, 'new-wnd': { use:'menu', path:ICON_CHROME_PATH+"/document-new.bmp" }, 'new-msg': { use:'menu', path:ICON_CHROME_PATH+"/gtk-edit.bmp" }, 'reset': { use:'menu', path:ICON_CHROME_PATH+"/gtk-apply.bmp" }, }; firetray.StatusIcon = { initialized: false, callbacks: {}, // pointers to JS functions. MUST LIVE DURING ALL THE EXECUTION notifyIconData: null, hwndProxy: null, WNDCLASS_NAME: "FireTrayHiddenWindowClass", WNDCLASS_ATOM: null, icons: (function(){return new ctypesMap(win32.HICON);})(), bitmaps: (function(){return new ctypesMap(win32.HBITMAP);})(), IMG_TYPES: { ico: { win_t: win32.HICON, load_const: user32.IMAGE_ICON, map: 'icons' }, bmp: { win_t: win32.HBITMAP, load_const: user32.IMAGE_BITMAP, map: 'bitmaps' } }, PREF_TO_ICON_NAME: { app_icon_custom: 'app-custom', mail_icon_custom: 'mail-custom' }, init: function() { this.loadImages(); this.create(); firetray.Handler.setIconImageDefault(); Cu.import("resource://firetray/winnt/FiretrayPopupMenu.jsm"); if (!firetray.PopupMenu.init()) return false; this.initialized = true; return true; }, shutdown: function() { log.debug("Disabling StatusIcon"); firetray.PopupMenu.shutdown(); this.destroy(); this.destroyImages(); this.initialized = false; return true; }, loadThemedIcons: function() { }, loadImages: function() { let topmost = firetray.Handler.getWindowInterface( Services.wm.getMostRecentWindow(null), "nsIBaseWindow"); let hwnd = firetray.Win32.hexStrToHwnd(topmost.nativeHandle); log.debug("topmost or hiddenWin hwnd="+hwnd); this.icons.insert('app', this.getIconFromWindow(hwnd)); ['app_icon_custom', 'mail_icon_custom'].forEach(function(elt) { firetray.StatusIcon.loadImageCustom(elt); }); /* we'll take the first icon in the .ico file. To get the icon count in the file, pass ctypes.cast(ctypes.int(-1), win32.UINT); */ for (let imgName in ICON_CHROME_FILES) { let path = firetray.Utils.chromeToPath(ICON_CHROME_FILES[imgName].path); let img = this.loadImageFromFile(path); if (img && ICON_CHROME_FILES[imgName].use == 'menu') /* Ideally we should rebuild the menu each time it is shown as the menu color may change. But, let's just consider it's not worth it for now. */ img.himg = this.makeBitMapTransparent(img.himg); if (img) this[this.IMG_TYPES[img['type']]['map']].insert(imgName, img['himg']); } }, loadImageCustom: function(prefname) { log.debug("loadImageCustom pref="+prefname); let filename = firetray.Utils.prefService.getCharPref(prefname); if (!filename) return; let img = this.loadImageFromFile(filename); if (!img) return; log.debug("loadImageCustom img type="+img['type']+" himg="+img['himg']); let hicon = img['himg']; if (img['type'] === 'bmp') hicon = this.HBITMAPToHICON(img['himg']); let name = this.PREF_TO_ICON_NAME[prefname]; log.debug(" name="+name); this.icons.insert(name, hicon); }, loadImageFromFile: function(path) { let imgType = path.substr(-3, 3).toLowerCase(); if (!(imgType in this.IMG_TYPES)) { throw Error("Unrecognized type '"+imgType+"'"); } let imgTypeRec = this.IMG_TYPES[imgType]; let himg = ctypes.cast( user32.LoadImageW(null, path, imgTypeRec['load_const'], 0, 0, user32.LR_LOADFROMFILE|user32.LR_SHARED), imgTypeRec['win_t']); if (himg.isNull()) { log.error("Could not load '"+path+"'="+himg+" winLastError="+ctypes.winLastError); return null; } return {type:imgType, himg:himg}; }, HBITMAPToHICON: function(hBitmap) { log.debug("HBITMAPToHICON hBitmap="+hBitmap); let hWnd = null; // firetray.StatusIcon.hwndProxy; let hdc = user32.GetDC(hWnd); let bitmap = new win32.BITMAP(); let err = gdi32.GetObjectW(hBitmap, win32.BITMAP.size, bitmap.address()); // get bitmap info let hBitmapMask = gdi32.CreateCompatibleBitmap(hdc, bitmap.bmWidth, bitmap.bmHeight); user32.ReleaseDC(hWnd, hdc); let iconInfo = win32.ICONINFO(); iconInfo.fIcon = true; iconInfo.xHotspot = 0; iconInfo.yHotspot = 0; iconInfo.hbmMask = hBitmapMask; iconInfo.hbmColor = hBitmap; let hIcon = user32.CreateIconIndirect(iconInfo.address()); log.debug(" CreateIconIndirect hIcon="+hIcon+" lastError="+ctypes.winLastError); gdi32.DeleteObject(hBitmap); gdi32.DeleteObject(hBitmapMask); return hIcon; }, // images loaded with LR_SHARED need't be destroyed destroyImages: function() { [this.icons, this.bitmaps].forEach(function(map, idx, ary) { let keys = map.keys; for (let i=0, len=keys.length; i