From 34632614b576108ca0bff16ca45a5359e51aa56a Mon Sep 17 00:00:00 2001 From: wang551 Date: Wed, 24 Dec 2025 23:07:14 +0800 Subject: [PATCH] Add wildcard pattern matching for applications whitelist/blacklist - Support * and ? wildcards (e.g., Firefox*, *Code*) - Case-insensitive matching - Pattern caching for performance --- resources/ui/applications.ui | 6 ++- src/components/applications.js | 81 ++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/resources/ui/applications.ui b/resources/ui/applications.ui index 452a6b6d..3abac67a 100644 --- a/resources/ui/applications.ui +++ b/resources/ui/applications.ui @@ -131,7 +131,8 @@ This may cause some latency or performance issues. Whitelist - A list of windows to blur. + A list of windows to blur. +Use * to match any characters (e.g., Firefox* or *Code*). @@ -168,7 +169,8 @@ This may cause some latency or performance issues. Blacklist - A list of windows not to blur. + A list of windows not to blur. +Use * to match any characters (e.g., Firefox* or *Code*). diff --git a/src/components/applications.js b/src/components/applications.js index e2a8bdf9..d6c1c4bf 100644 --- a/src/components/applications.js +++ b/src/components/applications.js @@ -6,6 +6,52 @@ import { ApplicationsService } from '../dbus/services.js'; import { PaintSignals } from '../conveniences/paint_signals.js'; import { DummyPipeline } from '../conveniences/dummy_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; @@ -15,6 +61,27 @@ export const ApplicationsBlur = class ApplicationsBlur { // 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() { @@ -108,6 +175,9 @@ export const ApplicationsBlur = class ApplicationsBlur { /// 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) => { @@ -184,11 +254,14 @@ export const ApplicationsBlur = class ApplicationsBlur { /// 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; - const whitelist = this.settings.applications.WHITELIST; - const blacklist = this.settings.applications.BLACKLIST; if (window_wm_class) this._log(`pid ${meta_window.bms_pid} associated to wm class name ${window_wm_class}`); @@ -197,8 +270,8 @@ export const ApplicationsBlur = class ApplicationsBlur { // or if we are in whitelist mode and the window is whitelisted if ( window_wm_class !== "" - && ((enable_all && !blacklist.includes(window_wm_class)) - || (!enable_all && whitelist.includes(window_wm_class)) + && ((enable_all && !matchesAnyPattern(window_wm_class, this._compiled_blacklist)) + || (!enable_all && matchesAnyPattern(window_wm_class, this._compiled_whitelist)) ) && [ Meta.FrameType.NORMAL,