Skip to content

Commit

Permalink
Add simulated input support
Browse files Browse the repository at this point in the history
Signed-off-by: Liam McLoughlin <[email protected]>
  • Loading branch information
Hexxeh committed Jan 17, 2020
1 parent 0938332 commit 6c8566a
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 1 deletion.
39 changes: 39 additions & 0 deletions packages/fdb-debugger/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,16 +449,55 @@ export class RemoteHost extends EventEmitter {
FDBTypes.AppDebugEvalResult,
);

private sendButtonInput = this.bindMethod(
'input.button',
FDBTypes.ButtonInput,
t.null,
);

private sendTouchInput = this.bindMethod(
'input.touch',
FDBTypes.TouchInput,
t.null,
);

hasEvalSupport() {
return this.hasCapability('appHost.debug.app.evalToString.supported') &&
this.info.capabilities.appHost!.debug!.app!.evalToString!.supported &&
!FBOS3_EVAL_QUIRK.test(this.info.device);
}

hasTouchInputSupport() {
return this.hasCapability('appHost.input.touch') &&
this.info.capabilities.appHost!.input!.touch!;
}

hasButtonInputSupport() {
return this.hasCapability('appHost.input.buttons') &&
this.info.capabilities.appHost!.input!.buttons! &&
this.info.capabilities.appHost!.input!.buttons!.length > 0;
}

buttons() {
if (!this.hasButtonInputSupport) return [];
return this.info.capabilities.appHost!.input!.buttons!;
}

eval(cmd: string) {
return this.sendEvalCmd({ cmd });
}

simulateButtonPress(button: FDBTypes.Button) {
return this.sendButtonInput({ button });
}

simulateTouch(location: FDBTypes.Point, state: FDBTypes.TouchState) {
return this.sendTouchInput({
location,
state,
});
}

supportsPartialAppInstall() {
return this.hasCapability('appHost.install.partialBundle') &&
this.info.capabilities.appHost!.install!.partialBundle!;
Expand Down
2 changes: 2 additions & 0 deletions packages/fdb-protocol/src/FDBTypes/Initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IOCapabilities } from './BulkData';
import { ConsoleDebuggerCapabilities } from './Console';
import { EvalToStringCapability } from './Eval';
import { HeapSnapshotCapability } from './HeapSnapshot';
import { InputCapabilities } from './Input';
import { LaunchCapabilities } from './Launch';
import { ProtocolCapabilities } from './Meta';
import { ScreenshotCapabilities } from './Screenshot';
Expand Down Expand Up @@ -84,6 +85,7 @@ export const ApplicationHostCapabilities = t.partial(
launch: LaunchCapabilities,
screenshot: ScreenshotCapabilities,
debug: DebugCapabilities,
input: InputCapabilities,
},
'ApplicationHostCapabilities',
);
Expand Down
66 changes: 66 additions & 0 deletions packages/fdb-protocol/src/FDBTypes/Input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as t from 'io-ts';

import { Point } from './Structures';

// Runtime types are variables which are used like types, which is
// reflected in their PascalCase naming scheme.
/* tslint:disable:variable-name */

export const Button = t.union(
[t.literal('up'), t.literal('down'), t.literal('back')],
'Button',
);
export type Button = t.TypeOf<typeof Button>;

export const ButtonInput = t.interface(
{
/**
* Which button is being pressed.
*/
button: Button,
},
'ButtonInput',
);
export type ButtonInput = t.TypeOf<typeof ButtonInput>;

export const TouchState = t.union(
[t.literal('up'), t.literal('down'), t.literal('move')],
'TouchState',
);
export type TouchState = t.TypeOf<typeof TouchState>;

export const TouchInput = t.interface(
{
/**
* Status of simulated touch.
* 'move' must only be sent in the period between a 'down' input and its corresponding 'up'.
*/
state: TouchState,

/**
* Location of touch event.
*/
location: Point,
},
'TouchInput',
);
export type TouchInput = t.TypeOf<typeof TouchInput>;

