| // 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); |
| } |
| } |
| } |