panel.js 18 KB

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