Update tooltip with current volume level
[rox-volume.git] / volume.py
blobcc6b6e372c2297120411c8953bf1bd72a0959c6f
1 """
2 volume.py (a volume control applet for the ROX Panel)
4 Copyright 2004 Kenneth Hayber <ken@hayber.us>
5 All rights reserved.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License.
11 This program is distributed in the hope that it will be useful
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 """
21 import rox, sys, gtk
22 from rox import app_options, applet, Menu, InfoWin
23 from rox.options import Option
24 from volumecontrol import VolumeControl
26 try:
27 import alsaaudio
28 except:
29 rox.croak(_("You need to install the pyalsaaudio module"))
31 APP_NAME = 'VolumeX'
32 APP_DIR = rox.app_dir
33 APP_SIZE = [28, 150]
35 #Options.xml processing
36 rox.setup_app_options(APP_NAME, site='hayber.us')
37 Menu.set_save_name(APP_NAME, site='hayber.us')
39 MIXER_DEVICE = Option('mixer_device', 'default')
40 VOLUME_CONTROL = Option('volume_control', 'Master')
41 SHOW_ICON = Option('show_icon', True)
42 SHOW_BAR = Option('show_bar', False)
44 rox.app_options.notify()
47 class Volume(applet.Applet):
48 icons = []
51 """An applet to control a sound card Master or PCM volume"""
52 def __init__(self, filename):
53 applet.Applet.__init__(self, filename)
55 self.vertical = self.get_panel_orientation() in ('Right', 'Left')
56 if self.vertical:
57 self.set_size_request(8, -1)
58 self.box = gtk.VBox()
59 bar_orient = gtk.PROGRESS_LEFT_TO_RIGHT
60 else:
61 self.set_size_request(-1, 8)
62 self.box = gtk.HBox()
63 bar_orient = gtk.PROGRESS_BOTTOM_TO_TOP
65 self.add(self.box)
67 self.image = gtk.Image()
68 try:
69 theme = gtk.icon_theme_get_default()
70 self.icons.append(theme.load_icon('audio-volume-muted', 24, 0))
71 self.icons.append(theme.load_icon('audio-volume-low', 24, 0))
72 self.icons.append(theme.load_icon('audio-volume-medium', 24, 0))
73 self.icons.append(theme.load_icon('audio-volume-high', 24, 0))
74 except:
75 self.icons.append(gtk.gdk.pixbuf_new_from_file(APP_DIR+'/images/stock_volume-mute.svg'))
76 self.icons.append(gtk.gdk.pixbuf_new_from_file(APP_DIR+'/images/stock_volume-min.svg'))
77 self.icons.append(gtk.gdk.pixbuf_new_from_file(APP_DIR+'/images/stock_volume-med.svg'))
78 self.icons.append(gtk.gdk.pixbuf_new_from_file(APP_DIR+'/images/stock_volume-max.svg'))
79 self.pixbuf = self.icons[2]
80 self.image.set_from_pixbuf(self.pixbuf)
81 self.size = 0
82 self.box.pack_start(self.image)
84 self.bar = gtk.ProgressBar()
85 self.bar.set_orientation(bar_orient)
86 self.bar.set_size_request(12,12)
87 self.box.pack_end(self.bar)
89 self.tips = gtk.Tooltips()
90 # self.tips.set_tip(self, _('Volume control'), tip_private=None)
92 rox.app_options.add_notify(self.get_options)
93 self.connect('size-allocate', self.event_callback)
94 self.connect('scroll_event', self.button_scroll)
96 self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
97 self.connect('button-press-event', self.button_press)
98 self.menu = Menu.Menu('main', [
99 Menu.Action(_('Mixer'), 'run_mixer', ''),
100 Menu.Separator(),
101 Menu.Action(_('Options'), 'show_options', '', gtk.STOCK_PREFERENCES),
102 Menu.Action(_('Info'), 'get_info', '', gtk.STOCK_DIALOG_INFO),
103 Menu.Action(_('Close'), 'quit', '', gtk.STOCK_CLOSE),
105 self.menu.attach(self, self)
107 self.thing = None
108 self.get_volume(0)
110 self.show_all()
111 self.show()
112 if not SHOW_ICON.int_value:
113 self.image.hide()
114 if not SHOW_BAR.int_value:
115 self.bar.hide()
119 def button_scroll(self, window, event):
120 channel = 0
121 vol = self.bar.get_fraction()
122 if event.direction == 0:
123 vol += 0.02
124 elif event.direction == 1:
125 vol -= 0.02
126 self.set_volume((vol*100, vol*100), channel)
128 def event_callback(self, widget, rectangle):
129 """Called when the panel sends a size."""
130 if self.vertical:
131 size = rectangle[2]
132 else:
133 size = rectangle[3]
134 if size != self.size:
135 self.resize_image(size)
137 def resize_image(self, size):
138 """Called to resize the image."""
139 #I like the look better with the -4, there is no technical reason for it.
140 scaled_pixbuf = self.pixbuf.scale_simple(size-4, size-4, gtk.gdk.INTERP_BILINEAR)
141 self.image.set_from_pixbuf(scaled_pixbuf)
142 self.size = size
144 def button_press(self, window, event):
145 """Show/Hide the volume control on button 1 and the menu on button 3"""
146 if event.button == 1:
147 if not self.hide_volume():
148 self.show_volume(event)
149 elif event.button == 3:
150 self.hide_volume()
151 self.menu.popup(self, event, self.position_menu)
153 def hide_volume(self, event=None):
154 """Destroy the popup volume control"""
155 if self.thing:
156 self.thing.destroy()
157 self.thing = None
158 return True
159 return False
161 def get_panel_orientation(self):
162 """Return the panel orientation ('Top', 'Bottom', 'Left', 'Right')
163 and the margin for displaying a popup menu"""
164 pos = self.socket.property_get('_ROX_PANEL_MENU_POS', 'STRING', False)
165 if pos: pos = pos[2]
166 if pos:
167 side, margin = pos.split(',')
168 margin = int(margin)
169 else:
170 side, margin = None, 2
171 return side
173 def set_position(self):
174 """Set the position of the popup"""
175 side = self.get_panel_orientation()
176 vertical = False
178 # widget (x, y, w, h, bits)
179 geometry = self.socket.get_geometry()
181 if side == 'Bottom':
182 vertical = True
183 self.thing.set_size_request(APP_SIZE[0], APP_SIZE[1])
184 self.thing.move(self.socket.get_origin()[0],
185 self.socket.get_origin()[1]-APP_SIZE[1])
186 elif side == 'Top':
187 vertical = True
188 self.thing.set_size_request(APP_SIZE[0], APP_SIZE[1])
189 self.thing.move(self.socket.get_origin()[0],
190 self.socket.get_origin()[1]+geometry[3])
191 elif side == 'Left':
192 vertical = False
193 self.thing.set_size_request(APP_SIZE[1], APP_SIZE[0])
194 self.thing.move(self.socket.get_origin()[0]+geometry[2],
195 self.socket.get_origin()[1])
196 elif side == 'Right':
197 vertical = False
198 self.thing.set_size_request(APP_SIZE[1], APP_SIZE[0])
199 self.thing.move(self.socket.get_origin()[0]-APP_SIZE[1],
200 self.socket.get_origin()[1])
201 else:
202 vertical = True
203 self.thing.set_size_request(APP_SIZE[0], APP_SIZE[1])
204 self.thing.move(self.socket.get_origin()[0],
205 self.socket.get_origin()[1]-APP_SIZE[1])
206 return vertical
208 def show_volume(self, event):
209 """Display the popup volume control"""
211 self.thing = gtk.Window(type=gtk.WINDOW_POPUP)
212 self.thing.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_MENU)
213 self.thing.set_decorated(False)
215 vertical = self.set_position()
216 self.volume = VolumeControl(0, 0, 0, True, None, vertical)
217 self.volume.set_level(self.get_volume(0))
218 self.volume.connect("volume_changed", self.adjust_volume)
220 self.thing.add(self.volume)
221 self.thing.show_all()
222 self.thing.show()
224 def adjust_volume(self, vol, channel, vol_left, vol_right):
225 """Set the playback volume"""
226 self.set_volume((vol_left, vol_right), channel)
228 def set_volume(self, vol, channel):
229 """Send the volume setting(s) to the mixer """
230 mixer = alsaaudio.Mixer(VOLUME_CONTROL.value, 0, MIXER_DEVICE.value)
231 try:
232 mixer.setvolume(vol[0], 0)
233 mixer.setvolume(vol[1], 1)
234 except:
235 pass
237 if vol[0] <= 0:
238 self.pixbuf = self.icons[0]
239 elif vol[0] >= 66:
240 self.pixbuf = self.icons[3]
241 elif vol[0] >= 33:
242 self.pixbuf = self.icons[2]
243 else:
244 self.pixbuf = self.icons[1]
245 self.resize_image(self.size)
246 self.tips.set_tip(self, _('Volume control') + ': %d%%' % min(vol[0], vol[1]))
247 self.bar.set_fraction(max(vol[0], vol[1])/100.0)
249 def get_volume(self, channel):
250 """Get the volume settings from the mixer"""
251 mixer = alsaaudio.Mixer(VOLUME_CONTROL.value, 0, MIXER_DEVICE.value)
252 vol = mixer.getvolume()
253 self.bar.set_fraction(max(vol[0], vol[1])/100.0)
255 return (vol[0], vol[1])
257 def get_options(self):
258 """Used as the notify callback when options change"""
259 if VOLUME_CONTROL.has_changed:
260 self.get_volume(VOLUME_CONTROL.value)
262 if SHOW_BAR.has_changed:
263 if SHOW_BAR.int_value:
264 self.bar.show()
265 else:
266 self.bar.hide()
268 if SHOW_ICON.has_changed:
269 if SHOW_ICON.int_value:
270 self.image.show()
271 else:
272 self.image.hide()
274 def show_options(self, button=None):
275 """Options edit dialog"""
276 rox.edit_options()
278 def get_info(self):
279 """Display an InfoWin box"""
280 InfoWin.infowin(APP_NAME)
282 def run_mixer(self, button=None):
283 from rox import filer
284 filer.spawn_rox((APP_DIR,))
286 def quit(self):
287 """Quit"""
288 self.destroy()