/*******************************************************************************
*                         Goggles Music Manager                                *
********************************************************************************
*           Copyright (C) 2006-2010 by Sander Jansen. All Rights Reserved      *
*                               ---                                            *
* This program is free software: you can redistribute it and/or modify         *
* it under the terms of the GNU General Public License as published by         *
* the Free Software Foundation, either version 3 of the License, or            *
* (at your option) any later version.                                          *
*                                                                              *
* This program is distributed in the hope that it will be useful,              *
* but WITHOUT ANY WARRANTY; without even the implied warranty of               *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                *
* GNU General Public License for more details.                                 *
*                                                                              *
* You should have received a copy of the GNU General Public License            *
* along with this program.  If not, see http://www.gnu.org/licenses.           *
********************************************************************************/
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

#include <xincs.h>
#include <X11/XF86keysym.h>


#include <xine.h>
#include "icons.h"
#include "common.h"

#ifdef HAVE_DBUS
#include "fxdbus.h"
#endif

#include <FXPNGIcon.h>
#include "GMApp.h"
#include "GMPlayer.h"
#include "GMAbout.h"
#include "GMWindow.h"
#include "GMTrackList.h"
#include "GMRemote.h"
#include "GMTag.h"
#include "GMThread.h"
#include "GMSearch.h"
#include "GMList.h"
#include "GMTrackView.h"
#include "GMSource.h"
#include "GMDatabaseSource.h"
#include "GMStreamSource.h"
#include "GMPlayListSource.h"

#include "GMIconTheme.h"
#include "GMPlayerManager.h"
#include "GMFetch.h"
#include "GMEQDialog.h"
#include "GMTrayIcon.h"
#include "GMSourceView.h"

#ifdef HAVE_DBUS
#include "GMNotifyDaemon.h"
#endif

#include "GMAudioScrobbler.h"

enum {
  FIFO_STATUS_ERROR  = 0,
  FIFO_STATUS_OWNER  = 1,
  FIFO_STATUS_EXISTS = 2
  };

#if APPLICATION_BETA_DB > 0
#define DATABASE_FILENAME "goggles_beta.db"
#else
#define DATABASE_FILENAME "goggles.db"
#endif


FXDEFMAP(GMPlayerManager) GMPlayerManagerMap[]={
  FXMAPFUNC(SEL_TIMEOUT,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY,GMPlayerManager::onUpdTrackDisplay),
  FXMAPFUNC(SEL_TIMEOUT,GMPlayerManager::ID_HANDLE_EVENTS,GMPlayerManager::onUpdEvents),
  FXMAPFUNC(SEL_TIMEOUT,GMPlayerManager::ID_SLEEP_TIMER,GMPlayerManager::onCmdSleepTimer),
  FXMAPFUNC(SEL_TIMEOUT,GMPlayerManager::ID_PLAY_NOTIFY,GMPlayerManager::onPlayNotify),
  FXMAPFUNC(SEL_IO_READ,GMPlayerManager::ID_DDE_MESSAGE,GMPlayerManager::onDDEMessage),
  FXMAPFUNC(SEL_CHORE,GMPlayerManager::ID_PLAYER_ERROR,GMPlayerManager::onPlayerError),
  FXMAPFUNC(SEL_CLOSE,GMPlayerManager::ID_REMOTE,GMPlayerManager::onCmdCloseRemote),
  FXMAPFUNC(SEL_CLOSE,GMPlayerManager::ID_WINDOW,GMPlayerManager::onCmdCloseWindow),
  FXMAPFUNC(SEL_SIGNAL,GMPlayerManager::ID_CHILD,GMPlayerManager::onCmdChild),

  FXMAPFUNC(SEL_COMMAND,GMPlayerManager::ID_SCROBBLER_ERROR,GMPlayerManager::onScrobblerError),
  FXMAPFUNC(SEL_COMMAND,GMPlayerManager::ID_EQUALIZER,GMPlayerManager::onCmdEqualizer),

  FXMAPFUNC(SEL_COMMAND,GMPlayerManager::ID_DOWNLOAD_COMPLETE,GMPlayerManager::onCmdDownloadComplete),
  };

FXIMPLEMENT(GMPlayerManager,FXObject,GMPlayerManagerMap,ARRAYNUMBER(GMPlayerManagerMap))

#ifdef HAVE_DBUS

#include "gogglesmm_xml.h"


static void gm_dbus_match_signal(DBusConnection*connection,const FXchar * path,const FXchar * interface,const FXchar * member){
  FXString rule = GMStringFormat("type='signal',path='%s',interface='%s',member='%s'",path,interface,member);
  dbus_bus_add_match(connection,rule.text(),NULL);
  }

#define GNOME_SETTINGS_DAEMON_PATH        "/org/gnome/SettingsDaemon/MediaKeys"
#define GNOME_SETTINGS_DAEMON_INTERFACE   "org.gnome.SettingsDaemon.MediaKeys"
#define GNOME_SETTINGS_DAEMON_NAME        "org.gnome.SettingsDaemon"

DBusHandlerResult dbus_gnome_settingsdaemon_filter(DBusConnection *,DBusMessage * msg,void * data){
  FXTRACE((80,"-----dbus_gnome_settingsdaemon_filter-------\n"));
  FXTRACE((80,"path: %s\n",dbus_message_get_path(msg)));
  FXTRACE((80,"member: \"%s\"\n",dbus_message_get_member(msg)));
  FXTRACE((80,"interface: %s\n",dbus_message_get_interface(msg)));
  FXTRACE((80,"sender: %s\n",dbus_message_get_sender(msg)));

  GMPlayerManager * p = (GMPlayerManager*)data;
  if (dbus_message_has_path(msg,GNOME_SETTINGS_DAEMON_PATH)){
    if (dbus_message_is_signal(msg,GNOME_SETTINGS_DAEMON_INTERFACE,"MediaPlayerKeyPressed")) {
      const FXchar * player=NULL;
      const FXchar * cmd=NULL;
      if (dbus_message_get_args(msg,NULL,DBUS_TYPE_STRING,&player,DBUS_TYPE_STRING,&cmd,DBUS_TYPE_INVALID)) {
        if (compare(player,"gogglesmm")==0) {
          if (comparecase(cmd,"play")==0 || comparecase(cmd,"pause")==0) {
             if (p->can_pause())
              p->pause();
            else if (p->can_unpause())
              p->unpause();
            else if (p->can_play())
              p->play();
            }
          else if (comparecase(cmd,"stop")==0) {
            if (p->can_stop()) p->stop();
            }
          else if (comparecase(cmd,"previous")==0) {
            if (p->can_prev()) p->prev();
            }
          else if (comparecase(cmd,"next")==0) {
            if (p->can_next()) p->next();
            }
          }
        }
      return DBUS_HANDLER_RESULT_HANDLED;
      }
    }
  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }


void GMPlayerManager::exit_gnome_settings_daemon() {
  FXuint serial;
  DBusMessage * msg = dbus_message_new_method_call(GNOME_SETTINGS_DAEMON_NAME,GNOME_SETTINGS_DAEMON_PATH,GNOME_SETTINGS_DAEMON_INTERFACE,"ReleaseMediaPlayerKeys");
  if (msg) {
    const FXchar * player = "gogglesmm";
    dbus_message_append_args(msg,DBUS_TYPE_STRING,&player,DBUS_TYPE_INVALID);
    dbus_message_set_no_reply(msg,true);
    dbus_connection_send(sessionbus->connection(),msg,&serial);
    dbus_message_unref(msg);
    }
  dbus_connection_remove_filter(sessionbus->connection(),dbus_gnome_settingsdaemon_filter,this);
  }

