diff options
| author | Nippy <nippy@Nippy-Mac-mini.local> | 2026-10-09 17:23:53 +0200 |
|---|---|---|
| committer | Nippy <nippy@Nippy-Mac-mini.local> | 2026-10-09 17:23:53 +0200 |
| commit | 9d5be1660d8f74038ebed3d74fdbc809e43787ae (patch) | |
| tree | 4cb46c3765be87ca4c5219f2f714425d7dfddb56 /raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup | |
| parent | deb4b8a9c407832c35100a643bd7a592589c9679 (diff) | |
| download | RaveOS-PKGBUILD-9d5be1660d8f74038ebed3d74fdbc809e43787ae.tar.gz RaveOS-PKGBUILD-9d5be1660d8f74038ebed3d74fdbc809e43787ae.zip | |
gnome fix
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup')
12 files changed, 3303 insertions, 0 deletions
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; +} |