Skip to content

Instantly share code, notes, and snippets.

@amunchet
Last active July 17, 2024 17:14
Show Gist options
  • Save amunchet/4cfaf0274f3d238946f9f8f94fa9ee02 to your computer and use it in GitHub Desktop.
Save amunchet/4cfaf0274f3d238946f9f8f94fa9ee02 to your computer and use it in GitHub Desktop.
Copy/Paste for noVNC Proxmox
// ==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);
})
})()
@117503445
Copy link

117503445 commented Feb 12, 2023

After applying this script, I can't open the option tab in syncthing's dashboard
The error is

logbar.js:11 TypeError: $(...).tab is not a function
    at editFolderModal (syncthingController.js:2056:57)
    at editFolder (syncthingController.js:2125:13)
    at $scope.editFolderExisting (syncthingController.js:2169:13)
    at $parseFunctionCall (angular.js:12493:18)
    at callback (angular.js:21719:17)
    at Scope.$eval (angular.js:14589:28)
    at Scope.$apply (angular.js:14688:23)
    at HTMLButtonElement.<anonymous> (angular.js:21724:23)
    at HTMLButtonElement.dispatch (jquery-2.2.2.js:4755:27)
    at elemData.handle (jquery-2.2.2.js:4567:28)

Currently my workaround is to modify the script's match to only work on pve's pages

@amunchet
Copy link
Author

amunchet commented Feb 12, 2023

Oh yeah, definitely change the @match directive to fit your needs - I have it as a catch all, but that's something each user should adjust themselves.

@yuriw
Copy link

yuriw commented Feb 18, 2023

Can you please provide steps how to install and use this script?

thx

@amunchet
Copy link
Author

