1 /*******************************************************************************
2 *                         Goggles Music Manager                                *
3 ********************************************************************************
4 *           Copyright (C) 2006-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 #include <unistd.h>
20 #include <signal.h>
21 #include <sys/wait.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <sys/fcntl.h>
25 
26 #include "gmdefs.h"
27 
28 #include <xincs.h>
29 #include <X11/XF86keysym.h>
30 
31 
32 #include "gmutils.h"
33 
34 #include "GMSession.h"
35 
36 #include "GMCover.h"
37 #include "GMTrack.h"
38 #include "GMTrackDatabase.h"
39 
40 #include "GMCoverManager.h"
41 
42 #ifdef HAVE_DBUS
43 #include "GMDBus.h"
44 #include "GMNotifyDaemon.h"
45 #include "GMSettingsDaemon.h"
46 #include "GMMediaPlayerService.h"
47 #endif
48 
49 #include <FXPNGIcon.h>
50 #include "GMApp.h"
51 #include "GMTrackList.h"
52 #include "GMTag.h"
53 #include "GMFilename.h"
54 
55 #include "GMTaskManager.h"
56 #include "GMList.h"
57 #include "GMTrackView.h"
58 #include "GMSourceView.h"
59 #include "GMSource.h"
60 #include "GMDatabaseSource.h"
61 #include "GMStreamSource.h"
62 #include "GMPlayListSource.h"
63 #include "GMPodcastSource.h"
64 
65 #include "GMFilter.h"
66 #include "GMFilterSource.h"
67 
68 
69 #include "GMPlayQueue.h"
70 #include "GMLocalSource.h"
71 
72 #include "GMIconTheme.h"
73 #include "GMPlayerManager.h"
74 #include "GMTrayIcon.h"
75 
76 #include "GMWindow.h"
77 #include "GMRemote.h"
78 
79 #include "GMCoverCache.h"
80 #include "GMAudioPlayer.h"
81 
82 #include "GMAudioScrobbler.h"
83 
84 
85 
86 #if APPLICATION_BETA_DB > 0
87 #define DATABASE_FILENAME "goggles_beta.db"
88 #else
89 #define DATABASE_FILENAME "goggles.db"
90 #endif
91 
92 
93 enum {
94   FIFO_STATUS_ERROR  = 0,
95   FIFO_STATUS_OWNER  = 1,
96   FIFO_STATUS_EXISTS = 2
97   };
98 
99 FXDEFMAP(GMPlayerManager) GMPlayerManagerMap[]={
100   FXMAPFUNC(SEL_TIMEOUT,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY,GMPlayerManager::onUpdTrackDisplay),
101   FXMAPFUNC(SEL_TIMEOUT,GMPlayerManager::ID_SLEEP_TIMER,GMPlayerManager::onCmdSleepTimer),
102   FXMAPFUNC(SEL_TIMEOUT,GMPlayerManager::ID_PLAY_NOTIFY,GMPlayerManager::onPlayNotify),
103   FXMAPFUNC(SEL_IO_READ,GMPlayerManager::ID_DDE_MESSAGE,GMPlayerManager::onDDEMessage),
104   FXMAPFUNC(SEL_CLOSE,GMPlayerManager::ID_WINDOW,GMPlayerManager::onCmdCloseWindow),
105   FXMAPFUNC(SEL_SIGNAL,GMPlayerManager::ID_CHILD,GMPlayerManager::onCmdChild),
106 
107   FXMAPFUNC(SEL_COMMAND,GMPlayerManager::ID_SCROBBLER,GMPlayerManager::onScrobblerError),
108   FXMAPFUNC(SEL_OPENED,GMPlayerManager::ID_SCROBBLER,GMPlayerManager::onScrobblerOpen),
109 
110 #ifdef HAVE_DBUS
111   FXMAPFUNC(SEL_KEYPRESS,GMPlayerManager::ID_GNOME_SETTINGS_DAEMON,GMPlayerManager::onCmdSettingsDaemon),
112 #endif
113   FXMAPFUNC(SEL_PLAYER_BOS,GMPlayerManager::ID_AUDIO_PLAYER,GMPlayerManager::onPlayerBOS),
114   FXMAPFUNC(SEL_PLAYER_EOS,GMPlayerManager::ID_AUDIO_PLAYER,GMPlayerManager::onPlayerEOS),
115   FXMAPFUNC(SEL_PLAYER_TIME,GMPlayerManager::ID_AUDIO_PLAYER,GMPlayerManager::onPlayerTime),
116   FXMAPFUNC(SEL_PLAYER_STATE,GMPlayerManager::ID_AUDIO_PLAYER,GMPlayerManager::onPlayerState),
117   FXMAPFUNC(SEL_PLAYER_META,GMPlayerManager::ID_AUDIO_PLAYER,GMPlayerManager::onPlayerMeta),
118   FXMAPFUNC(SEL_PLAYER_ERROR,GMPlayerManager::ID_AUDIO_PLAYER,GMPlayerManager::onPlayerError),
119   FXMAPFUNC(SEL_PLAYER_VOLUME,GMPlayerManager::ID_AUDIO_PLAYER,GMPlayerManager::onPlayerVolume),
120 
121   FXMAPFUNC(SEL_COMMAND,GMPlayerManager::ID_CANCEL_TASK,GMPlayerManager::onCancelTask),
122 
123   FXMAPFUNC(SEL_TASK_STATUS,GMPlayerManager::ID_TASKMANAGER,GMPlayerManager::onTaskManagerStatus),
124   FXMAPFUNC(SEL_TASK_IDLE,GMPlayerManager::ID_TASKMANAGER,GMPlayerManager::onTaskManagerIdle),
125   FXMAPFUNC(SEL_TASK_RUNNING,GMPlayerManager::ID_TASKMANAGER,GMPlayerManager::onTaskManagerRunning),
126 
127   FXMAPFUNC(SEL_TIMEOUT,GMPlayerManager::ID_TASKMANAGER_SHUTDOWN,GMPlayerManager::onTaskManagerShutdown),
128 
129   FXMAPFUNC(SEL_TASK_COMPLETED,GMPlayerManager::ID_IMPORT_TASK,GMPlayerManager::onImportTaskCompleted),
130   FXMAPFUNC(SEL_TASK_CANCELLED,GMPlayerManager::ID_IMPORT_TASK,GMPlayerManager::onImportTaskCompleted),
131 #ifdef HAVE_SESSION
132   FXMAPFUNC(SEL_SESSION_CLOSED,GMPlayerManager::ID_SESSION_MANAGER,GMPlayerManager::onCmdQuit)
133 #endif
134   };
135 
FXIMPLEMENT(GMPlayerManager,FXObject,GMPlayerManagerMap,ARRAYNUMBER (GMPlayerManagerMap))136 FXIMPLEMENT(GMPlayerManager,FXObject,GMPlayerManagerMap,ARRAYNUMBER(GMPlayerManagerMap))
137 
138 #ifdef HAVE_DBUS
139 
140 DBusHandlerResult dbus_systembus_filter(DBusConnection *,DBusMessage * msg,void * data){
141   FXTRACE((80,"-----dbus_systembus_filter-------\n"));
142   FXTRACE((80,"path: %s\n",dbus_message_get_path(msg)));
143   FXTRACE((80,"member: \"%s\"\n",dbus_message_get_member(msg)));
144   FXTRACE((80,"interface: %s\n",dbus_message_get_interface(msg)));
145   FXTRACE((80,"sender: %s\n",dbus_message_get_sender(msg)));
146 
147   GMPlayerManager * p = static_cast<GMPlayerManager*>(data);
148   if (dbus_message_has_path(msg,"/org/freedesktop/NetworkManager")){
149     if (dbus_message_is_signal(msg,"org.freedesktop.NetworkManager","StateChanged") ||
150         dbus_message_is_signal(msg,"org.freedesktop.NetworkManager","StateChange")) {
151 
152       FXuint state=0;
153       enum {
154 /*
155         // Network Manager 0.7 / 0.8
156 
157         NM7_STATE_UNKNOWN          =  0,
158         NM7_STATE_ASLEEP           =  1,
159         NM7_STATE_CONNECTING       =  2,
160         NM7_STATE_CONNECTED        =  3,
161         NM7_STATE_DISCONNECTED     =  4,
162 
163         // Network Manager 0.9
164         NM9_STATE_UNKNOWN          =  0,
165         NM9_STATE_ASLEEP           = 10,
166         NM9_STATE_DISCONNECTED     = 20,
167         NM9_STATE_DISCONNECTING    = 30,
168         NM9_STATE_CONNECTING       = 40,
169         NM9_STATE_CONNECTED_LOCAL  = 50,
170         NM9_STATE_CONNECTED_SITE   = 60,
171         NM9_STATE_CONNECTED_GLOBAL = 70,
172 
173 */
174         NM7_STATE_CONNECTED        =  3,
175         NM9_STATE_CONNECTED_GLOBAL = 70,
176         };
177 
178       if (dbus_message_get_args(msg,nullptr,DBUS_TYPE_UINT32,&state,DBUS_TYPE_INVALID)) {
179         if (p->getAudioScrobbler() && (state==NM7_STATE_CONNECTED ||
180                                        state==NM9_STATE_CONNECTED_GLOBAL))
181           p->getAudioScrobbler()->nudge();
182         }
183       return DBUS_HANDLER_RESULT_HANDLED;
184       }
185     }
186   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
187   }
188 #endif
189 
190 
191 
onPlayNotify(FXObject *,FXSelector,void *)192 long GMPlayerManager::onPlayNotify(FXObject*,FXSelector,void*){
193 
194   if (!trackinfo.title.empty() && !trackinfo.artist.empty()) {
195 
196     display_track_notification();
197 
198     if (scrobbler) scrobbler->nowplaying(trackinfo);
199     }
200   return 0;
201   }
202 
onUpdTrackDisplay(FXObject *,FXSelector,void *)203 long GMPlayerManager::onUpdTrackDisplay(FXObject*,FXSelector,void*){
204   //fxmessage("onUpdTrackDisplay\n");
205   update_track_display();
206   getTrackView()->showCurrent();
207   return 0;
208   }
209 
210 
211 /* Stop Playback! */
onCmdSleepTimer(FXObject *,FXSelector,void *)212 long GMPlayerManager::onCmdSleepTimer(FXObject*,FXSelector,void*){
213   if (playing()) {
214     stop();
215     }
216   return 1;
217   }
218 
219 
onDDEMessage(FXObject *,FXSelector,void *)220 long GMPlayerManager::onDDEMessage(FXObject*,FXSelector,void*){
221   FXString cmd;
222   FXchar buffer[1024];
223   int nread=fifo.readBlock(buffer,1024);
224   if (nread>0) {
225     cmd=FXString(buffer,nread);
226     cmd.trim();
227     if (cmd=="--previous") cmd_prev();
228     else if (cmd=="--play") cmd_play();
229     else if (cmd=="--play-pause") cmd_playpause();
230     else if (cmd=="--pause") cmd_pause();
231     else if (cmd=="--next") cmd_next();
232     else if (cmd=="--stop") cmd_stop();
233     else if (cmd=="--toggle-shown") cmd_toggle_shown();
234     else if (cmd=="--now-playing") display_track_notification();
235     else if (cmd=="--raise") cmd_raise();
236     else if (cmd=="--update-podcasts") cmd_update_podcasts();
237     else {
238       if (gm_is_local_file(cmd)) {
239         if (!FXPath::isAbsolute(cmd)) {
240           cmd=FXPath::absolute(cmd);
241           }
242         }
243       if (!cmd.empty())
244         open(cmd);
245       }
246     }
247   return 1;
248   }
249 
250 
251 
252 GMPlayerManager * GMPlayerManager::myself = nullptr;
253 
instance()254 GMPlayerManager* GMPlayerManager::instance() {
255   return myself;
256   }
257 
258 
259 /// Constructor
GMPlayerManager()260 GMPlayerManager::GMPlayerManager() {
261   FXASSERT(myself==NULL);
262   myself=this;
263   }
264 
265 
266 /// Destructor
~GMPlayerManager()267 GMPlayerManager::~GMPlayerManager() {
268 
269   /// Remove Signal Handlers
270   if (application) {
271     application->removeSignal(SIGINT);
272     application->removeSignal(SIGQUIT);
273     application->removeSignal(SIGTERM);
274     application->removeSignal(SIGHUP);
275     application->removeSignal(SIGPIPE);
276     application->removeSignal(SIGCHLD);
277     }
278 
279   /// Cleanup fifo crap
280   if (fifo.isOpen())
281     fifo.close();
282   if (!fifofilename.empty())
283     FXFile::remove(fifofilename);
284 
285   delete scrobbler;
286 
287   delete database;
288 
289 #ifdef HAVE_DBUS
290   delete sessionbus;
291   delete systembus;
292 #endif
293 
294   delete player;
295   delete taskmanager;
296 
297   myself=nullptr;
298 
299 #ifdef HAVE_SESSION
300   delete session;
301 #endif
302   delete application;
303   }
304 
305 
306 
307 
init_fifo(int & argc,char ** argv)308 FXint GMPlayerManager::init_fifo(int& argc,char** argv){
309   FXString fifodir = FXSystem::getHomeDirectory() + PATHSEPSTRING + ".goggles";
310   FXStat info;
311 
312   if ( (!FXStat::exists(fifodir) && !FXDir::create(fifodir) ) || !FXStat::isDirectory(fifodir) ) {
313     FXMessageBox::error(application,MBOX_OK,"Goggles Music Manager",fxtrformat("Unable to create directory %s\n"),fifodir.text());
314     return FIFO_STATUS_ERROR;
315     }
316 
317   fifofilename = fifodir + PATHSEPSTRING + "gmm.dde";
318 
319   /// Find existing fifo
320   if (FXStat::statFile(fifofilename,info)) {
321 
322     /// File exists, but it's not a fifo... try removing it
323     if (!info.isFifo() && !FXFile::remove(fifofilename)) {
324       fifofilename=FXString::null;
325       return FIFO_STATUS_ERROR;
326       }
327 
328     if (fifo.open(fifofilename,FXIO::WriteOnly|FXIO::NonBlocking)){
329       FXString commandline;
330 
331       for (FXint i=1;i<argc;i++){
332         commandline+=argv[i];
333         }
334 
335       /// Try raising the window
336       if (commandline.empty())
337         commandline="--raise";
338 
339       /// if write failed or command line was empty, user probably tries to start up normally
340       /// and fifo may be stale
341       if (fifo.writeBlock(commandline.text(),commandline.length())) {
342         fifofilename=FXString::null;
343         return FIFO_STATUS_EXISTS;
344         }
345 
346       /// Most likely left behind by crashed music manager
347       fifo.close();
348       }
349 
350     if (!FXFile::remove(fifofilename))
351       return FIFO_STATUS_OWNER;
352     }
353 
354   /// Create the fifo
355   if (mkfifo(fifofilename.text(),S_IWUSR|S_IRUSR)!=0){
356       fifofilename=FXString::null;
357       return FIFO_STATUS_OWNER;
358       }
359 
360   /// Try open the fifo
361   fifo.open(fifofilename,FXIO::Reading|FXIO::WriteOnly,FXIO::OwnerWrite);
362 
363   /// Close fifo on execute or remove fifo if we couldn't open it...
364   if (fifo.isOpen()) {
365     ap_set_closeonexec(fifo.handle());
366     //fcntl(fifo.handle(),F_SETFD,FD_CLOEXEC);
367     }
368   else {
369     FXFile::remove(fifofilename);
370     fifofilename=FXString::null;
371     }
372 
373   return FIFO_STATUS_OWNER;
374   }
375 
376 
377 
378 
init_sources()379 FXbool GMPlayerManager::init_sources() {
380 
381   // Main Database
382   database      = new GMTrackDatabase;
383   covermanager  = new GMCoverManager;
384 
385   // Make sure we can open it.
386   if (!init_database(database)) {
387     return false;
388     }
389 
390   /// Create the main database source
391   sources.append(new GMDatabaseSource(database));
392 
393   /// Load User Queries
394   GMFilterSource::init(database,sources);
395 
396   /// Create Play List Sources
397   FXIntList playlists;
398   if (database->listPlaylists(playlists)) {
399     for (FXint i=0;i<playlists.no();i++) {
400       sources.append(new GMPlayListSource(database,playlists[i]));
401       }
402     }
403 
404   /// Init Play Queue
405   if (preferences.play_from_queue) {
406     queue = new GMPlayQueue(database);
407     sources.append(queue);
408     }
409 
410   /// Internet Streams
411   sources.append(new GMStreamSource(database));
412 
413   /// File System
414   sources.append(new GMLocalSource());
415 
416   /// Podcast Source
417   podcast = new GMPodcastSource(database);
418   sources.append(podcast);
419 
420   /// Load Settings
421   for (FXint i=0;i<sources.no();i++) {
422     sources[i]->load(application->reg());
423     }
424 
425   return true;
426   }
427 
428 
getDatabaseSource() const429 GMDatabaseSource * GMPlayerManager::getDatabaseSource() const {
430   return dynamic_cast<GMDatabaseSource*>(sources[0]);
431   }
432 
433 
434 
setPlayQueue(FXbool enable)435 void GMPlayerManager::setPlayQueue(FXbool enable) {
436   preferences.play_from_queue=enable;
437   if (enable) {
438     if (!queue) {
439       queue = new GMPlayQueue(database);
440       sources.append(queue);
441       GMPlayerManager::instance()->getSourceView()->refresh();
442       }
443     }
444   else {
445     if (queue) {
446       removeSource(queue);
447       queue=nullptr;
448       }
449     }
450   }
451 
452 
removeSource(GMSource * src)453 void GMPlayerManager::removeSource(GMSource * src) {
454 
455   sources.remove(src);
456 
457   GMPlayerManager::instance()->getSourceView()->refresh();
458 
459   if (application->hasTimeout(src,GMSource::ID_TRACK_PLAYED))
460     application->removeTimeout(src,GMSource::ID_TRACK_PLAYED);
461 
462   if (src==source) {
463     source->resetCurrent();
464     source=nullptr;
465     }
466 
467   delete src;
468   }
469 
470 
471 
init_window(FXbool wizard)472 void GMPlayerManager::init_window(FXbool wizard) {
473   const FXint           argc = application->getArgc();
474   const FXchar *const * argv = application->getArgv();
475 
476   /// Create Main Window
477   mainwindow = new GMWindow(application,this,ID_WINDOW);
478   mainwindow->create();
479 
480   // Register Global Hotkeys
481   register_global_hotkeys();
482 
483   /// Handle interrupt to save stuff nicely
484   application->addSignal(SIGINT,mainwindow,GMWindow::ID_QUIT);
485   application->addSignal(SIGQUIT,mainwindow,GMWindow::ID_QUIT);
486   application->addSignal(SIGTERM,mainwindow,GMWindow::ID_QUIT);
487   application->addSignal(SIGHUP,mainwindow,GMWindow::ID_QUIT);
488   application->addSignal(SIGPIPE,mainwindow,GMWindow::ID_QUIT);
489 
490   /// Create Tooltip Window
491   FXToolTip * tooltip = new FXToolTip(application,TOOLTIP_VARIABLE);
492   tooltip->create();
493   ewmh_change_window_type(tooltip,WINDOWTYPE_TOOLTIP);
494 
495   if (database->isEmpty() && wizard) {
496     cleanSourceSettings();
497     mainwindow->init(SHOW_WIZARD);
498     }
499   else {
500     FXbool start_as_tray=false;
501     if (preferences.gui_tray_icon_disabled==false){
502       for(FXint i=1;i<argc;i++) {
503         if (comparecase(argv[i],"--tray")==0){
504           start_as_tray=true;
505           preferences.gui_tray_icon=true;
506           break;
507           }
508         }
509       }
510     if (start_as_tray)
511       mainwindow->init(SHOW_TRAY);
512     else
513       mainwindow->init(SHOW_NORMAL);
514     }
515   }
516 
517 
get_cmdline_url(int & argc,char ** argv)518 static FXString get_cmdline_url(int& argc,char** argv) {
519   FXString url;
520   for (FXint i=1;i<argc;i++) {
521     if (argv[i][0]!='-') {
522       url=argv[i];
523       }
524     }
525   return url;
526   }
527 
get_cmdline_update_podcasts(int & argc,char ** argv)528 static FXbool get_cmdline_update_podcasts(int & argc, char ** argv){
529   for (FXint i=1;i<argc;i++) {
530     if (compare(argv[i],"--update-podcasts")==0)
531       return true;
532     }
533   return false;
534   }
535 
536 
init_configuration()537 void GMPlayerManager::init_configuration() {
538   FXString xdg_config_home = GMApp::getConfigDirectory(true);
539   FXString xdg_data_home = GMApp::getDataDirectory(true);
540 
541   /// Check if we need to migrate old files to new directory.
542   FXString newbase = xdg_data_home+PATHSEPSTRING;
543   FXString oldbase = FXSystem::getHomeDirectory()+PATHSEPSTRING ".goggles" PATHSEPSTRING;
544 
545   if ( (FXStat::exists(newbase+DATABASE_FILENAME)==false) && FXStat::exists(oldbase+DATABASE_FILENAME) ) {
546 
547 
548     /// Move database (for now disabled untill we have a upgrade path)
549     /// FXFile::moveFiles(oldbase+DATABASE_FILENAME,newbase+DATABASE_FILENAME);
550     /// FXFile::moveFiles(oldbase+"scrobbler.cache",newbase+"scrobbler.cache");
551 
552     newbase = xdg_config_home+PATHSEPSTRING;
553 
554     /// Let's move
555     FXString oldconfig = FXSystem::getHomeDirectory()+PATHSEPSTRING ".foxrc" PATHSEPSTRING "musicmanager";
556 
557     /// Copy Settings
558     if (FXStat::exists(oldconfig))
559       FXFile::copyFiles(oldconfig,newbase+"settings.rc");
560 
561     ///FIXME enable this when we release 0.12. Remove the old config directory.
562     //FXFile::removeFiles(FXSystem::getHomeDirectory()+PATHSEPSTRING+".goggles",true);
563     }
564   }
565 
566 
567 
568 #ifdef HAVE_DBUS
init_dbus(int & argc,char ** argv)569 FXbool GMPlayerManager::init_dbus(int & argc,char**argv) {
570 
571   sessionbus=new GMDBus();
572   systembus =new GMDBus();
573 
574   FXASSERT(sessionbus);
575   FXASSERT(systembus);
576 
577   if (!sessionbus->open(DBUS_BUS_SESSION) || !sessionbus->connected()) {
578     FXMessageBox::warning(application,MBOX_OK,"Goggles Music Manager",fxtr("Session bus not available. All features requiring dbus are disabled."));
579     delete sessionbus;
580     sessionbus=nullptr;
581     }
582   else {
583 
584     mpris2=new GMMediaPlayerService2(sessionbus);
585     FXint result = mpris2->create();
586     switch(result) {
587       case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
588         // success
589         break;
590       case DBUS_REQUEST_NAME_REPLY_EXISTS:
591         if (argc>1 && strlen(argv[1])>0)
592           mpris2->request(argv[1]);
593         delete mpris2;
594         return false;
595         break;
596       default:
597         FXMessageBox::warning(application,MBOX_OK,"Goggles Music Manager",fxtr("Session Bus not available. All features requiring sessionbus are disabled."));
598         delete mpris2;
599         mpris2=nullptr;
600         delete sessionbus;
601         sessionbus=nullptr;
602         return true;
603         break;
604       }
605 
606     if (!systembus->open(DBUS_BUS_SYSTEM) || !systembus->connected()) {
607       delete systembus;
608       systembus=nullptr;
609       }
610 
611     if (systembus && !dbus_connection_add_filter(systembus->connection(),dbus_systembus_filter,this,nullptr)){
612       delete systembus;
613       systembus=nullptr;
614       }
615 
616     if (systembus) {
617       DBusError error;
618       dbus_error_init(&error);
619       dbus_bus_add_match(systembus->connection(),"type='signal',path='/org/freedesktop/NetworkManager',interface='org.freedesktop.NetworkManager',member='StateChanged'",&error);
620       if (dbus_error_is_set(&error)) {
621         dbus_error_free(&error);
622         delete systembus;
623         systembus=nullptr;
624         }
625       }
626 
627     if (systembus) {
628       DBusError error;
629       dbus_error_init(&error);
630       dbus_bus_add_match(systembus->connection(),"type='signal',path='/org/freedesktop/NetworkManager',interface='org.freedesktop.NetworkManager',member='StateChange'",&error);
631       if (dbus_error_is_set(&error)) {
632         dbus_error_free(&error);
633         delete systembus;
634         systembus=nullptr;
635         }
636       }
637     }
638   return true;
639   }
640 
641 #endif
642 
643 
644 
run(int & argc,char ** argv)645 FXint GMPlayerManager::run(int& argc,char** argv) {
646   FXint result;
647 
648   /// Initialize pre-thread libraries.
649   GMTag::init();
650 
651   // Initialize Crypto Support
652   if (!ap_init_crypto()) {
653     fxwarning("gogglesmm: failed to initialize ssl support\n");
654     return 1;
655     }
656 
657   /// Setup and migrate old config files.
658   init_configuration();
659 
660   /// Create Application so we can do things like popup dialogs and such
661   application = new GMApp();
662   application->init(argc,argv);
663   application->create();
664 
665   /// Keep track of child processes
666   application->addSignal(SIGCHLD,this,GMPlayerManager::ID_CHILD);
667 
668   /// Make sure we're threadsafe
669   if (GMDatabase::threadsafe()==0) {
670     FXMessageBox::error(application,MBOX_OK,"Goggles Music Manager",
671       fxtrformat("A non threadsafe version of SQLite (%s) is being used.\n"
672       "Goggles Music Manager requires a threadsafe SQLite.\n"
673       "Please upgrade your SQLite installation."),GMDatabase::version());
674     return 1;
675     }
676 
677   /// Give warning when PNG is not compiled in...
678   if (FXPNGIcon::supported==false) {
679     FXMessageBox::warning(application,MBOX_OK,"Goggles Music Manager",
680       fxtr("For some reason the FOX library was compiled without PNG support.\n"
681       "In order to show all icons, Goggles Music Manager requires PNG\n"
682       "support in the FOX library. If you've compiled FOX yourself, most\n"
683       "likely the libpng header files were not installed on your system."));
684     }
685 
686   taskmanager = new GMTaskManager(this,ID_TASKMANAGER);
687 
688 #ifdef HAVE_DBUS
689   /// Connect to the dbus...
690   if (!init_dbus(argc,argv))
691     return 0;
692 
693   /// Fallback to fifo method to check for existing instance
694   if (sessionbus==nullptr) {
695     result = init_fifo(argc,argv);
696     if (result==FIFO_STATUS_ERROR)
697       return 1;
698     else if (result==FIFO_STATUS_EXISTS)
699       return 0;
700     }
701 
702 #else
703   result = init_fifo(argc,argv);
704   if (result==FIFO_STATUS_ERROR)
705     return 1;
706   else if (result==FIFO_STATUS_EXISTS)
707     return 0;
708 #endif
709 
710 
711   /// Load Application Preferences
712   preferences.load(application->reg());
713 
714   /// Check for overrides on the command line
715   preferences.parseCommandLine(argc,argv);
716 
717   /// Open Database and initialize all sources.
718   if (!init_sources())
719     return false;
720 
721   /// Everything opened succesfully... now create the GUI
722   player = new GMAudioPlayer(application,this,ID_AUDIO_PLAYER);
723 
724   /// Open Audio Device if needed.
725   player->init();
726   player->loadSettings();
727 
728   /// FIXME move to player->loadSettings
729   switch(preferences.play_replaygain){
730     case 0: player->setReplayGain(ReplayGainOff); break;
731     case 1: player->setReplayGain(ReplayGainTrack); break;
732     case 2: player->setReplayGain(ReplayGainAlbum); break;
733     }
734 
735   if (preferences.play_crossfade)
736     player->setCrossFade(preferences.play_crossfade_duration);
737 
738 
739   /// Receive events from fifo
740   if (fifo.isOpen()) {
741     application->addInput(this,GMPlayerManager::ID_DDE_MESSAGE,fifo.handle(),INPUT_READ);
742     }
743 
744   FXString url = get_cmdline_url(argc,argv);
745 
746   /// Show user interface
747   init_window(url.empty());
748 
749 #ifdef HAVE_DBUS
750   if (sessionbus) {
751     /// Integrate Dbus into FOX Event Loop
752     GMDBus::initEventLoop();
753     }
754 #endif
755 
756   /// Start Services
757   scrobbler    = new GMAudioScrobbler(this,ID_SCROBBLER);
758 #ifdef HAVE_DBUS
759   if (sessionbus) {
760     notifydaemon = new GMNotifyDaemon(sessionbus);
761 
762     // KDE5 comes with mpris plugin on the toolbar, no need for
763     // tray icon
764     if (gm_desktop_session()==DESKTOP_SESSION_KDE_PLASMA) {
765       preferences.gui_tray_icon_disabled=false;
766       }
767 
768     /// Grab Media Player Keys
769     gsd = new GMSettingsDaemon(sessionbus,this,ID_GNOME_SETTINGS_DAEMON);
770     gsd->GrabMediaPlayerKeys("gogglesmm");
771 
772     update_mpris();
773 
774     notifydaemon->init();
775     }
776 #endif
777 
778 #ifdef HAVE_SESSION
779   /// Connect to the session manager
780   session = new GMSession(application,this,GMPlayerManager::ID_SESSION_MANAGER);
781   session->init(argc,argv);
782 #endif
783 
784   /// Open url from command line
785   if (!url.empty())
786     open(url);
787 
788 #ifndef HAVE_DBUS
789   update_tray_icon();
790 #endif
791 
792   // Update Podcasts on startup if desired.
793   if (get_cmdline_update_podcasts(argc,argv))
794     cmd_update_podcasts();
795 
796 
797   /// Run the application
798   return application->run();
799   }
800 
801 
exit()802 void GMPlayerManager::exit() {
803 
804   player->saveSettings();
805   player->stop();
806   player->exit();
807 
808   /// Save settings
809   for (FXint i=0;i<sources.no();i++)
810     sources[i]->save(application->reg());
811 
812   application->removeTimeout(this,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY);
813 
814   application->removeTimeout(this,GMPlayerManager::ID_SLEEP_TIMER);
815 
816   application->removeTimeout(this,GMPlayerManager::ID_PLAY_NOTIFY);
817 
818   application->removeTimeout(this,GMPlayerManager::ID_TASKMANAGER_SHUTDOWN);
819 
820   application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
821 
822   preferences.save(application->reg());
823 
824   if (scrobbler) scrobbler->shutdown();
825   if (taskmanager) taskmanager->shutdown();
826 
827 #ifdef HAVE_DBUS
828   if (sessionbus) {
829 
830     gsd->ReleaseMediaPlayerKeys("gogglesmm");
831     delete gsd;
832     gsd=nullptr;
833 
834     if (notifydaemon) {
835       notifydaemon->close();
836       delete notifydaemon;
837       notifydaemon=nullptr;
838       }
839 
840     if (mpris1) delete mpris1;
841     if (mpris2) delete mpris2;
842     }
843   if (systembus) {
844     dbus_connection_remove_filter(systembus->connection(),dbus_systembus_filter,this);
845     }
846 #endif
847 
848   // Destroy shared datastructures between playlists
849   getDatabaseSource()->shutdown();
850 
851   /// Delete Sources
852   for (FXint i=0;i<sources.no();i++)
853     delete sources[i];
854 
855   delete covermanager;
856 
857   ap_free_crypto();
858 
859   application->exit(0);
860   }
861 
862 #ifdef HAVE_DBUS
update_mpris()863 void GMPlayerManager::update_mpris() {
864   if (mpris1) {
865     if (!sessionbus || !preferences.dbus_mpris1){
866       delete mpris1;
867       mpris1=nullptr;
868       }
869     }
870 
871   if (!mpris1 && preferences.dbus_mpris1 && sessionbus)
872     mpris1=new GMMediaPlayerService1(sessionbus);
873   }
874 #endif
875 
876 
877 
update_tray_icon()878 void GMPlayerManager::update_tray_icon() {
879   if (trayicon){
880     if (!preferences.gui_tray_icon || preferences.gui_tray_icon_disabled) {
881       delete trayicon;
882       trayicon=nullptr;
883       }
884     }
885   else {
886     if (preferences.gui_tray_icon && !preferences.gui_tray_icon_disabled) {
887       trayicon = new GMTrayIcon(application);
888       trayicon->create();
889       }
890     }
891   }
892 
893 
getTrackView() const894 GMTrackView * GMPlayerManager::getTrackView() const {
895   return mainwindow->trackview;
896   }
897 
getSourceView() const898 GMSourceView * GMPlayerManager::getSourceView() const {
899   return mainwindow->sourceview;
900   }
901 
getDatabaseFilename() const902 FXString GMPlayerManager::getDatabaseFilename() const {
903   return GMApp::getDataDirectory() + PATHSEPSTRING + DATABASE_FILENAME;
904   }
905 
init_database(GMTrackDatabase * db)906 FXbool GMPlayerManager::init_database(GMTrackDatabase * db){
907   FXString databasefilename = getDatabaseFilename();
908 
909   if (FXStat::exists(databasefilename) && (!FXStat::isWritable(databasefilename) || !FXStat::isReadable(databasefilename)))
910     return false;
911 
912   /// Init Database
913   if (!db->init(databasefilename)) {
914     return false;
915     }
916 
917   return true;
918   }
919 
hasSourceWithKey(const FXString & key) const920 FXbool GMPlayerManager::hasSourceWithKey(const FXString & key) const{
921   for (FXint i=0;i<sources.no();i++){
922     if (key==sources[i]->settingKey())
923       return true;
924     }
925   return false;
926   }
927 
928 
cleanSourceSettings()929 void GMPlayerManager::cleanSourceSettings() {
930   FXint s;
931   FXStringList keys;
932 
933   for (s=0;s<application->reg().no();s++){
934     if (!application->reg().empty(s) && comparecase(application->reg().key(s),"database",8)==0){
935       if (!hasSourceWithKey(application->reg().key(s))) {
936         keys.append(application->reg().key(s));
937         }
938       }
939     }
940 
941   for (s=0;s<keys.no();s++){
942     application->reg().deleteSection(keys[s]);
943     }
944   }
945 
removePlayListSources()946 void GMPlayerManager::removePlayListSources(){
947   for (FXint i=sources.no()-1;i>=0;i--){
948     if (sources[i]->getType()==SOURCE_DATABASE_PLAYLIST) {
949       FXApp::instance()->reg().deleteSection(sources[i]->settingKey().text());
950       GMSource * src = sources[i];
951       sources.erase(i);
952       delete src;
953       }
954     }
955   }
956 
957 
open(const FXString & url)958 void GMPlayerManager::open(const FXString & url) {
959   FXint id;
960 
961   if (source) {
962     application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
963     source->resetCurrent();
964     source=nullptr;
965     }
966 
967   trackinfo.url = url;
968   if (gm_is_local_file(url) && sources[0]->hasTrack(url,id)) {
969     source       = sources[0];
970     trackinfoset = source->getTrack(trackinfo);
971     sources[0]->setCurrentTrack(id);
972     }
973   else {
974     trackinfoset=false;
975     getTrackView()->setActive(-1);
976     }
977 
978   player->open(url,true);
979   }
980 
981 
982 
playItem(FXuint whence)983 void GMPlayerManager::playItem(FXuint whence) {
984   FXint track=-1;
985 
986   // Any scheduled stops should be cancelled
987   scheduled_stop = false;
988 
989   /// Remove Current Timeout
990   if (source) {
991     application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
992     source->resetCurrent();
993     source=nullptr;
994     }
995 
996   if (queue) {
997     switch(whence) {
998       case TRACK_CURRENT :  track = queue->getCurrent();
999                             if (track==-1 && queue->canPlaySource(getTrackView()->getSource())) {
1000                               track = getTrackView()->getCurrent();
1001                               }
1002                            break;
1003       case TRACK_NEXT    : track = queue->getNext(); break;
1004       default            : FXASSERT(0); break;
1005       };
1006     if (track!=-1) {
1007       source = queue;
1008       }
1009     }
1010   else {
1011     if (track==-1) {
1012       switch(whence) {
1013         case TRACK_CURRENT : track = getTrackView()->getCurrent(); break;
1014         case TRACK_NEXT    : track = getTrackView()->getNext(true); break;
1015         case TRACK_PREVIOUS: track = getTrackView()->getPrevious(); break;
1016         default            : FXASSERT(0); break;
1017         };
1018       if (track!=-1) {
1019         source = getTrackView()->getSource();
1020         getTrackView()->setActive(track);
1021         }
1022       }
1023     }
1024 
1025   if (source) {
1026     trackinfoset = source->getTrack(trackinfo);
1027     player->open(trackinfo.url,true);
1028     }
1029   else {
1030     player->stop();
1031     }
1032   }
1033 
1034 
1035 
1036 
stop(FXbool)1037 void GMPlayerManager::stop(FXbool /*force_close*/) {
1038 
1039   // Any scheduled stops should be cancelled
1040   scheduled_stop = false;
1041 
1042   /// Reset Source
1043   if (source) {
1044     application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
1045     source->resetCurrent();
1046     source=nullptr;
1047     }
1048 
1049   player->stop();
1050 
1051   // We don't call reset_track_display() here, since we'll do that when we get the
1052   // PLAYER_STATE_STOPPED signal from the audio player.
1053   }
1054 
1055 
1056 
seekTime(FXint time)1057 void GMPlayerManager::seekTime(FXint time) {
1058   application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
1059   player->setPosition(time);
1060   has_seeked=true;
1061   }
1062 
seek(FXdouble pos)1063 void GMPlayerManager::seek(FXdouble pos) {
1064   application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
1065   player->seek(pos);
1066   has_seeked=true;
1067   }
1068 
1069 
pause()1070 void GMPlayerManager::pause() {
1071 
1072   // Any scheduled stops should be cancelled
1073   scheduled_stop = false;
1074 
1075   player->pause();
1076 
1077   if (application->hasTimeout(source,GMSource::ID_TRACK_PLAYED)) {
1078     count_track_remaining = application->remainingTimeout(source,GMSource::ID_TRACK_PLAYED);
1079     application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
1080     }
1081   else {
1082     count_track_remaining=0;
1083     }
1084   }
1085 
unpause()1086 void GMPlayerManager::unpause() {
1087   player->pause();
1088  }
1089 
playlist_empty()1090 FXbool GMPlayerManager::playlist_empty() {
1091   if (getTrackView()->hasTracks()) return false;
1092   if (preferences.play_repeat!=REPEAT_OFF) return false;
1093   if (getTrackView()->getNext()!=-1) return false;
1094   return true;
1095   }
1096 
notify_playback_finished()1097 void GMPlayerManager::notify_playback_finished() {
1098   /*
1099     The current track is still playing (and about to be finished) so don't call reset_track_display if
1100     there is nothing to play anymore. It will eventually be called by the PLAYER_STATE_STOPPED signal.
1101   */
1102   FXString errormsg;
1103   FXString filename;
1104   FXint track=-1;
1105 
1106   // Check whether playback should be stopped and reset flag
1107   FXbool stop_playback = scheduled_stop;
1108   scheduled_stop=false;
1109 
1110   if (queue) {
1111 
1112     /// Reset Source
1113     if (source!=queue) {
1114      source->resetCurrent();
1115      source=nullptr;
1116      }
1117 
1118     /// Nothing else to do, mark current as played
1119     if (stop_playback) {
1120       queue->getNext();
1121       return;
1122       }
1123 
1124     //FIXME handle stop_playback
1125     track = queue->getNext();
1126     if (track==-1) {
1127       source = nullptr;
1128 
1129       if (getTrackView()->getSource()==queue)
1130         getTrackView()->refresh();
1131 
1132      //reset_track_display();
1133      return;
1134      }
1135     else {
1136       source = queue;
1137       }
1138     trackinfoset = queue->getTrack(trackinfo);
1139     }
1140   else {
1141 
1142     /// Don't play anything if we didn't play anything from the library
1143     if (source==nullptr)
1144       return;
1145 
1146     /// Can we just start playback without user interaction
1147     if (!getTrackView()->getSource()->autoPlay() || stop_playback) {
1148 
1149       /// Reset Source
1150       if (source) {
1151         source->resetCurrent();
1152         source=nullptr;
1153         }
1154 
1155       if(preferences.play_repeat!=REPEAT_TRACK) {
1156         track = getTrackView()->getNext();
1157         if (track!=-1) getTrackView()->setCurrent(track);
1158         }
1159       return;
1160       }
1161 
1162     if (source) {
1163       source->resetCurrent();
1164       source=nullptr;
1165       }
1166 
1167     if (preferences.play_repeat==REPEAT_TRACK)
1168       track = getTrackView()->getActive();
1169     else
1170       track = getTrackView()->getNext();
1171 
1172     if (track==-1) {
1173       //reset_track_display();
1174       return;
1175       }
1176 
1177     // FIXME only does source->markCurrent
1178     getTrackView()->setActive(track,false);
1179 
1180     source = getTrackView()->getSource();
1181     trackinfoset = source->getTrack(trackinfo);
1182     }
1183   player->open(trackinfo.url,false);
1184   }
1185 
playing() const1186 FXbool GMPlayerManager::playing() const {
1187   return player->playing() ;
1188   }
1189 
1190 
reset_track_display()1191 void GMPlayerManager::reset_track_display() {
1192   FXTRACE((51,"GMPlayerManager::reset_track_display()\n"));
1193 
1194   /// Reset the cover
1195   covermanager->clear();
1196 
1197   /// Reset Main Window
1198   mainwindow->reset();
1199 
1200   if (trayicon) trayicon->reset();
1201 
1202   /// Reset Active Track
1203   getTrackView()->setActive(-1);
1204 
1205   /// Remove Notify
1206   application->removeTimeout(this,ID_PLAY_NOTIFY);
1207 
1208 #ifdef HAVE_DBUS
1209   if (notifydaemon && preferences.dbus_notify_daemon)
1210     notifydaemon->reset();
1211 #endif
1212 
1213   /// Update View in queue play.
1214   if (queue) {
1215     getSourceView()->refresh(queue);
1216     if (getTrackView()->getSource()==queue) {
1217       getTrackView()->refresh();
1218       }
1219     }
1220 
1221   /// Schedule a GUI update
1222   application->refresh();
1223   }
1224 
1225 
setStatus(const FXString & text)1226 void GMPlayerManager::setStatus(const FXString & text){
1227   mainwindow->statusbar->getStatusLine()->setNormalText(text);
1228   }
1229 
update_cover_display()1230 void GMPlayerManager::update_cover_display() {
1231   if (playing()) {
1232 
1233     if (preferences.gui_show_playing_albumcover && covermanager->getCover()==nullptr) {
1234 
1235       covermanager->load(trackinfo.url);
1236 
1237       mainwindow->update_meta_display();
1238 
1239       mainwindow->update_cover_display();
1240 
1241       }
1242 
1243     else if (preferences.gui_show_playing_albumcover==false && covermanager->getCover()) {
1244 
1245       covermanager->clear();
1246 
1247       mainwindow->update_meta_display();
1248 
1249       mainwindow->update_cover_display();
1250 
1251       }
1252 
1253     else {
1254       mainwindow->update_meta_display();
1255       }
1256     }
1257   else if (covermanager->getCover()) {
1258 
1259     covermanager->clear();
1260 
1261     mainwindow->update_meta_display();
1262 
1263     mainwindow->update_cover_display();
1264     }
1265   }
1266 
update_track_display(FXbool notify)1267 void GMPlayerManager::update_track_display(FXbool notify) {
1268   FXTRACE((51,"GMPlayerManager::update_track_display()\n"));
1269 
1270   /// If track information is not set, we need to get the latest from the player.
1271   if (source) {
1272     FXint time = (FXint) (((double)trackinfo.time) * 0.80);
1273     if (time <= 5) {
1274       application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
1275       source->handle(this,FXSEL(SEL_TIMEOUT,GMSource::ID_TRACK_PLAYED),nullptr);
1276       }
1277     else {
1278       count_track_remaining=0;
1279       application->addTimeout(source,GMSource::ID_TRACK_PLAYED,TIME_SEC(time));
1280       }
1281     }
1282 
1283   if (preferences.gui_show_playing_albumcover)
1284     covermanager->load(trackinfo.url);
1285 
1286   mainwindow->display(trackinfo);
1287 
1288   if (notify) application->addTimeout(this,ID_PLAY_NOTIFY,500_ms);
1289 
1290   if (queue) {
1291     getSourceView()->refresh(queue);
1292     if (getTrackView()->getSource()==queue)
1293       getTrackView()->refresh();
1294     getTrackView()->showCurrent();
1295     }
1296   }
1297 
1298 
volume() const1299 FXint GMPlayerManager::volume() const{
1300   return player->getVolume();
1301   }
1302 
volume(FXint l)1303 void GMPlayerManager::volume(FXint l) {
1304   player->volume((FXfloat)l/100.0f);
1305   }
1306 
can_stop() const1307 FXbool GMPlayerManager::can_stop() const {
1308   return (player->playing() || player->pausing());
1309   }
1310 
can_play() const1311 FXbool GMPlayerManager::can_play() const {
1312    return !player->playing() && ((queue==nullptr && getTrackView()->hasTracks()) ||
1313                                 (queue && (queue->getNumTracks() ||  (getTrackView()->hasTracks() && getPlayQueue()->canPlaySource(getTrackView()->getSource())))));
1314   }
1315 
can_pause() const1316 FXbool GMPlayerManager::can_pause() const {
1317   return (player->playing() && !player->pausing());
1318   }
1319 
can_unpause() const1320 FXbool GMPlayerManager::can_unpause() const {
1321   return player->pausing();
1322   }
1323 
can_next() const1324 FXbool GMPlayerManager::can_next() const {
1325   if (player->playing() && !player->pausing()) {
1326     if (( queue && queue->getNumTracks()>0) || getTrackView()->getNumTracks()>1)
1327       return true;
1328     }
1329   return false;
1330   }
1331 
can_prev() const1332 FXbool GMPlayerManager::can_prev() const {
1333   if (player->playing() && !player->pausing() && queue==nullptr) {
1334     return (getTrackView()->getNumTracks()>1);
1335     }
1336   return false;
1337   }
1338 
setSleepTimer(FXlong ns)1339 void GMPlayerManager::setSleepTimer(FXlong ns) {
1340   if (ns==0)
1341     application->removeTimeout(this,GMPlayerManager::ID_SLEEP_TIMER);
1342   else
1343     application->addTimeout(this,GMPlayerManager::ID_SLEEP_TIMER,ns);
1344   }
1345 
hasSleepTimer()1346 FXbool GMPlayerManager::hasSleepTimer() {
1347   return application->hasTimeout(this,GMPlayerManager::ID_SLEEP_TIMER);
1348   }
1349 
show_message(const FXchar * title,const FXchar * msg)1350 void GMPlayerManager::show_message(const FXchar * title,const FXchar * msg){
1351   if (application->getActiveWindow() && application->getActiveWindow()->shown()) {
1352     FXMessageBox::error(application->getActiveWindow(),MBOX_OK,title,"%s",msg);
1353     }
1354   else {
1355     if (mainwindow) {
1356       if (mainwindow->shown())
1357         FXMessageBox::error(mainwindow,MBOX_OK,title,"%s",msg);
1358       else if (mainwindow->getRemote())
1359         FXMessageBox::error(mainwindow->getRemote(),MBOX_OK,title,"%s",msg);
1360       else
1361         FXMessageBox::error(application,MBOX_OK,title,"%s",msg);
1362       }
1363     else {
1364       FXMessageBox::error(application,MBOX_OK,title,"%s",msg);
1365       }
1366     }
1367   }
1368 
1369 
onCmdCloseWindow(FXObject * sender,FXSelector,void *)1370 long GMPlayerManager::onCmdCloseWindow(FXObject*sender,FXSelector,void*){
1371   FXWindow * window = static_cast<FXWindow*>(sender);
1372   if (getPreferences().gui_hide_player_when_close && !getPreferences().gui_tray_icon_disabled) {
1373     window->hide();
1374     }
1375   else {
1376     getMainWindow()->handle(this,FXSEL(SEL_COMMAND,GMWindow::ID_QUIT),nullptr);
1377     }
1378   return 1;
1379   }
1380 
onCmdQuit(FXObject *,FXSelector,void *)1381 long GMPlayerManager::onCmdQuit(FXObject*,FXSelector,void*){
1382   getMainWindow()->handle(this,FXSEL(SEL_COMMAND,GMWindow::ID_QUIT),nullptr);
1383   return 1;
1384   }
1385 
1386 
onCmdChild(FXObject *,FXSelector,void *)1387 long GMPlayerManager::onCmdChild(FXObject*,FXSelector,void*){
1388   FXint pid;
1389   FXint status;
1390   while(1) {
1391     pid = waitpid(-1,&status,WNOHANG);
1392     if (pid>0) {
1393       continue;
1394       }
1395     break;
1396     }
1397   return 1;
1398   }
1399 
onScrobblerError(FXObject *,FXSelector,void * ptr)1400 long GMPlayerManager::onScrobblerError(FXObject*,FXSelector,void*ptr){
1401   show_message(fxtr("Last.FM Error"),(const FXchar*)ptr);
1402   return 1;
1403   }
1404 
onScrobblerOpen(FXObject *,FXSelector,void * ptr)1405 long GMPlayerManager::onScrobblerOpen(FXObject*,FXSelector,void*ptr){
1406   gm_open_browser((const FXchar*)ptr);
1407   return 1;
1408   }
1409 
1410 
1411 
onImportTaskCompleted(FXObject *,FXSelector sel,void * ptr)1412 long GMPlayerManager::onImportTaskCompleted(FXObject*,FXSelector sel,void*ptr){
1413   GMTask * task = *static_cast<GMTask**>(ptr);
1414   if (FXSELTYPE(sel)==SEL_TASK_COMPLETED) {
1415     database->initArtistLookup();
1416     getDatabaseSource()->updateCovers();
1417     GMSource * src = getTrackView()->getSource();
1418     if (src) {
1419       switch(src->getType()){
1420         case SOURCE_DATABASE:
1421         case SOURCE_DATABASE_FILTER:
1422         case SOURCE_DATABASE_PLAYLIST: getTrackView()->refresh(); break;
1423         default: break;
1424         }
1425       }
1426     }
1427   delete task;
1428   return 0;
1429   }
1430 
1431 
1432 
runTask(GMTask * task)1433 void GMPlayerManager::runTask(GMTask * task) {
1434   application->removeTimeout(this,GMPlayerManager::ID_TASKMANAGER_SHUTDOWN);
1435   taskmanager->run(task);
1436   }
1437 
onTaskManagerIdle(FXObject *,FXSelector,void *)1438 long GMPlayerManager::onTaskManagerIdle(FXObject*,FXSelector,void*){
1439   mainwindow->setStatus(FXString::null);
1440   GM_DEBUG_PRINT("Schedule taskmanager shutdown in 30s\n");
1441   application->addTimeout(this,GMPlayerManager::ID_TASKMANAGER_SHUTDOWN,30_s);
1442   return 0;
1443   }
1444 
onTaskManagerRunning(FXObject *,FXSelector,void *)1445 long GMPlayerManager::onTaskManagerRunning(FXObject*,FXSelector,void*){
1446   GM_DEBUG_PRINT("Taskmanager running\n");
1447   application->removeTimeout(this,GMPlayerManager::ID_TASKMANAGER_SHUTDOWN);
1448   return 0;
1449   }
1450 
1451 
onTaskManagerShutdown(FXObject *,FXSelector,void *)1452 long GMPlayerManager::onTaskManagerShutdown(FXObject*,FXSelector,void*){
1453   GM_DEBUG_PRINT("Shutdown taskmanager now\n");
1454   taskmanager->shutdown();
1455   return 0;
1456   }
1457 
1458 
onTaskManagerStatus(FXObject *,FXSelector,void * ptr)1459 long GMPlayerManager::onTaskManagerStatus(FXObject*,FXSelector,void*ptr){
1460   FXchar * msg = (FXchar*)ptr;
1461   mainwindow->setStatus(msg);
1462   return 0;
1463   }
1464 
onCancelTask(FXObject *,FXSelector,void *)1465 long GMPlayerManager::onCancelTask(FXObject*,FXSelector,void*){
1466   taskmanager->cancelTask();
1467   return 0;
1468   }
1469 
1470 
1471 
1472 
cmd_play()1473 void GMPlayerManager::cmd_play(){
1474   if (can_unpause())
1475     unpause();
1476   else if (can_play())
1477     playItem(TRACK_CURRENT);
1478   }
1479 
1480 
cmd_playpause()1481 void GMPlayerManager::cmd_playpause(){
1482   if (can_pause())
1483     pause();
1484   else if (can_unpause())
1485     unpause();
1486   else if (can_play())
1487     playItem(TRACK_CURRENT);
1488   }
1489 
1490 
cmd_pause()1491 void GMPlayerManager::cmd_pause(){
1492   if (can_pause())
1493     pause();
1494   }
1495 
cmd_schedule_stop()1496 void GMPlayerManager::cmd_schedule_stop(){
1497   if (scheduled_stop==false) {
1498     if (can_stop()) {
1499       GM_DEBUG_PRINT("enable scheduled stop\n");
1500       scheduled_stop=true;
1501       }
1502     }
1503   else {
1504     GM_DEBUG_PRINT("disable scheduled stop\n");
1505     scheduled_stop=false;
1506     }
1507   }
1508 
has_scheduled_stop() const1509 FXbool GMPlayerManager::has_scheduled_stop() const {
1510   if (can_stop() && scheduled_stop)
1511     return true;
1512   else
1513     return false;
1514   }
1515 
cmd_stop()1516 void GMPlayerManager::cmd_stop(){
1517   if (can_stop())
1518     stop();
1519   }
1520 
cmd_next()1521 void GMPlayerManager::cmd_next(){
1522   if (can_next())
1523     playItem(TRACK_NEXT);
1524   }
cmd_prev()1525 void GMPlayerManager::cmd_prev(){
1526   if (can_prev())
1527     playItem(TRACK_PREVIOUS);
1528   }
1529 
cmd_raise()1530 void GMPlayerManager::cmd_raise() {
1531   getMainWindow()->raiseWindow();
1532   }
1533 
cmd_toggle_shown()1534 void GMPlayerManager::cmd_toggle_shown(){
1535   getMainWindow()->toggleShown();
1536   }
1537 
cmd_update_podcasts()1538 void GMPlayerManager::cmd_update_podcasts(){
1539   FXASSERT(podcast);
1540   podcast->refreshFeeds();
1541   }
1542 
1543 
1544 
display_track_notification()1545 void GMPlayerManager::display_track_notification() {
1546 #ifdef DEBUG
1547   fxmessage("Track Change Notification\n");
1548   fxmessage("\t Title: %s\n",trackinfo.title.text());
1549   fxmessage("\tArtist: %s\n",trackinfo.artist.text());
1550   fxmessage("\t Album: %s\n",trackinfo.album.text());
1551 #endif
1552 #ifdef HAVE_DBUS
1553   if (sessionbus) {
1554     if (preferences.dbus_notify_daemon && notifydaemon) {
1555       notifydaemon->notify_track_change(trackinfo);
1556       }
1557     if (mpris1) mpris1->notify_track_change(trackinfo);
1558     if (mpris2) mpris2->notify_track_change(trackinfo);
1559     }
1560 #endif
1561   }
1562 
1563 
1564 #ifdef HAVE_DBUS
onCmdSettingsDaemon(FXObject *,FXSelector,void * ptr)1565 long GMPlayerManager::onCmdSettingsDaemon(FXObject*,FXSelector,void*ptr){
1566   const FXchar * cmd = (const FXchar*)ptr;
1567   if (comparecase(cmd,"play")==0) cmd_playpause();
1568   else if (comparecase(cmd,"pause")==0) cmd_playpause();
1569   else if (comparecase(cmd,"stop")==0) cmd_stop();
1570   else if (comparecase(cmd,"previous")==0) cmd_prev();
1571   else if (comparecase(cmd,"next")==0) cmd_next();
1572   else {
1573     GM_DEBUG_PRINT("Unknown or unhandled key press: %s\n",cmd);
1574     }
1575   return 1;
1576   }
1577 #endif
1578 
1579 
1580 // Perhaps should do something else...
xregisterhotkeys(Display * dpy,XErrorEvent * eev)1581 static int xregisterhotkeys(Display* dpy,XErrorEvent* eev){
1582   char buf[256];
1583 
1584   if(eev->error_code==BadAccess && eev->request_code==33) return 0;
1585 
1586   // A BadWindow due to X_SendEvent is likely due to XDND
1587   if(eev->error_code==BadWindow && eev->request_code==25) return 0;
1588 
1589   // WM_TAKE_FOCUS causes sporadic errors for X_SetInputFocus
1590   if(eev->request_code==42) return 0;
1591 
1592   // Get error codes
1593   XGetErrorText(dpy,eev->error_code,buf,sizeof(buf));
1594 
1595   // Print out meaningful warning
1596   fxwarning("gogglesmm X Error: code %d major %d minor %d: %s.\n",eev->error_code,eev->request_code,eev->minor_code,buf);
1597   return 1;
1598   }
1599 
1600 
register_global_hotkeys()1601 void GMPlayerManager::register_global_hotkeys() {
1602   Window root   = application->getRootWindow()->id();
1603   Display * display = (Display*) application->getDisplay();
1604   KeyCode keycode;
1605 
1606   XErrorHandler previous = XSetErrorHandler(xregisterhotkeys);
1607 
1608   /// Only register hotkeys on the rootwindow.
1609 #ifdef XF86XK_AudioPlay
1610   keycode = XKeysymToKeycode(display,XF86XK_AudioPlay);
1611   if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1612 #endif
1613 #ifdef XF86XK_AudioPause
1614   keycode = XKeysymToKeycode(display,XF86XK_AudioPause);
1615   if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1616 #endif
1617 #ifdef XF86XK_AudioStop
1618   keycode = XKeysymToKeycode(display,XF86XK_AudioStop);
1619   if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1620 #endif
1621 #ifdef XF86XK_AudioNext
1622   keycode = XKeysymToKeycode(display,XF86XK_AudioNext);
1623   if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1624 #endif
1625 #ifdef XF86XK_AudioPrev
1626   keycode = XKeysymToKeycode(display,XF86XK_AudioPrev);
1627   if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1628 #endif
1629 //#ifdef XF86XK_AudioMute
1630 //  keycode = XKeysymToKeycode(display,XF86XK_AudioMute);
1631 //  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1632 //#endif
1633 //#ifdef XF86XK_AudioLowerVolume
1634 //  keycode = XKeysymToKeycode(display,XF86XK_AudioLowerVolume);
1635 //  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1636 //#endif
1637 //#ifdef XF86XK_AudioRaiseVolume
1638 //  keycode = XKeysymToKeycode(display,XF86XK_AudioRaiseVolume);
1639 //  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1640 //#endif
1641 //#ifdef XF86XK_AudioRepeat
1642 //  keycode = XKeysymToKeycode(display,XF86XK_AudioRepeat);
1643 //  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1644 //#endif
1645 //#ifdef XF86XK_AudioRandomPlay
1646 //  keycode = XKeysymToKeycode(display,XF86XK_AudioRandomPlay);
1647 //  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
1648 //#endif
1649 
1650   XSync (display,False);
1651   XSetErrorHandler(previous);
1652   }
1653 
handle_global_hotkeys(FXuint code)1654 FXbool GMPlayerManager::handle_global_hotkeys(FXuint code) {
1655   switch(code) {
1656 //    case XF86XK_AudioMute	        : break;
1657 //    case XF86XK_AudioRaiseVolume	: break;
1658 //    case XF86XK_AudioLowerVolume	: break;
1659 #ifdef XF86XK_AudioPlay
1660     case XF86XK_AudioPlay         : cmd_playpause(); break;
1661 #endif
1662 #ifdef XF86XK_AudioPause
1663     case XF86XK_AudioPause	      : cmd_playpause(); break;
1664 #endif
1665 #ifdef XF86XK_AudioStop
1666     case XF86XK_AudioStop	        : cmd_stop(); break;
1667 #endif
1668 #ifdef XF86XK_AudioPrev
1669     case XF86XK_AudioPrev	        : cmd_prev(); break;
1670 #endif
1671 #ifdef XF86XK_AudioNext
1672     case XF86XK_AudioNext	        : cmd_next(); break;
1673 #endif
1674 //    case XF86XK_AudioRepeat	      : break;
1675 //    case XF86XK_AudioRandomPlay	  : break;
1676     default                       : return false; break;
1677     }
1678   return true;
1679   }
1680 
1681 //#ifndef HAVE_XINE_LIB
onPlayerBOS(FXObject *,FXSelector,void *)1682 long GMPlayerManager::onPlayerBOS(FXObject*,FXSelector,void*){
1683   GM_DEBUG_PRINT("[player] bos\n");
1684   update_track_display();
1685   getTrackView()->showCurrent();
1686   return 1;
1687   }
1688 
onPlayerEOS(FXObject *,FXSelector,void *)1689 long GMPlayerManager::onPlayerEOS(FXObject*,FXSelector,void*){
1690   GM_DEBUG_PRINT("[player] eos\n");
1691   notify_playback_finished();
1692   return 1;
1693   }
1694 
1695 
onPlayerTime(FXObject *,FXSelector,void * ptr)1696 long GMPlayerManager::onPlayerTime(FXObject*,FXSelector,void* ptr){
1697   const PlaybackTime * tm = static_cast<const PlaybackTime*>(ptr);
1698 
1699   TrackTime tm_progress;
1700   TrackTime tm_remaining;
1701 
1702   FXint time,pos=0;
1703 
1704   /// Time progressed
1705   time                = tm->position;
1706   tm_progress.hours   = (FXint) floor((double)time/3600.0);
1707   time               -= (3600*tm_progress.hours);
1708   tm_progress.minutes = (FXint) floor((double)time/60.0);
1709   time               -= (FXint) (60*tm_progress.minutes);
1710   tm_progress.seconds = (FXint) floor((double)time);
1711 
1712 
1713   /// Time Remaining
1714   if (tm->length) {
1715     time                 = (tm->length-tm->position);
1716     tm_remaining.hours   = (FXint) floor((double)time/3600.0);
1717     time                -= (3600*tm_remaining.hours);
1718     tm_remaining.minutes = (FXint) floor((double)time/60.0);
1719     time                -= (FXint) (60*tm_remaining.minutes);
1720     tm_remaining.seconds = (FXint) floor((double)time);
1721 
1722     pos = (FXint)(100000.0 * ( (double)tm->position / (double)tm->length));
1723     }
1724 
1725   mainwindow->update_time(tm_progress,tm_remaining,pos,true,true);
1726 
1727 #ifdef HAVE_DBUS
1728   if (has_seeked) {
1729     if (mpris2) mpris2->notify_seek(tm->position);
1730     has_seeked=false;
1731     }
1732 #endif
1733   return 1;
1734   }
1735 
onPlayerState(FXObject *,FXSelector,void * ptr)1736 long GMPlayerManager::onPlayerState(FXObject*,FXSelector,void* ptr){
1737   FXint state = (FXint)(FXival)ptr;
1738   switch(state){
1739     case PLAYER_STOPPED: GM_DEBUG_PRINT("[player] stopped\n"); reset_track_display(); break;
1740     case PLAYER_PLAYING: break;
1741     case PLAYER_PAUSING: break;
1742     }
1743 #ifdef HAVE_DBUS
1744   if (mpris1) mpris1->notify_status_change();
1745   if (mpris2) mpris2->notify_status_change();
1746 #endif
1747   return 1;
1748   }
1749 
onPlayerVolume(FXObject *,FXSelector,void * ptr)1750 long GMPlayerManager::onPlayerVolume(FXObject*,FXSelector,void* ptr){
1751   getMainWindow()->update_volume_display((FXint)(FXival)ptr);
1752 #ifdef HAVE_DBUS
1753   if (mpris2) mpris2->notify_volume((FXint)(FXival)ptr);
1754 #endif
1755   return 1;
1756   }
1757 
onPlayerMeta(FXObject *,FXSelector,void * ptr)1758 long GMPlayerManager::onPlayerMeta(FXObject*,FXSelector,void* ptr){
1759   GMTrack * track = static_cast<GMTrack*>(ptr);
1760   GM_DEBUG_PRINT("[player] meta\n");
1761   if (trackinfoset==false) {
1762     trackinfo.title.adopt(track->title);
1763     trackinfo.artist.adopt(track->artist);
1764     trackinfo.album.adopt(track->album);
1765     update_track_display();
1766     }
1767   return 1;
1768   }
1769 
1770 
onPlayerError(FXObject *,FXSelector,void * ptr)1771 long GMPlayerManager::onPlayerError(FXObject*,FXSelector,void*ptr){
1772   FXString * msg = (FXString*)ptr;
1773   show_message(fxtr("Playback Error"),msg->text());
1774   return 1;
1775   }
1776 
1777 
createPlaylist(const FXString & name)1778 FXint GMPlayerManager::createPlaylist(const FXString & name) {
1779   FXint playlist=-1;
1780   if (database->insertPlaylist(name,playlist)) {
1781     insertSource(new GMPlayListSource(database,playlist));
1782     getSourceView()->refresh();
1783     }
1784   return playlist;
1785   }
1786 
getMainWindowId() const1787 FXuint GMPlayerManager::getMainWindowId() const {
1788   return mainwindow->id();
1789   }
1790 
1791 
1792