Part of mt_stack, a Linux setup stack of independently installable projects.
Linux Automatic Window Tiling is a GNOME Shell extension for shortcut-driven tiling of same-application windows or all eligible windows.
By default, Super+T tiles windows from the focused app and Ctrl+Shift+T tiles all eligible windows on the current workspace and monitor. A keybinding or D-Bus request starts tiling; opening a window by itself does not reflow the layout.
Quick start · Features · Dependencies · Installation · Configuration · Development · Contributing · Integration · FAQ · License
From this checkout, install the extension without the private shared installer framework:
bash install.sh --standaloneFocus an app window and press Super+T to tile that app's windows. Press Ctrl+Shift+T to tile every eligible window on the same workspace and monitor.
The standalone command above installs directly from this checkout. It does not fetch setup repositories or require GitHub credentials. It needs the build and desktop tools listed below; obtaining a toolchain or container image can require network access.
The optional installation_scripts bundle clones and runs this installer
as its own selectable repository; linux_hotkey_setup does not configure it.
Bundle/menu modes use the included MIT .installer-support/ package.
They work without a sibling framework checkout or GitHub credentials.
--standalone retains the direct extension installer.
See the full cross-repository map in installation_scripts/DEPENDENCIES.md.
- Only normal, taskbar-visible windows are tiled.
- Windows must match the focused application's tile group: exact identity for
most apps, with plain
kittyand per-providerkitty-agent-*model-icon terminals co-tiling as one family (icons stay distinct via real WM_CLASS). - Windows must already be on the focused window's workspace and monitor.
- Maximized matching windows remain eligible: Super+T restores them before placing them in the grid, including when the focused window is maximized. Windows outside the selected application, workspace, or monitor keep their state.
- Ctrl+Shift+T includes every eligible application on the focused window's workspace and monitor, using one balanced grid for the mixed window set.
- One window fills the monitor work area.
- Two windows tile left/right when their minimum widths fit.
- Four windows start with a 2x2 grid.
- On a portrait (taller-than-wide) monitor, windows stack top-to-bottom in a single full-width column (two windows are top and bottom, a third continues below and is never cut in half); a configurable minimum tile height keeps a window usable before the next one wraps into a column to the right.
- Other counts use a balanced calculated grid.
- Application minimum sizes can force a less dense grid. For example, three GNOME Terminal windows on a small VM display use a 2x2 grid when three columns would overlap. Character-cell resize increments can leave small gaps. If no uniform grid can fit the application minimum sizes, the original window positions, sizes, and maximized/fullscreen/minimized states are restored. The journal explains why the request could not fit.
- Kitty uses a 1300px preferred width to choose grid density on wide monitors, and windows are requested to fill their complete cells subject to application size constraints.
- GNOME's work area is used, excluding panels and docks.
- Pressing the accelerator again while the windows already fill the grid rotates them by one cell instead of re-tiling them into the same places, so tapping the combination repeatedly cycles through the possible arrangements.
From the repository directory, install as the desktop user without sudo:
bash install.sh --standaloneSelect both shortcuts independently during installation:
bash install.sh --standalone --binding '<Super>t' --all-binding '<Super><Shift>t'Standalone mode deploys the complete extension and configures both shortcuts,
application widths, and portrait wrapping. It accepts the existing AWT_*
environment overrides below, and --default / --all are optional aliases for
that complete installation. On a terminal, omitted shortcuts can be typed at
the prompts; non-interactive runs use defaults or supplied values.
Use bash install.sh --standalone --help for usage.
Omit --standalone to use the included component menu and
selection/configuration controls. The core is installed when applying
a selection, including an empty selection; queries and uninstall never deploy
or enable the extension. The legacy positional accelerator installs defaults:
bash install.sh '<Super>t'
bash install.sh --binding '<Control><Alt>t'
bash install.sh --binding '<Super>t' --all-binding '<Super><Shift>t'The equivalent --binding=<accelerator> and
--all-binding=<accelerator> forms are also accepted. The legacy positional
accelerator remains an alias for --binding.
The same choices work in scripted standalone or bundle installs through environment overrides:
AWT_BINDING='<Super>t' AWT_ALL_BINDING='<Super><Shift>t' bash install.sh --defaultFor press-to-capture on a terminal, enable the shared capture UI for this component install through the shared framework:
ISC_HOTKEY_INTERACTIVE=1 bash install.sh --select keybindingThe master bundle passes AWT_BINDING / AWT_ALL_BINDING overrides through to
this installer. Explicit values bypass interactive capture.
Component-framework flags select exactly what to configure:
bash install.sh # interactive component menu on a TTY
bash install.sh --default # all default-on components, no prompts
bash install.sh --all # every component, no prompts
bash install.sh --select keybinding # only the listed component ids
bash install.sh --list-components # id<TAB>label<TAB>default, for tooling
bash install.sh --export-selection # export current component selection
bash install.sh --help # print installer help--select, --reconfigure, --uninstall, --config, and
--configure-component also accept --option=value syntax. Framework controls
are intentionally separate from --standalone; combining them is rejected
before changing the desktop.
Advanced framework queries include --list-configurable-components,
--list-select-configure-components, and --list-component-config-values.
--configure-component ID opens a component's registered configuration screen
when one exists; this repository currently exposes its tunings through the
environment and shortcut capture. Use --config NAME.json to select an
installation_configs/ profile, --export-selection to export its selection,
and --auth to enable the framework's authentication option.
All components are default-on, so a plain install reproduces the extension's built-in behavior. Each writes a single gsettings key, so behavior stays fully customizable without editing the extension source:
| Component | gsettings key | Env override | Default |
|---|---|---|---|
keybinding |
tile-focused-application-windows, tile-all-windows |
AWT_BINDING, AWT_ALL_BINDING |
<Super>t, <Control><Shift>t |
per_app_widths |
application-preferred-widths |
AWT_APP_WIDTHS |
kitty:1300 |
portrait_stacking |
portrait-minimum-tile-height |
AWT_PORTRAIT_MIN_HEIGHT |
0 |
AWT_ALL_BINDING changes the monitor-wide all-window accelerator while keeping
Ctrl+Shift+T as the default. AWT_APP_WIDTHS is a comma- or space-separated list
of name:width entries.
A portrait-minimum-tile-height of 0 (the default) keeps portrait monitors in a
single, full-height column (pure vertical stacking, top to bottom). Set a positive
value (for example 1080) to wrap windows into side-by-side columns once a column
would fall below that height, keeping many windows usable on a tall monitor. On a TTY the keybinding
component uses the shared press-to-capture engine for both keys when
ISC_HOTKEY_INTERACTIVE=1; otherwise it uses the configured values or defaults.
The installer can run directly as the desktop user or through sudo. When run
through sudo, it builds using the SUDO_USER toolchain, installs into that
user's local GNOME Shell extension directory, and applies settings through
the user's session bus. It
requires glib-compile-schemas (Ubuntu package libglib2.0-bin) and builds the
awt-tools Rust helper during installation. Provide a current stable Rust/Cargo
toolchain and a C linker (Ubuntu package build-essential), or Docker/Podman
for the container build fallback. Ubuntu 24.04's
distribution Cargo 1.75 cannot read the checked-in lockfile; use a current
toolchain through rustup instead. The component menu resolves its included support package offline;
--standalone also remains available.
GSettings accelerator changes apply live. If edited extension JavaScript needs a Shell reload, install the automation tool if missing and use GNOME's in-place run-dialog reload on X11:
command -v xdotool || sudo apt install -y xdotool
xdotool key --clearmodifiers alt+F2
sleep 0.5
xdotool type 'r'
xdotool key ReturnOn Wayland, deploy the files and log out and back in to load edited extension code.
Runtime modules:
extension.jsowns keybinding/D-Bus registration and lifecycle teardown.tiling.jsowns instance settings, focused/all/background placement, cycling, and rollback when a grid cannot fit.preferences.jsparses settings and validates effective application widths.layout.jsowns pure grid selection and tile geometry calculations.windows.jsowns GNOME window selection, identity, state management, and final frame placement after resizing.
Run the complete local test and build checks:
./tests/run_tests.shThe runner checks Rust tests and builds, Python tests, Bash syntax, and the
GSettings schema. It requires Cargo, Python with pytest, Node.js, and
glib-compile-schemas.
The individual checks can also be run separately:
cargo test
python3 -m pytest tests -q
bash -n install.sh
glib-compile-schemas --strict schemasThe compiled schemas/gschemas.compiled file is generated and ignored.
The installer helper has no external Cargo dependencies. Build it independently
with bash scripts/build_awt_tools.sh; --print prints the resulting binary
path, and --help prints build usage. Its public CLI appends one value to
CURRENT, preserving existing entries and removing duplicates:
CURRENT="@as ['existing@local']" dist/awt-tools automatic-window-tiling@local
# ['existing@local', 'automatic-window-tiling@local']Exactly one positional value is required; malformed existing array text is treated as empty for compatibility. Diagnostics remain separate from its machine-readable stdout.
The release checks passed 8 Rust and 101 Python tests, helper builds, shell syntax, and strict GSettings schema validation. They include public installer commands in isolated homes, clean installation/reinstallation, failed builds, invalid commands, target-user sudo builds, both shortcut callbacks, D-Bus grouping/teardown, and live settings changes.
Actual installation and geometry/cycling were checked on Ubuntu 24.04.5 LTS, GNOME 46, X11, including independent standalone and clean sudo installations. GNOME 45–50 are listed as compatible; newer Mutter maximization signatures have mocked regression coverage. Other GNOME versions and Wayland have not received the same live VM verification. Application minimum sizes can make a uniform grid impossible; such requests restore the previous layout.
tests/check_vm_tiling.py checks actual GTK window frames after pressing a
configured shortcut twice. It verifies that windows fit the work area without
overlap and that a second press rotates their positions. It moves the selected
windows, so use a dedicated test workspace with at least two GNOME Terminal
windows on the same monitor. For the all-window shortcut, include every eligible
window in that workspace. The checker requires Python 3, xdotool, xprop,
and xwininfo (the latter two are in Ubuntu's x11-utils package).
Run inside the VM's X11 desktop, or an SSH shell using that desktop's display,
X authority, runtime directory, and session bus. Use the installed bindings for
--key; these examples use the defaults (super+y is valid for a customized
Super+Y binding):
mapfile -t window_ids < <(xdotool search --onlyvisible --desktop "$(xdotool get_desktop)" --class Gnome-terminal)
python3 tests/check_vm_tiling.py --key super+t --windows "${window_ids[@]}"
python3 tests/check_vm_tiling.py --key ctrl+shift+t --windows "${window_ids[@]}"For a window set known to exceed the available grid capacity, add
--expect-unchanged. This checks that two repeated requests preserve the
original geometry instead of leaving partially placed windows off-screen.
Pass every eligible window ID, including other applications for the all-window
shortcut. Replace the ID1 placeholders with numeric X11 window IDs:
python3 tests/check_vm_tiling.py --expect-unchanged --key ctrl+shift+t --windows ID1 ID2 ID3 ID4This is a private repository, so contribution access is limited to authorized collaborators. Before changing installed behavior, read AGENTS.md and follow the development and verification steps in this README.
The installation_scripts master installer clones this repository and runs it
directly before linux_hotkey_setup. It exposes this installer's
keybinding component in the repository selection menu. Configure both hotkeys
for the bundle with:
sudo env AWT_BINDING='<Super>t' AWT_ALL_BINDING='<Super><Shift>t' bash install.shThe same accelerator options are available without the bundle through
bash install.sh --standalone.
The background D-Bus interface retains both public entrypoints:
gdbus call --session --dest org.gnome.shell.extensions.AutomaticWindowTiling \
--object-path /org/gnome/shell/extensions/automatic_window_tiling \
--method org.gnome.shell.extensions.AutomaticWindowTiling.TileWindowGroup '0x123456'
gdbus call --session --dest org.gnome.shell.extensions.AutomaticWindowTiling \
--object-path /org/gnome/shell/extensions/automatic_window_tiling \
--method org.gnome.shell.extensions.AutomaticWindowTiling.TileWindowGroups "['0x123456', '0x234567']"Replace example IDs with actual X11 window IDs. Invalid or unavailable IDs are ignored; background calls preserve focus/workspace and do not cycle layouts.
Framework modes track component configuration and support reapplying or resetting it:
bash install.sh --detect # show each component as installed|absent
bash install.sh --reconfigure a,b # re-apply (idempotent) these component ids
bash install.sh --uninstall a,b # uninstall these component idsComponent uninstall resets its settings to schema defaults; it does not remove the mandatory extension core. This applies only to the framework/menu path. To disable or remove the user extension itself, including a standalone install:
gnome-extensions disable automatic-window-tiling@local
gnome-extensions uninstall automatic-window-tiling@localIn the interactive menu (run bash install.sh on a terminal, or via the master
installer), already-installed components show a green ✓. Select one with
space to reconfigure it (~), press u to mark it for uninstall
(✗), or press r to reconfigure every installed component at once. Detection
uses a live check where deterministic and otherwise an install receipt under
${XDG_STATE_HOME:-~/.local/state}/isc/receipts/; a component without a reversal
step simply clears that receipt on uninstall.
Super+T arranges normal, taskbar-visible windows from the focused application's tile group on the current workspace and monitor. Plain kitty and kitty-agent-* terminal windows can share one group; other application identities remain separate.
Press Ctrl+Shift+T to arrange all eligible windows on the focused window's workspace and monitor. Applications outside that workspace or monitor keep their current layout.
No. Tiling starts when a configured shortcut or the extension's D-Bus method is called, so opening a window does not trigger reflow by itself.
An application can refuse a tile that is smaller than its minimum size. If no uniform grid fits every selected window, the extension restores their previous positions, sizes, and maximized, fullscreen, or minimized states.
The default setting stacks windows top to bottom in one full-height column. A positive portrait-minimum-tile-height wraps additional windows into another column when the current column would become too short.
Yes. The TileWindowGroup and TileWindowGroups D-Bus methods accept X11 window IDs and place groups without changing focus or cycling the current layout.
This software is provided under the MIT License on an “as is” basis, without warranties of any kind. To the maximum extent permitted by applicable law, the authors and copyright holders shall not be liable for any claims, damages, losses, or other liability arising from the use of this software.
You are solely responsible for determining whether this software is suitable, safe, lawful, and appropriate for your intended use. Unless explicitly stated otherwise, this project is general-purpose software and is not designed, tested, certified, or approved for safety-critical, medical, automotive, aviation, industrial-control, life-support, cybersecurity-critical, financial-critical, or other high-risk use cases.
The authors and copyright holders make no guarantees regarding security, reliability, availability, correctness, compliance, non-infringement, or fitness for any particular purpose.
This notice is intended to clarify the nature of the project and does not impose additional restrictions beyond the MIT License.
This project is licensed under the MIT License. See LICENSE.md for the full terms.
This project's root install.sh is also used by the public mt_stack hub from
a separate checkout. The installer carries its required MIT menu/runtime support
in .installer-support/, so a standalone install needs no private framework
repository, private credentials, populated framework cache or hub checkout.
Keep that directory with the source when installing from an archive.
bash install.sh --list-components
bash install.sh --export-selection
bash install.shThe existing component interface and options documented above remain available;
ISC_FUNCTIONS_DIR is an explicit support override. Run
python3 tests/check_standalone_public.py to check discovery and selection JSON
with downloads blocked. Runtime extension source is unchanged by this installer
support migration. The standalone support is available on the public default branch. Follow-up
fixes from the final local audit remain local until explicitly published.
The follow-up local suite passes 102 Python tests. Its installer fixture preserves
symlinks, including dangling editor-skill links, so unrelated editor integrations
do not prevent isolated public installer checks. The original VM is no longer
available; this fixture change was verified locally through bash tests/run_tests.sh.
VM verification on Ubuntu 24.04.5 / GNOME 46 X11 covered repeated standalone and hub installation, component-menu apply/cancel, and both shortcuts arranging and cycling three real terminal windows. Unknown component selections are rejected before deployment; JSON export preserves earlier redirected output.
python3 tests/check_invalid_selection.py
python3 tests/check_export_stream.py
bash tests/run_tests.shRun these commands from this checkout. The component IDs printed by discovery are the same IDs used by the terminal menu and programmatic actions.
| Command | Behavior |
|---|---|
bash install.sh |
Open component selection on a terminal; use the default/config selection otherwise. |
bash install.sh --help |
Show accepted options (-h is equivalent). |
bash install.sh --list-components |
Print component IDs, labels and defaults. |
bash install.sh --default |
Apply default-on components. |
bash install.sh --all |
Apply every component. |
bash install.sh --select ID,... |
Apply exactly the named components. |
bash install.sh --reconfigure ID,... |
Reapply those components through the same install operations. |
bash install.sh --uninstall ID,... |
Run the selected removal operations. |
bash install.sh --detect |
Print ID<TAB>installed or ID<TAB>absent. |
bash install.sh --export-selection |
Print selection JSON without applying it. |
The shared interface also accepts --config NAME (an installation JSON name
or path) and --auth (explicit permission for authenticated component actions).
--config=NAME, --select=ID,..., --reconfigure=ID,...,
--uninstall=ID,... and --configure-component=ID are equivalent forms.
Advanced discovery is available through --list-configurable-components,
--list-select-configure-components and --list-component-config-values;
--configure-component ID opens an available nested screen. Atomic owners
return empty advanced listings and reject unavailable nested configuration.
Explicit --select "" selects no optional components; a defined mandatory core
or installation preflight still runs. --uninstall "" is a no-op. Unknown or
duplicate IDs, missing operands and conflicting modes reject before component
installation/removal. Help, discovery, detection and export perform no installation.
Cancelling the component menu performs no install/removal. Configuration screens
may save their own settings when explicitly accepted.
The shared terminal screen uses arrows to move, Space to toggle, Enter to
apply and q/Escape to cancel; installed components support u for removal.
A right-arrow detail action appears where the owner provides configuration.
Removal-only selections skip core installation.
The licensed .installer-support/ package is generated by
linux_installation_scripts_functions; manifest.json records provenance and
SHA-256 hashes, and LICENSE.md retains the MIT grant. It is a shared library
subset, not an application checkout. The default bootstrap requires no private
download or sibling repository. Explicit ISC_COMPONENT_LIB_DIR /
ISC_FUNCTIONS_DIR overrides remain available for development and the old setup
chain. Regenerate support from its owner rather than editing copies. Bash and
Python 3 are needed for this library; project build/runtime requirements above
still apply.
For example, inspect IDs first, then use the same ID with either interface:
bash install.sh --list-components
bash install.sh --select keybinding
bash install.sh --detect
bash install.sh --uninstall keybinding