Teachers found lessons in Wonder Ink too small next to the ordinary browser. Measured by asking each page for devicePixelRatio: Wonder Ink follows the display scale exactly (dpr 1.5 at 150%), and Chrome is scaled TWICE (dpr 2.25) — under Plasma it follows the compositor itself and classroom-browser also forces UI_SCALE, a flag from the sway session. The browser is left as it is. Wonder Ink's pages start at zoom = UI_SCALE, the room's own number (1.0 at 100%), and measured afterwards match the browser: dpr 2.25 and 853 px against 2.25 and 854. The window is not zoomed; Qt scales it once, correctly. Ctrl+Plus/=/keypad, Ctrl+Minus, Ctrl+0, Ctrl+wheel and the right-click menu set one level for every tab, remembered as LEVEL= in its own file, relative to the room. Two actions on one key are ambiguous to Qt and it then does neither — Ctrl+Minus did nothing until each command became one action with all its keys, which also fixes Ctrl+Shift+Tab. The engine profile is built by one function, which the lab's checks now use: a check with its own profile showed every worksheet blank. The lab fences 10.10.0.0/16 as well: the development host reaches the real Elmer over Tailscale, and the lab provisions a queue pointing at it. Four lab checks written for older code are corrected; one had failed on every run since 0.2.3. tools/lab/wonderink-zoom-check: 18 checks. Lab: converged 120, print 39, worksheets 24, all passing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
159 lines
5.6 KiB
Bash
Executable File
159 lines
5.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# How large is a lesson in Wonder Ink, and do the keys change it?
|
|
#
|
|
# Runs the INSTALLED classroom-wonderink under a real QtWebEngine
|
|
# (offscreen), builds its window the way main() does, and asks the page
|
|
# itself what it is rendering at. Keys are delivered through Qt's own key
|
|
# path to the focused page — the same shortcut map a keyboard reaches —
|
|
# because the lab's injected keyboard does not reach the nested session.
|
|
# A physical Ctrl+Plus is a hardware check.
|
|
#
|
|
# tools/lab/wonderink-zoom-check
|
|
set -uo pipefail
|
|
|
|
NAME="${LAB_NAME:-classroom-v2-lab}"
|
|
COMMAND="${WONDERINK:-/usr/local/bin/classroom-wonderink}"
|
|
|
|
docker exec -i \
|
|
-e QT_QPA_PLATFORM=offscreen \
|
|
-e QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" \
|
|
-e COMMAND="$COMMAND" \
|
|
"$NAME" bash -c 'export XDG_RUNTIME_DIR=$(mktemp -d) HOME=$(mktemp -d); python3 -' <<'PY'
|
|
import importlib.util, os, sys
|
|
from importlib.machinery import SourceFileLoader
|
|
|
|
loader = SourceFileLoader("wonderink", os.environ["COMMAND"])
|
|
spec = importlib.util.spec_from_loader("wonderink", loader)
|
|
ink = importlib.util.module_from_spec(spec)
|
|
loader.exec_module(ink)
|
|
|
|
from PyQt6.QtCore import QEventLoop, Qt, QTimer
|
|
from PyQt6.QtTest import QTest
|
|
from PyQt6.QtWebEngineCore import QWebEngineProfile
|
|
from PyQt6.QtWidgets import QApplication
|
|
|
|
app = QApplication(sys.argv[:1])
|
|
passed = failed = 0
|
|
|
|
|
|
def check(label, condition, detail=""):
|
|
global passed, failed
|
|
if condition:
|
|
print(f" \033[32mPASS\033[0m {label}")
|
|
passed += 1
|
|
else:
|
|
print(f" \033[31mFAIL\033[0m {label}" + (f" [{detail}]" if detail else ""))
|
|
failed += 1
|
|
|
|
|
|
def wait(ms):
|
|
loop = QEventLoop()
|
|
QTimer.singleShot(ms, loop.quit)
|
|
loop.exec()
|
|
|
|
|
|
def page_dpr(view):
|
|
answer = []
|
|
view.page().runJavaScript("window.devicePixelRatio", answer.append)
|
|
for _ in range(50):
|
|
if answer:
|
|
return answer[0]
|
|
wait(100)
|
|
return None
|
|
|
|
|
|
PAGE = "data:text/html,<title>lesson</title><p>Noah built an ark.</p>"
|
|
KEY = {
|
|
"Ctrl+=": (Qt.Key.Key_Equal, Qt.KeyboardModifier.ControlModifier),
|
|
"Ctrl+Plus": (Qt.Key.Key_Plus, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier),
|
|
"keypad Ctrl+Plus": (Qt.Key.Key_Plus, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.KeypadModifier),
|
|
"Ctrl+Minus": (Qt.Key.Key_Minus, Qt.KeyboardModifier.ControlModifier),
|
|
"keypad Ctrl+Minus": (Qt.Key.Key_Minus, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.KeypadModifier),
|
|
"Ctrl+0": (Qt.Key.Key_0, Qt.KeyboardModifier.ControlModifier),
|
|
"Ctrl+Shift+Tab": (Qt.Key.Key_Backtab, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier),
|
|
}
|
|
|
|
|
|
def press(window, name):
|
|
target = QApplication.focusWidget() or window.current()
|
|
QTest.keyClick(target, *KEY[name])
|
|
wait(400)
|
|
|
|
|
|
def open_window(room_zoom):
|
|
profile = QWebEngineProfile(f"zoomcheck{room_zoom}", app)
|
|
window = ink.WonderInk(profile, room_zoom=room_zoom)
|
|
window.resize(1280, 800)
|
|
window.show()
|
|
for _ in range(3):
|
|
window.open_tab(PAGE)
|
|
wait(2500)
|
|
window.current().setFocus()
|
|
wait(300)
|
|
return window
|
|
|
|
|
|
def factors(window):
|
|
return [round(window._tabs.widget(i).zoomFactor(), 3) for i in range(window._tabs.count())]
|
|
|
|
|
|
print("1. a room's normal size is the browser's")
|
|
for room in (1.0, 1.5, 2.0):
|
|
ink.SETTINGS.unlink(missing_ok=True)
|
|
window = open_window(room)
|
|
check(f"UI_SCALE {room}: every tab starts at {room}", factors(window) == [room] * 3, factors(window))
|
|
window.close()
|
|
wait(300)
|
|
|
|
print()
|
|
print("2. the keys a teacher knows, at UI_SCALE 1.5")
|
|
ink.SETTINGS.unlink(missing_ok=True)
|
|
window = open_window(1.5)
|
|
for name, level in (("Ctrl+=", 1.1), ("Ctrl+Plus", 1.25), ("keypad Ctrl+Plus", 1.5),
|
|
("Ctrl+Minus", 1.25), ("keypad Ctrl+Minus", 1.1), ("Ctrl+0", 1.0)):
|
|
press(window, name)
|
|
check(f"{name} makes it {round(level * 100)}% in every tab",
|
|
window.level() == level and factors(window) == [round(1.5 * level, 3)] * 3,
|
|
f"level={window.level()} factors={factors(window)}")
|
|
|
|
press(window, "Ctrl+Plus")
|
|
dpr = page_dpr(window.current())
|
|
check("and the page itself renders at it", dpr is not None and abs(dpr - 1.65) < 0.01, dpr)
|
|
|
|
print()
|
|
print("3. new tabs, and the next time Wonder Ink opens")
|
|
view = window.open_tab(PAGE)
|
|
wait(1500)
|
|
check("a new tab starts at the teacher's size", round(view.zoomFactor(), 3) == 1.65, view.zoomFactor())
|
|
check("the level is saved on its own", ink.SETTINGS.read_text().strip() == "LEVEL=1.1",
|
|
ink.SETTINGS.read_text())
|
|
check("in its own file, not the web profile",
|
|
"wonderink-shell" not in str(ink.SETTINGS) and ink.SETTINGS.name == "wonderink.conf")
|
|
index = window._tabs.currentIndex()
|
|
press(window, "Ctrl+Shift+Tab")
|
|
check("Ctrl+Shift+Tab still goes back a tab", window._tabs.currentIndex() == index - 1,
|
|
f"{index} -> {window._tabs.currentIndex()}")
|
|
window.close()
|
|
wait(300)
|
|
|
|
window = open_window(2.0)
|
|
check("reopened in a room at 2.0, the teacher's 110% is kept",
|
|
window.level() == 1.1 and factors(window) == [2.2] * 3, factors(window))
|
|
window.close()
|
|
|
|
ink.SETTINGS.write_text("LEVEL=banana\n")
|
|
check("a damaged setting is the normal size", ink.load_level() == 1.0)
|
|
ink.SETTINGS.write_text("LEVEL=40\n")
|
|
check("an absurd one too", ink.load_level() == 1.0)
|
|
|
|
window = open_window(3.0)
|
|
press(window, "keypad Ctrl+Plus"); press(window, "keypad Ctrl+Plus"); press(window, "keypad Ctrl+Plus")
|
|
check("a large room zoomed further never passes the engine's limit",
|
|
max(factors(window)) <= 5.0, factors(window))
|
|
window.close()
|
|
|
|
print()
|
|
print(f"{passed} passed, {failed} failed")
|
|
sys.exit(1 if failed else 0)
|
|
PY
|