|
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,100 @@ 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.serialno] |
| 1548 | + break |
| 1549 | + elif isinstance(resource, RemoteADBDevice): |
| 1550 | + host, port = proxymanager.get_host_and_port(resource) |
| 1551 | + # ADB does not automatically remove a network device from its |
| 1552 | + # devices list when the connection is broken by the remote, so the |
| 1553 | + # adb connection may have gone "stale", resulting in adb blocking |
| 1554 | + # indefinitely when making calls to the device. To avoid this, |
| 1555 | + # always disconnect first. |
| 1556 | + subprocess.run( |
| 1557 | + ["adb", "disconnect", f"{host}:{str(port)}"], stderr=subprocess.DEVNULL, timeout=10, check=True |
| 1558 | + ) |
| 1559 | + subprocess.run( |
| 1560 | + ["adb", "connect", f"{host}:{str(port)}"], stdout=subprocess.DEVNULL, timeout=10, check=True |
| 1561 | + ) # Connect adb client to TCP adb device |
| 1562 | + adb_cmd = ["adb", "-s", f"{host}:{str(port)}"] |
| 1563 | + break |
| 1564 | + |
| 1565 | + adb_cmd += self.args.leftover |
| 1566 | + subprocess.run(adb_cmd, check=True) |
| 1567 | + |
| 1568 | + def scrcpy(self): |
| 1569 | + place = self.get_acquired_place() |
| 1570 | + target = self._get_target(place) |
| 1571 | + name = self.args.name |
| 1572 | + scrcpy_cmd = ["scrcpy"] |
| 1573 | + env_var = os.environ.copy() |
| 1574 | + |
| 1575 | + from ..resource.adb import NetworkADBDevice, RemoteADBDevice |
| 1576 | + |
| 1577 | + for resource in target.resources: |
| 1578 | + if name and resource.name != name: |
| 1579 | + continue |
| 1580 | + if isinstance(resource, NetworkADBDevice): |
| 1581 | + host, adb_port = proxymanager.get_host_and_port(resource) |
| 1582 | + ip_addr = gethostbyname(host) |
| 1583 | + env_var["ADB_SERVER_SOCKET"] = f"tcp:{ip_addr}:{adb_port}" |
| 1584 | + |
| 1585 | + # Find a free port on the exporter machine |
| 1586 | + scrcpy_port = sshmanager.get(host).run_check( |
| 1587 | + 'python -c "' |
| 1588 | + "import socket;" |
| 1589 | + "s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.bind((" |
| 1590 | + "'', 0));" |
| 1591 | + "addr = s.getsockname();" |
| 1592 | + "print(addr[1]);" |
| 1593 | + 's.close()"' |
| 1594 | + )[0] |
| 1595 | + |
| 1596 | + scrcpy_cmd = [ |
| 1597 | + "scrcpy", |
| 1598 | + "--port", |
| 1599 | + scrcpy_port, |
| 1600 | + "-s", |
| 1601 | + resource.serialno, |
| 1602 | + ] |
| 1603 | + |
| 1604 | + # If a proxy is required, we need to setup a ssh port forward for the port |
| 1605 | + # (27183) scrcpy will use to send data along side the adb port |
| 1606 | + if resource.extra.get("proxy_required") or self.args.proxy: |
| 1607 | + proxy = resource.extra.get("proxy") |
| 1608 | + scrcpy_cmd.append(f"--tunnel-host={ip_addr}") |
| 1609 | + scrcpy_cmd.append(f"--tunnel-port={sshmanager.request_forward(proxy, host, int(scrcpy_port))}") |
| 1610 | + break |
| 1611 | + |
| 1612 | + elif isinstance(resource, RemoteADBDevice): |
| 1613 | + host, port = proxymanager.get_host_and_port(resource) |
| 1614 | + # ADB does not automatically remove a network device from its |
| 1615 | + # devices list when the connection is broken by the remote, so the |
| 1616 | + # adb connection may have gone "stale", resulting in adb blocking |
| 1617 | + # indefinitely when making calls to the device. To avoid this, |
| 1618 | + # always disconnect first. |
| 1619 | + subprocess.run( |
| 1620 | + ["adb", "disconnect", f"{host}:{str(port)}"], stderr=subprocess.DEVNULL, timeout=10, check=True |
| 1621 | + ) |
| 1622 | + scrcpy_cmd = ["scrcpy", f"--tcpip={host}:{str(port)}"] |
| 1623 | + break |
| 1624 | + |
| 1625 | + scrcpy_cmd += self.args.leftover |
| 1626 | + subprocess.run(scrcpy_cmd, env=env_var, check=True) |
| 1627 | + |
1533 | 1628 |
|
1534 | 1629 | _loop: ContextVar["asyncio.AbstractEventLoop | None"] = ContextVar("_loop", default=None)
|
1535 | 1630 |
|
@@ -2031,9 +2126,17 @@ def main():
|
2031 | 2126 | subparser = subparsers.add_parser("version", help="show version")
|
2032 | 2127 | subparser.set_defaults(func=ClientSession.print_version)
|
2033 | 2128 |
|
| 2129 | + subparser = subparsers.add_parser("adb", help="Run Android Debug Bridge") |
| 2130 | + subparser.add_argument("--name", "-n", help="optional resource name") |
| 2131 | + subparser.set_defaults(func=ClientSession.adb) |
| 2132 | + |
| 2133 | + subparser = subparsers.add_parser("scrcpy", help="Run scrcpy to remote control an android device") |
| 2134 | + subparser.add_argument("--name", "-n", help="optional resource name") |
| 2135 | + subparser.set_defaults(func=ClientSession.scrcpy) |
| 2136 | + |
2034 | 2137 | # make any leftover arguments available for some commands
|
2035 | 2138 | args, leftover = parser.parse_known_args()
|
2036 |
| - if args.command not in ["ssh", "rsync", "forward"]: |
| 2139 | + if args.command not in ["ssh", "rsync", "forward", "adb", "scrcpy"]: |
2037 | 2140 | args = parser.parse_args()
|
2038 | 2141 | else:
|
2039 | 2142 | args.leftover = leftover
|
|
0 commit comments