void GMPlayerManager::init_gnome_settings_daemon() {
  FXuint serial;

/*
  if (dbus_bus_name_has_owner(sessionbus->connection(),GNOME_SETTINGS_DAEMON_NAME,NULL))
    fxmessage("found owner of %s\n",GNOME_SETTINGS_DAEMON_NAME);
  else
    fxmessage("unable to find owner of %s\n",GNOME_SETTINGS_DAEMON_NAME);
*/
  DBusMessage * msg = dbus_message_new_method_call(GNOME_SETTINGS_DAEMON_NAME,GNOME_SETTINGS_DAEMON_PATH,GNOME_SETTINGS_DAEMON_INTERFACE,"GrabMediaPlayerKeys");
  if (msg) {
    const FXchar * player = "gogglesmm";
    FXuint time = 0;

    dbus_message_append_args(msg,DBUS_TYPE_STRING,&player,DBUS_TYPE_UINT32,&time,DBUS_TYPE_INVALID);
    dbus_message_set_no_reply(msg,true);
    dbus_connection_send(sessionbus->connection(),msg,&serial);
    dbus_message_unref(msg);
    }
  gm_dbus_match_signal(sessionbus->connection(),GNOME_SETTINGS_DAEMON_PATH,GNOME_SETTINGS_DAEMON_INTERFACE,"MediaPlayerKeyPressed");
  dbus_connection_add_filter(sessionbus->connection(),dbus_gnome_settingsdaemon_filter,this,NULL);
  }




DBusHandlerResult dbus_systembus_filter(DBusConnection *,DBusMessage * msg,void * data){

  FXTRACE((80,"------------\n"));
  FXTRACE((80,"path: %s\n",dbus_message_get_path(msg)));
  FXTRACE((80,"member: \"%s\"\n",dbus_message_get_member(msg)));
  FXTRACE((80,"interface: %s\n",dbus_message_get_interface(msg)));
  FXTRACE((80,"sender: %s\n",dbus_message_get_sender(msg)));

  GMPlayerManager * p = (GMPlayerManager*)data;
  if (dbus_message_has_path(msg,"/org/freedesktop/NetworkManager")){
    if (dbus_message_is_signal(msg,"org.freedesktop.NetworkManager","StateChanged") ||
        dbus_message_is_signal(msg,"org.freedesktop.NetworkManager","StateChange")) {

      FXuint state=0;
      enum {
        NETWORK_STATE_UNKNOWN      = 0,
        NETWORK_STATE_ASLEEP       = 1,
        NETWORK_STATE_CONNECTING   = 2,
        NETWORK_STATE_CONNECTED    = 3,
        NETWORK_STATE_DISCONNECTED = 4
        };

      if (dbus_message_get_args(msg,NULL,DBUS_TYPE_UINT32,&state,DBUS_TYPE_INVALID)) {
        if (p->getAudioScrobbler() && state==NETWORK_STATE_CONNECTED)
          p->getAudioScrobbler()->nudge();
        }
      return DBUS_HANDLER_RESULT_HANDLED;
      }
    }
  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }


//typedef DBusHandlerResult(* ) DBusObjectPathMessageFunction(DBusConnection *connection, DBusMessage *message, void *user_data)


DBusHandlerResult dbus_playermanager_filter(DBusConnection *connection,DBusMessage * msg,void * data){
  FXuint serial;
  DBusMessage * reply=NULL;
  FXchar * mrl;
  GMPlayerManager * p = (GMPlayerManager*)data;

  FXTRACE((80,"-----dbus_playermanager_filter-------\n"));
  FXTRACE((80,"path: %s\n",dbus_message_get_path(msg)));
  FXTRACE((80,"member: \"%s\"\n",dbus_message_get_member(msg)));
  FXTRACE((80,"interface: %s\n",dbus_message_get_interface(msg)));
  FXTRACE((80,"sender: %s\n",dbus_message_get_sender(msg)));
  FXTRACE((80,"signature: %s\n",dbus_message_get_signature(msg)));

  if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"play")){
    if (p->can_unpause())
      p->unpause();
    else if (p->can_play())
      p->play();

    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"playpause")){
   if (p->can_pause())
      p->pause();
    else if (p->can_unpause())
      p->unpause();
    else if (p->can_play())
      p->play();

    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"stop")){
    if (p->can_stop()) p->stop();
    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"pause")){
    if (p->can_pause())
      p->pause();
    else if (p->can_unpause())
      p->unpause();

    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"next")){
    if (p->can_next()) p->next();
    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"prev")){
    if (p->can_prev()) p->prev();
    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"open")){
    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    if (dbus_message_get_args(msg,NULL,DBUS_TYPE_STRING,&mrl,DBUS_TYPE_INVALID)) {
      p->open(mrl);
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"raise")){
    if (p->getMainWindow()->shown())
      p->getMainWindow()->raise();
    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"notify")){
    p->display_track_notification();
   if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"toggleshown")){
    if (p->getRemote()) {
      if (p->getRemote()->shown())
        p->getRemote()->hide();
      else
        p->getRemote()->show();
      }
    else if (p->getMainWindow()) {
      if (p->getMainWindow()->shown()){
        p->getMainWindow()->hide();
        }
      else {
        p->getMainWindow()->show();
        }
      }
    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"getactions")){
    reply = dbus_message_new_method_return(msg);
    if (reply) {

      FXuint actions=0;

      enum {
        CAN_PLAY  		= 0x1,
        CAN_PAUSE 		= 0x2,
        CAN_STOP			= 0x4,
        CAN_NEXT  		= 0x8,
        CAN_PREV			= 0x10,
        };

      if (p->can_play() || p->can_unpause()) actions|=CAN_PLAY;
      if (p->can_stop()) actions|=CAN_STOP;
      if (p->can_pause()) actions|=CAN_PAUSE;
      if (p->can_prev()) actions|=CAN_PREV;
      if (p->can_next()) actions|=CAN_NEXT;

      dbus_message_append_args(reply,DBUS_TYPE_UINT32,&actions,DBUS_TYPE_INVALID);
      dbus_connection_send(connection,reply,&serial);
      dbus_message_unref(reply);
      }
    }
  else if (dbus_message_is_method_call(msg,GOGGLESMM_DBUS_INTERFACE,"exit")){
    if (!dbus_message_get_no_reply(msg)) {
      reply = dbus_message_new_method_return(msg);
      if (reply) {
        dbus_connection_send(connection,reply,&serial);
        dbus_message_unref(reply);
        }
      }
    if (p->getMainWindow()) p->getMainWindow()->handle(p,FXSEL(SEL_COMMAND,GMWindow::ID_QUIT),NULL);
    }
  else if (dbus_message_is_method_call(msg,"org.freedesktop.DBus.Introspectable","Introspect")){
    reply = dbus_message_new_method_return(msg);
    if (reply) {
      const FXchar * xml = gogglesmm_xml;
      dbus_message_append_args(reply,DBUS_TYPE_STRING,&xml,DBUS_TYPE_INVALID);
      dbus_connection_send(connection,reply,&serial);
      dbus_message_unref(reply);
      }
    }
  else {
    FXTRACE((80,"NOT HANDLED\n"));
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }
  return DBUS_HANDLER_RESULT_HANDLED;
  }


DBusObjectPathVTable org_fifthplanet_gogglesmm={
  NULL,
  &dbus_playermanager_filter,
  NULL,
  NULL,
  NULL,
  NULL
  };


void dbus_send_to_self(DBusConnection * connection,const FXchar * signal,FXString argument) {
  DBusMessage * msg;
//	FXuint serial=-1;
  msg = dbus_message_new_method_call(GOGGLESMM_DBUS_NAME,GOGGLESMM_DBUS_PATH,GOGGLESMM_DBUS_INTERFACE,signal);
  if (msg){
    if (!argument.empty()) {
      const FXchar * arg=argument.text();
      dbus_message_append_args(msg,DBUS_TYPE_STRING,&arg,DBUS_TYPE_INVALID);
      }
    dbus_message_set_no_reply(msg,true);
    dbus_connection_send(connection,msg,NULL);
    dbus_connection_flush(connection);
//    if (reply) dbus_message_unref(reply);
    dbus_message_unref(msg);
    }
  }


