Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b144a90902 | |||
| 89982c883d | |||
| 5204d40168 | |||
| 7b45cffeff | |||
| 560ca61e41 | |||
| 6e35a92e9e | |||
| 38baf60286 | |||
| 65f5447ea2 | |||
| 290d067287 | |||
| 87ab2e8669 | |||
| 209421f479 | |||
| dd2bc5e589 | |||
| 9148ef63c6 | |||
| 56ee5c8933 | |||
| 7a313f7fc4 | |||
| 62d2792889 |
@@ -0,0 +1,6 @@
|
|||||||
|
# GNOME Shell Extension specific
|
||||||
|
schemas/gschemas.compiled
|
||||||
|
|
||||||
|
# Common temporary files
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
# Simple Tiling
|
|
||||||
|
<h1 align="center">
|
||||||
|
Simple Tiling
|
||||||
|
</span>
|
||||||
|
<h4 align="center">
|
||||||
|
<span style="display:inline-flex; align-items:center; gap:12px;">
|
||||||
|
A lightweight, opinionated, and automatic tiling window manager for GNOME Shell 3.38.
|
||||||
|
</span>
|
||||||
|
<p>
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||

|

|
||||||
|
|
||||||
A lightweight, opinionated, and automatic tiling window manager for GNOME Shell 3.38.
|
<img width="2560" height="1440" alt="Simple Tiling v4" src="https://github.com/user-attachments/assets/b080483e-40fe-4ea2-b0dd-56fcb587f9b8" />
|
||||||
|
|
||||||
<img width="2560" height="1440" alt="Simple-Tiling-v3" src="https://github.com/user-attachments/assets/42c64902-baab-4c84-9857-470f7f1ff035" />
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@@ -22,7 +28,8 @@ This extension was built from the ground up to be stable and performant on **GNO
|
|||||||
* **Interactive Window Swapping:**
|
* **Interactive Window Swapping:**
|
||||||
* **Drag & Drop:** Swap any two windows by simply dragging one and dropping it over the other.
|
* **Drag & Drop:** Swap any two windows by simply dragging one and dropping it over the other.
|
||||||
* **Keyboard Shortcuts:** A full set of keyboard shortcuts allows you to swap the focused window with the master or with its nearest neighbor in any direction (left, right, up, down).
|
* **Keyboard Shortcuts:** A full set of keyboard shortcuts allows you to swap the focused window with the master or with its nearest neighbor in any direction (left, right, up, down).
|
||||||
* **Simple Settings Panel:** A simple settings panel within the gnome extension manager menu to adjust key bindings and windows gaps / margins.
|
* **Interactive Window Focus Switcher:** Change the current window focus with a set of customizable keyboard shortcuts in every direction.
|
||||||
|
* **Simple Settings Panel:** A simple settings panel within the gnome extension manager menu to adjust key bindings and window gaps / margins.
|
||||||
* **External Exception List:** Use a simple `exceptions.txt` file to list applications (by their `WM_CLASS`) that should be ignored by the tiling manager.
|
* **External Exception List:** Use a simple `exceptions.txt` file to list applications (by their `WM_CLASS`) that should be ignored by the tiling manager.
|
||||||
* **Smart Pop-up Handling:** Windows on the exception list, as well as dialogs and other pop-ups, are automatically centered and kept "always on top" for a smooth workflow.
|
* **Smart Pop-up Handling:** Windows on the exception list, as well as dialogs and other pop-ups, are automatically centered and kept "always on top" for a smooth workflow.
|
||||||
* **Configurable Tiling Window Delays:** Easily configure the tiling window delays if you have race condition issues by editing variables directly in the `extension.js`.
|
* **Configurable Tiling Window Delays:** Easily configure the tiling window delays if you have race condition issues by editing variables directly in the `extension.js`.
|
||||||
@@ -77,29 +84,28 @@ To find an application's `WM_CLASS`, open a terminal and run the command `xprop
|
|||||||
|
|
||||||
An Example of an exceptions.txt can be found in the repo.
|
An Example of an exceptions.txt can be found in the repo.
|
||||||
|
|
||||||
|
Ignored applications will be opened screen centered and kept above all other windows. These applications can be moved across the screen in floating mode.
|
||||||
|
|
||||||
#### Adjusting inner and/or outer Window Gaps / Margins
|
#### Adjusting inner and/or outer Window Gaps / Margins
|
||||||
|
|
||||||
You can adjust the window gap margins (inner gaps between windows, outer gaps horizontal as well as vertical) in the Settings panel of Simple Tiling (which can be found in the Gnome Extension Application).
|
You can adjust the window gap margins (inner gaps between windows, outer gaps horizontal as well as vertical) in the Settings panel of Simple Tiling (which can be found in the Gnome Extension Application).
|
||||||
|
|
||||||
#### Adjusting Tiling Window Delays
|
|
||||||
|
|
||||||
If you have race condition issues between mutter (Gnome WM) and the Simple Tiling extension, you can adjust the window delay settings (both for tiling windows as well as for centered application from the exceptions list) directly in the extensions.js (~/.local/share/gnome-shell/extensions/simple-tiling@domoel/extension.js). You will find the parameter at line 222 (commented). Defaults to "20" for General Tiling Window Delay and "5" for centered Apps on the Exception List.
|
|
||||||
|
|
||||||
#### Configurable New Window Behavior
|
#### Configurable New Window Behavior
|
||||||
|
|
||||||
A toogle setting allows you to control the behavior for newly opened windows. You can choose to either have them become the new master window (pushing the old master into the stack) or have them appended to the stack as the last window (Default).
|
A toogle setting allows you to control the behavior for newly opened windows. You can choose to either have them become the new master window (pushing the old master into the stack) or have them appended to the stack as the last window (Default).
|
||||||
|
|
||||||
|
#### Adjusting Tiling Window Delays
|
||||||
|
|
||||||
|
If you have race condition issues between mutter (Gnome WM) and the Simple Tiling extension, you can adjust the window delay settings (both for tiling windows as well as for centered application from the exceptions list) directly in the extensions.js (~/.local/share/gnome-shell/extensions/simple-tiling@domoel/extension.js). You will find the parameter at line 17 & 18. Defaults to "20" for General Tiling Window Delay and "5" for centered Apps on the Exception List.
|
||||||
|
|
||||||
## Future Development
|
## Future Development
|
||||||
|
|
||||||
This extension was built to solve a specific need. However, future enhancements could include:
|
This extension was built to solve a specific need. However, future enhancements could include:
|
||||||
* Multi-monitor support.
|
* Multi-monitor support.
|
||||||
|
* Support for newer Gnome shells
|
||||||
* 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.
|
||||||
|
|
||||||
## Misc
|
|
||||||
|
|
||||||
I recommend to add also [Focus Changer](https://github.com/martinhjartmyr/gnome-shell-extension-focus-changer) as an extension to make navigating between windows super easy. With Foocus Changer you can set keybindings (e.g. "Alt" + "Arrow-Keys") to change the selected / active window on a given workspace. Once selected you can now (with Simple Tiling) swap the selected window with any other window on your workspace (e.g. "Super" + "Arrow-Key"). Its super handy and really simple.
|
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
+312
-103
@@ -1,5 +1,5 @@
|
|||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
// Simple-Tiling – GNOME Shell 3.38 (X11) - Version 3 //
|
// Simple-Tiling – GNOME Shell 3.38 (X11) - Version 4 //
|
||||||
// © 2025 domoel – MIT //
|
// © 2025 domoel – MIT //
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
|
|
||||||
@@ -14,10 +14,25 @@ const Gio = imports.gi.Gio;
|
|||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const ByteArray = imports.byteArray;
|
const ByteArray = imports.byteArray;
|
||||||
|
const TILING_DELAY_MS = 20; // Change Tiling Window Delay
|
||||||
|
const CENTERING_DELAY_MS = 5; // Change Centered Window Delay
|
||||||
|
|
||||||
const Me = ExtensionUtils.getCurrentExtension();
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
const SCHEMA_NAME = 'org.gnome.shell.extensions.simple-tiling.domoel';
|
const SCHEMA_NAME = "org.gnome.shell.extensions.simple-tiling.domoel";
|
||||||
const WM_SCHEMA = 'org.gnome.desktop.wm.keybindings';
|
const WM_SCHEMA = "org.gnome.desktop.wm.keybindings";
|
||||||
|
|
||||||
|
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"),
|
||||||
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
// InteractionHandler //
|
// InteractionHandler //
|
||||||
@@ -37,63 +52,97 @@ class InteractionHandler {
|
|||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
if (this._wmKeysToDisable.length) {
|
if (this._wmKeysToDisable.length) {
|
||||||
this._wmKeysToDisable.forEach(key => this._wmSettings.set_value(key, new GLib.Variant('as', [])));
|
this._wmKeysToDisable.forEach((key) =>
|
||||||
|
this._wmSettings.set_value(key, new GLib.Variant("as", []))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this._bindAllShortcuts();
|
this._bindAllShortcuts();
|
||||||
this._settingsChangedId = this._settings.connect('changed', this._onSettingsChanged);
|
this._settingsChangedId = this._settings.connect(
|
||||||
this._grabOpIds.push(global.display.connect('grab-op-begin', (display, screen, window) => {
|
"changed",
|
||||||
if (this.tiler.windows.includes(window)) { this.tiler.grabbedWindow = window; }
|
this._onSettingsChanged
|
||||||
}));
|
);
|
||||||
this._grabOpIds.push(global.display.connect('grab-op-end', this._onGrabEnd.bind(this)));
|
this._grabOpIds.push(
|
||||||
|
global.display.connect(
|
||||||
|
"grab-op-begin",
|
||||||
|
(display, screen, window) => {
|
||||||
|
if (this.tiler.windows.includes(window)) {
|
||||||
|
this.tiler.grabbedWindow = window;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this._grabOpIds.push(
|
||||||
|
global.display.connect("grab-op-end", this._onGrabEnd.bind(this))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
if (this._wmKeysToDisable.length) {
|
if (this._wmKeysToDisable.length) {
|
||||||
this._wmKeysToDisable.forEach(key => this._wmSettings.set_value(key, this._savedWmShortcuts[key]));
|
this._wmKeysToDisable.forEach((key) =>
|
||||||
|
this._wmSettings.set_value(key, this._savedWmShortcuts[key])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this._unbindAllShortcuts();
|
this._unbindAllShortcuts();
|
||||||
if (this._settingsChangedId) {
|
if (this._settingsChangedId) {
|
||||||
this._settings.disconnect(this._settingsChangedId);
|
this._settings.disconnect(this._settingsChangedId);
|
||||||
this._settingsChangedId = null;
|
this._settingsChangedId = null;
|
||||||
}
|
}
|
||||||
this._grabOpIds.forEach(id => global.display.disconnect(id));
|
this._grabOpIds.forEach((id) => global.display.disconnect(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
_bind(key, callback) {
|
_bind(key, callback) {
|
||||||
Main.wm.addKeybinding(key, this._settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.NORMAL, callback.bind(this));
|
Main.wm.addKeybinding(key, this._settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.NORMAL,
|
||||||
}
|
() => callback(this));
|
||||||
|
}
|
||||||
|
|
||||||
_bindAllShortcuts() {
|
_bindAllShortcuts() {
|
||||||
this._bind('swap-master-window', this._swapWithMaster);
|
for (const [key, handler] of Object.entries(KEYBINDINGS)) {
|
||||||
this._bind('swap-left-window', () => this._swapInDirection('left'));
|
this._bind(key, handler);
|
||||||
this._bind('swap-right-window', () => this._swapInDirection('right'));
|
}
|
||||||
this._bind('swap-up-window', () => this._swapInDirection('up'));
|
}
|
||||||
this._bind('swap-down-window', () => this._swapInDirection('down'));
|
_unbindAllShortcuts() {
|
||||||
}
|
for (const key in KEYBINDINGS) {
|
||||||
|
Main.wm.removeKeybinding(key);
|
||||||
_unbindAllShortcuts() {
|
}
|
||||||
['swap-master-window', 'swap-left-window', 'swap-right-window', 'swap-up-window', 'swap-down-window']
|
}
|
||||||
.forEach(key => Main.wm.removeKeybinding(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSettingsChanged() {
|
_onSettingsChanged() {
|
||||||
this._unbindAllShortcuts();
|
this._unbindAllShortcuts();
|
||||||
this._bindAllShortcuts();
|
this._bindAllShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
_prepareWmShortcuts() {
|
_prepareWmShortcuts() {
|
||||||
const schema = this._wmSettings.settings_schema;
|
const schema = this._wmSettings.settings_schema;
|
||||||
const keys = [];
|
const keys = [];
|
||||||
if (schema.has_key('toggle-tiled-left')) keys.push('toggle-tiled-left', 'toggle-tiled-right');
|
if (schema.has_key("toggle-tiled-left"))
|
||||||
else if (schema.has_key('tile-left')) keys.push('tile-left', 'tile-right');
|
keys.push("toggle-tiled-left", "toggle-tiled-right");
|
||||||
if (schema.has_key('toggle-maximized')) keys.push('toggle-maximized');
|
else if (schema.has_key("tile-left"))
|
||||||
|
keys.push("tile-left", "tile-right");
|
||||||
|
if (schema.has_key("toggle-maximized")) keys.push("toggle-maximized");
|
||||||
else {
|
else {
|
||||||
if (schema.has_key('maximize')) keys.push('maximize');
|
if (schema.has_key("maximize")) keys.push("maximize");
|
||||||
if (schema.has_key('unmaximize')) keys.push('unmaximize');
|
if (schema.has_key("unmaximize")) keys.push("unmaximize");
|
||||||
}
|
}
|
||||||
if (keys.length) {
|
if (keys.length) {
|
||||||
this._wmKeysToDisable = keys;
|
this._wmKeysToDisable = keys;
|
||||||
keys.forEach(key => this._savedWmShortcuts[key] = this.tiler.wmSettings.get_value(key));
|
keys.forEach(
|
||||||
|
(key) =>
|
||||||
|
(this._savedWmShortcuts[
|
||||||
|
key
|
||||||
|
] = this._wmSettings.get_value(key))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_focusInDirection(direction) {
|
||||||
|
const sourceWindow = global.display.get_focus_window();
|
||||||
|
if (!sourceWindow || !this.tiler.windows.includes(sourceWindow)) return;
|
||||||
|
|
||||||
|
const targetWindow = this._findTargetInDirection(
|
||||||
|
sourceWindow,
|
||||||
|
direction
|
||||||
|
);
|
||||||
|
if (targetWindow) {
|
||||||
|
targetWindow.activate(global.get_current_time());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +153,10 @@ class InteractionHandler {
|
|||||||
if (!focusedWindow || !windows.includes(focusedWindow)) return;
|
if (!focusedWindow || !windows.includes(focusedWindow)) return;
|
||||||
const focusedIndex = windows.indexOf(focusedWindow);
|
const focusedIndex = windows.indexOf(focusedWindow);
|
||||||
if (focusedIndex > 0) {
|
if (focusedIndex > 0) {
|
||||||
[windows[0], windows[focusedIndex]] = [windows[focusedIndex], windows[0]];
|
[windows[0], windows[focusedIndex]] = [
|
||||||
|
windows[focusedIndex],
|
||||||
|
windows[0],
|
||||||
|
];
|
||||||
} else if (focusedIndex === 0) {
|
} else if (focusedIndex === 0) {
|
||||||
[windows[0], windows[1]] = [windows[1], windows[0]];
|
[windows[0], windows[1]] = [windows[1], windows[0]];
|
||||||
}
|
}
|
||||||
@@ -117,14 +169,21 @@ class InteractionHandler {
|
|||||||
if (!sourceWindow || !this.tiler.windows.includes(sourceWindow)) return;
|
if (!sourceWindow || !this.tiler.windows.includes(sourceWindow)) return;
|
||||||
let targetWindow = null;
|
let targetWindow = null;
|
||||||
const sourceIndex = this.tiler.windows.indexOf(sourceWindow);
|
const sourceIndex = this.tiler.windows.indexOf(sourceWindow);
|
||||||
if (sourceIndex === 0 && direction === 'right' && this.tiler.windows.length > 1) {
|
if (
|
||||||
|
sourceIndex === 0 &&
|
||||||
|
direction === "right" &&
|
||||||
|
this.tiler.windows.length > 1
|
||||||
|
) {
|
||||||
targetWindow = this.tiler.windows[1];
|
targetWindow = this.tiler.windows[1];
|
||||||
} else {
|
} else {
|
||||||
targetWindow = this._findTargetInDirection(sourceWindow, direction);
|
targetWindow = this._findTargetInDirection(sourceWindow, direction);
|
||||||
}
|
}
|
||||||
if (!targetWindow) return;
|
if (!targetWindow) return;
|
||||||
const targetIndex = this.tiler.windows.indexOf(targetWindow);
|
const targetIndex = this.tiler.windows.indexOf(targetWindow);
|
||||||
[this.tiler.windows[sourceIndex], this.tiler.windows[targetIndex]] = [this.tiler.windows[targetIndex], this.tiler.windows[sourceIndex]];
|
[this.tiler.windows[sourceIndex], this.tiler.windows[targetIndex]] = [
|
||||||
|
this.tiler.windows[targetIndex],
|
||||||
|
this.tiler.windows[sourceIndex],
|
||||||
|
];
|
||||||
this.tiler.tileNow();
|
this.tiler.tileNow();
|
||||||
sourceWindow.activate(global.get_current_time());
|
sourceWindow.activate(global.get_current_time());
|
||||||
}
|
}
|
||||||
@@ -136,10 +195,18 @@ class InteractionHandler {
|
|||||||
if (win === source) continue;
|
if (win === source) continue;
|
||||||
const targetRect = win.get_frame_rect();
|
const targetRect = win.get_frame_rect();
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case 'left': if (targetRect.x < sourceRect.x) candidates.push(win); break;
|
case "left":
|
||||||
case 'right': if (targetRect.x > sourceRect.x) candidates.push(win); break;
|
if (targetRect.x < sourceRect.x) candidates.push(win);
|
||||||
case 'up': if (targetRect.y < sourceRect.y) candidates.push(win); break;
|
break;
|
||||||
case 'down': if (targetRect.y > sourceRect.y) candidates.push(win); break;
|
case "right":
|
||||||
|
if (targetRect.x > sourceRect.x) candidates.push(win);
|
||||||
|
break;
|
||||||
|
case "up":
|
||||||
|
if (targetRect.y < sourceRect.y) candidates.push(win);
|
||||||
|
break;
|
||||||
|
case "down":
|
||||||
|
if (targetRect.y > sourceRect.y) candidates.push(win);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (candidates.length === 0) return null;
|
if (candidates.length === 0) return null;
|
||||||
@@ -148,7 +215,7 @@ class InteractionHandler {
|
|||||||
for (const win of candidates) {
|
for (const win of candidates) {
|
||||||
const targetRect = win.get_frame_rect();
|
const targetRect = win.get_frame_rect();
|
||||||
let deviation;
|
let deviation;
|
||||||
if (direction === 'left' || direction === 'right') {
|
if (direction === "left" || direction === "right") {
|
||||||
deviation = Math.abs(sourceRect.y - targetRect.y);
|
deviation = Math.abs(sourceRect.y - targetRect.y);
|
||||||
} else {
|
} else {
|
||||||
deviation = Math.abs(sourceRect.x - targetRect.x);
|
deviation = Math.abs(sourceRect.x - targetRect.x);
|
||||||
@@ -168,7 +235,13 @@ class InteractionHandler {
|
|||||||
if (targetWindow) {
|
if (targetWindow) {
|
||||||
const sourceIndex = this.tiler.windows.indexOf(grabbedWindow);
|
const sourceIndex = this.tiler.windows.indexOf(grabbedWindow);
|
||||||
const targetIndex = this.tiler.windows.indexOf(targetWindow);
|
const targetIndex = this.tiler.windows.indexOf(targetWindow);
|
||||||
[this.tiler.windows[sourceIndex], this.tiler.windows[targetIndex]] = [this.tiler.windows[targetIndex], this.tiler.windows[sourceIndex]];
|
[
|
||||||
|
this.tiler.windows[sourceIndex],
|
||||||
|
this.tiler.windows[targetIndex],
|
||||||
|
] = [
|
||||||
|
this.tiler.windows[targetIndex],
|
||||||
|
this.tiler.windows[sourceIndex],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
this.tiler.queueTile();
|
this.tiler.queueTile();
|
||||||
this.tiler.grabbedWindow = null;
|
this.tiler.grabbedWindow = null;
|
||||||
@@ -176,13 +249,27 @@ class InteractionHandler {
|
|||||||
|
|
||||||
_findTargetUnderPointer(excludeWindow) {
|
_findTargetUnderPointer(excludeWindow) {
|
||||||
let [pointerX, pointerY] = global.get_pointer();
|
let [pointerX, pointerY] = global.get_pointer();
|
||||||
let windows = global.get_window_actors().map(actor => actor.meta_window).filter(win => {
|
let windows = global
|
||||||
if (!win || win === excludeWindow || !this.tiler.windows.includes(win)) return false;
|
.get_window_actors()
|
||||||
let frame = win.get_frame_rect();
|
.map((actor) => actor.meta_window)
|
||||||
return pointerX >= frame.x && pointerX < frame.x + frame.width &&
|
.filter((win) => {
|
||||||
pointerY >= frame.y && pointerY < frame.y + frame.height;
|
if (
|
||||||
});
|
!win ||
|
||||||
if (windows.length > 0) { return windows[windows.length - 1]; }
|
win === excludeWindow ||
|
||||||
|
!this.tiler.windows.includes(win)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
let frame = win.get_frame_rect();
|
||||||
|
return (
|
||||||
|
pointerX >= frame.x &&
|
||||||
|
pointerX < frame.x + frame.width &&
|
||||||
|
pointerY >= frame.y &&
|
||||||
|
pointerY < frame.y + frame.height
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (windows.length > 0) {
|
||||||
|
return windows[windows.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
let bestTarget = null;
|
let bestTarget = null;
|
||||||
let maxOverlap = 0;
|
let maxOverlap = 0;
|
||||||
@@ -190,8 +277,20 @@ class InteractionHandler {
|
|||||||
for (const win of this.tiler.windows) {
|
for (const win of this.tiler.windows) {
|
||||||
if (win === excludeWindow) continue;
|
if (win === excludeWindow) continue;
|
||||||
const targetFrame = win.get_frame_rect();
|
const targetFrame = win.get_frame_rect();
|
||||||
const overlapX = Math.max(0, Math.min(sourceFrame.x + sourceFrame.width, targetFrame.x + targetFrame.width) - Math.max(sourceFrame.x, targetFrame.x));
|
const overlapX = Math.max(
|
||||||
const overlapY = Math.max(0, Math.min(sourceFrame.y + sourceFrame.height, targetFrame.y + targetFrame.height) - Math.max(sourceFrame.y, targetFrame.y));
|
0,
|
||||||
|
Math.min(
|
||||||
|
sourceFrame.x + sourceFrame.width,
|
||||||
|
targetFrame.x + targetFrame.width
|
||||||
|
) - Math.max(sourceFrame.x, targetFrame.x)
|
||||||
|
);
|
||||||
|
const overlapY = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(
|
||||||
|
sourceFrame.y + sourceFrame.height,
|
||||||
|
targetFrame.y + targetFrame.height
|
||||||
|
) - Math.max(sourceFrame.y, targetFrame.y)
|
||||||
|
);
|
||||||
const overlapArea = overlapX * overlapY;
|
const overlapArea = overlapX * overlapY;
|
||||||
if (overlapArea > maxOverlap) {
|
if (overlapArea > maxOverlap) {
|
||||||
maxOverlap = overlapArea;
|
maxOverlap = overlapArea;
|
||||||
@@ -211,85 +310,125 @@ class Tiler {
|
|||||||
this.windows = [];
|
this.windows = [];
|
||||||
this.grabbedWindow = null;
|
this.grabbedWindow = null;
|
||||||
this._settings = ExtensionUtils.getSettings(SCHEMA_NAME);
|
this._settings = ExtensionUtils.getSettings(SCHEMA_NAME);
|
||||||
this.wmSettings = new Gio.Settings({ schema: WM_SCHEMA });
|
|
||||||
this._signalIds = new Map();
|
this._signalIds = new Map();
|
||||||
this._tileInProgress = false;
|
this._tileInProgress = false;
|
||||||
|
|
||||||
this._innerGap = this._settings.get_int('inner-gap');
|
this._innerGap = this._settings.get_int("inner-gap");
|
||||||
this._outerGapVertical = this._settings.get_int('outer-gap-vertical');
|
this._outerGapVertical = this._settings.get_int("outer-gap-vertical");
|
||||||
this._outerGapHorizontal = this._settings.get_int('outer-gap-horizontal');
|
this._outerGapHorizontal = this._settings.get_int(
|
||||||
|
"outer-gap-horizontal"
|
||||||
|
);
|
||||||
|
|
||||||
// Window Delay Settings
|
this._tilingDelay = TILING_DELAY_MS;
|
||||||
this._tilingDelay = 20; // General Tiling Window Delay
|
this._centeringDelay = CENTERING_DELAY_MS;
|
||||||
this._centeringDelay = 5; //Delay for centered Apps on the Exception List
|
|
||||||
|
|
||||||
this._exceptions = [];
|
this._exceptions = [];
|
||||||
this._interactionHandler = new InteractionHandler(this);
|
this._interactionHandler = new InteractionHandler(this);
|
||||||
|
|
||||||
this._onWindowAdded = this._onWindowAdded.bind(this);
|
this._onWindowAdded = this._onWindowAdded.bind(this);
|
||||||
this._onWindowRemoved = this._onWindowRemoved.bind(this);
|
this._onWindowRemoved = this._onWindowRemoved.bind(this);
|
||||||
this._onActiveWorkspaceChanged = this._onActiveWorkspaceChanged.bind(this);
|
this._onActiveWorkspaceChanged = this._onActiveWorkspaceChanged.bind(
|
||||||
this._onWindowMinimizedStateChanged = this._onWindowMinimizedStateChanged.bind(this);
|
this
|
||||||
|
);
|
||||||
|
this._onWindowMinimizedStateChanged = this._onWindowMinimizedStateChanged.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
this._onSettingsChanged = this._onSettingsChanged.bind(this);
|
this._onSettingsChanged = this._onSettingsChanged.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
this._loadExceptions();
|
this._loadExceptions();
|
||||||
const workspaceManager = global.workspace_manager;
|
const workspaceManager = global.workspace_manager;
|
||||||
this._signalIds.set('workspace-changed', { object: workspaceManager, id: workspaceManager.connect('active-workspace-changed', this._onActiveWorkspaceChanged) });
|
this._signalIds.set("workspace-changed", {
|
||||||
|
object: workspaceManager,
|
||||||
|
id: workspaceManager.connect(
|
||||||
|
"active-workspace-changed",
|
||||||
|
this._onActiveWorkspaceChanged
|
||||||
|
),
|
||||||
|
});
|
||||||
this._connectToWorkspace();
|
this._connectToWorkspace();
|
||||||
this._interactionHandler.enable();
|
this._interactionHandler.enable();
|
||||||
this._signalIds.set('settings-changed', { object: this._settings, id: this._settings.connect('changed', this._onSettingsChanged) });
|
this._signalIds.set("settings-changed", {
|
||||||
|
object: this._settings,
|
||||||
|
id: this._settings.connect("changed", this._onSettingsChanged),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
this._interactionHandler.disable();
|
this._interactionHandler.disable();
|
||||||
this._disconnectFromWorkspace();
|
this._disconnectFromWorkspace();
|
||||||
for (const [, signal] of this._signalIds) {
|
for (const [, signal] of this._signalIds) {
|
||||||
try { signal.object.disconnect(signal.id); } catch(e) {}
|
try {
|
||||||
|
signal.object.disconnect(signal.id);
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
this._signalIds.clear();
|
this._signalIds.clear();
|
||||||
this.windows = [];
|
this.windows = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSettingsChanged() {
|
_onSettingsChanged() {
|
||||||
this._innerGap = this._settings.get_int('inner-gap');
|
this._innerGap = this._settings.get_int("inner-gap");
|
||||||
this._outerGapVertical = this._settings.get_int('outer-gap-vertical');
|
this._outerGapVertical = this._settings.get_int("outer-gap-vertical");
|
||||||
this._outerGapHorizontal = this._settings.get_int('outer-gap-horizontal');
|
this._outerGapHorizontal = this._settings.get_int(
|
||||||
|
"outer-gap-horizontal"
|
||||||
|
);
|
||||||
this.queueTile();
|
this.queueTile();
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadExceptions() {
|
_loadExceptions() {
|
||||||
const file = Gio.file_new_for_path(Me.path + '/exceptions.txt');
|
const file = Gio.file_new_for_path(Me.path + "/exceptions.txt");
|
||||||
if (!file.query_exists(null)) { this._exceptions = []; return; }
|
if (!file.query_exists(null)) {
|
||||||
|
this._exceptions = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
const [ok, data] = file.load_contents(null);
|
const [ok, data] = file.load_contents(null);
|
||||||
this._exceptions = ok ? ByteArray.toString(data).split('\n')
|
this._exceptions = ok
|
||||||
.map(l => l.trim()).filter(l => l && !l.startsWith('#'))
|
? ByteArray.toString(data)
|
||||||
.map(l => l.toLowerCase()) : [];
|
.split("\n")
|
||||||
|
.map((l) => l.trim())
|
||||||
|
.filter((l) => l && !l.startsWith("#"))
|
||||||
|
.map((l) => l.toLowerCase())
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
_isException(win) {
|
_isException(win) {
|
||||||
return !!win && this._exceptions.includes((win.get_wm_class() || '').toLowerCase());
|
return (
|
||||||
|
!!win &&
|
||||||
|
this._exceptions.includes((win.get_wm_class() || "").toLowerCase())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_isTileable(win) {
|
_isTileable(win) {
|
||||||
return win && !win.minimized && !this._isException(win) && win.get_window_type() === Meta.WindowType.NORMAL;
|
return (
|
||||||
|
win &&
|
||||||
|
!win.minimized &&
|
||||||
|
!this._isException(win) &&
|
||||||
|
win.get_window_type() === Meta.WindowType.NORMAL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_centerWindow(win) {
|
_centerWindow(win) {
|
||||||
Mainloop.timeout_add(this._centeringDelay, () => {
|
Mainloop.timeout_add(this._centeringDelay, () => {
|
||||||
if (!win || !win.get_display()) return GLib.SOURCE_REMOVE;
|
if (!win || !win.get_display()) return GLib.SOURCE_REMOVE;
|
||||||
if (win.get_maximized()) { win.unmaximize(Meta.MaximizeFlags.BOTH); }
|
if (win.get_maximized()) {
|
||||||
|
win.unmaximize(Meta.MaximizeFlags.BOTH);
|
||||||
|
}
|
||||||
const monitorIndex = win.get_monitor();
|
const monitorIndex = win.get_monitor();
|
||||||
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex);
|
const workArea = Main.layoutManager.getWorkAreaForMonitor(
|
||||||
|
monitorIndex
|
||||||
|
);
|
||||||
const frame = win.get_frame_rect();
|
const frame = win.get_frame_rect();
|
||||||
win.move_frame(true,
|
win.move_frame(
|
||||||
|
true,
|
||||||
workArea.x + Math.floor((workArea.width - frame.width) / 2),
|
workArea.x + Math.floor((workArea.width - frame.width) / 2),
|
||||||
workArea.y + Math.floor((workArea.height - frame.height) / 2));
|
workArea.y + Math.floor((workArea.height - frame.height) / 2)
|
||||||
|
);
|
||||||
Mainloop.idle_add(() => {
|
Mainloop.idle_add(() => {
|
||||||
if (win.get_display()) {
|
if (win.get_display()) {
|
||||||
if (typeof win.set_keep_above === 'function') win.set_keep_above(true);
|
if (typeof win.set_keep_above === "function")
|
||||||
else if (typeof win.make_above === 'function') win.make_above();
|
win.set_keep_above(true);
|
||||||
|
else if (typeof win.make_above === "function")
|
||||||
|
win.make_above();
|
||||||
}
|
}
|
||||||
return GLib.SOURCE_REMOVE;
|
return GLib.SOURCE_REMOVE;
|
||||||
});
|
});
|
||||||
@@ -310,16 +449,32 @@ class Tiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._isTileable(win)) {
|
if (this._isTileable(win)) {
|
||||||
if (this._settings.get_string('new-window-behavior') === 'master') {
|
if (this._settings.get_string("new-window-behavior") === "master") {
|
||||||
this.windows.unshift(win);
|
this.windows.unshift(win);
|
||||||
} else {
|
} else {
|
||||||
this.windows.push(win);
|
this.windows.push(win);
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = win.get_id();
|
const id = win.get_id();
|
||||||
this._signalIds.set(`unmanaged-${id}`, { object: win, id: win.connect('unmanaged', () => this._onWindowRemoved(null, win)) });
|
this._signalIds.set(`unmanaged-${id}`, {
|
||||||
this._signalIds.set(`size-changed-${id}`, { object: win, id: win.connect('size-changed', () => { if (!this.grabbedWindow) this.queueTile(); }) });
|
object: win,
|
||||||
this._signalIds.set(`minimized-${id}`, { object: win, id: win.connect('notify::minimized', this._onWindowMinimizedStateChanged) });
|
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();
|
this.queueTile();
|
||||||
}
|
}
|
||||||
@@ -331,11 +486,13 @@ class Tiler {
|
|||||||
this.windows.splice(index, 1);
|
this.windows.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
['unmanaged', 'size-changed', 'minimized'].forEach(prefix => {
|
["unmanaged", "size-changed", "minimized"].forEach((prefix) => {
|
||||||
const key = `${prefix}-${win.get_id()}`;
|
const key = `${prefix}-${win.get_id()}`;
|
||||||
if (this._signalIds.has(key)) {
|
if (this._signalIds.has(key)) {
|
||||||
const { object, id } = this._signalIds.get(key);
|
const { object, id } = this._signalIds.get(key);
|
||||||
try { object.disconnect(id); } catch(e) {}
|
try {
|
||||||
|
object.disconnect(id);
|
||||||
|
} catch (e) {}
|
||||||
this._signalIds.delete(key);
|
this._signalIds.delete(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -349,19 +506,29 @@ class Tiler {
|
|||||||
|
|
||||||
_connectToWorkspace() {
|
_connectToWorkspace() {
|
||||||
const workspace = global.workspace_manager.get_active_workspace();
|
const workspace = global.workspace_manager.get_active_workspace();
|
||||||
workspace.list_windows().forEach(win => this._onWindowAdded(workspace, win));
|
workspace
|
||||||
this._signalIds.set('window-added', { object: workspace, id: workspace.connect('window-added', this._onWindowAdded) });
|
.list_windows()
|
||||||
this._signalIds.set('window-removed', { object: workspace, id: workspace.connect('window-removed', this._onWindowRemoved) });
|
.forEach((win) => this._onWindowAdded(workspace, win));
|
||||||
|
this._signalIds.set("window-added", {
|
||||||
|
object: workspace,
|
||||||
|
id: workspace.connect("window-added", this._onWindowAdded),
|
||||||
|
});
|
||||||
|
this._signalIds.set("window-removed", {
|
||||||
|
object: workspace,
|
||||||
|
id: workspace.connect("window-removed", this._onWindowRemoved),
|
||||||
|
});
|
||||||
this.queueTile();
|
this.queueTile();
|
||||||
}
|
}
|
||||||
|
|
||||||
_disconnectFromWorkspace() {
|
_disconnectFromWorkspace() {
|
||||||
this.windows.slice().forEach(win => this._onWindowRemoved(null, win));
|
this.windows.slice().forEach((win) => this._onWindowRemoved(null, win));
|
||||||
|
|
||||||
['window-added', 'window-removed'].forEach(key => {
|
["window-added", "window-removed"].forEach((key) => {
|
||||||
if (this._signalIds.has(key)) {
|
if (this._signalIds.has(key)) {
|
||||||
const { object, id } = this._signalIds.get(key);
|
const { object, id } = this._signalIds.get(key);
|
||||||
try { object.disconnect(id); } catch(e) {}
|
try {
|
||||||
|
object.disconnect(id);
|
||||||
|
} catch (e) {}
|
||||||
this._signalIds.delete(key);
|
this._signalIds.delete(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -386,7 +553,13 @@ class Tiler {
|
|||||||
_splitLayout(windows, area) {
|
_splitLayout(windows, area) {
|
||||||
if (windows.length === 0) return;
|
if (windows.length === 0) return;
|
||||||
if (windows.length === 1) {
|
if (windows.length === 1) {
|
||||||
windows[0].move_resize_frame(true, area.x, area.y, area.width, area.height);
|
windows[0].move_resize_frame(
|
||||||
|
true,
|
||||||
|
area.x,
|
||||||
|
area.y,
|
||||||
|
area.width,
|
||||||
|
area.height
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,12 +570,32 @@ class Tiler {
|
|||||||
|
|
||||||
if (area.width > area.height) {
|
if (area.width > area.height) {
|
||||||
const primaryWidth = Math.floor(area.width / 2) - gap;
|
const primaryWidth = Math.floor(area.width / 2) - gap;
|
||||||
primaryArea = { x: area.x, y: area.y, width: primaryWidth, height: area.height };
|
primaryArea = {
|
||||||
secondaryArea = { x: area.x + primaryWidth + this._innerGap, y: area.y, width: area.width - primaryWidth - this._innerGap, height: area.height };
|
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 {
|
} else {
|
||||||
const primaryHeight = Math.floor(area.height / 2) - gap;
|
const primaryHeight = Math.floor(area.height / 2) - gap;
|
||||||
primaryArea = { x: area.x, y: area.y, width: area.width, height: primaryHeight };
|
primaryArea = {
|
||||||
secondaryArea = { x: area.x, y: area.y + primaryHeight + this._innerGap, width: area.width, height: area.height - primaryHeight - this._innerGap };
|
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(primaryWindows, primaryArea);
|
||||||
@@ -410,33 +603,49 @@ class Tiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_tileWindows() {
|
_tileWindows() {
|
||||||
const windowsToTile = this.windows.filter(win => !win.minimized);
|
const windowsToTile = this.windows.filter((win) => !win.minimized);
|
||||||
if (windowsToTile.length === 0) return;
|
if (windowsToTile.length === 0) return;
|
||||||
const monitor = Main.layoutManager.primaryMonitor;
|
const monitor = Main.layoutManager.primaryMonitor;
|
||||||
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index);
|
const workArea = Main.layoutManager.getWorkAreaForMonitor(
|
||||||
|
monitor.index
|
||||||
|
);
|
||||||
const innerArea = {
|
const innerArea = {
|
||||||
x: workArea.x + this._outerGapHorizontal,
|
x: workArea.x + this._outerGapHorizontal,
|
||||||
y: workArea.y + this._outerGapVertical,
|
y: workArea.y + this._outerGapVertical,
|
||||||
width: workArea.width - 2 * this._outerGapHorizontal,
|
width: workArea.width - 2 * this._outerGapHorizontal,
|
||||||
height: workArea.height - 2 * this._outerGapVertical
|
height: workArea.height - 2 * this._outerGapVertical,
|
||||||
};
|
};
|
||||||
|
|
||||||
windowsToTile.forEach(win => { if (win.get_maximized()) win.unmaximize(Meta.MaximizeFlags.BOTH); });
|
windowsToTile.forEach((win) => {
|
||||||
|
if (win.get_maximized()) win.unmaximize(Meta.MaximizeFlags.BOTH);
|
||||||
|
});
|
||||||
|
|
||||||
if (windowsToTile.length === 1) {
|
if (windowsToTile.length === 1) {
|
||||||
windowsToTile[0].move_resize_frame(true, innerArea.x, innerArea.y, innerArea.width, innerArea.height);
|
windowsToTile[0].move_resize_frame(
|
||||||
|
true,
|
||||||
|
innerArea.x,
|
||||||
|
innerArea.y,
|
||||||
|
innerArea.width,
|
||||||
|
innerArea.height
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gap = Math.floor(this._innerGap / 2);
|
const gap = Math.floor(this._innerGap / 2);
|
||||||
const masterWidth = Math.floor(innerArea.width / 2) - gap;
|
const masterWidth = Math.floor(innerArea.width / 2) - gap;
|
||||||
const master = windowsToTile[0];
|
const master = windowsToTile[0];
|
||||||
master.move_resize_frame(true, innerArea.x, innerArea.y, masterWidth, innerArea.height);
|
master.move_resize_frame(
|
||||||
|
true,
|
||||||
|
innerArea.x,
|
||||||
|
innerArea.y,
|
||||||
|
masterWidth,
|
||||||
|
innerArea.height
|
||||||
|
);
|
||||||
const stackArea = {
|
const stackArea = {
|
||||||
x: innerArea.x + masterWidth + this._innerGap,
|
x: innerArea.x + masterWidth + this._innerGap,
|
||||||
y: innerArea.y,
|
y: innerArea.y,
|
||||||
width: innerArea.width - masterWidth - this._innerGap,
|
width: innerArea.width - masterWidth - this._innerGap,
|
||||||
height: innerArea.height
|
height: innerArea.height,
|
||||||
};
|
};
|
||||||
this._splitLayout(windowsToTile.slice(1), stackArea);
|
this._splitLayout(windowsToTile.slice(1), stackArea);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"uuid": "simple-tiling@domoel",
|
"uuid": "simple-tiling@domoel",
|
||||||
"name": "Simple Tiling",
|
"name": "Simple Tiling",
|
||||||
"description": "A Simple Tiling Extension for Gnome Shell 3.38.",
|
"description": "A Simple Tiling Extension for Gnome Shell 3.38.",
|
||||||
"version": 3,
|
"version": 4,
|
||||||
"shell-version": [ "3.38" ],
|
"shell-version": [ "3.38" ],
|
||||||
"settings-schema": "org.gnome.shell.extensions.simple-tiling.domoel",
|
"settings-schema": "org.gnome.shell.extensions.simple-tiling.domoel",
|
||||||
"preferences_ui": "prefs.js",
|
"preferences_ui": "prefs.js",
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
// ------------------------------------------------------ //
|
// ------------------------------------------------------ //
|
||||||
// Extension Settings Menu for Simple Tiling - Version 2 //
|
// Extension Settings Menu for Simple Tiling - Version 4 //
|
||||||
// © 2025 domoel – MIT //
|
// © 2025 domoel – MIT //
|
||||||
// ------------------------------------------------------ //
|
// ------------------------------------------------------ //
|
||||||
|
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
// Global Imports //
|
// Global Imports //
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
const { Gtk, GObject, Gio } = imports.gi;
|
const { Gtk, GObject, Gio } = imports.gi;
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
|
||||||
const SCHEMA_NAME = 'org.gnome.shell.extensions.simple-tiling.domoel';
|
const SCHEMA_NAME = "org.gnome.shell.extensions.simple-tiling.domoel";
|
||||||
|
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
// Definition of Row Model //
|
// Definition of Row Model //
|
||||||
@@ -39,124 +39,233 @@ function buildPrefsWidget() {
|
|||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
// Section for Keybindings //
|
// Section for Keybindings //
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
const keysTitle = new Gtk.Label({ label: '<b>Tastenkürzel</b>', use_markup: true, halign: Gtk.Align.START, visible: true });
|
const keysTitle = new Gtk.Label({
|
||||||
const keysFrame = new Gtk.Frame({ label_widget: keysTitle, shadow_type: Gtk.ShadowType.NONE, visible: true });
|
label: "<b>Tastenkürzel</b>",
|
||||||
let keysBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: 12, spacing: 6, visible: true });
|
use_markup: true,
|
||||||
|
halign: Gtk.Align.START,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
const keysFrame = new Gtk.Frame({
|
||||||
|
label_widget: keysTitle,
|
||||||
|
shadow_type: Gtk.ShadowType.NONE,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
let keysBox = new Gtk.Box({
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
margin: 12,
|
||||||
|
spacing: 6,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
keysFrame.add(keysBox);
|
keysFrame.add(keysBox);
|
||||||
|
|
||||||
let store = new Gtk.ListStore();
|
let store = new Gtk.ListStore();
|
||||||
store.set_column_types([ GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_INT ]);
|
store.set_column_types([
|
||||||
|
GObject.TYPE_STRING,
|
||||||
|
GObject.TYPE_STRING,
|
||||||
|
GObject.TYPE_INT,
|
||||||
|
GObject.TYPE_INT,
|
||||||
|
]);
|
||||||
|
|
||||||
addKeybinding(store, settings, 'swap-master-window', 'Master-Fenster tauschen');
|
addKeybinding(
|
||||||
addKeybinding(store, settings, 'swap-left-window', 'Nach links tauschen');
|
store,
|
||||||
addKeybinding(store, settings, 'swap-right-window', 'Nach rechts tauschen');
|
settings,
|
||||||
addKeybinding(store, settings, 'swap-up-window', 'Nach oben tauschen');
|
"swap-master-window",
|
||||||
addKeybinding(store, settings, 'swap-down-window', 'Nach unten tauschen');
|
"Master-Fenster tauschen"
|
||||||
|
);
|
||||||
|
addKeybinding(
|
||||||
|
store,
|
||||||
|
settings,
|
||||||
|
"swap-left-window",
|
||||||
|
"Fenster nach links tauschen"
|
||||||
|
);
|
||||||
|
addKeybinding(
|
||||||
|
store,
|
||||||
|
settings,
|
||||||
|
"swap-right-window",
|
||||||
|
"Fenster nach rechts tauschen"
|
||||||
|
);
|
||||||
|
addKeybinding(
|
||||||
|
store,
|
||||||
|
settings,
|
||||||
|
"swap-up-window",
|
||||||
|
"Fenster nach oben tauschen"
|
||||||
|
);
|
||||||
|
addKeybinding(
|
||||||
|
store,
|
||||||
|
settings,
|
||||||
|
"swap-down-window",
|
||||||
|
"Fenster nach unten tauschen"
|
||||||
|
);
|
||||||
|
|
||||||
let treeView = new Gtk.TreeView({ model: store, headers_visible: false, hexpand: true, visible: true });
|
addKeybinding(store, settings, "focus-left", "Fokus nach links wechseln");
|
||||||
|
addKeybinding(store, settings, "focus-right", "Fokus nach rechts wechseln");
|
||||||
|
addKeybinding(store, settings, "focus-up", "Fokus nach oben wechseln");
|
||||||
|
addKeybinding(store, settings, "focus-down", "Fokus nach unten wechseln");
|
||||||
|
|
||||||
|
let treeView = new Gtk.TreeView({
|
||||||
|
model: store,
|
||||||
|
headers_visible: false,
|
||||||
|
hexpand: true,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
keysBox.add(treeView);
|
keysBox.add(treeView);
|
||||||
|
|
||||||
let descRenderer = new Gtk.CellRendererText();
|
let descRenderer = new Gtk.CellRendererText();
|
||||||
let descColumn = new Gtk.TreeViewColumn({ expand: true });
|
let descColumn = new Gtk.TreeViewColumn({ expand: true });
|
||||||
descColumn.pack_start(descRenderer, true);
|
descColumn.pack_start(descRenderer, true);
|
||||||
descColumn.add_attribute(descRenderer, 'text', COLUMN_DESC);
|
descColumn.add_attribute(descRenderer, "text", COLUMN_DESC);
|
||||||
treeView.append_column(descColumn);
|
treeView.append_column(descColumn);
|
||||||
|
|
||||||
let accelRenderer = new Gtk.CellRendererAccel({ 'accel-mode': Gtk.CellRendererAccelMode.GTK, 'editable': true });
|
let accelRenderer = new Gtk.CellRendererAccel({
|
||||||
|
"accel-mode": Gtk.CellRendererAccelMode.GTK,
|
||||||
|
editable: true,
|
||||||
|
});
|
||||||
let accelColumn = new Gtk.TreeViewColumn();
|
let accelColumn = new Gtk.TreeViewColumn();
|
||||||
accelColumn.pack_end(accelRenderer, false);
|
accelColumn.pack_end(accelRenderer, false);
|
||||||
accelColumn.add_attribute(accelRenderer, 'accel-key', COLUMN_KEY);
|
accelColumn.add_attribute(accelRenderer, "accel-key", COLUMN_KEY);
|
||||||
accelColumn.add_attribute(accelRenderer, 'accel-mods', COLUMN_MODS);
|
accelColumn.add_attribute(accelRenderer, "accel-mods", COLUMN_MODS);
|
||||||
treeView.append_column(accelColumn);
|
treeView.append_column(accelColumn);
|
||||||
|
|
||||||
accelRenderer.connect('accel-edited', (renderer, path_string, key, mods, hw_code) => {
|
accelRenderer.connect("accel-edited", (r, path, key, mods) => {
|
||||||
let [ok, iter] = store.get_iter_from_string(path_string);
|
let [ok, iter] = store.get_iter_from_string(path);
|
||||||
if (!ok) return;
|
if (ok) {
|
||||||
store.set(iter, [COLUMN_KEY, COLUMN_MODS], [key, mods]);
|
store.set(iter, [COLUMN_KEY, COLUMN_MODS], [key, mods]);
|
||||||
let id = store.get_value(iter, COLUMN_ID);
|
settings.set_strv(store.get_value(iter, COLUMN_ID), [
|
||||||
let accelString = Gtk.accelerator_name(key, mods);
|
Gtk.accelerator_name(key, mods),
|
||||||
settings.set_strv(id, [accelString]);
|
]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
accelRenderer.connect('accel-cleared', (renderer, path_string) => {
|
accelRenderer.connect("accel-cleared", (r, path) => {
|
||||||
let [ok, iter] = store.get_iter_from_string(path_string);
|
let [ok, iter] = store.get_iter_from_string(path);
|
||||||
if (!ok) return;
|
if (ok) {
|
||||||
store.set(iter, [COLUMN_KEY, COLUMN_MODS], [0, 0]);
|
store.set(iter, [COLUMN_KEY, COLUMN_MODS], [0, 0]);
|
||||||
let id = store.get_value(iter, COLUMN_ID);
|
settings.set_strv(store.get_value(iter, COLUMN_ID), []);
|
||||||
settings.set_strv(id, []);
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
prefsWidget.add(keysFrame);
|
prefsWidget.add(keysFrame);
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
// Section for Window Gaps //
|
// Section for Window Gaps //
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
const gapsTitle = new Gtk.Label({ label: '<b>Abstände (Gaps)</b>', use_markup: true, halign: Gtk.Align.START, visible: true });
|
const gapsTitle = new Gtk.Label({
|
||||||
const gapsFrame = new Gtk.Frame({ label_widget: gapsTitle, shadow_type: Gtk.ShadowType.NONE, visible: true });
|
label: "<b>Fensterabstände (Gaps)</b>",
|
||||||
const gapsGrid = new Gtk.Grid({ margin: 12, column_spacing: 12, row_spacing: 12, visible: true });
|
use_markup: true,
|
||||||
|
halign: Gtk.Align.START,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
const gapsFrame = new Gtk.Frame({
|
||||||
|
label_widget: gapsTitle,
|
||||||
|
shadow_type: Gtk.ShadowType.NONE,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
const gapsGrid = new Gtk.Grid({
|
||||||
|
margin: 12,
|
||||||
|
column_spacing: 12,
|
||||||
|
row_spacing: 12,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
gapsFrame.add(gapsGrid);
|
gapsFrame.add(gapsGrid);
|
||||||
|
|
||||||
addSpinButtonRow(gapsGrid, settings, "Innerer Abstand (zwischen Fenstern)", "inner-gap", 0);
|
addSpinButtonRow(gapsGrid, settings, "Innerer Abstand", "inner-gap", 0);
|
||||||
addSpinButtonRow(gapsGrid, settings, "Äußerer Abstand (horizontal)", "outer-gap-horizontal", 1);
|
addSpinButtonRow(
|
||||||
addSpinButtonRow(gapsGrid, settings, "Äußerer Abstand (vertikal)", "outer-gap-vertical", 2);
|
gapsGrid,
|
||||||
|
settings,
|
||||||
|
"Äußerer Abstand (horizontal)",
|
||||||
|
"outer-gap-horizontal",
|
||||||
|
1
|
||||||
|
);
|
||||||
|
addSpinButtonRow(
|
||||||
|
gapsGrid,
|
||||||
|
settings,
|
||||||
|
"Äußerer Abstand (vertikal)",
|
||||||
|
"outer-gap-vertical",
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
prefsWidget.add(gapsFrame);
|
prefsWidget.add(gapsFrame);
|
||||||
|
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
// Section for Window Behavior (Master vs. Stack) //
|
// Section for Window Behavior (Master vs. Stack) //
|
||||||
// ---------------------------------------------------- //
|
// ---------------------------------------------------- //
|
||||||
const behaviorTitle = new Gtk.Label({ label: '<b>Verhalten</b>', use_markup: true, halign: Gtk.Align.START, visible: true });
|
const behaviorTitle = new Gtk.Label({
|
||||||
const behaviorFrame = new Gtk.Frame({ label_widget: behaviorTitle, shadow_type: Gtk.ShadowType.NONE, visible: true });
|
label: "<b>Fensterverhalten</b>",
|
||||||
const behaviorGrid = new Gtk.Grid({ margin: 12, column_spacing: 12, row_spacing: 12, visible: true });
|
use_markup: true,
|
||||||
behaviorFrame.add(behaviorGrid);
|
halign: Gtk.Align.START,
|
||||||
|
visible: true,
|
||||||
const comboLabel = new Gtk.Label({ label: "Neues Fenster öffnen als", halign: Gtk.Align.START, visible: true });
|
|
||||||
behaviorGrid.attach(comboLabel, 0, 0, 1, 1);
|
|
||||||
|
|
||||||
const comboBox = new Gtk.ComboBoxText({ visible: true, halign: Gtk.Align.END });
|
|
||||||
comboBox.append('stack', 'Stack-Fenster (Standard)');
|
|
||||||
comboBox.append('master', 'Master-Fenster');
|
|
||||||
|
|
||||||
comboBox.set_active_id(settings.get_string('new-window-behavior'));
|
|
||||||
|
|
||||||
comboBox.connect('changed', () => {
|
|
||||||
settings.set_string('new-window-behavior', comboBox.get_active_id());
|
|
||||||
});
|
});
|
||||||
|
const behaviorFrame = new Gtk.Frame({
|
||||||
behaviorGrid.attach(comboBox, 1, 0, 1, 1);
|
label_widget: behaviorTitle,
|
||||||
|
shadow_type: Gtk.ShadowType.NONE,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
const behaviorGrid = new Gtk.Grid({
|
||||||
|
margin: 12,
|
||||||
|
column_spacing: 12,
|
||||||
|
row_spacing: 12,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
behaviorFrame.add(behaviorGrid);
|
||||||
|
addComboBoxRow(
|
||||||
|
behaviorGrid,
|
||||||
|
settings,
|
||||||
|
"Neues Fenster öffnen als",
|
||||||
|
"new-window-behavior",
|
||||||
|
0
|
||||||
|
);
|
||||||
prefsWidget.add(behaviorFrame);
|
prefsWidget.add(behaviorFrame);
|
||||||
|
|
||||||
return prefsWidget;
|
return prefsWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addKeybinding(model, settings, id, description) {
|
function addKeybinding(model, settings, id, desc) {
|
||||||
let [key, mods] = [0, 0];
|
let [key, mods] = [0, 0];
|
||||||
|
|
||||||
const strv = settings.get_strv(id);
|
const strv = settings.get_strv(id);
|
||||||
if (strv && strv.length > 0 && strv[0]) {
|
if (strv && strv[0]) {
|
||||||
[key, mods] = Gtk.accelerator_parse(strv[0]);
|
[key, mods] = Gtk.accelerator_parse(strv[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let iter = model.append();
|
let iter = model.append();
|
||||||
model.set(iter,
|
model.set(
|
||||||
|
iter,
|
||||||
[COLUMN_ID, COLUMN_DESC, COLUMN_KEY, COLUMN_MODS],
|
[COLUMN_ID, COLUMN_DESC, COLUMN_KEY, COLUMN_MODS],
|
||||||
[id, description, key, mods]
|
[id, desc, key, mods]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
function addSpinButtonRow(grid, settings, desc, key, pos) {
|
||||||
function addSpinButtonRow(grid, settings, description, key, position) {
|
const label = new Gtk.Label({
|
||||||
const label = new Gtk.Label({ label: description, halign: Gtk.Align.START, visible: true });
|
label: desc,
|
||||||
grid.attach(label, 0, position, 1, 1);
|
halign: Gtk.Align.START,
|
||||||
|
visible: true,
|
||||||
const adjustment = new Gtk.Adjustment({ lower: 0, upper: 50, step_increment: 1 });
|
});
|
||||||
const spinButton = new Gtk.SpinButton({
|
grid.attach(label, 0, pos, 1, 1);
|
||||||
adjustment: adjustment,
|
const adj = new Gtk.Adjustment({ lower: 0, upper: 50, step_increment: 1 });
|
||||||
|
const spin = new Gtk.SpinButton({
|
||||||
|
adjustment: adj,
|
||||||
climb_rate: 1,
|
climb_rate: 1,
|
||||||
digits: 0,
|
digits: 0,
|
||||||
halign: Gtk.Align.END,
|
halign: Gtk.Align.END,
|
||||||
visible: true,
|
visible: true,
|
||||||
});
|
});
|
||||||
settings.bind(key, spinButton, 'value', Gio.SettingsBindFlags.DEFAULT);
|
settings.bind(key, spin, "value", Gio.SettingsBindFlags.DEFAULT);
|
||||||
grid.attach(spinButton, 1, position, 1, 1);
|
grid.attach(spin, 1, pos, 1, 1);
|
||||||
|
}
|
||||||
|
function addComboBoxRow(grid, settings, desc, key, pos) {
|
||||||
|
const label = new Gtk.Label({
|
||||||
|
label: desc,
|
||||||
|
halign: Gtk.Align.START,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
grid.attach(label, 0, pos, 1, 1);
|
||||||
|
const combo = new Gtk.ComboBoxText({
|
||||||
|
visible: true,
|
||||||
|
halign: Gtk.Align.END,
|
||||||
|
});
|
||||||
|
combo.append("stack", "Stack-Fenster (Standard)");
|
||||||
|
combo.append("master", "Master-Fenster");
|
||||||
|
combo.set_active_id(settings.get_string(key));
|
||||||
|
combo.connect("changed", () => {
|
||||||
|
settings.set_string(key, combo.get_active_id());
|
||||||
|
});
|
||||||
|
grid.attach(combo, 1, pos, 1, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,23 @@
|
|||||||
<summary>Tauscht das Fenster mit dem unteren Nachbarn.</summary>
|
<summary>Tauscht das Fenster mit dem unteren Nachbarn.</summary>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
|
<key name="focus-up" type="as">
|
||||||
|
<default><![CDATA[['<Alt>k']]]></default>
|
||||||
|
<summary>Fokus zum oberen Fenster wechseln.</summary>
|
||||||
|
</key>
|
||||||
|
<key name="focus-down" type="as">
|
||||||
|
<default><![CDATA[['<Alt>j']]]></default>
|
||||||
|
<summary>Fokus zum unteren Fenster wechseln.</summary>
|
||||||
|
</key>
|
||||||
|
<key name="focus-left" type="as">
|
||||||
|
<default><![CDATA[['<Alt>h']]]></default>
|
||||||
|
<summary>Fokus zum linken Fenster wechseln.</summary>
|
||||||
|
</key>
|
||||||
|
<key name="focus-right" type="as">
|
||||||
|
<default><![CDATA[['<Alt>l']]]></default>
|
||||||
|
<summary>Fokus zum rechten Fenster wechseln.</summary>
|
||||||
|
</key>
|
||||||
|
|
||||||
<key name="inner-gap" type="i">
|
<key name="inner-gap" type="i">
|
||||||
<default>10</default>
|
<default>10</default>
|
||||||
<summary>Der Abstand zwischen den Fenstern in Pixeln.</summary>
|
<summary>Der Abstand zwischen den Fenstern in Pixeln.</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user