From 8a3de9e34318565dd167939d26bf5b147d53c648 Mon Sep 17 00:00:00 2001 From: foudfou Date: Mon, 31 Mar 2014 23:17:03 +0200 Subject: [PATCH] Add basic popup menu on tray icon. --- src/chrome/skin/winnt/application-exit.bmp | Bin 0 -> 2358 bytes src/chrome/skin/winnt/document-new.bmp | Bin 0 -> 2358 bytes src/chrome/skin/winnt/gtk-apply.bmp | Bin 0 -> 1654 bytes src/chrome/skin/winnt/gtk-edit.bmp | Bin 0 -> 2358 bytes src/chrome/skin/winnt/gtk-preferences.bmp | Bin 0 -> 2358 bytes src/modules/ctypes/winnt/user32.jsm | 93 ++++++++++++++++ src/modules/ctypes/winnt/win32.jsm | 22 ++++ src/modules/winnt/FiretrayPopupMenu.jsm | 121 +++++++++++++++++++++ src/modules/winnt/FiretrayStatusIcon.jsm | 53 +++++++-- 9 files changed, 278 insertions(+), 11 deletions(-) create mode 100644 src/chrome/skin/winnt/application-exit.bmp create mode 100644 src/chrome/skin/winnt/document-new.bmp create mode 100644 src/chrome/skin/winnt/gtk-apply.bmp create mode 100644 src/chrome/skin/winnt/gtk-edit.bmp create mode 100644 src/chrome/skin/winnt/gtk-preferences.bmp create mode 100644 src/modules/winnt/FiretrayPopupMenu.jsm diff --git a/src/chrome/skin/winnt/application-exit.bmp b/src/chrome/skin/winnt/application-exit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..99e6f29b52d809955c439461d0a6ee4ce11b6d43 GIT binary patch literal 2358 zcmc(gSx8h-7{~80E*Q8%)>}S=xnU7Hn3AO3~^iO^q?z)XFqNq@;%yb^hIR^A0oaCeeZ4<-FYA`QPuH^IgACff>aP z0rPZVSUm-gvSVg{Mvpl~5x4<>`d7x2rIGo ztoSN~bNVStx`JUN`8HA?&i|mL8MU|Tp{lNcI7y7HeqQkQ_kzcfFswTlVL)VY(tLg^ zilV)bZnVd{s1J>~QSw8{-+F@!+qqeS{tv^?v>3*^X*~??s zb9Ln2tI+p@`1CP0&ife?(+{$SrKJL;s{2^TFKbkRqx_NDz`t)_AEpx>-3L$4m&_U+ zu&_`-o?plA?_Bv+t##l~cQnoL^ZD(ds5S$5ytb(|I$>^}hm?#e%;j(BY5_;f>t_c3 zxVU$)x7WbYQ3EF@4V<03;ozWwo!xWT+IC{suD4jf{w{26D^7b2ng7SXPWoMGlhEQy^mc z9D!tAf}i=zO6|G++03|a)vIx>x(bCQMUW}vkYuDmbV`haA|cCC{QCbKlE1d@7F1WS zpx~ksneuE%(o>PhxMLFzBPw=3%X0jsWo0N(Dv_NlM+SW!lGL*}e)=SYi6R^n9%A`h OKY4FC=-1+3&3^;$Y^Oy4 literal 0 HcmV?d00001 diff --git a/src/chrome/skin/winnt/document-new.bmp b/src/chrome/skin/winnt/document-new.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e7cf0af1d2f69dd517c8fb8a1aeaca8e804a3bf8 GIT binary patch literal 2358 zcmbW3%Wl&^6o%6%AV6Zzk|hha^a0rOAZ(D}0T3WSZAhCUSfCQhMHRdOh-Fo(0HG|? zC<|x+RW$K6aqQT!efZDV<2oTx5|4Be+tPgg^v{_|u3lR{n-gOhpO;V<;#$gG#8os7 z&gXJmau5+;=253mXHZNzm$7&*GnkcUcFZr_xOMYNaeZy?L2)HSQN{|H!7T6#F?$Ku zr;FvaYHj};g<*)t*+viq^riNd@Qx}oQOp*6_RY%Xc|jP6`99vkEXju{E^`HbmSqSw zn7;4R&>sp!%oq8@{QRQ)$T=kT9Wr}3B+v8G{A7Lk9gFbCoxPz)F4zOtP58`A*S!1@ zcI@nWF4zNd22N&v!VRLFJ+P&-GZ>H!cHglx@e{VpS>$ozY&&4vHd%ImA%4OY&OVJe zBM<4!++N?JuGO2HpRgx7gU!y!Bh5B@-I;vB#a@wpmv)wGXoK5_v(-QGX138aXYzTE z%e|8ByYw24I~zTdbg(tkn8_FH34Iqi|DOjt>)>jpPO72JTDqkHv?z=@ihLlz7sje&N7dkPrJnZz=OT(^*TMPY&xs!rR&r9QQuj~ zeo6A+HH0&-p@N({nnJbj`{K;EEK9IAw<GI9wvJOtCh8>D{V6&_jQhKF)B;D@a^86^PTU0=iF^|M6$bJWG$u7 z0D`vzLcuIL{aDXs0s+Ih00^9*n2_K`5EEE!0%0OyvICQ7ml8Y(5`u`p{GS@E<%n@T zbGg=&FonRDrW1S!zK)&0bDZFZaWq ze(Tq%$Ga#a*iWJkeY)nx0b9lA7925GLD$;|nKr^oxtXrqf1QaREzMfjzwcSmu)e<; z;cw1jUb_Mc({=26m^UoZgw_5_gY%^0L7p!9<;D->nd)%fVnB@X0)o4=2EvGSD~`o2a? zIkleqp8F?FihEKIZ7Gkh3}5c4M_|cLEA?-nxDP={xt8Q-MRt|B20`r^*xy}-y-y46 z5>4p+Kk0Fd>U`ZbVJ%{n+(t-Z9VeI03%yQ8+4PnsY&8~QQR@+uSQ@dQXuDOU432a1 z#

hGm*E_z8272LprqB(3LYXUyAq9eV*gdVlLIl#F_G5t+5&@-SmHXnFg)-7Gmxy zhv;o`#;fPh+~}Xb`AY@;4r4mAv7${09leeBnP`5S#s8y8{naHr%pF#lsX`mtZGx^YF`7E=n||VRmirn>bUY~j_L9n z2M6=}>XGoa7^XCg4DKB1Qcwsl2$9l=Z{t6S02@_ NAdU9~o?C=#{yRHBjA8%) literal 0 HcmV?d00001 diff --git a/src/chrome/skin/winnt/gtk-edit.bmp b/src/chrome/skin/winnt/gtk-edit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..38b0a878fe85c0db884dbe5e421c885165e87a65 GIT binary patch literal 2358 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 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);