From 5a5cfbe24c307c842a736ac222307f8f63950296 Mon Sep 17 00:00:00 2001
From: Christian Beier <dontmind@freeshell.org>
Date: Sat, 23 Jul 2011 17:56:05 +0200
Subject: Add androidvncserver example.

---
 examples/android/README            |  54 ++++
 examples/android/jni/Android.mk    |  65 +++++
 examples/android/jni/fbvncserver.c | 518 +++++++++++++++++++++++++++++++++++++
 3 files changed, 637 insertions(+)
 create mode 100644 examples/android/README
 create mode 100644 examples/android/jni/Android.mk
 create mode 100644 examples/android/jni/fbvncserver.c

diff --git a/examples/android/README b/examples/android/README
new file mode 100644
index 0000000..5baea22
--- /dev/null
+++ b/examples/android/README
@@ -0,0 +1,54 @@
+
+This example VNC server for Android is adopted from
+http://code.google.com/p/android-vnc-server/ with some additional
+fixes applied.
+
+To build, you'll need the Android Native Development Kit from
+http://developer.android.com/sdk/ndk/.
+
+
+Building with autotools
+-----------------------
+
+This has the advantage that the LibVNCServer sources are properly set up
+using the configure script.
+
+1. Read <NDK location>/docs/STANDALONE-TOOLCHAIN.html.
+
+2. Setup your toolchain according to step 3 in the above file.
+
+3. Execute 
+
+    ./configure --host=arm-eabi CC=arm-linux-androideabi-gcc
+
+   in the LibVNCServer root directory.
+
+4. Execute
+
+    make
+
+   in the LibVNCServer root directory. This will build the whole
+   LibVNCServer distribution for Android, including androidvncserver.
+
+
+
+
+Building with the NDK build system
+----------------------------------
+
+This is probably easier than the autotools method, but you'll have to edit
+some files manually.
+
+1. Edit rfb/rfbconfig.h to match your Android target. For instance, comment out
+   LIBVNCSERVER_HAVE_LIBJPEG if you don't have libjpeg for Android.
+
+2. Edit the HAVE_X variables in jni/Android.mk accordingly.
+
+3. Execute
+
+    ndk-build -C .
+
+   in the examples/android directory. The resulting binary will be in libs/.
+
+
+
diff --git a/examples/android/jni/Android.mk b/examples/android/jni/Android.mk
new file mode 100644
index 0000000..731a790
--- /dev/null
+++ b/examples/android/jni/Android.mk
@@ -0,0 +1,65 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LIBVNCSERVER_ROOT:=../../..
+
+HAVE_LIBZ=1
+#HAVE_LIBJPEG=1
+
+ifdef HAVE_LIBZ
+ZLIBSRCS := \
+	$(LIBVNCSERVER_ROOT)/libvncserver/zlib.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/zrle.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/zrleoutstream.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/zrlepalettehelper.c \
+	$(LIBVNCSERVER_ROOT)/common/zywrletemplate.c
+ifdef HAVE_LIBJPEG
+TIGHTSRCS := $(LIBVNCSERVER_ROOT)/libvncserver/tight.c
+endif
+endif
+
+LOCAL_SRC_FILES:= \
+	fbvncserver.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/main.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/rfbserver.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/rfbregion.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/auth.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/sockets.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/stats.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/corre.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/hextile.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/rre.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/translate.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/cutpaste.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/httpd.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/cursor.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/font.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/draw.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/selbox.c \
+	$(LIBVNCSERVER_ROOT)/common/d3des.c \
+	$(LIBVNCSERVER_ROOT)/common/vncauth.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/cargs.c \
+	$(LIBVNCSERVER_ROOT)/common/minilzo.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/ultra.c \
+	$(LIBVNCSERVER_ROOT)/libvncserver/scale.c \
+	$(ZLIBSRCS) \
+	$(TIGHTSRCS)
+
+LOCAL_C_INCLUDES := \
+	$(LOCAL_PATH) \
+	$(LOCAL_PATH)/$(LIBVNCSERVER_ROOT)/libvncserver \
+	$(LOCAL_PATH)/$(LIBVNCSERVER_ROOT)/common \
+	$(LOCAL_PATH)/$(LIBVNCSERVER_ROOT) \
+	external/jpeg
+
+ifdef HAVE_LIBZ
+LOCAL_SHARED_LIBRARIES := libz
+LOCAL_LDLIBS := -lz
+endif
+ifdef HAVE_LIBJPEG
+LOCAL_STATIC_LIBRARIES := libjpeg
+endif
+
+LOCAL_MODULE:= androidvncserver
+
+include $(BUILD_EXECUTABLE)
diff --git a/examples/android/jni/fbvncserver.c b/examples/android/jni/fbvncserver.c
new file mode 100644
index 0000000..dea4d85
--- /dev/null
+++ b/examples/android/jni/fbvncserver.c
@@ -0,0 +1,518 @@
+/*
+ * $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * This project is an adaptation of the original fbvncserver for the iPAQ
+ * and Zaurus.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+
+#include <sys/stat.h>
+#include <sys/sysmacros.h>             /* For makedev() */
+
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <linux/input.h>
+
+#include <assert.h>
+#include <errno.h>
+
+/* libvncserver */
+#include "rfb/rfb.h"
+#include "rfb/keysym.h"
+
+/*****************************************************************************/
+
+/* Android does not use /dev/fb0. */
+#define FB_DEVICE "/dev/graphics/fb0"
+static char KBD_DEVICE[256] = "/dev/input/event3";
+static char TOUCH_DEVICE[256] = "/dev/input/event1";
+static struct fb_var_screeninfo scrinfo;
+static int fbfd = -1;
+static int kbdfd = -1;
+static int touchfd = -1;
+static unsigned short int *fbmmap = MAP_FAILED;
+static unsigned short int *vncbuf;
+static unsigned short int *fbbuf;
+
+/* Android already has 5900 bound natively. */
+#define VNC_PORT 5901
+static rfbScreenInfoPtr vncscr;
+
+static int xmin, xmax;
+static int ymin, ymax;
+
+/* No idea, just copied from fbvncserver as part of the frame differerencing
+ * algorithm.  I will probably be later rewriting all of this. */
+static struct varblock_t
+{
+	int min_i;
+	int min_j;
+	int max_i;
+	int max_j;
+	int r_offset;
+	int g_offset;
+	int b_offset;
+	int rfb_xres;
+	int rfb_maxy;
+} varblock;
+
+/*****************************************************************************/
+
+static void keyevent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
+static void ptrevent(int buttonMask, int x, int y, rfbClientPtr cl);
+
+/*****************************************************************************/
+
+static void init_fb(void)
+{
+	size_t pixels;
+	size_t bytespp;
+
+	if ((fbfd = open(FB_DEVICE, O_RDONLY)) == -1)
+	{
+		printf("cannot open fb device %s\n", FB_DEVICE);
+		exit(EXIT_FAILURE);
+	}
+
+	if (ioctl(fbfd, FBIOGET_VSCREENINFO, &scrinfo) != 0)
+	{
+		printf("ioctl error\n");
+		exit(EXIT_FAILURE);
+	}
+
+	pixels = scrinfo.xres * scrinfo.yres;
+	bytespp = scrinfo.bits_per_pixel / 8;
+
+	fprintf(stderr, "xres=%d, yres=%d, xresv=%d, yresv=%d, xoffs=%d, yoffs=%d, bpp=%d\n", 
+	  (int)scrinfo.xres, (int)scrinfo.yres,
+	  (int)scrinfo.xres_virtual, (int)scrinfo.yres_virtual,
+	  (int)scrinfo.xoffset, (int)scrinfo.yoffset,
+	  (int)scrinfo.bits_per_pixel);
+
+	fbmmap = mmap(NULL, pixels * bytespp, PROT_READ, MAP_SHARED, fbfd, 0);
+
+	if (fbmmap == MAP_FAILED)
+	{
+		printf("mmap failed\n");
+		exit(EXIT_FAILURE);
+	}
+}
+
+static void cleanup_fb(void)
+{
+	if(fbfd != -1)
+	{
+		close(fbfd);
+	}
+}
+
+static void init_kbd()
+{
+	if((kbdfd = open(KBD_DEVICE, O_RDWR)) == -1)
+	{
+		printf("cannot open kbd device %s\n", KBD_DEVICE);
+		exit(EXIT_FAILURE);
+	}
+}
+
+static void cleanup_kbd()
+{
+	if(kbdfd != -1)
+	{
+		close(kbdfd);
+	}
+}
+
+static void init_touch()
+{
+    struct input_absinfo info;
+        if((touchfd = open(TOUCH_DEVICE, O_RDWR)) == -1)
+        {
+                printf("cannot open touch device %s\n", TOUCH_DEVICE);
+                exit(EXIT_FAILURE);
+        }
+    // Get the Range of X and Y
+    if(ioctl(touchfd, EVIOCGABS(ABS_X), &info)) {
+        printf("cannot get ABS_X info, %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+    xmin = info.minimum;
+    xmax = info.maximum;
+    if(ioctl(touchfd, EVIOCGABS(ABS_Y), &info)) {
+        printf("cannot get ABS_Y, %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+    ymin = info.minimum;
+    ymax = info.maximum;
+
+}
+
+static void cleanup_touch()
+{
+	if(touchfd != -1)
+	{
+		close(touchfd);
+	}
+}
+
+/*****************************************************************************/
+
+static void init_fb_server(int argc, char **argv)
+{
+	printf("Initializing server...\n");
+
+	/* Allocate the VNC server buffer to be managed (not manipulated) by 
+	 * libvncserver. */
+	vncbuf = calloc(scrinfo.xres * scrinfo.yres, scrinfo.bits_per_pixel / 2);
+	assert(vncbuf != NULL);
+
+	/* Allocate the comparison buffer for detecting drawing updates from frame
+	 * to frame. */
+	fbbuf = calloc(scrinfo.xres * scrinfo.yres, scrinfo.bits_per_pixel / 2);
+	assert(fbbuf != NULL);
+
+	/* TODO: This assumes scrinfo.bits_per_pixel is 16. */
+	vncscr = rfbGetScreen(&argc, argv, scrinfo.xres, scrinfo.yres, 5, 2, 2);
+	assert(vncscr != NULL);
+
+	vncscr->desktopName = "Android";
+	vncscr->frameBuffer = (char *)vncbuf;
+	vncscr->alwaysShared = TRUE;
+	vncscr->httpDir = NULL;
+	vncscr->port = VNC_PORT;
+
+	vncscr->kbdAddEvent = keyevent;
+	vncscr->ptrAddEvent = ptrevent;
+
+	rfbInitServer(vncscr);
+
+	/* Mark as dirty since we haven't sent any updates at all yet. */
+	rfbMarkRectAsModified(vncscr, 0, 0, scrinfo.xres, scrinfo.yres);
+
+	/* No idea. */
+	varblock.r_offset = scrinfo.red.offset + scrinfo.red.length - 5;
+	varblock.g_offset = scrinfo.green.offset + scrinfo.green.length - 5;
+	varblock.b_offset = scrinfo.blue.offset + scrinfo.blue.length - 5;
+	varblock.rfb_xres = scrinfo.yres;
+	varblock.rfb_maxy = scrinfo.xres - 1;
+}
+
+/*****************************************************************************/
+void injectKeyEvent(uint16_t code, uint16_t value)
+{
+    struct input_event ev;
+    memset(&ev, 0, sizeof(ev));
+    gettimeofday(&ev.time,0);
+    ev.type = EV_KEY;
+    ev.code = code;
+    ev.value = value;
+    if(write(kbdfd, &ev, sizeof(ev)) < 0)
+    {
+        printf("write event failed, %s\n", strerror(errno));
+    }
+
+    printf("injectKey (%d, %d)\n", code , value);    
+}
+
+static int keysym2scancode(rfbBool down, rfbKeySym key, rfbClientPtr cl)
+{
+    int scancode = 0;
+
+    int code = (int)key;
+    if (code>='0' && code<='9') {
+        scancode = (code & 0xF) - 1;
+        if (scancode<0) scancode += 10;
+        scancode += KEY_1;
+    } else if (code>=0xFF50 && code<=0xFF58) {
+        static const uint16_t map[] =
+             {  KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN,
+                KEY_SOFT1, KEY_SOFT2, KEY_END, 0 };
+        scancode = map[code & 0xF];
+    } else if (code>=0xFFE1 && code<=0xFFEE) {
+        static const uint16_t map[] =
+             {  KEY_LEFTSHIFT, KEY_LEFTSHIFT,
+                KEY_COMPOSE, KEY_COMPOSE,
+                KEY_LEFTSHIFT, KEY_LEFTSHIFT,
+                0,0,
+                KEY_LEFTALT, KEY_RIGHTALT,
+                0, 0, 0, 0 };
+        scancode = map[code & 0xF];
+    } else if ((code>='A' && code<='Z') || (code>='a' && code<='z')) {
+        static const uint16_t map[] = {
+                KEY_A, KEY_B, KEY_C, KEY_D, KEY_E,
+                KEY_F, KEY_G, KEY_H, KEY_I, KEY_J,
+                KEY_K, KEY_L, KEY_M, KEY_N, KEY_O,
+                KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T,
+                KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z };
+        scancode = map[(code & 0x5F) - 'A'];
+    } else {
+        switch (code) {
+            case 0x0003:    scancode = KEY_CENTER;      break;
+            case 0x0020:    scancode = KEY_SPACE;       break;
+            case 0x0023:    scancode = KEY_SHARP;       break;
+            case 0x0033:    scancode = KEY_SHARP;       break;
+            case 0x002C:    scancode = KEY_COMMA;       break;
+            case 0x003C:    scancode = KEY_COMMA;       break;
+            case 0x002E:    scancode = KEY_DOT;         break;
+            case 0x003E:    scancode = KEY_DOT;         break;
+            case 0x002F:    scancode = KEY_SLASH;       break;
+            case 0x003F:    scancode = KEY_SLASH;       break;
+            case 0x0032:    scancode = KEY_EMAIL;       break;
+            case 0x0040:    scancode = KEY_EMAIL;       break;
+            case 0xFF08:    scancode = KEY_BACKSPACE;   break;
+            case 0xFF1B:    scancode = KEY_BACK;        break;
+            case 0xFF09:    scancode = KEY_TAB;         break;
+            case 0xFF0D:    scancode = KEY_ENTER;       break;
+            case 0x002A:    scancode = KEY_STAR;        break;
+            case 0xFFBE:    scancode = KEY_F1;        break; // F1
+            case 0xFFBF:    scancode = KEY_F2;         break; // F2
+            case 0xFFC0:    scancode = KEY_F3;        break; // F3
+            case 0xFFC5:    scancode = KEY_F4;       break; // F8
+            case 0xFFC8:    rfbShutdownServer(cl->screen,TRUE);       break; // F11            
+        }
+    }
+
+    return scancode;
+}
+
+static void keyevent(rfbBool down, rfbKeySym key, rfbClientPtr cl)
+{
+	int scancode;
+
+	printf("Got keysym: %04x (down=%d)\n", (unsigned int)key, (int)down);
+
+	if ((scancode = keysym2scancode(down, key, cl)))
+	{
+		injectKeyEvent(scancode, down);
+	}
+}
+
+void injectTouchEvent(int down, int x, int y)
+{
+    struct input_event ev;
+    
+    // Calculate the final x and y
+    x = xmin + (x * (xmax - xmin)) / (scrinfo.xres);
+    y = ymin + (y * (ymax - ymin)) / (scrinfo.yres);
+    
+    memset(&ev, 0, sizeof(ev));
+
+    // Then send a BTN_TOUCH
+    gettimeofday(&ev.time,0);
+    ev.type = EV_KEY;
+    ev.code = BTN_TOUCH;
+    ev.value = down;
+    if(write(touchfd, &ev, sizeof(ev)) < 0)
+    {
+        printf("write event failed, %s\n", strerror(errno));
+    }
+
+    // Then send the X
+    gettimeofday(&ev.time,0);
+    ev.type = EV_ABS;
+    ev.code = ABS_X;
+    ev.value = x;
+    if(write(touchfd, &ev, sizeof(ev)) < 0)
+    {
+        printf("write event failed, %s\n", strerror(errno));
+    }
+
+    // Then send the Y
+    gettimeofday(&ev.time,0);
+    ev.type = EV_ABS;
+    ev.code = ABS_Y;
+    ev.value = y;
+    if(write(touchfd, &ev, sizeof(ev)) < 0)
+    {
+        printf("write event failed, %s\n", strerror(errno));
+    }
+
+    // Finally send the SYN
+    gettimeofday(&ev.time,0);
+    ev.type = EV_SYN;
+    ev.code = 0;
+    ev.value = 0;
+    if(write(touchfd, &ev, sizeof(ev)) < 0)
+    {
+        printf("write event failed, %s\n", strerror(errno));
+    }
+
+    printf("injectTouchEvent (x=%d, y=%d, down=%d)\n", x , y, down);    
+}
+
+static void ptrevent(int buttonMask, int x, int y, rfbClientPtr cl)
+{
+	/* Indicates either pointer movement or a pointer button press or release. The pointer is
+now at (x-position, y-position), and the current state of buttons 1 to 8 are represented
+by bits 0 to 7 of button-mask respectively, 0 meaning up, 1 meaning down (pressed).
+On a conventional mouse, buttons 1, 2 and 3 correspond to the left, middle and right
+buttons on the mouse. On a wheel mouse, each step of the wheel upwards is represented
+by a press and release of button 4, and each step downwards is represented by
+a press and release of button 5. 
+  From: http://www.vislab.usyd.edu.au/blogs/index.php/2009/05/22/an-headerless-indexed-protocol-for-input-1?blog=61 */
+	
+	//printf("Got ptrevent: %04x (x=%d, y=%d)\n", buttonMask, x, y);
+	if(buttonMask & 1) {
+		// Simulate left mouse event as touch event
+		injectTouchEvent(1, x, y);
+		injectTouchEvent(0, x, y);
+	} 
+}
+
+#define PIXEL_FB_TO_RFB(p,r,g,b) ((p>>r)&0x1f001f)|(((p>>g)&0x1f001f)<<5)|(((p>>b)&0x1f001f)<<10)
+
+static void update_screen(void)
+{
+	unsigned int *f, *c, *r;
+	int x, y;
+
+	varblock.min_i = varblock.min_j = 9999;
+	varblock.max_i = varblock.max_j = -1;
+
+	f = (unsigned int *)fbmmap;        /* -> framebuffer         */
+	c = (unsigned int *)fbbuf;         /* -> compare framebuffer */
+	r = (unsigned int *)vncbuf;        /* -> remote framebuffer  */
+
+	for (y = 0; y < scrinfo.yres; y++)
+	{
+		/* Compare every 2 pixels at a time, assuming that changes are likely
+		 * in pairs. */
+		for (x = 0; x < scrinfo.xres; x += 2)
+		{
+			unsigned int pixel = *f;
+
+			if (pixel != *c)
+			{
+				*c = pixel;
+
+				/* XXX: Undo the checkered pattern to test the efficiency
+				 * gain using hextile encoding. */
+				if (pixel == 0x18e320e4 || pixel == 0x20e418e3)
+					pixel = 0x18e318e3;
+
+				*r = PIXEL_FB_TO_RFB(pixel,
+				  varblock.r_offset, varblock.g_offset, varblock.b_offset);
+
+				if (x < varblock.min_i)
+					varblock.min_i = x;
+				else
+				{
+					if (x > varblock.max_i)
+						varblock.max_i = x;
+
+					if (y > varblock.max_j)
+						varblock.max_j = y;
+					else if (y < varblock.min_j)
+						varblock.min_j = y;
+				}
+			}
+
+			f++, c++;
+			r++;
+		}
+	}
+
+	if (varblock.min_i < 9999)
+	{
+		if (varblock.max_i < 0)
+			varblock.max_i = varblock.min_i;
+
+		if (varblock.max_j < 0)
+			varblock.max_j = varblock.min_j;
+
+		fprintf(stderr, "Dirty page: %dx%d+%d+%d...\n",
+		  (varblock.max_i+2) - varblock.min_i, (varblock.max_j+1) - varblock.min_j,
+		  varblock.min_i, varblock.min_j);
+
+		rfbMarkRectAsModified(vncscr, varblock.min_i, varblock.min_j,
+		  varblock.max_i + 2, varblock.max_j + 1);
+
+		rfbProcessEvents(vncscr, 10000);
+	}
+}
+
+/*****************************************************************************/
+
+void print_usage(char **argv)
+{
+	printf("%s [-k device] [-t device] [-h]\n"
+		"-k device: keyboard device node, default is /dev/input/event3\n"
+		"-t device: touch device node, default is /dev/input/event1\n"
+		"-h : print this help\n");
+}
+
+int main(int argc, char **argv)
+{
+	if(argc > 1)
+	{
+		int i=1;
+		while(i < argc)
+		{
+			if(*argv[i] == '-')
+			{
+				switch(*(argv[i] + 1))
+				{
+					case 'h':
+						print_usage(argv);
+						exit(0);
+						break;
+					case 'k':
+						i++;
+						strcpy(KBD_DEVICE, argv[i]);
+						break;
+					case 't':
+						i++;
+						strcpy(TOUCH_DEVICE, argv[i]);
+						break;
+				}
+			}
+			i++;
+		}
+	}
+
+	printf("Initializing framebuffer device " FB_DEVICE "...\n");
+	init_fb();
+	printf("Initializing keyboard device %s ...\n", KBD_DEVICE);
+	init_kbd();
+	printf("Initializing touch device %s ...\n", TOUCH_DEVICE);
+	init_touch();
+
+	printf("Initializing VNC server:\n");
+	printf("	width:  %d\n", (int)scrinfo.xres);
+	printf("	height: %d\n", (int)scrinfo.yres);
+	printf("	bpp:    %d\n", (int)scrinfo.bits_per_pixel);
+	printf("	port:   %d\n", (int)VNC_PORT);
+	init_fb_server(argc, argv);
+
+	/* Implement our own event loop to detect changes in the framebuffer. */
+	while (1)
+	{
+		while (vncscr->clientHead == NULL)
+			rfbProcessEvents(vncscr, 100000);
+
+		rfbProcessEvents(vncscr, 100000);
+		update_screen();
+	}
+
+	printf("Cleaning up...\n");
+	cleanup_fb();
+	cleanup_kdb();
+	cleanup_touch();
+}
-- 
cgit v1.2.3

