mirror of
https://github.com/taigrr/adb.git
synced 2026-04-02 02:58:42 -07:00
202 lines
4.6 KiB
Go
202 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/charmbracelet/bubbles/list"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/taigrr/adb"
|
|
)
|
|
|
|
var (
|
|
command string
|
|
file string
|
|
|
|
chosen adb.Serial
|
|
titleStyle = lipgloss.NewStyle().MarginLeft(2)
|
|
itemStyle = lipgloss.NewStyle().PaddingLeft(4)
|
|
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
|
|
paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
|
|
helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
|
|
quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
|
)
|
|
|
|
func main() {
|
|
flag.StringVar(&command, "command", "rec", "rec or play")
|
|
flag.StringVar(&file, "file", "taps.json", "Name of the file to save taps to or to play from")
|
|
flag.Parse()
|
|
if command != "play" && command != "rec" {
|
|
flag.PrintDefaults()
|
|
os.Exit(1)
|
|
}
|
|
sigChan := make(chan os.Signal)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go func() {
|
|
<-sigChan
|
|
cancel()
|
|
}()
|
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
devs, err := adb.Devices(ctx)
|
|
if err != nil {
|
|
fmt.Printf("Error enumerating devices: %v\n", err)
|
|
return
|
|
}
|
|
devNames := []adb.Serial{}
|
|
for _, dev := range devs {
|
|
devNames = append(devNames, dev.SerialNo)
|
|
}
|
|
selected := chooseDev(devNames)
|
|
|
|
for _, dev := range devs {
|
|
if dev.SerialNo != selected {
|
|
continue
|
|
}
|
|
if !dev.IsAuthorized {
|
|
fmt.Printf("Dev `%s` is not authorized, authorize it to continue.\n", dev.SerialNo)
|
|
continue
|
|
}
|
|
switch command {
|
|
case "rec":
|
|
fmt.Println("Recording taps now. Hit ctrl+c to stop.")
|
|
t, err := dev.CaptureSequence(ctx)
|
|
if err != nil {
|
|
fmt.Printf("Error capturing sequence: %v\n", err)
|
|
return
|
|
}
|
|
b, _ := json.Marshal(t)
|
|
f, err := os.Create(file)
|
|
if err != nil {
|
|
fmt.Printf("Error creating tap file %s: %v", file, err)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
f.Write(b)
|
|
case "play":
|
|
fmt.Println("Replaying taps now. Hit ctrl+c to stop.")
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
fmt.Printf("Error opening tap file %s: %v", file, err)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
var b bytes.Buffer
|
|
b.ReadFrom(f)
|
|
t, err := adb.TapSequenceFromJSON(b.Bytes())
|
|
if err != nil {
|
|
fmt.Printf("Error parsing tap file %s: %v", file, err)
|
|
return
|
|
}
|
|
dev.ReplayTapSequence(ctx, t)
|
|
}
|
|
}
|
|
}
|
|
|
|
func NewModel(devs []adb.Serial) Model {
|
|
var m Model
|
|
items := []list.Item{}
|
|
for _, d := range devs {
|
|
items = append(items, DevEntry(d))
|
|
}
|
|
m.List = list.New(items, itemDelegate{}, 0, len(devs)+15)
|
|
|
|
return m
|
|
}
|
|
|
|
func chooseDev(devs []adb.Serial) adb.Serial {
|
|
if len(devs) == 0 {
|
|
return ""
|
|
}
|
|
if len(devs) == 1 {
|
|
return devs[0]
|
|
}
|
|
m := NewModel(devs)
|
|
m.List.Title = "Which device?"
|
|
m.List.SetShowStatusBar(false)
|
|
m.List.SetFilteringEnabled(false)
|
|
m.List.Styles.Title = titleStyle
|
|
m.List.Styles.PaginationStyle = paginationStyle
|
|
m.List.Styles.HelpStyle = helpStyle
|
|
|
|
if err := tea.NewProgram(m).Start(); err != nil {
|
|
fmt.Println("Error running program:", err)
|
|
os.Exit(1)
|
|
}
|
|
return adb.Serial(chosen)
|
|
}
|
|
|
|
type Model struct {
|
|
List list.Model
|
|
quitting bool
|
|
Choice DevEntry
|
|
}
|
|
|
|
type DevEntry adb.Serial
|
|
|
|
func (d DevEntry) FilterValue() string {
|
|
return ""
|
|
}
|
|
|
|
func (m Model) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.WindowSizeMsg:
|
|
m.List.SetWidth(msg.Width)
|
|
return m, nil
|
|
case tea.KeyMsg:
|
|
switch keypress := msg.String(); keypress {
|
|
case "ctrl+c":
|
|
m.quitting = true
|
|
return m, tea.Quit
|
|
case "enter":
|
|
i, ok := m.List.SelectedItem().(DevEntry)
|
|
if ok {
|
|
chosen = adb.Serial(i)
|
|
}
|
|
return m, tea.Quit
|
|
}
|
|
}
|
|
var cmd tea.Cmd
|
|
m.List, cmd = m.List.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
func (m Model) View() string {
|
|
if chosen != "" {
|
|
return quitTextStyle.Render("Chosen device: " + string(chosen))
|
|
}
|
|
return "\n" + m.List.View()
|
|
}
|
|
|
|
type itemDelegate struct{}
|
|
|
|
func (d itemDelegate) Height() int { return 1 }
|
|
func (d itemDelegate) Spacing() int { return 0 }
|
|
func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
|
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
|
i, ok := listItem.(DevEntry)
|
|
if !ok {
|
|
return
|
|
}
|
|
str := fmt.Sprintf("%d. %s", index+1, i)
|
|
fn := itemStyle.Render
|
|
if index == m.Index() {
|
|
fn = func(s string) string {
|
|
return selectedItemStyle.Render("> " + s)
|
|
}
|
|
}
|
|
|
|
fmt.Fprint(w, fn(str))
|
|
}
|