/* extension.js * * 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 2 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 . * * SPDX-License-Identifier: GPL-2.0-or-later */ import Clutter from 'gi://Clutter'; import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import { MediaSection } from 'resource:///org/gnome/shell/ui/mpris.js'; import { LibPanel, Panel } from './libs/libpanel/main.js'; import { ApplicationsMixer, waitProperty } from './libs/widgets.js'; const DateMenu = Main.panel.statusArea.dateMenu; const QuickSettings = Main.panel.statusArea.quickSettings; const CalendarMessageList = DateMenu._messageList; const MediaSection_DateMenu = CalendarMessageList._mediaSection; const SystemItem = QuickSettings._system._systemItem; // _volumeOutput is always defined here because `./libs/widgets.js` wait on it const OutputVolumeSlider = QuickSettings._volumeOutput._output; export default class QSAP extends Extension { async enable() { this.InputVolumeIndicator = await waitProperty(QuickSettings, '_volumeInput'); this.InputVolumeSlider = this.InputVolumeIndicator._input; this.settings = this.getSettings(); this._scasis_callback = this.settings.connect( 'changed::always-show-input-slider', () => this._set_always_show_input(this.settings.get_boolean('always-show-input-slider')) ); this.settings.emit('changed::always-show-input-slider', 'always-show-input-slider'); this._master_volumes = []; this._sc_callback = this.settings.connect('changed', () => this._refresh_panel()); this._refresh_panel(); } disable() { this.settings.disconnect(this._scasis_callback); this.settings.disconnect(this._sc_callback); for (const id of waitProperty.idle_ids) { GLib.Source.remove(id); } waitProperty.idle_ids = null; this._set_always_show_input(false); this._cleanup_panel(); this.settings = null; } _refresh_panel() { this._cleanup_panel(); const move_master_volume = this.settings.get_boolean('move-master-volume'); const media_control_action = this.settings.get_string('media-control'); const create_mixer_sliders = this.settings.get_boolean('create-mixer-sliders'); const merge_panel = this.settings.get_boolean('merge-panel'); const panel_position = this.settings.get_string("panel-position"); const widgets_ordering = this.settings.get_strv('ordering'); const filter_mode = this.settings.get_string('filter-mode'); const filters = this.settings.get_strv('filters'); if (move_master_volume || media_control_action !== 'none' || create_mixer_sliders) { LibPanel.enable(); this._panel = LibPanel.main_panel; let index = -1; if (!merge_panel) { this._panel = new Panel('main'); // Since the panel contains no element that have a minimal width (like QuickToggle) // we need to force it to take the same with as a normal panel this._panel.add_constraint(new Clutter.BindConstraint({ coordinate: Clutter.BindCoordinate.WIDTH, source: LibPanel.main_panel, })); LibPanel.addPanel(this._panel); } if (merge_panel && panel_position === 'top') { widgets_ordering.reverse(); index = this._panel.getItems().indexOf(SystemItem) + 2; } for (const widget of widgets_ordering) { if (widget === 'volume-output' && move_master_volume) { this._move_slider(index, OutputVolumeSlider); } else if (widget === 'volume-input' && move_master_volume) { this._move_slider(index, this.InputVolumeSlider); } else if (widget === 'media' && media_control_action === 'move') { this._move_media_controls(index); } else if (widget === 'media' && media_control_action === 'duplicate') { this._create_media_controls(index); } else if (widget === 'mixer' && create_mixer_sliders) { this._create_app_mixer(index, filter_mode, filters); } } } } _cleanup_panel() { if (!this._panel) return; if (this._applications_mixer) { this._applications_mixer.destroy(); this._applications_mixer = null; } if (this._media_section) { this._panel.removeItem(this._media_section); this._media_section = null; } if (MediaSection_DateMenu.has_style_class_name('QSAP-media-section')) { this._panel.removeItem(MediaSection_DateMenu); CalendarMessageList._sectionList.insert_child_at_index(MediaSection_DateMenu, 0); MediaSection_DateMenu.remove_style_class_name('QSAP-media-section'); } this._master_volumes.reverse(); for (const [slider, index] of this._master_volumes) { this._panel.removeItem(slider); LibPanel.main_panel.addItem(slider, 2); LibPanel.main_panel._grid.set_child_at_index(slider, index); } this._master_volumes = []; if (this._panel !== LibPanel.main_panel) { LibPanel.removePanel(this._panel); // prevent the panel's position being forgotten this._panel.destroy(); }; this._panel = null; LibPanel.disable(); } _move_slider(index, slider) { const old_index = slider.get_parent().get_children().indexOf(slider); LibPanel.main_panel.removeItem(slider); this._panel.addItem(slider, 2); this._panel._grid.set_child_at_index(slider, index); this._master_volumes.push([slider, old_index]); } _move_media_controls(index) { CalendarMessageList._sectionList.remove_child(MediaSection_DateMenu); this._panel.addItem(MediaSection_DateMenu, 2); this._panel._grid.set_child_at_index(MediaSection_DateMenu, index); MediaSection_DateMenu.add_style_class_name('QSAP-media-section'); } _create_media_controls(index) { this._media_section = new MediaSection(); this._media_section.add_style_class_name('QSAP-media-section'); this._panel.addItem(this._media_section, 2); this._panel._grid.set_child_at_index(this._media_section, index); } _create_app_mixer(index, filter_mode, filters) { this._applications_mixer = new ApplicationsMixer(this._panel, index, filter_mode, filters); } _set_always_show_input(enabled) { if (enabled) { this._ivssa_callback = this.InputVolumeSlider._control.connect('stream-added', () => { this.InputVolumeSlider.visible = true; this.InputVolumeIndicator.visible = this.InputVolumeSlider._shouldBeVisible(); }); this._ivssr_callback = this.InputVolumeSlider._control.connect('stream-removed', () => { this.InputVolumeSlider.visible = true; this.InputVolumeIndicator.visible = this.InputVolumeSlider._shouldBeVisible(); }); this.InputVolumeSlider.visible = true; this.InputVolumeIndicator.visible = this.InputVolumeSlider._shouldBeVisible(); } else { if (this._ivssr_callback) this.InputVolumeSlider._control.disconnect(this._ivssr_callback); if (this._ivssa_callback) this.InputVolumeSlider._control.disconnect(this._ivssa_callback); this._ivssr_callback = null; this._ivssa_callback = null; this.InputVolumeSlider.visible = this.InputVolumeSlider._shouldBeVisible(); this.InputVolumeIndicator.visible = this.InputVolumeSlider._shouldBeVisible(); } } }