Los salvapantallas son algo que siempre me llamó poderosamente la antención
pero hasta hace poco no sabía como se escribían, hoy aprenderemos a hacer uno
para el XScreensaver de Gnu/Linux.
El primer paso es hacernos con el archivo vroot.h del xscreensaver (si hacemos
apt-get source xscreensaver nos lo encontraremos en el directorio utils/ [o
aquí]), y meterlo en el mismo directorio en el que desarrollaremos el resto
del código. vroot.h es un archivo (bajo licencia de estilo BSD) usado
comunmente para tomar al ventana_raíz, en la que tenemos que escribir si, por
ejemplo, queremos hacer un salvapantallas.
Empezamos entonces con nuestro archivo, lo llamaremos salvapantallas.c y
empezamos con el código, si bien va todo junto los comentarios hacen las
"presentaciones":
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 | #include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Cabeceras de X11 */
#include <X11/Xlib.h>
#include "vroot.h"
/* Esto no es necesario para el programa en si,
per es muy útil en el caso de querer hacer
comprobaciones y en el caso de que resulten
mal, detener el programa.
*/
#include <assert.h>
/* Esta será la función que dibuje el salvapantallas
por ahora no hace nada
*/
void drawScreen(Display* display, Window window,
XWindowAttributes window_attributes,
GC gc){
}
/* En la función principal tomaremos la ventana en
la que escribiremos y la pasamos a la función anterior.
*/
int main (int argc, char **argv){
/* Tomamos el identificador de pantalla. */
char *display_id = getenv("DISPLAY");
/* Suponemos que el display_id no es nulo. */
assert(display_id != NULL);
/* Conectamos con la pantalla. */
Display *display = XOpenDisplay(display_id);
/* Obtenemos la ventana, es la que mostrará el programa. */
Window root = DefaultRootWindow(display);
/* Creamos un contexto gráfico, este es el lienzo
donde dibujamos.
*/
GC gc = XCreateGC(display, root, 0, NULL);
/* Por último leemos las dimensiones de la ventana,
que siempre es bueno saber cuales son
*/
XWindowAttributes window_attributes;
XGetWindowAttributes(display, root, &window_attributes);
/* Y solo queda dar vueltas esperando al usuario */
while (1){
/* Dibujamos algo. */
drawScreen(display, root, window_attributes, gc);
/* Forzamos los cambios. */
XFlush(display);
/* Y le damos un respiro a CPU. */
usleep(2000);
}
/* Esto solo está para que el compilador esté tranquilo ;) */
return 0;
}
|
Y ya tenemos el esqueleto de un salvapantallas, vamos a meter algún relleno en
drawScreen y ya lo configuramos, así ya se puede ir probando antes de meterse
a hacer alguna animación en concreto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 | /* Esta es la función que dibuja el salvapantallas. */
void drawScreen(Display* display, Window window,
XWindowAttributes window_attributes,
GC gc){
/* Creamos un segundo buffer, en este se van haciendo los
cambios antes de pasarlos a la pantalla, de esta forma
conseguimos que todo valla suave.
(Realmente ahora no hace falta, pero es una técnica
que es bueno saber si se va a trabajar con los gráficos
directamente.
Creamos entonces un bitmap basado en la ventana con la
misma altura, anchura y profundidad.
*/
Pixmap double_buffer = XCreatePixmap(display, window,
window_attributes.width,
window_attributes.height,
window_attributes.depth);
/* Pintamos de negro. */
XSetForeground(display, gc, 0);
/* Y limpiamos el doble buffer. */
XFillRectangle(display, double_buffer, gc,
0, 0, window_attributes.height,
window_attributes.width);
/* El código de ejemplo dibuja un cuadrado de 4 pixeles
que va dando vueltas alrededor del centro de la pantalla.
*/
/* Primero determinamos la posición. */
static int next_position = 0;
/* Esto abulta mucho pero no tiene gran misterio,
solo calcula la posición en la que deberáa estar el cuadrado.
*/
int x_center = window_attributes.width / 2,
y_center = window_attributes.height / 2;
int x_position, y_position;
if (next_position < 128){ // Borde superior
x_position = x_center + (next_position - 64);
y_position = y_center - 64;
}
else if (next_position < 256){ // Borde derecho
x_position = x_center + 64;
y_position = y_center + ((next_position % 128) - 64);
}
else if (next_position < 384){ // Borde inferior
x_position = x_center + (64 - (next_position % 128));
y_position = y_center + 64;
}
else{ // Borde izquierdo
x_position = x_center - 64;
y_position = y_center + (64 - (next_position % 128));
}
next_position = (next_position + 1) % 512;
/* Ahora dibujamos el rectángulo.
(Hay que tener en cuenta que la posición indica la
distancia desde la esquina superior izquierda.)
*/
/* De color blanco. */
XSetForeground(display, gc, 0xFFFFFF);
/* Con centro en x_position, y_position . */
XFillRectangle(display, double_buffer, gc,
x_position - 2, y_position - 2, 4, 4);
/* Y lo pasamos a la pantalla. */
XCopyArea(display, double_buffer, window,
gc, 0, 0,
window_attributes.width, window_attributes.height,
0, 0);
/* Por último eliminamos el doble buffer de la memoria. */
XFreePixmap(display, double_buffer);
}
|
El grupo de if/elses se hace un poco pesando, pero es lo más simple que
encontré que pudiese producir una animación coherente y mostrase un poco de
todo :/. Bien, ahora a probarlo, primero lo compilamos, hace falta avisarle al
| gcc de que tiene que enlazarlo con la librería de X11:
gcc salvapantallas.c -o salvapantallas -lX11
|
Hecho esto, tenemos que guardarlo en /usr/lib/xscreensaver/
sudo mv salvapantallas /usr/lib/xscreensaver/
Luego solo queda añadirlo al archivo .xscreensaver de la carpeta $HOME, al
final de la lista para poder encontrarlo fácilmente:
- salvapantallas -root \n\
Y ya lo podemos probar, lanzamos xscreensaver-demo y seleccionandolo se nos
muestra una animación:
Bien, así se hace un salvapantallas, ahora vamos a ponerle otra animación, lo
primero: borrar todo el código de drawAnimation :), vamos a dibujar la curva
del dragón
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 | /* Esta es la función que dibuja el salvapantallas. */
void drawScreen(Display* display, Window window,
XWindowAttributes window_attributes,
GC gc){
static int step = 0; // nº de paso
static int lastg = 0; // Último valor calculado (necesario para el fractal)
static int x_position = -1; // Posicion actual en la horizontal
static int y_position = -1; // Posicion actual en la vertical
static int direction = 0; // 0 = arriba, 1 = derecha, 2 = abajo, 3 = izquierda
static long color = 0; // Color actual, 0 = negro, 0xFFFFFF = blanco
/* Si es la primeira vez o salímos de la pantaia, reiniciamos las coordeadas, la
dirección y el número de paso y vamos cambiando el color entre blanco y negro.
*/
if ((x_position < 0) || (x_position >= window_attributes.width) ||
(y_position < 0) || (y_position >= window_attributes.height)){
x_position = window_attributes.width / 2;
y_position = window_attributes.height / 2 ;
// Dirección, numero de paso y último valor calculado a 0
direction = step = lastg = 0;
color ^= 0xFFFFFF; // Si es negro cambia a blanco y al revés
}
XSetForeground(display, gc, color);
// Cálculos del fractal
int g = step ^ (step >> 1);
int t = (~g) & lastg;
lastg = g;
if (t == 0){ // Se t == 0, giramos 90 graos
direction = (direction + 1) % 4;
}
else{
// Dado que en C (-1 % 4) == -1,
// usamos 3 como equivalente % 4
direction = (direction + 3) % 4;
}
// Avanzamos según la dirección
switch(direction){
case 0:
y_position--;
break;
case 1:
x_position++;
break;
case 2:
y_position++;
break;
case 3:
x_position--;
break;
}
XFillRectangle(display, window, gc,
x_position, y_position,
1, 1);
// Nos preparamos para el siguiente paso
step++;
}
|
Este es el resultado:
Que os parece, animaos a hacer uno vosotros también, todo sea por mirar los colorinchos :P.
ps: Aúa que tirara de tantos static, no se debe hacer, fue para evitar andar a hacer cambios por todo el código, non es algo que se deba hacer en código limpio ;)
Saludos