diff options
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js')
| -rw-r--r-- | raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js new file mode 100644 index 0000000..6c084cf --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/panel.js @@ -0,0 +1,577 @@ +import St from 'gi://St'; +import GLib from 'gi://GLib'; +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" +]; + +// global listener, so we don't miss the panel destruction event +let isMainPanelAlive = true; +Main.panel.connect('destroy', () => isMainPanelAlive = false); + +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() { + if (this.enabled) { + this._log("blur already enabled"); + return; + } + + 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 workareas change + this.connections.connect(global.display, 'workareas-changed', + _ => this.reset() + ); + + this.enabled = true; + } + + reset() { + if (!this.enabled) { + this._log("reset called but blur is not enabled"); + return; + } + + 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(); + + // blur main panel if requested in settings + if (this.settings.dash_to_panel.BLUR_ORIGINAL_PANEL && isMainPanelAlive) + this.maybe_blur_panel(Main.panel); + } else { + // if no dash-to-panel, blur the main and only panel + this.maybe_blur_panel(Main.panel); + } + } + + blur_dtp_panels() { + // Defer the blurring to the next idle cycle. + // This is crucial to ensure the panel actors have been allocated their + // final size and position by the compositor, avoiding race conditions + // during extension startup. + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + if (!global.dashToPanel?.panels) { + return GLib.SOURCE_REMOVE; + } + + this._log("Blurring Dash to Panel panels after idle."); + + // blur every panel, except Main.panel (this is handled in blur_existing_panels() method above) + global.dashToPanel.panels.forEach(p => { + if (p.panel != Main.panel) + this.maybe_blur_panel(p.panel); + }); + + return GLib.SOURCE_REMOVE; + }); + }; + + /// 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 [width, height] = panel_box.get_size(); + + // if static blur, need to clip the background + if (actors.static_blur) { + let monitor = Main.layoutManager.findMonitorForActor(panel); + if (!monitor) + return; + + // 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 + (width - panel.width) / 2; + let y = p_y + p_p_y - monitor.y + (height - panel.height) / 2; + + background.set_clip(x, y, panel.width, panel.height); + background.x = (width - panel.width) / 2 - x; + background.y = .5 + (height - panel.height) / 2 - y; + } else { + background.x = panel.x; + background.y = panel.y; + background.width = panel.width; + background.height = panel.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 (!isMainPanelAlive) { + this._log("cannot update light text classname, Main.panel is not alive"); + return; + } + + 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 ( + isMainPanelAlive && 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; + let window_vertical_bottom = window_vertical_pos + meta_window.get_frame_rect().height; + + // if so, and if in the same monitor, then it overlaps + if (same_monitor + && + // check if panel is on top + ((panel_top === 0 && window_vertical_pos < panel_bottom + 5 * scale) || + // check if panel is at the bottom + (panel_top > 0 && window_vertical_bottom > panel_top - 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 the classname if the panel to have or have not light text + this.update_light_text_classname(!should_override); + } + + 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() { + if (!this.enabled) { + this._log("blur already removed"); + return; + } + + 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}`); + } +}; |