|
18 | 18 | import json
|
19 | 19 | import itertools
|
20 | 20 | from textwrap import indent
|
21 |
| -from socket import gethostname |
| 21 | +from socket import gethostname, gethostbyname |
22 | 22 | from getpass import getuser
|
23 | 23 | from collections import defaultdict, OrderedDict
|
24 | 24 | from datetime import datetime
|
|
44 | 44 | from ..resource.remote import RemotePlaceManager, RemotePlace
|
45 | 45 | from ..util import diff_dict, flat_dict, dump, atomic_replace, labgrid_version, Timeout
|
46 | 46 | from ..util.proxy import proxymanager
|
| 47 | +from ..util.ssh import sshmanager |
47 | 48 | from ..util.helper import processwrapper
|
48 | 49 | from ..driver import Mode, ExecutionError
|
49 | 50 | from ..logging import basicConfig, StepLogger
|
@@ -1576,6 +1577,100 @@ async def export(self, place, target):
|
1576 | 1577 | def print_version(self):
|
1577 | 1578 | print(labgrid_version())
|
1578 | 1579 |
|
| 1580 | + def adb(self): |
| 1581 | + place = self.get_acquired_place() |
| 1582 | + target = self._get_target(place) |
| 1583 | + name = self.args.name |
| 1584 | + adb_cmd = ["adb"] |
| 1585 | + |
| 1586 | + from ..resource.adb import NetworkADBDevice, RemoteADBDevice |
| 1587 | + |
| 1588 | + for resource in target.resources: |
| 1589 | + if name and resource.name != name: |
| 1590 | + continue |
| 1591 | + if isinstance(resource, NetworkADBDevice): |
| 1592 | + host, port = proxymanager.get_host_and_port(resource) |
| 1593 | + adb_cmd = ["adb", "-H", host, "-P", str(port), "-s", resource.serialno] |
| 1594 | + break |
| 1595 | + elif isinstance(resource, RemoteADBDevice): |
| 1596 | + host, port = proxymanager.get_host_and_port(resource) |
| 1597 | + # ADB does not automatically remove a network device from its |
| 1598 | + # devices list when the connection is broken by the remote, so the |
| 1599 | + # adb connection may have gone "stale", resulting in adb blocking |
| 1600 | + # indefinitely when making calls to the device. To avoid this, |
| 1601 | + # always disconnect first. |
| 1602 | + subprocess.run( |
| 1603 | + ["adb", "disconnect", f"{host}:{str(port)}"], stderr=subprocess.DEVNULL, timeout=10, check=True |
| 1604 | + ) |
| 1605 | + subprocess.run( |
| 1606 | + ["adb", "connect", f"{host}:{str(port)}"], stdout=subprocess.DEVNULL, timeout=10, check=True |
| 1607 | + ) # Connect adb client to TCP adb device |
| 1608 | + adb_cmd = ["adb", "-s", f"{host}:{str(port)}"] |
| 1609 | + break |
| 1610 | + |
| 1611 | + adb_cmd += self.args.leftover |
| 1612 | + subprocess.run(adb_cmd, check=True) |
| 1613 | + |
| 1614 | + def scrcpy(self): |
| 1615 | + place = self.get_acquired_place() |
| 1616 | + target = self._get_target(place) |
| 1617 | + name = self.args.name |
| 1618 | + scrcpy_cmd = ["scrcpy"] |
| 1619 | + env_var = os.environ.copy() |
| 1620 | + |
| 1621 | + from ..resource.adb import NetworkADBDevice, RemoteADBDevice |
| 1622 | + |
| 1623 | + for resource in target.resources: |
| 1624 | + if name and resource.name != name: |
| 1625 | + continue |
| 1626 | + if isinstance(resource, NetworkADBDevice): |
| 1627 | + host, adb_port = proxymanager.get_host_and_port(resource) |
| 1628 | + ip_addr = gethostbyname(host) |
| 1629 | + env_var["ADB_SERVER_SOCKET"] = f"tcp:{ip_addr}:{adb_port}" |
| 1630 | + |
| 1631 | + # Find a free port on the exporter machine |
| 1632 | + scrcpy_port = sshmanager.get(host).run_check( |
| 1633 | + 'python -c "' |
| 1634 | + "import socket;" |
| 1635 | + "s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.bind((" |
| 1636 | + "'', 0));" |
| 1637 | + "addr = s.getsockname();" |
| 1638 | + "print(addr[1]);" |
| 1639 | + 's.close()"' |
| 1640 | + )[0] |
| 1641 | + |
| 1642 | + scrcpy_cmd = [ |
| 1643 | + "scrcpy", |
| 1644 | + "--port", |
| 1645 | + scrcpy_port, |
| 1646 | + "-s", |
| 1647 | + resource.serialno, |
| 1648 | + ] |
| 1649 | + |
| 1650 | + # If a proxy is required, we need to setup a ssh port forward for the port |
| 1651 | + # (27183) scrcpy will use to send data along side the adb port |
| 1652 | + if resource.extra.get("proxy_required") or self.args.proxy: |
| 1653 | + proxy = resource.extra.get("proxy") |
| 1654 | + scrcpy_cmd.append(f"--tunnel-host={ip_addr}") |
| 1655 | + scrcpy_cmd.append(f"--tunnel-port={sshmanager.request_forward(proxy, host, int(scrcpy_port))}") |
| 1656 | + break |
| 1657 | + |
| 1658 | + elif isinstance(resource, RemoteADBDevice): |
| 1659 | + host, port = proxymanager.get_host_and_port(resource) |
| 1660 | + # ADB does not automatically remove a network device from its |
| 1661 | + # devices list when the connection is broken by the remote, so the |
| 1662 | + # adb connection may have gone "stale", resulting in adb blocking |
| 1663 | + # indefinitely when making calls to the device. To avoid this, |
| 1664 | + # always disconnect first. |
| 1665 | + subprocess.run( |
| 1666 | + ["adb", "disconnect", f"{host}:{str(port)}"], stderr=subprocess.DEVNULL, timeout=10, check=True |
| 1667 | + ) |
| 1668 | + scrcpy_cmd = ["scrcpy", f"--tcpip={host}:{str(port)}"] |
| 1669 | + break |
| 1670 | + |
| 1671 | + scrcpy_cmd += self.args.leftover |
| 1672 | + subprocess.run(scrcpy_cmd, env=env_var, check=True) |
| 1673 | + |
1579 | 1674 |
|
1580 | 1675 | _loop: ContextVar["asyncio.AbstractEventLoop | None"] = ContextVar("_loop", default=None)
|
1581 | 1676 |
|
@@ -2092,9 +2187,17 @@ def main():
|
2092 | 2187 | subparser = subparsers.add_parser("version", help="show version")
|
2093 | 2188 | subparser.set_defaults(func=ClientSession.print_version)
|
2094 | 2189 |
|
| 2190 | + subparser = subparsers.add_parser("adb", help="Run Android Debug Bridge") |
| 2191 | + subparser.add_argument("--name", "-n", help="optional resource name") |
| 2192 | + subparser.set_defaults(func=ClientSession.adb) |
| 2193 | + |
| 2194 | + subparser = subparsers.add_parser("scrcpy", help="Run scrcpy to remote control an android device") |
| 2195 | + subparser.add_argument("--name", "-n", help="optional resource name") |
| 2196 | + subparser.set_defaults(func=ClientSession.scrcpy) |
| 2197 | + |
2095 | 2198 | # make any leftover arguments available for some commands
|
2096 | 2199 | args, leftover = parser.parse_known_args()
|
2097 |
| - if args.command not in ["ssh", "rsync", "forward"]: |
| 2200 | + if args.command not in ["ssh", "rsync", "forward", "adb", "scrcpy"]: |
2098 | 2201 | args = parser.parse_args()
|
2099 | 2202 | else:
|
2100 | 2203 | args.leftover = leftover
|
|
0 commit comments