diff --git a/capture.go b/capture.go index 7807c2f..a0da9db 100644 --- a/capture.go +++ b/capture.go @@ -2,6 +2,7 @@ package adb import ( "context" + "encoding/json" "errors" "log" "regexp" @@ -42,6 +43,29 @@ type SequenceImporter struct { Start time.Time End time.Time } + +func (si SequenceImporter) ToInput() Input { + switch si.Type { + case SeqSleep: + return SequenceSleep{Duration: si.Duration, Type: SeqSleep} + case SeqSwipe: + return SequenceSwipe{ + X1: si.X1, Y1: si.Y1, + X2: si.X2, Y2: si.Y2, + Start: si.Start, + End: si.End, Type: SeqSwipe, + } + case SeqTap: + return SequenceTap{ + X: si.X, Y: si.Y, + Start: si.Start, + End: si.End, Type: SeqTap, + } + default: + return SequenceSleep{Duration: time.Millisecond * 0, Type: SeqSleep} + } +} + type SequenceSleep struct { Duration time.Duration Type SeqType @@ -131,6 +155,25 @@ type Resolution struct { Height int } +func (t TapSequence) ToJSON() []byte { + b, _ := json.Marshal(t) + return b +} + +func TapSequenceFromJSON(j []byte) (TapSequence, error) { + var ti TapSequenceImporter + var t TapSequence + err := json.Unmarshal(j, &ti) + if err != nil { + return t, err + } + t.Resolution = ti.Resolution + for _, ie := range ti.Events { + t.Events = append(t.Events, ie.ToInput()) + } + return t, nil +} + // ShortenSleep allows you to shorten all the sleep times between tap and swipe events. // // Provide a scalar value to divide the sleeps by. Providing `2` will halve all @@ -188,12 +231,12 @@ func (d Device) CaptureSequence(ctx context.Context) (t TapSequence, err error) } // this command will never finish without ctx expiring. As a result, // it will always return error code 130 if successful - stdout, _, errCode, err := execute(ctx, []string{"shell", "getevent", "-tl"}) + stdout, _, errCode, err := execute(ctx, []string{"-s", string(d.SerialNo), "shell", "getevent", "-tl"}) // TODO better error checking here if errors.Is(err, ErrUnspecified) { err = nil } - if errCode != 130 && errCode != -1 { + if errCode != 130 && errCode != -1 && errCode != 1 { // TODO remove log output here log.Printf("Expected error code 130 or -1, but got %d\n", errCode) } diff --git a/examples/tapRecorder/go.mod b/examples/tapRecorder/go.mod index e91a407..2ff65b5 100644 --- a/examples/tapRecorder/go.mod +++ b/examples/tapRecorder/go.mod @@ -4,6 +4,26 @@ go 1.19 replace github.com/taigrr/adb => ../.. -require github.com/taigrr/adb v0.0.0-20220803063720-bd9524431495 +require ( + github.com/charmbracelet/bubbles v0.13.0 + github.com/charmbracelet/bubbletea v0.22.0 + github.com/charmbracelet/lipgloss v0.5.0 + github.com/taigrr/adb v0.0.0-20220803063720-bd9524431495 +) -require github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect +require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.1 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/sahilm/fuzzy v0.1.0 // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect +) diff --git a/examples/tapRecorder/go.sum b/examples/tapRecorder/go.sum index 0be9157..c3004fd 100644 --- a/examples/tapRecorder/go.sum +++ b/examples/tapRecorder/go.sum @@ -1,2 +1,48 @@ +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/charmbracelet/bubbles v0.13.0 h1:zP/ROH3wJEBqZWKIsD50ZKKlx3ydLInq3LdD/Nrlb8w= +github.com/charmbracelet/bubbles v0.13.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= +github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= +github.com/charmbracelet/bubbletea v0.22.0 h1:E1BTNSE3iIrq0G0X6TjGAmrQ32cGCbFDPcIuImikrUc= +github.com/charmbracelet/bubbletea v0.22.0/go.mod h1:aoVIwlNlr5wbCB26KhxfrqAn0bMp4YpJcoOelbxApjs= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/cancelreader v0.2.1 h1:Xzd1B4U5bWQOuSKuN398MyynIGTNT89dxzpEDsalXZs= +github.com/muesli/cancelreader v0.2.1/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/examples/tapRecorder/main.go b/examples/tapRecorder/main.go index 70bec96..a10a10e 100644 --- a/examples/tapRecorder/main.go +++ b/examples/tapRecorder/main.go @@ -6,22 +6,38 @@ import ( "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() { @@ -34,16 +50,20 @@ func main() { 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 } - //w, h, err := dev.GetScreenResolution(ctx) - //if err != nil { - // // handle error here - // fmt.Printf("Error: %v\n", err) - //} switch command { case "rec": fmt.Println("Recording taps now. Hit ctrl+c to stop.") @@ -68,55 +88,114 @@ func main() { return } defer f.Close() - var j map[string]interface{} - var t adb.TapSequence var b bytes.Buffer b.ReadFrom(f) - err = json.Unmarshal(b.Bytes(), &j) + t, err := adb.TapSequenceFromJSON(b.Bytes()) if err != nil { fmt.Printf("Error parsing tap file %s: %v", file, err) return } - if events, ok := j["Events"]; ok { - if sliceEvent, ok := events.([]interface{}); ok { - for _, e := range sliceEvent { - if mapEvent, ok := e.(map[string]interface{}); ok { - if eventType, ok := mapEvent["Type"]; ok { - if et, ok := eventType.(float64); ok { - switch int(et) { - case int(adb.SeqSleep): - t.Events = append(t.Events, adb.SequenceSleep{}) - case int(adb.SeqSwipe): - t.Events = append(t.Events, adb.SequenceSwipe{}) - case int(adb.SeqTap): - t.Events = append(t.Events, adb.SequenceTap{}) - } - } else { - fmt.Printf("Could not parse %v (%T) into JSON! 1\n", eventType, eventType) - } - } else { - fmt.Println("Could not parse JSON! 2") - } - } else { - fmt.Println("Could not parse JSON! 3") - } - } - } else { - fmt.Println("Could not parse JSON! 4") - } - } else { - fmt.Println("Could not parse JSON! 5") - } dev.ReplayTapSequence(ctx, t) - err = json.Unmarshal(b.Bytes(), &t) - if err != nil { - fmt.Printf("struct: %v\n",t) - fmt.Printf("bytes: %v\n",b.String()) - fmt.Printf("Error parsing tap file %s: %v", file, err) - return - } - - default: } } } + +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)) +} diff --git a/examples/taps/main.go b/examples/taps/main.go index 6855f4d..f763ae8 100644 --- a/examples/taps/main.go +++ b/examples/taps/main.go @@ -8,13 +8,6 @@ import ( "github.com/taigrr/adb" ) -var command string - -func init() { - // TODO allow for any input to be used as the command - command = "ls" -} - func main() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() @@ -28,11 +21,6 @@ func main() { fmt.Printf("Dev `%s` is not authorized, authorize it to continue.\n", dev.SerialNo) continue } - //w, h, err := dev.GetScreenResolution(ctx) - //if err != nil { - // // handle error here - // fmt.Printf("Error: %v\n", err) - //} fmt.Printf("Begin tapping on device %s now...\n", dev.SerialNo) t, err := dev.CaptureSequence(ctx) if err != nil {