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,255 @@
import Shell from 'gi://Shell';
import Clutter from 'gi://Clutter';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { PaintSignals } from '../conveniences/paint_signals.js';
// TODO drop Tweener in favour of Clutter's `ease` (will need to extend the blur effect for it)
const Tweener = imports.tweener.tweener;
const transparent = Clutter.Color.from_pixel(0x00000000);
const FOLDER_DIALOG_ANIMATION_TIME = 200;
const DIALOGS_STYLES = [
"appfolder-dialogs-transparent",
"appfolder-dialogs-light",
"appfolder-dialogs-dark"
];
let original_zoomAndFadeIn = null;
let original_zoomAndFadeOut = null;
let sigma;
let brightness;
let _zoomAndFadeIn = function () {
let [sourceX, sourceY] =
this._source.get_transformed_position();
let [dialogX, dialogY] =
this.child.get_transformed_position();
this.child.set({
translation_x: sourceX - dialogX,
translation_y: sourceY - dialogY,
scale_x: this._source.width / this.child.width,
scale_y: this._source.height / this.child.height,
opacity: 0,
});
this.set_background_color(transparent);
let blur_effect = this.get_effect("appfolder-blur");
blur_effect.radius = 0;
blur_effect.brightness = 1.0;
Tweener.addTween(blur_effect,
{
radius: sigma * 2,
brightness: brightness,
time: FOLDER_DIALOG_ANIMATION_TIME / 1000,
transition: 'easeOutQuad'
}
);
this.child.ease({
translation_x: 0,
translation_y: 0,
scale_x: 1,
scale_y: 1,
opacity: 255,
duration: FOLDER_DIALOG_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
this._needsZoomAndFade = false;
if (this._sourceMappedId === 0) {
this._sourceMappedId = this._source.connect(
'notify::mapped', this._zoomAndFadeOut.bind(this));
}
};
let _zoomAndFadeOut = function () {
if (!this._isOpen)
return;
if (!this._source.mapped) {
this.hide();
return;
}
let [sourceX, sourceY] =
this._source.get_transformed_position();
let [dialogX, dialogY] =
this.child.get_transformed_position();
this.set_background_color(transparent);
let blur_effect = this.get_effect("appfolder-blur");
Tweener.addTween(blur_effect,
{
radius: 0,
brightness: 1.0,
time: FOLDER_DIALOG_ANIMATION_TIME / 1000,
transition: 'easeInQuad'
}
);
this.child.ease({
translation_x: sourceX - dialogX,
translation_y: sourceY - dialogY,
scale_x: this._source.width / this.child.width,
scale_y: this._source.height / this.child.height,
opacity: 0,
duration: FOLDER_DIALOG_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this.child.set({
translation_x: 0,
translation_y: 0,
scale_x: 1,
scale_y: 1,
opacity: 255,
});
this.hide();
this._popdownCallbacks.forEach(func => func());
this._popdownCallbacks = [];
},
});
this._needsZoomAndFade = false;
};
export const AppFoldersBlur = class AppFoldersBlur {
// we do not use the effects manager and dummy pipelines here because we
// really want to manage our sigma value ourself during the transition
constructor(connections, settings, _) {
this.connections = connections;
this.paint_signals = new PaintSignals(connections);
this.settings = settings;
}
enable() {
this._log("blurring appfolders");
brightness = this.settings.appfolder.BRIGHTNESS;
sigma = this.settings.appfolder.SIGMA;
let appDisplay = Main.overview._overview.controls._appDisplay;
if (appDisplay._folderIcons.length > 0) {
this.blur_appfolders();
}
this.connections.connect(
appDisplay, 'view-loaded', _ => this.blur_appfolders()
);
}
blur_appfolders() {
let appDisplay = Main.overview._overview.controls._appDisplay;
if (this.settings.HACKS_LEVEL === 1)
this._log("appfolders hack level 1");
appDisplay._folderIcons.forEach(icon => {
icon._ensureFolderDialog();
if (original_zoomAndFadeIn == null) {
original_zoomAndFadeIn = icon._dialog._zoomAndFadeIn;
}
if (original_zoomAndFadeOut == null) {
original_zoomAndFadeOut = icon._dialog._zoomAndFadeOut;
}
let blur_effect = new Shell.BlurEffect({
name: "appfolder-blur",
radius: sigma * 2,
brightness: brightness,
mode: Shell.BlurMode.BACKGROUND
});
icon._dialog.remove_effect_by_name("appfolder-blur");
icon._dialog.add_effect(blur_effect);
DIALOGS_STYLES.forEach(
style => icon._dialog._viewBox.remove_style_class_name(style)
);
if (this.settings.appfolder.STYLE_DIALOGS > 0)
icon._dialog._viewBox.add_style_class_name(
DIALOGS_STYLES[this.settings.appfolder.STYLE_DIALOGS - 1]
);
// finally override the builtin functions
icon._dialog._zoomAndFadeIn = _zoomAndFadeIn;
icon._dialog._zoomAndFadeOut = _zoomAndFadeOut;
// HACK
//
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
//
// This does not entirely fix this bug (shadows caused by windows
// still cause artifacts), but it prevents the shadows of the panel
// buttons to cause artifacts on the panel itself
//
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
if (this.settings.HACKS_LEVEL === 1) {
this.paint_signals.disconnect_all_for_actor(icon._dialog);
this.paint_signals.connect(icon._dialog, blur_effect);
} else {
this.paint_signals.disconnect_all();
}
});
};
set_sigma(s) {
sigma = s;
if (this.settings.appfolder.BLUR)
this.blur_appfolders();
}
set_brightness(b) {
brightness = b;
if (this.settings.appfolder.BLUR)
this.blur_appfolders();
}
disable() {
this._log("removing blur from appfolders");
let appDisplay = Main.overview._overview.controls._appDisplay;
if (original_zoomAndFadeIn != null) {
appDisplay._folderIcons.forEach(icon => {
if (icon._dialog)
icon._dialog._zoomAndFadeIn = original_zoomAndFadeIn;
});
}
if (original_zoomAndFadeOut != null) {
appDisplay._folderIcons.forEach(icon => {
if (icon._dialog)
icon._dialog._zoomAndFadeOut = original_zoomAndFadeOut;
});
}
appDisplay._folderIcons.forEach(icon => {
if (icon._dialog) {
icon._dialog.remove_effect_by_name("appfolder-blur");
DIALOGS_STYLES.forEach(
s => icon._dialog._viewBox.remove_style_class_name(s)
);
}
});
this.connections.disconnect_all();
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > appfolders] ${str}`);
}
};

View File

@ -0,0 +1,451 @@
import Meta from 'gi://Meta';
import Gio from 'gi://Gio';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { ApplicationsService } from '../dbus/services.js';
import { PaintSignals } from '../conveniences/paint_signals.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.js';
export const ApplicationsBlur = class ApplicationsBlur {
constructor(connections, settings, effects_manager) {
this.connections = connections;
this.settings = settings;
this.effects_manager = effects_manager;
this.paint_signals = new PaintSignals(connections);
// stores every blurred meta window
this.meta_window_map = new Map();
}
enable() {
this._log("blurring applications...");
// export dbus service for preferences
this.service = new ApplicationsService;
this.service.export();
this.mutter_gsettings = new Gio.Settings({ schema: 'org.gnome.mutter' });
// blur already existing windows
this.update_all_windows();
// blur every new window
this.connections.connect(
global.display,
'window-created',
(_meta_display, meta_window) => {
this._log("window created");
if (meta_window)
this.track_new(meta_window);
}
);
// update window blur when focus is changed
this.focused_window_pid = null;
this.init_dynamic_opacity();
this.connections.connect(
global.display,
'focus-window',
(_meta_display, meta_window, _p0) => {
if (meta_window && meta_window.bms_pid != this.focused_window_pid)
this.set_focus_for_window(meta_window);
else if (!meta_window)
this.set_focus_for_window(null);
}
);
this.connect_to_overview();
}
/// Initializes the dynamic opacity for windows, without touching to the connections.
/// This is used both when enabling the component, and when changing the dynamic-opacity pref.
init_dynamic_opacity() {
if (this.settings.applications.DYNAMIC_OPACITY) {
// make the currently focused window solid
if (global.display.focus_window)
this.set_focus_for_window(global.display.focus_window);
} else {
// remove old focused window if the pref was changed
if (this.focused_window_pid)
this.set_focus_for_window(null);
}
}
/// Connect to the overview being opened/closed to force the blur being
/// shown on every window of the workspaces viewer.
connect_to_overview() {
this.connections.disconnect_all_for(Main.overview);
if (this.settings.applications.BLUR_ON_OVERVIEW) {
// when the overview is opened, show every window actors (which
// allows the blur to be shown too)
this.connections.connect(
Main.overview, 'showing',
_ => this.meta_window_map.forEach((meta_window, _pid) => {
let window_actor = meta_window.get_compositor_private();
window_actor?.show();
})
);
// when the overview is closed, hide every actor that is not on the
// current workspace (to mimic the original behaviour)
this.connections.connect(
Main.overview, 'hidden',
_ => {
this.meta_window_map.forEach((meta_window, _pid) => {
let window_actor = meta_window.get_compositor_private();
if (
!meta_window.get_workspace().active
)
window_actor.hide();
});
}
);
}
}
/// Iterate through all existing windows and add blur as needed.
update_all_windows() {
// remove all previously blurred windows, in the case where the
// whitelist was changed
this.meta_window_map.forEach(((_meta_window, pid) => {
this.remove_blur(pid);
}));
for (
let i = 0;
i < global.workspace_manager.get_n_workspaces();
++i
) {
let workspace = global.workspace_manager.get_workspace_by_index(i);
let windows = workspace.list_windows();
windows.forEach(meta_window => this.track_new(meta_window));
}
}
/// Adds the needed signals to every new tracked window, and adds blur if
/// needed.
/// Accepts only untracked meta windows (i.e no `bms_pid` set)
track_new(meta_window) {
// create a pid that will follow the window during its whole life
const pid = ("" + Math.random()).slice(2, 16);
meta_window.bms_pid = pid;
this._log(`new window tracked, pid: ${pid}`);
// register the blurred window
this.meta_window_map.set(pid, meta_window);
// update the blur when wm-class is changed
this.connections.connect(
meta_window, 'notify::wm-class',
_ => this.check_blur(meta_window)
);
// update the position and size when the window size changes
this.connections.connect(
meta_window, 'size-changed',
_ => this.update_size(pid)
);
// remove the blur when the window is unmanaged
this.connections.connect(
meta_window, 'unmanaging',
_ => this.untrack_meta_window(pid)
);
this.check_blur(meta_window);
}
/// Updates the size of the blur actor associated to a meta window from its pid.
/// Accepts only tracked meta window (i.e `bms_pid` set), be it blurred or not.
update_size(pid) {
if (this.meta_window_map.has(pid)) {
const meta_window = this.meta_window_map.get(pid);
const blur_actor = meta_window.blur_actor;
if (blur_actor) {
const allocation = this.compute_allocation(meta_window);
blur_actor.x = allocation.x;
blur_actor.y = allocation.y;
blur_actor.width = allocation.width;
blur_actor.height = allocation.height;
}
} else
// the pid was visibly not removed
this.untrack_meta_window(pid);
}
/// Checks if the given actor needs to be blurred.
/// Accepts only tracked meta window, be it blurred or not.
///
/// In order to be blurred, a window either:
/// - is whitelisted in the user preferences if not enable-all
/// - is not blacklisted if enable-all
check_blur(meta_window) {
const window_wm_class = meta_window.get_wm_class();
const enable_all = this.settings.applications.ENABLE_ALL;
const whitelist = this.settings.applications.WHITELIST;
const blacklist = this.settings.applications.BLACKLIST;
if (window_wm_class)
this._log(`pid ${meta_window.bms_pid} associated to wm class name ${window_wm_class}`);
// if we are in blacklist mode and the window is not blacklisted
// or if we are in whitelist mode and the window is whitelisted
if (
window_wm_class !== ""
&& ((enable_all && !blacklist.includes(window_wm_class))
|| (!enable_all && whitelist.includes(window_wm_class))
)
&& [
Meta.FrameType.NORMAL,
Meta.FrameType.DIALOG,
Meta.FrameType.MODAL_DIALOG
].includes(meta_window.get_frame_type())
) {
// only blur the window if it is not already done
if (!meta_window.blur_actor)
this.create_blur_effect(meta_window);
}
// remove blur it is not explicitly whitelisted or un-blacklisted
else if (meta_window.blur_actor)
this.remove_blur(meta_window.bms_pid);
}
/// Add the blur effect to the window.
/// Accepts only tracked meta window that is NOT already blurred.
create_blur_effect(meta_window) {
const pid = meta_window.bms_pid;
const window_actor = meta_window.get_compositor_private();
const pipeline = new DummyPipeline(this.effects_manager, this.settings.applications);
let [blur_actor, bg_manager] = pipeline.create_background_with_effect(
window_actor, 'bms-application-blurred-widget'
);
meta_window.blur_actor = blur_actor;
meta_window.bg_manager = bg_manager;
// if hacks are selected, force to repaint the window
if (this.settings.HACKS_LEVEL === 1) {
this._log("hack level 1");
this.paint_signals.disconnect_all_for_actor(blur_actor);
this.paint_signals.connect(blur_actor, pipeline.effect);
} else {
this.paint_signals.disconnect_all_for_actor(blur_actor);
}
// make sure window is blurred in overview
if (this.settings.applications.BLUR_ON_OVERVIEW)
this.enforce_window_visibility_on_overview_for(window_actor);
// update the size
this.update_size(pid);
// set the window actor's opacity
this.set_window_opacity(window_actor, this.settings.applications.OPACITY);
// now set up the signals, for the window actor only: they are disconnected
// in `remove_blur`, whereas the signals for the meta window are disconnected
// only when the whole component is disabled
// update the window opacity when it changes, else we don't control it fully
this.connections.connect(
window_actor, 'notify::opacity',
_ => {
if (this.focused_window_pid != pid)
this.set_window_opacity(window_actor, this.settings.applications.OPACITY);
}
);
// hide the blur if window becomes invisible
if (!window_actor.visible)
blur_actor.hide();
this.connections.connect(
window_actor,
'notify::visible',
window_actor => {
if (window_actor.visible)
meta_window.blur_actor.show();
else
meta_window.blur_actor.hide();
}
);
}
/// With `focus=true`, tells us we are focused on said window (which can be null if
/// we are not focused anymore). It automatically removes the ancient focus.
/// With `focus=false`, just remove the focus from said window (which can still be null).
set_focus_for_window(meta_window, focus = true) {
let blur_actor = null;
let window_actor = null;
let new_pid = null;
if (meta_window) {
blur_actor = meta_window.blur_actor;
window_actor = meta_window.get_compositor_private();
new_pid = meta_window.bms_pid;
}
if (focus) {
// remove old focused window if any
if (this.focused_window_pid) {
const old_focused_window = this.meta_window_map.get(this.focused_window_pid);
if (old_focused_window)
this.set_focus_for_window(old_focused_window, false);
}
// set new focused window pid
this.focused_window_pid = new_pid;
// if we have blur, hide it and make the window opaque
if (this.settings.applications.DYNAMIC_OPACITY && blur_actor) {
blur_actor.hide();
this.set_window_opacity(window_actor, 255);
}
}
// if we remove the focus and have blur, show it and make the window transparent
else if (blur_actor) {
blur_actor.show();
this.set_window_opacity(window_actor, this.settings.applications.OPACITY);
}
}
/// Makes sure that, when the overview is visible, the window actor will
/// stay visible no matter what.
/// We can instead hide the last child of the window actor, which will
/// improve performances without hiding the blur effect.
enforce_window_visibility_on_overview_for(window_actor) {
this.connections.connect(window_actor, 'notify::visible',
_ => {
if (this.settings.applications.BLUR_ON_OVERVIEW) {
if (
!window_actor.visible
&& Main.overview.visible
) {
window_actor.show();
window_actor.get_last_child().hide();
}
else if (
window_actor.visible
)
window_actor.get_last_child().show();
}
}
);
}
/// Set the opacity of the window actor that sits on top of the blur effect.
set_window_opacity(window_actor, opacity) {
window_actor?.get_children().forEach(child => {
if (child.name !== "blur-actor" && child.opacity != opacity)
child.opacity = opacity;
});
}
/// Update the opacity of all window actors.
set_opacity() {
let opacity = this.settings.applications.OPACITY;
this.meta_window_map.forEach(((meta_window, pid) => {
if (pid != this.focused_window_pid && meta_window.blur_actor) {
let window_actor = meta_window.get_compositor_private();
this.set_window_opacity(window_actor, opacity);
}
}));
}
/// Compute the size and position for a blur actor.
/// If `scale-monitor-framebuffer` experimental feature if on, we don't need to manage scaling.
/// Else, on wayland, we need to divide by the scale to get the correct result.
compute_allocation(meta_window) {
const scale_monitor_framebuffer = this.mutter_gsettings.get_strv('experimental-features')
.includes('scale-monitor-framebuffer');
const is_wayland = Meta.is_wayland_compositor();
const monitor_index = meta_window.get_monitor();
// check if the window is using wayland, or xwayland/xorg for rendering
const scale = !scale_monitor_framebuffer && is_wayland && meta_window.get_client_type() == 0
? Main.layoutManager.monitors[monitor_index].geometry_scale
: 1;
let frame = meta_window.get_frame_rect();
let buffer = meta_window.get_buffer_rect();
return {
x: (frame.x - buffer.x) / scale,
y: (frame.y - buffer.y) / scale,
width: frame.width / scale,
height: frame.height / scale
};
}
/// Removes the blur actor to make a blurred window become normal again.
/// It however does not untrack the meta window itself.
/// Accepts a pid corresponding (or not) to a blurred (or not) meta window.
remove_blur(pid) {
this._log(`removing blur for pid ${pid}`);
let meta_window = this.meta_window_map.get(pid);
if (meta_window) {
let window_actor = meta_window.get_compositor_private();
let blur_actor = meta_window.blur_actor;
let bg_manager = meta_window.bg_manager;
if (blur_actor && window_actor) {
// reset the opacity
this.set_window_opacity(window_actor, 255);
// remove the blurred actor
window_actor.remove_child(blur_actor);
bg_manager._bms_pipeline.destroy();
bg_manager.destroy();
blur_actor.destroy();
// kinda untrack the blurred actor, as its presence is how we know
// whether we are blurred or not
delete meta_window.blur_actor;
delete meta_window.bg_manager;
// disconnect the signals of the window actor
this.paint_signals.disconnect_all_for_actor(blur_actor);
this.connections.disconnect_all_for(window_actor);
}
}
}
/// Kinda the same as `remove_blur`, but better: it also untracks the window.
/// This needs to be called when the component is being disabled, else it
/// would cause havoc by having untracked windows during normal operations,
/// which is not the point at all!
/// Accepts a pid corresponding (or not) to a blurred (or not) meta window.
untrack_meta_window(pid) {
this.remove_blur(pid);
let meta_window = this.meta_window_map.get(pid);
if (meta_window) {
this.connections.disconnect_all_for(meta_window);
this.meta_window_map.delete(pid);
}
}
disable() {
this._log("removing blur from applications...");
this.service?.unexport();
delete this.mutter_gsettings;
this.meta_window_map.forEach((_meta_window, pid) => {
this.untrack_meta_window(pid);
});
this.connections.disconnect_all();
this.paint_signals.disconnect_all();
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > applications] ${str}`);
}
};

View File