FXint dbus_send_commands(DBusConnection * connection,int& argc,char** argv){
  FXString cmd="raise";
  FXString url;
  if (argc>1) {
    if (compare(argv[1],"--previous")==0)
      cmd="prev";
    else if (compare(argv[1],"--play")==0)
      cmd="play";
    else if (compare(argv[1],"--play-pause")==0)
      cmd="playpause";
    else if (compare(argv[1],"--pause")==0)
      cmd="pause";
    else if (compare(argv[1],"--next")==0)
      cmd="next";
    else if (compare(argv[1],"--stop")==0)
      cmd="stop";
    else if (compare(argv[1],"--toggle-shown")==0)
      cmd="toggleshown";
    else if (compare(argv[1],"--raise")==0)
      cmd="raise";
    else if (compare(argv[1],"--now-playing")==0)
      cmd="notify";
    else {
      cmd="open";
      url=argv[1];
      if (gm_is_local_file(url)) {
        if (!FXPath::isAbsolute(url)) {
          url=FXPath::absolute(url);
          }
        }
      }
    }
  dbus_send_to_self(connection,cmd.text(),url);
  return 1;
  }




#endif



long GMPlayerManager::onPlayNotify(FXObject*,FXSelector,void*){

  update_cover_display();

  if (!trackinfo.title.empty() && !trackinfo.artist.empty()) {

    display_track_notification();

    if (lastfm) lastfm->nowplaying(trackinfo);
    }
  return 0;
  }

long GMPlayerManager::onUpdTrackDisplay(FXObject*,FXSelector,void*){
  update_track_display();
  if (source)
    getTrackView()->showCurrent();
  return 0;
  }

long GMPlayerManager::onUpdEvents(FXObject*,FXSelector,void*){
  handle_async_events();
  application->addTimeout(this,GMPlayerManager::ID_HANDLE_EVENTS,TIME_MSEC(500));
  return 0;
  }

/* Stop Playback! */
long GMPlayerManager::onCmdSleepTimer(FXObject*,FXSelector,void*){
  if (playing()) {
    stop();
    }
  return 1;
  }


long GMPlayerManager::onDDEMessage(FXObject*,FXSelector,void*){
  FXString cmd;
  FXchar buffer[1024];
  int nread=fifo.readBlock(buffer,1024);
  if (nread>0) {
    cmd=FXString(buffer,nread);
    cmd.trim();
    if (cmd=="--previous") { // Skip backwards in playlist
      if (can_prev()) prev();
      }
    else if (cmd=="--play") { // Start playing current playlist
      if (can_play()) play();
      }
    else if (cmd=="--play-pause"){ // Play if stopped, pause if playing
      if (can_pause())
        pause();
      else if (can_unpause())
        unpause();
      else if (can_play())
        play();
      }
    else if (cmd=="--pause") { // Pause playback
      if (can_pause()) pause();
      }
    else if (cmd=="--next"){ //Skip forwards in playlist
      if (can_next()) next();
      }
    else if (cmd=="--stop"){ //Stop Playback
      if (can_stop()) stop();
      }
    else if (cmd=="--raise"){
      if (mainwindow->shown())
        mainwindow->raise();
      }
    else if (cmd=="--now-playing"){
      display_track_notification();
      }
    else {
      if (gm_is_local_file(cmd)) {
        if (!FXPath::isAbsolute(cmd)) {
          cmd=FXPath::absolute(cmd);
          }
        }
      if (!cmd.empty())
        open(cmd);
      }
    }
  return 1;
  }



GMPlayerManager * GMPlayerManager::myself = NULL;

GMPlayerManager* GMPlayerManager::instance() {
  return myself;
  }



/// Constructor
GMPlayerManager::GMPlayerManager() :
  count_track_remaining(0),
#ifdef HAVE_DBUS
  sessionbus(NULL),
  systembus(NULL),
#endif
  application(NULL),
  mainwindow(NULL),
  remote(NULL),
  player(NULL),
  trayicon(NULL),
#ifdef HAVE_DBUS
  daemon(NULL),
#endif
  lastfm(NULL),
  source(NULL) {
  FXASSERT(myself==NULL);
  myself=this;
  database=NULL;
  }




/// Destructor
GMPlayerManager::~GMPlayerManager() {

  /// Remove Signal Handlers
#ifndef DEBUG
  application->removeSignal(SIGINT);
  application->removeSignal(SIGQUIT);
  application->removeSignal(SIGTERM);
  application->removeSignal(SIGHUP);
  application->removeSignal(SIGPIPE);
#endif
  application->removeSignal(SIGCHLD);


  /// Cleanup fifo crap
  if (fifo.isOpen())
    fifo.close();
  if (!fifofilename.empty())
    FXFile::remove(fifofilename);

  delete lastfm;

  /// Delete Sources
  for (FXint i=0;i<sources.no();i++)
    delete sources[i];

#ifdef HAVE_DBUS
  delete sessionbus;
  delete systembus;
#endif

  delete player;

  /// Clean up global resources
  GMFetch::exit();

  myself=NULL;

  delete application;
  }

GMTrackView * GMPlayerManager::getTrackView() const {
  return mainwindow->trackview;
  }

GMSourceView * GMPlayerManager::getSourceView() const {
  return mainwindow->sourceview;
  }




FXint GMPlayerManager::init_fifo(int& argc,char** argv){
  FXString fifodir = FXSystem::getHomeDirectory() + PATHSEPSTRING + ".goggles";
  FXStat info;

  if ( (!FXStat::exists(fifodir) && !FXDir::create(fifodir) ) || !FXStat::isDirectory(fifodir) ) {
    FXMessageBox::error(application,MBOX_OK,"Goggles Music Manager",fxtrformat("Unable to create directory %s\n"),fifodir.text());
    return FIFO_STATUS_ERROR;
    }

  fifofilename = fifodir + PATHSEPSTRING + "gmm.dde";

  /// Find existing fifo
  if (FXStat::statFile(fifofilename,info)) {

    /// File exists, but it's not a fifo... try removing it
    if (!info.isFifo() && !FXFile::remove(fifofilename)) {
      fifofilename=FXString::null;
      return FIFO_STATUS_ERROR;
      }

    if (fifo.open(fifofilename,FXIO::WriteOnly|FXIO::NonBlocking)){
      FXString commandline;

      for (FXint i=1;i<argc;i++){
        commandline+=argv[i];
        }

      /// Try raising the window
      if (commandline.empty())
        commandline="--raise";

      /// if write failed or command line was empty, user probably tries to start up normally
      /// and fifo may be stale
      if (fifo.writeBlock(commandline.text(),commandline.length())) {
        fifofilename=FXString::null;
        return FIFO_STATUS_EXISTS;
        }

      /// Most likely left behind by crashed music manager
      fifo.close();
      }

    if (!FXFile::remove(fifofilename))
      return FIFO_STATUS_OWNER;
    }

  /// Create the fifo
  if (mkfifo(fifofilename.text(),S_IWUSR|S_IRUSR)!=0){
      fifofilename=FXString::null;
      return FIFO_STATUS_OWNER;
      }

  /// Try open the fifo
  fifo.open(fifofilename,FXIO::Reading|FXIO::WriteOnly,FXIO::OwnerWrite);

  /// Close fifo on execute or remove fifo if we couldn't open it...
  if (fifo.isOpen()) {
    fcntl(fifo.handle(),F_SETFD,FD_CLOEXEC);
    }
  else {
    FXFile::remove(fifofilename);
    fifofilename=FXString::null;
    }

  return FIFO_STATUS_OWNER;
  }




