Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6c8ef7f34 | |||
| 5b975187ba | |||
| fed6486932 | |||
| a7994f6742 | |||
| efd6bef0c4 | |||
| c9cbb56805 | |||
| d3fafa15ff | |||
| 8db89fc155 | |||
| 39e8a3541b | |||
| 47f5a734be | |||
| 1d5cdf480c | |||
| 5f09053e06 | |||
| 2295756f19 | |||
| 58b4e746b6 | |||
| e37cc5f8f4 | |||
| f10d3f0adf | |||
| 528bf11371 | |||
| 0598fa615d | |||
| 2e61ade64f | |||
| 37e8a149f1 | |||
| ef67c2d187 | |||
| 56ad942f24 | |||
| ba04453c65 | |||
| fd174e084e | |||
| 6ade2e1520 | |||
| b2099a6c1e | |||
| 034bdcb05b | |||
| 6d7acefed9 | |||
| ef098d8986 | |||
| 18d698cf7e | |||
| 0aa80fda6a | |||
| 7e51b5ff62 | |||
| fe41ea8312 |
@@ -1,40 +1,43 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
# Simple-Tiling – Makefile
|
# Simple-Tiling – Makefile
|
||||||
#
|
#
|
||||||
# make build → Erzeugt alle drei Versionen als Archivdatei
|
# make build → Erzeugt alle vier Versionen als Archivdatei
|
||||||
# make build-legacy → Erzeugt Legacy-ZIP (Shell 3.38)
|
# make build-legacy → Erzeugt Legacy-ZIP (Shell 3.38)
|
||||||
# make build-interim → Erzeugt Interim-ZIP (Shell 40-44)
|
# make build-enterprise → Erzeugt Enterprise-ZIP (Shell 40)
|
||||||
# make build-modern → Erzeugt Modern-ZIP (Shell 45+)
|
# make build-interim → Erzeugt Interim-ZIP (Shell 41-44)
|
||||||
# make install-legacy → Installiert Legacy Extension
|
# make build-modern → Erzeugt Modern-ZIP (Shell 45+)
|
||||||
# make install-interim → Installiert Interim Extension
|
# make install-legacy → Installiert Legacy Extension
|
||||||
# make install-modern → Installiert Modern Extension
|
# make install-enterprise → Installiert Enterprise Extension
|
||||||
# make clean → Bereinigt das Ausgangsverzeichnis
|
# make install-interim → Installiert Interim Extension
|
||||||
|
# make install-modern → Installiert Modern Extension
|
||||||
|
# make clean → Bereinigt das Ausgangsverzeichnis
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
UUID := simple-tiling@domoel
|
UUID := simple-tiling@domoel
|
||||||
VERSION := 7.2
|
VERSION := 7.6
|
||||||
EXTDIR := $(HOME)/.local/share/gnome-shell/extensions
|
EXTDIR := $(HOME)/.local/share/gnome-shell/extensions
|
||||||
|
|
||||||
COMMON_FILES := prefs.js schemas exceptions.txt locale *.css README.md LICENSE
|
COMMON_FILES := schemas exceptions.txt locale *.css README.md LICENSE
|
||||||
LEGACY_PREFS := prefs_legacy.js
|
LEGACY_PREFS := prefs_legacy.js
|
||||||
INTERIM_PREFS := prefs_interim.js
|
ENTERPRISE_PREFS := prefs_enterprise.js
|
||||||
MODERN_PREFS := prefs_modern.js
|
INTERIM_PREFS := prefs_interim.js
|
||||||
|
MODERN_PREFS := prefs_modern.js
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Helper: copies <file list> <dest>
|
# Helper: copies <file list> <dest>
|
||||||
###############################################################################
|
###############################################################################
|
||||||
define copies
|
define copies
|
||||||
@for f in $(1) ; do \
|
@for f in $(1) ; do \
|
||||||
if [ -e $$f ] ; then \
|
if [ -e $$f ] ; then \
|
||||||
cp -r $$f $(2)/ ; \
|
cp -r $$f $(2)/ ; \
|
||||||
fi ; \
|
fi ; \
|
||||||
done
|
done
|
||||||
endef
|
endef
|
||||||
|
|
||||||
.PHONY: build build-legacy build-interim build-modern \
|
.PHONY: build build-legacy build-enterprise build-interim build-modern \
|
||||||
install-legacy install-interim install-modern clean
|
install-legacy install-enterprise install-interim install-modern clean
|
||||||
|
|
||||||
build: build-legacy build-interim build-modern
|
build: build-legacy build-enterprise build-interim build-modern
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Erzeugt Legacy-ZIP (Shell 3.38)
|
# Erzeugt Legacy-ZIP (Shell 3.38)
|
||||||
@@ -47,25 +50,42 @@ build-legacy:
|
|||||||
@cp legacy.js build/$(UUID)/extension.js
|
@cp legacy.js build/$(UUID)/extension.js
|
||||||
@cp $(LEGACY_PREFS) build/$(UUID)/prefs.js
|
@cp $(LEGACY_PREFS) build/$(UUID)/prefs.js
|
||||||
@sed -e "s/__UUID__/$(UUID)/g" \
|
@sed -e "s/__UUID__/$(UUID)/g" \
|
||||||
-e "s/__VERSION__/$(VERSION)/g" \
|
-e "s/__VERSION__/$(VERSION)/g" \
|
||||||
metadata_legacy.json.in > build/$(UUID)/metadata.json
|
metadata_legacy.json.in > build/$(UUID)/metadata.json
|
||||||
@cd build && zip -qr ../$(UUID)-legacy-v$(VERSION).zip .
|
@cd build && zip -qr ../$(UUID)-legacy-v$(VERSION).zip .
|
||||||
@rm -rf build
|
@rm -rf build
|
||||||
@echo "✓ $(UUID)-legacy-v$(VERSION).zip created"
|
@echo "✓ $(UUID)-legacy-v$(VERSION).zip created"
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Erzeugt Interim-ZIP (Shell 40-44)
|
# Erzeugt Enterprise-ZIP (Shell 40)
|
||||||
|
###############################################################################
|
||||||
|
build-enterprise:
|
||||||
|
@echo "==> Building ENTERPRISE zip (for GNOME 40)..."
|
||||||
|
@rm -rf build && mkdir -p build/$(UUID)
|
||||||
|
$(call copies,$(COMMON_FILES),build/$(UUID))
|
||||||
|
@glib-compile-schemas build/$(UUID)/schemas
|
||||||
|
@cp enterprise.js build/$(UUID)/extension.js
|
||||||
|
@cp $(ENTERPRISE_PREFS) build/$(UUID)/prefs.js
|
||||||
|
@sed -e "s/__UUID__/$(UUID)/g" \
|
||||||
|
-e "s/__VERSION__/$(VERSION)/g" \
|
||||||
|
metadata_enterprise.json.in > build/$(UUID)/metadata.json
|
||||||
|
@cd build && zip -qr ../$(UUID)-enterprise-v$(VERSION).zip .
|
||||||
|
@rm -rf build
|
||||||
|
@echo "✓ $(UUID)-enterprise-v$(VERSION).zip created"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Erzeugt Interim-ZIP (Shell 41-44)
|
||||||
###############################################################################
|
###############################################################################
|
||||||
build-interim:
|
build-interim:
|
||||||
@echo "==> Building INTERIM zip (for GNOME 40-44)..."
|
@echo "==> Building INTERIM zip (for GNOME 41-44)..."
|
||||||
@rm -rf build && mkdir -p build/$(UUID)
|
@rm -rf build && mkdir -p build/$(UUID)
|
||||||
$(call copies,$(COMMON_FILES),build/$(UUID))
|
$(call copies,$(COMMON_FILES),build/$(UUID))
|
||||||
@glib-compile-schemas build/$(UUID)/schemas
|
@glib-compile-schemas build/$(UUID)/schemas
|
||||||
@cp interim.js build/$(UUID)/extension.js
|
@cp interim.js build/$(UUID)/extension.js
|
||||||
@cp $(INTERIM_PREFS) build/$(UUID)/prefs.js
|
@cp $(INTERIM_PREFS) build/$(UUID)/prefs.js
|
||||||
@sed -e "s/__UUID__/$(UUID)/g" \
|
@sed -e "s/__UUID__/$(UUID)/g" \
|
||||||
-e "s/__VERSION__/$(VERSION)/g" \
|
-e "s/__VERSION__/$(VERSION)/g" \
|
||||||
metadata_interim.json.in > build/$(UUID)/metadata.json
|
metadata_interim.json.in > build/$(UUID)/metadata.json
|
||||||
@cd build && zip -qr ../$(UUID)-interim-v$(VERSION).zip .
|
@cd build && zip -qr ../$(UUID)-interim-v$(VERSION).zip .
|
||||||
@rm -rf build
|
@rm -rf build
|
||||||
@echo "✓ $(UUID)-interim-v$(VERSION).zip created"
|
@echo "✓ $(UUID)-interim-v$(VERSION).zip created"
|
||||||
@@ -81,8 +101,8 @@ build-modern:
|
|||||||
@cp modern.js build/$(UUID)/extension.js
|
@cp modern.js build/$(UUID)/extension.js
|
||||||
@cp $(MODERN_PREFS) build/$(UUID)/prefs.js
|
@cp $(MODERN_PREFS) build/$(UUID)/prefs.js
|
||||||
@sed -e "s/__UUID__/$(UUID)/g" \
|
@sed -e "s/__UUID__/$(UUID)/g" \
|
||||||
-e "s/__VERSION__/$(VERSION)/g" \
|
-e "s/__VERSION__/$(VERSION)/g" \
|
||||||
metadata_modern.json.in > build/$(UUID)/metadata.json
|
metadata_modern.json.in > build/$(UUID)/metadata.json
|
||||||
@cd build && zip -qr ../$(UUID)-modern-v$(VERSION).zip .
|
@cd build && zip -qr ../$(UUID)-modern-v$(VERSION).zip .
|
||||||
@rm -rf build
|
@rm -rf build
|
||||||
@echo "✓ $(UUID)-modern-v$(VERSION).zip created"
|
@echo "✓ $(UUID)-modern-v$(VERSION).zip created"
|
||||||
@@ -93,21 +113,28 @@ build-modern:
|
|||||||
install-legacy: build-legacy
|
install-legacy: build-legacy
|
||||||
@echo "==> Installing LEGACY Extension..."
|
@echo "==> Installing LEGACY Extension..."
|
||||||
@rm -rf $(EXTDIR)/$(UUID)
|
@rm -rf $(EXTDIR)/$(UUID)
|
||||||
@unzip -q $(UUID)-legacy-v$(VERSION).zip -d $(EXTDIR)
|
@unzip -q $(UUID)-legacy-v$(VERSION).zip -d $(EXTDIR)/$(UUID)
|
||||||
@rm -f $(UUID)-legacy-v$(VERSION).zip
|
@rm -f $(UUID)-legacy-v$(VERSION).zip
|
||||||
@echo "✓ Legacy Extension installed to $(EXTDIR)/$(UUID). Restart GNOME Shell to apply."
|
@echo "✓ Legacy Extension installed to $(EXTDIR)/$(UUID). Restart GNOME Shell to apply."
|
||||||
|
|
||||||
|
install-enterprise: build-enterprise
|
||||||
|
@echo "==> Installing ENTERPRISE Extension..."
|
||||||
|
@rm -rf $(EXTDIR)/$(UUID)
|
||||||
|
@unzip -q $(UUID)-enterprise-v$(VERSION).zip -d $(EXTDIR)/$(UUID)
|
||||||
|
@rm -f $(UUID)-enterprise-v$(VERSION).zip
|
||||||
|
@echo "✓ Enterprise Extension installed to $(EXTDIR)/$(UUID). Restart GNOME Shell to apply."
|
||||||
|
|
||||||
install-interim: build-interim
|
install-interim: build-interim
|
||||||
@echo "==> Installing INTERIM Extension..."
|
@echo "==> Installing INTERIM Extension..."
|
||||||
@rm -rf $(EXTDIR)/$(UUID)
|
@rm -rf $(EXTDIR)/$(UUID)
|
||||||
@unzip -q $(UUID)-interim-v$(VERSION).zip -d $(EXTDIR)
|
@unzip -q $(UUID)-interim-v$(VERSION).zip -d $(EXTDIR)/$(UUID)
|
||||||
@rm -f $(UUID)-interim-v$(VERSION).zip
|
@rm -f $(UUID)-interim-v$(VERSION).zip
|
||||||
@echo "✓ Interim Extension installed to $(EXTDIR)/$(UUID). Restart GNOME Shell to apply."
|
@echo "✓ Interim Extension installed to $(EXTDIR)/$(UUID). Restart GNOME Shell to apply."
|
||||||
|
|
||||||
install-modern: build-modern
|
install-modern: build-modern
|
||||||
@echo "==> Installing MODERN Extension..."
|
@echo "==> Installing MODERN Extension..."
|
||||||
@rm -rf $(EXTDIR)/$(UUID)
|
@rm -rf $(EXTDIR)/$(UUID)
|
||||||
@unzip -q $(UUID)-modern-v$(VERSION).zip -d $(EXTDIR)
|
@unzip -q $(UUID)-modern-v$(VERSION).zip -d $(EXTDIR)/$(UUID)
|
||||||
@rm -f $(UUID)-modern-v$(VERSION).zip
|
@rm -f $(UUID)-modern-v$(VERSION).zip
|
||||||
@echo "✓ Modern Extension installed to $(EXTDIR)/$(UUID). Restart GNOME Shell to apply."
|
@echo "✓ Modern Extension installed to $(EXTDIR)/$(UUID). Restart GNOME Shell to apply."
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://ztfr.eu/matrix">
|
||||||
|
<img src="assets/community-badge.png" alt="Join Zeitfresser Matrix Community" height="70" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
Simple Tiling
|
Simple Tiling
|
||||||
@@ -8,7 +13,16 @@ A lightweight, opinionated, and automatic tiling window manager for GNOME Shell
|
|||||||
</span>
|
</span>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
<h6 align="center">
|
||||||
|
<a href="https://ztfr.eu">🏰 Website</a>
|
||||||
|
·
|
||||||
|
<a href="https://ztfr.eu/matrix">📰 Zeitfresser Matrix Community</a>
|
||||||
|
·
|
||||||
|
<a href="https://social.ztfr.eu/@dome">🐘 Mastodon</a>
|
||||||
|
·
|
||||||
|
<a href="https://look.ztfr.eu/#/#support:ztfr.eu">💬 Supportchat</a>
|
||||||
|
</h6>
|
||||||
|
<br>
|
||||||
|
|
||||||
<img width="2560" height="1440" alt="Simple-Tiling-v6" src="https://github.com/user-attachments/assets/eb0f7cc3-6a5a-4036-8a1e-8f945c52e55c" />
|
<img width="2560" height="1440" alt="Simple-Tiling-v6" src="https://github.com/user-attachments/assets/eb0f7cc3-6a5a-4036-8a1e-8f945c52e55c" />
|
||||||
|
|
||||||
@@ -49,7 +63,7 @@ Use the [GNOME Shell Extensions website](https://extensions.gnome.org/extension/
|
|||||||
|
|
||||||
#### Manual Installation
|
#### Manual Installation
|
||||||
|
|
||||||
The repository includes a Makefile that produces ready‑to‑install ZIP packages for the three supported Gnome‑Shell lines (a legacy build for Gnome-Shell 3.38, an interim build for Gnome-Shell 40 - 44 and a modern build for Gnome-Shell 45+).
|
The repository includes a Makefile that produces ready‑to‑install ZIP packages for the three supported Gnome‑Shell lines (a legacy build for Gnome-Shell 3.38, an enterprise build for Gnome-Shell 40, an interim build for Gnome-Shell 41 - 44 and a modern build for Gnome-Shell 45+).
|
||||||
|
|
||||||
1. **Clone the Source**
|
1. **Clone the Source**
|
||||||
```bash
|
```bash
|
||||||
@@ -62,10 +76,11 @@ The repository includes a Makefile that produces ready‑to‑install ZIP packag
|
|||||||
Open the Terminal within the Simple-Tiling directory and run
|
Open the Terminal within the Simple-Tiling directory and run
|
||||||
```bash
|
```bash
|
||||||
make install-legacy # Installs Legacy Extension (Gnome-Shell 3.38)
|
make install-legacy # Installs Legacy Extension (Gnome-Shell 3.38)
|
||||||
make install-interim # Installs Interim Extension (Gnome-Shell 40 - 44)
|
make install-enterprise # Installs Enterprise Extension (Gnome-Shell 40)
|
||||||
|
make install-interim # Installs Interim Extension (Gnome-Shell 41 - 44)
|
||||||
make install-modern # Installs Modern Extension (Gnome-Shell 45+)
|
make install-modern # Installs Modern Extension (Gnome-Shell 45+)
|
||||||
```
|
```
|
||||||
**Note:** This command will directly install the extension in the choosen variant (legacy, interim or modern). If you want to manually create and upload the extension to your gnome extensions directory `(~/.local/share/gnome-shell/extensions)` you can just run `make build` to create all versions as .zip or `make build-legacy`, `make build-interim` or `make build-modern` to create them seperately as .zip. To enable them you need to unzip these archives and put them into your extensions directory.
|
**Note:** This command will directly install the extension in the choosen variant (legacy, interim or modern). If you want to manually create and upload the extension to your gnome extensions directory `(~/.local/share/gnome-shell/extensions)` you can just run `make build` to create all versions as .zip or `make build-legacy`, `make build-enterprise`, `make build-interim` or `make build-modern` to create them seperately as .zip. To enable them you need to unzip these archives and put them into your extensions directory.
|
||||||
|
|
||||||
4. **Reload the shell**
|
4. **Reload the shell**
|
||||||
```bash
|
```bash
|
||||||
@@ -119,6 +134,10 @@ This extension was built to solve a specific need. However, future enhancements
|
|||||||
* Additional layout algorithms.
|
* Additional layout algorithms.
|
||||||
* A more detailed settings panel to configure other options via a GUI.
|
* A more detailed settings panel to configure other options via a GUI.
|
||||||
|
|
||||||
|
## Development & Support
|
||||||
|
|
||||||
|
If you need to get support or want to participate in the active development of this software, you can <a href="https://ztfr.eu/matrix">join our Zeitfresser Matrix Community</a> or the <a href="https://look.ztfr.eu/#/#support:ztfr.eu">Development & Support Channel</a> on Matrix.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 277 KiB |
+604
@@ -0,0 +1,604 @@
|
|||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
// Simple-Tiling – ENTERPRISE (GNOME Shell 40 non-ESM) //
|
||||||
|
// © 2025 domoel – MIT //
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// ── GLOBAL IMPORTS ───────────────────────────
|
||||||
|
const { Meta, Shell, Gio, GLib, Clutter } = imports.gi;
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
|
||||||
|
// ── CONST ────────────────────────────────────────────
|
||||||
|
const WM_SCHEMA = 'org.gnome.desktop.wm.keybindings';
|
||||||
|
|
||||||
|
const TILING_DELAY_MS = 20; // Change Tiling Window Delay
|
||||||
|
const CENTERING_DELAY_MS = 5; // Change Centered Window Delay
|
||||||
|
|
||||||
|
const KEYBINDINGS = {
|
||||||
|
'swap-master-window': (self) => self._swapWithMaster(),
|
||||||
|
'swap-left-window': (self) => self._swapInDirection('left'),
|
||||||
|
'swap-right-window': (self) => self._swapInDirection('right'),
|
||||||
|
'swap-up-window': (self) => self._swapInDirection('up'),
|
||||||
|
'swap-down-window': (self) => self._swapInDirection('down'),
|
||||||
|
'focus-left': (self) => self._focusInDirection('left'),
|
||||||
|
'focus-right': (self) => self._focusInDirection('right'),
|
||||||
|
'focus-up': (self) => self._focusInDirection('up'),
|
||||||
|
'focus-down': (self) => self._focusInDirection('down'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── HELPER‑FUNCTION ────────────────────────────────────────
|
||||||
|
function getPointerXY() {
|
||||||
|
if (global.get_pointer) {
|
||||||
|
const [x, y] = global.get_pointer();
|
||||||
|
return [x, y];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ev = Clutter.get_current_event();
|
||||||
|
if (ev) {
|
||||||
|
const coords = ev.get_coords();
|
||||||
|
if (Array.isArray(coords))
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
const device = Clutter.get_default_backend()
|
||||||
|
.get_default_seat()
|
||||||
|
.get_pointer();
|
||||||
|
return device ? device.get_position() : [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── INTERACTIONHANDLER ───────────────────────────────────
|
||||||
|
class InteractionHandler {
|
||||||
|
constructor(tiler) {
|
||||||
|
this.tiler = tiler;
|
||||||
|
this._settings = this.tiler.settings;
|
||||||
|
this._wmSettings = new Gio.Settings({ schema: WM_SCHEMA });
|
||||||
|
|
||||||
|
this._wmKeysToDisable = [];
|
||||||
|
this._savedWmShortcuts = {};
|
||||||
|
this._grabOpIds = [];
|
||||||
|
this._settingsChangedId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this._prepareWmShortcuts();
|
||||||
|
|
||||||
|
if (this._wmKeysToDisable.length)
|
||||||
|
this._wmKeysToDisable.forEach(k =>
|
||||||
|
this._wmSettings.set_value(k, new GLib.Variant('as', [])));
|
||||||
|
|
||||||
|
this._bindAllShortcuts();
|
||||||
|
this._settingsChangedId =
|
||||||
|
this._settings.connect('changed', () => this._onSettingsChanged());
|
||||||
|
|
||||||
|
this._grabOpIds.push(
|
||||||
|
global.display.connect('grab-op-begin',
|
||||||
|
(_, __, win) => { if (this.tiler.windows.includes(win))
|
||||||
|
this.tiler.grabbedWindow = win; })
|
||||||
|
);
|
||||||
|
this._grabOpIds.push(
|
||||||
|
global.display.connect('grab-op-end', () => this._onGrabEnd())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
if (this._wmKeysToDisable.length)
|
||||||
|
this._wmKeysToDisable.forEach(k =>
|
||||||
|
this._wmSettings.set_value(k, this._savedWmShortcuts[k]));
|
||||||
|
|
||||||
|
this._unbindAllShortcuts();
|
||||||
|
|
||||||
|
if (this._settingsChangedId) {
|
||||||
|
this._settings.disconnect(this._settingsChangedId);
|
||||||
|
this._settingsChangedId = null;
|
||||||
|
}
|
||||||
|
this._grabOpIds.forEach(id => global.display.disconnect(id));
|
||||||
|
this._grabOpIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_bind(key, handler) {
|
||||||
|
Main.wm.addKeybinding(
|
||||||
|
key,
|
||||||
|
this._settings,
|
||||||
|
Meta.KeyBindingFlags.NONE,
|
||||||
|
Shell.ActionMode.NORMAL,
|
||||||
|
(..._args) => handler(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindAllShortcuts() { for (const [k,h] of Object.entries(KEYBINDINGS)) this._bind(k, h); }
|
||||||
|
_unbindAllShortcuts(){ for (const k in KEYBINDINGS) Main.wm.removeKeybinding(k); }
|
||||||
|
|
||||||
|
_onSettingsChanged() {
|
||||||
|
this._unbindAllShortcuts();
|
||||||
|
this._bindAllShortcuts();
|
||||||
|
}
|
||||||
|
|
||||||
|
_prepareWmShortcuts() {
|
||||||
|
const schema = this._wmSettings.settings_schema;
|
||||||
|
if (!schema) return;
|
||||||
|
|
||||||
|
const keys = [];
|
||||||
|
|
||||||
|
const add = key => { if (schema.has_key(key)) keys.push(key); };
|
||||||
|
|
||||||
|
if (schema.has_key('toggle-tiled-left'))
|
||||||
|
keys.push('toggle-tiled-left', 'toggle-tiled-right');
|
||||||
|
else {
|
||||||
|
add('tile-left'); add('tile-right');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.has_key('toggle-maximized'))
|
||||||
|
keys.push('toggle-maximized');
|
||||||
|
else {
|
||||||
|
add('maximize'); add('unmaximize');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.length) {
|
||||||
|
this._wmKeysToDisable = keys;
|
||||||
|
keys.forEach(k => this._savedWmShortcuts[k] =
|
||||||
|
this._wmSettings.get_value(k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_focusInDirection(direction) {
|
||||||
|
const src = global.display.get_focus_window();
|
||||||
|
if (!src || !this.tiler.windows.includes(src)) return;
|
||||||
|
const tgt = this._findTargetInDirection(src, direction);
|
||||||
|
if (tgt) tgt.activate(global.get_current_time());
|
||||||
|
}
|
||||||
|
|
||||||
|
_swapWithMaster() {
|
||||||
|
const w = this.tiler.windows;
|
||||||
|
if (w.length < 2) return;
|
||||||
|
const foc = global.display.get_focus_window();
|
||||||
|
if (!foc || !w.includes(foc)) return;
|
||||||
|
const idx = w.indexOf(foc);
|
||||||
|
if (idx > 0) [w[0], w[idx]] = [w[idx], w[0]];
|
||||||
|
else [w[0], w[1]] = [w[1], w[0]];
|
||||||
|
this.tiler.tileNow();
|
||||||
|
w[0]?.activate(global.get_current_time());
|
||||||
|
}
|
||||||
|
|
||||||
|
_swapInDirection(direction) {
|
||||||
|
const src = global.display.get_focus_window();
|
||||||
|
if (!src || !this.tiler.windows.includes(src)) return;
|
||||||
|
let tgt = null;
|
||||||
|
const idx = this.tiler.windows.indexOf(src);
|
||||||
|
if (idx === 0 && direction==='right' && this.tiler.windows.length>1)
|
||||||
|
tgt = this.tiler.windows[1];
|
||||||
|
else
|
||||||
|
tgt = this._findTargetInDirection(src, direction);
|
||||||
|
if (!tgt) return;
|
||||||
|
const tidx = this.tiler.windows.indexOf(tgt);
|
||||||
|
[this.tiler.windows[idx], this.tiler.windows[tidx]] =
|
||||||
|
[this.tiler.windows[tidx], this.tiler.windows[idx]];
|
||||||
|
this.tiler.tileNow();
|
||||||
|
src.activate(global.get_current_time());
|
||||||
|
}
|
||||||
|
|
||||||
|
_findTargetInDirection(src, dir) {
|
||||||
|
const sRect = src.get_frame_rect(), cand=[];
|
||||||
|
for (const win of this.tiler.windows) {
|
||||||
|
if (win===src) continue;
|
||||||
|
const r=win.get_frame_rect();
|
||||||
|
if (dir==='left' && r.x<sRect.x) cand.push(win);
|
||||||
|
if (dir==='right'&& r.x>sRect.x) cand.push(win);
|
||||||
|
if (dir==='up' && r.y<sRect.y) cand.push(win);
|
||||||
|
if (dir==='down' && r.y>sRect.y) cand.push(win);
|
||||||
|
}
|
||||||
|
if (!cand.length) return null;
|
||||||
|
let best=null, min=Infinity;
|
||||||
|
for (const w of cand) {
|
||||||
|
const r=w.get_frame_rect();
|
||||||
|
const dev = (dir==='left'||dir==='right')
|
||||||
|
? Math.abs(sRect.y - r.y)
|
||||||
|
: Math.abs(sRect.x - r.x);
|
||||||
|
if (dev<min){min=dev; best=w;}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onGrabEnd() {
|
||||||
|
const grabbed = this.tiler.grabbedWindow;
|
||||||
|
if (!grabbed) return;
|
||||||
|
const tgt = this._findTargetUnderPointer(grabbed);
|
||||||
|
if (tgt) {
|
||||||
|
const a = this.tiler.windows.indexOf(grabbed);
|
||||||
|
const b = this.tiler.windows.indexOf(tgt);
|
||||||
|
[this.tiler.windows[a], this.tiler.windows[b]] =
|
||||||
|
[this.tiler.windows[b], this.tiler.windows[a]];
|
||||||
|
}
|
||||||
|
this.tiler.queueTile();
|
||||||
|
this.tiler.grabbedWindow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_findTargetUnderPointer(exclude) {
|
||||||
|
const [x,y] = getPointerXY();
|
||||||
|
const wins = global.get_window_actors()
|
||||||
|
.map(a=>a.meta_window)
|
||||||
|
.filter(w=>w && w!==exclude &&
|
||||||
|
this.tiler.windows.includes(w) && (()=>{const f=w.get_frame_rect();
|
||||||
|
return x>=f.x && x<f.x+f.width &&
|
||||||
|
y>=f.y && y<f.y+f.height;})());
|
||||||
|
if (wins.length) return wins[wins.length-1];
|
||||||
|
|
||||||
|
let best=null, max=0, sRect=exclude.get_frame_rect();
|
||||||
|
for (const w of this.tiler.windows) {
|
||||||
|
if (w===exclude) continue;
|
||||||
|
const r=w.get_frame_rect();
|
||||||
|
const ovX=Math.max(0, Math.min(sRect.x+sRect.width, r.x+r.width)-Math.max(sRect.x,r.x));
|
||||||
|
const ovY=Math.max(0, Math.min(sRect.y+sRect.height,r.y+r.height)-Math.max(sRect.y,r.y));
|
||||||
|
const area=ovX*ovY;
|
||||||
|
if (area>max){max=area; best=w;}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── TILER ────────────────────────────────────────────────
|
||||||
|
class Tiler {
|
||||||
|
constructor(extension) {
|
||||||
|
this._extension = extension;
|
||||||
|
this.settings = this._extension.getSettings();
|
||||||
|
|
||||||
|
this.windows = [];
|
||||||
|
this.grabbedWindow = null;
|
||||||
|
this._signalIds = new Map();
|
||||||
|
this._tileInProgress = false;
|
||||||
|
|
||||||
|
this._innerGap = this.settings.get_int('inner-gap');
|
||||||
|
this._outerGapVertical= this.settings.get_int('outer-gap-vertical');
|
||||||
|
this._outerGapHorizontal = this.settings.get_int('outer-gap-horizontal');
|
||||||
|
|
||||||
|
this._tilingDelay = TILING_DELAY_MS;
|
||||||
|
this._centeringDelay = CENTERING_DELAY_MS;
|
||||||
|
|
||||||
|
this._exceptions = [];
|
||||||
|
this._interactionHandler = new InteractionHandler(this);
|
||||||
|
|
||||||
|
this._tileTimeoutId = null;
|
||||||
|
this._centerTimeoutIds= [];
|
||||||
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this._loadExceptions();
|
||||||
|
this._workspaceManager = global.workspace_manager;
|
||||||
|
|
||||||
|
this._signalIds.set('workspace-changed', {
|
||||||
|
object: this._workspaceManager,
|
||||||
|
id: this._workspaceManager.connect('active-workspace-changed',
|
||||||
|
()=>this._onActiveWorkspaceChanged())
|
||||||
|
});
|
||||||
|
|
||||||
|
this._connectToWorkspace();
|
||||||
|
this._interactionHandler.enable();
|
||||||
|
|
||||||
|
this._signalIds.set('settings-changed', {
|
||||||
|
object: this.settings,
|
||||||
|
id: this.settings.connect('changed', ()=>this._onSettingsChanged())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
if (this._tileTimeoutId) {
|
||||||
|
GLib.source_remove(this._tileTimeoutId);
|
||||||
|
this._tileTimeoutId = null;
|
||||||
|
}
|
||||||
|
this._centerTimeoutIds.forEach(id=>GLib.source_remove(id));
|
||||||
|
this._centerTimeoutIds = [];
|
||||||
|
|
||||||
|
this._interactionHandler.disable();
|
||||||
|
this._disconnectFromWorkspace();
|
||||||
|
|
||||||
|
for (const [,sig] of this._signalIds) {
|
||||||
|
try { sig.object.disconnect(sig.id); } catch {}
|
||||||
|
}
|
||||||
|
this._signalIds.clear();
|
||||||
|
this.windows = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_onSettingsChanged() {
|
||||||
|
this._innerGap = this.settings.get_int('inner-gap');
|
||||||
|
this._outerGapVertical = this.settings.get_int('outer-gap-vertical');
|
||||||
|
this._outerGapHorizontal= this.settings.get_int('outer-gap-horizontal');
|
||||||
|
this.queueTile();
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadExceptions() {
|
||||||
|
const file = Gio.File.new_for_path(this._extension.path + '/exceptions.txt');
|
||||||
|
if (!file.query_exists(null)) { this._exceptions=[]; return; }
|
||||||
|
|
||||||
|
const [ok,data] = file.load_contents(null);
|
||||||
|
if (!ok) { this._exceptions=[]; return; }
|
||||||
|
|
||||||
|
const txt = new TextDecoder('utf-8').decode(data);
|
||||||
|
this._exceptions = txt.split('\n')
|
||||||
|
.map(l=>l.trim())
|
||||||
|
.filter(l=>l && !l.startsWith('#'))
|
||||||
|
.map(l=>l.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
_isException(win) {
|
||||||
|
if (!win) return false;
|
||||||
|
const wmClass = (win.get_wm_class() || "").toLowerCase();
|
||||||
|
const appId = (win.get_gtk_application_id() || "").toLowerCase();
|
||||||
|
return this._exceptions.includes(wmClass) || this._exceptions.includes(appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTileable(win) {
|
||||||
|
return (
|
||||||
|
win &&
|
||||||
|
!win.minimized &&
|
||||||
|
!this._isException(win) &&
|
||||||
|
win.get_window_type() === Meta.WindowType.NORMAL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_centerWindow(win) {
|
||||||
|
const timeoutId = GLib.timeout_add(
|
||||||
|
GLib.PRIORITY_DEFAULT,
|
||||||
|
this._centeringDelay,
|
||||||
|
() => {
|
||||||
|
const index = this._centerTimeoutIds.indexOf(timeoutId);
|
||||||
|
if (index > -1) this._centerTimeoutIds.splice(index, 1);
|
||||||
|
|
||||||
|
if (!win || !win.get_display()) return GLib.SOURCE_REMOVE;
|
||||||
|
if (win.get_maximized())
|
||||||
|
win.unmaximize(Meta.MaximizeFlags.BOTH);
|
||||||
|
|
||||||
|
const monitorIndex = win.get_monitor();
|
||||||
|
const workspace = this._workspaceManager.get_active_workspace();
|
||||||
|
const workArea = workspace.get_work_area_for_monitor(
|
||||||
|
monitorIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
const frame = win.get_frame_rect();
|
||||||
|
win.move_frame(
|
||||||
|
true,
|
||||||
|
workArea.x + Math.floor((workArea.width - frame.width) / 2),
|
||||||
|
workArea.y +
|
||||||
|
Math.floor((workArea.height - frame.height) / 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
|
||||||
|
if (win.get_display()) {
|
||||||
|
if (typeof win.set_keep_above === "function")
|
||||||
|
win.set_keep_above(true);
|
||||||
|
else if (typeof win.make_above === "function")
|
||||||
|
win.make_above();
|
||||||
|
}
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
});
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this._centerTimeoutIds.push(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onWindowMinimizedStateChanged() {
|
||||||
|
this.queueTile();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onWindowAdded(workspace, win) {
|
||||||
|
if (this.windows.includes(win)) return;
|
||||||
|
if (this._isException(win)) {
|
||||||
|
this._centerWindow(win);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._isTileable(win)) {
|
||||||
|
if (this.settings.get_string("new-window-behavior") === "master") {
|
||||||
|
this.windows.unshift(win);
|
||||||
|
} else {
|
||||||
|
this.windows.push(win);
|
||||||
|
}
|
||||||
|
const id = win.get_id();
|
||||||
|
this._signalIds.set(`unmanaged-${id}`, {
|
||||||
|
object: win,
|
||||||
|
id: win.connect("unmanaged", () =>
|
||||||
|
this._onWindowRemoved(null, win)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
this._signalIds.set(`size-changed-${id}`, {
|
||||||
|
object: win,
|
||||||
|
id: win.connect("size-changed", () => {
|
||||||
|
if (!this.grabbedWindow) this.queueTile();
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
this._signalIds.set(`minimized-${id}`, {
|
||||||
|
object: win,
|
||||||
|
id: win.connect("notify::minimized", () =>
|
||||||
|
this._onWindowMinimizedStateChanged()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
this.queueTile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onWindowRemoved(workspace, win) {
|
||||||
|
const index = this.windows.indexOf(win);
|
||||||
|
if (index > -1) this.windows.splice(index, 1);
|
||||||
|
|
||||||
|
["unmanaged", "size-changed", "minimized"].forEach((prefix) => {
|
||||||
|
const key = `${prefix}-${win.get_id()}`;
|
||||||
|
if (this._signalIds.has(key)) {
|
||||||
|
const { object, id } = this._signalIds.get(key);
|
||||||
|
try {
|
||||||
|
object.disconnect(id);
|
||||||
|
} catch (e) {}
|
||||||
|
this._signalIds.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.queueTile();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onActiveWorkspaceChanged() {
|
||||||
|
this._disconnectFromWorkspace();
|
||||||
|
this._connectToWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
_connectToWorkspace() {
|
||||||
|
const workspace = this._workspaceManager.get_active_workspace();
|
||||||
|
workspace
|
||||||
|
.list_windows()
|
||||||
|
.forEach((win) => this._onWindowAdded(workspace, win));
|
||||||
|
this._signalIds.set("window-added", {
|
||||||
|
object: workspace,
|
||||||
|
id: workspace.connect("window-added", (ws, win) =>
|
||||||
|
this._onWindowAdded(ws, win)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
this._signalIds.set("window-removed", {
|
||||||
|
object: workspace,
|
||||||
|
id: workspace.connect("window-removed", (ws, win) =>
|
||||||
|
this._onWindowRemoved(ws, win)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
this.queueTile();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disconnectFromWorkspace() {
|
||||||
|
this.windows.slice().forEach((win) => this._onWindowRemoved(null, win));
|
||||||
|
["window-added", "window-removed"].forEach((key) => {
|
||||||
|
if (this._signalIds.has(key)) {
|
||||||
|
const { object, id } = this._signalIds.get(key);
|
||||||
|
try {
|
||||||
|
object.disconnect(id);
|
||||||
|
} catch (e) {}
|
||||||
|
this._signalIds.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
queueTile() {
|
||||||
|
if (this._tileInProgress || this._tileTimeoutId) return;
|
||||||
|
this._tileInProgress = true;
|
||||||
|
this._tileTimeoutId = GLib.timeout_add(
|
||||||
|
GLib.PRIORITY_DEFAULT,
|
||||||
|
this._tilingDelay,
|
||||||
|
() => {
|
||||||
|
this._tileWindows();
|
||||||
|
this._tileInProgress = false;
|
||||||
|
this._tileTimeoutId = null;
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
tileNow() {
|
||||||
|
if (!this._tileInProgress) {
|
||||||
|
this._tileWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_splitLayout(windows, area) {
|
||||||
|
if (windows.length === 0) return;
|
||||||
|
if (windows.length === 1) {
|
||||||
|
windows[0].move_resize_frame(
|
||||||
|
true,
|
||||||
|
area.x,
|
||||||
|
area.y,
|
||||||
|
area.width,
|
||||||
|
area.height
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const gap = Math.floor(this._innerGap / 2);
|
||||||
|
const primaryWindows = [windows[0]];
|
||||||
|
const secondaryWindows = windows.slice(1);
|
||||||
|
let primaryArea, secondaryArea;
|
||||||
|
if (area.width > area.height) {
|
||||||
|
const primaryWidth = Math.floor(area.width / 2) - gap;
|
||||||
|
primaryArea = {
|
||||||
|
x: area.x,
|
||||||
|
y: area.y,
|
||||||
|
width: primaryWidth,
|
||||||
|
height: area.height,
|
||||||
|
};
|
||||||
|
secondaryArea = {
|
||||||
|
x: area.x + primaryWidth + this._innerGap,
|
||||||
|
y: area.y,
|
||||||
|
width: area.width - primaryWidth - this._innerGap,
|
||||||
|
height: area.height,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const primaryHeight = Math.floor(area.height / 2) - gap;
|
||||||
|
primaryArea = {
|
||||||
|
x: area.x,
|
||||||
|
y: area.y,
|
||||||
|
width: area.width,
|
||||||
|
height: primaryHeight,
|
||||||
|
};
|
||||||
|
secondaryArea = {
|
||||||
|
x: area.x,
|
||||||
|
y: area.y + primaryHeight + this._innerGap,
|
||||||
|
width: area.width,
|
||||||
|
height: area.height - primaryHeight - this._innerGap,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this._splitLayout(primaryWindows, primaryArea);
|
||||||
|
this._splitLayout(secondaryWindows, secondaryArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
_tileWindows() {
|
||||||
|
const windowsToTile = this.windows.filter((win) => !win.minimized);
|
||||||
|
if (windowsToTile.length === 0) return;
|
||||||
|
|
||||||
|
const monitor = Main.layoutManager.primaryMonitor;
|
||||||
|
const workspace = this._workspaceManager.get_active_workspace();
|
||||||
|
const workArea = workspace.get_work_area_for_monitor(monitor.index);
|
||||||
|
|
||||||
|
const innerArea = {
|
||||||
|
x: workArea.x + this._outerGapHorizontal,
|
||||||
|
y: workArea.y + this._outerGapVertical,
|
||||||
|
width: workArea.width - 2 * this._outerGapHorizontal,
|
||||||
|
height: workArea.height - 2 * this._outerGapVertical,
|
||||||
|
};
|
||||||
|
windowsToTile.forEach((win) => {
|
||||||
|
if (win.get_maximized()) win.unmaximize(Meta.MaximizeFlags.BOTH);
|
||||||
|
});
|
||||||
|
if (windowsToTile.length === 1) {
|
||||||
|
windowsToTile[0].move_resize_frame(
|
||||||
|
true,
|
||||||
|
innerArea.x,
|
||||||
|
innerArea.y,
|
||||||
|
innerArea.width,
|
||||||
|
innerArea.height
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const gap = Math.floor(this._innerGap / 2);
|
||||||
|
const masterWidth = Math.floor(innerArea.width / 2) - gap;
|
||||||
|
const master = windowsToTile[0];
|
||||||
|
master.move_resize_frame(
|
||||||
|
true,
|
||||||
|
innerArea.x,
|
||||||
|
innerArea.y,
|
||||||
|
masterWidth,
|
||||||
|
innerArea.height
|
||||||
|
);
|
||||||
|
const stackArea = {
|
||||||
|
x: innerArea.x + masterWidth + this._innerGap,
|
||||||
|
y: innerArea.y,
|
||||||
|
width: innerArea.width - masterWidth - this._innerGap,
|
||||||
|
height: innerArea.height,
|
||||||
|
};
|
||||||
|
this._splitLayout(windowsToTile.slice(1), stackArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── EXTENSION‑WRAPPER ──────────────────────────
|
||||||
|
let tiler;
|
||||||
|
|
||||||
|
function enable() {
|
||||||
|
tiler = new Tiler(Me);
|
||||||
|
tiler.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
function disable() {
|
||||||
|
if (tiler) {
|
||||||
|
tiler.disable();
|
||||||
|
tiler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-9
@@ -1,16 +1,17 @@
|
|||||||
///////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////
|
||||||
// Simple‑Tiling – MODERN (GNOME Shell 40 - 44) //
|
// Simple‑Tiling – INTERIM (GNOME Shell 41 - 44) //
|
||||||
// © 2025 domoel – MIT //
|
// © 2025 domoel – MIT //
|
||||||
/////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
// ── GLOBAL IMPORTS ────────────────────────────────────────
|
// ── GLOBAL IMPORTS ────────────────────────────────────────
|
||||||
import { Extension } from "resource:///org/gnome/shell/extensions/js/extensions/extension.js";
|
import Meta from 'gi://Meta';
|
||||||
import * as Main from "resource:///org/gnome/shell/ui/main.js";
|
import Shell from 'gi://Shell';
|
||||||
import Meta from "gi://Meta";
|
import Gio from 'gi://Gio';
|
||||||
import Shell from "gi://Shell";
|
import GLib from 'gi://GLib';
|
||||||
import Gio from "gi://Gio";
|
import Clutter from 'gi://Clutter';
|
||||||
import GLib from "gi://GLib";
|
import { Extension } from 'resource:///org/gnome/shell/extensions/js/extensions/extension.js';
|
||||||
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||||
|
|
||||||
// ── CONST ────────────────────────────────────────────
|
// ── CONST ────────────────────────────────────────────
|
||||||
const WM_SCHEMA = 'org.gnome.desktop.wm.keybindings';
|
const WM_SCHEMA = 'org.gnome.desktop.wm.keybindings';
|
||||||
@@ -100,16 +101,17 @@ class InteractionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_bind(key, handler) {
|
_bind(key, handler) {
|
||||||
global.display.add_keybinding(
|
Main.wm.addKeybinding(
|
||||||
key,
|
key,
|
||||||
this._settings,
|
this._settings,
|
||||||
Meta.KeyBindingFlags.NONE,
|
Meta.KeyBindingFlags.NONE,
|
||||||
|
Shell.ActionMode.NORMAL,
|
||||||
(..._args) => handler(this)
|
(..._args) => handler(this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_bindAllShortcuts() { for (const [k,h] of Object.entries(KEYBINDINGS)) this._bind(k, h); }
|
_bindAllShortcuts() { for (const [k,h] of Object.entries(KEYBINDINGS)) this._bind(k, h); }
|
||||||
_unbindAllShortcuts(){ for (const k in KEYBINDINGS) global.display.remove_keybinding(k); }
|
_unbindAllShortcuts(){ for (const k in KEYBINDINGS) Main.wm.removeKeybinding(k); }
|
||||||
|
|
||||||
_onSettingsChanged() {
|
_onSettingsChanged() {
|
||||||
this._unbindAllShortcuts();
|
this._unbindAllShortcuts();
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"uuid": "__UUID__",
|
||||||
|
"name": "Simple Tiling",
|
||||||
|
"description": "A Simple Tiling Extension for Gnome Shell.",
|
||||||
|
"version": __VERSION__,
|
||||||
|
"shell-version": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"settings-schema": "org.gnome.shell.extensions.simple-tiling.domoel",
|
||||||
|
"preferences_ui": "prefs.js",
|
||||||
|
"url": "https://github.com/Domoel/Simple-Tiling",
|
||||||
|
"gettext-domain": "__UUID__"
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
"description": "A Simple Tiling Extension for Gnome Shell.",
|
"description": "A Simple Tiling Extension for Gnome Shell.",
|
||||||
"version": __VERSION__,
|
"version": __VERSION__,
|
||||||
"shell-version": [
|
"shell-version": [
|
||||||
"40",
|
|
||||||
"41",
|
"41",
|
||||||
"42",
|
"42",
|
||||||
"43",
|
"43",
|
||||||
|
|||||||
@@ -5,16 +5,16 @@
|
|||||||
|
|
||||||
|
|
||||||
// ── GLOBAL IMPORTS ────────────────────────────────────────
|
// ── GLOBAL IMPORTS ────────────────────────────────────────
|
||||||
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
|
|
||||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|
||||||
import Meta from 'gi://Meta';
|
import Meta from 'gi://Meta';
|
||||||
import Shell from 'gi://Shell';
|
import Shell from 'gi://Shell';
|
||||||
import Gio from 'gi://Gio';
|
import Gio from 'gi://Gio';
|
||||||
import GLib from 'gi://GLib';
|
import GLib from 'gi://GLib';
|
||||||
import Clutter from 'gi://Clutter';
|
import Clutter from 'gi://Clutter';
|
||||||
|
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||||
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||||
|
import * as Config from 'resource:///org/gnome/shell/misc/config.js';
|
||||||
|
|
||||||
// ── CONST ────────────────────────────────────────────
|
// ── CONST ────────────────────────────────────────────
|
||||||
const SHELL_MAJOR = parseInt(Shell.get_session().get_shell_version().split('.')[0]);
|
|
||||||
const WM_SCHEMA = 'org.gnome.desktop.wm.keybindings';
|
const WM_SCHEMA = 'org.gnome.desktop.wm.keybindings';
|
||||||
|
|
||||||
const TILING_DELAY_MS = 20; // Change Tiling Window Delay
|
const TILING_DELAY_MS = 20; // Change Tiling Window Delay
|
||||||
@@ -32,6 +32,17 @@ const KEYBINDINGS = {
|
|||||||
'focus-down': (self) => self._focusInDirection('down'),
|
'focus-down': (self) => self._focusInDirection('down'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── VERSION CHECK ────────────────────────────────────────────
|
||||||
|
let shellVersion;
|
||||||
|
if (Shell.get_session) {
|
||||||
|
shellVersion = Shell.get_session().get_shell_version();
|
||||||
|
} else if (Config.PACKAGE_VERSION) {
|
||||||
|
shellVersion = Config.PACKAGE_VERSION;
|
||||||
|
} else {
|
||||||
|
shellVersion = global.shell_version;
|
||||||
|
}
|
||||||
|
const SHELL_MAJOR = parseInt(shellVersion.split('.')[0]);
|
||||||
|
|
||||||
// ── HELPER‑FUNCTION ────────────────────────────────────────
|
// ── HELPER‑FUNCTION ────────────────────────────────────────
|
||||||
function getPointerXY() {
|
function getPointerXY() {
|
||||||
if (global.get_pointer) {
|
if (global.get_pointer) {
|
||||||
@@ -102,16 +113,17 @@ class InteractionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_bind(key, handler) {
|
_bind(key, handler) {
|
||||||
global.display.add_keybinding(
|
Main.wm.addKeybinding(
|
||||||
key,
|
key,
|
||||||
this._settings,
|
this._settings,
|
||||||
Meta.KeyBindingFlags.NONE,
|
Meta.KeyBindingFlags.NONE,
|
||||||
|
Shell.ActionMode.NORMAL,
|
||||||
(..._args) => handler(this)
|
(..._args) => handler(this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_bindAllShortcuts() { for (const [k,h] of Object.entries(KEYBINDINGS)) this._bind(k, h); }
|
_bindAllShortcuts() { for (const [k,h] of Object.entries(KEYBINDINGS)) this._bind(k, h); }
|
||||||
_unbindAllShortcuts(){ for (const k in KEYBINDINGS) global.display.remove_keybinding(k); }
|
_unbindAllShortcuts(){ for (const k in KEYBINDINGS) Main.wm.removeKeybinding(k); }
|
||||||
|
|
||||||
_onSettingsChanged() {
|
_onSettingsChanged() {
|
||||||
this._unbindAllShortcuts();
|
this._unbindAllShortcuts();
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// Simple-Tiling – ENTERPRISE MENU (GNOME Shell 40 non-ESM) //
|
||||||
|
// © 2025 domoel – MIT //
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ── GLOBAL IMPORTS ────────────────────────────────────────
|
||||||
|
const { Adw, Gio, Gtk, GLib } = imports.gi;
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const _ = ExtensionUtils.gettext;
|
||||||
|
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPrefsWidget() {
|
||||||
|
const settings = ExtensionUtils.getSettings();
|
||||||
|
const page = new Adw.PreferencesPage();
|
||||||
|
|
||||||
|
// ── WINDOW GAPS ────────────────────────────────────────────
|
||||||
|
const groupGaps = new Adw.PreferencesGroup({ title: 'Window Gaps' });
|
||||||
|
page.add(groupGaps);
|
||||||
|
|
||||||
|
const rowInnerGap = new Adw.SpinRow({
|
||||||
|
title: 'Inner Gap',
|
||||||
|
subtitle: 'The gap between windows in pixels.',
|
||||||
|
adjustment: new Gtk.Adjustment({ lower: 0, upper: 100, step_increment: 1 }),
|
||||||
|
});
|
||||||
|
groupGaps.add(rowInnerGap);
|
||||||
|
settings.bind('inner-gap', rowInnerGap, 'value', Gio.SettingsBindFlags.DEFAULT);
|
||||||
|
|
||||||
|
const rowOuterH = new Adw.SpinRow({
|
||||||
|
title: 'Outer Gap (horizontal)',
|
||||||
|
subtitle: 'Left / right screen edges (pixels)',
|
||||||
|
adjustment: new Gtk.Adjustment({ lower: 0, upper: 100, step_increment: 1 }),
|
||||||
|
});
|
||||||
|
groupGaps.add(rowOuterH);
|
||||||
|
settings.bind('outer-gap-horizontal', rowOuterH, 'value', Gio.SettingsBindFlags.DEFAULT);
|
||||||
|
|
||||||
|
const rowOuterV = new Adw.SpinRow({
|
||||||
|
title: 'Outer Gap (vertical)',
|
||||||
|
subtitle: 'Top / bottom screen edges (pixels)',
|
||||||
|
adjustment: new Gtk.Adjustment({ lower: 0, upper: 100, step_increment: 1 }),
|
||||||
|
});
|
||||||
|
groupGaps.add(rowOuterV);
|
||||||
|
settings.bind('outer-gap-vertical', rowOuterV, 'value', Gio.SettingsBindFlags.DEFAULT);
|
||||||
|
|
||||||
|
// ── WINDOW BEHAVIOR ────────────────────────────────────────────
|
||||||
|
const groupBehavior = new Adw.PreferencesGroup({ title: 'Window Behavior' });
|
||||||
|
page.add(groupBehavior);
|
||||||
|
|
||||||
|
const rowNewWindow = new Adw.ComboRow({
|
||||||
|
title: 'Open new windows as',
|
||||||
|
subtitle: 'Whether a new window starts as Master or Stack',
|
||||||
|
model: new Gtk.StringList({
|
||||||
|
strings: ['Stack Window (Default)', 'Master Window'],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
groupBehavior.add(rowNewWindow);
|
||||||
|
|
||||||
|
const currentBehavior = settings.get_string('new-window-behavior');
|
||||||
|
rowNewWindow.selected = currentBehavior === 'master' ? 1 : 0;
|
||||||
|
|
||||||
|
rowNewWindow.connect('notify::selected', () => {
|
||||||
|
const newVal = rowNewWindow.selected === 1 ? 'master' : 'stack';
|
||||||
|
settings.set_string('new-window-behavior', newVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── KEYBINDINGS ────────────────────────────────────────────
|
||||||
|
const groupKeys = new Adw.PreferencesGroup({ title: 'Keybindings' });
|
||||||
|
page.add(groupKeys);
|
||||||
|
|
||||||
|
const rowKeys = new Adw.ActionRow({
|
||||||
|
title: 'Configure Shortcuts',
|
||||||
|
subtitle: 'Adjust all shortcuts in GNOME Keyboard settings.',
|
||||||
|
});
|
||||||
|
groupKeys.add(rowKeys);
|
||||||
|
|
||||||
|
const btnOpenKeyboard = new Gtk.Button({ label: 'Open Keyboard Settings' });
|
||||||
|
btnOpenKeyboard.connect('clicked', () => {
|
||||||
|
const appInfo = Gio.AppInfo.create_from_commandline(
|
||||||
|
'gnome-control-center keyboard', null, Gio.AppInfoCreateFlags.NONE
|
||||||
|
);
|
||||||
|
appInfo.launch([], null);
|
||||||
|
});
|
||||||
|
rowKeys.add_suffix(btnOpenKeyboard);
|
||||||
|
rowKeys.set_activatable_widget(btnOpenKeyboard);
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
+3
-2
@@ -1,5 +1,5 @@
|
|||||||
///////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////
|
||||||
// Simple-Tiling – MODERN MENU (GNOME Shell 40-44) //
|
// Simple-Tiling – MODERN MENU (GNOME Shell 41-44) //
|
||||||
// © 2025 domoel – MIT //
|
// © 2025 domoel – MIT //
|
||||||
///////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@@ -9,7 +9,8 @@ import Adw from 'gi://Adw';
|
|||||||
import Gio from 'gi://Gio';
|
import Gio from 'gi://Gio';
|
||||||
import Gtk from 'gi://Gtk';
|
import Gtk from 'gi://Gtk';
|
||||||
import GLib from 'gi://GLib';
|
import GLib from 'gi://GLib';
|
||||||
import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/shell/extensions/js/extensions/prefs.js';
|
import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
|
||||||
|
|
||||||
|
|
||||||
export default class SimpleTilingPrefs extends ExtensionPreferences {
|
export default class SimpleTilingPrefs extends ExtensionPreferences {
|
||||||
fillPreferencesWindow(window) {
|
fillPreferencesWindow(window) {
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
///////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////
|
||||||
// Simple‑Tiling – LEGACY MENU (GNOME Shell 3.38 ‑ 44) //
|
// Simple‑Tiling – LEGACY MENU (GNOME Shell 3.38) //
|
||||||
// © 2025 domoel – MIT //
|
// © 2025 domoel – MIT //
|
||||||
/////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user