Blog cerrado
Mmmmhh... bueno, como dijo el profeta, "todo lo que empieza acaba", y a este Blog le ha llegado su fin.
¿Por qué? Básicamente este blog era una mezcla de historias al buen "tuntún" de cosas dispares que iban pasando por mi cabeza, y creo que se me han acabado las ocurrencias. Me ha pasado algo que los psicólogos probablemente llamen el efecto "Forrest Gump", es decir, que "no tengo nada más que decir sobre este asunto".
Por otra parte mi inventiva humorística interna cada vez me resulta más agria y menos sutil, lo cual me está produciendo una acidez de estómago difícil de soportar, y que probablemente termine en úlcera si oso a transcribir los pensamientos en nuevas epístolas.
Así que no sé, cuando me apetezca haré otro blog distinto, qué sé yo, sobre alguna materia en contreto, por ejemplo la pesca de la trucha con sandalias o el arte de tocar la zambomba siempre son asuntos interesantes a los que nunca he dedicado tiempo.
En fin, gracias a la "audiencia". Hasta la próxima.
Por: danicafe # 12-03-08 (03:22:10) # URL # Tb () # Comentarios (3) # Cuentos
Cuestiones internas de Gambas (1)
Descripción del mecanismo de funcionamiento de los componentes en Gambas (1)Introducción
Gambas2 puede trabajar con componentes compilados o bien interpretados escritos en Gambas. En este documento se detalla el primer caso, no el segundo, así pues trata acerca de los componentes generalmente escritos en C y C++, si bien se podrían desarrollar en otros lenguajes capaces de generar librerías de enlace dinámico, como es el caso de FreePascal.
Los componentes son librerías
Los componentes escritos en C y C++ son librerías de enlace dinámico (en Linux tienen extensión ".so"), y el sistema de componentes sigue la filosofía de cualquier sistema tradicional de plugins, que consta de dos partes:
- a) La aplicación principal, en este caso Gambas2, oferta una API que pone a disposición del plugin.
- b) El plugin oferta a su vez nuevas capacidades al programa principal, lo que en Gambas2 se traduce en disponer de nuevas clases especializadas en diferentes tareas.
La aplicación principal entrega su funcionalidad al Plugin
Cuando se solicita a Gambas2 el uso de un componente, lo primero que hace es cargar de forma dinámica dicha librería. A continuación busca los siguientes símbolos:
int GB_INIT (void) -> Es una función a la que se llama una vez el componente está en memoria, y que se utiliza para permitir a la librería o plugin que inicialice todo lo necesario para su funcionamiento. Dicha función devuelve un entero, que puede ser cero si se desea que a posteriori se descargue la librería de memoria cuando el programa finalice, u otro valor para que no se realice dicha acción. Esta particularidad se debe a que algunas librerías hacen fallar a la aplicación principal con una violación de segmento si son descargadas de memoria de forma abrupta, antes de que la aplicación principal haya finalizado.
void GB_EXIT (void) -> Función a la que se llama cuando la aplicación principal va a finalizar, para permitir al plugin liberar todos los recusos que tenga ocupados.
GB_INTERFACE GB -> Es una estructura de tipo "GB_INTERFACE", la cual tiene punteros a funciones. Gambas2 rellena dicha estructura (en principio con valores nulos) con punteros a todas las funciones que conforman la API de Gambas2, de forma que el plugin cuenta a partir de ese momento con todos los recursos para manejar memoria, tipos de datos, ficheros, etc de forma acorde con el resto del intérprete y componentes de Gambas.
El plugin exporta sus clases al programa principal
En el sentido contrario, la aplicación principal recibe información del plugin acerca de sus capacidades, y la forma de llamar a sus objetos y clases. Para ello, la aplicación principal busca el símbolo "GB_CLASSES" en el plugin, que se trata de un array de punteros a descripciones de clases. Es decir, cada uno de estos punteros representa a una clase, la cual contiene información acerca de su nombre, particularidades, métodos, propiedades y eventos.
Cada puntero es del tipo génerico de la API de Gambas denominado "GB_DESC". Se trata de una estructura utilizada para cualquier tipo de descripción,y tiene la forma:
typedef struct {
char *name;
int val1;
int val2;
int val3;
int val4;
int val5;
} GB_DESC;
No siempre son utilizados todos los campos, ni tienen el mismo significado, pero tener una estructura de tamaño fijo facilita el recorrerlas después con punteros de forma sencilla.
La definiciones de la clase comienzan por una estructura GB_DESC, en la cual se indica el nombre de la clase, la versión de la API de Gambas con la que fue diseñada y el tamaño que ocupa el objeto de dicha clase una vez creado en memoria. A la hora de diseñar componentes en C y C++, el fichero de cabecera gambas.h aporta una macro para la declaración:
#define GB_DECLARE(name, size) { name, (int)GB_VERSION, (int)size }
A contuación vienen el resto de estructuras GB_DESC que pueden contener información sobre cada método, propiedad o evento provisto sobre la clase, o bien sobre particularidades de dicha clase. Las particularidades, traducidas a macros pueden ser:
#define GB_NOT_CREATABLE_ID ((char *)3)
#define GB_NOT_CREATABLE() { GB_NOT_CREATABLE_ID }
No se puede crear. Indica que no se pueden crear objetos de una clase desde el código Gambas, lo cual puede ser debido a que solo disponga de métodos y propiedades estáticas, o bien porque la única forma de crearlas sea llamando a funciones de otros objetos que nos devuelven estos objetos nuevos creados desde un componente en C o C++, es decir, en los casos en que su creación aislada de un contexto externo no tenga sentido.
#define GB_VIRTUAL_CLASS_ID ((char *)1)
#define GB_VIRTUAL_CLASS() { GB_VIRTUAL_CLASS_ID }, { GB_NOT_CREATABLE_ID }
Clase virtual, indica que la interfaz no es una clase independiente, si no una interfaz adicional para otra clase, y por tanto no se puede utilizar de forma aislada ni como objeto ni como clase. Así por ejemplo, para las filas de un Widget "Grid" que represente una parrilla de filas y columnas para representar datos, podemos referirnos a las propiedades de las filas con una clase llamada ".GridRows" que siempre dependerá del objeto "Grid", lo cual simplifica la sintaxis del lenguaje sin tener que crear objetos temporales: "Grid1.Rows.Count", "Grid1.Rows.Clear()"... que es más elegante y ordenado que llenar a la clase "Grid" directamente de métodos y propiedades referidas a las filas: "Grid1.RowsCount", "Grid1.RowsClear()"...
#define GB_AUTO_CREATABLE_ID ((char *)4)
#define GB_AUTO_CREATABLE() { GB_AUTO_CREATABLE_ID }
Autocreable. Indica que una llamada a la clase crea de forma implícita un objeto para tratar las llamadas. Se usa actualmente en el caso de formularios, de forma que se elimine la necesidad en el código Gambas de crear una instancia de un formulario, y en su lugar se puedan hacer llamadas genéricas a los métodos y propiedades de la clase, por ejemplo "Form1.Show()" donde "Form1" es el nombre de la clase, no de un objeto creado a partir de la clase.
#define GB_INHERITS_ID ((char *)5)
#define GB_INHERITS(symbol) { GB_INHERITS_ID, (int)symbol }
Indicador de herencia. Especifica que la clase hereda los métodos y propiedades de una clase padre.
#define GB_HOOK_CHECK_ID ((char *)2)
#define GB_HOOK_CHECK(hook) { GB_HOOK_CHECK_ID, (int)hook }
El "Hook Check" indica que antes de llamar a cada método o propiedad de la clase, se llamará a una función que definamos para comprobar si el objeto se encuentra en un estado válido para el trabajo a realizar. Esto es muy útil, por ejemplo, en el caso de Widgets: cuando se destruye el widget llamando al método "Destroy()", se liberan todos los recursos asociados a dicho widget, pero el objeto Gambas sigue existiendo, por lo cual antes de cada llamada a una propiedad o método se comprueba si dicho objeto aún aloja a un Widget real.
En cuanto a los métodos, propiedades, eventos y constantes, se indican por medio de otras macros. En general, se especifica un código como primera letra de del nombre, que especifica su especie, y después el resto de datos necesarios según su especie, por ejemplo, para la declaración de un método:
#define GB_METHOD(symbol, type, exec, signature)
{ "m" symbol, (int)(void *)type, (int)exec, (int)(void *)signature }
Se indica al principio una ’m’, que indica que es un método no estático, seguido de su nombre, como puede ser "Clear". Dado que se trata de un método, siguen los datos necesarios para completar la signatura: tipo de valor devuelto, codificado como una cadena, puntero a la función a llamar, y valores que recibe como parámetros codificados también como una cadena.
Del mismo modo, el resto de macros definen propiedades, constantes, eventos y otro tipo de métodos:
Declaración de constante:
#define GB_CONSTANT(symbol, type, value) { "C" symbol, (int)type, (int)value }
Declaración de propiedad no estática:
#define GB_PROPERTY(symbol, type, proc) { "p" symbol, (int)type, (int)proc }
Declaración de propiedad no estática de solo lectura:
#define GB_PROPERTY_READ(symbol, type, proc) { "r" symbol, (int)type, (int)proc }
Declaración de propiedad estática:
#define GB_STATIC_PROPERTY(symbol, type, proc)
{ "P" symbol, (int)type, (int)proc }
Declaración de propiedad estática de sólo lectura:
#define GB_STATIC_PROPERTY_READ(symbol, type, proc)
{ "R" symbol, (int)type, (int)proc }
Declaración de propiedad que devuelve un puntero a sí mismo (para utilizar con clases virtuales):
#define GB_PROPERTY_SELF(symbol, type)
{ "r" symbol, (int)type, (int)-1 }
Declaración estática de propiedad que devuelve un puntero a sí mismo (para utilizar con clases virtuales):
#define GB_STATIC_PROPERTY_SELF(symbol, type)
{ "R" symbol, (int)type, (-1) }
Declaración de método:
#define GB_METHOD(symbol, type, exec, signature)
{ "m" symbol, (int)(void *)type, (int)exec, (int)(void *)signature }
Declaración de método estático:
#define GB_STATIC_METHOD(symbol, type, exec, signature)
{ "M" symbol, (int)(void *)type, (int)exec, (int)(void *)signature }
Declaración de evento:
#define GB_EVENT(symbol, type, signature, id)
{ "::" symbol, (int)(void *)type, (int)id, (int)(void *)signature }
Ejemplo de funcionamiento
Para ilustrar toda la mecánica, crearemos un programa que actúa a modo de intérprete muy primitivo de Gambas2, capaz tan sólo de cargar un plugin, verificar su estado y recorrer todas las clases que aporta el componente para mostrar su tabla de símbolos Gambas2. La carga del plugin se hace con "
dlopen", como cualquier otra librería, aunque se pueden usar otras alternativas.
Llamaremos al programa gbexlore.c, y se compilará con:
gcc gbexlore.c -o gbexplore -ldl
#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>
/*
Definimos el tipo genérico de descripciones GB_DESC
conforme a la API actual de Gambas2
*/
typedef struct
{
const char *name;
int val1;
int val2;
int val3;
int val4;
int val5;
} GB_DESC;
/*
Se encarga de cargar em memoria la librería, y comprobar
que realmente contiene las funciones de entrada y salida
propias de un plugin de Gambas
*/
void explore_class (GB_DESC *clase);
void load_gambas_library (char *name);
void load_gambas_library (char *name)
{
/*
Puntero que almacena un descriptor que apunta a la
librería que vamos a cargar
*/
void *handle;
/*
Puntero para recoger símbolos de la librería
*/
void *test;
/*
Puntero para recorrer las clases que contiene
el componente
*/
GB_DESC **clases;
if (!name) { perror ("Ruta no válida"); exit (-1); }
/* Carga en memoria la librería */
handle=dlopen(name,RTLD_LAZY);
/* ¿Se pudo cargar? */
if (!handle) { perror ("No fue posible cargar la librería"); exit(-1); }
/* comprobamos de igual modo la existencia de los símbolos GB, GB_INIT y GB_EXIT */
test=dlsym(handle,"GB");
if (!test) { perror ("El símbolo GB no existe");exit (-1); }
test=dlsym(handle,"GB_INIT");
if (!test) { perror ("El símbolo GB_INIT no existe");exit (-1); }
test=dlsym(handle,"GB_EXIT");
if (!test) { perror ("El símbolo GB_EXIT no existe");exit (-1); }
/*
Recibimos la definición de todas las clases que contiene
el componente a través del símbolo "GB_CLASSES"
*/
test=dlsym(handle,"GB_CLASSES");
if (!test) { perror ("El símbolo GB_CLASSES no existe");exit (-1); }
/*
Cast para iterar por cada clase del componente
*/
clases=(GB_DESC**)test;
/*
Iteración por todas las clases, hasta que
lleguemos a un puntero nulo, que indica el
fin de las definiciones
*/
while (*clases)
{
explore_class( *clases );
clases++;
}
}
/*
Exploración interna de cada clase
*/
void explore_class (GB_DESC *clase)
{
char *mname;
/*
El primer miembro de la definición de la clase contiene
el nombre, versión y tamaño del objeto de la clase
*/
printf("_____________________________________ ");
printf("CLASE: %s ",(*clase).name);
printf("Versión de la API: %d ",(*clase).val1);
printf("Tamaño del objeto: %d ",(*clase).val2);
clase++;
/*
La definición de la clase es un array de estructuras GB_DESC,
la última de las cuales tiene el caracter especial 0 en el
campo ’name’ (macro GB_END_DECLARE)
*/
while ( (*clase).name != (char*)0 )
{
/*
El nombre puede ser el nombre de una propiedad, método,
evento o constante, o bien un cararter especial que
indique particularidades de la clase. Las constantes
especiales están definidas en la API de Gambas2
*/
switch ((int)(*clase).name )
{
case 1: printf("Clase virtual "); break;
case 2: printf("Contiene Hook de comprobación "); break;
case 3: printf("La clase no puede crear objetos "); break;
case 4: printf("La clase crea objetos implícitos "); break;
case 5: printf("la clase es hija de la clase: %s ",(char*)(*clase).val1); break;
default:
mname=(char*)(*clase).name;
mname++;
switch ( (*clase).name[0] )
{
case ’C’:
printf("Constante : %s ",mname);
break;
case ’p’:
printf("Propiedad : %s ",mname);
break;
case ’r’:
printf("Propiedad solo lectura : %s ",mname);
break;
case ’P’:
printf("Propiedad estática : %s ",mname);
break;
case ’R’:
printf("Propiedad estática, solo lectura: %s ",mname);
break;
case ’m’:
printf("Método : %s ",mname);
break;
case ’M’:
printf("Método estático : %s ",mname);
break;
}
}
clase++;
}
}
/*
Función principal
*/
int main (int argc, char *argv[])
{
if (argc != 2) { perror("Uso: gbexplore ruta"); exit(-1); }
load_gambas_library(argv[1]);
}
Referencias
* Programación de componentes con Gambas2.* API de Gambas2, contenida en el fichero gambas.h
Por: danicafe # 06-11-07 (13:58:49) # URL # Tb () # Comentarios (0) # Gambas
GTK+ y QT en Gambas (o KDE y GNOME)
Vídeo ilustrativo del caso:
en formato ogg.
En Gambas (versión en desarrollo SVN próxima a estabilizarse) existen dos implementaciones del componente gráfico, la primera utiliza como backend a QT, y la segunda a GTK+.
Ambos componentes comparten la misma interfaz externa, lo cual quiere decir que el programador las puede utilizar indistintamente: su programa funcionará igual con el mismo código.
Recientemente se ha añadido un componente "gb.gui" que reemplaza a ambos en el programa, y se encarga de seleccionar GTK+ o QT dependiendo del entorno, así, por ejemplo sobre Gnome o XFCE, carga el componente gb.gtk, y sobre KDE carga gb.qt. También puede modificarse con la variable de entorno "GB_GUI" para forzar la carga de uno u otro.
Así mismo, el componente gb.form se encarga tanto de aportar Widgets extra diseñados en Gambas (con lo cual funcionan indistintamente sobre QT y GTK+) sin duplicar código, como de aportar los iconos de Stock que se seleccionan teniendo en cuenta el entorno gráfico del escritorio actual: de esa forma se facilita que aplicaciones diseñadas exclusivamente para QT o GTK+, por utilizar extensiones no disponibles en el otro entorno (puede ser el caso de aplicaciones que usan DCOP, por ejemplo), adopten sin embargo un tema de iconos coherente con el entorno.
Por último, la estructura interna de Gambas permite que otros componentes escritos en C o C++ se comuniquen con el gráfico de forma independiente a la implementación, así, componentes como el de PDF basado en libpoppler o el de vídeo V4L pueden trabajar aportando sus capacidades a gb.qt y gb.gtk indistintamente, y sin duplicar código.
Por: danicafe # 18-10-07 (10:26:25) # URL # Tb () # Comentarios (0) # Gambas
gnuLinEx 2006R3 en Lenovo 3000
El portátil Lenovo 3000 v200 (identificado en la placa trasera como "modelo 0764"), incluye hardware que ha dado algunos problemas incluso con las distribuciones más recientes (Ubuntu, Debian ...). He aquí como configurarlo con gnuLinEx 2006:
En primer lugar, para la instalación necesitamos una copia de gnuLinEx 2006R3, que se puede descargar
haciendo click aquí.
Al iniciar la instalación, se indicará "Intalación en modo texto", dado que la tarjeta de problemas con los modos gráficos estándar.
Una vez finalizada la instalación, el sistema gráfico no arranca. Se entrará en modo texto con la contraseña de "root", y se editará el fichero /etc/apt/sources.list, de forma que quede así:
deb http://azores.linex.org/debian sarge xorg-7.3
deb http://apt.linex.org/linex2006 sarge linex2006
deb http://apt.linex.org/linex2006 sarge xorg-7.1
deb http://ftp.de.debian.org/debian sarge main contrib
Ahora hay que actualizar los repositorios, para conseguir red, hay que hacerlo por cable de momento, dado que la tarjeta inalámbrica habremos de configurarla más tarde. Conecta pues el cable de red a un router ADSL o a una red local, y ejecuta:
dhclient eth1
(O bien solicita del administrador de la red local la IP y puerta de enlace, si no dispone de servicio DHCP)
A continuación, ejecutaremos:
apt-get update && apt-get dist-upgrade
Para actualizar el sistema gráfico, de forma que el nuevo sistema incluya el nuevo driver de vídeo "intel". Pasado este punto, ejecutando:
killall gdm && gdm
Podremos entrar en el sistema gráfico. Desde el navegador, por ejemplo, podemos descargar ahora los paquetes del driver de red (Intel IWL4965) desde estos enlaces:
Paquete 1Paquete 2Una vez descargados en una carpeta, los instalaremos:
dpkg -i *deb
Al reiniciar el equipo, la red estará disponible. La propia casa Intel reconoce que de momento el driver es bastante extraño (ver README del driver) en su estructura (el funcionamiento sí es correcto) y me consta que el soporte en gnuLinEx ha costado lo suyo, pero ha quedado de perlas.
Ya está todo en marcha... intentarlo con un cd estándar de XP(R) puede ser una pesadilla, por cierto, no os lo recomiendo :-).
Por: danicafe # 11-10-07 (12:36:34) # URL # Tb () # Comentarios (1) # Linux
Evince 2.20: formularios PDF editables con gnuLinEx
El repositorio de gnuLinEx 2006 dispone ahora de la versión 2.20 de Evince, el visor de PDF.
Esta versión permite por primera vez rellenar formularios PDF e imprimirlos con los datos rellenados por el usuario.
La instalación es muy sencilla, basta, con hacer desde una consola, como usuario root:
apt-get dist-upgrade
O bien seleccionar la opción de actualizar el sistema con la interfaz gráfica desde el panel de control.
Por: danicafe # 28-09-07 (14:42:49) # URL # Tb () # Comentarios (1) # Linux
Estándar OASIS traducido
De acuerdo con la información aparecida en la página de
gnuLinEx, ya disponemos de la traducción al español del estándar OASIS, en formatos
PDF y
ODT.
La nota original reza así:
La Junta de Extremadura ha puesto a disposición de todos los ciudadanos la traducción de la norma ISO 26300 (El estándar ODF, open document format), utilizado por la mayoría de las herramientas abiertas de ofimática,y también utilizado por algunas herramientas propietarias.
Además, la Junta de Extremadura ha liberado dicha traducción bajo la licencia Creative Commons para que su difusión sea la máxima posible.
Entre otros proyectos libres con soporte de OASIS tenemos OpenOffice.org y Koffice. La aplicación on-line propietaria de Google permite también importar documentos dicho formato.
Por: danicafe # 21-09-07 (19:23:52) # URL # Tb () # Comentarios (0) # Linux
Gambas packages for Ubuntu Edgy/Feisty/Gutsy/Hardy and Guadalinex v4.1
Please follow this link.
Por: danicafe # 11-09-07 (13:52:11) # URL # Tb () # Comentarios (5) # Gambas
Toshiba / Phoenix: olvídate de instalar GNU/Linux
Curiosa historia:
.
Ha llegado a la lista gambas-users un mensaje de un hombre desesperado por no poder usar Gambas en su entorno, no porque su distribución no lo permita, si no porque de hecho no hay modo de instalar una distribución en su portátil:
Any news on compiling Gambas on a CygWin system? I really need to port my Gambas Project with me. My notebook is windows vista (arghhhh) and there is a problem, because i cant install linux on it... The BIOS do not allow another OS, unless Windows.....
El bloqueo se encuentra en la BIOS de Phoenix. La noticia que yo había creído un rumor se confirma así para mí, y también unos compañeros me han confirmado la triste historia (no solo no deja instalar GNU/Linux si no tampoco otras versiones de Windoze), con lo cual esa pieza de hardware puede ir a la basura directamente. No es cuestión de buscar un crack, si no de que el fabricante no te venda un producto con esas condiciones, no tengo ganas de perder el tiempo en esa mierda. Toshiba se puede olvidar de mí, y sospecho que de clientes más gordos de caracter más formal.
Por: danicafe # 06-06-07 (21:08:15) # URL # Tb () # Comentarios (0) # Linux
Problemilla Iceweasel/Firefox
A pesar de que "Firefox" es un navegador libre, su logotipo (el icono del "zorro de fuego") no cumple las condiciones de software libre. Por esa razón, Debian empaqueta a partir de Debian Etch a este navegador con otro nombre, que es "Iceweasel".
Hasta ahí es poco más que una de las guerras típicas entre software propietario, libre, marcas registradas, etc, ya que el navegador funciona aparentemente igual que su hermano gemelo (tienen el mismo código).
No obstante, y como todos los gemelos, no son exactamente iguales, e Iceweasel hace algo que está a mi entender fuera de lugar, al identificarse manda el servidor web una cadena en la cual no indica que se trata de "Firefox" si no de "Iceweasel", o lo que es lo mismo, teniendo las mismas capacidades exactamente que "Firefox", dice que es otro navegador, una verdadera estupidez, que queda fuera de las batallitas entre propietarios de marcas registradas, pues se trata solo de una cadena de texto enviada por la red.
En fin, el resultado es que algunos sitios web no reconocen a tal navegador, por estar diseñados solo para reconocer la cadena "FIrefox", y por tanto niegan ciertos servicios a este hermano díscolo, cosa que puede suceder, por ejemplo, al entrar en la página de algunos bancos o líneas de autobuses para consulta.
¿Que ya estás con Debian Etch o inestable y no sabes solucionarlo? Bien fácil:
1) En la barra de direcciones (vamos, donde normalmente pones http://www.loquesea.com), escribe "about:config" y pulsa return.
2) Busca de entre todas las cadenas (van por orden alfabético, o escribe las primeras letras en el cuadro de texto que aparece) una que se llama:
general.useragent.extra.firefox
3) Haz doble click sobre ella para editarla, y cambia su valor por:
Firefox/2.0.0.3
4) Reinicia el navegador, ya está listo.
Por: danicafe # 21-05-07 (17:56:53) # URL # Tb () # Comentarios (0) # Linux
Opening docx documents in Debian (and Ubuntu)
MS(R) includes a new format in Office 2007(R), called
OpenXML(R). That format is not compatible with OpenOffice.org 2.0 and OpenOffice.org 2.2. Recently
Novell(R) added support for it as an
OpenOffice.org plugin, available in .rpm format.
You can
click here to download an script to download that file, convert it using alien and install in the proper paths for Debian (and Ubuntu).
Execute that script as root:
sh descargaodf.sh
You"ll need a working internet connection to install the conversor. Once the plugin is installed, you can open any OpenXML(R) document directly with OpenOffice.org 2.X
Of couse soon it will be available as an option of the "LinexFacil" program for the gnuLinEx 2006 distribution, and the raw script can also be used in gnuLinEx 2006.
Por: danicafe # 15-05-07 (14:08:30) # URL # Tb () # Comentarios (0) # Linux