Deblan blog

Tag #i3wm

[Astuce] i3wm : liste des processus les plus actifs

Quand j'utilisais conky, l'un de mes objectifs était de connaître la liste des processus les plus actifs. En effet, il n'était pas rare (et ça continue aujourd'hui) qu'un programme s'affole un peu en mangeant un peu trop de ressources. Mon passage à i3-wm m'a légitiment fait abandonner conky puisque j'ai toujours une fenêtre affichée en plein écran. Du coup, j'ai décidé d'ajouter une barre qui s'affiche uniquement quand j’appuie sur une touche.

Voici un aperçu : i3-wm : barre de processus

Le code source du script est en shell :

#!/bin/sh                                          

echo "{\"version\":1}"

INFO='#E9F1FF'
NORMAL='#B3FF6C'
WARNING='#FF6836'
CRITICAL='#FF474A'

echo "[[]"

while true; do
	(
		echo -n ",["
		
		ps ux --sort -%cpu | head -n 30 | sed 's/\s\{2,\}/ /g' | cut -d" " -f3,11 | uniq | grep -v uniq | egrep -v '^0.0' | egrep '^[0-9.]* [a-z]' | while read cpu bin; do
			if [ $(echo "if (${cpu} < 3 ) 1 else 0" | bc) -eq 1 ]; then
				COLOR=$INFO
			elif [ $(echo "if (${cpu} < 40.0 ) 1 else 0" | bc) -eq 1 ]; then
				COLOR=$NORMAL
			elif [ $(echo "if (${cpu} < 70.0 ) 1 else 0" | bc) -eq 1 ]; then
				COLOR=$WARNING
			else
				COLOR=$CRITICAL
			fi
			echo -n "{\"full_text\": \" $bin ($cpu%) \", \"color\": \"$COLOR\"}," 
		done
		
		echo "]"
	) | sed 's/,\]/]/'
		
	sleep 5
done

Au niveau de la conf de i3, voila comment j'ai procédé :

bar {
    status_command /chemin/vers/le/script
    workspace_buttons yes
    position top
    tray_output none
    mode hide
    modifier mod1

    /* d'autres confs */    
}

That's all folks!


[Astuce] i3wm : Switcher entre les fenêtres d'un container

Le script que je vais vous donné a été écrit il y a quelques mois déjà. Il permet de switcher entre les fenêtres* d'un container. Si dans un container j'ai 3 fenêtres, alors je pourrai focus la première via alt+F2, la seconde via alt+F2 et la dernière via alt+F3. Si un container contient un autre container, le script détectera quel est celui focused et quelles fenêtres lui appartiennent.

Si des fenêtres et des containers appartiennent au même container, alors chacun des containers sera interprété comme une fenêtre.

* : une fenêtre est une application (terminal, navigateur, client mail, etc.)

Le script est une nouvelle fois écrit en PHP et les binds gèrent 6 windows. Pour pouvez bien sûr en ajouter.

Dans ~/bin/i3_switch_focus_container.php (ne pas oublier de faire un chmod u+x dessus) :

#!/usr/bin/php5
<?php

function getTree()
{
    return json_decode(shell_exec('i3-msg -t get_tree'));
}

function getFocusedWindowContainer($tree)
{
    if (empty($tree->nodes)) {
        return null;
    }

    foreach ($tree->nodes as $k => $container) {
        if (true === $container->focused) {
            return $tree->nodes;
        }

        if (null !== $container = getFocusedWindowContainer($container)) {
            return $container;
        }
    }

    return null;
}

function getFocusWindowNumber($default, $max)
{
    global $argv;

    if (!isset($argv[1])) {
        $argv[1] = $default;
    }

    return min($max, max(1, $argv[1])) - 1;
}

function getDefaultFocusWindowNumber($container)
{
    foreach ($container as $k => $v) {
        if ($v->focused) {
            return $k + 1;
        }
    }
}

function setFocusToWindow($window)
{
	shell_exec(sprintf("i3-msg '[con_id=\"%s\"] focus'", $window->id));
}

$container = getFocusedWindowContainer(getTree());

