/usr/share/gnome-shell/extensions/bettervolume@tudmotu.com/extension.js is in gnome-shell-extension-better-volume 0.0-git20161106.ff67408-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | /**
* Better Volume Indicator
*
* This simple extension implements two things:
* 1. When scrolling on the volume indicator, the volume slider/menu appears to
* display current level.
* 2. Middle/scroll clicking on the indicator, output is muted.
*
*/
const Clutter = imports.gi.Clutter;
const Mainloop = imports.mainloop;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const VolumeMenu = imports.ui.status.volume.VolumeMenu;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const prettyPrint = Convenience.dbPrintObj;
const POPUP_TIMEOUT_SECS = 1;
const VOLUME_MUTE_ICON = 'audio-volume-muted-symbolic';
let onlyVolumeMenuShown = false;
let volumeIndicator;
let aggregateMenu;
let _onAggregateMenuClickEventId;
let _onAggregateMenuEnterEventId;
let _onAggregateMenuLeaveEventId;
let _onVolumeIndicatorScrollEventId;
let _onVolumeIndicatorClickEventId;
let popupTimeout = null;
let availableMenus = [];
function _onVolumeIndicatorScroll(indicators, e) {
let menu = aggregateMenu.menu;
// Only if menu is not already open (and not due to our request)
if (menu.actor.visible && popupTimeout === null)
return;
// We need to remove the previous timeout if the user scrolled again since
// it means they are not ready yet for us to hide the menu
if (popupTimeout !== null) _removeTimeout();
// Set a timeout for the menu to close (we don't want it staying open forever)
_setTimeout();
// We want to hide all menus which are not the volume menu
_setMenusVisibility(false);
// Open the aggregateMenu so we can see some volume
aggregateMenu.menu.open();
}
function _onVolumeIndicatorScrollTimeout () {
// When time is up, we close the menu
aggregateMenu.menu.close();
// Re-show the aggegate menu entries
_setMenusVisibility(true);
_removeTimeout();
}
function _onAggregateMenuClick (actor, e) {
// Make sure menus are displayed
_setMenusVisibility(true);
// We want to see if the popup is already displayed, and if so, kill its
// timeout, so it won't suddenly disappear on us
// If it's not open, we don't won't to do anything
if (popupTimeout !== null) {
_removeTimeout();
// Make sure the menu is open
// This is kinda hacky - since the aggregateMenu toggles its own
// visibility, we want to reverse the effect. It's a dirty trick but I
// could not figure out a better way to overcome its self-toggling
aggregateMenu.menu.toggle();
}
}
function _onAggregateMenuEnter () {
_removeTimeout();
}
function _onAggregateMenuLeave () {
// When mouse leaves, set a new timeout only if menu was shown due to
// volume-scrolling (meaning - this extension caused the menu to appear, and
// not, say, a click on the aggregate-menu by the user)
if (onlyVolumeMenuShown) _setTimeout();
}
let _previousVolumeValue, _previousVolumeIcon;
function _onVolumeIndicatorClick (actor, e) {
// When middle-clicking on the indicator we want to toggle mute
if (e.get_button() === Clutter.BUTTON_MIDDLE) {
let sliderActor = volumeIndicator._volumeMenu._output._slider; // hummm.. hack?
let currentValue = sliderActor._getCurrentValue(); // starting to look like a hack
let currentIcon = volumeIndicator._primaryIndicator.icon_name;
if (currentValue === 0 && _previousVolumeValue) {
// this is definitely a hack
sliderActor.setValue(_previousVolumeValue);
sliderActor.emit('value-changed', _previousVolumeValue); // mimic slider behvaiour so volume will actually change
volumeIndicator._primaryIndicator.icon_name = _previousVolumeIcon;
}
else {
// a dirty dirty hack
sliderActor.setValue(0);
sliderActor.emit('value-changed', 0); // like above
volumeIndicator._primaryIndicator.icon_name = VOLUME_MUTE_ICON;
_previousVolumeValue = currentValue;
_previousVolumeIcon = currentIcon;
}
aggregateMenu.menu.toggle(); // again with that previous hack
}
}
function _setTimeout () {
popupTimeout = Mainloop.timeout_add_seconds(POPUP_TIMEOUT_SECS, _onVolumeIndicatorScrollTimeout);
}
function _removeTimeout () {
if (popupTimeout !== null)
Mainloop.source_remove(popupTimeout);
popupTimeout = null;
}
function _setMenusVisibility (visibility) {
// Find the menus inside the aggregateMenu (I couldn't find a better
// way for finding the menus except finding their indicators. seems to work
// though)
for (let k in aggregateMenu) {
let entry = aggregateMenu[k];
if (entry instanceof PanelMenu.SystemIndicator) {
if (entry !== volumeIndicator) {
entry.menu.actor.visible = visibility;
}
}
}
// Hide inner menus inside the volume section
volumeIndicator.menu._getMenuItems().forEach(function (item, i) {
if ( !(item instanceof VolumeMenu) ) {
item.actor.visible = visibility;
}
});
// Mark volume menu visibility accordingly
onlyVolumeMenuShown = !visibility;
}
function init() {
}
function enable() {
aggregateMenu = Main.panel.statusArea.aggregateMenu;
if (aggregateMenu.hasOwnProperty('_volume') && aggregateMenu._volume instanceof PanelMenu.SystemIndicator) {
volumeIndicator = aggregateMenu._volume;
_onVolumeIndicatorScrollEventId = volumeIndicator.indicators.connect('scroll-event', _onVolumeIndicatorScroll);
_onVolumeIndicatorClickEventId = volumeIndicator.indicators.connect('button-press-event', _onVolumeIndicatorClick);
_onAggregateMenuEnterEventId = aggregateMenu.menu.actor.connect('enter-event', _onAggregateMenuEnter);
_onAggregateMenuLeaveEventId = aggregateMenu.menu.actor.connect('leave-event', _onAggregateMenuLeave);
_onAggregateMenuClickEventId = aggregateMenu.actor.connect('button-press-event', _onAggregateMenuClick);
}
}
function disable() {
// We need to verify we still have connections and disconnect them
if (_onVolumeIndicatorScrollEventId)
volumeIndicator.indicators.disconnect(_onVolumeIndicatorScrollEventId);
if (_onVolumeIndicatorClickEventId)
volumeIndicator.indicators.disconnect(_onVolumeIndicatorClickEventId);
if (_onAggregateMenuEnterEventId)
aggregateMenu.menu.actor.disconnect(_onAggregateMenuEnterEventId);
if (_onAggregateMenuLeaveEventId)
aggregateMenu.menu.actor.disconnect(_onAggregateMenuLeaveEventId);
if (_onAggregateMenuClickEventId)
aggregateMenu.actor.disconnect(_onAggregateMenuClickEventId);
}
|