FXbool GMPlayerManager::init_sources() {
  GMDatabaseSource * src;

  // Main Database
  database = new GMTrackDatabase;

  // Make sure we can open it.
  if (!init_database(database,DATABASE_FILENAME)) {
    delete database;
    database=NULL;
    return false;
    }

  // Create the main database source
  src = new GMDatabaseSource(database);
  sources.append(src);

  /// Add the playlists
  src->initPlaylists(sources);

  sources.append(new GMStreamSource(database));

  return true;
  }




void GMPlayerManager::init_window(FXbool wizard) {
  const FXint           argc = application->getArgc();
  const FXchar *const * argv = application->getArgv();

  /// Create Main Window
  mainwindow = new GMWindow(application,this,ID_WINDOW);
  mainwindow->create();

  // Register Global Hotkeys
  register_global_hotkeys();

  /// Handle interrupt to save stuff nicely
#ifndef DEBUG
  application->addSignal(SIGINT,mainwindow,GMWindow::ID_QUIT);
  application->addSignal(SIGQUIT,mainwindow,GMWindow::ID_QUIT);
  application->addSignal(SIGTERM,mainwindow,GMWindow::ID_QUIT);
  application->addSignal(SIGHUP,mainwindow,GMWindow::ID_QUIT);
  application->addSignal(SIGPIPE,mainwindow,GMWindow::ID_QUIT);
#endif

  /// Create Tooltip Window
  FXToolTip * tooltip = new FXToolTip(application);
  tooltip->create();
  ewmh_change_window_type(tooltip,WINDOWTYPE_TOOLTIP);

  GMDatabaseSource * dbsrc = dynamic_cast<GMDatabaseSource*>(sources[0]);
  if (dbsrc->getNumTracks()==0 && dbsrc->getNumStreams()==0 && wizard) {
    cleanSourceSettings();
    mainwindow->init();
    mainwindow->show();
    mainwindow->handle(this,FXSEL(SEL_COMMAND,GMWindow::ID_IMPORT_DIRS),NULL);
    }
  else {

    FXbool start_as_tray=false;

    for(FXint i=1;i<argc;i++) {
      if (comparecase(argv[i],"--tray")==0){
        start_as_tray=true;
        break;
        }
      }

    if (start_as_tray) {
      preferences.gui_tray_icon=true;
      }
    else {

      if (application->reg().readBoolEntry("window","window-show",true)) {
        mainwindow->show();
        }

      if (application->reg().readBoolEntry("window","remote-show",false) || preferences.gui_always_show_remote || (!preferences.gui_tray_icon && !mainwindow->shown()) ) {
        remote = new GMRemote(application,this,ID_REMOTE);
        remote->create();
        }

      }

    mainwindow->init();
    }
  application->addTimeout(this,GMPlayerManager::ID_HANDLE_EVENTS,TIME_MSEC(500));
  }


static FXString get_cmdline_url(int& argc,char** argv) {
  FXString url;
  for (FXint i=1;i<argc;i++) {
    if (argv[i][0]!='-') {
      url=argv[1];
      if (gm_is_local_file(url)) {
        if (!FXPath::isAbsolute(url)) {
          url=FXPath::absolute(url);
          }
        }
      return url;
      }
    }
  return FXString::null;
  }

FXint GMPlayerManager::run(int& argc,char** argv) {
  FXint result;

  GMTag::init();
  GMFetch::init();

  /// Create Application so we can do things like popup dialogs and such
  application = new GMApp("musicmanager","");
  application->init(argc,argv);
  application->create();

  /// Keep track of child processes
  application->addSignal(SIGCHLD,this,GMPlayerManager::ID_CHILD);

  /// Make sure we use SQLITE 3.3.8 (DROP VIEW IF EXISTS support).
  if (sqlite3_libversion_number()<SQVERSION(3,3,8)) {
    FXMessageBox::error(application,MBOX_OK,"Goggles Music Manager",
      fxtrformat("An incompatible version of SQLite (%s) is being used.\n"
      "Goggles Music Manager requires at least SQLite 3.3.8.\n"
      "Please upgrade your SQLite installation."),sqlite3_libversion());
    return 1;
    }

  /// DISTINCT is broken in 3.6.{0,1,2}. Fixed in 3.6.3
  if (sqlite3_libversion_number()>=SQVERSION(3,6,0) && sqlite3_libversion_number()<=SQVERSION(3,6,2)) {
    FXMessageBox::error(application,MBOX_OK,"Goggles Music Manager",
      fxtrformat("This version of SQLite (%s) is broken.\n"
      "Please upgrade your SQLite installation to at least 3.6.3."),sqlite3_libversion());
    return 1;
    }

  /// Give warning when PNG is not compiled in...
  if (FXPNGIcon::supported==false) {
    FXMessageBox::warning(application,MBOX_OK,"Goggles Music Manager",
      fxtr("For some reason the FOX library was compiled without PNG support.\n"
      "In order to show all icons, Goggles Music Manager requires PNG\n"
      "support in the FOX library. If you've compiled FOX yourself, most\n"
      "likely the libpng header files were not installed on your system."));
    }

#ifdef HAVE_DBUS

  sessionbus= new FXDBusConnection();
  systembus=new FXDBusConnection();
  if (!sessionbus->open(DBUS_BUS_SESSION) || !sessionbus->connected()) {
    FXMessageBox::warning(application,MBOX_OK,"Goggles Music Manager",fxtr("Session bus not available. All features requiring dbus are disabled."));
    delete sessionbus;
    sessionbus=NULL;
    }
  else {
    FXint result = dbus_bus_request_name(sessionbus->connection(),GOGGLESMM_DBUS_NAME,DBUS_NAME_FLAG_DO_NOT_QUEUE,NULL);
    switch(result) {
      case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
        {
          if (!dbus_connection_register_object_path(sessionbus->connection(),"/org/fifthplanet/gogglesmm",&org_fifthplanet_gogglesmm,this)){
            FXMessageBox::warning(application,MBOX_OK,"Goggles Music Manager",fxtr("A DBus error occurred. All features requiring sessionbus are disabled."));
            delete sessionbus;
            sessionbus=NULL;
            }
        }
        break;
      case DBUS_REQUEST_NAME_REPLY_EXISTS:
        dbus_send_commands(sessionbus->connection(),argc,argv);
        return 0;
        break;
      default:
        FXMessageBox::warning(application,MBOX_OK,"Goggles Music Manager",fxtr("Session Bus not available. All features requiring sessionbus are disabled."));
        delete sessionbus;
        sessionbus=NULL;
        break;
      }
    }

  /// Fallback to fifo method to check for existing instance
  if (sessionbus==NULL) {
    result = init_fifo(argc,argv);
    if (result==FIFO_STATUS_ERROR)
      return 1;
    else if (result==FIFO_STATUS_EXISTS)
      return 0;
    }


  if (!systembus->open(DBUS_BUS_SYSTEM) || !systembus->connected()) {
    delete systembus;
    systembus=NULL;
    }

  if (systembus && !dbus_connection_add_filter(systembus->connection(),dbus_systembus_filter,this,NULL)){
    delete systembus;
    systembus=NULL;
    }

  if (systembus) {
    DBusError error;
    dbus_error_init(&error);
    dbus_bus_add_match(systembus->connection(),"type='signal',path='/org/freedesktop/NetworkManager',interface='org.freedesktop.NetworkManager',member='StateChanged'",&error);
    if (dbus_error_is_set(&error)) {
      dbus_error_free(&error);
      delete systembus;
      systembus=NULL;
      }
    }

  if (systembus) {
    DBusError error;
    dbus_error_init(&error);
    dbus_bus_add_match(systembus->connection(),"type='signal',path='/org/freedesktop/NetworkManager',interface='org.freedesktop.NetworkManager',member='StateChange'",&error);
    if (dbus_error_is_set(&error)) {
      dbus_error_free(&error);
      delete systembus;
      systembus=NULL;
      }
    }

#else
  result = init_fifo(argc,argv);
  if (result==FIFO_STATUS_ERROR)
    return 1;
  else if (result==FIFO_STATUS_EXISTS)
    return 0;
#endif

  /// Open Database and initialize all sources.
  if (!init_sources())
    return false;

  /// Everything opened succesfully... now create the GUI
  player = new GMPlayer(argc,argv);

  /// Load Application Preferences
  preferences.load(application->reg());

  /// Check for overrides on the command line
  preferences.parseCommandLine(argc,argv);


  /// Open Audio Device if needed.
  if (preferences.play_open_device_on_startup) {
    if (!player->initialize()) {
      FXString errormsg;
      player->getErrorMessage(errormsg);
      FXMessageBox::error(application,MBOX_OK,fxtr("Audio Device Error"),"%s",errormsg.text());
      }
    }

  /// Receive events from fifo
  if (fifo.isOpen()) {
#if FOXVERSION < FXVERSION(1,7,0)
    application->addInput(fifo.handle(),INPUT_READ,this,GMPlayerManager::ID_DDE_MESSAGE);
#else
    application->addInput(this,GMPlayerManager::ID_DDE_MESSAGE,fifo.handle(),INPUT_READ);
#endif
    }



  FXString url = get_cmdline_url(argc,argv);

  /// Show user interface
  init_window(url.empty());

#ifdef HAVE_DBUS
  if (sessionbus) {

    daemon = new GMNotifyDaemon(sessionbus);

    init_gnome_settings_daemon();

    /// Integrate Dbus into FOX Event Loop
    FXDBusConnection::initEventLoop();
    }
#endif

  update_tray_icon();

  lastfm = new GMAudioScrobbler(this,ID_SCROBBLER_ERROR);


  /// Open url from command line
  if (!url.empty())
    open(url);

  /// Run the application
  return application->run();
  }





