ComputerCraft Archive

keypress

computer utility LDDestroier github

Description

Keypress API

Installation

Copy one of these commands into your ComputerCraft terminal:

wget:wget https://raw.githubusercontent.com/LDDestroier/CC/master/keypress.lua keypress
Archive:wget https://cc.shobie.xyz/cc/get/gh-LDDestroier-CC-keypress keypress
Quick Install: wget https://cc.shobie.xyz/cc/get/gh-LDDestroier-CC-keypress keypress

Usage

Run: keypress

Tags

none

Source

View Original Source

Code Preview

-- Keypress API
-- by LDDestroier
--
-- keypress event:
-- kp = {
--	key          : numerical keycode of key pressed
--	char         : printable character of keypress
--	               use this in place of "char" events, like for text input
--	char_pressed : character of keypress, not necessarily printed
--	               for instance, pressing "A" will return a char event, but
--	               pressing "CTRL+A" will not
--	notation     : vim-style notation of key event, like <C-PageUp>
--	               Modifier keys do not have notation on their own.
--	name         : vim-style notaton of EVERY key event, including modifier keys.
--	               if you want to match against every possible keystroke, use this.
--	time         : os.epoch() time of keypress
--	ctrl         : true/false if CTRL was held
--	alt          : true/false if ALT was held
--	shift        : true/false if SHIFT was held
--	paste        : if not nil, contains text that had been pasted
-- }

local keypress = {}

local _DEMO = false

if select(1, ...) == "demo" then
	_DEMO = true
end

local r_keys = {}
for k,v in pairs(keys) do
	r_keys[v] = k
end

local keys_down = {}
local last_epoch, last_key = 0, 0
local last_evt
local delta

keypress.keys_down = keys_down

local nonprintable_keys = {
	[keys.backspace] = true,
	[keys.leftCtrl] = true,
	[keys.rightCtrl] = true,
	[keys.leftAlt] = true,
	[keys.rightAlt] = true,
	[keys.leftShift] = true,
	[keys.rightShift] = true,
	[keys.capsLock] = true,
	[keys.enter] = true,
	[keys.insert] = true,
	[keys.delete] = true,
	[keys.home] = true,
	[keys["end"]] = true,
	[keys.pageDown] = true,
	[keys.pageUp] = true,
	[keys.numLock] = true,
	[keys.scrollLock] = true,
	[keys.numPadEnter] = true,
	[keys.up] = true,
	[keys.down] = true,
	[keys.left] = true,
	[keys.right] = true,
}
for i = 1, 15 do
	nonprintable_keys[keys["f" .. i]] = true
end

-- TODO: make these local variables as to not polute the keys table
keys.ctrl = 1001
keys.shift = 1002
keys.alt = 1003

for k,v in pairs(keys) do
	keys_down[k] = false
end

local function modifier_keydowns()
	keys_down[keys.ctrl]  = keys_down[keys.leftCtrl]  or keys_down[keys.rightCtrl]
	keys_down[keys.shift] = keys_down[keys.leftShift] or keys_down[keys.rightShift]
	keys_down[keys.alt]   = keys_down[keys.leftAlt]   or keys_down[keys.rightAlt]
end

local modifier_lookup = {
	[ keys.leftCtrl ] = "Ctrl",
	[ keys.rightCtrl ] = "Ctrl",
	[ keys.ctrl ] = "Ctrl",
	[ keys.leftShift ] = "Shift",
	[ keys.rightShift ] = "Shift",
	[ keys.shift ] = "Shift",
	[ keys.leftAlt ] = "Alt",
	[ keys.rightAlt ] = "Alt",
	[ keys.alt ] = "Alt",

	ctrl = {
		[ keys.leftCtrl ] = true,
		[ keys.rightCtrl ] = true,
		[ keys.ctrl ] = true,
	},

	shift = {
		[ keys.leftShift ] = true,
		[ keys.rightShift ] = true,
		[ keys.shift ] = true,
	},

	alt = {
		[ keys.leftAlt ] = true,
		[ keys.rightAlt ] = true,
		[ keys.alt ] = true,
	}
}

