mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
227 lines
7.3 KiB
Dart
227 lines
7.3 KiB
Dart
// 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 'package:meta/meta.dart';
|
|
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 _kVmServiceUrl = 'vm-service-url';
|
|
const String _kDeviceType = 'device';
|
|
const String _kSkiaType = 'skia';
|
|
const String _kRasterizerType = 'rasterizer';
|
|
|
|
class ScreenshotCommand extends FlutterCommand {
|
|
ScreenshotCommand({required this.fs}) {
|
|
argParser.addOption(
|
|
_kOut,
|
|
abbr: 'o',
|
|
valueHelp: 'path/to/file',
|
|
help: 'Location to write the screenshot.',
|
|
);
|
|
argParser.addOption(
|
|
_kVmServiceUrl,
|
|
aliases: <String>[ 'observatory-url' ], // for historical reasons
|
|
valueHelp: 'URI',
|
|
help: 'The VM Service URL to which to connect.\n'
|
|
'This is required when "--$_kType" is "$_kSkiaType" or "$_kRasterizerType".\n'
|
|
'To find the VM service URL, use "flutter run" and look for '
|
|
'"A Dart VM Service ... 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 "--$_kVmServiceUrl".',
|
|
_kRasterizerType: 'Render the Flutter app using the rasterizer. Requires "--$_kVmServiceUrl."',
|
|
},
|
|
defaultsTo: _kDeviceType,
|
|
);
|
|
usesDeviceTimeoutOption();
|
|
}
|
|
|
|
final FileSystem fs;
|
|
|
|
@override
|
|
String get name => 'screenshot';
|
|
|
|
@override
|
|
String get description => 'Take a screenshot from a connected device.';
|
|
|
|
@override
|
|
final String category = FlutterCommandCategory.tools;
|
|
|
|
@override
|
|
final List<String> aliases = <String>['pic'];
|
|
|
|
Device? device;
|
|
|
|
Future<void> _validateOptions(String? screenshotType, String? vmServiceUrl) async {
|
|
switch (screenshotType) {
|
|
case _kDeviceType:
|
|
if (vmServiceUrl != null) {
|
|
throwToolExit('VM Service URI cannot be provided for screenshot type $screenshotType');
|
|
}
|
|
device = await findTargetDevice();
|
|
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 (vmServiceUrl == null) {
|
|
throwToolExit('VM Service URI must be specified for screenshot type $screenshotType');
|
|
}
|
|
if (vmServiceUrl.isEmpty || Uri.tryParse(vmServiceUrl) == null) {
|
|
throwToolExit('VM Service URI "$vmServiceUrl" is invalid');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<FlutterCommandResult> verifyThenRunCommand(String? commandPath) async {
|
|
await _validateOptions(stringArgDeprecated(_kType), stringArgDeprecated(_kVmServiceUrl));
|
|
return super.verifyThenRunCommand(commandPath);
|
|
}
|
|
|
|
@override
|
|
Future<FlutterCommandResult> runCommand() async {
|
|
File? outputFile;
|
|
if (argResults?.wasParsed(_kOut) ?? false) {
|
|
outputFile = fs.file(stringArgDeprecated(_kOut));
|
|
}
|
|
|
|
bool success = true;
|
|
switch (stringArgDeprecated(_kType)) {
|
|
case _kDeviceType:
|
|
await runScreenshot(outputFile);
|
|
break;
|
|
case _kSkiaType:
|
|
success = await runSkia(outputFile);
|
|
break;
|
|
case _kRasterizerType:
|
|
success = await runRasterizer(outputFile);
|
|
break;
|
|
}
|
|
|
|
return success ? FlutterCommandResult.success()
|
|
: FlutterCommandResult.fail();
|
|
}
|
|
|
|
Future<void> runScreenshot(File? outputFile) async {
|
|
outputFile ??= globals.fsUtils.getUniqueFile(
|
|
fs.currentDirectory,
|
|
'flutter',
|
|
'png',
|
|
);
|
|
|
|
try {
|
|
await device!.takeScreenshot(outputFile);
|
|
} on Exception catch (error) {
|
|
throwToolExit('Error taking screenshot: $error');
|
|
}
|
|
|
|
checkOutput(outputFile, fs);
|
|
|
|
try {
|
|
_showOutputFileInfo(outputFile);
|
|
} on Exception catch (error) {
|
|
throwToolExit(
|
|
'Error with provided file path: "${outputFile.path}"\n'
|
|
'Error: $error'
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<bool> runSkia(File? outputFile) async {
|
|
final Uri vmServiceUrl = Uri.parse(stringArgDeprecated(_kVmServiceUrl)!);
|
|
final FlutterVmService vmService = await connectToVmService(vmServiceUrl, logger: globals.logger);
|
|
final vm_service.Response? skp = await vmService.screenshotSkp();
|
|
if (skp == null) {
|
|
globals.printError(
|
|
'The Skia picture request failed, probably because the device was '
|
|
'disconnected',
|
|
);
|
|
return false;
|
|
}
|
|
outputFile ??= globals.fsUtils.getUniqueFile(
|
|
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);
|
|
return true;
|
|
}
|
|
|
|
Future<bool> runRasterizer(File? outputFile) async {
|
|
final Uri vmServiceUrl = Uri.parse(stringArgDeprecated(_kVmServiceUrl)!);
|
|
final FlutterVmService vmService = await connectToVmService(vmServiceUrl, logger: globals.logger);
|
|
final vm_service.Response? response = await vmService.screenshot();
|
|
if (response == null) {
|
|
globals.printError(
|
|
'The screenshot request failed, probably because the device was '
|
|
'disconnected',
|
|
);
|
|
return false;
|
|
}
|
|
outputFile ??= globals.fsUtils.getUniqueFile(
|
|
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);
|
|
return true;
|
|
}
|
|
|
|
static void checkOutput(File outputFile, FileSystem fs) {
|
|
if (!fs.file(outputFile.path).existsSync()) {
|
|
throwToolExit(
|
|
'File was not created, ensure path is valid\n'
|
|
'Path provided: "${outputFile.path}"'
|
|
);
|
|
}
|
|
}
|
|
|
|
@visibleForTesting
|
|
static 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 output.');
|
|
}
|
|
}
|
|
|
|
void _showOutputFileInfo(File outputFile) {
|
|
final int sizeKB = (outputFile.lengthSync()) ~/ 1024;
|
|
globals.printStatus('Screenshot written to ${fs.path.relative(outputFile.path)} (${sizeKB}kB).');
|
|
}
|
|
}
|