mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
"Type it" sent each character's keysym and let QEMU pick the key from its own US map, with Shift added for the US shifted characters. A VM on another layout got other characters: on Turkish Q, '"' typed 'İ', '@' typed "'", and ğ, ü, ş, ı, ö, ç typed nothing. The Clipboard dialog now asks for the VM's keyboard layout and remembers it per browser. Each character is typed as the key that makes it on that layout (its scancode), with Shift, AltGr or both (AltGr first: pressed after Shift, the right Alt is Alt on the Linux console), so the VM's own layout makes the character. The layout is fixed when typing starts. Text with a character no key types on the layout is not typed at all, and the dialog lists those characters: a password or a command with a character left out would be a wrong one. A layout is one file in console/layouts/: its name and, for each key, the characters it types plainly, with Shift, with AltGr and with Shift+AltGr. The view offers every file, US first. dev/console_layout.py writes a file from the system's XKB data as libxkbcommon computes it (through ctypes, no new dependency), mapping keys to DOM codes with noVNC's table from QEMU's keycodemapdb; it lists the dead keys it leaves out, and drops the keys the keyboard model lacks (the key left of Z on US pc104 keyboards). English (US) and Turkish Q come with it; the README says how to add one.
47 lines
2.1 KiB
Python
47 lines
2.1 KiB
Python
"""The keyboard layout files the VNC console types text with."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from django.conf import settings
|
|
from django.test import SimpleTestCase
|
|
|
|
from console.views import LAYOUTS_DIR
|
|
|
|
SCANCODES = Path(settings.BASE_DIR) / "static/js/novnc/core/input/xtscancodes.js"
|
|
|
|
|
|
def unique_keys(pairs):
|
|
keys = [key for key, _ in pairs]
|
|
if len(keys) != len(set(keys)):
|
|
raise ValueError(f"a key twice: {keys}")
|
|
return dict(pairs)
|
|
|
|
|
|
class LayoutFilesTestCase(SimpleTestCase):
|
|
def test_each_layout_file_is_a_layout(self):
|
|
# the key codes noVNC sends as scancodes
|
|
codes = {line.split('"')[1] for line in SCANCODES.read_text().splitlines() if line.startswith(' "')}
|
|
paths = sorted(LAYOUTS_DIR.glob("*.json"))
|
|
self.assertIn("us", [path.stem for path in paths])
|
|
for path in paths:
|
|
with self.subTest(path.name):
|
|
layout = json.loads(path.read_text(), object_pairs_hook=unique_keys)
|
|
self.assertRegex(path.stem, r"^[a-z]+(-[a-z0-9_]+)?$")
|
|
self.assertEqual(set(layout), {"name", "source", "keys"})
|
|
self.assertTrue(layout["name"])
|
|
for code, characters in layout["keys"].items():
|
|
self.assertIn(code, codes)
|
|
self.assertEqual(len(characters), 4, code)
|
|
for character in characters:
|
|
# one printable character, or none
|
|
self.assertTrue(character is None or (len(character) == 1 and character.isprintable()),
|
|
f"{code}: {character!r}")
|
|
|
|
def test_us_and_turkish_q_type_every_printable_ascii_character(self):
|
|
ascii_characters = {chr(c) for c in range(0x21, 0x7F)}
|
|
for layout_id in ("us", "tr"):
|
|
with self.subTest(layout_id):
|
|
layout = json.loads((LAYOUTS_DIR / f"{layout_id}.json").read_text())
|
|
typed = {c for characters in layout["keys"].values() for c in characters if c}
|
|
self.assertEqual(ascii_characters - typed, set())
|