11

This is similar to Quickly place a window to another screen using only the keyboard , but I want to be able to use the command line (so that all I need to do is to recall the command line from the bash history).

E.g., send

  • all the gnome terminal windows to eDP1,
  • all Emacs windows to VGA1, and
  • all Chrome windows to HDMI1

(and maximize them after moving - but not the crazy F11 way, the normal window-manager-style maximization).

I would like to specify windows by the executable name.

6
  • @JacobVlijm: I would like to specify windows by the executable name.
    – sds
    Nov 24, 2015 at 19:25
  • 1
    @JacobVlijm: move all windows with with the specified process name.
    – sds
    Nov 24, 2015 at 19:38
  • @JacobVlijm: just tell me how to move a window specified by anything. How to map window by title/class/executable should be a separate question.
    – sds
    Nov 24, 2015 at 19:39
  • Well, the command is pretty much straightforward: looks like wmctrl -ir 0x04200085 -e 0,746,443,468,205, but the issue is that you have to calculate the "move vector" from its current position to the targeted position, based on the output of wmctrl -lG and the screendata (the listed screen positions as in the output of xrandr. That makes it a bit more complicated, but also one of my favourite subjects :) Nov 24, 2015 at 19:51
  • Posted my answer. Please let me know if everything is clear. Nov 25, 2015 at 10:19

4 Answers 4

12

Moving all windows of a specific window class to a specific screen by (screen-) name

The script below will send windows, belonging to a specific WM_CLASS (application), to a specific screen, by the screen's name. How that is done is explained in the script and also further below.

The script assumes the screens are arranged horizontally, and more or less top- aligned (with a difference < 100 PX).

The script

#!/usr/bin/env python3
import subprocess
import sys

# just a helper function, to reduce the amount of code
get = lambda cmd: subprocess.check_output(cmd).decode("utf-8")

# get the data on all currently connected screens, their x-resolution
screendata = [l.split() for l in get(["xrandr"]).splitlines() if " connected" in l]
screendata = sum([[(w[0], s.split("+")[-2]) for s in w if s.count("+") == 2] for w in screendata], [])

def get_class(classname):
    # function to get all windows that belong to a specific window class (application)
    w_list = [l.split()[0] for l in get(["wmctrl", "-l"]).splitlines()]
    return [w for w in w_list if classname in get(["xprop", "-id", w])]

scr = sys.argv[2]

try:
    # determine the left position of the targeted screen (x)
    pos = [sc for sc in screendata if sc[0] == scr][0]
except IndexError:
    # warning if the screen's name is incorrect (does not exist)
    print(scr, "does not exist. Check the screen name")
else:
    for w in get_class(sys.argv[1]):
        # first move and resize the window, to make sure it fits completely inside the targeted screen
        # else the next command will fail...
        subprocess.Popen(["wmctrl", "-ir", w, "-e", "0,"+str(int(pos[1])+100)+",100,300,300"])
        # maximize the window on its new screen
        subprocess.Popen(["xdotool", "windowsize", "-sync", w, "100%", "100%"])

How to use

  1. The script needs both wmctrl and xdotool:

    sudo apt-get install xdotool wmctrl
    
  2. Copy the script below into an empty file, save it as move_wclass.py

  3. Run it by the command:

    python3 /path/to/move_wclass.py <WM_CLASS> <targeted_screen>
    

    for example:

    python3 /path/to/move_wclass.py gnome-terminal VGA-1
    

For the WM_CLASS, you may use part of the WM_CLASS, like in the example. The screen's name needs to be the exact and complete name.

How it is done (the concept)

The explanation is mostly on the concept, not so much on the coding.

In the output of xrandr, for every connected screen, there is a string/line, looking like:

VGA-1 connected 1280x1024+1680+0

This line gives us information on the screen's position and its name, as explained here.

