tileWindowManager.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. import Meta from 'gi://Meta';
  2. import GLib from 'gi://GLib';
  3. import {Orientation, Tile, TileState} from './tile.js';
  4. import {Position} from './position.js';
  5. import * as Resize from './resize.js';
  6. import Gio from 'gi://Gio';
  7. import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  8. import {Monitor} from './monitor.js';
  9. import Shell from 'gi://Shell';
  10. import Mtk from 'gi://Mtk';
  11. import {TopBarSearchEntry} from './topBarSearchEntry.js';
  12. export var Direction;
  13. (function (Direction) {
  14. Direction[Direction['North'] = 1] = 'North';
  15. Direction[Direction['South'] = 2] = 'South';
  16. Direction[Direction['West'] = 3] = 'West';
  17. Direction[Direction['East'] = 4] = 'East';
  18. })(Direction || (Direction = {}));
  19. export class TileWindowManager {
  20. /** ************************************************/
  21. // Store all signals to be restored when extension is disabled
  22. _wrappedWindows;
  23. _nMonitors;
  24. _windowCreatedSignal;
  25. _windowGrabSignal;
  26. _workspaceAddedSignal;
  27. _workspaceRemovedSignal;
  28. _activeWorkspaceSignal;
  29. _grabBeginSignal;
  30. _monitorChangedSignal;
  31. _workareasChangedSignal;
  32. /** ************************************************/
  33. _settings;
  34. _userResize;
  35. static locked = false;
  36. // Alternate windows rotation
  37. static rotateEven = [0, 0];
  38. // Search bar widgets
  39. _topBarSearchEntry;
  40. _modalSearchEntry;
  41. _sourceId;
  42. // Tiles structures
  43. static _workspaces = new Map();
  44. static _main_monitor;
  45. constructor(extension) {
  46. if (!TileWindowManager._workspaces)
  47. TileWindowManager._workspaces = new Map();
  48. TileWindowManager._main_monitor = global.display.get_primary_monitor();
  49. this._settings = extension._settings;
  50. this._nMonitors = global.display.get_n_monitors();
  51. for (let i = 0; i < global.workspace_manager.n_workspaces; i++) {
  52. let _monitors = new Array(this._nMonitors);
  53. for (let j = 0; j < _monitors.length; j++)
  54. _monitors[j] = new Monitor(j);
  55. TileWindowManager._workspaces.set(i, _monitors);
  56. }
  57. this._wrappedWindows = new Map();
  58. this._userResize = new Set();
  59. this._sourceId = null;
  60. if (TileWindowManager.locked)
  61. this._loadAfterSessionLock();
  62. global.get_window_actors().forEach(actor => {
  63. if (actor.meta_window &&
  64. actor.meta_window.get_window_type() === Meta.WindowType.NORMAL)
  65. this._addNewWindow(actor.meta_window);
  66. });
  67. this.updateMonitors();
  68. this._windowCreatedSignal = global.display.connect('window-created', (display, obj) => this._onWindowCreated(display, obj));
  69. this._workareasChangedSignal = global.display.connect('workareas-changed', () => {
  70. TileWindowManager._workspaces.forEach(val => {
  71. val.forEach(m => m.updateSize());
  72. });
  73. this.updateMonitors();
  74. });
  75. this._grabBeginSignal = global.display.connect('grab-op-begin', (_, w) => this._userResize.add(w));
  76. this._windowGrabSignal = global.display.connect('grab-op-end', (_, window, op) => {
  77. this._onGrab(window, op);
  78. this._userResize.delete(window);
  79. });
  80. this._workspaceAddedSignal = global.workspace_manager.connect('workspace-added', (_, index) => {
  81. this._onWorkspaceCreated(index);
  82. });
  83. this._workspaceRemovedSignal = global.workspace_manager.connect('workspace-removed', (_, index) => {
  84. this._onWorkspaceRemoved(index);
  85. });
  86. this._activeWorkspaceSignal = global.workspace_manager.connect('active-workspace-changed', () => {
  87. this.updateMonitors();
  88. this.updateAdjacents();
  89. });
  90. this._monitorChangedSignal = global.backend.get_monitor_manager().connect('monitors-changed', () => {
  91. const n = global.display.get_n_monitors();
  92. if (n !== this._nMonitors) {
  93. let diff = n - this._nMonitors;
  94. this._nMonitors = n;
  95. if (diff > 0)
  96. this._addMonitors();
  97. else
  98. this._removeMonitors();
  99. }
  100. });
  101. }
  102. static getMonitors(workspaceIndex = undefined) {
  103. let wk;
  104. if (!workspaceIndex)
  105. wk = TileWindowManager._workspaces.get(global.workspace_manager.get_active_workspace_index());
  106. else
  107. wk = TileWindowManager._workspaces.get(workspaceIndex);
  108. if (wk)
  109. return wk;
  110. else
  111. return [];
  112. }
  113. _addMonitors() {
  114. let newPrimMonitor = global.display.get_primary_monitor();
  115. TileWindowManager._workspaces.forEach((val, _, __) => {
  116. let currPrim = null;
  117. if (newPrimMonitor !== TileWindowManager._main_monitor)
  118. currPrim = val.filter(e => e.index === TileWindowManager._main_monitor)[0];
  119. while (val.length !== this._nMonitors) {
  120. const index = val.length;
  121. val.push(new Monitor(index));
  122. // Move content on the new primary screen
  123. if (index === newPrimMonitor) {
  124. val[index].fullscreen = currPrim?.fullscreen ?? false;
  125. val[index].root = currPrim?.root ?? null;
  126. val[index].root?.forEach(el => {
  127. el.monitor = index;
  128. });
  129. if (currPrim?.fullscreen)
  130. currPrim.fullscreen = false;
  131. if (currPrim?.root)
  132. currPrim.root = null;
  133. }
  134. }
  135. });
  136. TileWindowManager._main_monitor = newPrimMonitor;
  137. }
  138. _removeMonitors() {
  139. TileWindowManager._workspaces.forEach((val, _, __) => {
  140. let windows = [];
  141. while (val.length !== this._nMonitors)
  142. windows.push(val.pop());
  143. for (let i = 0; i < windows.length; i++) {
  144. windows[i]?.root?.forEach(t => {
  145. if (t.window)
  146. this._insertWindow(t.window, null, 0);
  147. });
  148. }
  149. });
  150. TileWindowManager._main_monitor = global.display.get_primary_monitor();
  151. this.updateMonitors();
  152. this.updateAdjacents();
  153. }
  154. /**
  155. * Refresh **ALL** existing tiles.
  156. */
  157. updateMonitors() {
  158. TileWindowManager._workspaces.forEach((value, _) => {
  159. value.forEach(el => el.root?.update());
  160. });
  161. }
  162. updateAdjacents() {
  163. TileWindowManager._workspaces.forEach((value, _) => {
  164. value.forEach(el => el.root?.forEach(t => t.findAdjacents()));
  165. });
  166. }
  167. destroy() {
  168. global.display.disconnect(this._windowCreatedSignal);
  169. global.display.disconnect(this._windowGrabSignal);
  170. global.display.disconnect(this._grabBeginSignal);
  171. global.display.disconnect(this._workareasChangedSignal);
  172. global.workspace_manager.disconnect(this._workspaceAddedSignal);
  173. global.workspace_manager.disconnect(this._workspaceRemovedSignal);
  174. global.workspace_manager.disconnect(this._activeWorkspaceSignal);
  175. global.backend.disconnect(this._monitorChangedSignal);
  176. if (Resize.resizeSourceId !== null)
  177. GLib.Source.remove(Resize.resizeSourceId);
  178. // Disconnect each window
  179. this._wrappedWindows.forEach((value, key) => {
  180. key.minimize = value[0];
  181. key.maximize = value[1];
  182. key.disconnect(value[2]);
  183. key.disconnect(value[3]);
  184. key.disconnect(value[4]);
  185. key.disconnect(value[5]);
  186. key.disconnect(value[6]);
  187. key.disconnect(value[7]);
  188. this._wrappedWindows.delete(key);
  189. });
  190. this._wrappedWindows.clear();
  191. this._topBarSearchEntry?.destroy();
  192. TileWindowManager._main_monitor = null;
  193. TileWindowManager._workspaces = null;
  194. }
  195. /** Check if the window is a `valid` window.
  196. * A `valid` window is a window created by user and
  197. * running an app.
  198. * It is tricky to filter windows correctly. Here we exclude
  199. * windows that don't have app id (the id is just the app number).
  200. * This method must be called when we're sure the window is **fully**
  201. * created (basically when wait for first-frame signal). Otherwise
  202. * we may badly filter some windows.
  203. *
  204. * @param {Meta.Window} window
  205. * @returns boolean
  206. */
  207. _isValidWindow(window) {
  208. if (!window)
  209. return false;
  210. if (window.get_window_type() !== Meta.WindowType.NORMAL)
  211. return false;
  212. let app = Shell.WindowTracker.get_default().get_window_app(window);
  213. if (!app)
  214. return false;
  215. if (app.get_id().startsWith('window:'))
  216. return false;
  217. for (var [_, value] of TileWindowManager._workspaces) {
  218. let containsWindow = value.reduce((acc, val) => val.root ? acc || val.root.contains(window) : acc, false);
  219. if (containsWindow)
  220. return false;
  221. }
  222. return true;
  223. }
  224. _onWorkspaceCreated(index) {
  225. let _monitors = new Array(global.display.get_n_monitors());
  226. for (let i = 0; i < _monitors.length; i++)
  227. _monitors[i] = new Monitor(i);
  228. if (!TileWindowManager._workspaces.has(index))
  229. TileWindowManager._workspaces.set(index, _monitors);
  230. }
  231. _onWorkspaceRemoved(index) {
  232. TileWindowManager._workspaces.delete(index);
  233. let newMap = new Map();
  234. TileWindowManager._workspaces.forEach((value, key) => {
  235. if (key > index) {
  236. value.forEach(el => el.root?.forEach(t => {
  237. t.workspace = key - 1;
  238. }));
  239. newMap.set(key - 1, value);
  240. } else {
  241. newMap.set(key, value);
  242. }
  243. });
  244. TileWindowManager._workspaces = newMap;
  245. this.updateMonitors();
  246. this.updateAdjacents();
  247. }
  248. _onWindowCreated(_, window) {
  249. // Wait for first frame to be sure window is fully created
  250. window.get_compositor_private().connect('first-frame', () => {
  251. this._addNewWindow(window);
  252. });
  253. }
  254. _windowWorkspaceChanged(window) {
  255. let tile = window.tile;
  256. if (tile) {
  257. let w = window.get_workspace()?.index();
  258. if (w !== null) {
  259. window.change_workspace_by_index(w, false);
  260. if (w !== window.tile.workspace) {
  261. this._removeWindow(window);
  262. this._insertWindow(window, w);
  263. tile.workspace = w;
  264. }
  265. this.updateMonitors();
  266. this.updateAdjacents();
  267. }
  268. }
  269. }
  270. /** Connect to signals and remove some functions
  271. *
  272. * @param {Meta.Window} window
  273. */
  274. configureWindowSignals(window) {
  275. let minimizeSignal = window.connect('notify::minimized', () => {
  276. if (!window.minimized) {
  277. let tile = window.tile;
  278. if (tile.state === TileState.MINIMIZED) {
  279. if (TileWindowManager.getMonitors()[tile.monitor].fullscreen) {
  280. if (this._settings?.get_int('fullscreen-switch') === 0)
  281. this.maximizeTile(window, true);
  282. else
  283. this.maximizeTile(window);
  284. }
  285. }
  286. } else {
  287. if (window.tile.state === TileState.MINIMIZED)
  288. return;
  289. window.unminimize();
  290. }
  291. });
  292. let maximizeSignal1 = window.connect('notify::maximized-horizontally', () => {
  293. if (window.tile.state === TileState.MAXIMIZED)
  294. return;
  295. if (window.maximized_horizontally || window.maximized_vertically)
  296. window.unmaximize(Meta.MaximizeFlags.BOTH);
  297. });
  298. let maximizeSignal2 = window.connect('notify::maximized-vertically', () => {
  299. if (window.tile.state === TileState.MAXIMIZED)
  300. return;
  301. if (window.maximized_horizontally || window.maximized_vertically)
  302. window.unmaximize(Meta.MaximizeFlags.BOTH);
  303. });
  304. let unmanagedSignal = window.connect('unmanaged', () => {
  305. this._removeWindow(window);
  306. this._removeWindowSignals(window);
  307. });
  308. let workspaceChangedSignal = window.connect('workspace-changed', w => this._windowWorkspaceChanged(w));
  309. let sizeChangedSignal = window.connect('size-changed', w => {
  310. let tile = w.tile;
  311. if (!this._userResize.has(w) &&
  312. (tile.position.width !== w.get_frame_rect().width ||
  313. tile.position.height !== w.get_frame_rect().height))
  314. tile.update();
  315. });
  316. window._originalMaximize = window.maximize;
  317. window._originalMinimize = window.minimize;
  318. this._wrappedWindows.set(window, [window.minimize, window.maximize, minimizeSignal,
  319. maximizeSignal1, maximizeSignal2,
  320. unmanagedSignal, workspaceChangedSignal, sizeChangedSignal]);
  321. window.minimize = () => { };
  322. window.maximize = () => { };
  323. }
  324. _addNewWindow(window) {
  325. if (!this._isValidWindow(window))
  326. return;
  327. this.configureWindowSignals(window);
  328. this._insertWindow(window);
  329. }
  330. _insertWindow(window, workspace = null, monitor = null) {
  331. let _monitors = TileWindowManager._workspaces.get(workspace != null ? workspace : window.get_workspace()?.index());
  332. if (!_monitors)
  333. return;
  334. let selectedMonitor;
  335. // Select monitor
  336. if (monitor !== null) {
  337. selectedMonitor = _monitors[monitor];
  338. } else if (this._settings?.get_int('monitor-tile-insertion-behavior') === 0) {
  339. selectedMonitor = Monitor.bestFitMonitor(_monitors);
  340. } else {
  341. let m = global.display.get_current_monitor();
  342. selectedMonitor = _monitors[m];
  343. }
  344. // Selected monitor index
  345. let index = selectedMonitor.index;
  346. // Now insert tile on selected monitor
  347. if (selectedMonitor.size() === 0) {
  348. let tile = Tile.createTileLeaf(window, new Position(1.0, 0, 0, 0, 0), index);
  349. tile.workspace = window.get_workspace().index();
  350. window.tile = tile;
  351. _monitors[index].root = tile;
  352. _monitors[index].root?.update();
  353. } else {
  354. _monitors[index].root?.addWindowOnBlock(window);
  355. window.tile.workspace = window.get_workspace().index();
  356. if (_monitors[index].fullscreen)
  357. window.tile.state = TileState.MINIMIZED;
  358. _monitors[index].root?.update();
  359. }
  360. }
  361. _removeWindowSignals(window) {
  362. window.tile?.destroy();
  363. // Disconnect signals
  364. let s = this._wrappedWindows.get(window);
  365. if (s) {
  366. window.minimize = s[0];
  367. window.maximize = s[1];
  368. window.disconnect(s[2]);
  369. window.disconnect(s[3]);
  370. window.disconnect(s[4]);
  371. window.disconnect(s[5]);
  372. window.disconnect(s[6]);
  373. window.disconnect(s[7]);
  374. }
  375. this._wrappedWindows.delete(window);
  376. }
  377. _removeWindow(window) {
  378. // get Tile from window
  379. let tile = window.tile;
  380. // Not found
  381. if (!tile)
  382. return;
  383. let m = tile.monitor;
  384. if (TileWindowManager.getMonitors()[m].fullscreen) {
  385. TileWindowManager.getMonitors()[m].fullscreen = false;
  386. let focus = null;
  387. TileWindowManager.getMonitors()[m].root?.forEach(el => {
  388. el.state = TileState.DEFAULT;
  389. el.window?.unminimize();
  390. if (!focus && el.window && el.window !== window) {
  391. el.window?.focus(0);
  392. focus = el;
  393. }
  394. });
  395. }
  396. if (tile.removeTile() === null)
  397. TileWindowManager.getMonitors(tile.workspace)[m].root = null;
  398. else
  399. TileWindowManager.getMonitors()[m].root?.update();
  400. }
  401. _onGrab(window, op) {
  402. if (!window)
  403. return;
  404. let tile = window.tile;
  405. if (!tile)
  406. return;
  407. let m = tile.monitor;
  408. let rect;
  409. switch (op) {
  410. case Meta.GrabOp.MOVING:
  411. if (this._sourceId !== null)
  412. GLib.Source.remove(this._sourceId);
  413. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  414. TileWindowManager.getMonitors()[m].root?.update();
  415. this._sourceId = null;
  416. return GLib.SOURCE_REMOVE;
  417. });
  418. break;
  419. case Meta.GrabOp.RESIZING_E:
  420. rect = window.get_frame_rect();
  421. if (this._sourceId !== null)
  422. GLib.Source.remove(this._sourceId);
  423. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  424. Resize.resizeE(tile, rect);
  425. this._sourceId = null;
  426. return GLib.SOURCE_REMOVE;
  427. });
  428. break;
  429. case Meta.GrabOp.RESIZING_W:
  430. rect = window.get_frame_rect();
  431. if (this._sourceId !== null)
  432. GLib.Source.remove(this._sourceId);
  433. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  434. Resize.resizeW(tile, rect);
  435. this._sourceId = null;
  436. return GLib.SOURCE_REMOVE;
  437. });
  438. break;
  439. case Meta.GrabOp.RESIZING_N:
  440. rect = window.get_frame_rect();
  441. if (this._sourceId !== null)
  442. GLib.Source.remove(this._sourceId);
  443. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  444. Resize.resizeN(tile, rect);
  445. this._sourceId = null;
  446. return GLib.SOURCE_REMOVE;
  447. });
  448. break;
  449. case Meta.GrabOp.RESIZING_S:
  450. rect = window.get_frame_rect();
  451. if (this._sourceId !== null)
  452. GLib.Source.remove(this._sourceId);
  453. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  454. Resize.resizeS(tile, rect);
  455. this._sourceId = null;
  456. return GLib.SOURCE_REMOVE;
  457. });
  458. break;
  459. case Meta.GrabOp.RESIZING_NE:
  460. rect = window.get_frame_rect();
  461. if (this._sourceId !== null)
  462. GLib.Source.remove(this._sourceId);
  463. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  464. Resize.resizeN(tile, rect);
  465. Resize.resizeE(tile, rect);
  466. this._sourceId = null;
  467. return GLib.SOURCE_REMOVE;
  468. });
  469. break;
  470. case Meta.GrabOp.RESIZING_NW:
  471. rect = window.get_frame_rect();
  472. if (this._sourceId !== null)
  473. GLib.Source.remove(this._sourceId);
  474. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  475. Resize.resizeN(tile, rect);
  476. Resize.resizeW(tile, rect);
  477. this._sourceId = null;
  478. return GLib.SOURCE_REMOVE;
  479. });
  480. break;
  481. case Meta.GrabOp.RESIZING_SE:
  482. rect = window.get_frame_rect();
  483. if (this._sourceId !== null)
  484. GLib.Source.remove(this._sourceId);
  485. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  486. Resize.resizeS(tile, rect);
  487. Resize.resizeE(tile, rect);
  488. this._sourceId = null;
  489. return GLib.SOURCE_REMOVE;
  490. });
  491. break;
  492. case Meta.GrabOp.RESIZING_SW:
  493. rect = window.get_frame_rect();
  494. if (this._sourceId !== null)
  495. GLib.Source.remove(this._sourceId);
  496. this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  497. Resize.resizeS(tile, rect);
  498. Resize.resizeW(tile, rect);
  499. this._sourceId = null;
  500. return GLib.SOURCE_REMOVE;
  501. });
  502. break;
  503. default:
  504. break;
  505. }
  506. }
  507. /** Resize operation with keyboard. This operation is
  508. * different from the one operated with the mouse (grab operation)
  509. * because we handle left/right resize differently.
  510. *
  511. * @param {Meta.GrabOp} op
  512. * @param {number} resizeGap
  513. * @returns void
  514. */
  515. resizeFocusedWindow(op, resizeGap = 10) {
  516. let window = global.display.focusWindow;
  517. if (!window)
  518. return;
  519. let tile = window.tile;
  520. if (op === Meta.GrabOp.RESIZING_E) {
  521. if (tile.adjacents[1]) {
  522. let r = new Mtk.Rectangle({
  523. x: tile.position.x,
  524. y: tile.position.y,
  525. width: tile.position.width + resizeGap,
  526. height: tile.position.height,
  527. });
  528. Resize.resizeE(tile, r);
  529. } else if (tile.adjacents[0]) {
  530. let r = new Mtk.Rectangle({
  531. x: tile.position.x + resizeGap,
  532. y: tile.position.y,
  533. width: tile.position.width - resizeGap,
  534. height: tile.position.height,
  535. });
  536. Resize.resizeW(tile, r);
  537. }
  538. } else if (op === Meta.GrabOp.RESIZING_W) {
  539. if (tile.adjacents[0]) {
  540. let r = new Mtk.Rectangle({
  541. x: tile.position.x - resizeGap,
  542. y: tile.position.y,
  543. width: tile.position.width + resizeGap,
  544. height: tile.position.height,
  545. });
  546. Resize.resizeW(tile, r);
  547. } else if (tile.adjacents[1]) {
  548. let r = new Mtk.Rectangle({
  549. x: tile.position.x,
  550. y: tile.position.y,
  551. width: tile.position.width - resizeGap,
  552. height: tile.position.height,
  553. });
  554. Resize.resizeE(tile, r);
  555. }
  556. } else if (op === Meta.GrabOp.RESIZING_N) {
  557. if (tile.adjacents[2]) {
  558. let r = new Mtk.Rectangle({
  559. x: tile.position.x,
  560. y: tile.position.y - resizeGap,
  561. width: tile.position.width,
  562. height: tile.position.height + resizeGap,
  563. });
  564. Resize.resizeN(tile, r);
  565. } else if (tile.adjacents[3]) {
  566. let r = new Mtk.Rectangle({
  567. x: tile.position.x,
  568. y: tile.position.y,
  569. width: tile.position.width,
  570. height: tile.position.height - resizeGap,
  571. });
  572. Resize.resizeS(tile, r);
  573. }
  574. } else if (op === Meta.GrabOp.RESIZING_S) {
  575. if (tile.adjacents[3]) {
  576. let r = new Mtk.Rectangle({
  577. x: tile.position.x,
  578. y: tile.position.y,
  579. width: tile.position.width,
  580. height: tile.position.height + resizeGap,
  581. });
  582. Resize.resizeS(tile, r);
  583. } else if (tile.adjacents[2]) {
  584. let r = new Mtk.Rectangle({
  585. x: tile.position.x,
  586. y: tile.position.y + resizeGap,
  587. width: tile.position.width,
  588. height: tile.position.height - resizeGap,
  589. });
  590. Resize.resizeN(tile, r);
  591. }
  592. }
  593. }
  594. /** Clock wise windows rotation.
  595. * Rotates the parent tile (if existing).
  596. *
  597. * @param {Meta.Window} window
  598. * @returns
  599. */
  600. rotateWindow(window) {
  601. let tile = window.tile;
  602. if (!tile)
  603. return;
  604. let parent = tile.parent;
  605. if (!parent)
  606. return;
  607. let newPositions;
  608. if (parent.orientation === Orientation.Horizontal) {
  609. newPositions = parent.position.split(Orientation.Vertical);
  610. if (parent.child1 && parent.child2) {
  611. newPositions[TileWindowManager.rotateEven[0] === 0 ? 0 : 1].splitProportion = parent.child1.position.splitProportion;
  612. newPositions[TileWindowManager.rotateEven[0] === 0 ? 1 : 0].splitProportion = parent.child2.position.splitProportion;
  613. parent.child1.resize(newPositions[TileWindowManager.rotateEven[0] === 0 ? 0 : 1]);
  614. parent.child2.resize(newPositions[TileWindowManager.rotateEven[0] === 0 ? 1 : 0]);
  615. parent.forEach(el => el.findAdjacents());
  616. TileWindowManager.rotateEven[0] = (TileWindowManager.rotateEven[0] + 1) % 2;
  617. parent.update();
  618. } else {
  619. return;
  620. }
  621. parent.orientation = Orientation.Vertical;
  622. } else if (parent.orientation === Orientation.Vertical) {
  623. newPositions = parent.position.split(Orientation.Horizontal);
  624. if (parent.child1 && parent.child2) {
  625. newPositions[TileWindowManager.rotateEven[1] === 0 ? 1 : 0].splitProportion = parent.child1.position.splitProportion;
  626. newPositions[TileWindowManager.rotateEven[1] === 0 ? 0 : 1].splitProportion = parent.child2.position.splitProportion;
  627. parent.child1.resize(newPositions[TileWindowManager.rotateEven[1] === 0 ? 1 : 0]);
  628. parent.child2.resize(newPositions[TileWindowManager.rotateEven[1] === 0 ? 0 : 1]);
  629. parent.forEach(el => el.findAdjacents());
  630. TileWindowManager.rotateEven[1] = (TileWindowManager.rotateEven[1] + 1) % 2;
  631. parent.update();
  632. } else {
  633. return;
  634. }
  635. parent.orientation = Orientation.Horizontal;
  636. }
  637. }
  638. /** Maximize the currently focused window.
  639. * Others windows are reduced using tile specific state.
  640. *
  641. * @param {Meta.Window} window
  642. * @param {boolean} replace true to stay in fullscreen and just replace the window otherwise false
  643. * @returns
  644. */
  645. maximizeTile(window, replace = false) {
  646. let tile = window.tile;
  647. if (!tile)
  648. return;
  649. let m = tile.monitor;
  650. if (TileWindowManager.getMonitors()[tile.monitor].fullscreen) {
  651. TileWindowManager.getMonitors()[tile.monitor].fullscreen = false;
  652. TileWindowManager.getMonitors()[m].root?.forEach(el => {
  653. el.state = TileState.DEFAULT;
  654. if (el.window?.minimized)
  655. el.window?.unminimize();
  656. });
  657. if (replace) {
  658. TileWindowManager.getMonitors()[m].fullscreen = true;
  659. TileWindowManager.getMonitors()[m].root?.forEach(el => {
  660. if (el.id === tile.id)
  661. el.state = TileState.MAXIMIZED;
  662. else
  663. el.state = TileState.MINIMIZED;
  664. });
  665. }
  666. } else {
  667. TileWindowManager.getMonitors()[tile.monitor].fullscreen = true;
  668. TileWindowManager.getMonitors()[m].root?.forEach(el => {
  669. if (el.id === tile.id)
  670. el.state = TileState.MAXIMIZED;
  671. else
  672. el.state = TileState.MINIMIZED;
  673. });
  674. }
  675. TileWindowManager.getMonitors()[m].root?.update();
  676. }
  677. moveTile(dir) {
  678. let window = global.display.get_focus_window();
  679. if (!window)
  680. return;
  681. let tile = window.tile;
  682. if (!tile.window)
  683. return;
  684. let exchangeTile = TileWindowManager.getMonitors()[tile.monitor].closestTile(tile, dir);
  685. if (!exchangeTile || !exchangeTile.window)
  686. return;
  687. let tmpWindow = exchangeTile.window;
  688. exchangeTile.window = tile.window;
  689. tile.window = tmpWindow;
  690. tile.window.tile = tile;
  691. exchangeTile.window.tile = exchangeTile;
  692. TileWindowManager.getMonitors()[tile.monitor].root?.update();
  693. }
  694. changeFocus(dir) {
  695. let window = global.display.get_focus_window();
  696. if (!window)
  697. return;
  698. let tile = window.tile;
  699. if (!tile.window)
  700. return;
  701. if (TileWindowManager.getMonitors()[tile.monitor].fullscreen === true) {
  702. let mon = TileWindowManager.getMonitors()[tile.monitor].closestMonitor(dir);
  703. if (mon === null)
  704. return;
  705. let newFocus;
  706. switch (dir) {
  707. case Direction.North:
  708. newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.South);
  709. if (newFocus)
  710. newFocus?.window?.focus(0);
  711. break;
  712. case Direction.South:
  713. newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.North);
  714. if (newFocus)
  715. newFocus?.window?.focus(0);
  716. break;
  717. case Direction.East:
  718. newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.West);
  719. if (newFocus)
  720. newFocus?.window?.focus(0);
  721. break;
  722. case Direction.West:
  723. newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.East);
  724. if (newFocus)
  725. newFocus?.window?.focus(0);
  726. break;
  727. }
  728. } else {
  729. let newFocus = TileWindowManager.getMonitors()[tile.monitor].closestTile(tile, dir);
  730. if (newFocus === null) {
  731. let mon = TileWindowManager.getMonitors()[tile.monitor].closestMonitor(dir);
  732. if (mon === null)
  733. return;
  734. switch (dir) {
  735. case Direction.North:
  736. newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.South);
  737. if (newFocus)
  738. newFocus?.window?.focus(0);
  739. break;
  740. case Direction.South:
  741. newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.North);
  742. if (newFocus)
  743. newFocus?.window?.focus(0);
  744. break;
  745. case Direction.East:
  746. newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.West);
  747. if (newFocus)
  748. newFocus?.window?.focus(0);
  749. break;
  750. case Direction.West:
  751. newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.East);
  752. if (newFocus)
  753. newFocus?.window?.focus(0);
  754. break;
  755. default:
  756. }
  757. } else {
  758. newFocus?.window?.focus(0);
  759. }
  760. }
  761. }
  762. createSearchBar() {
  763. if (this._topBarSearchEntry && this._topBarSearchEntry.isAlive()) {
  764. this._topBarSearchEntry.destroy();
  765. this._topBarSearchEntry = undefined;
  766. return;
  767. }
  768. if (this._settings)
  769. this._topBarSearchEntry = new TopBarSearchEntry(this._settings);
  770. }
  771. refresh() {
  772. TileWindowManager.getMonitors().forEach(el => el.root ? el.root.update() : null);
  773. }
  774. moveToWorkspace(next) {
  775. let window = global.display.get_focus_window();
  776. let tile = window.tile;
  777. let current = tile.workspace;
  778. if (next) {
  779. if (current < global.workspaceManager.get_n_workspaces() - 1) {
  780. tile.workspace = current + 1;
  781. window.change_workspace_by_index(tile.workspace, false);
  782. this._removeWindow(window);
  783. this._insertWindow(window, current + 1);
  784. }
  785. } else if (current > 0) {
  786. tile.workspace = current - 1;
  787. window.change_workspace_by_index(tile.workspace, false);
  788. this._removeWindow(window);
  789. this._insertWindow(window, current - 1);
  790. }
  791. this.updateMonitors();
  792. this.updateAdjacents();
  793. }
  794. moveToNextMonitor() {
  795. let window = global.display.get_focus_window();
  796. this._removeWindow(window);
  797. let tile = window.tile;
  798. let monitors = TileWindowManager.getMonitors();
  799. let newMonitorIndex = (tile.monitor + 1) % monitors.length;
  800. let newMonitor = monitors[newMonitorIndex];
  801. if (newMonitor.size() === 0) {
  802. let newTile = Tile.createTileLeaf(window, new Position(1.0, 0, 0, 0, 0), newMonitorIndex);
  803. newTile.workspace = window.get_workspace().index();
  804. window.tile = newTile;
  805. newMonitor.root = newTile;
  806. newMonitor.root?.update();
  807. } else {
  808. // Easier to create a new tile for insertion
  809. newMonitor.root?.addWindowOnBlock(window);
  810. window.tile.workspace = window.get_workspace().index();
  811. if (newMonitor.fullscreen)
  812. window.tile.state = TileState.MINIMIZED;
  813. newMonitor.root?.update();
  814. }
  815. }
  816. /** Extension is disabled on screen lock.
  817. * We save the state of the Monitors before we quit.
  818. *
  819. * @returns
  820. */
  821. _saveBeforeSessionLock() {
  822. if (!Main.sessionMode.isLocked)
  823. return;
  824. TileWindowManager.locked = true;
  825. const userPath = GLib.get_user_config_dir();
  826. const parentPath = GLib.build_filenamev([userPath, '/grimble']);
  827. const parent = Gio.File.new_for_path(parentPath);
  828. try {
  829. parent.make_directory_with_parents(null);
  830. } catch (e) {
  831. if (e.code !== Gio.IOErrorEnum.EXISTS)
  832. throw e;
  833. }
  834. const path = GLib.build_filenamev([parentPath, '/tilingWmSession2.json']);
  835. const file = Gio.File.new_for_path(path);
  836. try {
  837. file.create(Gio.FileCreateFlags.NONE, null);
  838. } catch (e) {
  839. if (e.code !== Gio.IOErrorEnum.EXISTS)
  840. throw e;
  841. }
  842. file.replace_contents(JSON.stringify({
  843. windows: Array.from(TileWindowManager._workspaces.entries()),
  844. }, (key, value) => {
  845. if (value instanceof Meta.Window)
  846. return value.get_id();
  847. else if (key === '_parent') // remove cyclic references
  848. return undefined;
  849. else
  850. return value;
  851. }), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
  852. }
  853. _loadAfterSessionLock() {
  854. if (!TileWindowManager.locked)
  855. return;
  856. TileWindowManager.locked = false;
  857. const userPath = GLib.get_user_config_dir();
  858. const path = GLib.build_filenamev([userPath, '/grimble/tilingWmSession2.json']);
  859. const file = Gio.File.new_for_path(path);
  860. if (!file.query_exists(null))
  861. return;
  862. try {
  863. file.create(Gio.FileCreateFlags.NONE, null);
  864. } catch (e) {
  865. if (e.code !== Gio.IOErrorEnum.EXISTS)
  866. throw e;
  867. }
  868. const [success, contents] = file.load_contents(null);
  869. if (!success || !contents.length)
  870. return;
  871. const openWindows = global.display.list_all_windows();
  872. const states = JSON.parse(new TextDecoder().decode(contents), (key, value) => key === '_window' && typeof value === 'number'
  873. ? openWindows.find(val => val.get_id() === value)
  874. : value);
  875. let map = new Map(states.windows);
  876. map.forEach((mapValue, mapKey, _) => {
  877. mapValue.forEach((value, index, array) => {
  878. // We have to rebuild correct types of each object
  879. array[index] = Monitor.fromObject(value);
  880. array[index].root?.forEach(el => {
  881. if (el.window) {
  882. this.configureWindowSignals(el.window);
  883. el.window.change_workspace_by_index(mapKey, false);
  884. }
  885. });
  886. });
  887. TileWindowManager._workspaces.set(mapKey, mapValue);
  888. });
  889. this.updateMonitors();
  890. }
  891. }