Error importando OAuthSignatureMethod_HMAC_SHA1 en python-oauth

jueves, octubre 25, 2012

Si estás utilizando la librería python-oauth para autenticarte contra un servicio por medio de OAuth y te consigues con el error:
AttributeError: 'module' object has no attribute 'OAuthSignatureMethod_HMAC_SHA1'

Entonces tengo la solución para ti.

El problema es que la clase no está en el módulo oauth, sino en oauth.oauth. Entonces, en lugar de hacer:
import oauth

Prueba con:
try:
    import oauth.oauth
except:
    import oauth

De nada ;)

Referencias:

Personalizar el unity-greeter de LightDM en ArchLinux

martes, octubre 09, 2012

Buscando opciones para un gestor de inicio de sesión bonito y ligero recordé a LightDM. Sé que seguramente me van a hablar de SLiM, pero ha estado abandonado mucho tiempo y no es tan lindo y funcional como LightDM, así que decidí no utilizarlo.

Instalación

LightDM se encuentra disponible en AUR, así que una buena forma de instalarlo es con yaourt: $ yaourt -S lightdm lightdm-unity-greeter
Para más información de cómo instalar LigthDM visita la wiki de ArchLinux.

Configuración

Esta es la parte divertida. Muchas recetas en internet te hablan de editar el infame /etc/lightdm/lightdm-unity-greeter.conf, pero la verdad es que la versión más nueva no utiliza ese archivo sino que, "en teoría", utiliza dinámicamente la imagen establecida como fondo de pantalla para el fondo del greeter. En mi caso siempre usaba la misma imagen, la que trae Ubuntu por defecto. Incluso agregué mi imagen a la ruta /usr/share/backgrounds/ y tampoco funcionaba.

¿Qué hacemos? Pues editemos el schema del dconf a mano. Abrimos con nuestro editor favorito el archivo usr/share/glib-2.0/schemas/com.canonical.unity-greeter.gschema.xml y ajustamos los valores correspondientes.

Yo edité solamente la línea del background y del color de fondo:
<key name="background" type="s">
  <default>'/usr/share/backgrounds/poweroff.jpg'</default>
  <summary>Background file to use, either an image path or a color</summary>
