#include "ofAppEGLWindow.h"

#include "ofGraphics.h" // used in runAppViaInfiniteLoop()
#include "ofAppRunner.h"
#include "ofUtils.h"
#include "ofFileUtils.h"
#include "ofGLProgrammableRenderer.h"
#include "ofGLRenderer.h"
#include <assert.h>

using namespace std;

// native events
struct udev* udev;
struct udev_device* dev;
struct udev_monitor* mon;
static int udev_fd = -1;

static int keyboard_fd = -1; // defaults to 0 ie console

// minimal map
const int lowercase_map[] = {
		0,  0,  '1',  '2',  '3',  '4',  '5', '6',  '7', '8', '9', '0',
		'-', '=', '\b', '\t', 'q',  'w',  'e', 'r',  't', 'y', 'u', 'i',
		'o', 'p', '[',  ']',  '\n', 0,   'a', 's',  'd', 'f', 'g', 'h',
		'j', 'k', 'l',  ';',  '\'',  '\n', 0,  '\\', 'z', 'x', 'c', 'v',
		'b', 'n', 'm',  ',',  '.',  '/',  0,  '*',  0,  ' ', 0,  0,
		0,  0,  0,   0,   0,   0,   0,  0,   0,  0,  0,  0, 0,  0, 0,
		0,  0,  0,   0,   0,   0,   0,  0,   0,  0,  0,  0, 0,  0, 0,
		0,  0,  0,   0,   0,   0,   0,  0,   0,  0,  0,  '\r'

};

// minimal keyboard map
const int uppercase_map[] = {
		0,  0,  '!',  '@',  '#',  '$',  '%', '^',  '&', '*', '(', ')',
		'_', '+', '\b', '\t', 'Q',  'W',  'E', 'R',  'T', 'Y', 'U', 'I',
		'O', 'P', '{',  '}',  '\n', 0,   'A', 'S',  'D', 'F', 'G', 'H',
		'J', 'K', 'L',  ':',  '"', '\n', 0,  '\\', 'Z', 'X', 'C', 'V',
		'B', 'N', 'M',  '<',  '>',  '?',  0,  '*',  0,  ' ', 0,  0,
		0,  0,  0,   0,   0,   0,   0,  0,   0,  0,  0,  0, 0,  0, 0,
		0,  0,  0,   0,   0,   0,   0,  0,   0,  0,  0,  0, 0,  0, 0,
		0,  0,  0,   0,   0,   0,   0,  0,   0,  0,  0,  '\r'
};

// keep track of a few things ...
typedef struct {
	bool shiftPressed;
	bool capsLocked;
} KeyboardState;

static KeyboardState kb;

static struct termios tc;
static struct termios ots;

typedef struct {
	int mouseButtonState;
} MouseState;

// TODO, make this match the upcoming additions to ofWindow
#define MOUSE_BUTTON_LEFT_MASK		1
#define MOUSE_BUTTON_MIDDLE_MASK 1 << 1
#define MOUSE_BUTTON_RIGHT_MASK  2 << 1

static MouseState mb;
ofAppEGLWindow* ofAppEGLWindow::instance = NULL;

static int string_ends_with(const char *str, const char *suffix) {
	if (!str || !suffix)
		return 0;
	size_t lenstr = strlen(str);
	size_t lensuffix = strlen(suffix);
	if (lensuffix > lenstr)
		return 0;
	return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
}

static int string_begins_with(const char *str, const char *prefix) {
	if (!str || !prefix)
		return 0;
	size_t lenstr = strlen(str);
	size_t lenprefix = strlen(prefix);
	if (lenprefix > lenstr)
		return 0;
	return strncmp(str, prefix, lenprefix) == 0;
}

static int dummy_sort(const struct dirent **a,const struct dirent **b) {
	return 1; // dummy sort
}

static int filter_kbd(const struct dirent *d) {
	if(d->d_type != DT_DIR && string_ends_with(d->d_name,"event-kbd")) {
		return 1;
	} else {
		return 0;
	}
}

static int filter_mouse(const struct dirent *d) {
	if(d->d_type != DT_DIR && string_ends_with(d->d_name,"event-mouse")) {
		return 1;
	} else {
		return 0;
	}
}

static int filter_event(const struct dirent *d) {
	if(d->d_type != DT_DIR && string_begins_with(d->d_name,"event")) {
		return 1;
	} else {
		return 0;
	}
}

// native
#define MOUSE_CURSOR_RUN_LENGTH_DECODE(image_buf, rle_data, size, bpp) do \
		{ unsigned int __bpp; unsigned char *__ip; const unsigned char *__il, *__rd; \
		__bpp = (bpp); __ip = (image_buf); __il = __ip + (size) * __bpp; \
		__rd = (rle_data); \
		while (__ip < __il) { unsigned int __l = *(__rd++); \
		if (__l & 128) { __l = __l - 128; \
		do { memcpy (__ip, __rd, 4); __ip += 4; } while (--__l); __rd += 4; \
		} else { __l *= 4; memcpy (__ip, __rd, __l); \
		__ip += __l; __rd += __l; } } \
		} while (0)
static const struct {
	unsigned int width;
	unsigned int height;
	unsigned int bpp; /* 2:RGB16, 3:RGB, 4:RGBA */
	unsigned char rle_pixel_data[382 + 1];
} mouse_cursor_data = {
		12, 19, 4,
		"\1\0\0\0\377\213\377\377\377\0\202\0\0\0\377\212\377\377\377\0\3\0\0\0\377"
		"\377\377\377\377\0\0\0\377\211\377\377\377\0\1\0\0\0\377\202\377\377\377"
		"\377\1\0\0\0\377\210\377\377\377\0\1\0\0\0\377\203\377\377\377\377\1\0\0"
		"\0\377\207\377\377\377\0\1\0\0\0\377\204\377\377\377\377\1\0\0\0\377\206"
		"\377\377\377\0\1\0\0\0\377\205\377\377\377\377\1\0\0\0\377\205\377\377\377"
		"\0\1\0\0\0\377\206\377\377\377\377\1\0\0\0\377\204\377\377\377\0\1\0\0\0"
		"\377\207\377\377\377\377\1\0\0\0\377\203\377\377\377\0\1\0\0\0\377\210\377"
		"\377\377\377\1\0\0\0\377\202\377\377\377\0\1\0\0\0\377\211\377\377\377\377"
		"\3\0\0\0\377\377\377\377\0\0\0\0\377\212\377\377\377\377\202\0\0\0\377\206"
		"\377\377\377\377\206\0\0\0\377\203\377\377\377\377\1\0\0\0\377\202\377\377"
		"\377\377\1\0\0\0\377\204\377\377\377\0\1\0\0\0\377\202\377\377\377\377\3"
		"\0\0\0\377\377\377\377\0\0\0\0\377\202\377\377\377\377\1\0\0\0\377\203\377"
		"\377\377\0\3\0\0\0\377\377\377\377\377\0\0\0\377\202\377\377\377\0\1\0\0"
		"\0\377\202\377\377\377\377\1\0\0\0\377\203\377\377\377\0\202\0\0\0\377\204"
		"\377\377\377\0\1\0\0\0\377\202\377\377\377\377\1\0\0\0\377\210\377\377\377"
		"\0\1\0\0\0\377\202\377\377\377\377\1\0\0\0\377\211\377\377\377\0\202\0\0"
		"\0\377\203\377\377\377\0",
};

// from http://cantuna.googlecode.com/svn-history/r16/trunk/src/screen.cpp
#define CASE_STR(x,y) case x: str = y; break

static const char* eglErrorString(EGLint err) {
	string str;
	switch (err) {
	CASE_STR(EGL_SUCCESS, "no error");
	CASE_STR(EGL_NOT_INITIALIZED, "EGL not, or could not be, initialized");
	CASE_STR(EGL_BAD_ACCESS, "access violation");
	CASE_STR(EGL_BAD_ALLOC, "could not allocate resources");
	CASE_STR(EGL_BAD_ATTRIBUTE, "invalid attribute");
	CASE_STR(EGL_BAD_CONTEXT, "invalid context specified");
	CASE_STR(EGL_BAD_CONFIG, "invald frame buffer configuration specified");
	CASE_STR(EGL_BAD_CURRENT_SURFACE, "current window, pbuffer or pixmap surface is no longer valid");
	CASE_STR(EGL_BAD_DISPLAY, "invalid display specified");
	CASE_STR(EGL_BAD_SURFACE, "invalid surface specified");
	CASE_STR(EGL_BAD_MATCH, "bad argument match");
	CASE_STR(EGL_BAD_PARAMETER, "invalid paramater");
	CASE_STR(EGL_BAD_NATIVE_PIXMAP, "invalid NativePixmap");
	CASE_STR(EGL_BAD_NATIVE_WINDOW, "invalid NativeWindow");
	CASE_STR(EGL_CONTEXT_LOST, "APM event caused context loss");
	default: str = "unknown error " + err; break;
	}
	return str.c_str();
}


// X11 events
#include <X11/XKBlib.h>


#ifdef TARGET_RASPBERRY_PI
// TODO: remove these when they enter system headers
// From : https://github.com/raspberrypi/userland/blob/master/interface/vmcs_host/vc_vchi_dispmanx.h
#ifndef ELEMENT_CHANGE_LAYER
#define ELEMENT_CHANGE_LAYER		  (1<<0)
#endif
#ifndef ELEMENT_CHANGE_OPACITY
#define ELEMENT_CHANGE_OPACITY		(1<<1)
#endif
#ifndef ELEMENT_CHANGE_DEST_RECT
#define ELEMENT_CHANGE_DEST_RECT	  (1<<2)
#endif
#ifndef ELEMENT_CHANGE_SRC_RECT
#define ELEMENT_CHANGE_SRC_RECT	   (1<<3)
#endif
#ifndef ELEMENT_CHANGE_MASK_RESOURCE
#define ELEMENT_CHANGE_MASK_RESOURCE  (1<<4)
#endif
#ifndef ELEMENT_CHANGE_TRANSFORM
#define ELEMENT_CHANGE_TRANSFORM	  (1<<5)
#endif
#endif


