Passa ai contenuti principali

Script per automatizzare il posizionamento delle finestre sul desktop


Ogni volta che accendo il computer avvio gli stessi programmi e sposto le finestre sempre nelle stesse posizioni e negli stessi desktop. E' arrivato il momento di far eseguire queste operazioni direttamente al computer, così ho scritto uno script in bash che serve proprio a questo scopo. Lo script utilizza il programma "wmctrl" per controllare le finestre; lo troverete quasi sicuramente nel gestore di pacchetti della vostra distribuzione.

Per iniziare leggo le dimensioni del desktop perché serviranno per il posizionamento delle finestre relativo ai bordi del desktop:

DESKTOP_GEOMETRY=$( wmctrl -d | grep '^0' | cut -d ' ' -f 5 )
DESKTOP_WIDTH=$( echo "$DESKTOP_GEOMETRY" | cut -d 'x' -f 1 )
DESKTOP_HEIGHT=$( echo "$DESKTOP_GEOMETRY" | cut -d 'x' -f 2 )

Poi ho definito la funzione get_win_property per poter recuperare comodamente una proprietà di una finestra. Le proprietà sono definite come costanti all'inizio dello script:

# field position in wmctrl's output
WIN_X=4
WIN_Y=5
WIN_W=6
WIN_H=7

e sono rispettivamente la posizione orizzontale e verticale, la larghezza e l'altezza di una finestra.
Questa funzione prende come parametri il titolo della finestra e una delle costanti che indica la proprietà il cui valore deve essere restituito.

function get_win_property {  # window_name field_number
 local window=$1
 local field=$2

 echo "get_win_property: $window, $field" >&2

 wmctrl -l -G -p | sed -e 's/  \+/ /g' | grep "$window" | cut -d ' ' -f "$field"
}

Come si vedrà, tutte le funzioni che agiscono su una finestra prendono come parametro il titolo della finestra, o parte del titolo, l'importante è che sia sufficiente per individuare univocamente la finestra all'interno dell'output di wmctrl. La "ricerca" della riga riguardante la nostra finestra viene effettuata con grep, quindi come già detto anche parte del titolo può essere sufficiente.

La funzione da chiamare per spostare una finestra in una data posizione è move_win. I parametri da passare sono il solito titolo della finestra seguito dalla posizione orizzontale e da quella verticale. Le posizioni possono essere espresse in pixel o utilizzando delle stringhe che specificano una posizione relativa ai bordi del desktop: LEFT, RIGHT, TOP, BOT.

