Tal vez compartir físicamente un ordenador …

27 Abr

… no sea tan malo como suena.

Como mencioné en esta otra entrada, en mi trabajo me han encargado que sitúe una máquina en la mesa de pedidos para que sea utilizada por tres personas -como mínimo- y más o menos al mismo tiempo.

El uso compartido es relativo: todos deben poder acceder a una aplicación web con su usuario correspondiente pero el acceso físico a la máquina tiene que simplificarse todo lo posible.

Así que tenemos un sistema Linux que debe:

  1. Arrancar el entorno gráfico automáticamente y sin contraseña.
  2. Lanzar un navegador web para cada uno de los tres operarios fijos con su correspondiente perfil.
  3. Permitir el cambio de operador sin necesitar contraseñas.

Parece más una cuestión de recursos que de técnica, pero voy a ver de cerca cómo podemos ir cumpliendo los requerimientos.

Arrancar el entorno gráfico automáticamente

En el escritorio corporativo se emplea el programa slim para dar acceso al sistema por lo que la primera opción consiste en configurarlo para que acceda automáticamente al sistema.

Según la documentación que he encontrado basta con cambiar la configuración del programa, situada en /etc/slim.conf y añadir lo siguiente:

# default user, leave blank or remove this line
# for avoid pre-loading the username.
default_user        almacen

# Automatically login the default user (without entering
# the password. Set to "yes" to enable this feature
auto_login          yes

Así pues ya tendríamos una cuenta real del sistema (almacen) que inicia sesión según arranca el sistema.

Un navegador web para cada operario

El escritorio corporativo que mencioné antes incluye un montón de ejecutables lanzados tras el arranque y está pensando para dar al operador bastante libertad en cuanto a programas que usa, pero no para que el sistema funcione en modo quiosco.

Afortunadamente invertí mucho tiempo en su momento para hacer que el escritorio fuese muy flexible. Elegí fluxbox ya que permite construir su configuración con archivos de texto plano y es ligero y muy manejable.

Al final su funcionamiento se reduce a, una vez que lanza ajustes de teclado, un tema y un componente VNC para acceso remoto al escritorio, invocar un simple script de usuario para ejecutar lo que se quiera. El servidor X ya está inicializado y sólo falta el gestor de ventanas en sí, que va a continuación.

Para lograr que cada operario tenga su propia sesión de navegador voy a utilizar los perfiles de Firefox.

Esto es, creo un perfil por cada usuario web y en el arranque los lanzo situándolos en escritorios virtuales diferentes. Los archivos de la cuenta local almacen quedan de la siguiente forma:

$HOME/.mozilla/
└── firefox
    ├── 81k5pnyr.maxi
    ├── p9vfybia.dani
    ├── profiles.ini
    └── smm9rd24.miguel

Y en este punto tengo varias opciones para hacer lo que pretendo:

  1. Emplear el mecanismo apps de fluxbox.
  2. Usar el programa devilspie.
  3. Emplear el programa wmctrl.

El mecanismo apps de fluxbox no está mal pensando. En el archivo $HOME/.fluxbox/apps conserva la configuración de ventanas con un mecanismo de selección y la posibilidad de designar posición, tamaño, escritorio y otras cosas más. No es muy cómodo eso sí, y en mi experiencia he visto como es fácilmente manipulable por el usuario con un par de toques de ratón. Tendría que conservar una copia para reemplazarla o intervenir directamente en la sesión gráfica.

Los programas devilspie y wmctrl funcionan de manera similar. Se emplean desde un script normal, lanzando primero las ventanas y luego organizándolas. Una buena explicación está en esta respuesta en un foro de xfce.

La diferencia entre los dos estriba en su forma de seleccionar ventanas y su gestión posterior de las mismas, además de que devilspie añade un servicio (daemon) y wmctrl no.

Por mi parte he escrito un pequeño script en Perl para realizar justamente esa parte.

#!/usr/bin/perl

use Modern::Perl;
use Getopt::Long;
use Pod::Usage;
use POSIX qw(setsid);
use User::pwent;
 
use X11::WMCtrl;
use UI::Dialog;

#   Variables   
my  $VERSION        =   0.3;
my  $help           =   0;
my  $verbose        =   0;
my  $exit_code      =   0;
my  $web_browser    =   q(firefox);
my  $sleep          =   2;

#   Entrada de parámetros
GetOptions( 'help:s'    =>  \$help,
            'verbose'   =>  \$verbose,
            'quiet'     =>  sub { $verbose = 0; },
            'browser:s' =>  \$web_browser,
            'sleep:i'   =>  \$sleep,
    ) or pod2usage ( 1 );