void GMPlayerManager::removeSource(GMSource *src) {
  sources.remove(src);
  }

void GMPlayerManager::exit() {

  /// Stop Playing
  stop();


  /// Close and Save Settings
  player->close();

#ifdef HAVE_DBUS
  if (sessionbus) {
    DBusMessage * msg = dbus_message_new_signal(GOGGLESMM_DBUS_PATH,GOGGLESMM_DBUS_INTERFACE,"quit");
    if (msg) {
      dbus_connection_send(sessionbus->connection(),msg,NULL);
      dbus_message_unref(msg);
      }


    }
#endif

  application->removeTimeout(this,GMPlayerManager::ID_HANDLE_EVENTS);

  application->removeTimeout(this,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY);

  application->removeTimeout(this,GMPlayerManager::ID_SLEEP_TIMER);

  application->removeTimeout(this,GMPlayerManager::ID_PLAY_NOTIFY);

  application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);

  preferences.save(application->reg());

  if (lastfm) lastfm->shutdown();

#ifdef HAVE_DBUS
  if (sessionbus) {
    exit_gnome_settings_daemon();

    dbus_connection_unregister_object_path(sessionbus->connection(),"/org/fifthplanet/gogglesmm");
//    dbus_connection_remove_filter(sessionbus->connection(),dbus_playermanager_filter,this);
    if (daemon) delete daemon;
    }

  if (systembus) {
    dbus_connection_remove_filter(systembus->connection(),dbus_systembus_filter,this);
    }
#endif

  application->exit(0);
  }


void GMPlayerManager::update_tray_icon() {
  if (trayicon && !preferences.gui_tray_icon) {
    delete trayicon;
    trayicon=NULL;
    }
  else if (!trayicon && preferences.gui_tray_icon) {
    trayicon = new GMTrayIcon(application);
    trayicon->create();
    }
  }



FXbool GMPlayerManager::init_database(GMTrackDatabase *& db, const FXString & filename){
  FXString dir = FXSystem::getHomeDirectory() + PATHSEPSTRING + ".goggles";
  FXString databasefilename = dir + PATHSEPSTRING + filename;

  if (FXStat::exists(dir)) {

    if (!FXStat::isDirectory(dir))
      return FALSE;

    }
  else {

    if (!FXDir::create(dir))
      return FALSE;
    }

  if (!FXStat::isWritable(dir) || !FXStat::isReadable(dir))
    return FALSE;

  if (FXStat::exists(databasefilename) && (!FXStat::isWritable(databasefilename) || !FXStat::isReadable(databasefilename)))
    return FALSE;

  if (!db) {
    db = new GMTrackDatabase();
    }

  /// Init Database
  if (!db->init(databasefilename,true)) {
    return false;
    }
  return TRUE;
  }

FXbool GMPlayerManager::hasSourceWithKey(const char * key) const{
  for (FXint i=0;i<sources.no();i++){
    if (sources[i]->settingKey()==key)
      return true;
    }
  return false;
  }


void GMPlayerManager::cleanSourceSettings() {
  FXint s;
  FXStringList keys;

  for (s=application->reg().first();s<application->reg().size();s=application->reg().next(s)){
    if (comparecase(application->reg().key(s),"database",8)==0){
      if (!hasSourceWithKey(application->reg().key(s))) {
        keys.append(application->reg().key(s));
        }
      }
    }

  for (s=0;s<keys.no();s++){
    application->reg().deleteSection(keys[s].text());
    }
  }

void GMPlayerManager::removePlayListSources(){
  GMSource * src;
  for (FXint i=sources.no()-1;i>=0;i--){
    src=sources[i];
    if (src->getType()==SOURCE_DATABASE_PLAYLIST) {
      FXApp::instance()->reg().deleteSection(sources[i]->settingKey().text());
      sources.erase(i);
      delete src;
      }
    }
  }

void GMPlayerManager::download(const FXString & filename){
#ifdef HAVE_CURL
  FXString status = "Downloading " + filename + " ...";
  GMPlayerManager::instance()->setStatus(status);
  GMFetch::download(filename);
#else
  show_message("Error","No buildin support for m3u/pls streams. GMM was build without libcurl.");
  reset_track_display();
#endif
  }

FXbool GMPlayerManager::play(const FXString & filename) {
  FXString errormsg;


  /// Open Filename
  if (!player->open(filename)){

    /// Reset Source
    if (source) {
      application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
      source->resetCurrent();
      source=NULL;
      }

    /// Reset Track Display
    reset_track_display();

    /// Show error dialog once we return to event loop
    application->addChore(this,ID_PLAYER_ERROR);

    /// Close
    if (preferences.play_close_stream)
      player->close();

    return false;
    }

  /// Start Playback
  if (!player->play()) {

    /// Reset Source
    if (source) {
      application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
      source->resetCurrent();
      source=NULL;
      }

    /// Reset Track Display
    reset_track_display();

    /// Show error dialog once we return to event loop
    application->addChore(this,ID_PLAYER_ERROR);

    /// Close
    if (preferences.play_close_stream)
      player->close();

    return false;
    }
  return true;
  }




FXbool GMPlayerManager::play(const FXStringList & list) {
  FXString errormsg;

  for (FXint i=0;i<list.no();i++) {
    /// Open Filename
    if (!player->open(list[i])){
      continue;
      }
    /// Start Playback
    if (!player->play()) {
      continue;
      }
    return true;
    }


  /// Show error dialog once we return to event loop
  application->addChore(this,ID_PLAYER_ERROR);


  /// Reset Source
  if (source) {
    application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
    source->resetCurrent();
    source=NULL;
    }

  /// Reset Track Display
  reset_track_display();

  /// Close
  if (preferences.play_close_stream)
    player->close();

  return false;
  }


