1#!/usr/local/bin/python3.8 2 3import gi 4gi.require_version('Cvc', '1.0') 5gi.require_version('Gtk', '3.0') 6from gi.repository import Gtk, Cvc, GdkPixbuf, Gio 7from SettingsWidgets import SidePage, GSettingsSoundFileChooser 8from xapp.GSettingsWidgets import * 9import dbus 10 11CINNAMON_SOUNDS = "org.cinnamon.sounds" 12CINNAMON_DESKTOP_SOUNDS = "org.cinnamon.desktop.sound" 13MAXIMUM_VOLUME_KEY = "maximum-volume" 14 15DECAY_STEP = .15 16 17EFFECT_LIST = [ 18 {"label": _("Starting Cinnamon"), "schema": CINNAMON_SOUNDS, "file": "login-file", "enabled": "login-enabled"}, 19 {"label": _("Leaving Cinnamon"), "schema": CINNAMON_SOUNDS, "file": "logout-file", "enabled": "logout-enabled"}, 20 {"label": _("Switching workspace"), "schema": CINNAMON_SOUNDS, "file": "switch-file", "enabled": "switch-enabled"}, 21 {"label": _("Opening new windows"), "schema": CINNAMON_SOUNDS, "file": "map-file", "enabled": "map-enabled"}, 22 {"label": _("Closing windows"), "schema": CINNAMON_SOUNDS, "file": "close-file", "enabled": "close-enabled"}, 23 {"label": _("Minimizing windows"), "schema": CINNAMON_SOUNDS, "file": "minimize-file", "enabled": "minimize-enabled"}, 24 {"label": _("Maximizing windows"), "schema": CINNAMON_SOUNDS, "file": "maximize-file", "enabled": "maximize-enabled"}, 25 {"label": _("Unmaximizing windows"), "schema": CINNAMON_SOUNDS, "file": "unmaximize-file", "enabled": "unmaximize-enabled"}, 26 {"label": _("Tiling and snapping windows"), "schema": CINNAMON_SOUNDS, "file": "tile-file", "enabled": "tile-enabled"}, 27 {"label": _("Inserting a device"), "schema": CINNAMON_SOUNDS, "file": "plug-file", "enabled": "plug-enabled"}, 28 {"label": _("Removing a device"), "schema": CINNAMON_SOUNDS, "file": "unplug-file", "enabled": "unplug-enabled"}, 29 {"label": _("Showing notifications"), "schema": CINNAMON_SOUNDS, "file": "notification-file", "enabled": "notification-enabled"}, 30 {"label": _("Changing the sound volume"), "schema": CINNAMON_DESKTOP_SOUNDS, "file": "volume-sound-file", "enabled": "volume-sound-enabled"} 31] 32 33SOUND_TEST_MAP = [ 34 # name, position, icon name, row, col, pa id 35 [_("Front Left"), "front-left", "audio-speaker-left", 0, 0, 1], 36 [_("Front Right"), "front-right", "audio-speaker-right", 0, 2, 2], 37 [_("Front Center"), "front-center", "audio-speaker-center", 0, 1, 3], 38 [_("Rear Left"), "rear-left", "audio-speaker-left-back", 2, 0, 5], 39 [_("Rear Right"), "rear-right", "audio-speaker-right-back", 2, 2, 6], 40 [_("Rear Center"), "rear-center", "audio-speaker-center-back", 2, 1, 4], 41 [_("Subwoofer"), "lfe", "audio-subwoofer", 1, 1, 7], 42 [_("Side Left"), "side-left", "audio-speaker-left-side", 1, 0, 10], 43 [_("Side Right"), "side-right", "audio-speaker-right-side", 1, 2, 11] 44] 45 46def list_header_func(row, before, user_data): 47 if before and not row.get_header(): 48 row.set_header(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)) 49 50class SoundBox(Gtk.Box): 51 def __init__(self, title): 52 Gtk.Box.__init__(self) 53 self.set_orientation(Gtk.Orientation.VERTICAL) 54 self.set_spacing(5) 55 56 label = Gtk.Label() 57 label.set_markup("<b>%s</b>" % title) 58 label.set_xalign(0.0) 59 self.add(label) 60 61 frame = Gtk.Frame() 62 frame.set_shadow_type(Gtk.ShadowType.IN) 63 frame_style = frame.get_style_context() 64 frame_style.add_class("view") 65 self.pack_start(frame, True, True, 0) 66 67 main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 68 frame.add(main_box) 69 70 scw = Gtk.ScrolledWindow() 71 scw.expand = True 72 scw.set_min_content_height (450) 73 scw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) 74 scw.set_shadow_type(Gtk.ShadowType.NONE) 75 main_box.pack_start(scw, True, True, 0) 76 self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 77 scw.add(self.box) 78 79 self.list_box = Gtk.ListBox() 80 self.list_box.set_selection_mode(Gtk.SelectionMode.NONE) 81 self.list_box.set_header_func(list_header_func, None) 82 self.box.add(self.list_box) 83 84 def add_row(self, row): 85 self.list_box.add(row) 86 87class Slider(SettingsWidget): 88 def __init__(self, title, minLabel, maxLabel, minValue, maxValue, sizeGroup, step=None, page=None, value=0, gicon=None, iconName=None): 89 super(Slider, self).__init__() 90 self.set_orientation(Gtk.Orientation.VERTICAL) 91 self.set_spacing(5) 92 self.set_margin_bottom(5) 93 94 if sizeGroup == None: 95 sizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL) 96 97 if step == None: 98 step = (maxValue - minValue) / 100 99 if page == None: 100 page = (maxValue - minValue) / 10 101 self.adjustment = Gtk.Adjustment.new(value, minValue, maxValue, step, page, 0) 102 103 topBox = Gtk.Box() 104 self.leftBox = Gtk.Box() 105 self.rightBox = Gtk.Box() 106 topGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL) 107 topGroup.add_widget(self.leftBox) 108 topGroup.add_widget(self.rightBox) 109 110 # add label and icon (if specified) 111 labelBox = Gtk.Box(spacing=5) 112 if gicon != None: 113 appIcon = Gtk.Image.new_from_gicon(gicon, 2) 114 labelBox.pack_start(appIcon, False, False, 0) 115 elif iconName != None: 116 appIcon = Gtk.Image.new_from_icon_name(iconName, 2) 117 labelBox.pack_start(appIcon, False, False, 0) 118 self.label = Gtk.Label(title) 119 labelBox.pack_start(self.label, False, False, 0) 120 labelBox.set_halign(Gtk.Align.CENTER) 121 122 topBox.pack_start(self.leftBox, False, False, 0) 123 topBox.pack_start(labelBox, True, True, 0) 124 topBox.pack_start(self.rightBox, False, False, 0) 125 126 # add scale 127 sliderBox = Gtk.Box() 128 self.slider = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.adjustment) 129 self.slider.props.draw_value = False 130 131 min_label= Gtk.Label() 132 max_label = Gtk.Label() 133 min_label.set_alignment(1.0, 0.75) 134 max_label.set_alignment(0.0, 0.75) 135 min_label.set_margin_right(6) 136 max_label.set_margin_left(6) 137 min_label.set_markup("<i><small>%s</small></i>" % minLabel) 138 max_label.set_markup("<i><small>%s</small></i>" % maxLabel) 139 sizeGroup.add_widget(min_label) 140 sizeGroup.add_widget(max_label) 141 142 sliderBox.pack_start(min_label, False, False, 0) 143 sliderBox.pack_start(self.slider, True, True, 0) 144 sliderBox.pack_start(max_label, False, False, 0) 145 146 self.pack_start(topBox, False, False, 0) 147 self.pack_start(sliderBox, False, False, 0) 148 self.show_all() 149 150 def setMark(self, val): 151 self.slider.add_mark(val, Gtk.PositionType.TOP, "") 152 153class VolumeBar(Slider): 154 def __init__(self, normVolume, maxPercent, title=_("Volume: "), gicon=None, sizeGroup=None): 155 self.normVolume = normVolume 156 self.volume = 0 157 self.isMuted = False 158 self.baseTitle = title 159 160 self.stream = None 161 162 self.mutedHandlerId = 0 163 self.volumeHandlerId = 0 164 165 super(VolumeBar, self).__init__(title, _("Softer"), _("Louder"), 0, maxPercent, sizeGroup, 1, 5, 0, gicon) 166 self.set_spacing(0) 167 self.set_border_width(2) 168 self.set_margin_left(23) 169 self.set_margin_right(23) 170 self.slider.set_sensitive(False) 171 172 self.muteImage = Gtk.Image.new_from_icon_name("audio-volume-muted-symbolic", 1) 173 self.muteSwitch = Gtk.ToggleButton() 174 self.muteSwitch.set_image(self.muteImage) 175 self.muteSwitch.set_relief(Gtk.ReliefStyle.NONE) 176 self.muteSwitch.set_active(False) 177 self.muteSwitch.set_sensitive(False) 178 179 self.leftBox.pack_start(self.muteSwitch, False, False, 0) 180 181 if maxPercent > 100: 182 self.setMark(100) 183 184 self.muteSwitchHandlerId = self.muteSwitch.connect("clicked", self.toggleMute) 185 self.adjustmentHandlerId = self.adjustment.connect("value-changed", self.onVolumeChanged) 186 187 def connectStream(self): 188 self.mutedHandlerId = self.stream.connect("notify::is-muted", self.setVolume) 189 self.volumeHandlerId = self.stream.connect("notify::volume", self.setVolume) 190 self.setVolume(None, None) 191 192 def disconnectStream(self): 193 if self.mutedHandlerId > 0: 194 self.stream.disconnect(self.mutedHandlerId) 195 self.mutedHandlerId = 0 196 197 if self.volumeHandlerId > 0: 198 self.stream.disconnect(self.volumeHandlerId) 199 self.volumeHandlerId = 0 200 201 def setStream(self, stream): 202 if self.stream and stream != self.stream: 203 self.disconnectStream() 204 205 self.stream = stream 206 207 self.connectStream() 208 209 self.slider.set_sensitive(True) 210 self.muteSwitch.set_sensitive(True) 211 212 def setVolume(self, a, b): 213 if self.stream.get_is_muted(): 214 newVolume = 0 215 self.isMuted = True 216 else: 217 newVolume = int(round(self.stream.props.volume / self.normVolume * 100)) 218 self.isMuted = False 219 220 self.volume = newVolume 221 222 self.adjustment.handler_block(self.adjustmentHandlerId) 223 self.adjustment.set_value(newVolume) 224 self.adjustment.handler_unblock(self.adjustmentHandlerId) 225 226 self.updateStatus() 227 228 def onVolumeChanged(self, adjustment): 229 newVolume = int(round(self.adjustment.get_value())) 230 231 muted = newVolume == 0 232 233 self.volume = newVolume 234 235 self.stream.handler_block(self.volumeHandlerId) 236 self.stream.set_volume(newVolume * self.normVolume / 100) 237 self.stream.push_volume() 238 self.stream.handler_unblock(self.volumeHandlerId) 239 240 if self.stream.get_is_muted() != muted: 241 self.setMuted(muted) 242 243 self.updateStatus() 244 245 def setMuted(self, muted): 246 self.isMuted = muted 247 self.stream.change_is_muted(muted) 248 249 def toggleMute(self, a=None): 250 self.setMuted(not self.isMuted) 251 252 def updateStatus(self): 253 self.muteSwitch.handler_block(self.muteSwitchHandlerId) 254 self.muteSwitch.set_active(self.isMuted) 255 self.muteSwitch.handler_unblock(self.muteSwitchHandlerId) 256 257 if self.isMuted: 258 self.muteImage.set_from_icon_name("audio-volume-muted-symbolic", 1) 259 self.label.set_label(self.baseTitle + _("Muted")) 260 self.muteSwitch.set_tooltip_text(_("Click to unmute")) 261 else: 262 self.muteImage.set_from_icon_name("audio-volume-high-symbolic", 1) 263 self.label.set_label(self.baseTitle + str(self.volume) + "%") 264 self.muteSwitch.set_tooltip_text(_("Click to mute")) 265 266class BalanceBar(Slider): 267 def __init__(self, type, minVal = -1, norm = 1, sizeGroup=None): 268 self.type = type 269 self.norm = norm 270 self.value = 0 271 272 if type == "balance": 273 title = _("Balance") 274 minLabel = _("Left") 275 maxLabel = _("Right") 276 elif type == "fade": 277 title = _("Fade") 278 minLabel = _("Rear") 279 maxLabel = _("Front") 280 elif type == "lfe": 281 title = _("Subwoofer") 282 minLabel = _("Soft") 283 maxLabel = _("Loud") 284 285 super(BalanceBar, self).__init__(title, minLabel, maxLabel, minVal, 1, sizeGroup, (1-minVal)/20.) 286 287 self.setMark(0) 288 self.slider.props.has_origin = False 289 290 self.adjustment.connect("value-changed", self.onLevelChanged) 291 292 def setChannelMap(self, channelMap): 293 self.channelMap = channelMap 294 self.channelMap.connect("volume-changed", self.getLevel) 295 self.set_sensitive(getattr(self.channelMap, "can_"+self.type)()) 296 self.getLevel() 297 298 def getLevel(self, a=None, b=None): 299 value = round(getattr(self.channelMap, "get_"+self.type)(), 3) 300 if self.type == "lfe": 301 value = value / self.norm 302 if value == self.value: 303 return 304 self.value = value 305 self.adjustment.set_value(self.value) 306 307 def onLevelChanged(self, adjustment): 308 value = round(self.adjustment.get_value(), 3) 309 if self.value == value: 310 return 311 self.value = value 312 if self.type == "lfe": 313 value = value * self.norm 314 getattr(self.channelMap, "set_"+self.type)(value) 315 316class VolumeLevelBar(SettingsWidget): 317 def __init__(self, sizeGroup): 318 super(VolumeLevelBar, self).__init__() 319 self.set_orientation(Gtk.Orientation.VERTICAL) 320 self.set_spacing(5) 321 322 self.lastPeak = 0 323 self.monitorId = None 324 self.stream = None 325 326 self.pack_start(Gtk.Label(_("Input level")), False, False, 0) 327 328 levelBox = Gtk.Box() 329 self.levelBar = Gtk.LevelBar() 330 331 leftPadding = Gtk.Box() 332 sizeGroup.add_widget(leftPadding) 333 rightPadding = Gtk.Box() 334 sizeGroup.add_widget(rightPadding) 335 336 levelBox.pack_start(leftPadding, False, False, 0) 337 levelBox.pack_start(self.levelBar, True, True, 0) 338 levelBox.pack_start(rightPadding, False, False, 0) 339 340 self.pack_start(levelBox, False, False, 5) 341 342 self.levelBar.set_min_value(0) 343 344 def setStream(self, stream): 345 if self.stream != None: 346 self.stream.remove_monitor() 347 self.stream.disconnect(self.monitorId) 348 self.stream = stream 349 self.stream.create_monitor() 350 self.monitorId = self.stream.connect("monitor-update", self.update) 351 352 def update(self, stream, value): 353 if self.lastPeak >= DECAY_STEP and value < self.lastPeak - DECAY_STEP: 354 value = self.lastPeak - DECAY_STEP 355 self.lastPeak = value 356 357 self.levelBar.set_value(value) 358 359class ProfileSelector(SettingsWidget): 360 def __init__(self, controller): 361 super(ProfileSelector, self).__init__() 362 self.controller = controller 363 self.model = Gtk.ListStore(str, str) 364 365 self.combo = Gtk.ComboBox() 366 self.combo.set_model(self.model) 367 render = Gtk.CellRendererText() 368 self.combo.pack_start(render, True) 369 self.combo.add_attribute(render, "text", 1) 370 self.combo.set_id_column(0) 371 372 self.pack_start(Gtk.Label(_("Output profile")), False, False, 0) 373 button = Gtk.Button.new_with_label(_("Test sound")) 374 self.pack_end(button, False, False, 0) 375 self.pack_end(self.combo, False, False, 0) 376 377 button.connect("clicked", self.testSpeakers) 378 self.combo.connect("changed", self.onProfileSelect) 379 380 def setDevice(self, device): 381 self.device = device 382 # set the available output profiles in the combo box 383 profiles = device.get_profiles() 384 self.model.clear() 385 for profile in profiles: 386 self.model.append([profile.profile, profile.human_profile]) 387 388 self.profile = device.get_active_profile() 389 self.combo.set_active_id(self.profile) 390 391 def onProfileSelect(self, a): 392 newProfile = self.combo.get_active_id() 393 if newProfile != self.profile and newProfile != None: 394 self.profile = newProfile 395 self.controller.change_profile_on_selected_device(self.device, newProfile) 396 397 def testSpeakers(self, a): 398 SoundTest(a.get_toplevel(), self.controller.get_default_sink()) 399 400class Effect(GSettingsSoundFileChooser): 401 def __init__(self, info, sizeGroup): 402 super(Effect, self).__init__(info["label"], info["schema"], info["file"]) 403 404 self.enabled_key = info["enabled"] 405 406 self.enabled_switch = Gtk.Switch() 407 self.pack_end(self.enabled_switch, False, False, 0) 408 self.reorder_child(self.enabled_switch, 1) 409 410 sizeGroup.add_widget(self.content_widget) 411 412 self.settings.bind(self.enabled_key, self.enabled_switch, "active", Gio.SettingsBindFlags.DEFAULT) 413 414class SoundTest(Gtk.Dialog): 415 def __init__(self, parent, stream): 416 Gtk.Dialog.__init__(self, _("Test Sound"), parent) 417 418 self.stream = stream 419 self.positions = [] 420 421 grid = Gtk.Grid() 422 grid.set_column_spacing(75) 423 grid.set_row_spacing(75) 424 grid.set_column_homogeneous(True) 425 grid.set_row_homogeneous(True) 426 sizeGroup = Gtk.SizeGroup(Gtk.SizeGroupMode.BOTH) 427 428 index = 0 429 for position in SOUND_TEST_MAP: 430 container = Gtk.Box() 431 button = Gtk.Button() 432 sizeGroup.add_widget(button) 433 button.set_relief(Gtk.ReliefStyle.NONE) 434 box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) 435 button.add(box) 436 437 icon = Gtk.Image.new_from_icon_name(position[2], Gtk.IconSize.DIALOG) 438 box.pack_start(icon, False, False, 0) 439 box.pack_start(Gtk.Label(position[0]), False, False, 0) 440 441 info = {"index":index, "icon":icon, "button":button} 442 443 button.connect("clicked", self.test, info) 444 container.add(button) 445 grid.attach(container, position[4], position[3], 1, 1) 446 447 index = index + 1 448 self.positions.append(info) 449 450 content_area = self.get_content_area() 451 content_area.set_border_width(12) 452 content_area.add(grid) 453 454 button = Gtk.Button.new_from_stock("gtk-close") 455 button.connect("clicked", self._destroy) 456 content_area.add(button) 457 458 self.show_all() 459 self.setPositionHideState() 460 461 def _destroy(self, widget): 462 self.destroy() 463 464 def test(self, b, info): 465 position = SOUND_TEST_MAP[info["index"]] 466 467 if position[1] == "lfe": 468 sound = "audio-test-signal" 469 else: 470 sound = "audio-channel-"+position[1] 471 472 session_bus = dbus.SessionBus() 473 sound_dbus = session_bus.get_object("org.cinnamon.SettingsDaemon.Sound", "/org/cinnamon/SettingsDaemon/Sound") 474 play = sound_dbus.get_dbus_method('PlaySoundWithChannel', 'org.cinnamon.SettingsDaemon.Sound') 475 play(0, sound, position[1]) 476 477 def setPositionHideState(self): 478 map = self.stream.get_channel_map() 479 for position in self.positions: 480 index = position["index"] 481 if map.has_position(SOUND_TEST_MAP[index][5]): 482 position["button"].show() 483 else: 484 position["button"].hide() 485 486class Module: 487 name = "sound" 488 category = "hardware" 489 comment = _("Manage sound settings") 490 491 def __init__(self, content_box): 492 keywords = _("sound, media, music, speakers, audio, microphone, headphone") 493 self.sidePage = SidePage(_("Sound"), "cs-sound", keywords, content_box, module=self) 494 self.sound_settings = Gio.Settings(CINNAMON_DESKTOP_SOUNDS) 495 496 def on_module_selected(self): 497 if not self.loaded: 498 print("Loading Sound module") 499 500 self.outputDeviceList = Gtk.ListStore(str, # name 501 str, # device 502 bool, # active 503 int, # id 504 GdkPixbuf.Pixbuf) # icon 505 506 self.inputDeviceList = Gtk.ListStore(str, # name 507 str, # device 508 bool, # active 509 int, # id 510 GdkPixbuf.Pixbuf) # icon 511 512 self.appList = {} 513 514 self.inializeController() 515 self.buildLayout() 516 517 self.checkAppState() 518 self.checkInputState() 519 520 def buildLayout(self): 521 self.sidePage.stack = SettingsStack() 522 self.sidePage.add_widget(self.sidePage.stack) 523 524 ## Output page 525 page = SettingsPage() 526 self.sidePage.stack.add_titled(page, "output", _("Output")) 527 528 self.outputSelector = self.buildDeviceSelect("output", self.outputDeviceList) 529 outputSection = page.add_section(_("Device")) 530 outputSection.add_row(self.outputSelector) 531 532 devSettings = page.add_section(_("Device settings")) 533 534 # output profiles 535 self.profile = ProfileSelector(self.controller) 536 devSettings.add_row(self.profile) 537 538 sizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL) 539 540 # ouput volume 541 max_volume = self.sound_settings.get_int(MAXIMUM_VOLUME_KEY) 542 self.outVolume = VolumeBar(self.controller.get_vol_max_norm(), max_volume, sizeGroup=sizeGroup) 543 devSettings.add_row(self.outVolume) 544 545 # balance 546 self.balance = BalanceBar("balance", sizeGroup=sizeGroup) 547 devSettings.add_row(self.balance) 548 self.fade = BalanceBar("fade", sizeGroup=sizeGroup) 549 devSettings.add_row(self.fade) 550 self.woofer = BalanceBar("lfe", 0, self.controller.get_vol_max_norm(), sizeGroup=sizeGroup) 551 devSettings.add_row(self.woofer) 552 553 ## Input page 554 page = SettingsPage() 555 self.sidePage.stack.add_titled(page, "input", _("Input")) 556 557 self.inputStack = Gtk.Stack() 558 page.pack_start(self.inputStack, True, True, 0) 559 560 inputBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=15) 561 self.inputSelector = self.buildDeviceSelect("input", self.inputDeviceList) 562 deviceSection = SettingsSection("Device") 563 inputBox.pack_start(deviceSection, False, False, 0) 564 deviceSection.add_row(self.inputSelector) 565 566 devSettings = SettingsSection(_("Device settings")) 567 inputBox.pack_start(devSettings, False, False, 0) 568 569 sizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL) 570 571 # input volume 572 self.inVolume = VolumeBar(self.controller.get_vol_max_norm(), max_volume, sizeGroup=sizeGroup) 573 devSettings.add_row(self.inVolume) 574 575 # input level 576 self.inLevel = VolumeLevelBar(sizeGroup) 577 devSettings.add_row(self.inLevel) 578 self.inputStack.add_named(inputBox, "inputBox") 579 580 noInputsMessage = Gtk.Box() 581 box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) 582 image = Gtk.Image.new_from_icon_name("action-unavailable-symbolic", Gtk.IconSize.DIALOG) 583 image.set_pixel_size(96) 584 box.pack_start(image, False, False, 0) 585 box.set_valign(Gtk.Align.CENTER) 586 label = Gtk.Label(_("No inputs sources are currently available.")) 587 box.pack_start(label, False, False, 0) 588 noInputsMessage.pack_start(box, True, True, 0) 589 self.inputStack.add_named(noInputsMessage, "noInputsMessage") 590 self.inputStack.show_all() 591 592 ## Sounds page 593 page = SettingsPage() 594 self.sidePage.stack.add_titled(page, "sounds", _("Sounds")) 595 596 soundsVolumeSection = page.add_section(_("Sounds Volume")) 597 self.soundsVolume = VolumeBar(self.controller.get_vol_max_norm(), 100) 598 soundsVolumeSection.add_row(self.soundsVolume) 599 600 soundsSection = SoundBox(_("Sounds")) 601 page.pack_start(soundsSection, True, True, 0) 602 sizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL) 603 for effect in EFFECT_LIST: 604 soundsSection.add_row(Effect(effect, sizeGroup)) 605 606 ## Applications page 607 page = SettingsPage() 608 self.sidePage.stack.add_titled(page, "applications", _("Applications")) 609 610 self.appStack = Gtk.Stack() 611 page.pack_start(self.appStack, True, True, 0) 612 613 box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 614 self.appSettings = SoundBox(_("Applications")) 615 box.pack_start(self.appSettings, True, True, 0) 616 self.appStack.add_named(box, "appSettings") 617 618 noAppsMessage = Gtk.Box() 619 box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) 620 image = Gtk.Image.new_from_icon_name("action-unavailable-symbolic", Gtk.IconSize.DIALOG) 621 image.set_pixel_size(96) 622 box.pack_start(image, False, False, 0) 623 box.set_valign(Gtk.Align.CENTER) 624 label = Gtk.Label(_("No application is currently playing or recording audio.")) 625 box.pack_start(label, False, False, 0) 626 noAppsMessage.pack_start(box, True, True, 0) 627 self.appStack.add_named(noAppsMessage, "noAppsMessage") 628 629 ## Settings page 630 page = SettingsPage() 631 self.sidePage.stack.add_titled(page, "settings", _("Settings")) 632 633 amplificationSection = page.add_section(_("Amplification")) 634 self.maxVolume = Slider(_("Maximum volume: %d") % max_volume + "%", _("Reduced"), _("Amplified"), 1, 150, None, step=1, page=10, value=max_volume, gicon=None, iconName=None) 635 self.maxVolume.adjustment.connect("value-changed", self.onMaxVolumeChanged) 636 self.maxVolume.setMark(100) 637 amplificationSection.add_row(self.maxVolume) 638 639 def onMaxVolumeChanged(self, adjustment): 640 newValue = int(round(adjustment.get_value())) 641 self.sound_settings.set_int(MAXIMUM_VOLUME_KEY, newValue) 642 self.maxVolume.label.set_label(_("Maximum volume: %d") % newValue + "%") 643 self.outVolume.adjustment.set_upper(newValue) 644 self.outVolume.slider.clear_marks() 645 if (newValue > 100): 646 self.outVolume.setMark(100) 647 648 def inializeController(self): 649 self.controller = Cvc.MixerControl(name = "cinnamon") 650 self.controller.connect("state-changed", self.setChannelMap) 651 self.controller.connect("output-added", self.deviceAdded, "output") 652 self.controller.connect("input-added", self.deviceAdded, "input") 653 self.controller.connect("output-removed", self.deviceRemoved, "output") 654 self.controller.connect("input-removed", self.deviceRemoved, "input") 655 self.controller.connect("active-output-update", self.activeOutputUpdate) 656 self.controller.connect("active-input-update", self.activeInputUpdate) 657 self.controller.connect("default-sink-changed", self.defaultSinkChanged) 658 self.controller.connect("default-source-changed", self.defaultSourceChanged) 659 self.controller.connect("stream-added", self.streamAdded) 660 self.controller.connect("stream-removed", self.streamRemoved) 661 self.controller.open() 662 663 def buildDeviceSelect(self, type, model): 664 select = Gtk.IconView.new_with_model(model) 665 select.set_margin(0) 666 select.set_pixbuf_column(4) 667 select.set_text_column(0) 668 select.set_column_spacing(0) 669 670 select.connect("selection-changed", self.setActiveDevice, type) 671 672 return select 673 674 def setActiveDevice(self, view, type): 675 selected = view.get_selected_items() 676 if len(selected) == 0: 677 return 678 679 model = view.get_model() 680 newDeviceId = model.get_value(model.get_iter(selected[0]), 3) 681 newDevice = getattr(self.controller, "lookup_"+type+"_id")(newDeviceId) 682 if newDevice != None and newDeviceId != getattr(self, type+"Id"): 683 getattr(self.controller, "change_"+type)(newDevice) 684 self.profile.setDevice(newDevice) 685 686 def deviceAdded(self, c, id, type): 687 device = getattr(self.controller, "lookup_"+type+"_id")(id) 688 689 iconTheme = Gtk.IconTheme.get_default() 690 gicon = device.get_gicon() 691 iconName = device.get_icon_name() 692 icon = None 693 if gicon is not None: 694 lookup = iconTheme.lookup_by_gicon(gicon, 32, 0) 695 if lookup is not None: 696 icon = lookup.load_icon() 697 698 if icon is None: 699 if (iconName is not None and "bluetooth" in iconName): 700 icon = iconTheme.load_icon("bluetooth", 32, 0) 701 else: 702 icon = iconTheme.load_icon("audio-card", 32, 0) 703 704 getattr(self, type+"DeviceList").append([device.get_description() + "\n" + device.get_origin(), "", False, id, icon]) 705 706 if type == "input": 707 self.checkInputState() 708 709 def deviceRemoved(self, c, id, type): 710 store = getattr(self, type+"DeviceList") 711 for row in store: 712 if row[3] == id: 713 store.remove(row.iter) 714 if type == "input": 715 self.checkInputState() 716 return 717 718 def checkInputState(self): 719 if len(self.inputDeviceList) == 0: 720 self.inputStack.set_visible_child_name("noInputsMessage") 721 else: 722 self.inputStack.set_visible_child_name("inputBox") 723 724 def activeOutputUpdate(self, c, id): 725 self.outputId = id 726 device = self.controller.lookup_output_id(id) 727 728 self.profile.setDevice(device) 729 730 # select current device in device selector 731 i = 0 732 for row in self.outputDeviceList: 733 if row[3] == id: 734 self.outputSelector.select_path(Gtk.TreePath.new_from_string(str(i))) 735 i = i + 1 736 737 self.setChannelMap() 738 739 def activeInputUpdate(self, c, id): 740 self.inputId = id 741 742 # select current device in device selector 743 i = 0 744 for row in self.inputDeviceList: 745 if row[3] == id: 746 self.inputSelector.select_path(Gtk.TreePath.new_from_string(str(i))) 747 i = i + 1 748 749 def defaultSinkChanged(self, c, id): 750 defaultSink = self.controller.get_default_sink() 751 if defaultSink == None: 752 return 753 self.outVolume.setStream(defaultSink) 754 self.setChannelMap() 755 756 def defaultSourceChanged(self, c, id): 757 defaultSource = self.controller.get_default_source() 758 if defaultSource == None: 759 return 760 self.inVolume.setStream(defaultSource) 761 self.inLevel.setStream(defaultSource) 762 763 def setChannelMap(self, a=None, b=None): 764 if self.controller.get_state() == Cvc.MixerControlState.READY: 765 channelMap = self.controller.get_default_sink().get_channel_map() 766 self.balance.setChannelMap(channelMap) 767 self.fade.setChannelMap(channelMap) 768 self.woofer.setChannelMap(channelMap) 769 770 def streamAdded(self, c, id): 771 stream = self.controller.lookup_stream_id(id) 772 773 if stream in self.controller.get_sink_inputs(): 774 name = stream.props.name 775 776 # FIXME: We use to filter out by PA_PROP_APPLICATION_ID. But 777 # most streams report this as null now... why?? 778 if name in ("speech-dispatcher", "libcanberra"): 779 # speech-dispatcher: orca/speechd/spd-say 780 # libcanberra: cinnamon effects, test sounds 781 return 782 783 if id in self.appList.keys(): 784 # Don't add an input more than once 785 return 786 787 if name == None: 788 name = _("Unknown") 789 790 label = "%s: " % name 791 792 self.appList[id] = VolumeBar(self.controller.get_vol_max_norm(), 793 100, 794 label, 795 stream.get_gicon()) 796 self.appList[id].setStream(stream) 797 self.appSettings.add_row(self.appList[id]) 798 self.appSettings.list_box.invalidate_headers() 799 self.appSettings.show_all() 800 elif stream == self.controller.get_event_sink_input(): 801 self.soundsVolume.setStream(stream) 802 803 self.checkAppState() 804 805 def streamRemoved(self, c, id): 806 if id in self.appList: 807 self.appList[id].get_parent().destroy() 808 self.appSettings.list_box.invalidate_headers() 809 del self.appList[id] 810 self.checkAppState() 811 812 def checkAppState(self): 813 if len(self.appList) == 0: 814 self.appStack.set_visible_child_name("noAppsMessage") 815 else: 816 self.appStack.set_visible_child_name("appSettings") 817