/**
* Capabilities specific to inputs.
*/
export const InputCapabilities = t.partial(
{
/**
* The Host supports sending simulated button presses.
*/
buttons: t.array(Button),

/**
* The Host supports sending simulated touch screen presses.
*/
touch: t.boolean,
},
'InputCapabilities',
);
export type InputCapabilities = t.TypeOf<typeof InputCapabilities>;
12 changes: 12 additions & 0 deletions packages/fdb-protocol/src/FDBTypes/Structures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,15 @@ export const ComponentBundleKind = t.union([
t.literal('companion'),
]);
export type ComponentBundleKind = t.TypeOf<typeof ComponentBundleKind>;

/**
* Describes a point on the simulated device's screen, relative to the top-left corner at (0,0).
*/
export const Point = t.interface(
{
x: NonNegativeInteger,
y: NonNegativeInteger,
},
'Point',
);
export type Point = t.TypeOf<typeof Point>;
1 change: 1 addition & 0 deletions packages/fdb-protocol/src/FDBTypes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './ContentsList';
export * from './Eval';
export * from './HeapSnapshot';
export * from './Initialize';
export * from './Input';
export * from './Launch';
export * from './Meta';
export * from './Screenshot';
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import buildAndInstall from './commands/buildAndInstall';
import connect from './commands/connect';
import heapSnapshot from './commands/heapSnapshot';
import hosts from './commands/hosts';
import input from './commands/input';
import install from './commands/install';
import logout from './commands/logout';
import mockHost from './commands/mockHost';
Expand All @@ -38,6 +39,7 @@ cli.use(build);
cli.use(buildAndInstall({ hostConnections, appContext }));
cli.use(connect({ hostConnections }));
cli.use(heapSnapshot({ hostConnections }));
cli.use(input({ hostConnections }));
cli.use(install({ hostConnections, appContext }));
cli.use(screenshot({ hostConnections }));
cli.use(setAppPackage({ appContext }));
Expand Down
94 changes: 94 additions & 0 deletions packages/sdk-cli/src/commands/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { FDBTypes } from '@fitbit/fdb-protocol';
import { isRight } from 'fp-ts/lib/Either';
import vorpal from 'vorpal';

import HostConnections from '../models/HostConnections';

function isSupportedButton(
supportedButtons: FDBTypes.Button[],
button: string,
): button is FDBTypes.Button {
return supportedButtons.includes(button as FDBTypes.Button);
}

function isValidTouchState(state: string): state is FDBTypes.TouchState {
return isRight(FDBTypes.TouchState.decode(state));
}

const wait = (durationMs: number) =>
new Promise(resolve => setTimeout(resolve, durationMs));

export default function input(stores: { hostConnections: HostConnections }) {
return (cli: vorpal) => {
cli
.command('input button <button>', 'Simulate a button press on device')
.hidden()
.action(async (args: vorpal.Args & { button?: string }) => {
const { appHost } = stores.hostConnections;
if (!appHost) {
cli.activeCommand.log('Not connected to a device');
return false;
}

if (!appHost.host.hasButtonInputSupport()) {
cli.activeCommand.log(
'Connected device does not support simulated button presses',
);
return false;
}

cli.activeCommand.log(args.button);
if (!isSupportedButton(appHost.host.buttons(), args.button!)) {
cli.activeCommand.log(
`Connected device does not support requested button type. Supported buttons: ${appHost.host
.buttons()
.join(', ')}`,
);
return false;
}

return appHost.host.simulateButtonPress(args.button);
});

cli
.command(
'input touch <state> <x> <y>',
'Simualate a touch event on device',
)
.hidden()
.action(
async (
args: vorpal.Args & { state?: string; x?: number; y?: number },
) => {
const { appHost } = stores.hostConnections;
if (!appHost) {
cli.activeCommand.log('Not connected to a device');
return false;
}

if (!appHost.host.hasTouchInputSupport()) {
cli.activeCommand.log(
'Connected device does not support simulated touch events',
);
return false;
}

if (args.state === 'tap') {
await appHost.host.simulateTouch({ x: args.x!, y: args.y! }, 'down');
await wait(250);
await appHost.host.simulateTouch({ x: args.x!, y: args.y! }, 'up');
} else {
if (!isValidTouchState(args.state!)) {
cli.activeCommand.log('Touch state provided was not valid');
return false;
}

return appHost.host.simulateTouch(
{ x: args.x!, y: args.y! },
args.state,
);
}
},
);
};
}
Loading

0 comments on commit 6c8566a

Please sign in to comment.