panel.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. import St from 'gi://St';
  2. import Meta from 'gi://Meta';
  3. import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  4. import { PaintSignals } from '../conveniences/paint_signals.js';
  5. import { Pipeline } from '../conveniences/pipeline.js';
  6. import { DummyPipeline } from '../conveniences/dummy_pipeline.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 workareas change
  49. this.connections.connect(global.display, 'workareas-changed',
  50. _ => this.reset()
  51. );
  52. this.enabled = true;
  53. }
  54. reset() {
  55. this._log("resetting...");
  56. this.disable();
  57. setTimeout(_ => this.enable(), 1);
  58. }
  59. /// Check for already existing panels and blur them if they are not already
  60. blur_existing_panels() {
  61. // check if dash-to-panel is present
  62. if (global.dashToPanel) {
  63. // blur already existing ones
  64. if (global.dashToPanel.panels)
  65. this.blur_dtp_panels();
  66. } else {
  67. // if no dash-to-panel, blur the main and only panel
  68. this.maybe_blur_panel(Main.panel);
  69. }
  70. }
  71. blur_dtp_panels() {
  72. // FIXME when Dash to Panel changes its size, it seems it creates new
  73. // panels; but I can't get to delete old widgets
  74. // blur every panel found
  75. global.dashToPanel.panels.forEach(p => {
  76. this.maybe_blur_panel(p.panel);
  77. });
  78. // if main panel is not included in the previous panels, blur it
  79. if (
  80. !global.dashToPanel.panels
  81. .map(p => p.panel)
  82. .includes(Main.panel)
  83. &&
  84. this.settings.dash_to_panel.BLUR_ORIGINAL_PANEL
  85. )
  86. this.maybe_blur_panel(Main.panel);
  87. };
  88. /// Blur a panel only if it is not already blurred (contained in the list)
  89. maybe_blur_panel(panel) {
  90. // check if the panel is contained in the list
  91. let actors = this.actors_list.find(
  92. actors => actors.widgets.panel == panel
  93. );
  94. if (!actors)
  95. // if the actors is not blurred, blur it
  96. this.blur_panel(panel);
  97. }
  98. /// Blur a panel
  99. blur_panel(panel) {
  100. let panel_box = panel.get_parent();
  101. let is_dtp_panel = false;
  102. if (!panel_box.name) {
  103. is_dtp_panel = true;
  104. panel_box = panel_box.get_parent();
  105. }
  106. let monitor = Main.layoutManager.findMonitorForActor(panel);
  107. if (!monitor)
  108. return;
  109. let background_group = new Meta.BackgroundGroup(
  110. { name: 'bms-panel-backgroundgroup', width: 0, height: 0 }
  111. );
  112. let background, bg_manager;
  113. let static_blur = this.settings.panel.STATIC_BLUR;
  114. if (static_blur) {
  115. let bg_manager_list = [];
  116. const pipeline = new Pipeline(
  117. this.effects_manager,
  118. global.blur_my_shell._pipelines_manager,
  119. this.settings.panel.PIPELINE
  120. );
  121. background = pipeline.create_background_with_effects(
  122. monitor.index, bg_manager_list,
  123. background_group, 'bms-panel-blurred-widget'
  124. );
  125. bg_manager = bg_manager_list[0];
  126. }
  127. else {
  128. const pipeline = new DummyPipeline(this.effects_manager, this.settings.panel);
  129. [background, bg_manager] = pipeline.create_background_with_effect(
  130. background_group, 'bms-panel-blurred-widget'
  131. );
  132. let paint_signals = new PaintSignals(this.connections);
  133. // HACK
  134. //
  135. //`Shell.BlurEffect` does not repaint when shadows are under it. [1]
  136. //
  137. // This does not entirely fix this bug (shadows caused by windows
  138. // still cause artifacts), but it prevents the shadows of the panel
  139. // buttons to cause artifacts on the panel itself
  140. //
  141. // [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
  142. {
  143. if (this.settings.HACKS_LEVEL === 1) {
  144. this._log("panel hack level 1");
  145. paint_signals.disconnect_all();
  146. paint_signals.connect(background, pipeline.effect);
  147. } else {
  148. paint_signals.disconnect_all();
  149. }
  150. }
  151. }
  152. // insert the background group to the panel box
  153. panel_box.insert_child_at_index(background_group, 0);
  154. // the object that is used to remembering each elements that is linked to the blur effect
  155. let actors = {
  156. widgets: { panel, panel_box, background, background_group },
  157. static_blur,
  158. monitor,
  159. bg_manager,
  160. is_dtp_panel
  161. };
  162. this.actors_list.push(actors);
  163. // update the size of the actor
  164. this.update_size(actors);
  165. // connect to panel, panel_box and its parent position or size change
  166. // this should fire update_size every time one of its params change
  167. this.connections.connect(
  168. panel,
  169. 'notify::position',
  170. _ => this.update_size(actors)
  171. );
  172. this.connections.connect(
  173. panel_box,
  174. ['notify::size', 'notify::position'],
  175. _ => this.update_size(actors)
  176. );
  177. this.connections.connect(
  178. panel_box.get_parent(),
  179. 'notify::position',
  180. _ => this.update_size(actors)
  181. );
  182. // connect to the panel getting destroyed
  183. this.connections.connect(
  184. panel,
  185. 'destroy',
  186. _ => this.destroy_blur(actors, true)
  187. );
  188. }
  189. update_size(actors) {
  190. let panel = actors.widgets.panel;
  191. let panel_box = actors.widgets.panel_box;
  192. let background = actors.widgets.background;
  193. let [width, height] = panel_box.get_size();
  194. // if static blur, need to clip the background
  195. if (actors.static_blur) {
  196. let monitor = Main.layoutManager.findMonitorForActor(panel);
  197. if (!monitor)
  198. return;
  199. // an alternative to panel.get_transformed_position, because it
  200. // sometimes yields NaN (probably when the actor is not fully
  201. // positionned yet)
  202. let [p_x, p_y] = panel_box.get_position();
  203. let [p_p_x, p_p_y] = panel_box.get_parent().get_position();
  204. let x = p_x + p_p_x - monitor.x + (width - panel.width) / 2;
  205. let y = p_y + p_p_y - monitor.y + (height - panel.height) / 2;
  206. background.set_clip(x, y, panel.width, panel.height);
  207. background.x = (width - panel.width) / 2 - x;
  208. background.y = .5 + (height - panel.height) / 2 - y;
  209. } else {
  210. background.x = panel.x;
  211. background.y = panel.y;
  212. background.width = panel.width;
  213. background.height = panel.height;
  214. }
  215. // update the monitor panel is on
  216. actors.monitor = Main.layoutManager.findMonitorForActor(panel);
  217. }
  218. /// Connect when overview if opened/closed to hide/show the blur accordingly
  219. ///
  220. /// If HIDETOPBAR is set, we need just to hide the blur when showing appgrid
  221. /// (so no shadow is cropped)
  222. connect_to_overview() {
  223. // may be called when panel blur is disabled, if hidetopbar
  224. // compatibility is toggled on/off
  225. // if this is the case, do nothing as only the panel blur interfers with
  226. // hidetopbar
  227. if (
  228. this.settings.panel.BLUR &&
  229. this.settings.panel.UNBLUR_IN_OVERVIEW
  230. ) {
  231. if (!this.settings.hidetopbar.COMPATIBILITY) {
  232. this.connections.connect(
  233. Main.overview, 'showing', _ => this.hide()
  234. );
  235. this.connections.connect(
  236. Main.overview, 'hidden', _ => this.show()
  237. );
  238. } else {
  239. let appDisplay = Main.overview._overview._controls._appDisplay;
  240. this.connections.connect(
  241. appDisplay, 'show', _ => this.hide()
  242. );
  243. this.connections.connect(
  244. appDisplay, 'hide', _ => this.show()
  245. );
  246. this.connections.connect(
  247. Main.overview, 'hidden', _ => this.show()
  248. );
  249. }
  250. }
  251. }
  252. /// Connect to windows disable transparency when a window is too close
  253. connect_to_windows() {
  254. if (
  255. this.settings.panel.OVERRIDE_BACKGROUND_DYNAMICALLY
  256. ) {
  257. // connect to overview opening/closing
  258. this.connections.connect(Main.overview, ['showing', 'hiding'],
  259. _ => this.update_visibility()
  260. );
  261. // connect to session mode update
  262. this.connections.connect(Main.sessionMode, 'updated',
  263. _ => this.update_visibility()
  264. );
  265. // manage already-existing windows
  266. for (const meta_window_actor of global.get_window_actors()) {
  267. this.on_window_actor_added(
  268. meta_window_actor.get_parent(), meta_window_actor
  269. );
  270. }
  271. // manage windows at their creation/removal
  272. this.connections.connect(global.window_group, 'child-added',
  273. this.on_window_actor_added.bind(this)
  274. );
  275. this.connections.connect(global.window_group, 'child-removed',
  276. this.on_window_actor_removed.bind(this)
  277. );
  278. // connect to a workspace change
  279. this.connections.connect(global.window_manager, 'switch-workspace',
  280. _ => this.update_visibility()
  281. );
  282. // perform early update
  283. this.update_visibility();
  284. } else {
  285. // reset transparency for every panels
  286. this.actors_list.forEach(
  287. actors => this.set_should_override_panel(actors, true)
  288. );
  289. }
  290. }
  291. /// An helper to connect to both the windows and overview signals.
  292. /// This is the only function that should be directly called, to prevent
  293. /// inconsistencies with signals not being disconnected.
  294. connect_to_windows_and_overview() {
  295. this.disconnect_from_windows_and_overview();
  296. this.connect_to_overview();
  297. this.connect_to_windows();
  298. }
  299. /// Disconnect all the connections created by connect_to_windows
  300. disconnect_from_windows_and_overview() {
  301. // disconnect the connections to actors
  302. for (const actor of [
  303. Main.overview, Main.sessionMode,
  304. global.window_group, global.window_manager,
  305. Main.overview._overview._controls._appDisplay
  306. ]) {
  307. this.connections.disconnect_all_for(actor);
  308. }
  309. // disconnect the connections from windows
  310. for (const [actor, ids] of this.window_signal_ids) {
  311. for (const id of ids) {
  312. actor.disconnect(id);
  313. }
  314. }
  315. this.window_signal_ids = new Map();
  316. }
  317. /// Update the css classname of the panel for light theme
  318. update_light_text_classname(disable = false) {
  319. if (this.settings.panel.FORCE_LIGHT_TEXT && !disable)
  320. Main.panel.add_style_class_name("panel-light-text");
  321. else
  322. Main.panel.remove_style_class_name("panel-light-text");
  323. }
  324. /// Callback when a new window is added
  325. on_window_actor_added(container, meta_window_actor) {
  326. this.window_signal_ids.set(meta_window_actor, [
  327. meta_window_actor.connect('notify::allocation',
  328. _ => this.update_visibility()
  329. ),
  330. meta_window_actor.connect('notify::visible',
  331. _ => this.update_visibility()
  332. )
  333. ]);
  334. this.update_visibility();
  335. }
  336. /// Callback when a window is removed
  337. on_window_actor_removed(container, meta_window_actor) {
  338. for (const signalId of this.window_signal_ids.get(meta_window_actor)) {
  339. meta_window_actor.disconnect(signalId);
  340. }
  341. this.window_signal_ids.delete(meta_window_actor);
  342. this.update_visibility();
  343. }
  344. /// Update the visibility of the blur effect
  345. update_visibility() {
  346. if (
  347. Main.panel.has_style_pseudo_class('overview')
  348. || !Main.sessionMode.hasWindows
  349. ) {
  350. this.actors_list.forEach(
  351. actors => this.set_should_override_panel(actors, true)
  352. );
  353. return;
  354. }
  355. if (!Main.layoutManager.primaryMonitor)
  356. return;
  357. // get all the windows in the active workspace that are visible
  358. const workspace = global.workspace_manager.get_active_workspace();
  359. const windows = workspace.list_windows().filter(meta_window =>
  360. meta_window.showing_on_its_workspace()
  361. && !meta_window.is_hidden()
  362. && meta_window.get_window_type() !== Meta.WindowType.DESKTOP
  363. // exclude Desktop Icons NG
  364. && meta_window.get_gtk_application_id() !== "com.rastersoft.ding"
  365. && meta_window.get_gtk_application_id() !== "com.desktop.ding"
  366. );
  367. // check if at least one window is near enough to each panel and act
  368. // accordingly
  369. const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor;
  370. this.actors_list
  371. // do not apply for dtp panels, as it would only cause bugs and it
  372. // can be done from its preferences anyway
  373. .filter(actors => !actors.is_dtp_panel)
  374. .forEach(actors => {
  375. let panel = actors.widgets.panel;
  376. let panel_top = panel.get_transformed_position()[1];
  377. let panel_bottom = panel_top + panel.get_height();
  378. // check if at least a window is near enough the panel
  379. let window_overlap_panel = false;
  380. windows.forEach(meta_window => {
  381. let window_monitor_i = meta_window.get_monitor();
  382. let same_monitor = actors.monitor.index == window_monitor_i;
  383. let window_vertical_pos = meta_window.get_frame_rect().y;
  384. // if so, and if in the same monitor, then it overlaps
  385. if (same_monitor
  386. &&
  387. window_vertical_pos < panel_bottom + 5 * scale
  388. )
  389. window_overlap_panel = true;
  390. });
  391. // if no window overlaps, then the panel is transparent
  392. this.set_should_override_panel(
  393. actors, !window_overlap_panel
  394. );
  395. });
  396. }
  397. /// Choose wether or not the panel background should be overriden, in
  398. /// respect to its argument and the `override-background` setting.
  399. set_should_override_panel(actors, should_override) {
  400. let panel = actors.widgets.panel;
  401. PANEL_STYLES.forEach(style => panel.remove_style_class_name(style));
  402. if (
  403. this.settings.panel.OVERRIDE_BACKGROUND
  404. &&
  405. should_override
  406. )
  407. panel.add_style_class_name(
  408. PANEL_STYLES[this.settings.panel.STYLE_PANEL]
  409. );
  410. }
  411. update_pipeline() {
  412. this.actors_list.forEach(actors =>
  413. actors.bg_manager._bms_pipeline.change_pipeline_to(
  414. this.settings.panel.PIPELINE
  415. )
  416. );
  417. }
  418. show() {
  419. this.actors_list.forEach(actors => {
  420. actors.widgets.background.show();
  421. });
  422. }
  423. hide() {
  424. this.actors_list.forEach(actors => {
  425. actors.widgets.background.hide();
  426. });
  427. }
  428. // IMPORTANT: do never call this in a mutable `this.actors_list.forEach`
  429. destroy_blur(actors, panel_already_destroyed) {
  430. this.set_should_override_panel(actors, false);
  431. actors.bg_manager._bms_pipeline.destroy();
  432. if (panel_already_destroyed)
  433. actors.bg_manager.backgroundActor = null;
  434. actors.bg_manager.destroy();
  435. if (!panel_already_destroyed) {
  436. actors.widgets.panel_box.remove_child(actors.widgets.background_group);
  437. actors.widgets.background_group.destroy_all_children();
  438. actors.widgets.background_group.destroy();
  439. }
  440. let index = this.actors_list.indexOf(actors);
  441. if (index >= 0)
  442. this.actors_list.splice(index, 1);
  443. }
  444. disable() {
  445. this._log("removing blur from top panel");
  446. this.disconnect_from_windows_and_overview();
  447. this.update_light_text_classname(true);
  448. const immutable_actors_list = [...this.actors_list];
  449. immutable_actors_list.forEach(actors => this.destroy_blur(actors, false));
  450. this.actors_list = [];
  451. this.connections.disconnect_all();
  452. this.enabled = false;
  453. }
  454. _log(str) {
  455. if (this.settings.DEBUG)
  456. console.log(`[Blur my Shell > panel] ${str}`);
  457. }
  458. _warn(str) {
  459. console.warn(`[Blur my Shell > panel] ${str}`);
  460. }
  461. };