55 Commits

Author SHA1 Message Date
Dome 95eb37ddd0 Add files via upload 2025-07-25 14:58:40 +00:00
Dome fe3b242476 Update org.gnome.shell.extensions.simple-tiling.domoel.gschema.xml 2025-07-25 16:58:14 +02:00
Dome 5614e5fff2 Update extension.js 2025-07-25 16:57:56 +02:00
Dome 4de841d8b0 Update metadata.json 2025-07-25 16:57:38 +02:00
Dome b0586e9c36 Update prefs.js 2025-07-25 16:57:22 +02:00
Dome c39a6799e2 Update README.md 2025-07-25 09:37:11 +02:00
Dome c495c85e95 Update README.md 2025-07-25 09:35:41 +02:00
Dome ca9ea8fcff Update org.gnome.shell.extensions.simple-tiling.domoel.gschema.xml 2025-07-25 00:57:33 +02:00
Dome 0a122565b8 Update org.gnome.shell.extensions.simple-tiling.domoel.gschema.xml 2025-07-25 00:48:37 +02:00
Dome 524098ce68 Update prefs.js 2025-07-25 00:48:18 +02:00
Dome c5e692bb45 Update README.md 2025-07-25 00:27:51 +02:00
Dome 421fb8796f Update README.md 2025-07-25 00:27:17 +02:00
Dome b144a90902 Update org.gnome.shell.extensions.simple-tiling.domoel.gschema.xml 2025-07-25 00:14:51 +02:00
Dome 89982c883d Update extension.js 2025-07-25 00:14:31 +02:00
Dome 5204d40168 Update metadata.json 2025-07-25 00:14:12 +02:00
Dome 7b45cffeff Update prefs.js 2025-07-25 00:13:53 +02:00
Dome 560ca61e41 Update README.md 2025-07-25 00:13:00 +02:00
Dome 6e35a92e9e Update README.md 2025-07-25 00:12:19 +02:00
Dome 38baf60286 Update README.md 2025-07-24 12:08:48 +02:00
Dome 65f5447ea2 Update README.md 2025-07-24 12:08:15 +02:00
Dome 290d067287 Update README.md 2025-07-24 12:04:37 +02:00
Dome 87ab2e8669 Update prefs.js 2025-07-24 00:29:39 +02:00
Dome 209421f479 Update README.md 2025-07-24 00:14:46 +02:00
Dome dd2bc5e589 Update README.md 2025-07-24 00:08:29 +02:00
Dome 9148ef63c6 Update README.md 2025-07-24 00:08:12 +02:00
Dome 56ee5c8933 Create .gitignore 2025-07-24 00:03:59 +02:00
Dome 7a313f7fc4 Update README.md 2025-07-23 23:49:46 +02:00
Dome 62d2792889 Update README.md 2025-07-23 23:48:17 +02:00
Dome 7aa9a30375 Update README.md 2025-07-23 23:43:48 +02:00
Dome ecfc739cc8 Update README.md 2025-07-23 23:42:27 +02:00
Dome 2a94a1c04f Update org.gnome.shell.extensions.simple-tiling.domoel.gschema.xml 2025-07-23 23:36:45 +02:00
Dome 8c1dce1644 Update extension.js 2025-07-23 23:36:27 +02:00
Dome 6a5f421fdf Update metadata.json 2025-07-23 23:36:08 +02:00
Dome 10e9faf528 Update prefs.js 2025-07-23 23:35:52 +02:00
Dome bf492ceeb3 Update README.md 2025-07-23 22:46:06 +02:00
Dome a5ba875df0 Update README.md 2025-07-23 22:45:09 +02:00
Dome 90b8dd952e Update README.md 2025-07-23 22:04:52 +02:00
Dome f3e1a30aae Update README.md 2025-07-23 21:56:47 +02:00
Dome 3a998f943a Update README.md 2025-07-23 21:55:30 +02:00
Dome 0bb95a577d Update README.md 2025-07-23 21:53:44 +02:00
Dome a47cac9f43 Update README.md 2025-07-23 21:50:35 +02:00
Dome 8408c357a5 Delete schemas/gschemas.compiled 2025-07-23 21:41:10 +02:00
Dome d58381e4ec Update org.gnome.shell.extensions.simple-tiling.domoel.gschema.xml 2025-07-23 21:39:49 +02:00
Dome 7ed0b64ffb Update extension.js 2025-07-23 21:39:16 +02:00
Dome b8dcc27bf8 Update metadata.json 2025-07-23 21:39:00 +02:00
Dome 136f49456a Update prefs.js 2025-07-23 21:38:42 +02:00
Dome 09a2740ffe Update README.md 2025-07-23 18:03:30 +02:00
Dome adcb02c953 Update README.md 2025-07-23 18:03:14 +02:00
Dome c9eb0c0212 Update README.md 2025-07-23 18:02:52 +02:00
Dome b0d00f03ec Update README.md 2025-07-23 18:02:26 +02:00
Dome 134c6b2cb6 Update README.md 2025-07-23 17:10:54 +02:00
Dome c4aee6f610 Update README.md 2025-07-23 17:09:31 +02:00
Dome a6045b73a1 Update README.md 2025-07-23 17:08:14 +02:00
Dome 005795cbd9 Update README.md 2025-07-23 17:07:41 +02:00
Dome da86ae9cd2 Update README.md 2025-07-23 17:02:53 +02:00
7 changed files with 674 additions and 216 deletions
+6
View File
@@ -0,0 +1,6 @@
# GNOME Shell Extension specific
schemas/gschemas.compiled
# Common temporary files
*~
*.swp
+42 -6
View File
@@ -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>
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
![GNOME Shell Version](https://img.shields.io/badge/GNOME%20Shell-3.38-blue) ![GNOME Shell Version](https://img.shields.io/badge/GNOME%20Shell-3.38-blue)
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" src="https://github.com/user-attachments/assets/18fcbb34-3e0e-45b8-abe1-0e752b8d970c" />
## Introduction ## Introduction
@@ -17,13 +23,16 @@ This extension was built from the ground up to be stable and performant on **GNO
* **Automatic Tiling:** Windows are automatically arranged into a master and stack layout without any manual intervention. * **Automatic Tiling:** Windows are automatically arranged into a master and stack layout without any manual intervention.
* **Master & Fibonacci Stack Layout:** The first window becomes the "master," occupying the left half of the screen. All subsequent windows form a "stack" on the right half, which is tiled using a space-efficient Fibonacci-style algorithm. * **Master & Fibonacci Stack Layout:** The first window becomes the "master," occupying the left half of the screen. All subsequent windows form a "stack" on the right half, which is tiled using a space-efficient Fibonacci-style algorithm.
* **Configurable New Window Behavior:** Choose whether new windows open as the new master or are appended to the end of the stack.
* **Tiling Lock:** The layout is strict by default. If you manually move a window with the mouse and drop it in an empty space, it will automatically "snap back" to its designated tile position, preserving the integrity of the layout. * **Tiling Lock:** The layout is strict by default. If you manually move a window with the mouse and drop it in an empty space, it will automatically "snap back" to its designated tile position, preserving the integrity of the layout.
* **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).
* **Configurable Gaps:** Easily configure inner and outer gaps by editing variables directly in the `extension.js` code to achieve your desired aesthetic. * **Interactive Window Focus Switcher:** Change the current window focus with a set of customizable keyboard shortcuts in every direction (left, right, up, down).
* **Simple Settings Panel:** A simple settings panel within the gnome extension manager menu to adjust key bindings, window gaps / margins and window behavior.
* **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`.
## Requirements ## Requirements
@@ -57,7 +66,7 @@ Use the [GNOME Shell Extensions website](https://extensions.gnome.org/extension/
#### Keyboard Shortcuts #### Keyboard Shortcuts
All keyboard shortcuts can be configured through the standard GNOME Settings panel: All keyboard shortcuts can be configured through the Settings panel of Simple Tiling (which can be found in the Gnome Extension Application):
1. Open **Settings**. 1. Open **Settings**.
2. Navigate to **Keyboard** -> **View and Customize Shortcuts**. 2. Navigate to **Keyboard** -> **View and Customize Shortcuts**.
3. Scroll down to the **Custom Shortcuts** section at the bottom. 3. Scroll down to the **Custom Shortcuts** section at the bottom.
@@ -74,3 +83,30 @@ To prevent an application from being tiled, you can add its `WM_CLASS` to the `e
To find an application's `WM_CLASS`, open a terminal and run the command `xprop WM_CLASS`. Your cursor will turn into a crosshair. Click on the window of the application you want to exclude. To find an application's `WM_CLASS`, open a terminal and run the command `xprop WM_CLASS`. Your cursor will turn into a crosshair. Click on the window of the application you want to exclude.
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
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).
#### 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).
#### 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
This extension was built to solve a specific need. However, future enhancements could include:
* Multi-monitor support.
* Support for newer Gnome shells
* Additional layout algorithms.
* A more detailed settings panel to configure other options via a GUI.
## License
This project is licensed under the MIT License - see the `LICENSE` file for details.
+364 -116
View File
@@ -1,24 +1,41 @@
// Simple-Tiling GNOME Shell 3.38 (X11) - Version 1.0 // ---------------------------------------------------- //
// Features: Fibonacci-Stack-Layout (50/50) · Tiling-Lock · Drag- & Keyboard-Swap // Simple-Tiling GNOME Shell 3.38 (X11) - Version 5 //
// Pop-ups: zentriert + Always-on-Top + Exception-List // © 2025 domoel MIT //
// © 2025 domoel MIT // ---------------------------------------------------- //
// Global Imports // ---------------------------------------------------- //
// Global Imports //
// ---------------------------------------------------- //
const Main = imports.ui.main; const Main = imports.ui.main;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Mainloop = imports.mainloop;
const Gio = imports.gi.Gio; 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";
// InteractionHandler const KEYBINDINGS = {
// Verwaltung aller Nutzerinteraktionen (Maus & Tastatur) "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 //
// ---------------------------------------------------- //
class InteractionHandler { class InteractionHandler {
constructor(tiler) { constructor(tiler) {
this.tiler = tiler; this.tiler = tiler;
@@ -28,70 +45,104 @@ class InteractionHandler {
this._savedWmShortcuts = {}; this._savedWmShortcuts = {};
this._grabOpIds = []; this._grabOpIds = [];
this._settingsChangedId = null; this._settingsChangedId = null;
this._onSettingsChanged = this._onSettingsChanged.bind(this); this._onSettingsChanged = this._onSettingsChanged.bind(this);
this._prepareWmShortcuts(); this._prepareWmShortcuts();
} }
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));
this._grabOpIds = [];
} }
_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());
} }
} }
@@ -102,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]];
} }
@@ -115,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());
} }
@@ -134,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;
@@ -146,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);
@@ -166,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;
@@ -174,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;
@@ -188,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;
@@ -200,89 +301,156 @@ class InteractionHandler {
} }
} }
// ---------------------------------------------------- //
// Tiler // Tiler //
// Die Hauptklasse für die Tiling-Logik. // Main Classes for Tiling Logic //
// ---------------------------------------------------- //
class Tiler { class Tiler {
constructor() { constructor() {
this.windows = []; this.windows = [];
this.grabbedWindow = null; this.grabbedWindow = null;
this.wmSettings = new Gio.Settings({ schema: WM_SCHEMA }); this._settings = ExtensionUtils.getSettings(SCHEMA_NAME);
this._signalIds = new Map(); this._signalIds = new Map();
this._tileInProgress = false; this._tileInProgress = false;
// Layout-Konfiguration this._innerGap = this._settings.get_int("inner-gap");
this._innerGap = 10; this._outerGapVertical = this._settings.get_int("outer-gap-vertical");
this._outerGapVertical = 5; this._outerGapHorizontal = this._settings.get_int(
this._outerGapHorizontal = 10; "outer-gap-horizontal"
);
// Delay-Zeiten für das Tiling und Exception Windows this._tilingDelay = TILING_DELAY_MS;
this._tilingDelay = 20; this._centeringDelay = CENTERING_DELAY_MS;
this._centeringDelay = 5;
this._exceptions = []; this._exceptions = [];
this._interactionHandler = new InteractionHandler(this); this._interactionHandler = new InteractionHandler(this);
this._tileTimeoutId = null;
this._centerTimeoutIds = [];
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);
} }
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),
});
} }
disable() { 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._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() {
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() { _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, () => { 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 || !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(() => { );
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
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;
}); });
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
this._centerTimeoutIds.push(timeoutId);
} }
_onWindowMinimizedStateChanged() { _onWindowMinimizedStateChanged() {
@@ -298,11 +466,33 @@ class Tiler {
} }
if (this._isTileable(win)) { if (this._isTileable(win)) {
this.windows.push(win); if (this._settings.get_string("new-window-behavior") === "master") {
this.windows.unshift(win);
} else {
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();
} }
} }
@@ -313,11 +503,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);
} }
}); });
@@ -331,30 +523,42 @@ 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);
} }
}); });
} }
queueTile() { queueTile() {
if (this._tileInProgress) return; if (this._tileInProgress || this._tileTimeoutId) return;
this._tileInProgress = true; this._tileInProgress = true;
Mainloop.timeout_add(this._tilingDelay, () => {
this._tileTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._tilingDelay, () => {
this._tileWindows(); this._tileWindows();
this._tileInProgress = false; this._tileInProgress = false;
this._tileTimeoutId = null;
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
} }
@@ -368,7 +572,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;
} }
@@ -379,12 +589,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);
@@ -392,39 +622,57 @@ 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);
} }
} }
// Extension Wrapper // ---------------------------------------------------- //
// Extension Wrapper //
// ---------------------------------------------------- //
class SimpleTilingExtension { class SimpleTilingExtension {
constructor() { constructor() {
this.tiler = null; this.tiler = null;
+1 -1
View File
@@ -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": 1, "version": 5,
"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",
+193 -54
View File
@@ -1,108 +1,247 @@
// Settings Menu for Simple-Tiling // ------------------------------------------------------ //
// Extension Settings Menu for Simple Tiling - Version 5 //
// © 2025 domoel MIT //
// ------------------------------------------------------ //
'use strict'; // ---------------------------------------------------- //
// Global Imports //
// ---------------------------------------------------- //
"use strict";
const { Gtk, GObject } = imports.gi; const { Gtk, GObject, Gio } = imports.gi;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const COLUMN_ID = 0; // z.B. 'swap-master-window' const SCHEMA_NAME = "org.gnome.shell.extensions.simple-tiling.domoel";
const COLUMN_DESC = 1; // z.B. 'Master-Fenster tauschen'
const COLUMN_KEY = 2; // Der Key-Code (eine Zahl) // ---------------------------------------------------- //
const COLUMN_MODS = 3; // Die Modifier-Maske (eine Zahl) // Definition of Row Model //
// ---------------------------------------------------- //
const COLUMN_ID = 0;
const COLUMN_DESC = 1;
const COLUMN_KEY = 2;
const COLUMN_MODS = 3;
function init() {} function init() {}
function buildPrefsWidget() { function buildPrefsWidget() {
const settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.simple-tiling.domoel'); const settings = ExtensionUtils.getSettings(SCHEMA_NAME);
const prefsWidget = new Gtk.Box({ const prefsWidget = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL, orientation: Gtk.Orientation.VERTICAL,
margin: 20, margin_top: 20,
spacing: 12, margin_bottom: 20,
visible: true margin_start: 20,
margin_end: 20,
spacing: 18,
visible: true,
}); });
const title = new Gtk.Label({ // ---------------------------------------------------- //
label: '<b>Tastenkürzel für Simple-Tiling</b>', // Section for Keybindings //
// ---------------------------------------------------- //
const keysTitle = new Gtk.Label({
label: "<b>Tastenkürzel</b>",
use_markup: true, use_markup: true,
halign: Gtk.Align.START, halign: Gtk.Align.START,
visible: true visible: true,
}); });
prefsWidget.add(title); 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);
let store = new Gtk.ListStore(); let store = new Gtk.ListStore();
store.set_column_types([ store.set_column_types([
GObject.TYPE_STRING, // COLUMN_ID GObject.TYPE_STRING,
GObject.TYPE_STRING, // COLUMN_DESC GObject.TYPE_STRING,
GObject.TYPE_INT, // COLUMN_KEY GObject.TYPE_INT,
GObject.TYPE_INT, // COLUMN_MODS GObject.TYPE_INT,
]); ]);
addKeybinding(store, settings, 'swap-master-window', 'Master-Fenster tauschen'); addKeybinding(store, settings, "swap-master-window", "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-up-window', 'Fenster nach oben tauschen'); addKeybinding(store, settings, "swap-down-window", "Fenster nach unten tauschen");
addKeybinding(store, settings, 'swap-down-window', 'Fenster nach unten tauschen'); addKeybinding(store, settings, "swap-left-window", "Fenster nach links tauschen");
addKeybinding(store, settings, "swap-right-window", "Fenster nach rechts tauschen");
addKeybinding(store, settings, "focus-up", "Fokus nach oben wechseln");
addKeybinding(store, settings, "focus-down", "Fokus nach unten wechseln");
addKeybinding(store, settings, "focus-left", "Fokus nach links wechseln");
addKeybinding(store, settings, "focus-right", "Fokus nach rechts wechseln");
let treeView = new Gtk.TreeView({ let treeView = new Gtk.TreeView({
model: store, model: store,
headers_visible: false, headers_visible: false,
hexpand: true, hexpand: true,
visible: true visible: true,
}); });
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({ let accelRenderer = new Gtk.CellRendererAccel({
'accel-mode': Gtk.CellRendererAccelMode.GTK, "accel-mode": Gtk.CellRendererAccelMode.GTK,
'editable': true 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);
prefsWidget.add(treeView); accelRenderer.connect("accel-edited", (r, path, key, mods) => {
let [ok, iter] = store.get_iter_from_string(path);
accelRenderer.connect('accel-edited', (renderer, path_string, key, mods, hw_code) => { if (ok) {
let [ok, iter] = store.get_iter_from_string(path_string); store.set(iter, [COLUMN_KEY, COLUMN_MODS], [key, mods]);
if (!ok) return; settings.set_strv(store.get_value(iter, COLUMN_ID), [
Gtk.accelerator_name(key, mods),
store.set(iter, [COLUMN_KEY, COLUMN_MODS], [key, mods]); ]);
}
let id = store.get_value(iter, COLUMN_ID);
let accelString = 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]); settings.set_strv(store.get_value(iter, COLUMN_ID), []);
let id = store.get_value(iter, COLUMN_ID); }
settings.set_strv(id, []);
}); });
prefsWidget.add(keysFrame);
// ---------------------------------------------------- //
// Section for Window Gaps //
// ---------------------------------------------------- //
const gapsTitle = new Gtk.Label({
label: "<b>Fensterabstände (Gaps)</b>",
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);
addSpinButtonRow(gapsGrid, settings, "Innerer Abstand", "inner-gap", 0);
addSpinButtonRow(
gapsGrid,
settings,
"Äußerer Abstand (horizontal)",
"outer-gap-horizontal",
1
);
addSpinButtonRow(
gapsGrid,
settings,
"Äußerer Abstand (vertikal)",
"outer-gap-vertical",
2
);
prefsWidget.add(gapsFrame);
// ---------------------------------------------------- //
// Section for Window Behavior (Master vs. Stack) //
// ---------------------------------------------------- //
const behaviorTitle = new Gtk.Label({
label: "<b>Fensterverhalten</b>",
use_markup: true,
halign: Gtk.Align.START,
visible: true,
});
const behaviorFrame = new Gtk.Frame({
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);
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) {
const label = new Gtk.Label({
label: desc,
halign: Gtk.Align.START,
visible: true,
});
grid.attach(label, 0, pos, 1, 1);
const adj = new Gtk.Adjustment({ lower: 0, upper: 50, step_increment: 1 });
const spin = new Gtk.SpinButton({
adjustment: adj,
climb_rate: 1,
digits: 0,
halign: Gtk.Align.END,
visible: true,
});
settings.bind(key, spin, "value", Gio.SettingsBindFlags.DEFAULT);
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);
}
Binary file not shown.
@@ -1,33 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<schemalist> <schemalist>
<schema id="org.gnome.shell.extensions.simple-tiling.domoel" <schema id="org.gnome.shell.extensions.simple-tiling.domoel" path="/org/gnome/shell/extensions/simple-tiling/domoel/">
path="/org/gnome/shell/extensions/simple-tiling-domoel/">
<!-- Master ↔ Stack -->
<key name="swap-master-window" type="as"> <key name="swap-master-window" type="as">
<default>['&lt;Super&gt;Return']</default> <default><![CDATA[['<Super>Return']]]></default>
<summary>Tauscht Master-Fenster mit dem fokussierten Fenster</summary> <summary>Tauscht das fokussierte Fenster mit dem Master.</summary>
</key> </key>
<!-- Richtungs-Swaps -->
<key name="swap-left-window" type="as">
<default>['&lt;Super&gt;Left']</default>
<summary>Tauscht mit linkem Nachbarfenster</summary>
</key>
<key name="swap-right-window" type="as">
<default>['&lt;Super&gt;Right']</default>
<summary>Tauscht mit rechtem Nachbarfenster</summary>
</key>
<key name="swap-up-window" type="as"> <key name="swap-up-window" type="as">
<default>['&lt;Super&gt;Up']</default> <default><![CDATA[['<Super>Up']]]></default>
<summary>Tauscht mit oberem Nachbarfenster</summary> <summary>Tauscht das Fenster mit dem oberen Nachbarn.</summary>
</key>
<key name="swap-down-window" type="as">
<default><![CDATA[['<Super>Down']]]></default>
<summary>Tauscht das Fenster mit dem unteren Nachbarn.</summary>
</key>
<key name="swap-left-window" type="as">
<default><![CDATA[['<Super>Left']]]></default>
<summary>Tauscht das Fenster mit dem linken Nachbarn.</summary>
</key>
<key name="swap-right-window" type="as">
<default><![CDATA[['<Super>Right']]]></default>
<summary>Tauscht das Fenster mit dem rechten Nachbarn.</summary>
</key> </key>
<key name="swap-down-window" type="as"> <key name="focus-up" type="as">
<default>['&lt;Super&gt;Down']</default> <default><![CDATA[['<Alt>Up']]]></default>
<summary>Tauscht mit unterem Nachbarfenster</summary> <summary>Fokus zum oberen Fenster wechseln.</summary>
</key>
<key name="focus-down" type="as">
<default><![CDATA[['<Alt>Down']]]></default>
<summary>Fokus zum unteren Fenster wechseln.</summary>
</key>
<key name="focus-left" type="as">
<default><![CDATA[['<Alt>Left']]]></default>
<summary>Fokus zum linken Fenster wechseln.</summary>
</key>
<key name="focus-right" type="as">
<default><![CDATA[['<Alt>Right']]]></default>
<summary>Fokus zum rechten Fenster wechseln.</summary>
</key>
<key name="inner-gap" type="i">
<default>10</default>
<summary>Der Abstand zwischen den Fenstern in Pixeln.</summary>
</key>
<key name="outer-gap-horizontal" type="i">
<default>5</default>
<summary>Der Abstand zum linken und rechten Bildschirmrand.</summary>
</key>
<key name="outer-gap-vertical" type="i">
<default>5</default>
<summary>Der Abstand zum oberen und unteren Bildschirmrand.</summary>
</key>
<key name="new-window-behavior" type="s">
<default>'stack'</default>
<summary>Verhalten für neu geöffnete Fenster.</summary>
<description>Legt fest, ob ein neues Fenster als Master oder als Teil des Stacks hinzugefügt wird.</description>
</key> </key>
</schema> </schema>