543 lines
18 KiB
JavaScript
Raw Normal View History

2024-07-08 22:46:35 +02:00
import St from 'gi://St';
import Meta from 'gi://Meta';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { PaintSignals } from '../conveniences/paint_signals.js';
import { Pipeline } from '../conveniences/pipeline.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.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();
// update the classname if the panel to have or have not light text
this.update_light_text_classname();
// connect to monitors change
this.connections.connect(Main.layoutManager, 'monitors-changed',
_ => 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);
}
/// 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 = Main.layoutManager.findMonitorForActor(panel);
if (!monitor)
return;
let background_group = new Meta.BackgroundGroup(
{ name: 'bms-panel-backgroundgroup', width: 0, height: 0 }
);
let background, bg_manager;
let static_blur = this.settings.panel.STATIC_BLUR;
if (static_blur) {
let bg_manager_list = [];
const pipeline = new Pipeline(
this.effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.panel.PIPELINE
);
background = pipeline.create_background_with_effects(
monitor.index, bg_manager_list,
background_group, 'bms-panel-blurred-widget'
);
bg_manager = bg_manager_list[0];
}
else {
const pipeline = new DummyPipeline(this.effects_manager, this.settings.panel);
[background, bg_manager] = pipeline.create_background_with_effect(
background_group, 'bms-panel-blurred-widget'
);
let paint_signals = new PaintSignals(this.connections);
// 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 (this.settings.HACKS_LEVEL === 1) {
this._log("panel hack level 1");
paint_signals.disconnect_all();
paint_signals.connect(background, pipeline.effect);
} else {
paint_signals.disconnect_all();
}
}
}
// insert the background group to the panel box
panel_box.insert_child_at_index(background_group, 0);
// the object that is used to remembering each elements that is linked to the blur effect
let actors = {
widgets: { panel, panel_box, background, background_group },
static_blur,
monitor,
bg_manager,
is_dtp_panel
};
this.actors_list.push(actors);
// update the size of the actor
this.update_size(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)
);
// connect to the panel getting destroyed
this.connections.connect(
panel,
'destroy',
_ => this.destroy_blur(actors, true)
);
}
update_size(actors) {
let panel = actors.widgets.panel;
let panel_box = actors.widgets.panel_box;
let background = actors.widgets.background;
let monitor = Main.layoutManager.findMonitorForActor(panel);
if (!monitor)
return;
let [width, height] = panel_box.get_size();
// if static blur, need to clip the background
if (actors.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;
} else {
background.x = panel.x;
background.y = panel.y;
background.width = width;
background.height = height;
}
// update the monitor panel is on
actors.monitor = Main.layoutManager.findMonitorForActor(panel);
}
/// 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()
);
this.connections.connect(
Main.overview, 'hidden', _ => this.show()
);
} else {
let appDisplay = Main.overview._overview._controls._appDisplay;
this.connections.connect(
appDisplay, 'show', _ => this.hide()
);
this.connections.connect(
appDisplay, 'hide', _ => this.show()
);
this.connections.connect(
Main.overview, 'hidden', _ => this.show()
);
}
}
}
/// 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()
);
// connect to session mode update
this.connections.connect(Main.sessionMode, 'updated',
_ => this.update_visibility()
);
// 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, 'child-added',
this.on_window_actor_added.bind(this)
);
this.connections.connect(global.window_group, 'child-removed',
this.on_window_actor_removed.bind(this)
);
// connect to a workspace change
this.connections.connect(global.window_manager, 'switch-workspace',
_ => this.update_visibility()
);
// 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();
}
/// Update the css classname of the panel for light theme
update_light_text_classname(disable = false) {
if (this.settings.panel.FORCE_LIGHT_TEXT && !disable)
Main.panel.add_style_class_name("panel-light-text");
else
Main.panel.remove_style_class_name("panel-light-text");
}
/// 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"
&& meta_window.get_gtk_application_id() !== "com.desktop.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]
);
}
update_pipeline() {
this.actors_list.forEach(actors =>
actors.bg_manager._bms_pipeline.change_pipeline_to(
this.settings.panel.PIPELINE
)
);
}
show() {
this.actors_list.forEach(actors => {
actors.widgets.background.show();
});
}
hide() {
this.actors_list.forEach(actors => {
actors.widgets.background.hide();
});
}
// IMPORTANT: do never call this in a mutable `this.actors_list.forEach`
destroy_blur(actors, panel_already_destroyed) {
this.set_should_override_panel(actors, false);
actors.bg_manager._bms_pipeline.destroy();
if (panel_already_destroyed)
actors.bg_manager.backgroundActor = null;
actors.bg_manager.destroy();
if (!panel_already_destroyed) {
actors.widgets.panel_box.remove_child(actors.widgets.background_group);
actors.widgets.background_group.destroy_all_children();
actors.widgets.background_group.destroy();
}
let index = this.actors_list.indexOf(actors);
if (index >= 0)
this.actors_list.splice(index, 1);
}
disable() {
this._log("removing blur from top panel");
this.disconnect_from_windows_and_overview();
this.update_light_text_classname(true);
const immutable_actors_list = [...this.actors_list];
immutable_actors_list.forEach(actors => this.destroy_blur(actors, false));
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}`);
}
};