From 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 Mon Sep 17 00:00:00 2001 From: AlexanderCurl Date: Sat, 18 Apr 2026 17:46:06 +0100 Subject: Replaced file structures for themes in the correct format --- .../conveniences/connections.js | 106 ++++++ .../conveniences/dummy_pipeline.js | 112 +++++++ .../conveniences/effects_manager.js | 90 +++++ .../blur-my-shell@aunetx/conveniences/keys.js | 190 +++++++++++ .../conveniences/paint_signals.js | 91 ++++++ .../blur-my-shell@aunetx/conveniences/pipeline.js | 223 +++++++++++++ .../conveniences/pipelines_manager.js | 168 ++++++++++ .../blur-my-shell@aunetx/conveniences/settings.js | 363 +++++++++++++++++++++ .../conveniences/settings_updater.js | 40 +++ .../blur-my-shell@aunetx/conveniences/utils.js | 38 +++ 10 files changed, 1421 insertions(+) create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/connections.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/dummy_pipeline.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/effects_manager.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/keys.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/paint_signals.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipeline.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipelines_manager.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/settings.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/settings_updater.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/utils.js (limited to 'raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences') diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/connections.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/connections.js new file mode 100644 index 0000000..0583db3 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/connections.js @@ -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}`); + } +}; \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/dummy_pipeline.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/dummy_pipeline.js new file mode 100644 index 0000000..544d68c --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/dummy_pipeline.js @@ -0,0 +1,112 @@ +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 + if (actor) + this.actor = actor; + else { + this.remove_pipeline_from_actor(); + return; + } + + // build the new effect to be added + this.build_effect({ + unscaled_radius: 2 * this.settings.SIGMA, + brightness: this.settings.BRIGHTNESS, + }); + + this.actor_destroy_id = this.actor.connect( + "destroy", () => this.remove_pipeline_from_actor() + ); + + // 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`); + } + + remove_pipeline_from_actor() { + this.remove_effect(); + if (this.actor && this.actor_destroy_id) + this.actor.disconnect(this.actor_destroy_id); + this.actor_destroy_id = null; + this.actor = null; + } + + 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() { + if (this.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.remove_pipeline_from_actor(); + } + + _warn(str) { + console.warn(`[Blur my Shell > dummy pip] ${str}`); + } +}; \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/effects_manager.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/effects_manager.js new file mode 100644 index 0000000..947d535 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/effects_manager.js @@ -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}`); + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/keys.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/keys.js new file mode 100644 index 0000000..fcde72b --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/keys.js @@ -0,0 +1,190 @@ +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.B, name: "static-blur" }, + { type: Type.S, name: "pipeline" }, + { 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: "coverflow-alt-tab", schemas: [ + { type: Type.B, name: "blur" }, + { type: Type.S, name: "pipeline" }, + ] + }, + { + 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" }, + ] + }, +]; \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/paint_signals.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/paint_signals.js new file mode 100644 index 0000000..5fbfee4 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/paint_signals.js @@ -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); + } + } +); \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipeline.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipeline.js new file mode 100644 index 0000000..84b1d7a --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipeline.js @@ -0,0 +1,223 @@ +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`. + /// If `use_absolute_position` is false, then the position used is at (0,0); useful when the + /// positioning is relative. + /// Note: exposed to public API. + create_background_with_effects( + monitor_index, + background_managers, + background_group, + widget_name, + use_absolute_position = true + ) { + let monitor = Main.layoutManager.monitors[monitor_index]; + + // create the new actor + this.actor = new St.Widget({ + name: widget_name, + x: use_absolute_position ? monitor.x : 0, + y: .5 + (use_absolute_position ? monitor.y : 0), // add 1 to correct z-position + z_position: 1, // seems to fix the multi-monitor glitch + 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 + if (actor) + this.actor = actor; + else { + this.remove_pipeline_from_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; + } + + this.actor_destroy_id = this.actor.connect( + "destroy", () => this.remove_pipeline_from_actor() + ); + + // update the effects + this.update_effects_from_pipeline(pipeline); + } + + remove_pipeline_from_actor() { + this.remove_all_effects(); + if (this.actor && this.actor_destroy_id) + this.actor.disconnect(this.actor_destroy_id); + this.actor_destroy_id = null; + this.actor = null; + } + + /// 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.remove_pipeline_from_actor(); + this.pipeline_id = null; + } + + _warn(str) { + console.warn(`[Blur my Shell > pipeline] ${str}`); + } +}; \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipelines_manager.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipelines_manager.js new file mode 100644 index 0000000..0fdfe9e --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipelines_manager.js @@ -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); \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/settings.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/settings.js new file mode 100644 index 0000000..53f8497 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/settings.js @@ -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); \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/settings_updater.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/settings_updater.js new file mode 100644 index 0000000..2affbc1 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/settings_updater.js @@ -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); +} \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/utils.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/utils.js new file mode 100644 index 0000000..0277e0e --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/utils.js @@ -0,0 +1,38 @@ +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; +} + +// In use for the effects, to prevent boilerplate code +export function setup_params(outer_this, params) { + // setup each parameter, either with the given or the default value + for (const params_name in outer_this.constructor.default_params) { + outer_this["_" + params_name] = null; + outer_this[params_name] = params_name in params ? + params[params_name] : + outer_this.constructor.default_params[params_name]; + } +}; + +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; + } +}; \ No newline at end of file -- cgit v1.3