-- requires kp.notation to be populated to work right
local function get_keypress_name( kp )
	if kp.notation then
		return kp.notation
		
	elseif modifier_lookup[ kp.key ] then
		return "<" .. modifier_lookup[ kp.key ] .. ">"
	
	else
		return "<UnknownKey>"
	end
end

function keypress.resume(...)

	local evt = {...}
	local output = {}
	local paste_contents

	if evt[1] == "keypress" then
		if _DEMO then
			-- exit demo with CTRL-C
			if evt[2].key == keys.c and evt[2].ctrl then
				return "keypress_terminatedemo"

			else
				print("key  = keys." .. (r_keys[evt[2]  .key] or "???"))
				
				if evt[2].char then
					write("char = '" .. evt[2].char .. "'")
				else
					write("char = nil")
				end

				if evt[2].char_pressed then
					print(" ('" .. evt[2].char_pressed .. "')")
				else
					print("")
				end

				print("name = " .. (evt[2].name or "nil"))

				print("notation = " .. (evt[2].notation or "(NONE)"))
				write("mods = ")
				write(evt[2].ctrl and "ctrl " or "")
				write(evt[2].alt and "alt " or "")
				print(evt[2].shift and "shift" or "")
				
				if evt[2].paste then
					print("paste = " .. evt[2].paste)
				end

				print("")
			end
		end

		-- keypress events should die when fed back into keypress.resume()
		return

	elseif evt[1] == "paste" then
		-- assume a paste event means you pressed 'v'
		evt[1] = "key"
		paste_contents = evt[2]
		evt[2] = keys.v
		evt[3] = false
	end

	if evt[1] == "key" then
		keys_down[evt[2]] = true
		modifier_keydowns()

		if nonprintable_keys[evt[2]] or (keys_down[keys.ctrl] or keys_down[keys.alt]) then
			output[1] = "keypress"
			output[2] = {
				key = evt[2],
				char = nil, -- represents a printable character -- use this if you're using keypress API for text input
				char_pressed = nil, -- represents the character pressed regardless of if it should print
				time = os.epoch(),
				ctrl = keys_down[keys.ctrl],
				shift = keys_down[keys.shift],
				alt = keys_down[keys.alt],
				paste = paste_contents
			}

			output[2].notation = keypress.to_vim_notation(output[2])
			output[2].name = get_keypress_name(output[2])
		else
			last_epoch = os.epoch()
			last_key = evt[2]
		end

	elseif evt[1] == "key_up" then
		keys_down[evt[2]] = false
		modifier_keydowns()

	elseif evt[1] == "char" and last_evt == "key" then
		delta = os.epoch() - last_epoch
		if delta <= 90 then
			output[1] = "keypress"
			output[2] = {
				key = last_key,
				char = evt[2],
				char_pressed = evt[2],
				time = os.epoch(),
				ctrl = keys_down[keys.ctrl],
				shift = keys_down[keys.shift],
				alt = keys_down[keys.alt],
				paste = paste_contents
			}

			output[2].notation = keypress.to_vim_notation(output[2])
			output[2].name = get_keypress_name(output[2])
		end

	else
		last_key = nil
	end

	if #output == 0 then
		output = evt
	end

	last_evt = evt[1]

	return table.unpack(output)
end

-- convert some key codes to characters
local keys_printable_lookup = {
	[ keys.one ]		= "1",
	[ keys.two ]		= "2",
	[ keys.three ]		= "3",
	[ keys.four ]		= "4",
	[ keys.five ]		= "5",
	[ keys.six ]		= "6",
	[ keys.seven ]		= "7",
	[ keys.eight ]		= "8",
	[ keys.nine ]		= "9",
	[ keys.zero ]		= "0",
	[ keys.grave ]		= "`",
	[ keys.equals ]		= "=",
	[ keys.minus ]		= "-",
	[ keys.underscore ]	= "_",
	[ keys.leftBracket ]	= "[",
	[ keys.rightBracket ]	= "]",
	[ keys.apostrophe ]	= "'",
	[ keys.colon ]		= ":",
	[ keys.semiColon ]	= ";",
	[ keys.period ]		= ".",
	[ keys.comma ]		= ",",
	[ keys.slash ]		= "/",
	[ keys.backslash ]	= "\\",
}

