diff options
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components')
19 files changed, 4071 insertions, 323 deletions
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/appfolders.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/appfolders.js index 8824777..b04e39d 100644 --- a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/appfolders.js +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/appfolders.js @@ -2,11 +2,8 @@ import Shell from 'gi://Shell'; import Clutter from 'gi://Clutter'; import Cogl from 'gi://Cogl'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; -import { adjustAnimationTime } from 'resource:///org/gnome/shell/misc/animationUtils.js'; import { PaintSignals } from '../conveniences/paint_signals.js'; -// TODO drop Tweener in favour of Clutter's `ease` (will need to extend the blur effect for it) -const Tweener = imports.tweener.tweener; // TODO: Drop GNOME 46 backwards compatibility const transparent = Clutter.Color ? @@ -30,6 +27,26 @@ let original_zoomAndFadeOut = null; let sigma; let brightness; +const BLUR_RADIUS_PROPERTY = '@effects.appfolder-blur.radius'; +const BLUR_BRIGHTNESS_PROPERTY = '@effects.appfolder-blur.brightness'; + +function stop_blur_animation(dialog) { + dialog.remove_transition(BLUR_RADIUS_PROPERTY); + dialog.remove_transition(BLUR_BRIGHTNESS_PROPERTY); +} + +function animate_blur(dialog, target, mode) { + stop_blur_animation(dialog); + dialog.ease_property(BLUR_RADIUS_PROPERTY, target.radius, { + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode, + }); + dialog.ease_property(BLUR_BRIGHTNESS_PROPERTY, target.brightness, { + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode, + }); +} + let _zoomAndFadeIn = function () { let [sourceX, sourceY] = this._source.get_transformed_position(); @@ -50,13 +67,10 @@ let _zoomAndFadeIn = function () { blur_effect.radius = 0; blur_effect.brightness = 1.0; - Tweener.addTween(blur_effect, - { - radius: sigma * 2, - brightness: brightness, - time: adjustAnimationTime(FOLDER_DIALOG_ANIMATION_TIME / 1000), - transition: 'easeOutQuad' - } + animate_blur( + this, + { radius: sigma * 2, brightness }, + Clutter.AnimationMode.EASE_OUT_QUAD ); this.child.ease({ @@ -93,14 +107,10 @@ let _zoomAndFadeOut = function () { this.set_background_color(transparent); - let blur_effect = this.get_effect("appfolder-blur"); - Tweener.addTween(blur_effect, - { - radius: 0, - brightness: 1.0, - time: adjustAnimationTime(FOLDER_DIALOG_ANIMATION_TIME / 1000), - transition: 'easeInQuad' - } + animate_blur( + this, + { radius: 0, brightness: 1.0 }, + Clutter.AnimationMode.EASE_IN_QUAD ); this.child.ease({ @@ -172,15 +182,19 @@ export const AppFoldersBlur = class AppFoldersBlur { original_zoomAndFadeOut = icon._dialog._zoomAndFadeOut; } - let blur_effect = new Shell.BlurEffect({ - name: "appfolder-blur", - radius: sigma * 2, - brightness: brightness, - mode: Shell.BlurMode.BACKGROUND - }); - - icon._dialog.remove_effect_by_name("appfolder-blur"); - icon._dialog.add_effect(blur_effect); + let blur_effect = icon._dialog.get_effect('appfolder-blur'); + if (!blur_effect) { + blur_effect = new Shell.BlurEffect({ + name: 'appfolder-blur', + radius: sigma * 2, + brightness, + mode: Shell.BlurMode.BACKGROUND, + }); + icon._dialog.add_effect(blur_effect); + } else { + stop_blur_animation(icon._dialog); + blur_effect.set({ radius: sigma * 2, brightness }); + } DIALOGS_STYLES.forEach( style => icon._dialog._viewBox.remove_style_class_name(style) @@ -218,19 +232,31 @@ export const AppFoldersBlur = class AppFoldersBlur { set_sigma(s) { sigma = s; if (this.settings.appfolder.BLUR) - this.blur_appfolders(); + this.update_blur_effects({ radius: sigma * 2 }); } set_brightness(b) { brightness = b; if (this.settings.appfolder.BLUR) - this.blur_appfolders(); + this.update_blur_effects({ brightness }); + } + + update_blur_effects(params) { + const appDisplay = Main.overview._overview.controls._appDisplay; + appDisplay._folderIcons.forEach(icon => { + if (!icon._dialog) + return; + + stop_blur_animation(icon._dialog); + icon._dialog.get_effect('appfolder-blur')?.set(params); + }); } disable() { this._log("removing blur from appfolders"); let appDisplay = Main.overview._overview.controls._appDisplay; + this.paint_signals.disconnect_all(); if (original_zoomAndFadeIn != null) { appDisplay._folderIcons.forEach(icon => { @@ -248,6 +274,7 @@ export const AppFoldersBlur = class AppFoldersBlur { appDisplay._folderIcons.forEach(icon => { if (icon._dialog) { + stop_blur_animation(icon._dialog); icon._dialog.remove_effect_by_name("appfolder-blur"); DIALOGS_STYLES.forEach( s => icon._dialog._viewBox.remove_style_class_name(s) @@ -262,4 +289,4 @@ export const AppFoldersBlur = class AppFoldersBlur { if (this.settings.DEBUG) console.log(`[Blur my Shell > appfolders] ${str}`); } -};
\ No newline at end of file +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js index 5280b55..4711a0e 100644 --- a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js @@ -145,34 +145,23 @@ export const ApplicationsBlur = class ApplicationsBlur { /// shown on every window of the workspaces viewer. connect_to_overview() { this.connections.disconnect_all_for(Main.overview); + this.overview_visible = Main.overview.visible; - if (this.settings.applications.BLUR_ON_OVERVIEW) { - // when the overview is opened, show every window actors (which - // allows the blur to be shown too) - this.connections.connect( - Main.overview, 'showing', - _ => this.meta_window_map.forEach((meta_window, _pid) => { - let window_actor = meta_window.get_compositor_private(); - window_actor?.show(); - }) - ); - - // when the overview is closed, hide every actor that is not on the - // current workspace (to mimic the original behaviour) - this.connections.connect( - Main.overview, 'hidden', - _ => { - this.meta_window_map.forEach((meta_window, _pid) => { - let window_actor = meta_window.get_compositor_private(); + const reconcile_windows = () => this.meta_window_map.forEach( + (meta_window, _pid) => this.reconcile_window_visibility(meta_window) + ); + this.connections.connect(Main.overview, 'showing', _ => { + this.overview_visible = true; + reconcile_windows(); + }); + this.connections.connect(Main.overview, 'hidden', _ => { + this.overview_visible = false; + reconcile_windows(); + }); - if ( - (!meta_window.get_workspace().active) || meta_window.minimized - ) - window_actor.hide(); - }); - } - ); - } + this.meta_window_map.forEach((meta_window, _pid) => + this.reconcile_window_visibility(meta_window) + ); } /// Iterate through all existing windows and add blur as needed. @@ -239,14 +228,14 @@ export const ApplicationsBlur = class ApplicationsBlur { if (this.settings.applications.STATIC_BLUR && meta_window.get_client_type() === Meta.WindowClientType.X11) { const window_actor = meta_window.get_compositor_private(); - window_actor.connect('child-added', _ => { - if (!meta_window.blur_actor) { - this._warn("can't move blur actor to back, it doesn't exist"); - return; - } + if (window_actor) { + this.connections.connect(window_actor, 'child-added', _ => { + if (!meta_window.blur_actor) + return; - window_actor.set_child_below_sibling(meta_window.blur_actor, null); - }); + window_actor.set_child_below_sibling(meta_window.blur_actor, null); + }); + } } } @@ -375,19 +364,17 @@ export const ApplicationsBlur = class ApplicationsBlur { meta_window.blur_actor = blur_actor; - // make sure window is blurred in overview - if (this.settings.applications.BLUR_ON_OVERVIEW) - this.enforce_window_visibility_on_overview_for(window_actor); + if (!this.settings.applications.BLUR_ON_OVERVIEW && Main.overview.visible) + blur_actor.hide(); // update the size this.update_size(pid); - // set the window actor's opacity - this.set_window_opacity(window_actor, this.settings.applications.OPACITY); - // update corner radius based on window state this.update_corner_radius(meta_window); + this.reconcile_window_visibility(meta_window); + // now set up the signals, for the window actor only: they are disconnected // in `remove_blur`, whereas the signals for the meta window are disconnected // only when the whole component is disabled @@ -403,15 +390,17 @@ export const ApplicationsBlur = class ApplicationsBlur { ); this.connections.connect( meta_window, 'notify::fullscreen', - _ => this.update_corner_radius(meta_window) + _ => { + this.update_corner_radius(meta_window); + this.reconcile_window_visibility(meta_window); + } ); // update the window opacity when it changes, else we don't control it fully this.connections.connect( window_actor, 'notify::opacity', _ => { - if (this.focused_window_pid != pid) - this.set_window_opacity(window_actor, this.settings.applications.OPACITY); + this.reconcile_window_visibility(meta_window); } ); @@ -422,12 +411,7 @@ export const ApplicationsBlur = class ApplicationsBlur { this.connections.connect( window_actor, 'notify::visible', - window_actor => { - if (window_actor.visible) - meta_window.blur_actor.show(); - else - meta_window.blur_actor.hide(); - } + _ => this.reconcile_window_visibility(meta_window) ); } @@ -435,57 +419,18 @@ export const ApplicationsBlur = class ApplicationsBlur { /// we are not focused anymore). It automatically removes the ancient focus. /// With `focus=false`, just remove the focus from said window (which can still be null). set_focus_for_window(meta_window, focus = true) { - let blur_actor = null; - let window_actor = null; let new_pid = null; if (meta_window) { - blur_actor = meta_window.blur_actor; - window_actor = meta_window.get_compositor_private(); new_pid = meta_window.bms_pid; } - if (focus) { - // remove old focused window if any - if (this.focused_window_pid) { - const old_focused_window = this.meta_window_map.get(this.focused_window_pid); - if (old_focused_window) - this.set_focus_for_window(old_focused_window, false); - } - // set new focused window pid + if (focus) this.focused_window_pid = new_pid; - // if we have blur, hide it and make the window opaque - if (this.settings.applications.DYNAMIC_OPACITY && blur_actor) { - blur_actor.hide(); - this.set_window_opacity(window_actor, 255); - } - } - // if we remove the focus and have blur, show it and make the window transparent - else if (blur_actor) { - blur_actor.show(); - this.set_window_opacity(window_actor, this.settings.applications.OPACITY); - } - } + else + this.focused_window_pid = null; - /// Makes sure that, when the overview is visible, the window actor will - /// stay visible no matter what. - /// We can instead hide the last child of the window actor, which will - /// improve performances without hiding the blur effect. - enforce_window_visibility_on_overview_for(window_actor) { - this.connections.connect(window_actor, 'notify::visible', - _ => { - if (this.settings.applications.BLUR_ON_OVERVIEW) { - if ( - !window_actor.visible - && Main.overview.visible - ) { - window_actor.show(); - window_actor.get_last_child().hide(); - } else if ( - window_actor.visible - ) - window_actor.get_last_child().show(); - } - } + this.meta_window_map.forEach((tracked_window, _pid) => + this.reconcile_window_visibility(tracked_window) ); } @@ -504,11 +449,38 @@ export const ApplicationsBlur = class ApplicationsBlur { }) } else { if (meta_window.bg_manager?._bms_pipeline?.effect) - meta_window.bg_manager._bms_pipeline.effect.corner_radius = use_0_radius ? + meta_window.bg_manager._bms_pipeline.effect.unscaled_corner_radius = use_0_radius ? 0 : this.settings.applications.CORNER_RADIUS; } } + reconcile_window_visibility(meta_window, overview_visible = this.overview_visible) { + const window_actor = meta_window.get_compositor_private(); + const blur_actor = meta_window.blur_actor; + if (!window_actor || !blur_actor) + return; + + const is_focused = this.settings.applications.DYNAMIC_OPACITY + && meta_window.bms_pid === this.focused_window_pid; + const is_fullscreen = this.settings.applications.UNBLUR_WHEN_FULLSCREEN + && meta_window.fullscreen; + const visible_for_blur = window_actor.visible + || (this.settings.applications.BLUR_ON_OVERVIEW && overview_visible); + const show_blur = visible_for_blur + && !is_focused + && !is_fullscreen + && (this.settings.applications.BLUR_ON_OVERVIEW || !overview_visible); + + if (show_blur) + blur_actor.show(); + else + blur_actor.hide(); + + this.set_window_opacity(window_actor, show_blur + ? this.settings.applications.OPACITY + : 255); + } + /// Update all corners, to use when the setting has been changed. update_all_corner_radii() { this.meta_window_map.forEach( @@ -516,24 +488,30 @@ export const ApplicationsBlur = class ApplicationsBlur { ) } + update_fullscreen_status() { + this.meta_window_map.forEach( + (meta_window, _pid) => this.reconcile_window_visibility(meta_window) + ); + } + /// Set the opacity of the window actor that sits on top of the blur effect. set_window_opacity(window_actor, opacity) { + // Define known blur actor names. This makes it easy to update if names change again. + const BLUR_ACTOR_NAMES = new Set(["blur-actor", "bms-application-blurred-widget"]); + window_actor?.get_children().forEach(child => { - if (child.name !== "blur-actor" && child.opacity != opacity) + // Check against the Set and the opacity + if (!BLUR_ACTOR_NAMES.has(child.name) && child.opacity != opacity) { child.opacity = opacity; + } }); } /// Update the opacity of all window actors. set_opacity() { - let opacity = this.settings.applications.OPACITY; - - this.meta_window_map.forEach(((meta_window, pid) => { - if (pid != this.focused_window_pid && meta_window.blur_actor) { - let window_actor = meta_window.get_compositor_private(); - this.set_window_opacity(window_actor, opacity); - } - })); + this.meta_window_map.forEach((meta_window, _pid) => + this.reconcile_window_visibility(meta_window) + ); } /// Find the system's window scaling. diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/dash_to_dock.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/dash_to_dock.js index 4ff212c..d386722 100644 --- a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/dash_to_dock.js +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/dash_to_dock.js @@ -3,6 +3,7 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Signals from 'resource:///org/gnome/shell/misc/signals.js'; import { PaintSignals } from '../conveniences/paint_signals.js'; +import * as utils from '../conveniences/utils.js'; import { Pipeline } from '../conveniences/pipeline.js'; import { DummyPipeline } from '../conveniences/dummy_pipeline.js'; @@ -21,7 +22,7 @@ const DASH_STYLES = [ class DashInfos { constructor( dash_blur, dash, dash_container, dash_background, - background, background_group, bg_manager + background, background_group, bg_manager, paint_signals ) { // the parent DashBlur object, to communicate this.dash_blur = dash_blur; @@ -31,28 +32,55 @@ class DashInfos { this.background = background; this.background_group = background_group; this.bg_manager = bg_manager; + this.paint_signals = paint_signals; this.settings = dash_blur.settings; - this.old_style = this.dash._background.style; + this.old_style = this.dash._background?.style; + + this.updateId = 0; + this._first_boot = true; + + this.bg_allocation_id = this.background_group?.connect('notify::allocation', () => { + this.schedule_update(); + }); + + let monitor = Main.layoutManager.findMonitorForActor(this.dash_container); + this.current_monitor_index = monitor ? monitor.index : null; - this.dash_destroy_id = dash.connect('destroy', () => this.remove_dash_blur(false)); + this.dash_destroy_id = dash.connect('destroy', () => this.remove_dash_blur()); this.dash_blur_connections_ids = []; this.dash_blur_connections_ids.push( this.dash_blur.connect('remove-dashes', () => this.remove_dash_blur()), this.dash_blur.connect('override-style', () => this.override_style()), this.dash_blur.connect('remove-style', () => this.remove_style()), - this.dash_blur.connect('show', () => this.background_group.show()), - this.dash_blur.connect('hide', () => this.background_group.hide()), - this.dash_blur.connect('update-size', () => this.update_size()), + this.dash_blur.connect('show', () => this.background_group?.show()), + this.dash_blur.connect('hide', () => this.background_group?.hide()), + this.dash_blur.connect('update-size', () => this.schedule_update()), this.dash_blur.connect('change-blur-type', () => this.change_blur_type()), this.dash_blur.connect('update-pipeline', () => this.update_pipeline()) ); } + schedule_update() { + this.clear_pending_idles(); + this.updateId = global.compositor.get_laters().add(Meta.LaterType.IDLE, () => { + this.updateId = 0; + this.update_size(); + return false; + }); + } + + clear_pending_idles() { + if (this.updateId) { + global.compositor.get_laters().remove(this.updateId); + this.updateId = 0; + } + } + // IMPORTANT: do never call this in a mutable `this.dash_blur.forEach` - remove_dash_blur(dash_not_already_destroyed = true) { + remove_dash_blur() { // remove the style and destroy the effects this.remove_style(); - this.destroy_dash(dash_not_already_destroyed); + this.destroy_dash(); // remove the dash infos from their list const dash_infos_index = this.dash_blur.dashes.indexOf(this); @@ -60,6 +88,10 @@ class DashInfos { this.dash_blur.dashes.splice(dash_infos_index, 1); // disconnect everything + if (this.bg_allocation_id && this.background_group) { + this.background_group.disconnect(this.bg_allocation_id); + this.bg_allocation_id = null; + } this.dash_blur_connections_ids.forEach(id => { if (id) this.dash_blur.disconnect(id); }); this.dash_blur_connections_ids = []; if (this.dash_destroy_id) @@ -70,36 +102,74 @@ class DashInfos { override_style() { this.remove_style(); - this.dash.set_style_class_name( - DASH_STYLES[this.settings.dash_to_dock.STYLE_DASH_TO_DOCK] - ); + const style = DASH_STYLES[ + this.settings.dash_to_dock.STYLE_DASH_TO_DOCK + ]; + + // Dash to Dock owns its style class list. Replacing it removes the + // extension's layout and border-radius rules. Add Blur My Shell's + // visual modifier alongside the native classes instead. + if (style && !this.dash.has_style_class_name(style)) + this.dash.add_style_class_name(style); } remove_style() { - this.dash._background.style = this.old_style; + if (this.dash?._background) + this.dash._background.style = this.old_style; DASH_STYLES.forEach( - style => this.dash.remove_style_class_name(style) + style => { + this.dash?.remove_style_class_name(style); + this.dash_background?.remove_style_class_name(style); + } ); } - destroy_dash(dash_not_already_destroyed = true) { - if (!dash_not_already_destroyed) + destroy_dash() { + this.clear_pending_idles(); + + if (this.paint_signals) + this.paint_signals.disconnect_all(); + + if (this.background_group && this.dash) { + const parent = this.dash.get_parent(); + if (parent) + parent.remove_child(this.background_group); + } + + if (this.bg_manager) { + if (this.bg_manager._bms_pipeline) { + this.bg_manager._bms_pipeline.destroy(); + this.bg_manager._bms_pipeline = null; + } this.bg_manager.backgroundActor = null; + this.bg_manager.destroy(); + this.bg_manager = null; + } + + if (this.bg_allocation_id && this.background_group) { + this.background_group.disconnect(this.bg_allocation_id); + this.bg_allocation_id = null; + } + + this._first_boot = null; - this.paint_signals?.disconnect_all(); - this.dash.get_parent().remove_child(this.background_group); - this.bg_manager._bms_pipeline.destroy(); - this.bg_manager.destroy(); - this.background_group.destroy(); + if (this.background_group) { + this.background_group.destroy(); + this.background_group = null; + } } change_blur_type() { this.destroy_dash(); + let blur_result = this.dash_blur.add_blur(this.dash, this.dash_container); + if (!blur_result) + return; + let [ background, background_group, bg_manager, paint_signals - ] = this.dash_blur.add_blur(this.dash); + ] = blur_result; this.background = background; this.background_group = background_group; @@ -108,50 +178,52 @@ class DashInfos { this.dash.get_parent().insert_child_at_index(this.background_group, 0); - this.update_size(); + // Apply the layout instantly before drawing the first frame + this.bg_allocation_id = this.background_group.connect('notify::allocation', () => { + this.schedule_update(); + }); + + this.schedule_update(); } update_pipeline() { - this.bg_manager._bms_pipeline.change_pipeline_to( - this.settings.dash_to_dock.PIPELINE - ); + if (this.bg_manager?._bms_pipeline) { + this.bg_manager._bms_pipeline.change_pipeline_to( + this.settings.dash_to_dock.PIPELINE + ); + } } update_size() { + if (!this.dash_blur._has_valid_allocation(this.dash_container) || + !this.dash_blur._has_valid_allocation(this.dash) || + !this.dash_blur._has_valid_allocation(this.dash_background)) + return; + if (this.dash_blur.is_static) { - let [x, y] = this.get_dash_position(this.dash_container, this.dash_background); + let monitor = Main.layoutManager.findMonitorForActor(this.dash_container); + if (!monitor) return; + + if (this.current_monitor_index !== monitor.index) { + this.current_monitor_index = monitor.index; + this.change_blur_type(); + return; + } - this.background.x = -x; - this.background.y = -y; + let dash_box = this.get_dash_position(this.dash_container); + if (!dash_box) + return; - if (this.dash_container.get_style_class_name().includes("top")) - this.background.set_clip( - x, - y + this.dash.y + this.dash_background.y, - this.dash_background.width, - this.dash_background.height - ); - else if (this.dash_container.get_style_class_name().includes("bottom")) - this.background.set_clip( - x, - y + this.dash.y + this.dash_background.y, - this.dash_background.width, - this.dash_background.height - ); - else if (this.dash_container.get_style_class_name().includes("left")) - this.background.set_clip( - x + this.dash.x + this.dash_background.x, - y + this.dash.y + this.dash_background.y, - this.dash_background.width, - this.dash_background.height - ); - else if (this.dash_container.get_style_class_name().includes("right")) - this.background.set_clip( - x + this.dash.x + this.dash_background.x, - y + this.dash.y + this.dash_background.y, - this.dash_background.width, - this.dash_background.height - ); + const inset = utils.static_blur_clip_inset(); + const clip_x = Math.floor(dash_box.clip_x) - inset; + const clip_y = Math.floor(dash_box.clip_y) - inset; + const clip_w = Math.ceil(this.dash_background.width) + inset * 2; + const clip_h = Math.ceil(this.dash_background.height) + inset * 2; + + this.background.x = dash_box.background_x + inset; + this.background.y = dash_box.background_y + inset; + + this.background.set_clip(clip_x, clip_y, clip_w, clip_h); } else { this.background.width = this.dash_background.width; this.background.height = this.dash_background.height; @@ -159,29 +231,38 @@ class DashInfos { this.background.x = this.dash_background.x; this.background.y = this.dash_background.y + this.dash.y; } + + if (this._first_boot) { + if (this.settings.dash_to_dock.UNBLUR_IN_OVERVIEW && Main.overview.visible) { + this.background_group?.hide(); + } + this._first_boot = false; + } } - get_dash_position(dash_container, dash_background) { - var x, y; + get_dash_position(dash_container) { + let monitor = Main.layoutManager.findMonitorForActor(this.dash_container); + if (!monitor) + return; - let monitor = Main.layoutManager.findMonitorForActor(dash_container); let dash_box = dash_container._slider.get_child(); + if (!dash_box) + return null; - if (dash_container.get_style_class_name().includes("top")) { - x = (monitor.width - dash_background.width) / 2; - y = dash_box.y; - } else if (dash_container.get_style_class_name().includes("bottom")) { - x = (monitor.width - dash_background.width) / 2; - y = monitor.height - dash_container.height; - } else if (dash_container.get_style_class_name().includes("left")) { - x = dash_box.x; - y = dash_container.y + (dash_container.height - dash_background.height) / 2 - dash_background.y; - } else if (dash_container.get_style_class_name().includes("right")) { - x = monitor.width - dash_container.width; - y = dash_container.y + (dash_container.height - dash_background.height) / 2 - dash_background.y; - } + let parent = this.background_group?.get_parent(); + if (!parent) + return; - return [x, y]; + let [parent_stage_x, parent_stage_y] = parent.get_transformed_position(); + let [bg_stage_x, bg_stage_y] = this.dash_background.get_transformed_position(); + + let background_x = monitor.x - parent_stage_x; + let background_y = monitor.y - parent_stage_y; + + let clip_x = bg_stage_x - monitor.x; + let clip_y = bg_stage_y - monitor.y; + + return {background_x, background_y, clip_x, clip_y}; } _log(str) { @@ -234,28 +315,110 @@ export const DashBlur = class DashBlur extends Signals.EventEmitter { }).forEach(dash_container => this.try_blur(dash_container)); } + _has_valid_allocation(actor) { + return actor && + (!actor.has_allocation || actor.has_allocation()) && + actor.width > 0 && actor.height > 0; + } + + _defer_blur_until_allocated(dash_container, dash) { + if (dash_container._bms_pending_blur_setup) + return; + + dash_container._bms_pending_blur_setup = true; + + const connections = []; + const cleanup = () => { + delete dash_container._bms_pending_blur_setup; + delete dash_container._bms_defer_cleanup; + connections.forEach(({ actor, id }) => { + if (actor && id) + actor.disconnect(id); + }); + }; + + const retry = () => { + if (!dash_container._bms_pending_blur_setup) + return; + + let current_dash_box = dash_container._slider?.get_child(); + let current_dash = dash || current_dash_box?.get_children().find(child => child.get_name() === 'dash'); + + if (!this._has_valid_allocation(dash_container) || + !this._has_valid_allocation(current_dash_box) || + !this._has_valid_allocation(current_dash)) + return; + + cleanup(); + this.try_blur(dash_container); + }; + + const dash_cont_id = dash_container.connect('notify::allocation', retry); + connections.push({ actor: dash_container, id: dash_cont_id }); + + if (dash) { + const dash_id = dash.connect('notify::allocation', retry); + connections.push({ actor: dash, id: dash_id }); + } + + const destroy_id = dash_container.connect('destroy', cleanup); + connections.push({ actor: dash_container, id: destroy_id }); + + dash_container._bms_defer_cleanup = cleanup; + } + // Tries to blur the dash contained in the given actor try_blur(dash_container) { - let dash_box = dash_container._slider.get_child(); + let dash_box = dash_container._slider?.get_child(); + if (!dash_box){ + this._defer_blur_until_allocated(dash_container); + return; + } - // verify that we did not already blur that dash - if (!dash_box.get_children().some(child => + let dash = dash_box.get_children().find(child => { + return child.get_name() === 'dash'; + }); + + if (!dash || + !this._has_valid_allocation(dash_container) || + !this._has_valid_allocation(dash_box) || + !this._has_valid_allocation(dash)) { + this._defer_blur_until_allocated(dash_container, dash); + return; + } + + let dash_exist = this.dashes.find(info => info.dash === dash); + if (dash_exist) { + dash_exist.remove_dash_blur(); + } + + let existing_bg = dash_box.get_children().find(child => child.get_name() === "bms-dash-backgroundgroup" - )) { - this._log("dash to dock found, blurring it"); + ); - // finally blur the dash - let dash = dash_box.get_children().find(child => { - return child.get_name() === 'dash'; - }); + if (existing_bg) { + if (dash_box.contains(existing_bg)) + dash_box.remove_child(existing_bg); + + existing_bg.destroy(); + } + + // verify that we did not already blur that dash + this._log("dash to dock found, blurring it"); - this.dashes.push(this.blur_dash_from(dash, dash_container)); + let infos = this.blur_dash_from(dash, dash_container); + if (infos) { + this.dashes.push(infos); } } // Blurs the dash and returns a `DashInfos` containing its information blur_dash_from(dash, dash_container) { - let [background, background_group, bg_manager, paint_signals] = this.add_blur(dash); + let blur_result = this.add_blur(dash, dash_container); + if (!blur_result) + return null; + + let [background, background_group, bg_manager, paint_signals] = blur_result; // insert the background group to the right element dash.get_parent().insert_child_at_index(background_group, 0); @@ -268,14 +431,32 @@ export const DashBlur = class DashBlur extends Signals.EventEmitter { ); this.connections.connect( dash_container, - ['notify::width', 'notify::height', 'notify::y', 'notify::x'], + [ + 'notify::width', + 'notify::height', + 'notify::y', + 'notify::x', + 'notify::style-class-name', + 'notify::allocation' + ], _ => this.update_size() ); - const dash_background = dash.get_children().find(child => { - return child.get_style_class_name() === 'dash-background'; - }); + const slider = dash_container._slider; + if (slider) { + this.connections.connect( + slider, + ['notify::slide-x', 'notify::allocation'], + _ => this.update_size() + ); + } + + const dash_background = dash._background || + dash.get_children().find(child => child.has_style_class_name('dash-background')) || + dash; + if (!dash_background) + return null; // create infos let infos = new DashInfos( this, @@ -289,16 +470,23 @@ export const DashBlur = class DashBlur extends Signals.EventEmitter { ); this.update_size(); + + global.compositor.get_laters().add(Meta.LaterType.BEFORE_REDRAW, () => { + this.update_size(); + return false; + }); + this.update_background(); // returns infos return infos; } - add_blur(dash) { - const monitor = Main.layoutManager.findMonitorForActor(dash); + add_blur(dash, dash_container = null) { + const target_actor = dash_container || dash; + const monitor = Main.layoutManager.findMonitorForActor(target_actor) || Main.layoutManager.primaryMonitor; if (!monitor) - return; + return null; const background_group = new Meta.BackgroundGroup({ name: 'bms-dash-backgroundgroup', width: 0, height: 0 @@ -404,6 +592,12 @@ export const DashBlur = class DashBlur extends Signals.EventEmitter { this.emit('remove-dashes'); + for (let child of Main.uiGroup.get_children()) { + if (child._bms_defer_cleanup) + child._bms_defer_cleanup(); + delete child._bms_pending_blur_setup; + } + this.dashes = []; this.connections.disconnect_all(); @@ -418,4 +612,4 @@ export const DashBlur = class DashBlur extends Signals.EventEmitter { _warn(str) { console.warn(`[Blur my Shell > dash manager] ${str}`); } -};
\ No newline at end of file +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/overview.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/overview.js index 3bbde61..c97a459 100644 --- a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/overview.js +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/overview.js @@ -104,13 +104,39 @@ export const OverviewBlur = class OverviewBlur { // this hides windows that are not on the current workspace if ( outer_this.settings.applications.BLUR - ) + ) { + // compile blacklist patterns once for this switch + const blacklist = outer_this.settings.applications.BLACKLIST || []; + const blacklist_regexes = blacklist.map(pattern => { + const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&'); + return new RegExp('^' + escaped.replace(/\*/g, '.*').replace(/\?/g, '.') + '$', 'i'); + }); + for (let i = 0; i < w_m.get_n_workspaces(); i++) { if (i != w_m.get_active_workspace_index()) w_m.get_workspace_by_index(i)?.list_windows().forEach( - window => window.get_compositor_private().hide() + window => { + // skip windows on all workspaces (e.g. secondary + // monitor windows under `workspaces-only-on-primary`); + // hiding them on any inactive workspace hides them globally + if (window.is_on_all_workspaces()) return; + + // skip desktop-type windows (e.g. DING, Nautilus desktop): + // they should always remain visible + if (window.get_window_type() === Meta.WindowType.DESKTOP) + return; + + // skip blacklisted windows: + // they are not blurred and should always remain visible + const wm_class = window.get_wm_class(); + if (wm_class && blacklist_regexes.some(re => re.test(wm_class))) + return; + + window.get_compositor_private().hide(); + } ); } + } Main.uiGroup.remove_child(outer_this.animation_background_group); }; @@ -149,14 +175,11 @@ export const OverviewBlur = class OverviewBlur { // add the container widget for the overview only to the overview group Main.layoutManager.overviewGroup.insert_child_at_index(this.overview_background_group, 0); // make sure it stays below - let _reordering = false; this.connections.connect(Main.layoutManager.overviewGroup, "child-added", (_, child) => { - if (!_reordering && child !== this.overview_background_group) { - _reordering = true; + if (child !== this.overview_background_group) { if (this.overview_background_group.get_parent()) Main.layoutManager.overviewGroup.remove_child(this.overview_background_group); Main.layoutManager.overviewGroup.insert_child_at_index(this.overview_background_group, 0); - _reordering = false; } }); } diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js index bd39166..7d00224 100644 --- a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js @@ -4,6 +4,7 @@ import Meta from 'gi://Meta'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import { PaintSignals } from '../conveniences/paint_signals.js'; +import * as utils from '../conveniences/utils.js'; import { Pipeline } from '../conveniences/pipeline.js'; import { DummyPipeline } from '../conveniences/dummy_pipeline.js'; @@ -16,9 +17,10 @@ const PANEL_STYLES = [ "contrasted-panel" ]; -// global listener, so we don't miss the panel destruction event -let isMainPanelAlive = true; -Main.panel.connect('destroy', () => isMainPanelAlive = false); +const GRADIENT_PANEL_STYLES = [ + "gradient-panel", + "gradient-panel-reverse" +]; export const PanelBlur = class PanelBlur { constructor(connections, settings, effects_manager) { @@ -27,7 +29,9 @@ export const PanelBlur = class PanelBlur { this.settings = settings; this.effects_manager = effects_manager; this.actors_list = []; + this.queued_updates = new Set(); this.enabled = false; + this._first_boot = true; } enable() { @@ -36,6 +40,12 @@ export const PanelBlur = class PanelBlur { return; } + // global listener, so we don't miss the panel destruction event + this._isMainPanelAlive = true; + this.connections.connect(Main.panel, 'destroy', () => { + this._isMainPanelAlive = false; + }); + this._log("blurring top panel"); // check for panels when Dash to Panel is activated @@ -59,6 +69,9 @@ export const PanelBlur = class PanelBlur { this.blur_existing_panels(); + // Hide the panel blur first to avoid the panel background from display on login + this.panel_hide_blur_startup(); + // connect to overview being opened/closed, and dynamically show or not // the blur when a window is near a panel this.connect_to_windows_and_overview(); @@ -113,20 +126,16 @@ export const PanelBlur = class PanelBlur { // blur already existing ones if (global.dashToPanel.panels) this.blur_dtp_panels(); - - // blur main panel if requested in settings - if (this.settings.dash_to_panel.BLUR_ORIGINAL_PANEL && isMainPanelAlive) - this.maybe_blur_panel(Main.panel); } else { // if no dash-to-panel, blur the main panel - if (isMainPanelAlive) + if (this._isMainPanelAlive) this.maybe_blur_panel(Main.panel); // blur panels already created by extension multi-monitors-bar@frederykabryan Main.uiGroup.get_children().forEach(actor => { if (actor.get_name() === "panelBox" && actor.get_n_children() === 1) { let multi_monitor_panel = actor.get_child_at_index(0); - if (isMainPanelAlive && multi_monitor_panel != Main.panel) + if (this._isMainPanelAlive && multi_monitor_panel != Main.panel) this.maybe_blur_panel(multi_monitor_panel); } }) @@ -145,12 +154,27 @@ export const PanelBlur = class PanelBlur { this._log("Blurring Dash to Panel panels after idle."); - // blur every panel, except Main.panel (this is handled in blur_existing_panels() method above) + // blur every panel found global.dashToPanel.panels.forEach(p => { - if (p.panel != Main.panel) + if ( + p.panel != Main.panel || + this.settings.dash_to_panel.BLUR_ORIGINAL_PANEL + ) this.maybe_blur_panel(p.panel); }); + // if main panel is not included in the previous panels, blur it + if ( + !global.dashToPanel.panels + .map(p => p.panel) + .includes(Main.panel) + && + this.settings.dash_to_panel.BLUR_ORIGINAL_PANEL + && + this._isMainPanelAlive + ) + this.maybe_blur_panel(Main.panel); + return GLib.SOURCE_REMOVE; }); }; @@ -169,10 +193,14 @@ export const PanelBlur = class PanelBlur { /// Blur a panel blur_panel(panel) { + let geometry_actor = panel; + let wrapper = null; let panel_box = panel.get_parent(); let is_dtp_panel = false; if (!panel_box.name) { is_dtp_panel = true; + wrapper = panel_box; + geometry_actor = panel; // Track the inner panel dynamically panel_box = panel_box.get_parent(); } @@ -186,9 +214,11 @@ export const PanelBlur = class PanelBlur { let background, bg_manager; let static_blur = this.settings.panel.STATIC_BLUR; + let pipeline; // Hoist the pipeline variable so our proxy can access it + if (static_blur) { let bg_manager_list = []; - const pipeline = new Pipeline( + pipeline = new Pipeline( this.effects_manager, global.blur_my_shell._pipelines_manager, this.settings.panel.PIPELINE @@ -200,32 +230,55 @@ export const PanelBlur = class PanelBlur { bg_manager = bg_manager_list[0]; } else { - const pipeline = new DummyPipeline(this.effects_manager, this.settings.panel); + pipeline = new DummyPipeline(this.effects_manager, this.settings.panel); [background, bg_manager] = pipeline.create_background_with_effect( background_group, 'bms-panel-blurred-widget' ); + } - let paint_signals = new PaintSignals(this.connections); + let paint_signals = new PaintSignals(this.connections); - // HACK - // - //`Shell.BlurEffect` does not repaint when shadows are under it. [1] - // - // This does not entirely fix this bug (shadows caused by windows - // still cause artifacts), but it prevents the shadows of the panel - // buttons to cause artifacts on the panel itself - // - // [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857 + // HACK + // + //`Shell.BlurEffect` does not repaint when shadows are under it. [1] + // + // This does not entirely fix this bug (shadows caused by windows + // still cause artifacts), but it prevents the shadows of the panel + // buttons to cause artifacts on the panel itself + // + // [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857 - { - if (this.settings.HACKS_LEVEL === 1) { - this._log("panel hack level 1"); + { + if (this.settings.HACKS_LEVEL === 1) { + this._log("panel hack level 1"); - paint_signals.disconnect_all(); - paint_signals.connect(background, pipeline.effect); - } else { - paint_signals.disconnect_all(); - } + // Proxy object to dynamically resolve the active blur effect. + // This ensures repaints continue safely even if pipeline effects are rebuilt. + let dynamic_target = { + queue_repaint: () => { + let active_pipeline = (bg_manager && bg_manager._bms_pipeline) ? bg_manager._bms_pipeline : pipeline; + + if (static_blur && active_pipeline && active_pipeline.effects) { + // Repaint all effects in the static pipeline to be agnostic of the active effects + for (let i = 0; i < active_pipeline.effects.length; i++) { + let eff = active_pipeline.effects[i]; + if (eff && typeof eff.queue_repaint === 'function') { + eff.queue_repaint(); + } + } + } else if (!static_blur && active_pipeline) { + let eff = active_pipeline.effect; // Dynamic pipeline uses a single effect + if (eff && typeof eff.queue_repaint === 'function') { + eff.queue_repaint(); + } + } + } + }; + + paint_signals.disconnect_all(); + paint_signals.connect(background, dynamic_target); + } else { + paint_signals.disconnect_all(); } } @@ -234,75 +287,151 @@ export const PanelBlur = class PanelBlur { // the object that is used to remembering each elements that is linked to the blur effect let actors = { - widgets: { panel, panel_box, background, background_group }, + widgets: { + panel, + wrapper, + panel_box, + background, + background_group, + geometry_actor + }, static_blur, monitor, bg_manager, is_dtp_panel }; this.actors_list.push(actors); + background_group.connect('destroy', () => { + actors.widgets.background_group = null; + }); - // update the size of the actor - this.update_size(actors); + // defer size update to idle to avoid allocation race + this.queue_update_size(actors); - // connect to panel, panel_box and its parent position or size change + // connect to the relevant actors geometry changes // this should fire update_size every time one of its params change this.connections.connect( - panel, - 'notify::position', - _ => this.update_size(actors) + geometry_actor, + ['notify::allocation', 'notify::size', 'notify::position'], + _ => this.queue_update_size(actors) ); + + if (wrapper) { + this.connections.connect( + wrapper, + ['notify::allocation', 'notify::size', 'notify::position'], + _ => this.queue_update_size(actors) + ); + } this.connections.connect( panel_box, ['notify::size', 'notify::position'], - _ => this.update_size(actors) + _ => this.queue_update_size(actors) ); this.connections.connect( panel_box.get_parent(), 'notify::position', - _ => this.update_size(actors) + _ => this.queue_update_size(actors) ); // connect to the panel getting destroyed this.connections.connect( panel, 'destroy', - _ => this.destroy_blur(actors, true) + _ => this.destroy_blur(actors) ); } + queue_update_size(actors) { + if (this.queued_updates.has(actors)) + return; + + this.queued_updates.add(actors); + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this.queued_updates.delete(actors); + + if (!this.enabled || !this.actors_list.includes(actors)) + return GLib.SOURCE_REMOVE; + + this.update_size(actors); + return GLib.SOURCE_REMOVE; + }); + } + update_size(actors) { - let panel = actors.widgets.panel; + let geometry_actor = actors.widgets.geometry_actor; let panel_box = actors.widgets.panel_box; + let wrapper = actors.widgets.wrapper; let background = actors.widgets.background; let [width, height] = panel_box.get_size(); + let [geometry_width, geometry_height] = geometry_actor.get_size(); + + if (!width || !height || !geometry_width || !geometry_height) { + this.queue_update_size(actors); + return; + } // if static blur, need to clip the background if (actors.static_blur) { - let monitor = Main.layoutManager.findMonitorForActor(panel); - if (!monitor) + let monitor = Main.layoutManager.findMonitorForActor(geometry_actor); + if (!monitor) { + this.queue_update_size(actors); return; + } - // an alternative to panel.get_transformed_position, because it - // sometimes yields NaN (probably when the actor is not fully - // positionned yet) let [p_x, p_y] = panel_box.get_position(); let [p_p_x, p_p_y] = panel_box.get_parent().get_position(); - let x = p_x + p_p_x - monitor.x + (width - panel.width) / 2; - let y = p_y + p_p_y - monitor.y + (height - panel.height) / 2; - background.set_clip(x, y, panel.width, panel.height); - background.x = (width - panel.width) / 2 - x; - background.y = .5 + (height - panel.height) / 2 - y; + // updated coordinates for static blur + let g_x, g_y; + if (actors.is_dtp_panel) { + let [w_x, w_y] = wrapper.get_position(); + let [pan_x, pan_y] = geometry_actor.get_position(); + g_x = w_x + pan_x; + g_y = w_y + pan_y; + } else { + [g_x, g_y] = geometry_actor.get_position(); + } + + let x = p_x + p_p_x - monitor.x + g_x; + let y = p_y + p_p_y - monitor.y + g_y; + let is_horizontal = geometry_width >= geometry_height; + let distance_to_top = Math.abs(y); + let distance_to_bottom = Math.abs( + monitor.height - (y + geometry_height) + ); + let is_bottom_panel = is_horizontal && + distance_to_bottom < distance_to_top; + + const inset = utils.static_blur_clip_inset(); + const offset = utils.subpixel_stage_offset(); + const workspace_edge_inset = is_bottom_panel + ? Math.max(inset, 1) + : inset; + const clip_x = Math.floor(x) - inset; + const clip_y = Math.floor(y) - workspace_edge_inset; + const clip_w = Math.ceil(geometry_width) + inset * 2; + const clip_h = Math.ceil(geometry_height) + + inset + workspace_edge_inset; + + background.set_clip(clip_x, clip_y, clip_w, clip_h); + background.x = g_x - x + inset; + background.y = offset + g_y - y + inset; } else { - background.x = panel.x; - background.y = panel.y; - background.width = panel.width; - background.height = panel.height; + // updated coordinates for dynamic blur + if (actors.is_dtp_panel) { + background.x = wrapper.x + geometry_actor.x; + background.y = wrapper.y + geometry_actor.y; + } else { + background.x = geometry_actor.x; + background.y = geometry_actor.y; + } + background.width = geometry_width; + background.height = geometry_height; } // update the monitor panel is on - actors.monitor = Main.layoutManager.findMonitorForActor(panel); + actors.monitor = Main.layoutManager.findMonitorForActor(geometry_actor); } /// Connect when overview if opened/closed to hide/show the blur accordingly @@ -322,23 +451,27 @@ export const PanelBlur = class PanelBlur { this.connections.connect( Main.overview, 'showing', _ => this.hide() ); + this.connections.connect( - Main.overview, 'hidden', _ => this.show() + Main.overview, 'hidden', _ => { + this.panel_hide_blur_dynamically(); + this.update_visibility(); + } ); } else { let appDisplay = Main.overview._overview._controls._appDisplay; - + this.connections.connect( appDisplay, 'show', _ => this.hide() ); + this.connections.connect( - appDisplay, 'hide', _ => this.show() + appDisplay, 'hide', _ => this.update_visibility() ); this.connections.connect( - Main.overview, 'hidden', _ => this.show() + Main.overview, 'hidden', _ => this.update_visibility() ); } - } } @@ -418,13 +551,13 @@ export const PanelBlur = class PanelBlur { /// Update the css classname of the panel for light theme update_light_text_classname(disable = false) { - if (!isMainPanelAlive) + if (!this._isMainPanelAlive) return; if (this.settings.panel.FORCE_LIGHT_TEXT && !disable) - Main.panel.add_style_class_name("panel-light-text"); + Main.uiGroup.add_style_class_name("panel-light-text"); else - Main.panel.remove_style_class_name("panel-light-text"); + Main.uiGroup.remove_style_class_name("panel-light-text"); } /// Callback when a new window is added @@ -452,7 +585,7 @@ export const PanelBlur = class PanelBlur { /// Update the visibility of the blur effect update_visibility() { if ( - isMainPanelAlive && Main.panel.has_style_pseudo_class('overview') + this._isMainPanelAlive && Main.panel.has_style_pseudo_class('overview') || !Main.sessionMode.hasWindows ) { this.actors_list.forEach( @@ -469,7 +602,13 @@ export const PanelBlur = class PanelBlur { const windows = workspace.list_windows().filter(meta_window => meta_window.showing_on_its_workspace() && !meta_window.is_hidden() - && meta_window.get_window_type() !== Meta.WindowType.DESKTOP + // Whitelist only standard application windows. + // This prevents system overlays from disabling panel blur. + && [ + Meta.WindowType.NORMAL, + Meta.WindowType.DIALOG, + Meta.WindowType.MODAL_DIALOG + ].includes(meta_window.get_window_type()) // exclude Desktop Icons NG && meta_window.get_gtk_application_id() !== "com.rastersoft.ding" && meta_window.get_gtk_application_id() !== "com.desktop.ding" @@ -499,10 +638,10 @@ export const PanelBlur = class PanelBlur { // if so, and if in the same monitor, then it overlaps if (same_monitor && - // check if panel is on top - ((panel_top === 0 && window_vertical_pos < panel_bottom + 5 * scale) || + // check if panel is on top (relative to the monitor's Y origin) + ((panel_top === actors.monitor.y && window_vertical_pos < panel_bottom + 5 * scale) || // check if panel is at the bottom - (panel_top > 0 && window_vertical_bottom > panel_top - 5 * scale)) + (panel_top > actors.monitor.y && window_vertical_bottom > panel_top - 5 * scale)) ) window_overlap_panel = true; }); @@ -519,22 +658,100 @@ export const PanelBlur = class PanelBlur { set_should_override_panel(actors, should_override) { let panel = actors.widgets.panel; - PANEL_STYLES.forEach(style => panel.remove_style_class_name(style)); - - if ( - this.settings.panel.OVERRIDE_BACKGROUND - && - should_override - ) { - panel.add_style_class_name( - PANEL_STYLES[this.settings.panel.STYLE_PANEL] - ); + if (this.settings.panel.OVERRIDE_BACKGROUND) { + if (this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY) { + if (this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY_MODE == 0) { + // This is an invert of the above behavior, + // Blur and all styling is hidden when "should_override" is true. + if (!should_override) { + this.proximity_show(actors, panel); + } + else { + this.proximity_hide(actors, panel); + }; + } + if (this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY_MODE == 1) { + if (should_override) { + this.update_panel_style_class(panel, PANEL_STYLES[this.settings.panel.STYLE_PANEL]); + } else { + this.update_panel_style_class(panel, null); // Clear custom styles + } + } + } + else { + this.update_panel_style_class(panel, PANEL_STYLES[this.settings.panel.STYLE_PANEL]); + } + } + else { + this.update_panel_style_class(panel, null); } // update the classname if the panel to have or have not light text this.update_light_text_classname(!should_override); } + update_panel_style_class(panel, target_class) { + const ALL_STYLES = [...PANEL_STYLES, ...GRADIENT_PANEL_STYLES]; + + // Remove all managed classes EXCEPT the target class we are moving to + ALL_STYLES.forEach(style => { + if (style !== target_class) { + panel.remove_style_class_name(style); + } + }); + + // Add the target class if specified and not already present + if (target_class && !panel.has_style_class_name(target_class)) { + panel.add_style_class_name(target_class); + } + } + + panel_hide_blur_dynamically() { + if (this.settings.panel.OVERRIDE_BACKGROUND && this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY) { + if (this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY_MODE == 0) { + this.hide() + } + else {this.show()} + } + else { + this.show() + } + } + + proximity_hide(actors, panel) { + let target_style = null; + if (this.settings.panel.GRADIENT_PANEL) { + if (!Main.overview.visible) { + target_style = GRADIENT_PANEL_STYLES[this.settings.panel.GRADIENT_PANEL_MODE]; + } + else { + target_style = this.settings.panel.UNBLUR_IN_OVERVIEW + ? PANEL_STYLES[0] + : GRADIENT_PANEL_STYLES[this.settings.panel.GRADIENT_PANEL_MODE]; + } + } + else { + target_style = PANEL_STYLES[this.settings.panel.STYLE_PANEL]; + } + + this.update_panel_style_class(panel, target_style); + actors.widgets.background.hide(); + } + + proximity_show(actors, panel) { + this.update_panel_style_class(panel, PANEL_STYLES[this.settings.panel.STYLE_PANEL]); + actors.widgets.background.show(); + } + + panel_hide_blur_startup(){ + if (this._first_boot) { + if (this.settings.panel.UNBLUR_IN_OVERVIEW && Main.overview.visible) { + this.hide(); + } + this._first_boot = false; + } + } + update_pipeline() { this.actors_list.forEach(actors => actors.bg_manager._bms_pipeline.change_pipeline_to( @@ -556,19 +773,22 @@ export const PanelBlur = class PanelBlur { } // IMPORTANT: do never call this in a mutable `this.actors_list.forEach` - destroy_blur(actors, panel_already_destroyed) { + destroy_blur(actors) { this.set_should_override_panel(actors, false); - actors.bg_manager._bms_pipeline.destroy(); - - if (panel_already_destroyed) + if (actors.bg_manager) { + if (actors.bg_manager._bms_pipeline) { + actors.bg_manager._bms_pipeline.destroy(); + actors.bg_manager._bms_pipeline = null; + } actors.bg_manager.backgroundActor = null; - actors.bg_manager.destroy(); + actors.bg_manager.destroy(); + actors.bg_manager = null; + } - if (!panel_already_destroyed) { - actors.widgets.panel_box.remove_child(actors.widgets.background_group); - actors.widgets.background_group.destroy_all_children(); + if (actors.widgets && actors.widgets.background_group) { actors.widgets.background_group.destroy(); + actors.widgets.background_group = null; } let index = this.actors_list.indexOf(actors); @@ -589,10 +809,12 @@ export const PanelBlur = class PanelBlur { this.update_light_text_classname(true); const immutable_actors_list = [...this.actors_list]; - immutable_actors_list.forEach(actors => this.destroy_blur(actors, false)); + immutable_actors_list.forEach(actors => this.destroy_blur(actors)); this.actors_list = []; + this.queued_updates.clear(); this._dirty = true; + this._first_boot = null; this.connections.disconnect_all(); diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup.js new file mode 100644 index 0000000..959b2f4 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup.js @@ -0,0 +1 @@ +export { PopupBlur } from './popup/index.js'; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/blur_surface.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/blur_surface.js new file mode 100644 index 0000000..8fcf82b --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/blur_surface.js @@ -0,0 +1,484 @@ +import GLib from 'gi://GLib'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; + +import { DummyPipeline } from '../../conveniences/dummy_pipeline.js'; +import { PaintSignals } from '../../conveniences/paint_signals.js'; +import { PopupBlurSurfaceFade } from './surface_fade.js'; +import { PopupBlurSurfacePlacement } from './surface_placement.js'; +import { PopupBlurSurfaceSignals } from './surface_signals.js'; +import { PopupBlurSurfaceStyle } from './surface_style.js'; +import { PopupBlurSurfaceTransitions } from './surface_transitions.js'; +import { PopupBlurStaticActor } from './static_actor.js'; + +const NOTIFICATION_STYLE_CLASSES = ['notification-banner']; +const FULL_GEOMETRY_STYLE_CLASSES = [ + 'popup-menu-content', 'candidate-popup-content', + 'quick-settings', 'quick-toggle-menu', 'screenshot-ui-panel', + 'notification-banner', 'snap-assistant', + 'osd-window', 'resize-popup', 'workspace-switcher', + 'modal-dialog', 'run-dialog', + 'bms-keyboard-surface', +]; +const IS_HEAVY_SURFACE_STYLE_CLASSES = [ + 'datemenu-popover', 'quick-settings', 'modal-dialog', + 'candidate-popup-content', 'candidate-popup-boxpointer', +]; +const IS_QUICK_SETTINGS_STYLE_CLASSES = [ + 'quick-toggle-menu', 'quick-settings', 'datemenu-popover', +]; + +export const PopupBlurSurface = class PopupBlurSurface { + constructor(connections, settings, effects_manager, target, root_actor, parent, sibling, corner_radius) { + this.connections = connections; + this.settings = settings; + this.effects_manager = effects_manager; + this.target = target; + this.root_actor = root_actor; + this.parent = parent; + this.sibling = sibling; + this.corner_radius = corner_radius; + this.paint_signals = new PaintSignals(connections); + this.placement = new PopupBlurSurfacePlacement(this); + this.signals = new PopupBlurSurfaceSignals(this); + this.style = new PopupBlurSurfaceStyle(this); + this.transitions = new PopupBlurSurfaceTransitions(this); + this.repaint_id = 0; + this.update_id = 0; + this.transition_update_id = 0; + this.opacity = null; + this.static_blur = settings.popup.STATIC_BLUR; + this.static_actor = null; + this.actor = null; + this.blur_actor = null; + } + + enable() { + if (!this.create_actor()) { + if (this.static_blur) + this.destroy_static_actor(); + return false; + } + + this.fade = new PopupBlurSurfaceFade( + this.actor, + this.target, + this.root_actor, + this.parent + ); + this.actor.hide(); + this.parent.add_child(this.actor); + this.set_actor_position(); + this.style.capture_target_style(); + this.style.update_target_style(); + + this.connect_repaints(); + this.signals.connect_actor(this.target); + this.signals.connect_actor(this.root_actor); + this.signals.connect_ancestors(this.target); + this.signals.connect_ancestors(this.root_actor); + if (this.style.has_any_style_class(this.target, ['screenshot-ui-panel'])) + this.signals.connect_actor(this.parent); + this.signals.connect_layout(); + this.signals.connect_settings(); + this.queue_update(); + + return true; + } + + create_actor() { + if (this.static_blur) + return this.create_static_actor(); + + this.blur_actor = new St.Widget({ + name: 'bms-popup-blurred-widget', + reactive: false, + }); + this.blur_actor.add_style_class_name('bms-popup-blurred-widget'); + this.track_owned_actor(this.blur_actor); + this.pipeline = new DummyPipeline( + this.effects_manager, + this.settings.popup, + this.blur_actor, + { + corner_radius_key: this.corner_radius.key, + corner_radius_getter: () => this.get_corner_radius(), + } + ); + this.actor = this.blur_actor; + return true; + } + + create_static_actor() { + this.static_actor = new PopupBlurStaticActor( + this.settings, + this.effects_manager, + this.target, + this.root_actor, + this.parent, + () => this.get_corner_radius() + ); + if (!this.static_actor.create()) + return false; + this.sync_static_actor(); + return true; + } + + sync_static_actor() { + this.actor = this.static_actor.actor; + this.blur_actor = this.static_actor.blur_actor; + this.pipeline = this.static_actor.pipeline; + } + + track_owned_actor(actor) { + this.connections.connect(actor, 'destroy', () => { + this.actor = null; + this.blur_actor = null; + }); + } + + set_actor_position() { + if (!this.is_actor_valid()) + return; + const sibling = this.sibling?.get_parent() === this.parent ? this.sibling : null; + if (this.is_below_sibling(sibling)) + return; + this.parent.set_child_below_sibling(this.actor, sibling); + } + + is_below_sibling(sibling) { + const children = this.parent.get_children(); + const actor_index = children.indexOf(this.actor); + if (actor_index < 0) + return false; + if (!sibling) + return actor_index === 0; + const sibling_index = children.indexOf(sibling); + return sibling_index >= 0 && actor_index < sibling_index; + } + + is_quick_settings() { + return this.style.has_any_style_class(this.target, IS_QUICK_SETTINGS_STYLE_CLASSES) + || this.style.has_any_style_class(this.root_actor, IS_QUICK_SETTINGS_STYLE_CLASSES); + } + + is_heavy_surface() { + return this.style.has_any_style_class(this.target, IS_HEAVY_SURFACE_STYLE_CLASSES) + || this.style.has_any_style_class(this.root_actor, IS_HEAVY_SURFACE_STYLE_CLASSES); + } + + update() { + if (this.is_notification_surface()) { + this.update_live_surface(); + return; + } + let transition_state = this.transitions.get_state(); + let geometry = this.placement.get_surface_geometry(); + const visible = this.is_visible(); + const geometry_changed = this.placement.has_surface_geometry_changed(geometry); + if ( + !visible + || !this.placement.has_valid_geometry(geometry) + || (!transition_state.geometry && geometry_changed) + ) + transition_state = this.transitions.complete_state(transition_state); + if (!visible && !transition_state.running) { + this.hide_surface(); + return; + } + if (!this.placement.has_valid_geometry(geometry)) { + if (this.placement.offscreen) + return this.hide_surface(); + if (this.placement.keep_transition_visible(transition_state)) { + this.queue_repaint(true); + this.queue_transition_update(transition_state); + return; + } + + if (transition_state.running) { + this.queue_transition_update(transition_state); + return; + } + this.hide_surface(); + return; + } + this.set_actor_position(); + if (!this.placement.update_surface_geometry(geometry)) + return; + if (!this.placement.prepare_visible_geometry()) + return; + const opacity = this.update_opacity(transition_state); + if (opacity > 0) + this.show_actors(); + else + this.hide_actors(); + this.queue_repaint(!visible); + this.queue_transition_update(transition_state); + } + + update_live_surface() { + if (!this.is_visible()) { + this.hide_surface(); + return; + } + this.set_actor_position(); + const geometry = this.placement.get_unclipped_monitor_surface_geometry(); + if (!this.placement.has_valid_geometry(geometry)) + return this.hide_surface(); + if (!this.placement.update_surface_geometry(geometry)) + return; + if (!this.placement.prepare_visible_geometry()) + return; + if (this.update_opacity() > 0) + this.show_actors(); + else + this.hide_actors(); + this.queue_repaint(); + this.queue_transition_update(); + } + + hide_surface() { + this.opacity = 0; + this.update_surface_opacity(0); + this.hide_actors(); + this.placement.hide(); + } + + update_opacity(transition_state = null) { + const opacity = this.fade.get_opacity(); + const transition_opacity = this.get_transition_opacity(opacity, transition_state); + if ( + this.opacity === transition_opacity + && this.has_surface_opacity(transition_opacity) + ) + return transition_opacity; + this.update_surface_opacity(transition_opacity); + this.opacity = transition_opacity; + return transition_opacity; + } + + get_transition_opacity(opacity, transition_state) { + if ( + this.static_blur + || !transition_state?.geometry + || transition_state.opacity + || this.opacity <= 0 + ) + return opacity; + return Math.max(opacity, this.opacity); + } + + has_surface_opacity(opacity) { + if (this.static_blur) + return this.static_actor.has_opacity(opacity); + if (!this.is_actor_valid()) + return false; + return this.actor.opacity === opacity; + } + + update_surface_opacity(opacity) { + const pipeline_opacity = this.get_pipeline_opacity(opacity); + if (this.static_blur) { + this.static_actor.set_opacity(opacity, pipeline_opacity); + return; + } + this.fade.set_opacity(opacity); + this.pipeline?.set_opacity_factor(pipeline_opacity / 255); + } + + get_pipeline_opacity(opacity) { + if (!this.style.has_any_style_class(this.target, ['screenshot-ui-panel'])) + return opacity; + return Math.round(opacity * (this.parent?.opacity ?? 255) / 255); + } + + get_geometry_actor() { + return this.target; + } + + should_use_content_geometry() { + return !this.uses_full_actor_geometry(); + } + + should_use_margin_geometry() { + return !this.uses_full_actor_geometry(); + } + + uses_full_actor_geometry() { + return ( + this.style.has_any_style_class(this.target, FULL_GEOMETRY_STYLE_CLASSES) + || this.style.has_any_style_class(this.root_actor, FULL_GEOMETRY_STYLE_CLASSES) + ); + } + + is_notification_surface() { + return ( + this.style.has_any_style_class(this.target, NOTIFICATION_STYLE_CLASSES) + || this.style.has_any_style_class(this.root_actor, NOTIFICATION_STYLE_CLASSES) + ); + } + + show_actors() { + if (this.is_actor_valid()) + this.actor?.show(); + } + + hide_actors() { + if (this.is_actor_valid()) + this.actor?.hide(); + } + + is_actor_valid() { + return ( + !!this.actor + && (!this.static_blur || !!this.static_actor?.background_group) + ); + } + + queue_update() { + if (!this.actor || this.update_id || this.transition_update_id) + return; + this.update_id = global.compositor.get_laters().add( + Meta.LaterType.BEFORE_REDRAW, + () => { + this.update_id = 0; + if (this.actor) + this.update(); + return GLib.SOURCE_REMOVE; + } + ); + } + + queue_transition_update(transition_state = null) { + const has_running_transition = transition_state?.running ?? this.transitions.has_running(); + if (!this.actor || this.transition_update_id || this.update_id || !has_running_transition) + return; + this.transition_update_id = global.compositor.get_laters().add( + Meta.LaterType.BEFORE_REDRAW, + () => { + this.transition_update_id = 0; + if (this.actor) + this.update(); + return GLib.SOURCE_REMOVE; + } + ); + } + + connect_repaints() { + if (this.static_blur || this.settings.HACKS_LEVEL !== 1) + return; + const repaint_source = { + queue_repaint: () => this.queue_repaint(), + }; + [ + this.blur_actor, + this.target, + ].forEach(actor => { + this.paint_signals.disconnect_all_for_actor(actor); + this.paint_signals.connect(actor, repaint_source); + }); + } + + update_settings() { + this.style.update_target_style(); + this.static_actor?.update_settings(); + } + + get_corner_radius() { + return this.settings.popup[this.corner_radius.property] ?? this.settings.popup.CORNER_RADIUS; + } + + queue_repaint(force = false) { + if (this.static_blur || this.repaint_id || (!force && !this.should_repaint())) + return; + this.repaint_id = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this.repaint_id = 0; + if (force || this.should_repaint()) + this.pipeline?.repaint_effect(); + return GLib.SOURCE_REMOVE; + }); + } + + should_repaint() { + return this.is_visible(); + } + + is_visible() { + return this.is_actor_visible(this.target) && this.is_actor_visible(this.root_actor); + } + + is_keyboard_surface() { + return ( + this.style.has_style_class(this.target, 'bms-keyboard-surface') + || this.style.has_style_class(this.root_actor, 'bms-keyboard-surface') + ); + } + + is_actor_visible(actor) { + if (!actor) + return false; + if (actor.visible && actor.mapped) + return true; + if (!this.is_keyboard_surface()) + return false; + + const parent = actor.get_parent(); + return parent?.visible && parent?.mapped; + } + + update_pipeline() { + if (this.static_blur) { + this.static_actor.update_pipeline(); + this.sync_static_actor(); + } + } + + destroy() { + if (this.update_id) { + global.compositor.get_laters().remove(this.update_id); + this.update_id = 0; + } + if (this.transition_update_id) { + global.compositor.get_laters().remove(this.transition_update_id); + this.transition_update_id = 0; + } + if (this.repaint_id) { + GLib.Source.remove(this.repaint_id); + this.repaint_id = 0; + } + + this.signals.disconnect_all(); + this.paint_signals.disconnect_all_for_actor(this.blur_actor); + this.paint_signals.disconnect_all_for_actor(this.target); + this.style.restore_target_style(); + + if (this.static_blur) + this.destroy_static_actor(); + else + this.destroy_dynamic_actor(); + + this.fade = null; + } + + destroy_dynamic_actor() { + const pipeline = this.pipeline; + const blur_actor = this.blur_actor; + this.pipeline = null; + this.blur_actor = null; + this.actor = null; + if (pipeline) + pipeline.destroy(); + if (blur_actor) + blur_actor.destroy(); + } + + destroy_static_actor() { + if (this.static_actor) + this.static_actor.destroy(); + this.static_actor = null; + this.actor = null; + this.blur_actor = null; + this.pipeline = null; + this.placement.clear(); + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/index.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/index.js new file mode 100644 index 0000000..edc9369 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/index.js @@ -0,0 +1,556 @@ +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +import { + POPUP_BACKGROUND_STYLES, + POPUP_SURFACE_STYLES, + PopupBlurTargets, +} from './targets.js'; +import { PopupBlurSurface } from './blur_surface.js'; +import { PopupBlurMessageStacks } from './message_stacks.js'; + +const POPUP_INTERNAL_STYLE_CLASSES = ['bms-popup-blurred-widget', 'bms-popup-backgroundgroup']; +const POPUP_INTERNAL_NAMES = ['bms-popup-blurred-widget', 'bms-popup-backgroundgroup']; +const KEYBOARD_STYLE_CLASS = 'bms-keyboard-surface'; + +export const PopupBlur = class PopupBlur { + constructor(connections, settings, effects_manager) { + this.connections = connections; + this.settings = settings; + this.effects_manager = effects_manager; + this.surfaces = new Map(); + this.containers = new Set(); + this.queued_actors = new Set(); + this.watched_actors = new WeakSet(); + this.destroyed_actors = new WeakSet(); + this.interface_settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' }); + this.message_stacks = new PopupBlurMessageStacks(connections); + this.targets = new PopupBlurTargets(this); + this.queue_id = 0; + this.follow_up_queue_id = 0; + this.follow_up_actors = new Set(); + this.reset_id = 0; + this.enabled = false; + } + + enable() { + if (this.enabled) { + this._log("blur already enabled"); + return; + } + + this._log("blurring popup surfaces"); + this.enabled = true; + + this.update_background(); + this.message_stacks.enable(); + this.connections.connect( + this.interface_settings, + 'changed::color-scheme', + () => this.update_background() + ); + this.connections.connect( + this.interface_settings, + 'changed::gtk-theme', + () => this.update_background() + ); + this.connections.connect( + Main.sessionMode, + 'updated', + () => this.update_background() + ); + this.connections.connect( + St.Settings.get(), + 'notify::color-scheme', + () => this.update_background() + ); + + this.track_container(Main.uiGroup); + (Main.osdWindowManager?._osdWindows ?? []).forEach(window => this.track_container(window)); + this.track_container(Main.layoutManager?.modalDialogGroup); + this.track_container(Main.layoutManager?.screenShieldGroup); + this.track_container(Main.layoutManager?.unlockDialogGroup); + this.track_container(Main.messageTray?._bannerBin); + this.track_quick_settings(); + this.track_container(global.window_group); + this.track_keyboard(); + this.track_container(Main.keyboard?.keyboardActor); + } + + track_container(container) { + if (!container || this.containers.has(container)) + return; + if (!this.watch_actor(container)) + return; + + this.containers.add(container); + this.message_stacks.track_container(container); + + this.get_children(container).forEach(child => this.try_blur(child)); + + this.connections.connect( + container, + 'child-added', + (_, child) => this.queue_try_blur(child) + ); + } + + track_quick_settings() { + const menu = Main.panel?.statusArea?.quickSettings?.menu; + this.track_container(menu?._overlay); + } + + track_keyboard() { + const keyboard_box = Main.layoutManager?.keyboardBox; + this.track_container(keyboard_box); + if (!keyboard_box) + return; + + this.connections.connect( + keyboard_box, + 'child-added', + (_, child) => this.update_keyboard_style(child) + ); + } + + try_blur(actor) { + if (this.is_internal_actor(actor) || !this.watch_actor(actor)) + return; + + this.message_stacks.scan(actor); + this.track_container(this.get_actor_overlay(actor)); + + const targets = this.get_blur_targets(actor); + + if (this.targets.is_blur_target_actor(actor)) + this.track_container(actor); + + if (this.has_style_class(actor, 'switcher-popup')) + this.track_container(actor); + + if (this.is_window_actor(actor)) + this.track_container(actor); + + targets.forEach(target => this.blur_actor(target, actor)); + } + + queue_try_blur(actor) { + if (this.is_internal_actor(actor) || !this.watch_actor(actor)) + return; + + this.queued_actors.add(actor); + if (this.queue_id) + return; + + this.queue_id = global.compositor.get_laters().add(Meta.LaterType.BEFORE_REDRAW, () => { + this.queue_id = 0; + const actors = [...this.queued_actors]; + this.queued_actors.clear(); + + this.try_blur_actors(actors); + this.queue_follow_up_blurs(actors); + + return GLib.SOURCE_REMOVE; + }); + } + + queue_follow_up_blurs(actors) { + actors.forEach(actor => this.follow_up_actors.add(actor)); + + if (this.follow_up_queue_id || this.follow_up_actors.size === 0) + return; + + this.follow_up_queue_id = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this.follow_up_queue_id = 0; + const actors = [...this.follow_up_actors]; + this.follow_up_actors.clear(); + + this.try_blur_actors(actors); + + return GLib.SOURCE_REMOVE; + }); + } + + try_blur_actors(actors) { + if (!this.enabled) + return; + + actors.forEach(actor => { + if (!this.destroyed_actors.has(actor) && !this.is_internal_actor(actor)) + this.try_blur(actor); + }); + } + + cancel_queued_blurs() { + if (this.queue_id) { + global.compositor.get_laters().remove(this.queue_id); + this.queue_id = 0; + } + if (this.follow_up_queue_id) { + GLib.source_remove(this.follow_up_queue_id); + this.follow_up_queue_id = 0; + } + this.queued_actors.clear(); + this.follow_up_actors.clear(); + } + + blur_actor(target, root_actor) { + if ( + this.is_internal_actor(target) + || this.is_internal_actor(root_actor) + || !this.watch_actor(target) + || !this.watch_actor(root_actor) + ) + return; + + if (this.surfaces.has(target)) + return; + + this.destroy_blurred_ancestors(target); + + const { parent, sibling } = this.get_overlay_parent(root_actor, target); + const surface = new PopupBlurSurface( + this.connections, + this.settings, + this.effects_manager, + target, + root_actor, + parent, + sibling, + this.get_corner_radius(target, root_actor), + () => this.enabled && this.surfaces.has(target) + ); + + this.surfaces.set(target, surface); + + if (surface.enable()) + return this.connect_surface(target, root_actor); + + this.surfaces.delete(target); + surface.destroy(); + } + + connect_surface(target, root_actor) { + this.connections.connect( + target, + 'destroy', + () => this.destroy_blur(target) + ); + + if (root_actor !== target) { + this.connections.connect( + root_actor, + 'destroy', + () => this.destroy_blur(target) + ); + } + } + + destroy_blurred_ancestors(actor) { + actor = actor ? actor.get_parent() : null; + + while (actor && !this.destroyed_actors.has(actor)) { + if (this.surfaces.has(actor) && this.targets.prefers_descendant_targets(actor)) { + this.destroy_blur(actor); + } + + actor = actor.get_parent(); + } + } + + get_overlay_parent(root_actor, target) { + if (this.has_any_style_class(target, ['screenshot-ui-panel'])) { + const parent = Main.screenshotUI ?? Main.uiGroup; + let sibling = target; + while (sibling && sibling.get_parent() && sibling.get_parent() !== parent) { + sibling = sibling.get_parent(); + } + return { + parent, + sibling: sibling ?? target, + }; + } + + let actor = root_actor; + let child = null; + while (actor && !this.destroyed_actors.has(actor)) { + let parent = null; + parent = actor.get_parent(); + + if (!parent) + break; + + if (parent === Main.layoutManager.unlockDialogGroup || + parent === Main.layoutManager.screenShieldGroup || + parent === Main.uiGroup || + parent === Main.layoutManager.uiGroup) + return { parent, sibling: actor }; + + if (parent === global.window_group) + return { parent: actor, sibling: child ?? actor.get_last_child() }; + + child = actor; + actor = parent; + } + + return { parent: Main.uiGroup, sibling: null }; + } + + get_corner_radius(target, root_actor) { + return this.targets.get_corner_radius(target, root_actor); + } + + get_blur_targets(actor) { + return this.targets.find(actor); + } + + has_any_style_class(actor, style_classes) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + + if (actor.get_style_class_name) { + const class_names = actor.get_style_class_name(); + if (typeof class_names === 'string') { + const normalized = ` ${class_names.trim().replace(/\s+/g, ' ')} `; + return style_classes.some(style_class => normalized.includes(` ${style_class} `)); + } + } + + return style_classes.some(style => this.has_style_class(actor, style)); + } + + has_style_class(actor, style_class) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + + if (actor.has_style_class_name) + return actor.has_style_class_name(style_class); + + if (actor.get_style_class_name) + return (actor.get_style_class_name() ?? '').split(/\s+/).includes(style_class); + + return false; + } + + is_window_actor(actor) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + return actor.get_parent() === global.window_group; + } + + is_internal_actor(actor) { + if (!actor) + return false; + + if (this.has_any_style_class(actor, POPUP_INTERNAL_STYLE_CLASSES)) + return true; + + if (this.destroyed_actors.has(actor)) + return false; + + return POPUP_INTERNAL_NAMES.includes(actor.name ?? actor.get_name()); + } + + watch_actor(actor) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + if (this.watched_actors.has(actor)) + return true; + + this.connections.connect(actor, 'destroy', () => { + this.destroyed_actors.add(actor); + this.containers.delete(actor); + }); + + this.watched_actors.add(actor); + return true; + } + + get_children(actor) { + if (!this.watch_actor(actor)) + return []; + + return actor.get_children(); + } + + get_actor_delegate(actor) { + if (!this.watch_actor(actor)) + return null; + + return actor._delegate || null; + } + + get_actor_overlay(actor) { + const delegate = this.get_actor_delegate(actor); + return (delegate && delegate._overlay) || null; + } + + get_actor_dialog_layout(actor) { + if (!this.watch_actor(actor)) + return null; + + return actor.dialogLayout || null; + } + + destroy_blur(actor) { + const surface = this.surfaces.get(actor); + if (!surface) + return; + + this.surfaces.delete(actor); + surface.destroy(); + } + + reset() { + if (!this.enabled) + return; + + this.cancel_queued_blurs(); + + this.disable(); + this.reset_id = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this.reset_id = 0; + + if (this.settings.popup.BLUR) + this.enable(); + + return GLib.SOURCE_REMOVE; + }); + } + + update_pipeline() { + this.surfaces.forEach(surface => surface.update_pipeline()); + } + + _get_keyboard_actors() { + const actors = new Set(); + if (Main.keyboard && Main.keyboard.keyboardActor) + actors.add(Main.keyboard.keyboardActor); + if (Main.keyboard && Main.keyboard._keyboard) + actors.add(Main.keyboard._keyboard); + if (Main.layoutManager.keyboardBox) + Main.layoutManager.keyboardBox.get_children().forEach(child => actors.add(child)); + return [...actors]; + } + + update_background() { + [...POPUP_BACKGROUND_STYLES, ...POPUP_SURFACE_STYLES] + .forEach(style => Main.uiGroup.remove_style_class_name(style)); + + const keyboard_actors = this._get_keyboard_actors(); + keyboard_actors.forEach(actor => this.clear_keyboard_style(actor)); + [...POPUP_BACKGROUND_STYLES, ...POPUP_SURFACE_STYLES] + .forEach(style => keyboard_actors.forEach(actor => actor.remove_style_class_name(style))); + + let background_style = null; + if (this.settings.popup.OVERRIDE_BACKGROUND) { + const style = this.get_background_style(); + background_style = this.settings.popup.PRESERVE_SHELL_THEME ? + POPUP_SURFACE_STYLES[style] : + POPUP_BACKGROUND_STYLES[style]; + Main.uiGroup.add_style_class_name( + background_style + ); + } + keyboard_actors.forEach(actor => this.update_keyboard_style(actor, background_style)); + this.surfaces.forEach(surface => surface.update_settings()); + } + + clear_keyboard_style(actor, remove_marker = false) { + if (!actor) + return; + + if (remove_marker) + actor.remove_style_class_name(KEYBOARD_STYLE_CLASS); + + if (!this.settings.popup.OVERRIDE_BACKGROUND) + return; + + [...POPUP_BACKGROUND_STYLES, ...POPUP_SURFACE_STYLES] + .forEach(style => actor.remove_style_class_name(style)); + } + + update_keyboard_style(actor, background_style = null) { + if (!actor) + return; + + this.clear_keyboard_style(actor); + actor.add_style_class_name(KEYBOARD_STYLE_CLASS); + if (!this.settings.popup.OVERRIDE_BACKGROUND) + return; + + const style = background_style ?? ( + this.settings.popup.PRESERVE_SHELL_THEME ? + POPUP_SURFACE_STYLES[this.get_background_style()] : + POPUP_BACKGROUND_STYLES[this.get_background_style()] + ); + actor.add_style_class_name(style); + } + + get_background_style() { + const style = this.settings.popup.STYLE_POPUP; + + if (style >= 0 && style < POPUP_BACKGROUND_STYLES.length) + return style; + + const theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); + let uri = theme?.default_stylesheet?.get_uri(); + if (theme?.application_stylesheet) + uri = theme?.application_stylesheet?.get_uri(); + const lower_uri = typeof uri === 'string' ? uri.toLowerCase() : ''; + if (lower_uri.includes('yaru')) + return lower_uri.includes('dark') ? 2 : 1; + + const shell_style = Main.getStyleVariant(); + if (shell_style === 'light') + return 1; + + if (shell_style === 'dark') + return 2; + + return this.interface_settings.get_string('color-scheme') === 'prefer-dark' ? 2 : 1; + } + + disable() { + if (this.reset_id) { + GLib.source_remove(this.reset_id); + this.reset_id = 0; + } + + this.cancel_queued_blurs(); + + if (!this.enabled) { + this._log("blur already removed"); + return; + } + + this._log("removing blur from popup surfaces"); + this.enabled = false; + + const keyboard_actors = this._get_keyboard_actors(); + keyboard_actors.forEach(actor => this.clear_keyboard_style(actor, true)); + [...POPUP_BACKGROUND_STYLES, ...POPUP_SURFACE_STYLES] + .forEach(style => keyboard_actors.forEach(actor => actor.remove_style_class_name(style))); + + [...POPUP_BACKGROUND_STYLES, ...POPUP_SURFACE_STYLES] + .forEach(style => Main.uiGroup.remove_style_class_name(style)); + + const actors = [...this.surfaces.keys()]; + actors.forEach(actor => this.destroy_blur(actor)); + this.surfaces.clear(); + this.containers.clear(); + this.watched_actors = new WeakSet(); + this.destroyed_actors = new WeakSet(); + this.message_stacks.disable(); + + this.connections.disconnect_all(); + } + + _log(str) { + if (this.settings.DEBUG) + console.log(`[Blur my Shell > popup blur] ${str}`); + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/message_stacks.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/message_stacks.js new file mode 100644 index 0000000..cc4af30 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/message_stacks.js @@ -0,0 +1,571 @@ +import GLib from 'gi://GLib'; + +const STACKED_PSEUDO_CLASSES = ['second-in-stack', 'lower-in-stack']; +const MESSAGE_CONTAINER_STYLE_CLASSES = ['message-list', 'message-view', 'message-notification-group']; +const INTERNAL_STYLE_CLASSES = ['bms-popup-blurred-widget', 'bms-popup-backgroundgroup']; +const INTERNAL_NAMES = ['bms-popup-blurred-widget', 'bms-popup-backgroundgroup']; + +export const PopupBlurMessageStacks = class PopupBlurMessageStacks { + constructor(connections) { + this.connections = connections; + this.containers = new Set(); + this.groups = new Set(); + this.messages = new Set(); + this.queued_actors = new WeakSet(); + this.watched_actors = new WeakSet(); + this.destroyed_actors = new WeakSet(); + this.bms_clipped = new WeakSet(); + this.group_connections = new Map(); + this.update_ids = new Map(); + this.original_opacity = new WeakMap(); + this.original_message_opacity = new WeakMap(); + this.enabled = false; + } + + enable() { + this.enabled = true; + } + + track_container(container) { + if (!container || this.containers.has(container)) + return; + if (!this.watch_actor(container)) + return; + + this.containers.add(container); + this.scan(container); + + this.connect( + container, + 'child-added', + (_, child) => { + this.queue_scan(child); + this.queue_update_all(); + } + ); + + this.connect( + container, + 'child-removed', + () => this.queue_update_all() + ); + } + + queue_scan(actor) { + if (this.is_internal_actor(actor) || !this.watch_actor(actor) || this.queued_actors.has(actor)) + return; + + this.queued_actors.add(actor); + this.scan(actor); + this.queued_actors.delete(actor); + } + + scan(actor, seen = new WeakSet()) { + if (this.is_internal_actor(actor) || !this.watch_actor(actor) || seen.has(actor)) + return; + + seen.add(actor); + + if (this.has_any_style_class(actor, MESSAGE_CONTAINER_STYLE_CLASSES)) + this.track_container(actor); + + if (this.has_style_class(actor, 'message-notification-group')) + this.track_group(actor); + + if (this.has_style_class(actor, 'message')) + this.track_message(actor); + + this.get_children(actor).forEach(child => this.scan(child, seen)); + + const child = this.get_child(actor); + if (child) + this.scan(child, seen); + } + + track_message(message) { + if (this.is_internal_actor(message) || !this.watch_actor(message) || this.messages.has(message)) + return; + + this.messages.add(message); + this.update_message(message); + + this.connect( + message, + 'style-changed', + () => this.queue_update_message(message) + ); + + this.connect( + message, + 'notify::pseudo-class', + () => this.queue_update_message(message) + ); + + this.connect( + message, + 'notify::allocation', + () => this.queue_update_message(message) + ); + + this.connect( + message, + 'destroy', + () => { + this.cancel_update(message); + this.remove_stack_mask(message); + this.messages.delete(message); + } + ); + } + + track_group(group) { + if (!this.watch_actor(group) || this.groups.has(group)) + return; + + this.groups.add(group); + this.update_group_header(group); + + this.connect( + group, + 'notify::expanded', + () => this.update_all() + ); + + this.connect( + group, + 'notify::allocation', + () => this.update_group_header(group) + ); + + if (group.layout_manager) { + const lm = group.layout_manager; + const id = lm.connect('notify::expansion', () => { + this.update_group_header(group); + this.update_group_messages(group); + }); + this.group_connections.set(group, { lm, id }); + } + } + + untrack_group(group) { + if (this.group_connections.has(group)) { + const { lm, id } = this.group_connections.get(group); + lm.disconnect(id); + this.group_connections.delete(group); + } + this.groups.delete(group); + } + + update_all() { + this.groups.forEach(group => { + this.update_group_header(group); + this.update_group_messages(group); + }); + this.messages.forEach(message => { + if (!this.get_message_group(message)) + this.update_message(message); + }); + } + + update_group_header(group) { + if (!this.watch_actor(group)) + return; + + const header = group._headerBox; + if (!header) + return; + + const expansion = group.layout_manager?.expansion ?? (group.expanded ? 1 : 0); + const target_opacity = this.enabled ? Math.round(expansion * 255) : 255; + + header.opacity = target_opacity; + } + + get_group_messages(group) { + const list = []; + const find_messages = actor => { + if (this.has_style_class(actor, 'message')) { + list.push(actor); + return; + } + this.get_children(actor).forEach(find_messages); + }; + find_messages(group); + return list; + } + + update_group_messages(group) { + if (!this.enabled || !this.watch_actor(group)) + return; + + const expansion = group.layout_manager?.expansion ?? (group.expanded ? 1.0 : 0.0); + const messages = this.get_group_messages(group); + + messages.forEach((message, index) => { + this.update_message_with_expansion(message, index, expansion, group); + }); + } + + queue_update_all() { + this.messages.forEach(message => this.queue_update_message(message)); + } + + queue_update_message(message) { + if (this.update_ids.has(message)) + return; + + const id = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this.update_ids.delete(message); + + if (this.enabled && this.messages.has(message)) + this.update_message(message); + + return GLib.SOURCE_REMOVE; + }); + + this.update_ids.set(message, id); + } + + update_message(message) { + if (!this.enabled) { + this.remove_stack_mask(message); + return; + } + + const group = this.get_message_group(message); + if (group) { + const expansion = group.layout_manager?.expansion ?? (group.expanded ? 1.0 : 0.0); + const index = this.get_message_index_in_group(message); + this.update_message_with_expansion(message, index >= 0 ? index : 0, expansion, group); + } else { + const is_expanded = this.is_in_expanded_group(message); + if (this.is_stacked(message) && !is_expanded) { + this.apply_stack_mask(message); + this.hide_message_content(message); + this.restore_message_opacity(message); + } else { + this.remove_stack_mask(message); + } + } + } + + update_message_with_expansion(message, index, expansion, group) { + if (!this.enabled || !this.watch_actor(message)) + return; + + const is_stacked = this.is_stacked(message) || index > 0; + + if (!is_stacked) { + this.remove_stack_mask(message); + return; + } + + if (expansion >= 0.99) { + this.remove_stack_mask(message); + return; + } + + if (expansion <= 0.01) { + this.apply_stack_mask(message); + this.hide_message_content(message); + this.restore_message_opacity(message); + return; + } + + // 0.01 < expansion < 0.99: Animating unravel / collapse + const height = message.height || message.get_height() || 0; + const width = message.width || message.get_width() || 0; + + if (height <= 0 || width <= 0) + return; + + const base_edge = (index === 1) ? 10 : (index === 2 ? 7 : 0); + const visible_edge = Math.round(base_edge + (height - base_edge) * expansion); + const clip_y = Math.max(0, height - visible_edge); + + message.set_clip(0, clip_y, width, visible_edge); + this.bms_clipped.add(message); + + // Staggered opacity progress for cards so they smoothly fade in as they unravel + const stagger_start = Math.min(0.8, index * 0.04); + const progress = Math.min(1.0, Math.max(0.0, (expansion - stagger_start) / (1.0 - stagger_start))); + const target_alpha = Math.round(255 * progress); + + this.set_child_opacity(message, target_alpha); + + if (index >= 3) { + this.set_message_opacity(message, target_alpha); + } else { + this.restore_message_opacity(message); + } + } + + apply_stack_mask(message) { + if (!this.watch_actor(message)) + return; + + const height = message.height || message.get_height() || 0; + const width = message.width || message.get_width() || 0; + + if (height <= 0 || width <= 0) + return; + + const index = this.get_message_index_in_group(message); + + if (index === 1) { + // 2nd card: tight visible strip of 10px (matches exact 10px bottom offset below card 1) + const visible_edge = 10; + const clip_y = Math.max(0, height - visible_edge); + message.set_clip(0, clip_y, width, visible_edge); + this.bms_clipped.add(message); + } else if (index === 2) { + // 3rd card: tight visible strip of 7px (matches exact 7px bottom offset below card 2) + const visible_edge = 7; + const clip_y = Math.max(0, height - visible_edge); + message.set_clip(0, clip_y, width, visible_edge); + this.bms_clipped.add(message); + } else if (index >= 3) { + // 4th+ cards: hidden when collapsed so they don't stack behind card 3 + message.set_clip(0, height, width, 0); + this.bms_clipped.add(message); + } else { + // Fallback by pseudo class + const is_second = this.has_pseudo_class(message, 'second-in-stack'); + const visible_edge = is_second ? 10 : 6; + const clip_y = Math.max(0, height - visible_edge); + message.set_clip(0, clip_y, width, visible_edge); + this.bms_clipped.add(message); + } + } + + remove_stack_mask(message) { + if (!message || this.destroyed_actors.has(message)) + return; + + if (this.bms_clipped.has(message)) { + message.remove_clip(); + this.bms_clipped.delete(message); + } + + this.restore_message_content(message); + this.restore_message_opacity(message); + } + + set_child_opacity(message, opacity) { + const child = this.get_child(message); + if (!child) + return; + + if (!this.original_opacity.has(child)) + this.original_opacity.set(child, child.opacity ?? 255); + + child.opacity = opacity; + } + + hide_message_content(message) { + this.set_child_opacity(message, 0); + } + + restore_message_content(message) { + const child = this.get_child(message); + if (!child) + return; + + if (this.original_opacity.has(child)) { + child.opacity = this.original_opacity.get(child); + this.original_opacity.delete(child); + } + } + + set_message_opacity(message, opacity) { + if (!this.watch_actor(message)) + return; + + if (!this.original_message_opacity.has(message)) + this.original_message_opacity.set(message, message.opacity ?? 255); + + message.opacity = opacity; + } + + restore_message_opacity(message) { + if (!message || this.destroyed_actors.has(message)) + return; + + if (this.original_message_opacity.has(message)) { + message.opacity = this.original_message_opacity.get(message); + this.original_message_opacity.delete(message); + } + } + + get_message_index_in_group(message) { + const group = this.get_message_group(message); + if (!group) + return -1; + + const list = []; + const find_messages = actor => { + if (this.has_style_class(actor, 'message')) { + list.push(actor); + return; + } + actor.get_children().forEach(find_messages); + }; + find_messages(group); + return list.indexOf(message); + } + + has_pseudo_class(actor, pseudo_class) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + + if (actor.has_style_pseudo_class) + return actor.has_style_pseudo_class(pseudo_class); + + if (actor.get_style_pseudo_class) + return (actor.get_style_pseudo_class() ?? '').split(/\s+/).includes(pseudo_class); + + return false; + } + + get_message_group(message) { + let actor = message ? message.get_parent() : null; + + while (actor && !this.destroyed_actors.has(actor)) { + if (this.has_style_class(actor, 'message-notification-group')) + return actor; + actor = actor.get_parent(); + } + return null; + } + + is_in_expanded_group(message) { + let actor = message ? message.get_parent() : null; + + while (actor && !this.destroyed_actors.has(actor)) { + if ( + this.has_style_class(actor, 'message-notification-group') + && actor.expanded + ) + return true; + actor = actor.get_parent(); + } + + return false; + } + + is_stacked(message) { + if (!message || this.destroyed_actors.has(message)) + return false; + return STACKED_PSEUDO_CLASSES.some(pseudo_class => { + if (message.has_style_pseudo_class) + return message.has_style_pseudo_class(pseudo_class); + + if (message.get_style_pseudo_class) + return (message.get_style_pseudo_class() ?? '').split(/\s+/).includes(pseudo_class); + + return false; + }); + } + + has_any_style_class(actor, style_classes) { + return style_classes.some(style_class => this.has_style_class(actor, style_class)); + } + + is_internal_actor(actor) { + if (!actor) + return false; + + if (this.has_any_style_class(actor, INTERNAL_STYLE_CLASSES)) + return true; + + if (this.destroyed_actors.has(actor)) + return false; + + return INTERNAL_NAMES.includes(actor.name ?? actor.get_name()); + } + + has_style_class(actor, style_class) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + + if (actor.has_style_class_name) + return actor.has_style_class_name(style_class); + + if (actor.get_style_class_name) + return (actor.get_style_class_name() ?? '').split(/\s+/).includes(style_class); + + return false; + } + + watch_actor(actor) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + if (this.watched_actors.has(actor)) + return true; + + this.connections.connect(actor, 'destroy', () => { + this.destroyed_actors.add(actor); + this.containers.delete(actor); + if (this.groups.has(actor)) { + this.untrack_group(actor); + } + this.messages.delete(actor); + this.cancel_update(actor); + }); + + this.watched_actors.add(actor); + return true; + } + + get_children(actor) { + if (this.is_internal_actor(actor) || !this.watch_actor(actor)) + return []; + + return actor.get_children(); + } + + get_child(actor) { + if (!this.watch_actor(actor)) + return null; + + return (actor.get_child ? actor.get_child() : actor.child) || null; + } + + cancel_update(message) { + const id = this.update_ids.get(message); + if (!id) + return; + + GLib.source_remove(id); + this.update_ids.delete(message); + } + + connect(actor, signal, handler) { + this.connections.connect(actor, signal, handler); + } + + disable() { + this.update_ids.forEach(id => GLib.source_remove(id)); + this.update_ids.clear(); + this.messages.forEach(message => this.remove_stack_mask(message)); + this.groups.forEach(group => { + if (group._headerBox) { + group._headerBox.opacity = 255; + } + this.untrack_group(group); + }); + this.group_connections.forEach(({ lm, id }) => { + lm.disconnect(id); + }); + this.group_connections.clear(); + this.messages.clear(); + this.groups.clear(); + this.containers.clear(); + this.watched_actors = new WeakSet(); + this.destroyed_actors = new WeakSet(); + this.bms_clipped = new WeakSet(); + this.original_opacity = new WeakMap(); + this.original_message_opacity = new WeakMap(); + this.enabled = false; + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/static_actor.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/static_actor.js new file mode 100644 index 0000000..c933b36 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/static_actor.js @@ -0,0 +1,301 @@ +import Meta from 'gi://Meta'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +import { Pipeline } from '../../conveniences/pipeline.js'; +import { transform_to_actor_space } from './surface_geometry.js'; +import { PopupBlurStaticCorner } from './static_corner.js'; +import { has_style_class } from './targets.js'; + +export const PopupBlurStaticActor = class PopupBlurStaticActor { + constructor(settings, effects_manager, target, root_actor, parent, get_corner_radius) { + this.settings = settings; + this.effects_manager = effects_manager; + this.target = target; + this.root_actor = root_actor; + this.parent = parent; + this.get_corner_radius = get_corner_radius; + this.static_corner = new PopupBlurStaticCorner(effects_manager, get_corner_radius); + this.background_group = null; + this.blur_actor = null; + this.bg_manager = null; + this.pipeline = null; + this.monitor_index = null; + this.opacity_factor = 1; + this.background_opacity = null; + this.x = null; + this.y = null; + this.width = null; + this.height = null; + } + + create() { + this.background_group = new Meta.BackgroundGroup({ + name: 'bms-popup-backgroundgroup', + width: 0, + height: 0, + }); + this.background_group.hide(); + this.background_group.connect('destroy', () => { + this.background_group = null; + }); + + return this.update_background(); + } + + get actor() { + return this.background_group; + } + + update_background(monitor_index = null) { + const monitor = this.find_monitor(monitor_index); + if (!monitor) + return false; + + if (monitor.index === this.monitor_index && this.blur_actor) + return true; + + this.destroy_background(); + + const bg_manager_list = []; + const pipeline = new Pipeline( + this.effects_manager, + global.blur_my_shell._pipelines_manager, + this.settings.popup.PIPELINE, + null, + { + effect_overrides: { + native_static_gaussian_blur: params => this.get_blur_effect_overrides(params, 'unscaled_radius'), + gaussian_blur: params => this.get_blur_effect_overrides(params, 'radius'), + monte_carlo_blur: params => this.get_blur_effect_overrides(params, 'radius'), + downscale: () => this.get_texture_effect_overrides(), + upscale: () => this.get_texture_effect_overrides(), + pixelize: () => this.get_texture_effect_overrides(), + derivative: () => this.get_texture_effect_overrides(), + refraction: () => this.get_texture_effect_overrides(), + color: params => this.get_color_effect_overrides(params), + luminosity: () => this.get_luminosity_effect_overrides(), + noise: params => this.get_noise_effect_overrides(params), + rgb_to_hsl: () => this.get_texture_effect_overrides(), + hsl_to_rgb: () => this.get_texture_effect_overrides(), + corner: () => ({ radius: this.get_corner_radius() }), + }, + } + ); + + this.blur_actor = pipeline.create_background_with_effects( + monitor.index, + bg_manager_list, + this.background_group, + 'bms-popup-blurred-widget' + ); + this.blur_actor.connect('destroy', () => { + this.blur_actor = null; + }); + this.blur_actor.hide(); + this.bg_manager = bg_manager_list[0]; + this.pipeline = pipeline; + this.monitor_index = monitor.index; + this.static_corner.bind(this.pipeline, this.blur_actor); + + return true; + } + + find_monitor(monitor_index = null) { + if (monitor_index !== null) + return Main.layoutManager.monitors[monitor_index] || null; + + return ( + Main.layoutManager.findMonitorForActor(this.target) + ?? Main.layoutManager.findMonitorForActor(this.root_actor) + ?? Main.layoutManager.primaryMonitor + ); + } + + is_screenshot_ui() { + return has_style_class(this.target, 'screenshot-ui-panel') + || has_style_class(this.root_actor, 'screenshot-ui-panel'); + } + + update_geometry(target_x, target_y, width, height, monitor_index = null) { + if (!this.update_background(monitor_index)) + return false; + if (!this.blur_actor) + return false; + + const monitor = Main.layoutManager.monitors[this.monitor_index]; + if (!monitor) + return false; + + const monitor_geometry = transform_to_actor_space(this.parent, monitor); + const target_geometry = transform_to_actor_space(this.parent, { + x: target_x, + y: target_y, + width, + height, + }); + if (!monitor_geometry || !target_geometry) + return false; + + const clip_x = Math.round(target_geometry.x - monitor_geometry.x); + const clip_y = Math.round(target_geometry.y - monitor_geometry.y); + const clip_width = Math.ceil(target_geometry.width); + const clip_height = Math.ceil(target_geometry.height); + + if (!this.blur_actor) + return false; + + if (this.is_screenshot_ui() && this.background_group) { + this.background_group.set_position(0, 0); + this.background_group.set_size(monitor_geometry.width, monitor_geometry.height); + } + + if (this.blur_actor.x !== monitor_geometry.x || this.blur_actor.y !== monitor_geometry.y) + this.blur_actor.set_position(monitor_geometry.x, monitor_geometry.y); + if ( + this.blur_actor.width !== monitor_geometry.width + || this.blur_actor.height !== monitor_geometry.height + ) + this.blur_actor.set_size(monitor_geometry.width, monitor_geometry.height); + + if ( + this.x !== clip_x + || this.y !== clip_y + || this.width !== clip_width + || this.height !== clip_height + ) { + this.blur_actor.set_clip(clip_x, clip_y, clip_width, clip_height); + this.x = clip_x; + this.y = clip_y; + this.width = clip_width; + this.height = clip_height; + } + + this.blur_actor.show(); + + return { x: clip_x, y: clip_y, width: clip_width, height: clip_height }; + } + + has_opacity(opacity) { + if (this.background_opacity !== opacity) + return false; + + const background_actor = this.get_background_actor(); + return !background_actor || background_actor.opacity === opacity; + } + + set_opacity(opacity, pipeline_opacity = opacity) { + this.set_opacity_factor(pipeline_opacity / 255); + if (this.background_group) + this.background_group.opacity = 255; + if (this.blur_actor) + this.blur_actor.opacity = 255; + + this.background_opacity = opacity; + + const background_actor = this.get_background_actor(); + if (background_actor) + background_actor.opacity = opacity; + + this.blur_actor?.get_children().forEach(child => child.opacity = opacity); + } + + 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.pipeline?.apply_effect_overrides(); + } + + get_blur_effect_overrides(params, radius_key) { + const overrides = {}; + + if (radius_key in params) + overrides[radius_key] = params[radius_key] * this.opacity_factor; + if ('brightness' in params) + overrides.brightness = 1 - (1 - params.brightness) * this.opacity_factor; + + return overrides; + } + + get_color_effect_overrides(params) { + const overrides = this.get_texture_effect_overrides(); + + if (Array.isArray(params.color) && params.color.length >= 4) + overrides.color = params.color; + + return overrides; + } + + get_luminosity_effect_overrides() { + return this.get_texture_effect_overrides(); + } + + get_noise_effect_overrides(params) { + const overrides = this.get_texture_effect_overrides(); + + if ('noise' in params) + overrides.noise = params.noise; + + return overrides; + } + + get_texture_effect_overrides() { + return { + opacity_factor: this.opacity_factor, + }; + } + + get_background_actor() { + return this.bg_manager ? (this.bg_manager.backgroundActor || null) : null; + } + + update_settings() { + this.static_corner.update(); + } + + update_pipeline() { + this.bg_manager?._bms_pipeline.change_pipeline_to(this.settings.popup.PIPELINE); + this.static_corner.update(); + } + + destroy() { + const background_group = this.background_group; + this.destroy_background(); + this.background_group = null; + + if (background_group) + background_group.destroy(); + } + + destroy_background() { + const bg_manager = this.bg_manager; + const blur_actor = this.blur_actor; + this.bg_manager = null; + this.blur_actor = null; + this.pipeline = null; + + this.static_corner.destroy(); + + if (bg_manager) { + if (bg_manager._bms_pipeline) { + bg_manager._bms_pipeline.destroy(); + bg_manager._bms_pipeline = null; + } + if (!blur_actor) + bg_manager.backgroundActor = null; + bg_manager.destroy(); + } else if (this.background_group) { + this.background_group.destroy_all_children(); + } + blur_actor?.destroy(); + + this.monitor_index = null; + this.background_opacity = null; + this.x = null; + this.y = null; + this.width = null; + this.height = null; + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/static_corner.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/static_corner.js new file mode 100644 index 0000000..e575e09 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/static_corner.js @@ -0,0 +1,70 @@ +export const PopupBlurStaticCorner = class PopupBlurStaticCorner { + constructor(effects_manager, get_radius) { + this.effects_manager = effects_manager; + this.get_radius = get_radius; + this.pipeline = null; + this.actor = null; + this.effect = null; + } + + bind(pipeline, actor) { + this.pipeline = pipeline; + this.actor = actor; + this.update(); + } + + update() { + if (!this.pipeline || !this.actor) + return; + + this.pipeline.apply_effect_overrides('corner'); + + if (this.pipeline_has_corner_effect()) { + this.destroy_external_effect(); + return; + } + + if (!this.effect) { + this.effect = this.effects_manager.new_corner_effect({ + radius: this.get_radius(), + }); + this.attach_external_effect_as_outermost(); + return; + } + + this.effect.radius = this.get_radius(); + } + + attach_external_effect_as_outermost() { + const existing_effects = this.pipeline?.effects ?? []; + + existing_effects.forEach(effect => { + this.actor.remove_effect(effect); + }); + + this.actor.add_effect(this.effect); + + existing_effects.forEach(effect => { + this.actor.add_effect(effect); + }); + } + + pipeline_has_corner_effect() { + const corner_class = this.effects_manager.SUPPORTED_EFFECTS.corner.class; + return this.pipeline.effects.some(effect => effect instanceof corner_class); + } + + destroy_external_effect() { + if (!this.effect) + return; + + this.effects_manager.remove(this.effect); + this.effect = null; + } + + destroy() { + this.destroy_external_effect(); + this.pipeline = null; + this.actor = null; + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_fade.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_fade.js new file mode 100644 index 0000000..2a0b9a5 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_fade.js @@ -0,0 +1,132 @@ +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import { has_style_class } from './targets.js'; + +export const PopupBlurSurfaceFade = class PopupBlurSurfaceFade { + constructor(actor, target, root_actor, parent) { + this.actor = actor; + this.target = target; + this.root_actor = root_actor; + this.parent = parent; + } + + get_opacity() { + let opacity = 255; + const visited = new WeakSet(); + let actor = this.target; + + while (actor && actor !== this.parent) { + opacity = this.apply_actor_opacity(opacity, actor, visited); + actor = actor.get_parent(); + } + + opacity = this.apply_actor_opacity(opacity, this.root_actor, visited); + + [ + this.target, + this.root_actor, + ].forEach(actor => { + const paint_opacity = this.get_paint_opacity(actor); + if (paint_opacity !== null) + opacity = Math.min(opacity, paint_opacity); + }); + + if (this.is_notification_banner()) { + opacity = Math.round(opacity * this.get_banner_position_alpha()); + } + + return opacity; + } + + is_notification_banner() { + if (has_style_class(this.target, 'notification-banner')) + return true; + if (this.root_actor?.name === 'notification-container') + return true; + let a = this.target; + while (a && a !== this.parent) { + if (has_style_class(a, 'notification-banner')) + return true; + a = a.get_parent(); + } + return false; + } + + get_banner_position_alpha() { + try { + const [, stage_y] = this.target.get_transformed_position(); + + if (!Main.panel) + return 1; + + const monitor = Main.layoutManager.findMonitorForActor(Main.panel) + ?? Main.layoutManager.primaryMonitor; + + if (!monitor) + return 1; + + const [, panel_y] = Main.panel.get_transformed_position(); + const panel_width = Main.panel.width || Main.panel.get_width() || 0; + const panel_height = Main.panel.height || Main.panel.get_height() || 36; + + // Must be a horizontal panel anchored at the monitor's top edge + const is_horizontal = panel_width > panel_height; + const is_at_monitor_top = Math.abs(panel_y - monitor.y) <= 5; + + if (!is_horizontal || !is_at_monitor_top) + return 1; + + const panel_bottom = panel_y + panel_height; + const threshold = panel_bottom + 5; + + if (stage_y <= panel_y) + return 0; + if (stage_y >= threshold) + return 1; + + return Math.clamp((stage_y - panel_y) / (threshold - panel_y), 0, 1); + } catch (e) { + return 1; + } + } + + set_opacity(opacity) { + this.actor.opacity = opacity; + } + + apply_actor_opacity(opacity, actor, visited) { + if (!actor || visited.has(actor)) + return opacity; + + visited.add(actor); + + let visible = actor.visible; + let mapped = actor.mapped; + if (!visible && this.is_keyboard_actor(actor)) { + const parent = actor.get_parent(); + visible = parent?.visible && parent?.mapped; + mapped = parent?.mapped; + } + if (!visible || !mapped) + return 0; + + return Math.round(opacity * (actor.opacity ?? 255) / 255); + } + + is_keyboard_actor(actor) { + return ( + has_style_class(actor, 'bms-keyboard-surface') + ); + } + + get_paint_opacity(actor) { + if (this.is_keyboard_actor(actor) && (!actor.visible || !actor.mapped)) { + const parent = actor.get_parent(); + if (parent?.visible && parent?.mapped) + return null; + } + if (actor?.get_paint_opacity) + return actor.get_paint_opacity(); + + return null; + } +};
\ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_geometry.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_geometry.js new file mode 100644 index 0000000..84832d9 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_geometry.js @@ -0,0 +1,291 @@ +import St from 'gi://St'; + +export function transform_to_actor_space(actor, { x, y, width, height }) { + if (!actor) + return null; + const [actor_x, actor_y] = actor.get_transformed_position(); + const [actor_width, actor_height] = actor.get_size(); + const [transformed_width, transformed_height] = actor.get_transformed_size(); + const scale_x = actor_width > 0 ? transformed_width / actor_width : 1; + const scale_y = actor_height > 0 ? transformed_height / actor_height : 1; + + return { + x: (x - actor_x) / scale_x, + y: (y - actor_y) / scale_y, + width: width / scale_x, + height: height / scale_y, + }; +} + +export const PopupBlurSurfaceGeometry = class PopupBlurSurfaceGeometry { + get(actor, { use_content = true, use_margins = true } = {}) { + const actor_geometry = this.get_transformed_actor(actor); + if (!this.has_valid_geometry(actor_geometry)) + return actor_geometry; + + const margin_geometry = use_margins + ? this.get_margin_adjusted(actor, actor_geometry) + : actor_geometry; + if (!use_content) + return margin_geometry; + + const content_geometry = this.get_transformed_content(actor, margin_geometry); + + if (!this.should_use_content(margin_geometry, content_geometry)) + return margin_geometry; + + return content_geometry; + } + + get_transformed_actor(actor) { + const extents = actor.get_transformed_extents(); + const top_left = extents?.get_top_left(); + const bottom_right = extents?.get_bottom_right(); + + if (top_left && bottom_right) { + return { + x: top_left.x, + y: top_left.y, + width: bottom_right.x - top_left.x, + height: bottom_right.y - top_left.y, + }; + } + + if (!actor) + return null; + const [x, y] = actor.get_transformed_position(); + const [width, height] = actor.get_transformed_size(); + return { x, y, width, height }; + } + + get_transformed_clip(actor) { + if (!actor?.has_clip) + return null; + + const [clip_x, clip_y, clip_width, clip_height] = actor.get_clip(); + if (clip_width <= 0 || clip_height <= 0) + return null; + + const [actor_x, actor_y] = actor.get_transformed_position(); + const [actor_width, actor_height] = actor.get_transformed_size(); + const scale_x = this.get_actor_scale(actor, actor_width, 'width'); + const scale_y = this.get_actor_scale(actor, actor_height, 'height'); + + return { + x: actor_x + clip_x * scale_x, + y: actor_y + clip_y * scale_y, + width: clip_width * scale_x, + height: clip_height * scale_y, + }; + } + + get_margin_adjusted(actor, geometry) { + const margins = this.get_margins(actor); + if (!margins) + return geometry; + + const scale_x = this.get_actor_scale(actor, geometry.width, 'width'); + const scale_y = this.get_actor_scale(actor, geometry.height, 'height'); + + return this.shrink(geometry, + margins.top * scale_y, + margins.right * scale_x, + margins.bottom * scale_y, + margins.left * scale_x + ) ?? geometry; + } + + get_transformed_content(actor, boundary) { + const children = this.get_transformed_children(actor); + if (!this.has_valid_geometry(children)) + return null; + + const insets = this.get_paint_insets(actor, boundary); + const geometry = this.inflate( + children, + insets.top, + insets.right, + insets.bottom, + insets.left + ); + + return this.intersect(geometry, boundary); + } + + get_transformed_children(actor) { + let geometry = null; + + this.get_children(actor).forEach(child => { + if (!this.is_visible(child)) + return; + + const child_geometry = this.get_transformed_actor(child); + if (!this.has_valid_geometry(child_geometry)) + return; + + geometry = this.union(geometry, child_geometry); + }); + + return geometry; + } + + get_margins(actor) { + const margins = { + top: this.get_margin(actor, 'top', St.Side.TOP), + right: this.get_margin(actor, 'right', St.Side.RIGHT), + bottom: this.get_margin(actor, 'bottom', St.Side.BOTTOM), + left: this.get_margin(actor, 'left', St.Side.LEFT), + }; + + return Object.values(margins).some(margin => margin > 0) ? margins : null; + } + + get_margin(actor, side_name, side) { + const property_names = [ + `margin_${side_name}`, + `margin${side_name[0].toUpperCase()}${side_name.slice(1)}`, + ]; + + for (const property of property_names) { + const value = actor[property]; + if (typeof value === 'number') + return value; + } + + const getter = actor[`get_margin_${side_name}`]; + if (getter) { + const value = getter.call(actor); + if (typeof value === 'number') + return value; + } + + try { + const theme_node = actor.get_theme_node(); + if (!theme_node?.get_margin) + return 0; + + return theme_node.get_margin(side); + } catch (e) { + // get_theme_node() may throw when actor is not yet mapped to a stage + return 0; + } + } + + get_paint_insets(actor, geometry) { + const scale_x = this.get_actor_scale(actor, geometry.width, 'width'); + const scale_y = this.get_actor_scale(actor, geometry.height, 'height'); + + return { + top: this.get_inset(actor, St.Side.TOP) * scale_y, + right: this.get_inset(actor, St.Side.RIGHT) * scale_x, + bottom: this.get_inset(actor, St.Side.BOTTOM) * scale_y, + left: this.get_inset(actor, St.Side.LEFT) * scale_x, + }; + } + + get_inset(actor, side) { + try { + const theme_node = actor.get_theme_node(); + if (!theme_node) + return 0; + + let inset = 0; + if (theme_node.get_padding) + inset += theme_node.get_padding(side); + if (theme_node.get_border_width) + inset += theme_node.get_border_width(side); + return inset; + } catch (e) { + // get_theme_node() may throw when actor is not yet mapped to a stage + return 0; + } + } + + get_actor_scale(actor, transformed_size, property) { + const size = actor ? (actor[property] ?? (actor[`get_${property}`] ? actor[`get_${property}`]() : 0)) : 0; + return size > 0 ? transformed_size / size : 1; + } + + should_use_content(outer, content) { + if (!this.has_valid_geometry(content)) + return false; + + if ( + content.width > outer.width + 1 + || content.height > outer.height + 1 + ) + return false; + + return outer.width - content.width > 2 || outer.height - content.height > 2; + } + + is_visible(actor) { + return actor?.visible && actor.mapped; + } + + get_children(actor) { + return actor ? actor.get_children() : []; + } + + has_valid_geometry(geometry) { + return geometry?.width > 0 && geometry?.height > 0; + } + + shrink(geometry, top, right, bottom, left) { + const width = geometry.width - left - right; + const height = geometry.height - top - bottom; + + if (width <= 0 || height <= 0) + return null; + + return { + x: geometry.x + left, + y: geometry.y + top, + width, + height, + }; + } + + inflate(geometry, top, right, bottom, left) { + return { + x: geometry.x - left, + y: geometry.y - top, + width: geometry.width + left + right, + height: geometry.height + top + bottom, + }; + } + + intersect(a, b) { + const x1 = Math.max(a.x, b.x); + const y1 = Math.max(a.y, b.y); + const x2 = Math.min(a.x + a.width, b.x + b.width); + const y2 = Math.min(a.y + a.height, b.y + b.height); + + if (x2 <= x1 || y2 <= y1) + return null; + + return { + x: x1, + y: y1, + width: x2 - x1, + height: y2 - y1, + }; + } + + union(a, b) { + if (!a) + return b; + + const x1 = Math.min(a.x, b.x); + const y1 = Math.min(a.y, b.y); + const x2 = Math.max(a.x + a.width, b.x + b.width); + const y2 = Math.max(a.y + a.height, b.y + b.height); + + return { + x: x1, + y: y1, + width: x2 - x1, + height: y2 - y1, + }; + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_placement.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_placement.js new file mode 100644 index 0000000..cad36f9 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_placement.js @@ -0,0 +1,328 @@ +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +import { PopupBlurSurfaceGeometry, transform_to_actor_space } from './surface_geometry.js'; + +export const PopupBlurSurfacePlacement = class PopupBlurSurfacePlacement { + constructor(surface) { + this.surface = surface; + this.geometry = new PopupBlurSurfaceGeometry(); + this.clear(); + this.ready = false; + } + + get_surface_geometry() { + this.offscreen = false; + + const geometry = this.get_unclipped_surface_geometry(); + if (!this.has_valid_geometry(geometry)) + return null; + + const clipped_geometry = this.get_clipped_surface_geometry(geometry); + if (!this.has_valid_geometry(clipped_geometry)) + return null; + + const monitor_geometry = this.get_monitor_clipped_surface_geometry(clipped_geometry); + if (!this.has_valid_geometry(monitor_geometry)) { + this.offscreen = true; + return null; + } + + return monitor_geometry; + } + + get_unclipped_monitor_surface_geometry() { + this.offscreen = false; + + const geometry = this.get_unclipped_surface_geometry(); + if (!this.has_valid_geometry(geometry)) + return null; + + const monitor_geometry = this.get_monitor_clipped_surface_geometry(geometry); + if (!this.has_valid_geometry(monitor_geometry)) { + this.offscreen = true; + return null; + } + + return monitor_geometry; + } + + get_unclipped_surface_geometry() { + const geometry_actor = this.surface.get_geometry_actor(); + const geometry = this.geometry.get(geometry_actor, { + use_content: this.surface.should_use_content_geometry(), + use_margins: this.surface.should_use_margin_geometry(), + }); + if (!this.has_valid_geometry(geometry)) + return null; + + return this.create_surface_geometry( + geometry.x, + geometry.y, + geometry.width, + geometry.height + ); + } + + create_surface_geometry(target_x, target_y, width, height) { + const local_geometry = transform_to_actor_space(this.surface.parent, { + x: target_x, + y: target_y, + width, + height, + }); + if (!local_geometry) + return null; + + return { + target_x, + target_y, + target_width: width, + target_height: height, + x: Math.round(local_geometry.x), + y: Math.round(local_geometry.y), + width: Math.ceil(local_geometry.width), + height: Math.ceil(local_geometry.height), + }; + } + + get_clipped_surface_geometry(geometry) { + let clipped = { + x: geometry.target_x, + y: geometry.target_y, + width: geometry.target_width, + height: geometry.target_height, + }; + + let actor = this.surface.get_geometry_actor(); + while (actor && actor !== this.surface.parent) { + const clip = this.geometry.get_transformed_clip(actor); + if (clip) { + clipped = this.geometry.intersect(clipped, clip); + if (!this.has_valid_geometry(clipped)) + return null; + } + + actor = actor.get_parent(); + } + + return this.create_surface_geometry( + clipped.x, + clipped.y, + clipped.width, + clipped.height + ); + } + + get_monitor_clipped_surface_geometry(geometry) { + const rect = { + x: geometry.target_x, + y: geometry.target_y, + width: geometry.target_width, + height: geometry.target_height, + }; + const cached_monitor = this.get_cached_monitor(); + const match = cached_monitor + ? this.get_monitor_intersection(rect, cached_monitor.monitor, cached_monitor.index) + : this.find_best_monitor_intersection(rect); + + if (!match) + return null; + + this.monitor_index = match.monitor_index; + + const surface_geometry = this.create_surface_geometry( + rect.x, + rect.y, + rect.width, + rect.height + ); + surface_geometry.monitor_index = match.monitor_index; + return surface_geometry; + } + + get_cached_monitor() { + if (this.monitor_index === null) + return null; + + const monitor = Main.layoutManager.monitors?.[this.monitor_index]; + if (!monitor) { + this.monitor_index = null; + return null; + } + + return { monitor, index: this.monitor_index }; + } + + find_best_monitor_intersection(rect) { + let best_match = null; + let best_area = 0; + + (Main.layoutManager.monitors ?? []).forEach((monitor, index) => { + const match = this.get_monitor_intersection(rect, monitor, index); + if (!match) + return; + + const area = match.intersection.width * match.intersection.height; + if (area > best_area) { + best_match = match; + best_area = area; + } + }); + + return best_match; + } + + get_monitor_intersection(rect, monitor, monitor_index) { + const intersection = this.geometry.intersect(rect, monitor); + if (!this.has_valid_geometry(intersection)) + return null; + + return { + monitor_index, + intersection, + }; + } + + has_valid_geometry(geometry) { + return geometry?.width > 0 && geometry?.height > 0; + } + + keep_transition_visible(transition_state) { + if (this.offscreen || !this.ready || !this.has_cached_geometry() || !transition_state.running) + return false; + + const opacity = this.surface.update_opacity(transition_state); + if (opacity <= 0) + return false; + + if (this.surface.static_blur) { + this.surface.blur_actor?.show(); + } + this.surface.show_actors(); + return true; + } + + has_cached_geometry() { + return ( + this.x !== null + && this.y !== null + && this.width > 0 + && this.height > 0 + ); + } + + has_cached_surface_geometry() { + return ( + this.surface_x !== null + && this.surface_y !== null + && this.surface_width > 0 + && this.surface_height > 0 + ); + } + + has_surface_geometry_changed(geometry) { + return ( + this.has_valid_geometry(geometry) + && ( + !this.has_cached_surface_geometry() + || this.surface_x !== geometry.x + || this.surface_y !== geometry.y + || this.surface_width !== geometry.width + || this.surface_height !== geometry.height + ) + ); + } + + store_surface_geometry(geometry) { + this.surface_x = geometry.x; + this.surface_y = geometry.y; + this.surface_width = geometry.width; + this.surface_height = geometry.height; + } + + prepare_visible_geometry() { + if (this.ready) + return true; + + this.ready = true; + this.surface.opacity = 0; + this.surface.update_surface_opacity(0); + this.surface.hide_actors(); + this.surface.queue_update(); + return false; + } + + update_surface_geometry(geometry) { + if (this.surface.static_blur) { + if (!this.update_static_geometry( + geometry.target_x, + geometry.target_y, + geometry.target_width, + geometry.target_height + )) + return false; + } else { + if (!this.update_dynamic_geometry(geometry.x, geometry.y, geometry.width, geometry.height)) + return false; + } + + this.store_surface_geometry(geometry); + return true; + } + + update_dynamic_geometry(x, y, width, height) { + if (this.x !== x || this.y !== y) { + this.surface.blur_actor.set_position(x, y); + this.x = x; + this.y = y; + } + + if (this.width !== width || this.height !== height) { + this.surface.blur_actor.set_size(width, height); + this.width = width; + this.height = height; + } + + return true; + } + + update_static_geometry(target_x, target_y, width, height) { + const geometry = this.surface.static_actor.update_geometry( + target_x, + target_y, + width, + height, + this.monitor_index + ); + this.surface.sync_static_actor(); + + if (!geometry) { + this.surface.hide_actors(); + return false; + } + + this.x = geometry.x; + this.y = geometry.y; + this.width = geometry.width; + this.height = geometry.height; + return true; + } + + hide() { + this.ready = false; + this.clear(); + } + + clear() { + this.x = null; + this.y = null; + this.width = null; + this.height = null; + this.surface_x = null; + this.surface_y = null; + this.surface_width = null; + this.surface_height = null; + this.monitor_index = null; + this.offscreen = false; + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_signals.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_signals.js new file mode 100644 index 0000000..47adb91 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_signals.js @@ -0,0 +1,117 @@ +import Meta from 'gi://Meta'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +const SURFACE_SIGNALS = [ + 'notify::allocation', + 'notify::position', + 'notify::size', + 'notify::x', + 'notify::y', + 'notify::width', + 'notify::height', + 'notify::clip-rect', + 'notify::visible', + 'notify::mapped', + 'notify::opacity', + 'notify::translation-x', + 'notify::translation-y', + 'notify::scale-x', + 'notify::scale-y', + 'notify::pseudo-class', +]; + +export const PopupBlurSurfaceSignals = class PopupBlurSurfaceSignals { + constructor(surface) { + this.surface = surface; + this.signal_ids = []; + this.signal_actors = new WeakSet(); + this.destroyed_actors = new WeakSet(); + } + + connect_actor(actor) { + if (!actor || this.signal_actors.has(actor)) + return; + + this.signal_actors.add(actor); + this.track_destroy(actor); + + const is_heavy_surface = this.surface.is_heavy_surface(); + + SURFACE_SIGNALS.forEach(signal => { + let id = actor.connect(signal, () => { + this.clear_pending_idles(); + const is_visibility_change = signal === 'notify::visible' || signal === 'notify::mapped'; + if (is_heavy_surface || is_visibility_change) { + this.surface.queue_update(); + return; + } + else { + this.updateId = global.compositor.get_laters().add(Meta.LaterType.IDLE, () => { + this.updateId = 0; + this.surface.queue_update(); + return false; + }); + } + }); + this.signal_ids.push([actor, id, signal]); + }); + } + + track_destroy(actor) { + const id = actor.connect('destroy', () => this.destroyed_actors.add(actor)); + this.signal_ids.push([actor, id]); + } + + connect_ancestors(actor) { + actor = actor ? actor.get_parent() : null; + + while (actor && actor !== this.surface.parent) { + this.connect_actor(actor); + actor = actor.get_parent(); + } + } + + connect_layout() { + if (!this.surface.static_blur) + return; + + this.signal_ids.push([ + Main.layoutManager, + Main.layoutManager.connect('monitors-changed', () => this.surface.queue_update()), + ]); + } + + connect_settings() { + [ + this.surface.corner_radius.key, + 'override-background', + 'style-popup', + ].forEach(key => { + const id = this.surface.settings.popup.settings.connect( + `changed::${key}`, + () => this.surface.update_settings() + ); + this.signal_ids.push([this.surface.settings.popup.settings, id]); + }); + } + + clear_pending_idles() { + if (this.updateId) { + global.compositor.get_laters().remove(this.updateId); + this.updateId = 0; + } + } + + disconnect_all() { + this.clear_pending_idles(); + this.signal_ids.forEach(([signal_actor, signal_id]) => { + if (this.destroyed_actors.has(signal_actor)) + return; + + signal_actor.disconnect(signal_id); + }); + this.signal_ids = []; + this.signal_actors = new WeakSet(); + this.destroyed_actors = new WeakSet(); + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_style.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_style.js new file mode 100644 index 0000000..1dfb947 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_style.js @@ -0,0 +1,59 @@ +export const PopupBlurSurfaceStyle = class PopupBlurSurfaceStyle { + constructor(surface) { + this.surface = surface; + this.original_target_style = null; + this.target_style_set = false; + } + + capture_target_style() { + this.original_target_style = this.surface.target ? this.surface.target.get_style() : null; + } + + update_target_style() { + if ( + !this.surface.target.set_style + || !this.surface.settings.popup.OVERRIDE_BACKGROUND + ) { + this.restore_target_style(); + return; + } + + if ( + this.has_style_class(this.surface.target, 'bms-keyboard-surface') + ) + return; + + const base_style = this.original_target_style ?? ''; + const separator = base_style.trim() && !base_style.trim().endsWith(';') ? '; ' : ''; + + this.surface.target.set_style( + `${base_style}${separator}border-radius: ${this.surface.get_corner_radius()}px;` + ); + this.target_style_set = true; + } + + restore_target_style() { + if (!this.target_style_set || !this.surface.target.set_style) + return; + + this.surface.target.set_style(this.original_target_style); + this.target_style_set = false; + } + + has_any_style_class(actor, style_classes) { + return style_classes.some(style_class => this.has_style_class(actor, style_class)); + } + + has_style_class(actor, style_class) { + if (!actor) + return false; + + if (actor.has_style_class_name) + return actor.has_style_class_name(style_class); + + if (actor.get_style_class_name) + return (actor.get_style_class_name() ?? '').split(/\s+/).includes(style_class); + + return false; + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_transitions.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_transitions.js new file mode 100644 index 0000000..70fd520 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/surface_transitions.js @@ -0,0 +1,139 @@ +const GEOMETRY_TRANSITION_PROPERTIES = [ + 'x', + 'y', + 'width', + 'height', + 'clip-rect', + 'clip_rect', + 'translation-x', + 'translation-y', + 'translation_x', + 'translation_y', + 'scale-x', + 'scale-y', + 'scale_x', + 'scale_y', +]; + +export const PopupBlurSurfaceTransitions = class PopupBlurSurfaceTransitions { + constructor(surface) { + this.surface = surface; + } + + has_running() { + return this.get_state().running; + } + + get_state(include_descendants = false) { + const surface_actors = this.get_surface_actors(); + const actors = include_descendants + ? this.get_actors(surface_actors) + : surface_actors; + const geometry_properties = this.get_running_properties( + GEOMETRY_TRANSITION_PROPERTIES, + actors + ); + const opacity = this.has_running_for(['opacity'], surface_actors); + + return { + complete: include_descendants, + running: opacity || geometry_properties.size > 0, + geometry: geometry_properties.size > 0, + geometry_properties, + opacity, + }; + } + + complete_state(transition_state) { + if (transition_state.complete) + return transition_state; + + return this.get_state(true); + } + + has_running_for(properties, actors) { + return actors.some(actor => + properties.some(property => this.has_transition(actor, property)) + ); + } + + get_running_properties(properties, actors) { + const running_properties = new Set(); + + actors.forEach(actor => { + properties.forEach(property => { + if (this.has_transition(actor, property)) + running_properties.add(this.normalize_property(property)); + }); + }); + + return running_properties; + } + + normalize_property(property) { + return property.replace(/_/g, '-'); + } + + get_actors(surface_actors = this.get_surface_actors()) { + const actors = [...surface_actors]; + const seen = new WeakSet(); + actors.forEach(actor => seen.add(actor)); + + if (!this.surface.is_quick_settings()) { + this.add_descendants(actors, seen, this.surface.target); + this.add_descendants(actors, seen, this.surface.root_actor); + } + + return actors; + } + + get_surface_actors() { + const actors = []; + const seen = new WeakSet(); + + this.add_actor(actors, seen, this.surface.target); + this.add_actor(actors, seen, this.surface.root_actor); + this.add_actor(actors, seen, this.surface.get_geometry_actor()); + this.add_ancestors(actors, seen, this.surface.target); + this.add_ancestors(actors, seen, this.surface.root_actor); + + return actors; + } + + add_actor(actors, seen, actor) { + if (!actor || seen.has(actor)) + return; + + seen.add(actor); + actors.push(actor); + } + + add_ancestors(actors, seen, actor) { + actor = actor?.get_parent(); + while (actor && actor !== this.surface.parent) { + this.add_actor(actors, seen, actor); + actor = actor.get_parent(); + } + } + + add_descendants(actors, seen, actor) { + const stack = this.get_children(actor).slice().reverse(); + + while (stack.length > 0) { + const child = stack.pop(); + if (seen.has(child)) + continue; + + this.add_actor(actors, seen, child); + stack.push(...this.get_children(child).slice().reverse()); + } + } + + has_transition(actor, property) { + return !!actor?.get_transition(property); + } + + get_children(actor) { + return actor ? actor.get_children() : []; + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/targets.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/targets.js new file mode 100644 index 0000000..dd537b0 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/targets.js @@ -0,0 +1,255 @@ +const POPUP_STYLE_CLASSES = ['popup-menu', 'quick-toggle-menu-container', 'candidate-popup-boxpointer']; +const POPUP_TARGET_STYLE_CLASSES = ['popup-menu-content', 'quick-settings', 'quick-toggle-menu', 'notification-banner', 'candidate-popup-content','screenshot-ui-panel']; +const POPUP_CHILD_STYLE_CLASSES = ['osd-window', 'resize-popup', 'switcher-list', 'workspace-switcher', 'modal-dialog', 'run-dialog']; +const POPUP_TARGET_STYLE_CLASSES_OSK = ['bms-keyboard-surface'] +const POPUP_DESCENDANT_TARGET_STYLE_CLASSES = ['switcher-list']; + +export const POPUP_BACKGROUND_STYLES = ['bms-popup-background-transparent', 'bms-popup-background-light', 'bms-popup-background-dark']; +export const POPUP_SURFACE_STYLES = POPUP_BACKGROUND_STYLES.map( + style => style.replace('bms-popup-background-', 'bms-popup-surface-') +); +export const DEFAULT_CORNER_RADIUS = { key: 'corner-radius', property: 'CORNER_RADIUS' }; +export const POPUP_CORNER_RADII = [ + { + key: 'quick-settings-corner-radius', + property: 'QUICK_SETTINGS_CORNER_RADIUS', + style_classes: ['quick-settings', 'quick-toggle-menu', 'datemenu-popover','screenshot-ui-panel'], + }, + { + key: 'notification-corner-radius', + property: 'NOTIFICATION_CORNER_RADIUS', + style_classes: ['notification-banner', 'message', 'message-view', 'message-list'], + }, + { + key: 'menu-corner-radius', + property: 'MENU_CORNER_RADIUS', + style_classes: ['popup-menu-content', 'popup-menu', 'candidate-popup-content', 'candidate-popup-boxpointer'], + }, + { + key: 'osd-corner-radius', + property: 'OSD_CORNER_RADIUS', + style_classes: ['osd-window', 'resize-popup', 'switcher-list', 'workspace-switcher'], + }, + { + key: 'dialog-corner-radius', + property: 'DIALOG_CORNER_RADIUS', + style_classes: ['modal-dialog', 'run-dialog'], + }, + { + key: 'osk-corner-radius', + property: 'OSK_CORNER_RADIUS', + style_classes: ['bms-keyboard-surface'], + }, +]; + +export const PopupBlurTargets = class PopupBlurTargets { + constructor(actor) { + this.actor = actor; + this.style_cache = null; + } + + find(actor) { + return this.with_style_cache(() => this.find_actor(actor)); + } + + find_actor(actor) { + if (this.actor.is_internal_actor(actor) || !this.actor.watch_actor(actor)) + return []; + + const targets = this.create_targets(); + const delegate = this.actor.get_actor_delegate(actor); + + if (this.prefers_descendant_targets(actor)) { + this.find_target_children(actor, targets); + if (targets.actors.length > 0) + return targets.actors; + } + + if (this.actor.watch_actor(delegate?.box) && (this.has_any_style_class(delegate.box, POPUP_TARGET_STYLE_CLASSES) || this.has_any_style_class(delegate.box, POPUP_TARGET_STYLE_CLASSES_OSK))) + this.add(targets, delegate.box); + + if (this.has_any_style_class(actor, POPUP_TARGET_STYLE_CLASSES)) + this.add(targets, actor); + + if (this.has_any_style_class(actor, POPUP_TARGET_STYLE_CLASSES_OSK)) + this.add(targets, actor); + + if (this.has_any_style_class(actor, POPUP_CHILD_STYLE_CLASSES)) + this.add(targets, actor); + + const dialog_layout = this.actor.get_actor_dialog_layout(actor); + if (this.actor.watch_actor(dialog_layout) && this.has_any_style_class(dialog_layout, POPUP_CHILD_STYLE_CLASSES)) + this.add(targets, dialog_layout); + + if (targets.actors.length === 0) + this.find_target_children(actor, targets); + + if ( + targets.actors.length === 0 + && this.has_any_style_class(actor, POPUP_STYLE_CLASSES) + ) + this.add(targets, actor); + + return targets.actors; + } + + find_target_children(actor, targets, seen = new WeakSet()) { + const stack = this.get_children(actor) + .slice() + .reverse() + .map(child => ({ actor: child, fallback_count: null })); + + while (stack.length > 0) { + const { actor: child, fallback_count } = stack.pop(); + if ( + !child + || this.actor.is_internal_actor(child) + ) + continue; + + if (fallback_count !== null) { + if ( + targets.actors.length === fallback_count + && this.is_blur_target_actor(child) + ) + this.add(targets, child); + continue; + } + + if (seen.has(child)) + continue; + + seen.add(child); + + if (this.prefers_descendant_targets(child)) { + const children = this.get_children(child); + if (children.length > 0) { + stack.push({ + actor: child, + fallback_count: targets.actors.length, + }); + this.push_children(stack, children); + continue; + } + } + + if (this.is_blur_target_actor(child)) { + this.add(targets, child); + continue; + } + + this.push_children(stack, this.get_children(child)); + } + } + + prefers_descendant_targets(actor) { + return this.has_any_style_class(actor, POPUP_DESCENDANT_TARGET_STYLE_CLASSES); + } + + is_blur_target_actor(actor) { + return ( + this.has_any_style_class(actor, POPUP_TARGET_STYLE_CLASSES) + || this.has_any_style_class(actor, POPUP_TARGET_STYLE_CLASSES_OSK) + || this.has_any_style_class(actor, POPUP_CHILD_STYLE_CLASSES) + ); + } + + get_corner_radius(target, root_actor) { + return POPUP_CORNER_RADII.find(radius => + this.has_any_style_class(target, radius.style_classes) + || this.has_any_style_class(root_actor, radius.style_classes) + ) ?? DEFAULT_CORNER_RADIUS; + } + + with_style_cache(callback) { + const previous_cache = this.style_cache; + this.style_cache = new WeakMap(); + + try { + return callback(); + } finally { + this.style_cache = previous_cache; + } + } + + has_any_style_class(actor, style_classes) { + if (!actor) + return false; + + const classes = this.get_style_classes(actor); + if (classes) + return style_classes.some(style_class => classes.has(style_class)); + + return style_classes.some(style_class => this.has_style_class(actor, style_class)); + } + + has_style_class(actor, style_class) { + return has_style_class(actor, style_class); + } + + get_style_classes(actor) { + if (!actor || !this.style_cache) + return null; + + if (this.style_cache.has(actor)) + return this.style_cache.get(actor); + + if (!actor.get_style_class_name) { + this.style_cache.set(actor, null); + return null; + } + + const class_names = actor.get_style_class_name(); + if (typeof class_names !== 'string') { + this.style_cache.set(actor, null); + return null; + } + + const classes = new Set(class_names.split(/\s+/).filter(Boolean)); + this.style_cache.set(actor, classes); + return classes; + } + + create_targets() { + return { + actors: [], + seen: new WeakSet(), + }; + } + + get_children(actor) { + return actor ? actor.get_children() : []; + } + + push_children(stack, children) { + children + .slice() + .reverse() + .forEach(child => stack.push({ actor: child, fallback_count: null })); + } + + add(targets, target) { + if ( + this.actor.is_internal_actor(target) + || !this.actor.watch_actor(target) + || targets.seen.has(target) + ) + return; + + targets.seen.add(target); + targets.actors.push(target); + } +}; + +export function has_style_class(actor, style_class) { + if (!actor || !style_class) + return false; + + if (actor.has_style_class_name) + return actor.has_style_class_name(style_class); + + if (actor.get_style_class_name) + return (actor.get_style_class_name() ?? '').split(/\s+/).includes(style_class); + + return false; +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/window_list.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/window_list.js index 6ab0644..33a9be3 100644 --- a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/window_list.js +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/window_list.js @@ -66,7 +66,7 @@ export const WindowListBlur = class WindowListBlur { this.connections.connect( actor, 'destroy', - _ => this.destroy_blur(pipeline, true) + _ => this.destroy_blur(pipeline) ); @@ -98,8 +98,8 @@ export const WindowListBlur = class WindowListBlur { } // IMPORTANT: do never call this in a mutable `this.pipelines.forEach` - destroy_blur(pipeline, actor_destroyed = false) { - if (!actor_destroyed) { + destroy_blur(pipeline) { + if (pipeline.actor) { this.remove_style(pipeline.actor); this.paint_signals.disconnect_all_for_actor(pipeline.actor); } @@ -108,7 +108,7 @@ export const WindowListBlur = class WindowListBlur { let index = this.pipelines.indexOf(pipeline); if (index >= 0) - this.pipelines.splice(pipeline, 1); + this.pipelines.splice(index, 1); } remove_style(actor) { |