blob: bb5d5189f05ab4f5793e274ef3a6a6a4f5d826d6 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crostini/crostini_terminal.h"
#include "base/containers/fixed_flat_map.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/window_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "net/base/escape.h"
#include "ui/base/base_window.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/geometry/point.h"
namespace crostini {
namespace {
constexpr char kSettingPrefix[] = "/hterm/profiles/default/";
const size_t kSettingPrefixSize = base::size(kSettingPrefix) - 1;
constexpr char kSettingBackgroundColor[] =
"/hterm/profiles/default/background-color";
constexpr char kDefaultBackgroundColor[] = "#202124";
constexpr char kSettingPassCtrlW[] = "/hterm/profiles/default/pass-ctrl-w";
constexpr bool kDefaultPassCtrlW = false;
GURL GenerateVshInCroshUrl(Profile* profile,
const ContainerId& container_id,
const std::string& cwd,
const std::vector<std::string>& terminal_args) {
std::string vsh_crosh = base::StrCat({chrome::kChromeUIUntrustedTerminalURL,
"html/terminal.html?command=vmshell"});
std::string vm_name_param = net::EscapeQueryParamValue(
base::StringPrintf("--vm_name=%s", container_id.vm_name.c_str()),
/*use_plus=*/true);
std::string container_name_param = net::EscapeQueryParamValue(
base::StringPrintf("--target_container=%s",
container_id.container_name.c_str()),
/*use_plus=*/true);
std::string owner_id_param = net::EscapeQueryParamValue(
base::StringPrintf("--owner_id=%s",
CryptohomeIdForProfile(profile).c_str()),
/*use_plus=*/true);
std::vector<std::string> pieces = {vsh_crosh, vm_name_param,
container_name_param, owner_id_param};
if (!cwd.empty()) {
pieces.push_back(net::EscapeQueryParamValue(
base::StringPrintf("--cwd=%s", cwd.c_str()), /*use_plus=*/true));
}
if (!terminal_args.empty()) {
// Separates the command args from the args we are passing into the
// terminal to be executed.
pieces.push_back("--");
for (auto arg : terminal_args) {
pieces.push_back(net::EscapeQueryParamValue(arg, /*use_plus=*/true));
}
}
return GURL(base::JoinString(pieces, "&args[]="));
}
} // namespace
void LaunchTerminal(Profile* profile,
int64_t display_id,
const ContainerId& container_id,
const std::string& cwd,
const std::vector<std::string>& terminal_args) {
crostini::RecordAppLaunchHistogram(
crostini::CrostiniAppLaunchAppType::kTerminal);
GURL vsh_in_crosh_url =
GenerateVshInCroshUrl(profile, container_id, cwd, terminal_args);
auto params = web_app::CreateSystemWebAppLaunchParams(
profile, web_app::SystemAppType::TERMINAL, display_id);
if (!params.has_value()) {
LOG(WARNING) << "Empty launch params for terminal";
return;
}
// Do not track Crostini apps or terminal in session restore. Apps will fail
// since VMs are not restarted on restore, and we don't want terminal to
// force the VM to start.
params->omit_from_session_restore = true;
// This LaunchSystemWebAppImpl call is necessary. Terminal App uses its own
// CrostiniApps publisher for launching. Calling LaunchSystemWebAppAsync
// would ask AppService to launch the App, which routes the launch request to
// this function, resulting in a loop.
//
// System Web Apps managed by Web App publisher should call
// LaunchSystemWebAppAsync.
web_app::LaunchSystemWebAppImpl(profile, web_app::SystemAppType::TERMINAL,
vsh_in_crosh_url, *params);
}
void LaunchTerminalSettings(Profile* profile, int64_t display_id) {
auto params = web_app::CreateSystemWebAppLaunchParams(
profile, web_app::SystemAppType::TERMINAL, display_id);
if (!params.has_value()) {
LOG(WARNING) << "Empty launch params for terminal";
return;
}
std::string path = "html/terminal_settings.html";
// Use an app pop window to host the settings page.
params->disposition = WindowOpenDisposition::NEW_POPUP;
// This LaunchSystemWebAppImpl call is necessary. Terminal App uses its own
// CrostiniApps publisher for launching. Calling LaunchSystemWebAppAsync
// would ask AppService to launch the App, which routes the launch request to
// this function, resulting in a loop.
//
// System Web Apps managed by Web App publisher should call
// LaunchSystemWebAppAsync.
web_app::LaunchSystemWebAppImpl(
profile, web_app::SystemAppType::TERMINAL,
GURL(base::StrCat({chrome::kChromeUIUntrustedTerminalURL, path})),
*params);
}
void RecordTerminalSettingsChangesUMAs(Profile* profile) {
static constexpr auto kSettingsMap = base::MakeFixedFlatMap<base::StringPiece,
TerminalSetting>({
{"alt-gr-mode", TerminalSetting::kAltGrMode},
{"alt-backspace-is-meta-backspace",
TerminalSetting::kAltBackspaceIsMetaBackspace},
{"alt-is-meta", TerminalSetting::kAltIsMeta},
{"alt-sends-what", TerminalSetting::kAltSendsWhat},
{"audible-bell-sound", TerminalSetting::kAudibleBellSound},
{"desktop-notification-bell", TerminalSetting::kDesktopNotificationBell},
{"background-color", TerminalSetting::kBackgroundColor},
{"background-image", TerminalSetting::kBackgroundImage},
{"background-size", TerminalSetting::kBackgroundSize},
{"background-position", TerminalSetting::kBackgroundPosition},
{"backspace-sends-backspace", TerminalSetting::kBackspaceSendsBackspace},
{"character-map-overrides", TerminalSetting::kCharacterMapOverrides},
{"close-on-exit", TerminalSetting::kCloseOnExit},
{"cursor-blink", TerminalSetting::kCursorBlink},
{"cursor-blink-cycle", TerminalSetting::kCursorBlinkCycle},
{"cursor-shape", TerminalSetting::kCursorShape},
{"cursor-color", TerminalSetting::kCursorColor},
{"color-palette-overrides", TerminalSetting::kColorPaletteOverrides},
{"copy-on-select", TerminalSetting::kCopyOnSelect},
{"use-default-window-copy", TerminalSetting::kUseDefaultWindowCopy},
{"clear-selection-after-copy", TerminalSetting::kClearSelectionAfterCopy},
{"ctrl-plus-minus-zero-zoom", TerminalSetting::kCtrlPlusMinusZeroZoom},
{"ctrl-c-copy", TerminalSetting::kCtrlCCopy},
{"ctrl-v-paste", TerminalSetting::kCtrlVPaste},
{"east-asian-ambiguous-as-two-column",
TerminalSetting::kEastAsianAmbiguousAsTwoColumn},
{"enable-8-bit-control", TerminalSetting::kEnable8BitControl},
{"enable-bold", TerminalSetting::kEnableBold},
{"enable-bold-as-bright", TerminalSetting::kEnableBoldAsBright},
{"enable-blink", TerminalSetting::kEnableBlink},
{"enable-clipboard-notice", TerminalSetting::kEnableClipboardNotice},
{"enable-clipboard-write", TerminalSetting::kEnableClipboardWrite},
{"enable-dec12", TerminalSetting::kEnableDec12},
{"enable-csi-j-3", TerminalSetting::kEnableCsiJ3},
{"environment", TerminalSetting::kEnvironment},
{"font-family", TerminalSetting::kFontFamily},
{"font-size", TerminalSetting::kFontSize},
{"font-smoothing", TerminalSetting::kFontSmoothing},
{"foreground-color", TerminalSetting::kForegroundColor},
{"enable-resize-status", TerminalSetting::kEnableResizeStatus},
{"hide-mouse-while-typing", TerminalSetting::kHideMouseWhileTyping},
{"home-keys-scroll", TerminalSetting::kHomeKeysScroll},
{"keybindings", TerminalSetting::kKeybindings},
{"media-keys-are-fkeys", TerminalSetting::kMediaKeysAreFkeys},
{"meta-sends-escape", TerminalSetting::kMetaSendsEscape},
{"mouse-right-click-paste", TerminalSetting::kMouseRightClickPaste},
{"mouse-paste-button", TerminalSetting::kMousePasteButton},
{"word-break-match-left", TerminalSetting::kWordBreakMatchLeft},
{"word-break-match-right", TerminalSetting::kWordBreakMatchRight},
{"word-break-match-middle", TerminalSetting::kWordBreakMatchMiddle},
{"page-keys-scroll", TerminalSetting::kPageKeysScroll},
{"pass-alt-number", TerminalSetting::kPassAltNumber},
{"pass-ctrl-number", TerminalSetting::kPassCtrlNumber},
{"pass-ctrl-n", TerminalSetting::kPassCtrlN},
{"pass-ctrl-t", TerminalSetting::kPassCtrlT},
{"pass-ctrl-tab", TerminalSetting::kPassCtrlTab},
{"pass-ctrl-w", TerminalSetting::kPassCtrlW},
{"pass-meta-number", TerminalSetting::kPassMetaNumber},
{"pass-meta-v", TerminalSetting::kPassMetaV},
{"paste-on-drop", TerminalSetting::kPasteOnDrop},
{"receive-encoding", TerminalSetting::kReceiveEncoding},
{"scroll-on-keystroke", TerminalSetting::kScrollOnKeystroke},
{"scroll-on-output", TerminalSetting::kScrollOnOutput},
{"scrollbar-visible", TerminalSetting::kScrollbarVisible},
{"scroll-wheel-may-send-arrow-keys",
TerminalSetting::kScrollWheelMaySendArrowKeys},
{"scroll-wheel-move-multiplier",
TerminalSetting::kScrollWheelMoveMultiplier},
{"terminal-encoding", TerminalSetting::kTerminalEncoding},
{"shift-insert-paste", TerminalSetting::kShiftInsertPaste},
{"user-css", TerminalSetting::kUserCss},
{"user-css-text", TerminalSetting::kUserCssText},
{"allow-images-inline", TerminalSetting::kAllowImagesInline},
{"theme", TerminalSetting::kTheme},
{"theme-variations", TerminalSetting::kThemeVariations},
});
const base::DictionaryValue* settings = profile->GetPrefs()->GetDictionary(
crostini::prefs::kCrostiniTerminalSettings);
for (const auto& item : settings->DictItems()) {
// Only record settings for /hterm/profiles/default/.
if (!base::StartsWith(item.first, kSettingPrefix,
base::CompareCase::SENSITIVE)) {
continue;
}
const auto* it = kSettingsMap.find(
base::StringPiece(item.first).substr(kSettingPrefixSize));
base::UmaHistogramEnumeration(
"Crostini.TerminalSettingsChanged",
it != kSettingsMap.end() ? it->second : TerminalSetting::kUnknown);
}
}
std::string GetTerminalSettingBackgroundColor(Profile* profile) {
const base::DictionaryValue* value = profile->GetPrefs()->GetDictionary(
crostini::prefs::kCrostiniTerminalSettings);
const std::string* result = value->FindStringKey(kSettingBackgroundColor);
return result ? *result : kDefaultBackgroundColor;
}
bool GetTerminalSettingPassCtrlW(Profile* profile) {
const base::DictionaryValue* value = profile->GetPrefs()->GetDictionary(
crostini::prefs::kCrostiniTerminalSettings);
return value->FindBoolKey(kSettingPassCtrlW).value_or(kDefaultPassCtrlW);
}
} // namespace crostini