local alphabet = "abcdefghijklmnopqrstuvwxyz"
for i = 1, #alphabet do
	keys_printable_lookup[ keys[alphabet:sub(i, i)] ] = alphabet:sub(i, i)
end

-- lookup table to turn keypress events into vim notation
local vim_notation_lookup = {
	[ keys.home ]		= "Home",
	[ keys["end"] ]		= "End",
	[ keys.pageUp ]		= "PageUp",
	[ keys.pageDown ]	= "PageDown",
	[ keys.insert ]		= "Insert",
	[ keys.delete ]		= "Del",
	[ keys.space ]		= "Space",
	[ keys.tab ]		= "Tab",
	[ keys.enter ]		= "Enter",
	[ keys.backspace ]	= "BS",

	[ "<" ]           = "lt",
	[ "|" ]           = "Bar",
	[ "\\" ]          = "Bslash",
	[ "\000" ]        = "Nul",

	[ keys.left ]  = "Left",
	[ keys.right ] = "Right",
	[ keys.up ]    = "Up",
	[ keys.down ]  = "Down",

	-- numpad keys that do not change if numlock is on or off
	[ keys.numPadAdd ]      = "kPlus",
	[ keys.numPadSubtract ] = "kMinus",
	[ keys.numPadDivide ]   = "kDivide",
	[ keys.multiply ]       = "kMultiply",
	[ keys.numPadComma ]    = "kComma",
	[ keys.numPadEnter ]    = "kEnter",
	[ keys.numPadEquals ]   = "kEqual",

	-- NOTE: unsure of actual Vim notation (if any), since I don't own a keyboard with these keys
	[ keys.kanji ]     = "Kanji",
	[ keys.kana ]      = "Kana",
	[ keys.ax ]        = "Ax",
	[ keys.yen ]       = "Yen",
	[ keys.stop ]      = "Stop",
	[ keys.convert ]   = "Convert",
	[ keys.noconvert ] = "NoConvert",

	-- NOTE: I am quite sure these keys are not recognized in Vim, but they *are* in CraftOS and are not modifiers
	[ keys.capsLock ]  = "CapsLock",
	[ keys.scollLock ] = "ScrollLock", -- 'scollLock' misspelled in CraftOS
	[ keys.numLock ]   = "NumLock",
	[ keys.pause ]     = "Pause",
}

-- function keys
for i = 1, 15 do
	vim_notation_lookup[ keys["f" .. i] ] = "F" .. i
end

-- treated as though numlock is OFF
local vim_notation_nonumlock = {
	[ keys.numPadDecimal ]  = "kDel",
	[ keys.numPad0 ]        = "Insert",
	[ keys.numPad1 ]        = "kEnd",
	[ keys.numPad2 ]        = "kDown",
	[ keys.numPad3 ]        = "kPageDown",
	[ keys.numPad4 ]        = "kLeft",
	[ keys.numPad5 ]        = "kOrigin",
	[ keys.numPad6 ]        = "kRight",
	[ keys.numPad7 ]        = "kHome",
	[ keys.numPad8 ]        = "kUp",
	[ keys.numPad9 ]        = "kPageUp",
}

-- what to register if numlock is ON
local vim_notation_numlock = {
	[ keys.numPadDecimal ]  = "kPoint",
	[ keys.numPad0 ]        = "k0",
	[ keys.numPad1 ]        = "k1",
	[ keys.numPad2 ]        = "k2",
	[ keys.numPad3 ]        = "k3",
	[ keys.numPad4 ]        = "k4",
	[ keys.numPad5 ]        = "k5",
	[ keys.numPad6 ]        = "k6",
	[ keys.numPad7 ]        = "k7",
	[ keys.numPad8 ]        = "k8",
	[ keys.numPad9 ]        = "k9",
}

