7dca5d4da53921f3f13f45986d35eb8d4dec1713
[snett.git] / bin / snett
1 #!/usr/bin/env ruby
2
3 # vim:syn=ruby
4 #
5 # Copyright (c) 2006 Tilman Sauerbeck (tilman at code-monkey de)
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be
16 # included in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26 require "gtk2"
27 require "xmmsclient"
28 require "xmmsclient_glib"
29 require "singleton"
30 require "pp"
31
32 PKG_NAME = "snett"
33 DATADIR = "/usr/local/share/#{PKG_NAME}/"
34 SIZE = 64
35
36 class Gtk::MenuItem
37         def set_callback(meth, args = [])
38                 args.unshift(meth)
39
40                 signal_connect("activate") do
41                         Snett::Snett.instance.xmms.send(*args)
42                         false
43                 end
44         end
45 end
46
47 class Xmms::Client
48         def playback_next
49                 playlist_set_next_rel(1).notifier { playback_tickle }
50         end
51
52         def playback_prev
53                 playlist_set_next_rel(-1).notifier { playback_tickle }
54         end
55 end
56
57 module Snett
58         VERSION = "0.0.2"
59
60         class Snett < Gtk::Window
61                 attr_reader :icon, :xmms
62
63                 include Singleton
64
65                 def initialize
66                         super(Gtk::Window::TOPLEVEL)
67
68                         @menu = nil
69                         @tooltips = Gtk::Tooltips.new
70
71                         @xmms = Xmms::Client.new(PKG_NAME)
72                         @xmms.connect(ENV["XMMS_PATH"])
73                         @xmms.add_to_glib_mainloop
74                         @xmms.on_disconnect { destroy }
75
76                         @broadcasts = []
77
78                         self.set_title(PKG_NAME)
79
80                         signal_connect("destroy") do
81                                 @broadcasts.each { |bc| bc.disconnect }
82
83                                 Gtk.main_quit
84                         end
85
86                         self.resizable = false
87                         self.decorated = false
88
89                         @icon = Gdk::Pixbuf.new(DATADIR + "logo#{SIZE}.png")
90
91                         self.resize(@icon.width, @icon.height)
92
93                         #self.colormap = self.screen.rgba_colormap
94
95                         pixmap, mask = @icon.render_pixmap_and_mask(self.colormap, 255)
96                         self.shape_combine_mask(mask, 0, 0)
97
98                         # load icon and put it in an eventbox
99                         event_box = Gtk::EventBox.new.add(Gtk::Image.new(@icon))
100                         event_box.events = Gdk::Event::BUTTON_PRESS_MASK |
101                                            Gdk::Event::BUTTON_RELEASE_MASK
102
103                         event_box.signal_connect("button_press_event") do |_, event|
104                                 if event.button == 1
105                                         x, y = Gdk::Display.default.pointer[1, 2]
106                                         self.begin_move_drag(event.button, x, y, event.time)
107                                 elsif event.button == 3
108                                         menu.popup(event)
109                                 end
110
111                                 false
112                         end
113
114                         @broadcasts << @xmms.broadcast_playback_current_id
115                         @broadcasts.last.notifier(&method(:on_playback_current_id))
116
117                         @xmms.playback_current_id.
118                               notifier(&method(:on_playback_current_id))
119
120                         add(event_box)
121                 end
122
123                 private
124                 def menu
125                         @menu ||= Menu.new
126                 end
127
128                 def on_playback_current_id(res)
129                         return if res.value.zero?
130
131                         @xmms.medialib_get_info(res.value).
132                               notifier(&method(:on_mlib_get_info))
133                 end
134
135                 def on_mlib_get_info(res)
136                         info = res.value[:server]
137
138                         s = "%s - %s" % [info[:artist], info[:title]]
139                         @tooltips.set_tip(self, s, nil)
140                 end
141         end
142
143         class Menu < Gtk::Menu
144                 def initialize
145                         super
146
147                         [
148                                 [Gtk::Stock::MEDIA_PLAY, :playback_start],
149                                 [Gtk::Stock::MEDIA_PAUSE, :playback_pause],
150                                 [Gtk::Stock::MEDIA_STOP, :playback_stop]
151                         ].each do |(stock, meth)|
152                                 item = Gtk::ImageMenuItem.new(stock)
153                                 item.set_callback(meth)
154
155                                 append(item)
156                         end
157
158                         append(Gtk::SeparatorMenuItem.new)
159
160                         [
161                                 [Gtk::Stock::MEDIA_NEXT, :playback_next],
162                                 [Gtk::Stock::MEDIA_PREVIOUS, :playback_prev]
163                         ].each do |(stock, meth)|
164                                 item = Gtk::ImageMenuItem.new(stock)
165                                 item.set_callback(meth)
166
167                                 append(item)
168                         end
169
170                         append(Gtk::SeparatorMenuItem.new)
171
172                         [
173                                 [Gtk::Stock::OPEN, "Load playlist...", :load],
174                                 [Gtk::Stock::ADD, "Append playlist...", :append]
175                         ].each do |(stock, text, sym)|
176                                 item = Gtk::ImageMenuItem.new(text)
177                                 item.image = Gtk::Image.new(stock, Gtk::IconSize::MENU)
178
179                                 item.signal_connect("activate") do
180                                         PlaylistDialog.new(sym).show_all
181                                         false
182                                 end
183
184                                 append(item)
185                         end
186
187                         item = Gtk::ImageMenuItem.new(Gtk::Stock::CLEAR)
188                         item.set_callback(:playlist_clear)
189
190                         append(item)
191
192                         append(Gtk::SeparatorMenuItem.new)
193
194                         item = Gtk::ImageMenuItem.new(Gtk::Stock::ABOUT)
195                         item.signal_connect("activate") do
196                                 props = {
197                                         "name" => PKG_NAME,
198                                         "version" => VERSION,
199                                         "copyright" => "(c) 2006 Tilman Sauerbeck",
200                                         "logo" => Gdk::Pixbuf.new(DATADIR + "logo128.png")
201                                 }
202
203                                 Gtk::AboutDialog.show(Snett.instance, props)
204                                 false
205                         end
206
207                         append(item)
208
209                         item = Gtk::ImageMenuItem.new(Gtk::Stock::QUIT)
210                         item.signal_connect("activate") do
211                                 Snett.instance.signal_emit("destroy")
212                                 false
213                         end
214
215                         append(item)
216
217                         show_all
218                 end
219
220                 def popup(event)
221                         super(nil, nil, event.button, event.time)
222                 end
223         end
224
225         class PlaylistDialog < Gtk::Dialog
226                 def initialize(mode)
227                         super("Playlists", Snett.instance,
228                               Gtk::Dialog::DESTROY_WITH_PARENT | Gtk::Dialog::MODAL,
229                               [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
230                                   [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
231
232                         @mode = mode
233
234                         signal_connect("response") do |_, id|
235                                 on_ok if id == Gtk::Dialog::RESPONSE_ACCEPT
236                                 destroy
237                         end
238
239                         @store = Gtk::ListStore.new(String)
240
241                         @view = Gtk::TreeView.new(@store)
242                         renderer = Gtk::CellRendererText.new
243
244                         col = Gtk::TreeViewColumn.new("Name", renderer, :text => 0)
245                         @view.append_column(col)
246
247                         scrolled = Gtk::ScrolledWindow.new
248                         scrolled.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
249                         scrolled.add(@view)
250
251                         vbox.add(scrolled)
252
253                         resize(350, 400)
254
255                         Snett.instance.xmms.medialib_playlists_list.
256                                             notifier(&method(:on_playlists_list))
257                 end
258
259                 private
260                 def on_playlists_list(res)
261                         res.value.sort.each do |list|
262                                 next if list[0, 1] == "_"
263
264                                 @store.append[0] = list
265                         end
266                 end
267
268                 def on_ok
269                         selection = @view.selection
270                         iter = selection.selected
271                         return if iter.nil?
272
273                         Snett.instance.xmms.playlist_clear if @mode == :load
274                         Snett.instance.xmms.medialib_playlist_load(iter[0])
275                 end
276         end
277 end
278
279 Gtk.init
280 Snett::Snett.instance.show_all
281 Gtk.main