add a screenshot command
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index f03a92c..23bc288 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -436,6 +436,20 @@ runCheckedSync(cmd); return true; } + + @override + bool get supportsScreenshot => true; + + @override + Future<bool> takeScreenshot(File outputFile) { + const String remotePath = '/data/local/tmp/flutter_screenshot.png'; + + runCheckedSync(adbCommandForDevice(<String>['shell', 'screencap', '-p', remotePath])); + runCheckedSync(adbCommandForDevice(<String>['pull', remotePath, outputFile.path])); + runCheckedSync(adbCommandForDevice(<String>['shell', 'rm', remotePath])); + + return new Future<bool>.value(true); + } } List<AndroidDevice> getAdbDevices() {
diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index cdbdec1..f88a834 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart
@@ -6,6 +6,7 @@ import 'dart:io'; import 'package:crypto/crypto.dart'; +import 'package:path/path.dart' as path; String hex(List<int> bytes) { StringBuffer result = new StringBuffer(); @@ -33,6 +34,18 @@ /// Return the plural of the given word (`cat(s)`). String pluralize(String word, int count) => count == 1 ? word : word + 's'; +File getUniqueFile(Directory dir, String baseName, String ext) { + int i = 1; + + while (true) { + String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext'; + File file = new File(path.join(dir.path, name)); + if (!file.existsSync()) + return file; + i++; + } +} + /// A class to maintain a list of items, fire events when items are added or /// removed, and calculate a diff of changes when a new list of items is /// available.
diff --git a/packages/flutter_tools/lib/src/commands/screenshot.dart b/packages/flutter_tools/lib/src/commands/screenshot.dart new file mode 100644 index 0000000..bb6014c --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/screenshot.dart
@@ -0,0 +1,65 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import '../base/utils.dart'; +import '../globals.dart'; +import '../runner/flutter_command.dart'; + +class ScreenshotCommand extends FlutterCommand { + ScreenshotCommand() { + argParser.addOption('out', + abbr: 'o', + help: 'Location to write the screenshot.'); + } + + @override + String get name => 'screenshot'; + + @override + String get description => 'Take a screenshot from a connected device.'; + + @override + final List<String> aliases = <String>['pic']; + + @override + bool get requiresProjectRoot => false; + + @override + bool get requiresDevice => true; + + @override + Future<int> runInProject() async { + if (!deviceForCommand.supportsScreenshot) { + printError('Screenshot not supported for ${deviceForCommand.name}.'); + return 1; + } + + File outputFile; + + if (argResults.wasParsed('out')) { + outputFile = new File(argResults['out']); + } else { + outputFile = getUniqueFile(Directory.current, 'flutter', 'png'); + } + + try { + bool result = await deviceForCommand.takeScreenshot(outputFile); + + if (result) { + int sizeKB = outputFile.lengthSync() ~/ 1000; + printStatus('Screenshot written to ${path.relative(outputFile.path)} (${sizeKB}kb).'); + return 0; + } + } catch (error) { + printError('Error taking screenshot: $error'); + } + + return 1; + } +}
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 1764dc1..eaf2362 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart
@@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'dart:math' as math; import 'android/android_device.dart'; @@ -191,6 +192,10 @@ /// Stop an app package on the current device. Future<bool> stopApp(ApplicationPackage app); + bool get supportsScreenshot => false; + + Future<bool> takeScreenshot(File outputFile) => new Future<bool>.error('unimplemented'); + @override int get hashCode => id.hashCode;
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index d005f32..6d676ca 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -250,6 +250,18 @@ @override void clearLogs() { } + + @override + bool get supportsScreenshot => false; + + @override + Future<bool> takeScreenshot(File outputFile) { + // We could use idevicescreenshot here (installed along with the brew + // ideviceinstaller tools). It however requires a developer disk image on + // the device. + + return new Future<bool>.value(false); + } } class _IOSDeviceLogReader extends DeviceLogReader {
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 151f808..2703232 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -615,6 +615,49 @@ if (!logFile.existsSync()) logFile.writeAsBytesSync(<int>[]); } + + @override + bool get supportsScreenshot => true; + + @override + Future<bool> takeScreenshot(File outputFile) async { + String homeDirPath = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']; + Directory desktopDir = new Directory(path.join(homeDirPath, 'Desktop')); + + // 'Simulator Screen Shot Mar 25, 2016, 2.59.43 PM.png' + + Set<File> getScreenshots() { + return new Set<File>.from(desktopDir.listSync().where((FileSystemEntity entity) { + String name = path.basename(entity.path); + return entity is File && name.startsWith('Simulator') && name.endsWith('.png'); + })); + }; + + Set<File> existingScreenshots = getScreenshots(); + + runSync(<String>[ + 'osascript', + '-e', + 'activate application "Simulator"\n' + 'tell application "System Events" to keystroke "s" using command down' + ]); + + // There is some latency here from the applescript call. + await new Future<Null>.delayed(new Duration(seconds: 1)); + + Set<File> shots = getScreenshots().difference(existingScreenshots); + + if (shots.isEmpty) { + printError('Unable to locate the screenshot file.'); + return false; + } + + File shot = shots.first; + outputFile.writeAsBytesSync(shot.readAsBytesSync()); + shot.delete(); + + return true; + } } class _IOSSimulatorLogReader extends DeviceLogReader {