Cet article est paru dans le magazine
francophone consacré à Linux et aux
logiciels libres n°36 (février 2002).

©2002
Editions Diamond


Construire un bureau 100% Java

Cette série a pour but de montrer la possibilité de construire un environnement utilisateur entièrement en Java/Swing au dessus de Linux et XFree. Le premier article décrit le projet, la gestion des fenêtres et le lancement des applications.

Objectif

La disponibilité d'un tel bureau présente de nombreux avantages:

Mise en place

Le serveur X

Il n'existe pas à ce jour de MVJ capable d'utiliser la SVGAlib ou le FrameBuffer. Il serait d'ailleurs intéressant de lancer le portage de Kaffe au dessus de DirectFB. La disponibilité d'un serveur X est donc indispensable pour permettre l'affichage graphique. Dans un premier temps, il est préférable de ne pas perturber le serveur X principal. On utilise d'abord un serveur secondaire comme Xnest qui définit un nouveau serveur X limité à une fenêtre.

Xnest -ac :2
export DISPLAY=:2
java <votre-application>

Dés que tout fonctionne correctement, on peut utiliser un nouveau serveur X, lancé en mode utilisateur. La commande switchto (qui nécessite l'accés en écriture à /dev/console) permet de passer du serveur principal au nouveau (équivalent à Ctrl-Alt-F8). Par ailleurs, on récupère le numéro du process du serveur X pour pouvoir l'arrêter.

#!/bin/sh
export DISPLAY=:1
XOPTS="-ac $DISPLAY"
X $XOPTS &
sleep 1
XP=`ps awux | grep "/etc/X11/X $XOPTS" | \
    grep -v grep | awk '{ print($2); }' `
echo "Lancement du bureau $XP sur $DISPLAY"

CLASSPATH=./classes
for j in ./extensions/*.jar
do
  CLASSPATH=$CLASSPATH:$j
done

export CLASSPATH
/usr/java/jdk1.3.1_01/bin/java -mx256M com.jdistro.Korte $*

#chmod +s /usr/bin/switchto
/usr/bin/switchto 7

if [ "$XP" != "" ]; then
  echo "Arrêt du bureau $XP sur $DISPLAY"
  sync; sync
  kill -3 $XP
fi

Il existe aussi un serveur X écrit en Java (et libre) [1], une solution envisageable pour afficher sur notre bureau les applications natives.

Le bureau (Körte)

Il s'agit d'une fenêtre sans bordure occupant tout l'écran (JWindow). C'est le seul élément natif. Elle contient un composant JDesktopPane chargé d'afficher les fenêtres des applications. Il n'y a pas besoin de windows manager natif car les fenêtres seront internes et gérées par le DesktopManager du JDesktopPane.

JDesktopPane d=new JDesktopPane();
JWindow      w=new JWindow();
Dimension    e=w.getToolkit().getScreenSize();
w.setSize(e.width,e.height);
w.setLocation(0,0);
w.setContentPane(d);
w.show();

On ajoutera par la suite le support des fenêtres sans bordures, des applets, du menu général, de la sélection des icônes... ainsi que des écrans virtuels et tout ce qui rend un bureau pratique.

Le lanceur (Wharf)

Similaire au panneau de Gnome ou de KDE, il regroupe des boutons pour lancer les applications principales, un menu pour les autres ainsi que de petits composants actifs (horloge, moniteur de mémoire, ...). Il s'agit simplement d'un JPanel avec un JMenu, des JButton et des composants dérivant de JComponent.

Exécution

Lancement d'une application

Pour exécuter une application, il suffit d'appeler sa méthode main(). Toutefois, pour assurer une certaine souplesse, cet appel doit être fait par réflexion.

import java.lang.reflect.*;
Class   c=Class.forName("mon.appli");
Class[] p=new Class[] { String[].class };
Method  m=c.getMethod("main",p);
m.invoke(null,new Object[0]);

Par défaut, les classes restent en mémoire jusqu'à l'arrêt de la MVJ. Pour assurer la libération de la mémoire, il est préférable d'utiliser un ClassLoader. Plutôt que Class.forName(), on écrira:

ClassLoader l=new WharfClassLoader();
Class       c=l.loadClass("mon.appli");
l=null;

Arrêt d'une application

Bien souvent, le développeur suppose que son application sera la seule à tourner dans la MVJ. Lorsque l'utilisateur souhaite la quitter, un appel explicite demande l'arrêt de la MVJ - typiquement System.exit(0);. Dans notre cas, ceci est très gênant car cela provoque l'arrêt non seulement de l'application en cours mais aussi de toutes les autres ainsi que du bureau. Heureusement, cet appel est soumis au veto du SecurityManager. En le redéfinissant, on évite ce comportement.

public class WharfSecurityManager
  extends SecurityManager
{
  public void checkExit(int status)
  {
    throw new SecurityException();
  }
}

Aspiration

La plupart des applications Java sont supposées tourner dans une fenêtre native (JFrame). Pour la faire apparaître sur notre bureau, nous devons la transformer en fenêtre interne (JInternalFrame). Pour cela, il existe deux possibilités: modifier la hiérarchie ou déléguer.

La première consiste à changer toutes les références à JFrame par des références à JInternalFrame. Cette méthode ne fonctionne pas dans tous les cas, car la nature même de l'objet est changé.

La seconde consiste à créer une classe dérivant de JFrame et déléguant tous ses appels à une JInternalFrame. La nature de la classe n'est pas modifiée mais il y a parfois quelques problèmes avec les logiciels s'appuyant sur l'arbre des composants (getTopLevelAncestor(), getParent(), ...) pour retrouver un élément (ce qui est à mon avis une erreur de conception).

Dans tous les cas, nous allons changer notre ClassLoader pour qu'il modifie le bytecode et remplace partout javax.swing.JFrame par com.jdistro.KFrame. Il est important que les noms complets aient la même taille, à savoir 18, pour ne pas perturber l'organisation interne de la classe.

protected Class findClass(String _name)
{
  FileInputStream in=new
    FileInputStream
    (_name.replace('.','/')+".class");
  ByteArrayOutputStream out=new
    ByteArrayOutputStream();
  while(in.available()>0)
    out.write(in.read());
  byte[] data=out.toByteArray();
  replace(data,
        "javax/swing/JFrame".getBytes(),
        "com/jdistro/KFrame".getBytes());
  return defineClass(data,0,data.length);
}
private void replace
  (byte[] _data, byte[] _key, byte[] _new)
{
  int i,j;

  for(i=0;i<_data.length;i++)
  {
    for(j=0;j<_key.length;j++)
      if(  (i+j>=_data.length)
         ||(_data[i+j]!=_key[j])) break;

    if(j==_key.length)
    {
      for(j=0;j<_new.length;j++)
        _data[i+j]=_new[j];
      i+=Math.max(_key.length,_new.length);
    }
  }
}

Des projets libres

Je voudrais présenter ici quelques projets très intéressants, complémentaires ou similaires à celui décrit dans cette article.

Echidna [2]

Echidna est une bibliothèque permettant la gestion de processus multiples au sein d'une même MVJ et distribuée sous les termes de la GNU LGPL. Robuste, elle pourra servir de base à la gestion des processus grâce à sa gestion de classpath par application.

Jsh [3]

Il s'agit d'une surcouche à Echidna fournissant une ligne de commande et les commandes classiques (cd, ls, ps, ...) fournies par la bibliothèque gnutools.

Jesktop [4]

Il s'agit du plus avancé des projets de bureau. Il est disponible sous licence BSD et repose sur l'architecture Avalon [5] de la fondation Apache. Son inconvénient principal est la nécessité de modifier les applications pour respecter une interface particulière, ce qui est éviter dans Körte.

Pour conclure, le bureau (Körte) et le lanceur (Wharf) présentés dans cet article sont disponibles sur le site www.jdistro.com sous les termes de la GNU GPL2. Ils ont été testés avec plusieurs applications mais restent en phase de développement.

____
Guillaume Desnoix
Août 2001 révisé Janvier 2002
RÉFÉRENCES
  1. WeirdX, www.jcraft.com/weirdx/
  2. Echidna, www.javagroup.org/echidna/
  3. Jshell, perso.club-internet.fr/collin_g/jsh/
  4. Jesktop, www.jesktop.org
  5. Avalon, jakarta.apache.org/avalon/
Ring
Pixels Million Dollar Desktop, vacation Thailand, sao paulo, ahvoc, A Kid , ...