321 lines
12 KiB
JavaScript
321 lines
12 KiB
JavaScript
|
"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);
|