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/strings.h>
25 #include <gflags/gflags.h>
26
27 #include "common/libs/utils/subprocess.h"
28 #include "host/commands/assemble_cvd/display_flags.h"
29 #include "host/commands/assemble_cvd/flags_defaults.h"
30 #include "host/libs/config/cuttlefish_config.h"
31
32 DEFINE_uint32(instance_num, 1, "Which instance to read the configs from");
33 DEFINE_uint32(width, 0,
34 "When adding a display, the width of the display in pixels");
35 DEFINE_uint32(height, 0,
36 "When adding a display, the height of the display in pixels");
37 DEFINE_uint32(dpi, 0,
38 "When adding a display, the pixels per inch of the display");
39 DEFINE_uint32(refresh_rate_hz, 0,
40 "When adding a display, the refresh rate of the display in "
41 "Hertz");
42
43 DEFINE_string(display0, "", cuttlefish::kDisplayHelp);
44 DEFINE_string(display1, "", cuttlefish::kDisplayHelp);
45 DEFINE_string(display2, "", cuttlefish::kDisplayHelp);
46 DEFINE_string(display3, "", cuttlefish::kDisplayHelp);
47
48 namespace cuttlefish {
49 namespace {
50
51 static const std::string kUsage =
52 R"(Cuttlefish Virtual Device (CVD) Display CLI.
53
54 usage: cvd display <command> <args>
55
56 Commands:
57 help Print this message.
58 help <command> Print help for a command.
59 add Adds a new display to a given device.
60 list Prints the currently connected displays.
61 remove Removes a display from a given device.
62 )";
63
64 static const std::string kAddUsage =
65 R"(Cuttlefish Virtual Device (CVD) Display CLI.
66
67 Adds and connects a display to the given virtual device.
68
69 usage: cvd display add --width=720 --height=1280
70
71 cvd display add \\
72 --display0=width=1280,height=800
73 --display1=width=1920,height=1080,refresh_rate_hz=60
74 )";
75
76 static const std::string kListUsage =
77 R"(Cuttlefish Virtual Device (CVD) Display CLI.
78
79 Lists all of the displays currently connected to a given virtual device.
80
81 usage: cvd display list
82 )";
83
84 static const std::string kRemoveUsage =
85 R"(Cuttlefish Virtual Device (CVD) Display CLI.
86
87 Disconnects and removes a display from the given virtual device.
88
89 usage: cvd display remove <display index>
90 cvd display remove <display index> <display index> ...
91 )";
92
93 static const std::unordered_map<std::string, std::string> kSubCommandUsages = {
94 {"add", kAddUsage},
95 {"list", kListUsage},
96 {"help", kUsage},
97 {"remove", kRemoveUsage},
98 };
99
RunCrosvmDisplayCommand(const std::vector<std::string> & args)100 Result<int> RunCrosvmDisplayCommand(const std::vector<std::string>& args) {
101 auto config = cuttlefish::CuttlefishConfig::Get();
102 if (!config) {
103 return CF_ERR("Failed to get Cuttlefish config.");
104 }
105 // TODO(b/260649774): Consistent executable API for selecting an instance
106 auto instance = config->ForInstance(FLAGS_instance_num);
107
108 const std::string crosvm_binary_path = instance.crosvm_binary();
109 const std::string crosvm_control_path =
110 instance.PerInstanceInternalUdsPath("crosvm_control.sock");
111
112 cuttlefish::Command command(crosvm_binary_path);
113 command.AddParameter("gpu");
114 for (const std::string& arg : args) {
115 command.AddParameter(arg);
116 }
117 command.AddParameter(crosvm_control_path);
118
119 std::string out;
120 std::string err;
121 auto ret = RunWithManagedStdio(std::move(command), NULL, &out, &err);
122 if (ret != 0) {
123 std::cerr << "Failed to run crosvm display command: ret code: " << ret
124 << "\n"
125 << out << "\n"
126 << err;
127 return ret;
128 }
129
130 std::cerr << err << std::endl;
131 std::cout << out << std::endl;
132 return 0;
133 }
134
DoHelp(const std::vector<std::string> & args)135 Result<int> DoHelp(const std::vector<std::string>& args) {
136 if (args.empty()) {
137 std::cout << kUsage << std::endl;
138 return 0;
139 }
140
141 const std::string& subcommand_str = args[0];
142 auto subcommand_usage = kSubCommandUsages.find(subcommand_str);
143 if (subcommand_usage == kSubCommandUsages.end()) {
144 std::cerr << "Unknown subcommand '" << subcommand_str
145 << "'. See `cvd display help`" << std::endl;
146 return 1;
147 }
148
149 std::cout << subcommand_usage->second << std::endl;
150 return 0;
151 }
152
153 Result<std::optional<CuttlefishConfig::DisplayConfig>>
ParseLegacyDisplayFlags()154 ParseLegacyDisplayFlags() {
155 if (FLAGS_width == 0 && FLAGS_height == 0 && FLAGS_dpi == 0 &&
156 FLAGS_refresh_rate_hz == 0) {
157 return std::nullopt;
158 }
159
160 CF_EXPECT_GT(FLAGS_width, 0,
161 "Must specify valid --width flag. Usage:\n"
162 << kAddUsage);
163 CF_EXPECT_GT(FLAGS_height, 0,
164 "Must specify valid --height flag. Usage:\n"
165 << kAddUsage);
166 CF_EXPECT_GT(FLAGS_dpi, 0,
167 "Must specify valid --dpi flag. Usage:\n"
168 << kAddUsage);
169 CF_EXPECT_GT(FLAGS_refresh_rate_hz, 0,
170 "Must specify valid --refresh_rate_hz flag. Usage:\n"
171 << kAddUsage);
172
173 const int display_width = FLAGS_width;
174 const int display_height = FLAGS_height;
175 const int display_dpi = FLAGS_dpi > 0 ? FLAGS_dpi : CF_DEFAULTS_DISPLAY_DPI;
176 const int display_rr = FLAGS_refresh_rate_hz > 0
177 ? FLAGS_refresh_rate_hz
178 : CF_DEFAULTS_DISPLAY_REFRESH_RATE;
179
180 return CuttlefishConfig::DisplayConfig{
181 .width = display_width,
182 .height = display_height,
183 .dpi = display_dpi,
184 .refresh_rate_hz = display_rr,
185 };
186 }
187
DoAdd(const std::vector<std::string> &)188 Result<int> DoAdd(const std::vector<std::string>&) {
189 std::vector<CuttlefishConfig::DisplayConfig> display_configs;
190
191 auto display = CF_EXPECT(ParseLegacyDisplayFlags());
192 if (display) {
193 display_configs.push_back(*display);
194 }
195 auto display0 = CF_EXPECT(ParseDisplayConfig(FLAGS_display0));
196 if (display0) {
197 display_configs.push_back(*display0);
198 }
199 auto display1 = CF_EXPECT(ParseDisplayConfig(FLAGS_display1));
200 if (display1) {
201 display_configs.push_back(*display1);
202 }
203 auto display2 = CF_EXPECT(ParseDisplayConfig(FLAGS_display2));
204 if (display2) {
205 display_configs.push_back(*display2);
206 }
207 auto display3 = CF_EXPECT(ParseDisplayConfig(FLAGS_display3));
208 if (display3) {
209 display_configs.push_back(*display3);
210 }
211
212 if (display_configs.empty()) {
213 return CF_ERR("No displays params provided. Usage:\n" << kAddUsage);
214 }
215
216 std::vector<std::string> add_displays_command_args;
217 add_displays_command_args.push_back("add-displays");
218
219 for (const auto& display_config : display_configs) {
220 const std::string w = std::to_string(display_config.width);
221 const std::string h = std::to_string(display_config.height);
222 const std::string dpi = std::to_string(display_config.dpi);
223 const std::string rr = std::to_string(display_config.refresh_rate_hz);
224
225 const std::string add_display_flag =
226 "--gpu-display=" + android::base::Join(
227 std::vector<std::string>{
228 "mode=windowed[" + w + "," + h + "]",
229 "dpi=[" + dpi + "," + dpi + "]",
230 "refresh-rate=" + rr,
231 },
232 ",");
233
234 add_displays_command_args.push_back(add_display_flag);
235 }
236
237 return CF_EXPECT(RunCrosvmDisplayCommand(add_displays_command_args));
238 }
239
DoList(const std::vector<std::string> &)240 Result<int> DoList(const std::vector<std::string>&) {
241 return CF_EXPECT(RunCrosvmDisplayCommand({"list-displays"}));
242 }
243
DoRemove(const std::vector<std::string> & args)244 Result<int> DoRemove(const std::vector<std::string>& args) {
245 if (args.empty()) {
246 std::cerr << "Must specify the display id to remove. Usage:" << std::endl;
247 std::cerr << kRemoveUsage << std::endl;
248 return 1;
249 }
250
251 std::vector<std::string> remove_displays_command_args;
252 remove_displays_command_args.push_back("remove-displays");
253 for (const auto& arg : args) {
254 remove_displays_command_args.push_back("--display-id=" + arg);
255 }
256
257 return CF_EXPECT(RunCrosvmDisplayCommand(remove_displays_command_args));
258 }
259
260 using DisplaySubCommand = Result<int> (*)(const std::vector<std::string>&);
261
DisplayMain(int argc,char ** argv)262 int DisplayMain(int argc, char** argv) {
263 ::android::base::InitLogging(argv, android::base::StderrLogger);
264 ::gflags::SetUsageMessage(kUsage);
265 ::gflags::ParseCommandLineFlags(&argc, &argv, true);
266
267 std::vector<std::string> args;
268 for (int i = 1; i < argc; i++) {
269 args.push_back(argv[i]);
270 }
271
272 if (args.empty()) {
273 args.push_back("help");
274 }
275
276 const std::unordered_map<std::string, DisplaySubCommand> kSubCommands = {
277 {"add", DoAdd},
278 {"list", DoList},
279 {"help", DoHelp},
280 {"remove", DoRemove},
281 };
282
283 const auto command_str = args[0];
284 args.erase(args.begin());
285
286 auto command_func_it = kSubCommands.find(command_str);
287 if (command_func_it == kSubCommands.end()) {
288 std::cerr << "Unknown display command: '" << command_str << "'."
289 << std::endl;
290 return 1;
291 }
292
293 auto result = command_func_it->second(args);
294 if (!result.ok()) {
295 std::cerr << result.error().Message();
296 return 1;
297 }
298 return result.value();
299 }
300
301 } // namespace
302 } // namespace cuttlefish
303
main(int argc,char ** argv)304 int main(int argc, char** argv) { return cuttlefish::DisplayMain(argc, argv); }
305