tree.js 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668
  1. /*
  2. * This file is part of the Forge extension for GNOME
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. */
  18. // Gnome imports
  19. import Clutter from "gi://Clutter";
  20. import GObject from "gi://GObject";
  21. import Meta from "gi://Meta";
  22. import Shell from "gi://Shell";
  23. import St from "gi://St";
  24. // Shared state
  25. import { Logger } from "../shared/logger.js";
  26. // App imports
  27. import * as Utils from "./utils.js";
  28. import * as Window from "./window.js";
  29. export const NODE_TYPES = Utils.createEnum([
  30. "ROOT",
  31. "MONITOR", //Output in i3
  32. "CON", //Container in i3
  33. "WINDOW",
  34. "WORKSPACE",
  35. ]);
  36. export const LAYOUT_TYPES = Utils.createEnum([
  37. "STACKED",
  38. "TABBED",
  39. "ROOT",
  40. "HSPLIT",
  41. "VSPLIT",
  42. "PRESET",
  43. ]);
  44. export const ORIENTATION_TYPES = Utils.createEnum(["NONE", "HORIZONTAL", "VERTICAL"]);
  45. export const POSITION = Utils.createEnum(["BEFORE", "AFTER", "UNKNOWN"]);
  46. /**
  47. * The Node data representation of the following elements in the user's display:
  48. *
  49. * Monitor,
  50. * Window,
  51. * Container (generic),
  52. * Workspace
  53. *
  54. */
  55. export class Node extends GObject.Object {
  56. static {
  57. GObject.registerClass(this);
  58. }
  59. constructor(type, data) {
  60. super();
  61. // TODO - move to GObject property definitions?
  62. this._type = type; // see NODE_TYPES
  63. // _data: Meta.Window, unique id strings (Monitor,
  64. // Workspace or St.Bin - a representation of Container)
  65. this._data = data;
  66. this._parent = null;
  67. this._nodes = []; // Child elements of this node
  68. this.mode = Window.WINDOW_MODES.DEFAULT;
  69. this.percent = 0.0;
  70. this._rect = null;
  71. this.tab = null;
  72. this.decoration = null;
  73. this.app = null;
  74. this.pointer = null;
  75. if (this.isWindow()) {
  76. // When destroy() is called on Meta.Window, it might not be
  77. // available so we store it immediately
  78. this._initMetaWindow();
  79. this._actor = this._data.get_compositor_private();
  80. this._createWindowTab();
  81. }
  82. if (this.isCon()) {
  83. this._createDecoration();
  84. }
  85. }
  86. get windowActor() {
  87. return this._actor;
  88. }
  89. get actor() {
  90. switch (this.nodeType) {
  91. case NODE_TYPES.WINDOW:
  92. // A Meta.Window was assigned during creation
  93. // But obtain the Clutter.Actor
  94. return this._actor;
  95. case NODE_TYPES.CON:
  96. case NODE_TYPES.ROOT:
  97. // A St.Bin was assigned during creation
  98. return this.nodeValue;
  99. case NODE_TYPES.MONITOR:
  100. case NODE_TYPES.WORKSPACE:
  101. // A separate St.Bin was assigned on another attribute during creation
  102. return this.actorBin;
  103. }
  104. }
  105. set rect(rect) {
  106. this._rect = rect;
  107. switch (this.nodeType) {
  108. case NODE_TYPES.WINDOW:
  109. break;
  110. case NODE_TYPES.CON:
  111. case NODE_TYPES.MONITOR:
  112. case NODE_TYPES.ROOT:
  113. case NODE_TYPES.WORKSPACE:
  114. if (this.actor) {
  115. this.actor.set_size(rect.width, rect.height);
  116. this.actor.set_position(rect.x, rect.y);
  117. }
  118. break;
  119. }
  120. }
  121. get rect() {
  122. return this._rect;
  123. }
  124. get childNodes() {
  125. return this._nodes;
  126. }
  127. set childNodes(nodes) {
  128. this._nodes = nodes;
  129. }
  130. get firstChild() {
  131. if (this._nodes && this._nodes.length >= 1) {
  132. return this._nodes[0];
  133. }
  134. return null;
  135. }
  136. get level() {
  137. let _level = 0;
  138. let refNode = this.parentNode;
  139. while (refNode) {
  140. _level += 1;
  141. refNode = refNode.parentNode;
  142. }
  143. return _level;
  144. }
  145. /**
  146. * Find the index of this relative to the siblings
  147. */
  148. get index() {
  149. if (this.parentNode) {
  150. let childNodes = this.parentNode.childNodes;
  151. for (let i = 0; i < childNodes.length; i++) {
  152. if (childNodes[i] === this) {
  153. return i;
  154. }
  155. }
  156. }
  157. return null;
  158. }
  159. get lastChild() {
  160. if (this._nodes && this._nodes.length >= 1) {
  161. return this._nodes[this._nodes.length - 1];
  162. }
  163. return null;
  164. }
  165. get nextSibling() {
  166. if (this.parentNode) {
  167. if (this.parentNode.lastChild !== this) {
  168. return this.parentNode.childNodes[this.index + 1];
  169. }
  170. }
  171. return null;
  172. }
  173. get nodeType() {
  174. return this._type;
  175. }
  176. get nodeValue() {
  177. return this._data;
  178. }
  179. get parentNode() {
  180. return this._parent;
  181. }
  182. set parentNode(node) {
  183. this._parent = node;
  184. }
  185. get previousSibling() {
  186. if (this.parentNode) {
  187. if (this.parentNode.firstChild !== this) {
  188. return this.parentNode.childNodes[this.index - 1];
  189. }
  190. }
  191. return null;
  192. }
  193. appendChild(node) {
  194. if (!node) return null;
  195. if (node.parentNode) node.parentNode.removeChild(node);
  196. this.childNodes.push(node);
  197. node.parentNode = this;
  198. return node;
  199. }
  200. /**
  201. * Checks if node is a descendant of this,
  202. * or a descendant of its childNodes, etc
  203. */
  204. contains(node) {
  205. if (!node) return false;
  206. let searchNode = this.getNodeByValue(node.nodeValue);
  207. return searchNode ? true : false;
  208. }
  209. getNodeByLayout(layout) {
  210. let results = this._search(layout, "LAYOUT");
  211. return results;
  212. }
  213. getNodeByMode(mode) {
  214. let results = this._search(mode, "MODE");
  215. return results;
  216. }
  217. getNodeByValue(value) {
  218. let results = this._search(value, "VALUE");
  219. return results && results.length >= 1 ? results[0] : null;
  220. }
  221. getNodeByType(type) {
  222. let results = this._search(type, "TYPE");
  223. return results;
  224. }
  225. /**
  226. * @param childNode - is a child of this
  227. */
  228. insertBefore(newNode, childNode) {
  229. if (!newNode) return null;
  230. if (newNode === childNode) return null;
  231. if (!childNode) {
  232. this.appendChild(newNode);
  233. return newNode;
  234. }
  235. if (childNode.parentNode !== this) return null;
  236. if (newNode.parentNode) newNode.parentNode.removeChild(newNode);
  237. let index = childNode.index;
  238. if (childNode.index === 0) {
  239. this.childNodes.unshift(newNode);
  240. } else if (childNode.index > 0) {
  241. this.childNodes.splice(index, 0, newNode);
  242. }
  243. newNode.parentNode = this;
  244. return newNode;
  245. }
  246. isLayout(name) {
  247. let layout = this.layout;
  248. if (!layout) return false;
  249. return name === layout;
  250. }
  251. isHSplit() {
  252. return this.isLayout(LAYOUT_TYPES.HSPLIT);
  253. }
  254. isVSplit() {
  255. return this.isLayout(LAYOUT_TYPES.VSPLIT);
  256. }
  257. isStacked() {
  258. return this.isLayout(LAYOUT_TYPES.STACKED);
  259. }
  260. isTabbed() {
  261. return this.isLayout(LAYOUT_TYPES.TABBED);
  262. }
  263. isType(name) {
  264. const type = this.nodeType;
  265. if (!type) return false;
  266. return name === type;
  267. }
  268. isWindow() {
  269. return this.isType(NODE_TYPES.WINDOW);
  270. }
  271. isCon() {
  272. return this.isType(NODE_TYPES.CON);
  273. }
  274. isMonitor() {
  275. return this.isType(NODE_TYPES.MONITOR);
  276. }
  277. isWorkspace() {
  278. return this.isType(NODE_TYPES.WORKSPACE);
  279. }
  280. isRoot() {
  281. return this.isType(NODE_TYPES.ROOT);
  282. }
  283. isMode(name) {
  284. const mode = this.mode;
  285. if (!name) return false;
  286. return name === mode;
  287. }
  288. isFloat() {
  289. return this.isMode(Window.WINDOW_MODES.FLOAT);
  290. }
  291. isTile() {
  292. return this.isMode(Window.WINDOW_MODES.TILE);
  293. }
  294. isGrabTile() {
  295. return this.isMode(Window.WINDOW_MODES.GRAB_TILE);
  296. }
  297. removeChild(node) {
  298. if (node.isTabbed() && node.decoration) {
  299. node.decoration.hide();
  300. node.decoration.destroy_all_children();
  301. node.decoration.destroy();
  302. node.decoration = null;
  303. }
  304. let refNode;
  305. if (this.contains(node)) {
  306. // Since contains() tries to find node on all descendants,
  307. // detach only from the immediate parent
  308. let parentNode = node.parentNode;
  309. refNode = parentNode.childNodes.splice(node.index, 1);
  310. refNode.parentNode = null;
  311. }
  312. if (!refNode) {
  313. throw `NodeNotFound ${node}`;
  314. }
  315. return refNode;
  316. }
  317. /**
  318. * Backend for getNodeBy[attribute]. It is similar to DOM.getElementBy functions
  319. */
  320. _search(term, criteria) {
  321. let results = [];
  322. let searchFn = (candidate) => {
  323. if (criteria) {
  324. switch (criteria) {
  325. case "VALUE":
  326. if (candidate.nodeValue === term) {
  327. results.push(candidate);
  328. }
  329. break;
  330. case "TYPE":
  331. if (candidate.nodeType === term) {
  332. results.push(candidate);
  333. }
  334. break;
  335. case "MODE":
  336. if (candidate.mode === term) {
  337. results.push(candidate);
  338. }
  339. case "LAYOUT":
  340. if (candidate.layout && candidate.layout === term) {
  341. results.push(candidate);
  342. }
  343. }
  344. } else {
  345. if (candidate === term) {
  346. results.push(candidate);
  347. }
  348. }
  349. };
  350. this._walk(searchFn, this._traverseBreadthFirst);
  351. return results;
  352. }
  353. // start walking from root and all child nodes
  354. _traverseBreadthFirst(callback) {
  355. let queue = new Queue();
  356. queue.enqueue(this);
  357. let currentNode = queue.dequeue();
  358. while (currentNode) {
  359. for (let i = 0, length = currentNode.childNodes.length; i < length; i++) {
  360. queue.enqueue(currentNode.childNodes[i]);
  361. }
  362. callback(currentNode);
  363. currentNode = queue.dequeue();
  364. }
  365. }
  366. // start walking from bottom to root
  367. _traverseDepthFirst(callback) {
  368. let recurse = (currentNode) => {
  369. for (let i = 0, length = currentNode.childNodes.length; i < length; i++) {
  370. recurse(currentNode.childNodes[i]);
  371. }
  372. callback(currentNode);
  373. };
  374. recurse(this);
  375. }
  376. _walk(callback, traversal) {
  377. traversal.call(this, callback);
  378. }
  379. _initMetaWindow() {
  380. if (this.isWindow()) {
  381. let windowTracker = Shell.WindowTracker.get_default();
  382. let metaWin = this.nodeValue;
  383. let app = windowTracker.get_window_app(metaWin);
  384. this.app = app;
  385. }
  386. }
  387. _createWindowTab() {
  388. if (this.tab || !this.isWindow()) return;
  389. let tabContents = new St.BoxLayout({
  390. style_class: "window-tabbed-tab",
  391. x_expand: true,
  392. });
  393. let app = this.app;
  394. let labelText = this._getTitle();
  395. let metaWin = this.nodeValue;
  396. let titleButton = new St.Button({
  397. x_expand: true,
  398. label: `${labelText}`,
  399. });
  400. let iconBin = new St.Button({
  401. style_class: "window-tabbed-tab-icon",
  402. });
  403. let icon = app.create_icon_texture(24 * Utils.dpi());
  404. iconBin.child = icon;
  405. let closeButton = new St.Button({
  406. style_class: "window-tabbed-tab-close",
  407. child: new St.Icon({ icon_name: "window-close-symbolic" }),
  408. });
  409. tabContents.add_child(iconBin);
  410. tabContents.add_child(titleButton);
  411. tabContents.add_child(closeButton);
  412. let clickFn = () => {
  413. this.parentNode.childNodes.forEach((c) => {
  414. if (c.tab) {
  415. c.tab.remove_style_class_name("window-tabbed-tab-active");
  416. c.render();
  417. }
  418. });
  419. tabContents.add_style_class_name("window-tabbed-tab-active");
  420. metaWin.activate(global.display.get_current_time());
  421. };
  422. let closeFn = () => {
  423. metaWin.delete(global.get_current_time());
  424. };
  425. let middleClickCloseFn = (_, event) => {
  426. if (event.get_button() === Clutter.BUTTON_MIDDLE) {
  427. metaWin.delete(global.get_current_time());
  428. }
  429. };
  430. iconBin.connect("clicked", clickFn);
  431. iconBin.connect("button-release-event", middleClickCloseFn);
  432. titleButton.connect("clicked", clickFn);
  433. titleButton.connect("button-release-event", middleClickCloseFn);
  434. closeButton.connect("clicked", closeFn);
  435. closeButton.connect("button-release-event", middleClickCloseFn);
  436. if (metaWin === global.display.get_focus_window()) {
  437. tabContents.add_style_class_name("window-tabbed-tab-active");
  438. }
  439. this.tab = tabContents;
  440. }
  441. _createDecoration() {
  442. if (this.decoration) return;
  443. let decoration = new St.BoxLayout();
  444. decoration.type = "forge-deco";
  445. decoration.parentNode = this;
  446. let globalWinGrp = global.window_group;
  447. decoration.style_class = "window-tabbed-bg";
  448. if (!globalWinGrp.contains(decoration)) {
  449. globalWinGrp.add_child(decoration);
  450. }
  451. decoration.hide();
  452. this.decoration = decoration;
  453. }
  454. _getTitle() {
  455. if (this.isWindow()) {
  456. return this.nodeValue.title ? this.nodeValue.title : this.app.get_name();
  457. }
  458. return null;
  459. }
  460. render() {
  461. // Always update the title for the tab
  462. if (this.tab !== null && this.tab !== undefined) {
  463. let titleLabel = this.tab.get_child_at_index(1);
  464. if (titleLabel) titleLabel.label = this._getTitle();
  465. }
  466. }
  467. set float(value) {
  468. if (this.isWindow()) {
  469. let metaWindow = this.nodeValue;
  470. let floatAlwaysOnTop = this.settings.get_boolean("float-always-on-top-enabled");
  471. if (value) {
  472. this.mode = Window.WINDOW_MODES.FLOAT;
  473. if (!metaWindow.is_above()) {
  474. floatAlwaysOnTop && metaWindow.make_above();
  475. }
  476. } else {
  477. this.mode = Window.WINDOW_MODES.TILE;
  478. if (metaWindow.is_above()) {
  479. metaWindow.unmake_above();
  480. }
  481. }
  482. }
  483. }
  484. set tile(value) {
  485. this.float = !value;
  486. }
  487. resetLayoutSingleChild() {
  488. let tabbedOrStacked = this.isTabbed() || this.isStacked();
  489. if (tabbedOrStacked && this.singleOrNoChild()) {
  490. this.layout = LAYOUT_TYPES.HSPLIT;
  491. }
  492. }
  493. singleOrNoChild() {
  494. return this.childNodes.length <= 1;
  495. }
  496. }
  497. /**
  498. * An implementation of Queue using arrays
  499. */
  500. export class Queue extends GObject.Object {
  501. static {
  502. GObject.registerClass(this);
  503. }
  504. constructor() {
  505. super();
  506. this._elements = [];
  507. }
  508. get length() {
  509. return this._elements.length;
  510. }
  511. enqueue(item) {
  512. this._elements.push(item);
  513. }
  514. dequeue() {
  515. return this._elements.shift();
  516. }
  517. }
  518. export class Tree extends Node {
  519. static {
  520. GObject.registerClass(this);
  521. }
  522. /** @param {Window.WindowManager} extWm */
  523. constructor(extWm) {
  524. let rootBin = new St.Bin();
  525. super(NODE_TYPES.ROOT, rootBin);
  526. this._extWm = extWm;
  527. this.defaultStackHeight = 35;
  528. this.settings = this.extWm.ext.settings;
  529. this.layout = LAYOUT_TYPES.ROOT;
  530. if (!global.window_group.contains(rootBin)) global.window_group.add_child(rootBin);
  531. this._initWorkspaces();
  532. }
  533. /** @type {Window.WindowManager} */
  534. get extWm() {
  535. return this._extWm;
  536. }
  537. /**
  538. * Handles new and existing workspaces in the tree
  539. */
  540. _initWorkspaces() {
  541. let wsManager = global.display.get_workspace_manager();
  542. let workspaces = wsManager.get_n_workspaces();
  543. for (let i = 0; i < workspaces; i++) {
  544. this.addWorkspace(i);
  545. }
  546. }
  547. // TODO move to monitor.js
  548. addMonitor(wsIndex) {
  549. let monitors = global.display.get_n_monitors();
  550. for (let mi = 0; mi < monitors; mi++) {
  551. let monitorWsNode = this.createNode(
  552. `ws${wsIndex}`,
  553. NODE_TYPES.MONITOR,
  554. `mo${mi}ws${wsIndex}`
  555. );
  556. monitorWsNode.layout = this.extWm.determineSplitLayout();
  557. monitorWsNode.actorBin = new St.Bin();
  558. if (!global.window_group.contains(monitorWsNode.actorBin))
  559. global.window_group.add_child(monitorWsNode.actorBin);
  560. }
  561. }
  562. // TODO move to workspace.js
  563. addWorkspace(wsIndex) {
  564. let wsManager = global.display.get_workspace_manager();
  565. let workspaceNodeValue = `ws${wsIndex}`;
  566. let existingWsNode = this.findNode(workspaceNodeValue);
  567. if (existingWsNode) {
  568. return false;
  569. }
  570. let newWsNode = this.createNode(this.nodeValue, NODE_TYPES.WORKSPACE, workspaceNodeValue);
  571. let workspace = wsManager.get_workspace_by_index(wsIndex);
  572. newWsNode.layout = LAYOUT_TYPES.HSPLIT;
  573. newWsNode.actorBin = new St.Bin({ style_class: "workspace-actor-bg" });
  574. if (!global.window_group.contains(newWsNode.actorBin))
  575. global.window_group.add_child(newWsNode.actorBin);
  576. this.extWm.bindWorkspaceSignals(workspace);
  577. this.addMonitor(wsIndex);
  578. return true;
  579. }
  580. // TODO move to workspace.js
  581. removeWorkspace(wsIndex) {
  582. let workspaceNodeData = `ws${wsIndex}`;
  583. let existingWsNode = this.findNode(workspaceNodeData);
  584. if (!existingWsNode) {
  585. return false;
  586. }
  587. if (global.window_group.contains(existingWsNode.actorBin))
  588. global.window_group.remove_child(existingWsNode.actorBin);
  589. this.removeChild(existingWsNode);
  590. return true;
  591. }
  592. get nodeWorkpaces() {
  593. let nodeWorkspaces = this.getNodeByType(NODE_TYPES.WORKSPACE);
  594. return nodeWorkspaces;
  595. }
  596. get nodeWindows() {
  597. let nodeWindows = this.getNodeByType(NODE_TYPES.WINDOW);
  598. return nodeWindows;
  599. }
  600. /**
  601. * Creates a new Node and attaches it to a parent toData.
  602. * Parent can be MONITOR or CON types only.
  603. */
  604. createNode(parentObj, type, value, mode = Window.WINDOW_MODES.TILE) {
  605. let parentNode = this.findNode(parentObj);
  606. let child;
  607. if (parentNode) {
  608. child = new Node(type, value);
  609. child.settings = this.settings;
  610. if (child.isWindow()) child.mode = mode;
  611. // Append after a window
  612. if (parentNode.isWindow()) {
  613. const grandParentNode = parentNode.parentNode;
  614. grandParentNode.insertBefore(child, parentNode.nextSibling);
  615. Logger.debug(
  616. `Parent is a window, attaching to this window's parent ${grandParentNode.nodeType}`
  617. );
  618. } else {
  619. // Append as the last item of the container
  620. parentNode.appendChild(child);
  621. }
  622. }
  623. return child;
  624. }
  625. /**
  626. * Finds any Node in the tree using data
  627. * Data types can be in the form of Meta.Window or unique id strings
  628. * for Workspace, Monitor and Container
  629. *
  630. * Workspace id strings takes the form `ws{n}`.
  631. * Monitor id strings takes the form `mo{m}ws{n}`
  632. * Container id strings takes the form `mo{m}ws{n}c{x}`
  633. *
  634. */
  635. findNode(data) {
  636. let searchNode = this.getNodeByValue(data);
  637. return searchNode;
  638. }
  639. /**
  640. * Find the NodeWindow using the Meta.WindowActor
  641. */
  642. findNodeByActor(windowActor) {
  643. let searchNode;
  644. let criteriaMatchFn = (node) => {
  645. if (node.isWindow() && node.actor === windowActor) {
  646. searchNode = node;
  647. }
  648. };
  649. this._walk(criteriaMatchFn, this._traverseDepthFirst);
  650. return searchNode;
  651. }
  652. /**
  653. * Focuses on the next node, if metaWindow and tiled, raise it
  654. */
  655. focus(node, direction) {
  656. if (!node) return null;
  657. let next = this.next(node, direction);
  658. if (!next) return null;
  659. let type = next.nodeType;
  660. let position = Utils.positionFromDirection(direction);
  661. const previous = position === POSITION.BEFORE;
  662. switch (type) {
  663. case NODE_TYPES.WINDOW:
  664. break;
  665. case NODE_TYPES.CON:
  666. const tiledConWindows = next.getNodeByType(NODE_TYPES.WINDOW).filter((w) => w.isTile());
  667. if (next.layout === LAYOUT_TYPES.STACKED) {
  668. next = next.lastChild;
  669. } else {
  670. if (tiledConWindows.length > 1) {
  671. if (previous) {
  672. next = tiledConWindows[tiledConWindows.length - 1];
  673. } else {
  674. next = tiledConWindows[0];
  675. }
  676. } else {
  677. next = tiledConWindows[0];
  678. }
  679. }
  680. break;
  681. case NODE_TYPES.MONITOR:
  682. if (next.layout === LAYOUT_TYPES.STACKED) {
  683. next = next.lastChild;
  684. } else {
  685. if (previous) {
  686. next = next.lastChild;
  687. } else {
  688. next = next.firstChild;
  689. }
  690. }
  691. if (next && next.nodeType === NODE_TYPES.CON) {
  692. const tiledConWindows = next.getNodeByType(NODE_TYPES.WINDOW).filter((w) => w.isTile());
  693. if (next.layout === LAYOUT_TYPES.STACKED) {
  694. next = next.lastChild;
  695. } else {
  696. if (tiledConWindows.length > 1) {
  697. if (previous) {
  698. next = tiledConWindows[tiledConWindows.length - 1];
  699. } else {
  700. next = tiledConWindows[0];
  701. }
  702. } else {
  703. next = tiledConWindows[0];
  704. }
  705. }
  706. }
  707. break;
  708. }
  709. if (!next) return null;
  710. let metaWindow = next.nodeValue;
  711. if (!metaWindow) return null;
  712. const previousMetaWindow = this.extWm.focusMetaWindow;
  713. if (metaWindow.minimized) {
  714. next = this.focus(next, direction);
  715. } else {
  716. metaWindow.raise();
  717. metaWindow.focus(global.display.get_current_time());
  718. metaWindow.activate(global.display.get_current_time());
  719. const monitorArea = metaWindow.get_work_area_current_monitor();
  720. const ptr = this.extWm.getPointer();
  721. const pointerInside = Utils.rectContainsPoint(monitorArea, [ptr[0], ptr[1]]);
  722. const monitorChanged =
  723. !!previousMetaWindow &&
  724. previousMetaWindow.get_monitor &&
  725. previousMetaWindow.get_monitor() !== metaWindow.get_monitor();
  726. if (this.settings.get_boolean("move-pointer-focus-enabled")) {
  727. this.extWm.movePointerWith(next);
  728. } else if (!pointerInside) {
  729. this.extWm.movePointerWith(next, { force: monitorChanged });
  730. }
  731. }
  732. return next;
  733. }
  734. /**
  735. * Obtains the non-floating, non-minimized list of nodes
  736. * Useful for calculating the rect areas
  737. */
  738. getTiledChildren(items) {
  739. let filterFn = (node) => {
  740. if (node.isWindow()) {
  741. let floating = node.isFloat();
  742. let grabTiling = node.isGrabTile();
  743. // A Node[Window]._data is a Meta.Window
  744. if (!node.nodeValue.minimized && !(floating || grabTiling)) {
  745. return true;
  746. }
  747. }
  748. // handle split containers
  749. if (node.isCon()) {
  750. return this.getTiledChildren(node.childNodes).length > 0;
  751. }
  752. return false;
  753. };
  754. return items ? items.filter(filterFn) : [];
  755. }
  756. /**
  757. * Move a given node into a direction
  758. *
  759. * TODO, handle minimized or floating windows
  760. *
  761. */
  762. move(node, direction) {
  763. let next = this.next(node, direction);
  764. let position = Utils.positionFromDirection(direction);
  765. if (!next || next === -1) {
  766. if (next === -1) {
  767. // TODO - update appending or prepending on the same monitor
  768. const currMonWsNode = this.extWm.currentMonWsNode;
  769. if (currMonWsNode) {
  770. if (position === POSITION.AFTER) {
  771. currMonWsNode.appendChild(node);
  772. } else {
  773. currMonWsNode.insertBefore(node, next.firstChild);
  774. }
  775. return true;
  776. }
  777. }
  778. return false;
  779. }
  780. let parentNode = node.parentNode;
  781. let parentTarget;
  782. switch (next.nodeType) {
  783. case NODE_TYPES.WINDOW:
  784. // If same parent, swap
  785. if (next === node.previousSibling || next === node.nextSibling) {
  786. parentTarget = next.parentNode;
  787. this.swapPairs(node, next);
  788. if (this.settings.get_boolean("move-pointer-focus-enabled")) {
  789. this.extWm.movePointerWith(node);
  790. }
  791. // do not reset percent when swapped
  792. return true;
  793. } else {
  794. parentTarget = next.parentNode;
  795. if (parentTarget) {
  796. if (position === POSITION.AFTER) {
  797. parentTarget.insertBefore(node, next);
  798. } else {
  799. parentTarget.insertBefore(node, next.nextSibling);
  800. }
  801. }
  802. }
  803. break;
  804. case NODE_TYPES.CON:
  805. parentTarget = next;
  806. if (next.isStacked()) {
  807. next.appendChild(node);
  808. } else {
  809. if (position === POSITION.AFTER) {
  810. next.insertBefore(node, next.firstChild);
  811. } else {
  812. next.appendChild(node);
  813. }
  814. }
  815. break;
  816. case NODE_TYPES.MONITOR:
  817. parentTarget = next;
  818. const currMonWsNode = this.extWm.currentMonWsNode;
  819. if (
  820. !next.contains(node) &&
  821. (node === currMonWsNode.firstChild || node === currMonWsNode.lastChild)
  822. ) {
  823. let targetMonRect = this.extWm.rectForMonitor(node, Utils.monitorIndex(next.nodeValue));
  824. if (!targetMonRect) return false;
  825. if (position === POSITION.AFTER) {
  826. next.insertBefore(node, next.firstChild);
  827. } else {
  828. next.appendChild(node);
  829. }
  830. let rect = targetMonRect;
  831. this.extWm.move(node.nodeValue, rect);
  832. this.extWm.movePointerWith(node);
  833. } else {
  834. if (position === POSITION.AFTER) {
  835. currMonWsNode.appendChild(node);
  836. } else {
  837. currMonWsNode.insertBefore(node, currMonWsNode.firstChild);
  838. }
  839. }
  840. break;
  841. default:
  842. break;
  843. }
  844. this.resetSiblingPercent(parentNode);
  845. this.resetSiblingPercent(parentTarget);
  846. parentNode.resetLayoutSingleChild();
  847. return true;
  848. }
  849. /**
  850. * Give the next sibling/parent/descendant on the tree based
  851. * on a given Meta.MotionDirection
  852. *
  853. * @param {Node} node
  854. * @param {Meta.MotionDirection} direction
  855. *
  856. * Credits: borrowed logic from tree.c of i3
  857. */
  858. next(node, direction) {
  859. if (!node) return null;
  860. let orientation = Utils.orientationFromDirection(direction);
  861. let position = Utils.positionFromDirection(direction);
  862. let previous = position === POSITION.BEFORE;
  863. const type = node.nodeType;
  864. switch (type) {
  865. case NODE_TYPES.ROOT:
  866. // Root is the top of the tree
  867. if (node.childNodes.length > 1) {
  868. if (previous) {
  869. return node.firstChild;
  870. } else {
  871. return node.lastChild;
  872. }
  873. } else {
  874. return node.firstChild;
  875. }
  876. case NODE_TYPES.WORKSPACE:
  877. // Let gnome-shell handle this?
  878. break;
  879. case NODE_TYPES.MONITOR:
  880. // Find the next monitor
  881. const nodeWindow = this.findFirstNodeWindowFrom(node);
  882. return this.nextMonitor(nodeWindow, position, orientation);
  883. }
  884. while (node.nodeType !== NODE_TYPES.WORKSPACE) {
  885. if (node.nodeType === NODE_TYPES.MONITOR) {
  886. return this.next(node, direction);
  887. }
  888. const parentNode = node.parentNode;
  889. const parentOrientation = Utils.orientationFromLayout(parentNode.layout);
  890. if (parentNode.childNodes.length > 1 && orientation === parentOrientation) {
  891. const next = previous ? node.previousSibling : node.nextSibling;
  892. if (next) {
  893. return next;
  894. }
  895. }
  896. node = node.parentNode;
  897. }
  898. }
  899. nextMonitor(nodeWindow, position, orientation) {
  900. if (!nodeWindow) return null;
  901. // Use the built in logic to determine adjacent monitors
  902. let monitorNode = null;
  903. let monitorDirection = Utils.directionFrom(position, orientation);
  904. let targetMonitor = -1;
  905. targetMonitor = global.display.get_monitor_neighbor_index(
  906. nodeWindow.nodeValue.get_monitor(),
  907. monitorDirection
  908. );
  909. if (targetMonitor < 0) return targetMonitor;
  910. let monWs = `mo${targetMonitor}ws${nodeWindow.nodeValue.get_workspace().index()}`;
  911. monitorNode = this.findNode(monWs);
  912. return monitorNode;
  913. }
  914. findAncestorMonitor(node) {
  915. return this.findAncestor(node, NODE_TYPES.MONITOR);
  916. }
  917. findAncestor(node, ancestorType) {
  918. let ancestorNode;
  919. while (node && ancestorType && !node.isRoot()) {
  920. if (node.isType(ancestorType)) {
  921. ancestorNode = node;
  922. break;
  923. } else {
  924. node = node.parentNode;
  925. }
  926. }
  927. return ancestorNode;
  928. }
  929. nextVisible(node, direction) {
  930. if (!node) return null;
  931. let next = this.next(node, direction);
  932. if (next && next.nodeType === NODE_TYPES.WINDOW && next.nodeValue && next.nodeValue.minimized) {
  933. next = this.nextVisible(next, direction);
  934. }
  935. return next;
  936. }
  937. /**
  938. * Credits: i3-like split
  939. */
  940. split(node, orientation, forceSplit = false) {
  941. if (!node) return;
  942. let type = node.nodeType;
  943. if (type === NODE_TYPES.WINDOW && node.mode === Window.WINDOW_MODES.FLOAT) {
  944. return;
  945. }
  946. if (!(type === NODE_TYPES.MONITOR || type === NODE_TYPES.CON || type === NODE_TYPES.WINDOW)) {
  947. return;
  948. }
  949. let parentNode = node.parentNode;
  950. let numChildren = parentNode.childNodes.length;
  951. // toggle the split
  952. if (
  953. !forceSplit &&
  954. numChildren === 1 &&
  955. (parentNode.layout === LAYOUT_TYPES.HSPLIT || parentNode.layout === LAYOUT_TYPES.VSPLIT)
  956. ) {
  957. parentNode.layout =
  958. orientation === ORIENTATION_TYPES.HORIZONTAL ? LAYOUT_TYPES.HSPLIT : LAYOUT_TYPES.VSPLIT;
  959. this.attachNode = parentNode;
  960. return;
  961. }
  962. // Push down the Meta.Window into a new Container
  963. let currentIndex = node.index;
  964. let container = new St.Bin();
  965. let newConNode = new Node(NODE_TYPES.CON, container);
  966. newConNode.settings = this.settings;
  967. // Take the direction of the parent
  968. newConNode.layout =
  969. orientation === ORIENTATION_TYPES.HORIZONTAL ? LAYOUT_TYPES.HSPLIT : LAYOUT_TYPES.VSPLIT;
  970. newConNode.rect = node.rect;
  971. newConNode.percent = node.percent;
  972. newConNode.parentNode = parentNode;
  973. parentNode.childNodes[currentIndex] = newConNode;
  974. this.createNode(container, node.nodeType, node.nodeValue);
  975. node.parentNode = newConNode;
  976. this.attachNode = newConNode;
  977. }
  978. swap(node, direction) {
  979. let nextSwapNode = this.next(node, direction);
  980. if (!nextSwapNode) {
  981. return;
  982. }
  983. let nodeSwapType = nextSwapNode.nodeType;
  984. switch (nodeSwapType) {
  985. case NODE_TYPES.WINDOW:
  986. break;
  987. case NODE_TYPES.CON:
  988. case NODE_TYPES.MONITOR:
  989. let childWindowNodes = nextSwapNode
  990. .getNodeByMode(Window.WINDOW_MODES.TILE)
  991. .filter((t) => t.nodeType === NODE_TYPES.WINDOW);
  992. if (nextSwapNode.layout === LAYOUT_TYPES.STACKED) {
  993. nextSwapNode = childWindowNodes[childWindowNodes.length - 1];
  994. } else {
  995. nextSwapNode = childWindowNodes[0];
  996. }
  997. break;
  998. }
  999. let isNextNodeWin =
  1000. nextSwapNode && nextSwapNode.nodeValue && nextSwapNode.nodeType === NODE_TYPES.WINDOW;
  1001. if (isNextNodeWin) {
  1002. if (!this.extWm.sameParentMonitor(node, nextSwapNode)) {
  1003. // TODO, there is a freeze bug if there are not in same monitor.
  1004. return;
  1005. }
  1006. this.swapPairs(node, nextSwapNode);
  1007. }
  1008. return nextSwapNode;
  1009. }
  1010. swapPairs(fromNode, toNode, focus = true) {
  1011. if (!(this._swappable(fromNode) && this._swappable(toNode))) return;
  1012. // Swap the items in the array
  1013. let parentForFrom = fromNode ? fromNode.parentNode : undefined;
  1014. let parentForTo = toNode.parentNode;
  1015. if (parentForTo && parentForFrom) {
  1016. let nextIndex = toNode.index;
  1017. let focusIndex = fromNode.index;
  1018. let transferMode = fromNode.mode;
  1019. fromNode.mode = toNode.mode;
  1020. toNode.mode = transferMode;
  1021. let transferRect = fromNode.nodeValue.get_frame_rect();
  1022. let transferToRect = toNode.nodeValue.get_frame_rect();
  1023. let transferPercent = fromNode.percent;
  1024. fromNode.percent = toNode.percent;
  1025. toNode.percent = transferPercent;
  1026. parentForTo.childNodes[nextIndex] = fromNode;
  1027. fromNode.parentNode = parentForTo;
  1028. parentForFrom.childNodes[focusIndex] = toNode;
  1029. toNode.parentNode = parentForFrom;
  1030. this.extWm.move(fromNode.nodeValue, transferToRect);
  1031. this.extWm.move(toNode.nodeValue, transferRect);
  1032. if (focus) {
  1033. // The fromNode is now on the parent-target
  1034. fromNode.nodeValue.raise();
  1035. fromNode.nodeValue.focus(global.get_current_time());
  1036. }
  1037. }
  1038. }
  1039. _swappable(node) {
  1040. if (!node) return false;
  1041. if (node.nodeType === NODE_TYPES.WINDOW && !node.nodeValue.minimized) {
  1042. return true;
  1043. }
  1044. return false;
  1045. }
  1046. /**
  1047. * Performs cleanup of dangling parents in addition to removing the
  1048. * node from the parent.
  1049. */
  1050. removeNode(node) {
  1051. let oldChild;
  1052. let cleanUpParent = (existParent) => {
  1053. if (this.getTiledChildren(existParent.childNodes).length === 0) {
  1054. existParent.percent = 0.0;
  1055. this.resetSiblingPercent(existParent.parentNode);
  1056. }
  1057. this.resetSiblingPercent(existParent);
  1058. };
  1059. let parentNode = node.parentNode;
  1060. // If parent has only this window, remove the parent instead
  1061. if (parentNode.childNodes.length === 1 && parentNode.nodeType !== NODE_TYPES.MONITOR) {
  1062. let existParent = parentNode.parentNode;
  1063. oldChild = existParent.removeChild(parentNode);
  1064. cleanUpParent(existParent);
  1065. } else {
  1066. let existParent = node.parentNode;
  1067. oldChild = existParent.removeChild(node);
  1068. if (!this.extWm.floatingWindow(node)) cleanUpParent(existParent);
  1069. }
  1070. // If only a single tab remains, exit tabbed layout
  1071. if (
  1072. this.settings.get_boolean("auto-exit-tabbed") &&
  1073. parentNode.nodeType === NODE_TYPES.CON &&
  1074. parentNode.layout === LAYOUT_TYPES.TABBED &&
  1075. parentNode.childNodes.length === 1
  1076. ) {
  1077. parentNode.layout = this.extWm.determineSplitLayout();
  1078. this.resetSiblingPercent(parentNode);
  1079. parentNode.lastTabFocus = null;
  1080. }
  1081. if (node === this.attachNode) {
  1082. this.attachNode = null;
  1083. } else {
  1084. // Find the next focus node as attachNode
  1085. this.attachNode = this.findNode(this.extWm.focusMetaWindow);
  1086. }
  1087. return oldChild ? true : false;
  1088. }
  1089. render(from) {
  1090. Logger.debug(`render tree ${from ? "from " + from : ""}`);
  1091. this.processNode(this);
  1092. this.apply(this);
  1093. this.cleanTree();
  1094. let debugMode = true;
  1095. if (debugMode) {
  1096. this.debugTree();
  1097. }
  1098. Logger.debug(`*********************************************`);
  1099. }
  1100. apply(node) {
  1101. if (!node) return;
  1102. let tiledChildren = node
  1103. .getNodeByMode(Window.WINDOW_MODES.TILE)
  1104. .filter((t) => t.nodeType === NODE_TYPES.WINDOW);
  1105. tiledChildren.forEach((w) => {
  1106. if (w.renderRect) {
  1107. if (w.renderRect.width > 0 && w.renderRect.height > 0) {
  1108. let metaWin = w.nodeValue;
  1109. this.extWm.move(metaWin, w.renderRect);
  1110. } else {
  1111. Logger.debug(`ignoring apply for ${w.renderRect.width}x${w.renderRect.height}`);
  1112. }
  1113. }
  1114. if (w.nodeValue.firstRender) w.nodeValue.firstRender = false;
  1115. });
  1116. }
  1117. cleanTree() {
  1118. // Phase 1: remove any cons with empty children
  1119. const orphanCons = this.getNodeByType(NODE_TYPES.CON).filter((c) => c.childNodes.length === 0);
  1120. const hasOrphanCons = orphanCons.length > 0;
  1121. orphanCons.forEach((o) => {
  1122. this.removeNode(o);
  1123. });
  1124. const invalidWindows = this.getNodeByType(NODE_TYPES.WINDOW).filter((w) => {
  1125. const metaWindow = w.nodeValue;
  1126. const title = metaWindow.title;
  1127. const wmClass = metaWindow.wm_class;
  1128. return wmClass === "gjs";
  1129. });
  1130. invalidWindows.forEach((w) => {
  1131. this.removeNode(w);
  1132. });
  1133. // Phase 2: remove any empty parent cons up to the single intermediate parent-window level
  1134. // Basically, flatten them?
  1135. // [con[con[con[con[window]]]]] --> [con[window]]
  1136. // TODO: help :)
  1137. const grandParentCons = this.getNodeByType(NODE_TYPES.CON).filter(
  1138. (c) => c.childNodes.length === 1 && c.childNodes[0].nodeType === NODE_TYPES.CON
  1139. );
  1140. grandParentCons.forEach((c) => {
  1141. c.layout = LAYOUT_TYPES.HSPLIT;
  1142. });
  1143. if (hasOrphanCons || invalidWindows.length > 0) {
  1144. this.processNode(this);
  1145. this.apply(this);
  1146. }
  1147. }
  1148. /**
  1149. *
  1150. * Credits: Do the i3-like calculations
  1151. *
  1152. */
  1153. processNode(node) {
  1154. if (!node) return;
  1155. // Render the Root, Workspace and Monitor
  1156. // For now, we let them render their children recursively
  1157. if (node.nodeType === NODE_TYPES.ROOT) {
  1158. node.childNodes.forEach((child) => {
  1159. this.processNode(child);
  1160. });
  1161. }
  1162. if (node.nodeType === NODE_TYPES.WORKSPACE) {
  1163. node.childNodes.forEach((child) => {
  1164. this.processNode(child);
  1165. });
  1166. }
  1167. let params = {};
  1168. if (node.nodeType === NODE_TYPES.MONITOR || node.nodeType === NODE_TYPES.CON) {
  1169. // The workarea from Meta.Window's assigned monitor
  1170. // is important so it computes to `remove` the panel size
  1171. // really well. However, this type of workarea would only
  1172. // appear if there is window present on the monitor.
  1173. if (node.childNodes.length === 0) {
  1174. return;
  1175. }
  1176. // If monitor, get the workarea
  1177. if (node.nodeType === NODE_TYPES.MONITOR) {
  1178. let monitorIndex = Utils.monitorIndex(node.nodeValue);
  1179. let monitorArea = global.display
  1180. .get_workspace_manager()
  1181. .get_active_workspace()
  1182. .get_work_area_for_monitor(monitorIndex);
  1183. if (!monitorArea) return; // there is no visible child window
  1184. node.rect = monitorArea;
  1185. node.rect = this.processGap(node);
  1186. }
  1187. let tiledChildren = this.getTiledChildren(node.childNodes);
  1188. let sizes = this.computeSizes(node, tiledChildren);
  1189. params.sizes = sizes;
  1190. let showTabs = this.settings.get_boolean("showtab-decoration-enabled");
  1191. params.stackedHeight = showTabs ? this.defaultStackHeight * Utils.dpi() : 0;
  1192. params.tiledChildren = tiledChildren;
  1193. let decoration = node.decoration;
  1194. if (decoration) {
  1195. let decoChildren = decoration.get_children();
  1196. decoChildren.forEach((decoChild) => {
  1197. decoration.remove_child(decoChild);
  1198. });
  1199. }
  1200. tiledChildren.forEach((child, index) => {
  1201. // A monitor can contain a window or container child
  1202. if (node.layout === LAYOUT_TYPES.HSPLIT || node.layout === LAYOUT_TYPES.VSPLIT) {
  1203. this.processSplit(node, child, params, index);
  1204. } else if (node.layout === LAYOUT_TYPES.STACKED) {
  1205. this.processStacked(node, child, params, index);
  1206. } else if (node.layout === LAYOUT_TYPES.TABBED) {
  1207. this.processTabbed(node, child, params, index);
  1208. }
  1209. this.processNode(child);
  1210. });
  1211. }
  1212. if (node.isWindow()) {
  1213. if (!node.rect) node.rect = node.nodeValue.get_work_area_current_monitor();
  1214. node.renderRect = this.processGap(node);
  1215. }
  1216. }
  1217. /**
  1218. * Forge processes both non-Window and Window gaps
  1219. */
  1220. processGap(node) {
  1221. let nodeWidth = node.rect.width;
  1222. let nodeHeight = node.rect.height;
  1223. let nodeX = node.rect.x;
  1224. let nodeY = node.rect.y;
  1225. let gap = this.extWm.calculateGaps(node);
  1226. if (nodeWidth > gap * 2 && nodeHeight > gap * 2) {
  1227. nodeX += gap;
  1228. nodeY += gap;
  1229. // TODO - detect inbetween windows and adjust accordingly
  1230. // Also adjust depending on display scaling
  1231. nodeWidth -= gap * 2;
  1232. nodeHeight -= gap * 2;
  1233. }
  1234. return { x: nodeX, y: nodeY, width: nodeWidth, height: nodeHeight };
  1235. }
  1236. processSplit(node, child, params, index) {
  1237. let layout = node.layout;
  1238. let nodeRect = node.rect;
  1239. let nodeWidth;
  1240. let nodeHeight;
  1241. let nodeX;
  1242. let nodeY;
  1243. if (layout === LAYOUT_TYPES.HSPLIT) {
  1244. // Divide the parent container's width
  1245. // depending on number of children. And use this
  1246. // to setup each child window's width.
  1247. nodeWidth = params.sizes[index];
  1248. nodeHeight = nodeRect.height;
  1249. nodeX = nodeRect.x;
  1250. if (index != 0) {
  1251. let i = 1;
  1252. while (i <= index) {
  1253. nodeX += params.sizes[i - 1];
  1254. i++;
  1255. }
  1256. }
  1257. nodeY = nodeRect.y;
  1258. } else if (layout === LAYOUT_TYPES.VSPLIT) {
  1259. // split vertically
  1260. // Conversely for vertical split, divide the parent container's height
  1261. // depending on number of children. And use this
  1262. // to setup each child window's height.
  1263. nodeWidth = nodeRect.width;
  1264. nodeHeight = params.sizes[index];
  1265. nodeX = nodeRect.x;
  1266. nodeY = nodeRect.y;
  1267. if (index != 0) {
  1268. let i = 1;
  1269. while (i <= index) {
  1270. nodeY += params.sizes[i - 1];
  1271. i++;
  1272. }
  1273. }
  1274. }
  1275. child.rect = {
  1276. x: nodeX,
  1277. y: nodeY,
  1278. width: nodeWidth,
  1279. height: nodeHeight,
  1280. };
  1281. }
  1282. /**
  1283. * Process the child node here for the dimensions of the child stack/window,
  1284. * It will be moved to the Node class in the future as Node.render()
  1285. *
  1286. */
  1287. processStacked(node, child, params, index) {
  1288. let layout = node.layout;
  1289. let nodeWidth = node.rect.width;
  1290. let nodeHeight = node.rect.height;
  1291. let nodeX = node.rect.x;
  1292. let nodeY = node.rect.y;
  1293. let stackHeight = this.defaultStackHeight;
  1294. if (layout === LAYOUT_TYPES.STACKED) {
  1295. if (node.childNodes.length > 1) {
  1296. nodeY += stackHeight * index;
  1297. nodeHeight -= stackHeight * index;
  1298. }
  1299. child.rect = {
  1300. x: nodeX,
  1301. y: nodeY,
  1302. width: nodeWidth,
  1303. height: nodeHeight,
  1304. };
  1305. }
  1306. }
  1307. /**
  1308. * Process the child node here for the dimensions of the child tab/window,
  1309. * It will be moved to the Node class in the future as Node.render()
  1310. *
  1311. */
  1312. processTabbed(node, child, params, _index) {
  1313. let layout = node.layout;
  1314. let nodeRect = node.rect;
  1315. let nodeWidth;
  1316. let nodeHeight;
  1317. let nodeX;
  1318. let nodeY;
  1319. if (layout === LAYOUT_TYPES.TABBED) {
  1320. nodeWidth = nodeRect.width;
  1321. nodeX = nodeRect.x;
  1322. nodeY = nodeRect.y;
  1323. nodeHeight = nodeRect.height;
  1324. let alwaysShowDecorationTab = true;
  1325. if (node.childNodes.length > 1 || alwaysShowDecorationTab) {
  1326. nodeY = nodeRect.y + params.stackedHeight;
  1327. nodeHeight = nodeRect.height - params.stackedHeight;
  1328. if (node.decoration && child.isWindow()) {
  1329. let gap = this.extWm.calculateGaps(node);
  1330. let renderRect = this.processGap(node);
  1331. let borderWidth = child.actor.border.get_theme_node().get_border_width(St.Side.TOP);
  1332. // Make adjustments to the gaps
  1333. let adjust = 4 * Utils.dpi();
  1334. let adjustWidth = renderRect.width + (borderWidth * 2 + gap) / adjust;
  1335. let adjustX = renderRect.x - (gap + borderWidth * 2) / (adjust * 2);
  1336. let adjustY = renderRect.y - adjust;
  1337. if (gap === 0) {
  1338. adjustY = renderRect.y;
  1339. }
  1340. let decoration = node.decoration;
  1341. if (decoration !== null && decoration !== undefined) {
  1342. decoration.set_size(adjustWidth, params.stackedHeight);
  1343. decoration.set_position(adjustX, adjustY);
  1344. if (params.tiledChildren.length > 0 && params.stackedHeight !== 0) {
  1345. decoration.show();
  1346. } else {
  1347. decoration.hide();
  1348. }
  1349. if (!decoration.contains(child.tab)) decoration.add_child(child.tab);
  1350. }
  1351. child.render();
  1352. }
  1353. }
  1354. child.rect = {
  1355. x: nodeX,
  1356. y: nodeY,
  1357. width: nodeWidth,
  1358. height: nodeHeight,
  1359. };
  1360. }
  1361. }
  1362. computeSizes(node, childItems) {
  1363. let sizes = [];
  1364. let orientation = Utils.orientationFromLayout(node.layout);
  1365. let totalSize =
  1366. orientation === ORIENTATION_TYPES.HORIZONTAL ? node.rect.width : node.rect.height;
  1367. let grabTiled = node.getNodeByMode(Window.WINDOW_MODES.GRAB_TILE).length > 0;
  1368. childItems.forEach((childNode, index) => {
  1369. let percent =
  1370. childNode.percent && childNode.percent > 0.0 && !grabTiled
  1371. ? childNode.percent
  1372. : 1.0 / childItems.length;
  1373. sizes[index] = Math.floor(percent * totalSize);
  1374. });
  1375. // TODO - make sure the totalSize = the sizes total
  1376. return sizes;
  1377. }
  1378. findFirstNodeWindowFrom(node) {
  1379. let results = node.getNodeByType(NODE_TYPES.WINDOW);
  1380. if (results.length > 0) {
  1381. return results[0];
  1382. }
  1383. return null;
  1384. }
  1385. resetSiblingPercent(parentNode) {
  1386. if (!parentNode) return;
  1387. let children = parentNode.childNodes;
  1388. children.forEach((n) => {
  1389. n.percent = 0.0;
  1390. });
  1391. }
  1392. debugTree() {
  1393. // this.debugChildNodes(this);
  1394. }
  1395. debugChildNodes(node) {
  1396. this.debugNode(this);
  1397. node.childNodes.forEach((child) => {
  1398. this.debugChildNodes(child);
  1399. });
  1400. }
  1401. debugParentNodes(node) {
  1402. if (node) {
  1403. if (node.parentNode) {
  1404. this.debugParentNodes(node.parentNode);
  1405. }
  1406. this.debugNode(node);
  1407. }
  1408. }
  1409. debugNode(node) {
  1410. let spacing = "";
  1411. let dashes = "-->";
  1412. let level = node.level;
  1413. for (let i = 0; i < level; i++) {
  1414. let parentSpacing = i === 0 ? " " : "|";
  1415. spacing += `${parentSpacing} `;
  1416. }
  1417. let rootSpacing = level === 0 ? "#" : "*";
  1418. let attributes = "";
  1419. if (node.isWindow && node.isWindow()) {
  1420. let metaWindow = node.nodeValue;
  1421. attributes += `class:'${metaWindow.get_wm_class()}',title:'${
  1422. metaWindow.title
  1423. }',string:'${metaWindow}'${metaWindow === this.extWm.focusMetaWindow ? " FOCUS" : ""}`;
  1424. } else if (node.isCon() || node.isMonitor() || node.isWorkspace()) {
  1425. attributes += `${node.nodeValue}`;
  1426. if (node.isCon() || node.isMonitor()) {
  1427. attributes += `,layout:${node.layout}`;
  1428. }
  1429. }
  1430. if (node.rect) {
  1431. attributes += `,rect:${node.rect.width}x${node.rect.height}+${node.rect.x}+${node.rect.y}`;
  1432. const pointerCoord = global.get_pointer();
  1433. const pointerInside = Utils.rectContainsPoint(node.rect, pointerCoord) ? "yes" : "no";
  1434. attributes += `,pointer:${pointerInside}`;
  1435. }
  1436. if (level !== 0) Logger.debug(`${spacing}|`);
  1437. Logger.debug(
  1438. `${spacing}${rootSpacing}${dashes} ${node.nodeType}#${
  1439. node.index !== null ? node.index : "-"
  1440. } @${attributes}`
  1441. );
  1442. }
  1443. findParent(childNode, parentNodeType) {
  1444. let parents = this.getNodeByType(parentNodeType);
  1445. // Only get the first parent
  1446. return parents.filter((p) => p.contains(childNode))[0];
  1447. }
  1448. }