refactor: move guia2 into uixt
This commit is contained in:
parent
736f1d6c7b
commit
66b5d5cc49
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.18
|
|||
require (
|
||||
github.com/andybalholm/brotli v1.0.4
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/electricbubble/gadb v0.0.7
|
||||
github.com/electricbubble/gidevice v0.6.2
|
||||
github.com/electricbubble/opencv-helper v0.0.3
|
||||
github.com/fatih/color v1.13.0
|
||||
|
|
2
go.sum
2
go.sum
|
@ -98,6 +98,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/electricbubble/gadb v0.0.7 h1:fxvVLVNs3IFKuYAEXDF2tDZUjT9jNCltoTSirjM5dgo=
|
||||
github.com/electricbubble/gadb v0.0.7/go.mod h1:3293YJ6OWHv/Q6NA5dwSbK43MbmYm8+Vz2d7h5J3IA8=
|
||||
github.com/electricbubble/gidevice v0.6.2 h1:eIeCHH7Xn5fTwnUv3qL8c7L4anKIHtjlTBkgr1LDVTc=
|
||||
github.com/electricbubble/gidevice v0.6.2/go.mod h1:bRHL2M9qgeEKju8KRvKMZUVEg7t5zMnTiG3SJ3QDH5o=
|
||||
github.com/electricbubble/opencv-helper v0.0.3 h1:p0sHTUPPPm8GqzVUtYH+wQbJoguzotUXVRAS7Ibk7nI=
|
||||
|
|
|
@ -38,13 +38,13 @@ This uixt module is initially forked from the following repos and made a lot of
|
|||
|
||||
- [electricbubble/gwda-ext-opencv]
|
||||
- [electricbubble/gwda]
|
||||
- [electricbubble/guia]
|
||||
- [electricbubble/guia2]
|
||||
|
||||
|
||||
[electricbubble/gwda-ext-opencv]: https://github.com/electricbubble/gwda-ext-opencv
|
||||
[appium/WebDriverAgent]: https://github.com/appium/WebDriverAgent
|
||||
[electricbubble/gwda]: https://github.com/electricbubble/gwda
|
||||
[electricbubble/guia]: https://github.com/electricbubble/guia2
|
||||
[electricbubble/guia2]: https://github.com/electricbubble/guia2
|
||||
[OpenCV 4]: https://opencv.org/
|
||||
[hybridgroup/gocv]: https://github.com/hybridgroup/gocv
|
||||
[volcengine]: https://www.volcengine.com/product/text-recognition
|
||||
|
|
|
@ -1 +1,158 @@
|
|||
package uixt
|
||||
|
||||
import "strings"
|
||||
|
||||
type touchGesture struct {
|
||||
Touch PointF `json:"touch"`
|
||||
Time float64 `json:"time"`
|
||||
}
|
||||
|
||||
type TouchAction []touchGesture
|
||||
|
||||
func NewTouchAction(cap ...int) *TouchAction {
|
||||
if len(cap) == 0 || cap[0] <= 0 {
|
||||
cap = []int{8}
|
||||
}
|
||||
tmp := make(TouchAction, 0, cap[0])
|
||||
return &tmp
|
||||
}
|
||||
|
||||
func (ta *TouchAction) Add(x, y int, startTime ...float64) *TouchAction {
|
||||
return ta.AddFloat(float64(x), float64(y), startTime...)
|
||||
}
|
||||
|
||||
func (ta *TouchAction) AddFloat(x, y float64, startTime ...float64) *TouchAction {
|
||||
if len(startTime) == 0 {
|
||||
var tmp float64 = 0
|
||||
if len(*ta) != 0 {
|
||||
g := (*ta)[len(*ta)-1]
|
||||
tmp = g.Time + 0.05
|
||||
}
|
||||
startTime = []float64{tmp}
|
||||
}
|
||||
*ta = append(*ta, touchGesture{Touch: PointF{x, y}, Time: startTime[0]})
|
||||
return ta
|
||||
}
|
||||
|
||||
func (ta *TouchAction) AddPoint(point Point, startTime ...float64) *TouchAction {
|
||||
return ta.AddFloat(float64(point.X), float64(point.Y), startTime...)
|
||||
}
|
||||
|
||||
func (ta *TouchAction) AddPointF(point PointF, startTime ...float64) *TouchAction {
|
||||
return ta.AddFloat(point.X, point.Y, startTime...)
|
||||
}
|
||||
|
||||
func (d *uiaDriver) MultiPointerGesture(gesture1 *TouchAction, gesture2 *TouchAction, tas ...*TouchAction) (err error) {
|
||||
// Must provide coordinates for at least 2 pointers
|
||||
actions := make([]*TouchAction, 0)
|
||||
actions = append(actions, gesture1, gesture2)
|
||||
if len(tas) != 0 {
|
||||
actions = append(actions, tas...)
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"actions": actions,
|
||||
}
|
||||
// register(postHandler, new MultiPointerGesture("/wd/hub/session/:sessionId/touch/multi/perform"))
|
||||
_, err = d.httpPOST(data, "/session", d.sessionId, "/touch/multi/perform")
|
||||
return
|
||||
}
|
||||
|
||||
type w3cGesture map[string]interface{}
|
||||
|
||||
func _newW3CGesture() w3cGesture {
|
||||
return make(w3cGesture)
|
||||
}
|
||||
|
||||
func (g w3cGesture) _set(key string, value interface{}) w3cGesture {
|
||||
g[key] = value
|
||||
return g
|
||||
}
|
||||
|
||||
func (g w3cGesture) pause(duration float64) w3cGesture {
|
||||
return g._set("type", "pause").
|
||||
_set("duration", duration)
|
||||
}
|
||||
|
||||
func (g w3cGesture) keyDown(value string) w3cGesture {
|
||||
return g._set("type", "keyDown").
|
||||
_set("value", value)
|
||||
}
|
||||
|
||||
func (g w3cGesture) keyUp(value string) w3cGesture {
|
||||
return g._set("type", "keyUp").
|
||||
_set("value", value)
|
||||
}
|
||||
|
||||
func (g w3cGesture) pointerDown(button int) w3cGesture {
|
||||
return g._set("type", "pointerDown")._set("button", button)
|
||||
}
|
||||
|
||||
func (g w3cGesture) pointerUp(button int) w3cGesture {
|
||||
return g._set("type", "pointerUp")._set("button", button)
|
||||
}
|
||||
|
||||
func (g w3cGesture) pointerMove(x, y float64, origin string, duration float64, pressureAndSize ...float64) w3cGesture {
|
||||
switch len(pressureAndSize) {
|
||||
case 1:
|
||||
g._set("pressure", pressureAndSize[0])
|
||||
case 2:
|
||||
g._set("pressure", pressureAndSize[0])
|
||||
g._set("size", pressureAndSize[1])
|
||||
}
|
||||
return g._set("type", "pointerMove").
|
||||
_set("duration", duration).
|
||||
_set("origin", origin).
|
||||
_set("x", x).
|
||||
_set("y", y)
|
||||
}
|
||||
|
||||
func (g w3cGesture) size(size ...float64) w3cGesture {
|
||||
if len(size) == 0 {
|
||||
size = []float64{1.0}
|
||||
}
|
||||
return g._set("size", size[0])
|
||||
}
|
||||
|
||||
func (g w3cGesture) pressure(pressure ...float64) w3cGesture {
|
||||
if len(pressure) == 0 {
|
||||
pressure = []float64{1.0}
|
||||
}
|
||||
return g._set("pressure", pressure[0])
|
||||
}
|
||||
|
||||
type W3CGestures []w3cGesture
|
||||
|
||||
func NewW3CGestures(cap ...int) *W3CGestures {
|
||||
if len(cap) == 0 || cap[0] <= 0 {
|
||||
cap = []int{8}
|
||||
}
|
||||
tmp := make(W3CGestures, 0, cap[0])
|
||||
return &tmp
|
||||
}
|
||||
|
||||
func (g *W3CGestures) Pause(duration ...float64) *W3CGestures {
|
||||
if len(duration) == 0 || duration[0] < 0 {
|
||||
duration = []float64{0.5}
|
||||
}
|
||||
*g = append(*g, _newW3CGesture().pause(duration[0]*1000))
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *W3CGestures) KeyDown(value string) *W3CGestures {
|
||||
*g = append(*g, _newW3CGesture().keyDown(value))
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *W3CGestures) KeyUp(value string) *W3CGestures {
|
||||
*g = append(*g, _newW3CGesture().keyUp(value))
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *W3CGestures) SendKeys(text string) *W3CGestures {
|
||||
ss := strings.Split(text, "")
|
||||
for i := range ss {
|
||||
g.KeyDown(ss[i])
|
||||
g.KeyUp(ss[i])
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
|
|
@ -1,7 +1,117 @@
|
|||
package uixt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
|
||||
"github.com/electricbubble/gadb"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
AdbServerHost = "localhost"
|
||||
AdbServerPort = gadb.AdbServerPort // 5037
|
||||
UIA2ServerPort = 6790
|
||||
DeviceTempPath = "/data/local/tmp"
|
||||
)
|
||||
|
||||
const forwardToPrefix = "forward-to-"
|
||||
|
||||
func InitUIAClient(device *AndroidDevice) (*DriverExt, error) {
|
||||
var deviceOptions []AndroidDeviceOption
|
||||
if device.SerialNumber != "" {
|
||||
deviceOptions = append(deviceOptions, WithSerialNumber(device.SerialNumber))
|
||||
}
|
||||
if device.IP != "" {
|
||||
deviceOptions = append(deviceOptions, WithAdbIP(device.IP))
|
||||
}
|
||||
if device.Port != 0 {
|
||||
deviceOptions = append(deviceOptions, WithAdbPort(device.Port))
|
||||
}
|
||||
|
||||
// init uia device
|
||||
androidDevice, err := NewAndroidDevice(deviceOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver, err := androidDevice.NewUSBDriver(nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to init UIA driver")
|
||||
}
|
||||
fmt.Println(driver)
|
||||
|
||||
var driverExt *DriverExt
|
||||
// TODO
|
||||
// driverExt, err = Extend(driver)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "failed to extend UIA Driver")
|
||||
// }
|
||||
|
||||
return driverExt, nil
|
||||
}
|
||||
|
||||
type AndroidDeviceOption func(*AndroidDevice)
|
||||
|
||||
func WithSerialNumber(serial string) AndroidDeviceOption {
|
||||
return func(device *AndroidDevice) {
|
||||
device.SerialNumber = serial
|
||||
}
|
||||
}
|
||||
|
||||
func WithAdbIP(ip string) AndroidDeviceOption {
|
||||
return func(device *AndroidDevice) {
|
||||
device.IP = ip
|
||||
}
|
||||
}
|
||||
|
||||
func WithAdbPort(port int) AndroidDeviceOption {
|
||||
return func(device *AndroidDevice) {
|
||||
device.Port = port
|
||||
}
|
||||
}
|
||||
|
||||
func WithAdbLogOn(logOn bool) AndroidDeviceOption {
|
||||
return func(device *AndroidDevice) {
|
||||
device.LogOn = logOn
|
||||
}
|
||||
}
|
||||
|
||||
func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, err error) {
|
||||
deviceList, err := DeviceList()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get attached devices failed: %v", err)
|
||||
}
|
||||
|
||||
device = &AndroidDevice{
|
||||
Port: UIA2ServerPort,
|
||||
IP: AdbServerHost,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(device)
|
||||
}
|
||||
|
||||
serialNumber := device.SerialNumber
|
||||
for _, dev := range deviceList {
|
||||
// find device by serial number if specified
|
||||
if serialNumber != "" && dev.Serial() != serialNumber {
|
||||
continue
|
||||
}
|
||||
|
||||
device.SerialNumber = dev.Serial()
|
||||
device.Device = dev
|
||||
return device, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("device %s not found", device.SerialNumber)
|
||||
}
|
||||
|
||||
type AndroidDevice struct {
|
||||
gadb.Device
|
||||
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
|
||||
IP string `json:"ip,omitempty" yaml:"ip,omitempty"`
|
||||
Port int `json:"port,omitempty" yaml:"port,omitempty"`
|
||||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
||||
}
|
||||
|
@ -10,6 +120,436 @@ func (o AndroidDevice) UUID() string {
|
|||
return o.SerialNumber
|
||||
}
|
||||
|
||||
func InitUIAClient(device *AndroidDevice) (*DriverExt, error) {
|
||||
return nil, nil
|
||||
func DeviceList() (devices []gadb.Device, err error) {
|
||||
var adbClient gadb.Client
|
||||
if adbClient, err = gadb.NewClientWith(AdbServerHost, AdbServerPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adbClient.DeviceList()
|
||||
}
|
||||
|
||||
// NewUSBDriver creates new client via USB connected device, this will also start a new session.
|
||||
// TODO: replace uiaDriver with WebDriver
|
||||
func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver *uiaDriver, err error) {
|
||||
var localPort int
|
||||
if localPort, err = getFreePort(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = dev.Forward(localPort, UIA2ServerPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawURL := fmt.Sprintf("http://%s%d:6790/wd/hub", forwardToPrefix, localPort)
|
||||
driver, err = NewUIADriver(capabilities, rawURL)
|
||||
if err != nil {
|
||||
_ = dev.ForwardKill(localPort)
|
||||
return nil, err
|
||||
}
|
||||
driver.adbDevice = dev.Device
|
||||
driver.localPort = localPort
|
||||
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adb forward: %w", err)
|
||||
}
|
||||
driver.client = convertToHTTPClient(conn)
|
||||
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// NewHTTPDriver creates new remote HTTP client, this will also start a new session.
|
||||
// TODO: replace uiaDriver with WebDriver
|
||||
func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver *uiaDriver, err error) {
|
||||
rawURL := fmt.Sprintf("http://%s:%d/wd/hub", dev.IP, dev.Port)
|
||||
if driver, err = NewUIADriver(capabilities, rawURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driver.adbDevice = dev.Device
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func getFreePort() (int, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("free port: %w", err)
|
||||
}
|
||||
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("free port: %w", err)
|
||||
}
|
||||
defer func() { _ = l.Close() }()
|
||||
return l.Addr().(*net.TCPAddr).Port, nil
|
||||
}
|
||||
|
||||
type UiSelectorHelper struct {
|
||||
value *bytes.Buffer
|
||||
}
|
||||
|
||||
func NewUiSelectorHelper() UiSelectorHelper {
|
||||
return UiSelectorHelper{value: bytes.NewBufferString("new UiSelector()")}
|
||||
}
|
||||
|
||||
func (s UiSelectorHelper) String() string {
|
||||
return s.value.String() + ";"
|
||||
}
|
||||
|
||||
// Text Set the search criteria to match the visible text displayed
|
||||
// in a widget (for example, the text label to launch an app).
|
||||
//
|
||||
// The text for the element must match exactly with the string in your input
|
||||
// argument. Matching is case-sensitive.
|
||||
func (s UiSelectorHelper) Text(text string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.text("%s")`, text))
|
||||
return s
|
||||
}
|
||||
|
||||
// TextMatches Set the search criteria to match the visible text displayed in a layout
|
||||
// element, using a regular expression.
|
||||
//
|
||||
// The text in the widget must match exactly with the string in your
|
||||
// input argument.
|
||||
func (s UiSelectorHelper) TextMatches(regex string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.textMatches("%s")`, regex))
|
||||
return s
|
||||
}
|
||||
|
||||
// TextStartsWith Set the search criteria to match visible text in a widget that is
|
||||
// prefixed by the text parameter.
|
||||
//
|
||||
// The matching is case-insensitive.
|
||||
func (s UiSelectorHelper) TextStartsWith(text string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.textStartsWith("%s")`, text))
|
||||
return s
|
||||
}
|
||||
|
||||
// TextContains Set the search criteria to match the visible text in a widget
|
||||
// where the visible text must contain the string in your input argument.
|
||||
//
|
||||
// The matching is case-sensitive.
|
||||
func (s UiSelectorHelper) TextContains(text string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.textContains("%s")`, text))
|
||||
return s
|
||||
}
|
||||
|
||||
// ClassName Set the search criteria to match the class property
|
||||
// for a widget (for example, "android.widget.Button").
|
||||
func (s UiSelectorHelper) ClassName(className string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.className("%s")`, className))
|
||||
return s
|
||||
}
|
||||
|
||||
// ClassNameMatches Set the search criteria to match the class property
|
||||
// for a widget, using a regular expression.
|
||||
func (s UiSelectorHelper) ClassNameMatches(regex string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.classNameMatches("%s")`, regex))
|
||||
return s
|
||||
}
|
||||
|
||||
// Description Set the search criteria to match the content-description
|
||||
// property for a widget.
|
||||
//
|
||||
// The content-description is typically used
|
||||
// by the Android Accessibility framework to
|
||||
// provide an audio prompt for the widget when
|
||||
// the widget is selected. The content-description
|
||||
// for the widget must match exactly
|
||||
// with the string in your input argument.
|
||||
//
|
||||
// Matching is case-sensitive.
|
||||
func (s UiSelectorHelper) Description(desc string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.description("%s")`, desc))
|
||||
return s
|
||||
}
|
||||
|
||||
// DescriptionMatches Set the search criteria to match the content-description
|
||||
// property for a widget.
|
||||
//
|
||||
// The content-description is typically used
|
||||
// by the Android Accessibility framework to
|
||||
// provide an audio prompt for the widget when
|
||||
// the widget is selected. The content-description
|
||||
// for the widget must match exactly
|
||||
// with the string in your input argument.
|
||||
func (s UiSelectorHelper) DescriptionMatches(regex string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.descriptionMatches("%s")`, regex))
|
||||
return s
|
||||
}
|
||||
|
||||
// DescriptionStartsWith Set the search criteria to match the content-description
|
||||
// property for a widget.
|
||||
//
|
||||
// The content-description is typically used
|
||||
// by the Android Accessibility framework to
|
||||
// provide an audio prompt for the widget when
|
||||
// the widget is selected. The content-description
|
||||
// for the widget must start
|
||||
// with the string in your input argument.
|
||||
//
|
||||
// Matching is case-insensitive.
|
||||
func (s UiSelectorHelper) DescriptionStartsWith(desc string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.descriptionStartsWith("%s")`, desc))
|
||||
return s
|
||||
}
|
||||
|
||||
// DescriptionContains Set the search criteria to match the content-description
|
||||
// property for a widget.
|
||||
//
|
||||
// The content-description is typically used
|
||||
// by the Android Accessibility framework to
|
||||
// provide an audio prompt for the widget when
|
||||
// the widget is selected. The content-description
|
||||
// for the widget must contain
|
||||
// the string in your input argument.
|
||||
//
|
||||
// Matching is case-insensitive.
|
||||
func (s UiSelectorHelper) DescriptionContains(desc string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.descriptionContains("%s")`, desc))
|
||||
return s
|
||||
}
|
||||
|
||||
// ResourceId Set the search criteria to match the given resource ID.
|
||||
func (s UiSelectorHelper) ResourceId(id string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.resourceId("%s")`, id))
|
||||
return s
|
||||
}
|
||||
|
||||
// ResourceIdMatches Set the search criteria to match the resource ID
|
||||
// of the widget, using a regular expression.
|
||||
func (s UiSelectorHelper) ResourceIdMatches(regex string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.resourceIdMatches("%s")`, regex))
|
||||
return s
|
||||
}
|
||||
|
||||
// Index Set the search criteria to match the widget by its node
|
||||
// index in the layout hierarchy.
|
||||
//
|
||||
// The index value must be 0 or greater.
|
||||
//
|
||||
// Using the index can be unreliable and should only
|
||||
// be used as a last resort for matching. Instead,
|
||||
// consider using the `Instance(int)` method.
|
||||
func (s UiSelectorHelper) Index(index int) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.index(%d)`, index))
|
||||
return s
|
||||
}
|
||||
|
||||
// Instance Set the search criteria to match the
|
||||
// widget by its instance number.
|
||||
//
|
||||
// The instance value must be 0 or greater, where
|
||||
// the first instance is 0.
|
||||
//
|
||||
// For example, to simulate a user click on
|
||||
// the third image that is enabled in a UI screen, you
|
||||
// could specify a a search criteria where the instance is
|
||||
// 2, the `className(String)` matches the image
|
||||
// widget class, and `enabled(boolean)` is true.
|
||||
// The code would look like this:
|
||||
// `new UiSelector().className("android.widget.ImageView")
|
||||
// .enabled(true).instance(2);`
|
||||
func (s UiSelectorHelper) Instance(instance int) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.instance(%d)`, instance))
|
||||
return s
|
||||
}
|
||||
|
||||
// Enabled Set the search criteria to match widgets that are enabled.
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) Enabled(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.enabled(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// Focused Set the search criteria to match widgets that have focus.
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) Focused(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.focused(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// Focusable Set the search criteria to match widgets that are focusable.
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) Focusable(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.focusable(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// Scrollable Set the search criteria to match widgets that are scrollable.
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) Scrollable(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.scrollable(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// Selected Set the search criteria to match widgets that
|
||||
// are currently selected.
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) Selected(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.selected(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// Checked Set the search criteria to match widgets that
|
||||
// are currently checked (usually for checkboxes).
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) Checked(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.checked(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// Checkable Set the search criteria to match widgets that are checkable.
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) Checkable(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.checkable(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// Clickable Set the search criteria to match widgets that are clickable.
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) Clickable(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.clickable(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// LongClickable Set the search criteria to match widgets that are long-clickable.
|
||||
//
|
||||
// Typically, using this search criteria alone is not useful.
|
||||
// You should also include additional criteria, such as text,
|
||||
// content-description, or the class name for a widget.
|
||||
//
|
||||
// If no other search criteria is specified, and there is more
|
||||
// than one matching widget, the first widget in the tree
|
||||
// is selected.
|
||||
func (s UiSelectorHelper) LongClickable(b bool) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.longClickable(%t)`, b))
|
||||
return s
|
||||
}
|
||||
|
||||
// packageName Set the search criteria to match the package name
|
||||
// of the application that contains the widget.
|
||||
func (s UiSelectorHelper) packageName(name string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.packageName(%s)`, name))
|
||||
return s
|
||||
}
|
||||
|
||||
// PackageNameMatches Set the search criteria to match the package name
|
||||
// of the application that contains the widget.
|
||||
func (s UiSelectorHelper) PackageNameMatches(regex string) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.packageNameMatches(%s)`, regex))
|
||||
return s
|
||||
}
|
||||
|
||||
// ChildSelector Adds a child UiSelector criteria to this selector.
|
||||
//
|
||||
// Use this selector to narrow the search scope to
|
||||
// child widgets under a specific parent widget.
|
||||
func (s UiSelectorHelper) ChildSelector(selector UiSelectorHelper) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.childSelector(%s)`, selector.value.String()))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s UiSelectorHelper) PatternSelector(selector UiSelectorHelper) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.patternSelector(%s)`, selector.value.String()))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s UiSelectorHelper) ContainerSelector(selector UiSelectorHelper) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.containerSelector(%s)`, selector.value.String()))
|
||||
return s
|
||||
}
|
||||
|
||||
// FromParent Adds a child UiSelector criteria to this selector which is used to
|
||||
// start search from the parent widget.
|
||||
//
|
||||
// Use this selector to narrow the search scope to
|
||||
// sibling widgets as well all child widgets under a parent.
|
||||
func (s UiSelectorHelper) FromParent(selector UiSelectorHelper) UiSelectorHelper {
|
||||
s.value.WriteString(fmt.Sprintf(`.fromParent(%s)`, selector.value.String()))
|
||||
return s
|
||||
}
|
||||
|
||||
type AndroidBySelector struct {
|
||||
// Set the search criteria to match the given resource ResourceIdID.
|
||||
ResourceIdID string `json:"id"`
|
||||
// Set the search criteria to match the content-description property for a widget.
|
||||
ContentDescription string `json:"accessibility id"`
|
||||
XPath string `json:"xpath"`
|
||||
// Set the search criteria to match the class property for a widget (for example, "android.widget.Button").
|
||||
ClassName string `json:"class name"`
|
||||
UiAutomator string `json:"-android uiautomator"`
|
||||
}
|
||||
|
||||
func (by AndroidBySelector) getMethodAndSelector() (method, selector string) {
|
||||
vBy := reflect.ValueOf(by)
|
||||
tBy := reflect.TypeOf(by)
|
||||
for i := 0; i < vBy.NumField(); i++ {
|
||||
vi := vBy.Field(i).Interface()
|
||||
// switch vi := vi.(type) {
|
||||
// case string:
|
||||
// selector = vi
|
||||
// }
|
||||
selector = vi.(string)
|
||||
if selector != "" && selector != "UNKNOWN" {
|
||||
method = tBy.Field(i).Tag.Get("json")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,238 @@
|
|||
package uixt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type uiaElement struct {
|
||||
parent *uiaDriver
|
||||
id string
|
||||
}
|
||||
|
||||
func (e *uiaElement) Text() (text string, err error) {
|
||||
// register(getHandler, new GetText("/wd/hub/session/:sessionId/element/:id/text"))
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/text"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
reply := new(struct{ Value string })
|
||||
if err = json.Unmarshal(rawResp, reply); err != nil {
|
||||
return "", err
|
||||
}
|
||||
text = reply.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) GetAttribute(name string) (attribute string, err error) {
|
||||
// register(getHandler, new GetElementAttribute("/wd/hub/session/:sessionId/element/:id/attribute/:name"))
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/attribute", name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
reply := new(struct{ Value string })
|
||||
if err = json.Unmarshal(rawResp, reply); err != nil {
|
||||
return "", err
|
||||
}
|
||||
attribute = reply.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) ContentDescription() (name string, err error) {
|
||||
// register(getHandler, new GetName("/wd/hub/session/:sessionId/element/:id/name"))
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/name"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
reply := new(struct{ Value string })
|
||||
if err = json.Unmarshal(rawResp, reply); err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = reply.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) Size() (size Size, err error) {
|
||||
// register(getHandler, new GetSize("/wd/hub/session/:sessionId/element/:id/size"))
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/size"); err != nil {
|
||||
return Size{-1, -1}, err
|
||||
}
|
||||
reply := new(struct{ Value Size })
|
||||
if err = json.Unmarshal(rawResp, reply); err != nil {
|
||||
return Size{-1, -1}, err
|
||||
}
|
||||
size = reply.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) Rect() (rect Rect, err error) {
|
||||
// register(getHandler, new GetRect("/wd/hub/session/:sessionId/element/:id/rect"))
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/rect"); err != nil {
|
||||
return Rect{}, err
|
||||
}
|
||||
reply := new(struct{ Value Rect })
|
||||
if err = json.Unmarshal(rawResp, reply); err != nil {
|
||||
return Rect{}, err
|
||||
}
|
||||
rect = reply.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) Screenshot() (raw *bytes.Buffer, err error) {
|
||||
// W3C endpoint
|
||||
// register(getHandler, new GetElementScreenshot("/wd/hub/session/:sessionId/element/:id/screenshot"))
|
||||
// JSONWP endpoint
|
||||
// register(getHandler, new GetElementScreenshot("/wd/hub/session/:sessionId/screenshot/:id"))
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/screenshot"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reply := new(struct{ Value string })
|
||||
if err = json.Unmarshal(rawResp, reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var decodeStr []byte
|
||||
if decodeStr, err = base64.StdEncoding.DecodeString(reply.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw = bytes.NewBuffer(decodeStr)
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) Location() (point Point, err error) {
|
||||
// register(getHandler, new Location("/wd/hub/session/:sessionId/element/:id/location"))
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/location"); err != nil {
|
||||
return Point{-1, -1}, err
|
||||
}
|
||||
reply := new(struct{ Value Point })
|
||||
if err = json.Unmarshal(rawResp, reply); err != nil {
|
||||
return Point{-1, -1}, err
|
||||
}
|
||||
point = reply.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) Click() (err error) {
|
||||
// register(postHandler, new Click("/wd/hub/session/:sessionId/element/:id/click"))
|
||||
_, err = e.parent.httpPOST(nil, "/session", e.parent.sessionId, "/element", e.id, "/click")
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) Clear() (err error) {
|
||||
// register(postHandler, new Clear("/wd/hub/session/:sessionId/element/:id/clear"))
|
||||
_, err = e.parent.httpPOST(nil, "/session", e.parent.sessionId, "/element", e.id, "/clear")
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) SendKeys(text string, isReplace ...bool) (err error) {
|
||||
if len(isReplace) == 0 {
|
||||
isReplace = []bool{true}
|
||||
}
|
||||
// register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/element/:id/value"))
|
||||
// https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/handler/SendKeysToElement.java#L76-L85
|
||||
data := map[string]interface{}{
|
||||
"text": text,
|
||||
"replace": isReplace[0],
|
||||
}
|
||||
_, err = e.parent.httpPOST(data, "/session", e.parent.sessionId, "/element", e.id, "/value")
|
||||
return
|
||||
}
|
||||
|
||||
func (e *uiaElement) FindElements(by AndroidBySelector) (elements []*uiaElement, err error) {
|
||||
method, selector := by.getMethodAndSelector()
|
||||
return e.parent._findElements(method, selector, e.id)
|
||||
}
|
||||
|
||||
func (e *uiaElement) FindElement(by AndroidBySelector) (elem *uiaElement, err error) {
|
||||
method, selector := by.getMethodAndSelector()
|
||||
return e.parent._findElement(method, selector, e.id)
|
||||
}
|
||||
|
||||
func (e *uiaElement) Swipe(startX, startY, endX, endY int, steps ...int) (err error) {
|
||||
return e.SwipeFloat(float64(startX), float64(startY), float64(endX), float64(endY), steps...)
|
||||
}
|
||||
|
||||
func (e *uiaElement) SwipeFloat(startX, startY, endX, endY float64, steps ...int) (err error) {
|
||||
if len(steps) == 0 {
|
||||
steps = []int{12}
|
||||
}
|
||||
return e.parent._swipe(startX, startY, endX, endY, steps[0], e.id)
|
||||
}
|
||||
|
||||
func (e *uiaElement) SwipePoint(startPoint, endPoint Point, steps ...int) (err error) {
|
||||
return e.Swipe(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, steps...)
|
||||
}
|
||||
|
||||
func (e *uiaElement) SwipePointF(startPoint, endPoint PointF, steps ...int) (err error) {
|
||||
return e.SwipeFloat(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, steps...)
|
||||
}
|
||||
|
||||
func (e *uiaElement) Drag(endX, endY int, steps ...int) (err error) {
|
||||
return e.DragFloat(float64(endX), float64(endY), steps...)
|
||||
}
|
||||
|
||||
func (e *uiaElement) DragFloat(endX, endY float64, steps ...int) error {
|
||||
if len(steps) == 0 {
|
||||
steps = []int{12 * 10}
|
||||
} else {
|
||||
steps[0] = 12 * 10
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"elementId": e.id,
|
||||
"endX": endX,
|
||||
"endY": endY,
|
||||
"steps": steps[0],
|
||||
}
|
||||
return e.parent._drag(data)
|
||||
}
|
||||
|
||||
func (e *uiaElement) DragPoint(endPoint Point, steps ...int) error {
|
||||
return e.Drag(endPoint.X, endPoint.Y, steps...)
|
||||
}
|
||||
|
||||
func (e *uiaElement) DragPointF(endPoint PointF, steps ...int) (err error) {
|
||||
return e.DragFloat(endPoint.X, endPoint.Y, steps...)
|
||||
}
|
||||
|
||||
func (e *uiaElement) DragTo(destElem *uiaElement, steps ...int) error {
|
||||
if len(steps) == 0 {
|
||||
steps = []int{12}
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"elementId": e.id,
|
||||
"destElId": destElem.id,
|
||||
"steps": steps[0],
|
||||
}
|
||||
return e.parent._drag(data)
|
||||
}
|
||||
|
||||
func (e *uiaElement) Flick(xOffset, yOffset, speed int) (err error) {
|
||||
data := map[string]interface{}{
|
||||
legacyWebElementIdentifier: e.id,
|
||||
webElementIdentifier: e.id,
|
||||
"xoffset": xOffset,
|
||||
"yoffset": yOffset,
|
||||
"speed": speed,
|
||||
}
|
||||
return e.parent._flick(data)
|
||||
}
|
||||
|
||||
func (e *uiaElement) ScrollTo(by AndroidBySelector, maxSwipes ...int) (err error) {
|
||||
if len(maxSwipes) == 0 {
|
||||
maxSwipes = []int{0}
|
||||
}
|
||||
method, selector := by.getMethodAndSelector()
|
||||
return e.parent._scrollTo(method, selector, maxSwipes[0], e.id)
|
||||
}
|
||||
|
||||
func (e *uiaElement) ScrollToElement(element *uiaElement) (err error) {
|
||||
// register(postHandler, new ScrollToElement("/wd/hub/session/:sessionId/appium/element/:id/scroll_to/:id2"))
|
||||
_, err = e.parent.httpPOST(nil, "/session", e.parent.sessionId, "/appium/element", e.id, "/scroll_to", element.id)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,879 @@
|
|||
package uixt
|
||||
|
||||
type KeyMeta int
|
||||
|
||||
const (
|
||||
KMEmpty KeyMeta = 0 // As a `null`
|
||||
KMCapLocked KeyMeta = 0x100 // SHIFT key locked in CAPS mode.
|
||||
KMAltLocked KeyMeta = 0x200 // ALT key locked.
|
||||
KMSymLocked KeyMeta = 0x400 // SYM key locked.
|
||||
KMSelecting KeyMeta = 0x800 // Text is in selection mode.
|
||||
// KMAltOn KeyMeta = 0x02 // This mask is used to check whether one of the ALT meta keys is pressed.
|
||||
// KMAltLeftOn KeyMeta = 0x10 // This mask is used to check whether the left ALT meta key is pressed.
|
||||
// KMAltRightOn KeyMeta = 0x20 // This mask is used to check whether the right the ALT meta key is pressed.
|
||||
// KMShiftOn KeyMeta = 0x1 // This mask is used to check whether one of the SHIFT meta keys is pressed.
|
||||
// KMShiftLeftOn KeyMeta = 0x40 // This mask is used to check whether the left SHIFT meta key is pressed.
|
||||
// KMShiftRightOn KeyMeta = 0x80 // This mask is used to check whether the right SHIFT meta key is pressed.
|
||||
// KMSymOn KeyMeta = 0x4 // This mask is used to check whether the SYM meta key is pressed.
|
||||
// KMFunctionOn KeyMeta = 0x8 // This mask is used to check whether the FUNCTION meta key is pressed.
|
||||
// KMCtrlOn KeyMeta = 0x1000 // This mask is used to check whether one of the CTRL meta keys is pressed.
|
||||
// KMCtrlLeftOn KeyMeta = 0x2000 // This mask is used to check whether the left CTRL meta key is pressed.
|
||||
// KMCtrlRightOn KeyMeta = 0x4000 // This mask is used to check whether the right CTRL meta key is pressed.
|
||||
// KMMetaOn KeyMeta = 0x10000 // This mask is used to check whether one of the META meta keys is pressed.
|
||||
// KMMetaLeftOn KeyMeta = 0x20000 // This mask is used to check whether the left META meta key is pressed.
|
||||
// KMMetaRightOn KeyMeta = 0x40000 // This mask is used to check whether the right META meta key is pressed.
|
||||
// KMCapsLockOn KeyMeta = 0x100000 // This mask is used to check whether the CAPS LOCK meta key is on.
|
||||
// KMNumLockOn KeyMeta = 0x200000 // This mask is used to check whether the NUM LOCK meta key is on.
|
||||
// KMScrollLockOn KeyMeta = 0x400000 // This mask is used to check whether the SCROLL LOCK meta key is on.
|
||||
// KMShiftMask = KMShiftOn | KMShiftLeftOn | KMShiftRightOn
|
||||
// KMAltMask = KMAltOn | KMAltLeftOn | KMAltRightOn
|
||||
// KMCtrlMask = KMCtrlOn | KMCtrlLeftOn | KMCtrlRightOn
|
||||
// KMMetaMask = KMMetaOn | KMMetaLeftOn | KMMetaRightOn
|
||||
)
|
||||
|
||||
type KeyFlag int
|
||||
|
||||
const (
|
||||
// KFWokeHere This mask is set if the device woke because of this key event.
|
||||
// Deprecated
|
||||
KFWokeHere KeyFlag = 0x1
|
||||
|
||||
// KFSoftKeyboard This mask is set if the key event was generated by a software keyboard.
|
||||
KFSoftKeyboard KeyFlag = 0x2
|
||||
|
||||
// KFKeepTouchMode This mask is set if we don't want the key event to cause us to leave touch mode.
|
||||
KFKeepTouchMode KeyFlag = 0x4
|
||||
|
||||
// KFFromSystem This mask is set if an event was known to come from a trusted part
|
||||
// of the system. That is, the event is known to come from the user,
|
||||
// and could not have been spoofed by a third party component.
|
||||
KFFromSystem KeyFlag = 0x8
|
||||
|
||||
// KFEditorAction This mask is used for compatibility, to identify enter keys that are
|
||||
// coming from an IME whose enter key has been auto-labelled "next" or
|
||||
// "done". This allows TextView to dispatch these as normal enter keys
|
||||
// for old applications, but still do the appropriate action when receiving them.
|
||||
KFEditorAction KeyFlag = 0x10
|
||||
|
||||
// KFCanceled When associated with up key events, this indicates that the key press
|
||||
// has been canceled. Typically this is used with virtual touch screen
|
||||
// keys, where the user can slide from the virtual key area on to the
|
||||
// display: in that case, the application will receive a canceled up
|
||||
// event and should not perform the action normally associated with the
|
||||
// key. Note that for this to work, the application can not perform an
|
||||
// action for a key until it receives an up or the long press timeout has expired.
|
||||
KFCanceled KeyFlag = 0x20
|
||||
|
||||
// KFVirtualHardKey This key event was generated by a virtual (on-screen) hard key area.
|
||||
// Typically this is an area of the touchscreen, outside of the regular
|
||||
// display, dedicated to "hardware" buttons.
|
||||
KFVirtualHardKey KeyFlag = 0x40
|
||||
|
||||
// KFLongPress This flag is set for the first key repeat that occurs after the long press timeout.
|
||||
KFLongPress KeyFlag = 0x80
|
||||
|
||||
// KFCanceledLongPress Set when a key event has `KFCanceled` set because a long
|
||||
// press action was executed while it was down.
|
||||
KFCanceledLongPress KeyFlag = 0x100
|
||||
|
||||
// KFTracking Set for `ACTION_UP` when this event's key code is still being
|
||||
// tracked from its initial down. That is, somebody requested that tracking
|
||||
// started on the key down and a long press has not caused
|
||||
// the tracking to be canceled.
|
||||
KFTracking KeyFlag = 0x200
|
||||
|
||||
// KFFallback Set when a key event has been synthesized to implement default behavior
|
||||
// for an event that the application did not handle.
|
||||
// Fallback key events are generated by unhandled trackball motions
|
||||
// (to emulate a directional keypad) and by certain unhandled key presses
|
||||
// that are declared in the key map (such as special function numeric keypad
|
||||
// keys when numlock is off).
|
||||
KFFallback KeyFlag = 0x400
|
||||
|
||||
// KFPredispatch Signifies that the key is being predispatched.
|
||||
// KFPredispatch KeyFlag = 0x20000000
|
||||
|
||||
// KFStartTracking Private control to determine when an app is tracking a key sequence.
|
||||
// KFStartTracking KeyFlag = 0x40000000
|
||||
|
||||
// KFTainted Private flag that indicates when the system has detected that this key event
|
||||
// may be inconsistent with respect to the sequence of previously delivered key events,
|
||||
// such as when a key up event is sent but the key was not down.
|
||||
// KFTainted KeyFlag = 0x80000000
|
||||
)
|
||||
|
||||
type KeyCode int
|
||||
|
||||
const (
|
||||
_ KeyCode = 0 // Unknown key code.
|
||||
|
||||
// KCSoftLeft Soft Left key
|
||||
// Usually situated below the display on phones and used as a multi-function
|
||||
// feature key for selecting a software defined function shown on the bottom left
|
||||
// of the display.
|
||||
KCSoftLeft KeyCode = 1
|
||||
|
||||
// KCSoftRight Soft Right key.
|
||||
// Usually situated below the display on phones and used as a multi-function
|
||||
// feature key for selecting a software defined function shown on the bottom right
|
||||
// of the display.
|
||||
KCSoftRight KeyCode = 2
|
||||
|
||||
// KCHome Home key.
|
||||
// This key is handled by the framework and is never delivered to applications.
|
||||
KCHome KeyCode = 3
|
||||
|
||||
KCBack KeyCode = 4 // Back key
|
||||
KCCall KeyCode = 5 // Call key
|
||||
KCEndCall KeyCode = 6 // End Call key
|
||||
KC0 KeyCode = 7 // '0' key
|
||||
KC1 KeyCode = 8 // '1' key
|
||||
KC2 KeyCode = 9 // '2' key
|
||||
KC3 KeyCode = 10 // '3' key
|
||||
KC4 KeyCode = 11 // '4' key
|
||||
KC5 KeyCode = 12 // '5' key
|
||||
KC6 KeyCode = 13 // '6' key
|
||||
KC7 KeyCode = 14 // '7' key
|
||||
KC8 KeyCode = 15 // '8' key
|
||||
KC9 KeyCode = 16 // '9' key
|
||||
KCStar KeyCode = 17 // '*' key
|
||||
KCPound KeyCode = 18 // '#' key
|
||||
|
||||
// KCDPadUp KeycodeDPadUp Directional Pad Up key.
|
||||
// May also be synthesized from trackball motions.
|
||||
KCDPadUp KeyCode = 19
|
||||
|
||||
// KCDPadDown Directional Pad Down key.
|
||||
// May also be synthesized from trackball motions.
|
||||
KCDPadDown KeyCode = 20
|
||||
|
||||
// KCDPadLeft Directional Pad Left key.
|
||||
// May also be synthesized from trackball motions.
|
||||
KCDPadLeft KeyCode = 21
|
||||
|
||||
// KCDPadRight Directional Pad Right key.
|
||||
// May also be synthesized from trackball motions.
|
||||
KCDPadRight KeyCode = 22
|
||||
|
||||
// KCDPadCenter Directional Pad Center key.
|
||||
// May also be synthesized from trackball motions.
|
||||
KCDPadCenter KeyCode = 23
|
||||
|
||||
// KCVolumeUp Volume Up key.
|
||||
// Adjusts the speaker volume up.
|
||||
KCVolumeUp KeyCode = 24
|
||||
|
||||
// KCVolumeDown Volume Down key.
|
||||
// Adjusts the speaker volume down.
|
||||
KCVolumeDown KeyCode = 25
|
||||
|
||||
// KCPower Power key.
|
||||
KCPower KeyCode = 26
|
||||
|
||||
// KCCamera Camera key.
|
||||
// Used to launch a camera application or take pictures.
|
||||
KCCamera KeyCode = 27
|
||||
|
||||
KCClear KeyCode = 28 // Clear key
|
||||
KCa KeyCode = 29 // 'a' key
|
||||
KCb KeyCode = 30 // 'b' key
|
||||
KCc KeyCode = 31 // 'c' key
|
||||
KCd KeyCode = 32 // 'd' key
|
||||
KCe KeyCode = 33 // 'e' key
|
||||
KCf KeyCode = 34 // 'f' key
|
||||
KCg KeyCode = 35 // 'g' key
|
||||
KCh KeyCode = 36 // 'h' key
|
||||
KCi KeyCode = 37 // 'i' key
|
||||
KCj KeyCode = 38 // 'j' key
|
||||
KCk KeyCode = 39 // 'k' key
|
||||
KCl KeyCode = 40 // 'l' key
|
||||
KCm KeyCode = 41 // 'm' key
|
||||
KCn KeyCode = 42 // 'n' key
|
||||
KCo KeyCode = 43 // 'o' key
|
||||
KCp KeyCode = 44 // 'p' key
|
||||
KCq KeyCode = 45 // 'q' key
|
||||
KCr KeyCode = 46 // 'r' key
|
||||
KCs KeyCode = 47 // 's' key
|
||||
KCt KeyCode = 48 // 't' key
|
||||
KCu KeyCode = 49 // 'u' key
|
||||
KCv KeyCode = 50 // 'v' key
|
||||
KCw KeyCode = 51 // 'w' key
|
||||
KCx KeyCode = 52 // 'x' key
|
||||
KCy KeyCode = 53 // 'y' key
|
||||
KCz KeyCode = 54 // 'z' key
|
||||
KCComma KeyCode = 55 // ',' key
|
||||
KCPeriod KeyCode = 56 // '.' key
|
||||
KCAltLeft KeyCode = 57 // Left Alt modifier key
|
||||
KCAltRight KeyCode = 58 // Right Alt modifier key
|
||||
KCShiftLeft KeyCode = 59 // Left Shift modifier key
|
||||
KCShiftRight KeyCode = 60 // Right Shift modifier key
|
||||
KCTab KeyCode = 61 // Tab key
|
||||
KCSpace KeyCode = 62 // Space key
|
||||
|
||||
// KCSym Symbol modifier key.
|
||||
// Used to enter alternate symbols.
|
||||
KCSym KeyCode = 63
|
||||
|
||||
// KCExplorer Explorer special function key.
|
||||
// Used to launch a browser application.
|
||||
KCExplorer KeyCode = 64
|
||||
|
||||
// KCEnvelope Envelope special function key.
|
||||
// Used to launch a mail application.
|
||||
KCEnvelope KeyCode = 65
|
||||
|
||||
// KCEnter Enter key.
|
||||
KCEnter KeyCode = 66
|
||||
|
||||
// KCDel Backspace key.
|
||||
// Deletes characters before the insertion point, unlike `KCForwardDel`.
|
||||
KCDel KeyCode = 67
|
||||
|
||||
KCGrave KeyCode = 68 // '`' (backtick) key
|
||||
KCMinus KeyCode = 69 // '-'
|
||||
KCEquals KeyCode = 70 // '=' key
|
||||
KCLeftBracket KeyCode = 71 // '[' key
|
||||
KCRightBracket KeyCode = 72 // ']' key
|
||||
KCBackslash KeyCode = 73 // '\' key
|
||||
KCSemicolon KeyCode = 74 // '' key
|
||||
KCApostrophe KeyCode = 75 // ''' (apostrophe) key
|
||||
KCSlash KeyCode = 76 // '/' key
|
||||
KCAt KeyCode = 77 // '@' key
|
||||
|
||||
// KCNum Number modifier key.
|
||||
// Used to enter numeric symbols.
|
||||
// This key is not Num Lock; it is more like `KCAltLeft` and is
|
||||
// interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}.
|
||||
KCNum KeyCode = 78
|
||||
|
||||
// KCHeadsetHook Headset Hook key.
|
||||
// Used to hang up calls and stop media.
|
||||
KCHeadsetHook KeyCode = 79
|
||||
|
||||
// KCFocus Camera Focus key.
|
||||
// Used to focus the camera.
|
||||
// *Camera* focus
|
||||
KCFocus KeyCode = 80
|
||||
|
||||
KCPlus KeyCode = 81 // '+' key.
|
||||
KCMenu KeyCode = 82 // Menu key.
|
||||
KCNotification KeyCode = 83 // Notification key.
|
||||
KCSearch KeyCode = 84 // Search key.
|
||||
KCMediaPlayPause KeyCode = 85 // Play/Pause media key.
|
||||
KCMediaStop KeyCode = 86 // Stop media key.
|
||||
KCMediaNext KeyCode = 87 // Play Next media key.
|
||||
KCMediaPrevious KeyCode = 88 // Play Previous media key.
|
||||
KCMediaRewind KeyCode = 89 // Rewind media key.
|
||||
KCMediaFastForward KeyCode = 90 // Fast Forward media key.
|
||||
|
||||
// KCMute Mute key.
|
||||
// Mutes the microphone, unlike `KCVolumeMute`
|
||||
KCMute KeyCode = 91
|
||||
|
||||
// KCPageUp Page Up key.
|
||||
KCPageUp KeyCode = 92
|
||||
|
||||
// KCPageDown Page Down key.
|
||||
KCPageDown KeyCode = 93
|
||||
|
||||
// KCPictSymbols Picture Symbols modifier key.
|
||||
// Used to switch symbol sets (Emoji, Kao-moji).
|
||||
// switch symbol-sets (Emoji,Kao-moji)
|
||||
KCPictSymbols KeyCode = 94
|
||||
|
||||
// KCSwitchCharset Switch Charset modifier key.
|
||||
// Used to switch character sets (Kanji, Katakana).
|
||||
// switch char-sets (Kanji,Katakana)
|
||||
KCSwitchCharset KeyCode = 95
|
||||
|
||||
// KCButtonA A Button key.
|
||||
// On a game controller, the A button should be either the button labeled A
|
||||
// or the first button on the bottom row of controller buttons.
|
||||
KCButtonA KeyCode = 96
|
||||
|
||||
// KCButtonB B Button key.
|
||||
// On a game controller, the B button should be either the button labeled B
|
||||
// or the second button on the bottom row of controller buttons.
|
||||
KCButtonB KeyCode = 97
|
||||
|
||||
// KCButtonC C Button key.
|
||||
// On a game controller, the C button should be either the button labeled C
|
||||
// or the third button on the bottom row of controller buttons.
|
||||
KCButtonC KeyCode = 98
|
||||
|
||||
// KCButtonX X Button key.
|
||||
// On a game controller, the X button should be either the button labeled X
|
||||
// or the first button on the upper row of controller buttons.
|
||||
KCButtonX KeyCode = 99
|
||||
|
||||
// KCButtonY Y Button key.
|
||||
// On a game controller, the Y button should be either the button labeled Y
|
||||
// or the second button on the upper row of controller buttons.
|
||||
KCButtonY KeyCode = 100
|
||||
|
||||
// KCButtonZ Z Button key.
|
||||
// On a game controller, the Z button should be either the button labeled Z
|
||||
// or the third button on the upper row of controller buttons.
|
||||
KCButtonZ KeyCode = 101
|
||||
|
||||
// KCButtonL1 L1 Button key.
|
||||
// On a game controller, the L1 button should be either the button labeled L1 (or L)
|
||||
// or the top left trigger button.
|
||||
KCButtonL1 KeyCode = 102
|
||||
|
||||
// KCButtonR1 R1 Button key.
|
||||
// On a game controller, the R1 button should be either the button labeled R1 (or R)
|
||||
// or the top right trigger button.
|
||||
KCButtonR1 KeyCode = 103
|
||||
|
||||
// KCButtonL2 L2 Button key.
|
||||
// On a game controller, the L2 button should be either the button labeled L2
|
||||
// or the bottom left trigger button.
|
||||
KCButtonL2 KeyCode = 104
|
||||
|
||||
// KCButtonR2 R2 Button key.
|
||||
// On a game controller, the R2 button should be either the button labeled R2
|
||||
// or the bottom right trigger button.
|
||||
KCButtonR2 KeyCode = 105
|
||||
|
||||
// KCButtonTHUMBL Left Thumb Button key.
|
||||
// On a game controller, the left thumb button indicates that the left (or only)
|
||||
// joystick is pressed.
|
||||
KCButtonTHUMBL KeyCode = 106
|
||||
|
||||
// KCButtonTHUMBR Right Thumb Button key.
|
||||
// On a game controller, the right thumb button indicates that the right
|
||||
// joystick is pressed.
|
||||
KCButtonTHUMBR KeyCode = 107
|
||||
|
||||
// KCButtonStart Start Button key.
|
||||
// On a game controller, the button labeled Start.
|
||||
KCButtonStart KeyCode = 108
|
||||
|
||||
// KCButtonSelect Select Button key.
|
||||
// On a game controller, the button labeled Select.
|
||||
KCButtonSelect KeyCode = 109
|
||||
|
||||
// KCButtonMode Mode Button key.
|
||||
// On a game controller, the button labeled Mode.
|
||||
KCButtonMode KeyCode = 110
|
||||
|
||||
// KCEscape Escape key.
|
||||
KCEscape KeyCode = 111
|
||||
|
||||
// KCForwardDel Forward Delete key.
|
||||
// Deletes characters ahead of the insertion point, unlike `KCDel`.
|
||||
KCForwardDel KeyCode = 112
|
||||
|
||||
KCCtrlLeft KeyCode = 113 // Left Control modifier key
|
||||
KCCtrlRight KeyCode = 114 // Right Control modifier key
|
||||
KCCapsLock KeyCode = 115 // Caps Lock key
|
||||
KCScrollLock KeyCode = 116 // Scroll Lock key
|
||||
KCMetaLeft KeyCode = 117 // Left Meta modifier key
|
||||
KCMetaRight KeyCode = 118 // Right Meta modifier key
|
||||
KCFunction KeyCode = 119 // Function modifier key
|
||||
KCSysRq KeyCode = 120 // System Request / Print Screen key
|
||||
KCBreak KeyCode = 121 // Break / Pause key
|
||||
|
||||
// KCMoveHome Home Movement key.
|
||||
// Used for scrolling or moving the cursor around to the start of a line
|
||||
// or to the top of a list.
|
||||
KCMoveHome KeyCode = 122
|
||||
|
||||
// KCMoveEnd End Movement key.
|
||||
// Used for scrolling or moving the cursor around to the end of a line
|
||||
// or to the bottom of a list.
|
||||
KCMoveEnd KeyCode = 123
|
||||
|
||||
// KCInsert Insert key.
|
||||
// Toggles insert / overwrite edit mode.
|
||||
KCInsert KeyCode = 124
|
||||
|
||||
// KCForward Forward key.
|
||||
// Navigates forward in the history stack. Complement of `KCBack`.
|
||||
KCForward KeyCode = 125
|
||||
|
||||
// KCMediaPlay Play media key.
|
||||
KCMediaPlay KeyCode = 126
|
||||
|
||||
// KCMediaPause Pause media key.
|
||||
KCMediaPause KeyCode = 127
|
||||
|
||||
// KCMediaClose Close media key.
|
||||
// May be used to close a CD tray, for example.
|
||||
KCMediaClose KeyCode = 128
|
||||
|
||||
// KCMediaEject Eject media key.
|
||||
// May be used to eject a CD tray, for example.
|
||||
KCMediaEject KeyCode = 129
|
||||
|
||||
// KCMediaRecord Record media key.
|
||||
KCMediaRecord KeyCode = 130
|
||||
|
||||
KCF1 KeyCode = 131 // F1 key.
|
||||
KCF2 KeyCode = 132 // F2 key.
|
||||
KCF3 KeyCode = 133 // F3 key.
|
||||
KCF4 KeyCode = 134 // F4 key.
|
||||
KCF5 KeyCode = 135 // F5 key.
|
||||
KCF6 KeyCode = 136 // F6 key.
|
||||
KCF7 KeyCode = 137 // F7 key.
|
||||
KCF8 KeyCode = 138 // F8 key.
|
||||
KCF9 KeyCode = 139 // F9 key.
|
||||
KCF10 KeyCode = 140 // F10 key.
|
||||
KCF11 KeyCode = 141 // F11 key.
|
||||
KCF12 KeyCode = 142 // F12 key.
|
||||
|
||||
// KCNumLock Num Lock key.
|
||||
// This is the Num Lock key; it is different from `KCNum`.
|
||||
// This key alters the behavior of other keys on the numeric keypad.
|
||||
KCNumLock KeyCode = 143
|
||||
|
||||
KCNumpad0 KeyCode = 144 // Numeric keypad '0' key
|
||||
KCNumpad1 KeyCode = 145 // Numeric keypad '1' key
|
||||
KCNumpad2 KeyCode = 146 // Numeric keypad '2' key
|
||||
KCNumpad3 KeyCode = 147 // Numeric keypad '3' key
|
||||
KCNumpad4 KeyCode = 148 // Numeric keypad '4' key
|
||||
KCNumpad5 KeyCode = 149 // Numeric keypad '5' key
|
||||
KCNumpad6 KeyCode = 150 // Numeric keypad '6' key
|
||||
KCNumpad7 KeyCode = 151 // Numeric keypad '7' key
|
||||
KCNumpad8 KeyCode = 152 // Numeric keypad '8' key
|
||||
KCNumpad9 KeyCode = 153 // Numeric keypad '9' key
|
||||
KCNumpadDivide KeyCode = 154 // Numeric keypad '/' key (for division)
|
||||
KCNumpadMultiply KeyCode = 155 // Numeric keypad '*' key (for multiplication)
|
||||
KCNumpadSubtract KeyCode = 156 // Numeric keypad '-' key (for subtraction)
|
||||
KCNumpadAdd KeyCode = 157 // Numeric keypad '+' key (for addition)
|
||||
KCNumpadDot KeyCode = 158 // Numeric keypad '.' key (for decimals or digit grouping)
|
||||
KCNumpadComma KeyCode = 159 // Numeric keypad ',' key (for decimals or digit grouping)
|
||||
KCNumpadEnter KeyCode = 160 // Numeric keypad Enter key
|
||||
KCNumpadEquals KeyCode = 161 // Numeric keypad 'KeyCode =' key
|
||||
KCNumpadLeftParen KeyCode = 162 // Numeric keypad '(' key
|
||||
KCNumpadRightParen KeyCode = 163 // Numeric keypad ')' key
|
||||
|
||||
// KCVolumeMute Volume Mute key.
|
||||
// Mutes the speaker, unlike `KCMute`.
|
||||
// This key should normally be implemented as a toggle such that the first press
|
||||
// mutes the speaker and the second press restores the original volume.
|
||||
KCVolumeMute KeyCode = 164
|
||||
|
||||
// KCInfo Info key.
|
||||
// Common on TV remotes to show additional information related to what is
|
||||
// currently being viewed.
|
||||
KCInfo KeyCode = 165
|
||||
|
||||
// KCChannelUp Channel up key.
|
||||
// On TV remotes, increments the television channel.
|
||||
KCChannelUp KeyCode = 166
|
||||
|
||||
// KCChannelDown Channel down key.
|
||||
// On TV remotes, decrements the television channel.
|
||||
KCChannelDown KeyCode = 167
|
||||
|
||||
// KCZoomIn Zoom in key.
|
||||
KCZoomIn KeyCode = 168
|
||||
|
||||
// KCZoomOut Zoom out key.
|
||||
KCZoomOut KeyCode = 169
|
||||
|
||||
// KCTv TV key.
|
||||
// On TV remotes, switches to viewing live TV.
|
||||
KCTv KeyCode = 170
|
||||
|
||||
// KCWindow Window key.
|
||||
// On TV remotes, toggles picture-in-picture mode or other windowing functions.
|
||||
// On Android Wear devices, triggers a display offset.
|
||||
KCWindow KeyCode = 171
|
||||
|
||||
// KCGuide Guide key.
|
||||
// On TV remotes, shows a programming guide.
|
||||
KCGuide KeyCode = 172
|
||||
|
||||
// KCDvr DVR key.
|
||||
// On some TV remotes, switches to a DVR mode for recorded shows.
|
||||
KCDvr KeyCode = 173
|
||||
|
||||
// KCBookmark Bookmark key.
|
||||
// On some TV remotes, bookmarks content or web pages.
|
||||
KCBookmark KeyCode = 174
|
||||
|
||||
// KCCaptions Toggle captions key.
|
||||
// Switches the mode for closed-captioning text, for example during television shows.
|
||||
KCCaptions KeyCode = 175
|
||||
|
||||
// KCSettings Settings key.
|
||||
// Starts the system settings activity.
|
||||
KCSettings KeyCode = 176
|
||||
|
||||
// KCTvPower TV power key.
|
||||
// On TV remotes, toggles the power on a television screen.
|
||||
KCTvPower KeyCode = 177
|
||||
|
||||
// KCTvInput TV input key.
|
||||
// On TV remotes, switches the input on a television screen.
|
||||
KCTvInput KeyCode = 178
|
||||
|
||||
// KCStbPower Set-top-box power key.
|
||||
// On TV remotes, toggles the power on an external Set-top-box.
|
||||
KCStbPower KeyCode = 179
|
||||
|
||||
// KCStbInput Set-top-box input key.
|
||||
// On TV remotes, switches the input mode on an external Set-top-box.
|
||||
KCStbInput KeyCode = 180
|
||||
|
||||
// KCAvrPower A/V Receiver power key.
|
||||
// On TV remotes, toggles the power on an external A/V Receiver.
|
||||
KCAvrPower KeyCode = 181
|
||||
|
||||
// KCAvrInput A/V Receiver input key.
|
||||
// On TV remotes, switches the input mode on an external A/V Receiver.
|
||||
KCAvrInput KeyCode = 182
|
||||
|
||||
// KCProgRed Red "programmable" key.
|
||||
// On TV remotes, acts as a contextual/programmable key.
|
||||
KCProgRed KeyCode = 183
|
||||
|
||||
// KCProgGreen Green "programmable" key.
|
||||
// On TV remotes, actsas a contextual/programmable key.
|
||||
KCProgGreen KeyCode = 184
|
||||
|
||||
// KCProgYellow Yellow "programmable" key.
|
||||
// On TV remotes, acts as a contextual/programmable key.
|
||||
KCProgYellow KeyCode = 185
|
||||
|
||||
// KCProgBlue Blue "programmable" key.
|
||||
// On TV remotes, acts as a contextual/programmable key.
|
||||
KCProgBlue KeyCode = 186
|
||||
|
||||
// KCAppSwitch App switch key.
|
||||
// Should bring up the application switcher dialog.
|
||||
KCAppSwitch KeyCode = 187
|
||||
|
||||
KCButton1 KeyCode = 188 // Generic Game Pad Button #1
|
||||
KCButton2 KeyCode = 189 // Generic Game Pad Button #2
|
||||
KCButton3 KeyCode = 190 // Generic Game Pad Button #3
|
||||
KCButton4 KeyCode = 191 // Generic Game Pad Button #4
|
||||
KCButton5 KeyCode = 192 // Generic Game Pad Button #5
|
||||
KCButton6 KeyCode = 193 // Generic Game Pad Button #6
|
||||
KCButton7 KeyCode = 194 // Generic Game Pad Button #7
|
||||
KCButton8 KeyCode = 195 // Generic Game Pad Button #8
|
||||
KCButton9 KeyCode = 196 // Generic Game Pad Button #9
|
||||
KCButton10 KeyCode = 197 // Generic Game Pad Button #10
|
||||
KCButton11 KeyCode = 198 // Generic Game Pad Button #11
|
||||
KCButton12 KeyCode = 199 // Generic Game Pad Button #12
|
||||
KCButton13 KeyCode = 200 // Generic Game Pad Button #13
|
||||
KCButton14 KeyCode = 201 // Generic Game Pad Button #14
|
||||
KCButton15 KeyCode = 202 // Generic Game Pad Button #15
|
||||
KCButton16 KeyCode = 203 // Generic Game Pad Button #16
|
||||
|
||||
// KCLanguageSwitch Language Switch key.
|
||||
// Toggles the current input language such as switching between English and Japanese on
|
||||
// a QWERTY keyboard. On some devices, the same function may be performed by
|
||||
// pressing Shift+Spacebar.
|
||||
KCLanguageSwitch KeyCode = 204
|
||||
|
||||
// Manner Mode key.
|
||||
// Toggles silent or vibrate mode on and off to make the device behave more politely
|
||||
// in certain settings such as on a crowded train. On some devices, the key may only
|
||||
// operate when long-pressed.
|
||||
KCMannerMode KeyCode = 205
|
||||
|
||||
// 3D Mode key.
|
||||
// Toggles the display between 2D and 3D mode.
|
||||
KC3dMode KeyCode = 206
|
||||
|
||||
// Contacts special function key.
|
||||
// Used to launch an address book application.
|
||||
KCContacts KeyCode = 207
|
||||
|
||||
// Calendar special function key.
|
||||
// Used to launch a calendar application.
|
||||
KCCalendar KeyCode = 208
|
||||
|
||||
// Music special function key.
|
||||
// Used to launch a music player application.
|
||||
KCMusic KeyCode = 209
|
||||
|
||||
// Calculator special function key.
|
||||
// Used to launch a calculator application.
|
||||
KCCalculator KeyCode = 210
|
||||
|
||||
// Japanese full-width / half-width key.
|
||||
KCZenkakuHankaku KeyCode = 211
|
||||
|
||||
// Japanese alphanumeric key.
|
||||
KCEisu KeyCode = 212
|
||||
|
||||
// Japanese non-conversion key.
|
||||
KCMuhenkan KeyCode = 213
|
||||
|
||||
// Japanese conversion key.
|
||||
KCHenkan KeyCode = 214
|
||||
|
||||
// Japanese katakana / hiragana key.
|
||||
KCKatakanaHiragana KeyCode = 215
|
||||
|
||||
// Japanese Yen key.
|
||||
KCYen KeyCode = 216
|
||||
|
||||
// Japanese Ro key.
|
||||
KCRo KeyCode = 217
|
||||
|
||||
// Japanese kana key.
|
||||
KCKana KeyCode = 218
|
||||
|
||||
// Assist key.
|
||||
// Launches the global assist activity. Not delivered to applications.
|
||||
KCAssist KeyCode = 219
|
||||
|
||||
// Brightness Down key.
|
||||
// Adjusts the screen brightness down.
|
||||
KCBrightnessDown KeyCode = 220
|
||||
|
||||
// Brightness Up key.
|
||||
// Adjusts the screen brightness up.
|
||||
KCBrightnessUp KeyCode = 221
|
||||
|
||||
// Audio Track key.
|
||||
// Switches the audio tracks.
|
||||
KCMediaAudioTrack KeyCode = 222
|
||||
|
||||
// Sleep key.
|
||||
// Puts the device to sleep. Behaves somewhat like {@link #KEYCODE_POWER} but it
|
||||
// has no effect if the device is already asleep.
|
||||
KCSleep KeyCode = 223
|
||||
|
||||
// Wakeup key.
|
||||
// Wakes up the device. Behaves somewhat like {@link #KEYCODE_POWER} but it
|
||||
// has no effect if the device is already awake.
|
||||
KCWakeup KeyCode = 224
|
||||
|
||||
// Pairing key.
|
||||
// Initiates peripheral pairing mode. Useful for pairing remote control
|
||||
// devices or game controllers, especially if no other input mode is
|
||||
// available.
|
||||
KCPairing KeyCode = 225
|
||||
|
||||
// Media Top Menu key.
|
||||
// Goes to the top of media menu.
|
||||
KCMediaTopMenu KeyCode = 226
|
||||
|
||||
// '11' key.
|
||||
KC11 KeyCode = 227
|
||||
|
||||
// '12' key.
|
||||
KC12 KeyCode = 228
|
||||
|
||||
// Last Channel key.
|
||||
// Goes to the last viewed channel.
|
||||
KCLastChannel KeyCode = 229
|
||||
|
||||
// TV data service key.
|
||||
// Displays data services like weather, sports.
|
||||
KCTvDataService KeyCode = 230
|
||||
|
||||
// Voice Assist key.
|
||||
// Launches the global voice assist activity. Not delivered to applications.
|
||||
KCVoiceAssist KeyCode = 231
|
||||
|
||||
// Radio key.
|
||||
// Toggles TV service / Radio service.
|
||||
KCTvRadioService KeyCode = 232
|
||||
|
||||
// Teletext key.
|
||||
// Displays Teletext service.
|
||||
KCTvTeletext KeyCode = 233
|
||||
|
||||
// Number entry key.
|
||||
// Initiates to enter multi-digit channel nubmber when each digit key is assigned
|
||||
// for selecting separate channel. Corresponds to Number Entry Mode (0x1D) of CEC
|
||||
// User Control Code.
|
||||
KCTvNumberEntry KeyCode = 234
|
||||
|
||||
// Analog Terrestrial key.
|
||||
// Switches to analog terrestrial broadcast service.
|
||||
KCTvTerrestrialAnalog KeyCode = 235
|
||||
|
||||
// Digital Terrestrial key.
|
||||
// Switches to digital terrestrial broadcast service.
|
||||
KCTvTerrestrialDigital KeyCode = 236
|
||||
|
||||
// Satellite key.
|
||||
// Switches to digital satellite broadcast service.
|
||||
KCTvSatellite KeyCode = 237
|
||||
|
||||
// BS key.
|
||||
// Switches to BS digital satellite broadcasting service available in Japan.
|
||||
KCTvSatelliteBs KeyCode = 238
|
||||
|
||||
// CS key.
|
||||
// Switches to CS digital satellite broadcasting service available in Japan.
|
||||
KCTvSatelliteCs KeyCode = 239
|
||||
|
||||
// BS/CS key.
|
||||
// Toggles between BS and CS digital satellite services.
|
||||
KCTvSatelliteService KeyCode = 240
|
||||
|
||||
// Toggle Network key.
|
||||
// Toggles selecting broacast services.
|
||||
KCTvNetwork KeyCode = 241
|
||||
|
||||
// Antenna/Cable key.
|
||||
// Toggles broadcast input source between antenna and cable.
|
||||
KCTvAntennaCable KeyCode = 242
|
||||
|
||||
// HDMI #1 key.
|
||||
// Switches to HDMI input #1.
|
||||
KCTvInputHdmi1 KeyCode = 243
|
||||
|
||||
// HDMI #2 key.
|
||||
// Switches to HDMI input #2.
|
||||
KCTvInputHdmi2 KeyCode = 244
|
||||
|
||||
// HDMI #3 key.
|
||||
// Switches to HDMI input #3.
|
||||
KCTvInputHdmi3 KeyCode = 245
|
||||
|
||||
// HDMI #4 key.
|
||||
// Switches to HDMI input #4.
|
||||
KCTvInputHdmi4 KeyCode = 246
|
||||
|
||||
// Composite #1 key.
|
||||
// Switches to composite video input #1.
|
||||
KCTvInputComposite1 KeyCode = 247
|
||||
|
||||
// Composite #2 key.
|
||||
// Switches to composite video input #2.
|
||||
KCTvInputComposite2 KeyCode = 248
|
||||
|
||||
// Component #1 key.
|
||||
// Switches to component video input #1.
|
||||
KCTvInputComponent1 KeyCode = 249
|
||||
|
||||
// Component #2 key.
|
||||
// Switches to component video input #2.
|
||||
KCTvInputComponent2 KeyCode = 250
|
||||
|
||||
// VGA #1 key.
|
||||
// Switches to VGA (analog RGB) input #1.
|
||||
KCTvInputVga1 KeyCode = 251
|
||||
|
||||
// Audio description key.
|
||||
// Toggles audio description off / on.
|
||||
KCTvAudioDescription KeyCode = 252
|
||||
|
||||
// Audio description mixing volume up key.
|
||||
// Louden audio description volume as compared with normal audio volume.
|
||||
KCTvAudioDescriptionMixUp KeyCode = 253
|
||||
|
||||
// Audio description mixing volume down key.
|
||||
// Lessen audio description volume as compared with normal audio volume.
|
||||
KCTvAudioDescriptionMixDown KeyCode = 254
|
||||
|
||||
// Zoom mode key.
|
||||
// Changes Zoom mode (Normal, Full, Zoom, Wide-zoom, etc.)
|
||||
KCTvZoomMode KeyCode = 255
|
||||
|
||||
// Contents menu key.
|
||||
// Goes to the title list. Corresponds to Contents Menu (0x0B) of CEC User Control
|
||||
// Code
|
||||
KCTvContentsMenu KeyCode = 256
|
||||
|
||||
// Media context menu key.
|
||||
// Goes to the context menu of media contents. Corresponds to Media Context-sensitive
|
||||
// Menu (0x11) of CEC User Control Code.
|
||||
KCTvMediaContextMenu KeyCode = 257
|
||||
|
||||
// Timer programming key.
|
||||
// Goes to the timer recording menu. Corresponds to Timer Programming (0x54) of
|
||||
// CEC User Control Code.
|
||||
KCTvTimerProgramming KeyCode = 258
|
||||
|
||||
// Help key.
|
||||
KCHelp KeyCode = 259
|
||||
|
||||
// Navigate to previous key.
|
||||
// Goes backward by one item in an ordered collection of items.
|
||||
KCNavigatePrevious KeyCode = 260
|
||||
|
||||
// Navigate to next key.
|
||||
// Advances to the next item in an ordered collection of items.
|
||||
KCNavigateNext KeyCode = 261
|
||||
|
||||
// Navigate in key.
|
||||
// Activates the item that currently has focus or expands to the next level of a navigation
|
||||
// hierarchy.
|
||||
KCNavigateIn KeyCode = 262
|
||||
|
||||
// Navigate out key.
|
||||
// Backs out one level of a navigation hierarchy or collapses the item that currently has
|
||||
// focus.
|
||||
KCNavigateOut KeyCode = 263
|
||||
|
||||
// Primary stem key for Wear
|
||||
// Main power/reset button on watch.
|
||||
KCStemPrimary KeyCode = 264
|
||||
|
||||
// Generic stem key 1 for Wear
|
||||
KCStem1 KeyCode = 265
|
||||
|
||||
// Generic stem key 2 for Wear
|
||||
KCStem2 KeyCode = 266
|
||||
|
||||
// Generic stem key 3 for Wear
|
||||
KCStem3 KeyCode = 267
|
||||
|
||||
// Directional Pad Up-Left
|
||||
KCDPadUpLeft KeyCode = 268
|
||||
|
||||
// Directional Pad Down-Left
|
||||
KCDPadDownLeft KeyCode = 269
|
||||
|
||||
// Directional Pad Up-Right
|
||||
KCDPadUpRight KeyCode = 270
|
||||
|
||||
// Directional Pad Down-Right
|
||||
KCDPadDownRight KeyCode = 271
|
||||
|
||||
// Skip forward media key.
|
||||
KCMediaSkipForward KeyCode = 272
|
||||
|
||||
// Skip backward media key.
|
||||
KCMediaSkipBackward KeyCode = 273
|
||||
|
||||
// Step forward media key.
|
||||
// Steps media forward, one frame at a time.
|
||||
KCMediaStepForward KeyCode = 274
|
||||
|
||||
// Step backward media key.
|
||||
// Steps media backward, one frame at a time.
|
||||
KCMediaStepBackward KeyCode = 275
|
||||
|
||||
// put device to sleep unless a wakelock is held.
|
||||
KCSoftSleep KeyCode = 276
|
||||
|
||||
// Cut key.
|
||||
KCCut KeyCode = 277
|
||||
|
||||
// Copy key.
|
||||
KCCopy KeyCode = 278
|
||||
|
||||
// Paste key.
|
||||
KCPaste KeyCode = 279
|
||||
|
||||
// Consumed by the system for navigation up
|
||||
KCSystemNavigationUp KeyCode = 280
|
||||
|
||||
// Consumed by the system for navigation down
|
||||
KCSystemNavigationDown KeyCode = 281
|
||||
|
||||
// Consumed by the system for navigation left*/
|
||||
KCSystemNavigationLeft KeyCode = 282
|
||||
|
||||
// Consumed by the system for navigation right
|
||||
KCSystemNavigationRight KeyCode = 283
|
||||
|
||||
// Show all apps
|
||||
KCAllApps KeyCode = 284
|
||||
|
||||
// Refresh key.
|
||||
KCRefresh KeyCode = 285
|
||||
)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +0,0 @@
|
|||
package uixt
|
||||
|
||||
type uiaWebDriver struct{}
|
|
@ -1 +0,0 @@
|
|||
package uixt
|
|
@ -0,0 +1,104 @@
|
|||
package uixt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (wd *Driver) concatURL(u *url.URL, elem ...string) string {
|
||||
var tmp *url.URL
|
||||
if u == nil {
|
||||
u = wd.urlPrefix
|
||||
}
|
||||
tmp, _ = url.Parse(u.String())
|
||||
tmp.Path = path.Join(append([]string{u.Path}, elem...)...)
|
||||
return tmp.String()
|
||||
}
|
||||
|
||||
func (wd *Driver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
|
||||
return wd.httpRequest(http.MethodGet, wd.concatURL(nil, pathElem...), nil)
|
||||
}
|
||||
|
||||
func (wd *Driver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
|
||||
var bsJSON []byte = nil
|
||||
if data != nil {
|
||||
if bsJSON, err = json.Marshal(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return wd.httpRequest(http.MethodPost, wd.concatURL(nil, pathElem...), bsJSON)
|
||||
}
|
||||
|
||||
func (wd *Driver) httpDELETE(pathElem ...string) (rawResp rawResponse, err error) {
|
||||
return wd.httpRequest(http.MethodDelete, wd.concatURL(nil, pathElem...), nil)
|
||||
}
|
||||
|
||||
func (wd *Driver) httpRequest(method string, rawURL string, rawBody []byte) (rawResp rawResponse, err error) {
|
||||
log.Debug().Str("method", method).Str("url", rawURL).Str("body", string(rawBody)).Msg("request WDA")
|
||||
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequest(method, rawURL, bytes.NewBuffer(rawBody)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
start := time.Now()
|
||||
var resp *http.Response
|
||||
if resp, err = wd.client.Do(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
// https://github.com/etcd-io/etcd/blob/v3.3.25/pkg/httputil/httputil.go#L16-L22
|
||||
_, _ = io.Copy(ioutil.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
rawResp, err = ioutil.ReadAll(resp.Body)
|
||||
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", time.Since(start).String())
|
||||
if !strings.HasSuffix(rawURL, "screenshot") {
|
||||
// avoid printing screenshot data
|
||||
logger.Str("response", string(rawResp))
|
||||
}
|
||||
logger.Msg("get WDA response")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = rawResp.checkErr(); err != nil {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return rawResp, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func convertToHTTPClient(conn net.Conn) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
},
|
||||
},
|
||||
Timeout: 0,
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ package uixt
|
|||
|
||||
import (
|
||||
"image"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func (dExt *DriverExt) GesturePassword(pathname string, password ...int) (err error) {
|
||||
|
|
|
@ -164,6 +164,8 @@ type BatteryInfo struct {
|
|||
|
||||
// Battery state ( 1: on battery, discharging; 2: plugged in, less than 100%, 3: plugged in, at 100% )
|
||||
State BatteryState `json:"state"`
|
||||
|
||||
Status BatteryStatus `json:"status"`
|
||||
}
|
||||
|
||||
type BatteryState int
|
||||
|
@ -683,6 +685,11 @@ type Point struct {
|
|||
Y int `json:"y"` // upper left Y coordinate of selected element
|
||||
}
|
||||
|
||||
type PointF struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
|
||||
type Rect struct {
|
||||
Point
|
||||
Size
|
||||
|
@ -708,6 +715,11 @@ func WithFrequency(frequency int) DataOption {
|
|||
}
|
||||
}
|
||||
|
||||
// current implemeted device: IOSDevice, AndroidDevice
|
||||
type Device interface {
|
||||
UUID() string
|
||||
}
|
||||
|
||||
// WebDriver defines methods supported by WebDriver drivers.
|
||||
type WebDriver interface {
|
||||
// NewSession starts a new session and returns the SessionInfo.
|
||||
|
|
|
@ -2,7 +2,6 @@ package uixt
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
builtinJSON "encoding/json"
|
||||
"fmt"
|
||||
|
@ -14,7 +13,6 @@ import (
|
|||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
giDevice "github.com/electricbubble/gidevice"
|
||||
|
@ -40,7 +38,7 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultPort = 8100
|
||||
defaultWDAPort = 8100
|
||||
defaultMjpegPort = 9100
|
||||
)
|
||||
|
||||
|
@ -102,10 +100,6 @@ func InitWDAClient(device *IOSDevice) (*DriverExt, error) {
|
|||
return driverExt, nil
|
||||
}
|
||||
|
||||
type Device interface {
|
||||
UUID() string
|
||||
}
|
||||
|
||||
type IOSDeviceOption func(*IOSDevice)
|
||||
|
||||
func WithUDID(udid string) IOSDeviceOption {
|
||||
|
@ -144,7 +138,7 @@ func NewIOSDevice(options ...IOSDeviceOption) (device *IOSDevice, err error) {
|
|||
}
|
||||
|
||||
device = &IOSDevice{
|
||||
Port: defaultPort,
|
||||
Port: defaultWDAPort,
|
||||
MjpegPort: defaultMjpegPort,
|
||||
}
|
||||
for _, option := range options {
|
||||
|
@ -191,37 +185,33 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver
|
|||
return nil, err
|
||||
}
|
||||
wd.sessionId = sessionInfo.SessionId
|
||||
wd.client = http.DefaultClient
|
||||
|
||||
if wd.mjpegConn, err = net.Dial(
|
||||
if wd.mjpegHTTPConn, err = net.Dial(
|
||||
"tcp",
|
||||
fmt.Sprintf("%s:%d", wd.urlPrefix.Hostname(),
|
||||
dev.MjpegPort),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wd.mjpegClient = convertToHTTPClient(wd.mjpegConn)
|
||||
wd.mjpegClient = convertToHTTPClient(wd.mjpegHTTPConn)
|
||||
|
||||
return wd, nil
|
||||
}
|
||||
|
||||
// NewUSBDriver creates new client via USB connected device, this will also start a new session.
|
||||
func (dev *IOSDevice) NewUSBDriver(capabilities Capabilities) (driver WebDriver, err error) {
|
||||
wd := &wdaDriver{
|
||||
usbCli: &struct {
|
||||
httpCli *http.Client
|
||||
defaultConn, mjpegConn giDevice.InnerConn
|
||||
sync.Mutex
|
||||
}{},
|
||||
}
|
||||
if wd.usbCli.defaultConn, err = dev.NewConnect(dev.Port, 0); err != nil {
|
||||
wd := new(wdaDriver)
|
||||
|
||||
if wd.defaultConn, err = dev.NewConnect(dev.Port, 0); err != nil {
|
||||
return nil, fmt.Errorf("create connection: %w", err)
|
||||
}
|
||||
wd.usbCli.httpCli = convertToHTTPClient(wd.usbCli.defaultConn.RawConn())
|
||||
wd.client = convertToHTTPClient(wd.defaultConn.RawConn())
|
||||
|
||||
if wd.usbCli.mjpegConn, err = dev.NewConnect(dev.MjpegPort, 0); err != nil {
|
||||
if wd.mjpegUSBConn, err = dev.NewConnect(dev.MjpegPort, 0); err != nil {
|
||||
return nil, fmt.Errorf("create connection MJPEG: %w", err)
|
||||
}
|
||||
wd.mjpegClient = convertToHTTPClient(wd.usbCli.mjpegConn.RawConn())
|
||||
wd.mjpegClient = convertToHTTPClient(wd.mjpegUSBConn.RawConn())
|
||||
|
||||
if wd.urlPrefix, err = url.Parse("http://" + dev.UDID); err != nil {
|
||||
return nil, err
|
||||
|
@ -245,17 +235,6 @@ func (dev *IOSDevice) NewUSBDriver(capabilities Capabilities) (driver WebDriver,
|
|||
return wd, err
|
||||
}
|
||||
|
||||
func convertToHTTPClient(_conn net.Conn) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
||||
return _conn, nil
|
||||
},
|
||||
},
|
||||
Timeout: 0,
|
||||
}
|
||||
}
|
||||
|
||||
type wdaResponse struct {
|
||||
Value string `json:"value"`
|
||||
SessionID string `json:"sessionId"`
|
||||
|
@ -374,9 +353,10 @@ type rawResponse []byte
|
|||
func (r rawResponse) checkErr() (err error) {
|
||||
reply := new(struct {
|
||||
Value struct {
|
||||
Err string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Traceback string `json:"traceback"`
|
||||
Err string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Traceback string `json:"traceback"` // wda
|
||||
Stacktrace string `json:"stacktrace"` // uia
|
||||
}
|
||||
})
|
||||
if err = json.Unmarshal(r, reply); err != nil {
|
||||
|
|
|
@ -5,57 +5,46 @@ import (
|
|||
"encoding/base64"
|
||||
builtinJSON "encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
giDevice "github.com/electricbubble/gidevice"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||
)
|
||||
|
||||
type wdaDriver struct {
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
Driver
|
||||
|
||||
usbCli *struct {
|
||||
httpCli *http.Client
|
||||
defaultConn, mjpegConn giDevice.InnerConn
|
||||
sync.Mutex
|
||||
}
|
||||
// default port
|
||||
defaultConn giDevice.InnerConn
|
||||
|
||||
mjpegClient *http.Client
|
||||
mjpegConn net.Conn
|
||||
// mjpeg port
|
||||
mjpegUSBConn giDevice.InnerConn // via USB
|
||||
mjpegHTTPConn net.Conn // via HTTP
|
||||
mjpegClient *http.Client
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) GetMjpegHTTPClient() *http.Client {
|
||||
func (wd *wdaDriver) GetMjpegClient() *http.Client {
|
||||
return wd.mjpegClient
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) Close() error {
|
||||
if wd.usbCli == nil {
|
||||
if wd.defaultConn != nil {
|
||||
wd.defaultConn.Close()
|
||||
}
|
||||
if wd.mjpegUSBConn != nil {
|
||||
wd.mjpegUSBConn.Close()
|
||||
}
|
||||
|
||||
if wd.mjpegClient != nil {
|
||||
wd.mjpegClient.CloseIdleConnections()
|
||||
return wd.mjpegConn.Close()
|
||||
}
|
||||
|
||||
wd.usbCli.Lock()
|
||||
defer wd.usbCli.Unlock()
|
||||
|
||||
if wd.usbCli.defaultConn != nil {
|
||||
wd.usbCli.defaultConn.Close()
|
||||
}
|
||||
if wd.usbCli.mjpegConn != nil {
|
||||
wd.usbCli.mjpegConn.Close()
|
||||
}
|
||||
return nil
|
||||
return wd.mjpegHTTPConn.Close()
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
|
||||
|
@ -844,83 +833,3 @@ func (wd *wdaDriver) WaitWithTimeout(condition Condition, timeout time.Duration)
|
|||
func (wd *wdaDriver) Wait(condition Condition) error {
|
||||
return wd.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) concatURL(u *url.URL, elem ...string) string {
|
||||
var tmp *url.URL
|
||||
if u == nil {
|
||||
u = wd.urlPrefix
|
||||
}
|
||||
tmp, _ = url.Parse(u.String())
|
||||
tmp.Path = path.Join(append([]string{u.Path}, elem...)...)
|
||||
return tmp.String()
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
|
||||
return wd.httpRequest(http.MethodGet, wd.concatURL(nil, pathElem...), nil)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
|
||||
var bsJSON []byte = nil
|
||||
if data != nil {
|
||||
if bsJSON, err = json.Marshal(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return wd.httpRequest(http.MethodPost, wd.concatURL(nil, pathElem...), bsJSON)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err error) {
|
||||
return wd.httpRequest(http.MethodDelete, wd.concatURL(nil, pathElem...), nil)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) httpRequest(method string, rawURL string, rawBody []byte) (rawResp rawResponse, err error) {
|
||||
log.Debug().Str("method", method).Str("url", rawURL).Str("body", string(rawBody)).Msg("request WDA")
|
||||
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequest(method, rawURL, bytes.NewBuffer(rawBody)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
var httpCli *http.Client
|
||||
if wd.usbCli != nil {
|
||||
wd.usbCli.Lock()
|
||||
defer wd.usbCli.Unlock()
|
||||
httpCli = wd.usbCli.httpCli
|
||||
} else {
|
||||
httpCli = http.DefaultClient
|
||||
}
|
||||
httpCli.Timeout = 0
|
||||
|
||||
start := time.Now()
|
||||
var resp *http.Response
|
||||
if resp, err = httpCli.Do(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
// https://github.com/etcd-io/etcd/blob/v3.3.25/pkg/httputil/httputil.go#L16-L22
|
||||
_, _ = io.Copy(ioutil.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
rawResp, err = ioutil.ReadAll(resp.Body)
|
||||
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", time.Since(start).String())
|
||||
if !strings.HasSuffix(rawURL, "screenshot") {
|
||||
// avoid printing screenshot data
|
||||
logger.Bytes("response", rawResp)
|
||||
}
|
||||
logger.Msg("get WDA response")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = rawResp.checkErr(); err != nil {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return rawResp, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -351,8 +351,6 @@ func Test_remoteWD_Homescreen(t *testing.T) {
|
|||
func Test_remoteWD_AppLaunch(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
// SetDebug(true)
|
||||
|
||||
// bundleId = "com.hustlzp.xcz"
|
||||
// bundleId = "com.github.stormbreaker.prod"
|
||||
// bundleId = "com.360buy.jdmobile"
|
||||
|
|
|
@ -10,8 +10,6 @@ func TestDriverExt_TapWithNumber(t *testing.T) {
|
|||
|
||||
pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/flag7.png"
|
||||
|
||||
// SetDebug(true)
|
||||
|
||||
err = driverExt.TapWithNumber(pathSearch, 3)
|
||||
checkErr(t, err)
|
||||
|
||||
|
|
Loading…
Reference in New Issue