if ($container !== null) {
    $number = getFocusWindowNumber(getDefaultFocusWindowNumber($container), count($container));
    
	foreach ($container as $k => $window) {
        if ($k == $number) {
            setFocusToWindow($window);
        }
    }
}

Dans votre fichier de configuration i3 :

bindsym $mod+F1 exec ~/bin/i3_switch_focus_container.php 1
bindsym $mod+F2 exec ~/bin/i3_switch_focus_container.php 2
bindsym $mod+F3 exec ~/bin/i3_switch_focus_container.php 3
bindsym $mod+F4 exec ~/bin/i3_switch_focus_container.php 4
bindsym $mod+F5 exec ~/bin/i3_switch_focus_container.php 5
bindsym $mod+F6 exec ~/bin/i3_switch_focus_container.php 6

[Astuce] i3wm : Switcher entre 2 moniteurs

On est capable de déplacer le focus vers un workspace donné mais je n'ai pas encore trouver comment basculer d'un moniteur à l'autre dans i3 (sans indiquer le nom du workspace cible).

J'ai écrit un script qui reçoit deux paramètres : la largeur (en pixels) du premier moniteur et celle du second. Par le biais de xdotool, je récupère la position en X du pointeur et je déduis le moniteur à atteindre.

Note : il est nécessaire d'installer xdotool et bc.

#!/bin/sh

MONITOR1_WIDTH=$1
MONITOR2_WIDTH=$2

getMouseX() {
	xdotool getmouselocation 2>/dev/null | awk '{ print $1 }' | cut -d: -f2
}

getMouveNextX() {
	if [ $(getMouseX) -lt $MONITOR1_WIDTH ]; then
		echo "$MONITOR1_WIDTH+$MONITOR2_WIDTH/2" | bc
	else
		echo "$MONITOR1_WIDTH/2" | bc
	fi
}

moveTo() {
	xdotool mousemove $1 540
}

moveTo $(getMouveNextX)

Il suffit maintenant de mapper une combinaison de touches pour exécuter le script. Par exemple :

bindsym $mod+c exec ~/bin/i3_switch_monitor.sh 1920 1920


[Astuce] i3wm : traverser les workspaces

Je suis devenu un vrai fan du gestionnaire de fenêtres i3wm et pour cause, il est ultra simple à scripter :)

