-
Star
(220)
You must be signed in to star a gist -
Fork
(52)
You must be signed in to fork a gist
-
-
Save amunchet/4cfaf0274f3d238946f9f8f94fa9ee02 to your computer and use it in GitHub Desktop.
| // ==UserScript== | |
| // @name noVNC Paste for Proxmox | |
| // @namespace http://tampermonkey.net/ | |
| // @version 0.2a | |
| // @description Pastes text into a noVNC window (for use with Proxmox specifically) | |
| // @author Chester Enright | |
| // @match https://* | |
| // @include /^.*novnc.*/ | |
| // @require http://code.jquery.com/jquery-3.3.1.min.js | |
| // @grant none | |
| // ==/UserScript== | |
| const delay = 1 | |
| ;(function () { | |
| 'use strict' | |
| window.sendString = function(text) { | |
| var el = document.getElementById("canvas-id") | |
| text.split("").forEach(x=>{ | |
| setTimeout(()=>{ | |
| var needs_shift = x.match(/[A-Z!@#$%^&*()_+{}:\"<>?~|]/) | |
| let evt | |
| if (needs_shift) { | |
| evt = new KeyboardEvent("keydown", {keyCode: 16}) | |
| el.dispatchEvent(evt) | |
| evt = new KeyboardEvent("keydown", {key: x, shiftKey: true}) | |
| el.dispatchEvent(evt) | |
| evt = new KeyboardEvent("keyup", {keyCode: 16}) | |
| el.dispatchEvent(evt) | |
| }else{ | |
| evt = new KeyboardEvent("keydown", {key: x}) | |
| } | |
| el.dispatchEvent(evt) | |
| }, delay) | |
| }) | |
| } | |
| $(document).ready(function() { | |
| setTimeout(()=>{ | |
| console.log("Starting up noVNC Copy/Paste (for Proxmox)") | |
| $("canvas").attr("id", "canvas-id") | |
| $("canvas").on("mousedown", (e)=>{ | |
| if(e.button == 2){ // Right Click | |
| navigator.clipboard.readText().then(text =>{ | |
| window.sendString(text) | |
| }) | |
| } | |
| }) | |
| }, 1000); | |
| }) | |
| })() |
Thank you. I'll check it out.
@amunchet Just came here to say I love you for making this script. It's such a massive quality of life improvement!
used this in console to paste ssh key using right mouse click.
(function () {
const delay = 50; // Slow down typing to avoid missed characters
function loadjQuery(callback) {
if (typeof window.jQuery !== 'undefined') {
callback(window.jQuery);
} else {
const script = document.createElement('script');
script.src = "https://code.jquery.com/jquery-3.3.1.min.js";
script.onload = () => callback(window.jQuery);
document.head.appendChild(script);
}
}
function sendChar(el, char) {
if (char === '\n' || char === '\r') {
// Handle Enter
el.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", code: "Enter", which: 13, keyCode: 13 }));
el.dispatchEvent(new KeyboardEvent("keyup", { key: "Enter", code: "Enter", which: 13, keyCode: 13 }));
return;
}
const needsShift = char.match(/[A-Z!@#$%^&*()_+{}:"<>?~|]/);
if (needsShift) {
el.dispatchEvent(new KeyboardEvent("keydown", { key: "Shift", code: "ShiftLeft", shiftKey: true }));
}
el.dispatchEvent(new KeyboardEvent("keydown", { key: char, code: undefined, shiftKey: !!needsShift }));
el.dispatchEvent(new KeyboardEvent("keyup", { key: char, code: undefined, shiftKey: !!needsShift }));
if (needsShift) {
el.dispatchEvent(new KeyboardEvent("keyup", { key: "Shift", code: "ShiftLeft", shiftKey: false }));
}
}
function sendString(text) {
const el = document.getElementById("canvas-id");
if (!el) {
console.warn("Canvas not found");
return;
}
text.split("").forEach((char, i) => {
setTimeout(() => sendChar(el, char), delay * i);
});
}
loadjQuery(($) => {
setTimeout(() => {
console.log("noVNC Paste active");
$("canvas").attr("id", "canvas-id");
$("canvas").on("mousedown", (e) => {
if (e.button === 2) { // Right-click
navigator.clipboard.readText().then(text => {
sendString(text);
});
}
});
}, 1000);
});
})();
The canvas finding logic is broken, use this for sendString() if you're using novnc and it'll work:
function sendString(text) {
var el = document.querySelector("#noVNC_container canvas")
text.split("").forEach(x=>{
setTimeout(()=>{
var needs_shift = x.match(/[A-Z!@#$%^&*()_+{}:\"<>?~|]/)
let evt
if (needs_shift) {
evt = new KeyboardEvent("keydown", {keyCode: 16})
el.dispatchEvent(evt)
evt = new KeyboardEvent("keydown", {key: x, shiftKey: true})
el.dispatchEvent(evt)
evt = new KeyboardEvent("keyup", {keyCode: 16})
el.dispatchEvent(evt)
}else{
evt = new KeyboardEvent("keydown", {key: x})
}
el.dispatchEvent(evt)
}, 100)
})
}
感谢作者的代码!
受此启发,我在 Claude Code 的帮助下基于原代码开发了一个增强版的 PVE 粘贴输入工具(现已更新至 2.0)。主要改进包括:
- 核心机制:优先直接调用 noVNC 客户端自身的按键发送接口(RFB.sendKey),传入 keysym 与物理按键 code,无需再逆向猜测事件映射;无法获取该接口时才回退到合成键盘事件。对 Shift 组合键和特殊符号的处理更稳定。
- 交互优化:图形化模态框,支持实时字符统计、可调节并记忆的字符输入间隔,以及输入过程中的进度浮层(可随时取消)。
- 智能适配:实时检测不支持的非 ASCII 字符(中文、Emoji 等)并提示;根据 PVE 界面语言自动切换中英文;虚拟机关机或仅查看模式时自动隐藏按钮。
- 部署方式:除油猴脚本外,还支持 Nginx 注入方式,适合反向代理场景。
📖 项目文档与界面均已支持中英双语。
项目地址:https://github.com/cjpjxjx/pve-paste-input
Thanks for the code!
Inspired by this, I built an enhanced PVE paste input tool with the help of Claude Code (now updated to 2.0). Key improvements include:
- Core Mechanism: Prefers calling noVNC's own key-send API directly (RFB.sendKey) with the keysym and physical key code, instead of reverse-engineering event mappings; falls back to synthetic keyboard events only when that API isn't reachable. More reliable handling of Shift combinations and special symbols.
- Better UX: A graphical modal dialog with real-time character counting, an adjustable-and-remembered typing interval, and a progress panel during input (cancelable anytime).
- Smart Adaptation: Detects unsupported non-ASCII characters (Chinese, Emoji, etc.) in real time; auto-switches between Chinese and English based on the PVE UI language; auto-hides the button when the VM is off or in view-only mode.
- Deployment: Supports both a Tampermonkey userscript and Nginx injection (suitable for reverse-proxy setups).
📖 Both the documentation and the interface are now available in Chinese and English.
@amunchet this script will run on all sites becuase
// @match https://*
// @include /^.*novnc.*/matches all https websites. Changing the two lines above to the single line below will easily fix this I think:
// @match *://*/*novnc*i updated the original script to vanilla js, without external libs
// ==UserScript==
// @name noVNC Paste for Proxmox (Fixed)
// @namespace http://tampermonkey.net/
// @version 0.3
// @description скрипт для вставки текста в noVNC (Proxmox) по правому клику
// @author Chester Enright & AI
// @match https://*:8006/*
// @match https://*/*vnc*
// @include /^.*novnc.*/
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// Функция поочередного ввода символов с правильной задержкой
async function sendString(el, text) {
const delayMs = 10; // Безопасная задержка между символами (в миллисекундах)
for (let i = 0; i < text.length; i++) {
const char = text[i];
const needsShift = /[A-Z!@#$%^&*()_+{}:"<>?~|]/.test(char);
if (needsShift) {
// Нажимаем Shift
el.dispatchEvent(new KeyboardEvent("keydown", { key: "Shift", keyCode: 16, bubbles: true }));
// Нажимаем символ с зажатым Shift
el.dispatchEvent(new KeyboardEvent("keydown", { key: char, shiftKey: true, bubbles: true }));
el.dispatchEvent(new KeyboardEvent("keypress", { key: char, shiftKey: true, bubbles: true }));
el.dispatchEvent(new KeyboardEvent("keyup", { key: char, shiftKey: true, bubbles: true }));
// Отпускаем Shift
el.dispatchEvent(new KeyboardEvent("keyup", { key: "Shift", keyCode: 16, bubbles: true }));
} else {
// Обычный ввод без Shift
el.dispatchEvent(new KeyboardEvent("keydown", { key: char, bubbles: true }));
el.dispatchEvent(new KeyboardEvent("keypress", { key: char, bubbles: true }));
el.dispatchEvent(new KeyboardEvent("keyup", { key: char, bubbles: true }));
// Ждем перед следующим символом, чтобы VNC успел его обработать
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
// Инициализация при клике (работает без jQuery и жестких таймаутов)
document.addEventListener('mousedown', async (e) => {
// Проверяем, что это правый клик (button == 2) и кликнули по canvas (экран VNC)
if (e.button === 2 && e.target.tagName.toLowerCase() === 'canvas') {
const canvas = e.target;
try {
// Запрашиваем текст из буфера обмена
const text = await navigator.clipboard.readText();
if (text) {
console.log(`[noVNC Paste] Вставляю ${text.length} символов...`);
// Отменяем стандартное контекстное меню браузера
e.preventDefault();
await sendString(canvas, text);
}
} catch (err) {
console.error('[noVNC Paste] Не удалось прочитать буфер обмена:', err);
}
}
}, true); // Используем capture-фазу, чтобы перехватить клик раньше встроенных скриптов Proxmox
})();
@Laz2047 I would try the version above by @zakhar-kogan - it might handle carriage returns a bit better (I haven't actually tried it myself). The original version was just meant to paste in long passwords or single line items, so I didn't really consider multi-lines.