5 Commits

Author SHA1 Message Date
Lucas Oskorep
656e448927 feat: add tabbed container layout mode with tab bar UI
All checks were successful
Build and Test / build (push) Successful in 25s
Build and Test / release (push) Successful in 3s
fix: tab bars no longer shown in overview.  Tab bars show name of app with pipe and then title of the app
2026-02-26 21:45:40 -05:00
Lucas Oskorep
93516b31fb fix: new bug from combining resize and move commands - if window has min-size set and resize goes smaller window would not move or resize causing vesktop and steam to frequently break when in arrays on a smaller monitors
All checks were successful
Build and Test / build (push) Successful in 39s
Build and Test / release (push) Successful in 10s
2026-02-26 01:54:49 -05:00
Lucas Oskorep
918c07c419 Merge pull request 'chore(deps): update dependency eslint to v10' (#16) from renovate/major-eslint-monorepo into main
All checks were successful
Build and Test / build (push) Successful in 25s
Build and Test / release (push) Successful in 2s
Reviewed-on: #16
2026-02-25 17:18:42 -05:00
Lucas Oskorep
15188b9990 Merge branch 'main' into renovate/major-eslint-monorepo
All checks were successful
Build and Test / build (pull_request) Successful in 24s
Build and Test / release (pull_request) Has been skipped
2026-02-25 17:02:44 -05:00
Renovate Bot
8f6e8582c9 chore(deps): update dependency eslint to v10
All checks were successful
Build and Test / build (pull_request) Successful in 24s
Build and Test / release (pull_request) Has been skipped
2026-02-06 23:13:10 +00:00
12 changed files with 733 additions and 272 deletions

View File

@@ -19,10 +19,15 @@ export default class aerospike extends Extension {
} }
enable() { enable() {
try {
Logger.log("STARTING AEROSPIKE!") Logger.log("STARTING AEROSPIKE!")
this.bindSettings(); this.bindSettings();
this.setupKeybindings(); this.setupKeybindings();
this.windowManager.enable() this.windowManager.enable()
Logger.log("AEROSPIKE ENABLED SUCCESSFULLY")
} catch (e) {
Logger.error("AEROSPIKE ENABLE FAILED", e);
}
} }
disable() { disable() {
@@ -39,6 +44,7 @@ export default class aerospike extends Extension {
'print-tree': () => { this.windowManager.printTreeStructure(); }, 'print-tree': () => { this.windowManager.printTreeStructure(); },
'toggle-orientation': () => { this.windowManager.toggleActiveContainerOrientation(); }, 'toggle-orientation': () => { this.windowManager.toggleActiveContainerOrientation(); },
'reset-ratios': () => { this.windowManager.resetActiveContainerRatios(); }, 'reset-ratios': () => { this.windowManager.resetActiveContainerRatios(); },
'toggle-tabbed': () => { this.windowManager.toggleActiveContainerTabbed(); },
}; };
} }

View File

@@ -25,7 +25,7 @@
"@girs/gnome-shell": "49.1.0", "@girs/gnome-shell": "49.1.0",
"@jest/globals": "^30.0.0", "@jest/globals": "^30.0.0",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"eslint": "^9.36.0", "eslint": "^10.0.0",
"eslint-plugin-jsdoc": "^62.0.0", "eslint-plugin-jsdoc": "^62.0.0",
"jest": "^30.0.0", "jest": "^30.0.0",
"ts-jest": "^29.1.2", "ts-jest": "^29.1.2",

272
pnpm-lock.yaml generated
View File