@ -0,0 +1,421 @@
import Meta from 'gi://Meta';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
import { PaintSignals } from '../conveniences/paint_signals.js';
import { Pipeline } from '../conveniences/pipeline.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.js';
const DASH_STYLES = [
"transparent-dash",
"light-dash",
"dark-dash"
];
/// This type of object is created for every dash found, and talks to the main
/// DashBlur thanks to signals.
///
/// This allows to dynamically track the created dashes for each screen.
class DashInfos {
constructor(
dash_blur, dash, dash_container, dash_background,
background, background_group, bg_manager
) {
// the parent DashBlur object, to communicate
this.dash_blur = dash_blur;
this.dash = dash;
this.dash_container = dash_container;
this.dash_background = dash_background;
this.background = background;
this.background_group = background_group;
this.bg_manager = bg_manager;
this.settings = dash_blur.settings;
this.old_style = this.dash._background.style;
this.dash_destroy_id = dash.connect('destroy', () => this.remove_dash_blur(false));
this.dash_blur_connections_ids = [];
this.dash_blur_connections_ids.push(
this.dash_blur.connect('remove-dashes', () => this.remove_dash_blur()),
this.dash_blur.connect('override-style', () => this.override_style()),
this.dash_blur.connect('remove-style', () => this.remove_style()),
this.dash_blur.connect('show', () => this.background_group.show()),
this.dash_blur.connect('hide', () => this.background_group.hide()),
this.dash_blur.connect('update-size', () => this.update_size()),
this.dash_blur.connect('change-blur-type', () => this.change_blur_type()),
this.dash_blur.connect('update-pipeline', () => this.update_pipeline())
);
}
// IMPORTANT: do never call this in a mutable `this.dash_blur.forEach`
remove_dash_blur(dash_not_already_destroyed = true) {
// remove the style and destroy the effects
this.remove_style();
this.destroy_dash(dash_not_already_destroyed);
// remove the dash infos from their list
const dash_infos_index = this.dash_blur.dashes.indexOf(this);
if (dash_infos_index >= 0)
this.dash_blur.dashes.splice(dash_infos_index, 1);
// disconnect everything
this.dash_blur_connections_ids.forEach(id => { if (id) this.dash_blur.disconnect(id); });
this.dash_blur_connections_ids = [];
if (this.dash_destroy_id)
this.dash.disconnect(this.dash_destroy_id);
this.dash_destroy_id = null;
}
override_style() {
this.remove_style();
this.dash.set_style_class_name(
DASH_STYLES[this.settings.dash_to_dock.STYLE_DASH_TO_DOCK]
);
}
remove_style() {
this.dash._background.style = this.old_style;
DASH_STYLES.forEach(
style => this.dash.remove_style_class_name(style)
);
}
destroy_dash(dash_not_already_destroyed = true) {
if (!dash_not_already_destroyed)
this.bg_manager.backgroundActor = null;
this.paint_signals?.disconnect_all();
this.dash.get_parent().remove_child(this.background_group);
this.bg_manager._bms_pipeline.destroy();
this.bg_manager.destroy();
this.background_group.destroy();
}
change_blur_type() {
this.destroy_dash();
let [
background, background_group, bg_manager, paint_signals
] = this.dash_blur.add_blur(this.dash);
this.background = background;
this.background_group = background_group;
this.bg_manager = bg_manager;
this.paint_signals = paint_signals;
this.dash.get_parent().insert_child_at_index(this.background_group, 0);
this.update_size();
}
update_pipeline() {
this.bg_manager._bms_pipeline.change_pipeline_to(
this.settings.dash_to_dock.PIPELINE
);
}
update_size() {
if (this.dash_blur.is_static) {
let [x, y] = this.get_dash_position(this.dash_container, this.dash_background);
this.background.x = -x;
this.background.y = -y;
if (this.dash_container.get_style_class_name().includes("top"))
this.background.set_clip(
x,
y + this.dash.y + this.dash_background.y,
this.dash_background.width,
this.dash_background.height
);
else if (this.dash_container.get_style_class_name().includes("bottom"))
this.background.set_clip(
x,
y + this.dash.y + this.dash_background.y,
this.dash_background.width,
this.dash_background.height
);
else if (this.dash_container.get_style_class_name().includes("left"))
this.background.set_clip(
x + this.dash.x + this.dash_background.x,
y + this.dash.y + this.dash_background.y,
this.dash_background.width,
this.dash_background.height
);
else if (this.dash_container.get_style_class_name().includes("right"))
this.background.set_clip(
x + this.dash.x + this.dash_background.x,
y + this.dash.y + this.dash_background.y,
this.dash_background.width,
this.dash_background.height
);
} else {
this.background.width = this.dash_background.width;
this.background.height = this.dash_background.height;
this.background.x = this.dash_background.x;
this.background.y = this.dash_background.y + this.dash.y;
}
}
get_dash_position(dash_container, dash_background) {
var x, y;
let monitor = Main.layoutManager.findMonitorForActor(dash_container);
let dash_box = dash_container._slider.get_child();
if (dash_container.get_style_class_name().includes("top")) {
x = (monitor.width - dash_background.width) / 2;
y = dash_box.y;
} else if (dash_container.get_style_class_name().includes("bottom")) {
x = (monitor.width - dash_background.width) / 2;
y = monitor.height - dash_container.height;
} else if (dash_container.get_style_class_name().includes("left")) {
x = dash_box.x;
y = dash_container.y + (dash_container.height - dash_background.height) / 2 - dash_background.y;
} else if (dash_container.get_style_class_name().includes("right")) {
x = monitor.width - dash_container.width;
y = dash_container.y + (dash_container.height - dash_background.height) / 2 - dash_background.y;
}
return [x, y];
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > dash] ${str}`);
}
_warn(str) {
console.warn(`[Blur my Shell > dash] ${str}`);
}
}
export const DashBlur = class DashBlur extends Signals.EventEmitter {
constructor(connections, settings, _) {
super();
this.dashes = [];
this.connections = connections;
this.settings = settings;
this.paint_signals = new PaintSignals(connections);
this.is_static = this.settings.dash_to_dock.STATIC_BLUR;
this.enabled = false;
}
enable() {
this.connections.connect(Main.uiGroup, 'child-added', (_, actor) => {
if (
(actor.get_name() === "dashtodockContainer") &&
(actor.constructor.name === 'DashToDock')
)
this.try_blur(actor);
});
this.blur_existing_dashes();
this.connect_to_overview();
this.update_size();
this.enabled = true;
}
// Finds all existing dashes on every monitor, and call `try_blur` on them
// We cannot only blur `Main.overview.dash`, as there could be several
blur_existing_dashes() {
this._log("searching for dash");
// blur every dash found, filtered by name
Main.uiGroup.get_children().filter((child) => {
return (child.get_name() === "dashtodockContainer") &&
(child.constructor.name === 'DashToDock');
}).forEach(dash_container => this.try_blur(dash_container));
}
// Tries to blur the dash contained in the given actor
try_blur(dash_container) {
let dash_box = dash_container._slider.get_child();
// verify that we did not already blur that dash
if (!dash_box.get_children().some(child =>
child.get_name() === "bms-dash-backgroundgroup"
)) {
this._log("dash to dock found, blurring it");
// finally blur the dash
let dash = dash_box.get_children().find(child => {
return child.get_name() === 'dash';
});
this.dashes.push(this.blur_dash_from(dash, dash_container));
}
}
// Blurs the dash and returns a `DashInfos` containing its information
blur_dash_from(dash, dash_container) {
let [background, background_group, bg_manager, paint_signals] = this.add_blur(dash);
// insert the background group to the right element
dash.get_parent().insert_child_at_index(background_group, 0);
// updates size and position on change
this.connections.connect(
dash,
['notify::width', 'notify::height'],
_ => this.update_size()
);
this.connections.connect(
dash_container,
['notify::width', 'notify::height', 'notify::y', 'notify::x'],
_ => this.update_size()
);
const dash_background = dash.get_children().find(child => {
return child.get_style_class_name() === 'dash-background';
});
// create infos
let infos = new DashInfos(
this,
dash,
dash_container,
dash_background,
background,
background_group,
bg_manager,
paint_signals
);
this.update_size();
this.update_background();
// returns infos
return infos;
}
add_blur(dash) {
const monitor = Main.layoutManager.findMonitorForActor(dash);
if (!monitor)
return;
const background_group = new Meta.BackgroundGroup({
name: 'bms-dash-backgroundgroup', width: 0, height: 0
});
let background, bg_manager, paint_signals;
let static_blur = this.settings.dash_to_dock.STATIC_BLUR;
if (static_blur) {
let bg_manager_list = [];
const pipeline = new Pipeline(
global.blur_my_shell._effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.dash_to_dock.PIPELINE
);
background = pipeline.create_background_with_effects(
monitor.index, bg_manager_list,
background_group, 'bms-dash-blurred-widget'
);
bg_manager = bg_manager_list[0];
}
else {
const pipeline = new DummyPipeline(
global.blur_my_shell._effects_manager,
this.settings.dash_to_dock
);
[background, bg_manager] = pipeline.create_background_with_effect(
background_group, 'bms-dash-blurred-widget'
);
paint_signals = new PaintSignals(this.connections);
// HACK
//
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
//
// This does not entirely fix this bug (shadows caused by windows
// still cause artifacts), but it prevents the shadows of the dash
// buttons to cause artifacts on the dash itself
//
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
if (this.settings.HACKS_LEVEL === 1) {
this._log("hack level 1");
paint_signals.disconnect_all();
paint_signals.connect(background, pipeline.effect);
} else {
paint_signals.disconnect_all();
}
}
return [background, background_group, bg_manager, paint_signals];
}
change_blur_type() {
this.is_static = this.settings.dash_to_dock.STATIC_BLUR;
this.emit('change-blur-type');
this.update_background();
}
/// Connect when overview if opened/closed to hide/show the blur accordingly
connect_to_overview() {
this.connections.disconnect_all_for(Main.overview);
if (this.settings.dash_to_dock.UNBLUR_IN_OVERVIEW) {
this.connections.connect(
Main.overview, 'showing', _ => this.hide()
);
this.connections.connect(
Main.overview, 'hidden', _ => this.show()
);
}
};
/// Updates the background to either remove it or not, according to the
/// user preferences.
update_background() {
this._log("updating background");
if (this.settings.dash_to_dock.OVERRIDE_BACKGROUND)
this.emit('override-style');
else
this.emit('remove-style');
}
update_pipeline() {
this.emit('update-pipeline');
}
update_size() {
this.emit('update-size');
}
show() {
this.emit('show');
}
hide() {
this.emit('hide');
}
disable() {
this._log("removing blur from dashes");
this.emit('remove-dashes');
this.dashes = [];
this.connections.disconnect_all();
this.enabled = false;
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > dash manager] ${str}`);
}
_warn(str) {
console.warn(`[Blur my Shell > dash manager] ${str}`);
}
};

View File

@ -0,0 +1,89 @@
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { UnlockDialog } from 'resource:///org/gnome/shell/ui/unlockDialog.js';
import { Pipeline } from '../conveniences/pipeline.js';
const original_createBackground =
UnlockDialog.prototype._createBackground;
const original_updateBackgroundEffects =
UnlockDialog.prototype._updateBackgroundEffects;
const original_updateBackgrounds =
UnlockDialog.prototype._updateBackgrounds;
export const LockscreenBlur = class LockscreenBlur {
constructor(connections, settings, effects_manager) {
this.connections = connections;
this.settings = settings;
this.effects_manager = effects_manager;
this.enabled = false;
}
enable() {
this._log("blurring lockscreen");
this.update_lockscreen();
this.enabled = true;
}
update_lockscreen() {
UnlockDialog.prototype._createBackground =
this._createBackground;
UnlockDialog.prototype._updateBackgroundEffects =
this._updateBackgroundEffects;
UnlockDialog.prototype._updateBackgrounds =
this._updateBackgrounds;
}
_createBackground(monitor_index) {
let pipeline = new Pipeline(
global.blur_my_shell._effects_manager, global.blur_my_shell._pipelines_manager,
global.blur_my_shell._settings.lockscreen.PIPELINE
);
pipeline.create_background_with_effects(
monitor_index,
this._bgManagers,
this._backgroundGroup,
"screen-shield-background"
);
}
_updateBackgroundEffects() {
this._updateBackgrounds();
}
_updateBackgrounds() {
for (let i = 0; i < this._bgManagers.length; i++) {
this._bgManagers[i]._bms_pipeline.destroy();
this._bgManagers[i].destroy();
}
this._bgManagers = [];
this._backgroundGroup.destroy_all_children();
for (let i = 0; i < Main.layoutManager.monitors.length; i++)
this._createBackground(i);
}
disable() {
this._log("removing blur from lockscreen");
UnlockDialog.prototype._createBackground =
original_createBackground;
UnlockDialog.prototype._updateBackgroundEffects =
original_updateBackgroundEffects;
UnlockDialog.prototype._updateBackgrounds =
original_updateBackgrounds;
this.connections.disconnect_all();
this.enabled = false;
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > lockscreen] ${str}`);
}
};

View File

@ -0,0 +1,209 @@
import Meta from 'gi://Meta';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { WorkspaceAnimationController } from 'resource:///org/gnome/shell/ui/workspaceAnimation.js';
const wac_proto = WorkspaceAnimationController.prototype;
import { Pipeline } from '../conveniences/pipeline.js';
const OVERVIEW_COMPONENTS_STYLE = [
"overview-components-light",
"overview-components-dark",
"overview-components-transparent"
];
export const OverviewBlur = class OverviewBlur {
constructor(connections, settings, effects_manager) {
this.connections = connections;
this.settings = settings;
this.effects_manager = effects_manager;
this.overview_background_managers = [];
this.overview_background_group = new Meta.BackgroundGroup(
{ name: 'bms-overview-backgroundgroup' }
);
this.animation_background_managers = [];
this.animation_background_group = new Meta.BackgroundGroup(
{ name: 'bms-animation-backgroundgroup' }
);
this.enabled = false;
}
enable() {
this._log("blurring overview");
// add css class name for workspace-switch background
Main.uiGroup.add_style_class_name("blurred-overview");
// add css class name to make components semi-transparent if wanted
this.update_components_classname();
// update backgrounds when the component is enabled
this.update_backgrounds();
// connect to monitors change
this.connections.connect(Main.layoutManager, 'monitors-changed',
_ => this.update_backgrounds()
);
// part for the workspace animation switch
// make sure not to do this part if the extension was enabled prior, as
// the functions would call themselves and cause infinite recursion
if (!this.enabled) {
// store original workspace switching methods for restoring them on
// disable()
this._original_PrepareSwitch = wac_proto._prepareWorkspaceSwitch;
this._original_FinishSwitch = wac_proto._finishWorkspaceSwitch;
const w_m = global.workspace_manager;
const outer_this = this;
// create a blurred background actor for each monitor during a
// workspace switch
wac_proto._prepareWorkspaceSwitch = function (...params) {
outer_this._log("prepare workspace switch");
outer_this._original_PrepareSwitch.apply(this, params);
// this permits to show the blur behind windows that are on
// workspaces on the left and right
if (
outer_this.settings.applications.BLUR
) {
let ws_index = w_m.get_active_workspace_index();
[ws_index - 1, ws_index + 1].forEach(
i => w_m.get_workspace_by_index(i)?.list_windows().forEach(
window => window.get_compositor_private().show()
)
);
}
Main.uiGroup.insert_child_above(
outer_this.animation_background_group,
global.window_group
);
outer_this.animation_background_managers.forEach(bg_manager => {
if (bg_manager._bms_pipeline.actor)
if (
Meta.prefs_get_workspaces_only_on_primary() &&
bg_manager._monitorIndex !== Main.layoutManager.primaryMonitor.index
)
bg_manager._bms_pipeline.actor.visible = false;
else
bg_manager._bms_pipeline.actor.visible = true;
});
};
// remove the workspace-switch actors when the switch is done
wac_proto._finishWorkspaceSwitch = function (...params) {
outer_this._log("finish workspace switch");
outer_this._original_FinishSwitch.apply(this, params);
// this hides windows that are not on the current workspace
if (
outer_this.settings.applications.BLUR
)
for (let i = 0; i < w_m.get_n_workspaces(); i++) {
if (i != w_m.get_active_workspace_index())
w_m.get_workspace_by_index(i)?.list_windows().forEach(
window => window.get_compositor_private().hide()
);
}
Main.uiGroup.remove_child(outer_this.animation_background_group);
};
}
this.enabled = true;
}
update_backgrounds() {
// remove every old background
this.remove_background_actors();
// create new backgrounds for the overview and the animation
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
const pipeline_overview = new Pipeline(
this.effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.overview.PIPELINE
);
pipeline_overview.create_background_with_effects(
i, this.overview_background_managers,
this.overview_background_group, 'bms-overview-blurred-widget'
);
const pipeline_animation = new Pipeline(
this.effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.overview.PIPELINE
);
pipeline_animation.create_background_with_effects(
i, this.animation_background_managers,
this.animation_background_group, 'bms-animation-blurred-widget'
);
}
// add the container widget for the overview only to the overview group
Main.layoutManager.overviewGroup.insert_child_at_index(this.overview_background_group, 0);
}
/// Updates the classname to style overview components with semi-transparent
/// backgrounds.
update_components_classname() {
OVERVIEW_COMPONENTS_STYLE.forEach(
style => Main.uiGroup.remove_style_class_name(style)
);
if (this.settings.overview.STYLE_COMPONENTS > 0)
Main.uiGroup.add_style_class_name(
OVERVIEW_COMPONENTS_STYLE[this.settings.overview.STYLE_COMPONENTS - 1]
);
}
remove_background_actors() {
this.overview_background_group.remove_all_children();
this.animation_background_group.remove_all_children();
this.overview_background_managers.forEach(background_manager => {
background_manager._bms_pipeline.destroy();
background_manager.destroy();
});
this.animation_background_managers.forEach(background_manager => {
background_manager._bms_pipeline.destroy();
background_manager.destroy();
});
this.overview_background_managers = [];
this.animation_background_managers = [];
}
disable() {
this._log("removing blur from overview");
this.remove_background_actors();
Main.uiGroup.remove_style_class_name("blurred-overview");
OVERVIEW_COMPONENTS_STYLE.forEach(
style => Main.uiGroup.remove_style_class_name(style)
);
// make sure to absolutely not do this if the component was not enabled
// prior, as this would cause infinite recursion
if (this.enabled) {
// restore original behavior
if (this._original_PrepareSwitch)
wac_proto._prepareWorkspaceSwitch = this._original_PrepareSwitch;
if (this._original_FinishSwitch)
wac_proto._finishWorkspaceSwitch = this._original_FinishSwitch;
}
this.connections.disconnect_all();
this.enabled = false;
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > overview] ${str}`);
}
_warn(str) {
console.warn(`[Blur my Shell > overview] ${str}`);
}
};

View File

