Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions resources/ui/applications.ui
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ This may cause some latency or performance issues.</property>
<child>
<object class="AdwPreferencesGroup" id="whitelist">
<property name="title" translatable="yes">Whitelist</property>
<property name="description" translatable="yes">A list of windows to blur.</property>
<property name="description" translatable="yes">A list of windows to blur.
Use * to match any characters (e.g., Firefox* or *Code*).</property>
<property name="sensitive" bind-source="blur" bind-property="state" bind-flags="sync-create" />
<property name="header-suffix">
<object class="GtkButton" id="add_window_whitelist">
Expand Down Expand Up @@ -168,7 +169,8 @@ This may cause some latency or performance issues.</property>
<child>
<object class="AdwPreferencesGroup" id="blacklist">
<property name="title" translatable="yes">Blacklist</property>
<property name="description" translatable="yes">A list of windows not to blur.</property>
<property name="description" translatable="yes">A list of windows not to blur.
Use * to match any characters (e.g., Firefox* or *Code*).</property>
<property name="sensitive" bind-source="blur" bind-property="state" bind-flags="sync-create" />
<property name="header-suffix">
<object class="GtkButton" id="add_window_blacklist">
Expand Down
81 changes: 77 additions & 4 deletions src/components/applications.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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}`);

Expand All @@ -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,
Expand Down