pod2usage( 2 ) if $help;

# Inicialización 
our $ui = UI::Dialog->new( title => 'Navegadores', 
                order => [ qw(zenity xdialog) ] );
our $wmctrl = X11::WMCtrl->new();

our $desktop = 0;

foreach my $name (@ARGV) {
    # Obtenemos información 
    my $ent = getpwnam( $name );
    my $fullname = $name;

    if (not $ent) {
        $ui->msgbox(title => 'error', 
            text => "El usuario ${name} no existe" );
        next;
    } 
    else {
        ($fullname) = split /\s*,\s*/, $ent->gecos;
    }

    # Lanzamos el navegador con el perfil indicado
    if (my $ret = launch_app( program   => $web_browser,
                              args      => [
                                '--new-instance',
                                "-P $name",
                                "--url https://erp.enexma.net",
                              ],
                              title     => $fullname,
                              desktop   => $desktop ) ne 'OK') {
        _show_error($name,$ret);
        next
    }
    else {
        $desktop++;
    }
} # foreach

# Fin del programa 
exit $exit_code;

sub _show_error {
    my  $name       =   shift;
    my  $errcode    =   shift;
    my  $msg        =   "";

    if ($errcode eq "FORK") {
        $msg = "No he podido ejecutar el navegador";
    }
    elsif ($errcode eq 'PID') {
        $msg = "No he encontrado el identificador de la ventana del navegador";
    }

    $ui->msgbox( title => 'error', text => sprintf("Error en perfil %s: %s",
            $name, $msg ));

    return;
}

sub launch_app {
    my  %params =   (
        program     =>  undef,
        args        =>  [],
        title       =>  undef,
        desktop     =>  0,
        @_ );
    my  $error      =   "OK";

    my $child_pid = fork();

    if (not defined $child_pid) {
        $error = "FORK";
    }
    elsif ($child_pid == 0) {
        # Somos el proceso hijo
        POSIX::setsid();
        my $cmd = join(" ",$params{program}, @{$params{args}});
        exec $cmd;
    }
    else {
        # Esperamos unos segundos 
        sleep($sleep) if $sleep;

        # e intentamos localizar el id de la ventana para poder modificarla
        if (my $winpid = search_window_from_pid( $wmctrl->wmctrl('-lp'),
                $child_pid )) {
            # Situamos la ventana en un escritorio
            $wmctrl->wmctrl('-i', '-r', "${winpid}", '-t', $params{desktop});
            
            # cambiamos el título 
            my $longtitle = sprintf("'%s'",$params{title});
            $wmctrl->wmctrl('-i', '-r', "${winpid}", '-N', $longtitle );

            # y la ponemos a pantalla completa
            $wmctrl->wmctrl('-i', '-r', "${winpid}", '-b', 'toggle,fullscreen');
        }
        else {
            $error = "PID";
        }
    }

    return $error;
}

sub search_window_from_pid {
    my  $data   =   shift;
    my  $pid    =   shift;

    foreach my $line (split(/\n/,$data)) {
        my ($id,$workspace,$p_id) = split(/ +/,$line,4);

        if ($p_id == $pid) {
            return $id;
        }
    }

    return undef;
}

Empleo un módulo Perl llamado X11::WMCtrl bastante decepcionante y algo de UI::Dialog para que los avisos de error sean más visuales.

Por si hay dudas lo que el programa hace es lo siguiente:

  1. Por cada una de las cuentas de usuario que se le proporcionan como parámetros:
    1. Busca su nombre completo.
    2. Crea un proceso hijo (fork):
      1. Independiza la sesión
      2. Lanza el navegador
    3. En el proceso padre:
      1. Espera unos segundos a que aparezca la ventana.
      2. Busca el identificador de la ventana según el ID del proceso.
      3. Modifica la ventana:
        1. La sitúa en un escritorio concreto.
        2. Cambia el título por el nombre completo del usuario.
        3. Redimensiona a pantalla completa.

Y funciona. Más o menos, porque existe cierta dependencia del tiempo en el que tarda en aparecer la ventana para poder buscar su PID en la lista y eso puede hacer que la abra y aparezca un mensaje de error al mismo tiempo.

Y como tiempo no tengo no puedo hacerlo más sólido. Tendrá que servir así.

Queda pendiente

Ver cómo de cómodo es su uso. Aunque no pueden emplearlo al mismo tiempo en el fondo están utilizando un mismo usuario de sistema (la cuenta almacen) así que espero dificultades al menos en:

  1. Impresión de trabajos.
  2. Guarda de documentos descargados.
  3. Cierre de sesión y apagado.