@ -0,0 +1,542 @@
import St from 'gi://St';
import Meta from 'gi://Meta';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { PaintSignals } from '../conveniences/paint_signals.js';
import { Pipeline } from '../conveniences/pipeline.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.js';
const DASH_TO_PANEL_UUID = 'dash-to-panel@jderose9.github.com';
const PANEL_STYLES = [
"transparent-panel",
"light-panel",
"dark-panel",
"contrasted-panel"
];
export const PanelBlur = class PanelBlur {
constructor(connections, settings, effects_manager) {
this.connections = connections;
this.window_signal_ids = new Map();
this.settings = settings;
this.effects_manager = effects_manager;
this.actors_list = [];
this.enabled = false;
}
enable() {
this._log("blurring top panel");
// check for panels when Dash to Panel is activated
this.connections.connect(
Main.extensionManager,
'extension-state-changed',
(_, extension) => {
if (extension.uuid === DASH_TO_PANEL_UUID
&& extension.state === 1
) {
this.connections.connect(
global.dashToPanel,
'panels-created',
_ => this.blur_dtp_panels()
);
this.blur_existing_panels();
}
}
);
this.blur_existing_panels();
// connect to overview being opened/closed, and dynamically show or not
// the blur when a window is near a panel
this.connect_to_windows_and_overview();
// update the classname if the panel to have or have not light text
this.update_light_text_classname();
// connect to monitors change
this.connections.connect(Main.layoutManager, 'monitors-changed',
_ => this.reset()
);
this.enabled = true;
}
reset() {
this._log("resetting...");
this.disable();
setTimeout(_ => this.enable(), 1);
}
/// Check for already existing panels and blur them if they are not already
blur_existing_panels() {
// check if dash-to-panel is present
if (global.dashToPanel) {
// blur already existing ones
if (global.dashToPanel.panels)
this.blur_dtp_panels();
} else {
// if no dash-to-panel, blur the main and only panel
this.maybe_blur_panel(Main.panel);
}
}
blur_dtp_panels() {
// FIXME when Dash to Panel changes its size, it seems it creates new
// panels; but I can't get to delete old widgets
// blur every panel found
global.dashToPanel.panels.forEach(p => {
this.maybe_blur_panel(p.panel);
});
// if main panel is not included in the previous panels, blur it
if (
!global.dashToPanel.panels
.map(p => p.panel)
.includes(Main.panel)
&&
this.settings.dash_to_panel.BLUR_ORIGINAL_PANEL
)
this.maybe_blur_panel(Main.panel);
};
/// Blur a panel only if it is not already blurred (contained in the list)
maybe_blur_panel(panel) {
// check if the panel is contained in the list
let actors = this.actors_list.find(
actors => actors.widgets.panel == panel
);
if (!actors)
// if the actors is not blurred, blur it
this.blur_panel(panel);
}
/// Blur a panel
blur_panel(panel) {
let panel_box = panel.get_parent();
let is_dtp_panel = false;
if (!panel_box.name) {
is_dtp_panel = true;
panel_box = panel_box.get_parent();
}
let monitor = Main.layoutManager.findMonitorForActor(panel);
if (!monitor)
return;
let background_group = new Meta.BackgroundGroup(
{ name: 'bms-panel-backgroundgroup', width: 0, height: 0 }
);
let background, bg_manager;
let static_blur = this.settings.panel.STATIC_BLUR;
if (static_blur) {
let bg_manager_list = [];
const pipeline = new Pipeline(
this.effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.panel.PIPELINE
);
background = pipeline.create_background_with_effects(
monitor.index, bg_manager_list,
background_group, 'bms-panel-blurred-widget'
);
bg_manager = bg_manager_list[0];
}
else {
const pipeline = new DummyPipeline(this.effects_manager, this.settings.panel);
[background, bg_manager] = pipeline.create_background_with_effect(
background_group, 'bms-panel-blurred-widget'
);
let paint_signals = new PaintSignals(this.connections);
// HACK
//
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
//
// This does not entirely fix this bug (shadows caused by windows
// still cause artifacts), but it prevents the shadows of the panel
// buttons to cause artifacts on the panel itself
//
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
{
if (this.settings.HACKS_LEVEL === 1) {
this._log("panel hack level 1");
paint_signals.disconnect_all();
paint_signals.connect(background, pipeline.effect);
} else {
paint_signals.disconnect_all();
}
}
}
// insert the background group to the panel box
panel_box.insert_child_at_index(background_group, 0);
// the object that is used to remembering each elements that is linked to the blur effect
let actors = {
widgets: { panel, panel_box, background, background_group },
static_blur,
monitor,
bg_manager,
is_dtp_panel
};
this.actors_list.push(actors);
// update the size of the actor
this.update_size(actors);
// connect to panel, panel_box and its parent position or size change
// this should fire update_size every time one of its params change
this.connections.connect(
panel,
'notify::position',
_ => this.update_size(actors)
);
this.connections.connect(
panel_box,
['notify::size', 'notify::position'],
_ => this.update_size(actors)
);
this.connections.connect(
panel_box.get_parent(),
'notify::position',
_ => this.update_size(actors)
);
// connect to the panel getting destroyed
this.connections.connect(
panel,
'destroy',
_ => this.destroy_blur(actors, true)
);
}
update_size(actors) {
let panel = actors.widgets.panel;
let panel_box = actors.widgets.panel_box;
let background = actors.widgets.background;
let monitor = Main.layoutManager.findMonitorForActor(panel);
if (!monitor)
return;
let [width, height] = panel_box.get_size();
// if static blur, need to clip the background
if (actors.static_blur) {
// an alternative to panel.get_transformed_position, because it
// sometimes yields NaN (probably when the actor is not fully
// positionned yet)
let [p_x, p_y] = panel_box.get_position();
let [p_p_x, p_p_y] = panel_box.get_parent().get_position();
let x = p_x + p_p_x - monitor.x;
let y = p_y + p_p_y - monitor.y;
background.set_clip(x, y, width, height);
background.x = -x;
background.y = -y;
} else {
background.x = panel.x;
background.y = panel.y;
background.width = width;
background.height = height;
}
// update the monitor panel is on
actors.monitor = Main.layoutManager.findMonitorForActor(panel);
}
/// Connect when overview if opened/closed to hide/show the blur accordingly
///
/// If HIDETOPBAR is set, we need just to hide the blur when showing appgrid
/// (so no shadow is cropped)
connect_to_overview() {
// may be called when panel blur is disabled, if hidetopbar
// compatibility is toggled on/off
// if this is the case, do nothing as only the panel blur interfers with
// hidetopbar
if (
this.settings.panel.BLUR &&
this.settings.panel.UNBLUR_IN_OVERVIEW
) {
if (!this.settings.hidetopbar.COMPATIBILITY) {
this.connections.connect(
Main.overview, 'showing', _ => this.hide()
);
this.connections.connect(
Main.overview, 'hidden', _ => this.show()
);
} else {
let appDisplay = Main.overview._overview._controls._appDisplay;
this.connections.connect(
appDisplay, 'show', _ => this.hide()
);
this.connections.connect(
appDisplay, 'hide', _ => this.show()
);
this.connections.connect(
Main.overview, 'hidden', _ => this.show()
);
}
}
}
/// Connect to windows disable transparency when a window is too close
connect_to_windows() {
if (
this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY
) {
// connect to overview opening/closing
this.connections.connect(Main.overview, ['showing', 'hiding'],
_ => this.update_visibility()
);
// connect to session mode update
this.connections.connect(Main.sessionMode, 'updated',
_ => this.update_visibility()
);
// manage already-existing windows
for (const meta_window_actor of global.get_window_actors()) {
this.on_window_actor_added(
meta_window_actor.get_parent(), meta_window_actor
);
}
// manage windows at their creation/removal
this.connections.connect(global.window_group, 'child-added',
this.on_window_actor_added.bind(this)
);
this.connections.connect(global.window_group, 'child-removed',
this.on_window_actor_removed.bind(this)
);
// connect to a workspace change
this.connections.connect(global.window_manager, 'switch-workspace',
_ => this.update_visibility()
);
// perform early update
this.update_visibility();
} else {
// reset transparency for every panels
this.actors_list.forEach(
actors => this.set_should_override_panel(actors, true)
);
}
}
/// An helper to connect to both the windows and overview signals.
/// This is the only function that should be directly called, to prevent
/// inconsistencies with signals not being disconnected.
connect_to_windows_and_overview() {
this.disconnect_from_windows_and_overview();
this.connect_to_overview();
this.connect_to_windows();
}
/// Disconnect all the connections created by connect_to_windows
disconnect_from_windows_and_overview() {
// disconnect the connections to actors
for (const actor of [
Main.overview, Main.sessionMode,
global.window_group, global.window_manager,
Main.overview._overview._controls._appDisplay
]) {
this.connections.disconnect_all_for(actor);
}
// disconnect the connections from windows
for (const [actor, ids] of this.window_signal_ids) {
for (const id of ids) {
actor.disconnect(id);
}
}
this.window_signal_ids = new Map();
}
/// Update the css classname of the panel for light theme
update_light_text_classname(disable = false) {
if (this.settings.panel.FORCE_LIGHT_TEXT && !disable)
Main.panel.add_style_class_name("panel-light-text");
else
Main.panel.remove_style_class_name("panel-light-text");
}
/// Callback when a new window is added
on_window_actor_added(container, meta_window_actor) {
this.window_signal_ids.set(meta_window_actor, [
meta_window_actor.connect('notify::allocation',
_ => this.update_visibility()
),
meta_window_actor.connect('notify::visible',
_ => this.update_visibility()
)
]);
this.update_visibility();
}
/// Callback when a window is removed
on_window_actor_removed(container, meta_window_actor) {
for (const signalId of this.window_signal_ids.get(meta_window_actor)) {
meta_window_actor.disconnect(signalId);
}
this.window_signal_ids.delete(meta_window_actor);
this.update_visibility();
}
/// Update the visibility of the blur effect
update_visibility() {
if (
Main.panel.has_style_pseudo_class('overview')
|| !Main.sessionMode.hasWindows
) {
this.actors_list.forEach(
actors => this.set_should_override_panel(actors, true)
);
return;
}
if (!Main.layoutManager.primaryMonitor)
return;
// get all the windows in the active workspace that are visible
const workspace = global.workspace_manager.get_active_workspace();
const windows = workspace.list_windows().filter(meta_window =>
meta_window.showing_on_its_workspace()
&& !meta_window.is_hidden()
&& meta_window.get_window_type() !== Meta.WindowType.DESKTOP
// exclude Desktop Icons NG
&& meta_window.get_gtk_application_id() !== "com.rastersoft.ding"
&& meta_window.get_gtk_application_id() !== "com.desktop.ding"
);
// check if at least one window is near enough to each panel and act
// accordingly
const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor;
this.actors_list
// do not apply for dtp panels, as it would only cause bugs and it
// can be done from its preferences anyway
.filter(actors => !actors.is_dtp_panel)
.forEach(actors => {
let panel = actors.widgets.panel;
let panel_top = panel.get_transformed_position()[1];
let panel_bottom = panel_top + panel.get_height();
// check if at least a window is near enough the panel
let window_overlap_panel = false;
windows.forEach(meta_window => {
let window_monitor_i = meta_window.get_monitor();
let same_monitor = actors.monitor.index == window_monitor_i;
let window_vertical_pos = meta_window.get_frame_rect().y;
// if so, and if in the same monitor, then it overlaps
if (same_monitor
&&
window_vertical_pos < panel_bottom + 5 * scale
)
window_overlap_panel = true;
});
// if no window overlaps, then the panel is transparent
this.set_should_override_panel(
actors, !window_overlap_panel
);
});
}
/// Choose wether or not the panel background should be overriden, in
/// respect to its argument and the `override-background` setting.
set_should_override_panel(actors, should_override) {
let panel = actors.widgets.panel;
PANEL_STYLES.forEach(style => panel.remove_style_class_name(style));
if (
this.settings.panel.OVERRIDE_BACKGROUND
&&
should_override
)
panel.add_style_class_name(
PANEL_STYLES[this.settings.panel.STYLE_PANEL]
);
}
update_pipeline() {
this.actors_list.forEach(actors =>
actors.bg_manager._bms_pipeline.change_pipeline_to(
this.settings.panel.PIPELINE
)
);
}
show() {
this.actors_list.forEach(actors => {
actors.widgets.background.show();
});
}
hide() {
this.actors_list.forEach(actors => {
actors.widgets.background.hide();
});
}
// IMPORTANT: do never call this in a mutable `this.actors_list.forEach`
destroy_blur(actors, panel_already_destroyed) {
this.set_should_override_panel(actors, false);
actors.bg_manager._bms_pipeline.destroy();
if (panel_already_destroyed)
actors.bg_manager.backgroundActor = null;
actors.bg_manager.destroy();
if (!panel_already_destroyed) {
actors.widgets.panel_box.remove_child(actors.widgets.background_group);
actors.widgets.background_group.destroy_all_children();
actors.widgets.background_group.destroy();
}
let index = this.actors_list.indexOf(actors);
if (index >= 0)
this.actors_list.splice(index, 1);
}
disable() {
this._log("removing blur from top panel");
this.disconnect_from_windows_and_overview();
this.update_light_text_classname(true);
const immutable_actors_list = [...this.actors_list];
immutable_actors_list.forEach(actors => this.destroy_blur(actors, false));
this.actors_list = [];
this.connections.disconnect_all();
this.enabled = false;
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > panel] ${str}`);
}
_warn(str) {
console.warn(`[Blur my Shell > panel] ${str}`);
}
};

View File

@ -0,0 +1,110 @@
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { Pipeline } from '../conveniences/pipeline.js';
export const ScreenshotBlur = class ScreenshotBlur {
constructor(connections, settings, effects_manager) {
this.connections = connections;
this.settings = settings;
this.screenshot_background_managers = [];
this.effects_manager = effects_manager;
}
enable() {
this._log("blurring screenshot's window selector");
// connect to monitors change
this.connections.connect(Main.layoutManager, 'monitors-changed',
_ => this.update_backgrounds()
);
// update backgrounds when the component is enabled
this.update_backgrounds();
}
update_backgrounds() {
// remove every old background
this.remove_background_actors();
// create new backgrounds for the screenshot window selector
for (let i = 0; i < Main.screenshotUI._windowSelectors.length; i++) {
const window_selector = Main.screenshotUI._windowSelectors[i];
const pipeline = new Pipeline(
this.effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.screenshot.PIPELINE
);
pipeline.create_background_with_effects(
window_selector._monitorIndex, this.screenshot_background_managers,
window_selector, 'bms-screenshot-blurred-widget'
);
// prevent old `BackgroundActor` from being accessed, which creates a whole bug of logs
this.connections.connect(window_selector.get_parent(), 'destroy', _ => {
this.screenshot_background_managers.forEach(background_manager => {
if (background_manager.backgroundActor) {
let widget = background_manager.backgroundActor.get_parent();
let parent = widget?.get_parent();
if (parent == window_selector) {
background_manager._bms_pipeline.destroy();
parent.remove_child(widget);
}
}
background_manager.destroy();
});
window_selector.get_children().forEach(child => {
if (child.get_name() == 'bms-screenshot-blurred-widget')
window_selector.remove_child(child);
});
let index = this.screenshot_background_managers.indexOf(window_selector);
this.screenshot_background_managers.splice(index, 1);
});
}
}
update_pipeline() {
this.screenshot_background_managers.forEach(background_manager =>
background_manager._bms_pipeline.change_pipeline_to(
this.settings.screenshot.PIPELINE
)
);
}
remove_background_actors() {
this.screenshot_background_managers.forEach(background_manager => {
background_manager._bms_pipeline.destroy();
if (background_manager.backgroundActor) {
let widget = background_manager.backgroundActor.get_parent();
widget?.get_parent()?.remove_child(widget);
}
background_manager.destroy();
});
Main.screenshotUI._windowSelectors.forEach(window_selector =>
window_selector.get_children().forEach(child => {
if (child.get_name() == 'bms-screenshot-blurred-widget')
window_selector.remove_child(child);
})
);
this.screenshot_background_managers = [];
}
disable() {
this._log("removing blur from screenshot's window selector");
this.remove_background_actors();
this.connections.disconnect_all();
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > screenshot] ${str}`);
}
_warn(str) {
console.warn(`[Blur my Shell > screenshot] ${str}`);
}
};

View File

