What makes us different from other similar websites? › Forums › Tech › Tiled Display Script [Xubuntu] › Reply To: Tiled Display Script [Xubuntu]
May 31, 2026 at 12:58 pm
#8489
Moderator
Here is the most up to date version I use and with all the comments in the script that you will need to make it work for you. Make sure to save it as dual-tile.sh
#!/bin/bash
# =============================================================================
# dual-tile.sh — Dual-Monitor 3×2 Window Tiling Script
# =============================================================================
#
# SYSTEM DETAILS:
# OS: Xubuntu (Linux 7.0)
# Desktop: Xfce
# Window Manager: XFWM4
# WM Theme: Win11-round-Dark-compact
# Terminal: kitty
#
# HARDWARE:
# Monitors: 2× Ultrawide (3440 × 1440 each)
# Left Monitor: HDMI-0, 3440×1440, positioned at +0+0
# Right Monitor: HDMI-1, 3440×1440, positioned at +3567+0
# Note: There is a 127px invisible gap between monitors
# in the desktop coordinate space.
#
# PANEL:
# Left Monitor: 24px top panel (Xfce panel)
# Right Monitor: No panel
#
# GRID LAYOUT:
# 3 columns × 2 rows per monitor (6 cells per monitor, 12 total)
#
# DEPENDENCIES & INSTALLATION:
# On Ubuntu/Xubuntu (apt):
# sudo apt install wmctrl xdotool
#
# On Fedora (dnf):
# sudo dnf install wmctrl xdotool
#
# On Arch (pacman):
# sudo pacman -S wmctrl xdotool
#
# Package purposes:
# wmctrl — window management (unmaximize, move, resize)
# xdotool — detect active window
# xprop — (not currently used, but available for frame detection)
#
# KEYBOARD SHORTCUT EXAMPLE (Xfce Settings → Keyboard → Shortcuts):
# super + 1 → /path/to/dual-tile.sh m1_top_left
# super + 2 → /path/to/dual-tile.sh m1_top_middle
# super + 3 → /path/to/dual-tile.sh m1_top_right
# super + 4 → /path/to/dual-tile.sh m1_bottom_left
# super + 5 → /path/to/dual-tile.sh m1_bottom_middle
# super + 6 → /path/to/dual-tile.sh m1_bottom_right
# super + 7 → /path/to/dual-tile.sh m2_top_left
# super + 8 → /path/to/dual-tile.sh m2_top_middle
# super + 9 → /path/to/dual-tile.sh m2_top_right
# ... etc.
#
# HOW TO ADAPT FOR YOUR SYSTEM:
# 1. Run: xrandr | grep " connected"
# — Note your monitor positions (the +X+Y values)
# 2. Manually arrange 12 windows in a 3×2 grid on each monitor
# 3. For each window, run:
# xwininfo -id $(xdotool getactivewindow) | grep "Absolute upper-left"
# — This gives the FRAME position (what wmctrl needs)
# 4. Then run:
# xdotool getactive window getwindowgeometry --shell
# — This gives the CLIENT width/height (what wmctrl uses for w,h)
# 5. Replace the coordinates below with your captured values
# 6. Subtract the panel height from all Y values (wmctrl uses workarea coords)
#
# IMPORTANT NOTES:
# - wmctrl -e positions relative to the WORKAREA (below panel/dock),
# NOT the absolute screen position. You must subtract the panel
# height from coordinates captured with xwininfo.
# - xdotool getwindowgeometry returns CLIENT (content) coordinates.
# - xwininfo "Absolute upper-left" returns FRAME coordinates.
# - wmctrl -e uses FRAME x,y but CLIENT width,height.
# - Frame coordinates = Client coordinates − decoration offsets
# (title bar height for Y, left border width for X)
#
# =============================================================================
# --- Helper Function for Tiling ---
tile_window() {
local target_x=$1
local target_y=$2
local target_w=$3
local target_h=$4
WIN_ID=$(xdotool getactivewindow)
if [ -z "$WIN_ID" ]; then
exit 1
fi
wmctrl -i -r "$WIN_ID" -b remove,maximized_vert,maximized_horz
wmctrl -i -r "$WIN_ID" -e 1,"$target_x","$target_y","$target_w","$target_h"
}
# --- Panel Configuration ---
# Height of the top panel in pixels. Subtract this from all Y coordinates
# because wmctrl positions relative to the workarea (below panel/dock).
# Set to 0 if no panel exists.
PANEL=24
# --- Main Script Logic ---
# Coordinates are FRAME position (x, y) and CLIENT size (w, h).
# Y values have already been adjusted by subtracting PANEL (24px)
# to convert from absolute screen coords to wmctrl workarea coords.
case "$1" in
# ================= MONITOR 1 (Left Screen: HDMI-0 at +0+0) =================
# Top row: flush below panel (Y=0 in workarea coords)
"m1_top_left") tile_window 2 0 1146 690 ;;
"m1_top_middle") tile_window 1152 0 1146 690 ;;
"m1_top_right") tile_window 2302 0 1148 690 ;;
# Bottom row: Y=746 in workarea coords
"m1_bottom_left") tile_window 2 746 1146 659 ;;
"m1_bottom_middle") tile_window 1152 746 1146 659 ;;
"m1_bottom_right") tile_window 2302 746 1136 659 ;;
# ================= MONITOR 2 (Right Screen: HDMI-1 at +3567+0) =================
# Top row: Y=3 in workarea coords (no panel on this monitor)
"m2_top_left") tile_window 3569 3 1136 690 ;;
"m2_top_middle") tile_window 4709 3 1136 690 ;;
"m2_top_right") tile_window 5849 3 1156 690 ;;
# Bottom row: Y=729 in workarea coords
"m2_bottom_left") tile_window 3569 729 1136 678 ;;
"m2_bottom_middle") tile_window 4709 729 1136 678 ;;
"m2_bottom_right") tile_window 5849 729 1146 678 ;;
*)
echo "Usage: $0 {m1_top_left|m1_top_middle|m1_top_right|m1_bottom_left|m1_bottom_middle|m1_bottom_right|m2_top_left|m2_top_middle|m2_top_right|m2_bottom_left|m2_bottom_middle|m2_bottom_right}"
exit 1
;;
esac
