blob: 4c18ea912c34b147d1ad4362e5b035eb43ae5ad8 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/dbus/menu/menu_property_list.h"
#include <string>
#include <utility>
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/memory/ref_counted_memory.h"
#include "base/notimplemented.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/menu_model.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/gfx/image/image.h"
namespace {
std::string ToDBusKeySym(ui::KeyboardCode code) {
const uint16_t c =
DomCodeToUsLayoutCharacter(UsLayoutKeyboardCodeToDomCode(code), 0);
if (!c) {
return std::string();
}
return base::UTF16ToUTF8(std::u16string(1, c));
}
std::vector<std::string> GetDbusMenuShortcut(ui::Accelerator accelerator) {
auto dbus_key_sym = ToDBusKeySym(accelerator.key_code());
if (dbus_key_sym.empty()) {
return {};
}
std::vector<std::string> parts;
if (accelerator.IsCtrlDown()) {
parts.emplace_back("Control");
}
if (accelerator.IsAltDown()) {
parts.emplace_back("Alt");
}
if (accelerator.IsShiftDown()) {
parts.emplace_back("Shift");
}
if (accelerator.IsCmdDown()) {
parts.emplace_back("Super");
}
parts.emplace_back(dbus_key_sym);
return parts;
}
} // namespace
MenuItemProperties ComputeMenuPropertiesForMenuItem(ui::MenuModel* menu,
size_t i) {
// Properties should only be set if they differ from the default values.
MenuItemProperties properties;
// The dbusmenu interface has no concept of a "sublabel", "minor text", or
// "minor icon" like MenuModel has. Ignore these rather than trying to
// merge them with the regular label and icon.
std::u16string label = menu->GetLabelAt(i);
if (!label.empty()) {
properties["label"] = dbus_utils::Variant::Wrap<"s">(
ui::ConvertAcceleratorsFromWindowsStyle(base::UTF16ToUTF8(label)));
}
if (!menu->IsEnabledAt(i)) {
properties["enabled"] = dbus_utils::Variant::Wrap<"b">(false);
}
if (!menu->IsVisibleAt(i)) {
properties["visible"] = dbus_utils::Variant::Wrap<"b">(false);
}
ui::ImageModel icon = menu->GetIconAt(i);
if (icon.IsImage()) {
auto png_bytes = icon.GetImage().As1xPNGBytes();
auto span = base::as_byte_span(*png_bytes);
properties["icon-data"] = dbus_utils::Variant::Wrap<"ay">(
std::vector<uint8_t>(span.begin(), span.end()));
}
ui::Accelerator accelerator;
if (menu->GetAcceleratorAt(i, &accelerator)) {
auto parts = GetDbusMenuShortcut(accelerator);
if (!parts.empty()) {
properties["shortcut"] = dbus_utils::Variant::Wrap<"aas">(
std::vector<std::vector<std::string>>{std::move(parts)});
}
}
switch (menu->GetTypeAt(i)) {
case ui::MenuModel::TYPE_COMMAND:
case ui::MenuModel::TYPE_HIGHLIGHTED:
case ui::MenuModel::TYPE_TITLE:
// Nothing special to do.
break;
case ui::MenuModel::TYPE_CHECK:
case ui::MenuModel::TYPE_RADIO:
properties["toggle-type"] = dbus_utils::Variant::Wrap<"s">(
menu->GetTypeAt(i) == ui::MenuModel::TYPE_CHECK ? "checkmark"
: "radio");
properties["toggle-state"] =
dbus_utils::Variant::Wrap<"i">(menu->IsItemCheckedAt(i) ? 1 : 0);
break;
case ui::MenuModel::TYPE_SEPARATOR:
// The dbusmenu interface doesn't have multiple types of separators like
// MenuModel. Just use a regular separator in all cases.
properties["type"] = dbus_utils::Variant::Wrap<"s">("separator");
break;
case ui::MenuModel::TYPE_BUTTON_ITEM:
// This type of menu represents a row of buttons, but the dbusmenu
// interface has no equivalent of this. Ignore these items for now
// since there's currently no uses of it that plumb into this codepath.
// If there are button menu items in the future, we'd have to fake them
// with multiple menu items.
NOTIMPLEMENTED();
break;
case ui::MenuModel::TYPE_SUBMENU:
case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU:
properties["children-display"] =
dbus_utils::Variant::Wrap<"s">("submenu");
break;
}
return properties;
}
void ComputeMenuPropertyChanges(const MenuItemProperties& old_properties,
const MenuItemProperties& new_properties,
MenuPropertyList* item_updated_props,
MenuPropertyList* item_removed_props) {
// Compute updated and removed properties.
for (const auto& pair : old_properties) {
const std::string& key = pair.first;
auto new_it = new_properties.find(key);
if (new_it != new_properties.end()) {
if (new_it->second != pair.second) {
item_updated_props->push_back(key);
}
} else {
item_removed_props->push_back(key);
}
}
// Compute added properties.
for (const auto& pair : new_properties) {
const std::string& key = pair.first;
if (!base::Contains(old_properties, key)) {
item_updated_props->push_back(key);
}
}
}