/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include "common/libs/utils/subprocess.h" #include "host/commands/assemble_cvd/display_flags.h" #include "host/commands/assemble_cvd/flags_defaults.h" #include "host/libs/config/cuttlefish_config.h" DEFINE_uint32(instance_num, 1, "Which instance to read the configs from"); DEFINE_uint32(width, 0, "When adding a display, the width of the display in pixels"); DEFINE_uint32(height, 0, "When adding a display, the height of the display in pixels"); DEFINE_uint32(dpi, 0, "When adding a display, the pixels per inch of the display"); DEFINE_uint32(refresh_rate_hz, 0, "When adding a display, the refresh rate of the display in " "Hertz"); DEFINE_string(display0, "", cuttlefish::kDisplayHelp); DEFINE_string(display1, "", cuttlefish::kDisplayHelp); DEFINE_string(display2, "", cuttlefish::kDisplayHelp); DEFINE_string(display3, "", cuttlefish::kDisplayHelp); namespace cuttlefish { namespace { static const std::string kUsage = R"(Cuttlefish Virtual Device (CVD) Display CLI. usage: cvd display Commands: help Print this message. help Print help for a command. add Adds a new display to a given device. list Prints the currently connected displays. remove Removes a display from a given device. )"; static const std::string kAddUsage = R"(Cuttlefish Virtual Device (CVD) Display CLI. Adds and connects a display to the given virtual device. usage: cvd display add --width=720 --height=1280 cvd display add \\ --display0=width=1280,height=800 --display1=width=1920,height=1080,refresh_rate_hz=60 )"; static const std::string kListUsage = R"(Cuttlefish Virtual Device (CVD) Display CLI. Lists all of the displays currently connected to a given virtual device. usage: cvd display list )"; static const std::string kRemoveUsage = R"(Cuttlefish Virtual Device (CVD) Display CLI. Disconnects and removes a display from the given virtual device. usage: cvd display remove cvd display remove ... )"; static const std::unordered_map kSubCommandUsages = { {"add", kAddUsage}, {"list", kListUsage}, {"help", kUsage}, {"remove", kRemoveUsage}, }; Result RunCrosvmDisplayCommand(const std::vector& args) { auto config = cuttlefish::CuttlefishConfig::Get(); if (!config) { return CF_ERR("Failed to get Cuttlefish config."); } // TODO(b/260649774): Consistent executable API for selecting an instance auto instance = config->ForInstance(FLAGS_instance_num); const std::string crosvm_binary_path = instance.crosvm_binary(); const std::string crosvm_control_path = instance.PerInstanceInternalUdsPath("crosvm_control.sock"); cuttlefish::Command command(crosvm_binary_path); command.AddParameter("gpu"); for (const std::string& arg : args) { command.AddParameter(arg); } command.AddParameter(crosvm_control_path); std::string out; std::string err; auto ret = RunWithManagedStdio(std::move(command), NULL, &out, &err); if (ret != 0) { std::cerr << "Failed to run crosvm display command: ret code: " << ret << "\n" << out << "\n" << err; return ret; } std::cerr << err << std::endl; std::cout << out << std::endl; return 0; } Result DoHelp(const std::vector& args) { if (args.empty()) { std::cout << kUsage << std::endl; return 0; } const std::string& subcommand_str = args[0]; auto subcommand_usage = kSubCommandUsages.find(subcommand_str); if (subcommand_usage == kSubCommandUsages.end()) { std::cerr << "Unknown subcommand '" << subcommand_str << "'. See `cvd display help`" << std::endl; return 1; } std::cout << subcommand_usage->second << std::endl; return 0; } Result> ParseLegacyDisplayFlags() { if (FLAGS_width == 0 && FLAGS_height == 0 && FLAGS_dpi == 0 && FLAGS_refresh_rate_hz == 0) { return std::nullopt; } CF_EXPECT_GT(FLAGS_width, 0, "Must specify valid --width flag. Usage:\n" << kAddUsage); CF_EXPECT_GT(FLAGS_height, 0, "Must specify valid --height flag. Usage:\n" << kAddUsage); CF_EXPECT_GT(FLAGS_dpi, 0, "Must specify valid --dpi flag. Usage:\n" << kAddUsage); CF_EXPECT_GT(FLAGS_refresh_rate_hz, 0, "Must specify valid --refresh_rate_hz flag. Usage:\n" << kAddUsage); const int display_width = FLAGS_width; const int display_height = FLAGS_height; const int display_dpi = FLAGS_dpi > 0 ? FLAGS_dpi : CF_DEFAULTS_DISPLAY_DPI; const int display_rr = FLAGS_refresh_rate_hz > 0 ? FLAGS_refresh_rate_hz : CF_DEFAULTS_DISPLAY_REFRESH_RATE; return CuttlefishConfig::DisplayConfig{ .width = display_width, .height = display_height, .dpi = display_dpi, .refresh_rate_hz = display_rr, }; } Result DoAdd(const std::vector&) { std::vector display_configs; auto display = CF_EXPECT(ParseLegacyDisplayFlags()); if (display) { display_configs.push_back(*display); } auto display0 = CF_EXPECT(ParseDisplayConfig(FLAGS_display0)); if (display0) { display_configs.push_back(*display0); } auto display1 = CF_EXPECT(ParseDisplayConfig(FLAGS_display1)); if (display1) { display_configs.push_back(*display1); } auto display2 = CF_EXPECT(ParseDisplayConfig(FLAGS_display2)); if (display2) { display_configs.push_back(*display2); } auto display3 = CF_EXPECT(ParseDisplayConfig(FLAGS_display3)); if (display3) { display_configs.push_back(*display3); } if (display_configs.empty()) { return CF_ERR("No displays params provided. Usage:\n" << kAddUsage); } std::vector add_displays_command_args; add_displays_command_args.push_back("add-displays"); for (const auto& display_config : display_configs) { const std::string w = std::to_string(display_config.width); const std::string h = std::to_string(display_config.height); const std::string dpi = std::to_string(display_config.dpi); const std::string rr = std::to_string(display_config.refresh_rate_hz); const std::string add_display_flag = "--gpu-display=" + android::base::Join( std::vector{ "mode=windowed[" + w + "," + h + "]", "dpi=[" + dpi + "," + dpi + "]", "refresh-rate=" + rr, }, ","); add_displays_command_args.push_back(add_display_flag); } return CF_EXPECT(RunCrosvmDisplayCommand(add_displays_command_args)); } Result DoList(const std::vector&) { return CF_EXPECT(RunCrosvmDisplayCommand({"list-displays"})); } Result DoRemove(const std::vector& args) { if (args.empty()) { std::cerr << "Must specify the display id to remove. Usage:" << std::endl; std::cerr << kRemoveUsage << std::endl; return 1; } std::vector remove_displays_command_args; remove_displays_command_args.push_back("remove-displays"); for (const auto& arg : args) { remove_displays_command_args.push_back("--display-id=" + arg); } return CF_EXPECT(RunCrosvmDisplayCommand(remove_displays_command_args)); } using DisplaySubCommand = Result (*)(const std::vector&); int DisplayMain(int argc, char** argv) { ::android::base::InitLogging(argv, android::base::StderrLogger); ::gflags::SetUsageMessage(kUsage); ::gflags::ParseCommandLineFlags(&argc, &argv, true); std::vector args; for (int i = 1; i < argc; i++) { args.push_back(argv[i]); } if (args.empty()) { args.push_back("help"); } const std::unordered_map kSubCommands = { {"add", DoAdd}, {"list", DoList}, {"help", DoHelp}, {"remove", DoRemove}, }; const auto command_str = args[0]; args.erase(args.begin()); auto command_func_it = kSubCommands.find(command_str); if (command_func_it == kSubCommands.end()) { std::cerr << "Unknown display command: '" << command_str << "'." << std::endl; return 1; } auto result = command_func_it->second(args); if (!result.ok()) { std::cerr << result.error().Message(); return 1; } return result.value(); } } // namespace } // namespace cuttlefish int main(int argc, char** argv) { return cuttlefish::DisplayMain(argc, argv); }