ff053dff957164d8b3b6db3b9be924bb5160d45a
[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("button_release_event") do
41                         Snett::Snett.instance.xmms.send(*args)
42                         false
43                 end
44         end
45 end
46
47 class XmmsClient::XmmsClient
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.1"
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 = XmmsClient::XmmsClient.new(PKG_NAME)
72                         @xmms.connect(ENV["XMMS_PATH"])
73                         @xmms.add_to_glib_mainloop
74
75                         @broadcasts = []
76
77                         self.set_title(PKG_NAME)
78
79                         signal_connect("destroy") do
80                                 @broadcasts.each { |bc| bc.disconnect }
81
82                                 Gtk.main_quit
83                         end
84
85                         self.resizable = false
86                         self.decorated = false
87
88                         @icon = Gdk::Pixbuf.new(DATADIR + "logo#{SIZE}.png")
89
90                         self.resize(@icon.width, @icon.height)
91
92                         #self.colormap = self.screen.rgba_colormap
93
94                         pixmap, mask = @icon.render_pixmap_and_mask(self.colormap, 255)
95                         self.shape_combine_mask(mask, 0, 0)
96
97                         # load icon and put it in an eventbox
98                         event_box = Gtk::EventBox.new.add(Gtk::Image.new(@icon))
99                         event_box.events = Gdk::Event::BUTTON_PRESS_MASK |
100                                            Gdk::Event::BUTTON_RELEASE_MASK
101
102                         event_box.signal_connect("button_release_event") do |_, event|
103                                 menu.popup(event) if event.button == 3
104                                 false
105                         end
106
107                         event_box.signal_connect("button_press_event") do |_, event|
108                                 if event.button == 1
109                                         x, y = Gdk::Display.default.pointer[1, 2]
110                                         self.begin_move_drag(event.button, x, y, event.time)
111                                 end
112
113                                 false
114                         end
115
116                         @broadcasts << @xmms.broadcast_playback_current_id
117                         @broadcasts.last.notifier(&method(:on_playback_current_id))
118
119                         @xmms.playback_current_id.
120                               notifier(&method(:on_playback_current_id))
121
122                         add(event_box)
123                 end
124
125                 private
126                 def menu
127                         @menu ||= Menu.new
128                 end
129
130                 def on_playback_current_id(res)
131                         return if res.value.zero?
132
133                         @xmms.medialib_get_info(res.value).
134                               notifier(&method(:on_mlib_get_info))
135                 end
136
137                 def on_mlib_get_info(res)
138                         info = res.value[:server]
139
140                         s = "%s - %s" % [info[:artist], info[:title]]
141                         @tooltips.set_tip(self, s, nil)
142                 end
143         end
144
145         class Menu < Gtk::Menu
146                 def initialize
147                         super
148
149                         [
150                                 [Gtk::Stock::MEDIA_PLAY, :playback_start],
151                                 [Gtk::Stock::MEDIA_PAUSE, :playback_pause],
152                                 [Gtk::Stock::MEDIA_STOP, :playback_stop]
153                         ].each do |(stock, meth)|
154                                 item = Gtk::ImageMenuItem.new(stock)
155                                 item.set_callback(meth)
156
157                                 append(item)
158                         end
159
160                         append(Gtk::SeparatorMenuItem.new)
161
162                         [
163                                 [Gtk::Stock::MEDIA_NEXT, :playback_next],
164                                 [Gtk::Stock::MEDIA_PREVIOUS, :playback_prev]
165                         ].each do |(stock, meth)|
166                                 item = Gtk::ImageMenuItem.new(stock)
167                                 item.set_callback(meth)
168
169                                 append(item)
170                         end
171
172                         append(Gtk::SeparatorMenuItem.new)
173
174                         [
175                                 [Gtk::Stock::OPEN, "Load playlist...", :load],
176                                 [Gtk::Stock::ADD, "Append playlist...", :append]
177                         ].each do |(stock, text, sym)|
178                                 item = Gtk::ImageMenuItem.new(text)
179                                 item.image = Gtk::Image.new(stock, Gtk::IconSize::MENU)
180
181                                 item.signal_connect("button_release_event") do
182                                         PlaylistDialog.new(sym).show_all
183                                         false
184                                 end
185
186                                 append(item)
187                         end
188
189                         item = Gtk::ImageMenuItem.new(Gtk::Stock::CLEAR)
190                         item.set_callback(:playlist_clear)
191
192                         append(item)
193
194                         append(Gtk::SeparatorMenuItem.new)
195
196                         item = Gtk::ImageMenuItem.new(Gtk::Stock::ABOUT)
197                         item.signal_connect("button_release_event") do
198                                 props = {
199                                         "name" => PKG_NAME,
200                                         "version" => VERSION,
201                                         "copyright" => "(c) 2006 Tilman Sauerbeck",
202                                         "logo" => Gdk::Pixbuf.new(DATADIR + "logo128.png")
203                                 }
204
205                                 Gtk::AboutDialog.show(Snett.instance, props)
206                                 false
207                         end
208
209                         append(item)
210
211                         item = Gtk::ImageMenuItem.new(Gtk::Stock::QUIT)
212                         item.signal_connect("button_release_event") do
213                                 Snett.instance.signal_emit("destroy")
214                                 false
215                         end
216
217                         append(item)
218
219                         show_all
220                 end
221
222                 def popup(event)
223                         super(nil, nil, event.button, event.time)
224                 end
225         end
226
227         class PlaylistDialog < Gtk::Dialog
228                 def initialize(mode)
229                         super("Playlists", Snett.instance,
230                               Gtk::Dialog::DESTROY_WITH_PARENT | Gtk::Dialog::MODAL,
231                               [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
232                                   [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
233
234                         @mode = mode
235
236                         signal_connect("response") do |_, id|
237                                 on_ok if id == Gtk::Dialog::RESPONSE_ACCEPT
238                                 destroy
239                         end
240
241                         @store = Gtk::ListStore.new(String)
242
243                         @view = Gtk::TreeView.new(@store)
244                         renderer = Gtk::CellRendererText.new
245
246                         col = Gtk::TreeViewColumn.new("Name", renderer, :text => 0)
247                         @view.append_column(col)
248
249                         vbox.add(@view)
250
251                         Snett.instance.xmms.medialib_playlists_list.
252                                             notifier(&method(:on_playlists_list))
253                 end
254
255                 private
256                 def on_playlists_list(res)
257                         res.value.sort.each do |list|
258                                 next if list[0, 1] == "_"
259
260                                 @store.append[0] = list
261                         end
262                 end
263
264                 def on_ok
265                         selection = @view.selection
266                         iter = selection.selected
267                         return if iter.nil?
268
269                         Snett.instance.xmms.playlist_clear if @mode == :load
270                         Snett.instance.xmms.medialib_playlist_load(iter[0])
271                 end
272         end
273 end
274
275 Gtk.init
276 Snett::Snett.instance.show_all
277 Gtk.main