-- aliases for vim notation into other vim notation
local vim_notation_alias = {
	[ "kDel" ] 	= "Del",
	[ "kEnd" ] 	= "End",
	[ "kDown" ] 	= "Down",
	[ "kPageDown" ] = "PageDown",
	[ "kLeft" ] 	= "Left",
	[ "kRight" ] 	= "Right",
	[ "kHome" ] 	= "Home",
	[ "kUp" ] 	= "Up",
	[ "kPageUp" ] 	= "PageUp",
}

-- lookup table for shift-modified characters
-- might not be representative of keyboards other than my own
local shifted_keys = {
	['1'] = '!',
	['2'] = '@',
	['3'] = '#',
	['4'] = '{{code}}#039;,
	['5'] = '%',
	['6'] = '^',
	['7'] = '&',
	['8'] = '*',
	['9'] = '(',
	['0'] = ')',
	['-'] = '_',
	['='] = '+',
	['`'] = '~',
	['['] = '{',
	[']'] = '}',
	['\\'] = '|',
	[';'] = ':',
	['\''] = '\"',
	[','] = "<",
	['.'] = ">",
	['/'] = "?",
}

local function uppersize(char)
	return shifted_keys[char] or char:upper()
end

function keypress.to_vim_notation( kp )
	if (not kp) or type(kp) ~= "table" then return "", false end
	if not kp.key then return "", false end

	local output = {"<", "", "", "", "", ">"}
	-- output[2] is "M" (alt)
	-- output[3] is "C" (ctrl)
	-- output[4] is "S" (shift)
	-- output[5] is the key code

	-- if the keypress has a printable character, omit the "S" notation
	local do_omit_s = false

	-- check if key is numlock-modifiable
	if vim_notation_numlock[ kp.key ] then
		-- if a character event was queued, that means numlock must have been on!
		if kp.char then
			output[5] = vim_notation_numlock[ kp.key ]
		else
			output[5] = vim_notation_nonumlock[ kp.key ]
		end

	else

		if vim_notation_lookup[ kp.char ] then
			output[5] = vim_notation_lookup[ kp.char ]

		elseif (kp.ctrl or kp.alt) and keys_printable_lookup[ kp.key ] then
			output[5] = keys_printable_lookup[ kp.key ]
			if kp.shift then
				output[5] = uppersize(output[5])
			end
			kp.char_pressed = output[5]
			do_omit_s = true
			output[5] = vim_notation_lookup[ output[5] ] or output[5]

		elseif vim_notation_lookup[ kp.key ] then
			output[5] = vim_notation_lookup[ kp.key ]
		end
	end

	if kp.char then
		do_omit_s = true
	end

	kp.char_pressed = kp.char_pressed or kp.char

	-- tack on modifier codes
	if kp.alt and not modifier_lookup.alt[ kp.key ] then
		output[2] = "M-"
	end

	if kp.ctrl and not modifier_lookup.ctrl[ kp.key ] then
		output[3] = "C-"
	end

	if kp.shift and (not do_omit_s) and not modifier_lookup.shift[ kp.key ] then
		output[4] = "S-"
	end

	-- enforce notation aliases

	if vim_notation_alias[ output[5] ] then
		output[5] = vim_notation_alias[ output[5] ]
	end

	-- for keys without notation, remove the chevrons and use the printed character
	if output[5] == "" then
		if not (kp.ctrl or kp.alt) then
			output[1] = ""
			output[6] = ""
		end
		output[5] = kp.char
	end

	if output[5] then
		return table.concat(output)
	else
		return nil
	end
end

function keypress.process()
	if _DEMO then
		print("Keypress API Demo")
		print("Press CTRL-C to exit.")
	end

	while true do
		local evt, kp = keypress.resume( os.pullEvent() )
		if evt == "keypress" then
			os.queueEvent(evt, kp)

		elseif _DEMO and evt == "keypress_terminatedemo" then
			print("Demo ended.")
			return
		end
	end
end

if _DEMO then
	keypress.process()
end

return keypress