209
"proxy_bypass": "",
210
"proxy_username": "",
211
"proxy_password": "",
212
+ "keyboard_layout": "",
213
+ "keyboard_variant": "",
214
"model_preset": "",
215
}
216
217
218
+def test_browser_config_normalizes_keyboard_layout():
219
+ config = normalize_browser_config(
220
+ {
221
+ "keyboard_layout": " DE ",
222
+ "keyboard_variant": " Mac ",
223
+ }
224
+ )
225
+
226
+ assert config["keyboard_layout"] == "de"
227
+ assert config["keyboard_variant"] == "mac"
228
+
229
+ cleared = normalize_browser_config(
230
+ {
231
+ "keyboard_layout": "",
232
+ "keyboard_variant": "mac",
233
+ }
234
+ )
235
+
236
+ assert cleared["keyboard_layout"] == ""
237
+ assert cleared["keyboard_variant"] == ""
238
+
239
+ unsafe = normalize_browser_config(
240
+ {
241
+ "keyboard_layout": "de; rm -rf /",
242
+ "keyboard_variant": "mac & echo pwned",
243
+ }
244
+ )
245
+
246
+ for token in (unsafe["keyboard_layout"], unsafe["keyboard_variant"]):
247
+ assert token == token.lower()
248
+ for forbidden in (" ", ";", "&", "|", "$", "`", "'", '"'):
249
+ assert forbidden not in token
250
+
251
+
252
+def test_browser_keyboard_layout_restarts_runtime():
253
+ from plugins._browser.helpers.config import browser_runtime_config
254
+
255
+ base = browser_runtime_config({"keyboard_layout": "de", "keyboard_variant": "mac"})
256
+
257
+ assert base["keyboard_layout"] == "de"
258
+ assert base["keyboard_variant"] == "mac"
259
+
260
+ changed = browser_runtime_config({"keyboard_layout": "us", "keyboard_variant": "mac"})
261
+ assert changed != base
262
+
263
+
264
def test_browser_config_normalizes_model_preset():
265
assert normalize_browser_config({"model_preset": " Research "})["model_preset"] == "Research"
266
assert "model" not in normalize_browser_config({"model": "main"})
2514
assert "PrintStyle.warning" in extension
2515
2516
2517
+
2518
+
2519
+def test_browser_interactive_view_keyboard_options(monkeypatch):
2520
+ import plugins._browser.helpers.interactive_view as iv_module
2521
+
2522
+ view = BrowserInteractiveView("ctx-kb")
2523
+
2524
+ # no layout -> no xpra keyboard args
2525
+ monkeypatch.setattr(
2526
+ iv_module, "keyboard_options", lambda: {"layout": "", "variant": ""}
2527
+ )
2528
+ assert view._keyboard_xpra_args() == []
2529
+
2530
+ # layout + variant -> pinned server layout, sync disabled
2531
+ monkeypatch.setattr(
2532
+ iv_module,
2533
+ "keyboard_options",
2534
+ lambda: {"layout": "de", "variant": "mac"},
2535
+ )
2536
+ assert view._keyboard_xpra_args() == [
2537
+ "--keyboard-sync=no",
2538
+ "--keyboard-layout", "de",
2539
+ "--keyboard-variant", "mac",
2540
+ ]
2541
+
2542
+
2543
+def test_browser_interactive_view_applies_keyboard_layout(monkeypatch):
2544
+ commands = []
2545
+
2546
+ def fake_run(command, **kwargs):
2547
+ commands.append(list(command))
2548
+ return SimpleNamespace(returncode=0)
2549
+
2550
+ monkeypatch.setattr(browser_interactive_view_module.subprocess, "run", fake_run)
2551
+ monkeypatch.setattr(
2552
+ browser_interactive_view_module.shutil,
2553
+ "which",
2554
+ lambda name: "/usr/bin/setxkbmap" if name == "setxkbmap" else None,
2555
+ )
2556
+ monkeypatch.setattr(
2557
+ browser_interactive_view_module,
2558
+ "keyboard_options",
2559
+ lambda: {"layout": "de", "variant": "mac"},
2560
+ )
2561
+
2562
+ view = BrowserInteractiveView("ctx-kb-apply")
2563
+ view.display = 42
2564
+ view._apply_keyboard_layout()
2565
+
2566
+ assert commands == [["/usr/bin/setxkbmap", "-display", ":42", "-layout", "de", "-variant", "mac"]]
2567
+
2568
+ # no configured layout -> no setxkbmap call
2569
+ monkeypatch.setattr(
2570
+ browser_interactive_view_module,
2571
+ "keyboard_options",
2572
+ lambda: {"layout": "", "variant": ""},
2573
+ )
2574
+ view._apply_keyboard_layout()
2575
+ assert len(commands) == 1
2576
+
2577
+
2578
def test_browser_interactive_views_use_isolated_loopback_sessions(monkeypatch, tmp_path):
2579
class FakeProcess:
2580
def __init__(self):