diff --git a/src/chrome/skin/winnt/application-exit.bmp b/src/chrome/skin/winnt/application-exit.bmp new file mode 100644 index 0000000..99e6f29 Binary files /dev/null and b/src/chrome/skin/winnt/application-exit.bmp differ diff --git a/src/chrome/skin/winnt/document-new.bmp b/src/chrome/skin/winnt/document-new.bmp new file mode 100644 index 0000000..e7cf0af Binary files /dev/null and b/src/chrome/skin/winnt/document-new.bmp differ diff --git a/src/chrome/skin/winnt/gtk-apply.bmp b/src/chrome/skin/winnt/gtk-apply.bmp new file mode 100644 index 0000000..7b5681a Binary files /dev/null and b/src/chrome/skin/winnt/gtk-apply.bmp differ diff --git a/src/chrome/skin/winnt/gtk-edit.bmp b/src/chrome/skin/winnt/gtk-edit.bmp new file mode 100644 index 0000000..38b0a87 Binary files /dev/null and b/src/chrome/skin/winnt/gtk-edit.bmp differ diff --git a/src/chrome/skin/winnt/gtk-preferences.bmp b/src/chrome/skin/winnt/gtk-preferences.bmp new file mode 100644 index 0000000..4d579e6 Binary files /dev/null and b/src/chrome/skin/winnt/gtk-preferences.bmp differ diff --git a/src/modules/ctypes/winnt/user32.jsm b/src/modules/ctypes/winnt/user32.jsm index 96680c8..c1fb5a8 100644 --- a/src/modules/ctypes/winnt/user32.jsm +++ b/src/modules/ctypes/winnt/user32.jsm @@ -266,6 +266,99 @@ function user32_defines(lib) { this.DT_NOPREFIX = 0x00000800; this.DT_INTERNAL = 0x00001000; + lib.lazy_bind("CreatePopupMenu", win32.HMENU); + lib.lazy_bind("DestroyMenu", win32.BOOL, win32.HMENU); + + this.MENUITEMINFOW = ctypes.StructType("MENUITEMINFOW", [ + { "cbSize": win32.UINT }, + { "fMask": win32.UINT }, + { "fType": win32.UINT }, + { "fState": win32.UINT }, + { "wID": win32.UINT }, + { "hSubMenu": win32.HMENU }, + { "hbmpChecked": win32.HBITMAP }, + { "hbmpUnchecked": win32.HBITMAP }, + { "dwItemData": win32.ULONG_PTR }, + { "dwTypeData": win32.LPWSTR }, + { "cch": win32.UINT }, + { "hbmpItem": win32.HBITMAP } + ]); + this.LPCMENUITEMINFO = this.LPMENUITEMINFOW = this.MENUITEMINFOW.ptr; + + lib.lazy_bind("InsertMenuItemW", win32.BOOL, win32.HMENU, win32.UINT, win32.BOOL, this.LPCMENUITEMINFO); + lib.lazy_bind("GetMenuItemInfoW", win32.BOOL, win32.HMENU, win32.UINT, win32.BOOL, this.LPCMENUITEMINFO); + + this.MIIM_STATE = 0x00000001; + this.MIIM_ID = 0x00000002; + this.MIIM_SUBMENU = 0x00000004; + this.MIIM_CHECKMARKS = 0x00000008; + this.MIIM_TYPE = 0x00000010; + this.MIIM_DATA = 0x00000020; + this.MIIM_STRING = 0x00000040; + this.MIIM_BITMAP = 0x00000080; + this.MIIM_FTYPE = 0x00000100; + + lib.lazy_bind("InsertMenuW", win32.BOOL, win32.HMENU, win32.UINT, win32.UINT, win32.UINT_PTR, win32.LPCTSTR); + + this.MF_INSERT = 0x00000000; + this.MF_CHANGE = 0x00000080; + this.MF_APPEND = 0x00000100; + this.MF_DELETE = 0x00000200; + this.MF_REMOVE = 0x00001000; + this.MF_BYCOMMAND = 0x00000000; + this.MF_BYPOSITION = 0x00000400; + this.MF_SEPARATOR = 0x00000800; + this.MF_ENABLED = 0x00000000; + this.MF_GRAYED = 0x00000001; + this.MF_DISABLED = 0x00000002; + this.MF_UNCHECKED = 0x00000000; + this.MF_CHECKED = 0x00000008; + this.MF_USECHECKBITMAPS = 0x00000200; + this.MF_STRING = 0x00000000; + this.MF_BITMAP = 0x00000004; + this.MF_OWNERDRAW = 0x00000100; + this.MF_POPUP = 0x00000010; + this.MF_MENUBARBREAK = 0x00000020; + this.MF_MENUBREAK = 0x00000040; + this.MF_UNHILITE = 0x00000000; + this.MF_HILITE = 0x00000080; + this.MF_DEFAULT = 0x00001000; + this.MF_RIGHTJUSTIFY = 0x00004000; + this.MFT_STRING = this.MF_STRING; + this.MFT_BITMAP = this.MF_BITMAP; + this.MFT_MENUBARBREAK = this.MF_MENUBARBREAK; + this.MFT_MENUBREAK = this.MF_MENUBREAK; + this.MFT_OWNERDRAW = this.MF_OWNERDRAW; + this.MFT_RADIOCHECK = 0x00000200; + this.MFT_SEPARATOR = this.MF_SEPARATOR; + this.MFT_RIGHTORDER = 0x00002000; + this.MFT_RIGHTJUSTIFY = this.MF_RIGHTJUSTIFY; + this.MFS_GRAYED = 0x00000003; + this.MFS_DISABLED = this.MFS_GRAYED; + this.MFS_CHECKED = this.MF_CHECKED; + this.MFS_HILITE = this.MF_HILITE; + this.MFS_ENABLED = this.MF_ENABLED; + this.MFS_UNCHECKED = this.MF_UNCHECKED; + this.MFS_UNHILITE = this.MF_UNHILITE; + this.MFS_DEFAULT = this.MF_DEFAULT; + + this.TPM_LEFTBUTTON = 0x0000; + this.TPM_RIGHTBUTTON = 0x0002; + this.TPM_LEFTALIGN = 0x0000; + this.TPM_CENTERALIGN = 0x0004; + this.TPM_RIGHTALIGN = 0x0008; + this.TPM_TOPALIGN = 0x0000; + this.TPM_VCENTERALIGN = 0x0010; + this.TPM_BOTTOMALIGN = 0x0020; + this.TPM_HORIZONTAL = 0x0000; + this.TPM_VERTICAL = 0x0040; + + lib.lazy_bind("CalculatePopupWindowPosition", win32.BOOL, win32.POINT.ptr, win32.SIZE, win32.UINT, win32.RECT.ptr, win32.RECT.ptr); + lib.lazy_bind("TrackPopupMenu", win32.BOOL, win32.HMENU, win32.UINT, ctypes.int, ctypes.int, ctypes.int, win32.HWND, win32.RECT.ptr); + lib.lazy_bind("SetForegroundWindow", win32.BOOL, win32.HWND); + lib.lazy_bind("GetCursorPos", win32.BOOL, win32.LPPOINT); + lib.lazy_bind("GetMessagePos", win32.DWORD); + } 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 a369106..00be95f 100644 --- a/src/modules/ctypes/winnt/win32.jsm +++ b/src/modules/ctypes/winnt/win32.jsm @@ -33,6 +33,7 @@ var win32 = new function() { this.LONG_PTR = is64bit ? ctypes.int64_t : ctypes.long; this.ULONG_PTR = is64bit ? ctypes.uint64_t : ctypes.unsigned_long; this.SIZE_T = this.ULONG_PTR; + this.DWORD_PTR = this.ULONG_PTR; this.ATOM = this.WORD; this.HANDLE = ctypes.voidptr_t; this.HWND = this.HANDLE; @@ -77,6 +78,21 @@ var win32 = new function() { return ctypes.jschar.array()(str); }; + /* + * #define LOWORD(l) ((WORD)((DWORD_PTR)(l) & 0xffff)) + * #define HIWORD(l) ((WORD)((DWORD_PTR)(l) >> 16)) + * #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) + * #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) + */ + this.LOWORD = function(l) {return l & 0x0000ffff;}; + this.HIWORD = function(l) {return l >> 16;}; + /* Although we shouldn't use LO-/HIWORD to get coords, because of negative + coords on multi-monitor displays, I'm not sure how to express the + GET_?_LPARAM macros with ctypes. */ + this.GET_X_LPARAM = this.LOWORD; + this.GET_Y_LPARAM = this.HIWORD; + + this.ERROR_INVALID_PARAMETER = 87; this.ERROR_INVALID_WINDOW_HANDLE = 1400; this.ERROR_RESOURCE_TYPE_NOT_FOUND = 1813; @@ -156,6 +172,12 @@ var win32 = new function() { ]); this.PICONINFO = this.ICONINFO.ptr; + this.POINT = ctypes.StructType("POINT", [ + { "x": this.LONG }, + { "y": this.LONG } + ]); + this.PPOINT = this.LPPOINT =this.POINT.ptr; + this.RECT = ctypes.StructType("RECT", [ { "left": this.LONG }, { "top": this.LONG }, diff --git a/src/modules/winnt/FiretrayPopupMenu.jsm b/src/modules/winnt/FiretrayPopupMenu.jsm new file mode 100644 index 0000000..f327779 --- /dev/null +++ b/src/modules/winnt/FiretrayPopupMenu.jsm @@ -0,0 +1,121 @@ +/* -*- 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/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/ctypes.jsm"); +Cu.import("resource://firetray/ctypes/winnt/win32.jsm"); +Cu.import("resource://firetray/ctypes/winnt/user32.jsm"); +Cu.import("resource://firetray/commons.js"); +firetray.Handler.subscribeLibsForClosing([user32]); + +let log = firetray.Logging.getLogger("firetray.PopupMenu"); + +if ("undefined" == typeof(firetray.StatusIcon)) + log.error("This module MUST be imported from/after StatusIcon !"); + +// popupmenu items +const IDM_PREF = 100; +const IDM_QUIT = 200; +const IDM_NEW_MSG = 300; +const IDM_NEW_WND = 400; +const IDM_RESET = 500; + + +firetray.PopupMenu = { + initialized: false, + menu: null, + + init: function() { + this.create(); + + this.initialized = true; + return true; + }, + + shutdown: function() { + this.destroy(); + + log.debug("Disabling PopupMenu"); + this.initialized = false; + }, + + create: function() { + this.menu = user32.CreatePopupMenu(); // FIXME: destroy + log.debug("menu="+this.menu); + + var addMenuSeparator = false; + + this.insertMenuItem('Quit', 'quit', IDM_QUIT); + user32.InsertMenuW(this.menu, 0, user32.MF_BYPOSITION|user32.MF_SEPARATOR, 0, null); + this.insertMenuItem('Preferences', 'prefs', IDM_PREF); + + if (firetray.Handler.inBrowserApp) { + this.insertMenuItem('NewWindow', 'new-wnd', IDM_NEW_WND); + addMenuSeparator = true; + } + + if (firetray.Handler.inMailApp) { + this.insertMenuItem('NewMessage', 'new-msg', IDM_NEW_MSG); + this.insertMenuItem('ResetIcon', 'reset', IDM_RESET); + addMenuSeparator = true; + } + + if (addMenuSeparator) { + user32.InsertMenuW(this.menu, 2, user32.MF_BYPOSITION|user32.MF_SEPARATOR, 0, null); + } + + // // We'll user InsertMenuW for hidden windows: + // user32.InsertMenuW(this.menu, 0, user32.MF_BYPOSITION|user32.MF_STRING, IDM_CLOSE, "Close"); // FIXME: ampersand doesn't work ? + + log.debug("PopupMenu created"); + }, + + destroy: function() { + user32.DestroyMenu(this.menu); + log.debug("PopupMenu destroyed"); + }, + + insertMenuItem: function(itemName, iconName, actionId) { + var menuItemLabel = firetray.Utils.strings.GetStringFromName("popupMenu.itemLabel."+itemName); + let mii = new user32.MENUITEMINFOW(); + // BUG: ctypes doesn't detect wrong field assignments mii.size = ... ('size' undefined) + mii.cbSize = user32.MENUITEMINFOW.size; + mii.fMask = user32.MIIM_ID | user32.MIIM_STRING | user32.MIIM_DATA; + mii.wID = actionId; + // mii.dwItemData = win32.ULONG_PTR(actionId); + mii.dwTypeData = win32._T(menuItemLabel); + /* Under XP, putting a bitmap into hbmpItem results in ugly icons. We + should probably use HBMMENU_CALLBACK as explained in + http://www.nanoant.com/programming/themed-menus-icons-a-complete-vista-xp-solution. + But for now, we just don't display icons in XP-. */ + if (win32.WINVER >= win32.WIN_VERSIONS["Vista"]) { + mii.fMask |= user32.MIIM_BITMAP; + mii.hbmpItem = firetray.StatusIcon.bitmaps.get(iconName); + } + log.debug("mii="+mii); + if (!user32.InsertMenuItemW(this.menu, 0, true, mii.address())) { + log.error("InsertMenuItemW failed winLastError="+ctypes.winLastError); + } + }, + + processMenuItem: function(itemId) { + switch (itemId) { + case IDM_PREF: firetray.Handler.openPrefWindow(); break; + case IDM_QUIT: firetray.Handler.quitApplication(); break; + case IDM_NEW_MSG: firetray.Handler.openMailMessage(); break; + case IDM_NEW_WND: firetray.Handler.openBrowserWindow(); break; + case IDM_RESET: firetray.Handler.setIconImageDefault(); break; + default: + log.error("no action for itemId ("+itemId+")"); + } + } + +}; // firetray.PopupMenu + +firetray.Handler.showHidePopupMenuItems = firetray.PopupMenu.showHideWindowItems; diff --git a/src/modules/winnt/FiretrayStatusIcon.jsm b/src/modules/winnt/FiretrayStatusIcon.jsm index 3b79fa7..22397fa 100644 --- a/src/modules/winnt/FiretrayStatusIcon.jsm +++ b/src/modules/winnt/FiretrayStatusIcon.jsm @@ -27,18 +27,23 @@ let log = firetray.Logging.getLogger("firetray.StatusIcon"); if ("undefined" == typeof(firetray.Handler)) log.error("This module MUST be imported from/after FiretrayHandler !"); -FIRETRAY_ICON_CHROME_PATHS = { +const ICON_CHROME_PATHS = { 'blank-icon': "chrome://firetray/skin/winnt/blank-icon.bmp", - 'mail-unread': "chrome://firetray/skin/winnt/mail-unread.ico" + 'mail-unread': "chrome://firetray/skin/winnt/mail-unread.ico", + // these are for the popup menu: + 'prefs': "chrome://firetray/skin/winnt/gtk-preferences.bmp", + 'quit': "chrome://firetray/skin/winnt/application-exit.bmp", + 'new-wnd': "chrome://firetray/skin/winnt/document-new.bmp", + 'new-msg': "chrome://firetray/skin/winnt/gtk-edit.bmp", + 'reset': "chrome://firetray/skin/winnt/gtk-apply.bmp" }; + firetray.StatusIcon = { initialized: false, callbacks: {}, // pointers to JS functions. MUST LIVE DURING ALL THE EXECUTION notifyIconData: null, hwndProxy: null, - icons: null, - bitmaps: null, WNDCLASS_NAME: "FireTrayHiddenWindowClass", WNDCLASS_ATOM: null, icons: (function(){return new ctypesMap(win32.HICON);})(), @@ -57,12 +62,17 @@ firetray.StatusIcon = { 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(); @@ -84,8 +94,8 @@ firetray.StatusIcon = { /* 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 FIRETRAY_ICON_CHROME_PATHS) { - let path = firetray.Utils.chromeToPath(FIRETRAY_ICON_CHROME_PATHS[imgName]); + for (let imgName in ICON_CHROME_PATHS) { + let path = firetray.Utils.chromeToPath(ICON_CHROME_PATHS[imgName]); let img = this.loadImageFromFile(path); if (img) this[this.IMG_TYPES[img['type']]['map']].insert(imgName, img['himg']); @@ -225,23 +235,44 @@ firetray.StatusIcon = { } else if (uMsg === firetray.Win32.WM_TRAYMESSAGE) { - switch (+lParam) { + switch (win32.LOWORD(lParam)) { case win32.WM_LBUTTONUP: log.debug("WM_LBUTTONUP"); firetray.Handler.showHideAllWindows(); break; case win32.WM_RBUTTONUP: log.debug("WM_RBUTTONUP"); - break; case win32.WM_CONTEXTMENU: log.debug("WM_CONTEXTMENU"); - break; - case win32.NIN_KEYSELECT: - log.debug("NIN_KEYSELECT"); + /* Can't determine tray icon position precisely: the mouse cursor can + move between WM_RBUTTONDOWN and WM_RBUTTONUP, or the icon can have + been moved inside the notification area... so we opt for the easy + solution. */ + let pos = user32.GetMessagePos(); + let xPos = win32.GET_X_LPARAM(pos), yPos = win32.GET_Y_LPARAM(pos); + log.debug(" x="+xPos+" y="+yPos); + user32.SetForegroundWindow(hWnd); + user32.TrackPopupMenu(firetray.PopupMenu.menu, user32.TPM_RIGHTALIGN|user32.TPM_BOTTOMALIGN, xPos, yPos, 0, hWnd, null); break; default: } + } else { + switch (uMsg) { + case win32.WM_SYSCOMMAND: + log.debug("WM_SYSCOMMAND wParam="+wParam+", lParam="+lParam); + break; + case win32.WM_COMMAND: + log.debug("WM_COMMAND wParam="+wParam+", lParam="+lParam); + firetray.PopupMenu.processMenuItem(wParam); + break; + case win32.WM_MENUCOMMAND: + log.debug("WM_MENUCOMMAND wParam="+wParam+", lParam="+lParam); + break; + case win32.WM_MENUCHAR: + log.debug("WM_MENUCHAR wParam="+wParam+", lParam="+lParam); + break; + } } return user32.DefWindowProcW(hWnd, uMsg, wParam, lParam);