Jusqu'à aujourd'hui une fonctionnalité majeure me manquait : pouvoir naviguer vers le workspace qui précède ou qui suit celui courant. On peut switcher entre les deux derniers workspaces qui ont été focused ou se déplacer directement vers un workspace (pour autant qu'un mapping soit prévu).

Pour arriver à combler ce manque, j'ai utilisé i3-msg qui permet de récupérer des informations sur i3wm et de lui donner des instructions. Je script en php car il faut lire du json...et en shell c'est compliqué !

L'algo est simple :

  • je récupère les workspaces avec : i3-msg -t get_workspaces
  • je les ordonne par ordre alphabétique (c'est comme ça que i3 les affiche)
  • je récupère le workspace courant
  • suivant ce qu'on demande, je retourne le workspace qui précède ou suit le workspace courant
  • je demande à i3 de switcher vers le workspace via : i3-msg 'workspace "le nom du workspace cible"'

Le script est disponible ici. Placez-le où vous voulez et renseignez un mapping tel que celui-ci (dans ~/.i3/config) :

bindsym $mod+p exec ~/bin/i3_switch_workspace.php previous
bindsym $mod+n exec ~/bin/i3_switch_workspace.php next

Note : dans ce mapping, mes raccourcis sont window+P pour aller au workspace précédant et window+N pour aller au suivant. Mon script se trouve dans ~/bin/ et le fichier est nommé i3_switch_workspace.php.

N'oubliez pas de rendre le script exécutable (chmod +x).


i3wm - menu d'applications

i3wm est un Tiling Window Manager, c'est à dire que l'ensemble des fenêtres de mon environnement de travail est géré de telle sorte que l'espace soit le plus optimisé possible. Dans ce type de configuration (et quelque soit le TWM), c'est au clavier que tout se pilote. Le problème c'est qu'au fond d'un lit, sous une couette, c'est chiant de faire des raccourcis claviers.

Avant de passer à i3wm, j'utilisais Fvwm-Crystal qui possédait un menu d'application pour lancer les logiciels à la souris. i3wm n'en dispose pas et ça me gène.

Pour répondre à cette problématique, il existe un outil qui se nomme SSHPlus. C'est un script écrit en python qui permet de générer un menu comme celui ci.

Manuberro de Absolacom a rédigé un excellent article qui explique comment configurer SSHPlus. Cependant, je suis un gros flemmard et j'ai envie générer automatiquement le menu d'applications. Pour ce faire, j'ai écrit un script PHP qui va piocher dans le répertoire /usr/share/menu/ de ma Debian et qui sort une conf au format SSHPlus.

Il suffira donc de lancer le script et de rediriger la sortie standard vers ~/.sshplus.

$ /chemin/vers/script.php > ~/.sshplus

Voici le script (vous pouvez modifier les fonctions prepend_config() et append_config() pour insérer des configurations spéciales) :

#!/usr/bin/env php
<?php

function append_config()
{
    $config = <<<EOF
sep
folder:Session
Éteindre|sudo|shutdown -h now
Reboot|sudo|shutdown -r now
folder:
EOF;

    return sprintf("%s\n", $config);
}

function prepend_config()
{
    $config = <<<EOF
EOF;

    return sprintf("%s\n", $config);
}

function is_x11_application($setting)
{
    return 0 !== preg_match('`needs="X11"`i', $setting);
}

function get_title($setting)
{
    return preg_replace('`.*title="([^"]+)".*`is', '$1', $setting);
}

function get_sections($setting)
{
    return explode('/', preg_replace('`.*section="([^"]+)".*`is', '$1', $setting));
}

function get_command($setting)
{
    return preg_replace('`.*command="([^"]+)".*`is', '$1', $setting);
}

function get_info($setting)
{
    return array(
        'title'    => get_title($setting),
        'command'  => get_command($setting),
        'sections' => get_sections($setting)
    );
}

function cleanup_config($config)
{
    return preg_replace("`folder:[^\n]+\nfolder:\n`", '', $config);
}

function get_config($menus)
{
    $config = '';

    foreach ($menus as $k => $menu) {
        if (is_array($menu)) {
            if (!empty($menu)) {
                $config.= sprintf("folder:%s\n%sfolder:\n", $k, get_config($menu));
            }
        } else {
            $config.= sprintf("%s\n", $menu);
        }
    }

    return cleanup_config($config);
}

$setting_files = glob('/usr/share/menu/*');
$menus         = array();
$apps          = array();
$final_menus   = array();

foreach ($setting_files as $k => $file) {
    $_settings = explode('package', file_get_contents($file));

    foreach ($_settings as $setting) {
        if (is_x11_application($setting)) {
            $info     = get_info($setting);
            $sections = $info['sections'];

            if (!in_array($sections, $menus)) {
                $menus[] = $sections;
            }

            $apps[] = $info;
        }
    }
}

foreach ($menus as $sections) {
    foreach ($sections as $k => $section) {
        if (!$k && !isset($final_menus[$section])) {
            $final_menus[$section] = array();
        }

        if ($k && !isset($final_menus[$sections[$k-1]][$section])) {
            $final_menus[$sections[$k-1]][$section] = array();
        }
    }
}

foreach ($apps as $app) {
    $eva = '$final_menus';

    foreach ($app['sections'] as $s) {
        $eva.= '["'.addslashes($s).'"]';
    }

    $full_command = explode(' ', addslashes($app['command']));
    $command = array_shift($full_command);
    $params  = implode(' ', $full_command);

    $eva.= '[] = "'.$app['title'].'|'.$command.'|'.$params.'";';

    eval($eva);
}

$config = prepend_config().get_config($final_menus).append_config();

echo $config;

J'admet que le code n'est pas très propre mais il fonctionne, c'est l'essentiel :)

Pour lancer SSHPlus au démarrage de i3wm, il suffira de placer dans ~/.i3/config cette ligne : exec /chemin/vers/sshplus.py (n'oubliez pas de faire un chmod +x sur le fichier et vous devrez disposer du paquet python-appindicator).