1 /*******************************************************************************
2 *                         Goggles Music Manager                                *
3 ********************************************************************************
4 *           Copyright (C) 2008-2021 by Sander Jansen. All Rights Reserved      *
5 *                               ---                                            *
6 * This program is free software: you can redistribute it and/or modify         *
7 * it under the terms of the GNU General Public License as published by         *
8 * the Free Software Foundation, either version 3 of the License, or            *
9 * (at your option) any later version.                                          *
10 *                                                                              *
11 * This program is distributed in the hope that it will be useful,              *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of               *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                *
14 * GNU General Public License for more details.                                 *
15 *                                                                              *
16 * You should have received a copy of the GNU General Public License            *
17 * along with this program.  If not, see http://www.gnu.org/licenses.           *
18 ********************************************************************************/
19 #ifndef _WIN32
20 
21 #include "gmdefs.h"
22 #include "gmutils.h"
23 
24 #include "xincs.h"
25 
26 #include "icons.h"
27 #include "FXPNGIcon.h"
28 
29 #include "GMTrack.h"
30 #include "GMPlayerManager.h"
31 #include "GMWindow.h"
32 #include "GMRemote.h"
33 
34 
35 #include "GMApp.h"
36 #include "GMTrayIcon.h"
37 #include "GMIconTheme.h"
38 
39 
40 enum {
41   SYSTEM_TRAY_REQUEST_DOCK  =0,
42   SYSTEM_TRAY_BEGIN_MESSAGE =1,
43   SYSTEM_TRAY_CANCEL_MESSAGE=2,
44   };
45 
46 enum {
47   SYSTEM_TRAY_HORIZONTAL = 0,
48   SYSTEM_TRAY_VERTICAL   = 1,
49   SYSTEM_TRAY_UNKNOWN    = 2
50   };
51 
52 
53 FXDEFMAP(GMTrayIcon) GMTrayIconMap[]={
54   FXMAPFUNC(SEL_PAINT,0,GMTrayIcon::onPaint),
55   FXMAPFUNC(SEL_CONFIGURE,0,GMTrayIcon::onConfigure),
56   FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,GMTrayIcon::onLeftBtnPress),
57   FXMAPFUNC(SEL_MIDDLEBUTTONPRESS,0,GMTrayIcon::onMiddleBtnPress),
58   FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,0,GMTrayIcon::onRightBtnRelease),
59   FXMAPFUNC(SEL_MOUSEWHEEL,0,GMTrayIcon::onMouseWheel),
60   FXMAPFUNC(SEL_QUERY_TIP,0,GMTrayIcon::onQueryTip),
61   };
62 
63 FXIMPLEMENT(GMTrayIcon,GMPlug,GMTrayIconMap,ARRAYNUMBER(GMTrayIconMap));
64 
65 
GMTrayIcon(FXApp * a)66 GMTrayIcon::GMTrayIcon(FXApp * a) : GMPlug(a) {
67   flags|=FLAG_ENABLED;
68   }
69 
~GMTrayIcon()70 GMTrayIcon::~GMTrayIcon(){
71   if (icon) delete icon;
72   }
73 
updateIcon()74 void GMTrayIcon::updateIcon() {
75   if (icon) {
76     FXint size = icon->getWidth();
77 
78     /// Delete the old
79     delete icon;
80     icon=nullptr;
81 
82     /// Update
83     if (size<=16) {
84       icon = new FXPNGIcon(getApp(),gogglesmm_16_png,0,IMAGE_ALPHAGUESS);
85       icon->setVisual(getVisual());
86       if (size!=16) icon->scale(size,size,FOX_SCALE_BEST);
87       }
88     else {
89       icon = new FXPNGIcon(getApp(),gogglesmm_32_png,0,IMAGE_ALPHAGUESS);
90       icon->setVisual(getVisual());
91       if (size!=32) icon->scale(size,size,FOX_SCALE_BEST);
92       }
93 
94     //icon->blend(GMPlayerManager::instance()->getPreferences().gui_tray_color);
95     icon->create();
96 
97     // Mark Dirty
98     update();
99     }
100   }
101 
display(const GMTrack & track)102 void GMTrayIcon::display(const GMTrack &track){
103   setToolTip(FXString::value("%s\n%s\n%s (%d)",track.title.text(),track.artist.text(),track.album.text(),track.year));
104   }
105 
reset()106 void GMTrayIcon::reset() {
107   setToolTip(FXString::null);
108   }
109 
findSystemTray()110 FXbool GMTrayIcon::findSystemTray() {
111   FXString systemtray = FXString::value("_NET_SYSTEM_TRAY_S%d",DefaultScreen((Display*)getApp()->getDisplay()));
112   Atom xtrayselection = XInternAtom((Display*)getApp()->getDisplay(),systemtray.text(),0);
113   if (xtrayselection!=None) {
114     xtraywindow = (FXID)XGetSelectionOwner((Display*)getApp()->getDisplay(),xtrayselection);
115     }
116   return (xtraywindow!=0);
117   }
118 
119 
requestDock()120 void GMTrayIcon::requestDock() {
121   if (xid && xtraywindow) {
122     XEvent ev;
123     memset(&ev,0,sizeof(ev));
124     ev.xclient.type = ClientMessage;
125     ev.xclient.window = xtraywindow;
126     ev.xclient.message_type = xtrayopcode;
127     ev.xclient.format = 32;
128     ev.xclient.data.l[0] = CurrentTime;
129     ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
130     ev.xclient.data.l[2] = xid;
131     XSendEvent((Display*)getApp()->getDisplay(),xtraywindow,False,NoEventMask,&ev);
132     XSync((Display*)getApp()->getDisplay(),0);
133     }
134   }
135 
getTrayOrientation()136 FXuint GMTrayIcon::getTrayOrientation(){
137   if (xtrayorientation || xtrayxfceorientation) {
138     FXuint orientation;
139     Atom returntype;
140     int returnformat;
141     unsigned long nitems;
142     unsigned long nbytes;
143     long * bytes=nullptr;
144 
145     if (xtrayorientation && (XGetWindowProperty((Display*)getApp()->getDisplay(),xtraywindow,xtrayorientation,0,2,False,XA_CARDINAL,&returntype,&returnformat,&nitems,&nbytes,(unsigned char**)&bytes)==Success) && returntype==XA_CARDINAL){
146       orientation=*(long*)bytes;
147       XFree(bytes);
148       return orientation;
149       }
150 
151     if (bytes!=nullptr) {
152       XFree(bytes);
153       bytes=nullptr;
154       }
155 
156     if (xtrayxfceorientation && (XGetWindowProperty((Display*)getApp()->getDisplay(),xtraywindow,xtrayxfceorientation,0,2,False,XA_CARDINAL,&returntype,&returnformat,&nitems,&nbytes,(unsigned char**)&bytes)==Success) && returntype==XA_CARDINAL){
157       orientation=*(long*)bytes;
158       XFree(bytes);
159       return orientation;
160       }
161 
162     if (bytes!=nullptr)  {
163       XFree(bytes);
164       bytes=nullptr;
165       }
166     }
167   return SYSTEM_TRAY_UNKNOWN;
168   }
169 
170 
getTrayVisual()171 FXuint GMTrayIcon::getTrayVisual(){
172   if (xtrayvisual) {
173     FXuint visualid;
174     Atom returntype;
175     int returnformat;
176     unsigned long nitems;
177     unsigned long nbytes;
178     long * bytes=nullptr;
179 
180     if (xtrayvisual && (XGetWindowProperty((Display*)getApp()->getDisplay(),xtraywindow,xtrayvisual,0,2,False,XA_VISUALID,&returntype,&returnformat,&nitems,&nbytes,(unsigned char**)&bytes)==Success) && returntype==XA_VISUALID){
181       visualid=*(long*)bytes;
182       XFree(bytes);
183       return visualid;
184       }
185 
186     if (bytes!=nullptr) {
187       XFree(bytes);
188       bytes=nullptr;
189       }
190     }
191   return 0;
192   }
193 
create()194 void GMTrayIcon::create(){
195   xtrayopcode           = (FXID)XInternAtom((Display*)getApp()->getDisplay(),"_NET_SYSTEM_TRAY_OPCODE",0);
196   xtrayorientation      = (FXID)XInternAtom((Display*)getApp()->getDisplay(),"_NET_SYSTEM_TRAY_ORIENTATION",0);
197   xtrayxfceorientation  = (FXID)XInternAtom((Display*)getApp()->getDisplay(),"_NET_XFCE_TRAY_MANAGER_ORIENTATION",0);
198   xtrayvisual           = (FXID)XInternAtom((Display*)getApp()->getDisplay(),"_NET_SYSTEM_TRAY_VISUAL",0);
199 
200   GMPlug::create();
201   if (xid) {
202 
203     /// Set the size hints...
204     XSizeHints size;
205     size.flags=PMinSize|PMaxSize|PBaseSize|PAspect;
206     size.x=0;
207     size.y=0;
208     size.width=0;
209     size.height=0;
210     size.width_inc=0;
211     size.height_inc=0;
212     size.min_aspect.x=1;
213     size.min_aspect.y=1;
214     size.max_aspect.x=1;
215     size.max_aspect.y=1;
216     size.win_gravity=0;
217     size.win_gravity=0;
218 
219     size.min_width=8;
220     size.min_height=8;
221     size.max_width=64;
222     size.max_height=64;
223     size.base_width=32;
224     size.base_height=32;
225     XSetWMNormalHints((Display*)getApp()->getDisplay(),xid,&size);
226 
227     dock();
228     }
229   }
230 
dock()231 void GMTrayIcon::dock() {
232   if (findSystemTray()){
233 
234     opaque=false;
235 
236     if (!opaque) {
237       /// Don't draw the background
238       XSetWindowAttributes sattr;
239       sattr.background_pixmap = ParentRelative;
240       XChangeWindowAttributes((Display*)getApp()->getDisplay(),xid,CWBackPixmap,&sattr);
241       }
242 
243     requestDock();
244     }
245   }
246 
onConfigure(FXObject *,FXSelector,void * ptr)247 long GMTrayIcon::onConfigure(FXObject*,FXSelector,void*ptr){
248   FXEvent * event = (FXEvent*)ptr;
249 
250   FXuint orientation = getTrayOrientation();
251   FXint size=0;
252   switch(orientation){
253     case SYSTEM_TRAY_HORIZONTAL: size=event->rect.h; break;
254     case SYSTEM_TRAY_VERTICAL  : size=event->rect.w; break;
255     default                    : size=FXMAX(event->rect.h,event->rect.w); break;
256     };
257 
258   resize(size,size);
259 
260   XSizeHints hint;
261   hint.flags=PMinSize|PMaxSize|PBaseSize;
262   hint.min_width = hint.max_width = hint.base_width = size;
263   hint.min_height = hint.max_height = hint.base_height = size;
264   XSetWMNormalHints((Display*)getApp()->getDisplay(),xid,&hint);
265 
266 
267   if (icon && icon->getWidth()!=size) {
268     delete icon;
269     icon=nullptr;
270     }
271 
272   if (icon==nullptr) {
273     if (size<=16) {
274       icon = new FXPNGIcon(getApp(),gogglesmm_16_png,0,IMAGE_ALPHAGUESS);
275       icon->setVisual(getVisual());
276       if (size!=16) icon->scale(size,size,FOX_SCALE_BEST);
277       }
278     else {
279       icon = new FXPNGIcon(getApp(),gogglesmm_32_png,0,IMAGE_ALPHAGUESS);
280       icon->setVisual(getVisual());
281       if (size!=32) icon->scale(size,size,FOX_SCALE_BEST);
282       }
283     //icon->blend(GMPlayerManager::instance()->getPreferences().gui_tray_color);
284     icon->create();
285     }
286   return 1;
287   }
288 
289 
onRightBtnRelease(FXObject *,FXSelector,void * ptr)290 long GMTrayIcon::onRightBtnRelease(FXObject*,FXSelector,void*ptr){
291   FXEvent * event=(FXEvent*)ptr;
292   if (event->moved) return 0;
293   GMMenuPane pane(this,POPUP_SHRINKWRAP);
294   new GMMenuCommand(&pane,pane.tr("Play"),GMIconTheme::instance()->icon_play,GMPlayerManager::instance()->getMainWindow(),GMWindow::ID_PLAYPAUSEMENU);
295   new GMMenuCommand(&pane,pane.tr("Stop"),GMIconTheme::instance()->icon_stop,GMPlayerManager::instance()->getMainWindow(),GMWindow::ID_STOP);
296   new GMMenuCommand(&pane,pane.tr("Previous Track"),GMIconTheme::instance()->icon_prev,GMPlayerManager::instance()->getMainWindow(),GMWindow::ID_PREV);
297   new GMMenuCommand(&pane,pane.tr("Next Track"),GMIconTheme::instance()->icon_next,GMPlayerManager::instance()->getMainWindow(),GMWindow::ID_NEXT);
298   new FXMenuSeparator(&pane);
299   new GMMenuCommand(&pane,tr("Quit"),GMIconTheme::instance()->icon_exit,GMPlayerManager::instance()->getMainWindow(),GMWindow::ID_QUIT);
300   gm_set_window_cursor(&pane,getApp()->getDefaultCursor(DEF_ARROW_CURSOR));
301   pane.create();
302   ewmh_change_window_type(&pane,WINDOWTYPE_POPUP_MENU);
303   gm_run_popup_menu(&pane,event->root_x+1,event->root_y+1);
304   return 1;
305   }
306 
307 
onLeftBtnPress(FXObject *,FXSelector,void *)308 long GMTrayIcon::onLeftBtnPress(FXObject*,FXSelector,void*){
309   GMPlayerManager::instance()->cmd_toggle_shown();
310   return 1;
311   }
312 
onMiddleBtnPress(FXObject *,FXSelector,void *)313 long GMTrayIcon::onMiddleBtnPress(FXObject*,FXSelector,void*){
314   GMPlayerManager::instance()->cmd_playpause();
315   return 1;
316   }
317 
onMouseWheel(FXObject * sender,FXSelector sel,void * ptr)318 long GMTrayIcon::onMouseWheel(FXObject*sender,FXSelector sel,void*ptr){
319   GMPlayerManager::instance()->getMainWindow()->handle(sender,sel,ptr);
320   return 1;
321   }
322 
onQueryTip(FXObject * sender,FXSelector sel,void * ptr)323 long GMTrayIcon::onQueryTip(FXObject*sender,FXSelector sel,void* ptr){
324   if(FXTopWindow::onQueryTip(sender,sel,ptr)) return 1;
325   if((flags&FLAG_TIP) && !tip.empty() && !grabbed() ){
326     sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&tip);
327     return 1;
328     }
329   return 0;
330   }
331 
332 
onPaint(FXObject *,FXSelector,void *)333 long GMTrayIcon::onPaint(FXObject*,FXSelector,void*){
334   if (icon) {
335     FXDCWindow dc(this);
336     dc.drawIcon(icon,0,0);
337     }
338   return 1;
339   }
340 #endif