Skip to content

Commit

Permalink
Use sendInputEvent for mouse, support hover
Browse files Browse the repository at this point in the history
Switch from mouse event logic in mouse.mm to Electron's own
sendInputEvent API. This appears to be more reliable and
makes it easier to send additional events that simulate hover
events.

We now send mouseEnter, mouseMove events after completing
a move. If two successive mouse moves are in two different
windows, we now send a mouseLeave event to the previous window.

Also support double-click events by overloading the click
argument to support integer values. To simulate a double-click,
use {click: 2, ...}
  • Loading branch information
ajbouh committed Nov 4, 2018
1 parent 55f4978 commit 23ec699
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 56 deletions.
101 changes: 92 additions & 9 deletions electron/demokit/mouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ const execute = require("demokit/execute");
const { wait } = require("./demokit");
const { Workspace, Scene, convert } = require("./coordinate-space");
const { getCenterRect, getBoundingClientRect } = require("./geometry");
const { getCursor, performClick } = require("bindings")("mouse");
const { getCursor } = require("bindings")("mouse");


exports.click = async function ({ move = true, effect = true, window, selector, nth, x, y, dx = 0, dy = 0, reveal = false, space = Scene })
exports.click = async function ({ move = true, effect = true, window, selector, nth, x, y, dx = 0, dy = 0, count = 1, reveal = false, space = Scene })
{
await moveAndClick({ move, effect, click: true, window, selector, nth, x, y, dx, dy, space });
await moveAndClick({ move, effect, click: count, window, selector, nth, x, y, dx, dy, space });

if (reveal)
{
Expand All @@ -29,22 +29,105 @@ exports.effect = async function ({ window, selector, nth, x, y, dx = 0, dy = 0,
await moveAndClick({ move: false, effect: true, click: false, window, selector, nth, x, y, dx, dy, space });
}

async function moveAndClick({ move, click, effect, window, selector, nth, x, y, dx, dy, space })
let PREVIOUS_MOVE = undefined;

async function moveAndClick({ move, click, effect, window, selector, fraction, nth, x, y, dx, dy, space })
{
if (selector)
await wait.visible({ window, selector, nth });

const positionWithinWindow = selector ?
getCenterRect({ rect: await getBoundingClientRect({ window, selector, nth, space: window }) }) :
{x: x, y: y};
const position = await getWorkspaceMousePosition({ window, selector, nth, x, y, space });
const adjusted = { x: position.x + dx, y: position.y + dy };

if (move)
let adjusted = { x: position.x + dx, y: position.y + dy };
let adjustedWithinWindow = { x: positionWithinWindow.x + dx, y: positionWithinWindow.y + dy };

if (move) {
if (PREVIOUS_MOVE && PREVIOUS_MOVE.id !== window) {
await sendInputEvents({
window: PREVIOUS_MOVE.id,
events: [
{
type: "mouseLeave",
x: PREVIOUS_MOVE.x,
y: PREVIOUS_MOVE.y,
},
]
});
}
PREVIOUS_MOVE = { id: window, x: Math.round(adjustedWithinWindow.x), y: Math.round(adjustedWithinWindow.y) };

await moveSmooth({ destination: adjusted });

await sendInputEvents({
window,
events: [
{
type: "mouseEnter",
x: Math.round(adjustedWithinWindow.x), y: Math.round(adjustedWithinWindow.y)
},
{
type: "mouseMove",
x: Math.round(adjustedWithinWindow.x), y: Math.round(adjustedWithinWindow.y)
},
]
});
}

if (effect)
await showClickEffect({ point: adjusted, space: Workspace });

if (click)
performClick(adjusted.x, adjusted.y);
if (click) {
const clickCount = click === true ? 1 : click;
await sendInputEvents({
window,
events: [
{
type: "mouseDown",
clickCount: clickCount,
button: 'left',
x: Math.round(adjustedWithinWindow.x), y: Math.round(adjustedWithinWindow.y)
},
{
type: "mouseUp",
clickCount: clickCount,
button: 'left',
x: Math.round(adjustedWithinWindow.x), y: Math.round(adjustedWithinWindow.y)
}
]
});
}

async function sendInputEvents({window, events, delay = 50}) {
await execute(
{
args: [{ id: window, events, delay }],
script: function ({ id, events, delay }, resolve, reject) {
const webview = document.querySelector("#" + id + " .window-content-webview");
const webcontents = webview.getWebContents()

const step = (ix) => {
if (ix >= events.length) {
resolve()
return
}

if (!webcontents.isFocused()) {
console.log(`[${id}] was not focused. focusing...`)
webcontents.focus()
setTimeout(step, 100, ix);
return
}

const event = events[ix];
webcontents.sendInputEvent(event)
setTimeout(step, delay, ix + 1)
}
step(0)
}
});
}
}

exports.hide = async function ()
Expand Down
47 changes: 0 additions & 47 deletions electron/demokit/mouse.mm
Original file line number Diff line number Diff line change
Expand Up @@ -96,56 +96,9 @@ void GetCursor(const Nan::FunctionCallbackInfo<v8::Value>& info)
return nil;
}

void PerformClick(const Nan::FunctionCallbackInfo<v8::Value>& info)
{
@autoreleasepool
{
if (info.Length() < 2)
{
Nan::ThrowTypeError("Wrong number of arguments");
return;
}

v8::Isolate* isolate = info.GetIsolate();
int x = info[0]->ToNumber(isolate)->Int32Value();
int y = info[1]->ToNumber(isolate)->Int32Value();

NSWindow * window = [NSApp windows][0];
int windowNumber = window.windowNumber;
NSView * contentView = window.contentView;
NSPoint contentPoint = NSMakePoint(x, NSHeight(contentView.frame) - y);
NSPoint windowPoint = [contentView convertPoint:contentPoint toView:nil];
NSEvent * mouseDown = [NSEvent mouseEventWithType:NSLeftMouseDown
location:windowPoint
modifierFlags:0
timestamp:[[NSDate date] timeIntervalSince1970]
windowNumber:windowNumber
context:nil
eventNumber:0
clickCount:1
pressure:0];

[NSApp sendEvent:mouseDown];

NSEvent * mouseUp = [NSEvent mouseEventWithType:NSLeftMouseUp
location:windowPoint
modifierFlags:0
timestamp:[[NSDate date] timeIntervalSince1970]
windowNumber:windowNumber
context:nil
eventNumber:0
clickCount:1
pressure:0];

[NSApp sendEvent:mouseUp];
}
}

void Init(v8::Local<v8::Object> exports) {
exports->Set(Nan::New("getCursor").ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(GetCursor)->GetFunction());
exports->Set(Nan::New("performClick").ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(PerformClick)->GetFunction());
}

NODE_MODULE(mouse, Init);

0 comments on commit 23ec699

Please sign in to comment.