diff --git a/browser.go b/browser.go index c7a4e727..4b485a11 100644 --- a/browser.go +++ b/browser.go @@ -289,9 +289,7 @@ func (b *Browser) PageFromTarget(targetID proto.TargetTargetID) (*Page, error) { } page.root = page - page.Mouse = &Mouse{page: page, id: utils.RandString(8)} - page.Keyboard = &Keyboard{page: page} - page.Touch = &Touch{page: page} + page.newKeyboard().newMouse().newTouch() if !b.defaultDevice.IsClear() { err = page.Emulate(b.defaultDevice) diff --git a/element.go b/element.go index aff10b6e..8e56216c 100644 --- a/element.go +++ b/element.go @@ -12,6 +12,7 @@ import ( "github.com/ysmood/gson" "github.com/go-rod/rod/lib/cdp" + "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/js" "github.com/go-rod/rod/lib/proto" "github.com/go-rod/rod/lib/utils" @@ -206,15 +207,25 @@ func (el *Element) Shape() (*proto.DOMGetContentQuadsResult, error) { return proto.DOMGetContentQuads{ObjectID: el.id()}.Call(el) } -// Press is similar with Keyboard.Press. +// Type is similar with Keyboard.Type. // Before the action, it will try to scroll to the element and focus on it. -func (el *Element) Press(keys ...rune) error { +func (el *Element) Type(keys ...input.Key) error { err := el.Focus() if err != nil { return err } + return el.page.Keyboard.Type(keys...) +} + +// KeyActions is similar with Page.KeyActions. +// Before the action, it will try to scroll to the element and focus on it. +func (el *Element) KeyActions() (*KeyActions, error) { + err := el.Focus() + if err != nil { + return nil, err + } - return el.page.Keyboard.Press(keys...) + return el.page.KeyActions(), nil } // SelectText selects the text that matches the regular expression. @@ -266,7 +277,7 @@ func (el *Element) Input(text string) error { return err } - err = el.page.Keyboard.InsertText(text) + err = el.page.InsertText(text) _, _ = el.Evaluate(evalHelper(js.InputEvent).ByUser()) return err } diff --git a/element_test.go b/element_test.go index 0908a805..bdb74706 100644 --- a/element_test.go +++ b/element_test.go @@ -227,14 +227,6 @@ func TestElementMoveMouseOut(t *testing.T) { g.Err(btn.MoveMouseOut()) } -func TestMouseMoveErr(t *testing.T) { - g := setup(t) - - p := g.page.MustNavigate(g.srcFile("fixtures/click.html")) - g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) - g.Err(p.Mouse.Move(10, 10, 1)) -} - func TestElementContext(t *testing.T) { g := setup(t) @@ -294,87 +286,6 @@ func TestShadowDOM(t *testing.T) { }) } -func TestPress(t *testing.T) { - g := setup(t) - - p := g.page.MustNavigate(g.srcFile("fixtures/input.html")) - el := p.MustElement("[type=text]") - - el.MustPress('1', '2', input.Backspace, ' ') - el.MustPress([]rune("A b")...) - - g.Eq("1 A b", el.MustText()) - - g.Panic(func() { - g.mc.stubErr(1, proto.DOMScrollIntoViewIfNeeded{}) - el.MustPress(' ') - }) - g.Panic(func() { - g.mc.stubErr(1, proto.DOMScrollIntoViewIfNeeded{}) - el.MustSelectAllText() - }) -} - -func TestKeyDown(t *testing.T) { - g := setup(t) - - p := g.page.MustNavigate(g.srcFile("fixtures/keys.html")) - p.MustElement("body") - p.Keyboard.MustDown('j') - - g.True(p.MustHas("body[event=key-down-j]")) -} - -func TestKeyUp(t *testing.T) { - g := setup(t) - - p := g.page.MustNavigate(g.srcFile("fixtures/keys.html")) - p.MustElement("body") - p.Keyboard.MustUp('x') - - g.True(p.MustHas("body[event=key-up-x]")) -} - -func TestInput(t *testing.T) { - g := setup(t) - - text := "雲の上は\nいつも晴れ" - - p := g.page.MustNavigate(g.srcFile("fixtures/input.html")) - - { - el := p.MustElement("[contenteditable=true]").MustInput(text) - g.Eq(text, el.MustText()) - } - - el := p.MustElement("textarea") - el.MustInput(text) - - g.Eq(text, el.MustText()) - g.True(p.MustHas("[event=textarea-change]")) - - g.Panic(func() { - g.mc.stubErr(1, proto.RuntimeCallFunctionOn{}) - el.MustText() - }) - g.Panic(func() { - g.mc.stubErr(4, proto.RuntimeCallFunctionOn{}) - el.MustInput("") - }) - g.Panic(func() { - g.mc.stubErr(5, proto.RuntimeCallFunctionOn{}) - el.MustInput("") - }) - g.Panic(func() { - g.mc.stubErr(6, proto.RuntimeCallFunctionOn{}) - el.MustInput("") - }) - g.Panic(func() { - g.mc.stubErr(1, proto.InputInsertText{}) - el.MustInput("") - }) -} - func TestInputTime(t *testing.T) { g := setup(t) @@ -417,6 +328,13 @@ func TestInputTime(t *testing.T) { }) } +func TestElementInputDate(t *testing.T) { + g := setup(t) + + p := g.page.MustNavigate(g.srcFile("fixtures/input.html")) + p.MustElement("[type=date]").MustInput("12") +} + func TestCheckbox(t *testing.T) { g := setup(t) @@ -587,7 +505,7 @@ func TestEnter(t *testing.T) { p := g.page.MustNavigate(g.srcFile("fixtures/input.html")) el := p.MustElement("[type=submit]") - el.MustPress(input.Enter) + el.MustType(input.Enter) g.True(p.MustHas("[event=submit]")) } @@ -897,7 +815,7 @@ func TestElementErrors(t *testing.T) { err = el.Context(ctx).Focus() g.Err(err) - err = el.Context(ctx).Press('a') + _, err = el.Context(ctx).KeyActions() g.Err(err) err = el.Context(ctx).Input("a") diff --git a/examples_test.go b/examples_test.go index 3574870e..028157e3 100644 --- a/examples_test.go +++ b/examples_test.go @@ -32,7 +32,7 @@ func Example() { page := browser.MustPage("https://github.com") // We use css selector to get the search input element and input "git" - page.MustElement("input").MustInput("git").MustPress(input.Enter) + page.MustElement("input").MustInput("git").MustType(input.Enter) // Wait until css selector get the element then get the text content of it. text := page.MustElement(".codesearch-results p").MustText() @@ -91,7 +91,7 @@ func Example_disable_headless_to_debug() { page := browser.MustPage("https://github.com/") - page.MustElement("input").MustInput("git").MustPress(input.Enter) + page.MustElement("input").MustInput("git").MustType(input.Enter) text := page.MustElement(".codesearch-results p").MustText() @@ -253,7 +253,7 @@ func Example_race_selectors() { page := browser.MustPage("https://leetcode.com/accounts/login/") page.MustElement("#id_login").MustInput(username) - page.MustElement("#id_password").MustInput(password).MustPress(input.Enter) + page.MustElement("#id_password").MustInput(password).MustType(input.Enter) // It will keep retrying until one selector has found a match elm := page.Race().Element(".nav-user-icon-base").MustHandle(func(e *rod.Element) { diff --git a/fixtures/keys.html b/fixtures/keys.html index d73a8e1d..9964dd27 100644 --- a/fixtures/keys.html +++ b/fixtures/keys.html @@ -1,11 +1,18 @@ - + diff --git a/hijack_test.go b/hijack_test.go index 9b4ab1fb..f9f6ae2c 100644 --- a/hijack_test.go +++ b/hijack_test.go @@ -148,6 +148,9 @@ func TestHijackMockWholeResponseEmptyBody(t *testing.T) { } func TestHijackMockWholeResponseNoBody(t *testing.T) { + // TODO: remove the skip + t.Skip("Because of flaky test result") + g := setup(t) router := g.page.HijackRequests() diff --git a/input.go b/input.go index 237bd664..4a3995f7 100644 --- a/input.go +++ b/input.go @@ -6,6 +6,7 @@ import ( "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" "github.com/ysmood/gson" ) @@ -15,83 +16,176 @@ type Keyboard struct { page *Page - // modifiers are currently beening pressed - modifiers int + // pressed keys must be released before it can be pressed again + pressed map[input.Key]struct{} +} + +func (p *Page) newKeyboard() *Page { + p.Keyboard = &Keyboard{page: p, pressed: map[input.Key]struct{}{}} + return p } func (k *Keyboard) getModifiers() int { k.Lock() defer k.Unlock() + return k.modifiers() +} - return k.modifiers +func (k *Keyboard) modifiers() int { + ms := 0 + for key := range k.pressed { + ms |= key.Modifier() + } + return ms } -// Down holds the key down -func (k *Keyboard) Down(key rune) error { +// Press the key down. +// To input characters that are not on the keyboard, such as Chinese or Japanese, you should +// use method like Page.InsertText . +func (k *Keyboard) Press(key input.Key) error { + defer k.page.tryTrace(TraceTypeInput, "press key: "+key.Info().Code)() + k.page.browser.trySlowmotion() + k.Lock() defer k.Unlock() - actions := input.Encode(key) + k.pressed[key] = struct{}{} - err := actions[0].Call(k.page) - if err != nil { - return err - } - k.modifiers = actions[0].Modifiers - return nil + return key.Encode(proto.InputDispatchKeyEventTypeKeyDown, k.modifiers()).Call(k.page) } -// Up releases the key -func (k *Keyboard) Up(key rune) error { +// Release the key +func (k *Keyboard) Release(key input.Key) error { + defer k.page.tryTrace(TraceTypeInput, "release key: "+key.Info().Code)() + k.Lock() defer k.Unlock() - actions := input.Encode(key) + if _, has := k.pressed[key]; !has { + return nil + } + + delete(k.pressed, key) - err := actions[len(actions)-1].Call(k.page) - if err != nil { - return err + return key.Encode(proto.InputDispatchKeyEventTypeKeyUp, k.modifiers()).Call(k.page) +} + +// Type releases the key after the press +func (k *Keyboard) Type(keys ...input.Key) (err error) { + for _, key := range keys { + err = k.Press(key) + if err != nil { + return + } + err = k.Release(key) + if err != nil { + return + } } - k.modifiers = 0 - return nil + return } -// Press keys one by one like a human typing on the keyboard. -// Each press is a combination of Keyboard.Down and Keyboard.Up. -// It can be used to input Chinese or Janpanese characters, you have to use InsertText to do that. -func (k *Keyboard) Press(keys ...rune) error { - k.Lock() - defer k.Unlock() +// KeyActionType enum +type KeyActionType int + +// KeyActionTypes +const ( + KeyActionPress KeyActionType = iota + KeyActionRelease + KeyActionTypeKey +) +// KeyAction to perform +type KeyAction struct { + Type KeyActionType + Key input.Key +} + +// KeyActions to simulate +type KeyActions struct { + keyboard *Keyboard + + Actions []KeyAction +} + +// KeyActions simulates the type actions on a physical keyboard. +// Useful when input shortcuts like ctrl+enter . +func (p *Page) KeyActions() *KeyActions { + return &KeyActions{keyboard: p.Keyboard} +} + +// Press keys is guaranteed to have a release at the end of actions +func (ka *KeyActions) Press(keys ...input.Key) *KeyActions { for _, key := range keys { - defer k.page.tryTrace(TraceTypeInput, "press "+input.Keys[key].Key)() + ka.Actions = append(ka.Actions, KeyAction{KeyActionPress, key}) + } + return ka +} - k.page.browser.trySlowmotion() +// Release keys +func (ka *KeyActions) Release(keys ...input.Key) *KeyActions { + for _, key := range keys { + ka.Actions = append(ka.Actions, KeyAction{KeyActionRelease, key}) + } + return ka +} - actions := input.Encode(key) +// Type will release the key immediately after the pressing +func (ka *KeyActions) Type(keys ...input.Key) *KeyActions { + for _, key := range keys { + ka.Actions = append(ka.Actions, KeyAction{KeyActionTypeKey, key}) + } + return ka +} + +// Do the actions +func (ka *KeyActions) Do() (err error) { + for _, a := range ka.balance() { + switch a.Type { + case KeyActionPress: + err = ka.keyboard.Press(a.Key) + case KeyActionRelease: + err = ka.keyboard.Release(a.Key) + case KeyActionTypeKey: + err = ka.keyboard.Type(a.Key) + } + if err != nil { + return + } + } + return +} - k.modifiers = actions[0].Modifiers - defer func() { k.modifiers = 0 }() +// Make sure there's at least one release after the presses, such as: +// p1,p2,p1,r1 => p1,p2,p1,r1,r2 +func (ka *KeyActions) balance() []KeyAction { + actions := ka.Actions + + h := map[input.Key]bool{} + for _, a := range actions { + switch a.Type { + case KeyActionPress: + h[a.Key] = true + case KeyActionRelease, KeyActionTypeKey: + h[a.Key] = false + } + } - for _, action := range actions { - err := action.Call(k.page) - if err != nil { - return err - } + for key, needRelease := range h { + if needRelease { + actions = append(actions, KeyAction{KeyActionRelease, key}) } } - return nil + + return actions } // InsertText is like pasting text into the page -func (k *Keyboard) InsertText(text string) error { - k.Lock() - defer k.Unlock() - - defer k.page.tryTrace(TraceTypeInput, "insert text "+text)() - k.page.browser.trySlowmotion() +func (p *Page) InsertText(text string) error { + defer p.tryTrace(TraceTypeInput, "insert text "+text)() + p.browser.trySlowmotion() - err := proto.InputInsertText{Text: text}.Call(k.page) + err := proto.InputInsertText{Text: text}.Call(p) return err } @@ -106,10 +200,15 @@ type Mouse struct { x float64 y float64 - // the buttons is currently beening pressed, reflects the press order + // the buttons is currently being pressed, reflects the press order buttons []proto.InputMouseButton } +func (p *Page) newMouse() *Page { + p.Mouse = &Mouse{page: p, id: utils.RandString(8)} + return p +} + // Move to the absolute position with specified steps func (m *Mouse) Move(x, y float64, steps int) error { m.Lock() @@ -267,6 +366,11 @@ type Touch struct { page *Page } +func (p *Page) newTouch() *Page { + p.Touch = &Touch{page: p} + return p +} + // Start a touch action func (t *Touch) Start(points ...*proto.InputTouchPoint) error { // TODO: https://crbug.com/613219 diff --git a/input_test.go b/input_test.go new file mode 100644 index 00000000..99b4f9c7 --- /dev/null +++ b/input_test.go @@ -0,0 +1,258 @@ +package rod_test + +import ( + "testing" + + "github.com/go-rod/rod/lib/devices" + "github.com/go-rod/rod/lib/input" + "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" +) + +func TestKeyActions(t *testing.T) { + g := setup(t) + + p := g.page.MustNavigate(g.srcFile("fixtures/keys.html")) + body := p.MustElement("body") + + p.KeyActions().Press(input.ControlLeft).Type(input.Enter).MustDo() + g.Eq(body.MustText(), `↓ "Control" ControlLeft 17 modifiers(ctrl) +↓ "Enter" Enter 13 modifiers(ctrl) +↑ "Enter" Enter 13 modifiers(ctrl) +↑ "Control" ControlLeft 17 modifiers() +`) + + body.MustEval("() => this.innerText = ''") + body.MustKeyActions(). + Press(input.ShiftLeft).Type('A', 'X').Release(input.ShiftLeft). + Type('a').MustDo() + g.Eq(body.MustText(), `↓ "Shift" ShiftLeft 16 modifiers(shift) +↓ "A" KeyA 65 modifiers(shift) +↑ "A" KeyA 65 modifiers(shift) +↓ "X" KeyX 88 modifiers(shift) +↑ "X" KeyX 88 modifiers(shift) +↑ "Shift" ShiftLeft 16 modifiers() +↓ "a" KeyA 65 modifiers() +↑ "a" KeyA 65 modifiers() +`) + + g.Nil(p.Keyboard.Release('a')) +} + +func TestKeyType(t *testing.T) { + g := setup(t) + + p := g.page.MustNavigate(g.srcFile("fixtures/input.html")) + el := p.MustElement("[type=text]") + + el.MustKeyActions().Type('1', '2', input.Backspace, ' ').MustDo() + el.MustKeyActions().Type('A', ' ', 'b').MustDo() + p.MustInsertText(" test") + p.Keyboard.MustType(input.Tab) + + g.Eq("1 A b test", el.MustText()) +} + +func TestKeyTypeErr(t *testing.T) { + g := setup(t) + + p := g.page.MustNavigate(g.srcFile("fixtures/keys.html")) + body := p.MustElement("body") + + g.mc.stubErr(1, proto.RuntimeCallFunctionOn{}) + g.Err(body.Type('a')) + + g.mc.stubErr(1, proto.InputDispatchKeyEvent{}) + g.Err(p.Keyboard.Type('a')) + + g.mc.stubErr(2, proto.InputDispatchKeyEvent{}) + g.Err(p.Keyboard.Type('a')) + + g.mc.stubErr(1, proto.InputDispatchKeyEvent{}) + g.Err(p.KeyActions().Press('a').Do()) +} + +func TestInput(t *testing.T) { + g := setup(t) + + text := "雲の上は\nいつも晴れ" + + p := g.page.MustNavigate(g.srcFile("fixtures/input.html")) + + { + el := p.MustElement("[contenteditable=true]").MustInput(text) + g.Eq(text, el.MustText()) + } + + el := p.MustElement("textarea") + el.MustInput(text) + + g.Eq(text, el.MustText()) + g.True(p.MustHas("[event=textarea-change]")) + + g.Panic(func() { + g.mc.stubErr(1, proto.RuntimeCallFunctionOn{}) + el.MustText() + }) + g.Panic(func() { + g.mc.stubErr(4, proto.RuntimeCallFunctionOn{}) + el.MustInput("") + }) + g.Panic(func() { + g.mc.stubErr(5, proto.RuntimeCallFunctionOn{}) + el.MustInput("") + }) + g.Panic(func() { + g.mc.stubErr(6, proto.RuntimeCallFunctionOn{}) + el.MustInput("") + }) + g.Panic(func() { + g.mc.stubErr(1, proto.InputInsertText{}) + el.MustInput("") + }) +} + +func TestMouse(t *testing.T) { + g := setup(t) + + page := g.page.MustNavigate(g.srcFile("fixtures/click.html")) + page.MustElement("button") + mouse := page.Mouse + + mouse.MustScroll(0, 10) + mouse.MustMove(140, 160) + mouse.MustDown("left") + mouse.MustUp("left") + + g.True(page.MustHas("[a=ok]")) + + g.Panic(func() { + g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) + mouse.MustScroll(0, 10) + }) + g.Panic(func() { + g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) + mouse.MustDown(proto.InputMouseButtonLeft) + }) + g.Panic(func() { + g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) + mouse.MustUp(proto.InputMouseButtonLeft) + }) + g.Panic(func() { + g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) + mouse.MustClick(proto.InputMouseButtonLeft) + }) +} + +func TestMouseHoldMultiple(t *testing.T) { + g := setup(t) + + p := g.page.MustNavigate(g.blank()) + + p.Mouse.MustDown("left") + defer p.Mouse.MustUp("left") + p.Mouse.MustDown("right") + defer p.Mouse.MustUp("right") +} + +func TestMouseClick(t *testing.T) { + g := setup(t) + + g.browser.SlowMotion(1) + defer func() { g.browser.SlowMotion(0) }() + + page := g.page.MustNavigate(g.srcFile("fixtures/click.html")) + page.MustElement("button") + mouse := page.Mouse + mouse.MustMove(140, 160) + mouse.MustClick("left") + g.True(page.MustHas("[a=ok]")) +} + +func TestMouseDrag(t *testing.T) { + g := setup(t) + + page := g.newPage().MustNavigate(g.srcFile("fixtures/drag.html")).MustWaitLoad() + mouse := page.Mouse + + mouse.MustMove(3, 3) + mouse.MustDown("left") + g.E(mouse.Move(60, 80, 3)) + mouse.MustUp("left") + + utils.Sleep(0.3) + g.Eq(page.MustEval(`() => dragTrack`).Str(), " move 3 3 down 3 3 move 22 28 move 41 54 move 60 80 up 60 80") +} + +func TestMouseScroll(t *testing.T) { + g := setup(t) + + p := g.page.MustNavigate(g.srcFile("fixtures/scroll.html")).MustWaitLoad() + + p.Mouse.MustMove(30, 30) + p.Mouse.MustClick(proto.InputMouseButtonLeft) + + p.Mouse.MustScroll(0, 10) + p.Mouse.MustScroll(100, 190) + g.E(p.Mouse.Scroll(200, 300, 5)) + + p.MustWait(`() => pageXOffset > 200 && pageYOffset > 300`) +} + +func TestMouseMoveErr(t *testing.T) { + g := setup(t) + + p := g.page.MustNavigate(g.srcFile("fixtures/click.html")) + g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) + g.Err(p.Mouse.Move(10, 10, 1)) +} + +func TestNativeDrag(t *testing.T) { // devtools doesn't support to use mouse event to simulate it for now + t.Skip() + + g := setup(t) + page := g.page.MustNavigate(g.srcFile("fixtures/drag.html")) + mouse := page.Mouse + + pt := page.MustElement("#draggable").MustShape().OnePointInside() + toY := page.MustElement(".dropzone:nth-child(2)").MustShape().OnePointInside().Y + + page.Overlay(pt.X, pt.Y, 10, 10, "from") + page.Overlay(pt.X, toY, 10, 10, "to") + + mouse.MustMove(pt.X, pt.Y) + mouse.MustDown("left") + g.E(mouse.Move(pt.X, toY, 5)) + page.MustScreenshot("") + mouse.MustUp("left") + + page.MustElement(".dropzone:nth-child(2) #draggable") +} + +func TestTouch(t *testing.T) { + g := setup(t) + + page := g.newPage().MustEmulate(devices.IPad) + + wait := page.WaitNavigation(proto.PageLifecycleEventNameLoad) + page.MustNavigate(g.srcFile("fixtures/touch.html")) + wait() + + touch := page.Touch + + touch.MustTap(10, 20) + + p := &proto.InputTouchPoint{X: 30, Y: 40} + + touch.MustStart(p).MustEnd() + touch.MustStart(p) + p.MoveTo(50, 60) + touch.MustMove(p).MustCancel() + + page.MustWait(`() => touchTrack == ' start 10 20 end start 30 40 end start 30 40 move 50 60 cancel'`) + + g.Panic(func() { + g.mc.stubErr(1, proto.InputDispatchTouchEvent{}) + touch.MustTap(1, 2) + }) +} diff --git a/lib/input/README.md b/lib/input/README.md index 6f2733fd..36687bf3 100644 --- a/lib/input/README.md +++ b/lib/input/README.md @@ -1,5 +1,3 @@ # input A lib to help encode inputs. - -Copied from [chromedp](https://github.com/chromedp/chromedp). But modified to make it completely independent. diff --git a/lib/input/keyboard.go b/lib/input/keyboard.go index 895bd98d..d11671af 100644 --- a/lib/input/keyboard.go +++ b/lib/input/keyboard.go @@ -1,97 +1,137 @@ package input import ( - "runtime" - "github.com/go-rod/rod/lib/proto" + "github.com/ysmood/gson" +) + +// Modifier values +const ( + ModifierAlt = 1 + ModifierControl = 2 + ModifierMeta = 4 + ModifierShift = 8 ) -// Key contains information for generating a key press based off the unicode -// value. -// -// Example data for the following runes: -// '\r' '\n' | ',' '<' | 'a' 'A' | '\u0a07' -// _____________________________________________________ -type Key struct { - // Code is the key code: - // "Enter" | "Comma" | "KeyA" | "MediaStop" - Code string - - // Key is the key value: - // "Enter" | "," "<" | "a" "A" | "MediaStop" - Key string - - // Text is the text for printable keys: - // "\r" "\r" | "," "<" | "a" "A" | "" - Text string - - // Unmodified is the unmodified text for printable keys: - // "\r" "\r" | "," "," | "a" "a" | "" - Unmodified string - - // Native is the native scan code. - // 0x13 0x13 | 0xbc 0xbc | 0x61 0x41 | 0x00ae - Native int - - // Windows is the windows scan code. - // 0x13 0x13 | 0xbc 0xbc | 0x61 0x41 | 0xe024 - Windows int - - // Shift indicates whether or not the Shift modifier should be sent. - // false false | false true | false true | false - Shift bool - - // Print indicates whether or not the character is a printable character - // (ie, should a "char" event be generated). - // true true | true true | true true | false - Print bool +// Key symbol +type Key rune + +// keyMap for key description +var keyMap = map[Key]KeyInfo{} + +// keyMapShifted for shifted key description +var keyMapShifted = map[Key]KeyInfo{} + +var keyShiftedMap = map[Key]Key{} + +// AddKey to KeyMap +func AddKey(key string, shiftedKey string, code string, keyCode int, location int) Key { + if len(key) == 1 { + r := Key(key[0]) + if _, has := keyMap[r]; !has { + keyMap[r] = KeyInfo{key, code, keyCode, location} + + if len(shiftedKey) == 1 { + rs := Key(shiftedKey[0]) + keyMapShifted[rs] = KeyInfo{shiftedKey, code, keyCode, location} + keyShiftedMap[r] = rs + } + return r + } + } + + k := Key(keyCode + (location+1)*256) + keyMap[k] = KeyInfo{key, code, keyCode, location} + + return k } -// Encode encodes a keyDown, char, and keyUp sequence for the specified rune. -func Encode(r rune) []*proto.InputDispatchKeyEvent { - // force \n -> \r - if r == '\n' { - r = '\r' +// Info of the key +func (k Key) Info() KeyInfo { + if k, has := keyMap[k]; has { + return k } + if k, has := keyMapShifted[k]; has { + return k + } + + panic("key not defined") +} - // if not known key, encode as unidentified - v := Keys[r] +// KeyInfo of a key +// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent +type KeyInfo struct { + // Here's the value for Shift key on the keyboard - // create - keyDown := proto.InputDispatchKeyEvent{ - Type: "keyDown", - Key: v.Key, - Code: v.Code, - NativeVirtualKeyCode: v.Native, - WindowsVirtualKeyCode: v.Windows, + Key string // Shift + Code string // ShiftLeft + KeyCode int // 16 + Location int // 1 +} + +// Shift returns the shifted key, such as shifted "1" is "!". +func (k Key) Shift() (Key, bool) { + s, has := keyShiftedMap[k] + return s, has +} + +// Printable returns true if the key is printable +func (k Key) Printable() bool { + return len(k.Info().Key) == 1 +} + +// Modifier returns the modifier value of the key +func (k Key) Modifier() int { + switch k.Info().KeyCode { + case 18: + return ModifierAlt + case 17: + return ModifierControl + case 91, 92: + return ModifierMeta + case 16: + return ModifierShift } - if runtime.GOOS == "darwin" { - keyDown.NativeVirtualKeyCode = 0 + return 0 +} + +// Encode general key event +func (k Key) Encode(t proto.InputDispatchKeyEventType, modifiers int) *proto.InputDispatchKeyEvent { + tp := t + if t == proto.InputDispatchKeyEventTypeKeyDown && !k.Printable() { + tp = proto.InputDispatchKeyEventTypeRawKeyDown + } + + info := k.Info() + l := gson.Int(info.Location) + keypad := false + if info.Location == 3 { + l = nil + keypad = true } - if v.Shift { - keyDown.Modifiers |= 8 + + txt := "" + if k.Printable() { + txt = info.Key + } + + var cmd []string + if IsMac { + cmd = macCommands[info.Key] } - keyUp := keyDown - keyUp.Type = "keyUp" - - // printable, so create char event - if v.Print { - keyChar := keyDown - keyChar.Type = "char" - keyChar.Text = v.Text - keyChar.UnmodifiedText = v.Unmodified - - // the virtual key code for char events for printable characters will - // be different than the defined keycode when not shifted... - // - // specifically, it always sends the ascii value as the scan code, - // which is available as the rune. - keyChar.NativeVirtualKeyCode = int(r) - keyChar.WindowsVirtualKeyCode = int(r) - - return []*proto.InputDispatchKeyEvent{&keyDown, &keyChar, &keyUp} + e := &proto.InputDispatchKeyEvent{ + Type: tp, + WindowsVirtualKeyCode: info.KeyCode, + Code: info.Code, + Key: info.Key, + Text: txt, + UnmodifiedText: txt, + Location: l, + IsKeypad: keypad, + Modifiers: modifiers, + Commands: cmd, } - return []*proto.InputDispatchKeyEvent{&keyDown, &keyUp} + return e } diff --git a/lib/input/keyboard_map.go b/lib/input/keyboard_map.go deleted file mode 100644 index 30e4eccc..00000000 --- a/lib/input/keyboard_map.go +++ /dev/null @@ -1,373 +0,0 @@ -package input - -// DOM keys. -const ( - Backspace = '\b' - Tab = '\t' - Enter = '\r' - Escape = '\u001b' - Quote = '\'' - Backslash = '\\' - Delete = '\u007f' - Alt = '\u0102' - CapsLock = '\u0104' - Control = '\u0105' - Fn = '\u0106' - FnLock = '\u0107' - Hyper = '\u0108' - Meta = '\u0109' - NumLock = '\u010a' - ScrollLock = '\u010c' - Shift = '\u010d' - Super = '\u010e' - ArrowDown = '\u0301' - ArrowLeft = '\u0302' - ArrowRight = '\u0303' - ArrowUp = '\u0304' - End = '\u0305' - Home = '\u0306' - PageDown = '\u0307' - PageUp = '\u0308' - Clear = '\u0401' - Copy = '\u0402' - Cut = '\u0404' - Insert = '\u0407' - Paste = '\u0408' - Redo = '\u0409' - Undo = '\u040a' - Again = '\u0502' - Cancel = '\u0504' - ContextMenu = '\u0505' - Find = '\u0507' - Help = '\u0508' - Pause = '\u0509' - Props = '\u050b' - Select = '\u050c' - ZoomIn = '\u050d' - ZoomOut = '\u050e' - BrightnessDown = '\u0601' - BrightnessUp = '\u0602' - Eject = '\u0604' - LogOff = '\u0605' - Power = '\u0606' - PrintScreen = '\u0608' - WakeUp = '\u060b' - Convert = '\u0705' - ModeChange = '\u070b' - NonConvert = '\u070d' - HangulMode = '\u0711' - HanjaMode = '\u0712' - Hiragana = '\u0716' - KanaMode = '\u0718' - Katakana = '\u071a' - ZenkakuHankaku = '\u071d' - F1 = '\u0801' - F2 = '\u0802' - F3 = '\u0803' - F4 = '\u0804' - F5 = '\u0805' - F6 = '\u0806' - F7 = '\u0807' - F8 = '\u0808' - F9 = '\u0809' - F10 = '\u080a' - F11 = '\u080b' - F12 = '\u080c' - F13 = '\u080d' - F14 = '\u080e' - F15 = '\u080f' - F16 = '\u0810' - F17 = '\u0811' - F18 = '\u0812' - F19 = '\u0813' - F20 = '\u0814' - F21 = '\u0815' - F22 = '\u0816' - F23 = '\u0817' - F24 = '\u0818' - Close = '\u0a01' - MailForward = '\u0a02' - MailReply = '\u0a03' - MailSend = '\u0a04' - MediaPlayPause = '\u0a05' - MediaStop = '\u0a07' - MediaTrackNext = '\u0a08' - MediaTrackPrevious = '\u0a09' - New = '\u0a0a' - Open = '\u0a0b' - Print = '\u0a0c' - Save = '\u0a0d' - SpellCheck = '\u0a0e' - AudioVolumeDown = '\u0a0f' - AudioVolumeUp = '\u0a10' - AudioVolumeMute = '\u0a11' - LaunchApplication2 = '\u0b01' - LaunchCalendar = '\u0b02' - LaunchMail = '\u0b03' - LaunchMediaPlayer = '\u0b04' - LaunchMusicPlayer = '\u0b05' - LaunchApplication1 = '\u0b06' - LaunchScreenSaver = '\u0b07' - LaunchSpreadsheet = '\u0b08' - LaunchWebBrowser = '\u0b09' - LaunchContacts = '\u0b0c' - LaunchPhone = '\u0b0d' - LaunchAssistant = '\u0b0e' - BrowserBack = '\u0c01' - BrowserFavorites = '\u0c02' - BrowserForward = '\u0c03' - BrowserHome = '\u0c04' - BrowserRefresh = '\u0c05' - BrowserSearch = '\u0c06' - BrowserStop = '\u0c07' - ChannelDown = '\u0d0a' - ChannelUp = '\u0d0b' - ClosedCaptionToggle = '\u0d12' - Exit = '\u0d15' - Guide = '\u0d22' - Info = '\u0d25' - MediaFastForward = '\u0d2c' - MediaLast = '\u0d2d' - MediaPause = '\u0d2e' - MediaPlay = '\u0d2f' - MediaRecord = '\u0d30' - MediaRewind = '\u0d31' - Settings = '\u0d43' - ZoomToggle = '\u0d4e' - AudioBassBoostToggle = '\u0e02' - SpeechInputToggle = '\u0f02' - AppSwitch = '\u1001' -) - -// Keys is the map of unicode characters to their DOM key data. -var Keys = map[rune]*Key{ - '\b': {"Backspace", "Backspace", "", "", 8, 8, false, false}, - '\t': {"Tab", "Tab", "", "", 9, 9, false, false}, - '\r': {"Enter", "Enter", "\r", "\r", 13, 13, false, true}, - '\u001b': {"Escape", "Escape", "", "", 27, 27, false, false}, - ' ': {"Space", " ", " ", " ", 32, 32, false, true}, - '!': {"Digit1", "!", "!", "1", 49, 49, true, true}, - '"': {"Quote", "\"", "\"", "'", 222, 222, true, true}, - '#': {"Digit3", "#", "#", "3", 51, 51, true, true}, - '$': {"Digit4", "$", "$", "4", 52, 52, true, true}, - '%': {"Digit5", "%", "%", "5", 53, 53, true, true}, - '&': {"Digit7", "&", "&", "7", 55, 55, true, true}, - '\'': {"Quote", "'", "'", "'", 222, 222, false, true}, - '(': {"Digit9", "(", "(", "9", 57, 57, true, true}, - ')': {"Digit0", ")", ")", "0", 48, 48, true, true}, - '*': {"Digit8", "*", "*", "8", 56, 56, true, true}, - '+': {"Equal", "+", "+", "=", 187, 187, true, true}, - ',': {"Comma", ",", ",", ",", 188, 188, false, true}, - '-': {"Minus", "-", "-", "-", 189, 189, false, true}, - '.': {"Period", ".", ".", ".", 190, 190, false, true}, - '/': {"Slash", "/", "/", "/", 191, 191, false, true}, - '0': {"Digit0", "0", "0", "0", 48, 48, false, true}, - '1': {"Digit1", "1", "1", "1", 49, 49, false, true}, - '2': {"Digit2", "2", "2", "2", 50, 50, false, true}, - '3': {"Digit3", "3", "3", "3", 51, 51, false, true}, - '4': {"Digit4", "4", "4", "4", 52, 52, false, true}, - '5': {"Digit5", "5", "5", "5", 53, 53, false, true}, - '6': {"Digit6", "6", "6", "6", 54, 54, false, true}, - '7': {"Digit7", "7", "7", "7", 55, 55, false, true}, - '8': {"Digit8", "8", "8", "8", 56, 56, false, true}, - '9': {"Digit9", "9", "9", "9", 57, 57, false, true}, - ':': {"Semicolon", ":", ":", ";", 186, 186, true, true}, - ';': {"Semicolon", ";", ";", ";", 186, 186, false, true}, - '<': {"Comma", "<", "<", ",", 188, 188, true, true}, - '=': {"Equal", "=", "=", "=", 187, 187, false, true}, - '>': {"Period", ">", ">", ".", 190, 190, true, true}, - '?': {"Slash", "?", "?", "/", 191, 191, true, true}, - '@': {"Digit2", "@", "@", "2", 50, 50, true, true}, - 'A': {"KeyA", "A", "A", "a", 65, 65, true, true}, - 'B': {"KeyB", "B", "B", "b", 66, 66, true, true}, - 'C': {"KeyC", "C", "C", "c", 67, 67, true, true}, - 'D': {"KeyD", "D", "D", "d", 68, 68, true, true}, - 'E': {"KeyE", "E", "E", "e", 69, 69, true, true}, - 'F': {"KeyF", "F", "F", "f", 70, 70, true, true}, - 'G': {"KeyG", "G", "G", "g", 71, 71, true, true}, - 'H': {"KeyH", "H", "H", "h", 72, 72, true, true}, - 'I': {"KeyI", "I", "I", "i", 73, 73, true, true}, - 'J': {"KeyJ", "J", "J", "j", 74, 74, true, true}, - 'K': {"KeyK", "K", "K", "k", 75, 75, true, true}, - 'L': {"KeyL", "L", "L", "l", 76, 76, true, true}, - 'M': {"KeyM", "M", "M", "m", 77, 77, true, true}, - 'N': {"KeyN", "N", "N", "n", 78, 78, true, true}, - 'O': {"KeyO", "O", "O", "o", 79, 79, true, true}, - 'P': {"KeyP", "P", "P", "p", 80, 80, true, true}, - 'Q': {"KeyQ", "Q", "Q", "q", 81, 81, true, true}, - 'R': {"KeyR", "R", "R", "r", 82, 82, true, true}, - 'S': {"KeyS", "S", "S", "s", 83, 83, true, true}, - 'T': {"KeyT", "T", "T", "t", 84, 84, true, true}, - 'U': {"KeyU", "U", "U", "u", 85, 85, true, true}, - 'V': {"KeyV", "V", "V", "v", 86, 86, true, true}, - 'W': {"KeyW", "W", "W", "w", 87, 87, true, true}, - 'X': {"KeyX", "X", "X", "x", 88, 88, true, true}, - 'Y': {"KeyY", "Y", "Y", "y", 89, 89, true, true}, - 'Z': {"KeyZ", "Z", "Z", "z", 90, 90, true, true}, - '[': {"BracketLeft", "[", "[", "[", 219, 219, false, true}, - '\\': {"Backslash", "\\", "\\", "\\", 220, 220, false, true}, - ']': {"BracketRight", "]", "]", "]", 221, 221, false, true}, - '^': {"Digit6", "^", "^", "6", 54, 54, true, true}, - '_': {"Minus", "_", "_", "-", 189, 189, true, true}, - '`': {"Backquote", "`", "`", "`", 192, 192, false, true}, - 'a': {"KeyA", "a", "a", "a", 65, 65, false, true}, - 'b': {"KeyB", "b", "b", "b", 66, 66, false, true}, - 'c': {"KeyC", "c", "c", "c", 67, 67, false, true}, - 'd': {"KeyD", "d", "d", "d", 68, 68, false, true}, - 'e': {"KeyE", "e", "e", "e", 69, 69, false, true}, - 'f': {"KeyF", "f", "f", "f", 70, 70, false, true}, - 'g': {"KeyG", "g", "g", "g", 71, 71, false, true}, - 'h': {"KeyH", "h", "h", "h", 72, 72, false, true}, - 'i': {"KeyI", "i", "i", "i", 73, 73, false, true}, - 'j': {"KeyJ", "j", "j", "j", 74, 74, false, true}, - 'k': {"KeyK", "k", "k", "k", 75, 75, false, true}, - 'l': {"KeyL", "l", "l", "l", 76, 76, false, true}, - 'm': {"KeyM", "m", "m", "m", 77, 77, false, true}, - 'n': {"KeyN", "n", "n", "n", 78, 78, false, true}, - 'o': {"KeyO", "o", "o", "o", 79, 79, false, true}, - 'p': {"KeyP", "p", "p", "p", 80, 80, false, true}, - 'q': {"KeyQ", "q", "q", "q", 81, 81, false, true}, - 'r': {"KeyR", "r", "r", "r", 82, 82, false, true}, - 's': {"KeyS", "s", "s", "s", 83, 83, false, true}, - 't': {"KeyT", "t", "t", "t", 84, 84, false, true}, - 'u': {"KeyU", "u", "u", "u", 85, 85, false, true}, - 'v': {"KeyV", "v", "v", "v", 86, 86, false, true}, - 'w': {"KeyW", "w", "w", "w", 87, 87, false, true}, - 'x': {"KeyX", "x", "x", "x", 88, 88, false, true}, - 'y': {"KeyY", "y", "y", "y", 89, 89, false, true}, - 'z': {"KeyZ", "z", "z", "z", 90, 90, false, true}, - '{': {"BracketLeft", "{", "{", "[", 219, 219, true, true}, - '|': {"Backslash", "|", "|", "\\", 220, 220, true, true}, - '}': {"BracketRight", "}", "}", "]", 221, 221, true, true}, - '~': {"Backquote", "~", "~", "`", 192, 192, true, true}, - '\u007f': {"Delete", "Delete", "", "", 46, 46, false, false}, - '¥': {"IntlYen", "¥", "¥", "¥", 220, 220, false, true}, - '\u0102': {"AltLeft", "Alt", "", "", 164, 164, false, false}, - '\u0104': {"CapsLock", "CapsLock", "", "", 20, 20, false, false}, - '\u0105': {"ControlLeft", "Control", "", "", 162, 162, false, false}, - '\u0106': {"Fn", "Fn", "", "", 0, 0, false, false}, - '\u0107': {"FnLock", "FnLock", "", "", 0, 0, false, false}, - '\u0108': {"Hyper", "Hyper", "", "", 0, 0, false, false}, - '\u0109': {"MetaLeft", "Meta", "", "", 91, 91, false, false}, - '\u010a': {"NumLock", "NumLock", "", "", 144, 144, false, false}, - '\u010c': {"ScrollLock", "ScrollLock", "", "", 145, 145, false, false}, - '\u010d': {"ShiftLeft", "Shift", "", "", 160, 160, false, false}, - '\u010e': {"Super", "Super", "", "", 0, 0, false, false}, - '\u0301': {"ArrowDown", "ArrowDown", "", "", 40, 40, false, false}, - '\u0302': {"ArrowLeft", "ArrowLeft", "", "", 37, 37, false, false}, - '\u0303': {"ArrowRight", "ArrowRight", "", "", 39, 39, false, false}, - '\u0304': {"ArrowUp", "ArrowUp", "", "", 38, 38, false, false}, - '\u0305': {"End", "End", "", "", 35, 35, false, false}, - '\u0306': {"Home", "Home", "", "", 36, 36, false, false}, - '\u0307': {"PageDown", "PageDown", "", "", 34, 34, false, false}, - '\u0308': {"PageUp", "PageUp", "", "", 33, 33, false, false}, - '\u0401': {"NumpadClear", "Clear", "", "", 12, 12, false, false}, - '\u0402': {"Copy", "Copy", "", "", 0, 0, false, false}, - '\u0404': {"Cut", "Cut", "", "", 0, 0, false, false}, - '\u0407': {"Insert", "Insert", "", "", 45, 45, false, false}, - '\u0408': {"Paste", "Paste", "", "", 0, 0, false, false}, - '\u0409': {"Redo", "Redo", "", "", 0, 0, false, false}, - '\u040a': {"Undo", "Undo", "", "", 0, 0, false, false}, - '\u0502': {"Again", "Again", "", "", 0, 0, false, false}, - '\u0504': {"Abort", "Cancel", "", "", 3, 3, false, false}, - '\u0505': {"ContextMenu", "ContextMenu", "", "", 93, 93, false, false}, - '\u0507': {"Find", "Find", "", "", 0, 0, false, false}, - '\u0508': {"Help", "Help", "", "", 47, 47, false, false}, - '\u0509': {"Pause", "Pause", "", "", 19, 19, false, false}, - '\u050b': {"Props", "Props", "", "", 0, 0, false, false}, - '\u050c': {"Select", "Select", "", "", 41, 41, false, false}, - '\u050d': {"ZoomIn", "ZoomIn", "", "", 0, 0, false, false}, - '\u050e': {"ZoomOut", "ZoomOut", "", "", 0, 0, false, false}, - '\u0601': {"BrightnessDown", "BrightnessDown", "", "", 216, 0, false, false}, - '\u0602': {"BrightnessUp", "BrightnessUp", "", "", 217, 0, false, false}, - '\u0604': {"Eject", "Eject", "", "", 0, 0, false, false}, - '\u0605': {"LogOff", "LogOff", "", "", 0, 0, false, false}, - '\u0606': {"Power", "Power", "", "", 152, 0, false, false}, - '\u0608': {"PrintScreen", "PrintScreen", "", "", 44, 44, false, false}, - '\u060b': {"WakeUp", "WakeUp", "", "", 0, 0, false, false}, - '\u0705': {"Convert", "Convert", "", "", 28, 28, false, false}, - '\u070b': {"KeyboardLayoutSelect", "ModeChange", "", "", 0, 0, false, false}, - '\u070d': {"NonConvert", "NonConvert", "", "", 29, 29, false, false}, - '\u0711': {"Lang1", "HangulMode", "", "", 21, 21, false, false}, - '\u0712': {"Lang2", "HanjaMode", "", "", 25, 25, false, false}, - '\u0716': {"Lang4", "Hiragana", "", "", 0, 0, false, false}, - '\u0718': {"KanaMode", "KanaMode", "", "", 21, 21, false, false}, - '\u071a': {"Lang3", "Katakana", "", "", 0, 0, false, false}, - '\u071d': {"Lang5", "ZenkakuHankaku", "", "", 0, 0, false, false}, - '\u0801': {"F1", "F1", "", "", 112, 112, false, false}, - '\u0802': {"F2", "F2", "", "", 113, 113, false, false}, - '\u0803': {"F3", "F3", "", "", 114, 114, false, false}, - '\u0804': {"F4", "F4", "", "", 115, 115, false, false}, - '\u0805': {"F5", "F5", "", "", 116, 116, false, false}, - '\u0806': {"F6", "F6", "", "", 117, 117, false, false}, - '\u0807': {"F7", "F7", "", "", 118, 118, false, false}, - '\u0808': {"F8", "F8", "", "", 119, 119, false, false}, - '\u0809': {"F9", "F9", "", "", 120, 120, false, false}, - '\u080a': {"F10", "F10", "", "", 121, 121, false, false}, - '\u080b': {"F11", "F11", "", "", 122, 122, false, false}, - '\u080c': {"F12", "F12", "", "", 123, 123, false, false}, - '\u080d': {"F13", "F13", "", "", 124, 124, false, false}, - '\u080e': {"F14", "F14", "", "", 125, 125, false, false}, - '\u080f': {"F15", "F15", "", "", 126, 126, false, false}, - '\u0810': {"F16", "F16", "", "", 127, 127, false, false}, - '\u0811': {"F17", "F17", "", "", 128, 128, false, false}, - '\u0812': {"F18", "F18", "", "", 129, 129, false, false}, - '\u0813': {"F19", "F19", "", "", 130, 130, false, false}, - '\u0814': {"F20", "F20", "", "", 131, 131, false, false}, - '\u0815': {"F21", "F21", "", "", 132, 132, false, false}, - '\u0816': {"F22", "F22", "", "", 133, 133, false, false}, - '\u0817': {"F23", "F23", "", "", 134, 134, false, false}, - '\u0818': {"F24", "F24", "", "", 135, 135, false, false}, - '\u0a01': {"Close", "Close", "", "", 0, 0, false, false}, - '\u0a02': {"MailForward", "MailForward", "", "", 0, 0, false, false}, - '\u0a03': {"MailReply", "MailReply", "", "", 0, 0, false, false}, - '\u0a04': {"MailSend", "MailSend", "", "", 0, 0, false, false}, - '\u0a05': {"MediaPlayPause", "MediaPlayPause", "", "", 179, 179, false, false}, - '\u0a07': {"MediaStop", "MediaStop", "", "", 178, 178, false, false}, - '\u0a08': {"MediaTrackNext", "MediaTrackNext", "", "", 176, 176, false, false}, - '\u0a09': {"MediaTrackPrevious", "MediaTrackPrevious", "", "", 177, 177, false, false}, - '\u0a0a': {"New", "New", "", "", 0, 0, false, false}, - '\u0a0b': {"Open", "Open", "", "", 43, 43, false, false}, - '\u0a0c': {"Print", "Print", "", "", 0, 0, false, false}, - '\u0a0d': {"Save", "Save", "", "", 0, 0, false, false}, - '\u0a0e': {"SpellCheck", "SpellCheck", "", "", 0, 0, false, false}, - '\u0a0f': {"AudioVolumeDown", "AudioVolumeDown", "", "", 174, 174, false, false}, - '\u0a10': {"AudioVolumeUp", "AudioVolumeUp", "", "", 175, 175, false, false}, - '\u0a11': {"AudioVolumeMute", "AudioVolumeMute", "", "", 173, 173, false, false}, - '\u0b01': {"LaunchApp2", "LaunchApplication2", "", "", 183, 183, false, false}, - '\u0b02': {"LaunchCalendar", "LaunchCalendar", "", "", 0, 0, false, false}, - '\u0b03': {"LaunchMail", "LaunchMail", "", "", 180, 180, false, false}, - '\u0b04': {"MediaSelect", "LaunchMediaPlayer", "", "", 181, 181, false, false}, - '\u0b05': {"LaunchMusicPlayer", "LaunchMusicPlayer", "", "", 0, 0, false, false}, - '\u0b06': {"LaunchApp1", "LaunchApplication1", "", "", 182, 182, false, false}, - '\u0b07': {"LaunchScreenSaver", "LaunchScreenSaver", "", "", 0, 0, false, false}, - '\u0b08': {"LaunchSpreadsheet", "LaunchSpreadsheet", "", "", 0, 0, false, false}, - '\u0b09': {"LaunchWebBrowser", "LaunchWebBrowser", "", "", 0, 0, false, false}, - '\u0b0c': {"LaunchContacts", "LaunchContacts", "", "", 0, 0, false, false}, - '\u0b0d': {"LaunchPhone", "LaunchPhone", "", "", 0, 0, false, false}, - '\u0b0e': {"LaunchAssistant", "LaunchAssistant", "", "", 153, 0, false, false}, - '\u0c01': {"BrowserBack", "BrowserBack", "", "", 166, 166, false, false}, - '\u0c02': {"BrowserFavorites", "BrowserFavorites", "", "", 171, 171, false, false}, - '\u0c03': {"BrowserForward", "BrowserForward", "", "", 167, 167, false, false}, - '\u0c04': {"BrowserHome", "BrowserHome", "", "", 172, 172, false, false}, - '\u0c05': {"BrowserRefresh", "BrowserRefresh", "", "", 168, 168, false, false}, - '\u0c06': {"BrowserSearch", "BrowserSearch", "", "", 170, 170, false, false}, - '\u0c07': {"BrowserStop", "BrowserStop", "", "", 169, 169, false, false}, - '\u0d0a': {"ChannelDown", "ChannelDown", "", "", 0, 0, false, false}, - '\u0d0b': {"ChannelUp", "ChannelUp", "", "", 0, 0, false, false}, - '\u0d12': {"ClosedCaptionToggle", "ClosedCaptionToggle", "", "", 0, 0, false, false}, - '\u0d15': {"Exit", "Exit", "", "", 0, 0, false, false}, - '\u0d22': {"Guide", "Guide", "", "", 0, 0, false, false}, - '\u0d25': {"Info", "Info", "", "", 0, 0, false, false}, - '\u0d2c': {"MediaFastForward", "MediaFastForward", "", "", 0, 0, false, false}, - '\u0d2d': {"MediaLast", "MediaLast", "", "", 0, 0, false, false}, - '\u0d2e': {"MediaPause", "MediaPause", "", "", 0, 0, false, false}, - '\u0d2f': {"MediaPlay", "MediaPlay", "", "", 0, 0, false, false}, - '\u0d30': {"MediaRecord", "MediaRecord", "", "", 0, 0, false, false}, - '\u0d31': {"MediaRewind", "MediaRewind", "", "", 0, 0, false, false}, - '\u0d43': {"LaunchControlPanel", "Settings", "", "", 154, 0, false, false}, - '\u0d4e': {"ZoomToggle", "ZoomToggle", "", "", 251, 251, false, false}, - '\u0e02': {"AudioBassBoostToggle", "AudioBassBoostToggle", "", "", 0, 0, false, false}, - '\u0f02': {"SpeechInputToggle", "SpeechInputToggle", "", "", 0, 0, false, false}, - '\u1001': {"SelectTask", "AppSwitch", "", "", 0, 0, false, false}, -} diff --git a/lib/input/keyboard_test.go b/lib/input/keyboard_test.go new file mode 100644 index 00000000..944424d7 --- /dev/null +++ b/lib/input/keyboard_test.go @@ -0,0 +1,148 @@ +package input_test + +import ( + "testing" + + "github.com/go-rod/rod/lib/input" + "github.com/go-rod/rod/lib/proto" + "github.com/ysmood/got" + "github.com/ysmood/got/lib/gop" + "github.com/ysmood/gson" +) + +func TestKeyMap(t *testing.T) { + g := got.T(t) + + k := input.Key('a') + g.Eq(k.Info(), input.KeyInfo{ + Key: "a", + Code: "KeyA", + KeyCode: 65, + Location: 0, + }) + + k = input.Key('A') + g.Eq(k.Info(), input.KeyInfo{ + Key: "A", + Code: "KeyA", + KeyCode: 65, + Location: 0, + }) + g.True(k.Printable()) + + k = input.Enter + g.Eq(k.Info(), input.KeyInfo{ + Key: "\r", + Code: "Enter", + KeyCode: 13, + Location: 0, + }) + + k = input.ShiftLeft + g.Eq(k.Info(), input.KeyInfo /* len=4 */ { + Key: "Shift", + Code: "ShiftLeft", + KeyCode: 16, + Location: 1, + }) + g.False(k.Printable()) + + k = input.ShiftRight + g.Eq(k.Info(), input.KeyInfo /* len=4 */ { + Key: "Shift", + Code: "ShiftRight", + KeyCode: 16, + Location: 2, + }) + + k, has := input.Digit1.Shift() + g.True(has) + g.Eq(k.Info().Key, "!") + + _, has = input.Enter.Shift() + g.False(has) + + g.Panic(func() { + input.Key('\n').Info() + }) +} + +func TestKeyModifier(t *testing.T) { + g := got.T(t) + + check := func(k input.Key, m int) { + g.Helper() + + g.Eq(k.Modifier(), m) + } + + check(input.KeyA, 0) + check(input.AltLeft, 1) + check(input.ControlLeft, 2) + check(input.MetaLeft, 4) + check(input.ShiftLeft, 8) +} + +func TestKeyEncode(t *testing.T) { + g := got.T(t) + + g.Eq(input.Key('a').Encode(proto.InputDispatchKeyEventTypeKeyDown, 0), &proto.InputDispatchKeyEvent{ + Type: "keyDown", + Text: "a", + UnmodifiedText: "a", + Code: "KeyA", + Key: "a", + WindowsVirtualKeyCode: 65, + Location: gson.Int(0), + }) + + g.Eq(input.Key('a').Encode(proto.InputDispatchKeyEventTypeKeyUp, 0), &proto.InputDispatchKeyEvent{ + Type: "keyUp", + Text: "a", + UnmodifiedText: "a", + Code: "KeyA", + Key: "a", + WindowsVirtualKeyCode: 65, + Location: gson.Int(0), + }) + + g.Eq(input.AltLeft.Encode(proto.InputDispatchKeyEventTypeKeyDown, 0), &proto.InputDispatchKeyEvent{ + Type: "rawKeyDown", + Code: "AltLeft", + Key: "Alt", + WindowsVirtualKeyCode: 18, + Location: gson.Int(1), + }) + + g.Eq(input.Numpad1.Encode(proto.InputDispatchKeyEventTypeKeyDown, 0), &proto.InputDispatchKeyEvent{ + Type: "keyDown", + Code: "Numpad1", + Key: "1", + Text: "1", + UnmodifiedText: "1", + WindowsVirtualKeyCode: 35, + IsKeypad: true, + }) +} + +func TestMac(t *testing.T) { + g := got.T(t) + + old := input.IsMac + input.IsMac = true + defer func() { input.IsMac = old }() + + g.Eq(input.ArrowDown.Encode(proto.InputDispatchKeyEventTypeKeyDown, 0), &proto.InputDispatchKeyEvent{ + Type: "rawKeyDown", + Code: "ArrowDown", + Key: "ArrowDown", + WindowsVirtualKeyCode: 40, + AutoRepeat: false, + IsKeypad: false, + IsSystemKey: false, + Location: gop.Ptr(0).(*int), + Commands: []string{ + "moveDown", + }, + }) +} diff --git a/lib/input/keymap.go b/lib/input/keymap.go new file mode 100644 index 00000000..f462a976 --- /dev/null +++ b/lib/input/keymap.go @@ -0,0 +1,134 @@ +package input + +// Key names +// Reference: https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/usKeyboardLayout.ts +var ( + // Functions row + // + Escape = AddKey("Escape", "", "Escape", 27, 0) + F1 = AddKey("F1", "", "F1", 112, 0) + F2 = AddKey("F2", "", "F2", 113, 0) + F3 = AddKey("F3", "", "F3", 114, 0) + F4 = AddKey("F4", "", "F4", 115, 0) + F5 = AddKey("F5", "", "F5", 116, 0) + F6 = AddKey("F6", "", "F6", 117, 0) + F7 = AddKey("F7", "", "F7", 118, 0) + F8 = AddKey("F8", "", "F8", 119, 0) + F9 = AddKey("F9", "", "F9", 120, 0) + F10 = AddKey("F10", "", "F10", 121, 0) + F11 = AddKey("F11", "", "F11", 122, 0) + F12 = AddKey("F12", "", "F12", 123, 0) + + // Numbers row + // + Backquote = AddKey("`", "~", "Backquote", 192, 0) + Digit1 = AddKey("1", "!", "Digit1", 49, 0) + Digit2 = AddKey("2", "@", "Digit2", 50, 0) + Digit3 = AddKey("3", "#", "Digit3", 51, 0) + Digit4 = AddKey("4", "$", "Digit4", 52, 0) + Digit5 = AddKey("5", "%", "Digit5", 53, 0) + Digit6 = AddKey("6", "^", "Digit6", 54, 0) + Digit7 = AddKey("7", "&", "Digit7", 55, 0) + Digit8 = AddKey("8", "*", "Digit8", 56, 0) + Digit9 = AddKey("9", "(", "Digit9", 57, 0) + Digit0 = AddKey("0", ")", "Digit0", 48, 0) + Minus = AddKey("-", "_", "Minus", 189, 0) + Equal = AddKey("=", "+", "Equal", 187, 0) + Backslash = AddKey(`\`, "|", "Backslash", 220, 0) + Backspace = AddKey("Backspace", "", "Backspace", 8, 0) + + // First row + // + Tab = AddKey("\t", "", "Tab", 9, 0) + KeyQ = AddKey("q", "Q", "KeyQ", 81, 0) + KeyW = AddKey("w", "W", "KeyW", 87, 0) + KeyE = AddKey("e", "E", "KeyE", 69, 0) + KeyR = AddKey("r", "R", "KeyR", 82, 0) + KeyT = AddKey("t", "T", "KeyT", 84, 0) + KeyY = AddKey("y", "Y", "KeyY", 89, 0) + KeyU = AddKey("u", "U", "KeyU", 85, 0) + KeyI = AddKey("i", "I", "KeyI", 73, 0) + KeyO = AddKey("o", "O", "KeyO", 79, 0) + KeyP = AddKey("p", "P", "KeyP", 80, 0) + BracketLeft = AddKey("[", "{", "BracketLeft", 219, 0) + BracketRight = AddKey("]", "}", "BracketRight", 221, 0) + + // Second row + // + CapsLock = AddKey("CapsLock", "", "CapsLock", 20, 0) + KeyA = AddKey("a", "A", "KeyA", 65, 0) + KeyS = AddKey("s", "S", "KeyS", 83, 0) + KeyD = AddKey("d", "D", "KeyD", 68, 0) + KeyF = AddKey("f", "F", "KeyF", 70, 0) + KeyG = AddKey("g", "G", "KeyG", 71, 0) + KeyH = AddKey("h", "H", "KeyH", 72, 0) + KeyJ = AddKey("j", "J", "KeyJ", 74, 0) + KeyK = AddKey("k", "K", "KeyK", 75, 0) + KeyL = AddKey("l", "L", "KeyL", 76, 0) + Semicolon = AddKey(";", ":", "Semicolon", 186, 0) + Quote = AddKey("'", `"`, "Quote", 222, 0) + Enter = AddKey("\r", "", "Enter", 13, 0) + + // Third row + // + ShiftLeft = AddKey("Shift", "", "ShiftLeft", 16, 1) + KeyZ = AddKey("z", "Z", "KeyZ", 90, 0) + KeyX = AddKey("x", "X", "KeyX", 88, 0) + KeyC = AddKey("c", "C", "KeyC", 67, 0) + KeyV = AddKey("v", "V", "KeyV", 86, 0) + KeyB = AddKey("b", "B", "KeyB", 66, 0) + KeyN = AddKey("n", "N", "KeyN", 78, 0) + KeyM = AddKey("m", "M", "KeyM", 77, 0) + Comma = AddKey(",", "<", "Comma", 188, 0) + Period = AddKey(".", ">", "Period", 190, 0) + Slash = AddKey("/", "?", "Slash", 191, 0) + ShiftRight = AddKey("Shift", "", "ShiftRight", 16, 2) + + // Last row + // + ControlLeft = AddKey("Control", "", "ControlLeft", 17, 1) + MetaLeft = AddKey("Meta", "", "MetaLeft", 91, 1) + AltLeft = AddKey("Alt", "", "AltLeft", 18, 1) + Space = AddKey(" ", "", "Space", 32, 0) + AltRight = AddKey("Alt", "", "AltRight", 18, 2) + AltGraph = AddKey("AltGraph", "", "AltGraph", 225, 0) + MetaRight = AddKey("Meta", "", "MetaRight", 92, 2) + ContextMenu = AddKey("ContextMenu", "", "ContextMenu", 93, 0) + ControlRight = AddKey("Control", "", "ControlRight", 17, 2) + + // Center block + // + PrintScreen = AddKey("PrintScreen", "", "PrintScreen", 44, 0) + ScrollLock = AddKey("ScrollLock", "", "ScrollLock", 145, 0) + Pause = AddKey("Pause", "", "Pause", 19, 0) + PageUp = AddKey("PageUp", "", "PageUp", 33, 0) + PageDown = AddKey("PageDown", "", "PageDown", 34, 0) + Insert = AddKey("Insert", "", "Insert", 45, 0) + Delete = AddKey("Delete", "", "Delete", 46, 0) + Home = AddKey("Home", "", "Home", 36, 0) + End = AddKey("End", "", "End", 35, 0) + ArrowLeft = AddKey("ArrowLeft", "", "ArrowLeft", 37, 0) + ArrowUp = AddKey("ArrowUp", "", "ArrowUp", 38, 0) + ArrowRight = AddKey("ArrowRight", "", "ArrowRight", 39, 0) + ArrowDown = AddKey("ArrowDown", "", "ArrowDown", 40, 0) + + // Numpad + // + NumLock = AddKey("NumLock", "", "NumLock", 144, 0) + NumpadDivide = AddKey("/", "", "NumpadDivide", 111, 3) + NumpadMultiply = AddKey("*", "", "NumpadMultiply", 106, 3) + NumpadSubtract = AddKey("-", "", "NumpadSubtract", 109, 3) + Numpad7 = AddKey("7", "", "Numpad7", 36, 3) + Numpad8 = AddKey("8", "", "Numpad8", 38, 3) + Numpad9 = AddKey("9", "", "Numpad9", 33, 3) + Numpad4 = AddKey("4", "", "Numpad4", 37, 3) + Numpad5 = AddKey("5", "", "Numpad5", 12, 3) + Numpad6 = AddKey("6", "", "Numpad6", 39, 3) + NumpadAdd = AddKey("+", "", "NumpadAdd", 107, 3) + Numpad1 = AddKey("1", "", "Numpad1", 35, 3) + Numpad2 = AddKey("2", "", "Numpad2", 40, 3) + Numpad3 = AddKey("3", "", "Numpad3", 34, 3) + Numpad0 = AddKey("0", "", "Numpad0", 45, 3) + NumpadDecimal = AddKey(".", "", "NumpadDecimal", 46, 3) + NumpadEnter = AddKey("\r", "", "NumpadEnter", 13, 3) +) diff --git a/lib/input/mac_comands.go b/lib/input/mac_comands.go new file mode 100644 index 00000000..24e38fdb --- /dev/null +++ b/lib/input/mac_comands.go @@ -0,0 +1,125 @@ +package input + +import "runtime" + +// IsMac OS +var IsMac = runtime.GOOS == "darwin" + +// commands for macOS +// Reference: https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/macEditingCommands.ts +var macCommands = map[string][]string{ + "Backspace": {"deleteBackward"}, + "Enter": {"insertNewline"}, + "NumpadEnter": {"insertNewline"}, + "Escape": {"cancelOperation"}, + "ArrowUp": {"moveUp"}, + "ArrowDown": {"moveDown"}, + "ArrowLeft": {"moveLeft"}, + "ArrowRight": {"moveRight"}, + "F5": {"complete"}, + "Delete": {"deleteForward"}, + "Home": {"scrollToBeginningOfDocument"}, + "End": {"scrollToEndOfDocument"}, + "PageUp": {"scrollPageUp"}, + "PageDown": {"scrollPageDown"}, + "Shift+Backspace": {"deleteBackward"}, + "Shift+Enter": {"insertNewline"}, + "Shift+NumpadEnter": {"insertNewline"}, + "Shift+Escape": {"cancelOperation"}, + "Shift+ArrowUp": {"moveUpAndModifySelection"}, + "Shift+ArrowDown": {"moveDownAndModifySelection"}, + "Shift+ArrowLeft": {"moveLeftAndModifySelection"}, + "Shift+ArrowRight": {"moveRightAndModifySelection"}, + "Shift+F5": {"complete"}, + "Shift+Delete": {"deleteForward"}, + "Shift+Home": {"moveToBeginningOfDocumentAndModifySelection"}, + "Shift+End": {"moveToEndOfDocumentAndModifySelection"}, + "Shift+PageUp": {"pageUpAndModifySelection"}, + "Shift+PageDown": {"pageDownAndModifySelection"}, + "Shift+Numpad5": {"delete"}, + "Control+Tab": {"selectNextKeyView"}, + "Control+Enter": {"insertLineBreak"}, + "Control+NumpadEnter": {"insertLineBreak"}, + "Control+Quote": {"insertSingleQuoteIgnoringSubstitution"}, + "Control+KeyA": {"moveToBeginningOfParagraph"}, + "Control+KeyB": {"moveBackward"}, + "Control+KeyD": {"deleteForward"}, + "Control+KeyE": {"moveToEndOfParagraph"}, + "Control+KeyF": {"moveForward"}, + "Control+KeyH": {"deleteBackward"}, + "Control+KeyK": {"deleteToEndOfParagraph"}, + "Control+KeyL": {"centerSelectionInVisibleArea"}, + "Control+KeyN": {"moveDown"}, + "Control+KeyO": {"insertNewlineIgnoringFieldEditor", "moveBackward"}, + "Control+KeyP": {"moveUp"}, + "Control+KeyT": {"transpose"}, + "Control+KeyV": {"pageDown"}, + "Control+KeyY": {"yank"}, + "Control+Backspace": {"deleteBackwardByDecomposingPreviousCharacter"}, + "Control+ArrowUp": {"scrollPageUp"}, + "Control+ArrowDown": {"scrollPageDown"}, + "Control+ArrowLeft": {"moveToLeftEndOfLine"}, + "Control+ArrowRight": {"moveToRightEndOfLine"}, + "Shift+Control+Enter": {"insertLineBreak"}, + "Shift+Control+NumpadEnter": {"insertLineBreak"}, + "Shift+Control+Tab": {"selectPreviousKeyView"}, + "Shift+Control+Quote": {"insertDoubleQuoteIgnoringSubstitution"}, + "Shift+Control+KeyA": {"moveToBeginningOfParagraphAndModifySelection"}, + "Shift+Control+KeyB": {"moveBackwardAndModifySelection"}, + "Shift+Control+KeyE": {"moveToEndOfParagraphAndModifySelection"}, + "Shift+Control+KeyF": {"moveForwardAndModifySelection"}, + "Shift+Control+KeyN": {"moveDownAndModifySelection"}, + "Shift+Control+KeyP": {"moveUpAndModifySelection"}, + "Shift+Control+KeyV": {"pageDownAndModifySelection"}, + "Shift+Control+Backspace": {"deleteBackwardByDecomposingPreviousCharacter"}, + "Shift+Control+ArrowUp": {"scrollPageUp"}, + "Shift+Control+ArrowDown": {"scrollPageDown"}, + "Shift+Control+ArrowLeft": {"moveToLeftEndOfLineAndModifySelection"}, + "Shift+Control+ArrowRight": {"moveToRightEndOfLineAndModifySelection"}, + "Alt+Backspace": {"deleteWordBackward"}, + "Alt+Enter": {"insertNewlineIgnoringFieldEditor"}, + "Alt+NumpadEnter": {"insertNewlineIgnoringFieldEditor"}, + "Alt+Escape": {"complete"}, + "Alt+ArrowUp": {"moveBackward", "moveToBeginningOfParagraph"}, + "Alt+ArrowDown": {"moveForward", "moveToEndOfParagraph"}, + "Alt+ArrowLeft": {"moveWordLeft"}, + "Alt+ArrowRight": {"moveWordRight"}, + "Alt+Delete": {"deleteWordForward"}, + "Alt+PageUp": {"pageUp"}, + "Alt+PageDown": {"pageDown"}, + "Shift+Alt+Backspace": {"deleteWordBackward"}, + "Shift+Alt+Enter": {"insertNewlineIgnoringFieldEditor"}, + "Shift+Alt+NumpadEnter": {"insertNewlineIgnoringFieldEditor"}, + "Shift+Alt+Escape": {"complete"}, + "Shift+Alt+ArrowUp": {"moveParagraphBackwardAndModifySelection"}, + "Shift+Alt+ArrowDown": {"moveParagraphForwardAndModifySelection"}, + "Shift+Alt+ArrowLeft": {"moveWordLeftAndModifySelection"}, + "Shift+Alt+ArrowRight": {"moveWordRightAndModifySelection"}, + "Shift+Alt+Delete": {"deleteWordForward"}, + "Shift+Alt+PageUp": {"pageUp"}, + "Shift+Alt+PageDown": {"pageDown"}, + "Control+Alt+KeyB": {"moveWordBackward"}, + "Control+Alt+KeyF": {"moveWordForward"}, + "Control+Alt+Backspace": {"deleteWordBackward"}, + "Shift+Control+Alt+KeyB": {"moveWordBackwardAndModifySelection"}, + "Shift+Control+Alt+KeyF": {"moveWordForwardAndModifySelection"}, + "Shift+Control+Alt+Backspace": {"deleteWordBackward"}, + "Meta+NumpadSubtract": {"cancel"}, + "Meta+Backspace": {"deleteToBeginningOfLine"}, + "Meta+ArrowUp": {"moveToBeginningOfDocument"}, + "Meta+ArrowDown": {"moveToEndOfDocument"}, + "Meta+ArrowLeft": {"moveToLeftEndOfLine"}, + "Meta+ArrowRight": {"moveToRightEndOfLine"}, + "Shift+Meta+NumpadSubtract": {"cancel"}, + "Shift+Meta+Backspace": {"deleteToBeginningOfLine"}, + "Shift+Meta+ArrowUp": {"moveToBeginningOfDocumentAndModifySelection"}, + "Shift+Meta+ArrowDown": {"moveToEndOfDocumentAndModifySelection"}, + "Shift+Meta+ArrowLeft": {"moveToLeftEndOfLineAndModifySelection"}, + "Shift+Meta+ArrowRight": {"moveToRightEndOfLineAndModifySelection"}, + + "Meta+KeyA": {"selectAll"}, + "Meta+KeyC": {"copy"}, + "Meta+KeyV": {"paste"}, + "Meta+KeyZ": {"undo"}, + "Shift+Meta+KeyZ": {"redo"}, +} diff --git a/lib/input/mouse.go b/lib/input/mouse.go index 36bee4ec..2bfe63e5 100644 --- a/lib/input/mouse.go +++ b/lib/input/mouse.go @@ -4,11 +4,11 @@ import "github.com/go-rod/rod/lib/proto" // MouseKeys is the map for mouse keys var MouseKeys = map[proto.InputMouseButton]int{ - "left": 1, - "right": 2, - "middle": 4, - "back": 8, - "forward": 16, + proto.InputMouseButtonLeft: 1, + proto.InputMouseButtonRight: 2, + proto.InputMouseButtonMiddle: 4, + proto.InputMouseButtonBack: 8, + proto.InputMouseButtonForward: 16, } // EncodeMouseButton into button flag diff --git a/lib/input/mouse_test.go b/lib/input/mouse_test.go new file mode 100644 index 00000000..ee0dd3ec --- /dev/null +++ b/lib/input/mouse_test.go @@ -0,0 +1,18 @@ +package input_test + +import ( + "testing" + + "github.com/go-rod/rod/lib/input" + "github.com/go-rod/rod/lib/proto" + "github.com/ysmood/got" +) + +func TestMouseEncode(t *testing.T) { + g := got.T(t) + + b, flag := input.EncodeMouseButton([]proto.InputMouseButton{proto.InputMouseButtonLeft}) + + g.Eq(b, proto.InputMouseButtonLeft) + g.Eq(flag, 1) +} diff --git a/must.go b/must.go index d61d02ee..12674206 100644 --- a/must.go +++ b/must.go @@ -18,6 +18,7 @@ import ( "time" "github.com/go-rod/rod/lib/devices" + "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/proto" "github.com/go-rod/rod/lib/utils" "github.com/ysmood/gson" @@ -620,28 +621,21 @@ func (m *Mouse) MustClick(button proto.InputMouseButton) *Mouse { return m } -// MustDown is similar to Keyboard.Down -func (k *Keyboard) MustDown(key rune) *Keyboard { - k.page.e(k.Down(key)) +// MustType is similar to Keyboard.Type +func (k *Keyboard) MustType(key ...input.Key) *Keyboard { + k.page.e(k.Type(key...)) return k } -// MustUp is similar to Keyboard.Up -func (k *Keyboard) MustUp(key rune) *Keyboard { - k.page.e(k.Up(key)) - return k +// MustDo is similar to KeyActions.Do +func (ka *KeyActions) MustDo() { + ka.keyboard.page.e(ka.Do()) } -// MustPress is similar to Keyboard.Press -func (k *Keyboard) MustPress(key rune) *Keyboard { - k.page.e(k.Press(key)) - return k -} - -// MustInsertText is similar to Keyboard.InsertText -func (k *Keyboard) MustInsertText(text string) *Keyboard { - k.page.e(k.InsertText(text)) - return k +// MustInsertText is similar to Page.InsertText +func (p *Page) MustInsertText(text string) *Page { + p.e(p.InsertText(text)) + return p } // MustStart is similar to Touch.Start @@ -749,12 +743,19 @@ func (el *Element) MustWaitInteractable() *Element { return el } -// MustPress is similar to Element.Press -func (el *Element) MustPress(keys ...rune) *Element { - el.e(el.Press(keys...)) +// MustType is similar to Element.Type +func (el *Element) MustType(keys ...input.Key) *Element { + el.e(el.Type(keys...)) return el } +// MustKeyActions is similar to Element.KeyActions +func (el *Element) MustKeyActions() *KeyActions { + ka, err := el.KeyActions() + el.e(err) + return ka +} + // MustSelectText is similar to Element.SelectText func (el *Element) MustSelectText(regex string) *Element { el.e(el.SelectText(regex)) diff --git a/page_eval_test.go b/page_eval_test.go index fefca3ba..a4204349 100644 --- a/page_eval_test.go +++ b/page_eval_test.go @@ -132,7 +132,7 @@ func TestPageExpose(t *testing.T) { }) } -func TestRelease(t *testing.T) { +func TestObjectRelease(t *testing.T) { g := setup(t) res, err := g.page.Evaluate(rod.Eval(`() => document`).ByObject()) diff --git a/page_test.go b/page_test.go index 9a91cfdc..ab075fdc 100644 --- a/page_test.go +++ b/page_test.go @@ -17,7 +17,6 @@ import ( "github.com/go-rod/rod/lib/cdp" "github.com/go-rod/rod/lib/defaults" "github.com/go-rod/rod/lib/devices" - "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/proto" "github.com/go-rod/rod/lib/utils" ) @@ -566,128 +565,6 @@ func TestAlert(t *testing.T) { handle(true, "") } -func TestMouse(t *testing.T) { - g := setup(t) - - page := g.page.MustNavigate(g.srcFile("fixtures/click.html")) - page.MustElement("button") - mouse := page.Mouse - - mouse.MustScroll(0, 10) - mouse.MustMove(140, 160) - mouse.MustDown("left") - mouse.MustUp("left") - - g.True(page.MustHas("[a=ok]")) - - g.Panic(func() { - g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) - mouse.MustScroll(0, 10) - }) - g.Panic(func() { - g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) - mouse.MustDown(proto.InputMouseButtonLeft) - }) - g.Panic(func() { - g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) - mouse.MustUp(proto.InputMouseButtonLeft) - }) - g.Panic(func() { - g.mc.stubErr(1, proto.InputDispatchMouseEvent{}) - mouse.MustClick(proto.InputMouseButtonLeft) - }) -} - -func TestMouseHoldMultiple(t *testing.T) { - g := setup(t) - - p := g.page.MustNavigate(g.blank()) - - p.Mouse.MustDown("left") - defer p.Mouse.MustUp("left") - p.Mouse.MustDown("right") - defer p.Mouse.MustUp("right") -} - -func TestMouseClick(t *testing.T) { - g := setup(t) - - g.browser.SlowMotion(1) - defer func() { g.browser.SlowMotion(0) }() - - page := g.page.MustNavigate(g.srcFile("fixtures/click.html")) - page.MustElement("button") - mouse := page.Mouse - mouse.MustMove(140, 160) - mouse.MustClick("left") - g.True(page.MustHas("[a=ok]")) -} - -func TestMouseDrag(t *testing.T) { - g := setup(t) - - page := g.newPage().MustNavigate(g.srcFile("fixtures/drag.html")).MustWaitLoad() - mouse := page.Mouse - - mouse.MustMove(3, 3) - mouse.MustDown("left") - g.E(mouse.Move(60, 80, 3)) - mouse.MustUp("left") - - utils.Sleep(0.3) - g.Eq(page.MustEval(`() => dragTrack`).Str(), " move 3 3 down 3 3 move 22 28 move 41 54 move 60 80 up 60 80") -} - -func TestNativeDrag(t *testing.T) { // devtools doesn't support to use mouse event to simulate it for now - t.Skip() - - g := setup(t) - page := g.page.MustNavigate(g.srcFile("fixtures/drag.html")) - mouse := page.Mouse - - pt := page.MustElement("#draggable").MustShape().OnePointInside() - toY := page.MustElement(".dropzone:nth-child(2)").MustShape().OnePointInside().Y - - page.Overlay(pt.X, pt.Y, 10, 10, "from") - page.Overlay(pt.X, toY, 10, 10, "to") - - mouse.MustMove(pt.X, pt.Y) - mouse.MustDown("left") - g.E(mouse.Move(pt.X, toY, 5)) - page.MustScreenshot("") - mouse.MustUp("left") - - page.MustElement(".dropzone:nth-child(2) #draggable") -} - -func TestTouch(t *testing.T) { - g := setup(t) - - page := g.newPage().MustEmulate(devices.IPad) - - wait := page.WaitNavigation(proto.PageLifecycleEventNameLoad) - page.MustNavigate(g.srcFile("fixtures/touch.html")) - wait() - - touch := page.Touch - - touch.MustTap(10, 20) - - p := &proto.InputTouchPoint{X: 30, Y: 40} - - touch.MustStart(p).MustEnd() - touch.MustStart(p) - p.MoveTo(50, 60) - touch.MustMove(p).MustCancel() - - page.MustWait(`() => touchTrack == ' start 10 20 end start 30 40 end start 30 40 move 50 60 cancel'`) - - g.Panic(func() { - g.mc.stubErr(1, proto.InputDispatchTouchEvent{}) - touch.MustTap(1, 2) - }) -} - func TestPageScreenshot(t *testing.T) { g := setup(t) @@ -752,55 +629,6 @@ func TestScreenshotFullPageInit(t *testing.T) { p.MustScreenshotFullPage() } -func TestPageInput(t *testing.T) { - g := setup(t) - - p := g.page.MustNavigate(g.srcFile("fixtures/input.html")) - - el := p.MustElement("input") - el.MustFocus() - p.Keyboard.MustPress('A') - p.Keyboard.MustInsertText(" Test") - p.Keyboard.MustPress(input.Tab) - - g.Eq("A Test", el.MustText()) - - g.Panic(func() { - g.mc.stubErr(1, proto.InputDispatchKeyEvent{}) - p.Keyboard.MustDown('a') - }) - g.Panic(func() { - g.mc.stubErr(1, proto.InputDispatchKeyEvent{}) - p.Keyboard.MustUp('a') - }) - g.Panic(func() { - g.mc.stubErr(3, proto.InputDispatchKeyEvent{}) - p.Keyboard.MustPress('a') - }) -} - -func TestPageInputDate(t *testing.T) { - g := setup(t) - - p := g.page.MustNavigate(g.srcFile("fixtures/input.html")) - p.MustElement("[type=date]").MustInput("12") -} - -func TestPageScroll(t *testing.T) { - g := setup(t) - - p := g.page.MustNavigate(g.srcFile("fixtures/scroll.html")).MustWaitLoad() - - p.Mouse.MustMove(30, 30) - p.Mouse.MustClick(proto.InputMouseButtonLeft) - - p.Mouse.MustScroll(0, 10) - p.Mouse.MustScroll(100, 190) - g.E(p.Mouse.Scroll(200, 300, 5)) - - p.MustWait(`() => pageXOffset > 200 && pageYOffset > 300`) -} - func TestPageConsoleLog(t *testing.T) { g := setup(t)