/******************************************************************************* * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . * ***************************************************************************** * Original Author: Gopi Sankar Karmegam ******************************************************************************/ /* jshint moz:true */ const Main = imports.ui.main; const PopupMenu = imports.ui.popupMenu; const VolumeMenu = imports.ui.status.volume; const {Atk, St, GObject, GLib} = imports.gi; const Gvc = imports.gi.Gvc; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Lib = Me.imports.convenience; const Prefs = Me.imports.prefs; const SignalManager = Lib.SignalManager; var SoundDeviceChooserBase = class SoundDeviceChooserBase{ constructor(deviceType) { Lib.log("SDC: init"); this.menuItem = new PopupMenu.PopupSubMenuMenuItem ('Extension initialising...', true); this.deviceType = deviceType; this._devices = {}; this._availableDevicesIds = {}; this._control = VolumeMenu.getMixerControl(); this._settings = Lib.getSettings(Prefs.SETTINGS_SCHEMA); Lib.log("Constructor" + deviceType); this._setLog(); this._signalManager = new SignalManager(); this._signalManager.addSignal(this._settings,"changed::" + Prefs.ENABLE_LOG, this._setLog.bind(this)); if(this._control.get_state() == Gvc.MixerControlState.READY) { this._onControlStateChanged(); } else { this._controlStateChangeSignal = this._signalManager.addSignal(this._control, "state-changed", this._onControlStateChanged.bind(this)); } } _setLog(){ Lib.setLog(this._settings.get_boolean(Prefs.ENABLE_LOG));} _onControlStateChanged() { if(this._control.get_state() == Gvc.MixerControlState.READY) { if(!this._initialised) { this._initialised = true; this._signalManager.addSignal(this._control, this.deviceType + "-added", this._deviceAdded.bind(this)); this._signalManager.addSignal(this._control, this.deviceType + "-removed", this._deviceRemoved.bind(this)); this._signalManager.addSignal(this._control, "active-" + this.deviceType + "-update", this._deviceActivated.bind(this)); this._signalManager.addSignal(this._settings,"changed::" + Prefs.HIDE_ON_SINGLE_DEVICE,this._setChooserVisibility.bind(this) ); this._signalManager.addSignal(this._settings,"changed::" + Prefs.SHOW_PROFILES , this._setProfileVisibility.bind(this)); this._signalManager.addSignal(this._settings,"changed::" + Prefs.ICON_THEME , this._setIcons.bind(this)); this._signalManager.addSignal(this._settings,"changed::" + Prefs.HIDE_MENU_ICONS , this._setIcons.bind(this)); this._signalManager.addSignal(this._settings,"changed::" + Prefs.PORT_SETTINGS , this._resetDevices.bind(this)); this._show_device_signal = Prefs["SHOW_" + this.deviceType.toUpperCase() + "_DEVICES"]; this._signalManager.addSignal(this._settings,"changed::" + this._show_device_signal, this._setVisibility.bind(this)); this._portsSettings = JSON.parse(this._settings.get_string(Prefs.PORT_SETTINGS)); /** * There is no direct way to get all the UI devices from * mixercontrol. When enabled after shell loads, the signals * will not be emitted, a simple hack to look for ids, until any * uidevice is not found. The UI devices are always serialed * from from 1 to n */ let id = 0; let maxId = -1; let dummyDevice = new Gvc.MixerUIDevice(); maxId = dummyDevice.get_id(); Lib.log("Max Id:" + maxId); let defaultDevice = this.getDefaultDevice(); while(++id < maxId) { let uidevice = this._deviceAdded(this._control, id); if(uidevice) { let stream = this._control.get_stream_from_device(uidevice); if(stream) { let stream_port = stream.get_port(); let uidevice_port = uidevice.get_port(); if(((!stream_port && !uidevice_port) || (stream_port && stream_port.port === uidevice_port)) && stream == defaultDevice) { this._deviceActivated(this._control, id); } } } } this.activeProfileTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, this._setActiveProfile.bind(this)); if(this._controlStateChangeSignal) { this._controlStateChangeSignal.disconnect(); delete this._controlStateChangeSignal; } this._setVisibility(); } } } _deviceAdded(control, id, dontcheck) { let obj = this._devices[id]; let uidevice = null; if(!obj) { uidevice = this.lookupDeviceById(id); if(!uidevice) { return null; } obj = new Object(); obj.uidevice = uidevice; obj.text = uidevice.description; if(uidevice.origin != "") obj.text += " (" + uidevice.origin + ")"; /* * obj.item = this.menu.addAction( obj.text, function() { * this.changeDevice(uidevice); }.bind(this)); let icon = * uidevice.get_icon_name(); if(icon == null || icon.trim() == "") * icon = this.getDefaultIcon(); obj.item._icon = new St.Icon({ * style_class: 'popup-menu-icon', icon_name: this._getIcon(icon)}); * obj.item.actor.insert_child_at_index(obj.item._icon,1); */ let icon = uidevice.get_icon_name(); if(icon == null || icon.trim() == "") icon = this.getDefaultIcon(); let icon_name = this._getIcon(icon); obj.item = this.menuItem.menu.addAction( obj.text, function() { this.changeDevice(uidevice); }.bind(this), icon_name); if(!obj.profiles) { obj.profiles = Lib.getProfiles(control, uidevice); } if(!obj.profilesitems) { obj.profilesitems = []; } this._devices[id] = obj; } else { uidevice = obj.uidevice; } Lib.log(obj.text); if (obj.profiles) { for (let profile of obj.profiles) { Lib.log(profile.name) } } if(obj.active) { return uidevice; } Lib.log("Added: " + id + ":" + uidevice.description + ":" + uidevice.port_name); if(!this._availableDevicesIds[id]){ this._availableDevicesIds[id] = 0; } this._availableDevicesIds[id] ++; obj.active = true; obj.activeProfile = uidevice.get_active_profile(); let showProfiles = this._settings.get_boolean(Prefs.SHOW_PROFILES); if (obj.profiles) { for (let profile of obj.profiles) { let profileItem = obj.profilesitems[profile.name]; if(!profileItem) { let profileName = profile.name; profileItem = this.menuItem.menu.addAction( "Profile: " + profile.human_name, function() { Lib.log("i am setting profile, " + profile.human_name + ":" + uidevice.description + ":" + uidevice.port_name); if(this._activeDevice && this._activeDevice.uidevice !== uidevice) { Lib.log("Changing active device to " + uidevice.description + ":" + uidevice.port_name); this.changeDevice(uidevice); } this._control.change_profile_on_selected_device(uidevice, profileName); this._setDeviceActiveProfile(obj); }.bind(this)); obj.profilesitems[profileName] = profileItem; profileItem.setProfileActive = function(active) { if(active) { // this._ornamentLabel.text = "\u2727"; this._ornamentLabel.text = "\t\u266A"; if(this.add_style_pseudo_class) { this.add_style_pseudo_class('checked'); this.remove_style_pseudo_class('insensitive'); } else { this.actor.add_style_pseudo_class('checked'); this.actor.remove_style_pseudo_class('insensitive'); } } else { this._ornamentLabel.text = "\t"; if(this.add_style_pseudo_class) { this.remove_style_pseudo_class('checked'); this.add_style_pseudo_class('insensitive'); } else { this.actor.remove_style_pseudo_class('checked'); this.actor.add_style_pseudo_class('insensitive'); } } }; // profileItem._ornamentLabel.width = "500em"; profileItem._ornamentLabel.set_style("min-width: 3em;"); } profileItem.setProfileActive(obj.activeProfile == profile.name); } } if (!dontcheck && !this._canShowDevice(uidevice, uidevice.port_available)) { this._deviceRemoved(control, id, true); } this._setChooserVisibility(); this._setVisibility(); return uidevice; } _deviceRemoved(control, id, dontcheck) { let obj = this._devices[id]; if(obj && obj.active) { Lib.log("Removed: " + id); if(!dontcheck && this._canShowDevice(obj.uidevice, false)) { Lib.log('Device removed, but not hiding as its set to be shown always'); return; } delete this._availableDevicesIds[id] ; obj.item.actor.visible = false; obj.active = false; if (obj.profiles) { for (let profile of obj.profiles) { let profileItem = obj.profilesitems[profile.name]; if(profileItem) { profileItem.actor.visible = false; } } } if(this.deviceRemovedTimout) { GLib.source_remove(this.deviceRemovedTimout); this.deviceRemovedTimout = null; } /** * If the active uidevice is removed, then need to activate the * first available uidevice. However for some cases like Headphones, * when the uidevice is removed, Speakers are automatically * activated. So, lets wait for sometime before activating. */ this.deviceRemovedTimout = GLib.timeout_add(GLib.PRIORITY_DEFAULT,1500, function() { if (obj === this._activeDevice) { for ( let id in this._devices) { let device = this._devices[id]; if(device.active == true) { this.changeDevice(device.uidevice); break; } } } this.deviceRemovedTimout = null; return false; }.bind(this)); this._setChooserVisibility(); this._setVisibility(); } } _deviceActivated(control, id) { let obj = this._devices[id]; if(obj && obj !== this._activeDevice) { Lib.log("Activated: " + id); if(this._activeDevice) { this._activeDevice.item.setOrnament(PopupMenu.Ornament.NONE); if(this._activeDevice.item.remove_style_pseudo_class) { this._activeDevice.item.remove_style_pseudo_class('checked'); } else { this._activeDevice.item.actor.remove_style_pseudo_class('checked'); } } this._activeDevice = obj; obj.item.setOrnament(PopupMenu.Ornament.CHECK); if(obj.item.add_style_pseudo_class) { obj.item.add_style_pseudo_class('checked'); } else { obj.item.actor.add_style_pseudo_class('checked'); } obj.item._ornamentLabel.text = '\u266B'; this.menuItem.label.text = obj.text; if (!this._settings.get_boolean(Prefs.HIDE_MENU_ICONS)) { let icon = obj.uidevice.get_icon_name(); if(icon == null || icon.trim() == "") icon = this.getDefaultIcon(); this.menuItem.icon.icon_name = this._getIcon(icon); } else { this.menuItem.icon.icon_name = "blank"; } } } _setActiveProfile() { for (let id in this._devices) { let device = this._devices[id]; if(device.active) { this._setDeviceActiveProfile(device); } } return true; } _setDeviceActiveProfile(device) { if (!device.uidevice.port_name) { return; } let activeProfile = device.uidevice.get_active_profile(); if(activeProfile && device.activeProfile != activeProfile) { device.activeProfile = activeProfile; for (let profile of device.profiles) { device.profilesitems[profile.name].setProfileActive(profile.name == device.activeProfile); } } } _getDeviceVisibility() { let hideChooser = this._settings.get_boolean(Prefs.HIDE_ON_SINGLE_DEVICE); if (hideChooser) { return (Object.keys(this._availableDevicesIds).length > 1); } else { return true; } } _setChooserVisibility() { let visibility = this._getDeviceVisibility(); for (let id in this._availableDevicesIds) { this._devices[id].item.actor.visible = visibility; } this.menuItem._triangleBin.visible = visibility; this._setProfileVisibility(); } _setProfileVisibility() { let visibility = this._settings.get_boolean(Prefs.SHOW_PROFILES); for (let id in this._availableDevicesIds) { let device = this._devices[id]; if ( device.profiles ) { for (let profile of device.profiles) { device.profilesitems[profile.name].actor.visible = (visibility && device.item.actor.visible && Object.keys(device.profilesitems).length > 1); } } } } _getIcon(name) { let iconsType = this._settings.get_string(Prefs.ICON_THEME); switch (iconsType) { case Prefs.ICON_THEME_COLORED: return name; case Prefs.ICON_THEME_MONOCHROME: return name + "-symbolic"; default: return "none"; } } _setIcons() { // Set the icons in the selection list for (let id in this._devices) { let device = this._devices[id]; let icon = device.uidevice.get_icon_name(); if(icon == null || icon.trim() == "") icon = this.getDefaultIcon(); // device.item._icon.icon_name = this._getIcon(icon); device.item.setIcon(this._getIcon(icon)); } // These indicate the active device, which is displayed directly in the // Gnome menu, not in the list. if (!this._settings.get_boolean(Prefs.HIDE_MENU_ICONS)) { let icon = this._activeDevice.uidevice.get_icon_name(); if(icon == null || icon.trim() == "") icon = this.getDefaultIcon(); this.menuItem.icon.icon_name = this._getIcon(icon); } else { this.menuItem.icon.icon_name = "blank"; } } _canShowDevice(uidevice, defaultValue) { if(!uidevice || !this._portsSettings || uidevice.port_name == null || uidevice.description == null) { return defaultValue; } for (let port of this._portsSettings) { if(port && port.name == uidevice.port_name && port.human_name == uidevice.description) { switch(port.display_option) { case 1: return true; case 2: return false; default: return defaultValue; } } } return defaultValue; } _resetDevices() { this._portsSettings = JSON.parse(this._settings.get_string(Prefs.PORT_SETTINGS)); for (let id in this._devices) { let device = this._devices[id]; let uidevice = device.uidevice; if(uidevice.port_name == null || uidevice.description == null) { continue; } switch(this._canShowDevice(uidevice, uidevice.port_available)) { case true: this._deviceAdded(this._control, uidevice.get_id(), true); break; case false: this._deviceRemoved(this._control, uidevice.get_id(), true); break; } } } _setVisibility () { if (!this._settings.get_boolean(this._show_device_signal)) this.menuItem.actor.visible = false; else // if setting says to show device, check for any device, otherwise // hide the "actor" this.menuItem.actor.visible = (Object.keys(this._availableDevicesIds).length > 0); } destroy() { this._signalManager.disconnectAll(); if(this.deviceRemovedTimout) { GLib.source_remove(this.deviceRemovedTimout); this.deviceRemovedTimout = null; } if(this.activeProfileTimeout) { GLib.source_remove(this.activeProfileTimeout); this.activeProfileTimeout = null; } this.menuItem.destroy(); } };