@ -0,0 +1,148 @@
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { PaintSignals } from '../conveniences/paint_signals.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.js';
export const WindowListBlur = class WindowListBlur {
constructor(connections, settings, effects_manager) {
this.connections = connections;
this.settings = settings;
this.paint_signals = new PaintSignals(connections);
this.effects_manager = effects_manager;
this.pipelines = [];
}
enable() {
this._log("blurring window list");
// blur if window-list is found
Main.layoutManager.uiGroup.get_children().forEach(
child => this.try_blur(child)
);
// listen to new actors in `Main.layoutManager.uiGroup` and blur it if
// if is window-list
this.connections.connect(
Main.layoutManager.uiGroup,
'child-added',
(_, child) => this.try_blur(child)
);
// connect to overview
this.connections.connect(Main.overview, 'showing', _ => {
this.hide();
});
this.connections.connect(Main.overview, 'hidden', _ => {
this.show();
});
}
try_blur(actor) {
if (
actor.constructor.name === "WindowList" &&
actor.style !== "background:transparent;"
) {
this._log("found window list to blur");
const pipeline = new DummyPipeline(
this.effects_manager, this.settings.window_list
);
pipeline.attach_effect_to_actor(actor);
this.pipelines.push(pipeline);
actor.set_style("background:transparent;");
actor._windowList.get_children().forEach(
window => this.style_window_button(window)
);
this.connections.connect(
actor._windowList,
'child-added',
(_, window) => this.style_window_button(window)
);
this.connections.connect(
actor,
'destroy',
_ => this.destroy_blur(pipeline, true)
);
// HACK
//
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
//
// This does not entirely fix this bug (shadows caused by windows
// still cause artifacts), but it prevents the shadows of the panel
// buttons to cause artifacts on the panel itself
//
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
if (this.settings.HACKS_LEVEL === 1) {
this._log("window list hack level 1");
this.paint_signals.disconnect_all_for_actor(actor);
this.paint_signals.connect(actor, pipeline.effect);
} else {
this.paint_signals.disconnect_all_for_actor(actor);
}
}
}
style_window_button(window) {
window.get_child_at_index(0).set_style(
"box-shadow:none; background-color:rgba(0,0,0,0.2); border-radius:5px;"
);
}
// IMPORTANT: do never call this in a mutable `this.pipelines.forEach`
destroy_blur(pipeline, actor_destroyed = false) {
if (!actor_destroyed) {
this.remove_style(pipeline.actor);
this.paint_signals.disconnect_all_for_actor(pipeline.actor);
}
pipeline.destroy();
let index = this.pipelines.indexOf(pipeline);
if (index >= 0)
this.pipelines.splice(pipeline, 1);
}
remove_style(actor) {
if (
actor.constructor.name === "WindowList" &&
actor.style === "background:transparent;"
) {
actor.style = null;
actor._windowList.get_children().forEach(
child => child.get_child_at_index(0).set_style(null)
);
}
}
hide() {
this.pipelines.forEach(pipeline => pipeline.effect?.set_enabled(false));
}
show() {
this.pipelines.forEach(pipeline => pipeline.effect?.set_enabled(true));
}
disable() {
this._log("removing blur from window list");
const immutable_pipelines_list = [...this.pipelines];
immutable_pipelines_list.forEach(pipeline => this.destroy_blur(pipeline));
this.pipelines = [];
this.connections.disconnect_all();
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > window list] ${str}`);
}
};

View File

@ -0,0 +1,106 @@
import GObject from 'gi://GObject';
/// An object to easily manage signals.
export const Connections = class Connections {
constructor() {
this.buffer = [];
}
/// Adds a connection.
///
/// Takes as arguments:
/// - an actor, which fires the signal
/// - signal(s) (string or array of strings), which are watched for
/// - a callback, which is called when the signal is fired
connect(actor, signals, handler) {
if (signals instanceof Array) {
signals.forEach(signal => {
let id = actor.connect(signal, handler);
this.process_connection(actor, id);
});
} else {
let id = actor.connect(signals, handler);
this.process_connection(actor, id);
}
}
/// Process the given actor and id.
///
/// This makes sure that the signal is disconnected when the actor is
/// destroyed, and that the signal can be managed through other Connections
/// methods.
process_connection(actor, id) {
let infos = {
actor: actor,
id: id
};
// remove the signal when the actor is destroyed
if (
actor.connect &&
(
!(actor instanceof GObject.Object) ||
GObject.signal_lookup('destroy', actor)
)
) {
let destroy_id = actor.connect('destroy', () => {
actor.disconnect(id);
actor.disconnect(destroy_id);
let index = this.buffer.indexOf(infos);
if (index >= 0) {
this.buffer.splice(index, 1);
}
});
infos.destroy_id = destroy_id;
}
this.buffer.push(infos);
}
/// Disconnects every connection found for an actor.
disconnect_all_for(actor) {
// get every connection stored for the actor
let actor_connections = this.buffer.filter(
infos => infos.actor === actor
);
// remove each of them
actor_connections.forEach(connection => {
// disconnect
try {
connection.actor.disconnect(connection.id);
if ('destroy_id' in connection)
connection.actor.disconnect(connection.destroy_id);
} catch (e) {
this._warn(`error removing connection: ${e}; continuing`);
}
// remove from buffer
let index = this.buffer.indexOf(connection);
this.buffer.splice(index, 1);
});
}
/// Disconnect every connection for each actor.
disconnect_all() {
this.buffer.forEach(connection => {
// disconnect
try {
connection.actor.disconnect(connection.id);
if ('destroy_id' in connection)
connection.actor.disconnect(connection.destroy_id);
} catch (e) {
this._warn(`error removing connection: ${e}; continuing`);
}
});
// reset buffer
this.buffer = [];
}
_warn(str) {
console.warn(`[Blur my Shell > connections] ${str}`);
}
};

View File

@ -0,0 +1,96 @@
import St from 'gi://St';
import Clutter from 'gi://Clutter';
/// A dummy `Pipeline`, for dynamic blur only.
/// Instead of a pipeline id, we take the settings of the component we want to blur.
export const DummyPipeline = class DummyPipeline {
constructor(effects_manager, settings, actor = null) {
this.effects_manager = effects_manager;
this.settings = settings;
this.effect = null;
this.attach_effect_to_actor(actor);
}
create_background_with_effect(
background_group,
widget_name
) {
// create the new actor
this.actor = new St.Widget({ name: widget_name });
this.attach_effect_to_actor(this.actor);
// a dummy `BackgroundManager`, just to access the pipeline easily
let bg_manager = new Clutter.Actor;
bg_manager.backgroundActor = this.actor;
bg_manager._bms_pipeline = this;
background_group.insert_child_at_index(this.actor, 0);
return [this.actor, bg_manager];
};
attach_effect_to_actor(actor) {
// set the actor
this.actor = actor;
if (!actor)
return;
// build the new effect to be added
this.build_effect({
unscaled_radius: 2 * this.settings.SIGMA,
brightness: this.settings.BRIGHTNESS,
});
// add the effect to the actor
if (this.actor)
this.actor.add_effect(this.effect);
else
this._warn(`could not add effect to actor, actor does not exist anymore`);
}
build_effect(params) {
// create the effect
this.effect = this.effects_manager.new_native_dynamic_gaussian_blur_effect(params);
// connect to settings changes, using the true gsettings object
this._sigma_changed_id = this.settings.settings.connect(
'changed::sigma', () => this.effect.unscaled_radius = 2 * this.settings.SIGMA
);
this._brightness_changed_id = this.settings.settings.connect(
'changed::brightness', () => this.effect.brightness = this.settings.BRIGHTNESS
);
}
repaint_effect() {
this.effect?.queue_repaint();
}
/// Remove every effect from the actor it is attached to. Please note that they are not
/// destroyed, but rather stored (thanks to the `EffectManager` class) to be reused later.
remove_effect() {
this.effects_manager.remove(this.effect);
this.effect = null;
if (this._sigma_changed_id)
this.settings.settings.disconnect(this._sigma_changed_id);
if (this._brightness_changed_id)
this.settings.settings.disconnect(this._brightness_changed_id);
delete this._sigma_changed_id;
delete this._brightness_changed_id;
}
/// Do nothing for this dummy pipeline.
/// Note: exposed to public API.
change_pipeline_to() { return; }
/// Note: exposed to public API.
destroy() {
this.remove_effect();
this.actor = null;
}
_warn(str) {
console.warn(`[Blur my Shell > dummy pip] ${str}`);
}
};

View File

@ -0,0 +1,90 @@
import { get_supported_effects } from '../effects/effects.js';
/// An object to manage effects (by not destroying them all the time)
export const EffectsManager = class EffectsManager {
constructor(connections) {
this.connections = connections;
this.used = [];
this.SUPPORTED_EFFECTS = get_supported_effects();
Object.keys(this.SUPPORTED_EFFECTS).forEach(effect_name => {
// init the arrays containing each unused effect
this[effect_name + '_effects'] = [];
// init the functions for each effect
this['new_' + effect_name + '_effect'] = function (params) {
let effect;
if (this[effect_name + '_effects'].length > 0) {
effect = this[effect_name + '_effects'].splice(0, 1)[0];
effect.set({
...this.SUPPORTED_EFFECTS[effect_name].class.default_params, ...params
});
} else
effect = new this.SUPPORTED_EFFECTS[effect_name].class({
...this.SUPPORTED_EFFECTS[effect_name].class.default_params, ...params
});
this.used.push(effect);
this.connect_to_destroy(effect);
return effect;
};
});
}
connect_to_destroy(effect) {
effect.old_actor = effect.get_actor();
if (effect.old_actor)
effect.old_actor_id = effect.old_actor.connect('destroy', _ => {
this.remove(effect, true);
});
this.connections.connect(effect, 'notify::actor', _ => {
let actor = effect.get_actor();
if (effect.old_actor && actor != effect.old_actor)
effect.old_actor.disconnect(effect.old_actor_id);
if (actor && actor != effect.old_actor) {
effect.old_actor_id = actor.connect('destroy', _ => {
this.remove(effect, true);
});
}
});
}
// IMPORTANT: do never call this in a mutable `this.used.forEach`
remove(effect, actor_already_destroyed = false) {
if (!actor_already_destroyed)
try {
effect.get_actor()?.remove_effect(effect);
} catch (e) {
this._warn(`could not remove the effect, continuing: ${e}`);
}
if (effect.old_actor)
effect.old_actor.disconnect(effect.old_actor_id);
delete effect.old_actor;
delete effect.old_actor_id;
let index = this.used.indexOf(effect);
if (index >= 0) {
this.used.splice(index, 1);
Object.keys(this.SUPPORTED_EFFECTS).forEach(effect_name => {
if (effect instanceof this.SUPPORTED_EFFECTS[effect_name].class)
this[effect_name + '_effects'].push(effect);
});
}
}
destroy_all() {
const immutable_used_list = [...this.used];
immutable_used_list.forEach(effect => this.remove(effect));
Object.keys(this.SUPPORTED_EFFECTS).forEach(effect_name => {
this[effect_name + '_effects'].splice(0, this[effect_name + '_effects'].length);
});
}
_warn(str) {
console.warn(`[Blur my Shell > effects mng] ${str}`);
}
};

View File

@ -0,0 +1,182 @@
import { Type } from './settings.js';
// This lists the preferences keys
export const KEYS = [
{
component: "general", schemas: [
{ type: Type.PIPELINES, name: "pipelines" },
{ type: Type.I, name: "hacks-level" },
{ type: Type.B, name: "debug" },
]
},
{
component: "overview", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.S, name: "pipeline" },
{ type: Type.I, name: "style-components" },
]
},
{
component: "appfolder", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.I, name: "style-dialogs" },
]
},
{
component: "panel", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.B, name: "static-blur" },
{ type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.B, name: "unblur-in-overview" },
{ type: Type.B, name: "force-light-text" },
{ type: Type.B, name: "override-background" },
{ type: Type.I, name: "style-panel" },
{ type: Type.B, name: "override-background-dynamically" },
]
},
{
component: "dash-to-dock", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.B, name: "static-blur" },
{ type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.B, name: "unblur-in-overview" },
{ type: Type.B, name: "override-background" },
{ type: Type.I, name: "style-dash-to-dock" },
]
},
{
component: "applications", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.I, name: "opacity" },
{ type: Type.B, name: "dynamic-opacity" },
{ type: Type.B, name: "blur-on-overview" },
{ type: Type.B, name: "enable-all" },
{ type: Type.AS, name: "whitelist" },
{ type: Type.AS, name: "blacklist" },
]
},
{
component: "lockscreen", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.S, name: "pipeline" },
]
},
{
component: "window-list", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
]
},
{
component: "screenshot", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.S, name: "pipeline" },
]
},
{
component: "hidetopbar", schemas: [
{ type: Type.B, name: "compatibility" },
]
},
{
component: "dash-to-panel", schemas: [
{ type: Type.B, name: "blur-original-panel" },
]
},
];
// This lists the deprecated preferences keys
export const DEPRECATED_KEYS = [
{
component: "general", schemas: [
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.B, name: "color-and-noise" },
]
},
{
component: "overview", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "appfolder", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "panel", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "dash-to-dock", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.I, name: "corner-radius" },
]
},
{
component: "applications", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "lockscreen", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "window-list", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "screenshot", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
];

View File

@ -0,0 +1,91 @@
import GObject from 'gi://GObject';
import Clutter from 'gi://Clutter';
export const PaintSignals = class PaintSignals {
constructor(connections) {
this.buffer = [];
this.connections = connections;
}
connect(actor, blur_effect) {
let paint_effect = new EmitPaintSignal();
let infos = {
actor: actor,
paint_effect: paint_effect
};
let counter = 0;
actor.add_effect(paint_effect);
this.connections.connect(paint_effect, 'update-blur', () => {
try {
// checking if blur_effect.queue_repaint() has been recently called
if (counter === 0) {
counter = 2;
blur_effect.queue_repaint();
}
else counter--;
} catch (e) { }
});
// remove the actor from buffer when it is destroyed
if (
actor.connect &&
(
!(actor instanceof GObject.Object) ||
GObject.signal_lookup('destroy', actor)
)
)
this.connections.connect(actor, 'destroy', () => {
const immutable_buffer = [...this.buffer];
immutable_buffer.forEach(infos => {
if (infos.actor === actor) {
// remove from buffer
let index = this.buffer.indexOf(infos);
this.buffer.splice(index, 1);
}
});
});
this.buffer.push(infos);
}
disconnect_all_for_actor(actor) {
const immutable_buffer = [...this.buffer];
immutable_buffer.forEach(infos => {
if (infos.actor === actor) {
this.connections.disconnect_all_for(infos.paint_effect);
infos.actor.remove_effect(infos.paint_effect);
// remove from buffer
let index = this.buffer.indexOf(infos);
this.buffer.splice(index, 1);
}
});
}
disconnect_all() {
this.buffer.forEach(infos => {
this.connections.disconnect_all_for(infos.paint_effect);
infos.actor.remove_effect(infos.paint_effect);
});
this.buffer = [];
}
};
export const EmitPaintSignal = GObject.registerClass({
GTypeName: 'EmitPaintSignal',
Signals: {
'update-blur': {
param_types: []
},
}
},
class EmitPaintSignal extends Clutter.Effect {
vfunc_paint(node, paint_context, paint_flags) {
this.emit("update-blur");
super.vfunc_paint(node, paint_context, paint_flags);
}
}
);

View File

@ -0,0 +1,204 @@
import St from 'gi://St';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Background from 'resource:///org/gnome/shell/ui/background.js';
/// A `Pipeline` object is a handy way to manage the effects attached to an actor. It only manages
/// one actor at a time (so blurring multiple widgets will need multiple `Pipeline`), and is
/// linked to a `pipeline_id` that has been (hopefully) defined in the settings.
///
/// It communicates with the settings through the `PipelinesManager` object, and receives different
/// signals (with `pipeline_id` being an unique string):
/// - `'pipeline_id'::pipeline-updated`, handing a new pipeline descriptor object, when the pipeline
/// has been changed enough that it needs to rebuild the effects configuration
/// - `'pipeline_id'::pipeline-destroyed`, when the pipeline has been destroyed; thus making the
/// `Pipeline` change its id to `pipeline_default`
///
/// And each effect, with an unique `id`, is connected to the `PipelinesManager` for the signals:
/// - `'pipeline_id'::effect-'id'-key-removed`, handing the key that was removed
/// - `'pipeline_id'::effect-'id'-key-updated`, handing the key that was changed and its new value
/// - `'pipeline_id'::effect-'id'-key-added`, handing the key that was added and its value
export const Pipeline = class Pipeline {
constructor(effects_manager, pipelines_manager, pipeline_id, actor = null) {
this.effects_manager = effects_manager;
this.pipelines_manager = pipelines_manager;
this.effects = [];
this.set_pipeline_id(pipeline_id);
this.attach_pipeline_to_actor(actor);
}
/// Create a background linked to the monitor with index `monitor_index`, with a
/// `BackgroundManager` that is appended to the list `background_managers`. The background actor
/// will be given the name `widget_name` and inserted into the given `background_group`.
/// Note: exposed to public API.
create_background_with_effects(
monitor_index,
background_managers,
background_group,
widget_name
) {
let monitor = Main.layoutManager.monitors[monitor_index];
// create the new actor
this.actor = new St.Widget({
name: widget_name,
x: monitor.x,
y: monitor.y,
width: monitor.width,
height: monitor.height
});
// remove the effects, wether or not we attach the pipeline to the actor: if they are fired
// while the actor has changed, this could go bad
this.remove_all_effects();
if (this.pipeline_id)
this.attach_pipeline_to_actor(this.actor);
let bg_manager = new Background.BackgroundManager({
container: this.actor,
monitorIndex: monitor_index,
controlPosition: false,
});
bg_manager._bms_pipeline = this;
background_managers.push(bg_manager);
background_group.insert_child_at_index(this.actor, 0);
return this.actor;
};
/// Set the pipeline id, correctly connecting the `Pipeline` object to listen the pipelines
/// manager for pipeline-wide changes. This does not update the effects in consequence, call
/// `change_pipeline_to` instead if you want to reconstruct the effects too.
set_pipeline_id(pipeline_id) {
// disconnect ancient signals
this.remove_connections();
// change the id
this.pipeline_id = pipeline_id;
// connect to settings changes
this._pipeline_changed_id = this.pipelines_manager.connect(
this.pipeline_id + '::pipeline-updated',
(_, new_pipeline) => this.update_effects_from_pipeline(new_pipeline)
);
this._pipeline_destroyed_id = this.pipelines_manager.connect(
this.pipeline_id + '::pipeline-destroyed',
_ => this.change_pipeline_to("pipeline_default")
);
}
/// Disconnect the signals for the pipeline changes. Please note that the signals related to the
/// effects are stored with them and removed with `remove_all_effects`.
remove_connections() {
if (this._pipeline_changed_id)
this.pipelines_manager.disconnect(this._pipeline_changed_id);
if (this._pipeline_destroyed_id)
this.pipelines_manager.disconnect(this._pipeline_destroyed_id);
this._pipeline_changed_id = null;
this._pipeline_destroyed_id = null;
}
/// Attach a Pipeline object with `pipeline_id` already set to an actor.
attach_pipeline_to_actor(actor) {
// set the actor
this.actor = actor;
if (!actor)
return;
// attach the pipeline
let pipeline = this.pipelines_manager.pipelines[this.pipeline_id];
if (!pipeline) {
this._warn(`could not attach pipeline to actor, pipeline "${this.pipeline_id}" not found`);
// do not recurse...
if ("pipeline_default" in this.pipelines_manager.pipelines) {
this.set_pipeline_id("pipeline_default");
pipeline = this.pipelines_manager.pipelines["pipeline_default"];
} else
return;
}
// update the effects
this.update_effects_from_pipeline(pipeline);
}
/// Update the effects from the given pipeline object, the hard way.
update_effects_from_pipeline(pipeline) {
// remove all effects
this.remove_all_effects();
// build the new effects to be added
pipeline.effects.forEach(effect => {
if ('new_' + effect.type + '_effect' in this.effects_manager)
this.build_effect(effect);
else
this._warn(`could not add effect to actor, effect "${effect.type}" not found`);
});
this.effects.reverse();
// add the effects to the actor
if (this.actor)
this.effects.forEach(effect => this.actor.add_effect(effect));
else
this._warn(`could not add effect to actor, actor does not exist anymore`);
}
/// Given an `effect_infos` object containing the effect type, id and params, build an effect
/// and append it to the effects list
build_effect(effect_infos) {
let effect = this.effects_manager['new_' + effect_infos.type + '_effect'](effect_infos.params);
this.effects.push(effect);
// connect to settings changes
effect._effect_key_removed_id = this.pipelines_manager.connect(
this.pipeline_id + '::effect-' + effect_infos.id + '-key-removed',
(_, key) => effect[key] = effect.constructor.default_params[key]
);
effect._effect_key_updated_id = this.pipelines_manager.connect(
this.pipeline_id + '::effect-' + effect_infos.id + '-key-updated',
(_, key, value) => effect[key] = value
);
effect._effect_key_added_id = this.pipelines_manager.connect(
this.pipeline_id + '::effect-' + effect_infos.id + '-key-added',
(_, key, value) => effect[key] = value
);
}
/// Remove every effect from the actor it is attached to. Please note that they are not
/// destroyed, but rather stored (thanks to the `EffectManager` class) to be reused later.
remove_all_effects() {
this.effects.forEach(effect => {
this.effects_manager.remove(effect);
[
effect._effect_key_removed_id,
effect._effect_key_updated_id,
effect._effect_key_added_id
].forEach(
id => { if (id) this.pipelines_manager.disconnect(id); }
);
delete effect._effect_key_removed_id;
delete effect._effect_key_updated_id;
delete effect._effect_key_added_id;
});
this.effects = [];
}
/// Change the pipeline id, and update the effects according to this change.
/// Note: exposed to public API.
change_pipeline_to(pipeline_id) {
this.set_pipeline_id(pipeline_id);
this.attach_pipeline_to_actor(this.actor);
}
/// Resets the `Pipeline` object to a sane state, removing every effect and signal.
/// Note: exposed to public API.
destroy() {
this.remove_all_effects();
this.remove_connections();
this.actor = null;
this.pipeline_id = null;
}
_warn(str) {
console.warn(`[Blur my Shell > pipeline] ${str}`);
}
};

View File

@ -0,0 +1,168 @@
const Signals = imports.signals;
/// The `PipelinesManager` object permits to store the list of pipelines and their effects in
/// memory. It is meant to *always* be in sync with the `org.gnome.shell.extensions.blur-my-shell`'s
/// `pipelines` gschema. However, we do not want to re-create every effect each time this schema is
/// changed, so the pipelines manager handles it, and dispatches the updates with targeted signals.
///
/// It is only connected to ONE signal (the pipelines schema being changed), and emits numerous
/// which are connected to by both the different `Pipeline` objects in the extension, and by the
/// different pages of the extension preferences.
/// It emits three different types of signals:
///
/// - general changes to the pipelines list, connected to by the extension preferences:
/// - `pipeline-list-changed`, when the list of pipelines has changed (by creation or deletion)
/// - `pipeline-names-changed`, when the name of a pipeline is changed
///
/// - signals that are targeted towards a given pipeline, with `pipeline_id` being its unique id:
/// - `'pipeline_id'::pipeline-updated`, handing a new pipeline descriptor object, when the
/// pipeline has been changed quite a bit (added/destroyed/reordered the effects)
/// - `'pipeline_id'::pipeline-destroyed`, when the pipeline has been destroyed
/// - `'pipeline_id'::pipeline-renamed`, handing the new name, when the pipeline has been
/// renamed, which is only important for the preferences
///
/// - signals that are targeted towards a given effect, with `effect_id` being its unique id, and
/// `pipeline_id` the unique id of the pipeline it is attached to:
/// - `'pipeline_id'::effect-'effect_id'-key-removed`, handing the key that was removed
/// - `'pipeline_id'::effect-'effect_id'-key-updated`, handing the key that was changed and its
/// new value
/// - `'pipeline_id'::effect-'effect_id'-key-added`, handing the key that was added and its
/// value
export class PipelinesManager {
constructor(settings) {
this.settings = settings;
this.pipelines = this.settings.PIPELINES;
this.settings.PIPELINES_changed(_ => this.on_pipeline_update());
}
create_pipeline(name, effects = []) {
// select a random id for the pipeline
let id = "pipeline_" + ("" + Math.random()).slice(2, 16);
// add a random ID for each effect, to help tracking them
effects.forEach(effect => effect.id = "effect_" + ("" + Math.random()).slice(2, 16));
this.pipelines[id] = { name, effects };
this.settings.PIPELINES = this.pipelines;
this._emit('pipeline-created', id, this.pipelines[id]);
this._emit('pipeline-list-changed');
return id;
}
duplicate_pipeline(id) {
if (!(id in this.pipelines)) {
this._warn(`could not duplicate pipeline, id ${id} does not exist`);
return;
}
const pipeline = this.pipelines[id];
this.create_pipeline(pipeline.name + " - duplicate", [...pipeline.effects]);
this.settings.PIPELINES = this.pipelines;
}
delete_pipeline(id) {
if (!(id in this.pipelines)) {
this._warn(`could not delete pipeline, id ${id} does not exist`);
return;
}
if (id == "pipeline_default") {
this._warn(`could not delete pipeline "pipeline_default" as it is immutable`);
return;
}
delete this.pipelines[id];
this.settings.PIPELINES = this.pipelines;
this._emit(id + '::pipeline-destroyed');
this._emit('pipeline-list-changed');
}
update_pipeline_effects(id, effects, emit_update_signal = true) {
if (!(id in this.pipelines)) {
this._warn(`could not update pipeline effects, id ${id} does not exist`);
return;
}
this.pipelines[id].effects = [...effects];
this.settings.PIPELINES = this.pipelines;
if (emit_update_signal)
this._emit(id + '::pipeline-updated');
}
rename_pipeline(id, name) {
if (!(id in this.pipelines)) {
this._warn(`could not rename pipeline, id ${id} does not exist`);
return;
}
this.pipelines[id].name = name.slice();
this.settings.PIPELINES = this.pipelines;
this._emit(id + '::pipeline-renamed', name);
this._emit('pipeline-names-changed');
}
on_pipeline_update() {
const old_pipelines = this.pipelines;
this.pipelines = this.settings.PIPELINES;
for (var pipeline_id in old_pipelines) {
// if we find a pipeline that does not exist anymore, signal it
if (!(pipeline_id in this.pipelines)) {
this._emit(pipeline_id + '::pipeline-destroyed');
continue;
}
const old_pipeline = old_pipelines[pipeline_id];
const new_pipeline = this.pipelines[pipeline_id];
// verify if both pipelines have effects in the same order
// if they have, then check for their parameters
if (
old_pipeline.effects.length == new_pipeline.effects.length &&
old_pipeline.effects.every((effect, i) => effect.id === new_pipeline.effects[i].id)
) {
for (let i = 0; i < old_pipeline.effects.length; i++) {
const old_effect = old_pipeline.effects[i];
const new_effect = new_pipeline.effects[i];
const id = old_effect.id;
for (let key in old_effect.params) {
// if a key was removed, we emit to tell the effect to use the default value
if (!(key in new_effect.params))
this._emit(
pipeline_id + '::effect-' + id + '-key-removed', key
);
// if a key was updated, we emit to tell the effect to change its value
else if (old_effect.params[key] != new_effect.params[key])
this._emit(
pipeline_id + '::effect-' + id + '-key-updated', key, new_effect.params[key]
);
}
for (let key in new_effect.params) {
// if a key was added, we emit to tell the effect the key and its value
if (!(key in old_effect.params))
this._emit(
pipeline_id + '::effect-' + id + '-key-added', key, new_effect.params[key]
);
}
}
}
// if either the order has changed, or there are new effects, then rebuild it
else
this._emit(pipeline_id + '::pipeline-updated', new_pipeline);
}
}
destroy() {
this.settings.PIPELINES_disconnect();
}
_emit(signal, ...args) {
this.emit(signal, ...args);
this._log(`signal: '${signal}', arguments: ${args}`);
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > pipelines] ${str}`);
}
_warn(str) {
console.warn(`[Blur my Shell > pipelines] ${str}`);
}
}
Signals.addSignalMethods(PipelinesManager.prototype);

