applications.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. import Shell from 'gi://Shell';
  2. import Clutter from 'gi://Clutter';
  3. import Meta from 'gi://Meta';
  4. import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  5. import { PaintSignals } from '../effects/paint_signals.js';
  6. import { ApplicationsService } from '../dbus/services.js';
  7. export const ApplicationsBlur = class ApplicationsBlur {
  8. constructor(connections, settings, _) {
  9. this.connections = connections;
  10. this.settings = settings;
  11. this.paint_signals = new PaintSignals(connections);
  12. // stores every blurred window
  13. this.window_map = new Map();
  14. // stores every blur actor
  15. this.blur_actor_map = new Map();
  16. }
  17. enable() {
  18. this._log("blurring applications...");
  19. // export dbus service for preferences
  20. this.service = new ApplicationsService;
  21. this.service.export();
  22. // blur already existing windows
  23. this.update_all_windows();
  24. // blur every new window
  25. this.connections.connect(
  26. global.display,
  27. 'window-created',
  28. (_meta_display, meta_window) => {
  29. this._log("window created");
  30. if (meta_window) {
  31. let window_actor = meta_window.get_compositor_private();
  32. this.track_new(window_actor, meta_window);
  33. }
  34. }
  35. );
  36. this.connect_to_overview();
  37. }
  38. /// Connect to the overview being opened/closed to force the blur being
  39. /// shown on every window of the workspaces viewer.
  40. connect_to_overview() {
  41. this.connections.disconnect_all_for(Main.overview);
  42. if (this.settings.applications.BLUR_ON_OVERVIEW) {
  43. // when the overview is opened, show every window actors (which
  44. // allows the blur to be shown too)
  45. this.connections.connect(
  46. Main.overview, 'showing',
  47. _ => this.window_map.forEach((meta_window, _pid) => {
  48. let window_actor = meta_window.get_compositor_private();
  49. window_actor.show();
  50. })
  51. );
  52. // when the overview is closed, hide every actor that is not on the
  53. // current workspace (to mimic the original behaviour)
  54. this.connections.connect(
  55. Main.overview, 'hidden',
  56. _ => {
  57. let active_workspace =
  58. global.workspace_manager.get_active_workspace();
  59. this.window_map.forEach((meta_window, _pid) => {
  60. let window_actor = meta_window.get_compositor_private();
  61. if (
  62. meta_window.get_workspace() !== active_workspace
  63. )
  64. window_actor.hide();
  65. });
  66. }
  67. );
  68. }
  69. }
  70. /// Iterate through all existing windows and add blur as needed.
  71. update_all_windows() {
  72. // remove all previously blurred windows, in the case where the
  73. // whitelist was changed
  74. this.window_map.forEach(((_meta_window, pid) => {
  75. this.remove_blur(pid);
  76. }));
  77. for (
  78. let i = 0;
  79. i < global.workspace_manager.get_n_workspaces();
  80. ++i
  81. ) {
  82. let workspace = global.workspace_manager.get_workspace_by_index(i);
  83. let windows = workspace.list_windows();
  84. windows.forEach(meta_window => {
  85. let window_actor = meta_window.get_compositor_private();
  86. // disconnect previous signals
  87. this.connections.disconnect_all_for(window_actor);
  88. this.track_new(window_actor, meta_window);
  89. });
  90. }
  91. }
  92. /// Adds the needed signals to every new tracked window, and adds blur if
  93. /// needed.
  94. track_new(window_actor, meta_window) {
  95. let pid = ("" + Math.random()).slice(2, 16);
  96. window_actor['blur_provider_pid'] = pid;
  97. meta_window['blur_provider_pid'] = pid;
  98. // remove the blur when the window is destroyed
  99. this.connections.connect(window_actor, 'destroy', window_actor => {
  100. let pid = window_actor.blur_provider_pid;
  101. if (this.blur_actor_map.has(pid)) {
  102. this.remove_blur(pid);
  103. }
  104. this.window_map.delete(pid);
  105. });
  106. // update the blur when mutter-hint or wm-class is changed
  107. for (const prop of ['mutter-hints', 'wm-class']) {
  108. this.connections.connect(
  109. meta_window,
  110. `notify::${prop}`,
  111. _ => {
  112. let pid = meta_window.blur_provider_pid;
  113. this._log(`${prop} changed for pid ${pid}`);
  114. let window_actor = meta_window.get_compositor_private();
  115. this.check_blur(pid, window_actor, meta_window);
  116. }
  117. );
  118. }
  119. // update the position and size when the window size changes
  120. this.connections.connect(meta_window, 'size-changed', () => {
  121. if (this.blur_actor_map.has(pid)) {
  122. let allocation = this.compute_allocation(meta_window);
  123. let blur_actor = this.blur_actor_map.get(pid);
  124. blur_actor.x = allocation.x;
  125. blur_actor.y = allocation.y;
  126. blur_actor.width = allocation.width;
  127. blur_actor.height = allocation.height;
  128. }
  129. });
  130. this.check_blur(pid, window_actor, meta_window);
  131. }
  132. /// Checks if the given actor needs to be blurred.
  133. ///
  134. /// In order to be blurred, a window either:
  135. /// - is whitelisted in the user preferences if not enable-all
  136. /// - is not blacklisted if enable-all
  137. /// - has a correct mutter hint, set to `blur-provider=sigma_value`
  138. check_blur(pid, window_actor, meta_window) {
  139. let mutter_hint = meta_window.get_mutter_hints();
  140. let window_wm_class = meta_window.get_wm_class();
  141. let enable_all = this.settings.applications.ENABLE_ALL;
  142. let whitelist = this.settings.applications.WHITELIST;
  143. let blacklist = this.settings.applications.BLACKLIST;
  144. this._log(`checking blur for ${pid}`);
  145. // either the window is included in whitelist
  146. if (window_wm_class !== ""
  147. && ((enable_all && !blacklist.includes(window_wm_class))
  148. || (!enable_all && whitelist.includes(window_wm_class))
  149. )
  150. && [
  151. Meta.FrameType.NORMAL,
  152. Meta.FrameType.DIALOG,
  153. Meta.FrameType.MODAL_DIALOG
  154. ].includes(meta_window.get_frame_type())
  155. ) {
  156. this._log(`application ${pid} listed, blurring it`);
  157. // get blur effect parameters
  158. let brightness, sigma;
  159. if (this.settings.applications.CUSTOMIZE) {
  160. brightness = this.settings.applications.BRIGHTNESS;
  161. sigma = this.settings.applications.SIGMA;
  162. } else {
  163. brightness = this.settings.BRIGHTNESS;
  164. sigma = this.settings.SIGMA;
  165. }
  166. this.update_blur(pid, window_actor, meta_window, brightness, sigma);
  167. }
  168. // or blur is asked by window itself
  169. else if (
  170. mutter_hint != null &&
  171. mutter_hint.includes("blur-provider")
  172. ) {
  173. this._log(`application ${pid} has hint ${mutter_hint}, parsing`);
  174. // get blur effect parameters
  175. let [brightness, sigma] = this.parse_xprop(mutter_hint);
  176. this.update_blur(pid, window_actor, meta_window, brightness, sigma);
  177. }
  178. // remove blur if the mutter hint is no longer valid, and the window
  179. // is not explicitly whitelisted or un-blacklisted
  180. else if (this.blur_actor_map.has(pid)) {
  181. this.remove_blur(pid);
  182. }
  183. }
  184. /// When given the xprop property, returns the brightness and sigma values
  185. /// matching. If one of the two values is invalid, or missing, then it uses
  186. /// default values.
  187. ///
  188. /// An xprop property is valid if it is in one of the following formats:
  189. ///
  190. /// blur-provider=sigma:60,brightness:0.9
  191. /// blur-provider=s:10,brightness:0.492
  192. /// blur-provider=b:1.0,s:16
  193. ///
  194. /// Brightness is a floating-point between 0.0 and 1.0 included.
  195. /// Sigma is an integer between 0 and 999 included.
  196. ///
  197. /// If sigma is set to 0, then the blur is removed.
  198. /// Setting "default" instead of the two values will make the
  199. /// extension use its default value.
  200. ///
  201. /// Note that no space can be inserted.
  202. ///
  203. parse_xprop(property) {
  204. // set brightness and sigma to default values
  205. let brightness, sigma;
  206. if (this.settings.applications.CUSTOMIZE) {
  207. brightness = this.settings.applications.BRIGHTNESS;
  208. sigma = this.settings.applications.SIGMA;
  209. } else {
  210. brightness = this.settings.BRIGHTNESS;
  211. sigma = this.settings.SIGMA;
  212. }
  213. // get the argument of the property
  214. let arg = property.match("blur-provider=(.*)");
  215. this._log(`argument = ${arg}`);
  216. // if argument is valid, parse it
  217. if (arg != null) {
  218. // verify if there is only one value: in this case, this is sigma
  219. let maybe_sigma = parseInt(arg[1]);
  220. if (
  221. !isNaN(maybe_sigma) &&
  222. maybe_sigma >= 0 &&
  223. maybe_sigma <= 999
  224. ) {
  225. sigma = maybe_sigma;
  226. } else {
  227. // perform pattern matching
  228. let res_b = arg[1].match("(brightness|b):(default|0?1?\.[0-9]*)");
  229. let res_s = arg[1].match("(sigma|s):(default|\\d{1,3})");
  230. // if values are valid and not default, change them to the xprop one
  231. if (
  232. res_b != null && res_b[2] !== 'default'
  233. ) {
  234. brightness = parseFloat(res_b[2]);
  235. }
  236. if (
  237. res_s != null && res_s[2] !== 'default'
  238. ) {
  239. sigma = parseInt(res_s[2]);
  240. }
  241. }
  242. }
  243. this._log(`brightness = ${brightness}, sigma = ${sigma}`);
  244. return [brightness, sigma];
  245. }
  246. /// Updates the blur on a window which needs to be blurred.
  247. update_blur(pid, window_actor, meta_window, brightness, sigma) {
  248. // the window is already blurred, update its blur effect
  249. if (this.blur_actor_map.has(pid)) {
  250. // window is already blurred, but sigma is null: remove the blur
  251. if (sigma === 0) {
  252. this.remove_blur(pid);
  253. }
  254. // window is already blurred and sigma is non-null: update it
  255. else {
  256. this.update_blur_effect(
  257. this.blur_actor_map.get(pid),
  258. brightness,
  259. sigma
  260. );
  261. }
  262. }
  263. // the window is not blurred, and sigma is a non-null value: blur it
  264. else if (sigma !== 0) {
  265. // window is not blurred, blur it
  266. this.create_blur_effect(
  267. pid,
  268. window_actor,
  269. meta_window,
  270. brightness,
  271. sigma
  272. );
  273. }
  274. }
  275. /// Add the blur effect to the window.
  276. create_blur_effect(pid, window_actor, meta_window, brightness, sigma) {
  277. let blur_effect = new Shell.BlurEffect({
  278. sigma: sigma,
  279. brightness: brightness,
  280. mode: Shell.BlurMode.BACKGROUND
  281. });
  282. let blur_actor = this.create_blur_actor(
  283. meta_window,
  284. window_actor,
  285. blur_effect
  286. );
  287. // if hacks are selected, force to repaint the window
  288. if (this.settings.HACKS_LEVEL === 1 || this.settings.HACKS_LEVEL === 2) {
  289. this._log("applications hack level 1 or 2");
  290. this.paint_signals.disconnect_all();
  291. this.paint_signals.connect(blur_actor, blur_effect);
  292. } else {
  293. this.paint_signals.disconnect_all();
  294. }
  295. // insert the blurred widget
  296. window_actor.insert_child_at_index(blur_actor, 0);
  297. // make sure window is blurred in overview
  298. if (this.settings.applications.BLUR_ON_OVERVIEW)
  299. this.enforce_window_visibility_on_overview_for(window_actor);
  300. // set the window actor's opacity
  301. this.set_window_opacity(window_actor, this.settings.applications.OPACITY);
  302. this.connections.connect(
  303. window_actor,
  304. 'notify::opacity',
  305. _ => this.set_window_opacity(window_actor, this.settings.applications.OPACITY)
  306. );
  307. // register the blur actor/effect
  308. blur_actor['blur_provider_pid'] = pid;
  309. this.blur_actor_map.set(pid, blur_actor);
  310. this.window_map.set(pid, meta_window);
  311. // hide the blur if window is invisible
  312. if (!window_actor.visible) {
  313. blur_actor.hide();
  314. }
  315. // hide the blur if window becomes invisible
  316. this.connections.connect(
  317. window_actor,
  318. 'notify::visible',
  319. window_actor => {
  320. let pid = window_actor.blur_provider_pid;
  321. if (window_actor.visible) {
  322. this.blur_actor_map.get(pid).show();
  323. } else {
  324. this.blur_actor_map.get(pid).hide();
  325. }
  326. }
  327. );
  328. }
  329. /// Makes sure that, when the overview is visible, the window actor will
  330. /// stay visible no matter what.
  331. /// We can instead hide the last child of the window actor, which will
  332. /// improve performances without hiding the blur effect.
  333. enforce_window_visibility_on_overview_for(window_actor) {
  334. this.connections.connect(window_actor, 'notify::visible',
  335. _ => {
  336. if (this.settings.applications.BLUR_ON_OVERVIEW) {
  337. if (
  338. !window_actor.visible
  339. && Main.overview.visible
  340. ) {
  341. window_actor.show();
  342. window_actor.get_last_child().hide();
  343. }
  344. else if (
  345. window_actor.visible
  346. )
  347. window_actor.get_last_child().show();
  348. }
  349. }
  350. );
  351. }
  352. /// Set the opacity of the window actor that sits on top of the blur effect.
  353. set_window_opacity(window_actor, opacity) {
  354. window_actor.get_children().forEach(child => {
  355. if (child.name !== "blur-actor" && child.opacity != opacity)
  356. child.opacity = opacity;
  357. });
  358. }
  359. /// Compute the size and position for a blur actor.
  360. /// On wayland, it seems like we need to divide by the scale to get the
  361. /// correct result.
  362. compute_allocation(meta_window) {
  363. const is_wayland = Meta.is_wayland_compositor();
  364. const monitor_index = meta_window.get_monitor();
  365. // check if the window is using wayland, or xwayland/xorg for rendering
  366. const scale = is_wayland && meta_window.get_client_type() == 0
  367. ? Main.layoutManager.monitors[monitor_index].geometry_scale
  368. : 1;
  369. let frame = meta_window.get_frame_rect();
  370. let buffer = meta_window.get_buffer_rect();
  371. return {
  372. x: (frame.x - buffer.x) / scale,
  373. y: (frame.y - buffer.y) / scale,
  374. width: frame.width / scale,
  375. height: frame.height / scale
  376. };
  377. }
  378. /// Returns a new already blurred widget, configured to follow the size and
  379. /// position of its target window.
  380. create_blur_actor(meta_window, window_actor, blur_effect) {
  381. // compute the size and position
  382. let allocation = this.compute_allocation(meta_window);
  383. // create the actor
  384. let blur_actor = new Clutter.Actor({
  385. x: allocation.x,
  386. y: allocation.y,
  387. width: allocation.width,
  388. height: allocation.height
  389. });
  390. // add the effect
  391. blur_actor.add_effect_with_name('blur-effect', blur_effect);
  392. return blur_actor;
  393. }
  394. /// Updates the blur effect by overwriting its sigma and brightness values.
  395. update_blur_effect(blur_actor, brightness, sigma) {
  396. let effect = blur_actor.get_effect('blur-effect');
  397. effect.sigma = sigma;
  398. effect.brightness = brightness;
  399. }
  400. /// Removes the blur actor from the shell and unregister it.
  401. remove_blur(pid) {
  402. this._log(`removing blur for pid ${pid}`);
  403. let meta_window = this.window_map.get(pid);
  404. // disconnect needed signals and untrack window
  405. if (meta_window) {
  406. this.window_map.delete(pid);
  407. let window_actor = meta_window.get_compositor_private();
  408. let blur_actor = this.blur_actor_map.get(pid);
  409. if (blur_actor) {
  410. this.blur_actor_map.delete(pid);
  411. if (window_actor) {
  412. // reset the opacity
  413. this.set_window_opacity(window_actor, 255);
  414. // remove the blurred actor
  415. window_actor.remove_child(blur_actor);
  416. // disconnect the signals about overview animation etc
  417. this.connections.disconnect_all_for(window_actor);
  418. }
  419. }
  420. }
  421. }
  422. disable() {
  423. this._log("removing blur from applications...");
  424. this.service?.unexport();
  425. this.blur_actor_map.forEach(((_blur_actor, pid) => {
  426. this.remove_blur(pid);
  427. }));
  428. this.connections.disconnect_all();
  429. this.paint_signals.disconnect_all();
  430. }
  431. /// Update the opacity of all window actors.
  432. set_opacity() {
  433. let opacity = this.settings.applications.OPACITY;
  434. this.window_map.forEach(((meta_window, _pid) => {
  435. let window_actor = meta_window.get_compositor_private();
  436. this.set_window_opacity(window_actor, opacity);
  437. }));
  438. }
  439. /// Updates each blur effect to use new sigma value
  440. // FIXME set_sigma and set_brightness are called when the extension is
  441. // loaded and when sigma is changed, and do not respect the per-app
  442. // xprop behaviour
  443. set_sigma(s) {
  444. this.blur_actor_map.forEach((actor, _) => {
  445. actor.get_effect('blur-effect').set_sigma(s);
  446. });
  447. }
  448. /// Updates each blur effect to use new brightness value
  449. set_brightness(b) {
  450. this.blur_actor_map.forEach((actor, _) => {
  451. actor.get_effect('blur-effect').set_brightness(b);
  452. });
  453. }
  454. // not implemented for dynamic blur
  455. set_color(c) { }
  456. set_noise_amount(n) { }
  457. set_noise_lightness(l) { }
  458. _log(str) {
  459. if (this.settings.DEBUG)
  460. console.log(`[Blur my Shell > applications] ${str}`);
  461. }
  462. };