On-screen keyboard that dispatches realistic keyboard events (keydown/keypress/keyup) to work with browser games and any website — full keyset, modifiers, repeat, highlight, sound, toggle button, HOLD SUPPORT FIXED.
目前為
// ==UserScript==
// @name Universal Live-Event On-Screen Keyboard (Game-ready, All Keys, Hold-Friendly)
// @namespace https://example.com/
// @version 1.0
// @description On-screen keyboard that dispatches realistic keyboard events (keydown/keypress/keyup) to work with browser games and any website — full keyset, modifiers, repeat, highlight, sound, toggle button, HOLD SUPPORT FIXED.
// @match *://*/*
// @grant none
// ==/UserScript==
(() => {
'use strict';
if (window.__USK_INSTALLED__) return;
window.__USK_INSTALLED__ = true;
// ---------- Utilities ----------
const make = (t, props = {}) => Object.assign(document.createElement(t), props);
const css = (el, rules) => Object.assign(el.style, rules);
// ---------- Audio click ----------
let audioCtx = null;
function playClick() {
try {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const o = audioCtx.createOscillator();
const g = audioCtx.createGain();
o.type = 'triangle';
o.frequency.value = 750;
g.gain.value = 0.06;
g.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 0.09);
o.connect(g); g.connect(audioCtx.destination);
o.start();
o.stop(audioCtx.currentTime + 0.09);
} catch (e) {}
}
// ---------- Key maps ----------
const KEYDB = (() => {
const db = {};
const add = (key, code, keyCode, location = 0) =>
db[key] = { key, code, keyCode, location };
for (let i = 65; i <= 90; i++) {
const ch = String.fromCharCode(i).toLowerCase();
add(ch, 'Key' + ch.toUpperCase(), i);
}
add('0','Digit0',48); add('1','Digit1',49); add('2','Digit2',50); add('3','Digit3',51);
add('4','Digit4',52); add('5','Digit5',53); add('6','Digit6',54); add('7','Digit7',55);
add('8','Digit8',56); add('9','Digit9',57);
add('`','Backquote',192); add('-','Minus',189); add('=','Equal',187);
add('[','BracketLeft',219); add(']','BracketRight',221);
add('\\','Backslash',220); add(';','Semicolon',186); add("'","Quote",222);
add(',','Comma',188); add('.','Period',190); add('/','Slash',191);
add('Space','Space',32); add('Enter','Enter',13); add('Tab','Tab',9);
add('Backspace','Backspace',8); add('Escape','Escape',27); add('Esc','Escape',27);
add('Delete','Delete',46); add('Del','Delete',46);
add('ArrowLeft','ArrowLeft',37); add('ArrowUp','ArrowUp',38);
add('ArrowRight','ArrowRight',39); add('ArrowDown','ArrowDown',40);
for (let i = 1; i <= 12; i++) add('F'+i, 'F'+i, 111 + i);
add('Shift','ShiftLeft',16); add('Control','ControlLeft',17);
add('Ctrl','ControlLeft',17); add('Alt','AltLeft',18);
add('Meta','MetaLeft',91); add('OS','MetaLeft',91);
return db;
})();
const SHIFT_MAP = {
'`':'~','1':'!','2':'@','3':'#','4':'$','5':'%','6':'^','7':'&','8':'*','9':'(','0':')',
'-':'_','=':'+','[':'{',']':'}','\\':'|',';':':',"'":'"',',':'<','.':'>','/':'?'
};
function makeKeyboardEvent(type, opts = {}) {
const ev = new KeyboardEvent(type, Object.assign({
key: opts.key || '',
code: opts.code || '',
location: opts.location || 0,
ctrlKey: !!opts.ctrlKey,
shiftKey: !!opts.shiftKey,
altKey: !!opts.altKey,
metaKey: !!opts.metaKey,
repeat: !!opts.repeat,
bubbles: true,
cancelable: true,
composed: true
}, opts));
try { Object.defineProperty(ev, 'keyCode', { get: () => opts.keyCode || 0 }); } catch(e){}
try { Object.defineProperty(ev, 'which', { get: () => opts.which || opts.keyCode || 0 }); } catch(e){}
return ev;
}
function dispatchToTargets(ev) {
const targets = [];
if (document.activeElement) targets.push(document.activeElement);
try {
const centerEl = document.elementFromPoint(innerWidth/2, innerHeight/2);
if (centerEl && !targets.includes(centerEl)) targets.push(centerEl);
} catch(e){}
if (!targets.includes(document)) targets.push(document);
if (!targets.includes(window)) targets.push(window);
for (const t of targets) {
try { t.dispatchEvent(ev); } catch(e){}
}
}
const modifierState = { Shift: false, Control: false, Alt: false, Meta: false, Caps: false };
function normalize(label) {
if (label === 'Space') return ' ';
if (label === 'Left') return 'ArrowLeft';
if (label === 'Right') return 'ArrowRight';
if (label === 'Up') return 'ArrowUp';
if (label === 'Down') return 'ArrowDown';
if (label === 'Esc') return 'Escape';
if (label === 'Back') return 'Backspace';
if (label === 'Ctrl') return 'Control';
if (label === 'Caps') return 'CapsLock';
return label;
}
function getKeyInfo(label, withShift = false) {
label = String(label);
if (label.length === 1) {
const ch = label;
const lower = ch.toLowerCase();
if (KEYDB[lower]) {
const def = KEYDB[lower];
let key = def.key;
if (/[a-z]/i.test(ch)) {
if (withShift || modifierState.Caps) key = ch.toUpperCase();
else key = ch.toLowerCase();
} else {
if (withShift && SHIFT_MAP[ch]) key = SHIFT_MAP[ch];
else key = ch;
}
return { key, code: def.code, keyCode: def.keyCode, location: def.location, isPrintable: true };
}
return { key: ch, code: 'Unknown', keyCode: ch.charCodeAt(0), location: 0, isPrintable: true };
}
const norm = normalize(label);
let def = KEYDB[label] || KEYDB[norm] || KEYDB[label.toUpperCase()] || null;
if (def)
return { key: def.key, code: def.code, keyCode: def.keyCode, location: def.location, isPrintable: false };
return { key: norm, code: norm, keyCode: 0, location: 0, isPrintable: false };
}
function simulateKeyPress(label, options = {}) {
const useShift = options.shift || modifierState.Shift;
const info = getKeyInfo(label, useShift);
const lower = label.toLowerCase();
if (['shift','control','ctrl','alt','meta','caps','capslock'].includes(lower)) {
modifierState[lower === 'ctrl' ? 'Control' :
lower === 'caps' ? 'Caps' :
lower.charAt(0).toUpperCase()+lower.slice(1)] =
!modifierState[lower === 'ctrl' ? 'Control' :
lower === 'caps' ? 'Caps' :
lower.charAt(0).toUpperCase()+lower.slice(1)];
highlightModifier(label, true);
return;
}
const key = info.key;
const code = info.code;
const keyCode = info.keyCode;
const location = info.location;
const ctrl = modifierState.Control;
const shift = modifierState.Shift || (/[A-Z]/.test(key) && info.isPrintable);
const alt = modifierState.Alt;
const meta = modifierState.Meta;
const downEvent = makeKeyboardEvent('keydown', {
key, code, keyCode, which: keyCode, location,
ctrlKey: ctrl, shiftKey: shift, altKey: alt, metaKey: meta
});
dispatchToTargets(downEvent);
if (info.isPrintable) {
const ch = key;
const pressEvent = makeKeyboardEvent('keypress', {
key: ch,
code,
keyCode: ch.charCodeAt(0) || keyCode,
which: ch.charCodeAt(0) || keyCode,
location, ctrlKey: ctrl, shiftKey: shift, altKey: alt, metaKey: meta
});
dispatchToTargets(pressEvent);
}
const upEvent = makeKeyboardEvent('keyup', {
key, code, keyCode, which: keyCode, location,
ctrlKey: ctrl, shiftKey: shift, altKey: alt, metaKey: meta
});
if (options.holdFor) setTimeout(() => dispatchToTargets(upEvent), options.holdFor);
else dispatchToTargets(upEvent);
}
const UI_LAYOUT = [
['Esc','F1','F2','F3','F4','F5','F6','F7','F8','F9','F10','F11','F12','Prt','Del'],
['`','1','2','3','4','5','6','7','8','9','0','-','=','Backspace'],
['Tab','q','w','e','r','t','y','u','i','o','p','[',']','\\'],
['Caps','a','s','d','f','g','h','j','k','l',';',"'",'Enter'],
['Shift','z','x','c','v','b','n','m',',','.','/','Shift'],
['Ctrl','Alt','Meta','Space','Left','Down','Up','Right']
];
const kb = make('div', { id: 'usk-kb' });
css(kb, {
position: 'fixed',
left: '50%',
bottom: '12px',
transform: 'translateX(-50%)',
background: 'rgba(12,12,12,0.96)',
padding: '10px',
borderRadius: '10px',
border: '1px solid rgba(255,255,255,0.06)',
zIndex: 2147483646,
color: '#fff',
fontFamily: 'system-ui,Segoe UI,Roboto,Arial',
touchAction: 'none',
display: 'none',
boxShadow: '0 6px 30px rgba(0,0,0,0.6)'
});
const keyBaseStyle = {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
background: '#333',
color: '#fff',
padding: '8px 10px',
margin: '3px',
borderRadius: '6px',
minWidth: '36px',
cursor: 'pointer',
userSelect: 'none',
boxSizing: 'border-box',
transition: 'box-shadow 0.06s, transform 0.06s'
};
function makeKey(label) {
const btn = make('div', { className: 'usk-key', textContent: label });
css(btn, keyBaseStyle);
if (label === 'Space') css(btn, { minWidth: '240px', flex: '5', padding: '10px 12px' });
if (label === 'Backspace') css(btn, { minWidth: '72px' });
if (['Tab','Enter','Shift','Caps'].includes(label)) css(btn, { minWidth: '64px' });
btn.dataset.keyLabel = label;
return btn;
}
UI_LAYOUT.forEach(row => {
const r = make('div');
css(r, { display: 'flex', justifyContent: 'center', marginBottom: '4px', alignItems: 'center' });
row.forEach(label => {
const k = makeKey(label);
r.appendChild(k);
});
kb.appendChild(r);
});
document.body.appendChild(kb);
const toggle = make('div', { id: 'usk-toggle', textContent: '⌨️' });
css(toggle, {
position: 'fixed', top: '12px', right: '12px',
width: '48px', height: '48px', display: 'flex',
alignItems: 'center', justifyContent: 'center',
fontSize: '22px', background: 'rgba(0,0,0,0.68)',
color: '#fff', borderRadius: '10px',
zIndex: 2147483647, cursor: 'pointer',
boxShadow: '0 6px 18px rgba(0,0,0,0.45)'
});
toggle.addEventListener('click', (e) => {
e.stopPropagation();
if (kb.style.display === 'none') {
kb.style.display = 'block';
toggle.style.boxShadow = '0 8px 30px rgba(0,170,255,0.25)';
} else {
kb.style.display = 'none';
toggle.style.boxShadow = '0 6px 18px rgba(0,0,0,0.45)';
}
});
document.body.appendChild(toggle);
(function draggable(el) {
let dragging = false, ox = 0, oy = 0;
el.addEventListener('pointerdown', (ev) => {
if (ev.target.closest('.usk-key')) return;
dragging = true;
ox = ev.clientX - el.offsetLeft;
oy = ev.clientY - el.offsetTop;
el.setPointerCapture(ev.pointerId);
});
document.addEventListener('pointermove', (ev) => {
if (!dragging) return;
css(el, { left: `${ev.clientX - ox}px`, top: `${ev.clientY - oy}px`, transform: 'none' });
});
document.addEventListener('pointerup', () => dragging = false);
})(kb);
function flashKeyEl(el) {
if (!el) return;
el.style.boxShadow = '0 0 14px 3px rgba(0,170,255,0.85)';
el.style.transform = 'translateY(1px)';
setTimeout(() => {
el.style.boxShadow = '';
el.style.transform = '';
}, 120);
}
function highlightModifier(mod, on) {
const keyEls = document.querySelectorAll('.usk-key');
keyEls.forEach(k => {
const l = k.dataset.keyLabel.toLowerCase();
if (
(mod.toLowerCase() === 'shift' && l === 'shift') ||
(mod.toLowerCase() === 'control' && (l === 'control' || l === 'ctrl')) ||
(mod.toLowerCase() === 'alt' && l === 'alt') ||
(mod.toLowerCase() === 'meta' && l === 'meta') ||
(mod.toLowerCase() === 'caps' && l === 'caps')
) {
if (on) {
k.style.background = '#0b6'; k.style.color = '#001';
k.style.boxShadow = '0 0 10px 2px rgba(11,204,119,0.25)';
} else {
k.style.background = '#333'; k.style.color = '#fff';
k.style.boxShadow = '';
}
}
});
}
const activeRepeat = new Map();
function startPressing(label, el) {
simulateKeyPress(label, { holdFor: 0 });
flashKeyEl(el);
playClick();
const repeatDelay = 400;
const repeatInterval = 55;
let timeoutId = setTimeout(() => {
const intervalId = setInterval(() => {
simulateKeyPress(label, { holdFor: 0 });
flashKeyEl(el);
playClick();
}, repeatInterval);
activeRepeat.set(el, intervalId);
}, repeatDelay);
activeRepeat.set(el, timeoutId);
}
function stopPressing(el) {
const id = activeRepeat.get(el);
if (!id) return;
clearTimeout(id);
clearInterval(id);
activeRepeat.delete(el);
}
document.querySelectorAll('.usk-key').forEach(el => {
// ⭐ CLICK ONLY TOGGLES MODIFIER KEYS — DOES NOT FIRE KEYPRESS ⭐
el.addEventListener('click', (ev) => {
ev.stopPropagation();
const label = el.dataset.keyLabel.toLowerCase();
if (['shift','ctrl','control','alt','meta','caps'].includes(label)) {
simulateKeyPress(el.dataset.keyLabel);
flashKeyEl(el);
playClick();
}
});
// HOLD SYSTEM
el.addEventListener('pointerdown', (ev) => {
ev.preventDefault();
ev.stopPropagation();
startPressing(el.dataset.keyLabel, el);
});
['pointerup','pointercancel','pointerleave'].forEach(evt => {
el.addEventListener(evt, (ev) => {
ev.preventDefault();
ev.stopPropagation();
stopPressing(el);
});
});
el.addEventListener('touchstart', (ev) => ev.preventDefault(), { passive: false });
});
toggle.addEventListener('mousedown', (e) => e.preventDefault());
toggle.addEventListener('pointerdown', (e) => e.preventDefault());
kb.addEventListener('mousedown', e => e.preventDefault());
console.info('Universal Keyboard Loaded with HOLD SUPPORT.');
})();