2
# Copyright (C) 2007 Richard "nazrat" Beyer, Jeff "Jawbreaker" Hubbard,
3
# Pavel Panchekha <pavpanchekha@gmail.com>,
4
# Spencer Creasey <screasey@gmail.com>
5
2
# Copyright (C) 2008 - 2010 onox <denkpadje@gmail.com>
7
4
# This program is free software: you can redistribute it and/or modify
43
40
# value-changed callback of the volume scale to avoid jittering
44
41
gstreamer_freeze_messages_interval = 0.2
46
applet_name = "Volume Control"
47
applet_description = "Applet to control your computer's volume"
43
applet_name = _("Volume Control")
44
applet_description = _("Applet to control your computer's volume")
49
46
theme_dir = "/usr/share/icons"
50
47
ui_file = os.path.join(os.path.dirname(__file__), "volume-control.ui")
52
system_theme_name = "System theme"
49
system_theme_name = _("System theme")
54
51
volume_control_apps = ["gnome-volume-control", "xfce4-mixer"]
62
59
# Logo of the applet, shown in the GTK About dialog
63
60
applet_logo = os.path.join(os.path.dirname(__file__), "volume-control.svg")
65
volume_ranges = {"high": (100, 66), "medium": (65, 36), "low": (35, 1)}
62
volume_ranges = [("high", 66), ("medium", 36), ("low", 1)]
68
65
mixer_names = ("pulsemixer", "oss4mixer", "alsamixer")
70
no_mixer_message = "Install one or more of the following GStreamer elements: %s."
71
no_devices_message = "Could not find any devices."
67
no_mixer_message = _("Install one or more of the following GStreamer elements: %s.")
68
no_devices_message = _("Could not find any devices.")
74
71
class BackendError(Exception):
103
100
self.setup_main_dialog(prefs)
104
101
self.setup_context_menu(prefs)
106
applet.connect("scroll-event", self.scroll_event_cb)
103
applet.connect("scroll-event", self.icon_scroll_event_cb)
107
104
applet.connect("position-changed", lambda a, o: self.refresh_orientation())
109
def scroll_event_cb(self, widget, event):
106
def icon_scroll_event_cb(self, widget, event):
110
107
if event.direction == gdk.SCROLL_UP:
108
self.backend.increase_volume()
112
109
elif event.direction == gdk.SCROLL_DOWN:
110
self.backend.decrease_volume()
115
112
def setup_main_dialog(self, prefs):
116
113
dialog = self.applet.dialog.new("main")
117
dialog.set_geometry_hints(min_width=50, min_height=200)
118
prefs.get_object("vbox-volume").reparent(dialog)
114
prefs.get_object("hbox-volume").reparent(dialog)
120
self.volume_scale = prefs.get_object("vscale-volume")
116
self.volume_scale = prefs.get_object("hscale-volume")
121
117
self.volume_scale.props.can_focus = False
122
self.volume_scale.set_increments(volume_step, 10)
119
if gtk.gtk_version >= (2, 16, 0) and gtk.pygtk_version >= (2, 15, 0):
120
self.volume_scale.add_mark(100, gtk.POS_BOTTOM, "<small>%s</small>" % "100%")
122
self.volume_label = prefs.get_object("label-volume")
123
self.mute_item = prefs.get_object("checkbutton-mute")
125
self.volume_scale.connect("button-press-event", self.volume_scale_pressed_cb)
126
self.volume_scale.connect("button-release-event", self.volume_scale_released_cb)
124
127
self.volume_scale.connect("value-changed", self.volume_scale_changed_cb)
125
prefs.get_object("button-inc-volume").connect("button-release-event", self.backend.up)
126
prefs.get_object("button-dec-volume").connect("button-release-event", self.backend.down)
128
self.volume_scale.connect("scroll-event", self.volume_scale_scroll_event_cb)
129
self.mute_item.connect("toggled", self.mute_toggled_cb)
128
131
self.applet.connect("middle-clicked", self.middle_clicked_cb)
131
134
# Toggle 'Mute' checkbutton
132
135
self.mute_item.set_active(not self.mute_item.get_active())
137
def volume_scale_pressed_cb(self, widget, event):
138
# Same hack as used by gnome-volume-control to make left-click behave as middle-click
139
if event.button == 1:
144
def volume_scale_released_cb(self, widget, event):
145
# Same hack as used by gnome-volume-control to make left-click behave as middle-click
146
if event.button == 1:
149
volume = widget.get_value()
150
self.mute_item.set_active(volume == 0)
134
154
def volume_scale_changed_cb(self, widget):
135
155
volume = widget.get_value()
145
165
self.message_delay_handler.start()
167
def volume_scale_scroll_event_cb(self, widget, event):
168
if event.direction == gdk.SCROLL_UP:
169
self.backend.increase_volume()
170
elif event.direction == gdk.SCROLL_DOWN:
171
self.backend.decrease_volume()
147
174
def setup_context_menu(self, prefs):
148
"""Add "Mute" and "Open Volume Control" to the context menu.
175
"""Add "Open Volume Control" to the context menu.
151
178
menu = self.applet.dialog.menu
152
179
menu_index = len(menu) - 1
154
self.mute_item = gtk.CheckMenuItem("Mu_te")
155
self.mute_item.connect("toggled", self.mute_toggled_cb)
156
menu.insert(self.mute_item, menu_index)
158
volume_control_item = gtk.MenuItem("_Open Volume Control")
181
volume_control_item = gtk.MenuItem(_("_Open Volume Control"))
159
182
volume_control_item.connect("activate", self.show_volume_control_cb)
160
menu.insert(volume_control_item, menu_index + 1)
183
menu.insert(volume_control_item, menu_index)
162
menu.insert(gtk.SeparatorMenuItem(), menu_index + 2)
185
menu.insert(gtk.SeparatorMenuItem(), menu_index + 1)
164
187
preferences_vbox = self.applet.dialog.new("preferences").vbox
165
188
prefs.get_object("dialog-vbox").reparent(preferences_vbox)
222
245
def reload_tracks(self):
223
246
track_labels = self.backend.get_track_labels()
225
if self.applet.settings["track"] not in track_labels:
226
self.applet.settings["track"] = self.backend.get_default_track()
248
track = self.applet.settings["track"]
249
if track not in track_labels:
250
track = self.backend.get_default_track_label()
228
self.combobox_track.get_model().clear()
252
tracks_model = self.combobox_track.get_model()
253
number_old_tracks = len(tracks_model)
254
# self.combobox_track.get_model().clear()
229
255
for i in track_labels:
230
256
self.combobox_track.append_text(i)
232
self.backend.set_track(self.applet.settings["track"])
257
# Hackish way to delete old tracks (clear() triggers exception in configbinder)
258
for i in range(number_old_tracks):
261
self.backend.set_track(track)
263
# Initialize mixer track combobox
264
self.applet.settings["track"] = track
234
266
def combobox_device_changed_cb(self, value):
235
267
self.backend.set_device(self.applet.settings["device"])
261
293
if os.path.isdir(os.path.join(moonbeam_theme_dir, self.theme)):
262
294
icon = str(filter(lambda i: volume >= i, moonbeam_ranges)[0])
264
icon = [key for key, value in volume_ranges.iteritems() if volume <= value[0] and volume >= value[1]][0]
296
icon = [key for key, value in volume_ranges if volume >= value][0]
266
298
self.applet.tooltip.set(self.backend.get_current_track_label() + ": " + title)
267
299
self.applet.theme.icon(icon)
301
# Update the volume label on the right of the volume scale
302
self.volume_label.set_text("%d%%" % volume)
269
304
self.volume_scale.set_value(volume)
271
306
self.__old_volume = volume
275
310
self.theme = self.applet.settings["theme"]
277
312
is_moonbeam_theme = os.path.isdir(os.path.join(moonbeam_theme_dir, self.theme))
278
keys = list(moonbeam_ranges) if is_moonbeam_theme else volume_ranges.keys()
313
keys = list(moonbeam_ranges) if is_moonbeam_theme else [key for key, value in volume_ranges]
280
315
states = { "muted": "audio-volume-muted" }
281
316
for i in map(str, keys):
338
373
useable_mixers = [i for i in mixer_names if i in found_mixers]
340
375
if len(useable_mixers) == 0:
341
parent.applet.errors.general(("No mixer found", no_mixer_message % ", ".join(mixer_names)))
376
parent.applet.errors.general((_("No mixer found"), no_mixer_message % ", ".join(mixer_names)))
342
377
raise BackendError("No mixer found")
344
379
mixer_devices = self.find_mixer_and_devices(useable_mixers)
346
381
if mixer_devices is None:
347
parent.applet.errors.general(("No devices found", no_devices_message))
382
parent.applet.errors.general((_("No devices found"), no_devices_message))
348
383
raise BackendError("No devices found")
350
385
self.__mixer, self.__devices = mixer_devices
352
# Prefer PulseAudio's volume control when using its GStreamer mixer element pulsemixer
387
# Prefer PulseAudio's volume control when using GStreamer mixer element pulsemixer
353
388
if self.__mixer.get_factory().get_name() == "pulsemixer":
354
389
volume_control_apps.insert(0, pa_control_app)
391
# Set-up the necessary mechanism to receive volume/mute change updates
356
392
self.__mixer.set_state(gst.STATE_READY)
357
393
if self.__mixer.get_mixer_flags() & gst.interfaces.MIXER_FLAG_AUTO_NOTIFICATIONS:
371
407
for mixer_name in names:
372
408
mixer = gst.element_factory_make(mixer_name)
373
devices = self.find_devices(mixer)
409
devices = self.__find_devices(mixer)
375
411
if len(devices) > 0:
376
412
return (mixer, devices)
378
def find_devices(self, mixer):
414
def __find_devices(self, mixer):
415
"""Return a list of devices found via the given GStreamer mixer element.
379
418
if not isinstance(mixer, gst.interfaces.PropertyProbe):
380
419
raise RuntimeError(mixer.get_factory().get_name() + " cannot probe properties")
385
424
mixer.probe_property_name("device")
386
425
for device in mixer.probe_get_values_name("device"):
387
self.init_mixer_device(mixer, device)
426
self.__init_mixer_device(mixer, device)
389
428
if not isinstance(mixer, gst.interfaces.Mixer) or len(self.get_mixer_tracks(mixer)) == 0:
390
429
mixer.set_state(gst.STATE_NULL)
441
def __init_mixer_device(self, mixer, device):
442
"""Set the mixer to use the given device name.
445
mixer.set_state(gst.STATE_NULL)
446
mixer.set_property("device", device)
447
mixer.set_state(gst.STATE_READY)
402
449
def message_element_cb(self, bus, message):
403
450
if not self.freeze_messages.isSet() \
404
451
and message.type is gst.MESSAGE_ELEMENT and message.src is self.__mixer \
417
464
return bool(track.flags & gst.interfaces.MIXER_TRACK_OUTPUT) and track.num_channels > 0
418
465
return filter(filter_track, mixer.list_tracks())
420
def init_mixer_device(self, mixer, device):
421
mixer.set_state(gst.STATE_NULL)
422
mixer.set_property("device", device)
423
mixer.set_state(gst.STATE_READY)
425
467
def set_device(self, device_label):
426
468
"""Set the mixer to the device labeled by the given label.
429
self.init_mixer_device(self.__mixer, self.__devices[device_label])
471
self.__init_mixer_device(self.__mixer, self.__devices[device_label])
431
473
self.__tracks = self.get_mixer_tracks(self.__mixer)
432
474
self.__track_labels = [track.label for track in self.__tracks]
434
476
def get_device_labels(self):
477
"""Return a list of labels of all devices that have previously
435
481
return self.__devices.keys()
437
483
def get_default_device_label(self):
484
"""Return the label of the first known device. Assumes at least
485
one device has been found.
438
488
return self.get_device_labels()[0]
440
490
def set_track(self, track_label):
453
503
self.__parent.refresh_icon(True)
455
505
def get_current_track_label(self):
506
"""Return the label of the current mixer track. The current
507
track is the track on which operations (getting/setting volume
508
and (un)muting) are performed.
456
511
return self.__current_track.label
458
513
def get_track_labels(self):
514
"""Return a list of labels of all tracks that belong to the
515
device to which the GStreamer mixer element is currently set.
459
518
return self.__track_labels
461
def get_default_track(self):
462
"""Return the default track of the current mixer device. This is the
463
master track or otherwise the first track of the list of known tracks.
520
def get_default_track_label(self):
521
"""Return the label of the default track of the device to which
522
the mixer is set. This is the master track or otherwise the
523
first track of the list of known tracks.
466
526
for track in self.__tracks:
500
560
# Update applet's icon
501
561
self.__parent.refresh_icon(True)
503
def up(self, widget=None, event=None):
563
def increase_volume(self):
504
564
self.set_volume(min(100, self.get_volume() + volume_step))
506
def down(self, widget=None, event=None):
566
def decrease_volume(self):
507
567
self.set_volume(max(0, self.get_volume() - volume_step))
515
575
"author": "onox",
516
576
"copyright-year": "2008 - 2010",
517
577
"authors": ["onox <denkpadje@gmail.com>"],
518
"artists": ["Jakub Steiner"],
519
"type": ["Audio", "Midi"]},
520
["settings-per-instance"])
578
"artists": ["Jakub Steiner"]})