/*************************************************************************** Interface to access mpd ------------------- begin : Tue Apr 19 18:31:00 BST 2005 copyright : (C) 2005 by William Robinson email : airbaggins@yahoo.co.uk ***************************************************************************/ /*************************************************************************** * * * 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 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include "mpdInterface.h" #include #include #include #include #include MpdInterface::MpdInterface() : PlayerInterface() , sock() , sock_mutex() , messagebox_mutex() , hostname("localhost") , port(6600) , slider_timer(0) , reconnect_timer(0) { connect(&sock, TQT_SIGNAL(error(int)), this, TQT_SLOT(connectionError(int))); connect(&sock, TQT_SIGNAL(error(int)), this, TQT_SLOT(stopSliderClock())); connect(&sock, TQT_SIGNAL(connected()), this, TQT_SLOT(startSliderClock())); connect(&sock, TQT_SIGNAL(connected()), this, TQT_SLOT(stopReconnectClock())); connect(&sock, TQT_SIGNAL(connected()), this, TQT_SLOT(connected())); connect(&sock, TQT_SIGNAL(connectionClosed()), this, TQT_SLOT(stopSliderClock())); connect(&sock, TQT_SIGNAL(connectionClosed()), this, TQT_SLOT(startReconnectClock())); connect(&sock, TQT_SIGNAL(connectionClosed()), this, TQT_SIGNAL(playerStopped())); reconnect(); } MpdInterface::~MpdInterface() { } void MpdInterface::startSliderClock() { if (!slider_timer) { //kdDebug(90200) << "Starting slider clock\n"; slider_timer = startTimer(SLIDER_TIMER_INTERVAL); } } void MpdInterface::stopSliderClock() { if (slider_timer) { //kdDebug(90200) << "Stopping slider clock\n"; killTimer(slider_timer); slider_timer=0; } } void MpdInterface::startReconnectClock() { if (!reconnect_timer) { //kdDebug(90200) << "Starting Reconnect clock\n"; reconnect_timer = startTimer(RECONNECT_TIMER_INTERVAL); } } void MpdInterface::stopReconnectClock() { if (reconnect_timer) { //kdDebug(90200) << "Stopping Reconnect clock\n"; killTimer(reconnect_timer); reconnect_timer=0; } } void MpdInterface::timerEvent(TQTimerEvent* te) { if (te->timerId() == slider_timer) updateSlider(); else if (te->timerId() == reconnect_timer) reconnect(); } void MpdInterface::reconnect() const { if (sock.state()==TQSocket::Idle) { sock_mutex.tryLock(); //kdDebug(90200) << "Connecting to " << hostname.latin1() << ":" << port << "...\n"; sock.connectToHost(hostname,port); } } void MpdInterface::connected() { if (fetchOk()) // unlocks { //kdDebug(90200) << "Connected ok\n"; emit playerStarted(); emit playingStatusChanged(playingStatus()); } else { //kdDebug(90200) << "Connection error\n"; emit playerStopped(); } } void MpdInterface::connectionError(int e) { sock_mutex.unlock(); emit playerStopped(); TQString message; if (messagebox_mutex.tryLock()) { switch (e) { case TQSocket::ErrConnectionRefused: message=i18n("Connection refused to %1:%2.\nIs mpd running?").arg(hostname).arg(port); break; case TQSocket::ErrHostNotFound: message=i18n("Host '%1' not found.").arg(hostname); break; case TQSocket::ErrSocketRead: message=i18n("Error reading socket."); break; default: message=i18n("Connection error"); break; } // :TODO: KSimpleConfig to prompt for hostname/port values ? if (KMessageBox::warningContinueCancel( 0, message, i18n("MediaControl MPD Error"), i18n("Reconnect"))==KMessageBox::Continue) { startReconnectClock(); } else { stopReconnectClock(); } messagebox_mutex.unlock(); } } bool MpdInterface::dispatch(const char* cmd) const { if (sock.state()==TQSocket::Connected && sock_mutex.tryLock()) { long cmd_len=strlen(cmd); //kdDebug(90200) << "sending: " << cmd; long written=sock.writeBlock(cmd,cmd_len); if (written==cmd_len) { //kdDebug(90200) << "All bytes written\n"; sock.flush(); return true; } else { //kdDebug(90200) << written << '/' << cmd_len << " bytes written\n"; } sock.flush(); } return false; } bool MpdInterface::fetchLine(TQString& res) const { TQString errormessage; while (sock.state()==TQSocket::Connected) { if (!sock.canReadLine()) { sock.waitForMore(20); continue; } res=sock.readLine().stripWhiteSpace(); //kdDebug(90200) << "received: " << res.latin1() << "\n"; if (res.startsWith("OK")) { sock_mutex.unlock(); // if theres a message and we clear it and there's no other messagebox if (!errormessage.isEmpty() && dispatch("clearerror\n") && fetchOk() && messagebox_mutex.tryLock()) { KMessageBox::error(0,errormessage,i18n("MediaControl MPD Error")); messagebox_mutex.unlock(); } return false; } else if (res.startsWith("ACK")) { sock_mutex.unlock(); return false; } else if (res.startsWith("error: ")) { errormessage=i18n(res.latin1()); } else { return true; } } sock_mutex.unlock(); return false; } bool MpdInterface::fetchOk() const { TQString res; while (fetchLine(res)) { } if (res.startsWith("OK")) return true; else return false; } void MpdInterface::updateSlider() { //kdDebug(90200) << "update slider\n"; if (!dispatch("status\n")) return; TQString res; TQRegExp time_re("time: (\\d+):(\\d+)"); while(fetchLine(res)) { if (res.startsWith("state: ")) { if (res.endsWith("play")) { emit playingStatusChanged(Playing); } else if (res.endsWith("pause")) { emit playingStatusChanged(Paused); } else { emit playingStatusChanged(Stopped); } } else if (time_re.search(res)>=0) { TQStringList timeinfo=time_re.capturedTexts(); timeinfo.pop_front(); int elapsed_seconds=timeinfo.first().toInt(); timeinfo.pop_front(); int total_seconds=timeinfo.first().toInt(); emit newSliderPosition(total_seconds,elapsed_seconds); } } } void MpdInterface::sliderStartDrag() { stopSliderClock(); } void MpdInterface::sliderStopDrag() { startSliderClock(); } void MpdInterface::jumpToTime(int sec) { reconnect(); if (!dispatch("status\n")) return; long songid=-1; TQString res; TQRegExp songid_re("songid: (\\d+)"); while(fetchLine(res)) { if (songid_re.search(res)>=0) { TQStringList songidinfo=songid_re.capturedTexts(); songidinfo.pop_front(); songid=songidinfo.first().toInt(); } } if (songid>-1) { if (dispatch(TQString("seekid %1 %2\n").arg(songid).arg(sec).latin1())) { fetchOk(); // unlocks } } } void MpdInterface::playpause() { reconnect(); if (playingStatus()==Stopped ? dispatch("play\n") : dispatch("pause\n")) { fetchOk(); } } void MpdInterface::stop() { reconnect(); if (dispatch("stop\n")) fetchOk(); } void MpdInterface::next() { reconnect(); if (dispatch("next\n")) fetchOk(); } void MpdInterface::prev() { reconnect(); if (dispatch("previous\n")) fetchOk(); } void MpdInterface::changeVolume(int delta) { reconnect(); if (!dispatch("status\n")) return; int volume=-1; TQString res; TQRegExp volume_re("volume: (\\d+)"); while(fetchLine(res)) { if (volume_re.search(res)>=0) { TQStringList info=volume_re.capturedTexts(); info.pop_front(); volume=info.first().toInt(); } } if (volume>-1) { volume+=delta; if (volume<0) volume=0; if (volume>100) volume=100; if (dispatch(TQString("setvol %1\n").arg(volume).latin1())) { fetchOk(); } } } void MpdInterface::volumeUp() { reconnect(); changeVolume(5); } void MpdInterface::volumeDown() { reconnect(); changeVolume(-5); } void MpdInterface::dragEnterEvent(TQDragEnterEvent* event) { event->accept( KURLDrag::canDecode(event) ); } void MpdInterface::dropEvent(TQDropEvent* event) { reconnect(); KURL::List list; if (KURLDrag::decode(event, list)) { if (list.count()==1) // just one file dropped { // check to see if its in the playlist already if (dispatch("playlistid\n")) { long songid=-1; TQString file; TQString res; while(fetchLine(res)) { TQRegExp file_re("file: (.+)"); TQRegExp id_re("Id: (.+)"); if (file.isEmpty() && file_re.search(res)>=0) { TQStringList info=file_re.capturedTexts(); info.pop_front(); // if the dropped file ends with the same name, record it if (list.front().path().endsWith(info.first())) { file=info.first().toInt(); } } else if (!file.isEmpty() && id_re.search(res)>=0) { // when we have the file, pick up the id (file scomes first) TQStringList info=id_re.capturedTexts(); info.pop_front(); songid=info.first().toInt(); fetchOk(); // skip to the end break; } } // found song, so lets play it if (songid>-1) { if (dispatch((TQString("playid %1\n").arg(songid)).latin1())) { if (fetchOk()) list.pop_front(); return; } } } } // now if we have got this far, just try to add any files for (KURL::List::const_iterator i = list.constBegin(); i!=list.constEnd(); ++i) { if ((*i).isLocalFile()) { TQStringList path=TQStringList::split("/",(*i).path()); while (!path.empty()) { if (dispatch((TQString("add \"") +path.join("/").replace("\"","\\\"") +TQString("\"\n")).latin1())) { if (fetchOk()) break; } path.pop_front(); } } else { // :TODO: can handle http:// urls but maybe should check port or something } } } } const TQString MpdInterface::getTrackTitle() const { TQString result; reconnect(); if (!dispatch("status\n")) return result; long songid=-1; TQString res; while(fetchLine(res)) { TQRegExp songid_re("songid: (\\d+)"); if (songid_re.search(res)>=0) { TQStringList songidinfo=songid_re.capturedTexts(); songidinfo.pop_front(); songid=songidinfo.first().toInt(); } } if (!(songid>-1)) return result; if (!dispatch(TQString("playlistid %1\n").arg(songid).latin1())) return result; TQString artist; TQString album; TQString title; TQString track; TQString file; while(fetchLine(res)) { TQRegExp artist_re("Artist: (.+)"); TQRegExp album_re("Album: (.+)"); TQRegExp track_re("Album: (.+)"); TQRegExp title_re("Title: (.+)"); TQRegExp file_re("file: (.+)"); if (artist_re.search(res)>=0) { TQStringList info=artist_re.capturedTexts(); info.pop_front(); artist=info.first(); } else if (album_re.search(res)>=0) { TQStringList info=album_re.capturedTexts(); info.pop_front(); album=info.first(); } else if (title_re.search(res)>=0) { TQStringList info=title_re.capturedTexts(); info.pop_front(); title=info.first(); } else if (track_re.search(res)>=0) { TQStringList info=track_re.capturedTexts(); info.pop_front(); track=info.first(); } else if (file_re.search(res)>=0) { TQStringList info=file_re.capturedTexts(); info.pop_front(); file=info.first(); } } if (!artist.isEmpty()) { if (!title.isEmpty()) return artist.append(" - ").append(title); else if (!album.isEmpty()) return artist.append(" - ").append(album); } else if (!title.isEmpty()) { if (!album.isEmpty()) return album.append(" - ").append(title); else return title; } else if (!album.isEmpty()) { if (!track.isEmpty()) return album.append(" - ").append(track); else return album; } return i18n("No tags: %1").arg(file); } int MpdInterface::playingStatus() { //kdDebug(90200) << "looking up playing status\n"; if (!dispatch("status\n")) return Stopped; PlayingStatus status=Stopped; TQString res; while(fetchLine(res)) { if (res.startsWith("state: ")) { if (res.endsWith("play")) status=Playing; else if (res.endsWith("pause")) status=Paused; else status=Stopped; } } return status; } #include "mpdInterface.moc"