548 lines
19 KiB
JavaScript
548 lines
19 KiB
JavaScript
import Shell from 'gi://Shell';
|
|
import Clutter from 'gi://Clutter';
|
|
import Meta from 'gi://Meta';
|
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|
|
|
import { PaintSignals } from '../effects/paint_signals.js';
|
|
import { ApplicationsService } from '../dbus/services.js';
|
|
|
|
|
|
export const ApplicationsBlur = class ApplicationsBlur {
|
|
constructor(connections, settings, _) {
|
|
this.connections = connections;
|
|
this.settings = settings;
|
|
this.paint_signals = new PaintSignals(connections);
|
|
|
|
// stores every blurred window
|
|
this.window_map = new Map();
|
|
// stores every blur actor
|
|
this.blur_actor_map = new Map();
|
|
}
|
|
|
|
enable() {
|
|
this._log("blurring applications...");
|
|
|
|
// export dbus service for preferences
|
|
this.service = new ApplicationsService;
|
|
this.service.export();
|
|
|
|
// 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) {
|
|
let window_actor = meta_window.get_compositor_private();
|
|
this.track_new(window_actor, meta_window);
|
|
}
|
|
}
|
|
);
|
|
|
|
this.connect_to_overview();
|
|
}
|
|
|
|
/// 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.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',
|
|
_ => {
|
|
let active_workspace =
|
|
global.workspace_manager.get_active_workspace();
|
|
|
|
this.window_map.forEach((meta_window, _pid) => {
|
|
let window_actor = meta_window.get_compositor_private();
|
|
|
|
if (
|
|
meta_window.get_workspace() !== active_workspace
|
|
)
|
|
window_actor.hide();
|
|
});
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Iterate through all existing windows and add blur as needed.
|
|
update_all_windows() {
|
|
// remove all previously blurred windows, in the case where the
|
|
// whitelist was changed
|
|
this.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 => {
|
|
let window_actor = meta_window.get_compositor_private();
|
|
|
|
// disconnect previous signals
|
|
this.connections.disconnect_all_for(window_actor);
|
|
|
|
this.track_new(window_actor, meta_window);
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Adds the needed signals to every new tracked window, and adds blur if
|
|
/// needed.
|
|
track_new(window_actor, meta_window) {
|
|
let pid = ("" + Math.random()).slice(2, 16);
|
|
|
|
window_actor['blur_provider_pid'] = pid;
|
|
meta_window['blur_provider_pid'] = pid;
|
|
|
|
// remove the blur when the window is destroyed
|
|
this.connections.connect(window_actor, 'destroy', window_actor => {
|
|
let pid = window_actor.blur_provider_pid;
|
|
if (this.blur_actor_map.has(pid)) {
|
|
this.remove_blur(pid);
|
|
}
|
|
this.window_map.delete(pid);
|
|
});
|
|
|
|
// update the blur when mutter-hint or wm-class is changed
|
|
for (const prop of ['mutter-hints', 'wm-class']) {
|
|
this.connections.connect(
|
|
meta_window,
|
|
`notify::${prop}`,
|
|
_ => {
|
|
let pid = meta_window.blur_provider_pid;
|
|
this._log(`${prop} changed for pid ${pid}`);
|
|
|
|
let window_actor = meta_window.get_compositor_private();
|
|
this.check_blur(pid, window_actor, meta_window);
|
|
}
|
|
);
|
|
}
|
|
|
|
// update the position and size when the window size changes
|
|
this.connections.connect(meta_window, 'size-changed', () => {
|
|
if (this.blur_actor_map.has(pid)) {
|
|
let allocation = this.compute_allocation(meta_window);
|
|
let blur_actor = this.blur_actor_map.get(pid);
|
|
blur_actor.x = allocation.x;
|
|
blur_actor.y = allocation.y;
|
|
blur_actor.width = allocation.width;
|
|
blur_actor.height = allocation.height;
|
|
}
|
|
});
|
|
|
|
this.check_blur(pid, window_actor, meta_window);
|
|
}
|
|
|
|
/// Checks if the given actor needs to be blurred.
|
|
///
|
|
/// In order to be blurred, a window either:
|
|
/// - is whitelisted in the user preferences if not enable-all
|
|
/// - is not blacklisted if enable-all
|
|
/// - has a correct mutter hint, set to `blur-provider=sigma_value`
|
|
check_blur(pid, window_actor, meta_window) {
|
|
let mutter_hint = meta_window.get_mutter_hints();
|
|
let window_wm_class = meta_window.get_wm_class();
|
|
|
|
let enable_all = this.settings.applications.ENABLE_ALL;
|
|
let whitelist = this.settings.applications.WHITELIST;
|
|
let blacklist = this.settings.applications.BLACKLIST;
|
|
|
|
this._log(`checking blur for ${pid}`);
|
|
|
|
// either the window is included in whitelist
|
|
if (window_wm_class !== ""
|
|
&& ((enable_all && !blacklist.includes(window_wm_class))
|
|
|| (!enable_all && whitelist.includes(window_wm_class))
|
|
)
|
|
&& [
|
|
Meta.FrameType.NORMAL,
|
|
Meta.FrameType.DIALOG,
|
|
Meta.FrameType.MODAL_DIALOG
|
|
].includes(meta_window.get_frame_type())
|
|
) {
|
|
this._log(`application ${pid} listed, blurring it`);
|
|
|
|
// get blur effect parameters
|
|
|
|
let brightness, sigma;
|
|
|
|
if (this.settings.applications.CUSTOMIZE) {
|
|
brightness = this.settings.applications.BRIGHTNESS;
|
|
sigma = this.settings.applications.SIGMA;
|
|
} else {
|
|
brightness = this.settings.BRIGHTNESS;
|
|
sigma = this.settings.SIGMA;
|
|
}
|
|
|
|
this.update_blur(pid, window_actor, meta_window, brightness, sigma);
|
|
}
|
|
|
|
// or blur is asked by window itself
|
|
else if (
|
|
mutter_hint != null &&
|
|
mutter_hint.includes("blur-provider")
|
|
) {
|
|
this._log(`application ${pid} has hint ${mutter_hint}, parsing`);
|
|
|
|
// get blur effect parameters
|
|
let [brightness, sigma] = this.parse_xprop(mutter_hint);
|
|
|
|
this.update_blur(pid, window_actor, meta_window, brightness, sigma);
|
|
}
|
|
|
|
// remove blur if the mutter hint is no longer valid, and the window
|
|
// is not explicitly whitelisted or un-blacklisted
|
|
else if (this.blur_actor_map.has(pid)) {
|
|
this.remove_blur(pid);
|
|
}
|
|
}
|
|
|
|
/// When given the xprop property, returns the brightness and sigma values
|
|
/// matching. If one of the two values is invalid, or missing, then it uses
|
|
/// default values.
|
|
///
|
|
/// An xprop property is valid if it is in one of the following formats:
|
|
///
|
|
/// blur-provider=sigma:60,brightness:0.9
|
|
/// blur-provider=s:10,brightness:0.492
|
|
/// blur-provider=b:1.0,s:16
|
|
///
|
|
/// Brightness is a floating-point between 0.0 and 1.0 included.
|
|
/// Sigma is an integer between 0 and 999 included.
|
|
///
|
|
/// If sigma is set to 0, then the blur is removed.
|
|
/// Setting "default" instead of the two values will make the
|
|
/// extension use its default value.
|
|
///
|
|
/// Note that no space can be inserted.
|
|
///
|
|
parse_xprop(property) {
|
|
// set brightness and sigma to default values
|
|
let brightness, sigma;
|
|
if (this.settings.applications.CUSTOMIZE) {
|
|
brightness = this.settings.applications.BRIGHTNESS;
|
|
sigma = this.settings.applications.SIGMA;
|
|
} else {
|
|
brightness = this.settings.BRIGHTNESS;
|
|
sigma = this.settings.SIGMA;
|
|
}
|
|
|
|
// get the argument of the property
|
|
let arg = property.match("blur-provider=(.*)");
|
|
this._log(`argument = ${arg}`);
|
|
|
|
// if argument is valid, parse it
|
|
if (arg != null) {
|
|
// verify if there is only one value: in this case, this is sigma
|
|
let maybe_sigma = parseInt(arg[1]);
|
|
|
|
if (
|
|
!isNaN(maybe_sigma) &&
|
|
maybe_sigma >= 0 &&
|
|
maybe_sigma <= 999
|
|
) {
|
|
sigma = maybe_sigma;
|
|
} else {
|
|
// perform pattern matching
|
|
let res_b = arg[1].match("(brightness|b):(default|0?1?\.[0-9]*)");
|
|
let res_s = arg[1].match("(sigma|s):(default|\\d{1,3})");
|
|
|
|
// if values are valid and not default, change them to the xprop one
|
|
if (
|
|
res_b != null && res_b[2] !== 'default'
|
|
) {
|
|
brightness = parseFloat(res_b[2]);
|
|
}
|
|
|
|
if (
|
|
res_s != null && res_s[2] !== 'default'
|
|
) {
|
|
sigma = parseInt(res_s[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
this._log(`brightness = ${brightness}, sigma = ${sigma}`);
|
|
|
|
return [brightness, sigma];
|
|
}
|
|
|
|
/// Updates the blur on a window which needs to be blurred.
|
|
update_blur(pid, window_actor, meta_window, brightness, sigma) {
|
|
// the window is already blurred, update its blur effect
|
|
if (this.blur_actor_map.has(pid)) {
|
|
// window is already blurred, but sigma is null: remove the blur
|
|
if (sigma === 0) {
|
|
this.remove_blur(pid);
|
|
}
|
|
// window is already blurred and sigma is non-null: update it
|
|
else {
|
|
this.update_blur_effect(
|
|
this.blur_actor_map.get(pid),
|
|
brightness,
|
|
sigma
|
|
);
|
|
}
|
|
}
|
|
|
|
// the window is not blurred, and sigma is a non-null value: blur it
|
|
else if (sigma !== 0) {
|
|
// window is not blurred, blur it
|
|
this.create_blur_effect(
|
|
pid,
|
|
window_actor,
|
|
meta_window,
|
|
brightness,
|
|
sigma
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Add the blur effect to the window.
|
|
create_blur_effect(pid, window_actor, meta_window, brightness, sigma) {
|
|
let blur_effect = new Shell.BlurEffect({
|
|
sigma: sigma,
|
|
brightness: brightness,
|
|
mode: Shell.BlurMode.BACKGROUND
|
|
});
|
|
|
|
let blur_actor = this.create_blur_actor(
|
|
meta_window,
|
|
window_actor,
|
|
blur_effect
|
|
);
|
|
|
|
// if hacks are selected, force to repaint the window
|
|
if (this.settings.HACKS_LEVEL === 1 || this.settings.HACKS_LEVEL === 2) {
|
|
this._log("applications hack level 1 or 2");
|
|
|
|
this.paint_signals.disconnect_all();
|
|
this.paint_signals.connect(blur_actor, blur_effect);
|
|
} else {
|
|
this.paint_signals.disconnect_all();
|
|
}
|
|
|
|
// insert the blurred widget
|
|
window_actor.insert_child_at_index(blur_actor, 0);
|
|
|
|
// make sure window is blurred in overview
|
|
if (this.settings.applications.BLUR_ON_OVERVIEW)
|
|
this.enforce_window_visibility_on_overview_for(window_actor);
|
|
|
|
// set the window actor's opacity
|
|
this.set_window_opacity(window_actor, this.settings.applications.OPACITY);
|
|
|
|
this.connections.connect(
|
|
window_actor,
|
|
'notify::opacity',
|
|
_ => this.set_window_opacity(window_actor, this.settings.applications.OPACITY)
|
|
);
|
|
|
|
// register the blur actor/effect
|
|
blur_actor['blur_provider_pid'] = pid;
|
|
this.blur_actor_map.set(pid, blur_actor);
|
|
this.window_map.set(pid, meta_window);
|
|
|
|
// hide the blur if window is invisible
|
|
if (!window_actor.visible) {
|
|
blur_actor.hide();
|
|
}
|
|
|
|
// hide the blur if window becomes invisible
|
|
this.connections.connect(
|
|
window_actor,
|
|
'notify::visible',
|
|
window_actor => {
|
|
let pid = window_actor.blur_provider_pid;
|
|
if (window_actor.visible) {
|
|
this.blur_actor_map.get(pid).show();
|
|
} else {
|
|
this.blur_actor_map.get(pid).hide();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/// 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;
|
|
});
|
|
}
|
|
|
|
/// Compute the size and position for a blur actor.
|
|
/// On wayland, it seems like we need to divide by the scale to get the
|
|
/// correct result.
|
|
compute_allocation(meta_window) {
|
|
const is_wayland = Meta.is_wayland_compositor();
|
|
const monitor_index = meta_window.get_monitor();
|
|
// check if the window is using wayland, or xwayland/xorg for rendering
|
|
const scale = is_wayland && meta_window.get_client_type() == 0
|
|
? Main.layoutManager.monitors[monitor_index].geometry_scale
|
|
: 1;
|
|
|
|
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
|
|
};
|
|
}
|
|
|
|
/// Returns a new already blurred widget, configured to follow the size and
|
|
/// position of its target window.
|
|
create_blur_actor(meta_window, window_actor, blur_effect) {
|
|
// compute the size and position
|
|
let allocation = this.compute_allocation(meta_window);
|
|
|
|
// create the actor
|
|
let blur_actor = new Clutter.Actor({
|
|
x: allocation.x,
|
|
y: allocation.y,
|
|
width: allocation.width,
|
|
height: allocation.height
|
|
});
|
|
|
|
// add the effect
|
|
blur_actor.add_effect_with_name('blur-effect', blur_effect);
|
|
|
|
return blur_actor;
|
|
}
|
|
|
|
/// Updates the blur effect by overwriting its sigma and brightness values.
|
|
update_blur_effect(blur_actor, brightness, sigma) {
|
|
let effect = blur_actor.get_effect('blur-effect');
|
|
effect.sigma = sigma;
|
|
effect.brightness = brightness;
|
|
}
|
|
|
|
/// Removes the blur actor from the shell and unregister it.
|
|
remove_blur(pid) {
|
|
this._log(`removing blur for pid ${pid}`);
|
|
|
|
let meta_window = this.window_map.get(pid);
|
|
// disconnect needed signals and untrack window
|
|
if (meta_window) {
|
|
this.window_map.delete(pid);
|
|
let window_actor = meta_window.get_compositor_private();
|
|
|
|
let blur_actor = this.blur_actor_map.get(pid);
|
|
if (blur_actor) {
|
|
this.blur_actor_map.delete(pid);
|
|
|
|
if (window_actor) {
|
|
// reset the opacity
|
|
this.set_window_opacity(window_actor, 255);
|
|
|
|
// remove the blurred actor
|
|
window_actor.remove_child(blur_actor);
|
|
|
|
// disconnect the signals about overview animation etc
|
|
this.connections.disconnect_all_for(window_actor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
disable() {
|
|
this._log("removing blur from applications...");
|
|
|
|
this.service?.unexport();
|
|
|
|
this.blur_actor_map.forEach(((_blur_actor, pid) => {
|
|
this.remove_blur(pid);
|
|
}));
|
|
|
|
this.connections.disconnect_all();
|
|
this.paint_signals.disconnect_all();
|
|
}
|
|
|
|
/// Update the opacity of all window actors.
|
|
set_opacity() {
|
|
let opacity = this.settings.applications.OPACITY;
|
|
|
|
this.window_map.forEach(((meta_window, _pid) => {
|
|
let window_actor = meta_window.get_compositor_private();
|
|
this.set_window_opacity(window_actor, opacity);
|
|
}));
|
|
}
|
|
|
|
/// Updates each blur effect to use new sigma value
|
|
// FIXME set_sigma and set_brightness are called when the extension is
|
|
// loaded and when sigma is changed, and do not respect the per-app
|
|
// xprop behaviour
|
|
set_sigma(s) {
|
|
this.blur_actor_map.forEach((actor, _) => {
|
|
actor.get_effect('blur-effect').set_sigma(s);
|
|
});
|
|
}
|
|
|
|
/// Updates each blur effect to use new brightness value
|
|
set_brightness(b) {
|
|
this.blur_actor_map.forEach((actor, _) => {
|
|
actor.get_effect('blur-effect').set_brightness(b);
|
|
});
|
|
}
|
|
|
|
// not implemented for dynamic blur
|
|
set_color(c) { }
|
|
set_noise_amount(n) { }
|
|
set_noise_lightness(l) { }
|
|
|
|
_log(str) {
|
|
if (this.settings.DEBUG)
|
|
console.log(`[Blur my Shell > applications] ${str}`);
|
|
}
|
|
}; |