1// Copyright 2018 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'package:meta/meta.dart'; 6 7import '../application_package.dart'; 8import '../base/io.dart'; 9import '../base/os.dart'; 10import '../base/platform.dart'; 11import '../base/process_manager.dart'; 12import '../build_info.dart'; 13import '../desktop.dart'; 14import '../device.dart'; 15import '../globals.dart'; 16import '../project.dart'; 17import '../protocol_discovery.dart'; 18import 'application_package.dart'; 19import 'build_windows.dart'; 20import 'windows_workflow.dart'; 21 22/// A device that represents a desktop Windows target. 23class WindowsDevice extends Device { 24 WindowsDevice() : super( 25 'Windows', 26 category: Category.desktop, 27 platformType: PlatformType.windows, 28 ephemeral: false, 29 ); 30 31 @override 32 void clearLogs() { } 33 34 @override 35 DeviceLogReader getLogReader({ ApplicationPackage app }) { 36 return _logReader; 37 } 38 final DesktopLogReader _logReader = DesktopLogReader(); 39 40 // Since the host and target devices are the same, no work needs to be done 41 // to install the application. 42 @override 43 Future<bool> installApp(ApplicationPackage app) async => true; 44 45 // Since the host and target devices are the same, no work needs to be done 46 // to install the application. 47 @override 48 Future<bool> isAppInstalled(ApplicationPackage app) async => true; 49 50 // Since the host and target devices are the same, no work needs to be done 51 // to install the application. 52 @override 53 Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true; 54 55 @override 56 Future<bool> get isLocalEmulator async => false; 57 58 @override 59 Future<String> get emulatorId async => null; 60 61 @override 62 bool isSupported() => true; 63 64 @override 65 String get name => 'Windows'; 66 67 @override 68 DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder(); 69 70 @override 71 Future<String> get sdkNameAndVersion async => os.name; 72 73 @override 74 Future<LaunchResult> startApp( 75 covariant WindowsApp package, { 76 String mainPath, 77 String route, 78 DebuggingOptions debuggingOptions, 79 Map<String, dynamic> platformArgs, 80 bool prebuiltApplication = false, 81 bool usesTerminalUi = true, 82 bool ipv6 = false, 83 }) async { 84 if (!prebuiltApplication) { 85 await buildWindows( 86 FlutterProject.current().windows, 87 debuggingOptions.buildInfo, 88 target: mainPath, 89 ); 90 } 91 await stopApp(package); 92 final Process process = await processManager.start(<String>[ 93 package.executable(debuggingOptions?.buildInfo?.mode) 94 ]); 95 if (debuggingOptions?.buildInfo?.isRelease == true) { 96 return LaunchResult.succeeded(); 97 } 98 _logReader.initializeProcess(process); 99 final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(_logReader); 100 try { 101 final Uri observatoryUri = await observatoryDiscovery.uri; 102 return LaunchResult.succeeded(observatoryUri: observatoryUri); 103 } catch (error) { 104 printError('Error waiting for a debug connection: $error'); 105 return LaunchResult.failed(); 106 } finally { 107 await observatoryDiscovery.cancel(); 108 } 109 } 110 111 @override 112 Future<bool> stopApp(covariant WindowsApp app) async { 113 // Assume debug for now. 114 final List<String> process = runningProcess(app.executable(BuildMode.debug)); 115 if (process == null) { 116 return false; 117 } 118 final ProcessResult result = await processManager.run(<String>['Taskkill', '/PID', process.first, '/F']); 119 return result.exitCode == 0; 120 } 121 122 @override 123 Future<TargetPlatform> get targetPlatform async => TargetPlatform.windows_x64; 124 125 // Since the host and target devices are the same, no work needs to be done 126 // to uninstall the application. 127 @override 128 Future<bool> uninstallApp(ApplicationPackage app) async => true; 129 130 @override 131 bool isSupportedForProject(FlutterProject flutterProject) { 132 return flutterProject.windows.existsSync(); 133 } 134} 135 136class WindowsDevices extends PollingDeviceDiscovery { 137 WindowsDevices() : super('windows devices'); 138 139 @override 140 bool get supportsPlatform => platform.isWindows; 141 142 @override 143 bool get canListAnything => windowsWorkflow.canListDevices; 144 145 @override 146 Future<List<Device>> pollingGetDevices() async { 147 if (!canListAnything) { 148 return const <Device>[]; 149 } 150 return <Device>[ 151 WindowsDevice(), 152 ]; 153 } 154 155 @override 156 Future<List<String>> getDiagnostics() async => const <String>[]; 157} 158 159final RegExp _whitespace = RegExp(r'\s+'); 160 161/// Returns the running process matching `process` name. 162/// 163/// This list contains the process name and id. 164@visibleForTesting 165List<String> runningProcess(String processName) { 166 // TODO(jonahwilliams): find a way to do this without powershell. 167 final ProcessResult result = processManager.runSync(<String>['powershell', '-script="Get-CimInstance Win32_Process"']); 168 if (result.exitCode != 0) { 169 return null; 170 } 171 for (String rawProcess in result.stdout.split('\n')) { 172 final String process = rawProcess.trim(); 173 if (!process.contains(processName)) { 174 continue; 175 } 176 final List<String> parts = process.split(_whitespace); 177 178 final String processPid = parts[0]; 179 final String currentRunningProcessPid = pid.toString(); 180 // Don't kill the flutter tool process 181 if (processPid == currentRunningProcessPid) { 182 continue; 183 } 184 final List<String> data = <String>[ 185 processPid, // ID 186 parts[1], // Name 187 ]; 188 return data; 189 } 190 return null; 191} 192