Path: blob/master/platform/web/js/libs/library_godot_input.js
21520 views
/**************************************************************************/1/* library_godot_input.js */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930/*31* IME API helper.32*/3334const GodotIME = {35$GodotIME__deps: ['$GodotRuntime', '$GodotEventListeners'],36$GodotIME__postset: 'GodotOS.atexit(function(resolve, reject) { GodotIME.clear(); resolve(); });',37$GodotIME: {38ime: null,39active: false,40focusTimerIntervalId: -1,4142getModifiers: function (evt) {43return (evt.shiftKey + 0) + ((evt.altKey + 0) << 1) + ((evt.ctrlKey + 0) << 2) + ((evt.metaKey + 0) << 3);44},4546ime_active: function (active) {47function clearFocusTimerInterval() {48clearInterval(GodotIME.focusTimerIntervalId);49GodotIME.focusTimerIntervalId = -1;50}5152function focusTimer() {53if (GodotIME.ime == null) {54clearFocusTimerInterval();55return;56}57GodotIME.ime.focus();58}5960if (GodotIME.focusTimerIntervalId > -1) {61clearFocusTimerInterval();62}6364if (GodotIME.ime == null) {65return;66}6768GodotIME.active = active;69if (active) {70GodotIME.ime.style.display = 'block';71GodotIME.focusTimerIntervalId = setInterval(focusTimer, 100);72} else {73GodotIME.ime.style.display = 'none';74GodotConfig.canvas.focus();75}76},7778ime_position: function (x, y) {79if (GodotIME.ime == null) {80return;81}82const canvas = GodotConfig.canvas;83const rect = canvas.getBoundingClientRect();84const rw = canvas.width / rect.width;85const rh = canvas.height / rect.height;86const clx = (x / rw) + rect.x;87const cly = (y / rh) + rect.y;8889GodotIME.ime.style.left = `${clx}px`;90GodotIME.ime.style.top = `${cly}px`;91},9293init: function (ime_cb, key_cb, code, key) {94function key_event_cb(pressed, evt) {95const modifiers = GodotIME.getModifiers(evt);96GodotRuntime.stringToHeap(evt.code, code, 32);97GodotRuntime.stringToHeap(evt.key, key, 32);98key_cb(pressed, evt.repeat, modifiers);99evt.preventDefault();100}101function ime_event_cb(event) {102if (GodotIME.ime == null) {103return;104}105switch (event.type) {106case 'compositionstart':107ime_cb(0, null);108GodotIME.ime.innerHTML = '';109break;110case 'compositionupdate': {111const ptr = GodotRuntime.allocString(event.data);112ime_cb(1, ptr);113GodotRuntime.free(ptr);114} break;115case 'compositionend': {116const ptr = GodotRuntime.allocString(event.data);117ime_cb(2, ptr);118GodotRuntime.free(ptr);119GodotIME.ime.innerHTML = '';120} break;121default:122// Do nothing.123}124}125126const ime = document.createElement('div');127ime.className = 'ime';128ime.style.background = 'none';129ime.style.opacity = 0.0;130ime.style.position = 'fixed';131ime.style.textAlign = 'left';132ime.style.fontSize = '1px';133ime.style.left = '0px';134ime.style.top = '0px';135ime.style.width = '100%';136ime.style.height = '40px';137ime.style.pointerEvents = 'none';138ime.style.display = 'none';139ime.contentEditable = 'true';140141GodotEventListeners.add(ime, 'compositionstart', ime_event_cb, false);142GodotEventListeners.add(ime, 'compositionupdate', ime_event_cb, false);143GodotEventListeners.add(ime, 'compositionend', ime_event_cb, false);144GodotEventListeners.add(ime, 'keydown', key_event_cb.bind(null, 1), false);145GodotEventListeners.add(ime, 'keyup', key_event_cb.bind(null, 0), false);146147ime.onblur = function () {148this.style.display = 'none';149GodotConfig.canvas.focus();150GodotIME.active = false;151};152153GodotConfig.canvas.parentElement.appendChild(ime);154GodotIME.ime = ime;155},156157clear: function () {158if (GodotIME.ime == null) {159return;160}161if (GodotIME.focusTimerIntervalId > -1) {162clearInterval(GodotIME.focusTimerIntervalId);163GodotIME.focusTimerIntervalId = -1;164}165GodotIME.ime.remove();166GodotIME.ime = null;167},168},169};170mergeInto(LibraryManager.library, GodotIME);171172/*173* Gamepad API helper.174*/175const GodotInputGamepads = {176$GodotInputGamepads__deps: ['$GodotRuntime', '$GodotEventListeners'],177$GodotInputGamepads: {178samples: [],179180get_pads: function () {181try {182// Will throw in iframe when permission is denied.183// Will throw/warn in the future for insecure contexts.184// See https://github.com/w3c/gamepad/pull/120185const pads = navigator.getGamepads();186if (pads) {187return pads;188}189return [];190} catch (e) {191return [];192}193},194195get_samples: function () {196return GodotInputGamepads.samples;197},198199get_sample: function (index) {200const samples = GodotInputGamepads.samples;201return index < samples.length ? samples[index] : null;202},203204sample: function () {205const pads = GodotInputGamepads.get_pads();206const samples = [];207for (let i = 0; i < pads.length; i++) {208const pad = pads[i];209if (!pad) {210samples.push(null);211continue;212}213const s = {214standard: pad.mapping === 'standard',215buttons: [],216axes: [],217connected: pad.connected,218};219for (let b = 0; b < pad.buttons.length; b++) {220s.buttons.push(pad.buttons[b].value);221}222for (let a = 0; a < pad.axes.length; a++) {223s.axes.push(pad.axes[a]);224}225samples.push(s);226}227GodotInputGamepads.samples = samples;228},229230init: function (onchange) {231GodotInputGamepads.samples = [];232function add(pad) {233const guid = GodotInputGamepads.get_guid(pad);234const c_id = GodotRuntime.allocString(pad.id);235const c_guid = GodotRuntime.allocString(guid);236onchange(pad.index, 1, c_id, c_guid);237GodotRuntime.free(c_id);238GodotRuntime.free(c_guid);239}240const pads = GodotInputGamepads.get_pads();241for (let i = 0; i < pads.length; i++) {242// Might be reserved space.243if (pads[i]) {244add(pads[i]);245}246}247GodotEventListeners.add(window, 'gamepadconnected', function (evt) {248if (evt.gamepad) {249add(evt.gamepad);250}251}, false);252GodotEventListeners.add(window, 'gamepaddisconnected', function (evt) {253if (evt.gamepad) {254onchange(evt.gamepad.index, 0);255}256}, false);257},258259get_guid: function (pad) {260if (pad.mapping) {261return pad.mapping;262}263const ua = navigator.userAgent;264let os = 'Unknown';265if (ua.indexOf('Android') >= 0) {266os = 'Android';267} else if (ua.indexOf('Linux') >= 0) {268os = 'Linux';269} else if (ua.indexOf('iPhone') >= 0) {270os = 'iOS';271} else if (ua.indexOf('Macintosh') >= 0) {272// Updated iPads will fall into this category.273os = 'MacOSX';274} else if (ua.indexOf('Windows') >= 0) {275os = 'Windows';276}277278const id = pad.id;279// Chrom* style: NAME (Vendor: xxxx Product: xxxx).280const exp1 = /vendor: ([0-9a-f]{4}) product: ([0-9a-f]{4})/i;281// Firefox/Safari style (Safari may remove leading zeroes).282const exp2 = /^([0-9a-f]+)-([0-9a-f]+)-/i;283let vendor = '';284let product = '';285if (exp1.test(id)) {286const match = exp1.exec(id);287vendor = match[1].padStart(4, '0');288product = match[2].padStart(4, '0');289} else if (exp2.test(id)) {290const match = exp2.exec(id);291vendor = match[1].padStart(4, '0');292product = match[2].padStart(4, '0');293}294if (!vendor || !product) {295return `${os}Unknown`;296}297return os + vendor + product;298},299},300};301mergeInto(LibraryManager.library, GodotInputGamepads);302303/*304* Drag and drop helper.305* This is pretty big, but basically detect dropped files on GodotConfig.canvas,306* process them one by one (recursively for directories), and copies them to307* the temporary FS path '/tmp/drop-[random]/' so it can be emitted as a godot308* event (that requires a string array of paths).309*310* NOTE: The temporary files are removed after the callback. This means that311* deferred callbacks won't be able to access the files.312*/313const GodotInputDragDrop = {314$GodotInputDragDrop__deps: ['$FS', '$GodotFS'],315$GodotInputDragDrop: {316promises: [],317pending_files: [],318319add_entry: function (entry) {320if (entry.isDirectory) {321GodotInputDragDrop.add_dir(entry);322} else if (entry.isFile) {323GodotInputDragDrop.add_file(entry);324} else {325GodotRuntime.error('Unrecognized entry...', entry);326}327},328329add_dir: function (entry) {330GodotInputDragDrop.promises.push(new Promise(function (resolve, reject) {331const reader = entry.createReader();332reader.readEntries(function (entries) {333for (let i = 0; i < entries.length; i++) {334GodotInputDragDrop.add_entry(entries[i]);335}336resolve();337});338}));339},340341add_file: function (entry) {342GodotInputDragDrop.promises.push(new Promise(function (resolve, reject) {343entry.file(function (file) {344const reader = new FileReader();345reader.onload = function () {346const f = {347'path': file.relativePath || file.webkitRelativePath,348'name': file.name,349'type': file.type,350'size': file.size,351'data': reader.result,352};353if (!f['path']) {354f['path'] = f['name'];355}356GodotInputDragDrop.pending_files.push(f);357resolve();358};359reader.onerror = function () {360GodotRuntime.print('Error reading file');361reject();362};363reader.readAsArrayBuffer(file);364}, function (err) {365GodotRuntime.print('Error!');366reject();367});368}));369},370371process: function (resolve, reject) {372if (GodotInputDragDrop.promises.length === 0) {373resolve();374return;375}376GodotInputDragDrop.promises.pop().then(function () {377setTimeout(function () {378GodotInputDragDrop.process(resolve, reject);379}, 0);380});381},382383_process_event: function (ev, callback) {384ev.preventDefault();385if (ev.dataTransfer.items) {386// Use DataTransferItemList interface to access the file(s)387for (let i = 0; i < ev.dataTransfer.items.length; i++) {388const item = ev.dataTransfer.items[i];389let entry = null;390if ('getAsEntry' in item) {391entry = item.getAsEntry();392} else if ('webkitGetAsEntry' in item) {393entry = item.webkitGetAsEntry();394}395if (entry) {396GodotInputDragDrop.add_entry(entry);397}398}399} else {400GodotRuntime.error('File upload not supported');401}402new Promise(GodotInputDragDrop.process).then(function () {403const DROP = `/tmp/drop-${parseInt(Math.random() * (1 << 30), 10)}/`;404const drops = [];405const files = [];406FS.mkdir(DROP.slice(0, -1)); // Without trailing slash407GodotInputDragDrop.pending_files.forEach((elem) => {408const path = elem['path'];409GodotFS.copy_to_fs(DROP + path, elem['data']);410let idx = path.indexOf('/');411if (idx === -1) {412// Root file413drops.push(DROP + path);414} else {415// Subdir416const sub = path.substr(0, idx);417idx = sub.indexOf('/');418if (idx < 0 && drops.indexOf(DROP + sub) === -1) {419drops.push(DROP + sub);420}421}422files.push(DROP + path);423});424GodotInputDragDrop.promises = [];425GodotInputDragDrop.pending_files = [];426callback(drops);427if (GodotConfig.persistent_drops) {428// Delay removal at exit.429GodotOS.atexit(function (resolve, reject) {430GodotInputDragDrop.remove_drop(files, DROP);431resolve();432});433} else {434GodotInputDragDrop.remove_drop(files, DROP);435}436});437},438439remove_drop: function (files, drop_path) {440const dirs = [drop_path.substr(0, drop_path.length - 1)];441// Remove temporary files442files.forEach(function (file) {443FS.unlink(file);444let dir = file.replace(drop_path, '');445let idx = dir.lastIndexOf('/');446while (idx > 0) {447dir = dir.substr(0, idx);448if (dirs.indexOf(drop_path + dir) === -1) {449dirs.push(drop_path + dir);450}451idx = dir.lastIndexOf('/');452}453});454// Remove dirs.455dirs.sort(function (a, b) {456const al = (a.match(/\//g) || []).length;457const bl = (b.match(/\//g) || []).length;458if (al > bl) {459return -1;460} else if (al < bl) {461return 1;462}463return 0;464}).forEach(function (dir) {465FS.rmdir(dir);466});467},468469handler: function (callback) {470return function (ev) {471GodotInputDragDrop._process_event(ev, callback);472};473},474},475};476mergeInto(LibraryManager.library, GodotInputDragDrop);477478/*479* Godot exposed input functions.480*/481const GodotInput = {482$GodotInput__deps: ['$GodotRuntime', '$GodotConfig', '$GodotEventListeners', '$GodotInputGamepads', '$GodotInputDragDrop', '$GodotIME'],483$GodotInput: {484inputKeyCallback: null,485setInputKeyData: null,486487getModifiers: function (evt) {488return (evt.shiftKey + 0) + ((evt.altKey + 0) << 1) + ((evt.ctrlKey + 0) << 2) + ((evt.metaKey + 0) << 3);489},490491computePosition: function (evt, rect) {492const canvas = GodotConfig.canvas;493const rw = canvas.width / rect.width;494const rh = canvas.height / rect.height;495const x = (evt.clientX - rect.x) * rw;496const y = (evt.clientY - rect.y) * rh;497return [x, y];498},499500onKeyEvent: function (pIsPressed, pEvent) {501if (GodotInput.inputKeyCallback == null) {502throw new TypeError('GodotInput.onKeyEvent(): GodotInput.inputKeyCallback is null, cannot process key event.');503}504if (GodotInput.setInputKeyData == null) {505throw new TypeError('GodotInput.onKeyEvent(): GodotInput.setInputKeyData is null, cannot process key event.');506}507508const modifiers = GodotInput.getModifiers(pEvent);509GodotInput.setInputKeyData(pEvent.code, pEvent.key);510GodotInput.inputKeyCallback(pIsPressed ? 1 : 0, pEvent.repeat, modifiers);511pEvent.preventDefault();512},513},514515/*516* Mouse API517*/518godot_js_input_mouse_move_cb__proxy: 'sync',519godot_js_input_mouse_move_cb__sig: 'vi',520godot_js_input_mouse_move_cb: function (callback) {521const func = GodotRuntime.get_func(callback);522const canvas = GodotConfig.canvas;523function move_cb(evt) {524const rect = canvas.getBoundingClientRect();525const pos = GodotInput.computePosition(evt, rect);526// Scale movement527const rw = canvas.width / rect.width;528const rh = canvas.height / rect.height;529const rel_pos_x = evt.movementX * rw;530const rel_pos_y = evt.movementY * rh;531const modifiers = GodotInput.getModifiers(evt);532func(pos[0], pos[1], rel_pos_x, rel_pos_y, modifiers, evt.pressure);533}534GodotEventListeners.add(window, 'pointermove', move_cb, false);535},536537godot_js_input_mouse_wheel_cb__proxy: 'sync',538godot_js_input_mouse_wheel_cb__sig: 'vi',539godot_js_input_mouse_wheel_cb: function (callback) {540const func = GodotRuntime.get_func(callback);541function wheel_cb(evt) {542if (func(evt.deltaMode, evt.deltaX ?? 0, evt.deltaY ?? 0)) {543evt.preventDefault();544}545}546GodotEventListeners.add(GodotConfig.canvas, 'wheel', wheel_cb, false);547},548549godot_js_input_mouse_button_cb__proxy: 'sync',550godot_js_input_mouse_button_cb__sig: 'vi',551godot_js_input_mouse_button_cb: function (callback) {552const func = GodotRuntime.get_func(callback);553const canvas = GodotConfig.canvas;554function button_cb(p_pressed, evt) {555const rect = canvas.getBoundingClientRect();556const pos = GodotInput.computePosition(evt, rect);557const modifiers = GodotInput.getModifiers(evt);558// Since the event is consumed, focus manually.559// NOTE: The iframe container may not have focus yet, so focus even when already active.560if (p_pressed) {561GodotConfig.canvas.focus();562}563if (func(p_pressed, evt.button, pos[0], pos[1], modifiers)) {564evt.preventDefault();565}566}567GodotEventListeners.add(canvas, 'mousedown', button_cb.bind(null, 1), false);568GodotEventListeners.add(window, 'mouseup', button_cb.bind(null, 0), false);569},570571/*572* Touch API573*/574godot_js_input_touch_cb__proxy: 'sync',575godot_js_input_touch_cb__sig: 'viii',576godot_js_input_touch_cb: function (callback, ids, coords) {577const func = GodotRuntime.get_func(callback);578const canvas = GodotConfig.canvas;579function touch_cb(type, evt) {580// Since the event is consumed, focus manually.581// NOTE: The iframe container may not have focus yet, so focus even when already active.582if (type === 0) {583GodotConfig.canvas.focus();584}585const rect = canvas.getBoundingClientRect();586const touches = evt.changedTouches;587for (let i = 0; i < touches.length; i++) {588const touch = touches[i];589const pos = GodotInput.computePosition(touch, rect);590GodotRuntime.setHeapValue(coords + (i * 2) * 8, pos[0], 'double');591GodotRuntime.setHeapValue(coords + (i * 2 + 1) * 8, pos[1], 'double');592GodotRuntime.setHeapValue(ids + i * 4, touch.identifier, 'i32');593}594func(type, touches.length);595if (evt.cancelable) {596evt.preventDefault();597}598}599GodotEventListeners.add(canvas, 'touchstart', touch_cb.bind(null, 0), false);600GodotEventListeners.add(canvas, 'touchend', touch_cb.bind(null, 1), false);601GodotEventListeners.add(canvas, 'touchcancel', touch_cb.bind(null, 1), false);602GodotEventListeners.add(canvas, 'touchmove', touch_cb.bind(null, 2), false);603},604605/*606* Key API607*/608godot_js_input_key_cb__proxy: 'sync',609godot_js_input_key_cb__sig: 'viii',610godot_js_input_key_cb: function (pCallback, pCodePtr, pKeyPtr) {611GodotInput.inputKeyCallback = GodotRuntime.get_func(pCallback);612GodotInput.setInputKeyData = (pCode, pKey) => {613GodotRuntime.stringToHeap(pCode, pCodePtr, 32);614GodotRuntime.stringToHeap(pKey, pKeyPtr, 32);615};616GodotEventListeners.add(GodotConfig.canvas, 'keydown', GodotInput.onKeyEvent.bind(null, true), false);617GodotEventListeners.add(GodotConfig.canvas, 'keyup', GodotInput.onKeyEvent.bind(null, false), false);618},619620/*621* IME API622*/623godot_js_set_ime_active__proxy: 'sync',624godot_js_set_ime_active__sig: 'vi',625godot_js_set_ime_active: function (p_active) {626GodotIME.ime_active(p_active);627},628629godot_js_set_ime_position__proxy: 'sync',630godot_js_set_ime_position__sig: 'vii',631godot_js_set_ime_position: function (p_x, p_y) {632GodotIME.ime_position(p_x, p_y);633},634635godot_js_set_ime_cb__proxy: 'sync',636godot_js_set_ime_cb__sig: 'viiii',637godot_js_set_ime_cb: function (p_ime_cb, p_key_cb, code, key) {638const ime_cb = GodotRuntime.get_func(p_ime_cb);639const key_cb = GodotRuntime.get_func(p_key_cb);640GodotIME.init(ime_cb, key_cb, code, key);641},642643godot_js_is_ime_focused__proxy: 'sync',644godot_js_is_ime_focused__sig: 'i',645godot_js_is_ime_focused: function () {646return GodotIME.active;647},648649/*650* Gamepad API651*/652godot_js_input_gamepad_cb__proxy: 'sync',653godot_js_input_gamepad_cb__sig: 'vi',654godot_js_input_gamepad_cb: function (change_cb) {655const onchange = GodotRuntime.get_func(change_cb);656GodotInputGamepads.init(onchange);657},658659godot_js_input_gamepad_sample_count__proxy: 'sync',660godot_js_input_gamepad_sample_count__sig: 'i',661godot_js_input_gamepad_sample_count: function () {662return GodotInputGamepads.get_samples().length;663},664665godot_js_input_gamepad_sample__proxy: 'sync',666godot_js_input_gamepad_sample__sig: 'i',667godot_js_input_gamepad_sample: function () {668GodotInputGamepads.sample();669return 0;670},671672godot_js_input_gamepad_sample_get__proxy: 'sync',673godot_js_input_gamepad_sample_get__sig: 'iiiiiii',674godot_js_input_gamepad_sample_get: function (p_index, r_btns, r_btns_num, r_axes, r_axes_num, r_standard) {675const sample = GodotInputGamepads.get_sample(p_index);676if (!sample || !sample.connected) {677return 1;678}679const btns = sample.buttons;680const btns_len = btns.length < 16 ? btns.length : 16;681for (let i = 0; i < btns_len; i++) {682GodotRuntime.setHeapValue(r_btns + (i << 2), btns[i], 'float');683}684GodotRuntime.setHeapValue(r_btns_num, btns_len, 'i32');685const axes = sample.axes;686const axes_len = axes.length < 10 ? axes.length : 10;687for (let i = 0; i < axes_len; i++) {688GodotRuntime.setHeapValue(r_axes + (i << 2), axes[i], 'float');689}690GodotRuntime.setHeapValue(r_axes_num, axes_len, 'i32');691const is_standard = sample.standard ? 1 : 0;692GodotRuntime.setHeapValue(r_standard, is_standard, 'i32');693return 0;694},695696/*697* Drag/Drop API698*/699godot_js_input_drop_files_cb__proxy: 'sync',700godot_js_input_drop_files_cb__sig: 'vi',701godot_js_input_drop_files_cb: function (callback) {702const func = GodotRuntime.get_func(callback);703const dropFiles = function (files) {704const args = files || [];705if (!args.length) {706return;707}708const argc = args.length;709const argv = GodotRuntime.allocStringArray(args);710func(argv, argc);711GodotRuntime.freeStringArray(argv, argc);712};713const canvas = GodotConfig.canvas;714GodotEventListeners.add(canvas, 'dragover', function (ev) {715// Prevent default behavior (which would try to open the file(s))716ev.preventDefault();717}, false);718GodotEventListeners.add(canvas, 'drop', GodotInputDragDrop.handler(dropFiles));719},720721/* Paste API */722godot_js_input_paste_cb__proxy: 'sync',723godot_js_input_paste_cb__sig: 'vi',724godot_js_input_paste_cb: function (callback) {725const func = GodotRuntime.get_func(callback);726GodotEventListeners.add(window, 'paste', function (evt) {727const text = evt.clipboardData.getData('text');728const ptr = GodotRuntime.allocString(text);729func(ptr);730GodotRuntime.free(ptr);731}, false);732},733734godot_js_input_vibrate_handheld__proxy: 'sync',735godot_js_input_vibrate_handheld__sig: 'vi',736godot_js_input_vibrate_handheld: function (p_duration_ms) {737if (typeof navigator.vibrate !== 'function') {738GodotRuntime.print('This browser does not support vibration.');739} else {740navigator.vibrate(p_duration_ms);741}742},743};744745autoAddDeps(GodotInput, '$GodotInput');746mergeInto(LibraryManager.library, GodotInput);747748749