/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

/**
 * @file    src/gwin/gwin_keyboard.c
 * @brief   GWIN sub-system virtual keyboard code
 */

#include "../../gfx.h"

#if GFX_USE_GWIN && GWIN_NEED_KEYBOARD

#include "gwin_class.h"
#include "gwin_keyboard_layout.h"

typedef uint8_t		utf8;
typedef uint16_t	utf16;
typedef uint32_t	utf32;

// A character code - note this is not UTF-32 but a representation of the UTF-8 code stream for a single character.
typedef uint32_t	ucode;

static GSourceHandle	AllKeyboards;

// Get the length of a UTF-8 string
static int UTF8StrLen(const utf8 *s) {
	int				len;

	len = 0;
	if (s) {
		while (*s) {
			len++;
			if (!(s[0] & 0x80))
				s++;
			else if ((s[0] & 0xE0) == 0xC0 && (s[1] & 0xC0) == 0x80)
				s+=2;
			else if ((s[0] & 0xF0) == 0xE0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80)
				s+=3;
			else if ((s[0] & 0xF8) == 0xF0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80)
				s+=4;
			else
				// Invalid UTF-8 sequence - assume a single byte
				s++;
		}
	}
	return len;
}

// Return the nth character of a UTF8 string
static ucode UTF8CharAt(const utf8 *s, int n) {
	ucode		u;

	u = 0;
	if (!s) return 0;

	while(*s) {
		if (!(s[0] & 0x80)) {
			u = s[0];
			s++;
		} else if ((s[0] & 0xE0) == 0xC0 && (s[1] & 0xC0) == 0x80) {
			u = s[1] | ((ucode)s[0] << 8);
			s+=2;
		} else if ((s[0] & 0xF0) == 0xE0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) {
			u = s[2] | ((ucode)s[1] << 8) | ((ucode)s[0] << 16);
			s+=3;
		} else if ((s[0] & 0xF8) == 0xF0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) {
			u = s[3] | ((ucode)s[2] << 8) | ((ucode)s[1] << 16) | ((ucode)s[0] << 24);
			s+=4;
		} else {
			// Invalid UTF-8 sequence - assume a single byte
			u = s[0];
			s++;
		}
		if (--n < 0)
			return u;
	}
	return 0;
}

// Convert a ucode to a UTF8 string (with no NULL on the end). Returns the number of bytes.
static unsigned UCode2UTF8(utf8 *dst, ucode u) {
	if (!(u & 0xFFFFFF00)) {
		dst[0] = u;
		return 1;
	}
	if (!(u & 0xFFFF0000)) {
		dst[0] = u >> 8;
		dst[1] = u;
		return 2;
	}
	if (!(u & 0xFF000000)) {
		dst[0] = u >> 16;
		dst[1] = u >> 8;
		dst[2] = u;
		return 3;
	}
	dst[0] = u >> 24;
	dst[1] = u >> 16;
	dst[2] = u >> 8;
	dst[3] = u;
	return 4;
}

static int NumKeyRows(const char **keyset) {
	int				len;

	len = 0;
	while(*keyset++)
		len++;
	return len;
}

static void SendVirtualKeyEventToListener(GSourceListener	*psl, GKeyboardObject *gk) {
	GEventKeyboard		*pe;
	const GVSpecialKey	*skey;
	unsigned			i;

	// If there is no event buffer just mark a missed event
	if (!(pe = (GEventKeyboard *)geventGetEventBuffer(psl))) {
		// This listener is missing - save the meta events that have happened
		psl->srcflags |= GKEYSTATE_MISSED_EVENT;
		return;
	}

	// The virtual keyboard can't generate repeats
	//if ((psl->listenflags & GLISTEN_KEYREPEATSOFF) && (k->keystate & GKEYSTATE_REPEAT))
	//	return;

	// The virtual keyboard can't generate special keys
	//if ((psl->listenflags & GLISTEN_KEYNOSPECIALS) && (k->keystate & GKEYSTATE_SPECIAL))
	//	return;

	// The virtual keyboard treats a key release as a keydown
	//if (!(psl->listenflags & GLISTEN_KEYUP) && (k->keystate & GKEYSTATE_KEYUP))
	//	k->cntc = 0;

	// The virtual keyboard has no transitions
	//if (!(psl->listenflags & GLISTEN_KEYTRANSITIONS) && !k->cntc)
	//	return;

	pe->type = GEVENT_KEYBOARD;
	if (gk->key < 0x20) {
		skey = &gk->keytable->skeys[gk->key-1];
		for(i=0; skey->sendkey[i]; i++)
			pe->c[i] = skey->sendkey[i];
	} else
		i = UCode2UTF8((utf8 *)pe->c, gk->key);
	pe->bytecount = i;
	for(; i < 8; i++)
		pe->c[i] = 0;
	pe->keystate = psl->srcflags;
	psl->srcflags = 0;
	geventSendEvent(psl);
}

static void SendVirtualKeyEvent(GKeyboardObject *gk) {
	GSourceListener	*psl;

	// Send to the "All Keyboards" source listeners
	psl = 0;
	while ((psl = geventGetSourceListener(AllKeyboards, psl)))
		SendVirtualKeyEventToListener(psl, gk);

	// Send to the keyboard specific source listeners
	psl = 0;
	while ((psl = geventGetSourceListener((GSourceHandle)gk, psl)))
		SendVirtualKeyEventToListener(psl, gk);
}


#if GINPUT_NEED_MOUSE
	// Find the key from the keyset and the x, y position
	static void KeyFindKey(GKeyboardObject *gk, coord_t x, coord_t y) {
		const utf8		*krow;
		fixed			f;
		int				idx;

		if (x < 0 || y < 0 || x >= gk->w.g.width || y >= gk->w.g.height) {
			gk->keyrow = gk->keycol = GKEY_BAD_ROWCOL;
			return;
		}

		// Get the y parameters
		f = FIXED(gk->w.g.height) / NumKeyRows(gk->keyset);
		gk->keyrow = FIXED(y) / f;
		gk->keyy = NONFIXED(f * gk->keyrow + FIXED0_5);
		gk->keycy = NONFIXED(f * (gk->keyrow+1) + FIXED0_5) - gk->keyy;

		// Get the current row
		krow = (const utf8 *)gk->keyset[gk->keyrow];

		// Get the x parameters
		f = FIXED(gk->w.g.width) / UTF8StrLen(krow);
		gk->keycol = FIXED(x) / f;

		// Get the key
		gk->key = UTF8CharAt(krow, gk->keycol);

		// Amalgamate identical keys into one big key
		idx = gk->keycol;
		while(gk->keycol > 0 && UTF8CharAt(krow, gk->keycol-1) == gk->key)
			gk->keycol--;
		while(UTF8CharAt(krow, ++idx) == gk->key);
		gk->keyx = NONFIXED(f * gk->keycol + FIXED0_5);
		gk->keycx = NONFIXED(f * idx + FIXED0_5) - gk->keyx;
	}

	// A mouse up has occurred (it may or may not be over the button)
	static void KeyMouseUp(GWidgetObject *gw, coord_t x, coord_t y) {
		#define gk		((GKeyboardObject *)gw)

		KeyFindKey(gk, x, y);

		// Do we have a valid key?
		if (gk->keyrow == GKEY_BAD_ROWCOL) {
			if (gk->lastkeyrow != GKEY_BAD_ROWCOL) {
				gw->g.flags |= GKEYBOARD_FLG_QUICKUPDATE;
				_gwinUpdate((GHandle)gw);
			}
			return;
		}

		// We are turning off the display of the key
		gk->keyrow = gk->keycol = GKEY_BAD_ROWCOL;

		// Is this one of the special keys
		if (gk->key < 0x20) {
			// This is a special key
			const GVSpecialKey	*skey;

			skey = &gk->keytable->skeys[gk->key - 1];

			if ((skey->flags & GVKEY_SINGLESET)) {
				// Single character switch to a new layout
				gk->keyset = gk->keytable->ksets[skey->newset];
				gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);
				gk->w.g.flags |= GKEYBOARD_FLG_REVERTSET;

			} else if ((skey->flags & GVKEY_LOCKSET)) {
				// Locked switch to a new layout
				gk->keyset = gk->keytable->ksets[skey->newset];
				gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);

			} else if ((gk->w.g.flags & GKEYBOARD_FLG_REVERTSET)) {
				// Revert to default layout
				gk->keyset = gk->keytable->ksets[0];
				gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);

			} else {
				// Just turning off a key
				gw->g.flags |= GKEYBOARD_FLG_QUICKUPDATE;
			}

			// Send the key if required
			if (skey->sendkey && skey->sendkey[0])
				SendVirtualKeyEvent(gk);

			// Update the display
			_gwinUpdate((GHandle)gw);

			return;
		}

		// Do we need to revert to the standard layout?
		if ((gk->w.g.flags & GKEYBOARD_FLG_REVERTSET)) {
			gk->keyset = gk->keytable->ksets[0];
			gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);
		} else {
			gw->g.flags |= GKEYBOARD_FLG_QUICKUPDATE;
		}

		// Send the key
		SendVirtualKeyEvent(gk);

		// Update the display
		_gwinUpdate((GHandle)gw);
	}

	// A mouse move has occurred (it may or may not be over the button)
	static void KeyMouseMove(GWidgetObject *gw, coord_t x, coord_t y) {
		#define gk		((GKeyboardObject *)gw)

		KeyFindKey(gk, x, y);

		if (gk->keyrow != gk->lastkeyrow || gk->keycol != gk->lastkeycol) {
			gk->w.g.flags |= GKEYBOARD_FLG_QUICKUPDATE;
			_gwinUpdate((GHandle)gw);
		}
		#undef gk
	}
#endif

extern const GVKeyTable GWIN_KEYBOARD_DEFAULT_LAYOUT;

// The button VMT table
static const gwidgetVMT keyboardVMT = {
	{
		"VKeyboard",				// The classname
		sizeof(GKeyboardObject),	// The object size
		_gwidgetDestroy,			// The destroy routine
		_gwidgetRedraw,				// The redraw routine
		0,							// The after-clear routine
	},
	gwinKeyboardDraw_Normal,		// The default drawing routine
	#if GINPUT_NEED_MOUSE
		{
			KeyMouseMove,			// Process mouse down events
			KeyMouseUp,				// Process mouse up events
			KeyMouseMove,			// Process mouse move events
		},
	#endif
	#if GINPUT_NEED_KEYBOARD || GWIN_NEED_KEYBOARD
		{
			0						// Process keyboard events
		},
	#endif
	#if GINPUT_NEED_TOGGLE
		{
			0,						// No toggle roles
			0,						// Assign Toggles
			0,						// Get Toggles
			0,						// Process toggle off events
			0,						// Process toggle on events
		},
	#endif
	#if GINPUT_NEED_DIAL
		{
			0,						// No dial roles
			0,						// Assign Dials (NOT USED)
			0,						// Get Dials (NOT USED)
			0,						// Process dial move events (NOT USED)
		},
	#endif
};

GHandle gwinGKeyboardCreate(GDisplay *g, GKeyboardObject *gk, const GWidgetInit *pInit) {
	if (!(gk = (GKeyboardObject *)_gwidgetCreate(g, &gk->w, pInit, &keyboardVMT)))
		return 0;

	gk->keytable = &GWIN_KEYBOARD_DEFAULT_LAYOUT;
	gk->keyset = gk->keytable->ksets[0];
	gk->lastkeyrow = gk->lastkeycol = gk->keyrow = gk->keycol = GKEY_BAD_ROWCOL;

	if (!AllKeyboards)
		AllKeyboards = ginputGetKeyboard(GKEYBOARD_ALL_INSTANCES);

	gwinSetVisible((GHandle)gk, pInit->g.show);
	return (GHandle)gk;
}

GSourceHandle gwinKeyboardGetEventSource(GHandle gh) {
	if (gh->vmt != (gwinVMT *)&keyboardVMT)
		return 0;
	return (GSourceHandle)gh;
}

void gwinKeyboardSetLayout(GHandle gh, const struct GVKeyTable *layout) {
	#define gk		((GKeyboardObject *)gh)

	if (gh->vmt != (gwinVMT *)&keyboardVMT)
		return;

	if (!layout)
		layout = &GWIN_KEYBOARD_DEFAULT_LAYOUT;
	gk->keytable = layout;
	gk->keyset = gk->keytable->ksets[0];
	gk->lastkeyrow = gk->lastkeycol = gk->keyrow = gk->keycol = GKEY_BAD_ROWCOL;
	gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);
	gwinRedraw(gh);
	#undef gk
}

/*----------------------------------------------------------
 * Custom Draw Routines
 *----------------------------------------------------------*/

/*
static const GColorSet *getDrawColors(GWidgetObject *gw) {
	if (!(gw->g.flags & GWIN_FLG_SYSENABLED))	return &gw->pstyle->disabled;
	if ((gw->g.flags & GBUTTON_FLG_PRESSED))	return &gw->pstyle->pressed;
	return &gw->pstyle->enabled;
}
*/

void gwinKeyboardDraw_Normal(GWidgetObject *gw, void *param) {
	#define gk ((GKeyboardObject *)gw)

	char cap[5];
	const char *pcap;
	const utf8 *krow;
	coord_t x, y, cx, cy;
	uint8_t rows, cols, row, col, kcols;
	ucode key;
	fixed fx, fy;
	const GColorSet *pcol;

	(void) param;

	// Make sure that this is a keyboard widget object
	if (gw->g.vmt != (gwinVMT *)&keyboardVMT)
		return;

	// Get the y parameters
	rows = NumKeyRows(gk->keyset);
	fy = FIXED(gk->w.g.height) / rows;
	for (row = 0; row < rows; row++) {
		y = NONFIXED(fy * row + FIXED0_5);
		cy = NONFIXED(fy * (row+1) + FIXED0_5) - y;

		// Get the current row
		krow = (const utf8 *)gk->keyset[row];

		// Get the x parameters
		cols = UTF8StrLen(krow);
		fx = FIXED(gk->w.g.width) / cols;
		for (col = 0; col < cols; col=kcols) {

			// Get the correct color set
			if (!(gk->w.g.flags & GWIN_FLG_SYSENABLED))
					pcol = &gk->w.pstyle->disabled;
			else 
				pcol = &gk->w.pstyle->enabled;
        	 
			// Get the key
			key = UTF8CharAt(krow, col);
	
			// Fuse identical keys into one big key
			kcols = col+1;
			while (UTF8CharAt(krow, kcols) == key)
				kcols++;
        	 
			// If quick update needed and keyboard already drawn (if not use this flag, then bug when screen touched before keyboard was drawn)
			if ( (gk->w.g.flags & GKEYBOARD_FLG_QUICKUPDATE) && !(gk->w.g.flags & GWIN_FLG_BGREDRAW) )  {

				// If key pressed
				if ( (gk->keyrow != GKEY_BAD_ROWCOL) && (gk->keycol != GKEY_BAD_ROWCOL) ) {

					// And previous key have
					if ( (gk->lastkeyrow != GKEY_BAD_ROWCOL) && (gk->lastkeycol != GKEY_BAD_ROWCOL) ) {
						
						if (gk->lastkeyrow == row && gk->lastkeycol == col) {
							// If keyboard has no "disabled" color
							if (pcol != &gk->w.pstyle->disabled)
								pcol = &gk->w.pstyle->enabled;
							gk->lastkeyrow = gk->lastkeycol = GKEY_BAD_ROWCOL;
						} else {
							continue;
						}
					}

					// If no previous key
					else {

						if (gk->keyrow == row && gk->keycol == col) {
							if (pcol != &gk->w.pstyle->disabled)
								pcol = &gk->w.pstyle->pressed;
							gk->lastkeyrow = row;
							gk->lastkeycol = col;
						}
						else if (gk->lastkeyrow == row && gk->lastkeycol == col)
						{
							if (pcol != &gk->w.pstyle->disabled) pcol = &gk->w.pstyle->enabled;
						}
						else continue;
					}
				}

				// If key up, and need clear the previous key
				else if ( (gk->lastkeyrow != GKEY_BAD_ROWCOL) && (gk->lastkeycol != GKEY_BAD_ROWCOL) )
				{
					if ( (gk->lastkeyrow == row) && (gk->lastkeycol == col) )
					{
						if (pcol != &gk->w.pstyle->disabled) pcol = &gk->w.pstyle->enabled;
					}
					else continue;
				}
			}                  
			else
			{
				gk->lastkeyrow = gk->lastkeycol = GKEY_BAD_ROWCOL;
			}

			x = NONFIXED(fx * col + FIXED0_5);
			cx = NONFIXED(fx * kcols + FIXED0_5) - x;
			
			if (key < 0x20) {
				pcap = gk->keytable->skeys[key-1].keycap;
			} else {
				cap[UCode2UTF8((utf8 *)cap, key)] = 0;
				pcap = cap;
			}
			
			switch(*pcap) {

			case  '\001':	// Shift (up-arrow)
				gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);

				gdispGDrawLine(gw->g.display, gw->g.x+x    +cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/4, pcol->text);               /*    / \    */
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx -cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/4, pcol->text); 
				gdispGDrawLine(gw->g.display, gw->g.x+x    +cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy/2, pcol->text);           /*    _ _    */
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx -cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy/2, pcol->text);
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy/2, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy -cy/3, pcol->text);      /*    ||     */
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy/2, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy -cy/3, pcol->text);
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy -cy/3, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy -cy/3, pcol->text);  /*    _      */

				break;

			case '\002':	// Shift locked (underlined up-arrow)
				gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);

				gdispGDrawLine(gw->g.display, gw->g.x+x    +cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/4, pcol->text);               /*   / \     */
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx -cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/4, pcol->text);    
				gdispGDrawLine(gw->g.display, gw->g.x+x    +cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy/2, pcol->text);           /*   _ _     */
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx -cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy/2, pcol->text);
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy/2, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy -cy/3, pcol->text);      /*    ||     */
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy/2, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy -cy/3, pcol->text);
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy -cy/3, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy -cy/3, pcol->text);  /*     _     */
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/5, gw->g.y+y+cy -cy/4, gw->g.x+x+cx/2+cx/5, gw->g.y+y+cy -cy/4, pcol->text);  /*    ___    */

				break;

			case '\t':	// Tabulator
				gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);

				gdispGDrawLine(gw->g.display, gw->g.x+x+1, gw->g.y+y+1, gw->g.x+x+cx-1, gw->g.y+y+cy/2, pcol->text);
				gdispGDrawLine(gw->g.display, gw->g.x+x+1, gw->g.y+y+cy-1, gw->g.x+x+cx-1, gw->g.y+y+cy/2, pcol->text);
				gdispGDrawLine(gw->g.display, gw->g.x+x+cx-1, gw->g.y+y+1, gw->g.x+x+cx-1, gw->g.y+y+cy-1, pcol->text);

				break;

			case '\b': // Backspace
				gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);

				gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/8, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y    +cy/3, pcol->text);               /* /      */
				gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/8, gw->g.y+y+cy/2, gw->g.x+x+cx-cx/8, gw->g.y+y+cy/2, pcol->text);                /*  --    */
				gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/8, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y+cy -cy/3, pcol->text);               /* \      */

				break;

			case '\r': // Enter
				gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);

				gdispGDrawLine(gw->g.display, gw->g.x+x+(cx/3)*2, gw->g.y+y+cy/2, gw->g.x+x+(cx/3)*2, gw->g.y+y+cy/5, pcol->text);            /*      | */
				gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/3, gw->g.y+y+cy/2, gw->g.x+x+cx/3 +cx/8, gw->g.y+y+cy/3, pcol->text);             /* /      */
				gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/3, gw->g.y+y+cy/2, gw->g.x+x+(cx/3)*2, gw->g.y+y+cy/2, pcol->text);               /*  --    */
				gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/3, gw->g.y+y+cy/2, gw->g.x+x+cx/3 +cx/8, gw->g.y+y+cy -cy/3, pcol->text);         /* \      */

				break;

			default:   // Regular character
				gdispGFillStringBox(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcap, gw->g.font, pcol->text, pcol->fill, justifyCenter);
				
				break;
			}
			
			// Draw the frame (border around the entire widget)
			gdispGDrawBox(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->edge);
			
			// If key up and we already cleared the previous key
			if ( (gk->keyrow == GKEY_BAD_ROWCOL) && (gk->keycol == GKEY_BAD_ROWCOL) && (gk->lastkeyrow == row) && (gk->lastkeycol == col) ) {
			   gk->lastkeyrow = gk->lastkeycol = GKEY_BAD_ROWCOL;
			   return;
			}

			// Just quit the cycle if we did all the work in order not to waste any CPU time
			if ( (row >= gk->keyrow && col >= gk->keycol) && (row >= gk->lastkeyrow && col >= gk->lastkeycol) ) {
				return;
			}
		}
	}

	#undef gk
}

#if !(GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD)
	GSourceHandle ginputGetKeyboard(unsigned instance) {
		if (instance == GKEYBOARD_ALL_INSTANCES)
			return (GSourceHandle)&AllKeyboards;
		return 0;
	}
#endif

#endif /* GFX_USE_GWIN && GWIN_NEED_KEYBOARD */