</key>
...
<key name="background-color" type="s">
  <default>'#444444'</default>
  <summary>Background color (e.g. #772953), set before wallpaper is seen</summary>
</key>

En el key con nombre "background" cambié la ruta al archivo de fondo, apuntando a /usr/share/backgrounds/poweroff.jpg y en el key con nombre "background-color" cambié el color a #444444. Ustedes editen los campos como mejor les parezca.

Para que estos cambios tengan efecto debemos recompilar los esquemas del dconf, así que ejecutamos: # glib-compile-schemas /usr/share/glib-2.0/schemas/
Reiniciamos el servicio y ya estaremos disfrutando de nuestros cambios en el gestor.

Espero que les haya servido esta receta.

Referencias

Respaldar archivos usando Rsync

martes, septiembre 18, 2012

En ocasiones nos toca respaldar gran cantidad de información de un equipo a otro y una de las maneras más cómodas de hacerlo es a través de la red. Para esta tarea usaremos Rsync que, al igual que netcat, son unas navajas suizas en lo que a red se refiere. Rsync, entre otras cosas, nos permite sincronizar directorios a través de la red y de esa forma lo usaremos para enviar nuestros archivos de un equipo a otro.

Comando y parámetros

Lo primero que necesitamos es acceso vía SSH al equipo remoto. Puede ser por clave pública o con usuario y contraseña. Luego nos ubicamos en la carpeta que queremos respaldar y ejecutamos el comando: $ rsync -ravz archivo_a_respaldar usuario@IP:directorio_destino Donde:
  • r: recorre los directorios de manera recursiva
  • a: conserva los atributos de los archivos (usuario, permisos, etc)
  • v: imprime información en pantalla
  • z: comprime los datos antes de enviar
Luego de la autenticación comenzará la copia de los archivos. Es importante resaltar que en el directorio_destino debemos colocar la ruta absoluta a la carpeta.

Copiar un archivo

Supongamos que queremos respaldar el archivo backup.tar.gz en un equipo remoto con IP 192.168.0.3, el usuario es satanas y el directorio destino es el home del usuario, el comando nos quedaría así:
$ rsync -avz backup.tar.gz satanas@192.168.0.3:~
El símbolo ~ puede sustituirse por la ruta absoluta /home/satanas.

Copiar un directorio y todos sus subdirectorios

Ahora supongamos que queremos respaldar el directorio /tmp/music en el mismo equipo anterior pero ahora el destino es la carpeta /home/satanas/musica, el comando nos quedaría así: $ rsync -ravz /tmp/music satanas@192.168.0.3:/home/satanas/musica

Con esta receta podremos pasar nuestro respaldo de archivos de un equipo a otro sin mucho inconveniente. Espero que les sirva

Tip: Obtener el espacio usado de un directorio

jueves, marzo 22, 2012

Situación

Queremos conocer el espacio en disco usado por un directorio (y sus subdirectorios inmediatos) con un comando de consola.

Comando

$ du -h -s /tu/directorio/*

Resultado

El comando te imprimirá algo como:
$ du -h --summarize /tu/directorio/*
2.5M /var/backups
286M /var/cache
139M /var/lib
4.1k /var/local
4.1k /var/lock
98M /var/log
4.4M /var/mail
4.1k /var/opt
103k /var/run
66k /var/spool
4.1k /var/tmp

Explicación

El comando du te da un estimado del espacio utilizado por un directorio, la opción -h te imprime los números en potencias de 1024, --sumarize muestra solo el total para cada elemento y /tu/directorio/* corresponde al directorio que deseas analizar. El * al final es la clave para indicarle al comando que queremos ver los subdirectorios.

Configurar opciones por defecto para el comando gem

sábado, marzo 10, 2012

Cada vez que voy a instalar una gema con el comando gem uso los parámetros --no-rdoc y --no-ri para que no me genere la documentación, principalmente porque nunca la uso y además demora demasiado el tiempo de instalación de las gemas. Pero escribir esos parámetros cada vez que ejecuto el comando es fastidioso.

Afortunadamente gem busca el archivo de configuración ~/.gemrc en el directorio personal del usuario y aplica las opciones que estén definidas, así que creando ese archivo en nuestro home con la siguiente línea no tendremos que tipear las opciones nunca más:

gem: --no-rdoc --no-ri

Enviar mensajes a través de D-Bus usando Python

martes, febrero 14, 2012

D-Bus es un sistema que permite la comunicación entre diferentes procesos. Es desarrollado como parte del proyecto freedesktop.org buscando ofrecer una solución simple y común para los distintos entornos de escritorio.

Conceptos básicos


Tipos de Bus

Hay dos tipos de buses que se pueden usar con D-Bus. El bus de sesión, que se crea con cada sesión de usuario y es local a esa sesión, y el bus de sistema. Este último es global, se inicia cuando arranca el equipo y generalmente se utiliza para comunicarse con procesos como udev, NetworkManager o HAL.

Rutas a Objetos

Cada lenguaje de programación tiene sus objetos nativos (usualmente representados por clases). La ruta a un objeto es la forma en que D-Bus permite hacer referencia a un objeto nativo y que las aplicaciones remotas puedan usarlo. Un ruta a un objeto luce como la ruta de un archivo (Unix-like) y es común generarlas como un nombre de dominio en reversa, por ejemplo: /org/gnome/myapp/MyObject. Sin embargo, cada desarrollador puede usar la ruta que mejor le parezca, siempre y cuando sea única.

Métodos y Señales

Los métodos son operaciones (con o sin parámetros) que pueden invocarse en un objeto y que eventualmente pueden devolver un resultado. Las señales son notificaciones que se envían al bus y son recibidas por los observadores (objetos que escuchan o se conectan a esas señales). Estas señales también pueden enviar datos de interés para el receptor.

Objetos Proxy

Un objeto proxy no es más que un objeto Python que viene a representar a un objeto remoto en otro proceso. Esto nos permite emplear los métodos del objeto remoto como si fueran métodos nativos. Para instanciar un objeto proxy necesitamos la "ruta del objeto".

Estructura Básica

Para usar D-Bus es importante entender la estructura e interacciones básicas. En palabras simples, lo que tenemos son dos aplicaciones que se "hablan" entre sí a través de un canal común. Generalmente una de ellas actúa como "servidor", ofreciendo métodos y señales que podrán ser usados por una aplicación "cliente". En la imagen a continuación se ilustra claramente el concepto.



Un ejemplo bastante común es el funcionamiento de un reproductor de música (como el servidor) que ofrece métodos para informar sobre su estado actual y una aplicación de notificaciones (como cliente) que usa estos métodos para mostrar diálogos en el escritorio cada vez que cambia una canción.

Aplicación "servidor"


Lo primero que debemos hacer para empezar a trabajar con D-Bus es conectarnos a un bus. En nuestro caso será al bus de sesión porque no nos interesa interactuar con procesos del sistema operativo.

Adicionalmente, para ejecutar llamadas asíncronas a los métodos (y que la aplicación no se bloquee mientras espera) debemos configurar un bucle principal. Para el momento de escribir este post, python-dbus solo soporta el bucle principal de GLib, así que usaremos esas librerías para crear nuestro loop.

Los import que necesitamos para esto serían:
import dbus
import dbus.service
import gobject

from dbus.mainloop.glib import DBusGMainLoop

Luego, definimos (en variable globales) el nombre del bus y la ruta de nuestro objeto:
DBUS_BUSNAME = 'org.example.ExampleDBus'
DBUS_MYOBJECT_PATH = '/org/example/ExampleDBus/MyObject'

Ahora definimos la clase de nuestro server, con la inicialización mínima para que funcione D-Bus:
class DBusService(dbus.service.Object):
    
    def __init__(self):
        # Le indicamos a D-Bus que usaremos el loop de GLib como bucle
        # predeterminado
        DBusGMainLoop(set_as_default=True)
        
        # Establecemos la conexión al bus de sesión
        self.session_bus = dbus.SessionBus()
        name = dbus.service.BusName(DBUS_BUSNAME, self.session_bus)
        
        # Inicializamos el objeto D-Bus
        dbus.service.Object.__init__(self, self.session_bus, DBUS_MYOBJECT_PATH)
        
        # Arrancamos el bucle principal
        loop = gobject.MainLoop()
        
        # Colocamos el loop dentro de un try/except para detectar cuando el 
        # usuario presione Ctrl + C y finalizar la aplicación limpiamente
        try:
            print "Servicio DBus iniciado"
            loop.run()
        except KeyboardInterrupt:
            loop.quit()
            print "Servicio DBus finalizado"

Si se fijan en el código anterior, nuestra clase hereda de dbus.service.Object (porque estamos construyendo un objeto D-Bus que será instanciado por una aplicación remota). En la inicialización le indicamos a D-Bus que usaremos el loop de GLib, establecemos la conexión al bus de sesión y arrancamos el loop.

He decidido colocar la ejecución del loop principal dentro de un try/except para que el usuario pueda usar Ctrl + C para salir elegantemente de la aplicación.

Hecho esto podemos proceder a definir un par de métodos. Hagamos un método que imprima un saludo y otro que nos devuelva un valor, en este caso la hora.
    @dbus.service.method(DBUS_BUSNAME)
    def say_hello(self, name):
        print "Hola, %s" % name
    
    @dbus.service.method(DBUS_BUSNAME)
    def get_time(self):
        return time.strftime("%H:%M")

En los párrafos previos comenté que un objeto proxy no es más que una representación de un objeto D-Bus en un objeto Python, así que para exportar un método nativo como método D-Bus usamos el decorador @dbus.service.method y le pasamos como parámetro el nombre del bus. Luego definimos nuestras funciones como lo haríamos normalmente en cualquier clase de Python y ya con esto tendríamos un servidor muy básico listo para funcionar.

El código completo debería quedar así:
#!/usr/bin/python
# -*- coding: utf-8 -*-

import time
import dbus
import gobject
import dbus.service

from dbus.mainloop.glib import DBusGMainLoop

DBUS_BUSNAME = 'org.example.ExampleDBus'
DBUS_MYOBJECT_PATH = '/org/example/ExampleDBus/MyObject'

class DBusService(dbus.service.Object):
    
    def __init__(self):
        # Le indicamos a D-Bus que usaremos el loop de GLib como bucle
        # predeterminado
        DBusGMainLoop(set_as_default=True)
        
        # Establecemos la conexión al bus de sesión
        self.session_bus = dbus.SessionBus()
        name = dbus.service.BusName(DBUS_BUSNAME, self.session_bus)
        
        # Inicializamos el objeto D-Bus
        dbus.service.Object.__init__(self, self.session_bus, DBUS_MYOBJECT_PATH)
        
        # Arrancamos el bucle principal
        loop = gobject.MainLoop()
        
        # Colocamos el loop dentro de un try/except para detectar cuando el 
        # usuario presione Ctrl + C y finalizar la aplicación limpiamente
        try:
            print "Servicio DBus iniciado"
            loop.run()
        except KeyboardInterrupt:
            loop.quit()
            print "Servicio DBus finalizado"
        
    @dbus.service.method(DBUS_BUSNAME)
    def say_hello(self, name):
        print "Hola, %s" % name
    
    @dbus.service.method(DBUS_BUSNAME)
    def get_time(self):
        return time.strftime("%H:%M")
    
    @dbus.service.signal(DBUS_BUSNAME)
    def kill(self):
        return 'killed'

if __name__ == '__main__':
    service = DBusService()

Aplicación "cliente"

La aplicación cliente es mucho más simple. La única librería que necesitamos es la de D-Bus, el nombre del bus y la ruta del objeto.
import dbus

DBUS_BUSNAME = 'org.example.ExampleDBus'
DBUS_MYOBJECT_PATH = '/org/example/ExampleDBus/MyObject'

Ahora pasemos a crear la clase. Al igual que en el servidor, necesitamos establecer la conexión con el bus y "mapear" el objeto D-Bus en un objeto Python. En este caso la variable self.my_object es la que contiene la representación de dicho objeto.
class DBusClient:
    
    def __init__(self):
        self.session_bus = dbus.SessionBus()
        self.my_object = self.session_bus.get_object(DBUS_BUSNAME, 
            DBUS_MYOBJECT_PATH)

Para llamar a un método del objeto remoto debemos importar el método correspondiente, y como estaremos usando varios métodos vamos a crear una función interna que nos ayude con esta tarea.
    def __get_dbus_method(self, name):
        return self.my_object.get_dbus_method(name)

Procedemos a crear nuestros métodos nativos que obtendrán el método remoto del objeto D-Bus y lo llamarán con los parámetros correspondientes según sea el caso.
    def say_hello(self, name):
        # Almacenamos el método en una variable y luego lo llamamos con los
        # parámetros correspondientes
        method = self.__get_dbus_method('say_hello')
        method(name)
    
    def print_time(self):
        method = self.__get_dbus_method('get_time')
        # Obtenemos el valor de retorno del método y la imprimimos en pantalla
        current_time = method()
        print "Son las %s" % current_time

Con eso ya deberíamos tener un cliente funcional. El código completo del cliente debería quedar algo como:
#!/usr/bin/python
# -*- coding: utf-8 -*-

import dbus

DBUS_BUSNAME = 'org.example.ExampleDBus'
DBUS_MYOBJECT_PATH = '/org/example/ExampleDBus/MyObject'
        
class DBusClient:
    
    def __init__(self):
        self.session_bus = dbus.SessionBus()
        self.my_object = self.session_bus.get_object(DBUS_BUSNAME, 
            DBUS_MYOBJECT_PATH)
    
    def __get_dbus_method(self, name):
        return self.my_object.get_dbus_method(name)
        
    def say_hello(self, name):
        # Almacenamos el método en una variable y luego lo llamamos con los
        # parámetros correspondientes
        method = self.__get_dbus_method('say_hello')
        method(name)
    
    def print_time(self):
        method = self.__get_dbus_method('get_time')
        # Obtenemos el valor de retorno del método y la imprimimos en pantalla
        current_time = method()
        print "Son las %s" % current_time

if __name__ == '__main__':
    client = DBusClient()
    client.say_hello('Pedro')
    client.say_hello('Maria')
    client.print_time()
Es importante que observe que al ejecutar el script del cliente se llamará al método say_hello dos veces (con los parámetros 'Pedro' y 'Maria' respectivamente) y luego al método print_time.

Pruebas

Para probar abrimos dos terminales. En la primera ejecutamos el script del servidor (yo lo llamé dbus_service.py) y veremos algo como:
$ python dbus_service.py 
Servicio DBus iniciado
Con un mensaje indicándonos que el servicio está corriendo y esperando ser utilizado.

En la segunda terminal ejecutamos el script del cliente (lo llamé dbus_client.py) y veremos algo como:
$ python dbus_client.py 
Son las 12:55
Muy bien, imprimió la hora pero... ¿No se suponía que también habíamos llamado al método say_hello dos veces? ¿Qué pasó?

Bueno, volvamos a ver la terminal del servicio y veremos nuestro ansiado resultado:
$ python dbus_service.py 
Servicio DBus iniciado
Hola, Pedro
Hola, Maria

Tal como lo esperabamos, el método say_hello imprime en la instancia del servidor el nombre envíado desde el cliente y por otro lado, el cliente imprime la hora que le devuelve el método get_time desde el servidor. Hemos enviado mensajes en ambas direcciones usando D-Bus, ¡todo un éxito!

Espero que esta receta simple les haya ayudado a comprender como funciona D-Bus y como usarlo desde Python. Más adelante estaré escribiendo otros posts sobre cómo emitir/recibir señales y cómo enviar grandes cantidades de datos "al vuelo" a través de D-Bus.

Fuentes:

Mostrar el log de ActiveRecord en la consola de Rails

miércoles, febrero 01, 2012

Si usualmente utilizas la consola interactiva de Ruby On Rails para realizar pruebas, correr tareas de Rake, entre otras cosas y te resultaría sumamente útil ver el log de lo que esta haciendo el ActiveRecord, entonces este tip es para ti.

Simplemente ejecuta la consola desde la carpeta raíz de tu aplicación Rails:

Para rails 3.x:
$ rails c
Para rails 2.x:
$ script/console
Y luego indícale al logger que use la salida estándar:
ActiveRecord::Base.logger = Logger.new(STDOUT)
Con eso los mensajes de log del ActiveRecord se imprimirán en la consola.

Implementar un Singleton en Python

domingo, enero 29, 2012

Singleton es un patrón de diseño cuya función es evitar que un objeto pueda ser instanciado más de una vez. En este post traigo una receta simple para implementar el patrón singleton en Python.

En esta implementación utilizaremos un archivo de bloqueo (lock file) para indicar si la aplicación está en ejecución o no. Python cuenta con el módulo fcntl que nos proporciona una interfaz bastante cómoda para el control de archivos pero está disponible solo para Linux/Unix, eso implica que debemos usar medidas alternativas para Windows.

Detectar SO e importar módulos

Lo primero que debemos hacer es detectar el sistema operativo y ejecutar los import correspondientes para cada caso. Usaremos además el módulo tempfile para generar el lock file como un archivo temporal del sistema.
#!/usr/bin/python2
# -*- coding: utf-8 -*-

import os
import sys
import tempfile

OS = None
if sys.platform.startswith('linux'):
    OS = 'linux'
    import fcntl
elif sys.platform.startswith('win32'):
    OS = 'windows'


Definir la clase Singleton

Después de detectar el sistema operativo definimos la clase Singleton. Básicamente, esta clase será la encargada de crear el lock file al inicio o generar una advertencia y termina la ejecución en caso de que el archivo ya exista (es decir, que ya existe una instancia de la aplicación en ejecución).
class Singleton:
    def __init__(self):
        # Variable para almacenar el file descriptor
        self.fd = None
        # Ruta para el lock file en la carpeta temporal del sistema
        self.filepath = os.path.abspath(os.path.join(tempfile.gettempdir(), 
            'myapp.pid'))
        
        if OS == 'linux':
            # Para el caso de linux usamos el módulo fcntl para crear el archivo
            # y bloquearlo automáticamente. Si la operación falla es porque el
            # archivo ya existe y está bloqueado.
            self.fd = open(self.filepath, 'w')
            try:
                fcntl.lockf(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
            except IOError:
                self.__exit()
        elif OS == 'windows':
            try:
                # Para el caso windows simplemente creamos el archivo "a mano",
                # pero verificamos primero si el archivo existe e intentamos 
                # removerlo (para casos en que la ejecución previa haya sido 
                # interrumpida)
                if os.path.exists(self.filepath):
                    os.unlink(self.filepath)
                self.fd = os.open(self.filepath, os.O_CREAT|os.O_EXCL|os.O_RDWR)
            except OSError, err:
                if err.errno == 13:
                    self.__exit()
    
    def __del__(self):
        # Para el caso de windows también debemos destruir el archivo "a mano" 
        # al finalizar la ejecución del programa.
        if OS == 'windows':
            if self.fd:
                os.close(self.fd)
                os.unlink(self.filepath)
    
    def __exit(self):
        print 'Ya hay una instancia en ejecución. Saliendo'
        sys.exit(-1)
En el __init__ se observa que creamos una variable para almacenar el file descriptor (fd), luego generamos una ruta para el lock file. Posteriormente creamos un lock file con el módulo fcntl (en el caso linux) o creamos un archivo regular (para el caso windows). Adicionalmente, en el caso windows necesitamos hacernos cargo del archivo al finalizar la ejecución, para eso sobreescribimos el método __del__ y colocamos nuestro código. Adicionalmente tenemos la función __exit(), que es la encargada de detener la ejecución del programa de forma elegante.

Clase de pruebas

Con los pasos anteriores tenemos lista nuestra implementación simple del patrón singleton. Ahora, ¿Cómo la usamos? Creamos una clase (por ejemplo MyApp) que herede de singleton y ponemos un bucle infinito para que se mantenga haciendo "algo".
class MyApp(Singleton):
    def __init__(self):
        Singleton.__init__(self)
        print 'Ejecutando MyApp'
        # Creamos un bucle infinito solo para mantener la aplicación en
        # ejecución
        while 1:
            continue


¿Cómo se vería nuestro script?

El código completo de nuestro script se vería así:
#!/usr/bin/python2
# -*- coding: utf-8 -*-

import os
import sys
import tempfile

OS = None
if sys.platform.startswith('linux'):
    OS = 'linux'
    import fcntl
elif sys.platform.startswith('win32'):
    OS = 'windows'

class Singleton:
    def __init__(self):
        # Variable para almacenar el file descriptor
        self.fd = None
        # Ruta para el lock file en la carpeta temporal del sistema
        self.filepath = os.path.abspath(os.path.join(tempfile.gettempdir(), 
            'myapp.pid'))
        
        if OS == 'linux':
            # Para el caso de linux usamos el módulo fcntl para crear el archivo
            # y bloquearlo automáticamente. Si la operación falla es porque el
            # archivo ya existe y está bloqueado.
            self.fd = open(self.filepath, 'w')
            try:
                fcntl.lockf(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
            except IOError:
                self.__exit()
        elif OS == 'windows':
            try:
                # Para el caso windows simplemente creamos el archivo "a mano",
                # pero verificamos primero si el archivo existe e intentamos 
                # removerlo (para casos en que la ejecución previa haya sido 
                # interrumpida)
                if os.path.exists(self.filepath):
                    os.unlink(self.filepath)
                self.fd = os.open(self.filepath, os.O_CREAT|os.O_EXCL|os.O_RDWR)
            except OSError, err:
                if err.errno == 13:
                    self.__exit()
    
    def __del__(self):
        # Para el caso de windows también debemos destruir el archivo "a mano" 
        # al finalizar la ejecución del programa.
        if OS == 'windows':
            if self.fd:
                os.close(self.fd)
                os.unlink(self.filepath)
    
    def __exit(self):
        print 'Ya hay una instancia en ejecución. Saliendo'
        sys.exit(-1)

class MyApp(Singleton):
    def __init__(self):
        Singleton.__init__(self)
        print 'Ejecutando MyApp'
        # Creamos un bucle infinito solo para mantener la aplicación en
        # ejecución
        while 1:
            continue
    
if __name__ == '__main__':
    app = MyApp()


Probando el singleton

Para probarlo abrimos un terminal, nos colocamos en la carpeta donde esté ubicado nuestro script y lo ejecutamos por primera vez. Eso nos dará como resultado algo como:
$ python myapp.py 
Ejecutando MyApp


Abrimos una terminal nueva (sin cerrar la terminal anterior) e intentamos ejecutar la aplicación por segunda vez. Eso nos devolverá:
$ python myapp.py 
Ya hay una instancia en ejecución. Saliendo
¡Y voilá! Logramos que un script de Python pueda ser ejecutado una sola vez.

Hay implementaciones más complejas que almacenan el ID del proceso dentro del lock file y cada vez que se intenta ejecutar una nueva instancia se lee el ID y se verifica que realmente exista un proceso en ejecución con ese identificador. Pero como dije al principio, esta es una receta simple, así que as implementaciones más complejas las dejamos como tareas para el lector ;)

Mi propuesta para Canaima-Instalador

sábado, enero 28, 2012

Actualmente estoy colaborando con el desarrollo de canaima-instalador, una aplicación para Canaima GNU/Linux que permitirá al usuario instalar/probar Canaima de una manera fácil e intuitiva, y he decidido hacer una propuesta sobre cómo debe lucir y funcionar el nuevo instalador. La propuesta a continuación.

Paso 1: LiveCD

Lo primero que hace falta cambiar es el menú de inicio del liveCD. Actualmente tenemos una pantalla llena de opciones que, más allá de facilitar el uso, pueden confundir a los usuarios menos experimentados. Mi sugerencia es que el liveCD arranque automáticamente y presente un menú inferior con las posibles opciones para usuarios más experimentados.


Paso 2: Bienvenida

Aquí es donde comienza a ejecutarse canaima-instalador. La idea es que sea tipo OEM, es decir, que no cargue el escritorio sino lo mínimo necesario para ejecutarse. Se le preguntará al usuario si desea probar la distribución o instalar. Para el primer caso se cierra canaima-instalador y se continúa con la carga del escritorio, para el segundo caso se sigue el flujo normal de la aplicación.


Paso 3: Requisitos

Se le muestra al usuario cuáles son los requisitos mínimos necesarios para obtener mejores resultados al instalar Canaima. En caso de que no se cumpla con un requisito crítico (por ejemplo, el espacio en disco) la instalación no debería continuar.


Paso 4: Modo de instalación

Acá (luego de haber analizado el hardware) se le presentan al usuario las distintas opciones que tiene para instalar. En teoría todas deberían ser automáticas excepto la de "Particionamiento Avanzado" que ejecutará una instancia de gparted y bloqueará canaima-instalador hasta que el usuario termine de definir sus particiones. Una vez que el usuario haga clic en "Siguiente" no podrá volver atrás. Acá termina la primera fase de instalación y comienza el copiado de los archivos en el disco duro en segundo plano. La idea es aprovechar el tiempo, mientras se copian los archivos solicitamos al usuario el resto de la información y una vez terminada la copia se ejecutan las tareas correspondientes. De esta forma logramos minimizar considerablemente el tiempo de instalación (tal como lo hace Ubuntu).


Paso 5: Configuración de teclado

Como expliqué en el punto anterior, mientras se copian los archivos en segundo plano solicitamos el resto de la información al usuario. Acá se le pedirá que seleccione la distribución del teclado. Es importante observar que el botón de "Anterior" no estará disponible en este paso.


Paso 6: Configuración de usuarios

En esta fase se le pedirán los datos de las cuentas (root y usuario regular). Es importante habilitar un link (o botón) de ayuda donde se pueda explicar qué significa root y por qué es importante definir esa contraseña. Es importante recalcar que si el usuario no ha terminado de introducir la información complementaria y la copia de archivos finaliza entonces las tareas en segundo plano se detendrán hasta que se culmine esta fase.


Paso 7: Imágenes aleatorias sobre las bondades de Canaima

Al igual que Ubuntu y otros sistemas operativos, podemos mostrar una serie de imágenes aleatorias que informen al usuario sobre las bondades de Canaima GNU/Linux y del Software Libre mientras termina la instalación.


Paso 8: Fin de la instalación

Al finalizar todo exitosamente se le mostrará un pequeño diálogo al usuario para que reinicie el equipo y comience a disfrutar de las bondandes del SL.



Bueno, esta es mi idea de lo que debería ser un instalador fácil e intuitivo para Canaima GNU/Linux. Se escuchan comentarios Update para los haters: Sí, mi propuesta está basada casi enteramente en el instalador de Ubuntu porque me parece un excelente instalador ¿Cuál es el problema con eso?

Configurar el Touchpad Synaptics de una HP Pavilion dm4 en Debian

lunes, octubre 31, 2011

En un artículo anterior expliqué cómo configurar el video Intel i915 con KMS en una HP Pavilion dm4. Esta vez explicaré cómo configurar el Touchpad Synaptics de esa misma laptop.

Leer el manual

Lo primero que debes hacer si deseas configurar correctamente el Touchpad es leer el manual (RTFM).
$ man synaptics

Yo configuré el touchpad a mi gusto, así que puedes probarlo y si no te gusta lo adaptas al tuyo. Sin embargo es una tarea de ensayo y error que no podrás completar a menos que conozcas todas las opciones que tienes disponibles.

Dependencias

Lo único que necesitamos es un Xorg bien configurado y los drivers synaptics. Para instalar los drivers ejecutamos:

En Debian:
# aptitude install xserver-xorg-input-synaptics


Entender cómo funciona el Touchpad

Básicamente no es un Touchpad, es un ClickPad y eso lo hace un poco especial. ¿Diferencias? Pues la superficie táctil también funciona como botón, que actua como uno u otro (left, right) dependiendo de la zona que se presione. No tenemos un área táctil con unos botones separados, tenemos con todo incluido, eso debe quedar muy claro.



Configuración

Xorg provee una serie de plantillas predeterminadas para la configuración de dispositivos. Estas plantillas se encuentran en /usr/share/X11/xorg.conf.d/ y basta con copiar a /etc/X11/xorg.conf.d/ la que necesitemos y comenzar a modificarla. Xorg se encargará de cargar estos valores luego de los valores predeterminados. Para nuestro caso usaremos la plantilla 50-synaptics.conf.

# mkdir /etc/X11/xorg.conf.d/
# cp /usr/share/X11/xorg.conf.d/50-synaptics.conf /etc/X11/xorg.conf.d/

Lo que haremos será modificar el InputClass para agregar los parámetros que deseamos configurar. En el manual de synaptics tenemos bien documentados cada una de las opciones con su descripción y posibles valores.

Mi configuración quedó así:
Section "InputClass"
    Identifier  "touchpad catchall"
    Driver      "synaptics"
    MatchIsTouchpad "on"
    MatchDevicePath "/dev/input/event*" 
        Option      "Protocol" "auto-dev"
        Option      "SHMConfig" "true"
        # Edges
        Option      "LeftEdge" "1500"
        Option      "RightEdge" "5200"
        Option      "TopEdge" "1350"
        Option      "BottomEdge" "3500"
        # Finger press
        Option      "FingerLow" "28"
        Option      "FingerHigh" "35"
        Option      "FingerPress" "70"
        # Taps time
        Option      "MaxTapTime" "180"
        Option      "MaxTapMove" "220"
        Option      "MaxDoubleTapTime" "180"
        Option      "SingleTapTimeout" "180"
        Option      "ClickTime" "100"
        Option      "FastTaps" "0"
        # Emulate
        Option      "EmulateMidButtonTime" "75"
        Option      "EmulateTwoFingerMinZ" "70"
        Option      "EmulateTwoFingerMinW" "5"
        # Scrolling
        Option      "VertScrollDelta" "100"
        Option      "HorizScrollDelta" "0"
        Option      "VertEdgeScroll" "1"
        Option      "HorizEdgeScroll" "0"
        Option      "CornerCoasting" "0"
        Option      "VertTwoFingerScroll" "1"
        Option      "HorizTwoFingerScroll" "1"
        # Pointer speed
        Option      "MinSpeed" "0.5"
        Option      "MaxSpeed" "7.0"
        Option      "AccelFactor" "0.35"
        Option      "TrackstickSpeed" "0"
        Option      "EdgeMotionMinZ" "29"
        Option      "EdgeMotionMaxZ" "59"
        Option      "EdgeMotionMinSpeed" "1"
        Option      "EdgeMotionMaxSpeed" "40"
        Option      "EdgeMotionUseAlways" "0"
        # Scrolling flags
        Option      "UpDownScrolling" "0"
        Option      "LeftRightScrolling" "0"
        Option      "UpDownScrollRepeat" "0"
        Option      "LeftRightScrollRepeat" "0"
        Option      "ScrollButtonRepeat" "100"
        # Touchpad mouse on/off
        Option      "TouchpadOff" "0"
        Option      "GuestMouseOff" "0"
        # Dragging
        Option      "LockedDrags" "0"
        Option      "LockedDragsTimeout" "5000"
        # Corners
        Option      "RTCornerButton" "0"
        Option      "RBCornerButton" "2"
        Option      "LTCornerButton" "0"
        Option      "LBCornerButton" "1"
        # Tap
        Option      "TapButton1" "1"
        Option      "TapButton2" "3"
        Option      "TapButton3" "2"
        # Click
        Option      "ClickFinger1" "1"
        Option      "ClickFinger2" "0"
        Option      "ClickFinger3" "0"
        # Circular
        Option      "CircularScrolling" "0"
        Option      "CircScrollDelta" "0.1"
        Option      "CircScrollTrigger" "0"
        Option      "CircularPad" "0"
        # Palm
        Option      "PalmDetect" "1"
        Option      "PalmMinWidth" "7"
        Option      "PalmMinZ" "40"
        Option      "CoastingSpeed" "0"
        # Grab
        Option      "GrabEventDevice" "1"
        Option      "TapAndDragGesture" "1"
        # Area
        Option      "AreaLeftEdge" "0"
        Option      "AreaRightEdge" "0"
        Option      "AreaTopEdge" "0"
        Option      "AreaBottomEdge" "3500"
EndSection

Section "InputClass"
    Identifier  "Ignore mouse devs"
    MatchDevicePath "/dev/input/mouse*"
    Driver      "synaptics"
    Option      "Ignore" "on"
EndSection

Actualización: Acá pueden ver la configuración actual que tengo en mi repo github

No explicaré en detalle cada una de las opciones (para eso está el manual de synaptics), solo explicaré las más relevantes o las "problemáticas". Lo primero es ignorar las pulsaciones en el área de los botones. Para eso definimos el rango de acción del dispositivo (límites):
        # Edges
        Option      "LeftEdge" "1500"
        Option      "RightEdge" "5200"
        Option      "TopEdge" "1350"
        Option      "BottomEdge" "3500"

        # Area
        Option      "AreaLeftEdge" "0"
        Option      "AreaRightEdge" "0"
        Option      "AreaTopEdge" "0"
        Option      "AreaBottomEdge" "3500"

Colocando el límite inferior (BottomEdge y AreaBottomEdge) en 3500 evitamos que el cursor se mueva al pasar sobre el área de botones. Estableciendo el límite derecho (RightEdge) a 5200 evitamos que el cursor se mueva en la columna derecha (para habilitar la zona de scrolling vertical). La imagen a continuación ilustra estos ajustes:



El dispositivo solo tiene un botón y a simple vista no encontré la forma de configurarlo para que funcione como ambos. Estuve compilando el driver synaptics con un patch para agregar el soporte de clickpads pero no he logrado generar el .ko, así que simplemente dejé su funcionamiento por defecto (left click) y configuré el click izquierdo con tap, el doble click con doble tap y el click derecho con tap de dos dedos.

Eso podemos verlo en:
        # Tap
        Option      "TapButton1" "1"
        Option      "TapButton2" "3"
        Option      "TapButton3" "2"

El botón para activar/desactivar el clickpad tampoco pude hacerlo funcionar pero creo que es cuestión de seguir trasteando hasta lograr una solución. Además de eso, el resto de la configuración es el ajuste de valores para la sensibilidad, aceleración y velocidad del puntero, pero eso lo dejo como tarea para el lector.

Tarea para la casa

Una de las herramientas más importantes para lograr una efectiva configuración es synclient. Gracias a esta herramienta pude determinar las coordenadas que definen los límites de mi dispositivo (que en las imágenes de arriba están marcados con azul y rojo) y verificar los valores de configuración

Usando el siguiente comando podemos verificar la actividad del touchpad en vivo:
$ synclient -m 10

La salida debería ser algo como:
   time     x    y   z f  w  l r u d m     multi  gl gm gr gdx gdy
 874.169  5392 4317   3 0  0  0 0 0 0 0  00000000
 874.269  5392 4317   0 0  0  0 0 0 0 0  00000000
 874.670  5392 4317   1 0  0  0 0 0 0 0  00000000
 874.770  5392 4317   7 0  0  0 0 0 0 0  00000000
 874.870  4143 1708   9 1 11  0 0 0 0 0  00000000
 874.970  4225 1811   3 0  0  0 0 0 0 0  00000000
 875.070  4225 1811   2 0  0  0 0 0 0 0  00000000

Donde:
  • x, y: definen las coordenadas de la pulsación
  • z: la presión aplicada
  • f: el número de dedos tocando la superficie
  • w: es una medida del ancho del dedo
  • l,r,m: el estado de los botones left, right y middle

Para más información:
$ man synclient

Espero que con esta guía puedas configurar tu ClickPad y usarlo decentemente. Me queda pendiente por investigar la función de deshabilitar el touchpad mientras se escribe, los botones left-right y el botón para activar. Cuando lo logré postearé nuevamente :)

Fuentes:

Configurar video Intel i915 + KMS en Debian Wheezy

viernes, octubre 28, 2011

Es esta receta explicaré como configurar y activar el KMS (Kernel Mode Setting) con el driver Intel i915 en una HP Pavilion dm4 . Las instrucciones están hechas para Debian Wheezy (testing a la fecha) y requiere de un kernel >= 2.6.29.

Instalar dependencias

Suponiendo que tenemos Xorg instalado y corriendo (quizás funcionando con el driver vesa) procedemos instalar el driver intel y sus dependencias:
# aptitude install libgl1-mesa-glx libglu1-mesa xserver-xorg-video-intel mesa-utils

Activar el KMS

Luego vamos al archivo /etc/modprobe.d/i915-kms.conf y agregamos la línea:
options i915 modeset=1

Regeneramos la imagen del kernel con:
# update-initramfs -u

Editamos el archivo /etc/default/grub y modificamos esta línea:
GRUB_CMDLINE_LINUX_DEFAULT="quiet i915.modeset=1 video=LVDS-1:e"

El parámetro i915.modeset=1 habilita el KMS al momento del booteo y para que la pantalla no se quede en negro luego de cargar el módulo, debemos agregar el parámetro video=LVDS-1:e.

Es importante no cargar ningún otro driver para framebuffer que no sea el de Intel y eliminar de los parámetros de booteo cualquier opción del tipo vga=xxx o video=xxx.

El parámetro video=LVDS-1:e tiene una razón de ser muy particular. El BIOS de esta máquina tiene un bug; cuando inicia, indica erróneamente que la tapa de la laptop está cerrada. El kernel, basándose en esa información, manda a apagar la pantalla y por eso todo se queda en negro, no es que se congela ni que deja de funcionar. Pueden corroborar esto cerrando y abriendo la tapa de la laptop cuando se apaga. Verán que el video vuelve mágicamente y que el equipo no se había detenido, seguía funcionando pero a ciegas.

Es bien conocido que el kernel no usa el BIOS para casi ninguna de sus tareas, sin embargo a partir del kernel 2.6.32 los desarrolladores decidieron "escuchar" al BIOS en este tipo de eventos, así que hasta que no se masifique el patch que corrige la situación debemos aplicar este correctivo.

Si desean más información pueden revisar el reporte y la resolución del bug en Launchpad o el reporte del error en fredesktop.org

Luego de ese paréntesis técnico continuamos con nuestra configuración. Reconfiguramos el grub para que se apliquen los cambios anteriores:
# update-grub

Configuración de Xorg

Lo siguiente es crear y personalizar el archivo de configuración de Xorg.

A partir de la versión 1.8, Xorg ya no necesita archivos de configuración. En lugar de eso intenta detectar y configurar todo el hardware (video y dispositivos de entrada) por si mismo. Sin embargo existen casos especiales que requieren algunos "ajustes". Este es uno de esos casos.

Procedemos entonces a detener el servidor X. Si tenemos un entorno de escritorio basta con detener el gestor de sesión (gdm, kdm, slim, xdm, etc), sino matamos las X.

Hecho esto pasamos a ejecutar:
# Xorg -configure

Eso debería generarnos un archivo xorg.conf.new que servirá como base. A ese archivo le editamos la sección "Device" para que nos quede algo como:
Section "Device"
        Option      "DRI"                       "True"
        Option      "Tiling"                    "True"
        Option      "XAANoOffscreenPixmaps"     "True"
        Identifier  "Card0"
        Driver      "intel"
        BusID       "PCI:0:2:0"
EndSection

Y agregamos al final del archivo la siguiente sección:
Section "Extensions"
        Option          "Composite" "enable"
EndSection

Observen que indicamos el driver de video y algunas opciones para mejorar el desempeño gráfico. Si conocen otras opciones (que apliquen para tarjetas Intel) también pueden agregarlas acá.

Con esto terminamos nuestra configuración del servidor X.

Reiniciar

Reiniciamos y ya deberíamos disfrutar de nuestro video usando KMS. Verán que la transición entre modos de video es casi instantánea y el parpadeo se redujo considerablemente. Además de que compiz y todas esas yerbas aromáticas se ven con una suavidad asombrosa.

Deshabilitar el KMS

Si desean deshabilitar el KMS basta con desactivar el modeset en la línea:
GRUB_CMDLINE_LINUX_DEFAULT="quiet i915.modeset=0 video=LVDS-1:e"

Reconfiguramos el grub:
# update-grub

Y KMS desactivado.

Espero que esta receta les haya servido de ayuda. En un próximo post explicaré cómo configurar el Touchpad Synaptics.

Fuentes:

Obtener audio de un video de Youtube

sábado, septiembre 17, 2011

¿No les ha pasado que a veces encuentran en Youtube videos que son unas verdaderas joyas musicales y les gustaría tenerlos en su colección musical? Bueno con este truco es muy fácil hacerlo.

Primero instalamos el Youtube Downloader. Desde Debian sería:
# aptitude install youtube-dl
Instalamos el ffmpeg (en caso de no tenerlo):
# aptitude install ffmpeg
Luego descargamosel video desde Youtube y extraemos el audio:
$ youtube-dl --extract-audio
Eso nos generará un archivo .aac, ahora procedemos a convertirlo a mp3 (o al formato de nuestra preferencia):
$ ffmpeg -i .aac -ar 22050 -ab 32 .mp3
Y ya podremos disfrutar de la pieza de audio en nuestro reproductor favorito a partir del video de Youtube

Instalar RVM en Debian Wheezy

sábado, mayo 28, 2011

RVM (Ruby Version Manager) es una fantástica herramienta que te permite gestionar muchas versiones de Ruby (y de sus gemas) en un mismo equipo. Las gemas se manejan mediante contenedores (gemsets), allí puedes instalar lo que desees sin afectar el resto de los componentes. Es un principio muy parecido al de las máquinas virtuales.

La receta es para Debian pero se puede aplicar para cualquier distro, solo hay que tener en cuenta los nombres de los paquetes a instalar.

Comencemos entonces con las dependencias. Necesitamos git, las herramientas básicas para compilar, los fuentes del readline y del ssl:

# aptitude install libreadline5-dev git curl build-essential libssl-dev

Luego, como usuario ejecutamos el siguiente script:

$ bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)

Ese script descarga rvm de git, lo compila y lo instala. Antes de usarlo debemos agregar una línea a nuestro .bash_profile, para eso ejecutamos:

$ echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM function' >> ~/.bash_profile

Hacemos un:

$ source ~/.bash_profile

Y verificamos que todo haya salido bien:

$ type rvm | head -1

El resultado de ese comando debería ser: rvm es una función. Si no obtenemos ese resultado algo ha ido mal. Les recomiendo entonces revisar la documentación de instalación o la página de problemas solucionados.

Ahora podemos ejecutar rvm como un comando más de consola. Instalemos un par de paquetes que nos harán falta antes de instalar una versión de Ruby:

$ rvm package install openssl
$ rvm package install readline


Instalamos una versión de Ruby (por ejemplo: 1.8.7):

$ rvm install 1.8.7

Y configuramos la versión que deseamos usar por defecto en el sistema:

$ rvm use 1.8.7 --default
Using /home/tu_usuario/.rvm/gems/ruby-1.8.7-p334


Ahora podemos crear gemsets a placer, por ejemplo:

$ rvm gemset create pruebas1
'pruebas1' gemset created (/home/tu_usuario/.rvm/gems/ruby-1.8.7-p334@pruebas1).


Notese que el @ en el mensaje anterior nos indica que el gemset ha sido asociado a la versión 1.8.7 de Ruby que habíamos instalado (y seleccionado para usar) previamente. Ahora instalemos otro gemset de pruebas para entender la magia del asunto.

$ rvm gemset create pruebas2
'pruebas2' gemset created (/home/tu_usuario/.rvm/gems/ruby-1.8.7-p334@pruebas2).


Podemos movernos a través de los gemsets usando el comando use con la siguiente forma:

$ rvm use 1.8.7@pruebas1
Using /home/tu_usuario/.rvm/gems/ruby-1.8.7-p334 with gemset pruebas1


Ahora probemos instalar una gema en el contenedor 'pruebas1':

$ gem install xml-simple --no-rdoc --no-ri
Fetching: xml-simple-1.0.15.gem (100%)
Successfully installed xml-simple-1.0.15
1 gem installed


Particularmente uso los argumentos --no-rdoc --no-ri para evitar la instalación de la documentación y otros archivos adicionales que no utilizo y demoran considerablemente la puesta a punto de la gema.

Veamos entonces qué tenemos ahora en 'pruebas1':

$ gem list --local

*** LOCAL GEMS ***

rake (0.8.7)
xml-simple (1.0.15)


Una nueva gema instalada en nuestro gemset. Y en 'pruebas2' ¿qué tenemos?:

$ rvm use 1.8.7@pruebas2
Using /home/tu_usuario/.rvm/gems/ruby-1.8.7-p334 with gemset pruebas2

$ gem list --local

*** LOCAL GEMS ***

rake (0.8.7)


¡Oh! No tenemos nada instalado. Eso quiere decir que nuestros contenedores están aislados uno del otro y que podemos instalar cosas que cada uno de ellos sin temor a romper algo en el otro (siempre teniendo cuidado del gemset que está en uso).

Si en algún momento dañamos un gemset basta con ejecutar:

$ rvm gemset delete pruebas1
WARN: Are you SURE you wish to remove the entire gemset directory 'pruebas1' (/home/tu_usuario/.rvm/gems/ruby-1.8.7-p334@pruebas1)?
(anything other than 'yes' will cancel) >


Respondemos yes y listo, desaparece el gemset con todo lo que tenía adentro.

Muy bonito todo, pero se preguntarán ¿Cómo le digo a un determinado proyecto que use un gemset específico? ¿O es que debo especificarle manualmente que gemset utilizará cada vez que vaya a ejecutarlo?

Pues la respuesta es muy simple, basta con crear un archivo .rvmrc en la raíz del proyecto con algo parecido a esto:

rvm use 1.8.7@pruebas2

Y desde ese momento, todo lo que está dentro de esa carpeta usará la versión del Ruby y el gemset especificado en el archivo.


NOTA: Es importante aclarar que Ruby, RubyGems, las gemas o cualquier otras cosa relacionada NO debe instalarse usando la paquetería de la distribución. TODO debe manejarse a través RVM, de lo contrario corromperemos toda la instalación.

Espero que esta receta les sea de utilidad, a mi me ha cambiado la vida xD

Debian Wheezy sin interfaz inalámbrica luego de un safe-upgrade

martes, mayo 24, 2011

Me ocurrió hace unos días que actualicé mi Debian y repentinamente me quedé sin conexión inalámbrica. Uso Debian Testing (Wheezy para la fecha) en una HP Pavilion dv2000 y la tarjeta de red inalámbrica es una Intel PRO/Wireless 4965 AG.

Leyendo y leyendo diferentes manuales todos afirmaban que con solo instalar el paquete firmware-iwlwifi del repo non-free la cosa funcionaba a la perfección y me constaba porque así lo había hecho en un principio. Sin embargo, luego de actualizar no anduvo más y lo curioso es que el lspci me devolvía señales de vida:

07:00.0 Network controller: Intel Corporation PRO/Wireless 4965 AG or AGN [Kedron] Network Connection (rev 61)

El paquete en efecto estaba instalado:

$ aptitude search iwl | grep ^i
i firmware-iwlwifi - Binary firmware for Intel Wireless 3945, 4


El lsmod me mostraba los módulos cargados y todo estaba bien:

$ lsmod | grep iwl
iwlagn                122417  0 
iwlcore                50368  1 iwlagn
mac80211              160285  2 iwlagn,iwlcore
cfg80211              106889  3 iwlagn,iwlcore,mac80211


Incluso, los binarios del firmware también parecían estar en orden:

# find / -name "*ucode*"
/sys/module/iwlagn/parameters/ucode_alternative
/lib/firmware/iwlwifi-6050-5.ucode
/lib/firmware/iwlwifi-3945-2.ucode
/lib/firmware/iwlwifi-6000g2b-5.ucode
/lib/firmware/iwlwifi-5000-5.ucode
/lib/firmware/iwlwifi-5000-1.ucode
/lib/firmware/iwlwifi-4965-2.ucode
/lib/firmware/iwlwifi-5000-2.ucode
/lib/firmware/iwlwifi-4965-1.ucode
/lib/firmware/iwlwifi-6000-4.ucode
/lib/firmware/iwlwifi-6000g2a-5.ucode
/lib/firmware/iwlwifi-1000-3.ucode
/lib/firmware/iwlwifi-6050-4.ucode
/lib/firmware/iwlwifi-3945-1.ucode
/lib/firmware/iwlwifi-5150-2.ucode
/lib/firmware/iwlwifi-100-5.ucode


Luego intento ver el log del kernel y me encuentro con algo que me dio algunas pistas:

# tail -f /var/log/syslog
May 24 23:03:31 mpn82 kernel: [ 6214.119061] iwlagn: Intel(R) Wireless WiFi Link AGN driver for Linux, in-tree:
May 24 23:03:31 mpn82 kernel: [ 6214.119064] iwlagn: Copyright(c) 2003-2010 Intel Corporation
May 24 23:03:31 mpn82 kernel: [ 6214.119140] iwlagn 0000:07:00.0: PCI INT A -> GSI 19 (level, low) -> IRQ 19
May 24 23:03:31 mpn82 kernel: [ 6214.119153] iwlagn 0000:07:00.0: setting latency timer to 64
May 24 23:03:31 mpn82 kernel: [ 6214.119187] iwlagn 0000:07:00.0: Detected Intel(R) Wireless WiFi Link 4965AGN, REV=0x4
May 24 23:03:31 mpn82 kernel: [ 6214.157779] iwlagn 0000:07:00.0: device EEPROM VER=0x36, CALIB=0x5
May 24 23:03:31 mpn82 kernel: [ 6214.157782] iwlagn 0000:07:00.0: Device SKU: 0Xb
May 24 23:03:31 mpn82 kernel: [ 6214.157979] iwlagn 0000:07:00.0: Tunable channels: 11 802.11bg, 13 802.11a channels
May 24 23:03:31 mpn82 kernel: [ 6214.158062] iwlagn 0000:07:00.0: irq 46 for MSI/MSI-X
May 24 23:03:31 mpn82 NetworkManager[1140]:  found WiFi radio killswitch rfkill4 (at /sys/devices/pci0000:00/0000:00:1c.3/0000:07:00.0/ieee80211/phy2/rfkill4) (driver )
May 24 23:03:31 mpn82 kernel: [ 6214.161805] iwlagn 0000:07:00.0: loaded firmware version 228.61.2.24
May 24 23:03:31 mpn82 kernel: [ 6214.162128] ieee80211 phy2: Selected rate control algorithm 'iwl-agn-rs'
May 24 23:03:31 mpn82 NetworkManager[1140]:    SCPlugin-Ifupdown: devices added (path: /sys/devices/pci0000:00/0000:00:1c.3/0000:07:00.0/net/wlan0, iface: wlan0)
May 24 23:03:31 mpn82 NetworkManager[1140]:    SCPlugin-Ifupdown: device added (path: /sys/devices/pci0000:00/0000:00:1c.3/0000:07:00.0/net/wlan0, iface: wlan0): no ifupdown configuration found.
May 24 23:03:31 mpn82 NetworkManager[1140]:  (wlan0): driver supports SSID scans (scan_capa 0x01).
May 24 23:03:31 mpn82 NetworkManager[1140]:  (wlan0): new 802.11 WiFi device (driver: 'iwlagn' ifindex: 6)
May 24 23:03:31 mpn82 NetworkManager[1140]:  (wlan0): exported as /org/freedesktop/NetworkManager/Devices/3
May 24 23:03:31 mpn82 NetworkManager[1140]:  (wlan0): now managed
May 24 23:03:31 mpn82 NetworkManager[1140]:  (wlan0): device state change: 1 -> 2 (reason 2)
May 24 23:03:31 mpn82 NetworkManager[1140]:  (wlan0): bringing up device.
May 24 23:03:31 mpn82 NetworkManager[1140]:  (wlan0): deactivating device (reason: 2).


1.- El firmware se estaba cargando con la versión más actualizada según la página de Intel
May 24 23:03:31 mpn82 kernel: [ 6214.161805] iwlagn 0000:07:00.0: loaded firmware version 228.61.2.24

2.- Por alguna razón la interfaz se estaba apagando luego que cargaba el NetworkManager

Decidí buscar un poco en internet y me conseguí con este reporte de bug en Red Hat que involucraba al NetworkManager, la interfaz wireless y un mensaje de error muy similar al mío.

Probé entonces con:

# rfkill list
0: hp-wifi: Wireless LAN
 Soft blocked: yes
 Hard blocked: no
2: hp-bluetooth: Bluetooth
 Soft blocked: yes
 Hard blocked: no
4: phy2: Wireless LAN
 Soft blocked: yes
 Hard blocked: yes


Jum... phy2 y hp-wifi bloqueados por software, adicionalmente phy2 bloqueado por hardware. Raro, raro.

Hago un:

# rfkill unblock 0 && rfkill unblock 4

Y voilá, volvió a enceder el led de la interfaz inalámbrica e inmediatamente se conectó a internet :D

Cosas tontas pero que te salvan la vida. Espero que les sea de utilidad

LoadError con el readline al intentar abrir una consola de Rails usando rvm

lunes, mayo 02, 2011

Si usando rvm, intentas abrir una consola de Rails (con rails c o con script/console) y te escupe un error como este:

no such file to load -- readline (LoadError)

Entonces sigue estos pasos y resolverás el problema.

Primero, debemos instalar el paquete del readline en rvm (y no en el Sistema Operativo). Para eso removemos la versión actual de Ruby que podamos tener:

$ rvm remove 1.8.7

Instalamos el paquete readline:

$ rvm install package

E instalamos Ruby pero indicándole la ubicación del paquete readline:

$ rvm install 1.8.7 --with-readline-dir=$rvm_path/usr

Acto seguido, entramos a la carpeta en donde rvm almacena los fuentes de las extensiones de Ruby, específicamente del readline. En mi caso:

$ cd ~/.rvm/src/ruby-1.8.7-p302/ext/readline

Luego (como root) instalamos las dependencias necesarias. En Debian sería:

# aptitude update
# aptitude install libreadline5-dev


Procedemos (como usuario) a compilar e instalar la extensión en cuestión:

$ ruby extconf.rb
$ make
$ make install


Y con eso ya deberíamos disfrutar nuevamente de la consola interactiva de Rails sin problemas.

Habilitar la función de "tap" en un TouchPad

viernes, abril 22, 2011

En estos días estaba usando mi netbook y me di cuenta que al hacer tap sobre el touchpad no se ejecutaba el click del ratón y no fue hasta hoy que me digné a buscar la solución. Es tan simple como agregar una línea en un archivo de texto.

Buscamos el archivo /etc/modprobe.d/options.conf, si no existe lo creamos y copiamos la siguiente línea:

options psmouse proto=imps

Luego hacemos (como root):

# modprobe -r psmouse
# modprobe psmouse proto=imps


Y a disfrutar del tap xD

Resaltado de sintaxis HAML en Gedit

lunes, febrero 28, 2011

Trabajando con Ruby On Rails me ha tocado escribir código usando HAML. Particularmente no me agrada mucho (entre otras cosas porque no tiene soporte multilínea, porque no identa correctamente las cadenas de texto plano, porque agrega otra capa más de interpretación a la aplicación, etc) pero cuando toca usarlo es bueno hacerlo con un buen soporte. Y cuando hablo de soporte me refiero al resaltado de sintaxis.

Para escribir código usualmente uso un editor de texto (Gedit o Geany), así que en ésta receta explicaré como agregar soporte para el resaltado de sintaxis en Gedit.

Primero agregamos soporte para que las extensiones usadas comúnmente en Rails sean reconocidas por el sistema. Abrimos una terminal y escribimos:

wget http://dl.dropbox.com/u/16349833/rails.xml 
sudo cp rails.xml /usr/share/mime/packages/rails.xml 
sudo update-mime-database /usr/share/mime

Luego agregamos los archivos de definición de sintaxis en la carpeta del sourceview de GTK ejecutando:

wget http://dl.dropbox.com/u/16349833/gedit_rails_syntax.zip
sudo unzip gedit_rails_syntax.zip -d /usr/share/gtksourceview-2.0/language-specs


Ahora con nuestro editor de texto favorito (y como root) abrimos el archivo /usr/share/gtksourceview-2.0/language-specs/ruby.lang para modificar el sourceview de GTK y agregar soporte para nuevas extensiones. Buscamos la línea:

*.rb

Y la cambiamos por:

*.rb;*.rake;*.rjs

Por último, abrimos el archivo /usr/share/gtksourceview-2.0/language-specs/html.lang y buscamos la línea que dice:

*..html;*.htm

Y la cambiamos por:

*.html;*.htm;*.erb;*.rhtml

Cerramos todas las instancias de Gedit y al abrir de nuevo ya nuestro código HAML debería verse resaltado.


Existen recetas que instalan una serie de plugins (con soporte para haml, rjs, etc) y te dejan al Gedit como TextMate, pero como no me gusta TextMate entonces prefiero hacerlo a patica xD

Espero que la receta les haya servido de ayuda.

Referencias:
http://groups.google.com/group/haml/browse_thread/thread/6c8babd81a46b6b8/b5100d80d9182c71?pli=1
http://blog.adsdevshop.com/2008/04/19/erb-syntax-highlighting-in-gedit/

¿Cómo desarrollar para Android? - Parte II

martes, diciembre 21, 2010

En la entrega anterior expliqué como instalar el ambiente de desarrollo para trabajar con Android. Ahora explicaré algunos fundamentos necesarios para poder entrar en calor.

Podemos decir que Android está compuesto por varias capas (muy al estilo del modelo OSI).

Esas capas las podemos diferenciar en 5 grupos:

  • Capa 1 - Kernel: La base de Android es sólida como una roca. Claro, esa base es el kernel Linux. Android usa Linux para manejar todo lo relacionado con el hardware (drivers), gestionar la memoria, los procesos y muchas otras tareas operativas. Sin embargo nuestra aplicación nunca tocará el kernel.
  • Capa 2 - Librerías nativas: La siguiente capa que se ubica por encima del kernel son las librerías nativas. Allí tenemos una serie de herramientas que vienen precompiladas por el fabricante y que nos permiten manejar desde renderizado web hasta bases de datos.
  • Capa 3 - Núcleo: Sobre ésta capa tenemos el núcleo de Android. Allí reside la máquina virtual Dalvik y las librerías de la plataforma.
  • Capa 4 - Framework para aplicaciones: Más arriba está el conjunto de herramientas que nos permite crear y manejar las aplicaciones, notificaciones, recursos y muchas otras cosas.
  • Capa 5 - Aplicaciones: Y por último tenemos la capa de aplicaciones, aquí es donde se encuentran los widgets y todas las aplicaciones que tienen contacto con el usuario.

Ya conocemos un poco mejor la arquitectura de Android, ahora veamos con qué contamos para desarrollar. Parte del framework para aplicaciones son los componentes. Estuadiaremos los más importantes por el momento, ellos son:

  • Activities: Los activities (o actividades) son las interfaces que permiten al usuario interactuar con la aplicación. Generalmente una aplicación está compuesta de muchas actividades. Una actividad puede ser mostrar la lista de contactos y otra puede ser enviar mensajes de texto
  • Intents: Un intent (o como se diría en español, una "intención") no es más que una acción. En Android todas las acciones se manejan con intenciones ;)
  • Services: Es una tarea que se ejecuta en segundo plano, sin interfaz gráfica y sin interacción directa con el usuario. Podemos pensar en el ejemplo típico: un reproductor de música. A todos nos gusta que la música siga sonando incluso si dejamos de ver el reprouctor para pasarnos al navegador web. Eso es un servicio
  • Content Providers: Es un contenedor que permite agrupar un conjunto de datos de una aplicación y ponerlos a disposición de otras aplicaciones. Podemos verlo como una manera de compartir información global entre aplicaciones

Por último vamos a mencionar las fases del ciclo de vida de una aplicación.

Lo primero que debemos entender es que (a diferencia de un sistema operativo de escritorio) en Android una aplicación no está casada con un proceso. En Android existe una sola aplicación que obtiene el foco y se presenta frente al usuario. Mientras ésta aplicación se encuentra en el frente, las demás aplicaciones entran en estados de pausa, detención o incluso, si la memoria es escasa, Android puede llegar a cerrarlas para liberar recursos y sin estar mediando muchas palabras.

Pero no se asusten, si la aplicación es "asesinada" por Android su estado se almacena para que pueda recuperarse cuando el usuario la ejecute nuevamente. Es en este ínterin de cerrar y abrir aplicaciones en que se liberan los procesos. Debemos ver a un proceso como un simple contenedor desechable para las aplicaciones, no más. Siempre ten presente éste comportamiento al momento de diseñar tus aplicaciones.

El ciclo de vida de una aplicación lo podemos ver en la siguiente imagen.


A continuación describo brevemente cada una de sus fases:

  • onCreate(): Se ejecuta la primera vez que la aplicación se muestra. Aquí se pueden realizar toda la inicialización estática (conexiones a bases de datos, creación de interfaces, etc). Recibe un parámetro que puede contener el estado anterior de la aplicación (si fue almacenado correctamente)
  • onStart(): Se ejecuta justo antes de que la aplicación se haga visible al usuario
  • onRestart(): Llamado justo después que la aplicación ha sido detenida. Justo antes de comenzar de nuevo
  • onResume(): Se llamará cuando la aplicación esté lista para interactuar con el usuario. Acá se pueden inicializar cosas como música y animaciones
  • onPause(): Se ejecuta justo antes de que la aplicación pase a segundo plano porque otra aplicación ha sido ejecutada. Este método puede ser lo último que vea tu aplicación antes de morir, pues Android puede matar una aplicación pausado sin previo aviso. Es por eso que este es un buen lugar para guardar el estado de tu aplicación.
  • onStop(): Es llamado cuando la aplicación ya no es visible al usuario y no se necesitará por un rato. Tal como se indicó en el punto anterior, este método es posible que ni siquiera se ejecute
  • onDestroy(): Se llama justo después que la aplicación es destruida. Acá debes hacer el sepelio y esas cosas. Tal como se indicó en el punto anterior, este método es posible que ni siquiera se ejecute

Con toda ésta base teórica podemos decir que estamos listos para empezar a escribir el código de nuestra primera aplicación. Ese será el tema de la próxima entrega.

Para más información sobre éste tema puedes consultar la documentación oficial de Android

¿Cómo desarrollar para Android? - Parte I

Python For Android - Mito o Realidad

miércoles, noviembre 24, 2010

Estuve unas cuantas horas investigando sobre la posibilidad de desarrollar con Python sobre Android y a continuación les daré un resumen de los resultados y conclusiones que obtuve.

Acá les dejo el script que usé para probar una parte de los widgets disponibles en la API por si quieren probarlo y echarle un ojo (no me detendré a explicarlo mucho porque el código prácticamente se explica a sí mismo). Además supongo que siguieron los pasos de mi tutorial sobre ¿Cómo desarrollar para Android? y tienen configurado su entorno de desarrollo y saben cómo usar el emulador.

droid = android.Android()

tiempo = droid.dialogGetInput("Escriba la cantidad de tiempo que desea para el demo (10-100)","Tiempo:")
print tiempo

tiempo = int(tiempo[1])
if tiempo < 10: tiempo = 10 elif tiempo > 100:
tiempo = 100

droid.dialogCreateHorizontalProgress('Barra de Progreso', 'Hola, estoy progresando...', tiempo)
droid.dialogShow()
for i in range(tiempo):
time.sleep(0.5)
droid.dialogSetCurrentProgress(i)
droid.dialogDismiss()

droid.dialogCreateSpinnerProgress('Spinner de progreso', 'Mirá, mirá... estoy girando')
droid.dialogShow()
time.sleep(tiempo)
droid.dialogDismiss()

droid.notify('Test de Python' , 'Oh! puedo notificar también')

droid.dialogCreateAlert(title, message)
droid.dialogSetPositiveButtonText('Si')
droid.dialogSetNegativeButtonText('No')
droid.dialogSetNeutralButtonText('Cancelar')
droid.dialogShow()
response = droid.dialogGetResponse().result
print response
droid.makeToast('El resultado de la ejecucion del boton es: %s' %response['which'])

droid.dialogCreateAlert('Selecciona un color')
colores = ('amarillo', 'azul', 'rojo')
droid.dialogSetItems(colores)
droid.dialogShow()
respuesta = droid.dialogGetResponse().result
print respuesta
droid.makeToast('El color seleccionado es: %s' %colores[respuesta['item']])

droid.makeToast('Bueno, chao')


Para correrlo deben instalar algunos paquetes en el cel. Pueden hacerlo con el comando del SDK:

$ adb install /ruta/del/archivo.apk

con los QR que están en la página del proyecto SL4A o con el método que mejor les parezca.

Ok, las cosas a instalar son:


Luego que las instalen van al dispositivo/emulador y buscan el icono de Python For Android, tap e instalan. Eso tardará unos minutos mientras descarga unas cosas. Al terminar entonces enviamos el script de prueba al cel, podemos usar el comando:

$ adb push /ruta/del/script.py /sdcard/sl4a/scripts/

Eso lo copiará en la carpeta del SL4A. Ahora cuando ejecutemos el Python For Android veremos en la lista de scripts nuestra infame creación, tap en el script y ejecutar. Lo demás es disfrutar las ventanitas y controles :P

Conclusiones


  1. La API de SL4A es reducida, es decir si bien está bastante adelantada aún faltan muchas cosas/funciones por implementar.
  2. Python For Android a diferencia de lo que pensaba no soporta widgets gráficos en Android, solamente notificaciones, toast y cosas básicas tipo entrada de texto, selección, etc. Así que nos olvidamos de interfaces gráficas ricas en imágenes y esas cosas.
  3. Sí existen algunos issues de performance, de hecho un pana que está trabajando duro con Android me dice que los desarrolladores todos los días se matan por tratar de mejorar el rendimiento pues de por si ya es algo lento (hablo de apps nativas). Sin embargo la versión de Python es cross-compiled y corre incluso más rápido que Jython
  4. Hay formas de empaquetar scripts en .apk y aunque son algo un poco experimentales funcionan bastante bien (son, digamos, "tweaks" del método de construcción de paquetes con Java)
  5. En cuanto a estabilidad no hay mayores issues, se puede usar para aplicaciones de producción


Finalmente puedo decir (muy a mi pesar) que no podemos contar con ésta alternativa para desarrollar aplicaciones ricas en interfaces gráficas, sin embargo es una opción interesante a tener en cuenta cuando madure un poco más. Personalmente le haré seguimiento tipo ninja y esperaré con ansias que en el futuro se puedan crear aplicaciones tan buenas como se hace con Java.

Más información en:

* http://code.google.com/p/android-scripting/
*http://code.google.com/p/android-scripting/wiki/FAQ
* http://code.google.com/p/android-scripting/wiki/Tutorials
* http://code.google.com/p/android-scripting/wiki/ApiReference

¿Cómo desarrollar para Android? - Parte I

sábado, noviembre 20, 2010



Actualmente me he visto en la necesidad de crear aplicaciones para Android y como no había incursionado en ese mundo, he decidido hacer una serie de artículos para documentar todo el proceso.

En ésta primera entrega explicaré los pasos que seguí para poner a punto el entorno de desarrollo. Debo resaltar que las recetas están hechas para Debian GNU/Linux, sin embargo puedes adaptarla a tu distro favorita con unos leves ajustes.

Para los que no están familiarizados con mis tutoriales les recuerdo que todas las instrucciones que comiencen con el caracter $ se ejecutan como usuario normal y las que comiencen con # se ejecutan como superusuario (root), por ejemplo:

$ ls -l (instrucción ejecutada como usuario)
# aptitude update (instrucción ejecutada como root)


Dicho esto comenzamos.

Instalar dependencias


Lo primero que debemos hacer es instalar los paquetes necesarios para el desarrollo, que básicamente son: la máquina virtual de Java (JRE) y el entorno de desarrollo (JDK). En una distribución Debian GNU/Linux bastaría con:

# aptitude update
# aptitude install sun-java6-jdk sun-java6-jre
# aptitude remove gcj-jdk


Si se dan cuenta usamos las implementaciones de SUN y removimos cualquier posible instalación del compilador GNU de Java (gcj) pues en la documentación oficial nos dicen que gcj NO es compatible con Android.

Para más información sobre los requerimientos del sistema visita: http://developer.android.com/sdk/requirements.html

Luego que instalamos los paquetes nos aseguramos de que el sistema seleccione las opciones correctas por defecto de cada ejecutable, pare eso usamos:

# update-alternatives --config javac
# update-alternatives --config java


Y en ambos casos, seleccionamos las opciones de SUN.

Instalar Ant


Ant es una herramienta que nos permite construir aplicaciones Java. En nuestro caso, nos permitirá compilar y construir archivos .apk (los instalables en Android) a partir de nuestro código fuente.

Nos vamos a la página de descargas de Ant, descargamos uno de los archivos comprimidos que se encuentran en la sección "Current Release of Ant" y lo descomprimimos en una carpeta de nuestra preferencia. En mi caso lo hice en /opt y me quedó la carpeta: /opt/apache-ant-1.8.1.

Instalar el Android SDK


Bueno, teniendo las herramientas y dependencias listas, vamos a instalar el entorno de desarrollo de Android. Nos vamos a la página del SDK de Android y descargamos la versión que corresponda a nuestra plataforma. Al igual que con Ant, descomprimimos el archivo en la carpeta de nuestra preferencia. Ésta vez también lo descomprimí en /opt, quedando en la carpeta: /opt/android-sdk-linux_x86.

Ahora editamos el archivo ~/.bashrc para agregar la ruta de los binarios al PATH del sistema y establecer las variables de entorno ANT_HOME y JAVA_HOME. Eso lo logramos agregando las siguientes líneas:

export PATH=${PATH}:/opt/android-sdk-linux_x86/tools
export PATH=${PATH}:/opt/apache-ant-1.8.1/bin
export ANT_HOME=/opt/apache-ant-1.8.1
export JAVA_HOME=/usr/lib/jvm/java-6-sun/jre


Recuerda cambiar las rutas de esas variables por las rutas donde se encuentran tus binarios. Si no sabes muy bien hacía dónde debe apuntar JAVA_HOME puedes ejecutar el comando:

# find / -name "tools.jar" | grep sun

Y entonces agrega la ruta que te arroje.

Nota: Debes cerrar las terminales abiertas y volverlas a abrir para que los cambios en las rutas surtan efecto.

Instalar plataformas de Android


¿Plataformas? ¿De qué me hablas? - Seguramente te estarás haciendo esas preguntas. Las plataformas son las "versiones" de Android disponibles (por ejemplo Android 1.6, Android 2.2) y necesitamos al menos una para poder compilar la aplicación y configurar un Android Virtual Device (AVD) para probarla.

Esto es muy simple, basta con abrir una terminal y ejecutar:

$ android

Nos aparecerá una aplicación (AVD Manager) como la que se muestra a continuación y allí nos vamos a la sección Available Packages, actualizamos el repositorio y seleccionamos la(s) plataforma(s) que deseamos instalar.


Finalmente creamos un nuevo dispositivo virtual en la sección Virtual Device. Hacemos clic en el botón New..., le asignamos un nombre, seleccionamos una plataforma y hacemos clic en Create AVD.


Para probar nuestro flamante dispositivo lo seleccionamos y hacemos clic en Start.... Luego clic en Launch y al cabo de unos segundos tendremos el emulador corriendo una instancia de Android :)


Desarrollo


Ahora viene la parte divertida, crear la aplicación. En las próximas entregas estaré hablando sobre el desarrollo, por los momentos pueden jugar creando un Hello World y pueden leer sobre cómo desarrollar con Eclipse y cómo desarrollar con otros IDEs

Compilar y construir el ejecutable


Luego de que tengamos nuestra aplicación lista debemos crear el instalador. Eso es tan simple como navegar hasta la carpeta raíz del proyecto (código fuente) y ejecutar:

$ ant debug

Si nuestro proyecto se llama "HelloAndroid" esto generará un archivo HelloAndroid-debug.apk y ese será nuestro instalador.

Probar en el emulador


Finalmente, una de las partes más esperadas. ¿Cómo se verá nuestra aplicación en un dispositivo? Pues fácil, en una consola ejecutamos:

$ android

E iniciamos nuestro dispositivo virtual. Luego que el dispositivo esté funcionando ejecutamos en otra consola:

$ adb install /ruta/de/nuestro/instalador/HelloAndroid-debug.apk

Y con eso enviaremos la aplicación al dispositivo. La buscamos en el menú y voilá! A jugar :D

Espero que les haya sido de utilidad éste tutorial. Próximamente seguiré documentando mis travesías en el mundo de Android. Cambio y fuera.