panel.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. import St from 'gi://St';
  2. import Shell from 'gi://Shell';
  3. import Meta from 'gi://Meta';
  4. import Mtk from 'gi://Mtk';
  5. import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  6. import { PaintSignals } from '../effects/paint_signals.js';
  7. const DASH_TO_PANEL_UUID = 'dash-to-panel@jderose9.github.com';
  8. const PANEL_STYLES = [
  9. "transparent-panel",
  10. "light-panel",
  11. "dark-panel",
  12. "contrasted-panel"
  13. ];
  14. export const PanelBlur = class PanelBlur {
  15. constructor(connections, settings, effects_manager) {
  16. this.connections = connections;
  17. this.window_signal_ids = new Map();
  18. this.settings = settings;
  19. this.effects_manager = effects_manager;
  20. this.actors_list = [];
  21. this.enabled = false;
  22. }
  23. enable() {
  24. this._log("blurring top panel");
  25. // check for panels when Dash to Panel is activated
  26. this.connections.connect(
  27. Main.extensionManager,
  28. 'extension-state-changed',
  29. (_, extension) => {
  30. if (extension.uuid === DASH_TO_PANEL_UUID
  31. && extension.state === 1
  32. ) {
  33. this.connections.connect(
  34. global.dashToPanel,
  35. 'panels-created',
  36. _ => this.blur_dtp_panels()
  37. );
  38. this.blur_existing_panels();
  39. }
  40. }
  41. );
  42. this.blur_existing_panels();
  43. // connect to overview being opened/closed, and dynamically show or not
  44. // the blur when a window is near a panel
  45. this.connect_to_windows_and_overview();
  46. // update the classname if the panel to have or have not light text
  47. this.update_light_text_classname();
  48. // connect to every background change (even without changing image)
  49. // FIXME this signal is fired very often, so we should find another one
  50. // fired only when necessary (but that still catches all cases)
  51. this.connections.connect(
  52. Main.layoutManager._backgroundGroup,
  53. 'notify',
  54. _ => this.actors_list.forEach(actors =>
  55. this.update_wallpaper(actors)
  56. )
  57. );
  58. // connect to monitors change
  59. this.connections.connect(
  60. Main.layoutManager,
  61. 'monitors-changed',
  62. _ => {
  63. if (Main.screenShield && !Main.screenShield.locked) {
  64. this.reset();
  65. }
  66. }
  67. );
  68. this.enabled = true;
  69. }
  70. reset() {
  71. this._log("resetting...");
  72. this.disable();
  73. setTimeout(_ => this.enable(), 1);
  74. }
  75. /// Check for already existing panels and blur them if they are not already
  76. blur_existing_panels() {
  77. // check if dash-to-panel is present
  78. if (global.dashToPanel) {
  79. // blur already existing ones
  80. if (global.dashToPanel.panels)
  81. this.blur_dtp_panels();
  82. } else {
  83. // if no dash-to-panel, blur the main and only panel
  84. this.maybe_blur_panel(Main.panel);
  85. }
  86. }
  87. blur_dtp_panels() {
  88. // FIXME when Dash to Panel changes its size, it seems it creates new
  89. // panels; but I can't get to delete old widgets
  90. // blur every panel found
  91. global.dashToPanel.panels.forEach(p => {
  92. this.maybe_blur_panel(p.panel);
  93. });
  94. // if main panel is not included in the previous panels, blur it
  95. if (
  96. !global.dashToPanel.panels
  97. .map(p => p.panel)
  98. .includes(Main.panel)
  99. &&
  100. this.settings.dash_to_panel.BLUR_ORIGINAL_PANEL
  101. )
  102. this.maybe_blur_panel(Main.panel);
  103. };
  104. /// Blur a panel only if it is not already blurred (contained in the list)
  105. maybe_blur_panel(panel) {
  106. // check if the panel is contained in the list
  107. let actors = this.actors_list.find(
  108. actors => actors.widgets.panel == panel
  109. );
  110. if (!actors)
  111. // if the actors is not blurred, blur it
  112. this.blur_panel(panel);
  113. else
  114. // if it is blurred, update the blur anyway
  115. this.change_blur_type(actors);
  116. }
  117. /// Blur a panel
  118. blur_panel(panel) {
  119. let panel_box = panel.get_parent();
  120. let is_dtp_panel = false;
  121. if (!panel_box.name) {
  122. is_dtp_panel = true;
  123. panel_box = panel_box.get_parent();
  124. }
  125. let monitor = this.find_monitor_for(panel);
  126. if (!monitor)
  127. return;
  128. let background_parent = new St.Widget({
  129. name: 'topbar-blurred-background-parent',
  130. x: 0, y: 0, width: 0, height: 0
  131. });
  132. let background = this.settings.panel.STATIC_BLUR
  133. ? new Meta.BackgroundActor({
  134. meta_display: global.display,
  135. monitor: monitor.index
  136. })
  137. : new St.Widget;
  138. background_parent.add_child(background);
  139. // insert background parent
  140. panel_box.insert_child_at_index(background_parent, 0);
  141. let blur = new Shell.BlurEffect({
  142. brightness: this.settings.panel.CUSTOMIZE
  143. ? this.settings.panel.BRIGHTNESS
  144. : this.settings.BRIGHTNESS,
  145. radius: (this.settings.panel.CUSTOMIZE
  146. ? this.settings.panel.SIGMA
  147. : this.settings.SIGMA) * 2 * monitor.geometry_scale,
  148. mode: this.settings.panel.STATIC_BLUR
  149. ? Shell.BlurMode.ACTOR
  150. : Shell.BlurMode.BACKGROUND
  151. });
  152. // store the scale in the effect in order to retrieve it in set_sigma
  153. blur.scale = monitor.geometry_scale;
  154. let color = this.effects_manager.new_color_effect({
  155. color: this.settings.panel.CUSTOMIZE
  156. ? this.settings.panel.COLOR
  157. : this.settings.COLOR
  158. }, this.settings);
  159. let noise = this.effects_manager.new_noise_effect({
  160. noise: this.settings.panel.CUSTOMIZE
  161. ? this.settings.panel.NOISE_AMOUNT
  162. : this.settings.NOISE_AMOUNT,
  163. lightness: this.settings.panel.CUSTOMIZE
  164. ? this.settings.panel.NOISE_LIGHTNESS
  165. : this.settings.NOISE_LIGHTNESS
  166. }, this.settings);
  167. let paint_signals = new PaintSignals(this.connections);
  168. let actors = {
  169. widgets: { panel, panel_box, background, background_parent },
  170. effects: { blur, color, noise },
  171. paint_signals,
  172. monitor,
  173. is_dtp_panel
  174. };
  175. this.actors_list.push(actors);
  176. // perform updates
  177. this.change_blur_type(actors);
  178. // connect to panel, panel_box and its parent position or size change
  179. // this should fire update_size every time one of its params change
  180. this.connections.connect(
  181. panel,
  182. 'notify::position',
  183. _ => this.update_size(actors)
  184. );
  185. this.connections.connect(
  186. panel_box,
  187. ['notify::size', 'notify::position'],
  188. _ => this.update_size(actors)
  189. );
  190. this.connections.connect(
  191. panel_box.get_parent(),
  192. 'notify::position',
  193. _ => this.update_size(actors)
  194. );
  195. }
  196. update_all_blur_type() {
  197. this.actors_list.forEach(actors => this.change_blur_type(actors));
  198. }
  199. change_blur_type(actors) {
  200. let is_static = this.settings.panel.STATIC_BLUR;
  201. // reset widgets to right state
  202. actors.widgets.background_parent.remove_child(actors.widgets.background);
  203. this.effects_manager.remove(actors.effects.blur);
  204. this.effects_manager.remove(actors.effects.color);
  205. this.effects_manager.remove(actors.effects.noise);
  206. // create new background actor
  207. actors.widgets.background = is_static
  208. ? new Meta.BackgroundActor({
  209. meta_display: global.display,
  210. monitor: this.find_monitor_for(actors.widgets.panel).index
  211. })
  212. : new St.Widget;
  213. // change blur mode
  214. actors.effects.blur.set_mode(is_static ? 0 : 1);
  215. // disable other effects if the blur is dynamic, as they makes it opaque
  216. actors.effects.color._static = is_static;
  217. actors.effects.noise._static = is_static;
  218. actors.effects.color.update_enabled();
  219. actors.effects.noise.update_enabled();
  220. // add the effects in order
  221. actors.widgets.background.add_effect(actors.effects.color);
  222. actors.widgets.background.add_effect(actors.effects.noise);
  223. actors.widgets.background.add_effect(actors.effects.blur);
  224. // add the background actor behing the panel
  225. actors.widgets.background_parent.add_child(actors.widgets.background);
  226. // perform updates
  227. this.update_wallpaper(actors);
  228. this.update_size(actors);
  229. // HACK
  230. //
  231. //`Shell.BlurEffect` does not repaint when shadows are under it. [1]
  232. //
  233. // This does not entirely fix this bug (shadows caused by windows
  234. // still cause artifacts), but it prevents the shadows of the panel
  235. // buttons to cause artifacts on the panel itself
  236. //
  237. // [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
  238. if (!is_static) {
  239. if (this.settings.HACKS_LEVEL === 1) {
  240. this._log("panel hack level 1");
  241. actors.paint_signals.disconnect_all();
  242. let rp = () => { actors.effects.blur.queue_repaint(); };
  243. this.connections.connect(actors.widgets.panel, [
  244. 'enter-event', 'leave-event', 'button-press-event'
  245. ], rp);
  246. actors.widgets.panel.get_children().forEach(child => {
  247. this.connections.connect(child, [
  248. 'enter-event', 'leave-event', 'button-press-event'
  249. ], rp);
  250. });
  251. } else if (this.settings.HACKS_LEVEL === 2) {
  252. this._log("panel hack level 2");
  253. actors.paint_signals.disconnect_all();
  254. actors.paint_signals.connect(
  255. actors.widgets.background, actors.effects.blur
  256. );
  257. } else {
  258. actors.paint_signals.disconnect_all();
  259. }
  260. }
  261. }
  262. update_wallpaper(actors) {
  263. // if static blur, get right wallpaper and update blur with it
  264. if (this.settings.panel.STATIC_BLUR) {
  265. let bg = Main.layoutManager._backgroundGroup.get_child_at_index(
  266. Main.layoutManager.monitors.length
  267. - this.find_monitor_for(actors.widgets.panel).index - 1
  268. );
  269. if (bg && bg.get_content())
  270. actors.widgets.background.content.set({
  271. background: bg.get_content().background
  272. });
  273. else
  274. this._warn("could not get background for panel");
  275. }
  276. }
  277. update_size(actors) {
  278. let panel = actors.widgets.panel;
  279. let panel_box = actors.widgets.panel_box;
  280. let background = actors.widgets.background;
  281. let monitor = this.find_monitor_for(panel);
  282. if (!monitor)
  283. return;
  284. let [width, height] = panel_box.get_size();
  285. background.width = width;
  286. background.height = height;
  287. // if static blur, need to clip the background
  288. if (this.settings.panel.STATIC_BLUR) {
  289. // an alternative to panel.get_transformed_position, because it
  290. // sometimes yields NaN (probably when the actor is not fully
  291. // positionned yet)
  292. let [p_x, p_y] = panel_box.get_position();
  293. let [p_p_x, p_p_y] = panel_box.get_parent().get_position();
  294. let x = p_x + p_p_x - monitor.x;
  295. let y = p_y + p_p_y - monitor.y;
  296. background.set_clip(x, y, width, height);
  297. background.x = -x;
  298. background.y = -y;
  299. // fixes a bug where the blur is washed away when changing the sigma
  300. this.invalidate_blur(actors);
  301. } else {
  302. background.x = panel.x;
  303. background.y = panel.y;
  304. }
  305. // update the monitor panel is on
  306. actors.monitor = this.find_monitor_for(panel);
  307. }
  308. /// An helper function to find the monitor in which an actor is situated,
  309. /// there might be a pre-existing function in GLib already
  310. find_monitor_for(actor) {
  311. let extents = actor.get_transformed_extents();
  312. let rect = new Mtk.Rectangle({
  313. x: extents.get_x(),
  314. y: extents.get_y(),
  315. width: extents.get_width(),
  316. height: extents.get_height(),
  317. });
  318. let index = global.display.get_monitor_index_for_rect(rect);
  319. return Main.layoutManager.monitors[index];
  320. }
  321. /// Connect when overview if opened/closed to hide/show the blur accordingly
  322. ///
  323. /// If HIDETOPBAR is set, we need just to hide the blur when showing appgrid
  324. /// (so no shadow is cropped)
  325. connect_to_overview() {
  326. // may be called when panel blur is disabled, if hidetopbar
  327. // compatibility is toggled on/off
  328. // if this is the case, do nothing as only the panel blur interfers with
  329. // hidetopbar
  330. if (
  331. this.settings.panel.BLUR &&
  332. this.settings.panel.UNBLUR_IN_OVERVIEW
  333. ) {
  334. if (!this.settings.hidetopbar.COMPATIBILITY) {
  335. this.connections.connect(
  336. Main.overview, 'showing', this.hide.bind(this)
  337. );
  338. this.connections.connect(
  339. Main.overview, 'hidden', this.show.bind(this)
  340. );
  341. } else {
  342. let appDisplay = Main.overview._overview._controls._appDisplay;
  343. this.connections.connect(
  344. appDisplay, 'show', this.hide.bind(this)
  345. );
  346. this.connections.connect(
  347. appDisplay, 'hide', this.show.bind(this)
  348. );
  349. this.connections.connect(
  350. Main.overview, 'hidden', this.show.bind(this)
  351. );
  352. }
  353. }
  354. }
  355. /// Connect to windows disable transparency when a window is too close
  356. connect_to_windows() {
  357. if (
  358. this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY
  359. ) {
  360. // connect to overview opening/closing
  361. this.connections.connect(Main.overview, ['showing', 'hiding'],
  362. this.update_visibility.bind(this)
  363. );
  364. // connect to session mode update
  365. this.connections.connect(Main.sessionMode, 'updated',
  366. this.update_visibility.bind(this)
  367. );
  368. // manage already-existing windows
  369. for (const meta_window_actor of global.get_window_actors()) {
  370. this.on_window_actor_added(
  371. meta_window_actor.get_parent(), meta_window_actor
  372. );
  373. }
  374. // manage windows at their creation/removal
  375. this.connections.connect(global.window_group, 'child-added',
  376. this.on_window_actor_added.bind(this)
  377. );
  378. this.connections.connect(global.window_group, 'child-removed',
  379. this.on_window_actor_removed.bind(this)
  380. );
  381. // connect to a workspace change
  382. this.connections.connect(global.window_manager, 'switch-workspace',
  383. this.update_visibility.bind(this)
  384. );
  385. // perform early update
  386. this.update_visibility();
  387. } else {
  388. // reset transparency for every panels
  389. this.actors_list.forEach(
  390. actors => this.set_should_override_panel(actors, true)
  391. );
  392. }
  393. }
  394. /// An helper to connect to both the windows and overview signals.
  395. /// This is the only function that should be directly called, to prevent
  396. /// inconsistencies with signals not being disconnected.
  397. connect_to_windows_and_overview() {
  398. this.disconnect_from_windows_and_overview();
  399. this.connect_to_overview();
  400. this.connect_to_windows();
  401. }
  402. /// Disconnect all the connections created by connect_to_windows
  403. disconnect_from_windows_and_overview() {
  404. // disconnect the connections to actors
  405. for (const actor of [
  406. Main.overview, Main.sessionMode,
  407. global.window_group, global.window_manager,
  408. Main.overview._overview._controls._appDisplay
  409. ]) {
  410. this.connections.disconnect_all_for(actor);
  411. }
  412. // disconnect the connections from windows
  413. for (const [actor, ids] of this.window_signal_ids) {
  414. for (const id of ids) {
  415. actor.disconnect(id);
  416. }
  417. }
  418. this.window_signal_ids = new Map();
  419. }
  420. /// Update the css classname of the panel for light theme
  421. update_light_text_classname(disable = false) {
  422. if (this.settings.panel.FORCE_LIGHT_TEXT && !disable)
  423. Main.panel.add_style_class_name("panel-light-text");
  424. else
  425. Main.panel.remove_style_class_name("panel-light-text");
  426. }
  427. /// Callback when a new window is added
  428. on_window_actor_added(container, meta_window_actor) {
  429. this.window_signal_ids.set(meta_window_actor, [
  430. meta_window_actor.connect('notify::allocation',
  431. _ => this.update_visibility()
  432. ),
  433. meta_window_actor.connect('notify::visible',
  434. _ => this.update_visibility()
  435. )
  436. ]);
  437. this.update_visibility();
  438. }
  439. /// Callback when a window is removed
  440. on_window_actor_removed(container, meta_window_actor) {
  441. for (const signalId of this.window_signal_ids.get(meta_window_actor)) {
  442. meta_window_actor.disconnect(signalId);
  443. }
  444. this.window_signal_ids.delete(meta_window_actor);
  445. this.update_visibility();
  446. }
  447. /// Update the visibility of the blur effect
  448. update_visibility() {
  449. if (
  450. Main.panel.has_style_pseudo_class('overview')
  451. || !Main.sessionMode.hasWindows
  452. ) {
  453. this.actors_list.forEach(
  454. actors => this.set_should_override_panel(actors, true)
  455. );
  456. return;
  457. }
  458. if (!Main.layoutManager.primaryMonitor)
  459. return;
  460. // get all the windows in the active workspace that are visible
  461. const workspace = global.workspace_manager.get_active_workspace();
  462. const windows = workspace.list_windows().filter(meta_window =>
  463. meta_window.showing_on_its_workspace()
  464. && !meta_window.is_hidden()
  465. && meta_window.get_window_type() !== Meta.WindowType.DESKTOP
  466. // exclude Desktop Icons NG
  467. && meta_window.get_gtk_application_id() !== "com.rastersoft.ding"
  468. && meta_window.get_gtk_application_id() !== "com.desktop.ding"
  469. );
  470. // check if at least one window is near enough to each panel and act
  471. // accordingly
  472. const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor;
  473. this.actors_list
  474. // do not apply for dtp panels, as it would only cause bugs and it
  475. // can be done from its preferences anyway
  476. .filter(actors => !actors.is_dtp_panel)
  477. .forEach(actors => {
  478. let panel = actors.widgets.panel;
  479. let panel_top = panel.get_transformed_position()[1];
  480. let panel_bottom = panel_top + panel.get_height();
  481. // check if at least a window is near enough the panel
  482. let window_overlap_panel = false;
  483. windows.forEach(meta_window => {
  484. let window_monitor_i = meta_window.get_monitor();
  485. let same_monitor = actors.monitor.index == window_monitor_i;
  486. let window_vertical_pos = meta_window.get_frame_rect().y;
  487. // if so, and if in the same monitor, then it overlaps
  488. if (same_monitor
  489. &&
  490. window_vertical_pos < panel_bottom + 5 * scale
  491. )
  492. window_overlap_panel = true;
  493. });
  494. // if no window overlaps, then the panel is transparent
  495. this.set_should_override_panel(
  496. actors, !window_overlap_panel
  497. );
  498. });
  499. }
  500. /// Choose wether or not the panel background should be overriden, in
  501. /// respect to its argument and the `override-background` setting.
  502. set_should_override_panel(actors, should_override) {
  503. let panel = actors.widgets.panel;
  504. PANEL_STYLES.forEach(style => panel.remove_style_class_name(style));
  505. if (
  506. this.settings.panel.OVERRIDE_BACKGROUND
  507. &&
  508. should_override
  509. )
  510. panel.add_style_class_name(
  511. PANEL_STYLES[this.settings.panel.STYLE_PANEL]
  512. );
  513. }
  514. /// Fixes a bug where the blur is washed away when changing the sigma, or
  515. /// enabling/disabling other effects.
  516. invalidate_blur(actors) {
  517. if (this.settings.panel.STATIC_BLUR && actors.widgets.background)
  518. actors.widgets.background.get_content()?.invalidate();
  519. }
  520. invalidate_all_blur() {
  521. this.actors_list.forEach(actors => this.invalidate_blur(actors));
  522. }
  523. set_sigma(s) {
  524. this.actors_list.forEach(actors => {
  525. actors.effects.blur.radius = s * 2 * actors.effects.blur.scale;
  526. this.invalidate_blur(actors);
  527. });
  528. }
  529. set_brightness(b) {
  530. this.actors_list.forEach(actors => {
  531. actors.effects.blur.brightness = b;
  532. });
  533. }
  534. set_color(c) {
  535. this.actors_list.forEach(actors => {
  536. actors.effects.color.color = c;
  537. });
  538. }
  539. set_noise_amount(n) {
  540. this.actors_list.forEach(actors => {
  541. actors.effects.noise.noise = n;
  542. });
  543. }
  544. set_noise_lightness(l) {
  545. this.actors_list.forEach(actors => {
  546. actors.effects.noise.lightness = l;
  547. });
  548. }
  549. show() {
  550. this.actors_list.forEach(actors => {
  551. actors.widgets.background_parent.show();
  552. });
  553. }
  554. hide() {
  555. this.actors_list.forEach(actors => {
  556. actors.widgets.background_parent.hide();
  557. });
  558. }
  559. // destroy every blurred background left, necessary after sleep
  560. destroy_blur_effects() {
  561. Main.panel?.get_parent()?.get_children().forEach(
  562. child => {
  563. if (child.name === 'topbar-blurred-background-parent') {
  564. child.get_children().forEach(meta_background_actor => {
  565. meta_background_actor.get_effects().forEach(effect => {
  566. this.effects_manager.remove(effect);
  567. });
  568. });
  569. child.destroy_all_children();
  570. child.destroy();
  571. }
  572. }
  573. );
  574. }
  575. disable() {
  576. this._log("removing blur from top panel");
  577. this.disconnect_from_windows_and_overview();
  578. this.update_light_text_classname(true);
  579. this.actors_list.forEach(actors => {
  580. this.set_should_override_panel(actors, false);
  581. this.effects_manager.remove(actors.effects.noise);
  582. this.effects_manager.remove(actors.effects.color);
  583. this.effects_manager.remove(actors.effects.blur);
  584. try {
  585. actors.widgets.panel_box.remove_child(
  586. actors.widgets.background_parent
  587. );
  588. } catch (e) { }
  589. actors.widgets.background_parent?.destroy();
  590. });
  591. this.destroy_blur_effects();
  592. this.actors_list = [];
  593. this.connections.disconnect_all();
  594. this.enabled = false;
  595. }
  596. _log(str) {
  597. if (this.settings.DEBUG)
  598. console.log(`[Blur my Shell > panel] ${str}`);
  599. }
  600. _warn(str) {
  601. console.warn(`[Blur my Shell > panel] ${str}`);
  602. }
  603. };