Mover ventanas a pantallas específicas usando la línea de comando

Mover ventanas a pantallas específicas usando la línea de comando

Esto es similar aColoque rápidamente una ventana en otra pantalla usando solo el teclado, pero quiero poder usar la línea de comando (de modo que todo lo que necesito hacer es recuperar la línea de comando del historial de bash).

Por ejemplo, enviar

  • todas las ventanas de terminal de gnome a eDP1,
  • todas las ventanas de Emacs a VGA1y
  • todas las ventanas de Chrome paraHDMI1

(y maximizarlos después de moverse, pero no de la F11manera loca, la maximización normal al estilo del administrador de ventanas).

Me gustaría especificar Windows por el nombre del ejecutable.

Respuesta1

Mover todas las ventanas de una clase de ventana específica a una pantalla específica por nombre (de pantalla)

El siguiente script enviará ventanas, pertenecientes a una WM_CLASS(aplicación) específica, a una pantalla específica, según la dirección de la pantalla.nombre. Cómo se hace esto se explica en el guión y también más adelante.

El guión supone que las pantallas están dispuestas horizontalmente y más o menos alineadas hacia arriba (con una diferencia < 100 PX).

La secuencia de comandos

#!/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%"])

Cómo utilizar

  1. El guión necesita ambos wmctrly xdotool:

    sudo apt-get install xdotool wmctrl
    
  2. Copie el siguiente script en un archivo vacío y guárdelo comomove_wclass.py

  3. Ejecútelo con el comando:

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

    Por ejemplo:

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

Para el WM_CLASS, puedes usarpartedel WM_CLASS, como en el ejemplo. El nombre de la pantalla debe ser elexactoy nombre completo.

Cómo se hace (el concepto)

La explicación se centra principalmente en el concepto, no tanto en la codificación.

En la salida de xrandr, para cada pantalla conectada, hay una cadena/línea que se ve así:

VGA-1 connected 1280x1024+1680+0

Esta línea nos da información sobre la pantalla.posicióny esnombre, como se explicaaquí.

El script enumera la información de todas las pantallas. Cuando el script se ejecuta con la pantalla y la clase de ventana como argumentos, busca la posición (x-) de la pantalla, busca todas las ventanas (-id) de una determinada clase (con la ayuda wmctrl -ly la salida de xprop -id <window_id>.

Posteriormente, el script mueve todas las ventanas, una por una, a una posición en la pantalla de destino (usando wmctrl -ir <window_id> -e 0,<x>,<y>,<width>,<height>) y la maximiza (con xdotool windowsize 100% 100%).

Nota

El script funcionó bien en las pruebas con las que lo ejecuté. Sin embargo, el uso de wmctrl, e incluso xdotool, en Unity puede tener algunas peculiaridades persistentes que a veces deben resolverse mediante experimentos en lugar de razonamiento. Si puede encontrar excepciones, menciónelas.

Respuesta2

Reescribí el código Python de @jacobs en bash simple y lo hice funcionar (probé esto en Ubuntu 16 Cinnamon).

Tuve que agregar remove,maximized_vert, remove,maximized_horzsin que las ventanas no se movieran.

#!/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"

Respuesta3

Para que conste, esto es lo que uso para la combinación de esta pregunta yRestaurar la configuración de varios monitores:

# 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()

necesitarías usarwmctrlversión 0.3 o posterior (debido a misolicitud de extracción).

Respuesta4

Según la respuesta de @AndrzejPiszczek, aquí hay una manera de mover todas las ventanas a una pantalla específica:

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
}

información relacionada