~ubuntu-branches/ubuntu/maverick/awn-extras-applets/maverick

« back to all changes in this revision

Viewing changes to applets/maintained/volume-control/volume-control.py

  • Committer: Bazaar Package Importer
  • Author(s): Julien Lavergne
  • Date: 2010-08-29 14:29:52 UTC
  • mto: This revision was merged to the branch mainline in revision 21.
  • Revision ID: james.westby@ubuntu.com-20100829142952-rhvuetyms9bv5uu7
Tags: upstream-0.4.0+bzr1372
ImportĀ upstreamĀ versionĀ 0.4.0+bzr1372

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
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>
6
3
#
7
4
# This program is free software: you can redistribute it and/or modify
28
25
import gtk
29
26
from gtk import gdk
30
27
 
31
 
from awn.extras import awnlib, __version__
 
28
from awn.extras import _, awnlib, __version__
32
29
 
33
30
import pygst
34
31
pygst.require("0.10")
43
40
# value-changed callback of the volume scale to avoid jittering
44
41
gstreamer_freeze_messages_interval = 0.2
45
42
 
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")
48
45
 
49
46
theme_dir = "/usr/share/icons"
50
47
ui_file = os.path.join(os.path.dirname(__file__), "volume-control.ui")
51
48
 
52
 
system_theme_name = "System theme"
 
49
system_theme_name = _("System theme")
53
50
 
54
51
volume_control_apps = ["gnome-volume-control", "xfce4-mixer"]
55
52
 
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")
64
61
 
65
 
volume_ranges = {"high": (100, 66), "medium": (65, 36), "low": (35, 1)}
 
62
volume_ranges = [("high", 66), ("medium", 36), ("low", 1)]
66
63
volume_step = 4
67
64
 
68
65
mixer_names = ("pulsemixer", "oss4mixer", "alsamixer")
69
66
 
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.")
72
69
 
73
70
 
74
71
class BackendError(Exception):
103
100
            self.setup_main_dialog(prefs)
104
101
            self.setup_context_menu(prefs)
105
102
 
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())
108
105
 
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:
111
 
            self.backend.up()
 
108
            self.backend.increase_volume()
112
109
        elif event.direction == gdk.SCROLL_DOWN:
113
 
            self.backend.down()
 
110
            self.backend.decrease_volume()
114
111
 
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)
119
115
 
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)
123
 
 
 
118
 
 
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%")
 
121
 
 
122
        self.volume_label = prefs.get_object("label-volume")
 
123
        self.mute_item = prefs.get_object("checkbutton-mute")
 
124
 
 
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)
127
130
 
128
131
        self.applet.connect("middle-clicked", self.middle_clicked_cb)
129
132
 
131
134
        # Toggle 'Mute' checkbutton
132
135
        self.mute_item.set_active(not self.mute_item.get_active())
133
136
 
 
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:
 
140
            event.button = 2
 
141
 
 
142
        return False
 
143
 
 
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:
 
147
            event.button = 2
 
148
 
 
149
        volume = widget.get_value()
 
150
        self.mute_item.set_active(volume == 0)
 
151
 
 
152
        return False
 
153
 
134
154
    def volume_scale_changed_cb(self, widget):
135
155
        volume = widget.get_value()
136
156
 
144
164
 
145
165
                self.message_delay_handler.start()
146
166
 
 
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()
 
172
        return True
 
173
 
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.
149
176
 
150
177
        """
151
178
        menu = self.applet.dialog.menu
152
179
        menu_index = len(menu) - 1
153
180
 
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)
157
 
 
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)
161
184
 
162
 
        menu.insert(gtk.SeparatorMenuItem(), menu_index + 2)
 
185
        menu.insert(gtk.SeparatorMenuItem(), menu_index + 1)
163
186
 
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()
224
247
 
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()
227
251
 
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)
231
 
 
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):
 
259
            del tracks_model[0]
 
260
 
 
261
        self.backend.set_track(track)
 
262
 
 
263
        # Initialize mixer track combobox
 
264
        self.applet.settings["track"] = track
233
265
 
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])
263
295
                else:
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]
265
297
 
266
298
            self.applet.tooltip.set(self.backend.get_current_track_label() + ": " + title)
267
299
            self.applet.theme.icon(icon)
268
300
 
 
301
            # Update the volume label on the right of the volume scale
 
302
            self.volume_label.set_text("%d%%" % volume)
 
303
 
269
304
            self.volume_scale.set_value(volume)
270
305
 
271
306
            self.__old_volume = volume
275
310
        self.theme = self.applet.settings["theme"]
276
311
 
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]
279
314
 
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]
339
374
 
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")
343
378
 
344
379
        mixer_devices = self.find_mixer_and_devices(useable_mixers)
345
380
 
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")
349
384
 
350
385
        self.__mixer, self.__devices = mixer_devices
351
386
 
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)
355
390
 
 
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:
358
394
            bus = gst.Bus()
370
406
        """
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)
374
410
 
375
411
            if len(devices) > 0:
376
412
                return (mixer, devices)
377
413
 
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.
 
416
 
 
417
        """
379
418
        if not isinstance(mixer, gst.interfaces.PropertyProbe):
380
419
            raise RuntimeError(mixer.get_factory().get_name() + " cannot probe properties")
381
420
 
384
423
 
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)
388
427
 
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)
399
438
 
400
439
        return devices
401
440
 
 
441
    def __init_mixer_device(self, mixer, device):
 
442
        """Set the mixer to use the given device name.
 
443
 
 
444
        """
 
445
        mixer.set_state(gst.STATE_NULL)
 
446
        mixer.set_property("device", device)
 
447
        mixer.set_state(gst.STATE_READY)
 
448
 
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())
419
466
 
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)
424
 
 
425
467
    def set_device(self, device_label):
426
468
        """Set the mixer to the device labeled by the given label.
427
469
 
428
470
        """
429
 
        self.init_mixer_device(self.__mixer, self.__devices[device_label])
 
471
        self.__init_mixer_device(self.__mixer, self.__devices[device_label])
430
472
 
431
473
        self.__tracks = self.get_mixer_tracks(self.__mixer)
432
474
        self.__track_labels = [track.label for track in self.__tracks]
433
475
 
434
476
    def get_device_labels(self):
 
477
        """Return a list of labels of all devices that have previously
 
478
        been found.
 
479
 
 
480
        """
435
481
        return self.__devices.keys()
436
482
 
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.
 
486
 
 
487
        """
438
488
        return self.get_device_labels()[0]
439
489
 
440
490
    def set_track(self, track_label):
453
503
        self.__parent.refresh_icon(True)
454
504
 
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.
 
509
 
 
510
        """
456
511
        return self.__current_track.label
457
512
 
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.
 
516
 
 
517
        """
459
518
        return self.__track_labels
460
519
 
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.
464
524
 
465
525
        """
466
526
        for track in self.__tracks:
486
546
 
487
547
    def get_gst_volume(self):
488
548
        volume_channels = self.__mixer.get_volume(self.__current_track)
489
 
        return sum(volume_channels) / len(volume_channels)
 
549
        return sum(volume_channels) / len(volume_channels) - self.__current_track.min_volume
490
550
 
491
551
    def get_volume(self):
492
552
        return int(round(self.get_gst_volume() * self.__volume_multiplier))
500
560
        # Update applet's icon
501
561
        self.__parent.refresh_icon(True)
502
562
 
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))
505
565
 
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))
508
568
 
509
569
 
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"]})