window.js 93 KB

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