miércoles, octubre 11, 2006

Abrir desde Java aplicación asociada por defecto para un fichero o URL (en Windows/Linux/MacOS)

Cuando programas aplicaciones para entornos de escritorio es común tener que abrir aplicaciones externas que estén asociadas con un determinado tipo de archivos. Es muy común tener que abrir una página web en el navegador, una imagen en el visor de imágenes del sistema operativo, etc. También es una situación bastante común lanzar el reproductor de audio/video del sistema operativo para no depender del JMF para reproducir un archivo multimedia (si alguien ha sufrido el JMF entenderá lo que estoy diciendo).

Esta tarea que a priori debería ser sencilla, en la realidad es una auténtica pesadilla en Java. El problema es que no existe ningún mecanismo estándar para averiguar cual es la aplicación asociada para un determinado tipo de archivo, por lo que normalmente hay que diseñar una solución muy específica que suele hacer que esa aplicación dependa de un programa externo vinculado a un sistema operativo.

La cosa se vuelve más complicada si pretendemos que esta solución sea multiplataforma. No existen muchos proyectos que ofrezcan soluciones generales para este problema, el único que ofrece una solución multiplataforma para este problema es jdic. Este proyecto usa JNI y depende de librerías nativas, lo que complica bastante el despliegue de la aplicación, por lo que a veces no es posible usarlo.

En entornos windows parece bastante arrancar la aplicación asociada a un archivo o URL, ya que existe el comando start que permite arrancar la aplicación asociada a un fichero o url. La única dificultad radica en diferenciar entre Windows 95 y las versiones actuales de Windows para ejecutar el intérprete de comandos adecuado. Existen otras soluciones más elaboradas, como puede ser averigual cuál es la aplicación asociada en el registro, pero creo que simplemente añaden complejidad al asunto y no aportan ninguna mejora.

En el caso de Linux la cosa se complica bastante, ya que no existe ningún registro centralizado de los programas asociados a un tipo de archivo o URL, por lo que cada entorno de escritorio ha solucionado este problema de formas distintas. Existe un subproyecto de FreeDesktop denominado Portland, que dentro de su paquete de utilidades xdg-utils incluye la aplicación xdg-open que permite solucionar el problema en Linux. Actualmente no está integrado dentro de todas las distribuciones, pero cuando lo esté xdg-open será un equivalente en Linux a su homólogo start en Windows.

Actualmente y a la espera de que se incluya el paguete xdg-utils en todas las distribuciones, la mejor opción es intentar detectar el entorno de escritorio que en el que se está ejecutando nuestra aplicación en Java, y en función de esto usar el programa específico para ese entorno de escritorio que nos proporcione la aplicación asociada. En el caso de GNOME tenemos la aplicación gnome-open y en el caso de KDE tenemos kmfclient.

Podemos encontrar un resumen de las opciones disponibles en Windows y Linux en este documento. También podemos ver ejemplos de uso de estas aplicaciones en Java en [1] y [2].

Con toda esta información he intentado crear una clase que recoge todas las ideas antes expuestas y que pretende solucionar el problema en el abanico más amplio de plataformas posible sin depender de librerías o programas externos.

Me gustaría compartir este código (que es tan sólo una primera versión) y que me comentaseis qué os parece y qué cosas cambiaríais/mejoraríais.


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.File;

public class ApplicationLauncher {

public static void main(String args[]){
if(args.length!=1) {
System.out.println("Número de parámetros erróneo forma de uso: java -cp . ApplicationLauncher fichero|URL");
return;
}
if(args[0].indexOf("http")!=-1) launchURL(args[0]);
else launchDefaultViewer(args[0]);
}

private static String linuxDesktop = null;

private static String getEnv(String envvar){
try{
Process p = Runtime.getRuntime().exec("/bin/sh echo $"+envvar);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String value = br.readLine();
if(value==null) return "";
else return value.trim();
}
catch(Exception error){
return "";
}
}

private static String getLinuxDesktop(){
//sólo se averigua el entorno de escritorio una vez, después se almacena en la variable estática
if(linuxDesktop!=null) return linuxDesktop;
if(!getEnv("KDE_FULL_SESSION").equals("") || !getEnv("KDE_MULTIHEAD").equals("")){
linuxDesktop="kde";
}
else if(!getEnv("GNOME_DESKTOP_SESSION_ID").equals("") || !getEnv("GNOME_KEYRING_SOCKET").equals("")){
linuxDesktop="gnome";
}
else linuxDesktop="";

return linuxDesktop;
}

public static Process launchURL(String url){
try{
if (System.getProperty("os.name").toUpperCase().indexOf("95") != -1)
return Runtime.getRuntime().exec( new String[]{"command.com", "/C", "start", url} );
if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") != -1)
return Runtime.getRuntime().exec( new String[]{"cmd.exe", "/C", "start", url} );
if (System.getProperty("os.name").toUpperCase().indexOf("MAC") != -1)
return Runtime.getRuntime().exec( new String[]{"open", url} );
if (System.getProperty("os.name").toUpperCase().indexOf("LINUX") != -1 ) {
if(getLinuxDesktop().equals("kde"))
return Runtime.getRuntime().exec( new String[]{"kfmclient", "exec", url} );
else
return Runtime.getRuntime().exec( new String[]{"gnome-open", url} );
}
}
catch(IOException ioex){System.out.println(ioex);}

return null;
}

public static Process launchDefaultViewer(String filepath){
return launchURL( new File(filepath).getAbsolutePath());
}

}

Del.icio.us Meneame