diff --git a/vm.py b/vm.py index 7028904d6c4a20a886826e9c21060bcaf2a56e7d..16185340350ea5db9b94c32842cca7a92a8e4f27 100755 --- a/vm.py +++ b/vm.py @@ -239,13 +239,15 @@ class Identification: user: Optional[str] display: Optional[str] wayland_display: Optional[str] + sway_socket: Optional[str] vnc_port: Optional[int] - def __init__(self, vm: str, user: Optional[str] = None, display: Optional[str] = None, wayland_display: Optional[str] = None, vnc_port: Optional[int] = None): + def __init__(self, vm: str, user: Optional[str] = None, display: Optional[str] = None, wayland_display: Optional[str] = None, sway_socket: Optional[str] = None, vnc_port: Optional[int] = None): self.vm = vm self.user = user self.display = display self.wayland_display = wayland_display + self.sway_socket = sway_socket self.vnc_port = vnc_port def __getitem__(self, key): @@ -255,6 +257,14 @@ class Identification: out = self.vm if self.user is not None: out = f"{self.user}@{out}" + if self.vnc_port: + out += f":{self.vnc_port}" + if self.display: + out += f":[{self.display}]" + if self.wayland_display: + out += f":wayland[{self.wayland_display}]" + if self.sway_socket: + out += f":sway[{self.sway_socket}]" return out def set_default_user(self, user: str = "u"): @@ -813,14 +823,36 @@ def modify_net(ucred, vm: str, wan: bool = False, lan: bool = False, pc: bool = # Using vm # ######################## -def ssh_args(ident: Identification, *arg: tuple[str, ...]): +def ssh_args(ident: Identification, *arg_in: tuple[str, ...]): ident.set_default_user() vm, user = ident vm = name_to_id(vm) + + set_env = [] + if ident.display: + set_env.append(f"export DISPLAY={ident.display};") + if ident.wayland_display: + set_env.append(f"export WAYLAND_DISPLAY={ident.wayland_display};") + if ident.sway_socket: + set_env.append(f"export SWAYSOCK={ident.sway_socket};") + arg = ['ssh', "-i", vm_dir(vm)+"id_ed25519", "-o", f"UserKnownHostsFile={vm_dir(vm)}/known_hosts", "-o", "HostKeyAlgorithms=ssh-ed25519", "-o", f"HostKeyAlias=vm_{vm}", - f"{user}@{get_ip(vm)}", *arg] + f"{user}@{get_ip(vm)}"] + + while len(arg_in) and arg_in[0].startswith("-"): + tmp, *arg_in = arg_in + if tmp == '--': + break + arg.append(tmp) + if len(arg_in): + arg += ["--"] + set_env + list(arg_in) + else: + if set_env: + arg += ["-t", "--"] + set_env + ["bash"] + if verbose: print(">>", " ".join(arg)) + return arg @@ -828,20 +860,24 @@ def ssh_args(ident: Identification, *arg: tuple[str, ...]): def ssh(ident: Identification, *arg: tuple[str, ...]): subprocess.run(ssh_args(ident, *arg)) +@cmd +def terminal_ssh(ident: Identification, *arg: tuple[str, ...], terminal: str = "alacritty"): + subprocess.run([terminal, "-e"] + ssh_args(ident, *arg)) + sshfs_root = lambda: os.environ["HOME"]+f"/m/vm/" @internal_cmd def sshfs_mountdir(ident: Identification): - return sshfs_root()+f"/{ident.user}@{name(ident.vm)}" + return sshfs_root()+f"/{ident.user or 'u'}@{name(ident.vm)}" @cmd -def sshfs(vm: str): +def sshfs(ident: Identification): vm, user = ident if user is None: sshfs(Identification(vm, "root")) sshfs(Identification(vm, "u")) return - mount_dir = sshfs_mountdir(vm, user) + mount_dir = sshfs_mountdir(ident) if os.path.isdir(mount_dir) and len(os.listdir(mount_dir)) != 0: return r("mkdir", "-p", mount_dir) @@ -866,6 +902,7 @@ def get_vnc_client_env(ident): vnc_client_env["VNC_PASSWORD"] = open(vm_dir(ident.vm)+"vnc_passwd", "r").read().strip() vnc_client_env["VNC_USERNAME"] = ident.user vnc_client_env["VM_IDENT"] = str(ident) + print(vnc_client_env) return vnc_client_env vncviewer_args = ["-FullscreenSystemKeys=0", "-AcceptClipboard=0", "-SendClipboard=0"] @@ -917,11 +954,12 @@ def vncapp(ident: Identification, cmd: str, wayland: bool = False): # WAYLAND_DISPLAY=wayland-1 SWAYSOCK=/run/user/1000/sway-ipc.1000.6544.sock swaymsg output HEADLESS-1 pos 0 0 res 1920x1080 @cmd -def waydroid(vm: str, *apps: tuple[str, ...]): +def waydroid(ident: Identification, *apps: tuple[str, ...]): if len(apps): - return vncapp(vm, f"(sleep 14; waydroid app launch {apps[0]}) & waydroid show-full-ui", wayland=True) + return vncapp(ident, f"bin/waydroid-run {apps[0]}", wayland=True) + # return vncapp(vm, f"(sleep 14; waydroid app launch {apps[0]}) & waydroid show-full-ui", wayland=True) else: - return vncapp(vm, f"waydroid show-full-ui", wayland=True) + return vncapp(ident, f"bin/waydroid-run", wayland=True) @cmd def vncsession(ident: Identification, wayland: bool = False): @@ -940,6 +978,14 @@ def vncsession(ident: Identification, wayland: bool = False): psutil.wait_procs([vnc_client, vnc_server], callback=on_terminate) ssh(ident, f"systemctl --user stop vncsession-{unit_id}") +@cmd +def resize_wayland(ident: Identification, x: int = None, y: int = None): + if x is None or y is None: + win_place = dict(v.split('=') for v in subprocess.check_output(["xdotool", "getactivewindow", "getwindowgeometry", "--shell"]).decode("utf-8").split('\n') if v) + if x is None: x = int(win_place["WIDTH"]) - 2 + if y is None: y = int(win_place["HEIGHT"]) - 2 + ssh(ident, f"swaymsg output HEADLESS-1 pos 0 0 res {x}x{y}; pgrep waydroid-run | while read p; do echo reload > /proc/$p/fd/0; done"); + @daemon() def chown_qemu_vnc_sock(ucred, vm): vm = name_to_id(vm) @@ -957,7 +1003,7 @@ def str_remove_prefix(s, prefix): return s[len(prefix):] @internal_cmd -def get_vm_by_window(win_id: int = None): +def get_ident_by_window(win_id: int = None): import psutil if win_id is None: win_id = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("utf-8")) @@ -966,10 +1012,9 @@ def get_vm_by_window(win_id: int = None): if '"Alacritty"' in win_class: pass else: - process = psutil.Process(pid=os.getpid()) + process = psutil.Process(pid=pid) process_env: Dict = process.environ() - print(process_env) - return win_class + return extended_name(process_env["VM_IDENT"]) ######################## @@ -1065,7 +1110,7 @@ if is_daemon: @internal_cmd @daemon() def get_prepared_fork(ucred, base: str = "base") -> Optional[str]: - name_to_id(base) + base = name_to_id(base) assert has_read_acces(ucred, base) if base in prepared_forks and len(prepared_forks[base]): pf = prepared_forks[base][0] @@ -1079,6 +1124,7 @@ def get_prepared_fork(ucred, base: str = "base") -> Optional[str]: @cmd @daemon(root_only=True) def prepare_forks(ucred, base: str = "base", count: int =1) -> Optional[str]: + base = name_to_id(base) if not base in prepared_forks: prepared_forks[base] = [] if len(prepared_forks[base]) < count: @@ -1093,6 +1139,7 @@ def prepare_forks(ucred, base: str = "base", count: int =1) -> Optional[str]: @cmd @daemon(root_only=True) def pause_prepared_forks(ucred, base: str = "base", runtime: int = 30): + base = name_to_id(base) for pf in prepared_forks[base]: if not pf.is_paused and pf.start_monotonic_time + runtime <= time.monotonic(): pause(ucred, pf.vm) @@ -1107,45 +1154,101 @@ def get_tmp_vm(base: str = "base"): start(target) return target +def multisplit_toplevel(s, *separators, brackets={'[':']'}): + out = [(None, None)] + stack = [] + i = 0 + begin = 0 + while i < len(s): + if s[i] in brackets: + stack.append(s[i]) + if s[i] in brackets.values(): + assert s[i] == brackets[stack[-1]] + stack.pop() + elif not stack: + for separator in separators: + if s[i:].startswith(separator): + out[-1] = (out[-1][0], s[begin: i]) + out.append((separator, None)) + begin = i+1 + i += 1 + out[-1] = (out[-1][0], s[begin: i]) + assert not stack + return out + +def split_toplevel(s, separator): + return [v for sep, v in multisplit_toplevel(s, separator)] + +def valid_bracket(s, brackets={'[':']'}): + stack = [] + for c in s: + if c in brackets: + stack.append(c) + if c in brackets.values(): + if c != brackets[stack[-1]]: return False + stack.pop() + return not stack + +def is_in_bracket(s, left='[', right=']'): + return s[0] == left and s[-1] == right and valid_bracket(s[1:-1], {left: right}) + @cmd def extended_name(name: str) -> tuple[str, str]: assert not is_daemon vm = name user = None - if len(vm.split("@"))==2: + if len(split_toplevel(vm, "@"))==2: user, vm = vm.split("@") do_power_on = False do_power_on_display= False net_options = None permanency = None - if len(vm.split("$"))==2: - vm, tmp = vm.split("$") - do_power_on = True - do_power_on_display = True + if len(multisplit_toplevel(vm, "!", "$"))==2: + (_, vm), (mark, tmp) = multisplit_toplevel(vm, "!", "$") assert tmp == "" - - if len(vm.split("!"))==2: - vm, tmp = vm.split("!") do_power_on = True - assert tmp == "" + do_power_on_display = mark = '$' - if len(vm.split("~"))==2: - vm, net_options = vm.split("~") - if len(vm.split("^"))==2: - vm, permanency = vm.split("^") + (_, vm), *modifires = multisplit_toplevel(vm, "~", "^", ":") - if len(vm.split("+"))==2: - base, vm = vm.split("+") - if not vm : - vm = get_tmp_vm(base or "base") + if is_in_bracket(vm, '[', ']'): + ident = get_ident_by_window(int(vm[1:-1]) if len(vm) >= 3 else None) + vm = ident.vm + if user is None: user = ident.user + elif len(split_toplevel(vm, "+"))==2: + base, vm = split_toplevel(vm, "+") + base_ident = extended_name(base or "base") + if not vm: + vm = get_tmp_vm(base_ident.vm) else: - vm = clone(vm, base or "base") + vm = clone(vm, base_ident.vm) start(vm) wait_started(vm) - - vm = name_to_id(vm) + ident = Identification(vm) + else: + vm = name_to_id(vm) + ident = Identification(vm) + + + for key, val in modifires: + if key == '~': + assert net_options is None + net_options = val + if key == '^': + assert permanency is None + permanency = val + if key == ':': + if val.isnumeric(): + ident.vnc_port = int(val) + elif val.startswith("[") and val.endswith("]"): + ident.display = val[len("["):-len("]")] + elif val.startswith("wayland[") and val.endswith("]"): + ident.wayland_display = val[len("wayland["):-len("]")] + elif val.startswith("sway[") and val.endswith("]"): + ident.sway_socket = val[len("sway["):-len("]")] + else: assert False if do_power_on: if state(vm) != state_running: @@ -1157,7 +1260,10 @@ def extended_name(name: str) -> tuple[str, str]: modify_net(vm, wan="w" in net_options, lan="l" in net_options, pc="p" in net_options or "P" in net_options, pc_all="P" in net_options) if permanency is not None: set_permanency(vm, permanency or "stable") - return Identification(vm, user) + + ident.vm = vm + ident.user = user + return ident @cmd def eval(ident: Identification):