//-------------------------------------------------------------------------------------
ofAppEGLWindow::Settings::Settings()
:ofGLESWindowSettings(){
	eglWindowPreference = OF_APP_WINDOW_AUTO;
	eglWindowOpacity = 255;

	// these are usually set as default, but set them here just to be sure
	frameBufferAttributes[EGL_RED_SIZE]	 = 8; // 8 bits for red
	frameBufferAttributes[EGL_GREEN_SIZE]   = 8; // 8 bits for green
	frameBufferAttributes[EGL_BLUE_SIZE]	= 8; // 8 bits for blue
	frameBufferAttributes[EGL_ALPHA_SIZE]   = 8; // 8 bits for alpha
	frameBufferAttributes[EGL_LUMINANCE_SIZE] = EGL_DONT_CARE; // 8 bits for alpha
	frameBufferAttributes[EGL_DEPTH_SIZE]   = 24; // 24 bits for depth
	frameBufferAttributes[EGL_STENCIL_SIZE] = 8; // 8 bits for stencil
	frameBufferAttributes[EGL_SAMPLES]	  = 1;

	initialClearColor = ofColor(0.15 * 255, 0.15 * 255, 0.15 * 255, 255);

	screenNum = 0; /* 0 = LCD on the raspberry pi */
	layer = 0;
}

ofAppEGLWindow::Settings::Settings(const ofGLESWindowSettings & settings)
:ofGLESWindowSettings(settings){
	eglWindowPreference = OF_APP_WINDOW_AUTO;
	eglWindowOpacity = 255;

	// these are usually set as default, but set them here just to be sure
	frameBufferAttributes[EGL_RED_SIZE]	 = 8; // 8 bits for red
	frameBufferAttributes[EGL_GREEN_SIZE]   = 8; // 8 bits for green
	frameBufferAttributes[EGL_BLUE_SIZE]	= 8; // 8 bits for blue
	frameBufferAttributes[EGL_ALPHA_SIZE]   = 8; // 8 bits for alpha
	frameBufferAttributes[EGL_LUMINANCE_SIZE] = EGL_DONT_CARE; // 8 bits for alpha
	frameBufferAttributes[EGL_DEPTH_SIZE]   = 24; // 24 bits for depth
	frameBufferAttributes[EGL_STENCIL_SIZE] = 8; // 8 bits for stencil
	frameBufferAttributes[EGL_SAMPLES]	  = 1;

	initialClearColor = ofColor(0.15 * 255, 0.15 * 255, 0.15 * 255, 255);

	screenNum = 0; /* 0 = LCD on the raspberry pi */
	layer = 0;
}

//------------------------------------------------------------
ofAppEGLWindow::ofAppEGLWindow() {
	keyboardDetected = false;
	mouseDetected = false;
	threadTimeout = ofThread::INFINITE_JOIN_TIMEOUT;
	bNewScreenMode = false;
	buttonInUse = -1;
	bEnableSetupScreen = false;
	bShowCursor = true;
	nFramesSinceWindowResized = 0;
	mouseScaleX = 2.0f;
	mouseScaleY = 2.0f;
	isUsingX11 = false;
	isWindowInited = false;
	isSurfaceInited = false;
	x11Display = NULL;
	x11Screen = NULL;
	x11ScreenNum = 0l;
	glesVersion = 1;

	if(instance!=NULL){
		ofLogError("ofAppEGLWindow") << "trying to create more than one instance";
	}
	instance = this;
}

//------------------------------------------------------------
ofAppEGLWindow::~ofAppEGLWindow() {
	close();
}

//------------------------------------------------------------
EGLDisplay ofAppEGLWindow::getEglDisplay() const {
	return eglDisplay;
}

//------------------------------------------------------------
EGLSurface ofAppEGLWindow::getEglSurface() const {
	return eglSurface;
}

//------------------------------------------------------------
EGLContext ofAppEGLWindow::getEglContext() const {
	return eglContext;
}

#ifndef TARGET_RASPBERRY_PI
//------------------------------------------------------------
Display* ofAppEGLWindow::getX11Display(){
	return x11Display;
}

//------------------------------------------------------------
Window ofAppEGLWindow::getX11Window(){
	return x11Window;
}
#endif
//------------------------------------------------------------
EGLConfig ofAppEGLWindow::getEglConfig() const {
	return eglConfig;
}

//------------------------------------------------------------
EGLint ofAppEGLWindow::getEglVersionMajor () const {
	return eglVersionMajor;
}

//------------------------------------------------------------
EGLint ofAppEGLWindow::getEglVersionMinor() const {
	return eglVersionMinor;
}

//------------------------------------------------------------
void ofAppEGLWindow::initNative() {
#ifdef TARGET_RASPBERRY_PI
	initRPiNative();
#endif
}

//------------------------------------------------------------
void ofAppEGLWindow::exitNative() {
#ifdef TARGET_RASPBERRY_PI
	exitRPiNative();
#endif
}

//------------------------------------------------------------
EGLNativeWindowType ofAppEGLWindow::getNativeWindow()  {
	if(!isWindowInited) {
		ofLogWarning("ofAppEGLWindow") << "getNativeDisplay(): window not initialized, returning NULL";
		return NULL;
	}

	if(isUsingX11) {
		return (EGLNativeWindowType)x11Window;
	} else {
#ifdef TARGET_RASPBERRY_PI
		return (EGLNativeWindowType)&dispman_native_window;
#else
		ofLogNotice("ofAppEGLWindow") << "getNativeWindow(): no native window type for this system, perhaps try X11?";
		return NULL;
#endif
	}
}

