305 lines
9.5 KiB
C++
305 lines
9.5 KiB
C++
|
|
/*
|
||
|
|
* 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 <iostream>
|
||
|
|
#include <ostream>
|
||
|
|
#include <string>
|
||
|
|
#include <unordered_map>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
#include <android-base/logging.h>
|
||
|
|
#include <android-base/strings.h>
|
||
|
|
#include <gflags/gflags.h>
|
||
|
|
|
||
|
|
#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 <command> <args>
|
||
|
|
|
||
|
|
Commands:
|
||
|
|
help Print this message.
|
||
|
|
help <command> 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 <display index>
|
||
|
|
cvd display remove <display index> <display index> ...
|
||
|
|
)";
|
||
|
|
|
||
|
|
static const std::unordered_map<std::string, std::string> kSubCommandUsages = {
|
||
|
|
{"add", kAddUsage},
|
||
|
|
{"list", kListUsage},
|
||
|
|
{"help", kUsage},
|
||
|
|
{"remove", kRemoveUsage},
|
||
|
|
};
|
||
|
|
|
||
|
|
Result<int> RunCrosvmDisplayCommand(const std::vector<std::string>& 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<int> DoHelp(const std::vector<std::string>& 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<std::optional<CuttlefishConfig::DisplayConfig>>
|
||
|
|
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<int> DoAdd(const std::vector<std::string>&) {
|
||
|
|
std::vector<CuttlefishConfig::DisplayConfig> 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<std::string> 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<std::string>{
|
||
|
|
"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<int> DoList(const std::vector<std::string>&) {
|
||
|
|
return CF_EXPECT(RunCrosvmDisplayCommand({"list-displays"}));
|
||
|
|
}
|
||
|
|
|
||
|
|
Result<int> DoRemove(const std::vector<std::string>& 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<std::string> 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<int> (*)(const std::vector<std::string>&);
|
||
|
|
|
||
|
|
int DisplayMain(int argc, char** argv) {
|
||
|
|
::android::base::InitLogging(argv, android::base::StderrLogger);
|
||
|
|
::gflags::SetUsageMessage(kUsage);
|
||
|
|
::gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||
|
|
|
||
|
|
std::vector<std::string> args;
|
||
|
|
for (int i = 1; i < argc; i++) {
|
||
|
|
args.push_back(argv[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (args.empty()) {
|
||
|
|
args.push_back("help");
|
||
|
|
}
|
||
|
|
|
||
|
|
const std::unordered_map<std::string, DisplaySubCommand> 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); }
|