"""Owner-routed passive HUD configuration and bounded plain-text publications.""" import math import re from .arguments import OVERLAY_TEXT, validate as validate_native ACTIONS = ('status','on','off','clear','global','actor','remove','list','list-clear') ACCENTS = ('normal','warning','critical') # Simple native publishers support Unicode; lists and CLI text use safe ASCII. # The exec transport uses quoted literals, so reject delimiters/quotes. TEXT = OVERLAY_TEXT def payload(args, generation): action = args['action'] if action == 'status': return 'overlay_status','' if action == 'list-clear': return 'overlay_list_clear','' if action in ('on','off','clear'): return 'overlay_config',f"{action}|{args.get('x',24)}|{args.get('y',210)}" key = 'global' if action == 'global' else args['reference'].split('|')[1] if action == 'remove': return 'overlay_remove',f'{generation}|{key}' if action == 'list': rows = args.get('rows',[]) return 'overlay_list', '|'.join((str(generation),key,args.get('title',''), args.get('profile',''),str(len(rows)), '~'.join(rows),args.get('accent','normal'))) return 'overlay_set', '|'.join((str(generation),key,args.get('title',''), '~'.join(args.get('rows',[])),args.get('accent','normal'))) def validate(args): action = args.get('action') if action not in ACTIONS: raise ValueError('overlay action must be '+', '.join(ACTIONS)) allowed = {'action'} if action in ('actor','remove','list'): allowed.add('reference') reference = args.get('reference','') validate_native('actor',reference) if int(reference.split('|')[1]) > 511: raise ValueError('overlay actor ID must be 0..511') if action in ('global','actor','list'): allowed |= {'title','rows','accent'} title,rows = args.get('title',''),args.get('rows',[]) if not isinstance(title,str) or len(title)>32 or not re.fullmatch(TEXT,title): raise ValueError('overlay title: <=32 safe ASCII characters; no quotes, pipes, tildes or markup evaluation') max_rows,max_length = (12,64) if action == 'list' else (3,40) if not isinstance(rows,list) or len(rows)>max_rows or any( not isinstance(row,str) or len(row)>max_length or not re.fullmatch(TEXT,row) for row in rows): raise ValueError(f'overlay rows: at most {max_rows} plain safe ASCII strings, <={max_length} characters each') if args.get('accent','normal') not in ACCENTS: raise ValueError('overlay accent must be normal, warning or critical') if action == 'list': allowed.add('profile') profile = args.get('profile','') if not isinstance(profile,str) or len(profile)>64 or not re.fullmatch(TEXT,profile): raise ValueError('overlay profile: <=64 safe ASCII characters; no transport delimiters') if action == 'on': allowed |= {'x','y'} for key,maximum in (('x',1600),('y',900)): v = args.get(key,24 if key=='x' else 210) if type(v) not in (int,float) or not math.isfinite(v) or not 0<=v<=maximum: raise ValueError(f'overlay {key} must be 0..{maximum} HUD units') if set(args)-allowed: raise ValueError('arguments not applicable to overlay '+action) command,value = payload(args,0) validate_native(command,value) def run(runtime, args): action = args['action'] generation = 0 if action in ('actor','remove','global','list'): snapshot = runtime.call('snapshot') if not snapshot['data'].get('ready'): raise ValueError('overlay_requires_ready_world') generation = snapshot['generation'] if action != 'global' and int(args['reference'].split('|')[0]) != generation: raise ValueError('stale_world_generation: overlay not published') command,value = payload(args,generation) return runtime.call(command,value)