summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Beier <dontmind@freeshell.org>2011-07-23 17:56:05 +0200
committerChristian Beier <dontmind@freeshell.org>2011-07-23 17:56:05 +0200
commit5a5cfbe24c307c842a736ac222307f8f63950296 (patch)
tree2ab320902e61ff6a5568803cc97d34eafc061358
parent488ad8a60969222eb0d417955820bac00982abb5 (diff)
downloadlibvncserver-5a5cfbe24c307c842a736ac222307f8f63950296.zip
libvncserver-5a5cfbe24c307c842a736ac222307f8f63950296.tar.gz
Add androidvncserver example.
-rw-r--r--examples/android/README54
-rw-r--r--examples/android/jni/Android.mk65
-rw-r--r--examples/android/jni/fbvncserver.c518
3 files changed, 637 insertions, 0 deletions
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();
+}
OpenPOWER on IntegriCloud