This commit is contained in:
2024-07-08 22:46:35 +02:00
parent 02f44c49d2
commit 27254d817a
56249 changed files with 808097 additions and 1 deletions

View File

@ -0,0 +1,186 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import { ApplicationRow } from './applications_management/application_row.js';
const make_array = prefs_group => {
let list_box = prefs_group
.get_first_child()
.get_last_child()
.get_first_child();
let elements = [];
let i = 0;
let element = list_box.get_row_at_index(i);
while (element) {
elements.push(element);
i++;
element = list_box.get_row_at_index(i);
}
return elements;
};
export const Applications = GObject.registerClass({
GTypeName: 'Applications',
Template: GLib.uri_resolve_relative(import.meta.url, '../ui/applications.ui', GLib.UriFlags.NONE),
InternalChildren: [
'blur',
'sigma',
'brightness',
'opacity',
'dynamic_opacity',
'blur_on_overview',
'enable_all',
'whitelist',
'add_window_whitelist',
'blacklist',
'add_window_blacklist'
],
}, class Applications extends Adw.PreferencesPage {
constructor(preferences, preferences_window) {
super({});
this._preferences_window = preferences_window;
this.preferences = preferences;
this.preferences.applications.settings.bind(
'blur', this._blur, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.applications.settings.bind(
'opacity', this._opacity, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.applications.settings.bind(
'dynamic-opacity', this._dynamic_opacity, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.applications.settings.bind(
'blur-on-overview', this._blur_on_overview, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.applications.settings.bind(
'enable-all', this._enable_all, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.applications.settings.bind(
'sigma', this._sigma, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.applications.settings.bind(
'brightness', this._brightness, 'value',
Gio.SettingsBindFlags.DEFAULT
);
// connect 'enable all' button to whitelist/blacklist visibility
this._enable_all.bind_property(
'active', this._whitelist, 'visible',
GObject.BindingFlags.INVERT_BOOLEAN
);
this._enable_all.bind_property(
'active', this._blacklist, 'visible',
GObject.BindingFlags.DEFAULT
);
// make sure that blacklist / whitelist is correctly hidden
if (this._enable_all.active)
this._whitelist.visible = false;
this._blacklist.visible = !this._whitelist.visible;
// listen to app row addition
this._add_window_whitelist.connect('clicked',
() => this.add_to_whitelist()
);
this._add_window_blacklist.connect('clicked',
() => this.add_to_blacklist()
);
// add initial applications
this.add_widgets_from_lists();
this.preferences.connect('reset', () => {
this.remove_all_widgets();
this.add_widgets_from_lists();
});
}
// A way to retriew the whitelist widgets.
get _whitelist_elements() {
return make_array(this._whitelist);
}
// A way to retriew the blacklist widgets.
get _blacklist_elements() {
return make_array(this._blacklist);
}
add_widgets_from_lists() {
this.preferences.applications.WHITELIST.forEach(
app_name => this.add_to_whitelist(app_name)
);
this.preferences.applications.BLACKLIST.forEach(
app_name => this.add_to_blacklist(app_name)
);
}
close_all_expanded_rows() {
this._whitelist_elements.forEach(
element => element.set_expanded(false)
);
this._blacklist_elements.forEach(
element => element.set_expanded(false)
);
}
remove_all_widgets() {
this._whitelist_elements.forEach(
element => this._whitelist.remove(element)
);
this._blacklist_elements.forEach(
element => this._blacklist.remove(element)
);
}
add_to_whitelist(app_name = null) {
let window_row = new ApplicationRow('whitelist', this, app_name);
this._whitelist.add(window_row);
}
add_to_blacklist(app_name = null) {
let window_row = new ApplicationRow('blacklist', this, app_name);
this._blacklist.add(window_row);
}
update_whitelist_titles() {
let titles = this._whitelist_elements
.map(element => element._window_class.buffer.text)
.filter(title => title != "");
this.preferences.applications.WHITELIST = titles;
}
update_blacklist_titles() {
let titles = this._blacklist_elements
.map(element => element._window_class.buffer.text)
.filter(title => title != "");
this.preferences.applications.BLACKLIST = titles;
}
remove_from_whitelist(widget) {
this._whitelist.remove(widget);
this.update_whitelist_titles();
}
remove_from_blacklist(widget) {
this._blacklist.remove(widget);
this.update_blacklist_titles();
}
});

View File

@ -0,0 +1,112 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import Gtk from 'gi://Gtk';
import { pick, on_picking, on_picked } from '../../dbus/client.js';
export const ApplicationRow = GObject.registerClass({
GTypeName: 'ApplicationRow',
Template: GLib.uri_resolve_relative(import.meta.url, '../../ui/application-row.ui', GLib.UriFlags.NONE),
InternalChildren: [
'window_picker',
'window_class',
'picking_failure_toast',
'window_not_found_toast'
],
}, class ApplicationRow extends Adw.ExpanderRow {
constructor(list, app_page, app_name) {
super({});
this._list = list;
this._app_page = app_page;
// add a 'remove' button before the text
let action_row = this.child.get_first_child().get_first_child();
let remove_button = new Gtk.Button({
'icon-name': 'remove-row-symbolic',
'width-request': 38,
'height-request': 38,
'margin-top': 6,
'margin-bottom': 6,
});
remove_button.add_css_class('circular');
remove_button.add_css_class('flat');
action_row.add_prefix(remove_button);
// connect the button to the whitelist / blacklist removal
remove_button.connect('clicked', () => this._remove_row());
// bind row title to text buffer
this._window_class.buffer.bind_property(
'text', this, 'title',
Gio.SettingsBindFlags.BIDIRECTIONNAL
);
// set application name if it exists, or open the revealer and pick one
if (app_name)
this._window_class.buffer.text = app_name;
else {
app_page.close_all_expanded_rows();
this.set_expanded(true);
this._do_pick_window(true);
}
// pick a window when the picker button is clicked
this._window_picker.connect('clicked', () => this._do_pick_window());
// update list on text buffer change
this._window_class.connect('changed',
() => this._update_rows_titles()
);
}
_remove_row() {
this._app_page["remove_from_" + this._list](this);
}
_update_rows_titles() {
this._app_page["update_" + this._list + "_titles"](this);
}
_do_pick_window(remove_if_failed = false) {
// a mechanism to know if the extension is listening correcly
let has_responded = false;
let should_take_answer = true;
setTimeout(() => {
if (!has_responded) {
// show toast about failure
this._app_page._preferences_window.add_toast(
this._picking_failure_toast
);
// prevent title from changing with later picks
should_take_answer = false;
// remove row if asked
if (remove_if_failed)
this._remove_row();
}
}, 250);
on_picking(() =>
has_responded = true
);
on_picked(wm_class => {
if (should_take_answer) {
if (wm_class == 'window-not-found') {
console.warn("Can't pick window from here");
this._app_page._preferences_window.add_toast(
this._window_not_found_toast
);
return;
}
this._window_class.buffer.text = wm_class;
}
});
pick();
}
});

View File

@ -0,0 +1,81 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
export const Dash = GObject.registerClass({
GTypeName: 'Dash',
Template: GLib.uri_resolve_relative(import.meta.url, '../ui/dash.ui', GLib.UriFlags.NONE),
InternalChildren: [
'blur',
'pipeline_choose_row',
'mode_static',
'mode_dynamic',
'sigma_row',
'sigma',
'brightness_row',
'brightness',
'override_background',
'style_dash_to_dock',
'unblur_in_overview'
],
}, class Dash extends Adw.PreferencesPage {
constructor(preferences, pipelines_manager, pipelines_page) {
super({});
this.preferences = preferences;
this.pipelines_manager = pipelines_manager;
this.pipelines_page = pipelines_page;
this.preferences.dash_to_dock.settings.bind(
'blur', this._blur, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this._pipeline_choose_row.initialize(
this.preferences.dash_to_dock, this.pipelines_manager, this.pipelines_page
);
this.change_blur_mode(this.preferences.dash_to_dock.STATIC_BLUR, true);
this._mode_static.connect('toggled',
() => this.preferences.dash_to_dock.STATIC_BLUR = this._mode_static.active
);
this.preferences.dash_to_dock.STATIC_BLUR_changed(
() => this.change_blur_mode(this.preferences.dash_to_dock.STATIC_BLUR, false)
);
this.preferences.dash_to_dock.settings.bind(
'sigma', this._sigma, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.dash_to_dock.settings.bind(
'brightness', this._brightness, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.dash_to_dock.settings.bind(
'override-background',
this._override_background, 'enable-expansion',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.dash_to_dock.settings.bind(
'style-dash-to-dock', this._style_dash_to_dock, 'selected',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.dash_to_dock.settings.bind(
'unblur-in-overview', this._unblur_in_overview, 'active',
Gio.SettingsBindFlags.DEFAULT
);
}
change_blur_mode(is_static_blur, first_run) {
this._mode_static.set_active(is_static_blur);
if (first_run)
this._mode_dynamic.set_active(!is_static_blur);
this._pipeline_choose_row.set_visible(is_static_blur);
this._sigma_row.set_visible(!is_static_blur);
this._brightness_row.set_visible(!is_static_blur);
}
});

View File

@ -0,0 +1,68 @@
import Gdk from 'gi://Gdk';
import Gtk from 'gi://Gtk';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
export function addMenu(window) {
const builder = new Gtk.Builder();
// add a dummy page and remove it immediately, to access headerbar
builder.add_from_file(GLib.filename_from_uri(GLib.uri_resolve_relative(import.meta.url, '../ui/menu.ui', GLib.UriFlags.NONE))[0]);
let menu_util = builder.get_object('menu_util');
window.add(menu_util);
try {
addMenuToHeader(window, builder);
} catch (error) {
// could not add menu... not so bad
}
window.remove(menu_util);
}
function addMenuToHeader(window, builder) {
// a little hack to get to the headerbar
const page = builder.get_object('menu_util');
const pages_stack = page.get_parent(); // AdwViewStack
const content_stack = pages_stack.get_parent().get_parent(); // GtkStack
const preferences = content_stack.get_parent(); // GtkBox
const headerbar = preferences.get_first_child().get_next_sibling()
.get_first_child().get_first_child().get_first_child(); // AdwHeaderBar
headerbar.pack_start(builder.get_object('info_menu'));
// setup menu actions
const actionGroup = new Gio.SimpleActionGroup();
window.insert_action_group('prefs', actionGroup);
// a list of actions with their associated link
const actions = [
{
name: 'open-bug-report',
link: 'https://github.com/aunetx/blur-my-shell/issues'
},
{
name: 'open-readme',
link: 'https://github.com/aunetx/blur-my-shell'
},
{
name: 'open-license',
link: 'https://github.com/aunetx/blur-my-shell/blob/master/LICENSE'
},
{
name: 'donate-github',
link: 'https://github.com/sponsors/aunetx'
},
{
name: 'donate-kofi',
link: 'https://ko-fi.com/aunetx'
},
];
actions.forEach(action => {
let act = new Gio.SimpleAction({ name: action.name });
act.connect(
'activate',
() => Gtk.show_uri(window, action.link, Gdk.CURRENT_TIME)
);
actionGroup.add_action(act);
});
}

View File

@ -0,0 +1,75 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
export const Other = GObject.registerClass({
GTypeName: 'Other',
Template: GLib.uri_resolve_relative(import.meta.url, '../ui/other.ui', GLib.UriFlags.NONE),
InternalChildren: [
'lockscreen_blur',
'lockscreen_pipeline_choose_row',
'screenshot_blur',
'screenshot_pipeline_choose_row',
'window_list_blur',
'window_list_sigma',
'window_list_brightness',
'hack_level',
'debug',
'reset'
],
}, class Overview extends Adw.PreferencesPage {
constructor(preferences, pipelines_manager, pipelines_page) {
super({});
this.preferences = preferences;
this.pipelines_manager = pipelines_manager;
this.pipelines_page = pipelines_page;
this.preferences.lockscreen.settings.bind(
'blur', this._lockscreen_blur, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this._lockscreen_pipeline_choose_row.initialize(
this.preferences.lockscreen, this.pipelines_manager, this.pipelines_page
);
this.preferences.screenshot.settings.bind(
'blur', this._screenshot_blur, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this._screenshot_pipeline_choose_row.initialize(
this.preferences.screenshot, this.pipelines_manager, this.pipelines_page
);
this.preferences.window_list.settings.bind(
'blur', this._window_list_blur, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.window_list.settings.bind(
'sigma', this._window_list_sigma, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.window_list.settings.bind(
'brightness', this._window_list_brightness, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.settings.bind(
'hacks-level', this._hack_level, 'selected',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.settings.bind(
'debug', this._debug, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this._reset.connect('clicked', () => this.preferences.reset());
}
});

View File

@ -0,0 +1,59 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
export const Overview = GObject.registerClass({
GTypeName: 'Overview',
Template: GLib.uri_resolve_relative(import.meta.url, '../ui/overview.ui', GLib.UriFlags.NONE),
InternalChildren: [
'overview_blur',
'pipeline_choose_row',
'overview_style_components',
'appfolder_blur',
'appfolder_sigma',
'appfolder_brightness',
'appfolder_style_dialogs'
],
}, class Overview extends Adw.PreferencesPage {
constructor(preferences, pipelines_manager, pipelines_page) {
super({});
this.preferences = preferences;
this.pipelines_manager = pipelines_manager;
this.pipelines_page = pipelines_page;
this.preferences.overview.settings.bind(
'blur', this._overview_blur, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this._pipeline_choose_row.initialize(
this.preferences.overview, this.pipelines_manager, this.pipelines_page
);
this.preferences.overview.settings.bind(
'style-components', this._overview_style_components, 'selected',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.appfolder.settings.bind(
'blur', this._appfolder_blur, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.appfolder.settings.bind(
'sigma', this._appfolder_sigma, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.appfolder.settings.bind(
'brightness', this._appfolder_brightness, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.appfolder.settings.bind(
'style-dialogs', this._appfolder_style_dialogs, 'selected',
Gio.SettingsBindFlags.DEFAULT
);
}
});

View File

@ -0,0 +1,102 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
export const Panel = GObject.registerClass({
GTypeName: 'Panel',
Template: GLib.uri_resolve_relative(import.meta.url, '../ui/panel.ui', GLib.UriFlags.NONE),
InternalChildren: [
'blur',
'pipeline_choose_row',
'mode_static',
'mode_dynamic',
'sigma_row',
'sigma',
'brightness_row',
'brightness',
'unblur_in_overview',
'force_light_text',
'override_background',
'style_panel',
'override_background_dynamically',
'hidetopbar_compatibility',
'dtp_blur_original_panel'
],
}, class Panel extends Adw.PreferencesPage {
constructor(preferences, pipelines_manager, pipelines_page) {
super({});
this.preferences = preferences;
this.pipelines_manager = pipelines_manager;
this.pipelines_page = pipelines_page;
this.preferences.panel.settings.bind(
'blur', this._blur, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this._pipeline_choose_row.initialize(
this.preferences.panel, this.pipelines_manager, this.pipelines_page
);
this.change_blur_mode(this.preferences.panel.STATIC_BLUR, true);
this._mode_static.connect('toggled',
() => this.preferences.panel.STATIC_BLUR = this._mode_static.active
);
this.preferences.panel.STATIC_BLUR_changed(
() => this.change_blur_mode(this.preferences.panel.STATIC_BLUR, false)
);
this.preferences.panel.settings.bind(
'sigma', this._sigma, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.panel.settings.bind(
'brightness', this._brightness, 'value',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.panel.settings.bind(
'unblur-in-overview', this._unblur_in_overview, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.panel.settings.bind(
'force-light-text', this._force_light_text, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.panel.settings.bind(
'override-background',
this._override_background, 'enable-expansion',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.panel.settings.bind(
'style-panel', this._style_panel, 'selected',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.panel.settings.bind(
'override-background-dynamically',
this._override_background_dynamically, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.hidetopbar.settings.bind(
'compatibility', this._hidetopbar_compatibility, 'active',
Gio.SettingsBindFlags.DEFAULT
);
this.preferences.dash_to_panel.settings.bind(
'blur-original-panel', this._dtp_blur_original_panel, 'active',
Gio.SettingsBindFlags.DEFAULT
);
}
change_blur_mode(is_static_blur, first_run) {
this._mode_static.set_active(is_static_blur);
if (first_run)
this._mode_dynamic.set_active(!is_static_blur);
this._pipeline_choose_row.set_visible(is_static_blur);
this._sigma_row.set_visible(!is_static_blur);
this._brightness_row.set_visible(!is_static_blur);
}
});

View File

@ -0,0 +1,98 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
import { PipelineGroup } from './pipelines_management/pipeline_group.js';
import { EffectsDialog } from './pipelines_management/effects_dialog.js';
export const Pipelines = GObject.registerClass({
GTypeName: 'Pipelines',
Template: GLib.uri_resolve_relative(import.meta.url, '../ui/pipelines.ui', GLib.UriFlags.NONE),
InternalChildren: [
'add_pipeline'
],
}, class Pipelines extends Adw.PreferencesPage {
constructor(preferences, pipelines_manager, window) {
super({});
this.preferences = preferences;
this.pipelines_manager = pipelines_manager;
this.window = window;
this.pipelines_map = new Map;
for (let pipeline_id in this.pipelines_manager.pipelines)
this.add_pipeline(pipeline_id, false);
this.preferences.connect('reset', _ => {
this.pipelines_map.forEach((_infos, pid) => this.remove_pipeline(pid));
for (let pipeline_id in this.pipelines_manager.pipelines)
this.add_pipeline(pipeline_id, false);
});
this._add_pipeline.connect(
"clicked",
() => this.pipelines_manager.create_pipeline(_("New pipeline"))
);
this.pipelines_manager.connect(
"pipeline-created",
(_obj, id, _pipeline) => this.add_pipeline(id, true)
);
}
add_pipeline(pipeline_id, scroll_to_bottom) {
let pipeline = this.pipelines_manager.pipelines[pipeline_id];
let pipeline_group = new PipelineGroup(
this.pipelines_manager, pipeline_id, pipeline, this
);
let pipeline_destroyed_id = this.pipelines_manager.connect(
pipeline_id + "::pipeline-destroyed",
() => this.remove_pipeline(pipeline_id)
);
let pipeline_renamed_id = this.pipelines_manager.connect(
pipeline_id + "::pipeline-renamed",
(_obj, name) => this.rename_pipeline(pipeline_id, name)
);
this.pipelines_map.set(pipeline_id, {
pipeline_group, pipeline_destroyed_id, pipeline_renamed_id
});
this.add(pipeline_group);
// scroll to the bottom of the page
if (scroll_to_bottom) {
this.window.set_visible_page(this);
setTimeout(() => {
const scroll_adjustment = this.get_first_child().get_vadjustment();
scroll_adjustment.value = scroll_adjustment.get_upper();
}, 10);
pipeline_group._title.grab_focus();
}
}
remove_pipeline(pipeline_id) {
let pipeline_infos = this.pipelines_map.get(pipeline_id);
if (pipeline_infos) {
this.pipelines_manager.disconnect(pipeline_infos.pipeline_destroyed_id);
this.remove(pipeline_infos.pipeline_group);
this.pipelines_map.delete(pipeline_id);
}
}
rename_pipeline(pipeline_id, name) {
let pipeline_infos = this.pipelines_map.get(pipeline_id);
if (pipeline_infos)
pipeline_infos.pipeline_group.set_title(name.length > 0 ? name : " ");
}
open_effects_dialog(pipeline_id) {
let dialog = new EffectsDialog(this.pipelines_manager, pipeline_id);
dialog.present(this.window);
}
});

View File

@ -0,0 +1,209 @@
import Adw from 'gi://Adw';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
import { get_supported_effects } from '../../effects/effects.js';
export const EffectRow = GObject.registerClass({
GTypeName: 'EffectRow',
InternalChildren: [],
}, class EffectRow extends Adw.ExpanderRow {
constructor(effect, effects_dialog) {
super({});
this.SUPPORTED_EFFECTS = get_supported_effects(_);
this.effect = effect;
this.effects_dialog = effects_dialog;
this.pipeline_id = effects_dialog.pipeline_id;
this.pipelines_manager = effects_dialog.pipelines_manager;
if (effect.type in this.SUPPORTED_EFFECTS) {
this.set_title(this.SUPPORTED_EFFECTS[effect.type].name);
this.set_subtitle(this.SUPPORTED_EFFECTS[effect.type].description);
this.populate_options();
}
else {
this._warn(`could not assign effect ${effect.type} to its correct name`);
this.set_title(effect.type);
}
let prefix_bin = new Gtk.Box({
spacing: 6
});
this.add_prefix(prefix_bin);
let move_bin = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
'width-request': 38,
'height-request': 38,
'margin-top': 6,
'margin-bottom': 6
});
prefix_bin.append(move_bin);
move_bin.add_css_class('linked');
this._move_up_button = new Gtk.Button({
'icon-name': 'go-up-symbolic',
'width-request': 38,
'height-request': 19
});
this._move_down_button = new Gtk.Button({
'icon-name': 'go-down-symbolic',
'width-request': 38,
'height-request': 19
});
this._move_up_button.add_css_class('flat');
this._move_down_button.add_css_class('flat');
move_bin.append(this._move_up_button);
move_bin.append(this._move_down_button);
this._move_up_button.connect('clicked', () => effects_dialog.move_row_by(this, -1));
this._move_down_button.connect('clicked', () => effects_dialog.move_row_by(this, +1));
let remove_button = new Gtk.Button({
'icon-name': 'remove-row-symbolic',
'width-request': 38,
'height-request': 38,
'margin-top': 6,
'margin-bottom': 6,
'valign': Gtk.Align.CENTER
});
prefix_bin.append(remove_button);
remove_button.add_css_class('destructive-action');
remove_button.connect('clicked', () => effects_dialog.remove_row(this));
}
populate_options() {
const editable_params = this.SUPPORTED_EFFECTS[this.effect.type].editable_params;
for (const param_key in editable_params) {
let param = editable_params[param_key];
let row;
switch (param.type) {
case "integer":
row = new Adw.SpinRow({
adjustment: new Gtk.Adjustment({
lower: param.min,
upper: param.max,
step_increment: param.increment
})
});
row.adjustment.set_value(this.get_effect_param(param_key));
row.adjustment.connect(
'value-changed', () => this.set_effect_param(param_key, row.adjustment.value)
);
break;
case "float":
row = new Adw.ActionRow;
let scale = new Gtk.Scale({
valign: Gtk.Align.CENTER,
hexpand: true,
width_request: 200,
draw_value: true,
value_pos: Gtk.PositionType.RIGHT,
digits: param.digits,
adjustment: new Gtk.Adjustment({
lower: param.min,
upper: param.max,
step_increment: param.increment,
page_increment: param.big_increment
})
});
// TODO check if it's a good idea to set the default parameter, as the "good"
// value really change depending on the user wallpaper... if so, do for dynamic
// blur too
scale.add_mark(
this.get_default_effect_param(param_key), Gtk.PositionType.BOTTOM, null
);
row.add_suffix(scale);
scale.adjustment.set_value(this.get_effect_param(param_key));
scale.adjustment.connect(
'value-changed', () => this.set_effect_param(param_key, scale.adjustment.value)
);
break;
case "boolean":
row = new Adw.SwitchRow;
row.set_active(this.get_effect_param(param_key));
row.connect(
'notify::active', () => this.set_effect_param(param_key, row.active)
);
break;
case "rgba":
row = new Adw.ActionRow;
let color_button = new Gtk.ColorButton({
valign: Gtk.Align.CENTER,
width_request: 75,
height_request: 45,
show_editor: true,
use_alpha: true
});
row.add_suffix(color_button);
// set original color
let c = color_button.get_rgba().copy();
[c.red, c.green, c.blue, c.alpha] = this.get_effect_param(param_key);
color_button.set_rgba(c);
// update on on 'color-set'
color_button.connect(
'color-set', () => {
let c = color_button.get_rgba();
this.set_effect_param(param_key, [c.red, c.green, c.blue, c.alpha]);
}
);
break;
default:
row = new Adw.ActionRow;
break;
}
row.set_title(param.name);
row.set_subtitle(param.description);
this.add_row(row);
}
}
get_effect_param(key) {
let effects = this.pipelines_manager.pipelines[this.pipeline_id].effects;
const gsettings_effect = effects.find(e => e.id == this.effect.id);
if ('params' in gsettings_effect && key in gsettings_effect.params)
return gsettings_effect.params[key];
else
return this.get_default_effect_param(key);
}
get_default_effect_param(key) {
return this.SUPPORTED_EFFECTS[this.effect.type].class.default_params[key];
}
set_effect_param(key, value) {
// we must pay attention not to change the effects in the pipelines manager before updating
// it in gsettings, else it won't be updated (or every effect will be)
let effects = this.pipelines_manager.pipelines[this.pipeline_id].effects;
const effect_index = effects.findIndex(e => e.id == this.effect.id);
if (effect_index >= 0) {
effects[effect_index] = {
...this.effect, params: { ...this.effect.params }
};
effects[effect_index].params[key] = value;
this.effect = effects[effect_index];
}
else
this._warn(`effect not found when setting key ${key}`);
this.pipelines_manager.update_pipeline_effects(this.pipeline_id, effects, false);
}
_warn(str) {
console.warn(
`[Blur my Shell > effect row] pipeline '${this.pipeline_id}',`
+ ` effect '${this.effect.id}': ${str}`
);
}
});

View File

@ -0,0 +1,150 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
import { EffectRow } from './effect_row.js';
import { get_effects_groups, get_supported_effects } from '../../effects/effects.js';
export const EffectsDialog = GObject.registerClass({
GTypeName: 'EffectsDialog',
Template: GLib.uri_resolve_relative(import.meta.url, '../../ui/effects-dialog.ui', GLib.UriFlags.NONE),
InternalChildren: [
"add_effect",
"effects_list"
],
}, class EffectsDialog extends Adw.PreferencesDialog {
constructor(pipelines_manager, pipeline_id) {
super({});
this.EFFECTS_GROUPS = get_effects_groups(_);
this.SUPPORTED_EFFECTS = get_supported_effects(_);
this.pipelines_manager = pipelines_manager;
this.pipeline_id = pipeline_id;
let pipeline = pipelines_manager.pipelines[pipeline_id];
this.set_title(pipeline.name.length > 0 ? _(`Effects for "${pipeline.name}"`) : _("Effects"));
pipeline.effects.forEach(effect => {
const effect_row = new EffectRow(effect, this);
this._effects_list.add(effect_row);
this.update_rows_insensitive_mover(effect_row);
});
this.build_effects_chooser();
this._add_effect.connect('clicked', () => this.effects_chooser_dialog.present(this));
}
build_effects_chooser() {
this.effects_chooser_dialog = new Adw.Dialog({
presentation_mode: Adw.DialogPresentationMode.BOTTOM_SHEET,
content_width: 450
});
let page = new Adw.PreferencesPage;
this.effects_chooser_dialog.set_child(page);
for (const effects_group in this.EFFECTS_GROUPS) {
const group_infos = this.EFFECTS_GROUPS[effects_group];
let group = new Adw.PreferencesGroup({
title: group_infos.name
});
page.add(group);
for (const effect_type of group_infos.contains) {
if (!(effect_type in this.SUPPORTED_EFFECTS))
continue;
let action_row = new Adw.ActionRow({
title: this.SUPPORTED_EFFECTS[effect_type].name,
subtitle: this.SUPPORTED_EFFECTS[effect_type].description
});
let select_button = new Gtk.Button({
'icon-name': 'select-row-symbolic',
'width-request': 38,
'height-request': 38,
'margin-top': 6,
'margin-bottom': 6
});
group.add(action_row);
select_button.add_css_class('flat');
action_row.add_suffix(select_button);
action_row.set_activatable_widget(select_button);
select_button.connect('clicked', () => {
this.append_effect(effect_type);
this.effects_chooser_dialog.close();
});
}
}
}
append_effect(effect_type) {
const effect = {
type: effect_type, id: "effect_" + ("" + Math.random()).slice(2, 16)
};
this.pipelines_manager.update_pipeline_effects(
this.pipeline_id,
[...this.pipelines_manager.pipelines[this.pipeline_id].effects, effect]
);
const effect_row = new EffectRow(effect, this);
this._effects_list.add(effect_row);
this.move_row_by(effect_row, 0);
this.update_rows_insensitive_mover(effect_row);
}
move_row_by(row, number) {
const effects = this.pipelines_manager.pipelines[this.pipeline_id].effects;
const effect_index = effects.findIndex(e => e.id == row.effect.id);
if (effect_index >= 0) {
effects.splice(effect_index, 1);
effects.splice(effect_index + number, 0, row.effect);
const listbox = row.get_parent();
listbox.set_sort_func((row_a, row_b) => {
const id_a = effects.findIndex(e => e.id == row_a.effect.id);
const id_b = effects.findIndex(e => e.id == row_b.effect.id);
return id_a > id_b;
});
this.update_rows_insensitive_mover(row);
this.pipelines_manager.update_pipeline_effects(
this.pipeline_id, effects
);
}
}
update_rows_insensitive_mover(any_row) {
if (this._insensitive_top)
this._insensitive_top.set_sensitive(true);
if (this._insensitive_bottom)
this._insensitive_bottom.set_sensitive(true);
const listbox = any_row.get_parent();
this._insensitive_top = listbox.get_first_child()._move_up_button;
this._insensitive_top?.set_sensitive(false);
this._insensitive_bottom = listbox.get_last_child()._move_down_button;
this._insensitive_bottom?.set_sensitive(false);
}
remove_row(row) {
const effects = this.pipelines_manager.pipelines[this.pipeline_id].effects;
const effect_index = effects.findIndex(e => e.id == row.effect.id);
if (effect_index >= 0) {
effects.splice(effect_index, 1);
this.pipelines_manager.update_pipeline_effects(
this.pipeline_id, effects
);
}
this._effects_list.remove(row);
}
});

View File

@ -0,0 +1,106 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
export const PipelineChooseRow = GObject.registerClass({
GTypeName: 'PipelineChooseRow',
Template: GLib.uri_resolve_relative(import.meta.url, '../../ui/pipeline-choose-row.ui', GLib.UriFlags.NONE),
InternalChildren: [
'pipeline_choose',
'pipeline_model',
'pipeline_edit'
],
}, class PipelineChooseRow extends Adw.ActionRow {
initialize(preferences, pipelines_manager, pipelines_page) {
this.preferences = preferences;
this.pipelines_manager = pipelines_manager;
this.pipelines_page = pipelines_page;
this.create_pipelines_list();
// display the correct pipeline name in the drop-down instead of their ids
const closure_func = string_object => {
const pipeline_id = string_object.get_string();
if (pipeline_id == 'create_new')
return _("Create new pipeline");
if (pipeline_id in this.pipelines_manager.pipelines)
return this.pipelines_manager.pipelines[pipeline_id].name;
else
return "";
};
const expression = new Gtk.ClosureExpression(GObject.TYPE_STRING, closure_func, []);
this._pipeline_choose.expression = expression;
// TODO fix the expression not being re-evaluated other than by setting it again
this.pipelines_manager.connect(
'pipeline-names-changed',
() => this._pipeline_choose.expression = new Gtk.ClosureExpression(
GObject.TYPE_STRING, closure_func, []
)
);
this.preferences.PIPELINE_changed(() => this.on_settings_pipeline_changed());
this.pipelines_manager.connect('pipeline-list-changed', () => this.create_pipelines_list());
this._pipeline_choose.connect('notify::selected', () => this.on_selected_pipeline_changed());
this._pipeline_edit.connect(
'clicked',
() => this.pipelines_page.open_effects_dialog(this.preferences.PIPELINE)
);
}
on_selected_pipeline_changed() {
if (!this._pipeline_choose.selected_item || this._is_creating_pipelines_list)
return;
const pipeline_id = this._pipeline_choose.selected_item.get_string();
if (pipeline_id == 'create_new') {
const id = this.pipelines_manager.create_pipeline(_("New pipeline"));
this.preferences.PIPELINE = id;
}
else
this.preferences.PIPELINE = pipeline_id;
}
on_settings_pipeline_changed() {
for (let i = 0; i < this._pipeline_model.n_items; i++) {
const pipeline_id = this._pipeline_model.get_string(i);
// if we have more pipelines than we should have: rebuild...
// that is the case when resetting the preferences for example
if (!(pipeline_id in this.pipelines_manager)) {
this.create_pipelines_list();
return;
}
if (pipeline_id == this.preferences.PIPELINE)
this._pipeline_choose.set_selected(i);
}
}
create_pipelines_list() {
// prevent the pipeline selector from being updated while re-creating the list
this._is_creating_pipelines_list = true;
// remove ancient items
this._pipeline_model.splice(0, this._pipeline_model.n_items, null);
// add new ones
let i = 0;
for (let pipeline_id in this.pipelines_manager.pipelines) {
this._pipeline_model.append(pipeline_id);
if (pipeline_id == this.preferences.PIPELINE)
this._pipeline_choose.set_selected(i);
i++;
}
this._pipeline_model.append('create_new');
// now update the drop-down selector
this._is_creating_pipelines_list = false;
this.on_selected_pipeline_changed();
}
});

View File

@ -0,0 +1,99 @@
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
import { get_supported_effects } from '../../effects/effects.js';
export const PipelineGroup = GObject.registerClass({
GTypeName: 'PipelineGroup',
Template: GLib.uri_resolve_relative(import.meta.url, '../../ui/pipeline-group.ui', GLib.UriFlags.NONE),
InternalChildren: [
"title",
"effects_description_row",
"manage_effects"
],
}, class PipelineGroup extends Adw.PreferencesGroup {
constructor(pipelines_manager, pipeline_id, pipeline, pipelines_page) {
super({});
this.SUPPORTED_EFFECTS = get_supported_effects(_);
this._pipelines_manager = pipelines_manager;
this._pipelines_page = pipelines_page;
this._pipeline_id = pipeline_id;
// set the description
this.set_description(_(`Pipeline id: "${pipeline_id}"`));
// set the title and connect it to the text entry
this.set_title(pipeline.name.length > 0 ? pipeline.name : " ");
this._title.set_text(pipeline.name);
this._title.connect(
'changed',
() => pipelines_manager.rename_pipeline(pipeline_id, this._title.get_text())
);
// the bin containing the actions
let prefix_bin = new Gtk.Box;
prefix_bin.add_css_class('linked');
this._title.add_prefix(prefix_bin);
// add a 'remove' button if we are not the default pipeline
if (pipeline_id != "pipeline_default") {
let remove_button = new Gtk.Button({
'icon-name': 'remove-row-symbolic',
'width-request': 38,
'height-request': 38,
'margin-top': 6,
'margin-bottom': 6
});
remove_button.add_css_class('destructive-action');
prefix_bin.append(remove_button);
remove_button.connect('clicked', () => pipelines_manager.delete_pipeline(pipeline_id));
}
// add a 'duplicate' button
let duplicate_button = new Gtk.Button({
'icon-name': 'duplicate-row-symbolic',
'width-request': 38,
'height-request': 38,
'margin-top': 6,
'margin-bottom': 6
});
prefix_bin.append(duplicate_button);
duplicate_button.connect('clicked', () => pipelines_manager.duplicate_pipeline(pipeline_id));
this.update_effects_description_row();
this._pipelines_manager.connect(
pipeline_id + '::pipeline-updated',
() => this.update_effects_description_row()
);
this._manage_effects.connect(
'clicked',
() => pipelines_page.open_effects_dialog(pipeline_id)
);
}
update_effects_description_row() {
const effects = this._pipelines_manager.pipelines[this._pipeline_id].effects;
if (effects.length == 0)
this._effects_description_row.set_title(_("No effect"));
else if (effects.length == 1)
this._effects_description_row.set_title(_("1 effect"));
else
this._effects_description_row.set_title(_(`${effects.length} effects`));
let subtitle = "";
effects.forEach(effect => {
if (effect.type in this.SUPPORTED_EFFECTS)
subtitle += _(`${this.SUPPORTED_EFFECTS[effect.type].name}, `);
else
subtitle += _("Unknown effect, ");
});
this._effects_description_row.set_subtitle(subtitle.slice(0, -2));
}
});