void GMPlayerManager::open(const FXString & filename) {

  /// Stop fetching
  GMFetch::cancel_and_wait();

  /// Reset track info
  trackinfoset=false;

  /// Stop Current Playback
  player->stop();

  /// Remove any pending track display updates
  application->removeTimeout(this,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY);

  /// Remove Current Timeout
  if (source) {
    application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
    source->resetCurrent();
    source=NULL;
    }

  /// Check for pls or m3u, since xine cannot handle that
  FXbool local = gm_is_local_file(filename);
  if (!local) {
    FXString extension = FXPath::extension(GMURL::path(filename));
    if ((comparecase(extension,"pls")==0) || (comparecase(extension,"m3u")==0)){
      download(filename);
      return;
      }
    }

  if (local) {
    FXint id;
    if (sources[0]->hasTrack(filename,id)) {
      sources[0]->setCurrentTrack(id);
      source=sources[0];
      getTrackView()->handle(this,FXSEL(SEL_COMMAND,GMTrackView::ID_SHOW_CURRENT),NULL);
      trackinfoset = source->getTrack(trackinfo);
      }
    else {
      /// Reset Active Track
      getTrackView()->mark(-1);
      }
    }
  else {
    /// Reset Active Track
    getTrackView()->mark(-1);
    }

  /// Play File
  if (play(filename) && local) {
    if (source==NULL) {
      player->getTrackInformation(trackinfo);
      }
    update_track_display();
    }

  }


void GMPlayerManager::play() {
  FXString filename;
  FXint track;

  /// Stop fetching
  GMFetch::cancel_and_wait();

  /// Reset trackinfoset
  trackinfoset=false;

  /// Stop Current Playback
  player->stop();

  /// Remove any pending track display updates
  application->removeTimeout(this,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY);

  /// Remove Current Timeout
  if (source) {
    application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
    source->resetCurrent();
    source=NULL;
    }

  track = getTrackView()->getCurrent();
  if (track==-1) return;

  /// Mark Track
  source = getTrackView()->getSource();
  getTrackView()->mark(track);

  /// Get the track info
  trackinfoset = source->getTrack(trackinfo);

  /// Check for pls or m3u, since xine cannot handle that
  FXbool local = gm_is_local_file(trackinfo.mrl);
  if (!local) {

    FXString extension = FXPath::extension(GMURL::path(trackinfo.mrl));
    if ((comparecase(extension,"pls")==0) || (comparecase(extension,"m3u")==0)){
      download(trackinfo.mrl);
      return;
      }
    }

  if (play(trackinfo.mrl) && local)
    update_track_display();
  }


void GMPlayerManager::stop(FXbool force_close) {

  /// Wait for download to finish.
  GMFetch::cancel_and_wait();

  /// Reset Source
  if (source) {
    application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
    source->resetCurrent();
    source=NULL;
    }

  if (preferences.play_close_stream || force_close){
    player->stop();
    player->close();
    }
  else {
    player->stop();
    }

  /// Reset Track Display
  reset_track_display();
  }

namespace FX {
extern FXlong fxgetticks();
}

//static FXint next_track;

void GMPlayerManager::next() {
  FXint track;

  GMFetch::cancel_and_wait();

  player->stop();

  /// Remove any pending track display updates
  application->removeTimeout(this,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY);

    /// Remove Current Timeout
  if (source) {
    application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
    source->resetCurrent();
    source=NULL;
    }

  track = getTrackView()->getNext(true);
  if (track==-1) return;

  /// Mark Track
  source = getTrackView()->getSource();
  getTrackView()->mark(track);

  /// Get the Track info
  trackinfoset = source->getTrack(trackinfo);

  /// Check for pls or m3u, since xine cannot handle that
  FXbool local = gm_is_local_file(trackinfo.mrl);
  if (!local) {
    FXString extension = FXPath::extension(GMURL::path(trackinfo.mrl));
    if ((comparecase(extension,"pls")==0) || (comparecase(extension,"m3u")==0)){
      download(trackinfo.mrl);
      return;
      }
    }

  if (play(trackinfo.mrl) && gm_is_local_file(trackinfo.mrl))
    update_track_display();
  }


void GMPlayerManager::prev() {
  FXString filename;
  FXint track;

  GMFetch::cancel_and_wait();

  /// Stop Current Playback
  player->stop();

  /// Remove any pending track display updates
  application->removeTimeout(this,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY);

  /// Remove Current Timeout
  if (source) {
    application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
    source->resetCurrent();
    source=NULL;
    }

  track = getTrackView()->getPrevious();
  if (track==-1) return;

  /// Mark Track
  source = getTrackView()->getSource();
  getTrackView()->mark(track);

  /// Get Track Information
  trackinfoset = source->getTrack(trackinfo);

  /// Check for pls or m3u, since xine cannot handle that
  FXbool local = gm_is_local_file(trackinfo.mrl);
  if (!local) {
    FXString extension = FXPath::extension(GMURL::path(trackinfo.mrl));
    if ((comparecase(extension,"pls")==0) || (comparecase(extension,"m3u")==0)){
      download(trackinfo.mrl);
      return;
      }
    }

  if (play(trackinfo.mrl) && gm_is_local_file(trackinfo.mrl))
    update_track_display();
  }

void GMPlayerManager::seek(FXint pos) {

  /// Remove Current Timeout
  application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);

  player->play(pos);
  /// Prevent jumping of time slider
  application->addTimeout(this,GMPlayerManager::ID_HANDLE_EVENTS,TIME_MSEC(500));
  }

void GMPlayerManager::pause() {
  if (preferences.play_pause_close_device){
    player->pause();
    player->close_device();
    }
  else {
    player->pause();
    }
  count_track_remaining = application->remainingTimeout(source,GMSource::ID_TRACK_PLAYED);
  application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
  }

void GMPlayerManager::unpause() {
  player->unpause();

  if (player->getVolume()==0)
    player->setVolume(50);

  if (count_track_remaining>0 && source){
    application->addTimeout(source,GMSource::ID_TRACK_PLAYED,count_track_remaining);
    }
  count_track_remaining=0;

  mainwindow->update_volume_display(player->getVolume());

  if (remote) remote->update_volume_display(player->getVolume());
  }

FXbool GMPlayerManager::playlist_empty() {
  if (getTrackView()->hasTracks()) return false;
  if (preferences.play_repeat!=REPEAT_OFF) return false;
  if (getTrackView()->getNext()!=-1) return false;
  return true;
  }

void GMPlayerManager::notify_playback_finished() {
  FXString errormsg;
  FXString filename;
  FXint track;
  FXint remaining = player->remaining();


  /// Can we just start playback without user interaction
  if (!getTrackView()->getSource()->autoPlay()) {

    /// Reset Source
    if (source) {
      source->resetCurrent();
      source=NULL;
      }

    reset_track_display();
    return;
    }

  if (source) {
    source->resetCurrent();
    source=NULL;
    }

  if (preferences.play_repeat==REPEAT_TRACK)
    track = getTrackView()->getCurrent();
  else
    track = getTrackView()->getNext();

  if (track==-1) {
    reset_track_display();
    return;
    }

  getTrackView()->mark(track,(remaining<=0));

  source = getTrackView()->getSource();
  trackinfoset = source->getTrack(trackinfo);

  if (play(trackinfo.mrl)) {
    if (remaining>0)
      application->addTimeout(this,GMPlayerManager::ID_UPDATE_TRACK_DISPLAY,TIME_SEC(remaining),(void*)(FXival)track);
    else
      update_track_display();
    }

  }

