feat: a lesson in Wonder Ink is the size the browser shows it, and zooms
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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
10433b850d
commit
48b3097c43
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user