diff --git a/keypress.lua b/keypress.lua new file mode 100644 index 0000000..e6036aa --- /dev/null +++ b/keypress.lua @@ -0,0 +1,435 @@ +-- Keypress API +-- by LDDestroier + +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 modfier_lookup = { + [ keys.leftCtrl ] = true, + [ keys.rightCtrl ] = true, + [ keys.ctrl ] = true, + [ keys.leftShift ] = true, + [ keys.rightShift ] = true, + [ keys.shift ] = true, + [ keys.leftAlt ] = true, + [ keys.rightAlt ] = true, + [ keys.alt ] = true, + + 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, + } +} + +function keypress.resume(...) + + local evt = {...} + local output1, output2 + + if evt[1] == "keypress" and _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 "???")) + write("char = " .. (evt[2].char or "nil")) + if evt[2].char_pressed then + print(" (" .. evt[2].char_pressed .. ")") + else print("") end + print("note = " .. (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 "") + print("") + end + + elseif 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 + output1, output2 = "keypress", { + 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] + } + + output2.notation = keypress.to_vim_notation(output2) + 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 + output1, output2 = "keypress", { + 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] + } + + output2.notation = keypress.to_vim_notation(output2) + end + +else + last_key = nil +end + +last_evt = evt[1] + +return output1, output2 +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 + [ 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'] = '$', + ['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 modfier_lookup.alt[ kp.key ] then + output[2] = "M-" + end + + if kp.ctrl and not modfier_lookup.ctrl[ kp.key ] then + output[3] = "C-" + end + + if kp.shift and (not do_omit_s) and not modfier_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 evt == "keypress_terminatedemo" then + print("Demo ended.") + return + end + end +end + +if _DEMO then + keypress.process() +end + +return keypress