menu.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  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. #include "components/dbus/menu/menu.h"
  5. #include <limits>
  6. #include <memory>
  7. #include <set>
  8. #include "base/bind.h"
  9. #include "base/callback.h"
  10. #include "base/containers/contains.h"
  11. #include "base/i18n/rtl.h"
  12. #include "base/memory/ref_counted_memory.h"
  13. #include "base/memory/scoped_refptr.h"
  14. #include "base/strings/utf_string_conversions.h"
  15. #include "components/dbus/properties/dbus_properties.h"
  16. #include "components/dbus/properties/success_barrier_callback.h"
  17. #include "ui/base/accelerators/accelerator.h"
  18. #include "ui/base/models/menu_model.h"
  19. #include "ui/base/models/simple_menu_model.h"
  20. namespace {
  21. // Interfaces.
  22. const char kInterfaceDbusMenu[] = "com.canonical.dbusmenu";
  23. // Methods.
  24. const char kMethodAboutToShow[] = "AboutToShow";
  25. const char kMethodAboutToShowGroup[] = "AboutToShowGroup";
  26. const char kMethodEvent[] = "Event";
  27. const char kMethodEventGroup[] = "EventGroup";
  28. const char kMethodGetGroupProperties[] = "GetGroupProperties";
  29. const char kMethodGetLayout[] = "GetLayout";
  30. const char kMethodGetProperty[] = "GetProperty";
  31. // Properties.
  32. const char kPropertyIconThemePath[] = "IconThemePath";
  33. const char kPropertyMenuStatus[] = "Status";
  34. const char kPropertyTextDirection[] = "TextDirection";
  35. const char kPropertyVersion[] = "Version";
  36. // Property values.
  37. const char kPropertyValueStatusNormal[] = "normal";
  38. uint32_t kPropertyValueVersion = 3;
  39. // Signals.
  40. const char kSignalItemsPropertiesUpdated[] = "ItemsPropertiesUpdated";
  41. const char kSignalLayoutUpdated[] = "LayoutUpdated";
  42. // Creates a variant with the default value for |property_name|, or an empty
  43. // variant if |property_name| is invalid.
  44. DbusVariant CreateDefaultPropertyValue(const std::string& property_name) {
  45. if (property_name == "type")
  46. return MakeDbusVariant(DbusString("standard"));
  47. if (property_name == "label")
  48. return MakeDbusVariant(DbusString(""));
  49. if (property_name == "enabled")
  50. return MakeDbusVariant(DbusBoolean(true));
  51. if (property_name == "visible")
  52. return MakeDbusVariant(DbusBoolean(true));
  53. if (property_name == "icon-name")
  54. return MakeDbusVariant(DbusString(""));
  55. if (property_name == "icon-data")
  56. return MakeDbusVariant(DbusByteArray());
  57. if (property_name == "shortcut")
  58. return MakeDbusVariant(DbusArray<DbusArray<DbusString>>());
  59. if (property_name == "toggle-type")
  60. return MakeDbusVariant(DbusString(""));
  61. if (property_name == "toggle-state")
  62. return MakeDbusVariant(DbusInt32(-1));
  63. if (property_name == "children-display")
  64. return MakeDbusVariant(DbusString(""));
  65. return DbusVariant();
  66. }
  67. DbusString DbusTextDirection() {
  68. return DbusString(base::i18n::IsRTL() ? "rtl " : "ltr");
  69. }
  70. void WriteRemovedProperties(dbus::MessageWriter* writer,
  71. const MenuPropertyChanges& removed_props) {
  72. dbus::MessageWriter removed_props_writer(nullptr);
  73. writer->OpenArray("(ias)", &removed_props_writer);
  74. for (const auto& pair : removed_props) {
  75. dbus::MessageWriter struct_writer(nullptr);
  76. removed_props_writer.OpenStruct(&struct_writer);
  77. struct_writer.AppendInt32(pair.first);
  78. struct_writer.AppendArrayOfStrings(pair.second);
  79. removed_props_writer.CloseContainer(&struct_writer);
  80. }
  81. writer->CloseContainer(&removed_props_writer);
  82. }
  83. } // namespace
  84. DbusMenu::MenuItem::MenuItem(int32_t id,
  85. std::map<std::string, DbusVariant>&& properties,
  86. std::vector<int32_t>&& children,
  87. ui::MenuModel* menu,
  88. ui::MenuModel* containing_menu,
  89. size_t containing_menu_index)
  90. : id(id),
  91. properties(std::move(properties)),
  92. children(std::move(children)),
  93. menu(menu),
  94. containing_menu(containing_menu),
  95. containing_menu_index(containing_menu_index) {}
  96. DbusMenu::MenuItem::~MenuItem() = default;
  97. DbusMenu::ScopedMethodResponse::ScopedMethodResponse(
  98. dbus::MethodCall* method_call,
  99. dbus::ExportedObject::ResponseSender response_sender)
  100. : method_call_(method_call),
  101. response_sender_(std::move(response_sender)),
  102. reader_(method_call_) {}
  103. DbusMenu::ScopedMethodResponse::~ScopedMethodResponse() {
  104. std::move(response_sender_).Run(std::move(response_));
  105. }
  106. dbus::MessageWriter& DbusMenu::ScopedMethodResponse::Writer() {
  107. EnsureResponse();
  108. if (!writer_)
  109. writer_ = std::make_unique<dbus::MessageWriter>(response_.get());
  110. return *writer_;
  111. }
  112. void DbusMenu::ScopedMethodResponse::EnsureResponse() {
  113. if (!response_)
  114. response_ = dbus::Response::FromMethodCall(method_call_);
  115. }
  116. DbusMenu::DbusMenu(dbus::ExportedObject* exported_object,
  117. InitializedCallback callback)
  118. : menu_(exported_object) {
  119. SetModel(nullptr, false);
  120. static constexpr struct {
  121. const char* name;
  122. void (DbusMenu::*callback)(ScopedMethodResponse*);
  123. } methods[7] = {
  124. {kMethodAboutToShow, &DbusMenu::OnAboutToShow},
  125. {kMethodAboutToShowGroup, &DbusMenu::OnAboutToShowGroup},
  126. {kMethodEvent, &DbusMenu::OnEvent},
  127. {kMethodEventGroup, &DbusMenu::OnEventGroup},
  128. {kMethodGetGroupProperties, &DbusMenu::OnGetGroupProperties},
  129. {kMethodGetLayout, &DbusMenu::OnGetLayout},
  130. {kMethodGetProperty, &DbusMenu::OnGetProperty},
  131. };
  132. // std::size(methods) calls for method export, 1 call for properties
  133. // initialization.
  134. barrier_ =
  135. SuccessBarrierCallback(std::size(methods) + 1, std::move(callback));
  136. for (const auto& method : methods) {
  137. menu_->ExportMethod(
  138. kInterfaceDbusMenu, method.name,
  139. WrapMethodCallback(
  140. base::BindRepeating(method.callback, weak_factory_.GetWeakPtr())),
  141. base::BindRepeating(&DbusMenu::OnExported, weak_factory_.GetWeakPtr()));
  142. }
  143. properties_ = std::make_unique<DbusProperties>(menu_, barrier_);
  144. properties_->RegisterInterface(kInterfaceDbusMenu);
  145. auto set_property = [&](const std::string& property_name, auto&& value) {
  146. properties_->SetProperty(kInterfaceDbusMenu, property_name,
  147. std::forward<decltype(value)>(value), false);
  148. };
  149. set_property(kPropertyIconThemePath, DbusArray<DbusString>());
  150. set_property(kPropertyMenuStatus, DbusString(kPropertyValueStatusNormal));
  151. set_property(kPropertyTextDirection, DbusTextDirection());
  152. set_property(kPropertyVersion, DbusUint32(kPropertyValueVersion));
  153. }
  154. DbusMenu::~DbusMenu() = default;
  155. void DbusMenu::SetModel(ui::MenuModel* model, bool send_signal) {
  156. items_.clear();
  157. std::map<std::string, DbusVariant> properties;
  158. std::vector<int32_t> children;
  159. if (model) {
  160. properties["children-display"] = MakeDbusVariant(DbusString("submenu"));
  161. children = ConvertMenu(model);
  162. }
  163. items_[0] = std::make_unique<MenuItem>(
  164. 0, std::move(properties), std::move(children), nullptr, nullptr, -1);
  165. if (send_signal)
  166. SendLayoutChangedSignal(0);
  167. }
  168. void DbusMenu::MenuLayoutUpdated(ui::MenuModel* model) {
  169. MenuItem* item = FindMenuItemForModel(model, items_[0].get());
  170. DCHECK(item);
  171. DeleteItemChildren(item);
  172. item->children = ConvertMenu(model);
  173. SendLayoutChangedSignal(item->id);
  174. }
  175. void DbusMenu::MenuItemsPropertiesUpdated(
  176. const std::vector<MenuItemReference>& menu_items) {
  177. if (menu_items.empty())
  178. return;
  179. MenuPropertyChanges updated_props;
  180. MenuPropertyChanges removed_props;
  181. for (const auto& menu_item : menu_items) {
  182. ui::MenuModel* menu = menu_item.first;
  183. size_t index = menu_item.second;
  184. MenuItem* parent = FindMenuItemForModel(menu, items_[0].get());
  185. MenuItem* item = nullptr;
  186. for (int32_t id : parent->children) {
  187. MenuItem* child = items_[id].get();
  188. DCHECK_EQ(child->containing_menu, menu);
  189. if (child->containing_menu_index == index) {
  190. item = child;
  191. break;
  192. }
  193. }
  194. DCHECK(item);
  195. auto old_properties = std::move(item->properties);
  196. item->properties = ComputeMenuPropertiesForMenuItem(menu, index);
  197. MenuPropertyList item_updated_props;
  198. MenuPropertyList item_removed_props;
  199. ComputeMenuPropertyChanges(old_properties, item->properties,
  200. &item_updated_props, &item_removed_props);
  201. if (!item_updated_props.empty())
  202. updated_props[item->id] = std::move(item_updated_props);
  203. if (!item_removed_props.empty())
  204. removed_props[item->id] = std::move(item_removed_props);
  205. }
  206. dbus::Signal signal(kInterfaceDbusMenu, kSignalItemsPropertiesUpdated);
  207. dbus::MessageWriter writer(&signal);
  208. WriteUpdatedProperties(&writer, updated_props);
  209. WriteRemovedProperties(&writer, removed_props);
  210. menu_->SendSignal(&signal);
  211. }
  212. // static
  213. dbus::ExportedObject::MethodCallCallback DbusMenu::WrapMethodCallback(
  214. base::RepeatingCallback<void(ScopedMethodResponse*)> callback) {
  215. return base::BindRepeating(
  216. [](base::RepeatingCallback<void(ScopedMethodResponse*)> bound_callback,
  217. dbus::MethodCall* method_call,
  218. dbus::ExportedObject::ResponseSender response_sender) {
  219. ScopedMethodResponse response(method_call, std::move(response_sender));
  220. bound_callback.Run(&response);
  221. },
  222. callback);
  223. }
  224. void DbusMenu::OnExported(const std::string& interface_name,
  225. const std::string& method_name,
  226. bool success) {
  227. barrier_.Run(success);
  228. }
  229. void DbusMenu::OnAboutToShow(ScopedMethodResponse* response) {
  230. int32_t id;
  231. if (!response->reader().PopInt32(&id))
  232. return;
  233. if (!AboutToShowImpl(id))
  234. return;
  235. response->Writer().AppendBool(false);
  236. }
  237. void DbusMenu::OnAboutToShowGroup(ScopedMethodResponse* response) {
  238. dbus::MessageReader array_reader(nullptr);
  239. if (!response->reader().PopArray(&array_reader))
  240. return;
  241. std::vector<int32_t> ids;
  242. while (array_reader.HasMoreData()) {
  243. int32_t id;
  244. if (!array_reader.PopInt32(&id))
  245. return;
  246. ids.push_back(id);
  247. }
  248. std::vector<int32_t> id_errors;
  249. for (int32_t id : ids) {
  250. if (!AboutToShowImpl(id))
  251. id_errors.push_back(id);
  252. }
  253. // IDs of updates needed (none).
  254. response->Writer().AppendArrayOfInt32s(nullptr, 0);
  255. // Invalid IDs.
  256. response->Writer().AppendArrayOfInt32s(id_errors.data(), id_errors.size());
  257. }
  258. void DbusMenu::OnEvent(ScopedMethodResponse* response) {
  259. if (!EventImpl(&response->reader(), nullptr))
  260. return;
  261. response->EnsureResponse();
  262. }
  263. void DbusMenu::OnEventGroup(ScopedMethodResponse* response) {
  264. dbus::MessageReader array_reader(nullptr);
  265. if (!response->reader().PopArray(&array_reader))
  266. return;
  267. std::vector<int32_t> id_errors;
  268. while (array_reader.HasMoreData()) {
  269. dbus::MessageReader struct_reader(nullptr);
  270. array_reader.PopStruct(&struct_reader);
  271. int32_t id_error = -1;
  272. if (!EventImpl(&struct_reader, &id_error)) {
  273. if (id_error < 0)
  274. return;
  275. id_errors.push_back(id_error);
  276. }
  277. }
  278. response->Writer().AppendArrayOfInt32s(id_errors.data(), id_errors.size());
  279. }
  280. void DbusMenu::OnGetGroupProperties(ScopedMethodResponse* response) {
  281. dbus::MessageReader id_reader(nullptr);
  282. if (!response->reader().PopArray(&id_reader))
  283. return;
  284. std::vector<int32_t> ids;
  285. while (id_reader.HasMoreData()) {
  286. int32_t id;
  287. if (!id_reader.PopInt32(&id))
  288. return;
  289. ids.push_back(id);
  290. }
  291. std::set<std::string> property_filter;
  292. dbus::MessageReader property_reader(nullptr);
  293. if (!response->reader().PopArray(&property_reader))
  294. return;
  295. while (property_reader.HasMoreData()) {
  296. std::string property;
  297. if (!property_reader.PopString(&property))
  298. return;
  299. property_filter.insert(property);
  300. }
  301. dbus::MessageWriter& writer = response->Writer();
  302. dbus::MessageWriter item_writer(nullptr);
  303. writer.OpenArray("(ia{sv})", &item_writer);
  304. auto write_item = [&](int32_t id, const MenuItem& item) {
  305. dbus::MessageWriter struct_writer(nullptr);
  306. item_writer.OpenStruct(&struct_writer);
  307. struct_writer.AppendInt32(id);
  308. dbus::MessageWriter property_writer(nullptr);
  309. struct_writer.OpenArray("{sv}", &property_writer);
  310. for (const auto& property_pair : item.properties) {
  311. if (!property_filter.empty() &&
  312. !base::Contains(property_filter, property_pair.first)) {
  313. continue;
  314. }
  315. dbus::MessageWriter dict_entry_writer(nullptr);
  316. property_writer.OpenDictEntry(&dict_entry_writer);
  317. dict_entry_writer.AppendString(property_pair.first);
  318. property_pair.second.Write(&dict_entry_writer);
  319. property_writer.CloseContainer(&dict_entry_writer);
  320. }
  321. struct_writer.CloseContainer(&property_writer);
  322. item_writer.CloseContainer(&struct_writer);
  323. };
  324. if (ids.empty()) {
  325. for (const auto& item_pair : items_)
  326. write_item(item_pair.first, *item_pair.second);
  327. } else {
  328. for (int32_t id : ids) {
  329. auto it = items_.find(id);
  330. if (it != items_.end())
  331. write_item(id, *it->second);
  332. }
  333. }
  334. writer.CloseContainer(&item_writer);
  335. }
  336. void DbusMenu::OnGetLayout(ScopedMethodResponse* response) {
  337. dbus::MessageReader& reader = response->reader();
  338. int32_t id;
  339. int32_t depth;
  340. MenuPropertyList property_filter;
  341. if (!reader.PopInt32(&id) || !reader.PopInt32(&depth) || depth < -1 ||
  342. !reader.PopArrayOfStrings(&property_filter)) {
  343. return;
  344. }
  345. auto it = items_.find(id);
  346. if (it == items_.end())
  347. return;
  348. dbus::MessageWriter& writer = response->Writer();
  349. writer.AppendUint32(revision_);
  350. WriteMenuItem(it->second.get(), &writer, depth, property_filter);
  351. }
  352. void DbusMenu::OnGetProperty(ScopedMethodResponse* response) {
  353. dbus::MessageReader& reader = response->reader();
  354. int32_t id;
  355. std::string name;
  356. if (!reader.PopInt32(&id) || !reader.PopString(&name))
  357. return;
  358. auto item_it = items_.find(id);
  359. if (item_it == items_.end())
  360. return;
  361. MenuItem* item = item_it->second.get();
  362. auto property_it = item->properties.find(name);
  363. if (property_it == item->properties.end()) {
  364. DbusVariant default_value = CreateDefaultPropertyValue(name);
  365. if (default_value)
  366. default_value.Write(&response->Writer());
  367. } else {
  368. property_it->second.Write(&response->Writer());
  369. }
  370. }
  371. bool DbusMenu::AboutToShowImpl(int32_t id) {
  372. auto item = items_.find(id);
  373. if (item == items_.end())
  374. return false;
  375. ui::MenuModel* menu = item->second->menu;
  376. if (!menu)
  377. return false;
  378. menu->MenuWillShow();
  379. return true;
  380. }
  381. bool DbusMenu::EventImpl(dbus::MessageReader* reader, int32_t* id_error) {
  382. int32_t id;
  383. if (!reader->PopInt32(&id))
  384. return false;
  385. auto item_it = items_.find(id);
  386. if (item_it == items_.end()) {
  387. if (id_error)
  388. *id_error = id;
  389. return false;
  390. }
  391. std::string type;
  392. if (!reader->PopString(&type))
  393. return false;
  394. if (type == "clicked") {
  395. MenuItem* item = item_it->second.get();
  396. if (!item->containing_menu)
  397. return false;
  398. item->containing_menu->ActivatedAt(item->containing_menu_index);
  399. } else {
  400. DCHECK(type == "hovered" || type == "opened" || type == "closed")
  401. << "Unexpected type: " << type;
  402. // Nothing to do.
  403. }
  404. return true;
  405. }
  406. std::vector<int32_t> DbusMenu::ConvertMenu(ui::MenuModel* menu) {
  407. std::vector<int32_t> items;
  408. if (!menu)
  409. return items;
  410. items.reserve(menu->GetItemCount());
  411. for (size_t i = 0; i < menu->GetItemCount(); ++i) {
  412. ui::MenuModel* submenu = menu->GetSubmenuModelAt(i);
  413. std::vector<int32_t> children = ConvertMenu(submenu);
  414. int32_t id = NextItemId();
  415. items_[id] = std::make_unique<MenuItem>(
  416. id, ComputeMenuPropertiesForMenuItem(menu, i), std::move(children),
  417. submenu, menu, i);
  418. items.push_back(id);
  419. }
  420. return items;
  421. }
  422. int32_t DbusMenu::NextItemId() {
  423. return last_item_id_ = last_item_id_ == std::numeric_limits<int32_t>::max()
  424. ? 1
  425. : last_item_id_ + 1;
  426. }
  427. void DbusMenu::WriteMenuItem(const MenuItem* item,
  428. dbus::MessageWriter* writer,
  429. int32_t depth,
  430. const MenuPropertyList& property_filter) const {
  431. dbus::MessageWriter struct_writer(nullptr);
  432. writer->OpenStruct(&struct_writer);
  433. struct_writer.AppendInt32(item->id);
  434. dbus::MessageWriter properties_writer(nullptr);
  435. struct_writer.OpenArray("{sv}", &properties_writer);
  436. for (const auto& property : item->properties) {
  437. if (property_filter.empty() ||
  438. base::Contains(property_filter, property.first)) {
  439. dbus::MessageWriter dict_entry_writer(nullptr);
  440. properties_writer.OpenDictEntry(&dict_entry_writer);
  441. dict_entry_writer.AppendString(property.first);
  442. property.second.Write(&dict_entry_writer);
  443. properties_writer.CloseContainer(&dict_entry_writer);
  444. }
  445. }
  446. struct_writer.CloseContainer(&properties_writer);
  447. dbus::MessageWriter children_writer(nullptr);
  448. struct_writer.OpenArray("v", &children_writer);
  449. if (depth != 0) {
  450. for (int32_t child : item->children) {
  451. dbus::MessageWriter variant_writer(nullptr);
  452. children_writer.OpenVariant("(ia{sv}av)", &variant_writer);
  453. WriteMenuItem(items_.find(child)->second.get(), &variant_writer,
  454. depth == -1 ? -1 : depth - 1, property_filter);
  455. children_writer.CloseContainer(&variant_writer);
  456. }
  457. }
  458. struct_writer.CloseContainer(&children_writer);
  459. writer->CloseContainer(&struct_writer);
  460. }
  461. void DbusMenu::WriteUpdatedProperties(
  462. dbus::MessageWriter* writer,
  463. const MenuPropertyChanges& updated_props) const {
  464. dbus::MessageWriter updated_props_writer(nullptr);
  465. writer->OpenArray("(ia{sv})", &updated_props_writer);
  466. for (const auto& pair : updated_props) {
  467. int32_t id = pair.first;
  468. MenuItem* item = items_.find(id)->second.get();
  469. dbus::MessageWriter struct_writer(nullptr);
  470. updated_props_writer.OpenStruct(&struct_writer);
  471. struct_writer.AppendInt32(id);
  472. dbus::MessageWriter array_writer(nullptr);
  473. struct_writer.OpenArray("{sv}", &array_writer);
  474. for (const std::string& key : pair.second) {
  475. dbus::MessageWriter dict_entry_writer(nullptr);
  476. array_writer.OpenDictEntry(&dict_entry_writer);
  477. dict_entry_writer.AppendString(key);
  478. item->properties[key].Write(&dict_entry_writer);
  479. array_writer.CloseContainer(&dict_entry_writer);
  480. }
  481. struct_writer.CloseContainer(&array_writer);
  482. updated_props_writer.CloseContainer(&struct_writer);
  483. }
  484. writer->CloseContainer(&updated_props_writer);
  485. }
  486. DbusMenu::MenuItem* DbusMenu::FindMenuItemForModel(const ui::MenuModel* model,
  487. MenuItem* item) const {
  488. if (item->menu == model)
  489. return item;
  490. for (int32_t id : item->children) {
  491. MenuItem* child = items_.find(id)->second.get();
  492. MenuItem* found = FindMenuItemForModel(model, child);
  493. if (found)
  494. return found;
  495. }
  496. return nullptr;
  497. }
  498. void DbusMenu::DeleteItem(MenuItem* item) {
  499. DeleteItemChildren(item);
  500. items_.erase(item->id);
  501. }
  502. void DbusMenu::DeleteItemChildren(MenuItem* item) {
  503. for (int32_t id : item->children)
  504. DeleteItem(items_.find(id)->second.get());
  505. }
  506. void DbusMenu::SendLayoutChangedSignal(int32_t id) {
  507. dbus::Signal signal(kInterfaceDbusMenu, kSignalLayoutUpdated);
  508. dbus::MessageWriter writer(&signal);
  509. writer.AppendUint32(++revision_); // Revision of the new layout.
  510. writer.AppendInt32(id); // Parent item whose layout changed.
  511. menu_->SendSignal(&signal);
  512. }