Sure!

  1. Install Tampermonkey for your browser (https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en)
  2. Open up this script in "Raw" (or go to this url: https://gist.github.com/amunchet/4cfaf0274f3d238946f9f8f94fa9ee02/raw/0b84970f89e1f282f09b86d46227eda71178c040/noVNCCopyPasteProxmox.user.js)
  3. Click install
  4. [Optional] if you have a non-US keyboard layout, you may need to modify the script.
  5. Copy some text to your clipboard (highlight some text and Ctrl + C)
  6. Open up your Promox console
  7. Right click to paste the text into the console (you may need to restart your browser, depending on your tampermonkey installation)

Note: if you use LXC containers, you do not need this script, as you can just copy and paste normally from that terminal.

If you have any other questions, please feel free to ask!

@yuriw
Copy link

yuriw commented Feb 18, 2023

@amunchet that actually worked!
Thx a million.
Now I need to understand of I want to use this solution vs Spice session.
But it is great!

@yuriw
Copy link

yuriw commented Feb 18, 2023

I wonder - will it work between iPad and the proxmox console?

@amunchet
Copy link
Author

I think you can install Tampermonkey on iOS - the question is how well Proxmox's web console works on mobile. I think you would just two finger press on the canvas area of the VM and it would paste.

You could also modify the script to create a button and then press it to type the text in (as someone did in one of the above comments). That might work better if the iPad is fussy about "right click" (or two finger press).

@yuriw
Copy link

yuriw commented Feb 19, 2023

@amunchet thx a million for this script!

I need to remove and install it on different system.
I can follow the steps from above to install it.

what’s the proper way to remove it?

@amunchet
Copy link
Author

You can either uninstall Tampermonkey as a browser extension or you can just click on the Tampermonkey extension (you might have to allow it to be shown on the browser extension bar), then click Delete from there.

@yuriw
Copy link

yuriw commented Feb 19, 2023

🙌

@felix822
Copy link

I'm still seeing this code when viewing Raw form:

if(e.button == 2){ // Right Click
navigator.clipboard.readText().then(text =>{
window.sendString(text);
})
}

@amunchet
Copy link
Author

@felix822 Not 100% sure what you're trying to do. Do you want to do the popup instead of using the right click?

If so, use this reply and modify the script as needed: https://gist.github.com/amunchet/4cfaf0274f3d238946f9f8f94fa9ee02?permalink_comment_id=4464804#gistcomment-4464804

@felix822
Copy link

Thanks for the quick reply. No, I would like to use the right click function. I followed the steps to install it, found here - https://gist.github.com/amunchet/4cfaf0274f3d238946f9f8f94fa9ee02?permalink_comment_id=4475025#gistcomment-4475025

But when I right click in the guest console, I get an error message:

"noVNC encountered an error:
TypeError: navigator.clipboard.readText is not a function
eval:52:41"

@felix822
Copy link

Sorry, I should have mentioned that this was in Firefox. I just tried the same thing in Chrome and it works. Any work-around for FF?

Thanks!

@shalak
Copy link

shalak commented Feb 23, 2023

@felix822 I've posted a workaround in one of my previous comments. Firefox doesn't allow to read clipboard, you must go with popup.

So first install the script, as described by @amunchet, then apply the fix I posted.

@dhoenig
Copy link

dhoenig commented Apr 18, 2023

I've got Germany keyboard layout and I wonder if the key mapping might need a lot more code to be accomplished.
I did update the needs_shift var, but there is lot more to be done as there are additional keys for the Umlauts (äüö) as well as swapping the y with the z. Is this easily possible, has anybody done this yet?

@amunchet
Copy link
Author

I don't have a German keyboard, so I'm not 100% sure, but I would imagine that the umlaut key is a modifier like shift.

You would just need another section of code like this:

                 var needs_shift = x.match(/[A-Z!@#$%^&*()_+{}:\"<>?~|]/)
                
                 // New code
                 var needs_umlaut = x.match(/äüö/)
             
                 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 if(needs_umlaut){
                     
                    var umlaut_keycode = 999999999999999999999 // Replace with the keyCode for the umlaut modifier is.
                      
                     evt = new KeyboardEvent("keydown", {keyCode: umlaut_keycode})
                     el.dispatchEvent(evt)

                     evt = new KeyboardEvent("keydown", {key: x})  // You might need to modify this, I'm not sure.  You can try it without, but it might need some reading of documentation on KeyboardEvents for German keyboards.  Would be something like the above code with the shiftKey

                     el.dispatchEvent(evt)
                     evt = new KeyboardEvent("keyup", {keyCode: umlaut_keycode})
                     el.dispatchEvent(evt)
                 }else{
                     evt = new KeyboardEvent("keydown", {key: x})
                }

I hope that gets you pointed in at least the right direction!

@dhoenig
Copy link

dhoenig commented Apr 19, 2023

Unfortunately, Umlauts are not like the shift key, they are additional keys that exist only in specific layouts like German.
Here's the German layout to demonstrate the differences to the English one:
image

  • I have marked the Umlauts extra keys in red. They are not like shift, but shift works on these (normal letters) to capitalize them
  • z and y are swapped, marked in green
  • to make things worse, there is yet another shift-like modifier key called Alt Gr (marked blue, to the right of the Space key). This modifier works on the other keys in that, if pressed, you get the dark blue result displayed on them

That must look pretty weird to English speakers (I guess it is ;)

@amunchet
Copy link
Author

amunchet commented Apr 19, 2023

(I actually took German in Jr. High, so it's not that odd to me :)....not as weird as like the Japanese layout).

Couple of thoughts:

  • The Alt Gr needs to have the shift style applied (like in the above example)
  • You'll probably need to just swap Z and Y in the javascript. This can be done like this (probably can be done better):
  x = x.split('').map(function(c) {
    if (c === 'z') {
      return 'y';
    } else if (c === 'y') {
      return 'z';
    } else {
      return c;
    }
  }).join('');
  • What keys do the Umlauts (U, O, and A) appear as? You will probably need to do some kind of swap for them as well.

This script's basic operating principle is to take a string and then pretend to type it into Proxmox via Javascript from the browser. I don't have a deep understanding into how the layouts for each browser work - there may be a way to adjust the internal key mappings that I'm
not aware of. KeyboardEvent is the crux of the idea - maybe there is some documentation on how that works for international layouts.

There may also be a way to temporarily switch your keyboard layout to US-en for the duration of the paste, then switch it immediately back. I think the internationalization issues all stem from trying to type letters into an international layout and emulating those keycodes.

@amosquet
Copy link

amosquet commented Jun 8, 2023

I have a QWERTY keyboard and for some reason, the script is replacing lowercase with uppercase and uppercase with lowercase. There also seems to be a character limit?

Is this just me?

@amunchet
Copy link
Author

amunchet commented Jun 8, 2023

So what's happening in this script is the keyboard itself is being emulated. If you have uppercase and lowercase switching, then it's likely something strange is happening on your keyboard (dumb question, but are you certain caps lock isn't on?)

If that's just the way your keyboard is, then change this line:
from

var needs_shift = x.match(/[A-Z!@#$%^&*()_+{}:\"<>?~|]/)

to

var needs_shift = x.match(/[!@#$%^&*()_+{}:\"<>?~|]/)

That removes the script pushing shift to get to those particular letters.

I don't think there should be a character limit in the script, but depending on your browser and operating system, there may be a clipboard character limit that's getting hit. I do know that some ad-block software might cause weird issues like that as well.

@d0zingcat
Copy link

Awesome! It saves my life!

@jorgepsmatos
Copy link

I have a QWERTY keyboard and for some reason, the script is replacing lowercase with uppercase and uppercase with lowercase. There also seems to be a character limit?

Is this just me?

I have the character limit issue too. Looking at the code, I have no clue what could be causing it

@amunchet
Copy link
Author

@jorgepsmatos First thing I can think of is your browser or OS might be limiting the number of characters in your clipboard. Try to see if you can copy and paste that number of characters into your specific browser in other situations (like into the address bar or something). The other possibility may be a timeout with Tampermonkey - there might be a setting somewhere that kills the script after a certain run period (if you're trying to paste a large amount of text). Hope that helps!

@oreganmike
Copy link

Absolute legend. Thanks.

@flightless22
Copy link

flightless22 commented Jan 2, 2024

For anyone interested, I removed the clipboard dependency by changing:

                if(e.button == 2){ // Right Click
                    navigator.clipboard.readText().then(text =>{
                        window.sendString(text);
                    })
                }

Into:

                if(e.button == 2){ // Right Click
                    let text = prompt("Enter text to paste:");
                    if (text != null) window.sendString(text);
                }

That way, you can paste the text into the native JS prompt.

I got errors with the original script. it needs to check if clipboard access is present and than use prompt or other method as work around. otherwise now it's perfect. almost. I also encountered the same issue with text getting cut off at the end with really long text. I'm using mint / firefox / violentmonkey

@dan3805
Copy link

dan3805 commented Mar 23, 2024

Wow! Thank you so much! This script is really nice!

@CrazyWolf13
Copy link

(I actually took German in Jr. High, so it's not that odd to me :)....not as weird as like the Japanese layout).

Couple of thoughts:

  • The Alt Gr needs to have the shift style applied (like in the above example)
  • You'll probably need to just swap Z and Y in the javascript. This can be done like this (probably can be done better):
  x = x.split('').map(function(c) {
    if (c === 'z') {
      return 'y';
    } else if (c === 'y') {
      return 'z';
    } else {
      return c;
    }
  }).join('');
  • What keys do the Umlauts (U, O, and A) appear as? You will probably need to do some kind of swap for them as well.

This script's basic operating principle is to take a string and then pretend to type it into Proxmox via Javascript from the browser. I don't have a deep understanding into how the layouts for each browser work - there may be a way to adjust the internal key mappings that I'm not aware of. KeyboardEvent is the crux of the idea - maybe there is some documentation on how that works for international layouts.

There may also be a way to temporarily switch your keyboard layout to US-en for the duration of the paste, then switch it immediately back. I think the internationalization issues all stem from trying to type letters into an international layout and emulating those keycodes.

Hi
Any updates on this?
I'd really love such a feature, as with the current code I can barely even use it.

Edit:
Swiss Keyboard Layout is even better:
image

@pesposito
Copy link

pesposito commented Jun 8, 2024

Hello all,
Thank you for this excellent script !
I forked your script to adapt french mac keyboard like this one :
macfr

I have to work more to be able to add all the keys but it's a good start.

Working key in french mac keyboard :
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890*=-_+&"(;/

Not working for now : :)[]{}!\' .....

    const keyMap = {

        'a': { key: 'q', keyCode: 65 }, 'b': { key: 'b', keyCode: 66 }, 'c': { key: 'c', keyCode: 67 },
        'd': { key: 'd', keyCode: 68 }, 'e': { key: 'e', keyCode: 69 }, 'f': { key: 'f', keyCode: 70 },
        'g': { key: 'g', keyCode: 71 }, 'h': { key: 'h', keyCode: 72 }, 'i': { key: 'i', keyCode: 73 },
        'j': { key: 'j', keyCode: 74 }, 'k': { key: 'k', keyCode: 75 }, 'l': { key: 'l', keyCode: 76 },
        'm': { key: ':', keyCode: 191 }, 'n': { key: 'n', keyCode: 78 }, 'o': { key: 'o', keyCode: 79 },
        'p': { key: 'p', keyCode: 80 }, 'q': { key: 'a', keyCode: 81 }, 'r': { key: 'r', keyCode: 82 },
        's': { key: 's', keyCode: 83 }, 't': { key: 't', keyCode: 84 }, 'u': { key: 'u', keyCode: 85 },
        'v': { key: 'v', keyCode: 86 }, 'w': { key: 'z', keyCode: 87 }, 'x': { key: 'x', keyCode: 88 },
        'y': { key: 'y', keyCode: 89 }, 'z': { key: 'w', keyCode: 90 },
        'A': { key: 'Q', keyCode: 65, shiftKey: true }, 'B': { key: 'B', keyCode: 66, shiftKey: true },
        'C': { key: 'C', keyCode: 67, shiftKey: true }, 'D': { key: 'D', keyCode: 68, shiftKey: true },
        'E': { key: 'E', keyCode: 69, shiftKey: true }, 'F': { key: 'F', keyCode: 70, shiftKey: true },
        'G': { key: 'G', keyCode: 71, shiftKey: true }, 'H': { key: 'H', keyCode: 72, shiftKey: true },
        'I': { key: 'I', keyCode: 73, shiftKey: true }, 'J': { key: 'J', keyCode: 74, shiftKey: true },
        'K': { key: 'K', keyCode: 75, shiftKey: true }, 'L': { key: 'L', keyCode: 76, shiftKey: true },
        'M': { key: ':', keyCode: 220, shiftKey: true }, 'N': { key: 'N', keyCode: 78, shiftKey: true },
        'O': { key: 'O', keyCode: 79, shiftKey: true }, 'P': { key: 'P', keyCode: 80, shiftKey: true },
        'Q': { key: 'A', keyCode: 81, shiftKey: true }, 'R': { key: 'R', keyCode: 82, shiftKey: true },
        'S': { key: 'S', keyCode: 83, shiftKey: true }, 'T': { key: 'T', keyCode: 84, shiftKey: true },
        'U': { key: 'U', keyCode: 85, shiftKey: true }, 'V': { key: 'V', keyCode: 86, shiftKey: true },
        'W': { key: 'Z', keyCode: 87, shiftKey: true }, 'X': { key: 'X', keyCode: 88, shiftKey: true },
        'Y': { key: 'Y', keyCode: 89, shiftKey: true }, 'Z': { key: 'W', keyCode: 90, shiftKey: true },
        '1': { key: '&', keyCode: 49, shiftKey: true }, '2': { key: 'é', keyCode: 50, shiftKey: true },
        '3': { key: '"', keyCode: 51, shiftKey: true }, '4': { key: '\'', keyCode: 52, shiftKey: true },
        '5': { key: '(', keyCode: 53, shiftKey: true }, '6': { key: '§', keyCode: 54, shiftKey: true },
        '7': { key: 'è', keyCode: 55, shiftKey: true }, '8': { key: '!', keyCode: 56, shiftKey: true },
        '9': { key: 'ç', keyCode: 57, shiftKey: true }, '0': { key: 'à', keyCode: 48, shiftKey: true },
        '=': { key: '=', keyCode: 187 }, // OK
        '(': { key: '(', keyCode: 53, }, // OK
        '&': { key: '&', keyCode: 49,}, // OK
        ';': { key: ',', keyCode: 188 }, // OK
        '"': { key: '"', keyCode: 51, }, // OK
        '+': { key: '+', keyCode: 187, shiftKey: true }, // OK
        '_': { key: '_', keyCode: 56 }, // OK
        '*': { key: '\\', keyCode: 220}, // OK
        '-': { key: '^', keyCode: 219 }, // OK
        '/': { key: '.', keyCode: 188, shiftKey: true }, // OK
/*        '/': { key: '/', keyCode: 191 },
        ':': { key: '/', keyCode: 186 },
        ')': { key: '°', keyCode: 48 },
        '[': { key: '^', keyCode: 219 },
        ']': { key: '$', keyCode: 221 },
        '{': { key: '^', keyCode: 219 },
        '}': { key: '4', keyCode: 221 },
        '!': { key: '', keyCode: 56 },
        '\'': { key: '4', keyCode: 52 }, */

    };

For anyone interested, I removed the clipboard dependency by changing:

Thank you too @flightless22, I used your code too.

@teneburu
Copy link

merci beaucoup <3 you're the best @pesposito

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment