Add terminal VM menu and focused session handoff
This commit is contained in:
@@ -130,7 +130,7 @@ identities = {name: {'pid': manager.paths(name).pid.read_text().strip(),
|
||||
assert first['pid'] != second['pid'], 'compositor was not refreshed'
|
||||
logs = {name: root / (name + '.log') for name in ('alpha', 'beta')}
|
||||
for name, path in logs.items():
|
||||
wait_for(lambda: path.exists() and 'set_title("HydraOS: ' + name + '")' in path.read_text(),
|
||||
wait_for(lambda: path.exists() and 'set_title("Hydra OS: ' + name + '")' in path.read_text(),
|
||||
name + ' product viewer did not connect')
|
||||
viewers = {name: json.loads((root / (name + '-viewer.json')).read_text()) for name in logs}
|
||||
assert len({data['config'] for data in viewers.values()}) == 2, 'viewer settings were shared'
|
||||
@@ -141,10 +141,28 @@ identities = {name: {'pid': manager.paths(name).pid.read_text().strip(),
|
||||
assert '--spice-usbredir-redirect-on-connect=-1,-1,-1,-1,0' in data['argv']
|
||||
alog, blog = logs['alpha'], logs['beta']
|
||||
display = next(p.name for p in xdg.glob('wayland-*') if not p.name.endswith('.lock'))
|
||||
# Exercise a real menu selection of the second, already running VM.
|
||||
# Its request refreshes only the private compositor/viewers.
|
||||
before_beta_focus = len(focus_events(blog))
|
||||
manifest = Path(second['config_directory']).parent / 'session.json'
|
||||
with (root / 'selection.log').open('w') as selection_log:
|
||||
selection = subprocess.run(cli + ['_session-helper', 'recover', str(manifest)],
|
||||
input='beta\n', text=True,
|
||||
env=dict(env, WAYLAND_DISPLAY=display),
|
||||
stdout=selection_log, stderr=selection_log, timeout=20)
|
||||
assert selection.returncode == 0, 'menu selection failed'
|
||||
wait_for(lambda: (root / 'snapshot-2.json').exists(), 'selected menu did not refresh compositor', timeout=15)
|
||||
third = json.loads((root / 'snapshot-2.json').read_text())
|
||||
from xml.etree import ElementTree as ET
|
||||
assert ET.fromstring((root / 'snapshot-2.xml').read_text()).findtext('./desktops/initial') == '2'
|
||||
wait_for(lambda: any(p.name.startswith('wayland-') and not p.name.endswith('.lock')
|
||||
for p in xdg.iterdir()), 'refreshed private display is not ready')
|
||||
display = next(p.name for p in xdg.glob('wayland-*') if not p.name.endswith('.lock'))
|
||||
keyboard_env = dict(env, WAYLAND_DISPLAY=display)
|
||||
injector = subprocess.Popen([args.keyboard], env=keyboard_env, stdin=subprocess.PIPE, text=True)
|
||||
time.sleep(.2)
|
||||
wait_for(lambda: focus_events(alog) and focus_events(alog)[-1] == 'enter', 'alpha did not focus')
|
||||
wait_for(lambda: len(focus_events(blog)) > before_beta_focus and focus_events(blog)[-1] == 'enter',
|
||||
'second VM did not gain fresh focus after menu selection')
|
||||
|
||||
def switch(code, source_log, target_log):
|
||||
before_source, before_target = focus_events(source_log), focus_events(target_log)
|
||||
@@ -153,10 +171,10 @@ identities = {name: {'pid': manager.paths(name).pid.read_text().strip(),
|
||||
wait_for(lambda: fresh_focus_transition(before_source, before_target,
|
||||
focus_events(source_log), focus_events(target_log)), 'fresh reserved focus transition failed')
|
||||
|
||||
switch(106, alog, blog)
|
||||
intervals = {'alpha': measure_hidden_interval(lambda: counter(manager, 'alpha'), alog, blog)}
|
||||
switch(105, blog, alog)
|
||||
intervals['beta'] = measure_hidden_interval(lambda: counter(manager, 'beta'), blog, alog)
|
||||
intervals = {'beta': measure_hidden_interval(lambda: counter(manager, 'beta'), blog, alog)}
|
||||
switch(106, alog, blog)
|
||||
intervals['alpha'] = measure_hidden_interval(lambda: counter(manager, 'alpha'), alog, blog)
|
||||
before = json.loads((root / 'before.json').read_text())
|
||||
after = {name: {'pid': manager.paths(name).pid.read_text().strip(),
|
||||
'uuid': manager.paths(name).uuid, 'counter': counter(manager, name)}
|
||||
@@ -166,15 +184,16 @@ identities = {name: {'pid': manager.paths(name).pid.read_text().strip(),
|
||||
assert after[name]['counter'] != before[name]['counter']
|
||||
# Only the PID recorded by our own wrapper, verified against its private
|
||||
# config path, may be signalled. Never inherit a host compositor PID.
|
||||
command = Path('/proc') / str(second['pid']) / 'cmdline'
|
||||
assert second['config_directory'].encode() in command.read_bytes()
|
||||
os.kill(second['pid'], signal.SIGTERM)
|
||||
command = Path('/proc') / str(third['pid']) / 'cmdline'
|
||||
assert third['config_directory'].encode() in command.read_bytes()
|
||||
os.kill(third['pid'], signal.SIGTERM)
|
||||
assert launcher.wait(timeout=10) == 0, 'product session exit failed'
|
||||
for name in before:
|
||||
assert manager.status(name)['status'] == 'running'
|
||||
assert manager.paths(name).pid.read_text().strip() == before[name]['pid']
|
||||
result.update(initial_names=first['names'], refreshed_names=second['names'],
|
||||
compositor_pids=[first['pid'], second['pid']], before=before, after=after,
|
||||
compositor_pids=[first['pid'], second['pid'], third['pid']],
|
||||
selected_name='beta', selected_workspace=2, before=before, after=after,
|
||||
viewers=viewers,
|
||||
hidden_intervals=intervals, survived_session_exit=True,
|
||||
expected_xwayland_diagnostics=classify_compositor_errors((root / 'session.log').read_text()),
|
||||
|
||||
Reference in New Issue
Block a user