void GMPlayerManager::fastforward(){
  player->incSpeed();
  }

void GMPlayerManager::fastbackward(){
  player->decSpeed();
  }

FXbool GMPlayerManager::playing() const {
  return player->playing() || GMFetch::busy() ;
  }

FXbool GMPlayerManager::audio_device_opened() const{
  return player->opened();
  }

FXint GMPlayerManager::current_position() const {
  return player->getPosition();
  }

void GMPlayerManager::reset_track_display() {
  FXTRACE((51,"GMPlayerManager::reset_track_display()\n"));

  /// Reset Main Window
  mainwindow->reset();

  if (remote) remote->reset();

  if (trayicon) trayicon->reset();

  /// Reset Active Track
  getTrackView()->mark(-1);

  /// Remove Notify
  application->removeTimeout(this,ID_PLAY_NOTIFY);


#ifdef HAVE_DBUS
  if (daemon && preferences.dbus_notify_daemon) daemon->close();
#endif

  /// Schedule a GUI update
  application->refresh();
  }


void GMPlayerManager::setStatus(const FXString & text){
  mainwindow->statusbar->getStatusLine()->setNormalText(text);
  }




void GMPlayerManager::update_track_display(FXbool notify) {
  FXTRACE((51,"GMPlayerManager::update_track_display()\n"));

  /// If track information is not set, we need to get the latest from the player.
  if (!trackinfoset && player->playing()) {
    player->getTrackInformation(trackinfo);
    if (source) source->setTrack(trackinfo);
    }

  if (source) {
    FXint time = (FXint) (((double)trackinfo.time) * 0.80);
    if (time <= 5) {
      application->removeTimeout(source,GMSource::ID_TRACK_PLAYED);
      source->handle(this,FXSEL(SEL_TIMEOUT,GMSource::ID_TRACK_PLAYED),NULL);
      }
    else {
      count_track_remaining=0;
      application->addTimeout(source,GMSource::ID_TRACK_PLAYED,TIME_SEC(time));
      }
    }

  if (remote && player->playing()) {
    remote->display(trackinfo);
    }

  if (trayicon && player->playing()) {
    trayicon->display(trackinfo);
    }

  mainwindow->display(trackinfo);

  /// Make sure Volume Level is up 2 date
  mainwindow->update_volume_display(player->getVolume());

  update_replay_gain();

  if (remote) remote->update_volume_display(player->getVolume());

  if (notify) application->addTimeout(this,ID_PLAY_NOTIFY,TIME_MSEC(500));
  }

void GMPlayerManager::update_replay_gain() {
  switch(preferences.play_replaygain){
    case REPLAYGAIN_OFF   : player->setReplayGain(NAN,NAN); break;
    case REPLAYGAIN_TRACK : player->setReplayGain(trackinfo.track_gain,trackinfo.track_peak); break;
    case REPLAYGAIN_ALBUM : player->setReplayGain(trackinfo.album_gain,trackinfo.album_peak); break;
    }
  }


void GMPlayerManager::handle_async_events() {

  /// Get Events from player
  player->handle_async_events();

  /// Mark Time
  mainwindow->update_elapsed_time(player->getHours(),player->getMinutes(),player->getSeconds(),player->getPosition(),player->playing(),player->seekable());

  if (remote)
    remote->elapsed_time(player->getHours(),player->getMinutes(),player->getSeconds(),player->getPosition(),player->playing());
//	if (miniwindow->shown() && player->playing()){
//    miniwindow->update_elapsed_time(player->getMinutes(),player->getSeconds(),player->playing());
//		}

  }

FXint GMPlayerManager::volume() const{
  return player->getVolume();
  }

void GMPlayerManager::volume(FXint l) {
  player->setVolume(l);
  }

FXbool GMPlayerManager::can_stop() const {
  if (player->playing() || GMFetch::busy() ) return true;
  return false;
  }

FXbool GMPlayerManager::can_play() const {
  if (!player->playing() && getTrackView()->hasTracks() && !GMFetch::busy()) return true;
  return false;
  }

FXbool GMPlayerManager::can_pause() const {
  if (player->playing() && !player->pausing())
    return true;
  return false;
  }

FXbool GMPlayerManager::can_unpause() const {
  if (player->playing() && player->pausing())
    return true;
  return false;
  }

FXbool GMPlayerManager::can_next() const {
  if (player->playing() && !player->pausing() && getTrackView()->getNumTracks()>1)
    return true;
  return false;
  }

FXbool GMPlayerManager::can_prev() const {
  if (player->playing() && !player->pausing() && getTrackView()->getNumTracks()>1)
    return true;
  return false;
  }

FXbool GMPlayerManager::can_fastforward() const {
  if (player->playing() && player->getSpeed()<XINE_SPEED_FAST_4 && player->getSpeed()>=XINE_SPEED_SLOW_4)
    return true;
  return false;
  }

FXbool GMPlayerManager::can_fastbackward() const {
  if (player->playing() && player->getSpeed()>XINE_SPEED_SLOW_4)
    return true;
  return false;
  }
#if FOXVERSION < 107000
void GMPlayerManager::setSleepTimer(FXuint ms) {
  if (ms==0)
    application->removeTimeout(this,GMPlayerManager::ID_SLEEP_TIMER);
  else
    application->addTimeout(this,GMPlayerManager::ID_SLEEP_TIMER,ms);
  }
#else
void GMPlayerManager::setSleepTimer(FXlong ns) {
  if (ns==0)
    application->removeTimeout(this,GMPlayerManager::ID_SLEEP_TIMER);
  else
    application->addTimeout(this,GMPlayerManager::ID_SLEEP_TIMER,ns);
  }
#endif

FXbool GMPlayerManager::hasSleepTimer() {
  return application->hasTimeout(this,GMPlayerManager::ID_SLEEP_TIMER);
  }

void GMPlayerManager::show_message(const FXchar * title,const FXchar * msg){
  if (application->getActiveWindow() && application->getActiveWindow()->shown()) {
    FXMessageBox::error(application->getActiveWindow(),MBOX_OK,title,"%s",msg);
    }
  else {
    if (mainwindow && mainwindow->shown())
      FXMessageBox::error(mainwindow,MBOX_OK,title,"%s",msg);
    else if (remote)
      FXMessageBox::error(remote,MBOX_OK,title,"%s",msg);
    else
      FXMessageBox::error(application,MBOX_OK,title,"%s",msg);
    }
  }


void GMPlayerManager::showRemote(){
  if (!remote) {
    remote = new GMRemote(application,this,ID_REMOTE);
    remote->create();
    if (player->playing()){
      remote->display(trackinfo);
      remote->updateCover(mainwindow->getSmallCover());
      }
    }
  }

void GMPlayerManager::hideRemote(){
  if (remote) {
    remote->handle(this,FXSEL(SEL_CLOSE,0),NULL);
    }
  }


long GMPlayerManager::onCmdCloseRemote(FXObject*,FXSelector,void*){
  getMainWindow()->show();
  if (getPreferences().gui_always_show_remote){
    return 1;
    }
  remote->writeRegistry();
  remote=NULL;
  return 0;
  }

long GMPlayerManager::onCmdCloseWindow(FXObject*,FXSelector,void*){
  if (getPreferences().gui_hide_player_when_close) {
    getMainWindow()->hide();
    if (!remote && !preferences.gui_tray_icon) {
      remote = new GMRemote(application,this,ID_REMOTE);
      remote->create();
      if (player->playing()){
        remote->display(trackinfo);
        remote->updateCover(mainwindow->getSmallCover());
        }
      }
    }
  else {
    getMainWindow()->handle(this,FXSEL(SEL_COMMAND,GMWindow::ID_QUIT),NULL);
    }
  return 1;
  }


