viewing source for "rmpc-notify.rb"
last commit
commit 0a8e6c8bf8e3e930786f4e7516b98b70db782845
Date: Sun Feb 3 11:15:09 2008 -0800
source code
001: #!/usr/bin/ruby
002: #
003: # Rmpc Notify v0.2
004: # Copyright 2007 Andrew Rader (nymb.us)
005: #
006: # This file is part of Rmpc Notify
007: #
008: # Rmpc Notify is free software; you can redistribute it and/or modify
009: # it under the terms of the GNU General Public License as published by
010: # the Free Software Foundation; either version 2 of the License, or
011: # (at your option) any later version.
012: #
013: # Rmpc Notify is distributed in the hope that it will be useful,
014: # but WITHOUT ANY WARRANTY; without even the implied warranty of
015: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: # GNU General Public License for more details.
017: #
018: # You should have received a copy of the GNU General Public License
019: # along with Rmpc Notify; if not, write to the Free Software
020: # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
021: #
022:
023: require 'rubygems'
024: require 'librmpd'
025: require 'rnotify'
026: require 'thread'
027:
028: # this daemon code was heavily inspired by ciconia's snippet
029: # at the following: http://snippets.dzone.com/posts/show/2265
030: module Daemon
031: HomeDir = File.expand_path( '~' )
032:
033: class Base
034: def self.pid_file
035: File.join( HomeDir, ".#{name.downcase}.pid" )
036: end
037:
038: def self.usr1_trap
039: # do nothing, this can be used in your code
040: end
041:
042: def self.daemonize( cmd, *args )
043: Controller.daemonize( self, cmd, args )
044: end
045: end
046:
047: module PidFile
048: def self.store( daemon, pid )
049: File.open( daemon.pid_file, 'w' ) do |f|
050: f << pid
051: end
052: end
053:
054: def self.load( daemon )
055: IO.read( daemon.pid_file ).to_i rescue nil
056: end
057: end
058:
059: module Controller
060: def self.daemonize( daemon, cmd, args )
061: case cmd
062: when 'start'
063: start daemon, args
064: when 'stop'
065: stop daemon
066: when 'restart'
067: stop daemon
068: sleep 0.5
069: start daemon, args
070: when 'zap'
071: File.delete daemon.pid_file if File.file? daemon.pid_file
072: when 'usr1'
073: if not File.file? daemon.pid_file
074: puts "Pid File not found, is the daemon running?"
075: exit
076: end
077: pid = PidFile.load daemon
078: pid && Process.kill( "USR1", pid )
079: else
080: puts "Invalid Command (valid: start|stop|restart|zap|usr1)"
081: exit
082: end
083: end
084:
085: def self.start( daemon, args )
086: if File.file? daemon.pid_file
087: puts "Pid file already exists, is the daemon running?"
088: puts "Use zap to clear old pid files"
089: exit
090: end
091: exit if fork # Parent exists, child runs
092: Process.setsid # Become session leader
093: exit if fork # Zap session leader
094: PidFile.store daemon, Process.pid
095: Dir.chdir "/" # Release working dir
096: File.umask 0000 # Ensure sensible umask
097: STDIN.reopen "/dev/null" # Free file descripters
098: STDOUT.reopen "/dev/null", "a"
099: STDERR.reopen STDOUT
100: trap( "TERM" ) do
101: daemon.stop
102: exit
103: end
104: trap( "SIGUSR1" ) do
105: daemon.usr1_trap
106: end
107: daemon.start args
108: end
109:
110: def self.stop( daemon )
111: if not File.file? daemon.pid_file
112: puts "Pid File not found, is the daemon running?"
113: exit
114: end
115: pid = PidFile.load daemon
116: File.delete daemon.pid_file
117: pid && Process.kill( "TERM", pid )
118: return pid
119: end
120: end
121: end
122:
123: class RmpcNotify < Daemon::Base
124:
125: PLAY_IMG = "media-playback-start"
126: PAUSE_IMG = "media-playback-pause"
127: STOP_IMG = "media-playback-stop"
128:
129: CONNECT_IMG = "info"
130: DISCONNECT_IMG = "error"
131:
132: def self.start( args )
133: @host = args[0]
134: @port = args[1]
135: @mpd = MPD.new @host, @port
136:
137: @mpd.register_callback( self.method('song_cb'), MPD::CURRENT_SONG_CALLBACK )
138: @mpd.register_callback( self.method('state_cb'), MPD::STATE_CALLBACK )
139: @mpd.register_callback( self.method('connection_cb'), MPD::CONNECTION_CALLBACK )
140:
141: Notify.init "RmpcNotify"
142:
143: @state_note = Notify::Notification.new "State","","",nil
144: @song_note = Notify::Notification.new "Song","","",nil
145: @conn_note = Notify::Notification.new "Conn","","",nil
146:
147: quit = false
148:
149: # Set up the trap for Ctrl-C presses
150: trap "SIGINT" do
151: quit = true
152: end
153:
154: @mpd.connect true
155:
156: loop do
157: break if quit
158: sleep 2
159: end
160:
161: if quit
162: self.stop
163: end
164: end
165:
166: def self.stop
167: puts "Shutting down rmpc-notify"
168: @mpd.disconnect
169: Notify.uninit
170: end
171:
172: def self.usr1_trap
173: if @mpd.connected?
174: state = @mpd.status['state']
175: if state != 'stop'
176: self.song_cb @mpd.current_song
177: else
178: self.state_cb state
179: end
180: else
181: self.connection_cb false
182: end
183: end
184:
185: def self.output_song( song )
186: ret = "<b>"
187: if not song.artist.nil?
188: ret += "#{song.artist} - "
189: end
190:
191: if not song.title.nil?
192: ret += "#{song.title}"
193: end
194:
195: if ret.empty?
196: ret = song.file
197: end
198:
199: status = @mpd.status
200:
201: return ret if status.nil?
202:
203: size = status['playlistlength']
204: time = status['time'].split ':'
205:
206: e_min = time[0].to_i / 60
207: e_hrs = e_min / 60
208: e_min = e_min % 60
209: e_sec = time[0].to_i % 60
210:
211: e_sec = "0#{e_sec}" if e_sec < 10
212:
213: hrs = e_hrs == 0 ? "" : "#{e_hrs}:"
214:
215: ret += "</b>\n\nTime: #{hrs}#{e_min}:#{e_sec}\n"
216: ret += "Bitrate: #{status['bitrate']} kbps\n"
217: ret += "Playlist: #{song.pos} of #{size}"
218:
219: return ret
220: end
221:
222: def self.state_cb( newstate )
223: @conn_note.close
224: @state_note.close
225: case newstate
226: when 'play'
227: song = @mpd.current_song
228: @state_note.update "MPD: Playing",output_song(song),PLAY_IMG
229: when 'pause'
230: song = @mpd.current_song
231: @state_note.update "MPD: Paused",output_song(song),PAUSE_IMG
232: when 'stop'
233: @state_note.update "MPD: Stopped",nil,STOP_IMG
234: else
235: return
236: end
237: @state_note.show
238: end
239:
240: def self.song_cb( current )
241: return if current.nil?
242: status = @mpd.status
243: return if status.nil? or status['state'] != 'play'
244: @conn_note.close
245: @state_note.close
246: @song_note.close
247: @song_note.update "Now Playing:",output_song(current),"audio-x-generic"
248: @song_note.show
249: end
250:
251: def self.connection_cb( connected )
252: @conn_note.close
253: if not connected
254: @conn_note.update "MPD: Disconnected",
255: "Disconnected from MPD at '#{@host}:#{@port}'",
256: DISCONNECT_IMG
257: @conn_note.urgency = Notify::Notification::URGENCY_CRITICAL
258: @conn_note.show
259: else
260: @conn_note.update "MPD: Connected",
261: "Successfully connected to MPD at '#{@host}:#{@port}'",
262: CONNECT_IMG
263: @conn_note.urgency = Notify::Notification::URGENCY_NORMAL
264: @conn_note.show
265: end
266: end
267: end
268:
269: def _rmpc_usage
270: puts "rmpc-notify.rb usage:\n"
271: puts "\t-h host - MPD hostname (default: localhost)\n"
272: puts "\t-p port - MPD port (default: 6600)\n"
273: puts "\t-d - enable daemon mode (default: off)\n"
274: puts "\t-s - Tells the currently running daemon to\n"
275: puts "\t display the current song\n"
276: puts "\t-k - stop an already running copy\n"
277: puts "\t--help - print this help\n"
278: puts "\nCopyright 2007 Andrew Rader"
279: end
280:
281: host = 'localhost'
282: port = 6600
283: daemon = false
284: kill = false
285:
286: i = 0
287: loop do
288: break if i >= ARGV.length
289: case ARGV[i]
290: when '-p'
291: i+=1
292: port = ARGV[i]
293: when '-h'
294: i+=1
295: host = ARGV[i]
296: when '-d'
297: daemon = true
298: when '-k'
299: pid = RmpcNotify.daemonize 'stop'
300: puts "Stopped rmpc-notify (pid #{pid})"
301: exit
302: when '-z'
303: RmpcNotify.daemonize 'zap'
304: puts "Zapped (removed) old pid file"
305: exit
306: when '-s'
307: RmpcNotify.daemonize 'usr1'
308: exit
309: when '--help'
310: _rmpc_usage
311: exit
312: else
313: _rmpc_usage
314: exit
315: end
316: i+=1
317: end
318:
319: if host.nil? or port.nil?
320: _rmpc_usage
321: exit
322: end
323:
324: if daemon
325: RmpcNotify.daemonize 'start', host, port
326: else
327: RmpcNotify.start [host,port]
328: end