//------------------------------------------------------------
EGLNativeDisplayType ofAppEGLWindow::getNativeDisplay() {
	if(!isWindowInited) {
		ofLogWarning("ofAppEGLWindow") << "getNativeDisplay(): window not initialized, returning NULL";
		return 0;
	}

	if(isUsingX11) {
		return (EGLNativeDisplayType)x11Display;
	} else {
#ifdef TARGET_RASPBERRY_PI
		return (EGLNativeDisplayType)NULL;
#else
		ofLogNotice("ofAppEGLWindow") << "getNativeDisplay(): no native window type for this system, perhaps try X11?";
		return 0;
#endif
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::setup(const ofGLESWindowSettings & settings){
	const Settings * glSettings = dynamic_cast<const Settings*>(&settings);
	if(glSettings){
		setup(*glSettings);
	}else{
		setup(Settings(settings));
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::setup(const Settings & _settings) {
	settings = _settings;
	windowMode = OF_WINDOW;
	bNewScreenMode = true;
	nFramesSinceWindowResized = 0;
	buttonInUse	= 0;
	bEnableSetupScreen = true;
	eglDisplayString = "";
	orientation = OF_ORIENTATION_DEFAULT;

	//TODO: 2.0f is an arbitrary factor that makes mouse speed ok at 1024x768,
	// to be totally correct we might need to take into account screen size
	// and add acceleration
	mouseScaleX = 2.0f;
	mouseScaleY = 2.0f;

	isUsingX11 = false;
	isWindowInited  = false;
	isSurfaceInited = false;

	eglDisplay = NULL;
	eglSurface = NULL;
	eglContext = NULL;
	eglConfig  = NULL;
	eglVersionMajor = -1;
	eglVersionMinor = -1;
	glesVersion = 1;

	// X11 check
	// char * pDisplay;
	// pDisplay = getenv ("DISPLAY");
	// bool bIsX11Available = (pDisplay != NULL);

	bool bIsX11Available = getenv("DISPLAY") != NULL;

	if(settings.eglWindowPreference == OF_APP_WINDOW_AUTO) {
		if(bIsX11Available) {
			isUsingX11 = true;
		} else {
			isUsingX11 = false;
		}
	} else if(settings.eglWindowPreference == OF_APP_WINDOW_NATIVE) {
		isUsingX11 = false;
	} else if(settings.eglWindowPreference == OF_APP_WINDOW_X11) {
		isUsingX11 = true;
		if(!bIsX11Available) {
			isUsingX11 = false;
			ofLogError("ofAppEGLWindow") << "init(): X11 window requested, but X11 is not available";
		}
	}

	////////////////
	// TODO remove the following ifdef once x11 is accelerated on RPI
#ifdef TARGET_RASPBERRY_PI
	if(isUsingX11) {
		isUsingX11 = false;
		ofLogWarning("ofAppEGLWindow") << "init(): X11 not availble on RPI yet, using a native window instead";
	}
#endif
	////////////////

	initNative();

	glesVersion = settings.glesVersion;
	// we set this here, and if we need to make a fullscreen
	// app, we do it during the first loop.
	windowMode = settings.windowMode;
	bShowCursor = true;

	nonFullscreenWindowRect.set(0,0,settings.width,settings.height);
	nonFullscreenWindowRect.standardize();

	ofRectangle startRect = nonFullscreenWindowRect;
	bNewScreenMode = false;

	if(windowMode == OF_GAME_MODE) {
		ofLogWarning("ofAppEGLWindow") << "setupOpenGL(): OF_GAME_MODE not supported, using OF_WINDOW";
		startRect = nonFullscreenWindowRect;
	} else if(windowMode == OF_FULLSCREEN) {
		startRect = getScreenRect();
	}

	isWindowInited = createWindow(startRect);
	isSurfaceInited = createSurface();

	if(!isWindowInited) {
		ofLogError("ofAppEGLWindow")  << "setupOpenGL(): screen creation failed, window not inited";
	}

	setupPeripherals();

	nFramesSinceWindowResized = 0;

	if(settings.glesVersion>1){
		currentRenderer = make_shared<ofGLProgrammableRenderer>(this);
	}else{
		currentRenderer = make_shared<ofGLRenderer>(this);
	}

	makeCurrent();
	if(currentRenderer->getType()==ofGLProgrammableRenderer::TYPE){
		static_cast<ofGLProgrammableRenderer*>(currentRenderer.get())->setup(settings.glesVersion,0);
	}else{
		static_cast<ofGLRenderer*>(currentRenderer.get())->setup();
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::setupPeripherals() {
	if(!isUsingX11) {
		// roll our own cursor!
		mouseCursor.allocate(mouse_cursor_data.width,mouse_cursor_data.height,OF_IMAGE_COLOR_ALPHA);
		MOUSE_CURSOR_RUN_LENGTH_DECODE(mouseCursor.getPixels().getData(),mouse_cursor_data.rle_pixel_data,mouse_cursor_data.width*mouse_cursor_data.height,mouse_cursor_data.bpp);
		mouseCursor.update();
		ofLogNotice("ofAppEGLWindow") << "setupPeripherals(): peripheral setup complete";
		setupNativeEvents();
		ofLogNotice("ofAppEGLWindow") << "setupPeripherals(): native event setup complete";

	} else {
		ofLogError("ofAppEGLWindow") << "setupPeripherals(): peripherals not supported on X11";
	}
}

//------------------------------------------------------------
bool ofAppEGLWindow::createSurface() {

	EGLNativeWindowType nativeWindow = getNativeWindow();
	EGLNativeDisplayType display = getNativeDisplay();

	ofLogNotice("ofAppEGLWindow") << "createSurface(): setting up EGL Display";
	// get an EGL eglDisplay connection

	isSurfaceInited = false;

	EGLint result;

	if(display==0){
		eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
	}else{
		eglDisplay = eglGetDisplay(display);
	}

	if(eglDisplay == EGL_NO_DISPLAY) {
		ofLogNotice("ofAppEGLWindow") << "createSurface(): eglGetDisplay returned: " << eglDisplay;
		return false;
	}else{
		ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL Display correctly set " << eglDisplay;
	}

	// initialize the EGL eglDisplay connection
	result = eglInitialize(eglDisplay,
			&eglVersionMajor,
			&eglVersionMinor);

	if(result == EGL_BAD_DISPLAY) {
		//  eglDisplay is not an EGL connection
		ofLogError("ofAppEGLWindow") << "createSurface(): eglInitialize returned EGL_BAD_DISPLAY";
		return false;
	} else if(result == EGL_NOT_INITIALIZED) {
		// eglDisplay cannot be intitialized
		ofLogError("ofAppEGLWindow") << "createSurface(): eglInitialize returned EGL_NOT_INITIALIZED";
		return false;
	} else if(result == EGL_FALSE) {
		// eglinitialize was not initialiezd
		ofLogError("ofAppEGLWindow") << "createSurface(): eglInitialize returned EGL_FALSE";
		return false;
	} else {
		// result == EGL_TRUE
		// success!
	}

	EGLint glesVersion;
	int glesVersionForContext;

	if(ofGetCurrentRenderer()) {
		ofLogNotice("ofAppEGLWindow") << "createSurface(): current renderer type: " << ofGetCurrentRenderer()->getType();
	} else {
		ofLogNotice("ofAppEGLWindow") << "createSurface(): no current renderer selected";
	}

	if(this->glesVersion==2){
		glesVersion = EGL_OPENGL_ES2_BIT;
		glesVersionForContext = 2;
		ofLogNotice("ofAppEGLWindow") << "createSurface(): GLES2 renderer detected";
	}else{
		glesVersion = EGL_OPENGL_ES_BIT;
		glesVersionForContext = 1;
		ofLogNotice("ofAppEGLWindow") << "createSurface(): default renderer detected";
	}

	ofEGLAttributeListIterator iter, iterEnd;
	int i;

	// each attribute has 2 values, and we need one extra for the EGL_NONE terminator
	EGLint attribute_list_framebuffer_config[settings.frameBufferAttributes.size() * 2 + 3];

	iter = settings.frameBufferAttributes.begin();
	iterEnd = settings.frameBufferAttributes.end();
	i = 0;
	for(; iter != iterEnd; iter++) {
		attribute_list_framebuffer_config[i++] = iter->first;
		attribute_list_framebuffer_config[i++] = iter->second;
	}
	attribute_list_framebuffer_config[i++] = EGL_RENDERABLE_TYPE;
	attribute_list_framebuffer_config[i++] = glesVersion; //openGL ES version
	attribute_list_framebuffer_config[i] = EGL_NONE; // add the terminator

	EGLint num_configs;

	// get an appropriate EGL frame buffer configuration
	// http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/eglChooseConfig.html
	result = eglChooseConfig(eglDisplay,
			attribute_list_framebuffer_config,
			&eglConfig,
			1, // we only want the first one.  if we want more,
			// we need to pass in an array.
			// we are optimistic and don't give it more chances
			// to find a good configuration
			&num_configs);

	if(result == EGL_FALSE) {
		EGLint error = eglGetError();
		ofLogError("ofAppEGLWindow") << "createSurface(): error finding valid configuration based on settings: " << eglErrorString(error);
		return false;
	}

	if(num_configs <= 0 || eglConfig == NULL) {
		ofLogError("ofAppEGLWindow") << "createSurface(): no matching configs were found, num_configs: " << num_configs;
		return false;
	}


	// each attribute has 2 values, and we need one extra for the EGL_NONE terminator
	EGLint attribute_list_window_surface[settings.windowSurfaceAttributes.size() * 2 + 1];

	iter = settings.windowSurfaceAttributes.begin();
	iterEnd = settings.windowSurfaceAttributes.end();

	i = 0;
	for(; iter != iterEnd; iter++) {
		attribute_list_window_surface[i++] = iter->first;
		attribute_list_window_surface[i++] = iter->second;
	}
	attribute_list_window_surface[i] = EGL_NONE; // add the terminator

	// create a surface
	eglSurface = eglCreateWindowSurface( eglDisplay, // our display handle
			eglConfig,	// our first config
			nativeWindow, // our native window
			attribute_list_window_surface); // surface attribute list

	if(eglSurface == EGL_NO_SURFACE) {
		EGLint error = eglGetError();
		switch(error) {
		case EGL_BAD_MATCH:
			ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: EGL_BAD_MATCH " << eglErrorString(error);
			ofLogError("ofAppEGLWindow") << "createSurface(): check window and EGLConfig attributes to determine compatibility, ";
			ofLogError("ofAppEGLWindow") << "createSurface(): or verify that the EGLConfig supports rendering to a window";
			break;
		case EGL_BAD_CONFIG:
			ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: EGL_BAD_CONFIG " << eglErrorString(error);
			ofLogError("ofAppEGLWindow") << "createSurface(): verify that provided EGLConfig is valid";
			break;
		case EGL_BAD_NATIVE_WINDOW:
			ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: EGL_BAD_NATIVE_WINDOW " << eglErrorString(error);
			ofLogError("ofAppEGLWindow") << "createSurface(): verify that provided EGLNativeWindow is valid";
			break;
		case EGL_BAD_ALLOC:
			ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: EGL_BAD_ALLOC " << eglErrorString(error);
			ofLogError("ofAppEGLWindow") << "createSurface(): not enough resources available";
			break;
		default:
			ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: << " << error << eglErrorString(error);
		}

		return false;
	}else{
		ofLogNotice("ofAppEGLWindow") << "createSurface(): surface created correctly";
	}

	// get an appropriate EGL frame buffer configuration
	result = eglBindAPI(EGL_OPENGL_ES_API);

	if(result == EGL_FALSE) {
		ofLogError("ofAppEGLWindow") << "createSurface(): error binding API: " << eglErrorString(eglGetError());
		return false;
	}else{
		ofLogNotice("ofAppEGLWindow") << "createSurface(): API bound correctly";
	}

	// create an EGL rendering eglContext
	EGLint attribute_list_surface_context[] = {
			EGL_CONTEXT_CLIENT_VERSION, glesVersionForContext,
			EGL_NONE
	};

	eglContext = eglCreateContext(eglDisplay,
			eglConfig,
			EGL_NO_CONTEXT,
			attribute_list_surface_context);

	if(eglContext == EGL_NO_CONTEXT) {
		EGLint error = eglGetError();
		if(error == EGL_BAD_CONFIG) {
			ofLogError("ofAppEGLWindow") << "createSurface(): error creating context: EGL_BAD_CONFIG " << eglErrorString(error);
			return false;
		} else {
			ofLogError("ofAppEGLWindow") << "createSurface(): error creating context: " << error << " " << eglErrorString(error);
			return false;
		}
	}

	// connect the eglContext to the eglSurface
	result = eglMakeCurrent(eglDisplay,
			eglSurface, // draw surface
			eglSurface, // read surface
			eglContext);

	if(eglContext == EGL_FALSE) {
		EGLint error = eglGetError();
		ofLogError("ofAppEGLWindow") << "createSurface(): couldn't making current surface: " << eglErrorString(error);
		return false;
	}

	// Set background color and clear buffers
	glClearColor(settings.initialClearColor.r / 255.0f,
			settings.initialClearColor.g / 255.0f,
			settings.initialClearColor.b / 255.0f,
			settings.initialClearColor.a / 255.0f);
	glClear( GL_COLOR_BUFFER_BIT );
	glClear( GL_DEPTH_BUFFER_BIT );

	ofLogNotice("ofAppEGLWindow") << "createSurface(): -----EGL-----";
	ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_VERSION_MAJOR = " << eglVersionMajor;
	ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_VERSION_MINOR = " << eglVersionMinor;
	ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_CLIENT_APIS = " << eglQueryString(eglDisplay, EGL_CLIENT_APIS);
	ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_VENDOR = "  << eglQueryString(eglDisplay, EGL_VENDOR);
	ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_VERSION = " << eglQueryString(eglDisplay, EGL_VERSION);
	ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_EXTENSIONS = " << eglQueryString(eglDisplay, EGL_EXTENSIONS);
	ofLogNotice("ofAppEGLWindow") << "createSurface(): GL_RENDERER = " << glGetString(GL_RENDERER);
	ofLogNotice("ofAppEGLWindow") << "createSurface(): GL_VERSION  = " << glGetString(GL_VERSION);
	ofLogNotice("ofAppEGLWindow") << "createSurface(): GL_VENDOR   = " << glGetString(GL_VENDOR);
	ofLogNotice("ofAppEGLWindow") << "createSurface(): -------------";

	isSurfaceInited = true;

	return true;
}

//------------------------------------------------------------
bool ofAppEGLWindow::destroySurface() {
	if(isSurfaceInited) {
		ofLogNotice("ofAppEGLWindow") << "destroySurface(): destroying EGL surface";
		eglSwapBuffers(eglDisplay, eglSurface);
		eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
		eglDestroySurface(eglDisplay, eglSurface);
		eglDestroyContext(eglDisplay, eglContext);
		eglTerminate(eglDisplay);
		isSurfaceInited = false;

		eglDisplay = NULL;
		eglSurface = NULL;
		eglContext = NULL;
		eglConfig  = NULL;
		eglVersionMinor = -1;
		eglVersionMinor = -1;

		return true;
	} else {
		ofLogError("ofAppEGLWindow") << "destroySurface(): attempted to destroy uninitialized window";
		return false;
	}
}

//------------------------------------------------------------
bool ofAppEGLWindow::destroyWindow() {
	if(isWindowInited) {
		if(isUsingX11) {
			// TODO: double check
			XDestroyWindow(x11Display,x11Window); // or XCloseWindow?
			XFree(x11Screen);
		} else {
#ifdef TARGET_RASPBERRY_PI
			dispman_update = vc_dispmanx_update_start(0);
			if (dispman_element != DISPMANX_NO_HANDLE) {
				vc_dispmanx_element_remove(dispman_update, dispman_element);
				dispman_element = DISPMANX_NO_HANDLE;
			}

			vc_dispmanx_update_submit_sync(dispman_update);

			if (dispman_display != DISPMANX_NO_HANDLE) {
				vc_dispmanx_display_close(dispman_display);
				dispman_display = DISPMANX_NO_HANDLE;
			}
#else
	ofLogNotice("ofAppEGLWindow") << "destroyWindow(): no native window type for this system, perhaps try X11?";
#endif
		}

	} else {
		ofLogNotice("ofAppEGLWindow") << "destroyWindow(): destroying (uninited) native window (not implemented yet)";
	}

	return true;
}


void ofAppEGLWindow::close(){
	if(!isUsingX11) {
		destroyNativeEvents();
	}

	// we got a terminate ... so clean up.
	destroySurface();
	destroyWindow();

	exitNative();
	events().notifyExit();
	events().disable();
}

//------------------------------------------------------------
void ofAppEGLWindow::makeCurrent(){
	eglMakeCurrent(eglDisplay,
			eglSurface, // draw surface
			eglSurface, // read surface
			eglContext);
}

//------------------------------------------------------------
void ofAppEGLWindow::swapBuffers(){
	EGLBoolean success = eglSwapBuffers(eglDisplay, eglSurface);
	if(!success) {
		GLint error = eglGetError();
		ofLogNotice("ofAppEGLWindow") << "display(): eglSwapBuffers failed: " << eglErrorString(error);
	}
}

//--------------------------------------------
void ofAppEGLWindow::startRender() {
	renderer()->startRender();
}

//--------------------------------------------
void ofAppEGLWindow::finishRender() {
	renderer()->finishRender();
}

//------------------------------------------------------------
void ofAppEGLWindow::update() {
	coreEvents.notifyUpdate();
}


//------------------------------------------------------------
void ofAppEGLWindow::draw() {
	// take care of any requests for a new screen mode
	if (windowMode != OF_GAME_MODE && bNewScreenMode){
		if( windowMode == OF_FULLSCREEN){
			setWindowRect(getScreenRect());
		} else if( windowMode == OF_WINDOW ){
			setWindowRect(nonFullscreenWindowRect);
		}
		bNewScreenMode = false;
	}

	currentRenderer->startRender();
	if( bEnableSetupScreen ) currentRenderer->setupScreen();

	coreEvents.notifyDraw();

	if(!isUsingX11) {
		if(bShowCursor){
			GLboolean bIsDepthTestEnabled = GL_FALSE;
			glGetBooleanv(GL_DEPTH_TEST, &bIsDepthTestEnabled);

			if(bIsDepthTestEnabled == GL_TRUE) {
				glDisable(GL_DEPTH_TEST);
			}

			bool isUsingNormalizedTexCoords = ofGetUsingNormalizedTexCoords();
			if(isUsingNormalizedTexCoords) {
				ofDisableNormalizedTexCoords();
			}

			currentRenderer->pushStyle();
			currentRenderer->setBlendMode(OF_BLENDMODE_ADD);
			currentRenderer->setColor(255);
			mouseCursor.draw(ofGetMouseX(),ofGetMouseY());

			currentRenderer->popStyle();

			if(bIsDepthTestEnabled == GL_TRUE) {
				glEnable(GL_DEPTH_TEST);
			}

			if(isUsingNormalizedTexCoords) {
				ofEnableNormalizedTexCoords();
			}
		}
	}
	currentRenderer->finishRender();

	EGLBoolean success = eglSwapBuffers(eglDisplay, eglSurface);
	if(!success) {
		GLint error = eglGetError();
		ofLogNotice("ofAppEGLWindow") << "display(): eglSwapBuffers failed: " << eglErrorString(error);
	}

	nFramesSinceWindowResized++;

}

//------------------------------------------------------------
ofCoreEvents & ofAppEGLWindow::events(){
	return coreEvents;
}

//------------------------------------------------------------
shared_ptr<ofBaseRenderer> & ofAppEGLWindow::renderer(){
	return currentRenderer;
}

//------------------------------------------------------------
void ofAppEGLWindow::setupNativeEvents() {
	setupNativeUDev();
	setupNativeMouse();
	setupNativeKeyboard();
	startThread();
}

//------------------------------------------------------------
void ofAppEGLWindow::destroyNativeEvents() {
	destroyNativeUDev();
	destroyNativeMouse();
	destroyNativeKeyboard();
	waitForThread(true, threadTimeout);
}

//------------------------------------------------------------
void ofAppEGLWindow::setWindowRect(const ofRectangle& requestedWindowRect) {
	if(!isWindowInited) {
		ofLogError("ofAppEGLWindow") << "setWindowRect(): window not inited";
		return;
	}

	ofRectangle newRect = requestedWindowRect.getStandardized();

	if(newRect != currentWindowRect) {
		ofRectangle oldWindowRect = currentWindowRect;

		if(isUsingX11) {
			int ret = XMoveResizeWindow(x11Display,
					x11Window,
					(int)newRect.x,
					(int)newRect.y,
					(unsigned int)newRect.width,
					(unsigned int)newRect.height);
			if(ret == BadValue) {
				ofLogError("ofAppEGLWindow") << "setWindowRect(): XMoveResizeWindow returned BadValue";
			} else if(ret == BadWindow) {
				ofLogError("ofAppEGLWindow") << "setWindowRect(): XMoveResizeWindow returned BadWindow";
			} else {
				// all is good
				currentWindowRect = newRect;
			}
		} else {
#ifdef TARGET_RASPBERRY_PI

			VC_RECT_T dst_rect;
			dst_rect.x = (int32_t)newRect.x;
			dst_rect.y = (int32_t)newRect.y;
			dst_rect.width = (int32_t)newRect.width;
			dst_rect.height = (int32_t)newRect.height;

			VC_RECT_T src_rect;
			src_rect.x = 0;
			src_rect.y = 0;
			src_rect.width = (int32_t)newRect.width << 16;
			src_rect.height = (int32_t)newRect.height << 16;

			DISPMANX_UPDATE_HANDLE_T dispman_update = vc_dispmanx_update_start(0);

			vc_dispmanx_element_change_attributes(dispman_update,
					dispman_element,
					ELEMENT_CHANGE_SRC_RECT|ELEMENT_CHANGE_DEST_RECT, // we do both when resizing
					0, // layer (we aren't changing it here)
					0, // opactiy (we aren't changing it here)
					&dst_rect,
					&src_rect,
					0, // mask (we aren't changing it here)
					(DISPMANX_TRANSFORM_T)0);


			vc_dispmanx_update_submit_sync(dispman_update);

			// next time swapBuffers is called, it will be resized based on this eglwindow size data
			dispman_native_window.element = dispman_element;
			dispman_native_window.width = (int32_t)newRect.width;
			dispman_native_window.height = (int32_t)newRect.height; // don't forget!

			currentWindowRect = newRect;

#else
			ofLogError("ofAppEGLWindow") << "createEGLWindow(): no native window type for this system, perhaps try X11?";
#endif
		}

		if(oldWindowRect.width  != currentWindowRect.width || oldWindowRect.height != currentWindowRect.height) {
			coreEvents.notifyWindowResized(currentWindowRect.width,	currentWindowRect.height);
			nFramesSinceWindowResized = 0;
		}
	}
}


//------------------------------------------------------------
bool ofAppEGLWindow::createWindow(const ofRectangle& requestedWindowRect) {
	if(isUsingX11) {
		return createX11NativeWindow(requestedWindowRect);
	} else {
#ifdef TARGET_RASPBERRY_PI
		return createRPiNativeWindow(requestedWindowRect);
#else
		ofLogError("ofAppEGLWindow") << "createEGLWindow(): no native window type for this system, perhaps try X11?";
		return false;
#endif
	}
}

//------------------------------------------------------------
int ofAppEGLWindow::getWindowWidth() {
	return currentWindowRect.width;
}

//------------------------------------------------------------
int ofAppEGLWindow::getWindowHeight() {
	return currentWindowRect.height;
}

//------------------------------------------------------------
void ofAppEGLWindow::pollEvents(){
	if(!instance) return;
	if(instance->isUsingX11) {
		while(1){
			XEvent event;
			if (::XCheckWindowEvent(instance->x11Display, instance->x11Window, -1, &event)){
				handleX11Event(event);
			}else if (::XCheckTypedEvent(instance->x11Display, ClientMessage, &event)){
				handleX11Event(event);
			}else{
				break;
			}
		}
	} else {
		queue<ofMouseEventArgs> mouseEventsCopy;
		instance->lock();
		mouseEventsCopy = instance->mouseEvents;
		while(!instance->mouseEvents.empty()){
			instance->mouseEvents.pop();
		}
		instance->unlock();
		while(!mouseEventsCopy.empty()){
			instance->coreEvents.notifyMouseEvent(mouseEventsCopy.front());
			mouseEventsCopy.pop();
		}

		// KEYBOARD EVENTS
		queue<ofKeyEventArgs> keyEventsCopy;
		instance->lock();
		keyEventsCopy = instance->keyEvents;
		while(!instance->keyEvents.empty()){
			instance->keyEvents.pop();
		}
		instance->unlock();
		while(!keyEventsCopy.empty()){
			instance->coreEvents.notifyKeyEvent(keyEventsCopy.front());
			keyEventsCopy.pop();
		}
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::hideCursor(){
	bShowCursor = false;
}

//------------------------------------------------------------
void ofAppEGLWindow::showCursor(){
	bShowCursor = true;
}

//------------------------------------------------------------
void ofAppEGLWindow::setWindowTitle(string title) {
	ofLogNotice("ofAppEGLWindow") << "setWindowTitle(): not implemented";
}

//------------------------------------------------------------
glm::vec2 ofAppEGLWindow::getWindowSize(){
	return {currentWindowRect.width, currentWindowRect.height};
}

//------------------------------------------------------------
glm::vec2 ofAppEGLWindow::getWindowPosition(){
	return currentWindowRect.getPosition().xy();
}

//------------------------------------------------------------
glm::vec2 ofAppEGLWindow::getScreenSize(){
	unsigned int screenWidth = 0;
	unsigned int screenHeight = 0;

	if(isUsingX11) {
		// TODO, there must be a way to get screensize if the window is not inited
		if(isWindowInited && x11Screen) {
			screenWidth  = XWidthOfScreen(x11Screen);
			screenHeight = XHeightOfScreen(x11Screen);
		} else {
			ofLogError("ofAppEGLWindow") << "getScreenSize(): tried to get display size but failed, x11Screen is not inited";
		}

	} else {
#ifdef TARGET_RASPBERRY_PI
		int success = graphics_get_display_size(settings.screenNum, &screenWidth, &screenHeight);
		if(success < 0) {
			ofLogError("ofAppEGLWindow") << "getScreenSize(): tried to get display size but failed";
		}

#else
		ofLogError("ofAppEGLWindow") << "getScreenSize(): no native window type for this system, perhaps try X11?";
#endif

	}

	return {screenWidth, screenHeight};
}

//------------------------------------------------------------
int ofAppEGLWindow::getWidth(){
	if( orientation == OF_ORIENTATION_DEFAULT || orientation == OF_ORIENTATION_180 ){
		return currentWindowRect.width;
	}
	return currentWindowRect.height;
}

//------------------------------------------------------------
int ofAppEGLWindow::getHeight(){
	if( orientation == OF_ORIENTATION_DEFAULT || orientation == OF_ORIENTATION_180 ){
		return currentWindowRect.height;
	}
	return currentWindowRect.width;
}

//------------------------------------------------------------
void ofAppEGLWindow::setOrientation(ofOrientation orientationIn){
	orientation = orientationIn;
}

//------------------------------------------------------------
ofOrientation ofAppEGLWindow::getOrientation(){
	return orientation;
}

//------------------------------------------------------------
bool ofAppEGLWindow::doesHWOrientation() {
	return false;
}

//------------------------------------------------------------
void ofAppEGLWindow::setWindowPosition(int x, int y){
	if(!isWindowInited) {
		ofLogError("ofAppEGLWindow") << "setWindowPosition(): window not inited";
		return;
	}

	if(isUsingX11) {
		int ret = XMoveWindow(x11Display,
				x11Window,
				x,
				y);
		if(ret == BadValue) {
			ofLogError("ofAppEGLWindow") << "setWindowPosition(): XMoveWindow returned BadValue";
		} else if(ret == BadWindow) {
			ofLogError("ofAppEGLWindow") << "setWindowPosition(): XMoveWindow returned BadWindow";
		} else {
			currentWindowRect.x = x;
			currentWindowRect.y = y;
			nonFullscreenWindowRect = currentWindowRect;
		}
	} else {
#ifdef TARGET_RASPBERRY_PI

		// keep it in bounds
		auto screenSize = getScreenSize();
		x = ofClamp(x, 0, screenSize.x - currentWindowRect.width);
		y = ofClamp(y, 0, screenSize.y - currentWindowRect.height);

		VC_RECT_T dst_rect;
		dst_rect.x = (int32_t)x;
		dst_rect.y = (int32_t)y;
		dst_rect.width = (int32_t)currentWindowRect.width;
		dst_rect.height = (int32_t)currentWindowRect.height;

		dispman_update = vc_dispmanx_update_start(0);

		vc_dispmanx_element_change_attributes(dispman_update,
				dispman_native_window.element,
				ELEMENT_CHANGE_DEST_RECT,
				0,
				0,
				&dst_rect,
				NULL,
				0,
				(DISPMANX_TRANSFORM_T)0);


vc_dispmanx_update_submit_sync(dispman_update);

currentWindowRect.x = x;
currentWindowRect.y = y;
nonFullscreenWindowRect = currentWindowRect;

#else
	ofLogError("ofAppEGLWindow") << "setWindowPosition(): no native window type for this system, perhaps try X11?";
#endif
	}

}

//------------------------------------------------------------
void ofAppEGLWindow::setWindowShape(int w, int h){
	if(!isWindowInited) {
		ofLogError("ofAppEGLWindow") << "setWindowPosition(): window not inited";
		return;
	}

	if(isUsingX11) {
		int ret = XResizeWindow(x11Display,
				x11Window,
				(unsigned int)w,
				(unsigned int)h);
		if(ret == BadValue) {
			ofLogError("ofAppEGLWindow") << "setWindowPosition(): XMoveWindow returned BadValue";
		} else if(ret == BadWindow) {
			ofLogError("ofAppEGLWindow") << "setWindowPosition(): XMoveWindow returned BadWindow";
		} else {
			currentWindowRect.width = w;
			currentWindowRect.height = h;
			nonFullscreenWindowRect = currentWindowRect;
		}
	} else {
#ifdef TARGET_RASPBERRY_PI
		setWindowRect(ofRectangle(currentWindowRect.x,currentWindowRect.y,w,h));
		nonFullscreenWindowRect = currentWindowRect;
#else
		ofLogError("ofAppEGLWindow") << "setWindowPosition(): no native window type for this system, perhaps try X11?";
#endif
	}
}

//------------------------------------------------------------
ofWindowMode ofAppEGLWindow::getWindowMode(){
	return windowMode;
}

//------------------------------------------------------------
void ofAppEGLWindow::toggleFullscreen(){
	if( windowMode == OF_GAME_MODE) return;

	if( windowMode == OF_WINDOW ){
		setFullscreen(true);
	}else{
		setFullscreen(false);
	}

}

//------------------------------------------------------------
void ofAppEGLWindow::setFullscreen(bool fullscreen){
	if( windowMode == OF_GAME_MODE) return;

	if(fullscreen && windowMode != OF_FULLSCREEN){
		bNewScreenMode = true;
		windowMode = OF_FULLSCREEN;
	}else if(!fullscreen && windowMode != OF_WINDOW) {
		bNewScreenMode = true;
		windowMode = OF_WINDOW;
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::enableSetupScreen(){
	bEnableSetupScreen = true;
}

//------------------------------------------------------------
void ofAppEGLWindow::disableSetupScreen(){
	bEnableSetupScreen = false;
}

//------------------------------------------------------------
ofRectangle ofAppEGLWindow::getScreenRect(){
	auto screenSize = getScreenSize();
	return ofRectangle(0,0,screenSize.x,screenSize.y);
}

//------------------------------------------------------------
void ofAppEGLWindow::setVerticalSync(bool enabled){
	eglSwapInterval(eglDisplay, enabled ? 1 : 0);
}

//------------------------------------------------------------
void ofAppEGLWindow::threadedFunction(){
	// TODO: a way to setup mouse and keyboard if
	// they are not plugged in upon start
	// This can be done with our udev device callbacks

	while(isThreadRunning()) {
		readNativeUDevEvents();
		readNativeMouseEvents();
		readNativeKeyboardEvents();

		// sleep briefly
		ofSleepMillis(20);
	}
}

//------------------------------------------------------------
// PLATFORM SPECIFIC RPI
//------------------------------------------------------------

//------------------------------------------------------------
void ofAppEGLWindow::setupNativeUDev() {

	udev = udev_new(); // create new udev object
	if(!udev) {
		ofLogError("ofAppEGLWindow") << "setupNativeUDev(): couldn't create udev object";
	} else {
		ofLogNotice("ofAppEGLWindow") << "setupNativeUDev(): created udev object";
		// setup udev to monitor for input devices
		mon = udev_monitor_new_from_netlink(udev, "udev");
		// just listen for input devices
		udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL);
		udev_monitor_enable_receiving(mon);
		// get the file descriptor for the mon (used w/ select);
		udev_fd = udev_monitor_get_fd(mon);
	}

	if(udev_fd < 0) {
		ofLogError("ofAppEGLWindow") << "setupNativeUDev(): did not create udev object, udev_fd < 0";
	}

}

//------------------------------------------------------------
void ofAppEGLWindow::destroyNativeUDev() {
	udev_unref(udev); // clean up
}


//------------------------------------------------------------
void ofAppEGLWindow::setupNativeMouse() {
	struct dirent **eps; // What eps is supposed to mean? 

	// fallback to /dev/input/eventX since some vnc servers use uinput to 
	// handle mouse & keyboard.
	typedef int (*filter_ptr)(const struct dirent *d);
	filter_ptr mouse_filters[2] = { filter_mouse, filter_event };
	string devicePathBuffers[2] = { "/dev/input/by-path/", "/dev/input/" };

	// Examine available input devices in directories at devicePathBuffers.
	for(auto i = 0; i < 2; i++){
		
		// Get number of devices in the current input device directory.
		int n = scandir(devicePathBuffers[i].c_str(), &eps, mouse_filters[i], dummy_sort);

		// Open the appropriate entries, 
		// we want to get all mouse device events.
		if(n >= 0 && eps != 0 && eps[0] != 0) {
			for(auto j = 0; j < n; ++j){
				string devicePathBuffer;
				devicePathBuffer.append(devicePathBuffers[i]);
				devicePathBuffer.append(eps[j]->d_name);
				
				ofLogNotice("ofAppEGLWindow::setupNativeMouse()")
					<< "attempt to open " << devicePathBuffer;

				// TODO: create a vector out of fildes entries
				int fildes = open(devicePathBuffer.c_str(), O_RDONLY | O_NONBLOCK);

				// No file descriptor, stop the current iteration of the nested loop here,
				// continue with the next value of j.
				if(fildes < 0){
					ofLogError("ofAppEGLWindow::setupNativeMouse()") 
						<< "failed to open mouse device";
					continue;
				}

				// Seems that we got a mouse device open! 
				// Get all the parameters.
				ofAppEGLWindowMouseArgs mouseArgs;
				mouseArgs.fildes = fildes;

				// TODO: consider a more reasonable way for calculating these.
				mouseArgs.scaleX = 2.0f;
				mouseArgs.scaleY = 2.0f;

				// Find out if input device is a absolute positioning device
				// set the minimum and maximum values of the axes.
				// Do this for the x axis. EVIOCGABS(0): 0 stands for x axis.
				struct input_absinfo mabsx;
				if (ioctl(fildes, EVIOCGABS(0), &mabsx) < 0){
					ofLogError("ofAppEGLWindow::setupNativeMouse()") 
						<< "failed to get absolute limits of input x axis";
					mouseArgs.absXMin = 0;
					mouseArgs.absXMax = 0;
				} else {
					mouseArgs.absXMin = mabsx.minimum;
					mouseArgs.absXMax = mabsx.maximum;
					ofLogNotice("ofAppEGLWindow::setupNativeMouse()") 
						<< "mouse x axis min, max: " << mouseArgs.absXMin 
						<< ", " << mouseArgs.absXMax;
				}
				
				// Do that for the y axis. EVIOCGABS(1): 1 stands for y axis.
				struct input_absinfo mabsy;
				if (ioctl(fildes, EVIOCGABS(1), &mabsy) < 0){
					ofLogError("ofAppEGLWindow::setupNativeMouse()") 
						<< "failed to get absolute limits of input y axis";
					mouseArgs.absYMin = 0;
					mouseArgs.absYMax = 0;
				} else {
					mouseArgs.absYMin = mabsy.minimum;
					mouseArgs.absYMax = mabsy.maximum;
					ofLogNotice("ofAppEGLWindow::setupNativeMouse()") 
						<< "mouse y axis min, max: " << mouseArgs.absYMin 
						<< ", " << mouseArgs.absYMax;
				}

				// Get input device name.
				char deviceNameBuffer[256] = "Unknown Device";
				ioctl(fildes, EVIOCGNAME(sizeof(deviceNameBuffer)), deviceNameBuffer);
				mouseArgs.deviceName = string(deviceNameBuffer);
				ofLogNotice("ofAppEGLWindow::setupNativeMouse()") 
					<< "setting up mouse device with name " 
					<< mouseArgs.deviceName;

				mice.push_back(mouseArgs);

				ofLogNotice("ofAppEGLWindow") << "setupMouse(): fildes=" 
					<<  fildes << " devicePath=" << devicePathBuffer;
			} // Nested for loop
		}

		// Do not examine other input device directories if we found some mice here.
		// This breaks the main for loop of setupNativeMouse().
		if(mice.size()){
			break;
		}
	} // Main for loop

	mb.mouseButtonState = 0;

	if(mice.size() <= 0) {
		mouseDetected = false;
		ofLogWrning("ofAppEGLWindow::setupNativeMouse()") 
			<< "no mouse found";
	} else {
		mouseDetected = true;
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::setupNativeKeyboard() {
	struct dirent **eps;
	typedef int (*filter_ptr)(const struct dirent *d);
	filter_ptr kbd_filters[2] = { filter_kbd, filter_event };
	string devicePathBuffers[2] = { "/dev/input/by-path/", "/dev/input/" };

	for(int i=0; i<2; i++){
		int n = scandir(devicePathBuffers[i].c_str(), &eps, kbd_filters[i], dummy_sort);

		// make sure that we found an appropriate entry
		if(n >= 0 && eps != 0 && eps[0] != 0) {
			string devicePathBuffer;
			devicePathBuffer.append(devicePathBuffers[i]);
			devicePathBuffer.append(eps[0]->d_name);
			keyboard_fd = open(devicePathBuffer.c_str(), O_RDONLY | O_NONBLOCK);
			ofLogNotice("ofAppEGLWindow") << "setupKeyboard(): keyboard_fd=" <<  keyboard_fd << " devicePath=" << devicePathBuffer;
			break;
		}
	}

	if (keyboard_fd >= 0) {
		char deviceNameBuffer[256] = "Unknown Device";
		ioctl(keyboard_fd, EVIOCGNAME(sizeof(deviceNameBuffer)), deviceNameBuffer);
		ofLogNotice("ofAppEGLWindow") << "setupKeyboard(): keyboard device name = " << deviceNameBuffer;


		// save current terminal settings
		tcgetattr (STDIN_FILENO, &tc);
		ots = tc;
		// disable echo on our temporary settings
		tc.c_lflag &= ~ECHO;
		tc.c_lflag |= ECHONL;
		tcsetattr(STDIN_FILENO, TCSAFLUSH, &tc);

	} else {
		ofLogError("ofAppEGLWindow") << "setupKeyboard(): did not open keyboard";
	}

	kb.shiftPressed = false;
	kb.capsLocked = false;

	if(keyboard_fd < 0) {
		ofLogError("ofAppEGLWindow") << "setupKeyboard(): did not open keyboard, keyboard_fd < 0";
	} else {
		keyboardDetected = true;
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::destroyNativeMouse() {
	for(auto i = 0; i < mice.size(); ++i){
		int retval = close(mice[i].fildes);
		if(retval < 0){
			ofLogError("ofAppEGLWindow::destroyNativeMouse()") 
				<< "Failed to close mouse device: "
				<< mice[i].deviceName;
		}
	}
	mice.clear();
}

//------------------------------------------------------------
void ofAppEGLWindow::destroyNativeKeyboard() {
	ofLogNotice("ofAppEGLWindow") << "destroyNativeKeyboard()";

	if (keyboard_fd >= 0) {
		tcsetattr (STDIN_FILENO, TCSAFLUSH, &ots);
	} else {
		ofLogNotice("ofAppEGLWindow") << "destroyNativeKeyboard(): unable to reset terminal";
	}
}


//------------------------------------------------------------
void ofAppEGLWindow::readNativeUDevEvents() {
	// look for devices being attatched / detatched

	fd_set fds;
	struct timeval tv;
	int ret;

	FD_ZERO(&fds);
	FD_SET(udev_fd, &fds);
	tv.tv_sec = 0;
	tv.tv_usec = 0;

	ret = select(udev_fd+1, &fds, NULL, NULL, &tv);

	/* Check if our file descriptor has received data. */
	if (ret > 0 && FD_ISSET(udev_fd, &fds)) {
		/* Make the call to receive the device.
		   select() ensured that this will not block. */
		dev = udev_monitor_receive_device(mon);
		if (dev) {
			// TODO: finish auto connect
			ofLogNotice() << "Got device";
			ofLogNotice() << "   node: %s\n", udev_device_get_devnode(dev);
			ofLogNotice() << "   subsystem: %s\n", udev_device_get_subsystem(dev);
			ofLogNotice() << "   devtype: %s\n", udev_device_get_devtype(dev);
			ofLogNotice() << "   action: %s\n", udev_device_get_action(dev);
			udev_device_unref(dev);
		}
		else {
			ofLogNotice("ofAppEGLWindow") << "readNativeUDevEvents(): device returned by receive_device() is NULL";
		}
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::readNativeKeyboardEvents() {
	// http://www.diegm.uniud.it/loghi/CE2/kbd.pdf
	// http://cgit.freedesktop.org/~whot/evtest/plain/evtest.c
	// https://strcpy.net/b/archives/2010/11/17/abusing_the_linux_input_subsystem/index.html
	struct input_event ev;
	char key = 0;

	int nBytesRead = read(keyboard_fd, &ev,sizeof(struct input_event));

	static ofKeyEventArgs keyEvent;
	bool pushKeyEvent = false;

	while(nBytesRead >= 0) {

		if (ev.type==EV_KEY) {
			if(ev.value == 0) {
				// key released
				keyEvent.type = ofKeyEventArgs::Released;
			} else if(ev.value == 1) {
				// key pressed
				keyEvent.type = ofKeyEventArgs::Pressed;
			} else if(ev.value == 2) {
				// key repeated
				keyEvent.type = ofKeyEventArgs::Pressed;
			} else {
				// unknown ev.value
			}

			switch (ev.code) {
			case KEY_RIGHTSHIFT:
			case KEY_LEFTSHIFT:
				kb.shiftPressed = ev.value;
				break;
			case KEY_RIGHTCTRL:
			case KEY_LEFTCTRL:
				break;
			case KEY_CAPSLOCK:
				if (ev.value == 1) {
					if (kb.capsLocked) {
						kb.capsLocked = 0;
					} else {
						kb.capsLocked = 1;
					}
				}
				break;

			case KEY_ESC:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_ESC;
				break;
			case KEY_BACKSPACE:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_BACKSPACE;
				break;
			case KEY_DELETE:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_DEL;
				break;
			case KEY_F1:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F1;
				break;
			case KEY_F2:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F2;
				break;
			case KEY_F3:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F3;
				break;
			case KEY_F4:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F4;
				break;
			case KEY_F5:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F5;
				break;
			case KEY_F6:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F6;
				break;
			case KEY_F7:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F7;
				break;
			case KEY_F8:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F8;
				break;
			case KEY_F9:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F9;
				break;
			case KEY_F10:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F10;
				break;
			case KEY_F11:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F11;
				break;
			case KEY_F12:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_F12;
				break;
			case KEY_LEFT:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_LEFT;
				break;
			case KEY_UP:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_UP;
				break;
			case KEY_RIGHT:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_RIGHT;
				break;
			case KEY_DOWN:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_DOWN;
				break;
			case KEY_PAGEUP:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_PAGE_UP;
				break;
			case KEY_PAGEDOWN:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_PAGE_DOWN;
				break;
			case KEY_HOME:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_HOME;
				break;
			case KEY_END:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_END;
				break;
			case KEY_INSERT:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_INSERT;
				break;
			case KEY_ENTER:
			case KEY_KPENTER:
				pushKeyEvent = true;
				keyEvent.key = OF_KEY_RETURN;
				break;

			default:
				// VERY RUDIMENTARY KEY MAPPING WITH MAPS ABOVE
				if(ev.code < sizeof(lowercase_map)) {
					if (kb.shiftPressed) {
						key = uppercase_map[ev.code];
						if (kb.capsLocked) keyEvent.key = tolower(key);
						keyEvent.key = key;
						pushKeyEvent = true;
					} else {
						key = lowercase_map[ev.code];
						if (kb.capsLocked) key = toupper(key);
						keyEvent.key = key;
						pushKeyEvent = true;
					}
				} else {
					ofLogNotice("ofAppEGLWindow") << "readKeyboardEvents(): input_event.code is outside of our small range";
				}
			}
		} else if(ev.type == EV_MSC) {
			// EV_MSC events are used for input and output events that
			// do not fall under other categories.
			// ofLogVerbose("ofAppEGLWindow") << "readKeyboardEvents(): EV_MSC";
		} else if(ev.type == EV_SYN ) {
			// EV_SYN Used as markers to separate events. Events may be
			// separated in time or in space, such as with the multitouch protocol.
			// ofLogVerbose("ofAppEGLWindow") << "readKeyboardEvents(): EV_SYN";
		} else {
			// unhandled type
		}

		// do we have a mouse svent to push?
		if(pushKeyEvent){
			lock();
			keyEvents.push(keyEvent);
			unlock();
			pushKeyEvent = false;
		}

		nBytesRead = read(keyboard_fd, &ev,sizeof(struct input_event));
	}
}

//------------------------------------------------------------
void ofAppEGLWindow::readNativeMouseEvents() {
	// http://cgit.freedesktop.org/~whot/evtest/plain/evtest.c
	struct input_event ev;
	static ofMouseEventArgs mouseEvent;
	bool pushMouseEvent = false;

	for(auto i = 0; i < mice.size(); ++i){
		// Loop through all open mouse devices

// -------------
	int nBytesRead = read(mice[i].fildes, &ev,sizeof(struct input_event));

	bool axisValuePending = false;

	while(nBytesRead >= 0) {

		if(ev.type == EV_REL || ev.type == EV_ABS) {
			int axis = ev.code;
			int amount = ev.value;

			switch(axis) {
			case 0:
				if(ev.type == EV_REL) {
					mouseEvent.x += amount * mouseScaleX;
				} else {
					mouseEvent.x = amount * (float)currentWindowRect.width / (float)mouseAbsXMax;
				}

				mouseEvent.x = ofClamp(mouseEvent.x, 0, currentWindowRect.width);
				axisValuePending = true;
				break;
			case 1:
				if(ev.type == EV_REL) {
					mouseEvent.y += amount * mouseScaleY;
				} else {
					mouseEvent.y = amount * (float)currentWindowRect.height / (float)mouseAbsYMax;
				}

				mouseEvent.y = ofClamp(mouseEvent.y, 0, currentWindowRect.height);
				axisValuePending = true;
				break;
			default:
				ofLogNotice("ofAppEGLWindow") << "readMouseEvents(): unknown mouse axis (perhaps it's the scroll wheel?)";
				break;
			}

		} else if(ev.type == EV_KEY) {
			// only tracking three buttons now ...
			if(ev.code == BTN_LEFT) {
				if(ev.value == 0) { // release
					mouseEvent.button = OF_MOUSE_BUTTON_LEFT;
				mouseEvent.type = ofMouseEventArgs::Released;
				mb.mouseButtonState &= ~MOUSE_BUTTON_LEFT_MASK;
				pushMouseEvent = true;
				} else if(ev.value == 1) { // press
					mb.mouseButtonState |= MOUSE_BUTTON_LEFT_MASK;
					mouseEvent.type = ofMouseEventArgs::Pressed;
					mouseEvent.button = OF_MOUSE_BUTTON_LEFT;
					pushMouseEvent = true;
				} else { // unknown
					ofLogNotice("ofAppEGLWindow") << "readMouseEvents(): EV_KEY : unknown ev.value = " << ev.value;
				}
			} else if(ev.code == BTN_MIDDLE) {
				if(ev.value == 0) { // release
					mouseEvent.button = OF_MOUSE_BUTTON_MIDDLE;
					mouseEvent.type = ofMouseEventArgs::Released;
					mb.mouseButtonState &= ~MOUSE_BUTTON_MIDDLE_MASK;
					pushMouseEvent = true;
				} else if(ev.value == 1) { // press
					mb.mouseButtonState |= MOUSE_BUTTON_MIDDLE_MASK;
					mouseEvent.type = ofMouseEventArgs::Pressed;
					mouseEvent.button = OF_MOUSE_BUTTON_MIDDLE;
					pushMouseEvent = true;
				} else { // unknown
					ofLogNotice("ofAppEGLWindow") << "readMouseEvents(): EV_KEY : unknown ev.value = " << ev.value;
				}
			} else if(ev.code == BTN_RIGHT) {
				if(ev.value == 0) { // release
					mouseEvent.button = OF_MOUSE_BUTTON_RIGHT;
					mouseEvent.type = ofMouseEventArgs::Released;
					mb.mouseButtonState &= ~MOUSE_BUTTON_RIGHT_MASK;
					pushMouseEvent = true;
				} else if(ev.value == 1) { // press
					mb.mouseButtonState |= MOUSE_BUTTON_RIGHT_MASK;
					mouseEvent.type = ofMouseEventArgs::Pressed;
					mouseEvent.button = OF_MOUSE_BUTTON_RIGHT;
					pushMouseEvent = true;
				} else {
					ofLogNotice("ofAppEGLWindow") << "readMouseEvents(): EV_KEY : unknown ev.value = " << ev.value;
				}
			} else {
				ofLogNotice("ofAppEGLWindow") << "readMouseEvents(): EV_KEY : unknown ev.code = " << ev.code;
			}
			// not sure why we are getting that event here
		} else if(ev.type == EV_MSC) {
			// EV_MSC events are used for input and output events that
			// do not fall under other categories.
			// ofLogVerbose("ofAppEGLWindow") << "readMouseEvents() : EV_MSC";
		} else if(ev.type == EV_SYN ) {
			// EV_SYN Used as markers to separate events. Events may be
			// separated in time or in space, such as with the multitouch protocol.

			// EV_SYN events are sent when axis value (one or a pair) are changed
			if(axisValuePending) {
				// TODO, this state doesn't make as much sense when the mouse is not dragging
				if(mb.mouseButtonState > 0) {
					// dragging (what if dragging w/ more than one button?)
					mouseEvent.type = ofMouseEventArgs::Dragged;
				} else {
					// just moving
					mouseEvent.type = ofMouseEventArgs::Moved;
				}

				mouseEvent.button = mb.mouseButtonState;

				pushMouseEvent = true;
				axisValuePending = false;
			}

			//ofLogVerbose("ofAppEGLWindow") << "readMouseEvents(): EV_SYN";
		} else {
			// unhandled type
		}

		// do we have a mouse event to push?
		if(pushMouseEvent){
			// lock the thread for a moment while we copy the data
			lock();
			mouseEvents.push(mouseEvent);
			unlock();
			pushMouseEvent = false;
		}

		// Why another read?
		nBytesRead = read(mice[i].fildes, &ev,sizeof(struct input_event));
	} // While loop
// ------------
	} // For loop

}

#ifdef TARGET_RASPBERRY_PI
//------------------------------------------------------------
void ofAppEGLWindow::initRPiNative() {
	bcm_host_init();

	memset(&dispman_native_window, 0x0, sizeof(EGL_DISPMANX_WINDOW_T));
	dispman_element = DISPMANX_NO_HANDLE;
	dispman_display = DISPMANX_NO_HANDLE;
	dispman_update  = DISPMANX_NO_HANDLE;
	memset(&dispman_clamp, 0x0, sizeof(DISPMANX_CLAMP_T));
	dispman_transform = DISPMANX_NO_ROTATE;
	memset(&dispman_alpha, 0x0, sizeof(VC_DISPMANX_ALPHA_T)); // zero dispman_alpha

}

//------------------------------------------------------------
void ofAppEGLWindow::exitRPiNative() {
	bcm_host_deinit();
}

//------------------------------------------------------------
bool ofAppEGLWindow::createRPiNativeWindow(const ofRectangle& requestedWindowRect){

	ofRectangle screenRect = getScreenRect();

	// make sure our requested window rectangle does not exceed the native
	// screen size, or start outside of it.
	ofRectangle windowRect = screenRect.getIntersection(requestedWindowRect);

	ofLogNotice("ofAppEGLWindow") << "setupRPiNativeWindow(): screenRect: " << screenRect.width << "x" << screenRect.height;
	ofLogNotice("ofAppEGLWindow") << "setupRPiNativeWindow(): windowRect: " << windowRect.width << "x" << windowRect.height;

	//////////////////////////
	VC_RECT_T dst_rect;

	dst_rect.x = (int32_t)windowRect.x;
	dst_rect.y = (int32_t)windowRect.y;
	dst_rect.width = (int32_t)windowRect.width;
	dst_rect.height = (int32_t)windowRect.height;

	VC_RECT_T src_rect;

	src_rect.x = 0;
	src_rect.y = 0;
	src_rect.width  = dst_rect.width << 16;
	src_rect.height = dst_rect.height << 16;

	memset(&dispman_alpha, 0x0, sizeof(VC_DISPMANX_ALPHA_T)); // zero dispman_alpha
	dispman_alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS;
	dispman_alpha.opacity = ofClamp(settings.eglWindowOpacity,0,255);
	dispman_alpha.mask = 0;

	memset(&dispman_clamp, 0x0, sizeof(DISPMANX_CLAMP_T));

	// there are other values for dispman_transform, but they do not seem to have an effect
	dispman_transform = DISPMANX_NO_ROTATE;

	// get the zero display
	dispman_display = vc_dispmanx_display_open(settings.screenNum);

	// begin the display manager interaction
	dispman_update  = vc_dispmanx_update_start( 0 );

	// add a "display manager element" with our parameters so
	// that it can fill in the structures.  we will pass this
	// filled dispman_element to our native window, which will
	// be used to construct the EGL surface, etc.
	dispman_element = vc_dispmanx_element_add ( dispman_update,
			dispman_display,
			settings.layer, // layer
			&dst_rect, // dst rect
			(DISPMANX_RESOURCE_HANDLE_T)0, // src
			&src_rect, // src rect
			DISPMANX_PROTECTION_NONE, // ?
			&dispman_alpha, // alpha
			&dispman_clamp, // clamp
			dispman_transform // transform
	);

	if(dispman_element == DISPMANX_NO_HANDLE) {
		ofLogError("ofAppEGLWindow") << "setupRPiNativeWindow(): dispman_element == DISPMANX_NO_HANDLE";
		return false;
	} else if(dispman_element == (unsigned)DISPMANX_INVALID) {
		ofLogError("ofAppEGLWindow") << "setupRPiNativeWindow(): dispman_element == DISPMANX_INVALID";
		return false;
	}

	// set dispman_native_window to zero
	memset(&dispman_native_window, 0x0, sizeof(EGL_DISPMANX_WINDOW_T));
	dispman_native_window.element = dispman_element;
	dispman_native_window.width = (int32_t)windowRect.width;
	dispman_native_window.height = (int32_t)windowRect.height;

	// set background to black (not required)
	vc_dispmanx_display_set_background(dispman_update, dispman_display, 0x00, 0x00, 0x00);

	// finished with display manager update, so sync
	vc_dispmanx_update_submit_sync( dispman_update );

	currentWindowRect = windowRect;

	return true;
}
#endif

//------------------------------------------------------------
// X11 BELOW
//------------------------------------------------------------
bool ofAppEGLWindow::createX11NativeWindow(const ofRectangle& requestedWindowRect){

	// X11 variables
	x11Window = 0;
	x11Display = 0;
	x11ScreenNum = 0; // TODO: settings.screenNum?
	x11Screen = 0;
	XVisualInfo* x11Visual = 0; // TODO does this need to be deleted?
	Colormap x11Colormap = 0;

	/*
	Step 0 - Create a NativeWindowType that we can use it for OpenGL ES output
	 */
	Window sRootWindow;
	XSetWindowAttributes sWA;
	unsigned int ui32Mask;
	int i32Depth;

	//ofRectangle screenRect = getScreenRect();

	// make sure our requested window rectangle does not exceed the native
	// screen size, or start outside of it.
	ofRectangle windowRect = requestedWindowRect.getStandardized();//screenRect.getIntersection(requestedWindowRect);

	// Initializes the display and screen
	x11Display = XOpenDisplay( 0 );
	if (!x11Display) {
		ofLogError("ofAppEGLWindow") << "unable to open X display";
		return false;
	}

	x11ScreenNum = XDefaultScreen( x11Display );

	x11Screen = XDefaultScreenOfDisplay(x11Display);

	// Gets the window parameters
	sRootWindow = RootWindow(x11Display, x11ScreenNum);
	i32Depth = DefaultDepth(x11Display, x11ScreenNum);
	x11Visual = new XVisualInfo();

	XMatchVisualInfo( x11Display,
			x11ScreenNum,
			i32Depth,
			TrueColor,
			x11Visual);

	if (!x11Visual) {
		ofLogError("ofAppEGLWindow") << "unable to acquire XVisualInfo";
		return false;
	}

	x11Colormap = XCreateColormap( x11Display, sRootWindow, x11Visual->visual, AllocNone );

	delete x11Visual;

	// set the colormap window attribuet
	sWA.colormap = x11Colormap;

	// Add to these for handling other events
	sWA.event_mask = 0;
	sWA.event_mask |= StructureNotifyMask;
	sWA.event_mask |= ExposureMask;
	sWA.event_mask |= ButtonPressMask;
	sWA.event_mask |= ButtonReleaseMask;
	sWA.event_mask |= PointerMotionMask;
	sWA.event_mask |= KeyPressMask;
	sWA.event_mask |= KeyReleaseMask;

	// setup background pixel attributes
	ui32Mask = 0;
	ui32Mask |= CWBackPixel;
	ui32Mask |= CWBorderPixel;
	ui32Mask |= CWEventMask;
	ui32Mask |= CWColormap;

	// Creates the X11 window
	x11Window = XCreateWindow(x11Display, // Specifies the connection to the X server.
			sRootWindow, // Specifies the parent window.
			(int)windowRect.x, (int)windowRect.y, // Specify the x and y coordinates,
			// which are the top-left outside corner
			// of the window's borders and are relative
			// to the inside of the parent window's borders.
			(unsigned int)windowRect.width, (unsigned int)windowRect.height, // Specify the width and height, which are the
			// created window's inside dimensions and do
			// not include the created window's borders.
			0, // Specifies the width of the created
			// window's border in pixels.
			CopyFromParent, // Specifies the window's depth.
			// A depth of CopyFromParent means
			// the depth is taken from the parent.
			InputOutput, // Specifies the created window's class.
			// You can pass InputOutput, InputOnly,
			// or CopyFromParent. A class of CopyFromParent
			// means the class is taken from the parent.
			CopyFromParent, // Specifies the visual type.
			// A visual of CopyFromParent means the visual type
			// is taken from the parent.
			ui32Mask, // Specifies which window attributes are
			// defined in the attributes argument. This mask is
			// the bitwise inclusive OR of the valid attribute
			// mask bits. If valuemask is zero, the attributes
			// are ignored and are not referenced.
			&sWA //Specifies the background pixel value of the window.
	);

	XMapWindow(x11Display, x11Window);
	XFlush(x11Display);

	// check success?
	currentWindowRect = windowRect;

	return true;
}

//------------------------------------------------------------
static KeySym KeyCodeToKeySym(Display * display, KeyCode keycode, unsigned int event_mask) {
	KeySym keysym = NoSymbol;

	//Get the map
	XkbDescPtr keyboard_map = XkbGetMap(display, XkbAllClientInfoMask, XkbUseCoreKbd);
	if (keyboard_map) {
		//What is diff between XkbKeyGroupInfo and XkbKeyNumGroups?
		unsigned char info = XkbKeyGroupInfo(keyboard_map, keycode);
		unsigned int num_groups = XkbKeyNumGroups(keyboard_map, keycode);

		//Get the group
		unsigned int group = 0x00;
		switch (XkbOutOfRangeGroupAction(info)) {
		case XkbRedirectIntoRange:
			/* If the RedirectIntoRange flag is set, the four least significant
			 * bits of the groups wrap control specify the index of a group to
			 * which all illegal groups correspond. If the specified group is
			 * also out of range, all illegal groups map to Group1.
			 */
			group = XkbOutOfRangeGroupInfo(info);
			if (group >= num_groups) {
				group = 0;
			}
			break;

		case XkbClampIntoRange:
			/* If the ClampIntoRange flag is set, out-of-range groups correspond
			 * to the nearest legal group. Effective groups larger than the
			 * highest supported group are mapped to the highest supported group;
			 * effective groups less than Group1 are mapped to Group1 . For
			 * example, a key with two groups of symbols uses Group2 type and
			 * symbols if the global effective group is either Group3 or Group4.
			 */
			group = num_groups - 1;
			break;

		case XkbWrapIntoRange:
			/* If neither flag is set, group is wrapped into range using integer
			 * modulus. For example, a key with two groups of symbols for which
			 * groups wrap uses Group1 symbols if the global effective group is
			 * Group3 or Group2 symbols if the global effective group is Group4.
			 */
		default:
			if (num_groups != 0) {
				group %= num_groups;
			}
			break;
		}

		XkbKeyTypePtr key_type = XkbKeyKeyType(keyboard_map, keycode, group);
		unsigned int active_mods = event_mask & key_type->mods.mask;

		int i, level = 0;
		for (i = 0; i < key_type->map_count; i++) {
			if (key_type->map[i].active && key_type->map[i].mods.mask == active_mods) {
				level = key_type->map[i].level;
			}
		}

		keysym = XkbKeySymEntry(keyboard_map, keycode, level, group);
		XkbFreeClientMap(keyboard_map, XkbAllClientInfoMask, true);
	}

	return keysym;
}

//------------------------------------------------------------
void ofAppEGLWindow::handleX11Event(const XEvent& event){
	ofMouseEventArgs mouseEvent;
	ofKeyEventArgs keyEvent;
	switch (event.type){
	case KeyPress:
	case KeyRelease:
	{
		KeySym key = KeyCodeToKeySym(instance->x11Display,event.xkey.keycode,event.xkey.state);
		keyEvent.key = key;
		if (event.type == KeyPress) {
			keyEvent.type = ofKeyEventArgs::Pressed;
			if(key == 65307){
				keyEvent.key = OF_KEY_ESC;
			}
		} else if (event.type == KeyRelease){
			keyEvent.type = ofKeyEventArgs::Released;
		}

		instance->coreEvents.notifyKeyEvent(keyEvent);
	}
	break;
	case ButtonPress:
	case ButtonRelease:
		mouseEvent.x = static_cast<float>(event.xbutton.x);
		mouseEvent.y = static_cast<float>(event.xbutton.y);
		mouseEvent.button = event.xbutton.button;
		if (event.type == ButtonPress){
			mouseEvent.type = ofMouseEventArgs::Pressed;
		} else {
			mouseEvent.type = ofMouseEventArgs::Released;
		}

		instance->coreEvents.notifyMouseEvent(mouseEvent);
		break;
	case MotionNotify:
		//cout << "motion notify" << endl;
		mouseEvent.x = static_cast<float>(event.xmotion.x);
		mouseEvent.y = static_cast<float>(event.xmotion.y);
		mouseEvent.button = event.xbutton.button;
		if(ofGetMousePressed()) {
			mouseEvent.type = ofMouseEventArgs::Dragged;
		} else {
			mouseEvent.type = ofMouseEventArgs::Moved;
		}

		instance->coreEvents.notifyMouseEvent(mouseEvent);
		break;
	case ConfigureNotify:
		instance->currentWindowRect.x = event.xconfigure.x;
		instance->currentWindowRect.y = event.xconfigure.y;
		instance->currentWindowRect.width = event.xconfigure.width;
		instance->currentWindowRect.height = event.xconfigure.height;
		instance->nonFullscreenWindowRect = instance->currentWindowRect;
		instance->coreEvents.notifyWindowResized(event.xconfigure.width,event.xconfigure.height);
		break;
	/*case ClientMessage:{
		if (event.xclient.message_type == wmProtocols_ &&
		event.xclient.format == 32 &&
		event.xclient.data.l[0] == (long) wmDeleteWindow_)
		{
		if (listener())
		{
		  if (listener()->onClose(wrapper() ? *wrapper() : *(WindowInterface*)this))
			isShuttingDown_ = true;
		}
		else
		{
		  isShuttingDown_ = true;
		}
		}
		break;
	  }*/
	}
}