screenshot.dart 3.26 KB
Newer Older
Devon Carew's avatar
Devon Carew committed
1 2 3 4 5
// 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';
6
import 'dart:convert';
Devon Carew's avatar
Devon Carew committed
7

8
import '../base/common.dart';
9
import '../base/file_system.dart';
Devon Carew's avatar
Devon Carew committed
10
import '../base/utils.dart';
11
import '../device.dart';
Devon Carew's avatar
Devon Carew committed
12 13
import '../globals.dart';
import '../runner/flutter_command.dart';
14
import '../vmservice.dart';
Devon Carew's avatar
Devon Carew committed
15

16 17 18
const String _kOut = 'out';
const String _kSkia = 'skia';

Devon Carew's avatar
Devon Carew committed
19 20
class ScreenshotCommand extends FlutterCommand {
  ScreenshotCommand() {
21 22
    argParser.addOption(
      _kOut,
Devon Carew's avatar
Devon Carew committed
23
      abbr: 'o',
24 25 26 27 28 29
      help: 'Location to write the screenshot.',
    );
    argParser.addOption(
      _kSkia,
      valueHelp: 'port',
      help: 'Retrieve the last frame rendered by a Flutter app as a Skia picture\n'
30 31 32
        'using the specified observatory port.\n'
        'To find the observatory port number, use "flutter run --verbose"\n'
        'and look for "Forwarded host port ... for Observatory" in the output.'
33
    );
Devon Carew's avatar
Devon Carew committed
34 35 36 37 38 39 40 41 42 43 44
  }

  @override
  String get name => 'screenshot';

  @override
  String get description => 'Take a screenshot from a connected device.';

  @override
  final List<String> aliases = <String>['pic'];

45
  Device device;
Devon Carew's avatar
Devon Carew committed
46 47

  @override
48
  Future<Null> verifyThenRunCommand() async {
49 50 51 52 53
    device = await findTargetDevice();
    if (device == null)
      throwToolExit('Must have a connected device');
    if (!device.supportsScreenshot && argResults[_kSkia] == null)
      throwToolExit('Screenshot not supported for ${device.name}.');
54 55
    return super.verifyThenRunCommand();
  }
Devon Carew's avatar
Devon Carew committed
56 57

  @override
58
  Future<Null> runCommand() async {
Devon Carew's avatar
Devon Carew committed
59
    File outputFile;
60
    if (argResults.wasParsed(_kOut))
61
      outputFile = fs.file(argResults[_kOut]);
Devon Carew's avatar
Devon Carew committed
62

63 64
    if (argResults[_kSkia] != null) {
      return runSkia(outputFile);
Devon Carew's avatar
Devon Carew committed
65
    } else {
66
      return runScreenshot(outputFile);
Devon Carew's avatar
Devon Carew committed
67
    }
68
  }
Devon Carew's avatar
Devon Carew committed
69

70
  Future<Null> runScreenshot(File outputFile) async {
71
    outputFile ??= getUniqueFile(fs.currentDirectory, 'flutter', 'png');
Devon Carew's avatar
Devon Carew committed
72
    try {
73
      await device.takeScreenshot(outputFile);
Devon Carew's avatar
Devon Carew committed
74
    } catch (error) {
75
      throwToolExit('Error taking screenshot: $error');
Devon Carew's avatar
Devon Carew committed
76
    }
77
    await showOutputFileInfo(outputFile);
Devon Carew's avatar
Devon Carew committed
78
  }
79

80
  Future<Null> runSkia(File outputFile) async {
81 82
    final Uri observatoryUri = new Uri(scheme: 'http', host: '127.0.0.1',
        port: int.parse(argResults[_kSkia]));
83
    final VMService vmService = await VMService.connect(observatoryUri);
84 85 86 87 88 89 90 91
    final Map<String, dynamic> skp = await vmService.vm.invokeRpcRaw('_flutter.screenshotSkp');

    outputFile ??= getUniqueFile(fs.currentDirectory, 'flutter', 'skp');
    final IOSink sink = outputFile.openWrite();
    sink.add(BASE64.decode(skp['skp']));
    await sink.close();
    await showOutputFileInfo(outputFile);
    if (await outputFile.length() < 1000) {
92 93 94
      final String content = await outputFile.readAsString(
        encoding: const AsciiCodec(allowInvalid: true),
      );
95 96
      if (content.startsWith('{"jsonrpc":"2.0", "error"'))
        throwToolExit('\nIt appears the output file contains an error message, not valid skia output.');
97 98 99 100
    }
  }

  Future<Null> showOutputFileInfo(File outputFile) async {
101
    final int sizeKB = (await outputFile.length()) ~/ 1024;
102
    printStatus('Screenshot written to ${fs.path.relative(outputFile.path)} (${sizeKB}kB).');
103
  }
Devon Carew's avatar
Devon Carew committed
104
}