tree.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663
  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. if (metaWindow.minimized) {
  713. next = this.focus(next, direction);
  714. } else {
  715. metaWindow.raise();
  716. metaWindow.focus(global.display.get_current_time());
  717. metaWindow.activate(global.display.get_current_time());
  718. if (this.settings.get_boolean("move-pointer-focus-enabled")) {
  719. this.extWm.movePointerWith(next);
  720. } else {
  721. let monitorArea = metaWindow.get_work_area_current_monitor();
  722. let ptr = this.extWm.getPointer();
  723. if (!Utils.rectContainsPoint(monitorArea, [ptr[0], ptr[1]])) {
  724. this.extWm.movePointerWith(next);
  725. }
  726. }
  727. }
  728. return next;
  729. }
  730. /**
  731. * Obtains the non-floating, non-minimized list of nodes
  732. * Useful for calculating the rect areas
  733. */
  734. getTiledChildren(items) {
  735. let filterFn = (node) => {
  736. if (node.isWindow()) {
  737. let floating = node.isFloat();
  738. let grabTiling = node.isGrabTile();
  739. // A Node[Window]._data is a Meta.Window
  740. if (!node.nodeValue.minimized && !(floating || grabTiling)) {
  741. return true;
  742. }
  743. }
  744. // handle split containers
  745. if (node.isCon()) {
  746. return this.getTiledChildren(node.childNodes).length > 0;
  747. }
  748. return false;
  749. };
  750. return items ? items.filter(filterFn) : [];
  751. }
  752. /**
  753. * Move a given node into a direction
  754. *
  755. * TODO, handle minimized or floating windows
  756. *
  757. */
  758. move(node, direction) {
  759. let next = this.next(node, direction);
  760. let position = Utils.positionFromDirection(direction);
  761. if (!next || next === -1) {
  762. if (next === -1) {
  763. // TODO - update appending or prepending on the same monitor
  764. const currMonWsNode = this.extWm.currentMonWsNode;
  765. if (currMonWsNode) {
  766. if (position === POSITION.AFTER) {
  767. currMonWsNode.appendChild(node);
  768. } else {
  769. currMonWsNode.insertBefore(node, next.firstChild);
  770. }
  771. return true;
  772. }
  773. }
  774. return false;
  775. }
  776. let parentNode = node.parentNode;
  777. let parentTarget;
  778. switch (next.nodeType) {
  779. case NODE_TYPES.WINDOW:
  780. // If same parent, swap
  781. if (next === node.previousSibling || next === node.nextSibling) {
  782. parentTarget = next.parentNode;
  783. this.swapPairs(node, next);
  784. if (this.settings.get_boolean("move-pointer-focus-enabled")) {
  785. this.extWm.movePointerWith(node);
  786. }
  787. // do not reset percent when swapped
  788. return true;
  789. } else {
  790. parentTarget = next.parentNode;
  791. if (parentTarget) {
  792. if (position === POSITION.AFTER) {
  793. parentTarget.insertBefore(node, next);
  794. } else {
  795. parentTarget.insertBefore(node, next.nextSibling);
  796. }
  797. }
  798. }
  799. break;
  800. case NODE_TYPES.CON:
  801. parentTarget = next;
  802. if (next.isStacked()) {
  803. next.appendChild(node);
  804. } else {
  805. if (position === POSITION.AFTER) {
  806. next.insertBefore(node, next.firstChild);
  807. } else {
  808. next.appendChild(node);
  809. }
  810. }
  811. break;
  812. case NODE_TYPES.MONITOR:
  813. parentTarget = next;
  814. const currMonWsNode = this.extWm.currentMonWsNode;
  815. if (
  816. !next.contains(node) &&
  817. (node === currMonWsNode.firstChild || node === currMonWsNode.lastChild)
  818. ) {
  819. let targetMonRect = this.extWm.rectForMonitor(node, Utils.monitorIndex(next.nodeValue));
  820. if (!targetMonRect) return false;
  821. if (position === POSITION.AFTER) {
  822. next.insertBefore(node, next.firstChild);
  823. } else {
  824. next.appendChild(node);
  825. }
  826. let rect = targetMonRect;
  827. this.extWm.move(node.nodeValue, rect);
  828. this.extWm.movePointerWith(node);
  829. } else {
  830. if (position === POSITION.AFTER) {
  831. currMonWsNode.appendChild(node);
  832. } else {
  833. currMonWsNode.insertBefore(node, currMonWsNode.firstChild);
  834. }
  835. }
  836. break;
  837. default:
  838. break;
  839. }
  840. this.resetSiblingPercent(parentNode);
  841. this.resetSiblingPercent(parentTarget);
  842. parentNode.resetLayoutSingleChild();
  843. return true;
  844. }
  845. /**
  846. * Give the next sibling/parent/descendant on the tree based
  847. * on a given Meta.MotionDirection
  848. *
  849. * @param {Node} node
  850. * @param {Meta.MotionDirection} direction
  851. *
  852. * Credits: borrowed logic from tree.c of i3
  853. */
  854. next(node, direction) {
  855. if (!node) return null;
  856. let orientation = Utils.orientationFromDirection(direction);
  857. let position = Utils.positionFromDirection(direction);
  858. let previous = position === POSITION.BEFORE;
  859. const type = node.nodeType;
  860. switch (type) {
  861. case NODE_TYPES.ROOT:
  862. // Root is the top of the tree
  863. if (node.childNodes.length > 1) {
  864. if (previous) {
  865. return node.firstChild;
  866. } else {
  867. return node.lastChild;
  868. }
  869. } else {
  870. return node.firstChild;
  871. }
  872. case NODE_TYPES.WORKSPACE:
  873. // Let gnome-shell handle this?
  874. break;
  875. case NODE_TYPES.MONITOR:
  876. // Find the next monitor
  877. const nodeWindow = this.findFirstNodeWindowFrom(node);
  878. return this.nextMonitor(nodeWindow, position, orientation);
  879. }
  880. while (node.nodeType !== NODE_TYPES.WORKSPACE) {
  881. if (node.nodeType === NODE_TYPES.MONITOR) {
  882. return this.next(node, direction);
  883. }
  884. const parentNode = node.parentNode;
  885. const parentOrientation = Utils.orientationFromLayout(parentNode.layout);
  886. if (parentNode.childNodes.length > 1 && orientation === parentOrientation) {
  887. const next = previous ? node.previousSibling : node.nextSibling;
  888. if (next) {
  889. return next;
  890. }
  891. }
  892. node = node.parentNode;
  893. }
  894. }
  895. nextMonitor(nodeWindow, position, orientation) {
  896. if (!nodeWindow) return null;
  897. // Use the built in logic to determine adjacent monitors
  898. let monitorNode = null;
  899. let monitorDirection = Utils.directionFrom(position, orientation);
  900. let targetMonitor = -1;
  901. targetMonitor = global.display.get_monitor_neighbor_index(
  902. nodeWindow.nodeValue.get_monitor(),
  903. monitorDirection
  904. );
  905. if (targetMonitor < 0) return targetMonitor;
  906. let monWs = `mo${targetMonitor}ws${nodeWindow.nodeValue.get_workspace().index()}`;
  907. monitorNode = this.findNode(monWs);
  908. return monitorNode;
  909. }
  910. findAncestorMonitor(node) {
  911. return this.findAncestor(node, NODE_TYPES.MONITOR);
  912. }
  913. findAncestor(node, ancestorType) {
  914. let ancestorNode;
  915. while (node && ancestorType && !node.isRoot()) {
  916. if (node.isType(ancestorType)) {
  917. ancestorNode = node;
  918. break;
  919. } else {
  920. node = node.parentNode;
  921. }
  922. }
  923. return ancestorNode;
  924. }
  925. nextVisible(node, direction) {
  926. if (!node) return null;
  927. let next = this.next(node, direction);
  928. if (next && next.nodeType === NODE_TYPES.WINDOW && next.nodeValue && next.nodeValue.minimized) {
  929. next = this.nextVisible(next, direction);
  930. }
  931. return next;
  932. }
  933. /**
  934. * Credits: i3-like split
  935. */
  936. split(node, orientation, forceSplit = false) {
  937. if (!node) return;
  938. let type = node.nodeType;
  939. if (type === NODE_TYPES.WINDOW && node.mode === Window.WINDOW_MODES.FLOAT) {
  940. return;
  941. }
  942. if (!(type === NODE_TYPES.MONITOR || type === NODE_TYPES.CON || type === NODE_TYPES.WINDOW)) {
  943. return;
  944. }
  945. let parentNode = node.parentNode;
  946. let numChildren = parentNode.childNodes.length;
  947. // toggle the split
  948. if (
  949. !forceSplit &&
  950. numChildren === 1 &&
  951. (parentNode.layout === LAYOUT_TYPES.HSPLIT || parentNode.layout === LAYOUT_TYPES.VSPLIT)
  952. ) {
  953. parentNode.layout =
  954. orientation === ORIENTATION_TYPES.HORIZONTAL ? LAYOUT_TYPES.HSPLIT : LAYOUT_TYPES.VSPLIT;
  955. this.attachNode = parentNode;
  956. return;
  957. }
  958. // Push down the Meta.Window into a new Container
  959. let currentIndex = node.index;
  960. let container = new St.Bin();
  961. let newConNode = new Node(NODE_TYPES.CON, container);
  962. newConNode.settings = this.settings;
  963. // Take the direction of the parent
  964. newConNode.layout =
  965. orientation === ORIENTATION_TYPES.HORIZONTAL ? LAYOUT_TYPES.HSPLIT : LAYOUT_TYPES.VSPLIT;
  966. newConNode.rect = node.rect;
  967. newConNode.percent = node.percent;
  968. newConNode.parentNode = parentNode;
  969. parentNode.childNodes[currentIndex] = newConNode;
  970. this.createNode(container, node.nodeType, node.nodeValue);
  971. node.parentNode = newConNode;
  972. this.attachNode = newConNode;
  973. }
  974. swap(node, direction) {
  975. let nextSwapNode = this.next(node, direction);
  976. if (!nextSwapNode) {
  977. return;
  978. }
  979. let nodeSwapType = nextSwapNode.nodeType;
  980. switch (nodeSwapType) {
  981. case NODE_TYPES.WINDOW:
  982. break;
  983. case NODE_TYPES.CON:
  984. case NODE_TYPES.MONITOR:
  985. let childWindowNodes = nextSwapNode
  986. .getNodeByMode(Window.WINDOW_MODES.TILE)
  987. .filter((t) => t.nodeType === NODE_TYPES.WINDOW);
  988. if (nextSwapNode.layout === LAYOUT_TYPES.STACKED) {
  989. nextSwapNode = childWindowNodes[childWindowNodes.length - 1];
  990. } else {
  991. nextSwapNode = childWindowNodes[0];
  992. }
  993. break;
  994. }
  995. let isNextNodeWin =
  996. nextSwapNode && nextSwapNode.nodeValue && nextSwapNode.nodeType === NODE_TYPES.WINDOW;
  997. if (isNextNodeWin) {
  998. if (!this.extWm.sameParentMonitor(node, nextSwapNode)) {
  999. // TODO, there is a freeze bug if there are not in same monitor.
  1000. return;
  1001. }
  1002. this.swapPairs(node, nextSwapNode);
  1003. }
  1004. return nextSwapNode;
  1005. }
  1006. swapPairs(fromNode, toNode, focus = true) {
  1007. if (!(this._swappable(fromNode) && this._swappable(toNode))) return;
  1008. // Swap the items in the array
  1009. let parentForFrom = fromNode ? fromNode.parentNode : undefined;
  1010. let parentForTo = toNode.parentNode;
  1011. if (parentForTo && parentForFrom) {
  1012. let nextIndex = toNode.index;
  1013. let focusIndex = fromNode.index;
  1014. let transferMode = fromNode.mode;
  1015. fromNode.mode = toNode.mode;
  1016. toNode.mode = transferMode;
  1017. let transferRect = fromNode.nodeValue.get_frame_rect();
  1018. let transferToRect = toNode.nodeValue.get_frame_rect();
  1019. let transferPercent = fromNode.percent;
  1020. fromNode.percent = toNode.percent;
  1021. toNode.percent = transferPercent;
  1022. parentForTo.childNodes[nextIndex] = fromNode;
  1023. fromNode.parentNode = parentForTo;
  1024. parentForFrom.childNodes[focusIndex] = toNode;
  1025. toNode.parentNode = parentForFrom;
  1026. this.extWm.move(fromNode.nodeValue, transferToRect);
  1027. this.extWm.move(toNode.nodeValue, transferRect);
  1028. if (focus) {
  1029. // The fromNode is now on the parent-target
  1030. fromNode.nodeValue.raise();
  1031. fromNode.nodeValue.focus(global.get_current_time());
  1032. }
  1033. }
  1034. }
  1035. _swappable(node) {
  1036. if (!node) return false;
  1037. if (node.nodeType === NODE_TYPES.WINDOW && !node.nodeValue.minimized) {
  1038. return true;
  1039. }
  1040. return false;
  1041. }
  1042. /**
  1043. * Performs cleanup of dangling parents in addition to removing the
  1044. * node from the parent.
  1045. */
  1046. removeNode(node) {
  1047. let oldChild;
  1048. let cleanUpParent = (existParent) => {
  1049. if (this.getTiledChildren(existParent.childNodes).length === 0) {
  1050. existParent.percent = 0.0;
  1051. this.resetSiblingPercent(existParent.parentNode);
  1052. }
  1053. this.resetSiblingPercent(existParent);
  1054. };
  1055. let parentNode = node.parentNode;
  1056. // If parent has only this window, remove the parent instead
  1057. if (parentNode.childNodes.length === 1 && parentNode.nodeType !== NODE_TYPES.MONITOR) {
  1058. let existParent = parentNode.parentNode;
  1059. oldChild = existParent.removeChild(parentNode);
  1060. cleanUpParent(existParent);
  1061. } else {
  1062. let existParent = node.parentNode;
  1063. oldChild = existParent.removeChild(node);
  1064. if (!this.extWm.floatingWindow(node)) cleanUpParent(existParent);
  1065. }
  1066. // If only a single tab remains, exit tabbed layout
  1067. if (
  1068. this.settings.get_boolean("auto-exit-tabbed") &&
  1069. parentNode.nodeType === NODE_TYPES.CON &&
  1070. parentNode.layout === LAYOUT_TYPES.TABBED &&
  1071. parentNode.childNodes.length === 1
  1072. ) {
  1073. parentNode.layout = this.extWm.determineSplitLayout();
  1074. this.resetSiblingPercent(parentNode);
  1075. parentNode.lastTabFocus = null;
  1076. }
  1077. if (node === this.attachNode) {
  1078. this.attachNode = null;
  1079. } else {
  1080. // Find the next focus node as attachNode
  1081. this.attachNode = this.findNode(this.extWm.focusMetaWindow);
  1082. }
  1083. return oldChild ? true : false;
  1084. }
  1085. render(from) {
  1086. Logger.debug(`render tree ${from ? "from " + from : ""}`);
  1087. this.processNode(this);
  1088. this.apply(this);
  1089. this.cleanTree();
  1090. let debugMode = true;
  1091. if (debugMode) {
  1092. this.debugTree();
  1093. }
  1094. Logger.debug(`*********************************************`);
  1095. }
  1096. apply(node) {
  1097. if (!node) return;
  1098. let tiledChildren = node
  1099. .getNodeByMode(Window.WINDOW_MODES.TILE)
  1100. .filter((t) => t.nodeType === NODE_TYPES.WINDOW);
  1101. tiledChildren.forEach((w) => {
  1102. if (w.renderRect) {
  1103. if (w.renderRect.width > 0 && w.renderRect.height > 0) {
  1104. let metaWin = w.nodeValue;
  1105. this.extWm.move(metaWin, w.renderRect);
  1106. } else {
  1107. Logger.debug(`ignoring apply for ${w.renderRect.width}x${w.renderRect.height}`);
  1108. }
  1109. }
  1110. if (w.nodeValue.firstRender) w.nodeValue.firstRender = false;
  1111. });
  1112. }
  1113. cleanTree() {
  1114. // Phase 1: remove any cons with empty children
  1115. const orphanCons = this.getNodeByType(NODE_TYPES.CON).filter((c) => c.childNodes.length === 0);
  1116. const hasOrphanCons = orphanCons.length > 0;
  1117. orphanCons.forEach((o) => {
  1118. this.removeNode(o);
  1119. });
  1120. const invalidWindows = this.getNodeByType(NODE_TYPES.WINDOW).filter((w) => {
  1121. const metaWindow = w.nodeValue;
  1122. const title = metaWindow.title;
  1123. const wmClass = metaWindow.wm_class;
  1124. return wmClass === "gjs";
  1125. });
  1126. invalidWindows.forEach((w) => {
  1127. this.removeNode(w);
  1128. });
  1129. // Phase 2: remove any empty parent cons up to the single intermediate parent-window level
  1130. // Basically, flatten them?
  1131. // [con[con[con[con[window]]]]] --> [con[window]]
  1132. // TODO: help :)
  1133. const grandParentCons = this.getNodeByType(NODE_TYPES.CON).filter(
  1134. (c) => c.childNodes.length === 1 && c.childNodes[0].nodeType === NODE_TYPES.CON
  1135. );
  1136. grandParentCons.forEach((c) => {
  1137. c.layout = LAYOUT_TYPES.HSPLIT;
  1138. });
  1139. if (hasOrphanCons || invalidWindows.length > 0) {
  1140. this.processNode(this);
  1141. this.apply(this);
  1142. }
  1143. }
  1144. /**
  1145. *
  1146. * Credits: Do the i3-like calculations
  1147. *
  1148. */
  1149. processNode(node) {
  1150. if (!node) return;
  1151. // Render the Root, Workspace and Monitor
  1152. // For now, we let them render their children recursively
  1153. if (node.nodeType === NODE_TYPES.ROOT) {
  1154. node.childNodes.forEach((child) => {
  1155. this.processNode(child);
  1156. });
  1157. }
  1158. if (node.nodeType === NODE_TYPES.WORKSPACE) {
  1159. node.childNodes.forEach((child) => {
  1160. this.processNode(child);
  1161. });
  1162. }
  1163. let params = {};
  1164. if (node.nodeType === NODE_TYPES.MONITOR || node.nodeType === NODE_TYPES.CON) {
  1165. // The workarea from Meta.Window's assigned monitor
  1166. // is important so it computes to `remove` the panel size
  1167. // really well. However, this type of workarea would only
  1168. // appear if there is window present on the monitor.
  1169. if (node.childNodes.length === 0) {
  1170. return;
  1171. }
  1172. // If monitor, get the workarea
  1173. if (node.nodeType === NODE_TYPES.MONITOR) {
  1174. let monitorIndex = Utils.monitorIndex(node.nodeValue);
  1175. let monitorArea = global.display
  1176. .get_workspace_manager()
  1177. .get_active_workspace()
  1178. .get_work_area_for_monitor(monitorIndex);
  1179. if (!monitorArea) return; // there is no visible child window
  1180. node.rect = monitorArea;
  1181. node.rect = this.processGap(node);
  1182. }
  1183. let tiledChildren = this.getTiledChildren(node.childNodes);
  1184. let sizes = this.computeSizes(node, tiledChildren);
  1185. params.sizes = sizes;
  1186. let showTabs = this.settings.get_boolean("showtab-decoration-enabled");
  1187. params.stackedHeight = showTabs ? this.defaultStackHeight * Utils.dpi() : 0;
  1188. params.tiledChildren = tiledChildren;
  1189. let decoration = node.decoration;
  1190. if (decoration) {
  1191. let decoChildren = decoration.get_children();
  1192. decoChildren.forEach((decoChild) => {
  1193. decoration.remove_child(decoChild);
  1194. });
  1195. }
  1196. tiledChildren.forEach((child, index) => {
  1197. // A monitor can contain a window or container child
  1198. if (node.layout === LAYOUT_TYPES.HSPLIT || node.layout === LAYOUT_TYPES.VSPLIT) {
  1199. this.processSplit(node, child, params, index);
  1200. } else if (node.layout === LAYOUT_TYPES.STACKED) {
  1201. this.processStacked(node, child, params, index);
  1202. } else if (node.layout === LAYOUT_TYPES.TABBED) {
  1203. this.processTabbed(node, child, params, index);
  1204. }
  1205. this.processNode(child);
  1206. });
  1207. }
  1208. if (node.isWindow()) {
  1209. if (!node.rect) node.rect = node.nodeValue.get_work_area_current_monitor();
  1210. node.renderRect = this.processGap(node);
  1211. }
  1212. }
  1213. /**
  1214. * Forge processes both non-Window and Window gaps
  1215. */
  1216. processGap(node) {
  1217. let nodeWidth = node.rect.width;
  1218. let nodeHeight = node.rect.height;
  1219. let nodeX = node.rect.x;
  1220. let nodeY = node.rect.y;
  1221. let gap = this.extWm.calculateGaps(node);
  1222. if (nodeWidth > gap * 2 && nodeHeight > gap * 2) {
  1223. nodeX += gap;
  1224. nodeY += gap;
  1225. // TODO - detect inbetween windows and adjust accordingly
  1226. // Also adjust depending on display scaling
  1227. nodeWidth -= gap * 2;
  1228. nodeHeight -= gap * 2;
  1229. }
  1230. return { x: nodeX, y: nodeY, width: nodeWidth, height: nodeHeight };
  1231. }
  1232. processSplit(node, child, params, index) {
  1233. let layout = node.layout;
  1234. let nodeRect = node.rect;
  1235. let nodeWidth;
  1236. let nodeHeight;
  1237. let nodeX;
  1238. let nodeY;
  1239. if (layout === LAYOUT_TYPES.HSPLIT) {
  1240. // Divide the parent container's width
  1241. // depending on number of children. And use this
  1242. // to setup each child window's width.
  1243. nodeWidth = params.sizes[index];
  1244. nodeHeight = nodeRect.height;
  1245. nodeX = nodeRect.x;
  1246. if (index != 0) {
  1247. let i = 1;
  1248. while (i <= index) {
  1249. nodeX += params.sizes[i - 1];
  1250. i++;
  1251. }
  1252. }
  1253. nodeY = nodeRect.y;
  1254. } else if (layout === LAYOUT_TYPES.VSPLIT) {
  1255. // split vertically
  1256. // Conversely for vertical split, divide the parent container's height
  1257. // depending on number of children. And use this
  1258. // to setup each child window's height.
  1259. nodeWidth = nodeRect.width;
  1260. nodeHeight = params.sizes[index];
  1261. nodeX = nodeRect.x;
  1262. nodeY = nodeRect.y;
  1263. if (index != 0) {
  1264. let i = 1;
  1265. while (i <= index) {
  1266. nodeY += params.sizes[i - 1];
  1267. i++;
  1268. }
  1269. }
  1270. }
  1271. child.rect = {
  1272. x: nodeX,
  1273. y: nodeY,
  1274. width: nodeWidth,
  1275. height: nodeHeight,
  1276. };
  1277. }
  1278. /**
  1279. * Process the child node here for the dimensions of the child stack/window,
  1280. * It will be moved to the Node class in the future as Node.render()
  1281. *
  1282. */
  1283. processStacked(node, child, params, index) {
  1284. let layout = node.layout;
  1285. let nodeWidth = node.rect.width;
  1286. let nodeHeight = node.rect.height;
  1287. let nodeX = node.rect.x;
  1288. let nodeY = node.rect.y;
  1289. let stackHeight = this.defaultStackHeight;
  1290. if (layout === LAYOUT_TYPES.STACKED) {
  1291. if (node.childNodes.length > 1) {
  1292. nodeY += stackHeight * index;
  1293. nodeHeight -= stackHeight * index;
  1294. }
  1295. child.rect = {
  1296. x: nodeX,
  1297. y: nodeY,
  1298. width: nodeWidth,
  1299. height: nodeHeight,
  1300. };
  1301. }
  1302. }
  1303. /**
  1304. * Process the child node here for the dimensions of the child tab/window,
  1305. * It will be moved to the Node class in the future as Node.render()
  1306. *
  1307. */
  1308. processTabbed(node, child, params, _index) {
  1309. let layout = node.layout;
  1310. let nodeRect = node.rect;
  1311. let nodeWidth;
  1312. let nodeHeight;
  1313. let nodeX;
  1314. let nodeY;
  1315. if (layout === LAYOUT_TYPES.TABBED) {
  1316. nodeWidth = nodeRect.width;
  1317. nodeX = nodeRect.x;
  1318. nodeY = nodeRect.y;
  1319. nodeHeight = nodeRect.height;
  1320. let alwaysShowDecorationTab = true;
  1321. if (node.childNodes.length > 1 || alwaysShowDecorationTab) {
  1322. nodeY = nodeRect.y + params.stackedHeight;
  1323. nodeHeight = nodeRect.height - params.stackedHeight;
  1324. if (node.decoration && child.isWindow()) {
  1325. let gap = this.extWm.calculateGaps(node);
  1326. let renderRect = this.processGap(node);
  1327. let borderWidth = child.actor.border.get_theme_node().get_border_width(St.Side.TOP);
  1328. // Make adjustments to the gaps
  1329. let adjust = 4 * Utils.dpi();
  1330. let adjustWidth = renderRect.width + (borderWidth * 2 + gap) / adjust;
  1331. let adjustX = renderRect.x - (gap + borderWidth * 2) / (adjust * 2);
  1332. let adjustY = renderRect.y - adjust;
  1333. if (gap === 0) {
  1334. adjustY = renderRect.y;
  1335. }
  1336. let decoration = node.decoration;
  1337. if (decoration !== null && decoration !== undefined) {
  1338. decoration.set_size(adjustWidth, params.stackedHeight);
  1339. decoration.set_position(adjustX, adjustY);
  1340. if (params.tiledChildren.length > 0 && params.stackedHeight !== 0) {
  1341. decoration.show();
  1342. } else {
  1343. decoration.hide();
  1344. }
  1345. if (!decoration.contains(child.tab)) decoration.add_child(child.tab);
  1346. }
  1347. child.render();
  1348. }
  1349. }
  1350. child.rect = {
  1351. x: nodeX,
  1352. y: nodeY,
  1353. width: nodeWidth,
  1354. height: nodeHeight,
  1355. };
  1356. }
  1357. }
  1358. computeSizes(node, childItems) {
  1359. let sizes = [];
  1360. let orientation = Utils.orientationFromLayout(node.layout);
  1361. let totalSize =
  1362. orientation === ORIENTATION_TYPES.HORIZONTAL ? node.rect.width : node.rect.height;
  1363. let grabTiled = node.getNodeByMode(Window.WINDOW_MODES.GRAB_TILE).length > 0;
  1364. childItems.forEach((childNode, index) => {
  1365. let percent =
  1366. childNode.percent && childNode.percent > 0.0 && !grabTiled
  1367. ? childNode.percent
  1368. : 1.0 / childItems.length;
  1369. sizes[index] = Math.floor(percent * totalSize);
  1370. });
  1371. // TODO - make sure the totalSize = the sizes total
  1372. return sizes;
  1373. }
  1374. findFirstNodeWindowFrom(node) {
  1375. let results = node.getNodeByType(NODE_TYPES.WINDOW);
  1376. if (results.length > 0) {
  1377. return results[0];
  1378. }
  1379. return null;
  1380. }
  1381. resetSiblingPercent(parentNode) {
  1382. if (!parentNode) return;
  1383. let children = parentNode.childNodes;
  1384. children.forEach((n) => {
  1385. n.percent = 0.0;
  1386. });
  1387. }
  1388. debugTree() {
  1389. // this.debugChildNodes(this);
  1390. }
  1391. debugChildNodes(node) {
  1392. this.debugNode(this);
  1393. node.childNodes.forEach((child) => {
  1394. this.debugChildNodes(child);
  1395. });
  1396. }
  1397. debugParentNodes(node) {
  1398. if (node) {
  1399. if (node.parentNode) {
  1400. this.debugParentNodes(node.parentNode);
  1401. }
  1402. this.debugNode(node);
  1403. }
  1404. }
  1405. debugNode(node) {
  1406. let spacing = "";
  1407. let dashes = "-->";
  1408. let level = node.level;
  1409. for (let i = 0; i < level; i++) {
  1410. let parentSpacing = i === 0 ? " " : "|";
  1411. spacing += `${parentSpacing} `;
  1412. }
  1413. let rootSpacing = level === 0 ? "#" : "*";
  1414. let attributes = "";
  1415. if (node.isWindow && node.isWindow()) {
  1416. let metaWindow = node.nodeValue;
  1417. attributes += `class:'${metaWindow.get_wm_class()}',title:'${
  1418. metaWindow.title
  1419. }',string:'${metaWindow}'${metaWindow === this.extWm.focusMetaWindow ? " FOCUS" : ""}`;
  1420. } else if (node.isCon() || node.isMonitor() || node.isWorkspace()) {
  1421. attributes += `${node.nodeValue}`;
  1422. if (node.isCon() || node.isMonitor()) {
  1423. attributes += `,layout:${node.layout}`;
  1424. }
  1425. }
  1426. if (node.rect) {
  1427. attributes += `,rect:${node.rect.width}x${node.rect.height}+${node.rect.x}+${node.rect.y}`;
  1428. const pointerCoord = global.get_pointer();
  1429. const pointerInside = Utils.rectContainsPoint(node.rect, pointerCoord) ? "yes" : "no";
  1430. attributes += `,pointer:${pointerInside}`;
  1431. }
  1432. if (level !== 0) Logger.debug(`${spacing}|`);
  1433. Logger.debug(
  1434. `${spacing}${rootSpacing}${dashes} ${node.nodeType}#${
  1435. node.index !== null ? node.index : "-"
  1436. } @${attributes}`
  1437. );
  1438. }
  1439. findParent(childNode, parentNodeType) {
  1440. let parents = this.getNodeByType(parentNodeType);
  1441. // Only get the first parent
  1442. return parents.filter((p) => p.contains(childNode))[0];
  1443. }
  1444. }