diff options
| author | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
|---|---|---|
| committer | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
| commit | 5d94c0a7d44a2255b81815a52a7056a94a39842d (patch) | |
| tree | 759bdea9645c6a62f9e1e4c001f7d81cccd120d2 /raveos-theme/gnome/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js | |
| parent | e79cdf210b267f21a186255ce1a4d50029439d54 (diff) | |
| download | RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.tar.gz RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.zip | |
update Raveos themes
Diffstat (limited to 'raveos-theme/gnome/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js')
| -rw-r--r-- | raveos-theme/gnome/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js b/raveos-theme/gnome/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js new file mode 100644 index 0000000..e783046 --- /dev/null +++ b/raveos-theme/gnome/theme-data/extensions/installed/blur-my-shell@aunetx/components/applications.js @@ -0,0 +1,614 @@ +import Meta from 'gi://Meta'; +import Gio from 'gi://Gio'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as Config from 'resource:///org/gnome/shell/misc/config.js'; + +import { ApplicationsService } from '../dbus/services.js'; +import { PaintSignals } from '../conveniences/paint_signals.js'; +import { DummyPipeline } from '../conveniences/dummy_pipeline.js'; +import { Pipeline } from '../conveniences/pipeline.js'; + + +/// Converts a wildcard pattern to a RegExp object. +/// Supports * (matches any sequence) and ? (matches any single character). +/// Matching is case-insensitive. +/// +/// @param {string} pattern - The wildcard pattern (e.g., "Firefox*", "*Code*") +/// @returns {RegExp} The compiled regex pattern +function wildcardToRegex(pattern) { + // Escape special regex characters except * and ? + const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&'); + // Convert wildcards: * -> .*, ? -> . + const regex = '^' + escaped.replace(/\*/g, '.*').replace(/\?/g, '.') + '$'; + return new RegExp(regex, 'i'); +} + + +/// Compiles an array of wildcard patterns into RegExp objects. +/// Caches the results to avoid recompilation on every check. +/// +/// @param {string[]} patterns - Array of wildcard patterns +/// @param {Map} cache - Cache map storing pattern -> regex mappings +/// @returns {RegExp[]} Array of compiled regex patterns +function compilePatterns(patterns, cache) { + return patterns.map(pattern => { + if (cache.has(pattern)) { + return cache.get(pattern); + } + const regex = wildcardToRegex(pattern); + cache.set(pattern, regex); + return regex; + }); +} + + +/// Tests if a value matches any of the compiled patterns. +/// +/// @param {string} value - The value to test (e.g., wm_class) +/// @param {RegExp[]} patterns - Array of compiled regex patterns +/// @returns {boolean} True if value matches any pattern +function matchesAnyPattern(value, patterns) { + if (!value || patterns.length === 0) { + return false; + } + return patterns.some(pattern => pattern.test(value)); +} + +export const ApplicationsBlur = class ApplicationsBlur { + constructor(connections, settings, effects_manager) { + this.connections = connections; + this.settings = settings; + this.effects_manager = effects_manager; + this.paint_signals = new PaintSignals(connections); + + // stores every blurred meta window + this.meta_window_map = new Map(); + + // cache for compiled patterns to avoid recompilation + this._whitelist_pattern_cache = new Map(); + this._blacklist_pattern_cache = new Map(); + this._compiled_whitelist = []; + this._compiled_blacklist = []; + + // compile initial patterns + this._update_patterns(); + } + + /// Updates the compiled whitelist and blacklist patterns from settings. + /// Called during initialization and when whitelist/blacklist settings change. + _update_patterns() { + const whitelist = this.settings.applications.WHITELIST || []; + const blacklist = this.settings.applications.BLACKLIST || []; + + this._compiled_whitelist = compilePatterns(whitelist, this._whitelist_pattern_cache); + this._compiled_blacklist = compilePatterns(blacklist, this._blacklist_pattern_cache); + + this._log(`Patterns updated - whitelist: ${whitelist.length}, blacklist: ${blacklist.length}`); + } + + enable() { + this._log("blurring applications..."); + + // export dbus service for preferences + this.service = new ApplicationsService; + this.service.export(); + + this.mutter_gsettings = new Gio.Settings({ schema: 'org.gnome.mutter' }); + + // blur already existing windows + this.update_all_windows(); + + // blur every new window + this.connections.connect( + global.display, + 'window-created', + (_meta_display, meta_window) => { + this._log("window created"); + + if (meta_window) + this.track_new(meta_window); + } + ); + + // update window blur when focus is changed + this.focused_window_pid = null; + this.init_dynamic_opacity(); + this.connections.connect( + global.display, + 'focus-window', + (_meta_display, meta_window, _p0) => { + if (meta_window && meta_window.bms_pid != this.focused_window_pid) + this.set_focus_for_window(meta_window); + else if (!meta_window) + this.set_focus_for_window(null); + } + ); + + this.connect_to_overview(); + } + + /// Initializes the dynamic opacity for windows, without touching to the connections. + /// This is used both when enabling the component, and when changing the dynamic-opacity pref. + init_dynamic_opacity() { + if (this.settings.applications.DYNAMIC_OPACITY) { + // make the currently focused window solid + if (global.display.focus_window) + this.set_focus_for_window(global.display.focus_window); + } else { + // remove old focused window if the pref was changed + if (this.focused_window_pid) + this.set_focus_for_window(null); + } + } + + /// Connect to the overview being opened/closed to force the blur being + /// shown on every window of the workspaces viewer. + connect_to_overview() { + this.connections.disconnect_all_for(Main.overview); + + if (this.settings.applications.BLUR_ON_OVERVIEW) { + // when the overview is opened, show every window actors (which + // allows the blur to be shown too) + this.connections.connect( + Main.overview, 'showing', + _ => this.meta_window_map.forEach((meta_window, _pid) => { + let window_actor = meta_window.get_compositor_private(); + window_actor?.show(); + }) + ); + + // when the overview is closed, hide every actor that is not on the + // current workspace (to mimic the original behaviour) + this.connections.connect( + Main.overview, 'hidden', + _ => { + this.meta_window_map.forEach((meta_window, _pid) => { + let window_actor = meta_window.get_compositor_private(); + + if ( + (!meta_window.get_workspace().active) || meta_window.minimized + ) + window_actor.hide(); + }); + } + ); + } + } + + /// Iterate through all existing windows and add blur as needed. + update_all_windows() { + // Recompile patterns in case whitelist/blacklist changed + this._update_patterns(); + + // remove all previously blurred windows, in the case where the + // whitelist was changed + this.meta_window_map.forEach(((_meta_window, pid) => { + this.remove_blur(pid); + })); + + for ( + let i = 0; + i < global.workspace_manager.get_n_workspaces(); + ++i + ) { + let workspace = global.workspace_manager.get_workspace_by_index(i); + let windows = workspace.list_windows(); + + windows.forEach(meta_window => this.track_new(meta_window)); + } + } + + /// Adds the needed signals to every new tracked window, and adds blur if + /// needed. + /// Accepts only untracked meta windows (i.e no `bms_pid` set) + track_new(meta_window) { + // create a pid that will follow the window during its whole life + const pid = ("" + Math.random()).slice(2, 16); + meta_window.bms_pid = pid; + + this._log(`new window tracked, pid: ${pid}`); + + // register the blurred window + this.meta_window_map.set(pid, meta_window); + + // update the blur when wm-class is changed + this.connections.connect( + meta_window, 'notify::wm-class', + _ => this.check_blur(meta_window) + ); + + // update the clip, position, and/or size when the window changes + this.connections.connect( + meta_window, 'size-changed', + _ => this.update_size(pid) + ); + if (this.settings.applications.STATIC_BLUR) { + this.connections.connect( + meta_window, 'position-changed', + _ => this.update_size(pid) + ); + } + + // remove the blur when the window is unmanaged + this.connections.connect( + meta_window, 'unmanaging', + _ => this.untrack_meta_window(pid) + ); + + this.check_blur(meta_window); + + 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; + } + + window_actor.set_child_below_sibling(meta_window.blur_actor, null); + }); + } + } + + /// Updates the size of the blur actor associated to a meta window from its pid. + /// Accepts only tracked meta window (i.e `bms_pid` set), be it blurred or not. + update_size(pid) { + if (this.meta_window_map.has(pid)) { + const meta_window = this.meta_window_map.get(pid); + const blur_actor = meta_window.blur_actor; + if (blur_actor) { + if (this.settings.applications.STATIC_BLUR) { + const bg_manager = meta_window.bg_manager; + const bg_actor_monitor_index = bg_manager.backgroundActor.monitor; + const window_monitor_index = meta_window.get_monitor(); + const monitor = Main.layoutManager.monitors[window_monitor_index]; + + if (bg_actor_monitor_index !== window_monitor_index) { + this._log(`application (pid ${pid}) switching to monitor: ${window_monitor_index}`); + + // Recreate the BackgroundActor on the right monitor. This is necessary to make sure differently + // sized monitors have the correct scaled image of the wallpaper. + bg_manager._monitorIndex = window_monitor_index; + bg_manager._updateBackgroundActor(); + + // Also to fix differently sized monitor issues. + blur_actor.width = monitor.width; + blur_actor.height = monitor.height; + } + + const frame = meta_window.get_frame_rect(); + const buffer = meta_window.get_buffer_rect(); + blur_actor.x = monitor.x - buffer.x; + blur_actor.y = monitor.y - buffer.y; + + // set_clip(x-offset, y-offset, width, height) + blur_actor.set_clip(frame.x - monitor.x, frame.y - monitor.y, frame.width, frame.height); + } else { + const allocation = this.compute_allocation(meta_window); + blur_actor.x = allocation.x; + blur_actor.y = allocation.y; + blur_actor.width = allocation.width; + blur_actor.height = allocation.height; + } + } + } else + // the pid was visibly not removed + this.untrack_meta_window(pid); + } + + /// Checks if the given actor needs to be blurred. + /// Accepts only tracked meta window, be it blurred or not. + /// + /// In order to be blurred, a window either: + /// - is whitelisted in the user preferences if not enable-all + /// - is not blacklisted if enable-all + /// + /// Whitelist and blacklist support wildcard patterns: + /// - * matches any sequence of characters + /// - ? matches any single character + /// - Matching is case-insensitive + check_blur(meta_window) { + const window_wm_class = meta_window.get_wm_class(); + const enable_all = this.settings.applications.ENABLE_ALL; + if (window_wm_class) + this._log(`pid ${meta_window.bms_pid} associated to wm class name ${window_wm_class}`); + + + // if we are in blacklist mode and the window is not blacklisted + // or if we are in whitelist mode and the window is whitelisted + if ( + window_wm_class !== "" + && ((enable_all && !matchesAnyPattern(window_wm_class, this._compiled_blacklist)) + || (!enable_all && matchesAnyPattern(window_wm_class, this._compiled_whitelist)) + ) + && [ + Meta.FrameType.NORMAL, + Meta.FrameType.DIALOG, + Meta.FrameType.MODAL_DIALOG + ].includes(meta_window.get_frame_type()) + ) { + // only blur the window if it is not already done + if (!meta_window.blur_actor) + this.create_blur_effect(meta_window); + } + + // remove blur it is not explicitly whitelisted or un-blacklisted + else if (meta_window.blur_actor) + this.remove_blur(meta_window.bms_pid); + } + + /// Add the blur effect to the window. + /// Accepts only tracked meta window that is NOT already blurred. + create_blur_effect(meta_window) { + const pid = meta_window.bms_pid; + const window_actor = meta_window.get_compositor_private(); + + let blur_actor; + + if (this.settings.applications.STATIC_BLUR) { + const pipeline = new Pipeline(this.effects_manager, global.blur_my_shell._pipelines_manager, this.settings.applications.PIPELINE); + const bg_managers = []; + blur_actor = pipeline.create_background_with_effects( + meta_window.get_monitor(), bg_managers, window_actor, + 'bms-application-blurred-widget' + ); + + if (bg_managers.length > 0) meta_window.bg_manager = bg_managers[0]; + else // I've never seen this happen, but just in case + this._warn(`no bg_manager on blur creation for pid ${pid}`); + } else { + const pipeline = new DummyPipeline(this.effects_manager, this.settings.applications); + [blur_actor, meta_window.bg_manager] = pipeline.create_background_with_effect( + window_actor, 'bms-application-blurred-widget' + ); + + // if hacks are selected, force to repaint the window + if (this.settings.HACKS_LEVEL === 1) { + this._log("hack level 1"); + + this.paint_signals.disconnect_all_for_actor(blur_actor); + this.paint_signals.connect(blur_actor, pipeline.effect); + } else { + this.paint_signals.disconnect_all_for_actor(blur_actor); + } + } + + 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); + + // update the size + this.update_size(pid); + + // set the window actor's opacity + this.set_window_opacity(window_actor, this.settings.applications.OPACITY); + + // now set up the signals, for the window actor only: they are disconnected + // in `remove_blur`, whereas the signals for the meta window are disconnected + // only when the whole component is disabled + + // update the window opacity when it changes, else we don't control it fully + this.connections.connect( + window_actor, 'notify::opacity', + _ => { + if (this.focused_window_pid != pid) + this.set_window_opacity(window_actor, this.settings.applications.OPACITY); + } + ); + + // hide the blur if window becomes invisible + if (!window_actor.visible) + blur_actor.hide(); + + this.connections.connect( + window_actor, + 'notify::visible', + window_actor => { + if (window_actor.visible) + meta_window.blur_actor.show(); + else + meta_window.blur_actor.hide(); + } + ); + } + + /// With `focus=true`, tells us we are focused on said window (which can be null if + /// we are not focused anymore). It automatically removes the ancient focus. + /// With `focus=false`, just remove the focus from said window (which can still be null). + set_focus_for_window(meta_window, focus = true) { + let blur_actor = null; + let window_actor = null; + let new_pid = null; + if (meta_window) { + blur_actor = meta_window.blur_actor; + window_actor = meta_window.get_compositor_private(); + new_pid = meta_window.bms_pid; + } + + if (focus) { + // remove old focused window if any + if (this.focused_window_pid) { + const old_focused_window = this.meta_window_map.get(this.focused_window_pid); + if (old_focused_window) + this.set_focus_for_window(old_focused_window, false); + } + // set new focused window pid + this.focused_window_pid = new_pid; + // if we have blur, hide it and make the window opaque + if (this.settings.applications.DYNAMIC_OPACITY && blur_actor) { + blur_actor.hide(); + this.set_window_opacity(window_actor, 255); + } + } + // if we remove the focus and have blur, show it and make the window transparent + else if (blur_actor) { + blur_actor.show(); + this.set_window_opacity(window_actor, this.settings.applications.OPACITY); + } + } + + /// Makes sure that, when the overview is visible, the window actor will + /// stay visible no matter what. + /// We can instead hide the last child of the window actor, which will + /// improve performances without hiding the blur effect. + enforce_window_visibility_on_overview_for(window_actor) { + this.connections.connect(window_actor, 'notify::visible', + _ => { + if (this.settings.applications.BLUR_ON_OVERVIEW) { + if ( + !window_actor.visible + && Main.overview.visible + ) { + window_actor.show(); + window_actor.get_last_child().hide(); + } else if ( + window_actor.visible + ) + window_actor.get_last_child().show(); + } + } + ); + } + + /// Set the opacity of the window actor that sits on top of the blur effect. + set_window_opacity(window_actor, opacity) { + window_actor?.get_children().forEach(child => { + if (child.name !== "blur-actor" && child.opacity != opacity) + child.opacity = opacity; + }); + } + + /// Update the opacity of all window actors. + set_opacity() { + let opacity = this.settings.applications.OPACITY; + + this.meta_window_map.forEach(((meta_window, pid) => { + if (pid != this.focused_window_pid && meta_window.blur_actor) { + let window_actor = meta_window.get_compositor_private(); + this.set_window_opacity(window_actor, opacity); + } + })); + } + + /// Find the system's window scaling. + /// If `scale-monitor-framebuffer` experimental feature if on, we don't need to manage scaling. + /// Else, on wayland, we need to divide by the scale to get the correct result. + compute_scale(meta_window) { + // TODO: Drop GNOME <50 compatibility + const gnome_shell_major_version = parseInt(Config.PACKAGE_VERSION.split('.')[0]); + const scale_monitor_framebuffer = + gnome_shell_major_version >= 50 || + this.mutter_gsettings + .get_strv('experimental-features') + .includes('scale-monitor-framebuffer'); + const is_wayland = gnome_shell_major_version >= 50 || + (typeof Meta.is_wayland_compositor === 'function' + ? Meta.is_wayland_compositor() + : global.display?.is_wayland_compositor?.() ?? true); + const monitor_index = meta_window.get_monitor(); + // check if the window is using wayland, or xwayland/xorg for rendering + return !scale_monitor_framebuffer && is_wayland && meta_window.get_client_type() == 0 + ? Main.layoutManager.monitors[monitor_index].geometry_scale + : 1; + } + + /// Compute the size and position for a blur actor. + /// Coordinates are relative to window buffer's corner. + compute_allocation(meta_window) { + const scale = this.compute_scale(meta_window); + + let frame = meta_window.get_frame_rect(); + let buffer = meta_window.get_buffer_rect(); + + return { + x: (frame.x - buffer.x) / scale, + y: (frame.y - buffer.y) / scale, + width: frame.width / scale, + height: frame.height / scale + }; + } + + change_blur_type() { + this._log("resetting..."); + + this.disable(); + setTimeout(_ => this.enable(), 1); + } + + change_pipeline() { + this.update_all_windows(); + } + + /// Removes the blur actor to make a blurred window become normal again. + /// It however does not untrack the meta window itself. + /// Accepts a pid corresponding (or not) to a blurred (or not) meta window. + remove_blur(pid) { + this._log(`removing blur for pid ${pid}`); + + let meta_window = this.meta_window_map.get(pid); + if (meta_window) { + let window_actor = meta_window.get_compositor_private(); + let blur_actor = meta_window.blur_actor; + let bg_manager = meta_window.bg_manager; + + if (blur_actor && window_actor) { + // reset the opacity + this.set_window_opacity(window_actor, 255); + + // remove the blurred actor + window_actor.remove_child(blur_actor); + bg_manager._bms_pipeline.destroy(); + bg_manager.destroy(); + blur_actor.destroy(); + + // kinda untrack the blurred actor, as its presence is how we know + // whether we are blurred or not + delete meta_window.blur_actor; + delete meta_window.bg_manager; + + // disconnect the signals of the window actor + this.paint_signals.disconnect_all_for_actor(blur_actor); + this.connections.disconnect_all_for(window_actor); + } + } + } + + /// Kinda the same as `remove_blur`, but better: it also untracks the window. + /// This needs to be called when the component is being disabled, else it + /// would cause havoc by having untracked windows during normal operations, + /// which is not the point at all! + /// Accepts a pid corresponding (or not) to a blurred (or not) meta window. + untrack_meta_window(pid) { + this.remove_blur(pid); + let meta_window = this.meta_window_map.get(pid); + if (meta_window) { + this.connections.disconnect_all_for(meta_window); + this.meta_window_map.delete(pid); + } + } + + disable() { + this._log("removing blur from applications..."); + + this.service?.unexport(); + delete this.mutter_gsettings; + + this.meta_window_map.forEach((_meta_window, pid) => { + this.untrack_meta_window(pid); + }); + + this.connections.disconnect_all(); + this.paint_signals.disconnect_all(); + } + + _log(str) { + if (this.settings.DEBUG) + console.log(`[Blur my Shell > applications] ${str}`); + } + + _warn(str) { + console.warn(`[Blur my Shell > applications] ${str}`); + } +}; |