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