|
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 |
@@ -1530,6 +1531,82 @@ async def export(self, place, target): |
1530 | 1531 | def print_version(self): |
1531 | 1532 | print(labgrid_version()) |
1532 | 1533 |
|
| 1534 | + def adb(self): |
| 1535 | + place = self.get_acquired_place() |
| 1536 | + target = self._get_target(place) |
| 1537 | + name = self.args.name |
| 1538 | + adb_cmd = ["adb"] |
| 1539 | + |
| 1540 | + from ..resource.adb import NetworkADBDevice, RemoteADBDevice |
| 1541 | + |
| 1542 | + for resource in target.resources: |
| 1543 | + if name and resource.name != name: |
| 1544 | + continue |
| 1545 | + if isinstance(resource, NetworkADBDevice): |
| 1546 | + host, port = proxymanager.get_host_and_port(resource) |
| 1547 | + adb_cmd = ["adb", "-H", host, "-P", str(port), "-s", resource.serial] |
| 1548 | + elif isinstance(resource, RemoteADBDevice): |
| 1549 | + host, port = proxymanager.get_host_and_port(resource) |
| 1550 | + # ADB does not automatically remove a network device from its |
| 1551 | + # devices list when the connection is broken by the remote, so the |
| 1552 | + # adb connection may have gone "stale", resulting in adb blocking |
| 1553 | + # indefinitely when making calls to the device. To avoid this, |
| 1554 | + # always disconnect first. |
| 1555 | + subprocess.run(["adb", "disconnect", f"{host}:{str(port)}"], stderr=subprocess.DEVNULL, timeout=10) |
| 1556 | + subprocess.run( |
| 1557 | + ["adb", "connect", f"{host}:{str(port)}"], stdout=subprocess.DEVNULL, timeout=10 |
| 1558 | + ) # Connect adb client to TCP adb device |
| 1559 | + adb_cmd = ["adb", "-s", f"{host}:{str(port)}"] |
| 1560 | + |
| 1561 | + adb_cmd += self.args.leftover |
| 1562 | + subprocess.run(adb_cmd) |
| 1563 | + |
| 1564 | + def scrcpy(self): |
| 1565 | + place = self.get_acquired_place() |
| 1566 | + target = self._get_target(place) |
| 1567 | + name = self.args.name |
| 1568 | + scrcpy_cmd = ["scrcpy"] |
| 1569 | + env_var = os.environ.copy() |
| 1570 | + |
| 1571 | + from ..resource.adb import NetworkADBDevice, RemoteADBDevice |
| 1572 | + |
| 1573 | + for resource in target.resources: |
| 1574 | + if name and resource.name != name: |
| 1575 | + continue |
| 1576 | + if isinstance(resource, NetworkADBDevice): |
| 1577 | + host, adb_port = proxymanager.get_host_and_port(resource) |
| 1578 | + |
| 1579 | + # If a proxy is required, we need to setup a ssh port forward for the port |
| 1580 | + # (27183) scrcpy will use to send data along side the adb port |
| 1581 | + if resource.extra.get("proxy_required") or self.args.force_proxy: |
| 1582 | + proxy = resource.extra.get("proxy") |
| 1583 | + scrcpy_port = sshmanager.request_forward(proxy, host, 27183) |
| 1584 | + |
| 1585 | + ip_addr = gethostbyname(host) |
| 1586 | + env_var["ADB_SERVER_SOCKET"] = f"tcp:{ip_addr}:{adb_port}" |
| 1587 | + scrcpy_cmd = [ |
| 1588 | + "scrcpy", |
| 1589 | + "--port", |
| 1590 | + "27183", |
| 1591 | + f"--tunnel-host={ip_addr}", |
| 1592 | + f"--tunnel-port={scrcpy_port}", |
| 1593 | + "-s", |
| 1594 | + resource.serial, |
| 1595 | + ] |
| 1596 | + |
| 1597 | + elif isinstance(resource, RemoteADBDevice): |
| 1598 | + host, port = proxymanager.get_host_and_port(resource) |
| 1599 | + # ADB does not automatically remove a network device from its |
| 1600 | + # devices list when the connection is broken by the remote, so the |
| 1601 | + # adb connection may have gone "stale", resulting in adb blocking |
| 1602 | + # indefinitely when making calls to the device. To avoid this, |
| 1603 | + # always disconnect first. |
| 1604 | + subprocess.run(["adb", "disconnect", f"{host}:{str(port)}"], stderr=subprocess.DEVNULL, timeout=10) |
| 1605 | + scrcpy_cmd = ["scrcpy", f"--tcpip={host}:{str(port)}"] |
| 1606 | + |
| 1607 | + scrcpy_cmd += self.args.leftover |
| 1608 | + subprocess.run(scrcpy_cmd, env=env_var) |
| 1609 | + |
1533 | 1610 |
|
1534 | 1611 | _loop: ContextVar["asyncio.AbstractEventLoop | None"] = ContextVar("_loop", default=None) |
1535 | 1612 |
|
@@ -2031,9 +2108,17 @@ def main(): |
2031 | 2108 | subparser = subparsers.add_parser("version", help="show version") |
2032 | 2109 | subparser.set_defaults(func=ClientSession.print_version) |
2033 | 2110 |
|
| 2111 | + subparser = subparsers.add_parser("adb", help="Run Android Debug Bridge") |
| 2112 | + subparser.add_argument("--name", "-n", help="optional resource name") |
| 2113 | + subparser.set_defaults(func=ClientSession.adb) |
| 2114 | + |
| 2115 | + subparser = subparsers.add_parser("scrcpy", help="Run scrcpy to remote control an android device") |
| 2116 | + subparser.add_argument("--name", "-n", help="optional resource name") |
| 2117 | + subparser.set_defaults(func=ClientSession.scrcpy) |
| 2118 | + |
2034 | 2119 | # make any leftover arguments available for some commands |
2035 | 2120 | args, leftover = parser.parse_known_args() |
2036 | | - if args.command not in ["ssh", "rsync", "forward"]: |
| 2121 | + if args.command not in ["ssh", "rsync", "forward", "adb", "scrcpy"]: |
2037 | 2122 | args = parser.parse_args() |
2038 | 2123 | else: |
2039 | 2124 | args.leftover = leftover |
|
0 commit comments