]> git.netwichtig.de Git - user/henk/code/ruby/grux.git/blob - bin/grux.rb
ea75e69f3a0126e623aa1632142bca55288898f2
[user/henk/code/ruby/grux.git] / bin / grux.rb
1 #!/usr/bin/env ruby -w
2
3 #$: << File.expand_path(File.dirname(FILE) + “/../lib”))
4
5 #require 'libgruxclient'
6 require 'gtk2'
7 require 'xmmsclient'
8 require 'xmmsclient_glib'
9
10 class Grux
11 end
12
13 class Grux::ClientWin < Gtk::VBox
14   attr_reader :status
15
16   def initialize( server_address )
17     super()
18
19
20     @server                 = Xmms::Client.new( 'Grux' )
21
22     @status                 = Hash.new
23     @status[:connected]     = self.connect( server_address )
24
25     @statusBox              = Gtk::HBox.new
26     @mainBox                = Gtk::VPaned.new
27     @statusBar              = Gtk::Statusbar.new
28
29     self.pack_start @statusBox, false
30     self.pack_start @mainBox
31     self.pack_end @statusBar, false
32
33
34     @statusWdgts            = [
35       @currentartistFrame     = Gtk::Frame.new( "Artist" ).add( @currentartistLabel = Gtk::Label.new ),
36       @currenttitleFrame      = Gtk::Frame.new( "Title" ).add( @currenttitleLabel = Gtk::Label.new ),
37       @currentalbumFrame      = Gtk::Frame.new( "Album" ).add( @currentalbumLabel = Gtk::Label.new )
38     ]
39     @statusWdgts.each { |frame| @statusBox.pack_start frame }
40
41
42     @mainWdgts              = [
43       @cntrlBox               = Gtk::HBox.new,
44       @medialistsWin          = Gtk::Notebook.new
45     ]
46     @mainBox.pack1 @cntrlBox, true, false
47     @mainBox.pack2 @medialistsWin, true, false
48
49
50     @toolWdgts              = [
51       @cntrlBtnBox            = Gtk::VButtonBox.new.set_layout_style( Gtk::ButtonBox::CENTER ),
52       @toolChsr               = Gtk::Notebook.new
53     ]
54     @cntrlBox.pack_start @cntrlBtnBox, false
55     @cntrlBox.pack_start @toolChsr
56
57
58     @cntrlbtns      = [
59       @playBtn        = Gtk::ToggleButton.new('_Play', true),
60       @pauseBtn       = Gtk::ToggleButton.new('Pa_use', true),
61       @stopBtn        = Gtk::ToggleButton.new('_Stop', true),
62       @nextBtn        = Gtk::Button.new('_Next'),
63       @prevBtn        = Gtk::Button.new('P_revious'),
64       @rndmBtn        = Gtk::Button.new('Randomi_ze'),
65       @rptBtn         = Gtk::ToggleButton.new('Repeat _All', true),
66       @rpt1Btn        = Gtk::ToggleButton.new('Repeat _One', true)
67     ]
68     @cntrlbtns.each do |button|
69       @cntrlBtnBox.pack_start button
70     end
71
72
73     @toolTabs                 = [
74       [ @extinfoTab             = Gtk::VBox.new,
75         @extinfoTabLbl          = Gtk::Label.new( "Status" ) ],
76         [ @tageditorTab           = Gtk::VBox.new,
77           @tageditorTabLbl        = Gtk::Label.new( "Tag Editor" ) ]
78     ]
79     @toolTabs.each do |content, label|
80       @toolChsr.append_page( Gtk::ScrolledWindow.new.add_with_viewport( content ), label )
81     end
82
83
84     @medialistTextRenderer    = Gtk::CellRendererText.new
85     @medialistColumns         = [ 'id', 'artist', 'title' ]
86
87     @medialists               = [
88       [ @playlistViewLbl        = Gtk::Label.new( "Current Playlist" ),
89         @playlistViewWin        = Gtk::ScrolledWindow.new,
90         @playlistView           = Gtk::TreeView.new,
91         @playlistStore          = Gtk::ListStore.new( String, String, String )
92     ],
93       [ @medialibViewLbl        = Gtk::Label.new( "Medialib" ),
94         @medialibViewWin        = Gtk::ScrolledWindow.new,
95         @medialibView           = Gtk::TreeView.new,
96         @medialibStore          = Gtk::ListStore.new( String, String, String )
97     ]
98     ]
99
100     @medialists.each do |label, window, view, store|
101       @medialistColumns.each_with_index do |tag, index|
102         view.append_column Gtk::TreeViewColumn.new( tag, @medialistTextRenderer, :text => index )
103       end
104       view.search_column = 2
105       @medialistsWin.append_page( window.add( view ), label )
106     end
107
108
109     self.show_all
110
111
112     @playBtnClkdHndlr = @playBtn.signal_connect :clicked do
113       self.start_playback
114     end
115     @pauseBtnClkdHndlr = @pauseBtn.signal_connect :clicked do
116       self.pause_playback
117     end
118     @stopBtnClkdHndlr = @stopBtn.signal_connect :clicked do
119       self.stop_playback
120     end
121     @nextBtn.signal_connect :clicked do
122       self.step_in_playlist( 1 )
123     end
124     @prevBtn.signal_connect :clicked do
125       self.step_in_playlist( -1 )
126     end
127     @rndmBtn.signal_connect :clicked do
128       self.randomize_playlist
129     end
130     @rptBtnClkdHndlr = @rptBtn.signal_connect :clicked do
131       self.toggle_repeat_all
132     end
133     @rpt1BtnClkdHndlr = @rpt1Btn.signal_connect :clicked do
134       self.toggle_repeat_one
135     end
136
137
138     @playlistView.signal_connect :row_activated do |view, path, column|
139       @server.playlist_set_next( path.to_str.to_i ).notifier do
140         @server.playback_tickle.notifier do
141           self.start_playback
142           true
143         end
144         true
145       end
146     end
147
148
149     @medialibView.signal_connect :row_activated do |view, path, column|
150       songid = @medialibStore.get_iter( path )[0].to_i
151       @server.playlist.add_entry( songid ).notifier do
152         current_row = @playlistStore.append
153         @server.medialib_get_info( songid ).notifier do |songinfo|
154           current_row[0] = songid.to_s
155           current_row[1] = songinfo[:artist] ? songinfo[:artist].first[1] : ""
156           current_row[2] = songinfo[:title] ? songinfo[:title].first[1] : ""
157           true
158         end
159         @server.playlist_set_next( current_row.to_s.to_i ).notifier do
160           @server.playback_tickle.notifier do
161             self.start_playback
162             true
163           end
164           true
165         end
166       end
167     end
168
169
170     if @status[:connected]
171       p "connected"
172       self.resync_complete
173     end
174   end
175
176
177   def connect( address )
178     begin
179       @server.connect( address )
180       @server.add_to_glib_mainloop
181     rescue Xmms::Client::ClientError => ex
182       warn "Connection failed. Error Code:"
183       warn ex
184       return false
185     end
186
187
188     @server.broadcast_coll_changed.notifier do |*foo|
189       self.handle_coll_changed foo
190       true
191     end
192     @server.broadcast_config_value_changed.notifier do |*foo|
193       self.handle_config_value_changed  foo
194       true
195     end
196     @server.broadcast_mediainfo_reader_status.notifier do |*foo|
197       self.handle_mediainfo_reader_status  foo
198       true
199     end
200     @server.broadcast_medialib_entry_added.notifier do |*foo|
201       self.handle_medialib_entry_added  foo
202       true
203     end
204     @server.broadcast_medialib_entry_changed.notifier do |*foo|
205       self.handle_medialib_entry_changed  foo
206       true
207     end
208     @server.broadcast_playback_current_id.notifier do |id|
209       self.handle_playback_current_id id
210       true
211     end
212     @server.broadcast_playback_status.notifier do |status|
213       self.handle_playback_status status
214       true
215     end
216     @server.broadcast_playback_volume_changed.notifier do |*foo|
217       self.handle_playback_volume_changed  foo
218       true
219     end
220     @server.broadcast_playlist_changed.notifier do |*foo|
221       self.handle_playlist_changed  foo
222       true
223     end
224     @server.broadcast_playlist_current_pos.notifier do |pos|
225       self.handle_playlist_current_pos pos
226       true
227     end
228     @server.broadcast_playlist_loaded.notifier do |*foo|
229       self.handle_playlist_loaded  foo
230       true
231     end
232     @server.broadcast_quit.notifier do |*foo|
233       self.handle_quit  foo
234       true
235     end
236     true
237   end
238
239
240   def handle_coll_changed( *foo )
241     p "Collection changed: #{foo}"
242     true
243   end
244   def handle_config_value_changed( *foo )
245     p "Config changed: #{foo}"
246     true
247   end
248   def handle_mediainfo_reader_status( *foo )
249     p "Mediainfo reader status changed: #{foo}"
250     true
251   end
252   def handle_medialib_entry_added( *foo )
253     p "Medialib entry added: #{foo}"
254     true
255   end
256   def handle_medialib_entry_changed( *foo )
257     p "Medialib entry changed: #{foo}"
258     true
259   end
260
261
262   def handle_playback_current_id( id )
263     p "Current playback ID changed: #{id}"
264     songpath = Gtk::TreePath.new( @status[:playlist].index id )
265
266     @playlistView.scroll_to_cell( songpath, nil, true, 0.5, 0.5 )
267     @playlistView.set_cursor( songpath, nil, false )
268
269     @server.medialib_get_info( id ).notifier do |songinfo|
270       @currentartistLabel.text = ""
271       @currenttitleLabel.text = ""
272       @currentalbumLabel.text = ""
273
274       @extinfoTab.each do |child| @extinfoTab.remove child end
275
276       songinfo[:artist] && songinfo[:artist].each_pair do |src, value|
277         @currentartistLabel.text += @currentartistLabel.text.empty? ? value : "\n#{value}"
278       end
279       songinfo[:title] && songinfo[:title].each_pair do |src, value|
280         @currenttitleLabel.text += @currenttitleLabel.text.empty? ? value : "\n#{value}"
281       end
282       songinfo[:album] && songinfo[:album].each_pair do |src, value|
283         @currentalbumLabel.text += @currentalbumLabel.text.empty? ? value : "\n#{value}"
284       end
285       songinfo.each_key do |key|
286         infos = String.new
287         songinfo[key].each_pair do |src,value|
288           infos.empty? || infos += "\n"
289           infos += "#{value} (#{src})"
290         end
291         @extinfoTab.pack_end( Gtk::Frame.new( key.to_s ).add( Gtk::Label.new( infos ) ) )
292       end
293       @extinfoTab.show_all
294       true
295     end
296     true
297   end
298
299
300   def handle_playback_status( status )
301     p "Current playback status changed: #{status}"
302     @playbackState = status
303     true
304   end
305
306
307   def handle_playback_volume_changed( *foo )
308     p "Playback volume changed: #{foo}"
309     true
310   end
311   def handle_playlist_changed( *foo )
312     p "Playlist changed: #{foo}"
313     true
314   end
315
316
317   def handle_playlist_current_pos( pos )
318     p "Current pos changed: #{pos}"
319     #@server.playlist.entries.notifier do |playlist|
320     #  @server.medialib_get_info( playlist[pos[:position]] ).notifier do |songinfo|
321     #    @nextSongLbl.text = ""
322     #    songinfo.each_pair do |key, info|
323     #      info.each_pair do |src, value|
324     #        @nextSongLbl.text += "[#{src}] #{key} = #{value}\n"
325     #      end
326     #    end
327     #    true
328     #  end
329     #  true
330     #end
331     true
332   end
333
334
335   def handle_playlist_loaded( *foo )
336     p "Playlist loaded: #{foo}"
337     true
338   end
339   def handle_quit( *foo )
340     p "Server quit: #{foo}"
341     true
342   end
343
344
345   def xmmspls_to_lststor( songids, store )
346     songids.each do |id|
347       current_row = store.append
348       @server.medialib_get_info( id ).notifier do |songinfo|
349         current_row[0] = id.to_s
350         current_row[1] = songinfo[:artist] ? songinfo[:artist].first[1] : ""
351         current_row[2] = songinfo[:title] ? songinfo[:title].first[1] : ""
352         true
353       end
354     end
355     store
356   end
357
358
359
360   def start_playback
361     @server.playback_start.notifier do |res|
362       @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
363         @playBtn.active = true
364       end
365       @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
366         @pauseBtn.active = false
367       end
368       @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
369         @stopBtn.active = false
370       end
371       @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
372       true
373     end
374   end
375
376
377   def pause_playback
378     @server.playback_pause.notifier do |res|
379       @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
380         @playBtn.active = false
381       end
382       @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
383         @pauseBtn.active = true
384       end
385       @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
386         @stopBtn.active = false
387       end
388       @statusBar.push( @statusBar.get_context_id( "playback" ), "Paused playback" )
389       true
390     end
391   end
392
393
394   def stop_playback
395     @server.playback_stop.notifier do |res|
396       @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
397         @playBtn.active = false
398       end
399       @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
400         @pauseBtn.active = false
401       end
402       @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
403         @stopBtn.active = true
404       end
405       @statusBar.push( @statusBar.get_context_id( "playback" ), "Stopped playback" )
406       true
407     end
408   end
409
410
411   def toggle_playback
412     if @playbackState == 1
413       @server.playback_pause.notifier do |res|
414         @statusBar.push( @statusBar.get_context_id( "playback" ), "Paused playback" )
415         true
416       end
417     elsif @playbackState == ( 0 || 2 )
418       @server.playback_start.notifier do |res|
419         @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
420         true
421       end
422     else
423       warn "Playbackstate unknown"
424       @server.playback_start.notifier do |res|
425         @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
426         true
427       end
428     end
429   end
430
431
432   def step_in_playlist( offset = 1 )
433     @server.playlist_set_next_rel( offset ).notifier do
434       @server.playback_tickle.notifier do |res|
435         @statusBar.push( @statusBar.get_context_id( "playback" ), "Skipped playback by #{offset} song#{offset.abs >= 1 ? "s" : ''}" )
436         true
437       end
438       true
439     end
440   end
441
442
443   def handle_repeat_all_state( state )
444     @currentconfig[:"playlist.repeat_all"] = ( state ? "1" : "0" )
445     @rptBtn.signal_handler_block( @rptBtnClkdHndlr ) do
446       @rptBtn.active = state
447     end
448   end
449
450
451   def handle_repeat_one_state( state )
452     @currentconfig[:"playlist.repeat_one"] = ( state ? "1" : "0" )
453     @rpt1Btn.signal_handler_block( @rpt1BtnClkdHndlr ) do
454       @rpt1Btn.active = state
455     end
456   end
457
458
459   def toggle_repeat_all
460     if @currentconfig[:"playlist.repeat_all"] == "1"
461       @server.config_set_value( "playlist.repeat_all", "0" ).notifier do |res|
462         @server.config_get_value( "playlist.repeat_all" ).notifier do |value|
463           self.handle_repeat_all_state( value == "1" ? true : false )
464           true
465         end
466         true
467       end
468     elsif @currentconfig[:"playlist.repeat_all"] == "0"
469       @server.config_set_value( "playlist.repeat_all", "1" ).notifier do |res|
470         @server.config_get_value( "playlist.repeat_all" ).notifier do |value|
471           self.handle_repeat_all_state( value == "1" ? true : false )
472           true
473         end
474         true
475       end
476     else
477       warn "Unknown state: #{@currentconfig[:"playlist.repeat_all"]}"
478     end
479     true
480   end
481
482
483   def toggle_repeat_one
484     if @currentconfig[:"playlist.repeat_one"] == "1"
485       @server.config_set_value( "playlist.repeat_one", "0" ).notifier do |res|
486         @server.config_get_value( "playlist.repeat_one" ).notifier do |value|
487           self.handle_repeat_one_state( value == "1" ? true : false )
488           true
489         end
490         true
491       end
492     elsif @currentconfig[:"playlist.repeat_one"] == "0"
493       @server.config_set_value( "playlist.repeat_one", "1" ).notifier do |res|
494         @server.config_get_value( "playlist.repeat_one" ).notifier do |value|
495           self.handle_repeat_one_state( value == "1" ? true : false )
496           true
497         end
498         true
499       end
500     else
501       warn "Unknown state: #{@currentconfig[:"playlist.repeat_one"]}"
502     end
503     true
504   end
505
506
507   def resync_complete
508     @server.playlist.entries.notifier do |playlist|
509       @status[:playlist] = playlist
510       @playlistView.model = self.xmmspls_to_lststor( playlist, @playlistStore )
511       true
512     end
513
514     @server.coll_query_ids( Xmms::Collection.universe ).notifier do |medialibids|
515       @medialibView.model = self.xmmspls_to_lststor( medialibids, @medialibStore )
516       true
517     end
518
519     @server.playback_current_id.notifier do |id|
520       self.handle_playback_current_id id
521       true
522     end
523
524     @server.config_list_values.notifier do |res|
525       @currentconfig = {}
526       res.each_pair do |key, value|
527         @currentconfig[key] = value
528       end
529
530       @rpt1Btn.signal_handler_block( @rpt1BtnClkdHndlr ) do
531         @rpt1Btn.active = if( @currentconfig[:"playlist.repeat_one"] == "1" )
532                             true
533                           else
534                             false
535                           end
536       end
537       @rptBtn.signal_handler_block( @rptBtnClkdHndlr ) do
538         @rptBtn.active = if @currentconfig[:"playlist.repeat_all"] == "1"
539                            true
540                          else
541                            false
542                          end
543       end
544       true
545     end
546
547     @server.coll_list.notifier do |colls|
548       colls.each do |collname|
549         @server.coll_get( collname ).notifier do |coll|
550           @server.coll_query_ids( coll ).notifier do |ids|
551             p collname
552             p ids.join( "', '" )
553             true
554           end
555           true
556         end
557       end
558       true
559     end
560
561     true
562   end
563 end
564
565
566 class Grux::MainWin < Gtk::Window
567   def initialize
568     super
569
570     @mainCont       = Gtk::VBox.new
571     @btnCont        = Gtk::HBox.new
572     @clntCont       = Gtk::Notebook.new
573
574     @cnnctInput     = Gtk::ComboBoxEntry.new
575     @cnnctBtn       = Gtk::Button.new "_Connect"
576     @quitBtn        = Gtk::Button.new "_Quit"
577
578     self.title      = "Grux"
579
580     @winDstryHndlr  = self.signal_connect :destroy do
581       Gtk.main_quit
582     end
583
584     @cnnctInput.append_text 'tcp://127.0.0.1:9667'
585
586     @cnnctBtnHndlr  = @cnnctBtn.signal_connect :clicked do
587       if not @cnnctInput.active_text.empty?
588         server_address = @cnnctInput.active_text
589       else
590         server_address = ENV['XMMS_PATH']
591       end
592
593       @clntCont.append_page( Grux::ClientWin.new( server_address ), Gtk::Label.new( server_address ) )
594     end
595     @quitBtnHndlr   = @quitBtn.signal_connect :clicked do
596       Gtk.main_quit
597     end
598
599     @btnCont.pack_start @cnnctInput
600     @btnCont.pack_start @cnnctBtn
601     @btnCont.pack_end @quitBtn
602
603     @mainCont.pack_start @btnCont, false
604     @mainCont.pack_end @clntCont
605
606     self.add @mainCont
607     self.show_all
608   end
609 end
610
611
612 gruxWin = Grux::MainWin.new
613 Gtk.main
614
615
616 #:added
617 #:chain
618 #:id
619 #:laststarted
620 #:status
621 #:timesplayed
622 #:url
623