dash_to_dock.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. const Signals = imports.signals;
  7. import { PaintSignals } from '../effects/paint_signals.js';
  8. import { BlurEffect } from '../effects/blur_effect.js';
  9. const DASH_STYLES = [
  10. "transparent-dash",
  11. "light-dash",
  12. "dark-dash"
  13. ];
  14. // An helper function to find the monitor in which an actor is situated,
  15. /// there might be a pre-existing function in GLib already
  16. function find_monitor_for(actor) {
  17. let extents = actor.get_transformed_extents();
  18. let rect = new Mtk.Rectangle({
  19. x: extents.get_x(),
  20. y: extents.get_y(),
  21. width: extents.get_width(),
  22. height: extents.get_height(),
  23. });
  24. let index = global.display.get_monitor_index_for_rect(rect);
  25. return Main.layoutManager.monitors[index];
  26. }
  27. /// This type of object is created for every dash found, and talks to the main
  28. /// DashBlur thanks to signals.
  29. ///
  30. /// This allows to dynamically track the created dashes for each screen.
  31. class DashInfos {
  32. constructor(dash_blur, dash, dash_container, dash_background, background, background_parent, effect) {
  33. // the parent DashBlur object, to communicate
  34. this.dash_blur = dash_blur;
  35. this.dash_container = dash_container;
  36. // the blurred dash
  37. this.dash = dash;
  38. this.dash_background = dash_background;
  39. this.background_parent = background_parent;
  40. this.background = background;
  41. this.effect = effect;
  42. this.settings = dash_blur.settings;
  43. this.old_style = this.dash._background.style;
  44. dash_blur.connections.connect(dash_blur, 'remove-dashes', () => {
  45. this._log("removing blur from dash");
  46. this.dash.get_parent().remove_child(this.background_parent);
  47. this.remove_style();
  48. });
  49. dash_blur.connections.connect(dash_blur, 'update-sigma', () => {
  50. if (this.dash_blur.is_static) {
  51. this.dash_blur.update_size();
  52. }
  53. this.effect.radius = 2 * this.dash_blur.sigma * this.effect.scale;
  54. });
  55. dash_blur.connections.connect(dash_blur, 'update-brightness', () => {
  56. this.effect.brightness = this.dash_blur.brightness;
  57. });
  58. dash_blur.connections.connect(dash_blur, 'update-corner-radius', () => {
  59. if (this.dash_blur.is_static) {
  60. let monitor = find_monitor_for(this.dash);
  61. let corner_radius = this.dash_blur.corner_radius * monitor.geometry_scale;
  62. this.effect.corner_radius = Math.min(
  63. corner_radius, this.effect.width / 2, this.effect.height / 2
  64. );
  65. }
  66. });
  67. dash_blur.connections.connect(dash_blur, 'override-background', () => {
  68. this.remove_style();
  69. this.dash.set_style_class_name(
  70. DASH_STYLES[this.settings.dash_to_dock.STYLE_DASH_TO_DOCK]
  71. );
  72. });
  73. dash_blur.connections.connect(dash_blur, 'reset-background', () => this.remove_style());
  74. dash_blur.connections.connect(dash_blur, 'show', () => {
  75. if (this.dash_blur.is_static)
  76. this.background_parent.show();
  77. else
  78. this.effect.radius = this.dash_blur.sigma * 2 * this.effect.scale;
  79. });
  80. dash_blur.connections.connect(dash_blur, 'hide', () => {
  81. if (this.dash_blur.is_static)
  82. this.background_parent.hide();
  83. else
  84. this.effect.radius = 0;
  85. });
  86. dash_blur.connections.connect(dash_blur, 'update-wallpaper', () => {
  87. if (this.dash_blur.is_static) {
  88. let bg = Main.layoutManager._backgroundGroup.get_child_at_index(
  89. Main.layoutManager.monitors.length
  90. - find_monitor_for(this.dash).index - 1
  91. );
  92. if (bg && bg.get_content()) {
  93. this.background.content.set({
  94. background: bg.get_content().background
  95. });
  96. this._log('wallpaper updated');
  97. } else {
  98. this._warn("could not get background for dash-to-dock");
  99. }
  100. }
  101. });
  102. dash_blur.connections.connect(dash_blur, 'update-size', () => {
  103. if (this.dash_blur.is_static) {
  104. let [x, y] = this.get_dash_position(this.dash_container, this.dash_background);
  105. this.background.x = -x;
  106. this.background.y = -y;
  107. this.effect.width = this.dash_background.width;
  108. this.effect.height = this.dash_background.height;
  109. this.dash_blur.set_corner_radius(this.dash_blur.corner_radius);
  110. if (dash_container.get_style_class_name().includes("top")) {
  111. this.background.set_clip(x, y + this.dash.y + this.dash_background.y, this.dash_background.width, this.dash_background.height);
  112. } else if (dash_container.get_style_class_name().includes("bottom")) {
  113. this.background.set_clip(x, y + this.dash.y + this.dash_background.y, this.dash_background.width, this.dash_background.height);
  114. } else if (dash_container.get_style_class_name().includes("left")) {
  115. this.background.set_clip(x + this.dash.x + this.dash_background.x, y + this.dash.y + this.dash_background.y, this.dash_background.width, this.dash_background.height);
  116. } else if (dash_container.get_style_class_name().includes("right")) {
  117. this.background.set_clip(x + this.dash.x + this.dash_background.x, y + this.dash.y + this.dash_background.y, this.dash_background.width, this.dash_background.height);
  118. }
  119. } else {
  120. this.background.width = this.dash_background.width;
  121. this.background.height = this.dash_background.height;
  122. this.background.x = this.dash_background.x;
  123. this.background.y = this.dash_background.y + this.dash.y;
  124. }
  125. });
  126. dash_blur.connections.connect(dash_blur, 'change-blur-type', () => {
  127. this.background_parent.remove_child(this.background);
  128. if (this.effect.chained_effect)
  129. this.effect.get_actor()?.remove_effect(this.effect.chained_effect);
  130. this.effect.get_actor()?.remove_effect(this.effect);
  131. let [background, effect] = this.dash_blur.add_blur(this.dash, this.dash_background, this.dash_container);
  132. this.background = background;
  133. this.effect = effect;
  134. this.background_parent.add_child(this.background);
  135. });
  136. }
  137. remove_style() {
  138. this.dash._background.style = this.old_style;
  139. DASH_STYLES.forEach(
  140. style => this.dash.remove_style_class_name(style)
  141. );
  142. }
  143. get_dash_position(dash_container, dash_background) {
  144. var x, y;
  145. let monitor = find_monitor_for(dash_container);
  146. let dash_box = dash_container._slider.get_child();
  147. if (dash_container.get_style_class_name().includes("top")) {
  148. x = (monitor.width - dash_background.width) / 2;
  149. y = dash_box.y;
  150. } else if (dash_container.get_style_class_name().includes("bottom")) {
  151. x = (monitor.width - dash_background.width) / 2;
  152. y = monitor.height - dash_container.height;
  153. } else if (dash_container.get_style_class_name().includes("left")) {
  154. x = dash_box.x;
  155. y = dash_container.y + (dash_container.height - dash_background.height) / 2 - dash_background.y;
  156. } else if (dash_container.get_style_class_name().includes("right")) {
  157. x = monitor.width - dash_container.width;
  158. y = dash_container.y + (dash_container.height - dash_background.height) / 2 - dash_background.y;
  159. }
  160. return [x, y];
  161. }
  162. _log(str) {
  163. if (this.settings.DEBUG)
  164. console.log(`[Blur my Shell > dash] ${str}`);
  165. }
  166. _warn(str) {
  167. console.warn(`[Blur my Shell > dash] ${str}`);
  168. }
  169. }
  170. export const DashBlur = class DashBlur {
  171. constructor(connections, settings, _) {
  172. this.dashes = [];
  173. this.connections = connections;
  174. this.settings = settings;
  175. this.paint_signals = new PaintSignals(connections);
  176. this.sigma = this.settings.dash_to_dock.CUSTOMIZE
  177. ? this.settings.dash_to_dock.SIGMA
  178. : this.settings.SIGMA;
  179. this.brightness = this.settings.dash_to_dock.CUSTOMIZE
  180. ? this.settings.dash_to_dock.BRIGHTNESS
  181. : this.settings.BRIGHTNESS;
  182. this.corner_radius = this.settings.dash_to_dock.CORNER_RADIUS;
  183. this.is_static = this.settings.dash_to_dock.STATIC_BLUR;
  184. this.enabled = false;
  185. }
  186. enable() {
  187. this.connections.connect(Main.uiGroup, 'child-added', (_, actor) => {
  188. if (
  189. (actor.get_name() === "dashtodockContainer") &&
  190. (actor.constructor.name === 'DashToDock')
  191. )
  192. this.try_blur(actor);
  193. });
  194. this.blur_existing_dashes();
  195. this.connect_to_overview();
  196. this.update_wallpaper();
  197. this.update_size();
  198. this.enabled = true;
  199. }
  200. // Finds all existing dashes on every monitor, and call `try_blur` on them
  201. // We cannot only blur `Main.overview.dash`, as there could be several
  202. blur_existing_dashes() {
  203. this._log("searching for dash");
  204. // blur every dash found, filtered by name
  205. Main.uiGroup.get_children().filter((child) => {
  206. return (child.get_name() === "dashtodockContainer") &&
  207. (child.constructor.name === 'DashToDock');
  208. }).forEach(this.try_blur.bind(this));
  209. }
  210. // Tries to blur the dash contained in the given actor
  211. try_blur(dash_container) {
  212. let dash_box = dash_container._slider.get_child();
  213. // verify that we did not already blur that dash
  214. if (!dash_box.get_children().some((child) => {
  215. return child.get_name() === "dash-blurred-background-parent";
  216. })) {
  217. this._log("dash to dock found, blurring it");
  218. // finally blur the dash
  219. let dash = dash_box.get_children().find(child => {
  220. return child.get_name() === 'dash';
  221. });
  222. this.dashes.push(this.blur_dash_from(dash, dash_container));
  223. }
  224. }
  225. // Blurs the dash and returns a `DashInfos` containing its information
  226. blur_dash_from(dash, dash_container) {
  227. // dash background parent, not visible
  228. let background_parent = new St.Widget({
  229. name: 'dash-blurred-background-parent',
  230. style_class: 'dash-blurred-background-parent',
  231. width: 0,
  232. height: 0
  233. });
  234. // finally blur the dash
  235. let dash_background = dash.get_children().find(child => {
  236. return child.get_style_class_name() === 'dash-background';
  237. });
  238. let [background, effect] = this.add_blur(dash, dash_background, dash_container);
  239. this.update_wallpaper();
  240. this.update_size();
  241. // updates size and position on change
  242. this.connections.connect(dash, 'notify::width', _ => {
  243. this.update_size();
  244. });
  245. this.connections.connect(dash, 'notify::height', _ => {
  246. this.update_size();
  247. });
  248. this.connections.connect(dash_container, 'notify::width', _ => {
  249. this.update_size();
  250. });
  251. this.connections.connect(dash_container, 'notify::height', _ => {
  252. this.update_size();
  253. });
  254. this.connections.connect(dash_container, 'notify::y', _ => {
  255. this.update_wallpaper();
  256. this.update_size();
  257. });
  258. this.connections.connect(dash_container, 'notify::x', _ => {
  259. this.update_wallpaper();
  260. this.update_size();
  261. });
  262. background_parent.add_child(background);
  263. dash.get_parent().insert_child_at_index(background_parent, 0);
  264. // create infos
  265. let infos = new DashInfos(
  266. this,
  267. dash,
  268. dash_container,
  269. dash_background,
  270. background,
  271. background_parent,
  272. effect
  273. );
  274. // update the background
  275. this.update_background();
  276. // returns infos
  277. return infos;
  278. }
  279. add_blur(dash, dash_background, dash_container) {
  280. let monitor = find_monitor_for(dash);
  281. // dash background widget
  282. let background = this.is_static
  283. ? new Meta.BackgroundActor({
  284. meta_display: global.display,
  285. monitor: monitor.index,
  286. })
  287. : new St.Widget({
  288. name: 'dash-blurred-background',
  289. style_class: 'dash-blurred-background',
  290. x: dash_background.x,
  291. y: dash_background.y + dash.y,
  292. width: dash_background.width,
  293. height: dash_background.height,
  294. });
  295. // the effect to be applied
  296. let effect;
  297. if (this.is_static) {
  298. let corner_radius = this.corner_radius * monitor.geometry_scale;
  299. corner_radius = Math.min(corner_radius, dash_background.width / 2, dash_background.height / 2);
  300. effect = new BlurEffect({
  301. radius: 2 * this.sigma * monitor.geometry_scale,
  302. brightness: this.brightness,
  303. width: dash_background.width,
  304. height: dash_background.height,
  305. corner_radius: corner_radius
  306. });
  307. // connect to every background change (even without changing image)
  308. // FIXME this signal is fired very often, so we should find another one
  309. // fired only when necessary (but that still catches all cases)
  310. this.connections.connect(
  311. Main.layoutManager._backgroundGroup,
  312. 'notify',
  313. _ => this.update_wallpaper()
  314. );
  315. } else {
  316. effect = new Shell.BlurEffect({
  317. brightness: this.brightness,
  318. radius: this.sigma * 2 * monitor.geometry_scale,
  319. mode: Shell.BlurMode.BACKGROUND
  320. });
  321. // HACK
  322. //
  323. //`Shell.BlurEffect` does not repaint when shadows are under it. [1]
  324. //
  325. // This does not entirely fix this bug (shadows caused by windows
  326. // still cause artifacts), but it prevents the shadows of the panel
  327. // buttons to cause artifacts on the panel itself
  328. //
  329. // [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
  330. if (this.settings.HACKS_LEVEL === 1) {
  331. this._log("dash hack level 1");
  332. this.paint_signals.disconnect_all();
  333. let rp = () => {
  334. effect.queue_repaint();
  335. };
  336. dash._box.get_children().forEach((icon) => {
  337. try {
  338. let zone = icon.get_child_at_index(0);
  339. this.connections.connect(zone, [
  340. 'enter-event', 'leave-event', 'button-press-event'
  341. ], rp);
  342. } catch (e) {
  343. this._warn(`${e}, continuing`);
  344. }
  345. });
  346. this.connections.connect(dash._box, 'actor-added', (_, actor) => {
  347. try {
  348. let zone = actor.get_child_at_index(0);
  349. this.connections.connect(zone, [
  350. 'enter-event', 'leave-event', 'button-press-event'
  351. ], rp);
  352. } catch (e) {
  353. this._warn(`${e}, continuing`);
  354. }
  355. });
  356. let show_apps = dash._showAppsIcon;
  357. this.connections.connect(show_apps, [
  358. 'enter-event', 'leave-event', 'button-press-event'
  359. ], rp);
  360. this.connections.connect(dash, 'leave-event', rp);
  361. } else if (this.settings.HACKS_LEVEL === 2) {
  362. this._log("dash hack level 2");
  363. this.paint_signals.connect(background, effect);
  364. } else {
  365. this.paint_signals.disconnect_all();
  366. }
  367. }
  368. // store the scale in the effect in order to retrieve it in set_sigma
  369. effect.scale = monitor.geometry_scale;
  370. background.add_effect(effect);
  371. return [background, effect];
  372. }
  373. change_blur_type() {
  374. this.is_static = this.settings.dash_to_dock.STATIC_BLUR;
  375. this.emit('change-blur-type', true);
  376. this.update_wallpaper();
  377. this.update_background();
  378. this.update_size();
  379. }
  380. /// Connect when overview if opened/closed to hide/show the blur accordingly
  381. connect_to_overview() {
  382. this.connections.disconnect_all_for(Main.overview);
  383. if (this.settings.dash_to_dock.UNBLUR_IN_OVERVIEW) {
  384. this.connections.connect(
  385. Main.overview, 'showing', this.hide.bind(this)
  386. );
  387. this.connections.connect(
  388. Main.overview, 'hidden', this.show.bind(this)
  389. );
  390. }
  391. };
  392. /// Updates the background to either remove it or not, according to the
  393. /// user preferences.
  394. update_background() {
  395. if (this.settings.dash_to_dock.OVERRIDE_BACKGROUND)
  396. this.emit('override-background', true);
  397. else
  398. this.emit('reset-background', true);
  399. }
  400. update_wallpaper() {
  401. if (this.is_static)
  402. this.emit('update-wallpaper', true);
  403. }
  404. update_size() {
  405. this.emit('update-size', true);
  406. }
  407. set_sigma(sigma) {
  408. this.sigma = sigma;
  409. this.emit('update-sigma', true);
  410. }
  411. set_brightness(brightness) {
  412. this.brightness = brightness;
  413. this.emit('update-brightness', true);
  414. }
  415. set_corner_radius(radius) {
  416. this.corner_radius = radius;
  417. this.emit('update-corner-radius', true);
  418. }
  419. // not implemented for dynamic blur
  420. set_color(c) { }
  421. set_noise_amount(n) { }
  422. set_noise_lightness(l) { }
  423. disable() {
  424. this._log("removing blur from dashes");
  425. this.emit('remove-dashes', true);
  426. this.dashes = [];
  427. this.connections.disconnect_all();
  428. this.enabled = false;
  429. }
  430. show() {
  431. this.emit('show', true);
  432. }
  433. hide() {
  434. this.emit('hide', true);
  435. }
  436. _log(str) {
  437. if (this.settings.DEBUG)
  438. console.log(`[Blur my Shell > dash manager] ${str}`);
  439. }
  440. _warn(str) {
  441. console.warn(`[Blur my Shell > dash manager] ${str}`);
  442. }
  443. };
  444. Signals.addSignalMethods(DashBlur.prototype);