123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 |
- import St from 'gi://St';
- import Shell from 'gi://Shell';
- import Meta from 'gi://Meta';
- import Mtk from 'gi://Mtk';
- import * as Main from 'resource:///org/gnome/shell/ui/main.js';
- import { PaintSignals } from '../effects/paint_signals.js';
- const DASH_TO_PANEL_UUID = 'dash-to-panel@jderose9.github.com';
- const PANEL_STYLES = [
- "transparent-panel",
- "light-panel",
- "dark-panel",
- "contrasted-panel"
- ];
- export const PanelBlur = class PanelBlur {
- constructor(connections, settings, effects_manager) {
- this.connections = connections;
- this.window_signal_ids = new Map();
- this.settings = settings;
- this.effects_manager = effects_manager;
- this.actors_list = [];
- this.enabled = false;
- }
- enable() {
- this._log("blurring top panel");
- // check for panels when Dash to Panel is activated
- this.connections.connect(
- Main.extensionManager,
- 'extension-state-changed',
- (_, extension) => {
- if (extension.uuid === DASH_TO_PANEL_UUID
- && extension.state === 1
- ) {
- this.connections.connect(
- global.dashToPanel,
- 'panels-created',
- _ => this.blur_dtp_panels()
- );
- this.blur_existing_panels();
- }
- }
- );
- this.blur_existing_panels();
- // connect to overview being opened/closed, and dynamically show or not
- // the blur when a window is near a panel
- this.connect_to_windows_and_overview();
- // connect to every background change (even without changing image)
- // FIXME this signal is fired very often, so we should find another one
- // fired only when necessary (but that still catches all cases)
- this.connections.connect(
- Main.layoutManager._backgroundGroup,
- 'notify',
- _ => this.actors_list.forEach(actors =>
- this.update_wallpaper(actors)
- )
- );
- // connect to monitors change
- this.connections.connect(
- Main.layoutManager,
- 'monitors-changed',
- _ => {
- if (Main.screenShield && !Main.screenShield.locked) {
- this.reset();
- }
- }
- );
- this.enabled = true;
- }
- reset() {
- this._log("resetting...");
- this.disable();
- setTimeout(_ => this.enable(), 1);
- }
- /// Check for already existing panels and blur them if they are not already
- blur_existing_panels() {
- // check if dash-to-panel is present
- if (global.dashToPanel) {
- // blur already existing ones
- if (global.dashToPanel.panels)
- this.blur_dtp_panels();
- } else {
- // if no dash-to-panel, blur the main and only panel
- this.maybe_blur_panel(Main.panel);
- }
- }
- blur_dtp_panels() {
- // FIXME when Dash to Panel changes its size, it seems it creates new
- // panels; but I can't get to delete old widgets
- // blur every panel found
- global.dashToPanel.panels.forEach(p => {
- this.maybe_blur_panel(p.panel);
- });
- // if main panel is not included in the previous panels, blur it
- if (
- !global.dashToPanel.panels
- .map(p => p.panel)
- .includes(Main.panel)
- &&
- this.settings.dash_to_panel.BLUR_ORIGINAL_PANEL
- )
- this.maybe_blur_panel(Main.panel);
- };
- /// Blur a panel only if it is not already blurred (contained in the list)
- maybe_blur_panel(panel) {
- // check if the panel is contained in the list
- let actors = this.actors_list.find(
- actors => actors.widgets.panel == panel
- );
- if (!actors)
- // if the actors is not blurred, blur it
- this.blur_panel(panel);
- else
- // if it is blurred, update the blur anyway
- this.change_blur_type(actors);
- }
- /// Blur a panel
- blur_panel(panel) {
- let panel_box = panel.get_parent();
- let is_dtp_panel = false;
- if (!panel_box.name) {
- is_dtp_panel = true;
- panel_box = panel_box.get_parent();
- }
- let monitor = this.find_monitor_for(panel);
- if (!monitor)
- return;
- let background_parent = new St.Widget({
- name: 'topbar-blurred-background-parent',
- x: 0, y: 0, width: 0, height: 0
- });
- let background = this.settings.panel.STATIC_BLUR
- ? new Meta.BackgroundActor({
- meta_display: global.display,
- monitor: monitor.index
- })
- : new St.Widget;
- background_parent.add_child(background);
- // insert background parent
- panel_box.insert_child_at_index(background_parent, 0);
- let blur = new Shell.BlurEffect({
- brightness: this.settings.panel.CUSTOMIZE
- ? this.settings.panel.BRIGHTNESS
- : this.settings.BRIGHTNESS,
- sigma: this.settings.panel.CUSTOMIZE
- ? this.settings.panel.SIGMA
- : this.settings.SIGMA
- * monitor.geometry_scale,
- mode: this.settings.panel.STATIC_BLUR
- ? Shell.BlurMode.ACTOR
- : Shell.BlurMode.BACKGROUND
- });
- // store the scale in the effect in order to retrieve it in set_sigma
- blur.scale = monitor.geometry_scale;
- let color = this.effects_manager.new_color_effect({
- color: this.settings.panel.CUSTOMIZE
- ? this.settings.panel.COLOR
- : this.settings.COLOR
- }, this.settings);
- let noise = this.effects_manager.new_noise_effect({
- noise: this.settings.panel.CUSTOMIZE
- ? this.settings.panel.NOISE_AMOUNT
- : this.settings.NOISE_AMOUNT,
- lightness: this.settings.panel.CUSTOMIZE
- ? this.settings.panel.NOISE_LIGHTNESS
- : this.settings.NOISE_LIGHTNESS
- }, this.settings);
- let paint_signals = new PaintSignals(this.connections);
- let actors = {
- widgets: { panel, panel_box, background, background_parent },
- effects: { blur, color, noise },
- paint_signals,
- monitor,
- is_dtp_panel
- };
- this.actors_list.push(actors);
- // perform updates
- this.change_blur_type(actors);
- // connect to panel, panel_box and its parent position or size change
- // this should fire update_size every time one of its params change
- this.connections.connect(
- panel,
- 'notify::position',
- _ => this.update_size(actors)
- );
- this.connections.connect(
- panel_box,
- ['notify::size', 'notify::position'],
- _ => this.update_size(actors)
- );
- this.connections.connect(
- panel_box.get_parent(),
- 'notify::position',
- _ => this.update_size(actors)
- );
- }
- update_all_blur_type() {
- this.actors_list.forEach(actors => this.change_blur_type(actors));
- }
- change_blur_type(actors) {
- let is_static = this.settings.panel.STATIC_BLUR;
- // reset widgets to right state
- actors.widgets.background_parent.remove_child(actors.widgets.background);
- this.effects_manager.remove(actors.effects.blur);
- this.effects_manager.remove(actors.effects.color);
- this.effects_manager.remove(actors.effects.noise);
- // create new background actor
- actors.widgets.background = is_static
- ? new Meta.BackgroundActor({
- meta_display: global.display,
- monitor: this.find_monitor_for(actors.widgets.panel).index
- })
- : new St.Widget;
- // change blur mode
- actors.effects.blur.set_mode(is_static ? 0 : 1);
- // disable other effects if the blur is dynamic, as they makes it opaque
- actors.effects.color._static = is_static;
- actors.effects.noise._static = is_static;
- actors.effects.color.update_enabled();
- actors.effects.noise.update_enabled();
- // add the effects in order
- actors.widgets.background.add_effect(actors.effects.color);
- actors.widgets.background.add_effect(actors.effects.noise);
- actors.widgets.background.add_effect(actors.effects.blur);
- // add the background actor behing the panel
- actors.widgets.background_parent.add_child(actors.widgets.background);
- // perform updates
- this.update_wallpaper(actors);
- this.update_size(actors);
- // HACK
- //
- //`Shell.BlurEffect` does not repaint when shadows are under it. [1]
- //
- // This does not entirely fix this bug (shadows caused by windows
- // still cause artifacts), but it prevents the shadows of the panel
- // buttons to cause artifacts on the panel itself
- //
- // [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
- if (!is_static) {
- if (this.settings.HACKS_LEVEL === 1) {
- this._log("panel hack level 1");
- actors.paint_signals.disconnect_all();
- let rp = () => { actors.effects.blur.queue_repaint(); };
- this.connections.connect(actors.widgets.panel, [
- 'enter-event', 'leave-event', 'button-press-event'
- ], rp);
- actors.widgets.panel.get_children().forEach(child => {
- this.connections.connect(child, [
- 'enter-event', 'leave-event', 'button-press-event'
- ], rp);
- });
- } else if (this.settings.HACKS_LEVEL === 2) {
- this._log("panel hack level 2");
- actors.paint_signals.disconnect_all();
- actors.paint_signals.connect(
- actors.widgets.background, actors.effects.blur
- );
- } else {
- actors.paint_signals.disconnect_all();
- }
- }
- }
- update_wallpaper(actors) {
- // if static blur, get right wallpaper and update blur with it
- if (this.settings.panel.STATIC_BLUR) {
- let bg = Main.layoutManager._backgroundGroup.get_child_at_index(
- Main.layoutManager.monitors.length
- - this.find_monitor_for(actors.widgets.panel).index - 1
- );
- if (bg)
- actors.widgets.background.content.set({
- background: bg.get_content().background
- });
- else
- this._warn("could not get background for panel");
- }
- }
- update_size(actors) {
- let panel = actors.widgets.panel;
- let panel_box = actors.widgets.panel_box;
- let background = actors.widgets.background;
- let monitor = this.find_monitor_for(panel);
- if (!monitor)
- return;
- let [width, height] = panel_box.get_size();
- background.width = width;
- background.height = height;
- // if static blur, need to clip the background
- if (this.settings.panel.STATIC_BLUR) {
- // an alternative to panel.get_transformed_position, because it
- // sometimes yields NaN (probably when the actor is not fully
- // positionned yet)
- let [p_x, p_y] = panel_box.get_position();
- let [p_p_x, p_p_y] = panel_box.get_parent().get_position();
- let x = p_x + p_p_x - monitor.x;
- let y = p_y + p_p_y - monitor.y;
- background.set_clip(x, y, width, height);
- background.x = -x;
- background.y = -y;
- // fixes a bug where the blur is washed away when changing the sigma
- this.invalidate_blur(actors);
- } else {
- background.x = panel.x;
- background.y = panel.y;
- }
- // update the monitor panel is on
- actors.monitor = this.find_monitor_for(panel);
- }
- /// An helper function to find the monitor in which an actor is situated,
- /// there might be a pre-existing function in GLib already
- find_monitor_for(actor) {
- let extents = actor.get_transformed_extents();
- let rect = new Mtk.Rectangle({
- x: extents.get_x(),
- y: extents.get_y(),
- width: extents.get_width(),
- height: extents.get_height(),
- });
- let index = global.display.get_monitor_index_for_rect(rect);
- return Main.layoutManager.monitors[index];
- }
- /// Connect when overview if opened/closed to hide/show the blur accordingly
- ///
- /// If HIDETOPBAR is set, we need just to hide the blur when showing appgrid
- /// (so no shadow is cropped)
- connect_to_overview() {
- // may be called when panel blur is disabled, if hidetopbar
- // compatibility is toggled on/off
- // if this is the case, do nothing as only the panel blur interfers with
- // hidetopbar
- if (
- this.settings.panel.BLUR &&
- this.settings.panel.UNBLUR_IN_OVERVIEW
- ) {
- if (!this.settings.hidetopbar.COMPATIBILITY) {
- this.connections.connect(
- Main.overview, 'showing', this.hide.bind(this)
- );
- this.connections.connect(
- Main.overview, 'hidden', this.show.bind(this)
- );
- } else {
- let appDisplay = Main.overview._overview._controls._appDisplay;
- this.connections.connect(
- appDisplay, 'show', this.hide.bind(this)
- );
- this.connections.connect(
- appDisplay, 'hide', this.show.bind(this)
- );
- }
- }
- }
- /// Connect to windows disable transparency when a window is too close
- connect_to_windows() {
- if (
- this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY
- ) {
- // connect to overview opening/closing
- this.connections.connect(Main.overview, ['showing', 'hiding'],
- this.update_visibility.bind(this)
- );
- // connect to session mode update
- this.connections.connect(Main.sessionMode, 'updated',
- this.update_visibility.bind(this)
- );
- // manage already-existing windows
- for (const meta_window_actor of global.get_window_actors()) {
- this.on_window_actor_added(
- meta_window_actor.get_parent(), meta_window_actor
- );
- }
- // manage windows at their creation/removal
- this.connections.connect(global.window_group, 'actor-added',
- this.on_window_actor_added.bind(this)
- );
- this.connections.connect(global.window_group, 'actor-removed',
- this.on_window_actor_removed.bind(this)
- );
- // connect to a workspace change
- this.connections.connect(global.window_manager, 'switch-workspace',
- this.update_visibility.bind(this)
- );
- // perform early update
- this.update_visibility();
- } else {
- // reset transparency for every panels
- this.actors_list.forEach(
- actors => this.set_should_override_panel(actors, true)
- );
- }
- }
- /// An helper to connect to both the windows and overview signals.
- /// This is the only function that should be directly called, to prevent
- /// inconsistencies with signals not being disconnected.
- connect_to_windows_and_overview() {
- this.disconnect_from_windows_and_overview();
- this.connect_to_overview();
- this.connect_to_windows();
- }
- /// Disconnect all the connections created by connect_to_windows
- disconnect_from_windows_and_overview() {
- // disconnect the connections to actors
- for (const actor of [
- Main.overview, Main.sessionMode,
- global.window_group, global.window_manager,
- Main.overview._overview._controls._appDisplay
- ]) {
- this.connections.disconnect_all_for(actor);
- }
- // disconnect the connections from windows
- for (const [actor, ids] of this.window_signal_ids) {
- for (const id of ids) {
- actor.disconnect(id);
- }
- }
- this.window_signal_ids = new Map();
- }
- /// Callback when a new window is added
- on_window_actor_added(container, meta_window_actor) {
- this.window_signal_ids.set(meta_window_actor, [
- meta_window_actor.connect('notify::allocation',
- _ => this.update_visibility()
- ),
- meta_window_actor.connect('notify::visible',
- _ => this.update_visibility()
- )
- ]);
- this.update_visibility();
- }
- /// Callback when a window is removed
- on_window_actor_removed(container, meta_window_actor) {
- for (const signalId of this.window_signal_ids.get(meta_window_actor)) {
- meta_window_actor.disconnect(signalId);
- }
- this.window_signal_ids.delete(meta_window_actor);
- this.update_visibility();
- }
- /// Update the visibility of the blur effect
- update_visibility() {
- if (
- Main.panel.has_style_pseudo_class('overview')
- || !Main.sessionMode.hasWindows
- ) {
- this.actors_list.forEach(
- actors => this.set_should_override_panel(actors, true)
- );
- return;
- }
- if (!Main.layoutManager.primaryMonitor)
- return;
- // get all the windows in the active workspace that are visible
- const workspace = global.workspace_manager.get_active_workspace();
- const windows = workspace.list_windows().filter(meta_window =>
- meta_window.showing_on_its_workspace()
- && !meta_window.is_hidden()
- && meta_window.get_window_type() !== Meta.WindowType.DESKTOP
- // exclude Desktop Icons NG
- && meta_window.get_gtk_application_id() !== "com.rastersoft.ding"
- );
- // check if at least one window is near enough to each panel and act
- // accordingly
- const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor;
- this.actors_list
- // do not apply for dtp panels, as it would only cause bugs and it
- // can be done from its preferences anyway
- .filter(actors => !actors.is_dtp_panel)
- .forEach(actors => {
- let panel = actors.widgets.panel;
- let panel_top = panel.get_transformed_position()[1];
- let panel_bottom = panel_top + panel.get_height();
- // check if at least a window is near enough the panel
- let window_overlap_panel = false;
- windows.forEach(meta_window => {
- let window_monitor_i = meta_window.get_monitor();
- let same_monitor = actors.monitor.index == window_monitor_i;
- let window_vertical_pos = meta_window.get_frame_rect().y;
- // if so, and if in the same monitor, then it overlaps
- if (same_monitor
- &&
- window_vertical_pos < panel_bottom + 5 * scale
- )
- window_overlap_panel = true;
- });
- // if no window overlaps, then the panel is transparent
- this.set_should_override_panel(
- actors, !window_overlap_panel
- );
- });
- }
- /// Choose wether or not the panel background should be overriden, in
- /// respect to its argument and the `override-background` setting.
- set_should_override_panel(actors, should_override) {
- let panel = actors.widgets.panel;
- PANEL_STYLES.forEach(style => panel.remove_style_class_name(style));
- if (
- this.settings.panel.OVERRIDE_BACKGROUND
- &&
- should_override
- )
- panel.add_style_class_name(
- PANEL_STYLES[this.settings.panel.STYLE_PANEL]
- );
- }
- /// Fixes a bug where the blur is washed away when changing the sigma, or
- /// enabling/disabling other effects.
- invalidate_blur(actors) {
- if (this.settings.panel.STATIC_BLUR && actors.widgets.background)
- actors.widgets.background.get_content().invalidate();
- }
- invalidate_all_blur() {
- this.actors_list.forEach(actors => this.invalidate_blur(actors));
- }
- set_sigma(s) {
- this.actors_list.forEach(actors => {
- actors.effects.blur.sigma = s * actors.effects.blur.scale;
- this.invalidate_blur(actors);
- });
- }
- set_brightness(b) {
- this.actors_list.forEach(actors => {
- actors.effects.blur.brightness = b;
- });
- }
- set_color(c) {
- this.actors_list.forEach(actors => {
- actors.effects.color.color = c;
- });
- }
- set_noise_amount(n) {
- this.actors_list.forEach(actors => {
- actors.effects.noise.noise = n;
- });
- }
- set_noise_lightness(l) {
- this.actors_list.forEach(actors => {
- actors.effects.noise.lightness = l;
- });
- }
- show() {
- this.actors_list.forEach(actors => {
- actors.widgets.background_parent.show();
- });
- }
- hide() {
- this.actors_list.forEach(actors => {
- actors.widgets.background_parent.hide();
- });
- }
- // destroy every blurred background left, necessary after sleep
- destroy_blur_effects() {
- Main.panel?.get_parent()?.get_children().forEach(
- child => {
- if (child.name === 'topbar-blurred-background-parent') {
- child.get_children().forEach(meta_background_actor => {
- meta_background_actor.get_effects().forEach(effect => {
- this.effects_manager.remove(effect);
- });
- });
- child.destroy_all_children();
- child.destroy();
- }
- }
- );
- }
- disable() {
- this._log("removing blur from top panel");
- this.disconnect_from_windows_and_overview();
- this.actors_list.forEach(actors => {
- this.set_should_override_panel(actors, false);
- this.effects_manager.remove(actors.effects.noise);
- this.effects_manager.remove(actors.effects.color);
- this.effects_manager.remove(actors.effects.blur);
- try {
- actors.widgets.panel_box.remove_child(
- actors.widgets.background_parent
- );
- } catch (e) { }
- actors.widgets.background_parent?.destroy();
- });
- this.destroy_blur_effects();
- this.actors_list = [];
- this.connections.disconnect_all();
- this.enabled = false;
- }
- _log(str) {
- if (this.settings.DEBUG)
- console.log(`[Blur my Shell > panel] ${str}`);
- }
- _warn(str) {
- console.warn(`[Blur my Shell > panel] ${str}`);
- }
- };
|