The script lists the information for all screens. When the script is run with the screen and the window class as arguments, it looks up the (x-) position of the screen, looks up all windows (-id's) of a certain class (with the help of wmctrl -l and the output of xprop -id <window_id>.

Subsequently, the script moves all windows, one by one, to a position on the targeted screen (using wmctrl -ir <window_id> -e 0,<x>,<y>,<width>,<height>) and maximizes it (with xdotool windowsize 100% 100%).

Note

The script worked fine on the tests I ran it with. Using wmctrl, and even xdotool, on Unity can have some stubborn peculiarities however that sometimes need to be solved by experiment rather than reasoning. If you might run into exceptions, please mention.

10
  • 1
    I would use wmctrl -b add,maximized_vert,maximized_horz instead of xdotool.
    – sds
    Nov 25, 2015 at 15:23
  • @sds possible, but I wouldn't. Xdotool's windowsize works differently under the hood and is less buggy i.c.w. Unity in general. I always use a mix of xdotool and wmctrl in these siruations. Nov 25, 2015 at 16:05
  • what is "i.c.w."?
    – sds
    Nov 25, 2015 at 16:15
  • 1
    @sds in combination with, might not be a usual expression in English :) Nov 25, 2015 at 16:57
  • 1
    @RoundSparrowhilltx sharp! must have been a change of plan during coding. fixed now. Jun 30, 2017 at 4:51
6

I've rewrited @jacobs python code to simple bash and make it works (I tested this on ubuntu 16 cinnamon).

I had to add remove,maximized_vert, remove,maximized_horz without that windows didn't move.

#!/bin/bash

if [ ! -z "$1" ] || [ -z "$2" ]; then
    command=$(wmctrl -l | grep $1 | cut -d" " -f1)

    if [ ! -z "$command" ]; then
        position=$(xrandr | grep "^$2" | cut -d"+" -f2)

        if [ ! -z "$position" ]; then
            for window in $command; do 
               wmctrl -ir $window -b remove,maximized_vert
               wmctrl -ir $window -b remove,maximized_horz 
               wmctrl -ir $window -e 0,$position,0,1920,1080
               wmctrl -ir $window -b add,maximized_vert
               wmctrl -ir $window -b add,maximized_horz 
            done
        else
            echo -e "not found monitor with given name"
        fi
    else
        echo -e "not found windows with given name"
    fi
else
    echo -e "specify window and monitor name;\nmove.sh window-name monitor-name"
fi
  1. sudo apt-get install xdotool wmctrl
  2. /path/to/script.sh "window-name" "monitor-name"
6
  • haven't tested it yet, do you have a script to move all windows at once?
    – goldylucks
    Jul 1, 2019 at 6:12
  • @AdamGoldman I named script as "move-window" and link it to /usr/bin/move-window then I created alias like that: alias mw='\ move-windows PhpStorm eDP-1;\ move-windows Sublime eDP-1;\ move-windows Chrome HDMI-1; '; Then it moves three apps at once. Jul 1, 2019 at 8:36
  • Thank you for the answer man. For me this only focus the window, doesn't move it to the other monitor
    – goldylucks
    Jul 1, 2019 at 20:07
  • Oh I see, there's a different problem, never mind :)
    – goldylucks
    Jul 1, 2019 at 20:16
  • Thanks for the bash version. My python loves to eat itself and die, packages broken and pip won't install. This is great. One minor edit I had was I needed the full window name for pycharm (or any idea IDE), so I changed the 4th line to ...wmctrl -lx... (added the x) so now I can run move-window jetbrains HDMI-1
    – fimbulvetr
    Nov 10, 2020 at 21:16
1

For the record, here is what I use for the combination of this question and Restore multiple monitor settings:

# configure multiple displays and
# move the windows to their appropriate displays

import subprocess
import os
import wmctrl
import re

mydisplays = [("VGA1",0,"left"),
              ("eDP1",1080,"normal"),
              ("HDMI1",3000,"left")]

