Skip to content

Instantly share code, notes, and snippets.

@mariushelf
Created April 2, 2026 20:52
Show Gist options
  • Select an option

  • Save mariushelf/f5b5028b0e78298c0064c7cf657340ae to your computer and use it in GitHub Desktop.

Select an option

Save mariushelf/f5b5028b0e78298c0064c7cf657340ae to your computer and use it in GitHub Desktop.
Cycle window through KDE tiles
#!/usr/bin/env bash
# Cycle the active window through available KWin Meta+T tiling spots
#
# KDE allows to tile windows to the left/right/top bottom with Meta+Left, Right, Top, Bottom.
# With the Meta+T shortcut you can define custom tiles, e.g. a 3-column layout. However it is not
# possible to move a window to a certain tile with a shortcut.
# This is what this script helps with: it moves the active window to the "next" tile.
#
# Usage: cycle-window-tile [index]
# index — move directly to this tile (0-based); logs error if out of range. If omitted,
# cycle through tiles.
#
# Assigning a shortcut:
# 1. place this script into `~/.local/bin/cycle-window-tile`
# 2. in KDE System Settings -> Keyboard -> Shortcuts -> Add New -> Command or script
# 3. set Command to `cycle-window-tile`
# 4. assign a shortcut to the command, e.g. `Meta+Shift+K`
TARGET_INDEX=-1
if [[ $# -ge 1 ]]; then
if [[ ! "$1" =~ ^[0-9]+$ ]]; then
echo "cycle-window-tile: invalid argument '$1': must be a non-negative integer" >&2
exit 1
fi
TARGET_INDEX="$1"
fi
SCRIPT_JS=$(mktemp /tmp/kwin_tile_XXXXXX.js)
cat > "$SCRIPT_JS" << JSEOF
var screen = workspace.activeScreen;
var tm = workspace.tilingForScreen(screen);
var win = workspace.activeWindow;
var targetIndex = ${TARGET_INDEX};
if (!win) {
print("kwin-tile-cycle: no active window");
} else if (!tm || !tm.rootTile || !tm.rootTile.tiles || tm.rootTile.tiles.length < 1) {
print("kwin-tile-cycle: no tiling layout found");
} else {
var tiles = tm.rootTile.tiles;
var nextIndex;
if (targetIndex >= 0) {
if (targetIndex >= tiles.length) {
print("kwin-tile-cycle: index " + targetIndex + " out of range (0-" + (tiles.length - 1) + ")");
} else {
nextIndex = targetIndex;
}
} else {
var currentIndex = -1;
for (var i = 0; i < tiles.length; i++) {
if (win.tile === tiles[i]) { currentIndex = i; break; }
}
nextIndex = (currentIndex + 1) % tiles.length;
}
if (nextIndex !== undefined) {
win.tile = tiles[nextIndex];
print("kwin-tile-cycle: moved '" + win.caption + "' to tile " + nextIndex);
}
}
JSEOF
PLUGIN_NAME="kwin_tile_middle_$$"
SCRIPT_ID=$(qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.loadScript "$SCRIPT_JS" "$PLUGIN_NAME" 2>&1)
qdbus6 org.kde.KWin "/Scripting/Script${SCRIPT_ID}" org.kde.kwin.Script.run 2>/dev/null
qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.unloadScript "$PLUGIN_NAME" 2>/dev/null
rm -f "$SCRIPT_JS"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment