From 1966fcd11290656185a428ac41e5205c3f29d9e9 Mon Sep 17 00:00:00 2001
From: knolax <1339802534.kk@gmail.com>
Date: Fri, 29 Dec 2017 15:42:27 -0500
Subject: added full functionality to the module. it now sends the correct
 keypresses to the input subsystem. As a reminder, i'm using git to upload
 code to the production system so this code hasn't been tested yet.

---
 skey.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 233 insertions(+), 17 deletions(-)

diff --git a/skey.c b/skey.c
index 917bf6c..55c86b4 100644
--- a/skey.c
+++ b/skey.c
@@ -50,33 +50,250 @@ static struct task_struct *update_task;
  */
 struct input_dev * skey_dev;
 /*
- * Name and ID
+ * Name and ID, for use with input.h
  */
 struct input_id skey_id;
 char * skey_name = "Switch Keyboard for the Omnicom";
 /*
+ * Array to hold the keystate for the board
+ * a char is assumed to be atleast 8 bits, the bit KEYSTATE_PHYS holds the previous physical
+ * states for detecting rising and falling edges whereas the other ones hold the previous states
+ * of the 3 keymaps when stickymode is turned on.
+ */
+char keystate[3][10]= {0};
+#define KEYSTATE_PHYS 0x1
+#define KEYSTATE_ALPHA 0x2
+#define KEYSTATE_NUMBER 0x4
+#define KEYSTATE_SPECIAL 0x8
+/*
+ * current keymapping,\
+ * 0 - ALPHA
+ * 1 - NUM
+ * 2 - SPECIAL
+ * 3 - SPECIAL with mouse
+ */
+int current_keymap = 0;
+/*
+ * Stickymode 
+ */
+int stickymode = 0;
+//number of pixels mousemove buttons do
+#define MOUSE_DIST 5
+//same but for scrollwheel
+#define WHEEL_DIST 5
+/*
+ * clears the state of stickymode keystates, sends a key up event if it was previously pressed
+ * 
+ */
+void clearkeystate(void) {
+	int row_index = 0;
+	int column_index = 0;
+	int map_index = 0; //range of 0 to 2, which are KEYSTATE_ALPHA to KEYSTATE_SPECIAL
+	int keystate_mask; // the actual map used
+	while (map_index < 3) {
+		//gets mask from map_index
+		switch(map_index) {
+			case 0:
+				keystate_mask = KEYSTATE_ALPHA;
+			break;
+			case 1:
+				keystate_mask = KEYSTATE_NUMBER;
+			break;
+			case 2:
+				keystate_mask = KEYSTATE_SPECIAL;
+			break;
+		}
+		row_index = 0;
+		while (row_index < 3) {
+			column_index = 0;
+			while (column_index < 10) {	
+				//sends a button released event if it is not sticky mode
+				if (keystate[row_index][column_index] & keystate_mask) {
+					//reports key up
+					input_report_key(skey_dev, skey_keymap[map_index][row_index][column_index], 0);
+					input_sync(skey_dev);
+					//sets the bit to low.
+					keystate[row_index][column_index] = (~keystate_mask) & keystate[row_index][column_index];
+				}
+				column_index++;
+			}
+			row_index++;
+		}
+		map_index++;
+	}
+	return;
+}
+/*
+ * The keystates are processed as following, the current read value is compared with the
+ * previous one stored in KEYSTATE_PHYS to detect a rising or falling edge. for regular keys, 
+ * the corresponding keycode from skey_keymap is sent, with the current_keymap. if stickymode 
+ * is on, then it toggles the value stored in KEYSTATE_ALPHA, *NUMBER or *SPECIAL depending on
+ * current_keymap. special keys that change the keymap and the mouse position are only triggered
+ * on rising level, when current_keymap is 3, or special map with mouse, the KEYSTATE_SPECIAL
+ * mask is used.
+ */
+/*
+ * This function is called by skey_update_thread every time it reads the keyboard, and
+ * implements the above. returns -EINVAL if the column and row are out of bouns, but that's
+ * not going to happen.
+ */
+int processkey(int row, int column, int state) {
+	//sanity checks
+	if ((column < 10) && (column >= 0) && (row < 3) && (row >= 0)) {
+	} else {
+		/*if this happens, whatever is calling this function is not expected to handle
+		 * this
+		 */
+		printk(KERN_ALERT "skey: keypress data sent to processkey() was invalid\n");
+		return -EINVAL;
+	}
+	//finds the KEYSTATE_* bitmask to use with the current keymapping
+	int keystate_bitmask;
+	switch (current_keymap) {
+		case 0:
+			keystate_bitmask = KEYSTATE_ALPHA;
+		break;
+		case 1:
+			keystate_bitmask = KEYSTATE_NUMBER;
+		break;
+		case 2:
+			keystate_bitmask = KEYSTATE_SPECIAL;
+		break;
+		case 3:
+			keystate_bitmask = KEYSTATE_SPECIAL;
+		break;
+		//if current_keymap is out of range [0,3]
+		default:
+			return -EINVAL;
+		break;
+	}
+
+	//falling edge
+	if ((KEYSTATE_PHYS & keystate[row][column]) && !state) {
+		//after the previous keystate is checked, it is updated
+		if (state) {
+			keystate[row][column] = KEYSTATE_PHYS | keystate[row][column];
+		} else {
+			keystate[row][column] = (~KEYSTATE_PHYS) & keystate[row][column];
+		}
+		
+		if (!stickymode) {
+			//reports key up
+			input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 0);
+			input_sync(skey_dev);
+
+		}
+	//rising edge
+	}else if (!(KEYSTATE_PHYS & keystate[row][column]) && state) {
+		//after the previous keystate is checked, it is updated
+		if (state) {
+			keystate[row][column] = KEYSTATE_PHYS | keystate[row][column];
+		} else {
+			keystate[row][column] = (~KEYSTATE_PHYS) & keystate[row][column];
+		}
+		/*
+		 *handles keys that have special behavior, and to which stickykey do not apply
+		 * if sticky keys don't apply then only the falling edge applies
+		 */
+		switch (skey_keymap[current_keymap][row][column]) {
+			case KEY_ALPHAMAP:
+				current_keymap = 0;
+				return 0;
+			break;
+			case KEY_NUMBERMAP:
+				current_keymap = 1;
+				return 0;
+			break;
+			case KEY_SPECIALMAP:
+				current_keymap = 2;
+				return 0;
+			break;
+			case KEY_STICKYMAP:
+				stickymode = !stickymode;
+				if (!stickymode) {
+					clearkeystate();
+				}
+				return 0;
+			break;
+			case KEY_MS_2ND:
+				if (current_keymap == 2) {
+					current_keymap = 3;
+				} else if (current_keymap == 3) {
+					current_keymap = 2;
+				}
+				return 0;
+			break;
+			case MS_UP:
+				input_event(skey_dev, EV_REL, REL_Y, +MOUSE_DIST);
+				input_sync(skey_dev);
+				return 0;
+			break;
+			case MS_DOWN:
+				input_event(skey_dev, EV_REL, REL_Y, -MOUSE_DIST);
+				input_sync(skey_dev);
+				return 0;
+			break;	
+			case MS_LEFT:
+				input_event(skey_dev, EV_REL, REL_X, -MOUSE_DIST);
+				input_sync(skey_dev);
+				return 0;
+			break;
+			case MS_RIGHT:
+				input_event(skey_dev, EV_REL, REL_X, +MOUSE_DIST);
+				input_sync(skey_dev);
+				return 0;
+			break;
+			case MS_SCRL_UP:
+				input_event(skey_dev, EV_REL, REL_WHEEL, +WHEEL_DIST);
+				input_sync(skey_dev);
+				return 0;
+			break;
+			case MS_SCRL_DOWN:
+				input_event(skey_dev, EV_REL, REL_WHEEL, -WHEEL_DIST);
+				input_sync(skey_dev);
+				return 0;
+			break;
+			}
+		//if it's stickymode, toggle the state  stored and send corresponding vent
+		if (stickymode) {
+			//it was high before, so now it's being toggled off
+			if (keystate[row][column] & keystate_bitmask) {
+				input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 0);
+				input_sync(skey_dev);
+
+				keystate[row][column] = (~keystate_bitmask) & keystate[row][column];
+			//it was low before, so now it's being toggled on
+			} else {
+				input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 1);
+				input_sync(skey_dev);
+				keystate[row][column] = keystate_bitmask | keystate[row][column];
+
+			}
+		//otherwise just send a key down event
+		} else {
+			input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 1);
+			input_sync(skey_dev);
+		}
+	}
+	return 0;
+}
+
+/*
  *This function is called every timer-period to actually read the keyboard
  */
 int skey_update_thread (void *data) {
 	printk(KERN_INFO "skey: starting update thread\n");
 	//thread stuff
+	//thread priority
 	const struct sched_param PARAM = { .sched_priority = 45};
+	//sets the thread to be kinda realtime
 	sched_setscheduler(current, SCHED_FIFO, &PARAM);
 	printk(KERN_INFO "skey: started update thread\n");
 	if (!USE_GPIO) {
 		printk(KERN_ALERT "skey: currently in test mode with gpio use disabled\n");
 	}
-	//a counter for testing prints
-	int i = 0;
-	int j = 0;
+
 	while (1) {
-		/*if (!i) {
-			printk(KERN_INFO "skey: update thread printed to kernel log every 1s\n");
-			//reports the a button pressed
-			input_report_key(skey_dev,KEY_CAPSLOCK,j);
-			input_sync(skey_dev);
-			j = !j;
-		}*/
 		if (USE_GPIO) {
 			//reads the keyboard
 			int row_index = 0;
@@ -90,9 +307,9 @@ int skey_update_thread (void *data) {
 				column_index = 0;
 				while (column_index < 10) {
 					if (gpio_get_value_cansleep(column_pins[column_index])) {
-						printk(KERN_INFO "skey: key pressed at column %d, row %d\n", column_index, row_index);
+						processkey(row_index, column_index, 1);
 					} else {
-						//printk(KERN_INFO "skey: key not pressed at column %d, row %d\n", column_index, row_index);
+						processkey(row_index, column_index, 0);
 					}
 					column_index++;
 				}
@@ -102,10 +319,9 @@ int skey_update_thread (void *data) {
 				row_index++;
 			}
 		}
-		//sleep 2000us= 2ms = 0.002 seconds
-		//usleep_range(2000, 2000);	
-		//sleeps = 1000000us = 1000ms = 1s
-		usleep_range(1000000,1000000);
+		//sleep 500us= .5ms = 0.0005 seconds
+		// 1 / 0.0005 = 2000 hz	fastest, but it's really 1/0.00052
+		usleep_range(500,500);
 		//for when the thread has to stop
 		if (kthread_should_stop()) {
 			 break;
-- 
cgit v1.1