function move_win { # window_name x y
 local window=$1
 local xpos=$2
 local ypos=$3

 echo "move_win: $window, $xpos, $ypos" >&2

 if [ $xpos == 'LEFT' ]; then
  xpos=2
 fi

[...]

Nel caso di posizionamento relativo (qui sotto abbiamo l'esempio di RIGHT) lo script utilizza la funzione get_win_property per ottenere la larghezza della finestra; con questo dato calcola la posizione dell'angolo in alto a sinistra della finestra sottraendo dalla larghezza del desktop la larghezza della finestra stessa.

[...]

 if [ $xpos == 'RIGHT' ]; then
  width=$( get_win_property "$window" $WIN_W )
  echo "  width: $width" >&2
  (( xpos = $DESKTOP_WIDTH - $width ))
 fi

[...]

Nel caso di un posizionamento relativo al bordo superiore del desktop non è necessario alcun calcolo e l'angolo superiore della finestra viene collocato a 25 pixel dall'alto. Questo numero è del tutto arbitrario e nel caso del mio desktop (Gnome 3 su Linux Mint 12) corrisponde all'altezza della toolbar superiore.

[...]

 if [ $ypos == 'TOP' ]; then
  ypos=25
 fi

 if [ $ypos == 'BOT' ]; then
  height=$( get_win_property "$window" $WIN_H )
  echo "  height: $height" >&2
  (( ypos = $DESKTOP_HEIGHT - $height ))
 fi

[...]

Con il seguente comando la finestra si sposta.

[...]

 wmctrl -r "$window" -e 0,$xpos,$ypos,-1,-1
 sleep 1
}

La funzione resize_win ridimensiona una finestra utilizzando le dimensioni che vengono passate come secondo (larghezza) e terzo parametro (altezza). In questo caso non ci sono calcoli particolari da effettuare.

function resize_win { # window_name w h
 local window=$1
 local width=$2
 local height=$3

 echo "resize_win: $window, $width, $height" >&2

 wmctrl -r "$window" -e 0,-1,-1,$width,$height
 sleep 1
}

La seguente funzione sposta una finestra sul desktop specificato come secondo parametro: i desktop sono individuati da un numero a partire da zero.

function move_to_desktop { # window_name desktop_number
 local window=$1
 local desktop=$2

 echo "move_to_desktop: $window, $desktop" >&2

 wmctrl -r "$window" -t $desktop
 sleep 1
}

Questa funzione chiude una finestra.

function close_win { # window_name
 local window=$1

 wmctrl -c "$window"
}

E, infine, la funzione wait_for_win. Questa funzione deve essere richiamata per attendere la comparsa di una finestra. In altre parole: quando avviate un programma non potete agire immediatamente sulla sua finestra utilizzando le funzioni che abbiamo visto sopra perché se la finestra non è disponibile tali funzioni falliscono e lo script procede oltre; l'effetto finale è che la finestra rimane nel posto in cui si è aperta.
Quindi si utilizza la seguente funzione:

function wait_for_win { # window_name
 local window=$1
 local MAX_WAIT=30 # seconds

 echo "waiting for window: $window" >&2

[...]

Il cuore della funzione è il ciclo while che segue: tale ciclo rimane in attesa che nell'output di wmctrl compaia la riga relativa alla nostra finestra (cercata sempre utilizzando il titolo). Per non entrare in un loop infinito il numero di secondi di attesa non deve superare il valore di MAX_WAIT (definito qui sopra uguale a 30 secondi ma liberamente modificabile).

[...]

 winspec=""
 count=0
 while [ -z "$winspec" -a $count -lt $MAX_WAIT ]; do
  sleep 1
  winspec=$( wmctrl -l -G -p | grep "$window" )
  (( count = count + 1 ))
 done

[...]

Se dopo MAX_WAIT secondi la finestra non è ancora comparsa scrivo un messaggio di errore in standard error e procedo con lo script.

[...]

 if [ $count -eq $MAX_WAIT ]; then
  echo "window not found" >&2
 else
  # I'll wait for 1 second more
  sleep 1
 fi
}


Per comodità riporto qui lo script completo. In fondo ci sono degli esempi d'uso.

#!/bin/bash

# field position in wmctrl's output
WIN_X=4
WIN_Y=5
WIN_W=6
WIN_H=7

# desktop geometry
DESKTOP_GEOMETRY=$( wmctrl -d | grep '^0' | cut -d ' ' -f 5 )
DESKTOP_WIDTH=$( echo "$DESKTOP_GEOMETRY" | cut -d 'x' -f 1 )
DESKTOP_HEIGHT=$( echo "$DESKTOP_GEOMETRY" | cut -d 'x' -f 2 )

# {{{ functions
function get_win_property {  # window_name field_number
 local window=$1
 local field=$2

 echo "get_win_property: $window, $field" >&2

 wmctrl -l -G -p | sed -e 's/  \+/ /g' | grep "$window" | cut -d ' ' -f "$field"
}

function move_win { # window_name x y
 local window=$1
 local xpos=$2
 local ypos=$3

 echo "move_win: $window, $xpos, $ypos" >&2

 if [ $xpos == 'LEFT' ]; then
  xpos=2
 fi

 if [ $xpos == 'RIGHT' ]; then
  width=$( get_win_property "$window" $WIN_W )
  echo "  width: $width" >&2
  (( xpos = $DESKTOP_WIDTH - $width ))
 fi

 if [ $ypos == 'TOP' ]; then
  ypos=25
 fi

 if [ $ypos == 'BOT' ]; then
  height=$( get_win_property "$window" $WIN_H )
  echo "  height: $height" >&2
  (( ypos = $DESKTOP_HEIGHT - $height ))
 fi

 wmctrl -r "$window" -e 0,$xpos,$ypos,-1,-1
 sleep 1
}

function resize_win { # window_name w h
 local window=$1
 local width=$2
 local height=$3

 echo "resize_win: $window, $width, $height" >&2

 wmctrl -r "$window" -e 0,-1,-1,$width,$height
 sleep 1
}

function move_to_desktop { # [window name=""] [desktop number=""]
 local window=$1
 local desktop=$2

 echo "move_to_desktop: $window, $desktop" >&2

 wmctrl -r "$window" -t $desktop
 sleep 1
}

function close_win { # [window name=""]
 local window=$1

 wmctrl -c "$window"
}

function wait_for_win { # [window name=""]
 local window=$1
 local MAX_WAIT=30 # seconds

 echo "waiting for window: $window" >&2

 winspec=""
 count=0
 while [ -z "$winspec" -a $count -lt $MAX_WAIT ]; do
  sleep 1
  winspec=$( wmctrl -l -G -p | grep "$window" )
  (( count = count + 1 ))
 done

 if [ $count -eq $MAX_WAIT ]; then
  echo "window not found" >&2
 else
  # I'll wait for 1 second more
  sleep 1
 fi
}

# }}}

Nello stesso script, dopo la definizione delle funzioni, io ho la sezione che avvia i programmi e utilizza le suddette funzioni per manovrare le loro finestre.

# HERE GO THE PROGRAMS TO START

empathy &
wait_for_win 'Elenco contatti'
move_win 'Elenco contatti' 'RIGHT' 'TOP'
close_win 'Elenco contatti'

thunderbird &
wait_for_win 'Posta'
move_win 'Posta' 47 57

terminator &
wait_for_win 'andrea@aglinux'
move_win 'andrea@aglinux' 119 66
resize_win 'andrea@aglinux' 1395 740
move_to_desktop 'andrea@aglinux' 1

Commenti

Post popolari in questo blog

Nuovo sito "Creare programmando" con una sezione per la didattica

Ho organizzato i miei tutorial, fatti con Scratch o altri linguaggi, in un nuovo sito che si chiama "Creare programmando" e che dovrebbe rendere più agevole lo scarico degli stessi. Questo sito contiene anche una sezione dedicata alla didattica tramite il computer in cui voglio raccogliere idee e tutorial che possono trovare applicazione nelle scuole oltre che nei CoderDojo, magari semplicemente perché forniscono degli spunti agli insegnanti sugli argomenti che si possono sviluppare (nel vero senso della parola) tramite il computer. Ho inaugurato la sezione didattica con un nuovo tutorial in Scratch dedicato alle regioni italiane. Cliccate qui per andarlo a vedere.

Un tutorial software per allenarsi all'uso dei sensori hardware

Durante l'ultima sessione di CoderDojo MXP abbiamo proposto ai ragazzi un tutorial che ha lo scopo di impratichirli nei ragionamenti che si rendono necessari quanto ci si trova a dover impostare la traiettoria di una macchinina utilizzando solo un sensore in grado di rilevare il colore della traccia visibile sul pavimento (in scala di grigi). Non avendo (ancora) a disposizione macchinine e sensori "reali" abbiamo iniziato a simulare la situazione disegnando una pista con Scratch utilizzando quattro diversi colori. Il compito dei ragazzi era di riuscire a fare in modo che lo sprite dell'automobilina seguisse la pista senza uscire di strada: le correzioni alla traiettoria avvenivano utilizzando il blocco "sta toccando il colore..." come se ci fosse un sensore in grado di vedere il colore della traccia sotto all'automobile. E se ne sono viste di tutti i colori perché il compito non era facile. Alla fine, per stimolare un po' di competizione tr

Sed: caratteri speciali che si possono usare nel lato destro delle sostituzioni

Il titolo è lungo, ma serve semplicemente per dire che nelle sostituzioni tramite la sed , ovvero quelle realizzate con il comando s/// , è possibile utilizzare alcune sequenze di escape oltre ai soliti backreference dei gruppi "catturati" con le parentesi nell'espressione regolare di sinistra. Nella parte destra dell'espressione (cioè quella dopo la seconda barra /) si possono usare: - \L per trasformare in minuscolo tutto  quello che segue (la L sta per "lowercase"); - \l (è una elle minuscola) per trasformare in minuscolo solo il carattere seguente; - \U per trasformare in maiuscolo tutto  quello che segue (la U sta per "uppercase"); - \u per traformare in maiuscolo solo il carattere seguente; - \E per marcare la fine della trasformazione dei caratteri. Gli "operatori" \L e \U agiscono fino alla fine dell'espressione (delimitata dalla barra / finale) oppure fino alla \E successiva. Quindi, per esempio, per trasformare sol