123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 |
- 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}`);
- }
- };
|