View File

@ -0,0 +1,363 @@
import GLib from 'gi://GLib';
const Signals = imports.signals;
/// An enum non-extensively describing the type of gsettings key.
export const Type = {
B: 'Boolean',
I: 'Integer',
D: 'Double',
S: 'String',
C: 'Color',
AS: 'StringArray',
PIPELINES: 'Pipelines'
};
/// An object to get and manage the gsettings preferences.
///
/// Should be initialized with an array of keys, for example:
///
/// let settings = new Settings([
/// { type: Type.I, name: "panel-corner-radius" },
/// { type: Type.B, name: "debug" }
/// ]);
///
/// Each {type, name} object represents a gsettings key, which must be created
/// in the gschemas.xml file of the extension.
export const Settings = class Settings {
constructor(keys, settings) {
this.settings = settings;
this.keys = keys;
this.keys.forEach(bundle => {
let component = this;
let component_settings = settings;
if (bundle.component !== "general") {
let bundle_component = bundle.component.replaceAll('-', '_');
this[bundle_component] = {
settings: this.settings.get_child(bundle.component)
};
component = this[bundle_component];
component_settings = settings.get_child(bundle.component);
}
bundle.schemas.forEach(key => {
let property_name = this.get_property_name(key.name);
switch (key.type) {
case Type.B:
Object.defineProperty(component, property_name, {
get() {
return component_settings.get_boolean(key.name);
},
set(v) {
component_settings.set_boolean(key.name, v);
}
});
break;
case Type.I:
Object.defineProperty(component, property_name, {
get() {
return component_settings.get_int(key.name);
},
set(v) {
component_settings.set_int(key.name, v);
}
});
break;
case Type.D:
Object.defineProperty(component, property_name, {
get() {
return component_settings.get_double(key.name);
},
set(v) {
component_settings.set_double(key.name, v);
}
});
break;
case Type.S:
Object.defineProperty(component, property_name, {
get() {
return component_settings.get_string(key.name);
},
set(v) {
component_settings.set_string(key.name, v);
}
});
break;
case Type.C:
Object.defineProperty(component, property_name, {
// returns the array [red, blue, green, alpha] with
// values between 0 and 1
get() {
let val = component_settings.get_value(key.name);
return val.deep_unpack();
},
// takes an array [red, blue, green, alpha] with
// values between 0 and 1
set(v) {
let val = new GLib.Variant("(dddd)", v);
component_settings.set_value(key.name, val);
}
});
break;
case Type.AS:
Object.defineProperty(component, property_name, {
get() {
let val = component_settings.get_value(key.name);
return val.deep_unpack();
},
set(v) {
let val = new GLib.Variant("as", v);
component_settings.set_value(key.name, val);
}
});
break;
case Type.PIPELINES:
Object.defineProperty(component, property_name, {
get() {
let pips = component_settings.get_value(key.name).deep_unpack();
Object.keys(pips).forEach(pipeline_id => {
let pipeline = pips[pipeline_id];
if (!('name' in pipeline)) {
this._warn('impossible to get pipelines, pipeline has not name, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let name = pipeline.name.deep_unpack();
if (typeof name !== 'string') {
this._warn('impossible to get pipelines, pipeline name is not a string, resetting');
component[property_name + '_reset']();
return component[property_name];
}
if (!('effects' in pipeline)) {
this._warn('impossible to get pipelines, pipeline has not effects, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let effects = pipeline.effects.deep_unpack();
if (!Array.isArray(effects)) {
this._warn('impossible to get pipelines, pipeline effects is not an array, resetting');
component[property_name + '_reset']();
return component[property_name];
}
effects = effects.map(effect => effect.deep_unpack());
effects.forEach(effect => {
if (!('type' in effect)) {
this._warn('impossible to get pipelines, effect has not type, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let type = effect.type.deep_unpack();
if (typeof type !== 'string') {
this._warn('impossible to get pipelines, effect type is not a string, resetting');
component[property_name + '_reset']();
return component[property_name];
}
if (!('id' in effect)) {
this._warn('impossible to get pipelines, effect has not id, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let id = effect.id.deep_unpack();
if (typeof id !== 'string') {
this._warn('impossible to get pipelines, effect id is not a string, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let params = {};
if ('params' in effect)
params = effect.params.deep_unpack();
if (!(params && typeof params === 'object' && params.constructor === Object)) {
this._warn('impossible to get pipelines, effect params is not an object, resetting');
component[property_name + '_reset']();
return component[property_name];
}
Object.keys(params).forEach(param_key => {
params[param_key] = params[param_key].deep_unpack();
});
effect.type = type;
effect.id = id;
effect.params = params;
});
pipeline.name = name;
pipeline.effects = effects;
});
return pips;
},
set(pips) {
let pipelines = {};
Object.keys(pips).forEach(pipeline_id => {
let pipeline = pips[pipeline_id];
if (!(pipeline && typeof pipeline === 'object' && pipeline.constructor === Object)) {
this._warn('impossible to set pipelines, pipeline is not an object');
return;
}
if (!('name' in pipeline)) {
this._warn('impossible to set pipelines, pipeline has no name');
return;
}
if (typeof pipeline.name !== 'string') {
this._warn('impossible to set pipelines, pipeline name is not a string');
return;
}
if (!('effects' in pipeline)) {
this._warn('impossible to set pipelines, pipeline has no effect');
return;
}
if (!Array.isArray(pipeline.effects)) {
this._warn('impossible to set pipelines, effects is not an array');
return;
}
let gvariant_effects = [];
pipeline.effects.forEach(effect => {
if (!(effect instanceof Object)) {
this._warn('impossible to set pipelines, effect is not an object');
return;
}
if (!('type' in effect)) {
this._warn('impossible to set pipelines, effect has not type');
return;
}
if (typeof effect.type !== 'string') {
this._warn('impossible to set pipelines, effect type is not a string');
return;
}
if (!('id' in effect)) {
this._warn('impossible to set pipelines, effect has not id');
return;
}
if (typeof effect.id !== 'string') {
this._warn('impossible to set pipelines, effect id is not a string');
return;
}
let params = {};
if ('params' in effect) {
params = effect.params;
}
let gvariant_params = {};
Object.keys(params).forEach(param_key => {
let param = params[param_key];
if (typeof param === 'boolean')
gvariant_params[param_key] = GLib.Variant.new_boolean(param);
else if (typeof param === 'number') {
if (Number.isInteger(param))
gvariant_params[param_key] = GLib.Variant.new_int32(param);
else
gvariant_params[param_key] = GLib.Variant.new_double(param);
} else if (typeof param === 'string')
gvariant_params[param_key] = GLib.Variant.new_string(param);
else if (Array.isArray(param) && param.length == 4)
gvariant_params[param_key] = new GLib.Variant("(dddd)", param);
else
this._warn('impossible to set pipeline, effect parameter type is unknown');
});
gvariant_effects.push(
new GLib.Variant("a{sv}", {
type: GLib.Variant.new_string(effect.type),
id: GLib.Variant.new_string(effect.id),
params: new GLib.Variant("a{sv}", gvariant_params)
})
);
});
pipelines[pipeline_id] = {
name: GLib.Variant.new_string(pipeline.name),
effects: new GLib.Variant("av", gvariant_effects)
};
});
let val = new GLib.Variant("a{sa{sv}}", pipelines);
component_settings.set_value(key.name, val);
}
});
break;
}
component[property_name + '_reset'] = function () {
return component_settings.reset(key.name);
};
component[property_name + '_signal_ids'] = [];
component[property_name + '_changed'] = function (cb) {
component[property_name + '_signal_ids'].push(
component_settings.connect('changed::' + key.name, cb)
);
};
component[property_name + '_disconnect'] = function () {
component[property_name + '_signal_ids'].forEach(
id => component_settings.disconnect(id)
);
component[property_name + '_signal_ids'] = [];
};
});
});
};
/// Reset the preferences.
reset() {
this.keys.forEach(bundle => {
let component = this;
if (bundle.component !== "general") {
let bundle_component = bundle.component.replaceAll('-', '_');
component = this[bundle_component];
}
bundle.schemas.forEach(key => {
let property_name = this.get_property_name(key.name);
component[property_name + '_reset']();
});
});
this.emit('reset', true);
}
/// From the gschema name, returns the name of the associated property on
/// the Settings object.
get_property_name(name) {
return name.replaceAll('-', '_').toUpperCase();
}
/// Remove all connections managed by the Settings object, i.e. created with
/// `settings.PROPERTY_changed(callback)`.
disconnect_all_settings() {
this.keys.forEach(bundle => {
let component = this;
if (bundle.component !== "general") {
let bundle_component = bundle.component.replaceAll('-', '_');
component = this[bundle_component];
}
bundle.schemas.forEach(key => {
let property_name = this.get_property_name(key.name);
component[property_name + '_disconnect']();
});
});
}
_warn(str) {
console.warn(`[Blur my Shell > settings] ${str}`);
}
};
Signals.addSignalMethods(Settings.prototype);

View File

@ -0,0 +1,40 @@
import { Settings } from './settings.js';
import { KEYS, DEPRECATED_KEYS } from './keys.js';
const CURRENT_SETTINGS_VERSION = 2;
export function update_from_old_settings(gsettings) {
const preferences = new Settings(KEYS, gsettings);
const deprecated_preferences = new Settings(DEPRECATED_KEYS, gsettings);
const old_version = preferences.settings.get_int('settings-version');
if (old_version < CURRENT_SETTINGS_VERSION) {
// set artifacts hacks to be 1 at most, as it should be suitable now that most big bugs have
// been resolved (and especially because hack levels to 2 now means disabling clipped
// redraws entirely, which is very much not what we want for users that update)
if (preferences.HACKS_LEVEL > 1)
preferences.HACKS_LEVEL = 1;
// enable dash-to-dock blurring, as most disabled it due to the lack of rounded corners; set
// it to static blur by default too and with transparent background
preferences.dash_to_dock.BLUR = true;
preferences.dash_to_dock.STATIC_BLUR = true;
preferences.dash_to_dock.STYLE_DASH_TO_DOCK = 0;
// 'customize' has been removed: we merge the current used settings
['appfolder', 'panel', 'dash_to_dock', 'applications', 'window_list'].forEach(
component_name => {
const deprecated_component = deprecated_preferences[component_name];
const new_component = preferences[component_name];
if (!deprecated_component.CUSTOMIZE) {
new_component.SIGMA = deprecated_preferences.SIGMA;
new_component.BRIGHTNESS = deprecated_preferences.BRIGHTNESS;
}
});
// remove old preferences in order not to clutter the gsettings
deprecated_preferences.reset();
}
preferences.settings.set_int('settings-version', CURRENT_SETTINGS_VERSION);
}

View File

@ -0,0 +1,27 @@
import GLib from 'gi://GLib';
export const IS_IN_PREFERENCES = typeof global === 'undefined';
// Taken from https://github.com/Schneegans/Burn-My-Windows/blob/main/src/utils.js
// This method can be used to import a module in the GNOME Shell process only. This
// is useful if you want to use a module in extension.js, but not in the preferences
// process. This method returns null if it is called in the preferences process.
export async function import_in_shell_only(module) {
if (IS_IN_PREFERENCES)
return null;
return (await import(module)).default;
}
export const get_shader_source = (Shell, shader_filename, self_uri) => {
if (!Shell)
return;
const shader_path = GLib.filename_from_uri(
GLib.uri_resolve_relative(self_uri, shader_filename, GLib.UriFlags.NONE)
)[0];
try {
return Shell.get_file_contents_utf8_sync(shader_path);
} catch (e) {
console.warn(`[Blur my Shell > effect] error loading shader from ${shader_path}: ${e}`);
return null;
}
};

View File

@ -0,0 +1,58 @@
import Gio from 'gi://Gio';
const bus_name = 'org.gnome.Shell';
const iface_name = 'dev.aunetx.BlurMyShell';
const obj_path = '/dev/aunetx/BlurMyShell';
/// Call pick() from the DBus service, it will open the Inspector from
/// gnome-shell to pick an actor on stage.
export function pick() {
Gio.DBus.session.call(
bus_name,
obj_path,
iface_name,
'pick',
null,
null,
Gio.DBusCallFlags.NO_AUTO_START,
-1,
null,
null
);
}
/// Connect to DBus 'picking' signal, which will be emitted when the inspector
/// is picking a window.
export function on_picking(cb) {
const id = Gio.DBus.session.signal_subscribe(
bus_name,
iface_name,
'picking',
obj_path,
null,
Gio.DBusSignalFlags.NONE,
_ => {
cb();
Gio.DBus.session.signal_unsubscribe(id);
}
);
}
/// Connect to DBus 'picked' signal, which will be emitted when a window is
/// picked.
export function on_picked(cb) {
const id = Gio.DBus.session.signal_subscribe(
bus_name,
iface_name,
'picked',
obj_path,
null,
Gio.DBusSignalFlags.NONE,
(conn, sender, obj_path, iface, signal, params) => {
const val = params.get_child_value(0);
cb(val.get_string()[0]);
Gio.DBus.session.signal_unsubscribe(id);
}
);
}

View File

@ -0,0 +1,12 @@
<node>
<interface name="dev.aunetx.BlurMyShell">
<!-- This method is called in preferences to pick a window -->
<method name="pick" />
<!-- When window is picking, send a signal to preferences -->
<signal name="picking"></signal>
<!-- If window is picked, send a signal to preferences -->
<signal name="picked">
<arg name="window" type="s" />
</signal>
</interface>
</node>

View File

@ -0,0 +1,93 @@
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as LookingGlass from 'resource:///org/gnome/shell/ui/lookingGlass.js';
export const ApplicationsService = class ApplicationsService {
constructor() {
let decoder = new TextDecoder();
let path = GLib.filename_from_uri(GLib.uri_resolve_relative(
import.meta.url, 'iface.xml', GLib.UriFlags.NONE)
)[0];
let [, buffer] = GLib.file_get_contents(path);
let iface = decoder.decode(buffer);
GLib.free(buffer);
this.DBusImpl = Gio.DBusExportedObject.wrapJSObject(iface, this);
}
/// Pick Window for Preferences Page, exported to DBus client.
pick() {
// emit `picking` signal to know we are listening
const send_picking_signal = _ =>
this.DBusImpl.emit_signal(
'picking',
null
);
// emit `picked` signal to send wm_class
const send_picked_signal = wm_class =>
this.DBusImpl.emit_signal(
'picked',
new GLib.Variant('(s)', [wm_class])
);
// notify the preferences that we are listening
send_picking_signal();
// A very interesting way to pick a window:
// 1. Open LookingGlass to mask all event handles of window
// 2. Use inspector to pick window, thats is also lookingGlass do
// 3. Close LookingGlass when done
// It will restore event handles of window
// open then hide LookingGlass
const looking_class = Main.createLookingGlass();
looking_class.open();
looking_class.hide();
// inspect window now
const inspector = new LookingGlass.Inspector(Main.createLookingGlass());
inspector.connect('target', (me, target, x, y) => {
// remove border effect when window is picked.
const effect_name = 'lookingGlass_RedBorderEffect';
target
.get_effects()
.filter(e => e.toString().includes(effect_name))
.forEach(e => target.remove_effect(e));
// get wm_class_instance property of window, then pass it to DBus
// client
const type_str = target.toString();
let actor = target;
if (type_str.includes('MetaSurfaceActor'))
actor = target.get_parent();
if (!actor.toString().includes('WindowActor'))
actor = actor.get_parent();
if (!actor.toString().includes('WindowActor'))
return send_picked_signal('window-not-found');
send_picked_signal(
actor.meta_window.get_wm_class() ?? 'window-not-found'
);
});
// close LookingGlass when we're done
inspector.connect('closed', _ => looking_class.close());
}
export() {
this.DBusImpl.export(
Gio.DBus.session,
'/dev/aunetx/BlurMyShell'
);
};
unexport() {
this.DBusImpl.unexport();
}
};

View File

@ -0,0 +1,13 @@
uniform sampler2D tex;
uniform float red;
uniform float green;
uniform float blue;
uniform float blend;
void main() {
vec4 c = texture2D(tex, cogl_tex_coord_in[0].st);
vec3 pix_color = c.xyz;
vec3 color = vec3(red, green, blue);
cogl_color_out = vec4(mix(pix_color, color, blend), 1.);
}

View File

@ -0,0 +1,151 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'color.glsl';
const DEFAULT_PARAMS = {
color: [0.0, 0.0, 0.0, 0.0]
};
export const ColorEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "ColorEffect",
Properties: {
'red': GObject.ParamSpec.double(
`red`,
`Red`,
`Red value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'green': GObject.ParamSpec.double(
`green`,
`Green`,
`Green value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'blue': GObject.ParamSpec.double(
`blue`,
`Blue`,
`Blue value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'blend': GObject.ParamSpec.double(
`blend`,
`Blend`,
`Amount of blending between the colors`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
}
}, class ColorEffect extends Clutter.ShaderEffect {
constructor(params) {
// initialize without color as a parameter
const { color, ...parent_params } = params;
super(parent_params);
this._red = null;
this._green = null;
this._blue = null;
this._blend = null;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
// set shader color
this.color = 'color' in params ? color : this.constructor.default_params.color;
}
static get default_params() {
return DEFAULT_PARAMS;
}
get red() {
return this._red;
}
set red(value) {
if (this._red !== value) {
this._red = value;
this.set_uniform_value('red', parseFloat(this._red - 1e-6));
}
}
get green() {
return this._green;
}
set green(value) {
if (this._green !== value) {
this._green = value;
this.set_uniform_value('green', parseFloat(this._green - 1e-6));
}
}
get blue() {
return this._blue;
}
set blue(value) {
if (this._blue !== value) {
this._blue = value;
this.set_uniform_value('blue', parseFloat(this._blue - 1e-6));
}
}
get blend() {
return this._blend;
}
set blend(value) {
if (this._blend !== value) {
this._blend = value;
this.set_uniform_value('blend', parseFloat(this._blend - 1e-6));
this.set_enabled(this.blend > 0);
}
}
set color(rgba) {
let [r, g, b, a] = rgba;
this.red = r;
this.green = g;
this.blue = b;
this.blend = a;
}
get color() {
return [this.red, this.green, this.blue, this.blend];
}
/// False set function, only cares about the color. Too hard to change.
set(params) {
this.color = params.color;
}
vfunc_paint_target(paint_node = null, paint_context = null) {
this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,93 @@
// Heavily based on https://github.com/yilozt/rounded-window-corners
// which is itself based on upstream Mutter code
uniform sampler2D tex;
uniform float radius;
uniform float width;
uniform float height;
uniform bool corners_top;
uniform bool corners_bottom;
uniform float clip_x0;
uniform float clip_y0;
uniform float clip_width;
uniform float clip_height;
float circle_bounds(vec2 p, vec2 center, float clip_radius) {
vec2 delta = p - center;
float dist_squared = dot(delta, delta);
float outer_radius = clip_radius + 0.5;
if (dist_squared >= (outer_radius * outer_radius))
return 0.0;
float inner_radius = clip_radius - 0.5;
if (dist_squared <= (inner_radius * inner_radius))
return 1.0;
return outer_radius - sqrt(dist_squared);
}
vec4 getTexture(vec2 uv) {
if (uv.x < 2. / width)
uv.x = 2. / width;
if (uv.y < 2. / height)
uv.y = 2. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
float rounded_rect_coverage(vec2 p, vec4 bounds, float clip_radius) {
// Outside the bounds
if (p.x < bounds.x || p.x > bounds.z || p.y < bounds.y || p.y > bounds.w) {
return 0.;
}
vec2 center;
float center_left = bounds.x + clip_radius;
float center_right = bounds.z - clip_radius;
if (p.x < center_left)
center.x = center_left + 2.;
else if (p.x > center_right)
center.x = center_right - 1.;
else
return 1.0;
float center_top = bounds.y + clip_radius;
float center_bottom = bounds.w - clip_radius;
if (corners_top && p.y < center_top)
center.y = center_top + 2.;
else if (corners_bottom && p.y > center_bottom)
center.y = center_bottom - 1.;
else
return 1.0;
return circle_bounds(p, center, clip_radius);
}
void main(void) {
vec2 uv = cogl_tex_coord_in[0].xy;
vec2 pos = uv * vec2(width, height);
vec4 c = getTexture(uv);
vec4 bounds;
if (clip_width < 0. || clip_height < 0.) {
bounds = vec4(clip_x0, clip_y0, clip_x0 + width, clip_y0 + height);
} else {
bounds = vec4(clip_x0, clip_y0, clip_x0 + clip_width, clip_y0 + clip_height);
}
float alpha = rounded_rect_coverage(pos, bounds, radius);
cogl_color_out = vec4(c.rgb * alpha, min(alpha, c.a));
}

View File

@ -0,0 +1,233 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'corner.glsl';
const DEFAULT_PARAMS = {
radius: 12, width: 0, height: 0,
corners_top: true, corners_bottom: true,
clip: [0, 0, -1, -1]
};
export const CornerEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "CornerEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Corner Radius`,
`Corner Radius`,
GObject.ParamFlags.READWRITE,
0, Number.MAX_SAFE_INTEGER,
12,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'corners_top': GObject.ParamSpec.boolean(
`corners_top`,
`Round top corners`,
`Round top corners`,
GObject.ParamFlags.READWRITE,
true,
),
'corners_bottom': GObject.ParamSpec.boolean(
`corners_bottom`,
`Round bottom corners`,
`Round bottom corners`,
GObject.ParamFlags.READWRITE,
true,
),
// FIXME this works but it logs an error, because I'm not a double...
// I don't want to fiddle with GVariants again
'clip': GObject.ParamSpec.double(
`clip`,
`Clip`,
`Clip`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
}
}, class CornerEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._radius = null;
this._width = null;
this._height = null;
this._corners_top = null;
this._corners_bottom = null;
this._clip_x0 = null;
this._clip_y0 = null;
this._clip_width = null;
this._clip_height = null;
this.radius = 'radius' in params ? params.radius : this.constructor.default_params.radius;
this.width = 'width' in params ? params.width : this.constructor.default_params.width;
this.height = 'height' in params ? params.height : this.constructor.default_params.height;
this.height = 'corners_top' in params ? params.corners_top : this.constructor.default_params.corners_top;
this.height = 'corners_bottom' in params ? params.corners_bottom : this.constructor.default_params.corners_bottom;
this.clip = 'clip' in params ? params.clip : this.constructor.default_params.clip;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject('notify::scale-factor', _ => this.update_radius(), this);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
this.update_radius();
}
}
update_radius() {
const theme_context = St.ThemeContext.get_for_stage(global.stage);
let radius = Math.min(
this.radius * theme_context.scale_factor,
this.width / 2, this.height / 2
);
if (this._clip_width >= 0 || this._clip_height >= 0)
radius = Math.min(radius, this._clip_width / 2, this._clip_height / 2);
this.set_uniform_value('radius', parseFloat(radius - 1e-6));
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
this.update_radius();
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
this.update_radius();
}
}
get corners_top() {
return this._corners_top;
}
set corners_top(value) {
if (this._corners_top !== value) {
this._corners_top = value;
this.set_uniform_value('corners_top', this._corners_top ? 1 : 0);
}
}
get corners_bottom() {
return this._corners_bottom;
}
set corners_bottom(value) {
if (this._corners_bottom !== value) {
this._corners_bottom = value;
this.set_uniform_value('corners_bottom', this._corners_bottom ? 1 : 0);
}
}
get clip() {
return [this._clip_x0, this._clip_y0, this._clip_width, this._clip_height];
}
set clip(value) {
[this._clip_x0, this._clip_y0, this._clip_width, this._clip_height] = value;
this.set_uniform_value('clip_x0', parseFloat(this._clip_x0 - 1e-6));
this.set_uniform_value('clip_y0', parseFloat(this._clip_y0 - 1e-6));
this.set_uniform_value('clip_width', parseFloat(this._clip_width + 3 - 1e-6));
this.set_uniform_value('clip_height', parseFloat(this._clip_height + 3 - 1e-6));
this.update_radius();
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (this._actor_connection_clip_rect_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_clip_rect_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
this.clip = actor.has_clip ? actor.get_clip() : [0, 0, -10, -10];
this._actor_connection_clip_rect_id = actor.connect('notify::clip-rect', _ => {
this.clip = actor.has_clip ? actor.get_clip() : [0, 0, -10, -10];
});
}
else {
this._actor_connection_size_id = null;
this._actor_connection_clip_rect_id = null;
}
super.vfunc_set_actor(actor);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
//this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,226 @@
import { NativeDynamicBlurEffect } from '../effects/native_dynamic_gaussian_blur.js';
import { NativeStaticBlurEffect } from '../effects/native_static_gaussian_blur.js';
import { GaussianBlurEffect } from '../effects/gaussian_blur.js';
import { MonteCarloBlurEffect } from '../effects/monte_carlo_blur.js';
import { ColorEffect } from '../effects/color.js';
import { PixelizeEffect } from './pixelize.js';
import { NoiseEffect } from '../effects/noise.js';
import { CornerEffect } from '../effects/corner.js';
// We do in this way because I've not found another way to store our preferences in a dictionnary
// while calling `gettext` on it while in preferences. Not so pretty, but works.
export function get_effects_groups(_ = _ => "") {
return {
blur_effects: {
name: _("Blur effects"),
contains: [
"native_static_gaussian_blur",
"gaussian_blur",
"monte_carlo_blur"
]
},
texture_effects: {
name: _("Texture effects"),
contains: [
"pixelize",
"noise",
"color"
]
},
shape_effects: {
name: _("Shape effects"),
contains: [
"corner"
]
}
};
};
export function get_supported_effects(_ = () => "") {
return {
native_dynamic_gaussian_blur: {
class: NativeDynamicBlurEffect
},
native_static_gaussian_blur: {
class: NativeStaticBlurEffect,
name: _("Native gaussian blur"),
description: _("An optimized blur effect that smoothly blends pixels within a given radius."),
editable_params: {
unscaled_radius: {
name: _("Radius"),
description: _("The intensity of the blur effect."),
type: "float",
min: 0.,
max: 100.,
increment: 1.0,
big_increment: 10.,
digits: 0
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
}
},
gaussian_blur: {
class: GaussianBlurEffect,
name: _("Gaussian blur"),
description: _("A blur effect that smoothly blends pixels within a given radius. This effect is more precise, but way less optimized."),
editable_params: {
radius: {
name: _("Radius"),
description: _("The intensity of the blur effect. The bigger it is, the slower it will be."),
type: "float",
min: 0.,
max: 100.,
increment: .1,
big_increment: 10.,
digits: 1
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
}
},
monte_carlo_blur: {
class: MonteCarloBlurEffect,
name: _("Monte Carlo blur"),
description: _("A blur effect that mimics a random walk, by picking pixels further and further away from its origin and mixing them all together."),
editable_params: {
radius: {
name: _("Radius"),
description: _("The maximum travel distance for each step in the random walk. A higher value will make the blur more randomized."),
type: "float",
min: 0.,
max: 10.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
iterations: {
name: _("Iterations"),
description: _("The number of iterations. The more there are, the smoother the blur is."),
type: "integer",
min: 0,
max: 50,
increment: 1
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
use_base_pixel: {
name: _("Use base pixel"),
description: _("Whether or not the original pixel is counted for the blur. If it is, the image will be more legible."),
type: "boolean"
}
}
},
color: {
class: ColorEffect,
name: _("Color"),
description: _("An effect that blends a color into the pipeline."),
// TODO make this RGB + blend
editable_params: {
color: {
name: _("Color"),
description: _("The color to blend in. The blending amount is controled by the opacity of the color."),
type: "rgba"
}
}
},
pixelize: {
class: PixelizeEffect,
name: _("Pixelize"),
description: _("An effect that pixelizes the image."),
editable_params: {
divider: {
name: _("Divider"),
description: _("How much to scale down the image."),
type: "integer",
min: 1,
max: 50,
increment: 1
}
}
},
noise: {
class: NoiseEffect,
name: _("Noise"),
description: _("An effect that adds a random noise. Prefer the Monte Carlo blur for a more organic effect if needed."),
editable_params: {
noise: {
name: _("Noise"),
description: _("The amount of noise to add."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
lightness: {
name: _("Lightness"),
description: _("The luminosity of the noise. A setting of '1.0' will make the effect transparent."),
type: "float",
min: 0.,
max: 2.,
increment: 0.01,
big_increment: 0.1,
digits: 2
}
}
},
corner: {
class: CornerEffect,
name: _("Corner"),
description: _("An effect that draws corners. Add it last not to have the other effects perturb the corners."),
editable_params: {
radius: {
name: _("Radius"),
description: _("The radius of the corner. GNOME apps use a radius of 12 px by default."),
type: "integer",
min: 0,
max: 50,
increment: 1,
},
corners_top: {
name: _("Top corners"),
description: _("Whether or not to round the top corners."),
type: "boolean"
},
corners_bottom: {
name: _("Bottom corners"),
description: _("Whether or not to round the bottom corners."),
type: "boolean"
}
}
}
};
};

View File

@ -0,0 +1,70 @@
uniform sampler2D tex;
uniform float sigma;
uniform int dir;
uniform float brightness;
uniform float width;
uniform float height;
vec4 getTexture(vec2 uv) {
if (uv.x < 3. / width)
uv.x = 3. / width;
if (uv.y < 3. / height)
uv.y = 3. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
void main(void) {
vec2 uv = cogl_tex_coord_in[0].xy;
vec2 direction = vec2(dir, (1.0 - dir));
float pixel_step;
if (dir == 0)
pixel_step = 1.0 / height;
else
pixel_step = 1.0 / width;
vec3 gauss_coefficient;
gauss_coefficient.x = 1.0 / (sqrt(2.0 * 3.14159265) * sigma);
gauss_coefficient.y = exp(-0.5 / (sigma * sigma));
gauss_coefficient.z = gauss_coefficient.y * gauss_coefficient.y;
float gauss_coefficient_total = gauss_coefficient.x;
vec4 ret = getTexture(uv) * gauss_coefficient.x;
gauss_coefficient.xy *= gauss_coefficient.yz;
int n_steps = int(ceil(1.5 * sigma)) * 2;
for (int i = 1; i <= n_steps; i += 2) {
float coefficient_subtotal = gauss_coefficient.x;
gauss_coefficient.xy *= gauss_coefficient.yz;
coefficient_subtotal += gauss_coefficient.x;
float gauss_ratio = gauss_coefficient.x / coefficient_subtotal;
float foffset = float(i) + gauss_ratio;
vec2 offset = direction * foffset * pixel_step;
ret += getTexture(uv + offset) * coefficient_subtotal;
ret += getTexture(uv - offset) * coefficient_subtotal;
gauss_coefficient_total += 2.0 * coefficient_subtotal;
gauss_coefficient.xy *= gauss_coefficient.yz;
}
vec4 outColor = ret / gauss_coefficient_total;
// apply brightness on the second pass (dir==0 comes last)
if (dir == 0) {
outColor.rgb *= brightness;
}
cogl_color_out = outColor;
}

View File

@ -0,0 +1,227 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'gaussian_blur.glsl';
const DEFAULT_PARAMS = {
radius: 30, brightness: .6,
width: 0, height: 0, direction: 0
};
export const GaussianBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "GaussianBlurEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Radius`,
`Blur radius`,
GObject.ParamFlags.READWRITE,
0.0, 2000.0,
30.0,
),
'brightness': GObject.ParamSpec.double(
`brightness`,
`Brightness`,
`Blur brightness`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.6,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'direction': GObject.ParamSpec.int(
`direction`,
`Direction`,
`Direction`,
GObject.ParamFlags.READWRITE,
0, 1,
0,
),
'chained_effect': GObject.ParamSpec.object(
`chained_effect`,
`Chained Effect`,
`Chained Effect`,
GObject.ParamFlags.READABLE,
GObject.Object,
),
}
}, class GaussianBlurEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._radius = null;
this._brightness = null;
this._width = null;
this._height = null;
this._direction = null;
this._chained_effect = null;
this.radius = 'radius' in params ? params.radius : this.constructor.default_params.radius;
this.brightness = 'brightness' in params ? params.brightness : this.constructor.default_params.brightness;
this.width = 'width' in params ? params.width : this.constructor.default_params.width;
this.height = 'height' in params ? params.height : this.constructor.default_params.height;
this.direction = 'direction' in params ? params.direction : this.constructor.default_params.direction;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject(
'notify::scale-factor', _ =>
this.set_uniform_value('sigma',
parseFloat(this.radius * theme_context.scale_factor / 2 - 1e-6)
),
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
const scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
// like Clutter, we use the assumption radius = 2*sigma
this.set_uniform_value('sigma', parseFloat(this._radius * scale_factor / 2 - 1e-6));
this.set_enabled(this.radius > 0.);
if (this._chained_effect)
this._chained_effect.radius = value;
}
}
get brightness() {
return this._brightness;
}
set brightness(value) {
if (this._brightness !== value) {
this._brightness = value;
this.set_uniform_value('brightness', parseFloat(this._brightness - 1e-6));
if (this._chained_effect)
this._chained_effect.brightness = value;
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
if (this._chained_effect)
this._chained_effect.width = value;
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
if (this._chained_effect)
this._chained_effect.height = value;
}
}
get direction() {
return this._direction;
}
set direction(value) {
if (this._direction !== value)
this._direction = value;
}
get chained_effect() {
return this._chained_effect;
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
if (this.direction == 0) {
if (this.chained_effect)
this._chained_effect.get_actor()?.remove_effect(this._chained_effect);
else
this._chained_effect = new GaussianBlurEffect({
radius: this.radius,
brightness: this.brightness,
width: this.width,
height: this.height,
direction: 1
});
if (actor !== null)
actor.add_effect(this._chained_effect);
}
}
vfunc_paint_target(paint_node = null, paint_context = null) {
//this.set_uniform_value("tex", 0);
this.set_uniform_value("dir", this._direction);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,44 @@
uniform sampler2D tex;
uniform float radius;
uniform int iterations;
uniform float brightness;
uniform float width;
uniform float height;
uniform bool use_base_pixel;
float srand(vec2 a) {
return sin(dot(a, vec2(1233.224, 1743.335)));
}
float rand(inout float r) {
r = fract(3712.65 * r + 0.61432);
return (r - 0.5) * 2.0;
}
void main() {
vec2 uv = cogl_tex_coord0_in.st;
vec2 p = 16 * radius / vec2(width, height);
float r = srand(uv);
vec2 rv;
int count = 0;
vec4 c = vec4(0.);
for (int i = 0; i < iterations; i++) {
rv.x = rand(r);
rv.y = rand(r);
vec2 new_uv = uv + rv * p;
if (new_uv.x > 2. / width && new_uv.y > 2. / height && new_uv.x < 1. - 3. / width && new_uv.y < 1. - 3. / height) {
c += texture2D(tex, new_uv);
count += 1;
}
}
if (count == 0 || use_base_pixel) {
c += texture2D(tex, uv);
count += 1;
}
c.xyz *= brightness;
cogl_color_out = c / count;
}

View File

@ -0,0 +1,211 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'monte_carlo_blur.glsl';
const DEFAULT_PARAMS = {
radius: 2., iterations: 5, brightness: .6,
width: 0, height: 0, use_base_pixel: true
};
export const MonteCarloBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "MonteCarloBlurEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Radius`,
`Blur radius`,
GObject.ParamFlags.READWRITE,
0.0, 2000.0,
2.0,
),
'iterations': GObject.ParamSpec.int(
`iterations`,
`Iterations`,
`Blur iterations`,
GObject.ParamFlags.READWRITE,
0, 64,
5,
),
'brightness': GObject.ParamSpec.double(
`brightness`,
`Brightness`,
`Blur brightness`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.6,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'use_base_pixel': GObject.ParamSpec.boolean(
`use_base_pixel`,
`Use base pixel`,
`Use base pixel`,
GObject.ParamFlags.READWRITE,
true,
),
}
}, class MonteCarloBlurEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._radius = null;
this._iterations = null;
this._brightness = null;
this._width = null;
this._height = null;
this._use_base_pixel = null;
this.radius = 'radius' in params ? params.radius : this.constructor.default_params.radius;
this.iterations = 'iterations' in params ? params.iterations : this.constructor.default_params.iterations;
this.brightness = 'brightness' in params ? params.brightness : this.constructor.default_params.brightness;
this.width = 'width' in params ? params.width : this.constructor.default_params.width;
this.height = 'height' in params ? params.height : this.constructor.default_params.height;
this.use_base_pixel = 'use_base_pixel' in params ? params.use_base_pixel : this.constructor.default_params.use_base_pixel;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject(
'notify::scale-factor',
_ => this.set_uniform_value('radius',
parseFloat(this._radius * theme_context.scale_factor - 1e-6)
),
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
const scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
this.set_uniform_value('radius', parseFloat(this._radius * scale_factor - 1e-6));
this.set_enabled(this.radius > 0. && this.iterations > 0);
}
}
get iterations() {
return this._iterations;
}
set iterations(value) {
if (this._iterations !== value) {
this._iterations = value;
this.set_uniform_value('iterations', this._iterations);
this.set_enabled(this.radius > 0. && this.iterations > 0);
}
}
get brightness() {
return this._brightness;
}
set brightness(value) {
if (this._brightness !== value) {
this._brightness = value;
this.set_uniform_value('brightness', parseFloat(this._brightness - 1e-6));
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
}
}
get use_base_pixel() {
return this._use_base_pixel;
}
set use_base_pixel(value) {
if (this._use_base_pixel !== value) {
this._use_base_pixel = value;
this.set_uniform_value('use_base_pixel', this._use_base_pixel ? 1 : 0);
}
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
//this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,44 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const DEFAULT_PARAMS = {
unscaled_radius: 30, brightness: 0.6
};
export const NativeDynamicBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "NativeDynamicBlurEffect"
}, class NativeDynamicBlurEffect extends Shell.BlurEffect {
constructor(params) {
const { unscaled_radius, ...parent_params } = params;
super({ ...parent_params, mode: Shell.BlurMode.BACKGROUND });
this._theme_context = St.ThemeContext.get_for_stage(global.stage);
this.unscaled_radius = 'unscaled_radius' in params ? unscaled_radius : this.constructor.default_params.unscaled_radius;
this._theme_context.connectObject(
'notify::scale-factor',
_ => this.radius = this.unscaled_radius * this._theme_context.scale_factor,
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get unscaled_radius() {
return this._unscaled_radius;
}
set unscaled_radius(value) {
this._unscaled_radius = value;
this.radius = value * this._theme_context.scale_factor;
}
});

View File

@ -0,0 +1,44 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const DEFAULT_PARAMS = {
unscaled_radius: 30, brightness: 0.6
};
export const NativeStaticBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "NativeStaticBlurEffect"
}, class NativeStaticBlurEffect extends Shell.BlurEffect {
constructor(params) {
const { unscaled_radius, ...parent_params } = params;
super({ ...parent_params, mode: Shell.BlurMode.ACTOR });
this._theme_context = St.ThemeContext.get_for_stage(global.stage);
this.unscaled_radius = 'unscaled_radius' in params ? unscaled_radius : this.constructor.default_params.unscaled_radius;
this._theme_context.connectObject(
'notify::scale-factor',
_ => this.radius = this.unscaled_radius * this._theme_context.scale_factor,
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get unscaled_radius() {
return this._unscaled_radius;
}
set unscaled_radius(value) {
this._unscaled_radius = value;
this.radius = value * this._theme_context.scale_factor;
}
});

View File

@ -0,0 +1,20 @@
uniform sampler2D tex;
uniform float noise;
uniform float lightness;
float PHI = 1.61803398874989484820459;
float SEED = 24;
float noise_gen(in vec2 xy) {
float r = fract(tan(distance(xy * PHI, xy) * SEED) * xy.x);
r = r != r ? 0.0 : r;
return r;
}
void main() {
vec4 c = texture2D(tex, cogl_tex_coord_in[0].st);
vec3 pix_color = c.xyz;
float blend = noise * (1. - noise_gen(gl_FragCoord.xy));
cogl_color_out = vec4(mix(pix_color, lightness * pix_color, blend), 1.);
}

View File

@ -0,0 +1,91 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'noise.glsl';
const DEFAULT_PARAMS = {
noise: 0.4, lightness: 0.4
};
export const NoiseEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "NoiseEffect",
Properties: {
'noise': GObject.ParamSpec.double(
`noise`,
`Noise`,
`Amount of noise integrated with the image`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
'lightness': GObject.ParamSpec.double(
`lightness`,
`Lightness`,
`Lightness of the grey used for the noise`,
GObject.ParamFlags.READWRITE,
0.0, 2.0,
0.4,
),
}
}, class NoiseEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._noise = null;
this._lightness = null;
this.noise = 'noise' in params ? params.noise : this.constructor.default_params.noise;
this.lightness = 'lightness' in params ? params.lightness : this.constructor.default_params.lightness;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get noise() {
return this._noise;
}
set noise(value) {
if (this._noise !== value) {
this._noise = value;
this.set_uniform_value('noise', parseFloat(this._noise - 1e-6));
this.set_enabled(this.noise > 0. && this.lightness != 1);
}
}
get lightness() {
return this._lightness;
}
set lightness(value) {
if (this._lightness !== value) {
this._lightness = value;
this.set_uniform_value('lightness', parseFloat(this._lightness - 1e-6));
this.set_enabled(this.noise > 0. && this.lightness != 1);
}
}
vfunc_paint_target(paint_node = null, paint_context = null) {
this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,27 @@
uniform sampler2D tex;
uniform int divider;
uniform float width;
uniform float height;
vec4 getTexture(vec2 uv) {
if (uv.x < 3. / width)
uv.x = 3. / width;
if (uv.y < 3. / height)
uv.y = 3. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
void main() {
vec2 uv = cogl_tex_coord0_in.st;
vec2 scaled_multiplier = vec2(width, height) / divider;
vec2 new_uv = 0.5 + floor((uv - 0.5) * scaled_multiplier) / scaled_multiplier;
cogl_color_out = getTexture(new_uv);
}

View File

@ -0,0 +1,130 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'pixelize.glsl';
const DEFAULT_PARAMS = {
divider: 8, width: 0, height: 0
};
export const PixelizeEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "PixelizeEffect",
Properties: {
'divider': GObject.ParamSpec.int(
`divider`,
`Divider`,
`Divider`,
GObject.ParamFlags.READWRITE,
0, 64,
5,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
)
}
}, class PixelizeEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._divider = null;
this._width = null;
this._height = null;
this.divider = 'divider' in params ? params.divider : this.constructor.default_params.divider;
this.width = 'width' in params ? params.width : this.constructor.default_params.width;
this.height = 'height' in params ? params.height : this.constructor.default_params.height;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get divider() {
return this._width;
}
set divider(value) {
if (this._divider !== value) {
this._divider = value;
this.set_uniform_value('divider', this._divider);
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
}
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
//this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,570 @@
import Meta from 'gi://Meta';
import Clutter from 'gi://Clutter';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
import { update_from_old_settings } from './conveniences/settings_updater.js';
import { PipelinesManager } from './conveniences/pipelines_manager.js';
import { EffectsManager } from './conveniences/effects_manager.js';
import { Connections } from './conveniences/connections.js';
import { Settings } from './conveniences/settings.js';
import { KEYS } from './conveniences/keys.js';
import { PanelBlur } from './components/panel.js';
import { OverviewBlur } from './components/overview.js';
import { DashBlur } from './components/dash_to_dock.js';
import { LockscreenBlur } from './components/lockscreen.js';
import { AppFoldersBlur } from './components/appfolders.js';
import { WindowListBlur } from './components/window_list.js';
import { ApplicationsBlur } from './components/applications.js';
import { ScreenshotBlur } from './components/screenshot.js';
/// The main extension class, created when the GNOME Shell is loaded.
export default class BlurMyShell extends Extension {
/// Enables the extension.
enable() {
// add the extension to global to make it accessible to other extensions
// create it first as it is very useful when debugging crashes
global.blur_my_shell = this;
// update from old settings, very important for hacks level specifically
update_from_old_settings(this.getSettings());
// create a Settings instance, to manage extension's preferences
// it needs to be loaded before logging, as it checks for DEBUG
this._settings = new Settings(KEYS, this.getSettings());
this._log("enabling extension...");
// create main extension Connections instance
this._connection = new Connections;
// store it in a global array
this._connections = [this._connection];
// create a global effects manager (to prevent RAM bleeding)
this._effects_manager = new EffectsManager(this._connection);
// create a global pipelines manager, that helps talking with preferences
this._pipelines_manager = new PipelinesManager(this._settings);
// create an instance of each component, with its associated Connections
let init = () => {
// create a Connections instance, to manage signals
let connection = new Connections;
// store it to keeps track of them globally
this._connections.push(connection);
return [connection, this._settings, this._effects_manager];
};
this._panel_blur = new PanelBlur(...init());
this._dash_to_dock_blur = new DashBlur(...init());
this._overview_blur = new OverviewBlur(...init());
this._lockscreen_blur = new LockscreenBlur(...init());
this._appfolder_blur = new AppFoldersBlur(...init());
this._window_list_blur = new WindowListBlur(...init());
this._applications_blur = new ApplicationsBlur(...init());
this._screenshot_blur = new ScreenshotBlur(...init());
// connect each component to preferences change
this._connect_to_settings();
// enable the lockscreen blur, only one important in both `user` session and `unlock-dialog`
if (this._settings.lockscreen.BLUR && !this._lockscreen_blur.enabled)
this._lockscreen_blur.enable();
// ensure we take the correct action for the current session mode
this._user_session_mode_enabled = false;
this._on_session_mode_changed(Main.sessionMode);
// watch for changes to the session mode
this._connection.connect(Main.sessionMode, 'updated',
() => this._on_session_mode_changed(Main.sessionMode)
);
}
/// Enables the components related to the user session (everything except lockscreen blur).
_enable_user_session() {
this._log("changing mode to user session...");
// maybe disable clipped redraw
this._update_clipped_redraws();
// enable every component
// if the shell is still starting up, wait for it to be entirely loaded;
// this should prevent bugs like #136 and #137
if (Main.layoutManager._startingUp) {
this._connection.connect(
Main.layoutManager,
'startup-complete',
() => this._enable_components()
);
} else
this._enable_components();
// try to enable the components as soon as possible anyway, this way the
// overview may load before the user sees it
try {
if (this._settings.overview.BLUR && !this._overview_blur.enabled)
this._overview_blur.enable();
} catch (e) {
this._log("Could not enable overview blur directly");
this._log(e);
}
try {
if (this._settings.dash_to_dock.BLUR
&& !this._dash_to_dock_blur.enabled)
this._dash_to_dock_blur.enable();
} catch (e) {
this._log("Could not enable dash-to-dock blur directly");
this._log(e);
}
try {
if (this._settings.panel.BLUR && !this._panel_blur.enabled)
this._panel_blur.enable();
} catch (e) {
this._log("Could not enable panel blur directly");
this._log(e);
}
// tells the extension we have enabled the user session components, so that we do not
// disable them later if they were not even enabled to begin with
this._user_session_mode_enabled = true;
}
/// Disables the extension.
///
/// This extension needs to use the 'unlock-dialog' session mode in order to change the blur on
/// the lockscreen. We have kind of two states of enablement for this extension:
/// - the 'enabled' state, which means that we have created the necessary components (which only
/// are js objects) and enabled the lockscreen blur (which means swapping two functions from
/// the `UnlockDialog` constructor with our ones;
/// - the 'user session enabled` mode, which means that we are in the 'enabled' mode AND we are
/// in the user mode, and so we enable all the other components that we created before.
/// We switch from one state to the other thanks to `this._on_session_mode_changed`, and we
/// track wether or not we are in the user mode with `this._user_session_mode_enabled` (because
/// `this._on_session_mode_changed` might be called multiple times while in the user session
/// mode, typically when going back from simple lockscreen and not sleep mode).
disable() {
this._log("disabling extension...");
// disable every component from user session mode
if (this._user_session_mode_enabled)
this._disable_user_session();
// disable lockscreen blur too
this._lockscreen_blur.disable();
// untrack them
this._panel_blur = null;
this._dash_to_dock_blur = null;
this._overview_blur = null;
this._appfolder_blur = null;
this._lockscreen_blur = null;
this._window_list_blur = null;
this._applications_blur = null;
this._screenshot_blur = null;
// make sure no settings change can re-enable them
this._settings.disconnect_all_settings();
// force disconnecting every signal, even if component crashed
this._connections.forEach((connections) => {
connections.disconnect_all();
});
this._connections = [];
// remove the clipped redraws flag
this._reenable_clipped_redraws();
// remove the extension from GJS's global
delete global.blur_my_shell;
this._log("extension disabled.");
this._settings = null;
}
/// Disables the components related to the user session (everything except lockscreen blur).
_disable_user_session() {
this._log("disabling user session mode...");
// disable every component except lockscreen blur
this._panel_blur.disable();
this._dash_to_dock_blur.disable();
this._overview_blur.disable();
this._appfolder_blur.disable();
this._window_list_blur.disable();
this._applications_blur.disable();
this._screenshot_blur.disable();
// remove the clipped redraws flag
this._reenable_clipped_redraws();
// tells the extension we have disabled the user session components, so that we do not
// disable them later again if they were already disabled
this._user_session_mode_enabled = false;
}
/// Restarts the components related to the user session.
_restart() {
this._log("restarting...");
this._disable_user_session();
this._enable_user_session();
this._log("restarted.");
}
/// Changes the extension to operate either on 'user' mode or 'unlock-dialog' mode, switching
/// from one to the other means enabling/disabling every component except lockscreen blur.
_on_session_mode_changed(session) {
if (session.currentMode === 'user' || session.parentMode === 'user') {
if (!this._user_session_mode_enabled)
// we need to activate everything
this._enable_user_session();
}
else if (session.currentMode === 'unlock-dialog') {
if (this._user_session_mode_enabled)
// we need to disable the components related to the user session mode
this._disable_user_session();
}
}
/// Add or remove the clutter debug flag to disable clipped redraws.
/// This will entirely fix the blur effect, but should not be used except if
/// the user really needs it, as clipped redraws are a huge performance
/// boost for the compositor.
_update_clipped_redraws() {
if (this._settings.HACKS_LEVEL === 2)
this._disable_clipped_redraws();
else
this._reenable_clipped_redraws();
}
/// Add the Clutter debug flag.
_disable_clipped_redraws() {
Meta.add_clutter_debug_flags(
null, Clutter.DrawDebugFlag.DISABLE_CLIPPED_REDRAWS, null
);
}
/// Remove the Clutter debug flag.
_reenable_clipped_redraws() {
Meta.remove_clutter_debug_flags(
null, Clutter.DrawDebugFlag.DISABLE_CLIPPED_REDRAWS, null
);
}
/// Enables every component from the user session needed, should be called when the shell is
/// entirely loaded as the `enable` methods interact with it.
_enable_components() {
// enable each component if needed, and if it is not already enabled
if (this._settings.panel.BLUR && !this._panel_blur.enabled)
this._panel_blur.enable();
if (this._settings.dash_to_dock.BLUR && !this._dash_to_dock_blur.enabled)
this._dash_to_dock_blur.enable();
if (this._settings.overview.BLUR && !this._overview_blur.enabled)
this._overview_blur.enable();
if (this._settings.appfolder.BLUR)
this._appfolder_blur.enable();
if (this._settings.applications.BLUR)
this._applications_blur.enable();
if (this._settings.window_list.BLUR)
this._window_list_blur.enable();
if (this._settings.screenshot.BLUR)
this._screenshot_blur.enable();
this._log("all components enabled.");
}
/// Updates needed things in each component when a preference changed
_connect_to_settings() {
// restart the extension when hacks level is changed, easier than
// restarting individual components and should not happen often either
this._settings.HACKS_LEVEL_changed(() => this._restart());
// ---------- OVERVIEW ----------
// toggled on/off
this._settings.overview.BLUR_changed(() => {
if (this._settings.overview.BLUR)
this._overview_blur.enable();
else
this._overview_blur.disable();
});
// overview pipeline changed
this._settings.overview.PIPELINE_changed(() => {
if (this._settings.overview.BLUR)
this._overview_blur.update_backgrounds();
});
// overview components style changed
this._settings.overview.STYLE_COMPONENTS_changed(() => {
if (this._settings.overview.BLUR)
this._overview_blur.update_components_classname();
});
// ---------- APPFOLDER ----------
// toggled on/off
this._settings.appfolder.BLUR_changed(() => {
if (this._settings.appfolder.BLUR)
this._appfolder_blur.enable();
else
this._appfolder_blur.disable();
});
// appfolder sigma changed
this._settings.appfolder.SIGMA_changed(() => {
if (this._settings.appfolder.BLUR)
this._appfolder_blur.set_sigma(
this._settings.appfolder.SIGMA
);
});
// appfolder brightness changed
this._settings.appfolder.BRIGHTNESS_changed(() => {
if (this._settings.appfolder.BLUR)
this._appfolder_blur.set_brightness(
this._settings.appfolder.BRIGHTNESS
);
});
// appfolder dialogs style changed
this._settings.appfolder.STYLE_DIALOGS_changed(() => {
if (this._settings.appfolder.BLUR)
this._appfolder_blur.blur_appfolders();
});
// ---------- PANEL ----------
// toggled on/off
this._settings.panel.BLUR_changed(() => {
if (this._settings.panel.BLUR)
this._panel_blur.enable();
else
this._panel_blur.disable();
});
// static blur toggled on/off, really we can just reload the blur at this point
this._settings.panel.STATIC_BLUR_changed(() => {
if (this._settings.panel.BLUR)
this._panel_blur.reset();
});
// panel pipeline changed
this._settings.panel.PIPELINE_changed(() => {
if (this._settings.panel.BLUR)
this._panel_blur.update_pipeline();
});
// panel blur's overview connection toggled on/off
this._settings.panel.UNBLUR_IN_OVERVIEW_changed(() => {
this._panel_blur.connect_to_windows_and_overview();
});
// force light text toggled on/off
this._settings.panel.FORCE_LIGHT_TEXT_changed(() => {
if (this._settings.panel.BLUR)
this._panel_blur.update_light_text_classname();
});
// panel override background toggled on/off
this._settings.panel.OVERRIDE_BACKGROUND_changed(() => {
if (this._settings.panel.BLUR)
this._panel_blur.connect_to_windows_and_overview();
});
// panel style changed
this._settings.panel.STYLE_PANEL_changed(() => {
if (this._settings.panel.BLUR)
this._panel_blur.connect_to_windows_and_overview();
});
// panel background's dynamic overriding toggled on/off
this._settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY_changed(() => {
if (this._settings.panel.BLUR)
this._panel_blur.connect_to_windows_and_overview();
});
// ---------- DASH TO DOCK ----------
// toggled on/off
this._settings.dash_to_dock.BLUR_changed(() => {
if (this._settings.dash_to_dock.BLUR)
this._dash_to_dock_blur.enable();
else
this._dash_to_dock_blur.disable();
});
// static blur toggled on/off
this._settings.dash_to_dock.STATIC_BLUR_changed(() => {
if (this._settings.dash_to_dock.BLUR)
this._dash_to_dock_blur.change_blur_type();
});
// overview pipeline changed
this._settings.dash_to_dock.PIPELINE_changed(() => {
if (this._settings.dash_to_dock.BLUR)
this._dash_to_dock_blur.update_pipeline();
});
// dash-to-dock override background toggled on/off
this._settings.dash_to_dock.OVERRIDE_BACKGROUND_changed(() => {
if (this._settings.dash_to_dock.BLUR)
this._dash_to_dock_blur.update_background();
});
// dash-to-dock style changed
this._settings.dash_to_dock.STYLE_DASH_TO_DOCK_changed(() => {
if (this._settings.dash_to_dock.BLUR)
this._dash_to_dock_blur.update_background();
});
// dash-to-dock blur's overview connection toggled on/off
this._settings.dash_to_dock.UNBLUR_IN_OVERVIEW_changed(() => {
if (this._settings.dash_to_dock.BLUR)
this._dash_to_dock_blur.connect_to_overview();
});
// ---------- APPLICATIONS ----------
// toggled on/off
this._settings.applications.BLUR_changed(() => {
if (this._settings.applications.BLUR)
this._applications_blur.enable();
else
this._applications_blur.disable();
});
// application opacity changed
this._settings.applications.OPACITY_changed(() => {
if (this._settings.applications.BLUR)
this._applications_blur.set_opacity(
this._settings.applications.OPACITY
);
});
// application dynamic-opacity changed
this._settings.applications.DYNAMIC_OPACITY_changed(() => {
if (this._settings.applications.BLUR)
this._applications_blur.init_dynamic_opacity();
});
// application blur-on-overview changed
this._settings.applications.BLUR_ON_OVERVIEW_changed(() => {
if (this._settings.applications.BLUR)
this._applications_blur.connect_to_overview();
});
// application enable-all changed
this._settings.applications.ENABLE_ALL_changed(() => {
if (this._settings.applications.BLUR)
this._applications_blur.update_all_windows();
});
// application whitelist changed
this._settings.applications.WHITELIST_changed(() => {
if (
this._settings.applications.BLUR
&& !this._settings.applications.ENABLE_ALL
)
this._applications_blur.update_all_windows();
});
// application blacklist changed
this._settings.applications.BLACKLIST_changed(() => {
if (
this._settings.applications.BLUR
&& this._settings.applications.ENABLE_ALL
)
this._applications_blur.update_all_windows();
});
// ---------- LOCKSCREEN ----------
// toggled on/off
this._settings.lockscreen.BLUR_changed(() => {
if (this._settings.lockscreen.BLUR)
this._lockscreen_blur.enable();
else
this._lockscreen_blur.disable();
});
// lockscreen pipeline changed
this._settings.lockscreen.PIPELINE_changed(() => {
if (this._settings.lockscreen.BLUR)
this._lockscreen_blur.update_lockscreen();
});
// ---------- WINDOW LIST ----------
// toggled on/off
this._settings.window_list.BLUR_changed(() => {
if (this._settings.window_list.BLUR)
this._window_list_blur.enable();
else
this._window_list_blur.disable();
});
// ---------- HIDETOPBAR ----------
// toggled on/off
this._settings.hidetopbar.COMPATIBILITY_changed(() => {
// no need to verify if it is enabled or not, it is done anyway
this._panel_blur.connect_to_windows_and_overview();
});
// ---------- DASH TO PANEL ----------
// toggled on/off
this._settings.dash_to_panel.BLUR_ORIGINAL_PANEL_changed(() => {
if (this._settings.panel.BLUR)
this._panel_blur.reset();
});
// ---------- SCREENSHOT ----------
// toggled on/off
this._settings.screenshot.BLUR_changed(() => {
if (this._settings.screenshot.BLUR)
this._screenshot_blur.enable();
else
this._screenshot_blur.disable();
});
// screenshot pipeline changed
this._settings.screenshot.PIPELINE_changed(() => {
if (this._settings.screenshot.BLUR)
this._screenshot_blur.update_pipeline();
});
}
_log(str) {
if (this._settings.DEBUG)
console.log(`[Blur my Shell > extension] ${str}`);
}
}

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" enable-background="new" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="7" width="11" height="1" fill="#363636"/>
<rect transform="rotate(90)" x="2" y="-8" width="11" height="1" fill="#363636"/>
</svg>

After

Width:  |  Height:  |  Size: 249 B

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
id="svg7"
sodipodi:docname="applications.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="44.6875"
inkscape:cx="5.5944056"
inkscape:cy="8"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path
d="M 2,1 C 0.892,1 0,1.892 0,3 v 9 c 0,1.108 0.892,2 2,2 h 12 c 1.108,0 2,-0.892 2,-2 V 3 C 16,1.892 15.108,1 14,1 Z m 0,1 h 12 c 0.554,0 1,0.446 1,1 H 1 C 1,2.446 1.446,2 2,2 Z M 1,3 h 14 v 9 c 0,0.554 -0.446,1 -1,1 H 2 C 1.446,13 1,12.554 1,12 Z"
style="fill:currentColor"
class="ColorScheme-Text"
id="path5"
sodipodi:nodetypes="sssssssssssccsccssssc" />
<g
id="g68"
transform="translate(0.3993007,0.15314685)">
<path
d="M 9.5,4 C 9.223,4 9,4.223 9,4.5 v 2 A 0.499,0.499 0 0 0 9.5,7 h 2 C 11.777,7 12,6.777 12,6.5 v -2 C 12,4.223 11.777,4 11.5,4 Z M 10,5 h 1 v 1 h -1 z"
fill="#363636"
id="path2" />
<path
d="M 4,6 C 3.446,6 3,6.446 3,7 v 3 c 0,0.554 0.446,1 1,1 h 3 c 0.554,0 1,-0.446 1,-1 V 7 C 8,6.446 7.554,6 7,6 Z m 0,1 h 3 v 3 H 4 Z"
fill="#363636"
id="path8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
id="svg7"
sodipodi:docname="panel.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="44.6875"
inkscape:cx="5.5944056"
inkscape:cy="8"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path
d="M 2,1 C 0.892,1 0,1.892 0,3 v 9 c 0,1.108 0.892,2 2,2 h 12 c 1.108,0 2,-0.892 2,-2 V 3 C 16,1.892 15.108,1 14,1 Z m 0,1 h 12 c 0.554,0 1,0.446 1,1 H 1 C 1,2.446 1.446,2 2,2 Z M 1,4 h 14 v 8 c 0,0.554 -0.446,1 -1,1 H 2 C 1.446,13 1,12.554 1,12 Z"
style="fill:currentColor"
class="ColorScheme-Text"
id="path5"
sodipodi:nodetypes="sssssssssssccsccssssc" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
id="svg7"
sodipodi:docname="top-panel.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="44.6875"
inkscape:cx="5.5944056"
inkscape:cy="8"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path
d="M 2,1 C 0.892,1 0,1.892 0,3 v 9 c 0,1.108 0.892,2 2,2 h 12 c 1.108,0 2,-0.892 2,-2 V 3 C 16,1.892 15.108,1 14,1 Z m 0,1 h 12 c 0.554,0 1,0.446 1,1 H 1 C 1,2.446 1.446,2 2,2 Z M 1,3 h 14 v 9 c 0,0.554 -0.446,1 -1,1 H 2 C 1.446,13 1,12.554 1,12 Z m 3,7 c -0.554,0 -1,0.446 -1,1 0,0.554 0.446,1 1,1 h 8 c 0.554,0 1,-0.446 1,-1 0,-0.554 -0.446,-1 -1,-1 z"
style="fill:currentColor"
class="ColorScheme-Text"
id="path5"
sodipodi:nodetypes="sssssssssssccsccsssscsssssss" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M7 5c-1.108 0-2 .892-2 2v7c0 1.108.892 2 2 2h7c1.108 0 2-.892 2-2V7c0-1.108-.892-2-2-2Zm0 1h7c.554 0 1 .446 1 1v7c0 .554-.446 1-1 1H7c-.554 0-1-.446-1-1V7c0-.554.446-1 1-1Z" style="fill:#dedede;stroke:none;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers;stop-color:#000"/><path d="M2 0C.892 0 0 .892 0 2v7c0 1.108.892 2 2 2h2v-1H2c-.554 0-1-.446-1-1V2c0-.554.446-1 1-1h7c.554 0 1 .446 1 1v2h1V2c0-1.108-.892-2-2-2Z" style="fill:#dedede;stroke:none;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers;stop-color:#000"/></svg>

After

Width:  |  Height:  |  Size: 646 B

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
id="svg5"
sodipodi:docname="dynamic-mode-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5" />
<sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="44.75"
inkscape:cx="7.9888268"
inkscape:cy="8"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg5"
showguides="true" />
<path
d="m 11.5,11.34375 a 5.75,5.75 0 0 1 -1,0.134766 v 2.234375 C 10.5,14.425813 9.9258135,15 9.2128906,15 H 2.2871094 C 1.5741865,15 1,14.425813 1,13.712891 V 6.7871094 C 1,6.0741865 1.5741865,5.5 2.2871094,5.5 h 2.234375 A 5.75,5.75 0 0 1 4.65625,4.5 H 1.5585937 C 0.69558189,4.5 0,5.1955819 0,6.0585937 V 14.441406 C 0,15.304418 0.69558189,16 1.5585937,16 H 9.9414063 C 10.804418,16 11.5,15.304418 11.5,14.441406 Z"
style="opacity:1;fill:#dedede;stroke-linejoin:round"
id="path14" />
<path
d="M 10.5,11.478516 A 5.75,5.75 0 0 1 10.25,11.5 5.75,5.75 0 0 1 4.5,5.75 5.75,5.75 0 0 1 4.5214844,5.5 H 2.2871094 C 1.5741865,5.5 1,6.0741865 1,6.7871094 V 13.712891 C 1,14.425813 1.5741865,15 2.2871094,15 H 9.2128906 C 9.9258135,15 10.5,14.425813 10.5,13.712891 Z"
style="opacity:0.25;fill:#dedede;stroke-width:0.826087;stroke-linejoin:round"
id="path15" />
<path
d="m 11.5,11.34375 v -1.029297 a 4.75,4.75 0 0 1 -1,0.164063 v 1 a 5.75,5.75 0 0 0 1,-0.134766 z"
style="opacity:1;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.25351;stroke-linejoin:round;-inkscape-stroke:hairline"
id="path22" />
<path
d="M 10.25,0 A 5.75,5.75 0 0 0 4.65625,4.5 H 5.6855469 A 4.75,4.75 0 0 1 10.25,1 4.75,4.75 0 0 1 15,5.75 4.75,4.75 0 0 1 11.5,10.314453 V 11.34375 A 5.75,5.75 0 0 0 16,5.75 5.75,5.75 0 0 0 10.25,0 Z"
style="opacity:1;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.25351;stroke-linejoin:round;-inkscape-stroke:hairline"
id="path18" />
<path
d="m 4.5214844,5.5 h 1 a 4.75,4.75 0 0 1 0.1640625,-1 H 4.65625 a 5.75,5.75 0 0 0 -0.1347656,1 z"
style="opacity:1;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.25351;stroke-linejoin:round;-inkscape-stroke:hairline"
id="path17" />
<path
d="m 10.5,11.478516 v -1 A 4.75,4.75 0 0 1 10.25,10.5 4.75,4.75 0 0 1 5.5,5.75 4.75,4.75 0 0 1 5.5214844,5.5 h -1 A 5.75,5.75 0 0 0 4.5,5.75 a 5.75,5.75 0 0 0 5.75,5.75 5.75,5.75 0 0 0 0.25,-0.02148 z"
style="opacity:1;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.25351;stroke-linejoin:round;-inkscape-stroke:hairline"
id="path16" />
<path
d="M 10.5,10.478516 V 6.7871094 C 10.5,6.0741865 9.9258135,5.5 9.2128906,5.5 H 5.5214844 A 4.75,4.75 0 0 0 5.5,5.75 a 4.75,4.75 0 0 0 4.75,4.75 4.75,4.75 0 0 0 0.25,-0.02148 z"
style="opacity:0.125;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.03551;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1"
id="path21" />
<path
d="m 5.6855469,4.5 a 4.75,4.75 0 0 0 -0.1640625,1 H 9.2128906 C 9.9258135,5.5 10.5,6.0741865 10.5,6.7871094 v 3.6914066 a 4.75,4.75 0 0 0 1,-0.164063 V 6.0585937 C 11.5,5.1955819 10.804418,4.5 9.9414063,4.5 Z"
style="opacity:0.5;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.03551;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1"
id="path20" />
<path
d="M 5.6855469,4.5 H 9.9414063 C 10.804418,4.5 11.5,5.1955819 11.5,6.0585937 V 10.314453 A 4.75,4.75 0 0 0 15,5.75 4.75,4.75 0 0 0 10.25,1 4.75,4.75 0 0 0 5.6855469,4.5 Z"
style="opacity:0;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.03551;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1"
id="path19" />
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<path d="m4 1c-1.108 0-2 0.892-2 2v10c0 1.108 0.892 2 2 2h8c1.108 0 2-0.892 2-2v-5.0488l-1 1v4.0488c0 0.554-0.446 1-1 1h-8c-0.554 0-1-0.446-1-1v-10c0-0.554 0.446-1 1-1h6.0488l1-1z" fill="#dedede"/>
<path class="ColorScheme-Text" d="m14.607 1.6863c-0.78347-0.78347-2.045-0.78347-2.8284 0l3.5355 3.5355c0.78347-0.78347 0.78348-2.045 0-2.8284zm-3.5355 0.70711-7.0711 7.0711-6e-7 3.5355h3.5355l7.0711-7.0711zm0 1.4142 2.1213 2.1213-5.6569 5.6569-2.1213-2.1213z" fill="#dedede"/>
</svg>

After

Width:  |  Height:  |  Size: 959 B

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 5.074219 1.9375 c -3.136719 -0.3125 -5.875 3.914062 -2.816407 6.613281 l 5.742188 5.117188 l 5.746094 -5.117188 c 2.585937 -2.238281 1.070312 -6.492187 -2.351563 -6.585937 c -0.972656 -0.027344 -1.921875 0.324218 -2.636719 0.984375 l -0.757812 0.675781 l -0.753906 -0.675781 c -0.703125 -0.628907 -1.449219 -0.941407 -2.171875 -1.011719 z m -0.097657 0.988281 c 0.507813 0.046875 1.039063 0.265625 1.597657 0.769531 h 0.003906 l 1.421875 1.269532 l 1.425781 -1.273438 l 0.007813 -0.007812 c 0.523437 -0.480469 1.21875 -0.738282 1.933594 -0.71875 c 1.300781 0.035156 2.15625 0.816406 2.515624 1.824218 c 0.355469 1.007813 0.191407 2.15625 -0.792968 3.003907 l -0.003906 0.007812 l -5.085938 4.527344 l -5.078125 -4.527344 h -0.003906 c -0.753907 -0.664062 -0.992188 -1.332031 -0.996094 -2 c 0 -0.667969 0.285156 -1.351562 0.761719 -1.890625 c 0.480468 -0.535156 1.128906 -0.898437 1.789062 -0.976562 c 0.167969 -0.015625 0.335938 -0.019532 0.503906 -0.007813 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" enable-background="new"><defs><filter id="a" color-interpolation-filters="sRGB"><feBlend mode="darken" in2="BackgroundImage"/></filter></defs><g transform="matrix(0 -1 -1 0 -413 -83.997)" color="#000" fill="#dedede"><rect y="-418" x="-92.997" width="2" style="marker:none" ry="1" rx="1" height="2" overflow="visible" enable-background="new"/><rect y="-422" x="-92.997" width="2" style="marker:none" ry="1" rx="1" height="2" overflow="visible" enable-background="new"/><rect y="-426" x="-92.997" width="2" style="marker:none" ry="1" rx="1" height="2" overflow="visible" enable-background="new"/></g></svg>

After

Width:  |  Height:  |  Size: 667 B

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
id="svg7"
sodipodi:docname="overview.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="44.6875"
inkscape:cx="5.5944056"
inkscape:cy="8"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g68"
inkscape:snap-bbox="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path
d="M 2,1 C 0.892,1 0,1.892 0,3 v 9 c 0,1.108 0.892,2 2,2 h 12 c 1.108,0 2,-0.892 2,-2 V 3 C 16,1.892 15.108,1 14,1 Z m 0,1 h 12 c 0.554,0 1,0.446 1,1 H 1 C 1,2.446 1.446,2 2,2 Z M 1,3 h 14 v 9 c 0,0.554 -0.446,1 -1,1 H 2 C 1.446,13 1,12.554 1,12 Z"
style="fill:currentColor"
class="ColorScheme-Text"
id="path5"
sodipodi:nodetypes="sssssssssssccsccssssc" />
<g
id="g68"
transform="translate(0.3993007,0.15314685)">
<path
d="m 3.1756095,3.5602457 c -0.8440582,0 -1,0.446 -1,1 v 5.5732153 c 0,0.677136 0.446,1 1,1 H 12.02579 c 0.554,0 0.999999,-0.446 0.999999,-1 V 4.5602457 c 0,-0.554 -0.445999,-1 -0.999999,-1 z m 0,1 H 12.02579 V 10.133461 H 3.1756095 Z"
fill="#363636"
id="path8"
sodipodi:nodetypes="sssssssssccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
enable-background="new"
version="1.1"
id="svg16"
sodipodi:docname="pipelines-symbolic.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs20" />
<sodipodi:namedview
id="namedview18"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="24.174578"
inkscape:cx="13.98163"
inkscape:cy="8.3765683"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g14" />
<g
fill="#363636"
id="g14"
transform="rotate(90,7.75,8.25)">
<path
d="m 2.9500144,1 c -0.277,0 -0.5,0.223 -0.5,0.5 v 6.5547 a 2.5,2.5 0 0 1 0.5,-0.054688 2.5,2.5 0 0 1 0.5,0.050781 v -6.5508 c 0,-0.277 -0.223,-0.5 -0.5,-0.5 z m 0.5,11.945 a 2.5,2.5 0 0 1 -0.5,0.05469 2.5,2.5 0 0 1 -0.5,-0.05078 v 1.5508 c 0,0.277 0.223,0.5 0.5,0.5 0.277,0 0.5,-0.223 0.5,-0.5 v -1.5547 z"
id="path2" />
<path
d="M 7.5,1 C 7.223,1 7,1.223 7,1.5 V 3.0547 A 2.5,2.5 0 0 1 7.5,3.000012 2.5,2.5 0 0 1 8,3.050793 v -1.5508 c 0,-0.277 -0.223,-0.5 -0.5,-0.5 z M 8,7.9453 A 2.5,2.5 0 0 1 7.5,7.999988 2.5,2.5 0 0 1 7,7.949207 v 6.5508 c 0,0.277 0.223,0.5 0.5,0.5 0.277,0 0.5,-0.223 0.5,-0.5 v -6.5547 z"
id="path4" />
<path
d="m 12.049986,1.0001482 c -0.277,0 -0.5,0.223 -0.5,0.5 v 6.5547 a 2.5,2.5 0 0 1 0.5,-0.054688 2.5,2.5 0 0 1 0.5,0.050781 v -6.5508 c 0,-0.277 -0.223,-0.5 -0.5,-0.5 z m 0.5,11.9450008 a 2.5,2.5 0 0 1 -0.5,0.05469 2.5,2.5 0 0 1 -0.5,-0.05078 v 1.5508 c 0,0.276999 0.223,0.5 0.5,0.5 0.277,0 0.5,-0.223001 0.5,-0.5 v -1.5547 z"
id="path6" />
<circle
cx="2.9500144"
cy="10.5"
id="circle8"
r="1.5" />
<circle
cx="7.5"
cy="5.5"
r="1.5"
id="circle10" />
<circle
cx="12.049986"
cy="10.500149"
id="circle12"
r="1.5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="m4.4647 3.9648c-0.12775 0-0.2555 0.048567-0.35339 0.14649-0.19578 0.19586-0.19578 0.51116 0 0.70703l3.1816 3.1816-3.1816 3.1816c-0.19578 0.19586-0.19578 0.51116 0 0.70703 0.19578 0.19586 0.51118 0.19586 0.70704 0l3.1816-3.1816 3.1816 3.1816c0.19578 0.19586 0.51114 0.19586 0.70704 0 0.19578-0.19586 0.19578-0.51116 0-0.70703l-3.1816-3.1816 3.1816-3.1816c0.19578-0.19586 0.19578-0.51116 0-0.70703-0.19578-0.19586-0.51118-0.19586-0.70704 0l-3.1816 3.1816-3.1816-3.1816c-0.09789-0.097928-0.22564-0.14649-0.35339-0.14649z" fill="#363636" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9149" style="paint-order:stroke fill markers"/>
</svg>

After

Width:  |  Height:  |  Size: 740 B

View File

@ -0,0 +1,7 @@
<svg width="16" height="16" enable-background="new" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g fill="#363636">
<path d="m8 3a6 6 0 0 0-6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6h-1a5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5z"/>
<path d="m8.1719 0.67188-0.70703 0.70703 2.1211 2.1211-2.1211 2.1211 0.70703 0.70703 2.8281-2.8281-0.70703-0.70703z"/>
<rect x="8" y="3" width="2" height="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 406 B

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -860 -60)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -860 -60)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -860 -60)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g><path d="m 7.292969 1.707031 l 7 7 v -1.414062 l -7 7 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 s 1.023437 0.390625 1.414062 0 l 7 -7 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 l -7 -7 c -0.390625 -0.3906252 -1.023437 -0.3906252 -1.414062 0 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 z m 0 0"/><path d="m 15 7 h -14 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 h 14 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 z m 0 0"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 2.953125 1.074219 l 2.417969 13.210937 l 3.238281 -2.398437 l 2.054687 2.648437 c 1.03125 1.433594 3.148438 -0.210937 2.011719 -1.5625 l -2.015625 -2.59375 l 2.984375 -2.175781 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 346 B

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
id="svg5"
sodipodi:docname="static-mode-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5" />
<sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="44.75"
inkscape:cx="8"
inkscape:cy="8"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg5"
showguides="true" />
<path
d="m 11.5,11.34375 a 5.75,5.75 0 0 1 -1,0.134766 v 2.234375 C 10.5,14.425813 9.9258135,15 9.2128906,15 H 2.2871094 C 1.5741865,15 1,14.425813 1,13.712891 V 6.7871094 C 1,6.0741865 1.5741865,5.5 2.2871094,5.5 h 2.234375 A 5.75,5.75 0 0 1 4.65625,4.5 H 1.5585937 C 0.69558189,4.5 0,5.1955819 0,6.0585937 V 14.441406 C 0,15.304418 0.69558189,16 1.5585937,16 H 9.9414063 C 10.804418,16 11.5,15.304418 11.5,14.441406 Z"
style="opacity:1;fill:#dedede;stroke-linejoin:round"
id="path14" />
<path
d="M 10.5,11.478516 A 5.75,5.75 0 0 1 10.25,11.5 5.75,5.75 0 0 1 4.5,5.75 5.75,5.75 0 0 1 4.5214844,5.5 H 2.2871094 C 1.5741865,5.5 1,6.0741865 1,6.7871094 V 13.712891 C 1,14.425813 1.5741865,15 2.2871094,15 H 9.2128906 C 9.9258135,15 10.5,14.425813 10.5,13.712891 Z"
style="opacity:0.25;fill:#dedede;stroke-width:0.826087;stroke-linejoin:round"
id="path15" />
<path
d="m 11.5,11.34375 v -1.029297 a 4.75,4.75 0 0 1 -1,0.164063 v 1 a 5.75,5.75 0 0 0 1,-0.134766 z"
style="opacity:1;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.25351;stroke-linejoin:round;-inkscape-stroke:hairline"
id="path22" />
<path
d="M 10.25,0 A 5.75,5.75 0 0 0 4.65625,4.5 H 5.6855469 A 4.75,4.75 0 0 1 10.25,1 4.75,4.75 0 0 1 15,5.75 4.75,4.75 0 0 1 11.5,10.314453 V 11.34375 A 5.75,5.75 0 0 0 16,5.75 5.75,5.75 0 0 0 10.25,0 Z"
style="opacity:1;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.25351;stroke-linejoin:round;-inkscape-stroke:hairline"
id="path18" />
<path
d="m 4.5214844,5.5 h 1 a 4.75,4.75 0 0 1 0.1640625,-1 H 4.65625 a 5.75,5.75 0 0 0 -0.1347656,1 z"
style="opacity:1;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.25351;stroke-linejoin:round;-inkscape-stroke:hairline"
id="path17" />
<path
d="m 10.5,11.478516 v -1 A 4.75,4.75 0 0 1 10.25,10.5 4.75,4.75 0 0 1 5.5,5.75 4.75,4.75 0 0 1 5.5214844,5.5 h -1 A 5.75,5.75 0 0 0 4.5,5.75 a 5.75,5.75 0 0 0 5.75,5.75 5.75,5.75 0 0 0 0.25,-0.02148 z"
style="opacity:1;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.25351;stroke-linejoin:round;-inkscape-stroke:hairline"
id="path16" />
<path
d="M 10.5,10.478516 V 6.7871094 C 10.5,6.0741865 9.9258135,5.5 9.2128906,5.5 H 5.5214844 A 4.75,4.75 0 0 0 5.5,5.75 a 4.75,4.75 0 0 0 4.75,4.75 4.75,4.75 0 0 0 0.25,-0.02148 z"
style="opacity:0;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.03551;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1"
id="path21" />
<path
d="m 5.6855469,4.5 a 4.75,4.75 0 0 0 -0.1640625,1 H 9.2128906 C 9.9258135,5.5 10.5,6.0741865 10.5,6.7871094 v 3.6914066 a 4.75,4.75 0 0 0 1,-0.164063 V 6.0585937 C 11.5,5.1955819 10.804418,4.5 9.9414063,4.5 Z"
style="opacity:0;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.03551;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1"
id="path20" />
<path
d="M 5.6855469,4.5 H 9.9414063 C 10.804418,4.5 11.5,5.1955819 11.5,6.0585937 V 10.314453 A 4.75,4.75 0 0 0 15,5.75 4.75,4.75 0 0 0 10.25,1 4.75,4.75 0 0 0 5.6855469,4.5 Z"
style="opacity:0;vector-effect:non-scaling-stroke;fill:#dedede;stroke-width:1.03551;stroke-linejoin:round;-inkscape-stroke:hairline;fill-opacity:1"
id="path19" />
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,24 @@
{
"_generated": "Generated by SweetTooth, do not edit",
"description": "Adds a blur look to different parts of the GNOME Shell, including the top panel, dash and overview.\n\nYou can support my work by sponsoring me on:\n- github: https://github.com/sponsors/aunetx\n- ko-fi: https://ko-fi.com/aunetx\n\nNote: if the extension shows an error after updating, please make sure to restart your session to see if it persists. This is due to a bug in gnome shell, which I can't fix by myself.",
"donations": {
"github": "aunetx",
"kofi": "aunetx"
},
"gettext-domain": "blur-my-shell@aunetx",
"name": "Blur my Shell",
"original-authors": [
"me@aunetx.dev"
],
"session-modes": [
"unlock-dialog",
"user"
],
"settings-schema": "org.gnome.shell.extensions.blur-my-shell",
"shell-version": [
"46"
],
"url": "https://github.com/aunetx/blur-my-shell",
"uuid": "blur-my-shell@aunetx",
"version": 61
}

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);
}
});

Some files were not shown because too many files have changed in this diff Show More