panel.js 23 KB

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