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

7
import 'package:http/http.dart' as http;
Devon Carew's avatar
Devon Carew committed
8

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

17 18 19 20
const String _kOut = 'out';
const String _kSkia = 'skia';
const String _kSkiaServe = 'skiaserve';

Devon Carew's avatar
Devon Carew committed
21 22
class ScreenshotCommand extends FlutterCommand {
  ScreenshotCommand() {
23 24
    argParser.addOption(
      _kOut,
Devon Carew's avatar
Devon Carew committed
25
      abbr: 'o',
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
      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'
        'using the specified diagnostic server port.\n'
        'To find the diagnostic server port number, use "flutter run --verbose"\n'
        'and look for "Diagnostic server listening on" in the output.'
    );
    argParser.addOption(
      _kSkiaServe,
      valueHelp: 'url',
      help: 'Post the picture to a skiaserve debugger at this URL.',
    );
Devon Carew's avatar
Devon Carew committed
41 42 43 44 45 46 47 48 49 50 51
  }

  @override
  String get name => 'screenshot';

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

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

52
  Device device;
Devon Carew's avatar
Devon Carew committed
53 54

  @override
55
  Future<Null> verifyThenRunCommand() async {
56
    if (argResults[_kSkia] != null) {
57 58
      if (argResults[_kOut] != null && argResults[_kSkiaServe] != null)
        throwToolExit('Cannot specify both --$_kOut and --$_kSkiaServe');
59
    } else {
60 61
      if (argResults[_kSkiaServe] != null)
        throwToolExit('Must specify --$_kSkia with --$_kSkiaServe');
62
      device = await findTargetDevice();
63 64 65 66
      if (device == null)
        throwToolExit('Must specify --$_kSkia or have a connected device');
      if (!device.supportsScreenshot && argResults[_kSkia] == null)
        throwToolExit('Screenshot not supported for ${device.name}.');
67
    }
68 69
    return super.verifyThenRunCommand();
  }
Devon Carew's avatar
Devon Carew committed
70 71

  @override
72
  Future<Null> runCommand() async {
Devon Carew's avatar
Devon Carew committed
73
    File outputFile;
74
    if (argResults.wasParsed(_kOut))
75
      outputFile = fs.file(argResults[_kOut]);
Devon Carew's avatar
Devon Carew committed
76

77 78
    if (argResults[_kSkia] != null) {
      return runSkia(outputFile);
Devon Carew's avatar
Devon Carew committed
79
    } else {
80
      return runScreenshot(outputFile);
Devon Carew's avatar
Devon Carew committed
81
    }
82
  }
Devon Carew's avatar
Devon Carew committed
83

84
  Future<Null> runScreenshot(File outputFile) async {
85
    outputFile ??= getUniqueFile(fs.currentDirectory, 'flutter', 'png');
Devon Carew's avatar
Devon Carew committed
86
    try {
87
      await device.takeScreenshot(outputFile);
Devon Carew's avatar
Devon Carew committed
88
    } catch (error) {
89
      throwToolExit('Error taking screenshot: $error');
Devon Carew's avatar
Devon Carew committed
90
    }
91
    await showOutputFileInfo(outputFile);
Devon Carew's avatar
Devon Carew committed
92
  }
93

94
  Future<Null> runSkia(File outputFile) async {
95
    final Uri skpUri = new Uri(scheme: 'http', host: '127.0.0.1',
96 97 98
        port: int.parse(argResults[_kSkia]),
        path: '/skp');

99 100 101 102
    const String errorHelpText =
        'Be sure the --$_kSkia= option specifies the diagnostic server port, not the observatory port.\n'
        'To find the diagnostic server port number, use "flutter run --verbose"\n'
        'and look for "Diagnostic server listening on" in the output.';
103 104 105 106

    http.StreamedResponse skpResponse;
    try {
      skpResponse = await new http.Request('GET', skpUri).send();
107
    } on SocketException catch (e) {
108
      throwToolExit('Skia screenshot failed: $skpUri\n$e\n\n$errorHelpText');
109
    }
110
    if (skpResponse.statusCode != HttpStatus.OK) {
111
      final String error = await skpResponse.stream.toStringStream().join();
112
      throwToolExit('Error: $error\n\n$errorHelpText');
113 114 115
    }

    if (argResults[_kSkiaServe] != null) {
116 117 118
      final Uri skiaserveUri = Uri.parse(argResults[_kSkiaServe]);
      final Uri postUri = new Uri.http(skiaserveUri.authority, '/new');
      final http.MultipartRequest postRequest = new http.MultipartRequest('POST', postUri);
119 120 121
      postRequest.files.add(new http.MultipartFile(
          'file', skpResponse.stream, skpResponse.contentLength));

122
      final http.StreamedResponse postResponse = await postRequest.send();
123
      if (postResponse.statusCode != HttpStatus.OK)
124
        throwToolExit('Failed to post Skia picture to skiaserve.\n\n$errorHelpText');
125
    } else {
126
      outputFile ??= getUniqueFile(fs.currentDirectory, 'flutter', 'skp');
127
      final IOSink sink = outputFile.openWrite();
128 129 130 131
      await sink.addStream(skpResponse.stream);
      await sink.close();
      await showOutputFileInfo(outputFile);
      if (await outputFile.length() < 1000) {
132
        final String content = await outputFile.readAsString();
133 134
        if (content.startsWith('{"jsonrpc":"2.0", "error"'))
          throwToolExit('\nIt appears the output file contains an error message, not valid skia output.\n\n$errorHelpText');
135 136 137 138 139
      }
    }
  }

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