bare-pointer 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,7 +32,9 @@ pointer.destroy()
32
32
  | Android | `bare-ndk` | Motion events dispatched to the root of the view |
33
33
  | Windows | `bare-win-ui` | `SendInput()` through `bare-user32` |
34
34
 
35
- On Linux and Windows the input goes through the system, so the window of the view has to be the one on top at the point. On macOS, a control tracks the mouse until it is released, so press one with `tap()` rather than `down()`.
35
+ On Linux the input goes through the system, so the window of the view has to be the one on top at the point. On Windows it does too, and a window covered at the point is brought to the foreground first. On macOS, a control tracks the mouse until it is released, so press one with `tap()` rather than `down()`.
36
+
37
+ On Windows, a press that no element marks as handled can leave windows opened after it without pointer input once one of them has closed. Have something take every press, such as the root of each window.
36
38
 
37
39
  ## License
38
40
 
package/index.js CHANGED
@@ -14,10 +14,14 @@ module.exports = class Pointer {
14
14
  async down(view, x, y) {
15
15
  if (this._view !== null) throw new Error('Pointer is already down')
16
16
 
17
- this._backend.down(view, ...this._point(view, x, y))
17
+ const point = this._point(view, x, y)
18
+
19
+ await this._reach(view, point)
20
+
21
+ this._backend.down(view, ...point)
18
22
  this._view = view
19
23
 
20
- await frame(view)
24
+ await this._frame(view)
21
25
  }
22
26
 
23
27
  async move(view, x, y) {
@@ -25,7 +29,7 @@ module.exports = class Pointer {
25
29
 
26
30
  this._backend.move(view, ...this._point(view, x, y))
27
31
 
28
- await frame(view)
32
+ await this._frame(view)
29
33
  }
30
34
 
31
35
  async up() {
@@ -36,7 +40,7 @@ module.exports = class Pointer {
36
40
  this._backend.up()
37
41
  this._view = null
38
42
 
39
- await frame(view)
43
+ await this._frame(view)
40
44
  }
41
45
 
42
46
  async tap(view, x, y) {
@@ -44,18 +48,20 @@ module.exports = class Pointer {
44
48
 
45
49
  const point = this._point(view, x, y)
46
50
 
51
+ await this._reach(view, point)
52
+
47
53
  if (this._backend.tap) {
48
54
  this._backend.tap(view, ...point)
49
55
  } else {
50
56
  this._backend.down(view, ...point)
51
57
 
52
58
  // UIKit controls only see a press that lasts past a frame.
53
- await frame(view)
59
+ await this._frame(view)
54
60
 
55
61
  this._backend.up()
56
62
  }
57
63
 
58
- await frame(view)
64
+ await this._frame(view)
59
65
  }
60
66
 
61
67
  destroy() {
@@ -65,6 +71,51 @@ module.exports = class Pointer {
65
71
  this._backend.destroy()
66
72
  }
67
73
 
74
+ async _reach(view, point) {
75
+ if (this._backend.covered !== undefined) {
76
+ for (let frames = 0; this._backend.covered(view, ...point); frames++) {
77
+ if (frames === 60) throw new Error('The view is covered at the point')
78
+
79
+ await this._frame(view)
80
+ }
81
+ }
82
+
83
+ if (this._backend.hover !== undefined) {
84
+ this._backend.hover(view, ...point)
85
+
86
+ await this._frame(view)
87
+ }
88
+ }
89
+
90
+ _frame(view) {
91
+ const provider = new AnimationFrameProvider(view)
92
+
93
+ if (this._backend.watch === undefined) return provider.afterAnimationFrame()
94
+
95
+ return new Promise((resolve) => {
96
+ let ended = false
97
+ let unwatch = null
98
+
99
+ const request = provider.afterAnimationFrame(done)
100
+
101
+ unwatch = this._backend.watch(view, done)
102
+
103
+ if (ended) unwatch()
104
+
105
+ function done() {
106
+ if (ended) return
107
+
108
+ ended = true
109
+
110
+ request.cancel()
111
+
112
+ if (unwatch !== null) unwatch()
113
+
114
+ resolve()
115
+ }
116
+ })
117
+ }
118
+
68
119
  _point(view, x, y) {
69
120
  if (x !== undefined && y !== undefined) return [x, y]
70
121
 
@@ -73,7 +124,3 @@ module.exports = class Pointer {
73
124
  return [x === undefined ? width / 2 : x, y === undefined ? height / 2 : y]
74
125
  }
75
126
  }
76
-
77
- function frame(view) {
78
- return new AnimationFrameProvider(view).afterAnimationFrame()
79
- }
package/lib/android.js CHANGED
@@ -30,8 +30,6 @@ module.exports = class AndroidPointer {
30
30
 
31
31
  destroy() {}
32
32
 
33
- // The root is not always at the origin of the window, so the view is placed
34
- // by its offset from the root.
35
33
  _dispatch(action, view, x, y) {
36
34
  const root = view.rootView
37
35
  const here = view.locationInWindow
package/lib/darwin.js CHANGED
@@ -1,3 +1,4 @@
1
+ const AnimationFrameProvider = require('bare-animation-frame')
1
2
  const AppKitApplication = require('bare-app-kit/application')
2
3
  const AppKitEvent = require('bare-app-kit/event')
3
4
 
@@ -15,6 +16,35 @@ module.exports = class DarwinPointer {
15
16
  return [width, height]
16
17
  }
17
18
 
19
+ covered(view) {
20
+ const window = view.window
21
+
22
+ if (window.keyWindow || !window.canBecomeKeyWindow || !AppKitApplication.active) {
23
+ return false
24
+ }
25
+
26
+ window.makeKeyAndOrderFront()
27
+
28
+ return true
29
+ }
30
+
31
+ watch(view, onhidden) {
32
+ let request = null
33
+
34
+ check()
35
+
36
+ function check() {
37
+ const window = view.window
38
+
39
+ if (window === null || !window.visible) onhidden()
40
+ else request = AnimationFrameProvider.requestAnimationFrame(check)
41
+ }
42
+
43
+ return () => {
44
+ if (request !== null) request.cancel()
45
+ }
46
+ }
47
+
18
48
  down(view, x, y) {
19
49
  this._send(TYPE.LEFT_MOUSE_DOWN, view, x, y)
20
50
  }
@@ -27,9 +57,6 @@ module.exports = class DarwinPointer {
27
57
  this._window.sendEvent(this._event(TYPE.LEFT_MOUSE_UP, this._window, this._point))
28
58
  }
29
59
 
30
- // A control tracks the mouse by reading the event queue until it is released,
31
- // and a press that could start a double click is held back until the event
32
- // after it, so both have to go through the queue in order.
33
60
  tap(view, x, y) {
34
61
  const window = view.window
35
62
  const point = view.convertPointToView(x, y, null)
package/lib/linux.js CHANGED
@@ -10,6 +10,20 @@ module.exports = class LinuxPointer {
10
10
  return [view.width, view.height]
11
11
  }
12
12
 
13
+ watch(view, onhidden) {
14
+ const window = view.native
15
+
16
+ if (window === null || !window.visible) {
17
+ onhidden()
18
+
19
+ return () => {}
20
+ }
21
+
22
+ window.on('hide', onhidden)
23
+
24
+ return () => window.off('hide', onhidden)
25
+ }
26
+
13
27
  down(view, x, y) {
14
28
  this._move(view, x, y)
15
29
 
@@ -40,9 +54,6 @@ module.exports = class LinuxPointer {
40
54
  xtest.fakeMotionEvent(this._display, xtest.CURRENT_SCREEN, point.x, point.y)
41
55
  }
42
56
 
43
- // A point in the widget becomes one in its native, then in the surface of
44
- // that, which is drawn in device pixels on an X11 window that the server can
45
- // place on the root.
46
57
  _point(view, x, y) {
47
58
  const native = view.native
48
59
  const surface = native.surface
package/lib/win32.js CHANGED
@@ -2,6 +2,7 @@ const Win32Interop = require('bare-win-ui/win32-interop')
2
2
  const user32 = require('bare-user32')
3
3
 
4
4
  const {
5
+ GA_ROOT,
5
6
  SM_XVIRTUALSCREEN,
6
7
  SM_YVIRTUALSCREEN,
7
8
  SM_CXVIRTUALSCREEN,
@@ -15,47 +16,53 @@ const {
15
16
  } = user32.constants
16
17
 
17
18
  module.exports = class Win32Pointer {
18
- constructor() {
19
- this._point = null
20
- }
21
-
22
19
  size(view) {
23
20
  return [view.actualWidth, view.actualHeight]
24
21
  }
25
22
 
26
- down(view, x, y) {
27
- this._point = locate(view, x, y)
28
- this._send(MOUSEEVENTF_LEFTDOWN)
23
+ covered(view, x, y) {
24
+ const { window, screen } = locate(view, x, y)
25
+ const top = user32.windowFromPoint(screen.x, screen.y)
26
+
27
+ if (top !== null && user32.getAncestor(top, GA_ROOT) === window) return false
28
+
29
+ user32.setForegroundWindow(window)
30
+
31
+ return true
32
+ }
33
+
34
+ hover(view, x, y) {
35
+ this._move(view, x, y)
36
+ }
37
+
38
+ down() {
39
+ this._send({ flags: MOUSEEVENTF_LEFTDOWN })
29
40
  }
30
41
 
31
42
  move(view, x, y) {
32
- this._point = locate(view, x, y)
33
- this._send(0)
43
+ this._move(view, x, y)
34
44
  }
35
45
 
36
46
  up() {
37
- this._send(MOUSEEVENTF_LEFTUP)
47
+ this._send({ flags: MOUSEEVENTF_LEFTUP })
38
48
  }
39
49
 
40
50
  destroy() {}
41
51
 
42
- _send(flags) {
43
- const sent = user32.sendInput([
44
- {
45
- type: INPUT_MOUSE,
46
- mi: {
47
- ...this._point,
48
- flags: flags | MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK
49
- }
50
- }
51
- ])
52
+ _move(view, x, y) {
53
+ this._send({
54
+ ...normalize(locate(view, x, y).screen),
55
+ flags: MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK
56
+ })
57
+ }
58
+
59
+ _send(mi) {
60
+ const sent = user32.sendInput([{ type: INPUT_MOUSE, mi }])
52
61
 
53
62
  if (sent === 0) throw new Error('Input was blocked')
54
63
  }
55
64
  }
56
65
 
57
- // An element is placed in DIPs from the XAML root, which fills the client area
58
- // of its window. Absolute input divides the virtual screen into 65536 steps.
59
66
  function locate(view, x, y) {
60
67
  const root = view.xamlRoot
61
68
  const scale = root.rasterizationScale
@@ -68,13 +75,17 @@ function locate(view, x, y) {
68
75
  Math.round(point.y * scale)
69
76
  )
70
77
 
78
+ return { window, screen }
79
+ }
80
+
81
+ function normalize(screen) {
71
82
  const left = user32.getSystemMetrics(SM_XVIRTUALSCREEN)
72
83
  const top = user32.getSystemMetrics(SM_YVIRTUALSCREEN)
73
84
  const width = user32.getSystemMetrics(SM_CXVIRTUALSCREEN)
74
85
  const height = user32.getSystemMetrics(SM_CYVIRTUALSCREEN)
75
86
 
76
87
  return {
77
- dx: Math.round(((screen.x - left) * 65536) / width),
78
- dy: Math.round(((screen.y - top) * 65536) / height)
88
+ dx: Math.round(((screen.x - left + 0.5) * 65536) / width),
89
+ dy: Math.round(((screen.y - top + 0.5) * 65536) / height)
79
90
  }
80
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-pointer",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Simulated pointer input for testing Bare apps",
5
5
  "exports": {
6
6
  "./package": "./package.json",
@@ -51,13 +51,13 @@
51
51
  "bare-app-kit": "^0.3.1",
52
52
  "bare-ndk": "^0.3.4",
53
53
  "bare-ui-kit-touch": "^0.1.0",
54
- "bare-user32": "^0.1.0",
54
+ "bare-user32": "^0.1.1",
55
55
  "bare-win-ui": "^0.3.7",
56
56
  "bare-x11": "^0.1.0",
57
57
  "bare-x11-xtest": "^0.1.0"
58
58
  },
59
59
  "devDependencies": {
60
- "bare-gtk": "^0.4.1",
60
+ "bare-gtk": "^0.4.2",
61
61
  "bare-native-test": "^0.1.3",
62
62
  "bare-tap": "^1.1.0",
63
63
  "bare-ui-kit": "^0.3.1",