1 /*
2  *      FXTrayIcon.cpp
3  *
4  *      Copyright (c) 2008, Hendrik Rittich
5  *      Copyright (C) 2006-2011 by Sander Jansen
6  *      Copyright 2012 David Vachulka <david@konstrukce-cad.com>
7  *
8  *      This program is free software; you can redistribute it and/or modify
9  *      it under the terms of the GNU General Public License as published by
10  *      the Free Software Foundation; either version 2 of the License, or
11  *      (at your option) any later version.
12  *
13  *      This program is distributed in the hope that it will be useful,
14  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *      GNU General Public License for more details.
17  *
18  *      You should have received a copy of the GNU General Public License
19  *      along with this program; if not, write to the Free Software
20  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21  *      MA 02110-1301, USA.
22  */
23 
24 #include <xincs.h>
25 #include "FXTrayIcon.h"
26 #include "FXTrayApp.h"
27 
28 namespace FX {
29 
30 #ifdef WIN32
31 FXDEFMAP(FXTrayIcon) FXTrayIconMap[] = {
32     FXMAPFUNC(SEL_TIMEOUT, FXTrayIcon::ID_POPTIMEOUT, FXTrayIcon::onTimeout),
33     FXMAPFUNC(SEL_MOTION, 0, FXTrayIcon::onEvent)
34 };
35 #else
36 enum
37 {
38     SYSTEM_TRAY_REQUEST_DOCK = 0,
39     SYSTEM_TRAY_BEGIN_MESSAGE = 1,
40     SYSTEM_TRAY_CANCEL_MESSAGE = 2,
41 };
42 
43 enum
44 {
45     SYSTEM_TRAY_HORIZONTAL = 0,
46     SYSTEM_TRAY_VERTICAL = 1,
47     SYSTEM_TRAY_UNKNOWN = 2
48 };
49 
50 
51 FXDEFMAP(FXTrayIcon) FXTrayIconMap[] = {
52     FXMAPFUNC(SEL_PAINT, 0, FXTrayIcon::onPaint),
53     FXMAPFUNC(SEL_CONFIGURE, 0, FXTrayIcon::onConfigure),
54     FXMAPFUNC(SEL_LEFTBUTTONPRESS, 0, FXTrayIcon::onLeftBtnPress),
55     FXMAPFUNC(SEL_RIGHTBUTTONRELEASE, 0, FXTrayIcon::onRightBtnRelease),
56     FXMAPFUNC(SEL_QUERY_TIP, 0, FXTrayIcon::onQueryTip),
57     FXMAPFUNC(SEL_EMBED_NOTIFY, 0, FXTrayIcon::onEmbedded),
58     FXMAPFUNC(SEL_TIMEOUT, FXTrayIcon::ID_POPTIMEOUT, FXTrayIcon::onTimeout)
59 };
60 #endif
61 
62 FXIMPLEMENT(FXTrayIcon, FXTopWindow, FXTrayIconMap, ARRAYNUMBER(FXTrayIconMap))
63 
64 #ifdef WIN32
65 DWORD FXTrayIcon::sTrayIconCount = 1;
66 
FXTrayIcon(FXApp * app,const FXString & text,FXIcon * icon,FXPopup * popup,FXObject * target,FXSelector sel,FXuint opts)67 FXTrayIcon::FXTrayIcon(FXApp* app, const FXString& text, FXIcon* icon,
68         FXPopup* popup, FXObject* target, FXSelector sel,
69         FXuint opts) :
70     FXTopWindow(app, text, 0, 0, 0, 0,0,0,0, 0,0,0,0, 0,0),
71     mIcon(icon),
72     m_popup(popup),
73     m_opts(opts),
74     mTooltip(text)
75 {
76     mWIcon = 0;
77     mTrayID = sTrayIconCount;
78     sTrayIconCount++;
79 
80     setTarget(target);
81     setSelector(sel);
82 }
83 
~FXTrayIcon()84 FXTrayIcon::~FXTrayIcon() {
85     if (mWIcon)
86     {
87         // Icon entfernen
88         NOTIFYICONDATA tray_data;
89         ZeroMemory(&tray_data, sizeof(tray_data));
90         tray_data.cbSize = sizeof(tray_data);
91         tray_data.hWnd = static_cast<HWND>(xid);
92         tray_data.uID = mTrayID;
93         Shell_NotifyIcon(NIM_DELETE, &tray_data);
94 
95         // Icon loeschen
96         DestroyIcon(mWIcon);
97     }
98 }
99 
create()100 void FXTrayIcon::create() {
101     FXTopWindow::create();
102 
103     if (mWIcon)
104         return;
105 
106     mIcon->create();
107 
108     // Windows Icon erzeugen
109     mWIcon = createMswIcon(mIcon);
110 
111     mapToManager();
112 }
113 
mapToManager()114 void FXTrayIcon::mapToManager()
115 {
116     NOTIFYICONDATA tray_data;
117     setupNotifyData(&tray_data);
118 
119     Shell_NotifyIcon(NIM_ADD, &tray_data);
120 }
121 
setupNotifyData(NOTIFYICONDATA * data)122 void FXTrayIcon::setupNotifyData(NOTIFYICONDATA* data)
123 {
124     ZeroMemory(data, sizeof(*data));
125     data->cbSize = sizeof(*data);
126     data->hWnd = static_cast<HWND>(xid);
127     data->uID = mTrayID;
128     data->hIcon = mWIcon;
129     data->uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
130     data->uCallbackMessage = WM_MOUSEMOVE; // Change ID
131 
132     // Tooltip
133 #ifdef UNICODE
134     utf2ncs(data->szTip, mTooltip.text(), 64);
135 #else
136     //SetWindowTextA((HWND)xid,title.text());
137 #error USE DEFINE UNICODE
138 #endif
139 }
140 
createMswIcon(FXIcon * icon)141 HICON FXTrayIcon::createMswIcon(FXIcon* icon)
142 {
143     // Windows Icon erzeugen
144     ICONINFO iconinfo;
145     ZeroMemory(&iconinfo, sizeof(iconinfo));
146     iconinfo.fIcon=TRUE;
147     iconinfo.hbmMask=(HBITMAP)icon->shape;
148     iconinfo.hbmColor=(HBITMAP)icon->xid;
149     return CreateIconIndirect(&iconinfo);
150 }
151 
onEvent(FXObject * obj,FXSelector,void * ptr)152 long FXTrayIcon::onEvent(FXObject* obj, FXSelector, void* ptr)
153 {
154     if (ptr == 0)
155         return 0;
156 
157     FXEvent* event = static_cast<FXEvent*>(ptr);
158     int lParam = event->root_x;
159 
160     if ((lParam == WM_LBUTTONUP && (m_opts & TRAY_MENU_ON_LEFT)) ||
161         (lParam == WM_RBUTTONUP && (m_opts & TRAY_MENU_ON_RIGHT)))
162     {
163         POINT p;
164         GetCursorPos(&p);
165         m_popup->popup(0, p.x, p.y);
166         getApp()->addTimeout(this, ID_POPTIMEOUT, 10000);
167 
168         return 1;
169     }
170     if (target &&
171         (lParam == WM_LBUTTONUP && (m_opts & TRAY_CMD_ON_LEFT)) ||
172         (lParam == WM_RBUTTONUP && (m_opts & TRAY_CMD_ON_RIGHT)))
173     {
174         return target->tryHandle(obj, FXSEL(SEL_COMMAND, message), ptr);
175     }
176     return 0;
177 }
178 
setIcon(FXIcon * icon)179 void FXTrayIcon::setIcon(FXIcon* icon)
180 {
181     mIcon = icon;
182 
183     if (!mWIcon)
184         return; // not created
185 
186     // Icon loeschen
187     DestroyIcon(mWIcon);
188 
189     mWIcon = createMswIcon(icon);
190 
191     NOTIFYICONDATA tray_data;
192     setupNotifyData(&tray_data);
193     Shell_NotifyIcon(NIM_MODIFY, &tray_data);
194 }
195 
setText(const FXString & text)196 void FXTrayIcon::setText(const FXString& text)
197 {
198     mTooltip = text;
199 
200     if (!mWIcon)
201         return; // not created
202 
203     NOTIFYICONDATA tray_data;
204     setupNotifyData(&tray_data);
205     Shell_NotifyIcon(NIM_MODIFY, &tray_data);
206 }
207 #else
208 FXTrayIcon::FXTrayIcon()
209 {
210     m_icon = NULL;
211     flags |= FLAG_ENABLED;
212 }
213 
214 FXTrayIcon::FXTrayIcon(FXApp *app, const FXString& tip, FXIcon* icon,
215             FXPopup* popup, FXObject* target, FXSelector sel,
216             FXuint opts)
217 : FXTopWindow(app, "test", NULL, NULL, DECOR_NONE, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0), m_xtraywindow(0),
218   m_xtrayopcode(0), m_socket(0), m_icon(icon), m_opaque(false), m_tip(tip), m_popup(popup), m_opts(opts), m_traycolor(getBackColor())
219 {
220     flags |= FLAG_ENABLED;
221     setTarget(target);
222     setSelector(sel);
223     static_cast<FXTrayApp*> (app)->registerIcon(this);
224 }
225 
226 FXTrayIcon::~FXTrayIcon()
227 {
228     static_cast<FXTrayApp*> (getApp())->unregisterIcon();
229 }
230 
231 bool FXTrayIcon::doesOverrideRedirect() const
232 {
233     return true;
234 }
235 
236 FXbool FXTrayIcon::findSystemTray()
237 {
238     FXString systemtray = FXStringFormat("_NET_SYSTEM_TRAY_S%d", DefaultScreen((Display*) getApp()->getDisplay()));
239     Atom xtrayselection = XInternAtom((Display*) getApp()->getDisplay(), systemtray.text(), 0);
240     if (xtrayselection != None)
241     {
242         m_xtraywindow = (FXID) XGetSelectionOwner((Display*) getApp()->getDisplay(), xtrayselection);
243     }
244     return (m_xtraywindow != 0);
245 }
246 
247 void FXTrayIcon::requestDock()
248 {
249     if (xid && m_xtraywindow)
250     {
251         XEvent ev;
252         memset(&ev, 0, sizeof (ev));
253         ev.xclient.type = ClientMessage;
254         ev.xclient.window = m_xtraywindow;
255         ev.xclient.message_type = m_xtrayopcode;
256         ev.xclient.format = 32;
257         ev.xclient.data.l[0] = CurrentTime;
258         ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
259         ev.xclient.data.l[2] = xid;
260         XSendEvent((Display*) getApp()->getDisplay(), m_xtraywindow, False, NoEventMask, &ev);
261         XSync((Display*) getApp()->getDisplay(), 0);
262     }
263 }
264 
265 FXuint FXTrayIcon::getTrayOrientation()
266 {
267     if (m_xtrayorientation || m_xtrayxfceorientation)
268     {
269         FXuint orientation;
270         Atom returntype;
271         int returnformat;
272         unsigned long nitems;
273         unsigned long nbytes;
274         long * bytes = NULL;
275 
276         if (m_xtrayorientation && (XGetWindowProperty((Display*) getApp()->getDisplay(), (Atom) m_xtraywindow, (Atom) m_xtrayorientation, 0, 2, False, XA_CARDINAL, &returntype, &returnformat, &nitems, &nbytes, (unsigned char**) (void*) &bytes) == Success) && returntype == XA_CARDINAL)
277         {
278             orientation = *(long*) bytes;
279             XFree(bytes);
280             return orientation;
281         }
282 
283         if (bytes != NULL)
284         {
285             XFree(bytes);
286             bytes = NULL;
287         }
288 
289         if (m_xtrayxfceorientation && (XGetWindowProperty((Display*) getApp()->getDisplay(), (Atom) m_xtraywindow, (Atom) m_xtrayxfceorientation, 0, 2, False, XA_CARDINAL, &returntype, &returnformat, &nitems, &nbytes, (unsigned char**) (void*) &bytes) == Success) && returntype == XA_CARDINAL)
290         {
291             orientation = *(long*) bytes;
292             XFree(bytes);
293             return orientation;
294         }
295 
296         if (bytes != NULL)
297         {
298             XFree(bytes);
299             bytes = NULL;
300         }
301     }
302     return SYSTEM_TRAY_UNKNOWN;
303 }
304 
305 FXuint FXTrayIcon::getTrayVisual()
306 {
307     if (m_xtrayvisual)
308     {
309         FXuint visualid;
310         Atom returntype;
311         int returnformat;
312         unsigned long nitems;
313         unsigned long nbytes;
314         long * bytes = NULL;
315 
316         if (m_xtrayvisual && (XGetWindowProperty((Display*) getApp()->getDisplay(), m_xtraywindow, m_xtrayvisual, 0, 2, False, XA_VISUALID, &returntype, &returnformat, &nitems, &nbytes, (unsigned char**) (void*) &bytes) == Success) && returntype == XA_VISUALID)
317         {
318             visualid = *(long*) bytes;
319             XFree(bytes);
320             return visualid;
321         }
322 
323         if (bytes != NULL)
324         {
325             XFree(bytes);
326             bytes = NULL;
327         }
328     }
329     return 0;
330 }
331 
332 void FXTrayIcon::create()
333 {
334     m_xtrayopcode = (FXID) XInternAtom((Display*) getApp()->getDisplay(), "_NET_SYSTEM_TRAY_OPCODE", 0);
335     m_xtrayorientation = (FXID) XInternAtom((Display*) getApp()->getDisplay(), "_NET_SYSTEM_TRAY_ORIENTATION", 0);
336     m_xtrayxfceorientation = (FXID) XInternAtom((Display*) getApp()->getDisplay(), "_NET_XFCE_TRAY_MANAGER_ORIENTATION", 0);
337     m_xtrayvisual = (FXID) XInternAtom((Display*) getApp()->getDisplay(), "_NET_SYSTEM_TRAY_VISUAL", 0);
338 
339     FXTopWindow::create();
340     if (xid)
341     {
342         Atom xembedinfo = XInternAtom((Display*) getApp()->getDisplay(), "_XEMBED_INFO", 0);
343         if (xembedinfo != None)
344         {
345             unsigned long info[2] = {0, (1 << 0)};
346             XChangeProperty((Display*) getApp()->getDisplay(), xid, xembedinfo, xembedinfo, 32, PropModeReplace, (unsigned char*) info, 2);
347         }
348 
349         /// Set the size hints...
350         XSizeHints size;
351         size.flags = PMinSize | PMaxSize | PBaseSize | PAspect;
352         size.x = 0;
353         size.y = 0;
354         size.width = 0;
355         size.height = 0;
356         size.width_inc = 0;
357         size.height_inc = 0;
358         size.min_aspect.x = 1;
359         size.min_aspect.y = 1;
360         size.max_aspect.x = 1;
361         size.max_aspect.y = 1;
362         size.win_gravity = 0;
363         size.win_gravity = 0;
364 
365         size.min_width = 8;
366         size.min_height = 8;
367         size.max_width = 64;
368         size.max_height = 64;
369         size.base_width = 32;
370         size.base_height = 32;
371         XSetWMNormalHints((Display*) getApp()->getDisplay(), xid, &size);
372     }
373 }
374 
375 FXbool FXTrayIcon::dock()
376 {
377     if (findSystemTray())
378     {
379         FXuint id = getTrayVisual();
380         if (id)
381         {
382             if (id != XVisualIDFromVisual((Visual*) getVisual()->getVisual()))
383                 m_opaque = true;
384             else
385                 m_opaque = false;
386         }
387         if (!m_opaque)
388         {
389             /// Don't draw the background
390             XSetWindowAttributes sattr;
391             sattr.background_pixmap = ParentRelative;
392             XChangeWindowAttributes((Display*) getApp()->getDisplay(), xid, CWBackPixmap, &sattr);
393         }
394         requestDock();
395         return true;
396     }
397     return false;
398 }
399 
400 void FXTrayIcon::setFocus()
401 {
402     FXShell::setFocus();
403     if (xid && m_socket)
404     {
405         XEvent ev;
406         memset(&ev, 0, sizeof (ev));
407         ev.xclient.type = ClientMessage;
408         ev.xclient.window = m_socket;
409         ev.xclient.message_type = ((FXTrayApp*) getApp())->mXembed;
410         ev.xclient.format = 32;
411         ev.xclient.data.l[0] = CurrentTime;
412         ev.xclient.data.l[1] = XEMBED_REQUEST_FOCUS;
413         XSendEvent((Display*) getApp()->getDisplay(), m_socket, False, NoEventMask, &ev);
414     }
415 }
416 
417 long FXTrayIcon::onConfigure(FXObject*, FXSelector, void*ptr)
418 {
419     FXEvent * event = (FXEvent*) ptr;
420     FXuint orientation = getTrayOrientation();
421     FXint size = 0;
422     switch (orientation)
423     {
424     case SYSTEM_TRAY_HORIZONTAL: size = event->rect.h;
425         break;
426     case SYSTEM_TRAY_VERTICAL: size = event->rect.w;
427         break;
428     default: size = FXMAX(event->rect.h, event->rect.w);
429         break;
430     };
431     resize(size, size);
432     XSizeHints hint;
433     hint.flags = PMinSize | PMaxSize | PBaseSize;
434     hint.min_width = hint.max_width = hint.base_width = size;
435     hint.min_height = hint.max_height = hint.base_height = size;
436     XSetWMNormalHints((Display*) getApp()->getDisplay(), xid, &hint);
437     return 1;
438 }
439 
440 long FXTrayIcon::onRightBtnRelease(FXObject *obj, FXSelector, void *ptr)
441 {
442     FXEvent *event = (FXEvent*) ptr;
443     if (event->moved) return 0;
444     if (m_opts & TRAY_MENU_ON_RIGHT)
445     {
446         FXEvent* event = static_cast<FXEvent*> (ptr);
447         popup(event->click_x, event->click_y);
448         return 1;
449     }
450     if (m_opts & TRAY_CMD_ON_RIGHT && target)
451     {
452         return target->tryHandle(obj, FXSEL(SEL_COMMAND, message), ptr);
453     }
454     return 0;
455 }
456 
457 long FXTrayIcon::onLeftBtnPress(FXObject *obj, FXSelector, void *ptr)
458 {
459     if (m_opts & TRAY_MENU_ON_LEFT)
460     {
461         FXEvent* event = static_cast<FXEvent*> (ptr);
462         popup(event->click_x, event->click_y);
463         return 1;
464     }
465     if (m_opts & TRAY_CMD_ON_LEFT && target)
466     {
467         return target->tryHandle(obj, FXSEL(SEL_COMMAND, message), ptr);
468     }
469     return 0;
470 }
471 
472 long FXTrayIcon::onQueryTip(FXObject*sender, FXSelector sel, void* ptr)
473 {
474     if (FXTopWindow::onQueryTip(sender, sel, ptr)) return 1;
475     if ((flags & FLAG_TIP) && !m_tip.empty() && !grabbed())
476     {
477         sender->handle(this, FXSEL(SEL_COMMAND, ID_SETSTRINGVALUE), (void*) &m_tip);
478         return 1;
479     }
480     return 0;
481 }
482 
483 long FXTrayIcon::onPaint(FXObject*, FXSelector, void*)
484 {
485     if (m_icon)
486     {
487         FXDCWindow dc(this);
488         FXint dx = 0;
489         FXint w = getWidth();
490         FXint iw = m_icon->getWidth();
491         if(w>iw) dx = (w-iw)/2;
492         FXint dy = 0;
493         FXint h = getHeight();
494         FXint ih = m_icon->getHeight();
495         if(h>ih) dy = (h-ih)/2;
496         if (m_opaque)
497         {
498             dc.setForeground(m_traycolor);
499             dc.fillRectangle(0, 0, w, h);
500             dc.drawIcon(m_icon, dx, dy);
501         }
502         else
503         {
504             dc.drawIcon(m_icon, 0, 0);
505             //move(dx,dy);
506         }
507     }
508     return 1;
509 }
510 
511 long FXTrayIcon::onEmbedded(FXObject*, FXSelector, void*ptr)
512 {
513     flags |= FLAG_SHOWN;
514     m_socket = (FXID) (FXival) ptr;
515     return 1;
516 }
517 
518 void FXTrayIcon::popup(FXint x, FXint y)
519 {
520     if(!m_popup) return;
521     translateCoordinatesTo(x, y, getRoot(), x, y);
522 
523     if (y > getRoot()->getHeight() / 2)
524     {
525         y -= m_popup->getDefaultHeight();
526     }
527 
528     m_popup->popup(0, x, y);
529     getApp()->addTimeout(this, ID_POPTIMEOUT, 10000);
530 
531 }
532 
533 void FXTrayIcon::setIcon(FXIcon* ic)
534 {
535     if(m_icon != ic && ic)
536     {
537         m_icon = ic;
538         if(!m_opaque) setShape(m_icon);
539         update();
540     }
541 }
542 
543 FXIcon* FXTrayIcon::getIcon() const
544 {
545     return m_icon;
546 }
547 
548 void FXTrayIcon::setTrayColor(FXColor color)
549 {
550     m_traycolor = color;
551     if(m_opaque) update();
552 }
553 #endif
554 
onTimeout(FXObject *,FXSelector,void *)555 long FXTrayIcon::onTimeout(FXObject*, FXSelector, void*)
556 {
557     if(!m_popup->shown()) return 1;
558     FXint x,y, px, py;
559     FXuint button;
560     getCursorPosition(x,y,button);
561     translateCoordinatesTo(px,py,getParent(),x,y);
562     m_popup->contains(px,py) ? getApp()->addTimeout(this, ID_POPTIMEOUT, 10000) : m_popup->popdown();
563     return 1;
564 }
565 }
566