# https://askubuntu.com/questions/702002/restore-multiple-monitor-settings
def set_displays ():
    subprocess.check_call(" && ".join([
        "xrandr --output %s --pos %dx0  --rotate %s" % d for d in mydisplays]),
                          shell=True)

# https://askubuntu.com/questions/702071/move-windows-to-specific-screens-using-the-command-line
mywindows = [("/emacs$","VGA1"),
             ("/chrome$","HDMI1"),
             ("gnome-terminal","eDP1")]
def max_windows ():
    didi = dict([(d,x) for d,x,_ in mydisplays])
    for w in wmctrl.Window.list():
        try:
            exe = os.readlink("/proc/%d/exe" % (w.pid))
            for (r,d) in mywindows:
                if re.search(r,exe):
                    x = didi[d]
                    print "%s(%s) --> %s (%d)" % (r,exe,d,x)
                    w.set_properties(("remove","maximized_vert","maximized_horz"))
                    w.resize_and_move(x,0,w.w,w.h)
                    w.set_properties(("add","maximized_vert","maximized_horz"))
                    break
        except OSError:
            continue

def cmdlines (cmd):
    return subprocess.check_output(cmd).splitlines()

def show_displays ():
    for l in cmdlines(["xrandr"]):
        if " connected " in l:
            print l

if __name__ == '__main__':
    show_displays()
    set_displays()
    show_displays()
    max_windows()

you would need to use wmctrl version 0.3 or later (because of my pull request).

1

Based on @AndrzejPiszczek's answer, here's a way to move all windows to a specific screen:

function move_win {
    if [ -z "$1" ]; then
        echo -e "Specify a screen, possible options: "
        echo -e $(xrandr | grep " connected " | cut -d'-' -f1)
        return
    fi

    MONITOR=$1

    # get all relevant windows on all screens
    windows=$(wmctrl -l | egrep -v " -1 " | cut -d" " -f1)

    if [ ! -z "$windows" ]; then
        # get the necessary metrics from the screen the windows should be moved to 
        # will contain: width, height, offsetX, offsetY
        screen_values=($(xrandr | grep "^$MONITOR-.* connected" | grep -Eo '[0-9]+x[0-9]+\+[0-9]+\+[0-9]+' | sed 's/x/ /g; s/+/ /g'))

        if (( ${#screen_values[@]} )); then
            # get the start/end position of the screen so we can later determine
            # if the window is already on the screen or not
            screen_start_pos=$(( ${screen_values[2]} ))
            screen_end_pos=$(( ${screen_values[2]} + ${screen_values[0]} ))

            for window in $windows; do
                # get the window name
                window_name=$(wmctrl -lG | grep "$window" | awk -F "$HOSTNAME " '{print $2}')
                # extract relevant window geometry values such as x, y, width, height
                window_values=($(wmctrl -lG | grep "$window" | awk -F " " '{print $3, $5, $6}'))

                # if the window's X origin position is already inside the screen's 
                # total width then don't move it (this won't work exactly for windows only partially on the screen)
                if (( ${window_values[0]} >= $screen_end_pos || ${window_values[0]} < $screen_start_pos )); then
                    echo -e "Moving to screen $MONITOR: $window_name"
                  
                    wmctrl -ir $window -b remove,maximized_vert
                    wmctrl -ir $window -b remove,maximized_horz
                    # the -e parameters are gradient,x,y,width,height
                    # move window to (X,Y) -> (0,0) of new screen and the same window dimensions
                    wmctrl -ir $window -e 0,$screen_start_pos,0,${window_values[1]},${window_values[2]}
                else
                    echo -e "Already on screen $MONITOR: $window_name"
                fi
            done
        else 
            echo -e "No screen found"
        fi
    else
        echo -e "No windows found"
    fi
}
1
  • the only one that works, although be warned: it does its think on All workspaces, not just the current
    – allanlaal
    Jul 14, 2022 at 15:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .