Skip to content

Commit 825f23d

Browse files
committed
UI rework
- Split into two different commands: `connect` and `pair` - Add some CLI flags, replacing interactive prompts - Shorten binary name to `adb-helper`
1 parent f3a555f commit 825f23d

12 files changed

Lines changed: 433 additions & 368 deletions

File tree

.github/workflows/go.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ jobs:
3232
goos: ${{ matrix.goos }}
3333
goarch: ${{ matrix.goarch }}
3434
compress_assets: OFF
35+
binary_name: "adb-helper"
36+
ldflags: "-s -w"

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ go.work.sum
2525
.env
2626

2727
.idea
28+
29+
adb-helper

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ CLI tool to simplify some ADB operations
44
Usage:
55

66
```shell
7-
adb-helper-cli
7+
adb-helper connect
88
```
99

1010
With custom timeout (in seconds, default is 5)
1111

1212
```shell
13-
adb-helper-cli --timeout 15
13+
adb-helper --timeout 15
1414
```
1515

1616
If ADB executable is not in your system Path, you can specify it using `--adb`:
1717

1818
```shell
19-
adb-helper-cli --adb "C:\Android\platform-tools\adb.exe"
19+
adb-helper --adb "C:\Android\platform-tools\adb.exe"
2020
```
2121

2222
## Roadmap

cmd/connect.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"downace/adb-helper-cli/internal/adb"
6+
"downace/adb-helper-cli/internal/mdns"
7+
"downace/adb-helper-cli/internal/ui"
8+
"fmt"
9+
"github.com/libp2p/zeroconf/v2"
10+
"github.com/spf13/cobra"
11+
"github.com/ttacon/chalk"
12+
"strings"
13+
"time"
14+
)
15+
16+
var timeout uint
17+
var connectFirst bool
18+
19+
var connectCmd = &cobra.Command{
20+
Use: "connect",
21+
Short: "Search devices and connect to them",
22+
Run: run,
23+
GroupID: cmdGroupApp,
24+
}
25+
26+
func init() {
27+
rootCmd.AddCommand(connectCmd)
28+
29+
connectCmd.Flags().UintVarP(&timeout, "timeout", "t", 5, "Search timeout in seconds. Specify 0 to search indefinitely")
30+
connectCmd.Flags().BoolVarP(&connectFirst, "use-first", "f", false, "When first device found, stop searching and connect to this device")
31+
}
32+
33+
func run(_ *cobra.Command, _ []string) {
34+
var hosts []*mdns.DeviceWithHost
35+
36+
hosts = discover(time.Second*time.Duration(timeout), connectFirst)
37+
38+
if len(hosts) == 0 {
39+
fmt.Println(chalk.Yellow.Color("No devices found"))
40+
fmt.Println(
41+
chalk.Magenta.Color("TIP: Ensure that device is paired. You can use"),
42+
chalk.Cyan.Color("pair"),
43+
chalk.Magenta.Color("command"),
44+
)
45+
return
46+
}
47+
48+
var host *mdns.DeviceWithHost
49+
50+
if connectFirst {
51+
host = hosts[0]
52+
} else {
53+
host = hosts[ui.SelectPrompt("Select device to connect:", hosts)]
54+
}
55+
56+
if host == nil {
57+
return
58+
}
59+
60+
connectToHost(host)
61+
}
62+
63+
func discover(timeout time.Duration, stopOnFirst bool) []*mdns.DeviceWithHost {
64+
fmt.Println(chalk.Blue.Color("Searching devices..."))
65+
66+
hosts := make([]*mdns.DeviceWithHost, 0)
67+
68+
showTip := true
69+
70+
mdns.DiscoverServices(timeout, "_adb-tls-connect._tcp", func(entry *zeroconf.ServiceEntry, stop context.CancelFunc) {
71+
for _, ip := range entry.AddrIPv4 {
72+
host := mdns.Host{Addr: ip, Port: entry.Port}
73+
device := mdns.DeviceWithHost{
74+
Label: fmt.Sprintf("%s (%s)", host.String(), entry.ServiceRecord.Instance),
75+
ServiceEntry: entry,
76+
Host: host,
77+
}
78+
fmt.Println(chalk.Green.Color(fmt.Sprintf("Device found: %v", device)))
79+
if showTip {
80+
fmt.Println(chalk.Magenta.Color("TIP: You can add --use-first to immediately connect to the first found device"))
81+
showTip = false
82+
}
83+
hosts = append(hosts, &device)
84+
}
85+
86+
if stopOnFirst {
87+
stop()
88+
}
89+
})
90+
91+
return hosts
92+
}
93+
94+
func connectToHost(host *mdns.DeviceWithHost) {
95+
output, err := adb.ExecAdb("connect", host.Host.String())
96+
if err != nil {
97+
return
98+
}
99+
// `adb connect` returns exit code 0 irrespective of whether the connection is established or not.
100+
if strings.Contains(output, "failed to connect") {
101+
fmt.Println(chalk.Red.Color(output))
102+
fmt.Println(chalk.Magenta.Color("TIP: Maybe device is not paired? Try using ") + chalk.Blue.Color("pair") + chalk.Yellow.Color(" command"))
103+
} else {
104+
fmt.Println(chalk.Green.Color(output))
105+
}
106+
}

cmd/pair.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"downace/adb-helper-cli/internal/adb"
6+
"downace/adb-helper-cli/internal/mdns"
7+
"downace/adb-helper-cli/internal/ui"
8+
"errors"
9+
"fmt"
10+
"github.com/libp2p/zeroconf/v2"
11+
gonanoid "github.com/matoous/go-nanoid/v2"
12+
"github.com/skip2/go-qrcode"
13+
"github.com/spf13/cobra"
14+
"github.com/ttacon/chalk"
15+
"time"
16+
)
17+
18+
var useQrCode bool
19+
var usePairingCode bool
20+
21+
var pairCmd = &cobra.Command{
22+
Use: "pair",
23+
Short: "Pair device using QR-code or pairing code",
24+
Run: pair,
25+
GroupID: cmdGroupApp,
26+
}
27+
28+
func init() {
29+
rootCmd.AddCommand(pairCmd)
30+
31+
pairCmd.Flags().BoolVar(&useQrCode, "qr", false, "use QR-code")
32+
pairCmd.Flags().BoolVar(&usePairingCode, "code", false, "use pairing code")
33+
pairCmd.MarkFlagsOneRequired("qr", "code")
34+
pairCmd.MarkFlagsMutuallyExclusive("qr", "code")
35+
}
36+
37+
func pair(_ *cobra.Command, _ []string) {
38+
var err error
39+
40+
if useQrCode {
41+
err = pairUsingQRCode()
42+
} else if usePairingCode {
43+
err = pairUsingPairingCode()
44+
}
45+
46+
if err != nil {
47+
fmt.Println(chalk.Red.Color(err.Error()))
48+
}
49+
}
50+
51+
func pairUsingQRCode() error {
52+
name, password, err := genNameAndPassword()
53+
54+
if err != nil {
55+
return err
56+
}
57+
58+
err = printPairingQrCode(name, password)
59+
60+
if err != nil {
61+
return err
62+
}
63+
64+
printPairingHelp("Pair device with QR code")
65+
66+
pairingHost, err := discoverPairingHost()
67+
68+
if pairingHost == nil {
69+
return err
70+
}
71+
72+
output, err := adb.ExecAdb("pair", pairingHost.String(), password)
73+
74+
if err == nil {
75+
fmt.Println(chalk.Green.Color(output))
76+
}
77+
78+
return err
79+
}
80+
81+
func pairUsingPairingCode() error {
82+
printPairingHelp("Pair device with pairing code")
83+
84+
pairingHost, err := discoverPairingHost()
85+
86+
if pairingHost == nil {
87+
return err
88+
}
89+
90+
code := ui.StringPrompt("Enter pairing code")
91+
92+
var output string
93+
94+
output, err = adb.ExecAdb("pair", pairingHost.String(), code)
95+
96+
if err == nil {
97+
fmt.Println(chalk.Green.Color(output))
98+
}
99+
100+
return err
101+
}
102+
103+
func printPairingHelp(lastSegment string) {
104+
fmt.Println(fmt.Sprintf("Go to %s -> %s -> %s",
105+
chalk.Cyan.Color("Developer options"),
106+
chalk.Cyan.Color("Wireless debugging"),
107+
chalk.Cyan.Color(lastSegment),
108+
))
109+
}
110+
111+
func discoverPairingHost() (*mdns.Host, error) {
112+
var pairingHost *mdns.Host
113+
114+
mdns.DiscoverServices(time.Hour, "_adb-tls-pairing._tcp", func(entry *zeroconf.ServiceEntry, stop context.CancelFunc) {
115+
pairingHost = &mdns.Host{Addr: entry.AddrIPv4[0], Port: entry.Port}
116+
stop()
117+
})
118+
119+
if pairingHost == nil {
120+
return nil, errors.New("pairing failed")
121+
}
122+
123+
return pairingHost, nil
124+
}
125+
126+
func genNameAndPassword() (name string, password string, err error) {
127+
name = ""
128+
password = ""
129+
130+
uid, err := gonanoid.New()
131+
if err != nil {
132+
return
133+
}
134+
name = "ADB_WIFI_" + uid
135+
uid, err = gonanoid.New()
136+
if err != nil {
137+
return
138+
}
139+
password = uid
140+
141+
return
142+
}
143+
144+
func printPairingQrCode(name string, password string) error {
145+
content := fmt.Sprintf("WIFI:T:ADB;S:%s;P:%s;;", name, password)
146+
147+
qr, err := qrcode.New(content, qrcode.Low)
148+
149+
if err != nil {
150+
return err
151+
}
152+
fmt.Println(qr.ToString(false))
153+
154+
return nil
155+
}

cmd/root.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package cmd
2+
3+
import (
4+
"downace/adb-helper-cli/internal/adb"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
)
9+
10+
const cmdGroupApp = "app"
11+
12+
var rootCmd = &cobra.Command{
13+
Use: "adb-helper",
14+
Short: "CLI tool to simplify some ADB operations",
15+
}
16+
17+
func init() {
18+
rootCmd.AddGroup(&cobra.Group{ID: cmdGroupApp})
19+
rootCmd.PersistentFlags().StringVarP(&adb.Binary, "adb", "a", "adb", "ADB binary path")
20+
}
21+
22+
func Execute() {
23+
err := rootCmd.Execute()
24+
if err != nil {
25+
os.Exit(1)
26+
}
27+
}

go.mod

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@ module downace/adb-helper-cli
33
go 1.24.0
44

55
require (
6-
github.com/alexflint/go-arg v1.5.1
76
github.com/libp2p/zeroconf/v2 v2.2.0
8-
github.com/manifoldco/promptui v0.9.0
97
github.com/matoous/go-nanoid/v2 v2.1.0
108
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
9+
github.com/spf13/cobra v1.9.1
1110
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
1211
)
1312

1413
require (
15-
github.com/alexflint/go-scalar v1.2.0 // indirect
16-
github.com/chzyer/readline v1.5.1 // indirect
14+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1715
github.com/miekg/dns v1.1.63 // indirect
16+
github.com/spf13/pflag v1.0.6 // indirect
1817
golang.org/x/mod v0.23.0 // indirect
1918
golang.org/x/net v0.35.0 // indirect
2019
golang.org/x/sync v0.11.0 // indirect

0 commit comments

Comments
 (0)