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