pipeline.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import St from 'gi://St';
  2. import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  3. import * as Background from 'resource:///org/gnome/shell/ui/background.js';
  4. /// A `Pipeline` object is a handy way to manage the effects attached to an actor. It only manages
  5. /// one actor at a time (so blurring multiple widgets will need multiple `Pipeline`), and is
  6. /// linked to a `pipeline_id` that has been (hopefully) defined in the settings.
  7. ///
  8. /// It communicates with the settings through the `PipelinesManager` object, and receives different
  9. /// signals (with `pipeline_id` being an unique string):
  10. /// - `'pipeline_id'::pipeline-updated`, handing a new pipeline descriptor object, when the pipeline
  11. /// has been changed enough that it needs to rebuild the effects configuration
  12. /// - `'pipeline_id'::pipeline-destroyed`, when the pipeline has been destroyed; thus making the
  13. /// `Pipeline` change its id to `pipeline_default`
  14. ///
  15. /// And each effect, with an unique `id`, is connected to the `PipelinesManager` for the signals:
  16. /// - `'pipeline_id'::effect-'id'-key-removed`, handing the key that was removed
  17. /// - `'pipeline_id'::effect-'id'-key-updated`, handing the key that was changed and its new value
  18. /// - `'pipeline_id'::effect-'id'-key-added`, handing the key that was added and its value
  19. export const Pipeline = class Pipeline {
  20. constructor(effects_manager, pipelines_manager, pipeline_id, actor = null) {
  21. this.effects_manager = effects_manager;
  22. this.pipelines_manager = pipelines_manager;
  23. this.effects = [];
  24. this.set_pipeline_id(pipeline_id);
  25. this.attach_pipeline_to_actor(actor);
  26. }
  27. /// Create a background linked to the monitor with index `monitor_index`, with a
  28. /// `BackgroundManager` that is appended to the list `background_managers`. The background actor
  29. /// will be given the name `widget_name` and inserted into the given `background_group`.
  30. /// If `use_absolute_position` is false, then the position used is at (0,0); useful when the
  31. /// positioning is relative.
  32. /// Note: exposed to public API.
  33. create_background_with_effects(
  34. monitor_index,
  35. background_managers,
  36. background_group,
  37. widget_name,
  38. use_absolute_position = true
  39. ) {
  40. let monitor = Main.layoutManager.monitors[monitor_index];
  41. // create the new actor
  42. this.actor = new St.Widget({
  43. name: widget_name,
  44. x: use_absolute_position ? monitor.x : 0,
  45. y: .5 + (use_absolute_position ? monitor.y : 0), // add 1 to correct z-position
  46. z_position: 1, // seems to fix the multi-monitor glitch
  47. width: monitor.width,
  48. height: monitor.height
  49. });
  50. // remove the effects, wether or not we attach the pipeline to the actor: if they are fired
  51. // while the actor has changed, this could go bad
  52. this.remove_all_effects();
  53. if (this.pipeline_id)
  54. this.attach_pipeline_to_actor(this.actor);
  55. let bg_manager = new Background.BackgroundManager({
  56. container: this.actor,
  57. monitorIndex: monitor_index,
  58. controlPosition: false,
  59. });
  60. bg_manager._bms_pipeline = this;
  61. background_managers.push(bg_manager);
  62. background_group.insert_child_at_index(this.actor, 0);
  63. return this.actor;
  64. };
  65. /// Set the pipeline id, correctly connecting the `Pipeline` object to listen the pipelines
  66. /// manager for pipeline-wide changes. This does not update the effects in consequence, call
  67. /// `change_pipeline_to` instead if you want to reconstruct the effects too.
  68. set_pipeline_id(pipeline_id) {
  69. // disconnect ancient signals
  70. this.remove_connections();
  71. // change the id
  72. this.pipeline_id = pipeline_id;
  73. // connect to settings changes
  74. this._pipeline_changed_id = this.pipelines_manager.connect(
  75. this.pipeline_id + '::pipeline-updated',
  76. (_, new_pipeline) => this.update_effects_from_pipeline(new_pipeline)
  77. );
  78. this._pipeline_destroyed_id = this.pipelines_manager.connect(
  79. this.pipeline_id + '::pipeline-destroyed',
  80. _ => this.change_pipeline_to("pipeline_default")
  81. );
  82. }
  83. /// Disconnect the signals for the pipeline changes. Please note that the signals related to the
  84. /// effects are stored with them and removed with `remove_all_effects`.
  85. remove_connections() {
  86. if (this._pipeline_changed_id)
  87. this.pipelines_manager.disconnect(this._pipeline_changed_id);
  88. if (this._pipeline_destroyed_id)
  89. this.pipelines_manager.disconnect(this._pipeline_destroyed_id);
  90. this._pipeline_changed_id = null;
  91. this._pipeline_destroyed_id = null;
  92. }
  93. /// Attach a Pipeline object with `pipeline_id` already set to an actor.
  94. attach_pipeline_to_actor(actor) {
  95. // set the actor
  96. if (actor)
  97. this.actor = actor;
  98. else {
  99. this.remove_pipeline_from_actor();
  100. return;
  101. }
  102. // attach the pipeline
  103. let pipeline = this.pipelines_manager.pipelines[this.pipeline_id];
  104. if (!pipeline) {
  105. this._warn(`could not attach pipeline to actor, pipeline "${this.pipeline_id}" not found`);
  106. // do not recurse...
  107. if ("pipeline_default" in this.pipelines_manager.pipelines) {
  108. this.set_pipeline_id("pipeline_default");
  109. pipeline = this.pipelines_manager.pipelines["pipeline_default"];
  110. } else
  111. return;
  112. }
  113. this.actor_destroy_id = this.actor.connect(
  114. "destroy", () => this.remove_pipeline_from_actor()
  115. );
  116. // update the effects
  117. this.update_effects_from_pipeline(pipeline);
  118. }
  119. remove_pipeline_from_actor() {
  120. this.remove_all_effects();
  121. if (this.actor && this.actor_destroy_id)
  122. this.actor.disconnect(this.actor_destroy_id);
  123. this.actor_destroy_id = null;
  124. this.actor = null;
  125. }
  126. /// Update the effects from the given pipeline object, the hard way.
  127. update_effects_from_pipeline(pipeline) {
  128. // remove all effects
  129. this.remove_all_effects();
  130. // build the new effects to be added
  131. pipeline.effects.forEach(effect => {
  132. if ('new_' + effect.type + '_effect' in this.effects_manager)
  133. this.build_effect(effect);
  134. else
  135. this._warn(`could not add effect to actor, effect "${effect.type}" not found`);
  136. });
  137. this.effects.reverse();
  138. // add the effects to the actor
  139. if (this.actor)
  140. this.effects.forEach(effect => this.actor.add_effect(effect));
  141. else
  142. this._warn(`could not add effect to actor, actor does not exist anymore`);
  143. }
  144. /// Given an `effect_infos` object containing the effect type, id and params, build an effect
  145. /// and append it to the effects list
  146. build_effect(effect_infos) {
  147. let effect = this.effects_manager['new_' + effect_infos.type + '_effect'](effect_infos.params);
  148. this.effects.push(effect);
  149. // connect to settings changes
  150. effect._effect_key_removed_id = this.pipelines_manager.connect(
  151. this.pipeline_id + '::effect-' + effect_infos.id + '-key-removed',
  152. (_, key) => effect[key] = effect.constructor.default_params[key]
  153. );
  154. effect._effect_key_updated_id = this.pipelines_manager.connect(
  155. this.pipeline_id + '::effect-' + effect_infos.id + '-key-updated',
  156. (_, key, value) => effect[key] = value
  157. );
  158. effect._effect_key_added_id = this.pipelines_manager.connect(
  159. this.pipeline_id + '::effect-' + effect_infos.id + '-key-added',
  160. (_, key, value) => effect[key] = value
  161. );
  162. }
  163. /// Remove every effect from the actor it is attached to. Please note that they are not
  164. /// destroyed, but rather stored (thanks to the `EffectManager` class) to be reused later.
  165. remove_all_effects() {
  166. this.effects.forEach(effect => {
  167. this.effects_manager.remove(effect);
  168. [
  169. effect._effect_key_removed_id,
  170. effect._effect_key_updated_id,
  171. effect._effect_key_added_id
  172. ].forEach(
  173. id => { if (id) this.pipelines_manager.disconnect(id); }
  174. );
  175. delete effect._effect_key_removed_id;
  176. delete effect._effect_key_updated_id;
  177. delete effect._effect_key_added_id;
  178. });
  179. this.effects = [];
  180. }
  181. /// Change the pipeline id, and update the effects according to this change.
  182. /// Note: exposed to public API.
  183. change_pipeline_to(pipeline_id) {
  184. this.set_pipeline_id(pipeline_id);
  185. this.attach_pipeline_to_actor(this.actor);
  186. }
  187. /// Resets the `Pipeline` object to a sane state, removing every effect and signal.
  188. /// Note: exposed to public API.
  189. destroy() {
  190. this.remove_all_effects();
  191. this.remove_connections();
  192. this.remove_pipeline_from_actor();
  193. this.pipeline_id = null;
  194. }
  195. _warn(str) {
  196. console.warn(`[Blur my Shell > pipeline] ${str}`);
  197. }
  198. };