window.js 91 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730
  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 GLib from "gi://GLib";
  20. import Clutter from "gi://Clutter";
  21. import GObject from "gi://GObject";
  22. import Meta from "gi://Meta";
  23. import St from "gi://St";
  24. // Gnome Shell imports
  25. import { gettext as _ } from "resource:///org/gnome/shell/extensions/extension.js";
  26. import * as Main from "resource:///org/gnome/shell/ui/main.js";
  27. import { PACKAGE_VERSION } from "resource:///org/gnome/shell/misc/config.js";
  28. // Shared state
  29. import { Logger } from "../shared/logger.js";
  30. // App imports
  31. import * as Utils from "./utils.js";
  32. import { Keybindings } from "./keybindings.js";
  33. import {
  34. Tree,
  35. Queue,
  36. Node,
  37. POSITION,
  38. LAYOUT_TYPES,
  39. ORIENTATION_TYPES,
  40. NODE_TYPES,
  41. } from "./tree.js";
  42. import { production } from "../shared/settings.js";
  43. /** @typedef {import('../../extension.js').default} ForgeExtension */
  44. export const WINDOW_MODES = Utils.createEnum(["FLOAT", "TILE", "GRAB_TILE", "DEFAULT"]);
  45. // Simplify the grab modes
  46. export const GRAB_TYPES = Utils.createEnum(["RESIZING", "MOVING", "UNKNOWN"]);
  47. export class WindowManager extends GObject.Object {
  48. static {
  49. GObject.registerClass(this);
  50. }
  51. /** @type {ForgeExtension} */
  52. ext;
  53. /** @param {ForgeExtension} ext */
  54. constructor(ext) {
  55. super();
  56. this.ext = ext;
  57. this.prefsTitle = `Forge ${_("Settings")} - ${
  58. !production ? "DEV" : `${PACKAGE_VERSION}-${ext.metadata.version}`
  59. }`;
  60. this.windowProps = this.ext.configMgr.windowProps;
  61. this.windowProps.overrides = this.windowProps.overrides.filter((override) => !override.wmId);
  62. this._kbd = this.ext.keybindings;
  63. this._tree = new Tree(this);
  64. this.eventQueue = new Queue();
  65. this.theme = this.ext.theme;
  66. this.lastFocusedWindow = null;
  67. this.shouldFocusOnHover = this.ext.settings.get_boolean("focus-on-hover-enabled");
  68. Logger.info("forge initialized");
  69. if (this.shouldFocusOnHover) {
  70. // Start the pointer loop to observe the pointer position
  71. // and change the focus window accordingly
  72. this.pointerLoopInit();
  73. }
  74. }
  75. pointerLoopInit() {
  76. if (this._pointerFocusTimeoutId) {
  77. GLib.Source.remove(this._pointerFocusTimeoutId);
  78. }
  79. this._pointerFocusTimeoutId = GLib.timeout_add(
  80. GLib.PRIORITY_DEFAULT,
  81. 16,
  82. this._focusWindowUnderPointer.bind(this)
  83. );
  84. }
  85. addFloatOverride(metaWindow, withWmId) {
  86. let overrides = this.windowProps.overrides;
  87. let wmClass = metaWindow.get_wm_class();
  88. let wmId = metaWindow.get_id();
  89. for (let override in overrides) {
  90. // if the window is already floating
  91. if (override.wmClass === wmClass && override.mode === "float" && !override.wmTitle) return;
  92. }
  93. overrides.push({
  94. wmClass: wmClass,
  95. wmId: withWmId ? wmId : undefined,
  96. mode: "float",
  97. });
  98. this.windowProps.overrides = overrides;
  99. this.ext.configMgr.windowProps = this.windowProps;
  100. }
  101. removeFloatOverride(metaWindow, withWmId) {
  102. let overrides = this.windowProps.overrides;
  103. let wmClass = metaWindow.get_wm_class();
  104. let wmId = metaWindow.get_id();
  105. overrides = overrides.filter(
  106. (override) =>
  107. !(
  108. override.wmClass === wmClass &&
  109. // rules with a Title are written by the user and peristent
  110. !override.wmTitle &&
  111. (!withWmId || override.wmId === wmId)
  112. )
  113. );
  114. this.windowProps.overrides = overrides;
  115. this.ext.configMgr.windowProps = this.windowProps;
  116. }
  117. toggleFloatingMode(action, metaWindow) {
  118. let nodeWindow = this.findNodeWindow(metaWindow);
  119. if (!nodeWindow || !(action || action.mode)) return;
  120. if (nodeWindow.nodeType !== NODE_TYPES.WINDOW) return;
  121. let withWmId = action.name === "FloatToggle";
  122. let floatingExempt = this.isFloatingExempt(metaWindow);
  123. if (floatingExempt) {
  124. this.removeFloatOverride(metaWindow, withWmId);
  125. if (!this.isActiveWindowWorkspaceTiled(metaWindow)) {
  126. nodeWindow.mode = WINDOW_MODES.FLOAT;
  127. } else {
  128. nodeWindow.mode = WINDOW_MODES.TILE;
  129. }
  130. } else {
  131. this.addFloatOverride(metaWindow, withWmId);
  132. nodeWindow.mode = WINDOW_MODES.FLOAT;
  133. }
  134. }
  135. queueEvent(eventObj, interval = 220) {
  136. this.eventQueue.enqueue(eventObj);
  137. if (!this._queueSourceId) {
  138. this._queueSourceId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, interval, () => {
  139. const currEventObj = this.eventQueue.dequeue();
  140. if (currEventObj) {
  141. currEventObj.callback();
  142. }
  143. const result = this.eventQueue.length !== 0;
  144. if (!result) {
  145. this._queueSourceId = 0;
  146. }
  147. return result;
  148. });
  149. }
  150. }
  151. /**
  152. * This is the central place to bind all the non-window signals.
  153. */
  154. _bindSignals() {
  155. if (this._signalsBound) return;
  156. const display = global.display;
  157. const shellWm = global.window_manager;
  158. this._displaySignals = [
  159. display.connect("window-created", this.trackWindow.bind(this)),
  160. display.connect("grab-op-begin", this._handleGrabOpBegin.bind(this)),
  161. display.connect("window-entered-monitor", (_, monitor, metaWindow) => {
  162. this.updateMetaWorkspaceMonitor("window-entered-monitor", monitor, metaWindow);
  163. this.trackCurrentMonWs();
  164. }),
  165. display.connect("grab-op-end", this._handleGrabOpEnd.bind(this)),
  166. display.connect("showing-desktop-changed", () => {
  167. this.hideWindowBorders();
  168. this.updateDecorationLayout();
  169. }),
  170. display.connect("in-fullscreen-changed", () => {
  171. this.renderTree("full-screen-changed");
  172. }),
  173. display.connect("workareas-changed", (_display) => {
  174. if (global.display.get_n_monitors() == 0) {
  175. Logger.debug(`workareas-changed: no monitors, ignoring signal`);
  176. return;
  177. }
  178. if (this.tree.getNodeByType("WINDOW").length > 0) {
  179. let workspaceReload = this.workspaceAdded || this.workspaceRemoved;
  180. if (workspaceReload) {
  181. this.trackCurrentWindows();
  182. this.workspaceRemoved = false;
  183. this.workspaceAdded = false;
  184. } else {
  185. this.renderTree("workareas-changed");
  186. }
  187. }
  188. }),
  189. ];
  190. this._windowManagerSignals = [
  191. shellWm.connect("minimize", () => {
  192. this.hideWindowBorders();
  193. let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
  194. if (focusNodeWindow) {
  195. if (this.tree.getTiledChildren(focusNodeWindow.parentNode.childNodes).length === 0) {
  196. this.tree.resetSiblingPercent(focusNodeWindow.parentNode.parentNode);
  197. }
  198. this.tree.resetSiblingPercent(focusNodeWindow.parentNode);
  199. }
  200. let prevFrozen = this._freezeRender;
  201. if (prevFrozen) this.unfreezeRender();
  202. this.renderTree("minimize");
  203. if (prevFrozen) this.freezeRender();
  204. }),
  205. shellWm.connect("unminimize", () => {
  206. let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
  207. if (focusNodeWindow) {
  208. this.tree.resetSiblingPercent(focusNodeWindow.parentNode);
  209. }
  210. let prevFrozen = this._freezeRender;
  211. if (prevFrozen) this.unfreezeRender();
  212. this.renderTree("unminimize");
  213. if (prevFrozen) this.freezeRender();
  214. }),
  215. shellWm.connect("show-tile-preview", (_, _metaWindow, _rect, _num) => {
  216. // Empty
  217. }),
  218. ];
  219. const globalWsm = global.workspace_manager;
  220. this._workspaceManagerSignals = [
  221. globalWsm.connect("showing-desktop-changed", () => {
  222. this.hideWindowBorders();
  223. this.updateDecorationLayout();
  224. }),
  225. globalWsm.connect("workspace-added", (_, wsIndex) => {
  226. this.tree.addWorkspace(wsIndex);
  227. this.trackCurrentMonWs();
  228. this.workspaceAdded = true;
  229. this.renderTree("workspace-added");
  230. }),
  231. globalWsm.connect("workspace-removed", (_, wsIndex) => {
  232. this.tree.removeWorkspace(wsIndex);
  233. this.trackCurrentMonWs();
  234. this.workspaceRemoved = true;
  235. this.updateDecorationLayout();
  236. this.renderTree("workspace-removed");
  237. }),
  238. globalWsm.connect("active-workspace-changed", () => {
  239. this.hideWindowBorders();
  240. this.trackCurrentMonWs();
  241. this.updateDecorationLayout();
  242. this.renderTree("active-workspace-changed");
  243. }),
  244. ];
  245. let numberOfWorkspaces = globalWsm.get_n_workspaces();
  246. for (let i = 0; i < numberOfWorkspaces; i++) {
  247. let workspace = globalWsm.get_workspace_by_index(i);
  248. this.bindWorkspaceSignals(workspace);
  249. }
  250. let settings = this.ext.settings;
  251. settings.connect("changed", (_, settingName) => {
  252. switch (settingName) {
  253. case "focus-border-toggle":
  254. this.renderTree(settingName);
  255. break;
  256. case "focus-on-hover-enabled":
  257. this.shouldFocusOnHover = settings.get_boolean(settingName);
  258. if (this.shouldFocusOnHover) {
  259. this.pointerLoopInit();
  260. }
  261. break;
  262. case "tiling-mode-enabled":
  263. this.renderTree(settingName);
  264. break;
  265. case "window-gap-size-increment":
  266. case "window-gap-size":
  267. case "window-gap-hidden-on-single":
  268. case "workspace-skip-tile":
  269. this.renderTree(settingName, true);
  270. break;
  271. case "stacked-tiling-mode-enabled":
  272. if (!settings.get_boolean(settingName)) {
  273. let stackedNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.STACKED);
  274. stackedNodes.forEach((node) => {
  275. node.prevLayout = node.layout;
  276. node.layout = this.determineSplitLayout();
  277. });
  278. } else {
  279. let hSplitNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.HSPLIT);
  280. let vSplitNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.VSPLIT);
  281. Array.prototype.push.apply(hSplitNodes, vSplitNodes);
  282. hSplitNodes.forEach((node) => {
  283. if (node.prevLayout && node.prevLayout === LAYOUT_TYPES.STACKED) {
  284. node.layout = LAYOUT_TYPES.STACKED;
  285. }
  286. });
  287. }
  288. this.renderTree(settingName);
  289. break;
  290. case "tabbed-tiling-mode-enabled":
  291. if (!settings.get_boolean(settingName)) {
  292. let tabbedNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.TABBED);
  293. tabbedNodes.forEach((node) => {
  294. node.prevLayout = node.layout;
  295. node.layout = this.determineSplitLayout();
  296. });
  297. } else {
  298. let hSplitNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.HSPLIT);
  299. let vSplitNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.VSPLIT);
  300. Array.prototype.push.apply(hSplitNodes, vSplitNodes);
  301. hSplitNodes.forEach((node) => {
  302. if (node.prevLayout && node.prevLayout === LAYOUT_TYPES.TABBED) {
  303. node.layout = LAYOUT_TYPES.TABBED;
  304. }
  305. });
  306. }
  307. this.renderTree(settingName);
  308. break;
  309. case "css-updated":
  310. this.theme.reloadStylesheet();
  311. break;
  312. case "float-always-on-top-enabled":
  313. if (!settings.get_boolean(settingName)) {
  314. this.cleanupAlwaysFloat();
  315. } else {
  316. this.restoreAlwaysFloat();
  317. }
  318. break;
  319. default:
  320. break;
  321. }
  322. });
  323. this._overviewSignals = [
  324. Main.overview.connect("hiding", () => {
  325. this.fromOverview = true;
  326. const eventObj = {
  327. name: "focus-after-overview",
  328. callback: () => {
  329. const focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
  330. this.updateStackedFocus(focusNodeWindow);
  331. this.updateTabbedFocus(focusNodeWindow);
  332. this.movePointerWith(focusNodeWindow);
  333. },
  334. };
  335. this.queueEvent(eventObj);
  336. }),
  337. Main.overview.connect("showing", () => {
  338. this.toOverview = true;
  339. }),
  340. ];
  341. this._signalsBound = true;
  342. }
  343. cleanupAlwaysFloat() {
  344. // remove the setting for each node window
  345. this.allNodeWindows.forEach((w) => {
  346. if (w.mode === WINDOW_MODES.FLOAT) {
  347. w.nodeValue.is_above() && w.nodeValue.unmake_above();
  348. }
  349. });
  350. }
  351. restoreAlwaysFloat() {
  352. this.allNodeWindows.forEach((w) => {
  353. if (w.mode === WINDOW_MODES.FLOAT) {
  354. !w.nodeValue.is_above() && w.nodeValue.make_above();
  355. }
  356. });
  357. }
  358. trackCurrentMonWs() {
  359. let metaWindow = this.focusMetaWindow;
  360. if (!metaWindow) return;
  361. const currentMonitor = global.display.get_current_monitor();
  362. const currentWorkspace = global.display.get_workspace_manager().get_active_workspace_index();
  363. let currentMonWs = `mo${currentMonitor}ws${currentWorkspace}`;
  364. let activeMetaMonWs = `mo${metaWindow.get_monitor()}ws${metaWindow.get_workspace().index()}`;
  365. let currentWsNode = this.tree.findNode(`ws${currentWorkspace}`);
  366. if (!currentWsNode) {
  367. return;
  368. }
  369. // Search for all the valid windows on the workspace
  370. const monWindows = currentWsNode.getNodeByType(NODE_TYPES.WORKSPACE).flatMap((ws) => {
  371. return ws
  372. .getNodeByType(NODE_TYPES.WINDOW)
  373. .filter(
  374. (w) =>
  375. !w.nodeValue.minimized &&
  376. w.isTile() &&
  377. w.nodeValue !== metaWindow &&
  378. // The searched window should be on the same monitor workspace
  379. // This ensures that Forge already updated the workspace node tree:
  380. currentMonWs === activeMetaMonWs
  381. )
  382. .map((w) => w.nodeValue);
  383. });
  384. this.sortedWindows = global.display.sort_windows_by_stacking(monWindows).reverse();
  385. }
  386. // TODO move this to workspace.js
  387. bindWorkspaceSignals(metaWorkspace) {
  388. if (metaWorkspace) {
  389. if (!metaWorkspace.workspaceSignals) {
  390. let workspaceSignals = [
  391. metaWorkspace.connect("window-added", (_, metaWindow) => {
  392. if (!this._wsWindowAddSrcId) {
  393. this._wsWindowAddSrcId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => {
  394. this.updateMetaWorkspaceMonitor(
  395. "window-added",
  396. metaWindow.get_monitor(),
  397. metaWindow
  398. );
  399. this._wsWindowAddSrcId = 0;
  400. return false;
  401. });
  402. }
  403. }),
  404. ];
  405. metaWorkspace.workspaceSignals = workspaceSignals;
  406. }
  407. }
  408. }
  409. // TODO move this in command.js
  410. command(action) {
  411. let focusWindow = this.focusMetaWindow;
  412. // Do not check if the node window is null, some of the commands do not need the focus window
  413. let focusNodeWindow = this.findNodeWindow(focusWindow);
  414. let currentLayout;
  415. switch (action.name) {
  416. case "FloatNonPersistentToggle":
  417. case "FloatToggle":
  418. case "FloatClassToggle":
  419. this.toggleFloatingMode(action, focusWindow);
  420. const rectRequest = {
  421. x: action.x,
  422. y: action.y,
  423. width: action.width,
  424. height: action.height,
  425. };
  426. let moveRect = {
  427. x: Utils.resolveX(rectRequest, focusWindow),
  428. y: Utils.resolveY(rectRequest, focusWindow),
  429. width: Utils.resolveWidth(rectRequest, focusWindow),
  430. height: Utils.resolveHeight(rectRequest, focusWindow),
  431. };
  432. this.move(focusWindow, moveRect);
  433. let existParent = focusNodeWindow.parentNode;
  434. if (this.tree.getTiledChildren(existParent.childNodes).length <= 1) {
  435. existParent.percent = 0.0;
  436. this.tree.resetSiblingPercent(existParent.parentNode);
  437. }
  438. this.tree.resetSiblingPercent(existParent);
  439. this.renderTree("float-toggle", true);
  440. break;
  441. case "Move":
  442. this.unfreezeRender();
  443. let moveDirection = Utils.resolveDirection(action.direction);
  444. let prev = focusNodeWindow;
  445. let moved = this.tree.move(focusNodeWindow, moveDirection);
  446. if (!focusNodeWindow) {
  447. focusNodeWindow = this.findNodeWindow(this.focusMetaWindow);
  448. }
  449. this.queueEvent({
  450. name: "move",
  451. callback: () => {
  452. if (this.eventQueue.length <= 0) {
  453. this.unfreezeRender();
  454. if (focusNodeWindow.parentNode.layout === LAYOUT_TYPES.STACKED) {
  455. focusNodeWindow.parentNode.appendChild(focusNodeWindow);
  456. focusNodeWindow.nodeValue.raise();
  457. focusNodeWindow.nodeValue.activate(global.display.get_current_time());
  458. this.renderTree("move-stacked-queue");
  459. }
  460. if (focusNodeWindow.parentNode.layout === LAYOUT_TYPES.TABBED) {
  461. focusNodeWindow.nodeValue.raise();
  462. focusNodeWindow.nodeValue.activate(global.display.get_current_time());
  463. if (prev) prev.parentNode.lastTabFocus = prev.nodeValue;
  464. this.renderTree("move-tabbed-queue");
  465. }
  466. this.movePointerWith(focusNodeWindow);
  467. }
  468. },
  469. });
  470. if (moved) {
  471. if (prev) prev.parentNode.lastTabFocus = prev.nodeValue;
  472. this.renderTree("move-window");
  473. }
  474. break;
  475. case "Focus":
  476. let focusDirection = Utils.resolveDirection(action.direction);
  477. focusNodeWindow = this.tree.focus(focusNodeWindow, focusDirection);
  478. if (!focusNodeWindow) {
  479. focusNodeWindow = this.findNodeWindow(this.focusMetaWindow);
  480. }
  481. break;
  482. case "Swap":
  483. if (!focusNodeWindow) return;
  484. this.unfreezeRender();
  485. let swapDirection = Utils.resolveDirection(action.direction);
  486. this.tree.swap(focusNodeWindow, swapDirection);
  487. focusNodeWindow.nodeValue.raise();
  488. this.updateTabbedFocus(focusNodeWindow);
  489. this.updateStackedFocus(focusNodeWindow);
  490. this.movePointerWith(focusNodeWindow);
  491. this.renderTree("swap", true);
  492. break;
  493. case "Split":
  494. if (!focusNodeWindow) return;
  495. currentLayout = focusNodeWindow.parentNode.layout;
  496. if (currentLayout === LAYOUT_TYPES.STACKED || currentLayout === LAYOUT_TYPES.TABBED) {
  497. return;
  498. }
  499. let orientation = action.orientation
  500. ? action.orientation.toUpperCase()
  501. : ORIENTATION_TYPES.NONE;
  502. this.tree.split(focusNodeWindow, orientation);
  503. this.renderTree("split");
  504. break;
  505. case "LayoutToggle":
  506. if (!focusNodeWindow) return;
  507. currentLayout = focusNodeWindow.parentNode.layout;
  508. if (currentLayout === LAYOUT_TYPES.HSPLIT) {
  509. focusNodeWindow.parentNode.layout = LAYOUT_TYPES.VSPLIT;
  510. } else if (currentLayout === LAYOUT_TYPES.VSPLIT) {
  511. focusNodeWindow.parentNode.layout = LAYOUT_TYPES.HSPLIT;
  512. }
  513. this.tree.attachNode = focusNodeWindow.parentNode;
  514. this.renderTree("layout-split-toggle");
  515. break;
  516. case "FocusBorderToggle":
  517. let focusBorderEnabled = this.ext.settings.get_boolean("focus-border-toggle");
  518. this.ext.settings.set_boolean("focus-border-toggle", !focusBorderEnabled);
  519. break;
  520. case "TilingModeToggle":
  521. // FIXME, not sure if this toggle is still needed from a use case
  522. // perspective, since Extension.disable also should do the same thing.
  523. let tilingModeEnabled = this.ext.settings.get_boolean("tiling-mode-enabled");
  524. this.ext.settings.set_boolean("tiling-mode-enabled", !tilingModeEnabled);
  525. if (tilingModeEnabled) {
  526. this.floatAllWindows();
  527. } else {
  528. this.unfloatAllWindows();
  529. }
  530. this.renderTree(`tiling-mode-toggle ${!tilingModeEnabled}`);
  531. break;
  532. case "GapSize":
  533. let gapIncrement = this.ext.settings.get_uint("window-gap-size-increment");
  534. let amount = action.amount;
  535. gapIncrement = gapIncrement + amount;
  536. if (gapIncrement < 0) gapIncrement = 0;
  537. if (gapIncrement > 8) gapIncrement = 8;
  538. this.ext.settings.set_uint("window-gap-size-increment", gapIncrement);
  539. break;
  540. case "WorkspaceActiveTileToggle":
  541. let activeWorkspace = global.workspace_manager.get_active_workspace_index();
  542. let skippedWorkspaces = this.ext.settings.get_string("workspace-skip-tile");
  543. let workspaceSkipped = false;
  544. let skippedArr = [];
  545. if (skippedWorkspaces.length === 0) {
  546. skippedArr.push(`${activeWorkspace}`);
  547. this.floatWorkspace(activeWorkspace);
  548. } else {
  549. skippedArr = skippedWorkspaces.split(",");
  550. for (let i = 0; i < skippedArr.length; i++) {
  551. if (`${skippedArr[i]}` === `${activeWorkspace}`) {
  552. workspaceSkipped = true;
  553. break;
  554. }
  555. }
  556. if (workspaceSkipped) {
  557. // tile this workspace
  558. let indexWs = skippedArr.indexOf(`${activeWorkspace}`);
  559. skippedArr.splice(indexWs, 1);
  560. this.unfloatWorkspace(activeWorkspace);
  561. } else {
  562. // skip tiling workspace
  563. skippedArr.push(`${activeWorkspace}`);
  564. this.floatWorkspace(activeWorkspace);
  565. }
  566. }
  567. this.ext.settings.set_string("workspace-skip-tile", skippedArr.toString());
  568. this.renderTree("workspace-toggle");
  569. break;
  570. case "LayoutStackedToggle":
  571. if (!focusNodeWindow) return;
  572. if (!this.ext.settings.get_boolean("stacked-tiling-mode-enabled")) return;
  573. if (focusNodeWindow.parentNode.isMonitor()) {
  574. this.tree.split(focusNodeWindow, ORIENTATION_TYPES.HORIZONTAL, true);
  575. }
  576. currentLayout = focusNodeWindow.parentNode.layout;
  577. if (currentLayout === LAYOUT_TYPES.STACKED) {
  578. focusNodeWindow.parentNode.layout = this.determineSplitLayout();
  579. this.tree.resetSiblingPercent(focusNodeWindow.parentNode);
  580. } else {
  581. if (currentLayout === LAYOUT_TYPES.TABBED) {
  582. focusNodeWindow.parentNode.lastTabFocus = null;
  583. }
  584. focusNodeWindow.parentNode.layout = LAYOUT_TYPES.STACKED;
  585. let lastChild = focusNodeWindow.parentNode.lastChild;
  586. if (lastChild.nodeType === NODE_TYPES.WINDOW) {
  587. lastChild.nodeValue.activate(global.display.get_current_time());
  588. }
  589. }
  590. this.unfreezeRender();
  591. this.tree.attachNode = focusNodeWindow.parentNode;
  592. this.renderTree("layout-stacked-toggle");
  593. break;
  594. case "LayoutTabbedToggle":
  595. if (!focusNodeWindow) return;
  596. if (!this.ext.settings.get_boolean("tabbed-tiling-mode-enabled")) return;
  597. if (focusNodeWindow.parentNode.isMonitor()) {
  598. this.tree.split(focusNodeWindow, ORIENTATION_TYPES.HORIZONTAL, true);
  599. }
  600. currentLayout = focusNodeWindow.parentNode.layout;
  601. if (currentLayout === LAYOUT_TYPES.TABBED) {
  602. focusNodeWindow.parentNode.layout = this.determineSplitLayout();
  603. this.tree.resetSiblingPercent(focusNodeWindow.parentNode);
  604. focusNodeWindow.parentNode.lastTabFocus = null;
  605. } else {
  606. focusNodeWindow.parentNode.layout = LAYOUT_TYPES.TABBED;
  607. focusNodeWindow.parentNode.lastTabFocus = focusNodeWindow.nodeValue;
  608. }
  609. this.unfreezeRender();
  610. this.tree.attachNode = focusNodeWindow.parentNode;
  611. this.renderTree("layout-tabbed-toggle");
  612. break;
  613. case "CancelOperation":
  614. if (focusNodeWindow.mode === WINDOW_MODES.GRAB_TILE) {
  615. this.cancelGrab = true;
  616. }
  617. break;
  618. case "PrefsOpen":
  619. let existWindow = Utils.findWindowWith(this.prefsTitle);
  620. if (existWindow && existWindow.get_workspace()) {
  621. existWindow
  622. .get_workspace()
  623. .activate_with_focus(existWindow, global.display.get_current_time());
  624. this.moveCenter(existWindow);
  625. } else {
  626. this.ext.openPreferences();
  627. }
  628. break;
  629. case "WindowSwapLastActive":
  630. if (focusNodeWindow) {
  631. let lastActiveWindow = global.display.get_tab_next(
  632. Meta.TabList.NORMAL,
  633. global.display.get_workspace_manager().get_active_workspace(),
  634. focusNodeWindow.nodeValue,
  635. false
  636. );
  637. let lastActiveNodeWindow = this.tree.findNode(lastActiveWindow);
  638. this.tree.swapPairs(lastActiveNodeWindow, focusNodeWindow);
  639. this.movePointerWith(focusNodeWindow);
  640. this.renderTree("swap-last-active");
  641. }
  642. break;
  643. case "SnapLayoutMove":
  644. if (focusNodeWindow) {
  645. let workareaRect = focusNodeWindow.nodeValue.get_work_area_current_monitor();
  646. let layoutAmount = action.amount;
  647. let layoutDirection = action.direction.toUpperCase();
  648. let layout = {};
  649. let processGap = false;
  650. switch (layoutDirection) {
  651. case "LEFT":
  652. layout.width = layoutAmount * workareaRect.width;
  653. layout.height = workareaRect.height;
  654. layout.x = workareaRect.x;
  655. layout.y = workareaRect.y;
  656. processGap = true;
  657. break;
  658. case "RIGHT":
  659. layout.width = layoutAmount * workareaRect.width;
  660. layout.height = workareaRect.height;
  661. layout.x = workareaRect.x + (workareaRect.width - layout.width);
  662. layout.y = workareaRect.y;
  663. processGap = true;
  664. break;
  665. case "CENTER":
  666. let metaRect = this.focusMetaWindow.get_frame_rect();
  667. layout.x = "center";
  668. layout.y = "center";
  669. layout = {
  670. x: Utils.resolveX(layout, this.focusMetaWindow),
  671. y: Utils.resolveY(layout, this.focusMetaWindow),
  672. width: metaRect.width,
  673. height: metaRect.height,
  674. };
  675. break;
  676. default:
  677. break;
  678. }
  679. focusNodeWindow.rect = layout;
  680. if (processGap) {
  681. focusNodeWindow.rect = this.tree.processGap(focusNodeWindow);
  682. }
  683. if (!focusNodeWindow.isFloat()) {
  684. this.addFloatOverride(focusNodeWindow.nodeValue, false);
  685. }
  686. this.move(focusNodeWindow.nodeValue, focusNodeWindow.rect);
  687. this.queueEvent({
  688. name: "snap-layout-move",
  689. callback: () => {
  690. this.renderTree("snap-layout-move");
  691. },
  692. });
  693. break;
  694. }
  695. case "ShowTabDecorationToggle":
  696. if (!focusNodeWindow) return;
  697. if (!this.ext.settings.get_boolean("tabbed-tiling-mode-enabled")) return;
  698. let showTabs = this.ext.settings.get_boolean("showtab-decoration-enabled");
  699. this.ext.settings.set_boolean("showtab-decoration-enabled", !showTabs);
  700. this.unfreezeRender();
  701. this.tree.attachNode = focusNodeWindow.parentNode;
  702. this.renderTree("showtab-decoration-enabled");
  703. break;
  704. case "WindowResizeRight":
  705. this.resize(Meta.GrabOp.KEYBOARD_RESIZING_E, action.amount);
  706. break;
  707. case "WindowResizeLeft":
  708. this.resize(Meta.GrabOp.KEYBOARD_RESIZING_W, action.amount);
  709. break;
  710. case "WindowResizeTop":
  711. this.resize(Meta.GrabOp.KEYBOARD_RESIZING_N, action.amount);
  712. break;
  713. case "WindowResizeBottom":
  714. this.resize(Meta.GrabOp.KEYBOARD_RESIZING_S, action.amount);
  715. break;
  716. default:
  717. break;
  718. }
  719. }
  720. resize(grabOp, amount) {
  721. let metaWindow = this.focusMetaWindow;
  722. let display = global.display;
  723. this._handleGrabOpBegin(display, metaWindow, grabOp);
  724. let rect = metaWindow.get_frame_rect();
  725. let direction = Utils.directionFromGrab(grabOp);
  726. switch (direction) {
  727. case Meta.MotionDirection.RIGHT:
  728. rect.width = rect.width + amount;
  729. break;
  730. case Meta.MotionDirection.LEFT:
  731. rect.width = rect.width + amount;
  732. rect.x = rect.x - amount;
  733. break;
  734. case Meta.MotionDirection.UP:
  735. rect.height = rect.height + amount;
  736. break;
  737. case Meta.MotionDirection.DOWN:
  738. rect.height = rect.height + amount;
  739. rect.y = rect.y - amount;
  740. break;
  741. }
  742. this.move(metaWindow, rect);
  743. this.queueEvent(
  744. {
  745. name: "manual-resize",
  746. callback: () => {
  747. if (this.eventQueue.length === 0) {
  748. this._handleGrabOpEnd(display, metaWindow, grabOp);
  749. }
  750. },
  751. },
  752. 50
  753. );
  754. }
  755. disable() {
  756. Utils._disableDecorations();
  757. this._removeSignals();
  758. this.disabled = true;
  759. Logger.debug(`extension:disable`);
  760. }
  761. enable() {
  762. this._bindSignals();
  763. this.reloadTree("enable");
  764. Logger.debug(`extension:enable`);
  765. }
  766. findNodeWindow(metaWindow) {
  767. return this.tree.findNode(metaWindow);
  768. }
  769. get focusMetaWindow() {
  770. return global.display.get_focus_window();
  771. }
  772. get tree() {
  773. if (!this._tree) {
  774. this._tree = new Tree(this);
  775. }
  776. return this._tree;
  777. }
  778. get kbd() {
  779. if (!this._kbd) {
  780. this._kbd = new Keybindings(this.ext);
  781. this.ext.keybindings = this._kbd;
  782. }
  783. return this._kbd;
  784. }
  785. get windowsActiveWorkspace() {
  786. let wsManager = global.workspace_manager;
  787. return global.display.get_tab_list(Meta.TabList.NORMAL_ALL, wsManager.get_active_workspace());
  788. }
  789. get windowsAllWorkspaces() {
  790. let wsManager = global.workspace_manager;
  791. let windowsAll = [];
  792. for (let i = 0; i < wsManager.get_n_workspaces(); i++) {
  793. Array.prototype.push.apply(
  794. windowsAll,
  795. global.display.get_tab_list(Meta.TabList.NORMAL_ALL, wsManager.get_workspace_by_index(i))
  796. );
  797. }
  798. windowsAll.sort((w1, w2) => {
  799. return w1.get_stable_sequence() - w2.get_stable_sequence();
  800. });
  801. return windowsAll;
  802. }
  803. getWindowsOnWorkspace(workspaceIndex) {
  804. const workspaceNode = this.tree.findNode(`ws${workspaceIndex}`);
  805. const workspaceWindows = workspaceNode.getNodeByType(NODE_TYPES.WINDOW);
  806. return workspaceWindows;
  807. }
  808. determineSplitLayout() {
  809. // if the monitor width is less than height, the monitor could be vertical orientation;
  810. let monitorRect = global.display.get_monitor_geometry(global.display.get_current_monitor());
  811. if (monitorRect.width < monitorRect.height) {
  812. return LAYOUT_TYPES.VSPLIT;
  813. }
  814. return LAYOUT_TYPES.HSPLIT;
  815. }
  816. floatWorkspace(workspaceIndex) {
  817. const workspaceWindows = this.getWindowsOnWorkspace(workspaceIndex);
  818. if (!workspaceWindows) return;
  819. workspaceWindows.forEach((w) => {
  820. w.float = true;
  821. });
  822. }
  823. unfloatWorkspace(workspaceIndex) {
  824. const workspaceWindows = this.getWindowsOnWorkspace(workspaceIndex);
  825. if (!workspaceWindows) return;
  826. workspaceWindows.forEach((w) => {
  827. w.tile = true;
  828. });
  829. }
  830. hideActorBorder(actor) {
  831. if (actor.border) {
  832. actor.border.hide();
  833. }
  834. if (actor.splitBorder) {
  835. actor.splitBorder.hide();
  836. }
  837. }
  838. hideWindowBorders() {
  839. this.tree.nodeWindows.forEach((nodeWindow) => {
  840. let actor = nodeWindow.windowActor;
  841. if (actor) {
  842. this.hideActorBorder(actor);
  843. }
  844. if (nodeWindow.parentNode.isTabbed()) {
  845. if (nodeWindow.tab) {
  846. // TODO: review the cleanup of the tab:St.Widget variable
  847. try {
  848. nodeWindow.tab.remove_style_class_name("window-tabbed-tab-active");
  849. } catch (e) {
  850. // Logger.warn(e);
  851. }
  852. }
  853. }
  854. });
  855. }
  856. // Window movement API
  857. move(metaWindow, rect) {
  858. if (!metaWindow) return;
  859. if (metaWindow.grabbed) return;
  860. metaWindow.unmaximize(Meta.MaximizeFlags.HORIZONTAL);
  861. metaWindow.unmaximize(Meta.MaximizeFlags.VERTICAL);
  862. metaWindow.unmaximize(Meta.MaximizeFlags.BOTH);
  863. let windowActor = metaWindow.get_compositor_private();
  864. if (!windowActor) return;
  865. windowActor.remove_all_transitions();
  866. metaWindow.move_frame(true, rect.x, rect.y);
  867. metaWindow.move_resize_frame(true, rect.x, rect.y, rect.width, rect.height);
  868. }
  869. moveCenter(metaWindow) {
  870. if (!metaWindow) return;
  871. let frameRect = metaWindow.get_frame_rect();
  872. const rectRequest = {
  873. x: "center",
  874. y: "center",
  875. width: frameRect.width,
  876. height: frameRect.height,
  877. };
  878. let moveRect = {
  879. x: Utils.resolveX(rectRequest, metaWindow),
  880. y: Utils.resolveY(rectRequest, metaWindow),
  881. width: Utils.resolveWidth(rectRequest, metaWindow),
  882. height: Utils.resolveHeight(rectRequest, metaWindow),
  883. };
  884. this.move(metaWindow, moveRect);
  885. }
  886. rectForMonitor(node, targetMonitor) {
  887. if (!node || (node && node.nodeType !== NODE_TYPES.WINDOW)) return null;
  888. if (targetMonitor < 0) return null;
  889. let currentWorkArea = node.nodeValue.get_work_area_current_monitor();
  890. let nextWorkArea = node.nodeValue.get_work_area_for_monitor(targetMonitor);
  891. if (currentWorkArea && nextWorkArea) {
  892. let rect = node.rect;
  893. if (!rect && node.mode === WINDOW_MODES.FLOAT) {
  894. rect = node.nodeValue.get_frame_rect();
  895. }
  896. let hRatio = 1;
  897. let wRatio = 1;
  898. hRatio = nextWorkArea.height / currentWorkArea.height;
  899. wRatio = nextWorkArea.width / currentWorkArea.width;
  900. rect.height *= hRatio;
  901. rect.width *= wRatio;
  902. if (nextWorkArea.y < currentWorkArea.y) {
  903. rect.y =
  904. ((nextWorkArea.y + rect.y - currentWorkArea.y) / currentWorkArea.height) *
  905. nextWorkArea.height;
  906. } else if (nextWorkArea.y > currentWorkArea.y) {
  907. rect.y = (rect.y / currentWorkArea.height) * nextWorkArea.height + nextWorkArea.y;
  908. }
  909. if (nextWorkArea.x < currentWorkArea.x) {
  910. rect.x =
  911. ((nextWorkArea.x + rect.x - currentWorkArea.x) / currentWorkArea.width) *
  912. nextWorkArea.width;
  913. } else if (nextWorkArea.x > currentWorkArea.x) {
  914. rect.x = (rect.x / currentWorkArea.width) * nextWorkArea.width + nextWorkArea.x;
  915. }
  916. return rect;
  917. }
  918. return null;
  919. }
  920. _removeSignals() {
  921. if (!this._signalsBound) return;
  922. if (this._displaySignals) {
  923. for (const displaySignal of this._displaySignals) {
  924. global.display.disconnect(displaySignal);
  925. }
  926. this._displaySignals.length = 0;
  927. this._displaySignals = undefined;
  928. }
  929. if (this._windowManagerSignals) {
  930. for (const windowManagerSignal of this._windowManagerSignals) {
  931. global.window_manager.disconnect(windowManagerSignal);
  932. }
  933. this._windowManagerSignals.length = 0;
  934. this._windowManagerSignals = undefined;
  935. }
  936. const globalWsm = global.workspace_manager;
  937. if (this._workspaceManagerSignals) {
  938. for (const workspaceManagerSignal of this._workspaceManagerSignals) {
  939. globalWsm.disconnect(workspaceManagerSignal);
  940. }
  941. this._workspaceManagerSignals.length = 0;
  942. this._workspaceManagerSignals = undefined;
  943. }
  944. let numberOfWorkspaces = globalWsm.get_n_workspaces();
  945. for (let i = 0; i < numberOfWorkspaces; i++) {
  946. let workspace = globalWsm.get_workspace_by_index(i);
  947. if (workspace.workspaceSignals) {
  948. for (const workspaceSignal of workspace.workspaceSignals) {
  949. workspace.disconnect(workspaceSignal);
  950. }
  951. workspace.workspaceSignals.length = 0;
  952. workspace.workspaceSignals = undefined;
  953. }
  954. }
  955. let allWindows = this.windowsAllWorkspaces;
  956. if (allWindows) {
  957. for (let metaWindow of allWindows) {
  958. if (metaWindow.windowSignals !== undefined) {
  959. for (const windowSignal of metaWindow.windowSignals) {
  960. metaWindow.disconnect(windowSignal);
  961. }
  962. metaWindow.windowSignals.length = 0;
  963. metaWindow.windowSignals = undefined;
  964. }
  965. let windowActor = metaWindow.get_compositor_private();
  966. if (windowActor && windowActor.actorSignals) {
  967. for (const actorSignal of windowActor.actorSignals) {
  968. windowActor.disconnect(actorSignal);
  969. }
  970. windowActor.actorSignals.length = 0;
  971. windowActor.actorSignals = undefined;
  972. }
  973. if (windowActor && windowActor.border) {
  974. windowActor.border.hide();
  975. if (global.window_group) {
  976. global.window_group.remove_child(windowActor.border);
  977. }
  978. windowActor.border = undefined;
  979. }
  980. if (windowActor && windowActor.splitBorder) {
  981. windowActor.splitBorder.hide();
  982. if (global.window_group) {
  983. global.window_group.remove_child(windowActor.splitBorder);
  984. }
  985. windowActor.splitBorder = undefined;
  986. }
  987. }
  988. }
  989. if (this._renderTreeSrcId) {
  990. GLib.Source.remove(this._renderTreeSrcId);
  991. this._renderTreeSrcId = 0;
  992. }
  993. if (this._reloadTreeSrcId) {
  994. GLib.Source.remove(this._reloadTreeSrcId);
  995. this._reloadTreeSrcId = 0;
  996. }
  997. if (this._wsWindowAddSrcId) {
  998. GLib.Source.remove(this._wsWindowAddSrcId);
  999. this._wsWindowAddSrcId = 0;
  1000. }
  1001. if (this._queueSourceId) {
  1002. GLib.Source.remove(this._queueSourceId);
  1003. this._queueSourceId = 0;
  1004. }
  1005. if (this._pointerFocusTimeoutId) {
  1006. GLib.Source.remove(this._pointerFocusTimeoutId);
  1007. this._pointerFocusTimeoutId = 0;
  1008. }
  1009. if (this._prefsOpenSrcId) {
  1010. GLib.Source.remove(this._prefsOpenSrcId);
  1011. this._prefsOpenSrcId = 0;
  1012. }
  1013. if (this._overviewSignals) {
  1014. for (const overviewSignal of this._overviewSignals) {
  1015. Main.overview.disconnect(overviewSignal);
  1016. }
  1017. this._overviewSignals.length = 0;
  1018. this._overviewSignals = null;
  1019. }
  1020. this._signalsBound = false;
  1021. }
  1022. renderTree(from, force = false) {
  1023. let wasFrozen = this._freezeRender;
  1024. if (force && wasFrozen) this.unfreezeRender();
  1025. if (this._freezeRender || !this.ext.settings.get_boolean("tiling-mode-enabled")) {
  1026. this.updateDecorationLayout();
  1027. this.updateBorderLayout();
  1028. } else {
  1029. if (!this._renderTreeSrcId) {
  1030. this._renderTreeSrcId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
  1031. this.processFloats();
  1032. this.tree.render(from);
  1033. this._renderTreeSrcId = 0;
  1034. this.updateDecorationLayout();
  1035. this.updateBorderLayout();
  1036. if (wasFrozen) this.freezeRender();
  1037. return false;
  1038. });
  1039. }
  1040. }
  1041. }
  1042. processFloats() {
  1043. this.allNodeWindows.forEach((nodeWindow) => {
  1044. let metaWindow = nodeWindow.nodeValue;
  1045. if (this.isFloatingExempt(metaWindow) || !this.isActiveWindowWorkspaceTiled(metaWindow)) {
  1046. nodeWindow.float = true;
  1047. } else {
  1048. nodeWindow.float = false;
  1049. }
  1050. });
  1051. }
  1052. get allNodeWindows() {
  1053. return this.tree.getNodeByType(NODE_TYPES.WINDOW);
  1054. }
  1055. /**
  1056. * Reloads the tree. This is an expensive operation.
  1057. * Useful when using dynamic workspaces in GNOME-shell.
  1058. *
  1059. * TODO: add support to reload the tree from a JSON dump file.
  1060. * TODO: move this to tree.js
  1061. */
  1062. reloadTree(from) {
  1063. if (!this._reloadTreeSrcId) {
  1064. this._reloadTreeSrcId = GLib.idle_add(GLib.PRIORITY_LOW, () => {
  1065. Utils._disableDecorations();
  1066. let treeWorkspaces = this.tree.nodeWorkpaces;
  1067. let wsManager = global.workspace_manager;
  1068. let globalWsNum = wsManager.get_n_workspaces();
  1069. // empty out the root children nodes
  1070. this.tree.childNodes.length = 0;
  1071. this.tree.attachNode = undefined;
  1072. // initialize the workspaces and monitors id strings
  1073. this.tree._initWorkspaces();
  1074. this.trackCurrentWindows();
  1075. this.renderTree(from);
  1076. this._reloadTreeSrcId = 0;
  1077. return false;
  1078. });
  1079. }
  1080. }
  1081. sameParentMonitor(firstNode, secondNode) {
  1082. if (!firstNode || !secondNode) return false;
  1083. if (!firstNode.nodeValue || !secondNode.nodeValue) return false;
  1084. if (!firstNode.nodeValue.get_workspace()) return false;
  1085. if (!secondNode.nodeValue.get_workspace()) return false;
  1086. let firstMonWs = `mo${firstNode.nodeValue.get_monitor()}ws${firstNode.nodeValue
  1087. .get_workspace()
  1088. .index()}`;
  1089. let secondMonWs = `mo${secondNode.nodeValue.get_monitor()}ws${secondNode.nodeValue
  1090. .get_workspace()
  1091. .index()}`;
  1092. return firstMonWs === secondMonWs;
  1093. }
  1094. showWindowBorders() {
  1095. let metaWindow = this.focusMetaWindow;
  1096. if (!metaWindow) return;
  1097. let windowActor = metaWindow.get_compositor_private();
  1098. if (!windowActor) return;
  1099. let nodeWindow = this.findNodeWindow(metaWindow);
  1100. if (!nodeWindow) return;
  1101. if (metaWindow.get_wm_class() === null) return;
  1102. let borders = [];
  1103. let focusBorderEnabled = this.ext.settings.get_boolean("focus-border-toggle");
  1104. let splitBorderEnabled = this.ext.settings.get_boolean("split-border-toggle");
  1105. let tilingModeEnabled = this.ext.settings.get_boolean("tiling-mode-enabled");
  1106. let gap = this.calculateGaps(nodeWindow);
  1107. let maximized = () => {
  1108. return metaWindow.get_maximized() === 3 || metaWindow.is_fullscreen() || gap === 0;
  1109. };
  1110. let monitorCount = global.display.get_n_monitors();
  1111. let tiledChildren = this.tree.getTiledChildren(nodeWindow.parentNode.childNodes);
  1112. let inset = 3;
  1113. let parentNode = nodeWindow.parentNode;
  1114. const floatingWindow = nodeWindow.isFloat();
  1115. const tiledBorder = windowActor.border;
  1116. if (parentNode.isTabbed()) {
  1117. if (nodeWindow.tab) {
  1118. nodeWindow.tab.add_style_class_name("window-tabbed-tab-active");
  1119. }
  1120. }
  1121. if (tiledBorder && focusBorderEnabled) {
  1122. if (
  1123. !maximized() ||
  1124. (gap === 0 && tiledChildren.length === 1 && monitorCount > 1) ||
  1125. (gap === 0 && tiledChildren.length > 1)
  1126. ) {
  1127. if (tilingModeEnabled) {
  1128. if (parentNode.isStacked()) {
  1129. if (!floatingWindow) {
  1130. tiledBorder.set_style_class_name("window-stacked-border");
  1131. } else {
  1132. tiledBorder.set_style_class_name("window-floated-border");
  1133. }
  1134. } else if (parentNode.isTabbed()) {
  1135. if (!floatingWindow) {
  1136. tiledBorder.set_style_class_name("window-tabbed-border");
  1137. if (nodeWindow.backgroundTab) {
  1138. tiledBorder.add_style_class_name("window-tabbed-bg");
  1139. }
  1140. } else {
  1141. tiledBorder.set_style_class_name("window-floated-border");
  1142. }
  1143. } else {
  1144. if (!floatingWindow) {
  1145. tiledBorder.set_style_class_name("window-tiled-border");
  1146. } else {
  1147. tiledBorder.set_style_class_name("window-floated-border");
  1148. }
  1149. }
  1150. } else {
  1151. tiledBorder.set_style_class_name("window-floated-border");
  1152. }
  1153. borders.push(tiledBorder);
  1154. }
  1155. }
  1156. if (gap === 0 || metaWindow.get_maximized() === 1 || metaWindow.get_maximized() === 2) {
  1157. inset = 0;
  1158. }
  1159. // handle the split border
  1160. // It should only show when V or H-Split and with single child CONs
  1161. if (
  1162. splitBorderEnabled &&
  1163. focusBorderEnabled &&
  1164. tilingModeEnabled &&
  1165. !nodeWindow.isFloat() &&
  1166. !maximized &&
  1167. parentNode.childNodes.length === 1 &&
  1168. (parentNode.isCon() || parentNode.isMonitor()) &&
  1169. !(parentNode.isTabbed() || parentNode.isStacked())
  1170. ) {
  1171. if (!windowActor.splitBorder) {
  1172. let splitBorder = new St.Bin({ style_class: "window-split-border" });
  1173. global.window_group.add_child(splitBorder);
  1174. windowActor.splitBorder = splitBorder;
  1175. }
  1176. let splitBorder = windowActor.splitBorder;
  1177. splitBorder.remove_style_class_name("window-split-vertical");
  1178. splitBorder.remove_style_class_name("window-split-horizontal");
  1179. if (parentNode.isVSplit()) {
  1180. splitBorder.add_style_class_name("window-split-vertical");
  1181. } else if (parentNode.isHSplit()) {
  1182. splitBorder.add_style_class_name("window-split-horizontal");
  1183. }
  1184. borders.push(splitBorder);
  1185. }
  1186. let rect = metaWindow.get_frame_rect();
  1187. borders.forEach((border) => {
  1188. border.set_size(rect.width + inset * 2, rect.height + inset * 2);
  1189. border.set_position(rect.x - inset, rect.y - inset);
  1190. if (metaWindow.appears_focused && !metaWindow.minimized) {
  1191. border.show();
  1192. }
  1193. if (global.window_group && global.window_group.contains(border)) {
  1194. // TODO - sort the borders with split border being on top
  1195. global.window_group.remove_child(border);
  1196. // Add the border just above the focused window
  1197. global.window_group.insert_child_above(border, metaWindow.get_compositor_private());
  1198. }
  1199. });
  1200. }
  1201. updateBorderLayout() {
  1202. this.hideWindowBorders();
  1203. this.showWindowBorders();
  1204. }
  1205. calculateGaps(node) {
  1206. if (!node) return 0;
  1207. let settings = this.ext.settings;
  1208. let gapSize = settings.get_uint("window-gap-size");
  1209. let gapIncrement = settings.get_uint("window-gap-size-increment");
  1210. let gap = gapSize * gapIncrement;
  1211. if (!node.isRoot()) {
  1212. let hideGapWhenSingle = settings.get_boolean("window-gap-hidden-on-single");
  1213. let parentNode = this.tree.findParent(node, NODE_TYPES.MONITOR);
  1214. if (parentNode) {
  1215. let tiled = parentNode
  1216. .getNodeByMode(WINDOW_MODES.TILE)
  1217. .filter((t) => t.isWindow() && !t.nodeValue.minimized);
  1218. if (tiled.length == 1 && hideGapWhenSingle) gap = 0;
  1219. }
  1220. }
  1221. return gap;
  1222. }
  1223. /**
  1224. * Track meta/mutter windows and append them to the tree.
  1225. * Windows can be attached on any of the following Node Types:
  1226. * MONITOR, CONTAINER
  1227. *
  1228. */
  1229. trackWindow(_display, metaWindow) {
  1230. let autoSplit = this.ext.settings.get_boolean("auto-split-enabled");
  1231. if (autoSplit && this.focusMetaWindow) {
  1232. let currentFocusNode = this.tree.findNode(this.focusMetaWindow);
  1233. if (currentFocusNode) {
  1234. let currentParentFocusNode = currentFocusNode.parentNode;
  1235. let layout = currentParentFocusNode.layout;
  1236. if (layout === LAYOUT_TYPES.HSPLIT || layout === LAYOUT_TYPES.VSPLIT) {
  1237. let frameRect = this.focusMetaWindow.get_frame_rect();
  1238. let splitHorizontal = frameRect.width > frameRect.height;
  1239. let orientation = splitHorizontal ? "horizontal" : "vertical";
  1240. this.command({ name: "Split", orientation: orientation });
  1241. }
  1242. }
  1243. }
  1244. // Make window types configurable
  1245. if (this._validWindow(metaWindow)) {
  1246. let existNodeWindow = this.tree.findNode(metaWindow);
  1247. Logger.debug(`Meta Window ${metaWindow.get_title()} ${metaWindow.get_window_type()}`);
  1248. if (!existNodeWindow) {
  1249. let attachTarget;
  1250. const activeMonitor = global.display.get_current_monitor();
  1251. const activeWorkspace = global.display.get_workspace_manager().get_active_workspace_index();
  1252. let metaMonWs = `mo${activeMonitor}ws${activeWorkspace}`;
  1253. // Check if the active monitor / workspace has windows
  1254. let metaMonWsNode = this.tree.findNode(metaMonWs);
  1255. if (!metaMonWsNode) {
  1256. // Reload the tree as a last resort
  1257. this.reloadTree("no-meta-monws");
  1258. return;
  1259. }
  1260. let windowNodes = metaMonWsNode.getNodeByType(NODE_TYPES.WINDOW);
  1261. let hasWindows = windowNodes.length > 0;
  1262. attachTarget = this.tree.attachNode;
  1263. attachTarget = attachTarget ? this.tree.findNode(attachTarget.nodeValue) : null;
  1264. if (!attachTarget) {
  1265. attachTarget = metaMonWsNode;
  1266. } else {
  1267. if (hasWindows) {
  1268. if (attachTarget && metaMonWsNode.contains(attachTarget)) {
  1269. // Use the attach target
  1270. } else {
  1271. // Find the first window
  1272. attachTarget = windowNodes[0];
  1273. }
  1274. } else {
  1275. attachTarget = metaMonWsNode;
  1276. }
  1277. }
  1278. let nodeWindow = this.tree.createNode(
  1279. attachTarget.nodeValue,
  1280. NODE_TYPES.WINDOW,
  1281. metaWindow,
  1282. WINDOW_MODES.FLOAT
  1283. );
  1284. metaWindow.firstRender = true;
  1285. let windowActor = metaWindow.get_compositor_private();
  1286. if (!metaWindow.windowSignals) {
  1287. let windowSignals = [
  1288. metaWindow.connect("position-changed", (_metaWindow) => {
  1289. let from = "position-changed";
  1290. this.updateMetaPositionSize(_metaWindow, from);
  1291. }),
  1292. metaWindow.connect("size-changed", (_metaWindow) => {
  1293. let from = "size-changed";
  1294. this.updateMetaPositionSize(_metaWindow, from);
  1295. }),
  1296. metaWindow.connect("unmanaged", (_metaWindow) => {
  1297. this.hideActorBorder(windowActor);
  1298. }),
  1299. metaWindow.connect("focus", (_metaWindowFocus) => {
  1300. this.queueEvent({
  1301. name: "focus-update",
  1302. callback: () => {
  1303. this.unfreezeRender();
  1304. this.updateBorderLayout();
  1305. this.updateDecorationLayout();
  1306. this.updateStackedFocus();
  1307. this.updateTabbedFocus();
  1308. let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
  1309. this.movePointerWith(focusNodeWindow);
  1310. },
  1311. });
  1312. let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
  1313. if (focusNodeWindow) {
  1314. // handle the attach node
  1315. this.tree.attachNode = focusNodeWindow._parent;
  1316. if (this.floatingWindow(focusNodeWindow)) {
  1317. this.queueEvent({
  1318. name: "raise-float",
  1319. callback: () => {
  1320. this.renderTree("raise-float-queue");
  1321. },
  1322. });
  1323. }
  1324. this.tree.attachNode = focusNodeWindow;
  1325. }
  1326. this.renderTree("focus", true);
  1327. }),
  1328. metaWindow.connect("workspace-changed", (_metaWindow) => {
  1329. this.updateMetaWorkspaceMonitor("metawindow-workspace-changed", null, _metaWindow);
  1330. this.trackCurrentMonWs();
  1331. }),
  1332. ];
  1333. metaWindow.windowSignals = windowSignals;
  1334. }
  1335. if (!windowActor.actorSignals) {
  1336. let actorSignals = [windowActor.connect("destroy", this.windowDestroy.bind(this))];
  1337. windowActor.actorSignals = actorSignals;
  1338. }
  1339. if (!windowActor.border) {
  1340. let border = new St.Bin({ style_class: "window-tiled-border" });
  1341. if (global.window_group) global.window_group.add_child(border);
  1342. windowActor.border = border;
  1343. border.show();
  1344. }
  1345. this.postProcessWindow(nodeWindow);
  1346. this.queueEvent(
  1347. {
  1348. name: "window-create-queue",
  1349. callback: () => {
  1350. metaWindow.unmaximize(Meta.MaximizeFlags.HORIZONTAL);
  1351. metaWindow.unmaximize(Meta.MaximizeFlags.VERTICAL);
  1352. metaWindow.unmaximize(Meta.MaximizeFlags.BOTH);
  1353. this.renderTree("window-create", true);
  1354. },
  1355. },
  1356. 200
  1357. );
  1358. let childNodes = this.tree.getTiledChildren(nodeWindow.parentNode.childNodes);
  1359. childNodes.forEach((n) => {
  1360. n.percent = 0.0;
  1361. });
  1362. }
  1363. }
  1364. }
  1365. postProcessWindow(nodeWindow) {
  1366. let metaWindow = nodeWindow.nodeValue;
  1367. if (metaWindow) {
  1368. if (metaWindow.get_title() === this.prefsTitle) {
  1369. metaWindow
  1370. .get_workspace()
  1371. .activate_with_focus(metaWindow, global.display.get_current_time());
  1372. this.moveCenter(metaWindow);
  1373. } else {
  1374. this.movePointerWith(metaWindow);
  1375. }
  1376. }
  1377. }
  1378. updateStackedFocus(focusNodeWindow) {
  1379. if (!focusNodeWindow) return;
  1380. const parentNode = focusNodeWindow.parentNode;
  1381. if (parentNode.layout === LAYOUT_TYPES.STACKED && !this._freezeRender) {
  1382. parentNode.appendChild(focusNodeWindow);
  1383. parentNode.childNodes
  1384. .filter((child) => child.isWindow())
  1385. .forEach((child) => child.nodeValue.raise());
  1386. this.queueEvent({
  1387. name: "render-focus-stack",
  1388. callback: () => {
  1389. this.renderTree("focus-stacked");
  1390. },
  1391. });
  1392. }
  1393. }
  1394. updateTabbedFocus(focusNodeWindow) {
  1395. if (!focusNodeWindow) return;
  1396. if (focusNodeWindow.parentNode.layout === LAYOUT_TYPES.TABBED && !this._freezeRender) {
  1397. const metaWindow = focusNodeWindow.nodeValue;
  1398. metaWindow.raise();
  1399. }
  1400. }
  1401. /**
  1402. * Check if a Meta Window's workspace is skipped for tiling.
  1403. */
  1404. isActiveWindowWorkspaceTiled(metaWindow) {
  1405. if (!metaWindow) return true;
  1406. let skipWs = this.ext.settings.get_string("workspace-skip-tile");
  1407. let skipArr = skipWs.split(",");
  1408. let skipThisWs = false;
  1409. for (let i = 0; i < skipArr.length; i++) {
  1410. let activeWorkspaceForWin = metaWindow.get_workspace();
  1411. if (activeWorkspaceForWin) {
  1412. let wsIndex = activeWorkspaceForWin.index();
  1413. if (skipArr[i].trim() === `${wsIndex}`) {
  1414. skipThisWs = true;
  1415. break;
  1416. }
  1417. }
  1418. }
  1419. return !skipThisWs;
  1420. }
  1421. /**
  1422. * Check the current active workspace's tiling mode
  1423. */
  1424. isCurrentWorkspaceTiled() {
  1425. let skipWs = this.ext.settings.get_string("workspace-skip-tile");
  1426. let skipArr = skipWs.split(",");
  1427. let skipThisWs = false;
  1428. let wsMgr = global.workspace_manager;
  1429. let wsIndex = wsMgr.get_active_workspace_index();
  1430. for (let i = 0; i < skipArr.length; i++) {
  1431. if (skipArr[i].trim() === `${wsIndex}`) {
  1432. skipThisWs = true;
  1433. break;
  1434. }
  1435. }
  1436. return !skipThisWs;
  1437. }
  1438. trackCurrentWindows() {
  1439. this.tree.attachNode = null;
  1440. let windowsAll = this.windowsAllWorkspaces;
  1441. for (let i = 0; i < windowsAll.length; i++) {
  1442. let metaWindow = windowsAll[i];
  1443. this.trackWindow(global.display, metaWindow);
  1444. // This updates and handles dynamic workspaces
  1445. this.updateMetaWorkspaceMonitor(
  1446. "track-current-windows",
  1447. metaWindow.get_monitor(),
  1448. metaWindow
  1449. );
  1450. }
  1451. this.updateDecorationLayout();
  1452. }
  1453. _validWindow(metaWindow) {
  1454. let windowType = metaWindow.get_window_type();
  1455. return (
  1456. windowType === Meta.WindowType.NORMAL ||
  1457. windowType === Meta.WindowType.MODAL_DIALOG ||
  1458. windowType === Meta.WindowType.DIALOG
  1459. );
  1460. }
  1461. windowDestroy(actor) {
  1462. // Release any resources on the window
  1463. let border = actor.border;
  1464. if (border) {
  1465. if (global.window_group) {
  1466. global.window_group.remove_child(border);
  1467. border.hide();
  1468. border = null;
  1469. }
  1470. }
  1471. let splitBorder = actor.splitBorder;
  1472. if (splitBorder) {
  1473. if (global.window_group) {
  1474. global.window_group.remove_child(splitBorder);
  1475. splitBorder.hide();
  1476. splitBorder = null;
  1477. }
  1478. }
  1479. let nodeWindow;
  1480. nodeWindow = this.tree.findNodeByActor(actor);
  1481. if (nodeWindow?.isWindow()) {
  1482. this.tree.removeNode(nodeWindow);
  1483. this.renderTree("window-destroy-quick", true);
  1484. this.removeFloatOverride(nodeWindow.nodeValue, true);
  1485. }
  1486. // find the next attachNode here
  1487. let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
  1488. if (focusNodeWindow) {
  1489. this.tree.attachNode = focusNodeWindow.parentNode;
  1490. }
  1491. this.queueEvent({
  1492. name: "window-destroy",
  1493. callback: () => {
  1494. this.renderTree("window-destroy", true);
  1495. },
  1496. });
  1497. }
  1498. /**
  1499. * Handles any workspace/monitor update for the Meta.Window.
  1500. */
  1501. updateMetaWorkspaceMonitor(from, _monitor, metaWindow) {
  1502. if (this._validWindow(metaWindow)) {
  1503. if (metaWindow.get_workspace() === null) return;
  1504. let existNodeWindow = this.tree.findNode(metaWindow);
  1505. let metaMonWs = `mo${metaWindow.get_monitor()}ws${metaWindow.get_workspace().index()}`;
  1506. let metaMonWsNode = this.tree.findNode(metaMonWs);
  1507. if (existNodeWindow) {
  1508. if (existNodeWindow.parentNode && metaMonWsNode) {
  1509. // Uses the existing workspace, monitor that the metaWindow
  1510. // belongs to.
  1511. let containsWindow = metaMonWsNode.contains(existNodeWindow);
  1512. if (!containsWindow) {
  1513. // handle cleanup of resize percentages
  1514. let existParent = existNodeWindow.parentNode;
  1515. this.tree.resetSiblingPercent(existParent);
  1516. metaMonWsNode.appendChild(existNodeWindow);
  1517. // Ensure that the workspace tiling is honored
  1518. if (this.isActiveWindowWorkspaceTiled(metaWindow)) {
  1519. if (!this.grabOp === Meta.GrabOp.WINDOW_BASE) this.updateTabbedFocus(existNodeWindow);
  1520. this.updateStackedFocus(existNodeWindow);
  1521. } else {
  1522. if (this.floatingWindow(existNodeWindow)) {
  1523. existNodeWindow.nodeValue.raise();
  1524. }
  1525. }
  1526. }
  1527. }
  1528. }
  1529. this.renderTree(from);
  1530. }
  1531. }
  1532. /**
  1533. * Handle any updates to the current focused window's position.
  1534. * Useful for updating the active window border, etc.
  1535. */
  1536. updateMetaPositionSize(_metaWindow, from) {
  1537. let focusMetaWindow = this.focusMetaWindow;
  1538. if (!focusMetaWindow) return;
  1539. let focusNodeWindow = this.findNodeWindow(focusMetaWindow);
  1540. if (!focusNodeWindow) return;
  1541. let tilingModeEnabled = this.ext.settings.get_boolean("tiling-mode-enabled");
  1542. if (focusNodeWindow.grabMode && tilingModeEnabled) {
  1543. if (focusNodeWindow.grabMode === GRAB_TYPES.RESIZING) {
  1544. this._handleResizing(focusNodeWindow);
  1545. } else if (focusNodeWindow.grabMode === GRAB_TYPES.MOVING) {
  1546. this._handleMoving(focusNodeWindow);
  1547. }
  1548. } else {
  1549. if (focusMetaWindow.get_maximized() === 0) {
  1550. this.renderTree(from);
  1551. }
  1552. }
  1553. this.updateBorderLayout();
  1554. this.updateDecorationLayout();
  1555. }
  1556. updateDecorationLayout() {
  1557. if (this._freezeRender) return;
  1558. let activeWsNode = this.currentWsNode;
  1559. let allCons = this.tree.getNodeByType(NODE_TYPES.CON);
  1560. // First, hide all decorations:
  1561. allCons.forEach((con) => {
  1562. if (con.decoration) {
  1563. con.decoration.hide();
  1564. }
  1565. });
  1566. // Next, handle showing-desktop usually by Super + D
  1567. if (!activeWsNode) return;
  1568. let allWindows = activeWsNode.getNodeByType(NODE_TYPES.WINDOW);
  1569. let allHiddenWindows = allWindows.filter((w) => {
  1570. let metaWindow = w.nodeValue;
  1571. return !metaWindow.showing_on_its_workspace() || metaWindow.minimized;
  1572. });
  1573. // Then if all hidden, do not proceed showing the decorations at all;
  1574. if (allWindows.length === allHiddenWindows.length) return;
  1575. // Show the decoration where on all monitors of active workspace
  1576. // But not on the monitor where there is a maximized or fullscreen window
  1577. // Note, that when multi-display, user can have multi maximized windows,
  1578. // So it needs to be fully filtered:
  1579. let monWsNoMaxWindows = activeWsNode.getNodeByType(NODE_TYPES.MONITOR).filter((monitor) => {
  1580. return (
  1581. monitor.getNodeByType(NODE_TYPES.WINDOW).filter((w) => {
  1582. return (
  1583. w.nodeValue.get_maximized() === Meta.MaximizeFlags.BOTH || w.nodeValue.is_fullscreen()
  1584. );
  1585. }).length === 0
  1586. );
  1587. });
  1588. monWsNoMaxWindows.forEach((monitorWs) => {
  1589. let activeMonWsCons = monitorWs.getNodeByType(NODE_TYPES.CON);
  1590. activeMonWsCons.forEach((con) => {
  1591. let tiled = this.tree.getTiledChildren(con.childNodes);
  1592. let showTabs = this.ext.settings.get_boolean("showtab-decoration-enabled");
  1593. if (con.decoration && tiled.length > 0 && showTabs) {
  1594. con.decoration.show();
  1595. if (global.window_group.contains(con.decoration) && this.focusMetaWindow) {
  1596. global.window_group.remove_child(con.decoration);
  1597. // Show it below the focused window
  1598. global.window_group.insert_child_below(
  1599. con.decoration,
  1600. this.focusMetaWindow.get_compositor_private()
  1601. );
  1602. }
  1603. con.childNodes.forEach((cn) => {
  1604. cn.render();
  1605. });
  1606. }
  1607. });
  1608. });
  1609. }
  1610. freezeRender() {
  1611. this._freezeRender = true;
  1612. }
  1613. unfreezeRender() {
  1614. this._freezeRender = false;
  1615. }
  1616. floatingWindow(node) {
  1617. if (!node) return false;
  1618. return node.nodeType === NODE_TYPES.WINDOW && node.mode === WINDOW_MODES.FLOAT;
  1619. }
  1620. /**
  1621. * Moves the pointer along with the nodeWindow's meta
  1622. *
  1623. * This is useful for making sure that Forge calculates the attachNode
  1624. * properly
  1625. */
  1626. movePointerWith(nodeWindow) {
  1627. if (!nodeWindow || !nodeWindow._data) return;
  1628. if (this.ext.settings.get_boolean("move-pointer-focus-enabled")) {
  1629. this.storePointerLastPosition(this.lastFocusedWindow);
  1630. if (this.canMovePointerInsideNodeWindow(nodeWindow)) {
  1631. this.warpPointerToNodeWindow(nodeWindow);
  1632. }
  1633. }
  1634. this.lastFocusedWindow = nodeWindow;
  1635. this.tree.debugParentNodes(nodeWindow);
  1636. }
  1637. warpPointerToNodeWindow(nodeWindow) {
  1638. const newCoord = this.getPointerPositionInside(nodeWindow);
  1639. if (newCoord && newCoord.x && newCoord.y) {
  1640. const seat = Clutter.get_default_backend().get_default_seat();
  1641. if (seat) {
  1642. const wmTitle = nodeWindow.nodeValue.get_title();
  1643. Logger.debug(`moved pointer to [${wmTitle}] at (${newCoord.x},${newCoord.y})`);
  1644. seat.warp_pointer(newCoord.x, newCoord.y);
  1645. }
  1646. }
  1647. }
  1648. getPointer() {
  1649. return global.get_pointer();
  1650. }
  1651. minimizedWindow(node) {
  1652. if (!node) return false;
  1653. return node._type === NODE_TYPES.WINDOW && node._data && node._data.minimized;
  1654. }
  1655. swapWindowsUnderPointer(focusNodeWindow) {
  1656. if (this.cancelGrab) {
  1657. return;
  1658. }
  1659. let nodeWinAtPointer = this.findNodeWindowAtPointer(focusNodeWindow);
  1660. if (nodeWinAtPointer) this.tree.swapPairs(focusNodeWindow, nodeWinAtPointer);
  1661. }
  1662. /**
  1663. *
  1664. * Handle previewing and applying where a drag-drop window is going to be tiled
  1665. *
  1666. */
  1667. moveWindowToPointer(focusNodeWindow, preview = false) {
  1668. if (this.cancelGrab) {
  1669. return;
  1670. }
  1671. if (!focusNodeWindow || focusNodeWindow.mode !== WINDOW_MODES.GRAB_TILE) return;
  1672. let nodeWinAtPointer = this.nodeWinAtPointer;
  1673. if (nodeWinAtPointer) {
  1674. const targetRect = nodeWinAtPointer.nodeValue.get_frame_rect();
  1675. const parentNodeTarget = nodeWinAtPointer.parentNode;
  1676. const currPointer = this.getPointer();
  1677. const horizontal = parentNodeTarget.isHSplit() || parentNodeTarget.isTabbed();
  1678. const isMonParent = parentNodeTarget.nodeType === NODE_TYPES.MONITOR;
  1679. const isConParent = parentNodeTarget.nodeType === NODE_TYPES.CON;
  1680. const centerLayout = this.ext.settings.get_string("dnd-center-layout").toUpperCase();
  1681. const stacked = parentNodeTarget.isStacked();
  1682. const tabbed = parentNodeTarget.isTabbed();
  1683. const stackedOrTabbed = stacked || tabbed;
  1684. const updatePreview = (focusNodeWindow, previewParams) => {
  1685. let previewHint = focusNodeWindow.previewHint;
  1686. let previewHintEnabled = this.ext.settings.get_boolean("preview-hint-enabled");
  1687. const previewRect = previewParams.targetRect;
  1688. if (previewHint && previewHintEnabled) {
  1689. if (!previewRect) {
  1690. previewHint.hide();
  1691. return;
  1692. }
  1693. previewHint.set_style_class_name(previewParams.className);
  1694. previewHint.set_position(previewRect.x, previewRect.y);
  1695. previewHint.set_size(previewRect.width, previewRect.height);
  1696. previewHint.show();
  1697. }
  1698. };
  1699. const regions = (targetRect, regionWidth) => {
  1700. leftRegion = {
  1701. x: targetRect.x,
  1702. y: targetRect.y,
  1703. width: targetRect.width * regionWidth,
  1704. height: targetRect.height,
  1705. };
  1706. rightRegion = {
  1707. x: targetRect.x + targetRect.width * (1 - regionWidth),
  1708. y: targetRect.y,
  1709. width: targetRect.width * regionWidth,
  1710. height: targetRect.height,
  1711. };
  1712. topRegion = {
  1713. x: targetRect.x,
  1714. y: targetRect.y,
  1715. width: targetRect.width,
  1716. height: targetRect.height * regionWidth,
  1717. };
  1718. bottomRegion = {
  1719. x: targetRect.x,
  1720. y: targetRect.y + targetRect.height * (1 - regionWidth),
  1721. width: targetRect.width,
  1722. height: targetRect.height * regionWidth,
  1723. };
  1724. centerRegion = {
  1725. x: targetRect.x + targetRect.width * regionWidth,
  1726. y: targetRect.y + targetRect.height * regionWidth,
  1727. width: targetRect.width - targetRect.width * regionWidth * 2,
  1728. height: targetRect.height - targetRect.height * regionWidth * 2,
  1729. };
  1730. return {
  1731. left: leftRegion,
  1732. right: rightRegion,
  1733. top: topRegion,
  1734. bottom: bottomRegion,
  1735. center: centerRegion,
  1736. };
  1737. };
  1738. let referenceNode = null;
  1739. let containerNode;
  1740. let childNode = focusNodeWindow;
  1741. let previewParams = {
  1742. className: "",
  1743. targetRect: null,
  1744. };
  1745. let leftRegion;
  1746. let rightRegion;
  1747. let topRegion;
  1748. let bottomRegion;
  1749. let centerRegion;
  1750. let previewWidth = 0.5;
  1751. let hoverWidth = 0.3;
  1752. // Hover region detects where the pointer is on the target drop window
  1753. const hoverRegions = regions(targetRect, hoverWidth);
  1754. // Preview region interprets the hover intersect where the focus window
  1755. // would go when dropped
  1756. const previewRegions = regions(targetRect, previewWidth);
  1757. leftRegion = hoverRegions.left;
  1758. rightRegion = hoverRegions.right;
  1759. topRegion = hoverRegions.top;
  1760. bottomRegion = hoverRegions.bottom;
  1761. centerRegion = hoverRegions.center;
  1762. const isLeft = Utils.rectContainsPoint(leftRegion, currPointer);
  1763. const isRight = Utils.rectContainsPoint(rightRegion, currPointer);
  1764. const isTop = Utils.rectContainsPoint(topRegion, currPointer);
  1765. const isBottom = Utils.rectContainsPoint(bottomRegion, currPointer);
  1766. const isCenter = Utils.rectContainsPoint(centerRegion, currPointer);
  1767. if (isCenter) {
  1768. if (centerLayout == "SWAP") {
  1769. referenceNode = nodeWinAtPointer;
  1770. previewParams = {
  1771. targetRect: targetRect,
  1772. };
  1773. } else {
  1774. if (stackedOrTabbed) {
  1775. containerNode = parentNodeTarget;
  1776. referenceNode = null;
  1777. previewParams = {
  1778. className: stacked ? "window-tilepreview-stacked" : "window-tilepreview-tabbed",
  1779. targetRect: targetRect,
  1780. };
  1781. } else {
  1782. if (isMonParent) {
  1783. childNode.createCon = true;
  1784. containerNode = parentNodeTarget;
  1785. referenceNode = nodeWinAtPointer;
  1786. previewParams = {
  1787. targetRect: targetRect,
  1788. };
  1789. } else {
  1790. containerNode = parentNodeTarget;
  1791. referenceNode = null;
  1792. const parentTargetRect = this.tree.processGap(parentNodeTarget);
  1793. previewParams = {
  1794. targetRect: parentTargetRect,
  1795. };
  1796. }
  1797. }
  1798. }
  1799. } else if (isLeft) {
  1800. previewParams = {
  1801. targetRect: previewRegions.left,
  1802. };
  1803. if (stackedOrTabbed) {
  1804. // treat any windows on stacked or tabbed layouts to be
  1805. // a single node unit: the con itself and then
  1806. // split left, top, right or bottom accordingly (subsequent if conditions):
  1807. childNode.detachWindow = true;
  1808. if (!isMonParent) {
  1809. referenceNode = parentNodeTarget;
  1810. containerNode = parentNodeTarget.parentNode;
  1811. } else {
  1812. // It is a monitor that's a stack/tab
  1813. // TODO: update the stacked/tabbed toggles to not
  1814. // change layout if the parent is a monitor?
  1815. }
  1816. } else {
  1817. if (horizontal) {
  1818. containerNode = parentNodeTarget;
  1819. referenceNode = nodeWinAtPointer;
  1820. } else {
  1821. // vertical orientation
  1822. childNode.createCon = true;
  1823. containerNode = parentNodeTarget;
  1824. referenceNode = nodeWinAtPointer;
  1825. }
  1826. }
  1827. } else if (isRight) {
  1828. previewParams = {
  1829. targetRect: previewRegions.right,
  1830. };
  1831. if (stackedOrTabbed) {
  1832. // treat any windows on stacked or tabbed layouts to be
  1833. // a single node unit: the con itself and then
  1834. // split left, top, right or bottom accordingly (subsequent if conditions):
  1835. childNode.detachWindow = true;
  1836. if (!isMonParent) {
  1837. referenceNode = parentNodeTarget.nextSibling;
  1838. containerNode = parentNodeTarget.parentNode;
  1839. } else {
  1840. // It is a monitor that's a stack/tab
  1841. // TODO: update the stacked/tabbed toggles to not
  1842. // change layout if the parent is a monitor?
  1843. }
  1844. } else {
  1845. if (horizontal) {
  1846. containerNode = parentNodeTarget;
  1847. referenceNode = nodeWinAtPointer.nextSibling;
  1848. } else {
  1849. childNode.createCon = true;
  1850. containerNode = parentNodeTarget;
  1851. referenceNode = nodeWinAtPointer.nextSibling;
  1852. }
  1853. }
  1854. } else if (isTop) {
  1855. previewParams = {
  1856. targetRect: previewRegions.top,
  1857. };
  1858. if (stackedOrTabbed) {
  1859. // treat any windows on stacked or tabbed layouts to be
  1860. // a single node unit: the con itself and then
  1861. // split left, top, right or bottom accordingly (subsequent if conditions):
  1862. if (!isMonParent) {
  1863. containerNode = parentNodeTarget;
  1864. referenceNode = null;
  1865. previewParams = {
  1866. className: stacked ? "window-tilepreview-stacked" : "window-tilepreview-tabbed",
  1867. targetRect: targetRect,
  1868. };
  1869. } else {
  1870. // It is a monitor that's a stack/tab
  1871. // TODO: update the stacked/tabbed toggles to not
  1872. // change layout if the parent is a monitor?
  1873. }
  1874. } else {
  1875. if (horizontal) {
  1876. childNode.createCon = true;
  1877. containerNode = parentNodeTarget;
  1878. referenceNode = nodeWinAtPointer;
  1879. } else {
  1880. containerNode = parentNodeTarget;
  1881. referenceNode = nodeWinAtPointer;
  1882. }
  1883. }
  1884. } else if (isBottom) {
  1885. previewParams = {
  1886. targetRect: previewRegions.bottom,
  1887. };
  1888. if (stackedOrTabbed) {
  1889. // treat any windows on stacked or tabbed layouts to be
  1890. // a single node unit: the con itself and then
  1891. // split left, top, right or bottom accordingly (subsequent if conditions):
  1892. if (!isMonParent) {
  1893. containerNode = parentNodeTarget;
  1894. referenceNode = null;
  1895. previewParams = {
  1896. className: stacked ? "window-tilepreview-stacked" : "window-tilepreview-tabbed",
  1897. targetRect: targetRect,
  1898. };
  1899. } else {
  1900. // It is a monitor that's a stack/tab
  1901. // TODO: update the stacked/tabbed toggles to not
  1902. // change layout if the parent is a monitor?
  1903. }
  1904. } else {
  1905. if (horizontal) {
  1906. childNode = focusNodeWindow;
  1907. childNode.createCon = true;
  1908. containerNode = parentNodeTarget;
  1909. referenceNode = nodeWinAtPointer.nextSibling;
  1910. } else {
  1911. childNode = focusNodeWindow;
  1912. containerNode = parentNodeTarget;
  1913. referenceNode = nodeWinAtPointer.nextSibling;
  1914. }
  1915. }
  1916. }
  1917. if (!isCenter) {
  1918. if (stackedOrTabbed) {
  1919. if (isLeft || isRight) {
  1920. previewParams.className = "window-tilepreview-tiled";
  1921. } else if (isTop || isBottom) {
  1922. previewParams.className = stacked
  1923. ? "window-tilepreview-stacked"
  1924. : "window-tilepreview-tabbed";
  1925. }
  1926. } else {
  1927. previewParams.className = "window-tilepreview-tiled";
  1928. }
  1929. } else if (isCenter) {
  1930. if (!stackedOrTabbed) previewParams.className = this._getDragDropCenterPreviewStyle();
  1931. }
  1932. if (!preview) {
  1933. const previousParent = focusNodeWindow.parentNode;
  1934. this.tree.resetSiblingPercent(containerNode);
  1935. this.tree.resetSiblingPercent(previousParent);
  1936. if (focusNodeWindow.tab) {
  1937. let decoParent = focusNodeWindow.tab.get_parent();
  1938. if (decoParent) decoParent.remove_child(focusNodeWindow.tab);
  1939. }
  1940. if (childNode.createCon) {
  1941. const numWin = parentNodeTarget.childNodes.filter(
  1942. (c) => c.nodeType === NODE_TYPES.WINDOW
  1943. ).length;
  1944. const numChild = parentNodeTarget.childNodes.length;
  1945. const sameNumChild = numWin === numChild;
  1946. // Child Node will still be created
  1947. if (
  1948. !isCenter &&
  1949. ((isConParent && numWin === 1 && sameNumChild) ||
  1950. (isMonParent && numWin == 2 && sameNumChild))
  1951. ) {
  1952. childNode = parentNodeTarget;
  1953. } else {
  1954. childNode = new Node(NODE_TYPES.CON, new St.Bin());
  1955. containerNode.insertBefore(childNode, referenceNode);
  1956. childNode.appendChild(nodeWinAtPointer);
  1957. }
  1958. if (isLeft || isTop) {
  1959. childNode.insertBefore(focusNodeWindow, nodeWinAtPointer);
  1960. } else if (isRight || isBottom || isCenter) {
  1961. childNode.insertBefore(focusNodeWindow, null);
  1962. }
  1963. if (isLeft || isRight) {
  1964. childNode.layout = LAYOUT_TYPES.HSPLIT;
  1965. } else if (isTop || isBottom) {
  1966. childNode.layout = LAYOUT_TYPES.VSPLIT;
  1967. } else if (isCenter) {
  1968. childNode.layout = LAYOUT_TYPES[centerLayout];
  1969. }
  1970. } else if (childNode.detachWindow) {
  1971. const orientation =
  1972. isLeft || isRight ? ORIENTATION_TYPES.HORIZONTAL : ORIENTATION_TYPES.VERTICAL;
  1973. this.tree.split(childNode, orientation);
  1974. containerNode.insertBefore(childNode.parentNode, referenceNode);
  1975. } else if (isCenter && centerLayout == "SWAP") {
  1976. this.tree.swapPairs(referenceNode, focusNodeWindow);
  1977. this.renderTree("drag-swap");
  1978. } else {
  1979. // Child Node is a WINDOW
  1980. containerNode.insertBefore(childNode, referenceNode);
  1981. if (isLeft || isRight) {
  1982. containerNode.layout = LAYOUT_TYPES.HSPLIT;
  1983. } else if (isTop || isBottom) {
  1984. if (!stackedOrTabbed) containerNode.layout = LAYOUT_TYPES.VSPLIT;
  1985. } else if (isCenter) {
  1986. if (containerNode.isHSplit() || containerNode.isVSplit()) {
  1987. containerNode.layout = LAYOUT_TYPES[centerLayout];
  1988. }
  1989. }
  1990. }
  1991. previousParent.resetLayoutSingleChild();
  1992. } else {
  1993. updatePreview(focusNodeWindow, previewParams);
  1994. }
  1995. childNode.createCon = false;
  1996. childNode.detachWindow = false;
  1997. }
  1998. }
  1999. canMovePointerInsideNodeWindow(nodeWindow) {
  2000. if (nodeWindow && nodeWindow._data) {
  2001. const metaWindow = nodeWindow.nodeValue;
  2002. const metaRect = metaWindow.get_frame_rect();
  2003. const pointerCoord = global.get_pointer();
  2004. return (
  2005. metaRect &&
  2006. // xdg-copy creates a 1x1 pixel window to capture mouse events.
  2007. metaRect.width > 8 &&
  2008. metaRect.height > 8 &&
  2009. !Utils.rectContainsPoint(metaRect, pointerCoord) &&
  2010. !metaWindow.minimized &&
  2011. !Main.overview.visible &&
  2012. !this.pointerIsOverParentDecoration(nodeWindow, pointerCoord)
  2013. );
  2014. }
  2015. return false;
  2016. }
  2017. pointerIsOverParentDecoration(nodeWindow, pointerCoord) {
  2018. if (pointerCoord && nodeWindow && nodeWindow.parentNode) {
  2019. let node = nodeWindow.parentNode;
  2020. if (node.isTabbed() || node.isStacked()) {
  2021. return Utils.rectContainsPoint(node.rect, pointerCoord);
  2022. }
  2023. }
  2024. return false;
  2025. }
  2026. getPointerPositionInside(nodeWindow) {
  2027. if (nodeWindow && nodeWindow._data) {
  2028. const metaWindow = nodeWindow.nodeValue;
  2029. const metaRect = metaWindow.get_frame_rect();
  2030. // on: last position of cursor inside window
  2031. // on: titlebar: near to app toolbars, menubar, tabs, etc...
  2032. let [wx, wy] = nodeWindow.pointer
  2033. ? [nodeWindow.pointer.x, nodeWindow.pointer.y]
  2034. : [metaRect.width / 2, 8];
  2035. let px = wx >= metaRect.width ? metaRect.width - 8 : wx;
  2036. let py = wy >= metaRect.height ? metaRect.height - 8 : wy;
  2037. return {
  2038. x: metaRect.x + px,
  2039. y: metaRect.y + py,
  2040. };
  2041. }
  2042. return null;
  2043. }
  2044. storePointerLastPosition(nodeWindow) {
  2045. if (nodeWindow && nodeWindow._data) {
  2046. const metaWindow = nodeWindow.nodeValue;
  2047. const metaRect = metaWindow.get_frame_rect();
  2048. const pointerCoord = global.get_pointer();
  2049. if (Utils.rectContainsPoint(metaRect, pointerCoord)) {
  2050. let px = pointerCoord[0] - metaRect.x;
  2051. let py = pointerCoord[1] - metaRect.y;
  2052. if (px > 0 && py > 0) {
  2053. nodeWindow.pointer = { x: px, y: py };
  2054. Logger.debug(`stored pointer for [${metaWindow.get_title()}] at (${px},${py})`);
  2055. }
  2056. }
  2057. }
  2058. }
  2059. findNodeWindowAtPointer(focusNodeWindow) {
  2060. let pointerCoord = global.get_pointer();
  2061. let nodeWinAtPointer = this._findNodeWindowAtPointer(focusNodeWindow.nodeValue, pointerCoord);
  2062. return nodeWinAtPointer;
  2063. }
  2064. /**
  2065. * Focus the window under the pointer and raise it.
  2066. *
  2067. * @returns {boolean} true if we should continue polling, false otherwise
  2068. */
  2069. _focusWindowUnderPointer() {
  2070. // Break the loop if the user has disabled the feature
  2071. // or if the window manager is disabled
  2072. if (!this.shouldFocusOnHover || this.disabled) return false;
  2073. // We don't want to focus windows when the overview is visible
  2074. if (Main.overview.visible) return true;
  2075. // Get the global mouse position
  2076. let pointer = global.get_pointer();
  2077. const metaWindow = this._getMetaWindowAtPointer(pointer);
  2078. if (metaWindow) {
  2079. // If window is not null, focus it
  2080. metaWindow.focus(global.get_current_time());
  2081. // Raise it to the top
  2082. metaWindow.raise();
  2083. }
  2084. // Continue polling
  2085. return true;
  2086. }
  2087. /**
  2088. * Get the Meta.Window at the pointer coordinates
  2089. *
  2090. * @param {[number, number]} pointer x and y coordinates
  2091. * @returns null if no window is found, otherwise the Meta.Window
  2092. */
  2093. _getMetaWindowAtPointer(pointer) {
  2094. const windows = global.get_window_actors();
  2095. const [x, y] = pointer;
  2096. // Iterate through the windows in reverse order to get the top-most window
  2097. for (let i = windows.length - 1; i >= 0; i--) {
  2098. let window = windows[i];
  2099. let metaWindow = window.meta_window;
  2100. let { x: wx, y: wy, width, height } = metaWindow.get_frame_rect();
  2101. // Check if the position is within the window bounds
  2102. if (x >= wx && x <= wx + width && y >= wy && y <= wy + height) {
  2103. return metaWindow;
  2104. }
  2105. }
  2106. // No window found at the pointer
  2107. return null;
  2108. }
  2109. /**
  2110. * Finds the NodeWindow under the Meta.Window and the
  2111. * current pointer coordinates;
  2112. */
  2113. _findNodeWindowAtPointer(metaWindow, pointer) {
  2114. if (!metaWindow) return undefined;
  2115. let sortedWindows = this.sortedWindows;
  2116. if (!sortedWindows) {
  2117. Logger.warn("No sorted windows");
  2118. return;
  2119. }
  2120. for (let i = 0, n = sortedWindows.length; i < n; i++) {
  2121. const w = sortedWindows[i];
  2122. const metaRect = w.get_frame_rect();
  2123. const atPointer = Utils.rectContainsPoint(metaRect, pointer);
  2124. if (atPointer) return this.tree.getNodeByValue(w);
  2125. }
  2126. return null;
  2127. }
  2128. _handleGrabOpBegin(_display, _metaWindow, grabOp) {
  2129. this.grabOp = grabOp;
  2130. this.trackCurrentMonWs();
  2131. let focusMetaWindow = this.focusMetaWindow;
  2132. if (focusMetaWindow) {
  2133. let focusNodeWindow = this.findNodeWindow(focusMetaWindow);
  2134. if (!focusNodeWindow) return;
  2135. const frameRect = focusMetaWindow.get_frame_rect();
  2136. const gaps = this.calculateGaps(focusNodeWindow);
  2137. focusNodeWindow.grabMode = Utils.grabMode(grabOp);
  2138. if (
  2139. focusNodeWindow.grabMode === GRAB_TYPES.MOVING &&
  2140. focusNodeWindow.mode === WINDOW_MODES.TILE
  2141. ) {
  2142. this.freezeRender();
  2143. focusNodeWindow.mode = WINDOW_MODES.GRAB_TILE;
  2144. }
  2145. focusNodeWindow.initGrabOp = grabOp;
  2146. focusNodeWindow.initRect = Utils.removeGapOnRect(frameRect, gaps);
  2147. }
  2148. }
  2149. _handleGrabOpEnd(_display, _metaWindow, grabOp) {
  2150. this.unfreezeRender();
  2151. let focusMetaWindow = this.focusMetaWindow;
  2152. if (!focusMetaWindow) return;
  2153. let focusNodeWindow = this.findNodeWindow(focusMetaWindow);
  2154. if (focusNodeWindow && !this.cancelGrab) {
  2155. // WINDOW_BASE is when grabbing the window decoration
  2156. // COMPOSITOR is when something like Overview requesting a grab, especially when Super is pressed.
  2157. if (
  2158. grabOp === Meta.GrabOp.WINDOW_BASE ||
  2159. grabOp === Meta.GrabOp.COMPOSITOR ||
  2160. grabOp === Meta.GrabOp.MOVING_UNCONSTRAINED
  2161. ) {
  2162. if (this.allowDragDropTile()) {
  2163. this.moveWindowToPointer(focusNodeWindow);
  2164. }
  2165. }
  2166. }
  2167. this._grabCleanup(focusNodeWindow);
  2168. if (focusMetaWindow.get_maximized() === 0) {
  2169. this.renderTree("grab-op-end");
  2170. }
  2171. this.updateStackedFocus(focusNodeWindow);
  2172. this.updateTabbedFocus(focusNodeWindow);
  2173. this.nodeWinAtPointer = null;
  2174. }
  2175. _grabCleanup(focusNodeWindow) {
  2176. this.cancelGrab = false;
  2177. if (!focusNodeWindow) return;
  2178. focusNodeWindow.initRect = null;
  2179. focusNodeWindow.grabMode = null;
  2180. focusNodeWindow.initGrabOp = null;
  2181. if (focusNodeWindow.previewHint) {
  2182. focusNodeWindow.previewHint.hide();
  2183. global.window_group.remove_child(focusNodeWindow.previewHint);
  2184. focusNodeWindow.previewHint.destroy();
  2185. focusNodeWindow.previewHint = null;
  2186. }
  2187. if (focusNodeWindow.mode === WINDOW_MODES.GRAB_TILE) {
  2188. focusNodeWindow.mode = WINDOW_MODES.TILE;
  2189. }
  2190. }
  2191. allowDragDropTile() {
  2192. return this.kbd.allowDragDropTile();
  2193. }
  2194. _handleResizing(focusNodeWindow) {
  2195. if (!focusNodeWindow || focusNodeWindow.isFloat()) return;
  2196. let grabOps = Utils.decomposeGrabOp(this.grabOp);
  2197. for (let grabOp of grabOps) {
  2198. let initGrabOp = focusNodeWindow.initGrabOp;
  2199. let direction = Utils.directionFromGrab(grabOp);
  2200. let orientation = Utils.orientationFromGrab(grabOp);
  2201. let parentNodeForFocus = focusNodeWindow.parentNode;
  2202. let position = Utils.positionFromGrabOp(grabOp);
  2203. // normalize the rect without gaps
  2204. let frameRect = this.focusMetaWindow.get_frame_rect();
  2205. let gaps = this.calculateGaps(focusNodeWindow);
  2206. let currentRect = Utils.removeGapOnRect(frameRect, gaps);
  2207. let firstRect;
  2208. let secondRect;
  2209. let parentRect;
  2210. let resizePairForWindow;
  2211. if (initGrabOp === Meta.GrabOp.RESIZING_UNKNOWN) {
  2212. // the direction is null so do not process yet below.
  2213. return;
  2214. } else {
  2215. resizePairForWindow = this.tree.nextVisible(focusNodeWindow, direction);
  2216. }
  2217. let sameParent = resizePairForWindow
  2218. ? resizePairForWindow.parentNode === focusNodeWindow.parentNode
  2219. : false;
  2220. if (orientation === ORIENTATION_TYPES.HORIZONTAL) {
  2221. if (sameParent) {
  2222. // use the window or con pairs
  2223. if (this.tree.getTiledChildren(parentNodeForFocus.childNodes).length <= 1) {
  2224. return;
  2225. }
  2226. firstRect = focusNodeWindow.initRect;
  2227. if (resizePairForWindow) {
  2228. if (
  2229. !this.floatingWindow(resizePairForWindow) &&
  2230. !this.minimizedWindow(resizePairForWindow)
  2231. ) {
  2232. secondRect = resizePairForWindow.rect;
  2233. } else {
  2234. // TODO try to get the next resize pair?
  2235. }
  2236. }
  2237. if (!firstRect || !secondRect) {
  2238. return;
  2239. }
  2240. parentRect = parentNodeForFocus.rect;
  2241. let changePx = currentRect.width - firstRect.width;
  2242. let firstPercent = (firstRect.width + changePx) / parentRect.width;
  2243. let secondPercent = (secondRect.width - changePx) / parentRect.width;
  2244. focusNodeWindow.percent = firstPercent;
  2245. resizePairForWindow.percent = secondPercent;
  2246. } else {
  2247. // use the parent pairs (con to another con or window)
  2248. if (resizePairForWindow && resizePairForWindow.parentNode) {
  2249. if (this.tree.getTiledChildren(resizePairForWindow.parentNode.childNodes).length <= 1) {
  2250. return;
  2251. }
  2252. let firstWindowRect = focusNodeWindow.initRect;
  2253. let index = resizePairForWindow.index;
  2254. if (position === POSITION.BEFORE) {
  2255. // Find the opposite node
  2256. index = index + 1;
  2257. } else {
  2258. index = index - 1;
  2259. }
  2260. parentNodeForFocus = resizePairForWindow.parentNode.childNodes[index];
  2261. firstRect = parentNodeForFocus.rect;
  2262. secondRect = resizePairForWindow.rect;
  2263. if (!firstRect || !secondRect) {
  2264. return;
  2265. }
  2266. parentRect = parentNodeForFocus.parentNode.rect;
  2267. let changePx = currentRect.width - firstWindowRect.width;
  2268. let firstPercent = (firstRect.width + changePx) / parentRect.width;
  2269. let secondPercent = (secondRect.width - changePx) / parentRect.width;
  2270. parentNodeForFocus.percent = firstPercent;
  2271. resizePairForWindow.percent = secondPercent;
  2272. }
  2273. }
  2274. } else if (orientation === ORIENTATION_TYPES.VERTICAL) {
  2275. if (sameParent) {
  2276. // use the window or con pairs
  2277. if (this.tree.getTiledChildren(parentNodeForFocus.childNodes).length <= 1) {
  2278. return;
  2279. }
  2280. firstRect = focusNodeWindow.initRect;
  2281. if (resizePairForWindow) {
  2282. if (
  2283. !this.floatingWindow(resizePairForWindow) &&
  2284. !this.minimizedWindow(resizePairForWindow)
  2285. ) {
  2286. secondRect = resizePairForWindow.rect;
  2287. } else {
  2288. // TODO try to get the next resize pair?
  2289. }
  2290. }
  2291. if (!firstRect || !secondRect) {
  2292. return;
  2293. }
  2294. parentRect = parentNodeForFocus.rect;
  2295. let changePx = currentRect.height - firstRect.height;
  2296. let firstPercent = (firstRect.height + changePx) / parentRect.height;
  2297. let secondPercent = (secondRect.height - changePx) / parentRect.height;
  2298. focusNodeWindow.percent = firstPercent;
  2299. resizePairForWindow.percent = secondPercent;
  2300. } else {
  2301. // use the parent pairs (con to another con or window)
  2302. if (resizePairForWindow && resizePairForWindow.parentNode) {
  2303. if (this.tree.getTiledChildren(resizePairForWindow.parentNode.childNodes).length <= 1) {
  2304. return;
  2305. }
  2306. let firstWindowRect = focusNodeWindow.initRect;
  2307. let index = resizePairForWindow.index;
  2308. if (position === POSITION.BEFORE) {
  2309. // Find the opposite node
  2310. index = index + 1;
  2311. } else {
  2312. index = index - 1;
  2313. }
  2314. parentNodeForFocus = resizePairForWindow.parentNode.childNodes[index];
  2315. firstRect = parentNodeForFocus.rect;
  2316. secondRect = resizePairForWindow.rect;
  2317. if (!firstRect || !secondRect) {
  2318. return;
  2319. }
  2320. parentRect = parentNodeForFocus.parentNode.rect;
  2321. let changePx = currentRect.height - firstWindowRect.height;
  2322. let firstPercent = (firstRect.height + changePx) / parentRect.height;
  2323. let secondPercent = (secondRect.height - changePx) / parentRect.height;
  2324. parentNodeForFocus.percent = firstPercent;
  2325. resizePairForWindow.percent = secondPercent;
  2326. }
  2327. }
  2328. }
  2329. }
  2330. }
  2331. _handleMoving(focusNodeWindow) {
  2332. if (!focusNodeWindow || focusNodeWindow.mode !== WINDOW_MODES.GRAB_TILE) return;
  2333. const nodeWinAtPointer = this.findNodeWindowAtPointer(focusNodeWindow);
  2334. this.nodeWinAtPointer = nodeWinAtPointer;
  2335. const hidePreview = () => {
  2336. if (focusNodeWindow.previewHint) {
  2337. focusNodeWindow.previewHint.hide();
  2338. }
  2339. };
  2340. if (nodeWinAtPointer) {
  2341. if (!focusNodeWindow.previewHint) {
  2342. let previewHint = new St.Bin();
  2343. global.window_group.add_child(previewHint);
  2344. focusNodeWindow.previewHint = previewHint;
  2345. }
  2346. if (this.allowDragDropTile()) {
  2347. this.moveWindowToPointer(focusNodeWindow, true);
  2348. } else {
  2349. hidePreview();
  2350. }
  2351. } else {
  2352. hidePreview();
  2353. }
  2354. }
  2355. isFloatingExempt(metaWindow) {
  2356. if (!metaWindow) return true;
  2357. let windowTitle = metaWindow.get_title();
  2358. let windowType = metaWindow.get_window_type();
  2359. let floatByType =
  2360. windowType === Meta.WindowType.DIALOG ||
  2361. windowType === Meta.WindowType.MODAL_DIALOG ||
  2362. metaWindow.get_transient_for() !== null ||
  2363. metaWindow.get_wm_class() === null ||
  2364. windowTitle === null ||
  2365. windowTitle === "" ||
  2366. windowTitle.length === 0 ||
  2367. !metaWindow.allows_resize();
  2368. const knownFloats = this.windowProps.overrides.filter((wprop) => wprop.mode === "float");
  2369. let floatOverride =
  2370. knownFloats.filter((kf) => {
  2371. let matchTitle = false;
  2372. let matchClass = false;
  2373. let matchId = false;
  2374. if (kf.wmTitle) {
  2375. if (kf.wmTitle === " ") {
  2376. matchTitle = kf.wmTitle === windowTitle;
  2377. } else {
  2378. let titles = kf.wmTitle.split(",");
  2379. matchTitle =
  2380. titles.filter((t) => {
  2381. if (windowTitle) {
  2382. if (t.startsWith("!")) {
  2383. return !windowTitle.includes(t.slice(1));
  2384. } else {
  2385. return windowTitle.includes(t);
  2386. }
  2387. }
  2388. return false;
  2389. }).length > 0;
  2390. }
  2391. }
  2392. if (kf.wmClass) {
  2393. matchClass = kf.wmClass.includes(metaWindow.get_wm_class());
  2394. }
  2395. if (kf.wmId) {
  2396. matchId = kf.wmId === metaWindow.get_id();
  2397. }
  2398. return (!kf.wmId || matchId) && (!kf.wmTitle || matchTitle) && matchClass;
  2399. }).length > 0;
  2400. return floatByType || floatOverride;
  2401. }
  2402. _getDragDropCenterPreviewStyle() {
  2403. const centerLayout = this.ext.settings.get_string("dnd-center-layout");
  2404. return `window-tilepreview-${centerLayout}`;
  2405. }
  2406. get currentMonWsNode() {
  2407. const monWs = this.currentMonWs;
  2408. if (monWs) {
  2409. return this.tree.findNode(monWs);
  2410. }
  2411. return null;
  2412. }
  2413. get currentWsNode() {
  2414. const ws = this.currentWs;
  2415. if (ws) {
  2416. return this.tree.findNode(ws);
  2417. }
  2418. return null;
  2419. }
  2420. get currentMonWs() {
  2421. const monWs = `${this.currentMon}${this.currentWs}`;
  2422. return monWs;
  2423. }
  2424. get currentWs() {
  2425. const display = global.display;
  2426. const wsMgr = display.get_workspace_manager();
  2427. return `ws${wsMgr.get_active_workspace_index()}`;
  2428. }
  2429. get currentMon() {
  2430. const display = global.display;
  2431. return `mo${display.get_current_monitor()}`;
  2432. }
  2433. floatAllWindows() {
  2434. this.tree.getNodeByType(NODE_TYPES.WINDOW).forEach((w) => {
  2435. if (w.isFloat()) {
  2436. w.prevFloat = true;
  2437. }
  2438. w.mode = WINDOW_MODES.FLOAT;
  2439. });
  2440. }
  2441. unfloatAllWindows() {
  2442. this.tree.getNodeByType(NODE_TYPES.WINDOW).forEach((w) => {
  2443. if (!w.prevFloat) {
  2444. w.mode = WINDOW_MODES.TILE;
  2445. } else {
  2446. // Reset the float marker
  2447. w.prevFloat = false;
  2448. }
  2449. });
  2450. }
  2451. }