@@ -43,11 +43,11 @@ importers:
specifier: ^30.0.0 specifier: ^30.0.0
version: 30.0.0 version: 30.0.0
eslint: eslint:
specifier: ^9.36.0 specifier: ^10.0.0
version: 9.39.3 version: 10.0.0
eslint-plugin-jsdoc: eslint-plugin-jsdoc:
specifier: ^62.0.0 specifier: ^62.0.0
version: 62.4.1(eslint@9.39.3) version: 62.4.1(eslint@10.0.0)
jest: jest:
specifier: ^30.0.0 specifier: ^30.0.0
version: 30.2.0(@types/node@25.1.0) version: 30.2.0(@types/node@25.1.0)
@@ -252,33 +252,25 @@ packages:
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/config-array@0.21.1': '@eslint/config-array@0.23.0':
resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} resolution: {integrity: sha512-T5Swqd+PZxBekRuMsIFCySM3NUE8GjuqyksIIsXgkF2GCuiDaqpxKyPkv9VMEKpq5D7r5DLss1tM8tCsvRSjeg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/config-helpers@0.4.2': '@eslint/config-helpers@0.5.2':
resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/core@0.17.0': '@eslint/core@1.1.0':
resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/eslintrc@3.3.4': '@eslint/object-schema@3.0.0':
resolution: {integrity: sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==} resolution: {integrity: sha512-nWl20RtHQP2A2yvKU6Fee62Xo1AoNRqBLixtyg45zJhU8ljPFCyBK90d8e8XTnPns1RggSf4HH3bM8AhevkPVg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/js@9.39.3': '@eslint/plugin-kit@0.6.0':
resolution: {integrity: sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==} resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/object-schema@2.1.7':
resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.4.1':
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38': '@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38':
resolution: {integrity: sha512-6QzytM5dztmMynF2bxN73EuNK9ArMFxkP2L8wUC7IH45zBeBOfYcqL85BFh2PmkGmqRk+Rli5EFR8dAkx3Ig5Q==} resolution: {integrity: sha512-6QzytM5dztmMynF2bxN73EuNK9ArMFxkP2L8wUC7IH45zBeBOfYcqL85BFh2PmkGmqRk+Rli5EFR8dAkx3Ig5Q==}
@@ -425,6 +417,14 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'} engines: {node: '>=18.18'}
'@isaacs/balanced-match@4.0.1':
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
engines: {node: 20 || >=22}
'@isaacs/brace-expansion@5.0.1':
resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==}
engines: {node: 20 || >=22}
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -574,6 +574,9 @@ packages:
'@types/babel__traverse@7.28.0': '@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/esrecurse@4.3.1':
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -724,13 +727,8 @@ packages:
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
acorn@8.16.0: ajv@6.12.6:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
engines: {node: '>=0.4.0'}
hasBin: true
ajv@6.14.0:
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
ansi-escapes@4.3.2: ansi-escapes@4.3.2:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
@@ -767,9 +765,6 @@ packages:
argparse@1.0.10: argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
babel-jest@30.2.0: babel-jest@30.2.0:
resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==} resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -798,10 +793,6 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
balanced-match@4.0.4:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
baseline-browser-mapping@2.9.19: baseline-browser-mapping@2.9.19:
resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
hasBin: true hasBin: true
@@ -809,9 +800,8 @@ packages:
brace-expansion@1.1.12: brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
brace-expansion@5.0.3: brace-expansion@2.0.2:
resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
engines: {node: 18 || 20 || >=22}
braces@3.0.3: braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
@@ -959,25 +949,21 @@ packages:
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
eslint-scope@8.4.0: eslint-scope@9.1.0:
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} resolution: {integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint-visitor-keys@3.4.3: eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-visitor-keys@4.2.1:
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint-visitor-keys@5.0.0: eslint-visitor-keys@5.0.0:
resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==} resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint@9.39.3: eslint@10.0.0:
resolution: {integrity: sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==} resolution: {integrity: sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
jiti: '*' jiti: '*'
@@ -985,10 +971,6 @@ packages:
jiti: jiti:
optional: true optional: true
espree@10.4.0:
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
espree@11.1.0: espree@11.1.0:
resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==} resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
@@ -1102,10 +1084,6 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
globals@14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
graceful-fs@4.2.11: graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -1132,10 +1110,6 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
import-local@3.2.0: import-local@3.2.0:
resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1340,10 +1314,6 @@ packages:
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
hasBin: true hasBin: true
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
jsdoc-type-pratt-parser@7.1.0: jsdoc-type-pratt-parser@7.1.0:
resolution: {integrity: sha512-SX7q7XyCwzM/MEDCYz0l8GgGbJAACGFII9+WfNYr5SLEKukHWRy2Jk3iWRe7P+lpYJNs7oQ+OSei4JtKGUjd7A==} resolution: {integrity: sha512-SX7q7XyCwzM/MEDCYz0l8GgGbJAACGFII9+WfNYr5SLEKukHWRy2Jk3iWRe7P+lpYJNs7oQ+OSei4JtKGUjd7A==}
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
@@ -1395,9 +1365,6 @@ packages:
lodash.memoize@4.1.2: lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -1425,11 +1392,15 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}
minimatch@3.1.3: minimatch@10.1.2:
resolution: {integrity: sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==} resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==}
engines: {node: 20 || >=22}
minimatch@9.0.6: minimatch@3.1.2:
resolution: {integrity: sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
minimist@1.2.8: minimist@1.2.8:
@@ -1504,10 +1475,6 @@ packages:
package-json-from-dist@1.0.1: package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
parse-imports-exports@0.2.4: parse-imports-exports@0.2.4:
resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
@@ -1583,10 +1550,6 @@ packages:
resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
engines: {node: '>=8'} engines: {node: '>=8'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
resolve-from@5.0.0: resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2042,50 +2005,34 @@ snapshots:
'@es-joy/resolve.exports@1.2.0': {} '@es-joy/resolve.exports@1.2.0': {}
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.3)': '@eslint-community/eslint-utils@4.9.1(eslint@10.0.0)':
dependencies: dependencies:
eslint: 9.39.3 eslint: 10.0.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {} '@eslint-community/regexpp@4.12.2': {}
'@eslint/config-array@0.21.1': '@eslint/config-array@0.23.0':
dependencies: dependencies:
'@eslint/object-schema': 2.1.7 '@eslint/object-schema': 3.0.0
debug: 4.4.3 debug: 4.4.3
minimatch: 3.1.3 minimatch: 10.1.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@eslint/config-helpers@0.4.2': '@eslint/config-helpers@0.5.2':
dependencies: dependencies:
'@eslint/core': 0.17.0 '@eslint/core': 1.1.0
'@eslint/core@0.17.0': '@eslint/core@1.1.0':
dependencies: dependencies:
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
'@eslint/eslintrc@3.3.4': '@eslint/object-schema@3.0.0': {}
'@eslint/plugin-kit@0.6.0':
dependencies: dependencies:
ajv: 6.14.0 '@eslint/core': 1.1.0
debug: 4.4.3
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.1.1
minimatch: 3.1.3
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
'@eslint/js@9.39.3': {}
'@eslint/object-schema@2.1.7': {}
'@eslint/plugin-kit@0.4.1':
dependencies:
'@eslint/core': 0.17.0
levn: 0.4.1 levn: 0.4.1
'@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38': '@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38':
@@ -2552,6 +2499,12 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {} '@humanwhocodes/retry@0.4.3': {}
'@isaacs/balanced-match@4.0.1': {}
'@isaacs/brace-expansion@5.0.1':
dependencies:
'@isaacs/balanced-match': 4.0.1
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
dependencies: dependencies:
string-width: 5.1.2 string-width: 5.1.2
@@ -2819,6 +2772,8 @@ snapshots:
dependencies: dependencies:
'@babel/types': 7.28.6 '@babel/types': 7.28.6
'@types/esrecurse@4.3.1': {}
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-coverage@2.0.6': {}
@@ -2917,15 +2872,9 @@ snapshots:
dependencies: dependencies:
acorn: 8.15.0 acorn: 8.15.0
acorn-jsx@5.3.2(acorn@8.16.0):
dependencies:
acorn: 8.16.0
acorn@8.15.0: {} acorn@8.15.0: {}
acorn@8.16.0: {} ajv@6.12.6:
ajv@6.14.0:
dependencies: dependencies:
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0 fast-json-stable-stringify: 2.1.0
@@ -2959,8 +2908,6 @@ snapshots:
dependencies: dependencies:
sprintf-js: 1.0.3 sprintf-js: 1.0.3
argparse@2.0.1: {}
babel-jest@30.2.0(@babel/core@7.28.6): babel-jest@30.2.0(@babel/core@7.28.6):
dependencies: dependencies:
'@babel/core': 7.28.6 '@babel/core': 7.28.6
@@ -3015,8 +2962,6 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
balanced-match@4.0.4: {}
baseline-browser-mapping@2.9.19: {} baseline-browser-mapping@2.9.19: {}
brace-expansion@1.1.12: brace-expansion@1.1.12:
@@ -3024,9 +2969,9 @@ snapshots:
balanced-match: 1.0.2 balanced-match: 1.0.2
concat-map: 0.0.1 concat-map: 0.0.1
brace-expansion@5.0.3: brace-expansion@2.0.2:
dependencies: dependencies:
balanced-match: 4.0.4 balanced-match: 1.0.2
braces@3.0.3: braces@3.0.3:
dependencies: dependencies:
@@ -3129,7 +3074,7 @@ snapshots:
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}
eslint-plugin-jsdoc@62.4.1(eslint@9.39.3): eslint-plugin-jsdoc@62.4.1(eslint@10.0.0):
dependencies: dependencies:
'@es-joy/jsdoccomment': 0.83.0 '@es-joy/jsdoccomment': 0.83.0
'@es-joy/resolve.exports': 1.2.0 '@es-joy/resolve.exports': 1.2.0
@@ -3137,7 +3082,7 @@ snapshots:
comment-parser: 1.4.5 comment-parser: 1.4.5
debug: 4.4.3 debug: 4.4.3
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint: 9.39.3 eslint: 10.0.0
espree: 11.1.0 espree: 11.1.0
esquery: 1.7.0 esquery: 1.7.0
html-entities: 2.6.0 html-entities: 2.6.0
@@ -3149,39 +3094,36 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-scope@8.4.0: eslint-scope@9.1.0:
dependencies: dependencies:
'@types/esrecurse': 4.3.1
'@types/estree': 1.0.8
esrecurse: 4.3.0 esrecurse: 4.3.0
estraverse: 5.3.0 estraverse: 5.3.0
eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@3.4.3: {}
eslint-visitor-keys@4.2.1: {}
eslint-visitor-keys@5.0.0: {} eslint-visitor-keys@5.0.0: {}
eslint@9.39.3: eslint@10.0.0:
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3) '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0)
'@eslint-community/regexpp': 4.12.2 '@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.21.1 '@eslint/config-array': 0.23.0
'@eslint/config-helpers': 0.4.2 '@eslint/config-helpers': 0.5.2
'@eslint/core': 0.17.0 '@eslint/core': 1.1.0
'@eslint/eslintrc': 3.3.4 '@eslint/plugin-kit': 0.6.0
'@eslint/js': 9.39.3
'@eslint/plugin-kit': 0.4.1
'@humanfs/node': 0.16.7 '@humanfs/node': 0.16.7
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3 '@humanwhocodes/retry': 0.4.3
'@types/estree': 1.0.8 '@types/estree': 1.0.8
ajv: 6.14.0 ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6 cross-spawn: 7.0.6
debug: 4.4.3 debug: 4.4.3
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 8.4.0 eslint-scope: 9.1.0
eslint-visitor-keys: 4.2.1 eslint-visitor-keys: 5.0.0
espree: 10.4.0 espree: 11.1.0
esquery: 1.7.0 esquery: 1.7.0
esutils: 2.0.3 esutils: 2.0.3
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
@@ -3192,19 +3134,12 @@ snapshots:
imurmurhash: 0.1.4 imurmurhash: 0.1.4
is-glob: 4.0.3 is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1 json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2 minimatch: 10.1.2
minimatch: 3.1.3
natural-compare: 1.4.0 natural-compare: 1.4.0
optionator: 0.9.4 optionator: 0.9.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
espree@10.4.0:
dependencies:
acorn: 8.16.0
acorn-jsx: 5.3.2(acorn@8.16.0)
eslint-visitor-keys: 4.2.1
espree@11.1.0: espree@11.1.0:
dependencies: dependencies:
acorn: 8.15.0 acorn: 8.15.0
@@ -3309,7 +3244,7 @@ snapshots:
dependencies: dependencies:
foreground-child: 3.3.1 foreground-child: 3.3.1
jackspeak: 3.4.3 jackspeak: 3.4.3
minimatch: 9.0.6 minimatch: 9.0.5
minipass: 7.1.2 minipass: 7.1.2
package-json-from-dist: 1.0.1 package-json-from-dist: 1.0.1
path-scurry: 1.11.1 path-scurry: 1.11.1
@@ -3319,12 +3254,10 @@ snapshots:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
inflight: 1.0.6 inflight: 1.0.6
inherits: 2.0.4 inherits: 2.0.4
minimatch: 3.1.3 minimatch: 3.1.2
once: 1.4.0 once: 1.4.0
path-is-absolute: 1.0.1 path-is-absolute: 1.0.1
globals@14.0.0: {}
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
handlebars@4.7.8: handlebars@4.7.8:
@@ -3346,11 +3279,6 @@ snapshots:
ignore@5.3.2: {} ignore@5.3.2: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
import-local@3.2.0: import-local@3.2.0:
dependencies: dependencies:
pkg-dir: 4.2.0 pkg-dir: 4.2.0
@@ -3738,10 +3666,6 @@ snapshots:
argparse: 1.0.10 argparse: 1.0.10
esprima: 4.0.1 esprima: 4.0.1
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
jsdoc-type-pratt-parser@7.1.0: {} jsdoc-type-pratt-parser@7.1.0: {}
jsesc@3.1.0: {} jsesc@3.1.0: {}
@@ -3779,8 +3703,6 @@ snapshots:
lodash.memoize@4.1.2: {} lodash.memoize@4.1.2: {}
lodash.merge@4.6.2: {}
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lru-cache@5.1.1: lru-cache@5.1.1:
@@ -3806,13 +3728,17 @@ snapshots:
mimic-fn@2.1.0: {} mimic-fn@2.1.0: {}
minimatch@3.1.3: minimatch@10.1.2:
dependencies:
'@isaacs/brace-expansion': 5.0.1
minimatch@3.1.2:
dependencies: dependencies:
brace-expansion: 1.1.12 brace-expansion: 1.1.12
minimatch@9.0.6: minimatch@9.0.5:
dependencies: dependencies:
brace-expansion: 5.0.3 brace-expansion: 2.0.2
minimist@1.2.8: {} minimist@1.2.8: {}
@@ -3875,10 +3801,6 @@ snapshots:
package-json-from-dist@1.0.1: {} package-json-from-dist@1.0.1: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
parse-imports-exports@0.2.4: parse-imports-exports@0.2.4:
dependencies: dependencies:
parse-statements: 1.0.11 parse-statements: 1.0.11
@@ -3937,8 +3859,6 @@ snapshots:
dependencies: dependencies:
resolve-from: 5.0.0 resolve-from: 5.0.0
resolve-from@4.0.0: {}
resolve-from@5.0.0: {} resolve-from@5.0.0: {}
semver@6.3.1: {} semver@6.3.1: {}
@@ -4026,7 +3946,7 @@ snapshots:
dependencies: dependencies:
'@istanbuljs/schema': 0.1.3 '@istanbuljs/schema': 0.1.3
glob: 7.2.3 glob: 7.2.3
minimatch: 3.1.3 minimatch: 3.1.2
tmpl@1.0.5: {} tmpl@1.0.5: {}

