// Router - Pipeline for processing input through stages var time = use('time') // Valid emacs keys /*var valid_emacs_keys = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'return', 'enter', 'space', 'escape', 'tab', 'backspace', 'delete', 'up', 'down', 'left', 'right', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]*/ var emacs_special = { 'return': 'RET', 'enter': 'RET', 'space': 'SPC', 'escape': 'ESC', 'tab': 'TAB', 'backspace': 'DEL', 'delete': 'delete' } // Gesture stage - detects swipes and pinches from touchpad function gesture_stage(cfg) { var o = cfg || {} var min_swipe = o.swipe_min_dist || 30 var max_time = o.swipe_max_time || 500 var pinch_th = o.pinch_threshold || 10 var touches = {} var gesture_state = null var start_dist = 0 function dist(p1, p2) { var dx = p2[0] - p1[0] var dy = p2[1] - p1[1] return Math.sqrt(dx * dx + dy * dy) } return { process: function(events) { var output = [] var i = 0 var ev = null var fingerId = null var count = 0 var fingers = null var currentDist = 0 var d = 0 var touch = null var dt = 0 var dx = 0 var dy = 0 var absX = 0 var absY = 0 var dir = null for (i = 0; i < length(events); i++) { ev = events[i] // Only process gamepad touchpad events if (ev.control != 'gamepad_touchpad') { push(output, ev) continue } fingerId = (ev.touchpad || 0) + '_' + (ev.finger || 0) if (ev.pressed) { touches[fingerId] = { pos: ev.pos, startPos: ev.pos, startTime: time.number() } count = length(array(touches)) if (count == 1) gesture_state = 'single' else if (count == 2) { gesture_state = 'multi' fingers = Object.values(touches) start_dist = dist(fingers[0].pos, fingers[1].pos) } } else if (ev.kind == 'axis') { if (touches[fingerId]) { touches[fingerId].pos = ev.pos count = length(array(touches)) if (count == 2 && gesture_state == 'multi') { fingers = Object.values(touches) currentDist = dist(fingers[0].pos, fingers[1].pos) d = currentDist - start_dist if (Math.abs(d) >= pinch_th / 100) { push(output, { kind: 'gesture', device_id: ev.device_id, control: d > 0 ? 'pinch_out' : 'pinch_in', pressed: true, released: false, delta: d, time: ev.time }) start_dist = currentDist } } } } else if (ev.released) { if (touches[fingerId]) { touch = touches[fingerId] count = length(array(touches)) if (count == 1 && gesture_state == 'single') { dt = (time.number() - touch.startTime) * 1000 dx = ev.pos[0] - touch.startPos[0] dy = ev.pos[1] - touch.startPos[1] if (dt < max_time) { absX = Math.abs(dx) absY = Math.abs(dy) if (absX > min_swipe / 100 || absY > min_swipe / 100) { dir = absX > absY ? (dx > 0 ? 'swipe_right' : 'swipe_left') : (dy > 0 ? 'swipe_down' : 'swipe_up') push(output, { kind: 'gesture', device_id: ev.device_id, control: dir, pressed: true, released: false, time: ev.time }) } } } delete touches[fingerId] if (length(array(touches)) == 0) gesture_state = null } } } return output } } } // Emacs stage - converts keyboard input to emacs chords function emacs_stage() { var prefix = null return { process: function(events) { var output = [] var i = 0 var ev = null var notation = null var chord = null for (i = 0; i < length(events); i++) { ev = events[i] // Only process keyboard button events if (ev.device_id != 'kbm' || ev.kind != 'button' || !ev.pressed) { push(output, ev) continue } /* if (find(valid_emacs_keys, ev.control) == null) { push(output, ev) continue } */ // Only process if we have modifiers OR waiting for chord if (!ev.mods?.ctrl && !ev.mods?.alt && !prefix) { push(output, ev) continue } notation = "" if (ev.mods?.ctrl) notation += "C-" if (ev.mods?.alt) notation += "M-" if (length(ev.control) == 1) { notation += lower(ev.control) } else { notation += emacs_special[ev.control] || ev.control } // Handle prefix keys if (notation == "C-x" || notation == "C-c") { prefix = notation continue // Consume, don't output } // Complete chord if we have prefix if (prefix) { chord = prefix + " " + notation prefix = null push(output, { kind: 'chord', device_id: ev.device_id, control: chord, pressed: true, released: false, time: ev.time }) } else { push(output, { kind: 'chord', device_id: ev.device_id, control: notation, pressed: true, released: false, time: ev.time }) } } return output } } } // Action mapping stage - converts controls to named actions function action_stage(bindings) { var down = {} return { down: down, process: function(events) { var output = [] var i = 0 var ev = null var actions = null var j = 0 var action = null for (i = 0; i < length(events); i++) { ev = events[i] // Pass through non-button events if (ev.kind != 'button' && ev.kind != 'chord' && ev.kind != 'gesture') { push(output, ev) continue } actions = bindings.get_actions(ev.control) if (length(actions) == 0) { push(output, ev) continue } for (j = 0; j < length(actions); j++) { action = actions[j] if (ev.pressed) down[action] = true else if (ev.released) down[action] = false push(output, { kind: 'action', device_id: ev.device_id, control: action, pressed: ev.pressed, released: ev.released, time: ev.time, original: ev }) } } return output } } } // Delivery stage - dispatches to possessed entity function delivery_stage(user) { return { process: function(events) { var i = 0 var ev = null for (i = 0; i < length(events); i++) { ev = events[i] // Only deliver actions if (ev.kind != 'action') continue user.dispatch(ev.control, { pressed: ev.pressed, released: ev.released, time: ev.time }) } return events } } } // Create router with pipeline function make(user, cfg) { var o = cfg || {} var stages = [] var action = null if (o.gestures != false) { push(stages, gesture_stage(o)) } if (o.emacs != false) { push(stages, emacs_stage()) } action = action_stage(user.bindings) push(stages, action) push(stages, delivery_stage(user)) return { stages: stages, down() { return action.down }, handle: function(canon) { var events = [canon] var i = 0 for (i = 0; i < length(this.stages); i++) { events = this.stages[i].process(events) } return events } } } return { make, gesture_stage, emacs_stage, action_stage, delivery_stage }