1import * as osascript from '@expo/osascript';
2import { execFileSync } from 'child_process';
3
4import { Device } from './adb';
5
6const debug = require('debug')('expo:start:platforms:android:activateWindow') as typeof console.log;
7
8function getUnixPID(port: number | string): string {
9  // Runs like `lsof -i:8081 -P -t -sTCP:LISTEN`
10  const args = [`-i:${port}`, '-P', '-t', '-sTCP:LISTEN'];
11  debug('lsof ' + args.join(' '));
12  return execFileSync('lsof', args, {
13    encoding: 'utf8',
14    stdio: ['pipe', 'pipe', 'ignore'],
15  })
16    .split('\n')[0]
17    ?.trim?.();
18}
19
20/** Activate the Emulator window on macOS. */
21export async function activateWindowAsync(device: Pick<Device, 'type' | 'pid'>): Promise<boolean> {
22  debug(`Activating window for device (pid: ${device.pid}, type: ${device.type})`);
23  if (
24    // only mac is supported for now.
25    process.platform !== 'darwin' ||
26    // can only focus emulators
27    device.type !== 'emulator'
28  ) {
29    return false;
30  }
31
32  // Google Emulator ID: `emulator-5554` -> `5554`
33  const androidPid = device.pid!.match(/-(\d+)/)?.[1];
34  if (!androidPid) {
35    return false;
36  }
37  // Unix PID
38  const pid = getUnixPID(androidPid);
39
40  if (!pid) {
41    return false;
42  }
43  debug(`Activate window for pid:`, pid);
44  try {
45    await osascript.execAsync(`
46    tell application "System Events"
47      set frontmost of the first process whose unix id is ${pid} to true
48    end tell`);
49    return true;
50  } catch {
51    // noop -- this feature is very specific and subject to failure.
52    return false;
53  }
54}
55