1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright 2014 The Flutter 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 'package:vm_service/vm_service.dart' as vm_service;
import '../base/common.dart';
import '../base/file_system.dart';
import '../convert.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../runner/flutter_command.dart';
import '../vmservice.dart';
const String _kOut = 'out';
const String _kType = 'type';
const String _kObservatoryUri = 'observatory-uri';
const String _kDeviceType = 'device';
const String _kSkiaType = 'skia';
const String _kRasterizerType = 'rasterizer';
class ScreenshotCommand extends FlutterCommand {
ScreenshotCommand() {
argParser.addOption(
_kOut,
abbr: 'o',
valueHelp: 'path/to/file',
help: 'Location to write the screenshot.',
);
argParser.addOption(
_kObservatoryUri,
valueHelp: 'URI',
help: 'The observatory URI to connect to.\n'
'This is required when --$_kType is "$_kSkiaType" or "$_kRasterizerType".\n'
'To find the observatory URI, use "flutter run" and look for '
'"An Observatory ... is available at" in the output.',
);
argParser.addOption(
_kType,
valueHelp: 'type',
help: 'The type of screenshot to retrieve.',
allowed: const <String>[_kDeviceType, _kSkiaType, _kRasterizerType],
allowedHelp: const <String, String>{
_kDeviceType: "Delegate to the device's native screenshot capabilities. This "
'screenshots the entire screen currently being displayed (including content '
'not rendered by Flutter, like the device status bar).',
_kSkiaType: 'Render the Flutter app as a Skia picture. Requires --$_kObservatoryUri',
_kRasterizerType: 'Render the Flutter app using the rasterizer. Requires --$_kObservatoryUri',
},
defaultsTo: _kDeviceType,
);
}
@override
String get name => 'screenshot';
@override
String get description => 'Take a screenshot from a connected device.';
@override
final List<String> aliases = <String>['pic'];
Device device;
static void validateOptions(String screenshotType, Device device, String observatoryUri) {
switch (screenshotType) {
case _kDeviceType:
if (device == null) {
throwToolExit('Must have a connected device for screenshot type $screenshotType');
}
if (!device.supportsScreenshot) {
throwToolExit('Screenshot not supported for ${device.name}.');
}
break;
default:
if (observatoryUri == null) {
throwToolExit('Observatory URI must be specified for screenshot type $screenshotType');
}
}
}
@override
Future<FlutterCommandResult> verifyThenRunCommand(String commandPath) async {
device = await findTargetDevice();
validateOptions(stringArg(_kType), device, stringArg(_kObservatoryUri));
return super.verifyThenRunCommand(commandPath);
}
@override
Future<FlutterCommandResult> runCommand() async {
File outputFile;
if (argResults.wasParsed(_kOut)) {
outputFile = globals.fs.file(stringArg(_kOut));
}
switch (stringArg(_kType)) {
case _kDeviceType:
await runScreenshot(outputFile);
return FlutterCommandResult.success();
case _kSkiaType:
await runSkia(outputFile);
return FlutterCommandResult.success();
case _kRasterizerType:
await runRasterizer(outputFile);
return FlutterCommandResult.success();
}
return FlutterCommandResult.success();
}
Future<void> runScreenshot(File outputFile) async {
outputFile ??= globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'png',
);
try {
await device.takeScreenshot(outputFile);
} on Exception catch (error) {
throwToolExit('Error taking screenshot: $error');
}
_showOutputFileInfo(outputFile);
}
Future<void> runSkia(File outputFile) async {
final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri));
final vm_service.VmService vmService = await connectToVmService(observatoryUri);
final vm_service.Response skp = await vmService.screenshotSkp();
outputFile ??= globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'skp',
);
final IOSink sink = outputFile.openWrite();
sink.add(base64.decode(skp.json['skp'] as String));
await sink.close();
_showOutputFileInfo(outputFile);
_ensureOutputIsNotJsonRpcError(outputFile);
}
Future<void> runRasterizer(File outputFile) async {
final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri));
final vm_service.VmService vmService = await connectToVmService(observatoryUri);
final vm_service.Response response = await vmService.screenshot();
outputFile ??= globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'png',
);
final IOSink sink = outputFile.openWrite();
sink.add(base64.decode(response.json['screenshot'] as String));
await sink.close();
_showOutputFileInfo(outputFile);
_ensureOutputIsNotJsonRpcError(outputFile);
}
void _ensureOutputIsNotJsonRpcError(File outputFile) {
if (outputFile.lengthSync() >= 1000) {
return;
}
final String content = outputFile.readAsStringSync(
encoding: const AsciiCodec(allowInvalid: true),
);
if (content.startsWith('{"jsonrpc":"2.0", "error"')) {
throwToolExit('It appears the output file contains an error message, not valid skia output.');
}
}
void _showOutputFileInfo(File outputFile) {
final int sizeKB = (outputFile.lengthSync()) ~/ 1024;
globals.printStatus('Screenshot written to ${globals.fs.path.relative(outputFile.path)} (${sizeKB}kB).');
}
}