1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/dbus/menu/menu_property_list.h"
6 
7 #include <utility>
8 
9 #include "base/strings/string16.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "build/build_config.h"
12 #include "ui/base/accelerators/accelerator.h"
13 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
14 #include "ui/base/models/image_model.h"
15 #include "ui/base/models/menu_model.h"
16 #include "ui/gfx/image/image.h"
17 
18 #if defined(USE_X11)
19 #include "ui/events/keycodes/keyboard_code_conversion_x.h"  // nogncheck
20 #include "ui/events/keycodes/keysym_to_unicode.h"           // nogncheck
21 #endif
22 
23 #if defined(USE_OZONE)
24 #include "ui/base/ui_base_features.h"             // nogncheck
25 #include "ui/ozone/public/ozone_platform.h"       // nogncheck
26 #include "ui/ozone/public/platform_menu_utils.h"  // nogncheck
27 #endif
28 
29 namespace {
30 
ToDBusKeySym(ui::KeyboardCode code)31 std::string ToDBusKeySym(ui::KeyboardCode code) {
32 #if defined(USE_OZONE)
33   if (features::IsUsingOzonePlatform()) {
34     const auto* const platorm_menu_utils =
35         ui::OzonePlatform::GetInstance()->GetPlatformMenuUtils();
36     if (platorm_menu_utils)
37       return platorm_menu_utils->ToDBusKeySym(code);
38     return {};
39   }
40 #endif
41 #if defined(USE_X11)
42   return base::UTF16ToUTF8(
43       base::string16(1, ui::GetUnicodeCharacterFromXKeySym(
44                             XKeysymForWindowsKeyCode(code, false))));
45 #endif
46   return {};
47 }
48 
GetDbusMenuShortcut(ui::Accelerator accelerator)49 std::vector<DbusString> GetDbusMenuShortcut(ui::Accelerator accelerator) {
50   auto dbus_key_sym = ToDBusKeySym(accelerator.key_code());
51   if (dbus_key_sym.empty())
52     return {};
53 
54   std::vector<DbusString> parts;
55   if (accelerator.IsCtrlDown())
56     parts.emplace_back("Control");
57   if (accelerator.IsAltDown())
58     parts.emplace_back("Alt");
59   if (accelerator.IsShiftDown())
60     parts.emplace_back("Shift");
61   if (accelerator.IsCmdDown())
62     parts.emplace_back("Super");
63   parts.emplace_back(dbus_key_sym);
64   return parts;
65 }
66 
67 }  // namespace
68 
ComputeMenuPropertiesForMenuItem(ui::MenuModel * menu,int i)69 MenuItemProperties ComputeMenuPropertiesForMenuItem(ui::MenuModel* menu,
70                                                     int i) {
71   // Properties should only be set if they differ from the default values.
72   MenuItemProperties properties;
73 
74   // The dbusmenu interface has no concept of a "sublabel", "minor text", or
75   // "minor icon" like MenuModel has.  Ignore these rather than trying to
76   // merge them with the regular label and icon.
77   base::string16 label = menu->GetLabelAt(i);
78   if (!label.empty()) {
79     properties["label"] = MakeDbusVariant(DbusString(
80         ui::ConvertAcceleratorsFromWindowsStyle(base::UTF16ToUTF8(label))));
81   }
82 
83   if (!menu->IsEnabledAt(i))
84     properties["enabled"] = MakeDbusVariant(DbusBoolean(false));
85   if (!menu->IsVisibleAt(i))
86     properties["visible"] = MakeDbusVariant(DbusBoolean(false));
87 
88   ui::ImageModel icon = menu->GetIconAt(i);
89   if (icon.IsImage()) {
90     properties["icon-data"] =
91         MakeDbusVariant(DbusByteArray(icon.GetImage().As1xPNGBytes()));
92   }
93 
94   ui::Accelerator accelerator;
95   if (menu->GetAcceleratorAt(i, &accelerator)) {
96     auto parts = GetDbusMenuShortcut(accelerator);
97     if (!parts.empty()) {
98       properties["shortcut"] = MakeDbusVariant(
99           MakeDbusArray(DbusArray<DbusString>(std::move(parts))));
100     }
101   }
102 
103   switch (menu->GetTypeAt(i)) {
104     case ui::MenuModel::TYPE_COMMAND:
105     case ui::MenuModel::TYPE_HIGHLIGHTED:
106     case ui::MenuModel::TYPE_TITLE:
107       // Nothing special to do.
108       break;
109     case ui::MenuModel::TYPE_CHECK:
110     case ui::MenuModel::TYPE_RADIO:
111       properties["toggle-type"] = MakeDbusVariant(DbusString(
112           menu->GetTypeAt(i) == ui::MenuModel::TYPE_CHECK ? "checkmark"
113                                                           : "radio"));
114       properties["toggle-state"] =
115           MakeDbusVariant(DbusInt32(menu->IsItemCheckedAt(i) ? 1 : 0));
116       break;
117     case ui::MenuModel::TYPE_SEPARATOR:
118       // The dbusmenu interface doesn't have multiple types of separators like
119       // MenuModel.  Just use a regular separator in all cases.
120       properties["type"] = MakeDbusVariant(DbusString("separator"));
121       break;
122     case ui::MenuModel::TYPE_BUTTON_ITEM:
123       // This type of menu represents a row of buttons, but the dbusmenu
124       // interface has no equivalent of this.  Ignore these items for now
125       // since there's currently no uses of it that plumb into this codepath.
126       // If there are button menu items in the future, we'd have to fake them
127       // with multiple menu items.
128       NOTIMPLEMENTED();
129       break;
130     case ui::MenuModel::TYPE_SUBMENU:
131     case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU:
132       properties["children-display"] = MakeDbusVariant(DbusString("submenu"));
133       break;
134   }
135 
136   return properties;
137 }
138 
ComputeMenuPropertyChanges(const MenuItemProperties & old_properties,const MenuItemProperties & new_properties,MenuPropertyList * item_updated_props,MenuPropertyList * item_removed_props)139 void ComputeMenuPropertyChanges(const MenuItemProperties& old_properties,
140                                 const MenuItemProperties& new_properties,
141                                 MenuPropertyList* item_updated_props,
142                                 MenuPropertyList* item_removed_props) {
143   // Compute updated and removed properties.
144   for (const auto& pair : old_properties) {
145     const std::string& key = pair.first;
146     auto new_it = new_properties.find(key);
147     if (new_it != new_properties.end()) {
148       if (new_it->second != pair.second)
149         item_updated_props->push_back(key);
150     } else {
151       item_removed_props->push_back(key);
152     }
153   }
154   // Compute added properties.
155   for (const auto& pair : new_properties) {
156     const std::string& key = pair.first;
157     if (!base::Contains(old_properties, key))
158       item_updated_props->push_back(key);
159   }
160 }
161