1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <iostream>
18 #include <ostream>
19 #include <string>
20 #include <unordered_map>
21 #include <vector>
22 
23 #include <android-base/logging.h>
24 #include <android-base/no_destructor.h>
25 #include <android-base/parseint.h>
26 #include <android-base/strings.h>
27 
28 #include "common/libs/utils/flag_parser.h"
29 #include "common/libs/utils/subprocess.h"
30 #include "device/google/cuttlefish/host/libs/command_util/runner/run_cvd.pb.h"
31 #include "host/commands/assemble_cvd/flags_defaults.h"
32 #include "host/libs/command_util/util.h"
33 #include "host/libs/config/cuttlefish_config.h"
34 #include "host/libs/config/display.h"
35 
36 namespace cuttlefish {
37 namespace {
38 
39 static const char kAddUsage[] =
40     R"(
41 
42 Adds and connects a display to the given virtual device.
43 
44 usage: cvd display add \\
45         --display=width=1280,height=800 \\
46         --display=width=1920,height=1080,refresh_rate_hz=60
47 )";
48 
49 static const char kListUsage[] =
50     R"(
51 
52 Lists all of the displays currently connected to a given virtual device.
53 
54 usage: cvd display list
55 )";
56 
57 static const char kRemoveUsage[] =
58     R"(
59 
60 Disconnects and removes displays from the given virtual device.
61 
62 usage: cvd display remove \\
63         --display=<display id> \\
64         --display=<display id> ...
65 )";
66 
67 static const char kScreenshotUsage[] =
68     R"(
69 Screenshots the contents of a given display.
70 
71 Currently supported output formats: jpg, png, webp.
72 
73 usage: cvd display screenshot <display id> <screenshot path>
74 )";
75 
RunCrosvmDisplayCommand(int instance_num,const std::vector<std::string> & args)76 Result<int> RunCrosvmDisplayCommand(int instance_num,
77                                     const std::vector<std::string>& args) {
78   auto config = cuttlefish::CuttlefishConfig::Get();
79   if (!config) {
80     return CF_ERR("Failed to get Cuttlefish config.");
81   }
82   // TODO(b/260649774): Consistent executable API for selecting an instance
83   auto instance = config->ForInstance(instance_num);
84 
85   const std::string crosvm_binary_path = instance.crosvm_binary();
86   const std::string crosvm_control_path = instance.CrosvmSocketPath();
87 
88   cuttlefish::Command command(crosvm_binary_path);
89   command.AddParameter("gpu");
90   for (const std::string& arg : args) {
91     command.AddParameter(arg);
92   }
93   command.AddParameter(crosvm_control_path);
94 
95   std::string out;
96   std::string err;
97   auto ret = RunWithManagedStdio(std::move(command), NULL, &out, &err);
98   if (ret != 0) {
99     std::cerr << "Failed to run crosvm display command: ret code: " << ret
100               << "\n"
101               << out << "\n"
102               << err;
103     return ret;
104   }
105 
106   std::cerr << err << std::endl;
107   std::cout << out << std::endl;
108   return 0;
109 }
110 
GetInstanceNum(std::vector<std::string> & args)111 Result<int> GetInstanceNum(std::vector<std::string>& args) {
112   int instance_num = 1;
113   CF_EXPECT(
114       ConsumeFlags({GflagsCompatFlag("instance_num", instance_num)}, args));
115   return instance_num;
116 }
117 
DoHelp(std::vector<std::string> & args)118 Result<int> DoHelp(std::vector<std::string>& args) {
119   static const android::base::NoDestructor<
120       std::unordered_map<std::string, std::string>>
121       kSubCommandUsages({
122           {"add", kAddUsage},
123           {"list", kListUsage},
124           {"remove", kRemoveUsage},
125           {"screenshot", kScreenshotUsage},
126       });
127 
128   const std::string& subcommand_str = args[0];
129   auto subcommand_usage = kSubCommandUsages->find(subcommand_str);
130   if (subcommand_usage == kSubCommandUsages->end()) {
131     std::cerr << "Unknown subcommand '" << subcommand_str
132               << "'. See `cvd display help`" << std::endl;
133     return 1;
134   }
135 
136   std::cout << subcommand_usage->second << std::endl;
137   return 0;
138 }
139 
DoAdd(std::vector<std::string> & args)140 Result<int> DoAdd(std::vector<std::string>& args) {
141   const int instance_num = CF_EXPECT(GetInstanceNum(args));
142 
143   const auto display_configs = CF_EXPECT(ParseDisplayConfigsFromArgs(args));
144   if (display_configs.empty()) {
145     std::cerr << "Must provide at least 1 display to add. Usage:" << std::endl;
146     std::cerr << kAddUsage << std::endl;
147     return 1;
148   }
149 
150   std::vector<std::string> add_displays_command_args;
151   add_displays_command_args.push_back("add-displays");
152 
153   for (const auto& display_config : display_configs) {
154     const std::string w = std::to_string(display_config.width);
155     const std::string h = std::to_string(display_config.height);
156     const std::string dpi = std::to_string(display_config.dpi);
157     const std::string rr = std::to_string(display_config.refresh_rate_hz);
158 
159     const std::string add_display_flag =
160         "--gpu-display=" + android::base::Join(
161                                std::vector<std::string>{
162                                    "mode=windowed[" + w + "," + h + "]",
163                                    "dpi=[" + dpi + "," + dpi + "]",
164                                    "refresh-rate=" + rr,
165                                },
166                                ",");
167 
168     add_displays_command_args.push_back(add_display_flag);
169   }
170 
171   return CF_EXPECT(
172       RunCrosvmDisplayCommand(instance_num, add_displays_command_args));
173 }
174 
DoList(std::vector<std::string> & args)175 Result<int> DoList(std::vector<std::string>& args) {
176   const int instance_num = CF_EXPECT(GetInstanceNum(args));
177   return CF_EXPECT(RunCrosvmDisplayCommand(instance_num, {"list-displays"}));
178 }
179 
DoRemove(std::vector<std::string> & args)180 Result<int> DoRemove(std::vector<std::string>& args) {
181   const int instance_num = CF_EXPECT(GetInstanceNum(args));
182 
183   std::vector<std::string> displays;
184   const std::vector<Flag> remove_displays_flags = {
185       GflagsCompatFlag(kDisplayFlag)
186           .Help("Display id of a display to remove.")
187           .Setter([&](const FlagMatch& match) -> Result<void> {
188             displays.push_back(match.value);
189             return {};
190           }),
191   };
192   auto parse_res = ConsumeFlags(remove_displays_flags, args);
193   if (!parse_res.ok()) {
194     std::cerr << parse_res.error().FormatForEnv() << std::endl;
195     std::cerr << "Failed to parse flags. Usage:" << std::endl;
196     std::cerr << kRemoveUsage << std::endl;
197     return 1;
198   }
199 
200   if (displays.empty()) {
201     std::cerr << "Must specify at least one display id to remove. Usage:"
202               << std::endl;
203     std::cerr << kRemoveUsage << std::endl;
204     return 1;
205   }
206 
207   std::vector<std::string> remove_displays_command_args;
208   remove_displays_command_args.push_back("remove-displays");
209   for (const auto& display : displays) {
210     remove_displays_command_args.push_back("--display-id=" + display);
211   }
212 
213   return CF_EXPECT(
214       RunCrosvmDisplayCommand(instance_num, remove_displays_command_args));
215 }
216 
DoScreenshot(std::vector<std::string> & args)217 Result<int> DoScreenshot(std::vector<std::string>& args) {
218   const int instance_num = CF_EXPECT(GetInstanceNum(args));
219 
220   auto config = cuttlefish::CuttlefishConfig::Get();
221   if (!config) {
222     return CF_ERR("Failed to get Cuttlefish config.");
223   }
224 
225   int display_number = 0;
226   std::string screenshot_path;
227 
228   std::vector<std::string> displays;
229   const std::vector<Flag> screenshot_flags = {
230       GflagsCompatFlag(kDisplayFlag, display_number)
231           .Help("Display id of a display to screenshot."),
232       GflagsCompatFlag("screenshot_path", screenshot_path)
233           .Help("Path for the resulting screenshot file."),
234   };
235   auto parse_res = ConsumeFlags(screenshot_flags, args);
236   if (!parse_res.ok()) {
237     std::cerr << parse_res.error().FormatForEnv() << std::endl;
238     std::cerr << "Failed to parse flags. Usage:" << std::endl;
239     std::cerr << kScreenshotUsage << std::endl;
240     return 1;
241   }
242   CF_EXPECT(!screenshot_path.empty(),
243             "Must provide --screenshot_path. Usage:" << kScreenshotUsage);
244 
245   run_cvd::ExtendedLauncherAction extended_action;
246   extended_action.mutable_screenshot_display()->set_display_number(
247       display_number);
248   extended_action.mutable_screenshot_display()->set_screenshot_path(
249       screenshot_path);
250 
251   std::cout << "Requesting to save screenshot for display " << display_number
252             << " to " << screenshot_path << "." << std::endl;
253 
254   auto socket = CF_EXPECT(
255       GetLauncherMonitor(*config, instance_num, /*timeout_seconds=*/5));
256   CF_EXPECT(RunLauncherAction(socket, extended_action, std::nullopt),
257             "Failed to get success response from launcher.");
258   return 0;
259 }
260 
261 using DisplaySubCommand = Result<int> (*)(std::vector<std::string>&);
262 
DisplayMain(int argc,char ** argv)263 int DisplayMain(int argc, char** argv) {
264   ::android::base::InitLogging(argv, android::base::StderrLogger);
265 
266   const std::unordered_map<std::string, DisplaySubCommand> kSubCommands = {
267       {"add", DoAdd},                //
268       {"list", DoList},              //
269       {"help", DoHelp},              //
270       {"remove", DoRemove},          //
271       {"screenshot", DoScreenshot},  //
272   };
273 
274   auto args = ArgsToVec(argc - 1, argv + 1);
275   if (args.empty()) {
276     args.push_back("help");
277   }
278 
279   const std::string command_str = args[0];
280   args.erase(args.begin());
281 
282   auto command_func_it = kSubCommands.find(command_str);
283   if (command_func_it == kSubCommands.end()) {
284     std::cerr << "Unknown display command: '" << command_str << "'."
285               << std::endl;
286     return 1;
287   }
288 
289   auto result = command_func_it->second(args);
290   if (!result.ok()) {
291     std::cerr << result.error().FormatForEnv();
292     return 1;
293   }
294   return result.value();
295 }
296 
297 }  // namespace
298 }  // namespace cuttlefish
299 
main(int argc,char ** argv)300 int main(int argc, char** argv) { return cuttlefish::DisplayMain(argc, argv); }
301