#!/usr/bin/python3
import gi
gi.require_version('Cvc', '1.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Cvc, GdkPixbuf, Gio
from SettingsWidgets import SidePage, GSettingsSoundFileChooser
from xapp.GSettingsWidgets import *
import util
CINNAMON_SOUNDS = "org.cinnamon.sounds"
CINNAMON_DESKTOP_SOUNDS = "org.cinnamon.desktop.sound"
MAXIMUM_VOLUME_KEY = "maximum-volume"
DECAY_STEP = .15
EFFECT_LIST = [
{"label": _("Starting Cinnamon"), "schema": CINNAMON_SOUNDS, "file": "login-file", "enabled": "login-enabled"},
{"label": _("Leaving Cinnamon"), "schema": CINNAMON_SOUNDS, "file": "logout-file", "enabled": "logout-enabled"},
{"label": _("Switching workspace"), "schema": CINNAMON_SOUNDS, "file": "switch-file", "enabled": "switch-enabled"},
{"label": _("Opening new windows"), "schema": CINNAMON_SOUNDS, "file": "map-file", "enabled": "map-enabled"},
{"label": _("Closing windows"), "schema": CINNAMON_SOUNDS, "file": "close-file", "enabled": "close-enabled"},
{"label": _("Minimizing windows"), "schema": CINNAMON_SOUNDS, "file": "minimize-file", "enabled": "minimize-enabled"},
{"label": _("Maximizing windows"), "schema": CINNAMON_SOUNDS, "file": "maximize-file", "enabled": "maximize-enabled"},
{"label": _("Unmaximizing windows"), "schema": CINNAMON_SOUNDS, "file": "unmaximize-file", "enabled": "unmaximize-enabled"},
{"label": _("Tiling and snapping windows"), "schema": CINNAMON_SOUNDS, "file": "tile-file", "enabled": "tile-enabled"},
{"label": _("Inserting a device"), "schema": CINNAMON_SOUNDS, "file": "plug-file", "enabled": "plug-enabled"},
{"label": _("Removing a device"), "schema": CINNAMON_SOUNDS, "file": "unplug-file", "enabled": "unplug-enabled"},
{"label": _("Showing notifications"), "schema": CINNAMON_SOUNDS, "file": "notification-file", "enabled": "notification-enabled"},
{"label": _("Changing the sound volume"), "schema": CINNAMON_DESKTOP_SOUNDS, "file": "volume-sound-file", "enabled": "volume-sound-enabled"}
]
SOUND_TEST_MAP = [
# name, position, icon name, row, col, pa id
[_("Front Left"), "front-left", "audio-speaker-left", 0, 0, 1],
[_("Front Right"), "front-right", "audio-speaker-right", 0, 2, 2],
[_("Front Center"), "front-center", "audio-speaker-center", 0, 1, 3],
[_("Rear Left"), "rear-left", "audio-speaker-left-back", 2, 0, 5],
[_("Rear Right"), "rear-right", "audio-speaker-right-back", 2, 2, 6],
[_("Rear Center"), "rear-center", "audio-speaker-center-back", 2, 1, 4],
[_("Subwoofer"), "lfe", "audio-subwoofer", 1, 1, 7],
[_("Side Left"), "side-left", "audio-speaker-left-side", 1, 0, 10],
[_("Side Right"), "side-right", "audio-speaker-right-side", 1, 2, 11]
]
def list_header_func(row, before, user_data):
if before and not row.get_header():
row.set_header(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
class SoundBox(Gtk.Box):
def __init__(self, title):
Gtk.Box.__init__(self)
self.set_orientation(Gtk.Orientation.VERTICAL)
self.set_spacing(5)
label = Gtk.Label()
label.set_markup("%s" % title)
label.set_xalign(0.0)
self.add(label)
frame = Gtk.Frame()
frame.set_shadow_type(Gtk.ShadowType.IN)
frame_style = frame.get_style_context()
frame_style.add_class("view")
self.pack_start(frame, True, True, 0)
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
frame.add(main_box)
scw = Gtk.ScrolledWindow()
scw.expand = True
scw.set_min_content_height (450)
scw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
scw.set_shadow_type(Gtk.ShadowType.NONE)
main_box.pack_start(scw, True, True, 0)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
scw.add(self.box)
self.list_box = Gtk.ListBox()
self.list_box.set_selection_mode(Gtk.SelectionMode.NONE)
self.list_box.set_header_func(list_header_func, None)
self.box.add(self.list_box)
def add_row(self, row):
self.list_box.add(row)
class Slider(SettingsWidget):
def __init__(self, title, minLabel, maxLabel, minValue, maxValue, sizeGroup, step=None, page=None, value=0, gicon=None, iconName=None):
super(Slider, self).__init__()
self.set_orientation(Gtk.Orientation.VERTICAL)
self.set_spacing(5)
self.set_margin_bottom(5)
if sizeGroup is None:
sizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
if step is None:
step = (maxValue - minValue) / 100
if page is None:
page = (maxValue - minValue) / 10
self.adjustment = Gtk.Adjustment.new(value, minValue, maxValue, step, page, 0)
topBox = Gtk.Box()
self.leftBox = Gtk.Box()
self.rightBox = Gtk.Box()
topGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
topGroup.add_widget(self.leftBox)
topGroup.add_widget(self.rightBox)
# add label and icon (if specified)
labelBox = Gtk.Box(spacing=5)
if gicon is not None:
appIcon = Gtk.Image.new_from_gicon(gicon, 2)
labelBox.pack_start(appIcon, False, False, 0)
elif iconName is not None:
appIcon = Gtk.Image.new_from_icon_name(iconName, 2)
labelBox.pack_start(appIcon, False, False, 0)
self.label = Gtk.Label(title)
labelBox.pack_start(self.label, False, False, 0)
labelBox.set_halign(Gtk.Align.CENTER)
topBox.pack_start(self.leftBox, False, False, 0)
topBox.pack_start(labelBox, True, True, 0)
topBox.pack_start(self.rightBox, False, False, 0)
# add scale
sliderBox = Gtk.Box()
self.slider = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.adjustment)
self.slider.props.draw_value = False
min_label= Gtk.Label()
max_label = Gtk.Label()
min_label.set_alignment(1.0, 0.75)
max_label.set_alignment(0.0, 0.75)
min_label.set_margin_right(6)
max_label.set_margin_left(6)
min_label.set_markup("%s" % minLabel)
max_label.set_markup("%s" % maxLabel)
sizeGroup.add_widget(min_label)
sizeGroup.add_widget(max_label)
sliderBox.pack_start(min_label, False, False, 0)
sliderBox.pack_start(self.slider, True, True, 0)
sliderBox.pack_start(max_label, False, False, 0)
self.pack_start(topBox, False, False, 0)
self.pack_start(sliderBox, False, False, 0)
self.show_all()
def setMark(self, val):
self.slider.add_mark(val, Gtk.PositionType.TOP, "")
class VolumeBar(Slider):
def __init__(self, normVolume, maxPercent, title=_("Volume: "), gicon=None, sizeGroup=None):
self.normVolume = normVolume
self.volume = 0
self.isMuted = False
self.baseTitle = title
self.stream = None
self.mutedHandlerId = 0
self.volumeHandlerId = 0
super(VolumeBar, self).__init__(title, _("Softer"), _("Louder"), 0, maxPercent, sizeGroup, 1, 5, 0, gicon)
self.set_spacing(0)
self.set_border_width(2)
self.set_margin_left(23)
self.set_margin_right(23)
self.slider.set_sensitive(False)
self.muteImage = Gtk.Image.new_from_icon_name("audio-volume-muted-symbolic", 1)
self.muteSwitch = Gtk.ToggleButton()
self.muteSwitch.set_image(self.muteImage)
self.muteSwitch.set_relief(Gtk.ReliefStyle.NONE)
self.muteSwitch.set_active(False)
self.muteSwitch.set_sensitive(False)
self.leftBox.pack_start(self.muteSwitch, False, False, 0)
if maxPercent > 100:
self.setMark(100)
self.muteSwitchHandlerId = self.muteSwitch.connect("clicked", self.toggleMute)
self.adjustmentHandlerId = self.adjustment.connect("value-changed", self.onVolumeChanged)
def connectStream(self):
self.mutedHandlerId = self.stream.connect("notify::is-muted", self.setVolume)
self.volumeHandlerId = self.stream.connect("notify::volume", self.setVolume)
self.setVolume(None, None)
def disconnectStream(self):
if self.mutedHandlerId > 0:
self.stream.disconnect(self.mutedHandlerId)
self.mutedHandlerId = 0
if self.volumeHandlerId > 0:
self.stream.disconnect(self.volumeHandlerId)
self.volumeHandlerId = 0
def setStream(self, stream):
if self.stream and stream != self.stream:
self.disconnectStream()
self.stream = stream
self.connectStream()
self.slider.set_sensitive(True)
self.muteSwitch.set_sensitive(True)
def setVolume(self, a, b):
if self.stream.get_is_muted():
newVolume = 0
self.isMuted = True
else:
newVolume = int(round(self.stream.props.volume / self.normVolume * 100))
self.isMuted = False
self.volume = newVolume
self.adjustment.handler_block(self.adjustmentHandlerId)
self.adjustment.set_value(newVolume)
self.adjustment.handler_unblock(self.adjustmentHandlerId)
self.updateStatus()
def onVolumeChanged(self, adjustment):
newVolume = int(round(self.adjustment.get_value()))
muted = newVolume == 0
self.volume = newVolume
self.stream.handler_block(self.volumeHandlerId)
self.stream.set_volume(newVolume * self.normVolume / 100)
self.stream.push_volume()
self.stream.handler_unblock(self.volumeHandlerId)
if self.stream.get_is_muted() != muted:
self.setMuted(muted)
self.updateStatus()
def setMuted(self, muted):
self.isMuted = muted
self.stream.change_is_muted(muted)
def toggleMute(self, a=None):
self.setMuted(not self.isMuted)
def updateStatus(self):
self.muteSwitch.handler_block(self.muteSwitchHandlerId)
self.muteSwitch.set_active(self.isMuted)
self.muteSwitch.handler_unblock(self.muteSwitchHandlerId)
if self.isMuted:
self.muteImage.set_from_icon_name("audio-volume-muted-symbolic", 1)
self.label.set_label(self.baseTitle + _("Muted"))
self.muteSwitch.set_tooltip_text(_("Click to unmute"))
else:
self.muteImage.set_from_icon_name("audio-volume-high-symbolic", 1)
self.label.set_label(self.baseTitle + str(self.volume) + "%")
self.muteSwitch.set_tooltip_text(_("Click to mute"))
class BalanceBar(Slider):
def __init__(self, settingType, minVal = -1, norm = 1, sizeGroup=None):
self.type = settingType
self.norm = norm
self.value = 0
if settingType == "balance":
title = _("Balance")
minLabel = _("Left")
maxLabel = _("Right")
elif settingType == "fade":
title = _("Fade")
minLabel = _("Rear")
maxLabel = _("Front")
elif settingType == "lfe":
title = _("Subwoofer")
minLabel = _("Soft")
maxLabel = _("Loud")
super(BalanceBar, self).__init__(title, minLabel, maxLabel, minVal, 1, sizeGroup, (1-minVal)/20.)
self.setMark(0)
self.slider.props.has_origin = False
self.adjustment.connect("value-changed", self.onLevelChanged)
def setChannelMap(self, channelMap):
self.channelMap = channelMap
self.channelMap.connect("volume-changed", self.getLevel)
self.set_sensitive(getattr(self.channelMap, "can_"+self.type)())
self.getLevel()
def getLevel(self, a=None, b=None):
value = round(getattr(self.channelMap, "get_"+self.type)(), 3)
if self.type == "lfe":
value = value / self.norm
if value == self.value:
return
self.value = value
self.adjustment.set_value(self.value)
def onLevelChanged(self, adjustment):
value = round(self.adjustment.get_value(), 3)
if self.value == value:
return
self.value = value
if self.type == "lfe":
value = value * self.norm
getattr(self.channelMap, "set_"+self.type)(value)
class VolumeLevelBar(SettingsWidget):
def __init__(self, sizeGroup):
super(VolumeLevelBar, self).__init__()
self.set_orientation(Gtk.Orientation.VERTICAL)
self.set_spacing(5)
self.lastPeak = 0
self.monitorId = None
self.stream = None
self.pack_start(Gtk.Label(_("Input level")), False, False, 0)
levelBox = Gtk.Box()
self.levelBar = Gtk.LevelBar()
leftPadding = Gtk.Box()
sizeGroup.add_widget(leftPadding)
rightPadding = Gtk.Box()
sizeGroup.add_widget(rightPadding)
levelBox.pack_start(leftPadding, False, False, 0)
levelBox.pack_start(self.levelBar, True, True, 0)
levelBox.pack_start(rightPadding, False, False, 0)
self.pack_start(levelBox, False, False, 5)
self.levelBar.set_min_value(0)
def setStream(self, stream):
if self.stream is not None:
self.stream.remove_monitor()
self.stream.disconnect(self.monitorId)
self.stream = stream
self.stream.create_monitor()
self.monitorId = self.stream.connect("monitor-update", self.update)
def update(self, stream, value):
if self.lastPeak >= DECAY_STEP and value < self.lastPeak - DECAY_STEP:
value = self.lastPeak - DECAY_STEP
self.lastPeak = value
self.levelBar.set_value(value)
class ProfileSelector(SettingsWidget):
def __init__(self, controller):
super(ProfileSelector, self).__init__()
self.controller = controller
self.model = Gtk.ListStore(str, str)
self.combo = Gtk.ComboBox()
self.combo.set_model(self.model)
render = Gtk.CellRendererText()
self.combo.pack_start(render, True)
self.combo.add_attribute(render, "text", 1)
self.combo.set_id_column(0)
self.pack_start(Gtk.Label(_("Output profile")), False, False, 0)
button = Gtk.Button.new_with_label(_("Test sound"))
self.pack_end(button, False, False, 0)
self.pack_end(self.combo, False, False, 0)
button.connect("clicked", self.testSpeakers)
self.combo.connect("changed", self.onProfileSelect)
def setDevice(self, device):
self.device = device
# set the available output profiles in the combo box
profiles = device.get_profiles()
self.model.clear()
for profile in profiles:
self.model.append([profile.profile, profile.human_profile])
self.profile = device.get_active_profile()
self.combo.set_active_id(self.profile)
def onProfileSelect(self, a):
newProfile = self.combo.get_active_id()
if newProfile != self.profile and newProfile is not None:
self.profile = newProfile
self.controller.change_profile_on_selected_device(self.device, newProfile)
def testSpeakers(self, a):
SoundTest(a.get_toplevel(), self.controller.get_default_sink())
class Effect(GSettingsSoundFileChooser):
def __init__(self, info, sizeGroup):
super(Effect, self).__init__(info["label"], info["schema"], info["file"])
self.enabled_key = info["enabled"]
self.enabled_switch = Gtk.Switch()
self.pack_end(self.enabled_switch, False, False, 0)
self.reorder_child(self.enabled_switch, 1)
sizeGroup.add_widget(self.content_widget)
self.settings.bind(self.enabled_key, self.enabled_switch, "active", Gio.SettingsBindFlags.DEFAULT)
class SoundTest(Gtk.Dialog):
def __init__(self, parent, stream):
Gtk.Dialog.__init__(self, _("Test Sound"), parent)
self.stream = stream
self.positions = []
grid = Gtk.Grid()
grid.set_column_spacing(75)
grid.set_row_spacing(75)
grid.set_column_homogeneous(True)
grid.set_row_homogeneous(True)
sizeGroup = Gtk.SizeGroup(Gtk.SizeGroupMode.BOTH)
index = 0
for position in SOUND_TEST_MAP:
container = Gtk.Box()
button = Gtk.Button()
sizeGroup.add_widget(button)
button.set_relief(Gtk.ReliefStyle.NONE)
box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
button.add(box)
icon = Gtk.Image.new_from_icon_name(position[2], Gtk.IconSize.DIALOG)
box.pack_start(icon, False, False, 0)
box.pack_start(Gtk.Label(position[0]), False, False, 0)
info = {"index":index, "icon":icon, "button":button}
button.connect("clicked", self.test, info)
container.add(button)
grid.attach(container, position[4], position[3], 1, 1)
index = index + 1
self.positions.append(info)
content_area = self.get_content_area()
content_area.set_border_width(12)
content_area.add(grid)
button = Gtk.Button.new_from_stock("gtk-close")
button.connect("clicked", self._destroy)
content_area.add(button)
self.show_all()
self.setPositionHideState()
def _destroy(self, widget):
self.destroy()
def test(self, b, info):
position = SOUND_TEST_MAP[info["index"]]
if position[1] == "lfe":
sound = "audio-test-signal"
else:
sound = "audio-channel-"+position[1]
try:
util.play_sound_name(sound, position[1])
except GLib.Error as e:
print("Could not play test sound: %s" % e.message)
def setPositionHideState(self):
map = self.stream.get_channel_map()
for position in self.positions:
index = position["index"]
if map.has_position(SOUND_TEST_MAP[index][5]):
position["button"].show()
else:
position["button"].hide()
class Module:
name = "sound"
category = "hardware"
comment = _("Manage sound settings")
def __init__(self, content_box):
keywords = _("sound, media, music, speakers, audio, microphone, headphone")
self.sidePage = SidePage(_("Sound"), "cs-sound", keywords, content_box, module=self)
self.sound_settings = Gio.Settings(CINNAMON_DESKTOP_SOUNDS)
def on_module_selected(self):
if not self.loaded:
print("Loading Sound module")
self.outputDeviceList = Gtk.ListStore(str, # name
str, # device
bool, # active
int, # id
GdkPixbuf.Pixbuf) # icon
self.inputDeviceList = Gtk.ListStore(str, # name
str, # device
bool, # active
int, # id
GdkPixbuf.Pixbuf) # icon
self.appList = {}
self.inializeController()
self.buildLayout()
self.checkAppState()
self.checkInputState()
def buildLayout(self):
self.sidePage.stack = SettingsStack()
self.sidePage.add_widget(self.sidePage.stack)
## Output page
page = SettingsPage()
self.sidePage.stack.add_titled(page, "output", _("Output"))
self.outputSelector = self.buildDeviceSelect("output", self.outputDeviceList)
outputSection = page.add_section(_("Device"))
outputSection.add_row(self.outputSelector)
devSettings = page.add_section(_("Device settings"))
# output profiles
self.profile = ProfileSelector(self.controller)
devSettings.add_row(self.profile)
sizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
# output volume
max_volume = self.sound_settings.get_int(MAXIMUM_VOLUME_KEY)
self.outVolume = VolumeBar(self.controller.get_vol_max_norm(), max_volume, sizeGroup=sizeGroup)
devSettings.add_row(self.outVolume)
# balance
self.balance = BalanceBar("balance", sizeGroup=sizeGroup)
devSettings.add_row(self.balance)
self.fade = BalanceBar("fade", sizeGroup=sizeGroup)
devSettings.add_row(self.fade)
self.woofer = BalanceBar("lfe", 0, self.controller.get_vol_max_norm(), sizeGroup=sizeGroup)
devSettings.add_row(self.woofer)
## Input page
page = SettingsPage()
self.sidePage.stack.add_titled(page, "input", _("Input"))
self.inputStack = Gtk.Stack()
page.pack_start(self.inputStack, True, True, 0)
inputBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=15)
self.inputSelector = self.buildDeviceSelect("input", self.inputDeviceList)
deviceSection = SettingsSection("Device")
inputBox.pack_start(deviceSection, False, False, 0)
deviceSection.add_row(self.inputSelector)
devSettings = SettingsSection(_("Device settings"))
inputBox.pack_start(devSettings, False, False, 0)
sizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
# input volume
self.inVolume = VolumeBar(self.controller.get_vol_max_norm(), max_volume, sizeGroup=sizeGroup)
devSettings.add_row(self.inVolume)
# input level
self.inLevel = VolumeLevelBar(sizeGroup)
devSettings.add_row(self.inLevel)
self.inputStack.add_named(inputBox, "inputBox")
noInputsMessage = Gtk.Box()
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
image = Gtk.Image.new_from_icon_name("action-unavailable-symbolic", Gtk.IconSize.DIALOG)
image.set_pixel_size(96)
box.pack_start(image, False, False, 0)
box.set_valign(Gtk.Align.CENTER)
label = Gtk.Label(_("No inputs sources are currently available."))
box.pack_start(label, False, False, 0)
noInputsMessage.pack_start(box, True, True, 0)
self.inputStack.add_named(noInputsMessage, "noInputsMessage")
self.inputStack.show_all()
## Sounds page
page = SettingsPage()
self.sidePage.stack.add_titled(page, "sounds", _("Sounds"))
soundsVolumeSection = page.add_section(_("Sounds Volume"))
self.soundsVolume = VolumeBar(self.controller.get_vol_max_norm(), 100)
soundsVolumeSection.add_row(self.soundsVolume)
soundsSection = SoundBox(_("Sounds"))
page.pack_start(soundsSection, True, True, 0)
sizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
for effect in EFFECT_LIST:
soundsSection.add_row(Effect(effect, sizeGroup))
## Applications page
page = SettingsPage()
self.sidePage.stack.add_titled(page, "applications", _("Applications"))
self.appStack = Gtk.Stack()
page.pack_start(self.appStack, True, True, 0)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.appSettings = SoundBox(_("Applications"))
box.pack_start(self.appSettings, True, True, 0)
self.appStack.add_named(box, "appSettings")
noAppsMessage = Gtk.Box()
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
image = Gtk.Image.new_from_icon_name("action-unavailable-symbolic", Gtk.IconSize.DIALOG)
image.set_pixel_size(96)
box.pack_start(image, False, False, 0)
box.set_valign(Gtk.Align.CENTER)
label = Gtk.Label(_("No application is currently playing or recording audio."))
box.pack_start(label, False, False, 0)
noAppsMessage.pack_start(box, True, True, 0)
self.appStack.add_named(noAppsMessage, "noAppsMessage")
## Settings page
page = SettingsPage()
self.sidePage.stack.add_titled(page, "settings", _("Settings"))
amplificationSection = page.add_section(_("Amplification"))
self.maxVolume = Slider(_("Maximum volume: %d") % max_volume + "%", _("Reduced"), _("Amplified"), 1, 150, None, step=1, page=10, value=max_volume, gicon=None, iconName=None)
self.maxVolume.adjustment.connect("value-changed", self.onMaxVolumeChanged)
self.maxVolume.setMark(100)
amplificationSection.add_row(self.maxVolume)
def onMaxVolumeChanged(self, adjustment):
newValue = int(round(adjustment.get_value()))
self.sound_settings.set_int(MAXIMUM_VOLUME_KEY, newValue)
self.maxVolume.label.set_label(_("Maximum volume: %d") % newValue + "%")
self.outVolume.adjustment.set_upper(newValue)
self.outVolume.slider.clear_marks()
if newValue > 100:
self.outVolume.setMark(100)
def inializeController(self):
self.controller = Cvc.MixerControl(name = "cinnamon")
self.controller.connect("state-changed", self.setChannelMap)
self.controller.connect("output-added", self.deviceAdded, "output")
self.controller.connect("input-added", self.deviceAdded, "input")
self.controller.connect("output-removed", self.deviceRemoved, "output")
self.controller.connect("input-removed", self.deviceRemoved, "input")
self.controller.connect("active-output-update", self.activeOutputUpdate)
self.controller.connect("active-input-update", self.activeInputUpdate)
self.controller.connect("default-sink-changed", self.defaultSinkChanged)
self.controller.connect("default-source-changed", self.defaultSourceChanged)
self.controller.connect("stream-added", self.streamAdded)
self.controller.connect("stream-removed", self.streamRemoved)
self.controller.open()
def buildDeviceSelect(self, direction, model):
select = Gtk.IconView.new_with_model(model)
select.set_margin(0)
select.set_pixbuf_column(4)
select.set_text_column(0)
select.set_column_spacing(0)
select.connect("selection-changed", self.setActiveDevice, direction)
return select
def setActiveDevice(self, view, direction):
selected = view.get_selected_items()
if len(selected) == 0:
return
model = view.get_model()
newDeviceId = model.get_value(model.get_iter(selected[0]), 3)
newDevice = getattr(self.controller, "lookup_"+direction+"_id")(newDeviceId)
if newDevice is not None and newDeviceId != getattr(self, direction+"Id"):
getattr(self.controller, "change_"+direction)(newDevice)
self.profile.setDevice(newDevice)
def deviceAdded(self, c, deviceId, direction):
device = getattr(self.controller, "lookup_"+direction+"_id")(deviceId)
iconTheme = Gtk.IconTheme.get_default()
gicon = device.get_gicon()
iconName = device.get_icon_name()
icon = None
if gicon is not None:
lookup = iconTheme.lookup_by_gicon(gicon, 32, 0)
if lookup is not None:
icon = lookup.load_icon()
if icon is None:
if iconName is not None and "bluetooth" in iconName:
icon = iconTheme.load_icon("bluetooth", 32, 0)
elif iconTheme.has_icon("audio-card"):
# The audio-card icon was removed from adwaita, so may be absent in the current theme
icon = iconTheme.load_icon("audio-card", 32, 0)
else:
icon = iconTheme.load_icon("sound", 32, 0)
getattr(self, direction+"DeviceList").append([device.get_description() + "\n" + device.get_origin(), "", False, deviceId, icon])
if direction == "input":
self.checkInputState()
def deviceRemoved(self, c, deviceId, direction):
store = getattr(self, direction+"DeviceList")
for row in store:
if row[3] == deviceId:
store.remove(row.iter)
if direction == "input":
self.checkInputState()
return
def checkInputState(self):
if len(self.inputDeviceList) == 0:
self.inputStack.set_visible_child_name("noInputsMessage")
else:
self.inputStack.set_visible_child_name("inputBox")
def activeOutputUpdate(self, c, deviceId):
self.outputId = deviceId
device = self.controller.lookup_output_id(deviceId)
self.profile.setDevice(device)
# select current device in device selector
i = 0
for row in self.outputDeviceList:
if row[3] == deviceId:
self.outputSelector.select_path(Gtk.TreePath.new_from_string(str(i)))
i = i + 1
self.setChannelMap()
def activeInputUpdate(self, c, deviceId):
self.inputId = deviceId
# select current device in device selector
i = 0
for row in self.inputDeviceList:
if row[3] == deviceId:
self.inputSelector.select_path(Gtk.TreePath.new_from_string(str(i)))
i = i + 1
def defaultSinkChanged(self, c, deviceId):
defaultSink = self.controller.get_default_sink()
if defaultSink is None:
return
self.outVolume.setStream(defaultSink)
self.setChannelMap()
def defaultSourceChanged(self, c, deviceId):
defaultSource = self.controller.get_default_source()
if defaultSource is None:
return
self.inVolume.setStream(defaultSource)
self.inLevel.setStream(defaultSource)
def setChannelMap(self, a=None, b=None):
if self.controller.get_state() == Cvc.MixerControlState.READY:
channelMap = self.controller.get_default_sink().get_channel_map()
self.balance.setChannelMap(channelMap)
self.fade.setChannelMap(channelMap)
self.woofer.setChannelMap(channelMap)
def streamAdded(self, c, deviceId):
stream = self.controller.lookup_stream_id(deviceId)
if stream in self.controller.get_sink_inputs():
name = stream.props.name
# FIXME: We use to filter out by PA_PROP_APPLICATION_ID. But
# most streams report this as null now... why??
if name in ("cinnamon-settings.py",
"speech-dispatcher",
"speech-dispatcher-dummy",
"libcanberra",
"Muffin"):
# cinnamon-settings.py: test sounds
# speech-dispatcher[-dummy]: orca/speechd/spd-say
# libcanberra: cinnamon effects, test sounds - don't think this is needed any more?
# Muffin: window effects, some other cinnamon effects.
return
if deviceId in self.appList.keys():
# Don't add an input more than once
return
if name is None:
name = _("Unknown")
label = "%s: " % name
self.appList[deviceId] = VolumeBar(self.controller.get_vol_max_norm(),
100,
label,
stream.get_gicon())
self.appList[deviceId].setStream(stream)
self.appSettings.add_row(self.appList[deviceId])
self.appSettings.list_box.invalidate_headers()
self.appSettings.show_all()
elif stream == self.controller.get_event_sink_input():
self.soundsVolume.setStream(stream)
self.checkAppState()
def streamRemoved(self, c, deviceId):
if deviceId in self.appList:
self.appList[deviceId].get_parent().destroy()
self.appSettings.list_box.invalidate_headers()
del self.appList[deviceId]
self.checkAppState()
def checkAppState(self):
if len(self.appList) == 0:
self.appStack.set_visible_child_name("noAppsMessage")
else:
self.appStack.set_visible_child_name("appSettings")