.dotfiles/.local/share/gnome-shell/extensions/quick-settings-audio-panel@.../prefs.js

321 lines
12 KiB
JavaScript
Raw Normal View History

"use strict";
import Adw from 'gi://Adw';
import GObject from 'gi://GObject';
import Gdk from 'gi://Gdk';
import Gio from 'gi://Gio';
import Gtk from 'gi://Gtk';
import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
import { get_settings, get_stack, rsplit, split } from './libs/libpanel/utils.js';
export default class QSAPPreferences extends ExtensionPreferences {
fillPreferencesWindow(window) {
const settings = this.getSettings();
const page = new Adw.PreferencesPage();
window.add(page);
// ====================================== Main group ======================================
const main_group = new Adw.PreferencesGroup();
page.add(main_group);
main_group.add(create_switch(
settings, 'move-master-volume',
{
title: _("Move master volume sliders"),
subtitle: _("Thoses are the speaker / headphone and microphone volume sliders")
}
));
main_group.add(create_switch(
settings, 'always-show-input-slider',
{
title: _("Always show microphone volume slider"),
subtitle: _("Show even when there is no application recording audio")
}
));
main_group.add(create_dropdown(
settings, 'media-control',
{
title: _("Media controls"),
subtitle: _("What should we do with media controls ?"),
fields: [
['none', _("Leave as is")],
['move', _("Move into new panel")],
['duplicate', _("Duplicate into new panel")]
]
}
));
if (settings.get_strv('ordering').length != 4) {
settings.set_strv('ordering', ['volume-output', 'volume-input', 'media', 'mixer']);
}
main_group.add(create_switch(
settings, 'create-mixer-sliders',
{
title: _("Create applications mixer"),
subtitle: _("Thoses sliders are the same you can find in pavucontrol or in the sound settings")
}
));
main_group.add(create_switch(
settings, 'merge-panel',
{
title: _("Merge the new panel into the main one"),
subtitle: _("The new panel will not be separated from the main one")
}
));
const position_dropdown = create_dropdown(
settings, 'panel-position',
{
title: _("Panel position"),
subtitle: _("Where the new panel should be located relative to the main panel"),
fields: [
['top', _("Top")],
['bottom', _("Bottom")]
]
}
);
settings.bind('merge-panel', position_dropdown, 'visible', Gio.SettingsBindFlags.GET);
main_group.add(position_dropdown);
// ================================= Widget ordering group ================================
const widgets_order_group = new ReorderablePreferencesGroup(settings, 'ordering', {
title: _("Elements order"),
description: _("Reorder elements in the new panel (disabled elments will just be ignored)")
});
page.add(widgets_order_group);
widgets_order_group.add(new DraggableRow('volume-output', { title: _("Speaker / Headphone volume slider") }));
widgets_order_group.add(new DraggableRow('volume-input', { title: _("Microphone volume slider") }));
widgets_order_group.add(new DraggableRow('media', { title: _("Media controls") }));
widgets_order_group.add(new DraggableRow('mixer', { title: _("Applications mixer") }));
// ================================== Mixer filter group ==================================
const add_filter_button = new Gtk.Button({ icon_name: 'list-add', has_frame: false });
const mixer_filter_group = new Adw.PreferencesGroup({
title: _("Mixer filtering"),
description: _("Allow you to filter the streams that show up in the application mixer **using regexes**"),
header_suffix: add_filter_button
});
mixer_filter_group.add(create_dropdown(
settings, 'filter-mode',
{
title: _("Filtering mode"),
subtitle: _("On blacklist mode, matching elements are removed from the list. On whitelist mode, only matching elements will be shown"),
fields: [
['blacklist', _("Blacklist")],
['whitelist', _("Whitelist")],
]
}
));
page.add(mixer_filter_group);
const filters = [];
const create_filter_row = (text) => {
const new_row = new Adw.EntryRow({ 'title': _("Stream name") });
if (text != undefined) new_row.text = text;
const delete_button = new Gtk.Button({ icon_name: 'user-trash-symbolic', has_frame: false });
delete_button.connect('clicked', () => {
mixer_filter_group.remove(new_row);
filters.splice(filters.indexOf(new_row), 1);
save_filters(settings, filters);
});
new_row.add_suffix(delete_button);
new_row.connect('changed', () => save_filters(settings, filters));
filters.push(new_row);
mixer_filter_group.add(new_row);
};
add_filter_button.connect('clicked', () => {
create_filter_row();
});
for (const filter of settings.get_strv('filters')) {
create_filter_row(filter);
}
// ==================================== LibPanel group ====================================
// we remove the 'file://' and the filename at the end
const parent_folder = '/' + split(rsplit(get_stack()[0].file, '/', 1)[0], '/', 3)[3];
const libpanel_settings = get_settings(`${parent_folder}/libs/libpanel/org.gnome.shell.extensions.libpanel.gschema.xml`);
const libpanel_group = new Adw.PreferencesGroup({
title: _("LibPanel settings"),
description: _("Those settings are not specific to this extension, they apply to every panels"),
});
page.add(libpanel_group);
libpanel_group.add(create_switch_spin(
libpanel_settings, 'padding-enabled', 'padding',
{
title: _("Padding"),
subtitle: _("Use this to override the default padding of the panels")
}, 0, 100
));
libpanel_group.add(create_switch_spin(
libpanel_settings, 'row-spacing-enabled', 'row-spacing',
{
title: _("Row spacing"),
subtitle: _("Use this to override the default row spacing of the panels")
}, 0, 100
));
libpanel_group.add(create_switch_spin(
libpanel_settings, 'column-spacing-enabled', 'column-spacing',
{
title: _("Column spacing"),
subtitle: _("Use this to override the default column spacing of the panels")
}, 0, 100
));
}
}
function create_switch(settings, id, options) {
const row = new Adw.SwitchRow(options);
settings.bind(
id,
row,
'active',
Gio.SettingsBindFlags.DEFAULT
);
return row;
}
function create_dropdown(settings, id, options) {
const fields = options.fields;
delete options.fields;
const model = Gtk.StringList.new(fields.map(x => x[1]));
const row = new Adw.ComboRow({
model: model,
selected: fields.map(x => x[0]).indexOf(settings.get_string(id)),
...options
});
row.connect('notify::selected', () => {
settings.set_string(id, fields[row.selected][0]);
});
return row;
}
function create_switch_spin(settings, switch_id, spin_id, options, lower = 0, higher = 100) {
const row = Adw.SpinRow.new_with_range(lower, higher, 1);
row.title = options.title;
row.subtitle = options.subtitle;
const switch_ = new Gtk.Switch({ valign: Gtk.Align.CENTER });
settings.bind(
switch_id,
switch_,
'active',
Gio.SettingsBindFlags.DEFAULT
);
row.add_prefix(switch_);
row.activatable_widget = switch_;
settings.bind(
spin_id,
row,
'value',
Gio.SettingsBindFlags.DEFAULT
);
return row;
}
function save_filters(settings, filters) {
settings.set_strv('filters', filters.map(filter => filter.text));
}
// From this point onwards, the code is mostly a reimplementation of this:
// https://gitlab.gnome.org/GNOME/gnome-control-center/-/tree/main/panels/search
const ReorderablePreferencesGroup = GObject.registerClass(class extends Adw.PreferencesGroup {
constructor(settings, key, options) {
super(options);
this._settings = settings;
this._key = key;
this._list_box = new Gtk.ListBox({ selection_mode: Gtk.SelectionMode.NONE });
this._list_box.add_css_class('boxed-list');
this._list_box.set_sort_func((a, b) => {
const data = settings.get_strv(key);
const index_a = data.indexOf(a.id);
const index_b = data.indexOf(b.id);
return index_a < index_b ? -1 : 1;
});
super.add(this._list_box);
}
add(row) {
this._list_box.set_valign(Gtk.Align.FILL);
row.connect('move-row', (source, target) => {
this.selected_row = source;
const data = this._settings.get_strv(this._key);
const source_index = data.indexOf(source.id);
const target_index = data.indexOf(target.id);
if (target_index < source_index) {
data.splice(source_index, 1); // remove 1 element at source_index
data.splice(target_index, 0, source.id); // insert source.id at target_index
} else {
data.splice(target_index + 1, 0, source.id); // insert source.id at target_index
data.splice(source_index, 1); // remove 1 element at source_index
}
this._settings.set_strv(this._key, data);
this._list_box.invalidate_sort();
});
this._list_box.append(row);
}
});
class DraggableRowClass extends Adw.ActionRow {
constructor(id, options) {
super(options);
this.id = id;
const drag_handle = new Gtk.Image({ icon_name: 'list-drag-handle-symbolic' });
// css don't work
drag_handle.add_css_class('drag-handle');
this.add_prefix(drag_handle);
const drag_source = new Gtk.DragSource({ actions: Gdk.DragAction.MOVE });
drag_source.connect('prepare', (source, x, y) => {
this._drag_x = x;
this._drag_y = y;
return Gdk.ContentProvider.new_for_value(this);
});
drag_source.connect('drag-begin', (source, drag) => {
this._drag_widget = new Gtk.ListBox();
this._drag_widget.set_size_request(this.get_allocated_width(), this.get_allocated_height());
const row_copy = new DraggableRow("", options);
this._drag_widget.append(row_copy);
this._drag_widget.drag_highlight_row(row_copy);
Gtk.DragIcon.get_for_drag(drag).set_child(this._drag_widget);
drag.set_hotspot(this._drag_x, this._drag_y);
});
this.add_controller(drag_source);
const drop_target = Gtk.DropTarget.new(DraggableRow, Gdk.DragAction.MOVE);
drop_target.preload = true;
drop_target.connect('drop', (target, source, x, y) => {
source.emit('move-row', this);
return true;
});
this.add_controller(drop_target);
}
}
const DraggableRow = GObject.registerClass({
Signals: {
flags: GObject.SignalFlags.RUN_LAST,
'move-row': {
param_types: [DraggableRowClass],
}
},
}, DraggableRowClass);