diff options
| author | Nippy <nippy@Nippy-Mac-mini.local> | 2026-10-09 17:23:53 +0200 |
|---|---|---|
| committer | Nippy <nippy@Nippy-Mac-mini.local> | 2026-10-09 17:23:53 +0200 |
| commit | 9d5be1660d8f74038ebed3d74fdbc809e43787ae (patch) | |
| tree | 4cb46c3765be87ca4c5219f2f714425d7dfddb56 /raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences | |
| parent | deb4b8a9c407832c35100a643bd7a592589c9679 (diff) | |
| download | RaveOS-PKGBUILD-9d5be1660d8f74038ebed3d74fdbc809e43787ae.tar.gz RaveOS-PKGBUILD-9d5be1660d8f74038ebed3d74fdbc809e43787ae.zip | |
gnome fix
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences')
11 files changed, 732 insertions, 391 deletions
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 index 0583db3..8d17b62 100644 --- 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 @@ -1,106 +1,98 @@ import GObject from 'gi://GObject'; -/// An object to easily manage signals. +function supports_destroy_signal(object) { + const gtype = object?.constructor?.$gtype; + return gtype && GObject.signal_lookup('destroy', gtype) !== 0; +} + export const Connections = class Connections { constructor() { - this.buffer = []; + this.records = new Map(); } - /// 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); - } + connect(object, signals, handler) { + const names = Array.isArray(signals) ? signals : [signals]; + const record = this.get_record(object); + const ids = []; + for (const signal of names) { + const id = object.connect(signal, handler); + record.ids.add(id); + ids.push(id); + } + return Array.isArray(signals) ? ids : ids[0]; } - /// 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 - }; + get_record(object) { + let record = this.records.get(object); + if (record) + return record; - // 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); + record = { + ids: new Set(), + destroy_id: null, + }; + this.records.set(object, record); - let index = this.buffer.indexOf(infos); - if (index >= 0) { - this.buffer.splice(index, 1); - } + if (supports_destroy_signal(object)) + record.destroy_id = object.connect('destroy', () => { + this.records.delete(object); + record.ids.clear(); + record.destroy_id = null; }); - infos.destroy_id = destroy_id; - } - this.buffer.push(infos); + return record; } - /// 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 - ); + disconnect_all_for(object) { + const record = this.records.get(object); + if (!record) + return; - // 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); - }); + this.records.delete(object); + for (const id of record.ids) + this.raw_disconnect(object, id); + if (record.destroy_id) + this.raw_disconnect(object, record.destroy_id); } - /// 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`); - } - }); + for (const object of [...this.records.keys()]) + this.disconnect_all_for(object); + } - // reset buffer - this.buffer = []; + disconnect(object, id) { + const record = this.records.get(object); + if (record) + record.ids.delete(id); + + this.raw_disconnect(object, id); + + if (record && record.ids.size === 0) { + this.records.delete(object); + if (record.destroy_id) + this.raw_disconnect(object, record.destroy_id); + } + } + + raw_disconnect(object, id) { + if (!id) + return; + + try { + if ( + object instanceof GObject.Object + && !GObject.signal_handler_is_connected(object, id) + ) + return; + + object.disconnect(id); + } catch (error) { + this._warn(`error removing connection: ${error}; continuing`); + } } _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 index 02b2d13..4d71d46 100644 --- 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 @@ -4,9 +4,12 @@ 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) { + constructor(effects_manager, settings, actor = null, options = {}) { this.effects_manager = effects_manager; this.settings = settings; + this.corner_radius_key = options.corner_radius_key ?? 'corner-radius'; + this.corner_radius_getter = options.corner_radius_getter ?? (() => this.settings.CORNER_RADIUS); + this.opacity_factor = 1; this.effect = null; this.attach_effect_to_actor(actor); } @@ -41,9 +44,9 @@ export const DummyPipeline = class DummyPipeline { // build the new effect to be added this.build_effect({ - unscaled_radius: 2 * this.settings.SIGMA, - brightness: this.settings.BRIGHTNESS, - corner_radius: this.settings.CORNER_RADIUS, + unscaled_radius: this.get_unscaled_radius(), + brightness: this.get_brightness(), + unscaled_corner_radius: this.corner_radius_getter(), }); this.actor_destroy_id = this.actor.connect( @@ -70,17 +73,40 @@ export const DummyPipeline = class DummyPipeline { 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 - ); + this._sigma_changed_id = this.settings.settings.connect('changed::sigma', () => this.update_effect()); + this._brightness_changed_id = this.settings.settings.connect('changed::brightness', () => this.update_effect()); this._corner_radius_changed_id = this.settings.settings.connect( - 'changed::corner-radius', () => this.effect.corner_radius = this.settings.CORNER_RADIUS + `changed::${this.corner_radius_key}`, + () => this.update_effect() ); } + set_opacity_factor(opacity_factor) { + opacity_factor = Math.max(0, Math.min(1, opacity_factor)); + if (this.opacity_factor === opacity_factor) + return; + + this.opacity_factor = opacity_factor; + this.update_effect(); + } + + update_effect() { + if (!this.effect) + return; + + this.effect.unscaled_radius = this.get_unscaled_radius(); + this.effect.brightness = this.get_brightness(); + this.effect.unscaled_corner_radius = this.corner_radius_getter(); + } + + get_unscaled_radius() { + return 2 * this.settings.SIGMA * this.opacity_factor; + } + + get_brightness() { + return 1 - (1 - this.settings.BRIGHTNESS) * this.opacity_factor; + } + repaint_effect() { this.effect?.queue_repaint(); } @@ -109,7 +135,6 @@ export const DummyPipeline = class DummyPipeline { /// Note: exposed to public API. destroy() { - this.remove_effect(); this.remove_pipeline_from_actor(); } 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 index 947d535..8b99396 100644 --- 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 @@ -4,7 +4,7 @@ import { get_supported_effects } from '../effects/effects.js'; export const EffectsManager = class EffectsManager { constructor(connections) { this.connections = connections; - this.used = []; + this.used = new Set(); this.SUPPORTED_EFFECTS = get_supported_effects(); Object.keys(this.SUPPORTED_EFFECTS).forEach(effect_name => { @@ -15,60 +15,56 @@ export const EffectsManager = class EffectsManager { 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 = this[effect_name + '_effects'].pop(); effect.set({ ...this.SUPPORTED_EFFECTS[effect_name].class.default_params, ...params }); - } else + } else { effect = new this.SUPPORTED_EFFECTS[effect_name].class({ ...this.SUPPORTED_EFFECTS[effect_name].class.default_params, ...params }); + this.connect_to_destroy(effect); + } - this.used.push(effect); - this.connect_to_destroy(effect); + this.used.add(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(); + const update_actor = () => { + const actor = effect.get_actor(); + if (actor === effect._bms_actor) + return; - if (effect.old_actor && actor != effect.old_actor) - effect.old_actor.disconnect(effect.old_actor_id); + this.disconnect_actor_destroy(effect); + effect._bms_actor = actor; + if (actor) + effect._bms_actor_destroy_id = actor.connect( + 'destroy', () => this.remove(effect, true) + ); + }; - if (actor && actor != effect.old_actor) { - effect.old_actor_id = actor.connect('destroy', _ => { - this.remove(effect, true); - }); - } - }); + this.connections.connect(effect, 'notify::actor', update_actor); + update_actor(); } - // 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; + disconnect_actor_destroy(effect) { + if (effect._bms_actor && effect._bms_actor_destroy_id) { + effect._bms_actor.disconnect(effect._bms_actor_destroy_id); + } + effect._bms_actor = null; + effect._bms_actor_destroy_id = null; + } - let index = this.used.indexOf(effect); - if (index >= 0) { - this.used.splice(index, 1); + remove(effect) { + const actor = effect.get_actor(); + if (actor) + actor.remove_effect(effect); + this.disconnect_actor_destroy(effect); + if (this.used.delete(effect)) { Object.keys(this.SUPPORTED_EFFECTS).forEach(effect_name => { if (effect instanceof this.SUPPORTED_EFFECTS[effect_name].class) this[effect_name + '_effects'].push(effect); 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 index d821b88..0876d16 100644 --- 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 @@ -38,6 +38,9 @@ export const KEYS = [ { type: Type.B, name: "override-background" }, { type: Type.I, name: "style-panel" }, { type: Type.B, name: "override-background-dynamically" }, + { type: Type.I, name: "override-background-dynamically-mode" }, + { type: Type.B, name: "gradient-panel" }, + { type: Type.I, name: "gradient-panel-mode" }, ] }, { @@ -65,6 +68,7 @@ export const KEYS = [ { type: Type.I, name: "opacity" }, { type: Type.B, name: "dynamic-opacity" }, { type: Type.B, name: "blur-on-overview" }, + { type: Type.B, name: "unblur-when-fullscreen" }, { type: Type.B, name: "enable-all" }, { type: Type.AS, name: "whitelist" }, { type: Type.AS, name: "blacklist" }, @@ -97,6 +101,25 @@ export const KEYS = [ ] }, { + component: "popup", 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: "corner-radius" }, + { type: Type.I, name: "menu-corner-radius" }, + { type: Type.I, name: "quick-settings-corner-radius" }, + { type: Type.I, name: "notification-corner-radius" }, + { type: Type.I, name: "osd-corner-radius" }, + { type: Type.I, name: "dialog-corner-radius" }, + { type: Type.I, name: "osk-corner-radius" }, + { type: Type.B, name: "override-background" }, + { type: Type.B, name: "preserve-shell-theme" }, + { type: Type.I, name: "style-popup" }, + ] + }, + { component: "hidetopbar", schemas: [ { type: Type.B, name: "compatibility" }, ] 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 index 5fbfee4..10a6d26 100644 --- 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 @@ -1,91 +1,64 @@ import GObject from 'gi://GObject'; import Clutter from 'gi://Clutter'; +const PAINTS_BETWEEN_REPAINTS = 2; export const PaintSignals = class PaintSignals { constructor(connections) { - this.buffer = []; + this.entries = new Map(); this.connections = connections; } connect(actor, blur_effect) { - let paint_effect = new EmitPaintSignal(); - let infos = { - actor: actor, - paint_effect: paint_effect - }; - let counter = 0; + this.disconnect_all_for_actor(actor); - 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) { } - }); + let paints_to_skip = 0; + const paint_effect = new PaintCallbackEffect(); + paint_effect.set_callback(() => { + if (paints_to_skip > 0) { + paints_to_skip--; + return; + } - // 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); - } - }); - }); + paints_to_skip = PAINTS_BETWEEN_REPAINTS; + blur_effect.queue_repaint(); + }); - this.buffer.push(infos); + actor.add_effect(paint_effect); + const destroy_id = this.connections.connect(actor, 'destroy', () => { + paint_effect.set_callback(null); + this.entries.delete(actor); + }); + this.entries.set(actor, { paint_effect, destroy_id }); } 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); + const entry = this.entries.get(actor); + if (!entry) + return; - // remove from buffer - let index = this.buffer.indexOf(infos); - this.buffer.splice(index, 1); - } - }); + this.entries.delete(actor); + entry.paint_effect.set_callback(null); + this.connections.disconnect(actor, entry.destroy_id); + actor.remove_effect(entry.paint_effect); } disconnect_all() { - this.buffer.forEach(infos => { - this.connections.disconnect_all_for(infos.paint_effect); - infos.actor.remove_effect(infos.paint_effect); - }); - - this.buffer = []; + for (const actor of [...this.entries.keys()]) + this.disconnect_all_for_actor(actor); } }; -export const EmitPaintSignal = GObject.registerClass({ - GTypeName: 'EmitPaintSignal', - Signals: { - 'update-blur': { - param_types: [] - }, - } -}, - class EmitPaintSignal extends Clutter.Effect { +const PaintCallbackEffect = GObject.registerClass( + { GTypeName: 'BmsPaintCallbackEffect' }, + class PaintCallbackEffect extends Clutter.Effect { + set_callback(callback) { + this._callback = callback; + } + vfunc_paint(node, paint_context, paint_flags) { - this.emit("update-blur"); + this._callback(); 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 index 84b1d7a..f52e26f 100644 --- 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 @@ -1,6 +1,9 @@ import St from 'gi://St'; +import Meta from 'gi://Meta'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Background from 'resource:///org/gnome/shell/ui/background.js'; +import * as uniforms from './shader_uniforms.js'; +import * as utils from './utils.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 @@ -18,10 +21,11 @@ import * as Background from 'resource:///org/gnome/shell/ui/background.js'; /// - `'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) { + constructor(effects_manager, pipelines_manager, pipeline_id, actor = null, options = {}) { this.effects_manager = effects_manager; this.pipelines_manager = pipelines_manager; this.effects = []; + this.effect_overrides = options.effect_overrides ?? {}; this.set_pipeline_id(pipeline_id); this.attach_pipeline_to_actor(actor); } @@ -41,19 +45,18 @@ export const Pipeline = class Pipeline { ) { let monitor = Main.layoutManager.monitors[monitor_index]; + this.remove_pipeline_from_actor(); + // 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 + y: utils.subpixel_stage_offset() + (use_absolute_position ? monitor.y : 0), 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); @@ -64,6 +67,17 @@ export const Pipeline = class Pipeline { }); bg_manager._bms_pipeline = this; + // 'controlPosition: false' skips BackgroundManager's default layout pass, which also diables + // sibling re-ordering. + // Without it, new actors render on top while loading, causing a solid color flash through + // on the surface. + this.child_added_id = this.actor.connect( + 'child-added', (container, child) => { + if (child instanceof Meta.BackgroundActor) + container.set_child_below_sibling(child, null); + } + ); + background_managers.push(bg_manager); background_group.insert_child_at_index(this.actor, 0); @@ -104,14 +118,14 @@ export const Pipeline = class Pipeline { /// 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 { + if (!actor) { this.remove_pipeline_from_actor(); return; } + this.disconnect_actor_destroy(); + this.actor = actor; + // attach the pipeline let pipeline = this.pipelines_manager.pipelines[this.pipeline_id]; if (!pipeline) { @@ -134,10 +148,23 @@ export const Pipeline = class Pipeline { remove_pipeline_from_actor() { this.remove_all_effects(); - if (this.actor && this.actor_destroy_id) + this.disconnect_actor_destroy(); + this.disconnect_child_added(); + this.actor = null; + } + + disconnect_actor_destroy() { + if (this.actor && this.actor_destroy_id) { this.actor.disconnect(this.actor_destroy_id); + } this.actor_destroy_id = null; - this.actor = null; + } + + disconnect_child_added() { + if (this.actor && this.child_added_id) { + this.actor.disconnect(this.child_added_id); + } + this.child_added_id = null; } /// Update the effects from the given pipeline object, the hard way. @@ -147,7 +174,9 @@ export const Pipeline = class Pipeline { // build the new effects to be added pipeline.effects.forEach(effect => { - if ('new_' + effect.type + '_effect' in this.effects_manager) + if (effect.type === 'pixelize') + this.build_pixelize_effect(effect); + else 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`); @@ -156,32 +185,143 @@ export const Pipeline = class Pipeline { // add the effects to the actor if (this.actor) - this.effects.forEach(effect => this.actor.add_effect(effect)); + this.effects.forEach(effect => { + this.actor.add_effect(effect); + uniforms.mark_dirty(effect); + }); else this._warn(`could not add effect to actor, actor does not exist anymore`); } + build_pixelize_effect(effect_infos) { + const effect_params = this.get_effect_params(effect_infos); + const effect_overrides = this.get_effect_overrides(effect_infos.type, effect_params); + const params = { + ...effect_params, + ...effect_overrides, + }; + + const downscale = this.effects_manager.new_downscale_effect({ + divider: params.factor, + downsampling_mode: params.downsampling_mode, + opacity_factor: params.opacity_factor, + }); + const upscale = this.effects_manager.new_upscale_effect({ + factor: params.factor, + opacity_factor: params.opacity_factor, + }); + + this.setup_effect(downscale, effect_infos, effect_params, 'downscale'); + this.setup_effect(upscale, effect_infos, effect_params, 'upscale'); + } + /// 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); + const effect_params = this.get_effect_params(effect_infos); + const effect_overrides = this.get_effect_overrides(effect_infos.type, effect_params); + let effect = this.effects_manager['new_' + effect_infos.type + '_effect']({ + ...effect_params, + ...effect_overrides, + }); + this.setup_effect(effect, effect_infos, effect_params); + } + + setup_effect(effect, effect_infos, effect_params, pixelize_role = null) { + effect._bms_effect_type = effect_infos.type; + effect._bms_effect_id = effect_infos.id; + effect._bms_effect_params = effect_params; + effect._bms_pixelize_role = pixelize_role; 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] + this.pipeline_id + '::effect-' + effect_infos.id + '-key-removed', (_, key) => { + const default_value = this.get_effect_default_param(effect, key); + effect._bms_effect_params[key] = default_value; + if (!this.apply_effect_override(effect, key)) + this.set_effect_param(effect, key, default_value); + } ); effect._effect_key_updated_id = this.pipelines_manager.connect( - this.pipeline_id + '::effect-' + effect_infos.id + '-key-updated', - (_, key, value) => effect[key] = value + this.pipeline_id + '::effect-' + effect_infos.id + '-key-updated', (_, key, value) => { + effect._bms_effect_params[key] = value; + if (!this.apply_effect_override(effect, key)) + this.set_effect_param(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 + this.pipeline_id + '::effect-' + effect_infos.id + '-key-added', (_, key, value) => { + effect._bms_effect_params[key] = value; + if (!this.apply_effect_override(effect, key)) + this.set_effect_param(effect, key, value); + } ); } + get_effect_params(effect_infos) { + const effect_class = this.effects_manager.SUPPORTED_EFFECTS[effect_infos.type]?.class; + return { + ...(effect_class?.default_params ?? {}), + ...(effect_infos.params ?? {}), + }; + } + + get_effect_default_param(effect, key) { + return this.effects_manager.SUPPORTED_EFFECTS[effect._bms_effect_type] + ?.class + ?.default_params + ?.[key]; + } + + get_effect_overrides(effect_type, effect_params = {}) { + const overrides = this.effect_overrides[effect_type] ?? {}; + return typeof overrides === 'function' ? overrides(effect_params) : overrides; + } + + apply_effect_override(effect, key) { + const overrides = this.get_effect_overrides(effect._bms_effect_type, effect._bms_effect_params); + if (!(key in overrides)) + return false; + + this.set_effect_param(effect, key, overrides[key]); + return true; + } + + apply_effect_overrides(effect_type = null) { + this.effects.forEach(effect => { + if (effect_type && effect._bms_effect_type !== effect_type) + return; + + const overrides = this.get_effect_overrides(effect._bms_effect_type, effect._bms_effect_params); + Object.keys(overrides).forEach(key => this.set_effect_param(effect, key, overrides[key])); + }); + } + + set_effect_param(effect, key, value) { + if (effect._bms_effect_type !== 'pixelize') { + effect[key] = value; + return; + } + + if (key === 'factor') { + if (effect._bms_pixelize_role === 'downscale') + effect.divider = value; + else if (effect._bms_pixelize_role === 'upscale') + effect.factor = value; + return; + } + + if (key === 'downsampling_mode') { + if (effect._bms_pixelize_role === 'downscale') + effect.downsampling_mode = value; + return; + } + + if (key in effect) + 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() { @@ -197,6 +337,10 @@ export const Pipeline = class Pipeline { delete effect._effect_key_removed_id; delete effect._effect_key_updated_id; delete effect._effect_key_added_id; + delete effect._bms_effect_type; + delete effect._bms_effect_id; + delete effect._bms_effect_params; + delete effect._bms_pixelize_role; }); this.effects = []; } @@ -211,7 +355,6 @@ export const Pipeline = class Pipeline { /// 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; @@ -220,4 +363,4 @@ export const Pipeline = class Pipeline { _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/pipeline_settings.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipeline_settings.js new file mode 100644 index 0000000..eb5c173 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/pipeline_settings.js @@ -0,0 +1,136 @@ +import GLib from 'gi://GLib'; + +function is_plain_object(value) { + return value !== null + && typeof value === 'object' + && value.constructor === Object; +} + +function unpack_string(value, label) { + const unpacked = value?.deep_unpack(); + if (typeof unpacked !== 'string') + throw new Error(`${label} is not a string`); + + return unpacked; +} + +function unpack_effect(effect_variant) { + const effect = effect_variant?.deep_unpack(); + if (!is_plain_object(effect)) + throw new Error('effect is not an object'); + if (!('type' in effect)) + throw new Error('effect has no type'); + if (!('id' in effect)) + throw new Error('effect has no id'); + + const params = 'params' in effect ? effect.params.deep_unpack() : {}; + if (!is_plain_object(params)) + throw new Error('effect params is not an object'); + + return { + type: unpack_string(effect.type, 'effect type'), + id: unpack_string(effect.id, 'effect id'), + params: Object.fromEntries( + Object.entries(params).map(([key, value]) => [key, value.deep_unpack()]) + ), + }; +} + +export function unpack_pipelines(value) { + const unpacked = value.deep_unpack(); + if (!is_plain_object(unpacked)) + throw new Error('pipelines is not an object'); + + const pipelines = {}; + for (const [pipeline_id, pipeline] of Object.entries(unpacked)) { + if (!is_plain_object(pipeline)) + throw new Error(`pipeline ${pipeline_id} is not an object`); + if (!('name' in pipeline)) + throw new Error(`pipeline ${pipeline_id} has no name`); + if (!('effects' in pipeline)) + throw new Error(`pipeline ${pipeline_id} has no effects`); + + const effects = pipeline.effects.deep_unpack(); + if (!Array.isArray(effects)) + throw new Error(`pipeline ${pipeline_id} effects is not an array`); + + pipelines[pipeline_id] = { + name: unpack_string(pipeline.name, `pipeline ${pipeline_id} name`), + effects: effects.map(unpack_effect), + }; + } + + return pipelines; +} + +function pack_param(value, label) { + if (typeof value === 'boolean') + return GLib.Variant.new_boolean(value); + if (typeof value === 'number') + return Number.isInteger(value) + ? GLib.Variant.new_int32(value) + : GLib.Variant.new_double(value); + if (typeof value === 'string') + return GLib.Variant.new_string(value); + if ( + Array.isArray(value) + && value.length === 4 + && value.every(component => typeof component === 'number') + ) + return new GLib.Variant('(dddd)', value); + + throw new Error(`${label} has an unsupported type`); +} + +function pack_effect(effect, label) { + if (!is_plain_object(effect)) + throw new Error(`${label} is not an object`); + if (typeof effect.type !== 'string') + throw new Error(`${label} type is not a string`); + if (typeof effect.id !== 'string') + throw new Error(`${label} id is not a string`); + + const params = effect.params ?? {}; + if (!is_plain_object(params)) + throw new Error(`${label} params is not an object`); + + const packed_params = Object.fromEntries( + Object.entries(params).map(([key, value]) => [ + key, + pack_param(value, `${label} parameter ${key}`), + ]) + ); + + return 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}', packed_params), + }); +} + +export function pack_pipelines(pipelines) { + if (!is_plain_object(pipelines)) + throw new Error('pipelines is not an object'); + + const packed = {}; + for (const [pipeline_id, pipeline] of Object.entries(pipelines)) { + if (!is_plain_object(pipeline)) + throw new Error(`pipeline ${pipeline_id} is not an object`); + if (typeof pipeline.name !== 'string') + throw new Error(`pipeline ${pipeline_id} name is not a string`); + if (!Array.isArray(pipeline.effects)) + throw new Error(`pipeline ${pipeline_id} effects is not an array`); + + packed[pipeline_id] = { + name: GLib.Variant.new_string(pipeline.name), + effects: new GLib.Variant( + 'av', + pipeline.effects.map((effect, index) => + pack_effect(effect, `pipeline ${pipeline_id} effect ${index}`) + ) + ), + }; + } + + return new GLib.Variant('a{sa{sv}}', packed); +} 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 index 0fdfe9e..6d9749a 100644 --- 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 @@ -1,5 +1,23 @@ +import GLib from 'gi://GLib'; + const Signals = imports.signals; +function new_id(prefix) { + return `${prefix}_${GLib.uuid_string_random()}`; +} + +function clone_effect(effect) { + return { + ...effect, + params: Object.fromEntries( + Object.entries(effect.params ?? {}).map(([key, value]) => [ + key, + Array.isArray(value) ? [...value] : value, + ]) + ), + }; +} + /// 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 @@ -36,10 +54,8 @@ export class PipelinesManager { } 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)); + const id = new_id('pipeline'); + effects.forEach(effect => effect.id = new_id('effect')); this.pipelines[id] = { name, effects }; this.settings.PIPELINES = this.pipelines; @@ -54,8 +70,10 @@ export class PipelinesManager { return; } const pipeline = this.pipelines[id]; - this.create_pipeline(pipeline.name + " - duplicate", [...pipeline.effects]); - this.settings.PIPELINES = this.pipelines; + this.create_pipeline( + `${pipeline.name} - duplicate`, + pipeline.effects.map(clone_effect) + ); } delete_pipeline(id) { @@ -89,7 +107,7 @@ export class PipelinesManager { this._warn(`could not rename pipeline, id ${id} does not exist`); return; } - this.pipelines[id].name = name.slice(); + this.pipelines[id].name = name; this.settings.PIPELINES = this.pipelines; this._emit(id + '::pipeline-renamed', name); this._emit('pipeline-names-changed'); @@ -98,8 +116,18 @@ export class PipelinesManager { on_pipeline_update() { const old_pipelines = this.pipelines; this.pipelines = this.settings.PIPELINES; + const old_ids = Object.keys(old_pipelines); + const new_ids = Object.keys(this.pipelines); + const list_changed = old_ids.length !== new_ids.length + || old_ids.some(id => !(id in this.pipelines)); + let names_changed = false; - for (var pipeline_id in old_pipelines) { + for (const pipeline_id of new_ids) { + if (!(pipeline_id in old_pipelines)) + this._emit('pipeline-created', pipeline_id, this.pipelines[pipeline_id]); + } + + for (const pipeline_id of old_ids) { // if we find a pipeline that does not exist anymore, signal it if (!(pipeline_id in this.pipelines)) { this._emit(pipeline_id + '::pipeline-destroyed'); @@ -109,6 +137,11 @@ export class PipelinesManager { const old_pipeline = old_pipelines[pipeline_id]; const new_pipeline = this.pipelines[pipeline_id]; + if (old_pipeline.name !== new_pipeline.name) { + this._emit(pipeline_id + '::pipeline-renamed', new_pipeline.name); + names_changed = true; + } + // verify if both pipelines have effects in the same order // if they have, then check for their parameters if ( @@ -144,6 +177,11 @@ export class PipelinesManager { else this._emit(pipeline_id + '::pipeline-updated', new_pipeline); } + + if (list_changed) + this._emit('pipeline-list-changed'); + if (names_changed) + this._emit('pipeline-names-changed'); } destroy() { @@ -165,4 +203,4 @@ export class PipelinesManager { } } -Signals.addSignalMethods(PipelinesManager.prototype);
\ No newline at end of file +Signals.addSignalMethods(PipelinesManager.prototype); 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 index 53f8497..337c8c2 100644 --- 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 @@ -1,4 +1,5 @@ import GLib from 'gi://GLib'; +import { pack_pipelines, unpack_pipelines } from './pipeline_settings.js'; const Signals = imports.signals; /// An enum non-extensively describing the type of gsettings key. @@ -122,171 +123,20 @@ export const Settings = class Settings { 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; + try { + return unpack_pipelines(component_settings.get_value(key.name)); + } catch (error) { + this._warn(`impossible to get pipelines, resetting: ${error.message}`); + component_settings.reset(key.name); + return unpack_pipelines(component_settings.get_value(key.name)); + } }, - 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); + set(pipelines) { + try { + component_settings.set_value(key.name, pack_pipelines(pipelines)); + } catch (error) { + this._warn(`impossible to set pipelines: ${error.message}`); + } } }); break; @@ -360,4 +210,4 @@ export const Settings = class Settings { } }; -Signals.addSignalMethods(Settings.prototype);
\ No newline at end of file +Signals.addSignalMethods(Settings.prototype); diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/shader_uniforms.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/shader_uniforms.js new file mode 100644 index 0000000..a82b261 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/conveniences/shader_uniforms.js @@ -0,0 +1,61 @@ +import GObject from 'gi://GObject'; + +const INTEGRAL_UNIFORMS = new Set([ + 'corners_bottom', + 'corners_top', + 'divider', + 'dir', + 'downsampling_mode', + 'factor', + 'iterations', + 'mode', + 'operation', + 'prefer_closer_pixels', + 'straight_corners', + 'use_base_pixel', +]); + +export function set_uniform(effect, name, value) { + if (!effect._bms_uniforms) + effect._bms_uniforms = new Map(); + + effect._bms_uniforms.set(name, value); + effect._bms_uniforms_dirty = true; + effect.queue_repaint(); +} + +export function mark_dirty(effect) { + if (effect._bms_uniforms) + effect._bms_uniforms_dirty = true; +} + +export function upload_uniforms(effect) { + if (!effect._bms_uniforms_dirty || !effect._bms_uniforms || !effect.get_actor()) + return; + + for (const [name, value] of effect._bms_uniforms) { + try { + effect.set_uniform_value(name, get_shader_value(name, value)); + } catch (e) { + if (!effect._bms_uniform_warning_shown) { + effect._bms_uniform_warning_shown = true; + console.warn(`[Blur my Shell] shader uniform upload failed for ${effect.constructor.name}`, e); + } + } + } + + effect._bms_uniforms_dirty = false; +} + +function get_shader_value(name, value) { + if (typeof value !== 'number') + return value; + + if (INTEGRAL_UNIFORMS.has(name)) + return Math.trunc(value); + + const float_value = new GObject.Value(); + float_value.init(GObject.TYPE_FLOAT); + float_value.set_float(value); + return float_value; +} 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 index 20def0b..b7bcff3 100644 --- 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 @@ -1,4 +1,5 @@ import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; export const IS_IN_PREFERENCES = typeof global === 'undefined'; @@ -10,12 +11,32 @@ export async function import_in_shell_only(module) { if (IS_IN_PREFERENCES) return null; try { - return (await import(module)).default; + const imported = await import(module); + return imported.default ?? imported; } catch (e) { return null; } } +const Clutter = await import_in_shell_only('gi://Clutter'); +const Cogl = await import_in_shell_only('gi://Cogl'); +const USES_SHADER_SNIPPET_API = + typeof Clutter?.ShaderEffect?.new_with_snippet === 'function'; + +export function is_usable_blur_module(ns) { + if (!ns) + return false; + try { + const { BlurEffect } = ns; + if (typeof BlurEffect !== 'function' || !BlurEffect.$gtype) + return false; + GObject.type_name(BlurEffect.$gtype); + return true; + } catch (e) { + return false; + } +} + // 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 @@ -39,4 +60,87 @@ export const get_shader_source = (Shell, shader_filename, self_uri) => { console.warn(`[Blur my Shell > effect] error loading shader from ${shader_path}: ${e}`); return null; } -};
\ No newline at end of file +}; + +export function subpixel_stage_offset() { + return USES_SHADER_SNIPPET_API ? 0 : 0.5; +} + +export function static_blur_clip_inset() { + return USES_SHADER_SNIPPET_API ? 1 : 0; +} + +export function register_shader_effect(meta, effect_class, source) { + if (USES_SHADER_SNIPPET_API) { + Object.defineProperty(effect_class.prototype, 'vfunc_get_static_snippet', { + value() { + return create_fragment_shader_snippet(source); + }, + }); + } + + return GObject.registerClass(meta, effect_class); +} + +function split_fragment_shader(source) { + const main_index = source.search(/void\s+main\s*(?:\(\s*(?:void)?\s*\))?\s*\{/); + if (main_index < 0) + return null; + + const declarations = source.slice(0, main_index).trim(); + const brace_index = source.indexOf('{', main_index); + if (brace_index < 0) + return null; + + let depth = 0; + for (let i = brace_index; i < source.length; i++) { + if (source[i] === '{') + depth++; + else if (source[i] === '}') { + depth--; + if (depth === 0) { + return { + declarations, + body: source.slice(brace_index + 1, i).trim(), + }; + } + } + } + + return null; +} + +function create_fragment_shader_snippet(source) { + if (!Cogl || !source) + return null; + + const parts = split_fragment_shader(source); + if (!parts) { + console.warn('[Blur my Shell > effect] could not split shader source'); + return null; + } + + try { + const snippet = Cogl.Snippet.new( + Cogl.SnippetHook.FRAGMENT, + parts.declarations, + null + ); + snippet.set_replace(parts.body); + return snippet; + } catch (e) { + console.warn(`[Blur my Shell > effect] could not create shader snippet: ${e}`); + return null; + } +} + +export function initialize_shader_effect(effect, source) { + if (!source || !effect || USES_SHADER_SNIPPET_API) + return; + + try { + effect.set_shader_source(source); + } catch (e) { + console.warn(`[Blur my Shell > effect] set_shader_source failed: ${e}`); + } +} |