2 volume.py (a volume control applet for the ROX Panel)
4 Copyright 2004 Kenneth Hayber <ken@hayber.us>
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
22 from rox
import app_options
, applet
, Menu
, InfoWin
23 from rox
.options
import Option
24 from volumecontrol
import VolumeControl
29 rox
.croak(_("You need to install the pyalsaaudio module"))
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
):
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')
57 self
.set_size_request(8, -1)
59 bar_orient
= gtk
.PROGRESS_LEFT_TO_RIGHT
61 self
.set_size_request(-1, 8)
63 bar_orient
= gtk
.PROGRESS_BOTTOM_TO_TOP
67 self
.image
= gtk
.Image()
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))
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
)
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', ''),
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
)
112 if not SHOW_ICON
.int_value
:
114 if not SHOW_BAR
.int_value
:
119 def button_scroll(self
, window
, event
):
121 vol
= self
.bar
.get_fraction()
122 if event
.direction
== 0:
124 elif event
.direction
== 1:
126 self
.set_volume((vol
*100, vol
*100), channel
)
128 def event_callback(self
, widget
, rectangle
):
129 """Called when the panel sends a size."""
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
)
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:
151 self
.menu
.popup(self
, event
, self
.position_menu
)
153 def hide_volume(self
, event
=None):
154 """Destroy the popup volume control"""
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)
167 side
, margin
= pos
.split(',')
170 side
, margin
= None, 2
173 def set_position(self
):
174 """Set the position of the popup"""
175 side
= self
.get_panel_orientation()
178 # widget (x, y, w, h, bits)
179 geometry
= self
.socket
.get_geometry()
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])
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])
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':
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])
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])
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()
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
)
232 mixer
.setvolume(vol
[0], 0)
233 mixer
.setvolume(vol
[1], 1)
238 self
.pixbuf
= self
.icons
[0]
240 self
.pixbuf
= self
.icons
[3]
242 self
.pixbuf
= self
.icons
[2]
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
:
268 if SHOW_ICON
.has_changed
:
269 if SHOW_ICON
.int_value
:
274 def show_options(self
, button
=None):
275 """Options edit dialog"""
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
,))