long GMPlayerManager::onCmdChild(FXObject*,FXSelector,void*){
  FXint pid;
  FXint status;
  while(1) {
    pid = waitpid(-1,&status,WNOHANG);
    if (pid>0) {
      continue;
      }
    break;
    }
  return 1;
  }

long GMPlayerManager::onScrobblerError(FXObject*,FXSelector,void*ptr){
  show_message(fxtr("Last.FM Error"),(const FXchar*)ptr);
  return 1;
  }

long GMPlayerManager::onPlayerError(FXObject*,FXSelector,void*){
  FXString errormsg;
  player->getErrorMessage(errormsg);
  show_message(fxtr("Playback Error"),errormsg.text());
  return 1;
  }



long GMPlayerManager::onCmdDownloadComplete(FXObject*,FXSelector,void*ptr){
  GMFetchResponse * response = NULL;
  if (ptr)
    response = *((GMFetchResponse**)ptr);

  if (response) {
    if (response->url == trackinfo.mrl ) {
      FXStringList list;
      FXString extension = FXPath::extension(GMURL::path(response->url));
      if (comparecase(extension,"pls")==0 || response->content_type.find("audio/x-scpls")!=-1)
        gm_parse_pls(response->data,list);
      else if (comparecase(extension,"m3u")==0 || response->content_type.find("audio/mpegurl")!=-1)
        gm_parse_m3u(response->data,list);

      if (list.no())
        play(list);
      else
        reset_track_display();
      }
    delete response;
    }
  else {
    reset_track_display();
    }
  return 1;
/*


  if ( fetch ) {
    if (!fetch->errormsg.empty()) {
      FXString msg = GMStringFormat("Unable to open %s.\nReason: %s",fetch->filename.text(),fetch->errormsg.text());
      show_message(fxtr("Playback Error"),msg.text());
      delete fetch;
      fetch=NULL;
      reset_track_display();
      }
    else {
      if (fetch->mrl.no()) {
        play(fetch->mrl);
        }
      else {
        reset_track_display();
        }
      }
    delete fetch;
    fetch=NULL;
    }
  return 1;
*/
  }


long GMPlayerManager::onCmdEqualizer(FXObject *,FXSelector,void*){
  GMEQDialog * eqdialog = GMEQDialog::instance();
  if (eqdialog==NULL) {
    eqdialog = new GMEQDialog(mainwindow);
    eqdialog->create();
    }
  eqdialog->show();
  return 1;
  }


// Perhaps should do something else...
static int xregisterhotkeys(Display* dpy,XErrorEvent* eev){
  char buf[256];

  if(eev->error_code==BadAccess && eev->request_code==33) return 0;

  // A BadWindow due to X_SendEvent is likely due to XDND
  if(eev->error_code==BadWindow && eev->request_code==25) return 0;

  // WM_TAKE_FOCUS causes sporadic errors for X_SetInputFocus
  if(eev->request_code==42) return 0;

  // Get error codes
  XGetErrorText(dpy,eev->error_code,buf,sizeof(buf));

  // Print out meaningful warning
  fxwarning("GMM X Error: code %d major %d minor %d: %s.\n",eev->error_code,eev->request_code,eev->minor_code,buf);
  return 1;
  }


void GMPlayerManager::register_global_hotkeys() {
  Window root   = application->getRootWindow()->id();
  Display * display = (Display*) application->getDisplay();
  KeyCode keycode;

  XErrorHandler previous = XSetErrorHandler(xregisterhotkeys);

  /// Only register hotkeys on the rootwindow.
#ifdef XF86XK_AudioPlay
  keycode = XKeysymToKeycode(display,XF86XK_AudioPlay);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
#ifdef XF86XK_AudioPause
  keycode = XKeysymToKeycode(display,XF86XK_AudioPause);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
#ifdef XF86XK_AudioStop
  keycode = XKeysymToKeycode(display,XF86XK_AudioStop);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
#ifdef XF86XK_AudioNext
  keycode = XKeysymToKeycode(display,XF86XK_AudioNext);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
#ifdef XF86XK_AudioPrev
  keycode = XKeysymToKeycode(display,XF86XK_AudioPrev);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
/*
#ifdef XF86XK_AudioMute
  keycode = XKeysymToKeycode(display,XF86XK_AudioMute);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
#ifdef XF86XK_AudioLowerVolume
  keycode = XKeysymToKeycode(display,XF86XK_AudioLowerVolume);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
#ifdef XF86XK_AudioRaiseVolume
  keycode = XKeysymToKeycode(display,XF86XK_AudioRaiseVolume);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
#ifdef XF86XK_AudioRepeat
  keycode = XKeysymToKeycode(display,XF86XK_AudioRepeat);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
#ifdef XF86XK_AudioRandomPlay
  keycode = XKeysymToKeycode(display,XF86XK_AudioRandomPlay);
  if (keycode) XGrabKey(display,keycode,AnyModifier,root,False,GrabModeAsync,GrabModeAsync);
#endif
*/
  XSync (display,False);
  XSetErrorHandler(previous);
  }

FXbool GMPlayerManager::handle_global_hotkeys(FXuint code) {
  switch(code) {
//    case XF86XK_AudioMute	        : break;
//    case XF86XK_AudioRaiseVolume	: break;
//    case XF86XK_AudioLowerVolume	: break;
#ifdef XF86XK_AudioPlay
    case XF86XK_AudioPlay         : if (can_pause())
                                      pause();
                                    else if (can_unpause())
                                      unpause();
                                    else if (can_play())
                                      play();
                                    break;
#endif
#ifdef XF86XK_AudioPause
    case XF86XK_AudioPause	      : if (can_unpause())
                                      unpause();
                                    else
                                      play();
                                    break;
#endif
#ifdef XF86XK_AudioStop
    case XF86XK_AudioStop	        : if (can_stop())
                                      stop();
                                    break;
#endif
#ifdef XF86XK_AudioPrev
    case XF86XK_AudioPrev	        : if (can_prev())
                                      prev();
                                    break;
#endif
#ifdef XF86XK_AudioNext
    case XF86XK_AudioNext	        : if (can_next())
                                      next();
                                    break;
#endif
//    case XF86XK_AudioRepeat	      : break;
//    case XF86XK_AudioRandomPlay	  : break;
    default                       : return false; break;
    }
  return true;
  }


void GMPlayerManager::update_cover_display() {
  if (preferences.gui_show_playing_albumcover && gm_is_local_file(trackinfo.mrl))
    mainwindow->loadCover(trackinfo.mrl);
  }


FXint GMPlayerManager::createPlaylist(const FXString & name) {
  FXint playlist;
  database->insertPlaylist(name,playlist);
  insertSource(new GMPlayListSource(database,playlist));
  getSourceView()->refresh();
  return playlist;
  }


GMDatabaseSource * GMPlayerManager::getDatabaseSource() const {
  return dynamic_cast<GMDatabaseSource*>(sources[0]);
  }

void GMPlayerManager::display_track_notification() {
#ifdef HAVE_DBUS
  if (sessionbus && !trackinfo.title.empty() && !trackinfo.artist.empty() && preferences.dbus_notify_daemon) {
    if (daemon) {
      FXString body = GMStringFormat(fxtrformat("%s\n%s (%d)"),trackinfo.artist.text(),trackinfo.album.text(),trackinfo.year);
      /// Dirty Hack. According to the spec, we shouldn't have to do this,
      /// but try finding a notification daemon that actually implements it...
      /// http://www.galago-project.org/specs/notification/0.9/index.html
      body.substitute("&","&amp;");
      daemon->notify("gogglesmm","gmm",trackinfo.title.text(),body.text(),-1,mainwindow->getSmallCover());
      }
    //if (mpris) mpris->notify_track_change(trackinfo);
    }
#endif
  }