View File

@@ -44,7 +44,7 @@
</key> </key>
<key name="toggle-orientation" type="as"> <key name="toggle-orientation" type="as">
<default><![CDATA[['<Super><Shift>comma']]]></default> <default><![CDATA[['<Primary>comma']]]></default>
<summary>Toggle active container orientation</summary> <summary>Toggle active container orientation</summary>
<description>Toggles the orientation of the container holding the active window between horizontal and vertical</description> <description>Toggles the orientation of the container holding the active window between horizontal and vertical</description>
</key> </key>
@@ -55,5 +55,11 @@
<description>Resets all window size ratios in the active window's container to equal splits</description> <description>Resets all window size ratios in the active window's container to equal splits</description>
</key> </key>
<key name="toggle-tabbed" type="as">
<default><![CDATA[['<Primary>slash']]]></default>
<summary>Toggle tabbed container mode</summary>
<description>Toggles the active window's container between tabbed and accordion layout modes</description>
</key>
</schema> </schema>
</schemalist> </schemalist>

View File

@@ -20,25 +20,38 @@ jest.mock('../utils/events.js', () => ({
describe('Container Logic Tests', () => { describe('Container Logic Tests', () => {
describe('Orientation Toggle Logic', () => { describe('Orientation Toggle Logic', () => {
enum Orientation { enum Layout {
HORIZONTAL = 0, HORIZONTAL = 0,
VERTICAL = 1, VERTICAL = 1,
TABBED = 2,
} }
const toggleOrientation = (current: Orientation): Orientation => { const toggleOrientation = (current: Layout): Layout => {
return current === Orientation.HORIZONTAL if (current === Layout.TABBED) return Layout.HORIZONTAL;
? Orientation.VERTICAL return current === Layout.HORIZONTAL
: Orientation.HORIZONTAL; ? Layout.VERTICAL
: Layout.HORIZONTAL;
}; };
test('should toggle from HORIZONTAL to VERTICAL', () => { test('should toggle from HORIZONTAL to VERTICAL', () => {
const result = toggleOrientation(Orientation.HORIZONTAL); const result = toggleOrientation(Layout.HORIZONTAL);
expect(result).toBe(Orientation.VERTICAL); expect(result).toBe(Layout.VERTICAL);
}); });
test('should toggle from VERTICAL to HORIZONTAL', () => { test('should toggle from VERTICAL to HORIZONTAL', () => {
const result = toggleOrientation(Orientation.VERTICAL); const result = toggleOrientation(Layout.VERTICAL);
expect(result).toBe(Orientation.HORIZONTAL); expect(result).toBe(Layout.HORIZONTAL);
});
test('should toggle from TABBED to HORIZONTAL', () => {
const result = toggleOrientation(Layout.TABBED);
expect(result).toBe(Layout.HORIZONTAL);
});
test('enum reverse mapping should return string names', () => {
expect(Layout[Layout.HORIZONTAL]).toBe('HORIZONTAL');
expect(Layout[Layout.VERTICAL]).toBe('VERTICAL');
expect(Layout[Layout.TABBED]).toBe('TABBED');
}); });
}); });
@@ -100,6 +113,71 @@ describe('Container Logic Tests', () => {
}); });
}); });
describe('Tabbed Bounds Calculation', () => {
const TAB_BAR_HEIGHT = 24;
test('should give all items the same content rect in tabbed mode', () => {
const workArea = { x: 100, y: 0, width: 1000, height: 500 };
const itemCount = 3;
const contentRect = {
x: workArea.x,
y: workArea.y + TAB_BAR_HEIGHT,
width: workArea.width,
height: workArea.height - TAB_BAR_HEIGHT,
};
const bounds = Array.from({ length: itemCount }, () => contentRect);
expect(bounds.length).toBe(3);
// All bounds should be identical
bounds.forEach(b => {
expect(b.x).toBe(100);
expect(b.y).toBe(TAB_BAR_HEIGHT);
expect(b.width).toBe(1000);
expect(b.height).toBe(500 - TAB_BAR_HEIGHT);
});
});
test('tab bar rect should occupy top of work area', () => {
const workArea = { x: 200, y: 50, width: 800, height: 600 };
const tabBarRect = {
x: workArea.x,
y: workArea.y,
width: workArea.width,
height: TAB_BAR_HEIGHT,
};
expect(tabBarRect.x).toBe(200);
expect(tabBarRect.y).toBe(50);
expect(tabBarRect.width).toBe(800);
expect(tabBarRect.height).toBe(TAB_BAR_HEIGHT);
});
test('active tab index should clamp after removal', () => {
let activeTabIndex = 2;
const itemCount = 2; // after removing one from 3
if (activeTabIndex >= itemCount) {
activeTabIndex = itemCount - 1;
}
expect(activeTabIndex).toBe(1);
});
test('active tab index should stay at 0 when first item removed', () => {
let activeTabIndex = 0;
const itemCount = 2; // after removing one from 3
if (activeTabIndex >= itemCount) {
activeTabIndex = itemCount - 1;
}
expect(activeTabIndex).toBe(0);
});
});
describe('Window Index Finding', () => { describe('Window Index Finding', () => {
test('should find window index in array', () => { test('should find window index in array', () => {
const windows = [ const windows = [

View File

@@ -182,6 +182,15 @@ export default class AerospikeExtensions extends ExtensionPreferences {
}) })
); );
keybindingsGroup.add(
new EntryRow({
title: _('Toggle Tabbed Mode'),
settings: settings,
bind: 'toggle-tabbed',
map: keybindingMap
})
);
} }
// Helper function to create a keybinding mapping object // Helper function to create a keybinding mapping object

View File

@@ -2,10 +2,12 @@ import {WindowWrapper} from "./window.js";
import {Logger} from "../utils/logger.js"; import {Logger} from "../utils/logger.js";
import queueEvent from "../utils/events.js"; import queueEvent from "../utils/events.js";
import {Rect} from "../utils/rect.js"; import {Rect} from "../utils/rect.js";
import {TabBar, TAB_BAR_HEIGHT} from "./tabBar.js";
enum Orientation { export enum Layout {
HORIZONTAL = 0, ACC_HORIZONTAL = 0,
VERTICAL = 1, ACC_VERTICAL = 1,
TABBED = 2,
} }
// Returns equal ratios summing exactly to 1.0, with float drift absorbed by the last slot. // Returns equal ratios summing exactly to 1.0, with float drift absorbed by the last slot.
@@ -22,10 +24,17 @@ export default class WindowContainer {
_tiledItems: (WindowWrapper | WindowContainer)[]; _tiledItems: (WindowWrapper | WindowContainer)[];
_tiledWindowLookup: Map<number, WindowWrapper>; _tiledWindowLookup: Map<number, WindowWrapper>;
_orientation: Orientation = Orientation.HORIZONTAL; _orientation: Layout = Layout.ACC_HORIZONTAL;
_workArea: Rect; _workArea: Rect;
// -- Accordion Mode States
_splitRatios: number[]; _splitRatios: number[];
// -- Tabbed mode state -----------------------------------------------------
_activeTabIndex: number = 0;
_tabBar: TabBar | null = null;
constructor(workspaceArea: Rect) { constructor(workspaceArea: Rect) {
this._tiledItems = []; this._tiledItems = [];
this._tiledWindowLookup = new Map<number, WindowWrapper>(); this._tiledWindowLookup = new Map<number, WindowWrapper>();
@@ -33,7 +42,7 @@ export default class WindowContainer {
this._splitRatios = []; this._splitRatios = [];
} }
// ─── Helpers ──────────────────────────────────────────────────────────────── // --- Helpers ----------------------------------------------------------------
private _resetRatios(): void { private _resetRatios(): void {
this._splitRatios = equalRatios(this._tiledItems.length); this._splitRatios = equalRatios(this._tiledItems.length);
@@ -54,33 +63,128 @@ export default class WindowContainer {
} }
private _totalDimension(): number { private _totalDimension(): number {
return this._orientation === Orientation.HORIZONTAL return this._orientation === Layout.ACC_HORIZONTAL
? this._workArea.width ? this._workArea.width
: this._workArea.height; : this._workArea.height;
} }
// ─── Public API ───────────────────────────────────────────────────────────── isTabbed(): boolean {
return this._orientation === Layout.TABBED;
}
// --- Public API -------------------------------------------------------------
move(rect: Rect): void { move(rect: Rect): void {
this._workArea = rect; this._workArea = rect;
this.tileWindows(); this.drawWindows();
} }
toggleOrientation(): void { toggleOrientation(): void {
this._orientation = this._orientation === Orientation.HORIZONTAL if (this._orientation === Layout.TABBED) {
? Orientation.VERTICAL // Tabbed → Horizontal: restore accordion mode
: Orientation.HORIZONTAL; this.setAccordion(Layout.ACC_HORIZONTAL);
Logger.info(`Container orientation toggled to ${this._orientation === Orientation.HORIZONTAL ? 'HORIZONTAL' : 'VERTICAL'}`); } else {
this.tileWindows(); this._orientation = this._orientation === Layout.ACC_HORIZONTAL
? Layout.ACC_VERTICAL
: Layout.ACC_HORIZONTAL;
Logger.info(`Container orientation toggled to ${Layout[this._orientation]}`);
this.drawWindows();
}
}
/**
* Switch this container to tabbed mode.
*/
setTabbed(): void {
if (this._orientation === Layout.TABBED) return;
Logger.info("Container switching to TABBED mode");
this._orientation = Layout.TABBED;
// Clamp active tab index
if (this._activeTabIndex < 0 || this._activeTabIndex >= this._tiledItems.length) {
this._activeTabIndex = 0;
}
// Create tab bar
this._tabBar = new TabBar((index) => {
this.setActiveTab(index);
});
this.drawWindows();
}
/**
* Switch this container back to accordion (H or V) mode.
*/
setAccordion(orientation: Layout.ACC_HORIZONTAL | Layout.ACC_VERTICAL): void {
if (this._orientation !== Layout.TABBED) {
// Already accordion — just set the orientation
this._orientation = orientation;
this.drawWindows();
return;
}
Logger.info(`Container switching from TABBED to ${Layout[orientation]}`);
this._orientation = orientation;
// Destroy tab bar
if (this._tabBar) {
this._tabBar.destroy();
this._tabBar = null;
}
// Show all windows (they may have been hidden in tabbed mode)
this._showAllWindows();
this.drawWindows();
}
/**
* Set the active tab by index. Shows that window, hides others, updates tab bar.
*/
setActiveTab(index: number): void {
if (!this.isTabbed()) return;
if (index < 0 || index >= this._tiledItems.length) return;
this._activeTabIndex = index;
Logger.info(`Active tab set to ${index}`);
this._applyTabVisibility();
this._updateTabBar();
// Tile to resize the active window to the content area
this.drawWindows();
}
getActiveTabIndex(): number {
return this._activeTabIndex;
}
hideTabBar(): void {
this._tabBar?.hide();
}
showTabBar(): void {
if (this.isTabbed() && this._tabBar) {
this._tabBar.show();
}
} }
addWindow(winWrap: WindowWrapper): void { addWindow(winWrap: WindowWrapper): void {
this._tiledItems.push(winWrap); this._tiledItems.push(winWrap);
this._tiledWindowLookup.set(winWrap.getWindowId(), winWrap); this._tiledWindowLookup.set(winWrap.getWindowId(), winWrap);
this._addRatioForNewWindow(); this._addRatioForNewWindow();
if (this.isTabbed()) {
// TODO: make it so that when tabs are added they are made the current active tab
this._applyTabVisibility();
this._updateTabBar();
}
queueEvent({ queueEvent({
name: "tiling-windows", name: "tiling-windows",
callback: () => this.tileWindows(), callback: () => this.drawWindows(),
}, 100); }, 100);
} }
@@ -111,13 +215,28 @@ export default class WindowContainer {
removeWindow(win_id: number): void { removeWindow(win_id: number): void {
if (this._tiledWindowLookup.has(win_id)) { if (this._tiledWindowLookup.has(win_id)) {
// Get index before deleting from lookup to avoid race condition
const index = this._getIndexOfWindow(win_id); const index = this._getIndexOfWindow(win_id);
this._tiledWindowLookup.delete(win_id); this._tiledWindowLookup.delete(win_id);
if (index !== -1) { if (index !== -1) {
// If removing the window that was hidden in tabbed mode,
// make sure to show it first so it doesn't stay invisible
const item = this._tiledItems[index];
if (item instanceof WindowWrapper) {
item.showWindow();
}
this._tiledItems.splice(index, 1); this._tiledItems.splice(index, 1);
} }
this._resetRatios(); this._resetRatios();
if (this.isTabbed()) {
if (this._tiledItems.length === 0) {
this._activeTabIndex = 0;
} else if (this._activeTabIndex >= this._tiledItems.length) {
this._activeTabIndex = this._tiledItems.length - 1;
}
this._applyTabVisibility();
this._updateTabBar();
}
} else { } else {
for (const item of this._tiledItems) { for (const item of this._tiledItems) {
if (item instanceof WindowContainer) { if (item instanceof WindowContainer) {
@@ -125,7 +244,7 @@ export default class WindowContainer {
} }
} }
} }
this.tileWindows(); this.drawWindows();
} }
disconnectSignals(): void { disconnectSignals(): void {
@@ -139,37 +258,152 @@ export default class WindowContainer {
} }
removeAllWindows(): void { removeAllWindows(): void {
// tabbed mode hides all windows - this ensures they are available before removal
this._showAllWindows();
if (this._tabBar) {
this._tabBar.destroy();
this._tabBar = null;
}
this._tiledItems = []; this._tiledItems = [];
this._tiledWindowLookup.clear(); this._tiledWindowLookup.clear();
this._splitRatios = []; this._splitRatios = [];
this._activeTabIndex = 0;
} }
tileWindows(): void { drawWindows(): void {
Logger.log("TILING WINDOWS IN CONTAINER"); Logger.log("TILING WINDOWS IN CONTAINER");
Logger.log("WorkArea", this._workArea); Logger.log("WorkArea", this._workArea);
this._tileItems();
if (this.isTabbed()) {
this._tileTab();
} else {
this._tileAccordion();
}
} }
_tileItems() { _tileAccordion() {
if (this._tiledItems.length === 0) return; if (this._tiledItems.length === 0) return;
const bounds = this.getBounds(); const bounds = this.getBounds();
Logger.info(`_tileItems: ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}] bounds=[${bounds.map(b => `(${b.x},${b.y},${b.width},${b.height})`).join(', ')}]`); Logger.info(`_tileAccordion: ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}] bounds=[${bounds.map(b => `(${b.x},${b.y},${b.width},${b.height})`).join(', ')}]`);
this._tiledItems.forEach((item, index) => { this._tiledItems.forEach((item, index) => {
const rect = bounds[index]; const rect = bounds[index];
if (item instanceof WindowContainer) { if (item instanceof WindowContainer) {
item.move(rect); item.move(rect);
} else { } else {
Logger.info(`_tileItems: window[${index}] id=${item.getWindowId()} dragging=${item._dragging} → rect=(${rect.x},${rect.y},${rect.width},${rect.height})`); Logger.info(`_tileAccordion: window[${index}] id=${item.getWindowId()} dragging=${item._dragging} → rect=(${rect.x},${rect.y},${rect.width},${rect.height})`);
item.safelyResizeWindow(rect); item.safelyResizeWindow(rect);
} }
}); });
} }
// ─── Bounds Calculation ────────────────────────────────────────────────────── private _tileTab(): void {
if (this._tiledItems.length === 0) return;
const tabBarRect: Rect = {
x: this._workArea.x,
y: this._workArea.y,
width: this._workArea.width,
height: TAB_BAR_HEIGHT,
};
const contentRect: Rect = {
x: this._workArea.x,
y: this._workArea.y + TAB_BAR_HEIGHT,
width: this._workArea.width,
height: this._workArea.height - TAB_BAR_HEIGHT,
};
// Position and show the tab bar
if (this._tabBar) {
this._tabBar.setPosition(tabBarRect);
if (!this._tabBar.isVisible()) {
this._rebuildAndShowTabBar();
}
}
this._applyTabVisibility();
const activeItem = this._tiledItems[this._activeTabIndex];
if (activeItem) {
if (activeItem instanceof WindowContainer) {
activeItem.move(contentRect);
} else {
Logger.info(`_tileTabbed: active tab[${this._activeTabIndex}] id=${activeItem.getWindowId()} → rect=(${contentRect.x},${contentRect.y},${contentRect.width},${contentRect.height})`);
activeItem.safelyResizeWindow(contentRect);
}
}
}
/**
* Show the active tab window, hide all others.
*/
private _applyTabVisibility(): void {
this._tiledItems.forEach((item, index) => {
if (item instanceof WindowWrapper) {
if (index === this._activeTabIndex) {
item.showWindow();
} else {
item.hideWindow();
}
}
});
}
/**
* Show all windows (used when leaving tabbed mode).
*/
private _showAllWindows(): void {
this._tiledItems.forEach((item) => {
if (item instanceof WindowWrapper) {
item.showWindow();
}
});
}
/**
* Rebuild the tab bar buttons and show it.
*/
private _rebuildAndShowTabBar(): void {
if (!this._tabBar) return;
const windowItems = this._tiledItems.filter(
(item): item is WindowWrapper => item instanceof WindowWrapper
);
this._tabBar.rebuild(windowItems, this._activeTabIndex);
this._tabBar.show();
}
/**
* Update tab bar state (active highlight, titles) without a full rebuild.
*/
private _updateTabBar(): void {
if (!this._tabBar) return;
// Rebuild is cheap — just recreate buttons from the current items
const windowItems = this._tiledItems.filter(
(item): item is WindowWrapper => item instanceof WindowWrapper
);
this._tabBar.rebuild(windowItems, this._activeTabIndex);
}
getBounds(): Rect[] { getBounds(): Rect[] {
return this._orientation === Orientation.HORIZONTAL if (this._orientation === Layout.TABBED) {
// In tabbed mode, all items share the same content rect
const contentRect: Rect = {
x: this._workArea.x,
y: this._workArea.y + TAB_BAR_HEIGHT,
width: this._workArea.width,
height: this._workArea.height - TAB_BAR_HEIGHT,
};
return this._tiledItems.map(() => contentRect);
}
return this._orientation === Layout.ACC_HORIZONTAL
? this._computeBounds('horizontal') ? this._computeBounds('horizontal')
: this._computeBounds('vertical'); : this._computeBounds('vertical');
} }
@@ -187,14 +421,15 @@ export default class WindowContainer {
used += size; used += size;
return isHorizontal return isHorizontal
? { x: this._workArea.x + offset, y: this._workArea.y, width: size, height: this._workArea.height } ? {x: this._workArea.x + offset, y: this._workArea.y, width: size, height: this._workArea.height}
: { x: this._workArea.x, y: this._workArea.y + offset, width: this._workArea.width, height: size }; : {x: this._workArea.x, y: this._workArea.y + offset, width: this._workArea.width, height: size};
}); });
} }
// ─── Boundary Adjustment ─────────────────────────────────────────────────────
adjustBoundary(boundaryIndex: number, deltaPixels: number): boolean { adjustBoundary(boundaryIndex: number, deltaPixels: number): boolean {
// No boundary adjustment in tabbed mode
if (this.isTabbed()) return false;
if (boundaryIndex < 0 || boundaryIndex >= this._tiledItems.length - 1) { if (boundaryIndex < 0 || boundaryIndex >= this._tiledItems.length - 1) {
Logger.warn(`adjustBoundary: invalid boundaryIndex ${boundaryIndex}`); Logger.warn(`adjustBoundary: invalid boundaryIndex ${boundaryIndex}`);
return false; return false;
@@ -219,7 +454,7 @@ export default class WindowContainer {
return true; return true;
} }
// ─── Container Lookup ──────────────────────────────────────────────────────── // --- Container Lookup --------------------------------------------------------
getContainerForWindow(win_id: number): WindowContainer | null { getContainerForWindow(win_id: number): WindowContainer | null {
for (const item of this._tiledItems) { for (const item of this._tiledItems) {
@@ -248,6 +483,12 @@ export default class WindowContainer {
// TODO: update this to work with nested containers - all other logic should already be working // TODO: update this to work with nested containers - all other logic should already be working
itemDragged(item: WindowWrapper, x: number, y: number): void { itemDragged(item: WindowWrapper, x: number, y: number): void {
// In tabbed mode, dragging reorders tabs but doesn't change layout
if (this.isTabbed()) {
// Don't reorder during tabbed mode — tabs have a fixed visual layout
return;
}
const original_index = this.getIndexOfItemNested(item); const original_index = this.getIndexOfItemNested(item);
if (original_index === -1) { if (original_index === -1) {
@@ -266,12 +507,12 @@ export default class WindowContainer {
Logger.info(`itemDragged: swapped slots ${original_index}<->${new_index}, ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}]`); Logger.info(`itemDragged: swapped slots ${original_index}<->${new_index}, ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}]`);
[this._tiledItems[original_index], this._tiledItems[new_index]] = [this._tiledItems[original_index], this._tiledItems[new_index]] =
[this._tiledItems[new_index], this._tiledItems[original_index]]; [this._tiledItems[new_index], this._tiledItems[original_index]];
this.tileWindows(); this.drawWindows();
} }
} }
resetRatios(): void { resetRatios(): void {
this._resetRatios(); this._resetRatios();
this.tileWindows(); this.drawWindows();
} }
} }

View File

@@ -1,7 +1,6 @@
import {WindowWrapper} from "./window.js"; import {WindowWrapper} from "./window.js";
import {Rect} from "../utils/rect.js"; import {Rect} from "../utils/rect.js";
import {Logger} from "../utils/logger.js"; import {Logger} from "../utils/logger.js";
import Meta from "gi://Meta";
import WindowContainer from "./container.js"; import WindowContainer from "./container.js";
@@ -73,6 +72,18 @@ export default class Monitor {
this._workspaces.push(new WindowContainer(this._workArea)); this._workspaces.push(new WindowContainer(this._workArea));
} }
hideTabBars(): void {
for (const container of this._workspaces) {
container.hideTabBar();
}
}
showTabBars(): void {
for (const container of this._workspaces) {
container.showTabBar();
}
}
itemDragged(item: WindowWrapper, x: number, y: number): void { itemDragged(item: WindowWrapper, x: number, y: number): void {
this._workspaces[item.getWorkspace()].itemDragged(item, x, y); this._workspaces[item.getWorkspace()].itemDragged(item, x, y);
} }

125
src/wm/tabBar.ts Normal file
View File

@@ -0,0 +1,125 @@
import Clutter from 'gi://Clutter';
import St from 'gi://St';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import {Logger} from "../utils/logger.js";
import {WindowWrapper} from "./window.js";
import {Rect} from "../utils/rect.js";
export const TAB_BAR_HEIGHT = 24;
type TabClickedCallback = (index: number) => void;
export class TabBar {
private _bar: St.BoxLayout;
private _buttons: St.Button[] = [];
private _activeIndex: number = 0;
private _onTabClicked: TabClickedCallback;
private _visible: boolean = false;
constructor(onTabClicked: TabClickedCallback) {
this._onTabClicked = onTabClicked;
this._bar = new St.BoxLayout({
style_class: 'aerospike-tab-bar',
vertical: false,
reactive: true,
can_focus: false,
track_hover: false,
});
}
/**
* Rebuild all tab buttons from the current list of window items.
*/
rebuild(items: WindowWrapper[], activeIndex: number): void {
// Remove old buttons
this._bar.destroy_all_children();
this._buttons = [];
items.forEach((item, index) => {
const button = new St.Button({
style_class: 'aerospike-tab',
reactive: true,
can_focus: false,
track_hover: true,
x_expand: true,
child: new St.Label({
text: item.getTabLabel(),
style_class: 'aerospike-tab-label',
y_align: Clutter.ActorAlign.CENTER,
x_align: Clutter.ActorAlign.CENTER,
x_expand: true,
}),
});
button.connect('clicked', () => {
this._onTabClicked(index);
});
this._bar.add_child(button);
this._buttons.push(button);
});
this.setActive(activeIndex);
}
/**
* Update just the title text of a single tab (e.g. when a window title changes).
*/
updateTabTitle(index: number, title: string): void {
if (index < 0 || index >= this._buttons.length) return;
const label = this._buttons[index].get_child() as St.Label;
if (label) label.set_text(title);
}
/**
* Highlight the active tab and dim the rest.
*/
setActive(index: number): void {
this._activeIndex = index;
this._buttons.forEach((btn, i) => {
if (i === index) {
btn.add_style_class_name('aerospike-tab-active');
} else {
btn.remove_style_class_name('aerospike-tab-active');
}
});
}
/**
* Position and size the tab bar at the given screen rect.
*/
setPosition(rect: Rect): void {
this._bar.set_position(rect.x, rect.y);
this._bar.set_size(rect.width, rect.height);
}
show(): void {
if (this._visible) return;
this._visible = true;
Main.layoutManager.uiGroup.add_child(this._bar);
this._bar.show();
Logger.log("TabBar shown");
}
hide(): void {
if (!this._visible) return;
this._visible = false;
this._bar.hide();
if (this._bar.get_parent()) {
Main.layoutManager.uiGroup.remove_child(this._bar);
}
Logger.log("TabBar hidden");
}
destroy(): void {
this.hide();
this._bar.destroy_all_children();
this._buttons = [];
this._bar.destroy();
Logger.log("TabBar destroyed");
}
isVisible(): boolean {
return this._visible;
}
}

View File

@@ -47,6 +47,25 @@ export class WindowWrapper {
return this._window.get_frame_rect(); return this._window.get_frame_rect();
} }
getTabLabel(): string {
const appName = this._window.get_wm_class() ?? '';
const title = this._window.get_title() ?? 'Untitled';
if (appName && appName.toLowerCase() !== title.toLowerCase()) {
return `${appName} | ${title}`;
}
return title;
}
hideWindow(): void {
const actor = this._window.get_compositor_private() as Clutter.Actor | null;
if (actor) actor.hide();
}
showWindow(): void {
const actor = this._window.get_compositor_private() as Clutter.Actor | null;
if (actor) actor.show();
}
startDragging(): void { startDragging(): void {
this._dragging = true; this._dragging = true;
} }
@@ -119,6 +138,10 @@ export class WindowWrapper {
} }
actor.remove_all_transitions(); actor.remove_all_transitions();
// Move first to guarantee the window reaches the correct position even
// if the subsequent resize is clamped by minimum-size hints.
this._window.move_frame(true, rect.x, rect.y);
this._window.move_resize_frame(true, rect.x, rect.y, rect.width, rect.height); this._window.move_resize_frame(true, rect.x, rect.y, rect.width, rect.height);
const new_rect = this._window.get_frame_rect(); const new_rect = this._window.get_frame_rect();
@@ -129,6 +152,19 @@ export class WindowWrapper {
Math.abs(new_rect.height - rect.height) > WindowWrapper.RESIZE_TOLERANCE; Math.abs(new_rect.height - rect.height) > WindowWrapper.RESIZE_TOLERANCE;
if (_retry > 0 && mismatch) { if (_retry > 0 && mismatch) {
// If the window's actual size is larger than requested, it has a
// minimum-size constraint — retrying won't help. Just make sure
// it's at the correct position with its actual size.
const sizeConstrained =
new_rect.width > rect.width + WindowWrapper.RESIZE_TOLERANCE ||
new_rect.height > rect.height + WindowWrapper.RESIZE_TOLERANCE;
if (sizeConstrained) {
Logger.info("Window has min-size constraint, accepting actual size",
`want(${rect.x},${rect.y},${rect.width},${rect.height})`,
`actual(${new_rect.x},${new_rect.y},${new_rect.width},${new_rect.height})`);
this._window.move_frame(true, rect.x, rect.y);
} else {
Logger.warn("RESIZE MISMATCH, retrying", Logger.warn("RESIZE MISMATCH, retrying",
`want(${rect.x},${rect.y},${rect.width},${rect.height})`, `want(${rect.x},${rect.y},${rect.width},${rect.height})`,
`got(${new_rect.x},${new_rect.y},${new_rect.width},${new_rect.height})`); `got(${new_rect.x},${new_rect.y},${new_rect.width},${new_rect.height})`);
@@ -138,4 +174,5 @@ export class WindowWrapper {
}, 50); }, 50);
} }
} }
}
} }

View File

@@ -5,7 +5,7 @@ import {WindowWrapper} from './window.js';
import * as Main from "resource:///org/gnome/shell/ui/main.js"; import * as Main from "resource:///org/gnome/shell/ui/main.js";
import {Logger} from "../utils/logger.js"; import {Logger} from "../utils/logger.js";
import Monitor from "./monitor.js"; import Monitor from "./monitor.js";
import WindowContainer from "./container.js"; import WindowContainer, {Layout} from "./container.js";
import {Rect} from "../utils/rect.js"; import {Rect} from "../utils/rect.js";
@@ -44,7 +44,7 @@ export default class WindowManager implements IWindowManager {
_changingGrabbedMonitor: boolean = false; _changingGrabbedMonitor: boolean = false;
_showingOverview: boolean = false; _showingOverview: boolean = false;
// ── Resize-drag tracking ────────────────────────────────────────────────── // -- Resize-drag tracking --------------------------------------------------
_isResizeDrag: boolean = false; _isResizeDrag: boolean = false;
_resizeDragWindowId: number = _UNUSED_WINDOW_ID; _resizeDragWindowId: number = _UNUSED_WINDOW_ID;
_resizeDragOp: Meta.GrabOp = Meta.GrabOp.NONE; _resizeDragOp: Meta.GrabOp = Meta.GrabOp.NONE;
@@ -132,10 +132,16 @@ export default class WindowManager implements IWindowManager {
Logger.log("HIDING OVERVIEW") Logger.log("HIDING OVERVIEW")
this._showingOverview = false; this._showingOverview = false;
this._tileMonitors(); this._tileMonitors();
for (const monitor of this._monitors.values()) {
monitor.showTabBars();
}
}), }),
Main.overview.connect("showing", () => { Main.overview.connect("showing", () => {
this._showingOverview = true; this._showingOverview = true;
Logger.log("SHOWING OVERVIEW"); Logger.log("SHOWING OVERVIEW");
for (const monitor of this._monitors.values()) {
monitor.hideTabBars();
}
}), }),
]; ];
} }
@@ -329,7 +335,7 @@ export default class WindowManager implements IWindowManager {
const itemIndex = container._getIndexOfWindow(winId); const itemIndex = container._getIndexOfWindow(winId);
if (itemIndex === -1) return; if (itemIndex === -1) return;
const isHorizontal = container._orientation === 0; const isHorizontal = container._orientation === Layout.ACC_HORIZONTAL;
// E/S edge → boundary after the item; W/N edge → boundary before it. // E/S edge → boundary after the item; W/N edge → boundary before it.
let adjusted = false; let adjusted = false;
@@ -350,7 +356,7 @@ export default class WindowManager implements IWindowManager {
if (adjusted) { if (adjusted) {
this._isTiling = true; this._isTiling = true;
try { try {
container.tileWindows(); container.drawWindows();
} finally { } finally {
this._isTiling = false; this._isTiling = false;
} }
@@ -434,6 +440,8 @@ export default class WindowManager implements IWindowManager {
for (const monitor of this._monitors.values()) { for (const monitor of this._monitors.values()) {
monitor.tileWindows(); monitor.tileWindows();
} }
} catch (e) {
Logger.error("_tileMonitors FAILED", e);
} finally { } finally {
this._isTiling = false; this._isTiling = false;
} }
@@ -512,6 +520,29 @@ export default class WindowManager implements IWindowManager {
} }
} }
public toggleActiveContainerTabbed(): void {
if (this._activeWindowId === null) {
Logger.warn("No active window, cannot toggle tabbed mode");
return;
}
const container = this._findContainerForWindowAcrossMonitors(this._activeWindowId);
if (container) {
if (container.isTabbed()) {
container.setAccordion(Layout.ACC_HORIZONTAL);
} else {
// Set the active tab to the focused window
const activeIndex = container._getIndexOfWindow(this._activeWindowId);
if (activeIndex !== -1) {
container._activeTabIndex = activeIndex;
}
container.setTabbed();
}
this._tileMonitors();
} else {
Logger.warn("Could not find container for active window");
}
}
public printTreeStructure(): void { public printTreeStructure(): void {
Logger.info("=".repeat(80)); Logger.info("=".repeat(80));
Logger.info("WINDOW TREE STRUCTURE"); Logger.info("WINDOW TREE STRUCTURE");
@@ -531,8 +562,11 @@ export default class WindowManager implements IWindowManager {
monitor._workspaces.forEach((workspace, workspaceIndex) => { monitor._workspaces.forEach((workspace, workspaceIndex) => {
const isActiveWorkspace = workspaceIndex === activeWorkspaceIndex; const isActiveWorkspace = workspaceIndex === activeWorkspaceIndex;
Logger.info(` Workspace ${workspaceIndex}${isActiveWorkspace && isActiveMonitor ? ' *' : ''}:`); Logger.info(` Workspace ${workspaceIndex}${isActiveWorkspace && isActiveMonitor ? ' *' : ''}:`);
Logger.info(` Orientation: ${workspace._orientation === 0 ? 'HORIZONTAL' : 'VERTICAL'}`); Logger.info(` Orientation: ${Layout[workspace._orientation]}`);
Logger.info(` Items: ${workspace._tiledItems.length}`); Logger.info(` Items: ${workspace._tiledItems.length}`);
if (workspace.isTabbed()) {
Logger.info(` Active Tab: ${workspace._activeTabIndex}`);
}
this._printContainerTree(workspace, 4); this._printContainerTree(workspace, 4);
}); });
}); });
@@ -547,7 +581,7 @@ export default class WindowManager implements IWindowManager {
if (item instanceof WindowContainer) { if (item instanceof WindowContainer) {
const containsActive = this._activeWindowId !== null && const containsActive = this._activeWindowId !== null &&
item.getWindow(this._activeWindowId) !== undefined; item.getWindow(this._activeWindowId) !== undefined;
Logger.info(`${indent}[${index}] Container (${item._orientation === 0 ? 'HORIZONTAL' : 'VERTICAL'})${containsActive ? ' *' : ''}:`); Logger.info(`${indent}[${index}] Container (${Layout[item._orientation]})${containsActive ? ' *' : ''}:`);
Logger.info(`${indent} Items: ${item._tiledItems.length}`); Logger.info(`${indent} Items: ${item._tiledItems.length}`);
Logger.info(`${indent} Work Area: x=${item._workArea.x}, y=${item._workArea.y}, w=${item._workArea.width}, h=${item._workArea.height}`); Logger.info(`${indent} Work Area: x=${item._workArea.x}, y=${item._workArea.y}, w=${item._workArea.width}, h=${item._workArea.height}`);
this._printContainerTree(item, indentLevel + 4); this._printContainerTree(item, indentLevel + 4);

View File

@@ -1,37 +1,31 @@
/* Add your custom extension styling here */ .aerospike-tab-bar {
.active-window-border { background-color: rgba(30, 30, 30, 0.95);
/*border: 2px solid rgba(191, 0, 255, 0.8);*/ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
/*border-radius: 3px;*/ spacing: 1px;
padding: 2px 2px 0 2px;
/* border-image-source: linear-gradient(to left, #743ad5, #d53a9d);*/
/* !*border: 4px solid transparent;*!*/
/* !*border-radius: 5px;*!*/
/* !*!* Gradient border using border-image *!*/
/* border-image: linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) 1;*/
} }
/*.border-gradient-purple {*/ .aerospike-tab {
/* border-image-source: linear-gradient(to left, #743ad5, #d53a9d);*/ background-color: rgba(50, 50, 50, 0.8);
/*}*/ border-radius: 6px 6px 0 0;
padding: 2px 12px;
margin: 0 1px;
color: rgba(255, 255, 255, 0.5);
font-size: 11px;
font-weight: 400;
}
/*@keyframes rainbow-border {*/ .aerospike-tab:hover {
/* 0% {*/ background-color: rgba(70, 70, 70, 0.9);
/* border-image: linear-gradient(0deg, red, orange, yellow, green, blue, indigo, violet, red) 1;*/ color: rgba(255, 255, 255, 0.8);
/* }*/ }
/* 100% {*/
/* border-image: linear-gradient(360deg, red, orange, yellow, green, blue, indigo, violet, red) 1;*/
/* }*/
/*}*/
/*.active-window-border {*/ .aerospike-tab-active {
/* border: 4px solid transparent;*/ background-color: rgba(80, 80, 80, 1);
/* border-radius: 5px;*/ color: rgba(255, 255, 255, 1);
font-weight: 500;
}
/* !* Initial gradient border *!*/ .aerospike-tab-label {
/* border-image: linear-gradient(0deg, red, orange, yellow, green, blue, indigo, violet, red) 1;*/ font-size: 11px;
}
/* !* Apply animation *!*/
/* animation: rainbow-border 5s linear infinite;*/
/*}*/