global_menu_bar_x11.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
1// Copyright 2013 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 "chrome/browser/ui/views/frame/global_menu_bar_x11.h"
6
7#include <dlfcn.h>
8#include <glib-object.h>
9
10#include "base/logging.h"
11#include "base/prefs/pref_service.h"
12#include "base/stl_util.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/strings/stringprintf.h"
15#include "base/strings/utf_string_conversions.h"
16#include "chrome/app/chrome_command_ids.h"
17#include "chrome/browser/chrome_notification_types.h"
18#include "chrome/browser/history/top_sites.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/sessions/tab_restore_service.h"
21#include "chrome/browser/sessions/tab_restore_service_factory.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/browser_commands.h"
24#include "chrome/browser/ui/browser_tab_restore_service_delegate.h"
25#include "chrome/browser/ui/views/frame/browser_desktop_root_window_host_x11.h"
26#include "chrome/browser/ui/views/frame/browser_view.h"
27#include "chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h"
28#include "chrome/common/pref_names.h"
29#include "content/public/browser/notification_source.h"
30#include "grit/generated_resources.h"
31#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
32#include "ui/base/l10n/l10n_util.h"
33#include "ui/events/keycodes/keyboard_code_conversion_x.h"
34#include "ui/gfx/text_elider.h"
35
36// libdbusmenu-glib types
37typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
38typedef DbusmenuMenuitem* (*dbusmenu_menuitem_new_func)();
39typedef bool (*dbusmenu_menuitem_child_add_position_func)(
40    DbusmenuMenuitem* parent,
41    DbusmenuMenuitem* child,
42    unsigned int position);
43typedef DbusmenuMenuitem* (*dbusmenu_menuitem_child_append_func)(
44    DbusmenuMenuitem* parent,
45    DbusmenuMenuitem* child);
46typedef bool (*dbusmenu_menuitem_child_delete_func)(
47    DbusmenuMenuitem* parent,
48    DbusmenuMenuitem* child);
49typedef GList* (*dbusmenu_menuitem_get_children_func)(
50    DbusmenuMenuitem* item);
51typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_func)(
52    DbusmenuMenuitem* item,
53    const char* property,
54    const char* value);
55typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_variant_func)(
56    DbusmenuMenuitem* item,
57    const char* property,
58    GVariant* value);
59typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_bool_func)(
60    DbusmenuMenuitem* item,
61    const char* property,
62    bool value);
63typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_int_func)(
64    DbusmenuMenuitem* item,
65    const char* property,
66    int value);
67
68typedef struct _DbusmenuServer      DbusmenuServer;
69typedef DbusmenuServer* (*dbusmenu_server_new_func)(const char* object);
70typedef void (*dbusmenu_server_set_root_func)(DbusmenuServer* self,
71                                              DbusmenuMenuitem* root);
72
73// A line in the static menu definitions.
74struct GlobalMenuBarCommand {
75  int str_id;
76  int command;
77  int tag;
78};
79
80namespace {
81
82// Retrieved functions from libdbusmenu-glib.
83
84// DbusmenuMenuItem methods:
85dbusmenu_menuitem_new_func menuitem_new = NULL;
86dbusmenu_menuitem_get_children_func menuitem_get_children = NULL;
87dbusmenu_menuitem_child_add_position_func menuitem_child_add_position = NULL;
88dbusmenu_menuitem_child_append_func menuitem_child_append = NULL;
89dbusmenu_menuitem_child_delete_func menuitem_child_delete = NULL;
90dbusmenu_menuitem_property_set_func menuitem_property_set = NULL;
91dbusmenu_menuitem_property_set_variant_func menuitem_property_set_variant =
92    NULL;
93dbusmenu_menuitem_property_set_bool_func menuitem_property_set_bool = NULL;
94dbusmenu_menuitem_property_set_int_func menuitem_property_set_int = NULL;
95
96// DbusmenuServer methods:
97dbusmenu_server_new_func server_new = NULL;
98dbusmenu_server_set_root_func server_set_root = NULL;
99
100// Properties that we set on menu items:
101const char kPropertyEnabled[] = "enabled";
102const char kPropertyLabel[] = "label";
103const char kPropertyShortcut[] = "shortcut";
104const char kPropertyType[] = "type";
105const char kPropertyToggleType[] = "toggle-type";
106const char kPropertyToggleState[] = "toggle-state";
107const char kPropertyVisible[] = "visible";
108
109const char kTypeCheckmark[] = "checkmark";
110const char kTypeSeparator[] = "separator";
111
112// Data set on GObjectgs.
113const char kTypeTag[] = "type-tag";
114const char kHistoryItem[] = "history-item";
115
116// The maximum number of most visited items to display.
117const unsigned int kMostVisitedCount = 8;
118
119// The number of recently closed items to get.
120const unsigned int kRecentlyClosedCount = 8;
121
122// Menus more than this many chars long will get trimmed.
123const int kMaximumMenuWidthInChars = 50;
124
125// Constants used in menu definitions.
126const int MENU_SEPARATOR =-1;
127const int MENU_END = -2;
128const int MENU_DISABLED_ID = -3;
129
130// These tag values are used to refer to menu items.
131const int TAG_MOST_VISITED = 1;
132const int TAG_RECENTLY_CLOSED = 2;
133const int TAG_MOST_VISITED_HEADER = 3;
134const int TAG_RECENTLY_CLOSED_HEADER = 4;
135
136GlobalMenuBarCommand file_menu[] = {
137  { IDS_NEW_TAB, IDC_NEW_TAB },
138  { IDS_NEW_WINDOW, IDC_NEW_WINDOW },
139  { IDS_NEW_INCOGNITO_WINDOW, IDC_NEW_INCOGNITO_WINDOW },
140  { IDS_REOPEN_CLOSED_TABS_LINUX, IDC_RESTORE_TAB },
141  { IDS_OPEN_FILE_LINUX, IDC_OPEN_FILE },
142  { IDS_OPEN_LOCATION_LINUX, IDC_FOCUS_LOCATION },
143
144  { MENU_SEPARATOR, MENU_SEPARATOR },
145
146  { IDS_CREATE_SHORTCUTS, IDC_CREATE_SHORTCUTS },
147
148  { MENU_SEPARATOR, MENU_SEPARATOR },
149
150  { IDS_CLOSE_WINDOW_LINUX, IDC_CLOSE_WINDOW },
151  { IDS_CLOSE_TAB_LINUX, IDC_CLOSE_TAB },
152  { IDS_SAVE_PAGE, IDC_SAVE_PAGE },
153
154  { MENU_SEPARATOR, MENU_SEPARATOR },
155
156  { IDS_PRINT, IDC_PRINT },
157
158  { MENU_END, MENU_END }
159};
160
161GlobalMenuBarCommand edit_menu[] = {
162  { IDS_CUT, IDC_CUT },
163  { IDS_COPY, IDC_COPY },
164  { IDS_PASTE, IDC_PASTE },
165
166  { MENU_SEPARATOR, MENU_SEPARATOR },
167
168  { IDS_FIND, IDC_FIND },
169
170  { MENU_SEPARATOR, MENU_SEPARATOR },
171
172  { IDS_PREFERENCES, IDC_OPTIONS },
173
174  { MENU_END, MENU_END }
175};
176
177GlobalMenuBarCommand view_menu[] = {
178  { IDS_SHOW_BOOKMARK_BAR, IDC_SHOW_BOOKMARK_BAR },
179
180  { MENU_SEPARATOR, MENU_SEPARATOR },
181
182  { IDS_STOP_MENU_LINUX, IDC_STOP },
183  { IDS_RELOAD_MENU_LINUX, IDC_RELOAD },
184
185  { MENU_SEPARATOR, MENU_SEPARATOR },
186
187  { IDS_FULLSCREEN, IDC_FULLSCREEN },
188  { IDS_TEXT_DEFAULT_LINUX, IDC_ZOOM_NORMAL },
189  { IDS_TEXT_BIGGER_LINUX, IDC_ZOOM_PLUS },
190  { IDS_TEXT_SMALLER_LINUX, IDC_ZOOM_MINUS },
191
192  { MENU_END, MENU_END }
193};
194
195GlobalMenuBarCommand history_menu[] = {
196  { IDS_HISTORY_HOME_LINUX, IDC_HOME },
197  { IDS_HISTORY_BACK_LINUX, IDC_BACK },
198  { IDS_HISTORY_FORWARD_LINUX, IDC_FORWARD },
199
200  { MENU_SEPARATOR, MENU_SEPARATOR },
201
202  { IDS_HISTORY_VISITED_LINUX, MENU_DISABLED_ID, TAG_MOST_VISITED_HEADER },
203
204  { MENU_SEPARATOR, MENU_SEPARATOR },
205
206  { IDS_HISTORY_CLOSED_LINUX, MENU_DISABLED_ID, TAG_RECENTLY_CLOSED_HEADER },
207
208  { MENU_SEPARATOR, MENU_SEPARATOR },
209
210  { IDS_SHOWFULLHISTORY_LINK, IDC_SHOW_HISTORY },
211
212  { MENU_END, MENU_END }
213};
214
215GlobalMenuBarCommand tools_menu[] = {
216  { IDS_SHOW_DOWNLOADS, IDC_SHOW_DOWNLOADS },
217  { IDS_SHOW_HISTORY, IDC_SHOW_HISTORY },
218  { IDS_SHOW_EXTENSIONS, IDC_MANAGE_EXTENSIONS },
219
220  { MENU_SEPARATOR, MENU_SEPARATOR },
221
222  { IDS_TASK_MANAGER, IDC_TASK_MANAGER },
223  { IDS_CLEAR_BROWSING_DATA, IDC_CLEAR_BROWSING_DATA },
224
225  { MENU_SEPARATOR, MENU_SEPARATOR },
226
227  { IDS_VIEW_SOURCE, IDC_VIEW_SOURCE },
228  { IDS_DEV_TOOLS, IDC_DEV_TOOLS },
229  { IDS_DEV_TOOLS_CONSOLE, IDC_DEV_TOOLS_CONSOLE },
230  { IDS_DEV_TOOLS_DEVICES, IDC_DEV_TOOLS_DEVICES },
231
232  { MENU_END, MENU_END }
233};
234
235GlobalMenuBarCommand help_menu[] = {
236#if defined(GOOGLE_CHROME_BUILD)
237  { IDS_FEEDBACK, IDC_FEEDBACK },
238#endif
239  { IDS_HELP_PAGE , IDC_HELP_PAGE_VIA_MENU },
240  { MENU_END, MENU_END }
241};
242
243void EnsureMethodsLoaded() {
244  static bool attempted_load = false;
245  if (attempted_load)
246    return;
247  attempted_load = true;
248
249  void* dbusmenu_lib = dlopen("libdbusmenu-glib.so", RTLD_LAZY);
250  if (!dbusmenu_lib)
251    dbusmenu_lib = dlopen("libdbusmenu-glib.so.4", RTLD_LAZY);
252  if (!dbusmenu_lib)
253    return;
254
255  // DbusmenuMenuItem methods.
256  menuitem_new = reinterpret_cast<dbusmenu_menuitem_new_func>(
257      dlsym(dbusmenu_lib, "dbusmenu_menuitem_new"));
258  menuitem_child_add_position =
259      reinterpret_cast<dbusmenu_menuitem_child_add_position_func>(
260          dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_add_position"));
261  menuitem_child_append = reinterpret_cast<dbusmenu_menuitem_child_append_func>(
262      dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_append"));
263  menuitem_child_delete = reinterpret_cast<dbusmenu_menuitem_child_delete_func>(
264      dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_delete"));
265  menuitem_get_children = reinterpret_cast<dbusmenu_menuitem_get_children_func>(
266      dlsym(dbusmenu_lib, "dbusmenu_menuitem_get_children"));
267  menuitem_property_set = reinterpret_cast<dbusmenu_menuitem_property_set_func>(
268      dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set"));
269  menuitem_property_set_variant =
270      reinterpret_cast<dbusmenu_menuitem_property_set_variant_func>(
271          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_variant"));
272  menuitem_property_set_bool =
273      reinterpret_cast<dbusmenu_menuitem_property_set_bool_func>(
274          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_bool"));
275  menuitem_property_set_int =
276      reinterpret_cast<dbusmenu_menuitem_property_set_int_func>(
277          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_int"));
278
279  // DbusmenuServer methods.
280  server_new = reinterpret_cast<dbusmenu_server_new_func>(
281      dlsym(dbusmenu_lib, "dbusmenu_server_new"));
282  server_set_root = reinterpret_cast<dbusmenu_server_set_root_func>(
283      dlsym(dbusmenu_lib, "dbusmenu_server_set_root"));
284}
285
286}  // namespace
287
288struct GlobalMenuBarX11::HistoryItem {
289  HistoryItem() : session_id(0) {}
290
291  // The title for the menu item.
292  string16 title;
293  // The URL that will be navigated to if the user selects this item.
294  GURL url;
295
296  // This ID is unique for a browser session and can be passed to the
297  // TabRestoreService to re-open the closed window or tab that this
298  // references. A non-0 session ID indicates that this is an entry can be
299  // restored that way. Otherwise, the URL will be used to open the item and
300  // this ID will be 0.
301  SessionID::id_type session_id;
302
303  // If the HistoryItem is a window, this will be the vector of tabs. Note
304  // that this is a list of weak references. The |menu_item_map_| is the owner
305  // of all items. If it is not a window, then the entry is a single page and
306  // the vector will be empty.
307  std::vector<HistoryItem*> tabs;
308
309 private:
310  DISALLOW_COPY_AND_ASSIGN(HistoryItem);
311};
312
313GlobalMenuBarX11::GlobalMenuBarX11(BrowserView* browser_view,
314                                   BrowserDesktopRootWindowHostX11* host)
315    : browser_(browser_view->browser()),
316      profile_(browser_->profile()),
317      browser_view_(browser_view),
318      host_(host),
319      server_(NULL),
320      root_item_(NULL),
321      history_menu_(NULL),
322      top_sites_(NULL),
323      tab_restore_service_(NULL),
324      weak_ptr_factory_(this) {
325  EnsureMethodsLoaded();
326
327  if (server_new)
328    host_->AddObserver(this);
329}
330
331GlobalMenuBarX11::~GlobalMenuBarX11() {
332  if (server_) {
333    Disable();
334
335    if (tab_restore_service_)
336      tab_restore_service_->RemoveObserver(this);
337
338    g_object_unref(server_);
339    host_->RemoveObserver(this);
340  }
341}
342
343// static
344std::string GlobalMenuBarX11::GetPathForWindow(unsigned long xid) {
345  return base::StringPrintf("/com/canonical/menu/%lX", xid);
346}
347
348DbusmenuMenuitem* GlobalMenuBarX11::BuildSeparator() {
349  DbusmenuMenuitem* item = menuitem_new();
350  menuitem_property_set(item, kPropertyType, kTypeSeparator);
351  menuitem_property_set_bool(item, kPropertyVisible, true);
352  return item;
353}
354
355DbusmenuMenuitem* GlobalMenuBarX11::BuildMenuItem(
356    const std::string& label,
357    int tag_id) {
358  DbusmenuMenuitem* item = menuitem_new();
359  menuitem_property_set(item, kPropertyLabel, label.c_str());
360  menuitem_property_set_bool(item, kPropertyVisible, true);
361
362  if (tag_id)
363    g_object_set_data(G_OBJECT(item), kTypeTag, GINT_TO_POINTER(tag_id));
364
365  return item;
366}
367
368void GlobalMenuBarX11::InitServer(unsigned long xid) {
369  std::string path = GetPathForWindow(xid);
370  server_ = server_new(path.c_str());
371
372  root_item_ = menuitem_new();
373  menuitem_property_set(root_item_, kPropertyLabel, "Root");
374  menuitem_property_set_bool(root_item_, kPropertyVisible, true);
375
376  // First build static menu content.
377  BuildStaticMenu(root_item_, IDS_FILE_MENU_LINUX, file_menu);
378  BuildStaticMenu(root_item_, IDS_EDIT_MENU_LINUX, edit_menu);
379  BuildStaticMenu(root_item_, IDS_VIEW_MENU_LINUX, view_menu);
380  history_menu_ = BuildStaticMenu(
381      root_item_, IDS_HISTORY_MENU_LINUX, history_menu);
382  BuildStaticMenu(root_item_, IDS_TOOLS_MENU_LINUX, tools_menu);
383  BuildStaticMenu(root_item_, IDS_HELP_MENU_LINUX, help_menu);
384
385  // We have to connect to |history_menu_item|'s "activate" signal instead of
386  // |history_menu|'s "show" signal because we are not supposed to modify the
387  // menu during "show"
388  g_signal_connect(history_menu_, "about-to-show",
389                   G_CALLBACK(OnHistoryMenuAboutToShowThunk), this);
390
391  for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
392       it != id_to_menu_item_.end(); ++it) {
393    menuitem_property_set_bool(it->second, kPropertyEnabled,
394                               chrome::IsCommandEnabled(browser_, it->first));
395
396    ui::Accelerator accelerator;
397    if (browser_view_->GetAccelerator(it->first, &accelerator))
398      RegisterAccelerator(it->second, accelerator);
399
400    chrome::AddCommandObserver(browser_, it->first, this);
401  }
402
403  pref_change_registrar_.Init(browser_->profile()->GetPrefs());
404  pref_change_registrar_.Add(
405      prefs::kShowBookmarkBar,
406      base::Bind(&GlobalMenuBarX11::OnBookmarkBarVisibilityChanged,
407                 base::Unretained(this)));
408  OnBookmarkBarVisibilityChanged();
409
410  top_sites_ = profile_->GetTopSites();
411  if (top_sites_) {
412    GetTopSitesData();
413
414    // Register for notification when TopSites changes so that we can update
415    // ourself.
416    registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
417                   content::Source<history::TopSites>(top_sites_));
418  }
419
420  server_set_root(server_, root_item_);
421}
422
423void GlobalMenuBarX11::Disable() {
424  for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
425       it != id_to_menu_item_.end(); ++it) {
426    chrome::RemoveCommandObserver(browser_, it->first, this);
427  }
428  id_to_menu_item_.clear();
429
430  pref_change_registrar_.RemoveAll();
431}
432
433DbusmenuMenuitem* GlobalMenuBarX11::BuildStaticMenu(
434    DbusmenuMenuitem* parent,
435    int menu_str_id,
436    GlobalMenuBarCommand* commands) {
437  DbusmenuMenuitem* top = menuitem_new();
438  menuitem_property_set(
439      top, kPropertyLabel,
440      ui::RemoveWindowsStyleAccelerators(
441          l10n_util::GetStringUTF8(menu_str_id)).c_str());
442  menuitem_property_set_bool(top, kPropertyVisible, true);
443
444  for (int i = 0; commands[i].str_id != MENU_END; ++i) {
445    DbusmenuMenuitem* menu_item = NULL;
446    int command_id = commands[i].command;
447    if (commands[i].str_id == MENU_SEPARATOR) {
448      menu_item = BuildSeparator();
449    } else {
450      std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
451          l10n_util::GetStringUTF8(commands[i].str_id));
452
453      menu_item = BuildMenuItem(label, commands[i].tag);
454
455      if (command_id == MENU_DISABLED_ID) {
456        menuitem_property_set_bool(menu_item, kPropertyEnabled, false);
457      } else {
458        if (command_id == IDC_SHOW_BOOKMARK_BAR)
459          menuitem_property_set(menu_item, kPropertyToggleType, kTypeCheckmark);
460
461        id_to_menu_item_.insert(std::make_pair(command_id, menu_item));
462        g_object_set_data(G_OBJECT(menu_item), "command-id",
463                          GINT_TO_POINTER(command_id));
464        g_signal_connect(menu_item, "item-activated",
465                         G_CALLBACK(OnItemActivatedThunk), this);
466      }
467    }
468
469    menuitem_child_append(top, menu_item);
470    g_object_unref(menu_item);
471  }
472
473  menuitem_child_append(parent, top);
474  g_object_unref(top);
475  return top;
476}
477
478void GlobalMenuBarX11::RegisterAccelerator(DbusmenuMenuitem* item,
479                                           const ui::Accelerator& accelerator) {
480  // A translation of libdbusmenu-gtk's menuitem_property_set_shortcut()
481  // translated from GDK types to ui::Accelerator types.
482  GVariantBuilder builder;
483  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
484
485  if (accelerator.IsCtrlDown())
486    g_variant_builder_add(&builder, "s", "Control");
487  if (accelerator.IsAltDown())
488    g_variant_builder_add(&builder, "s", "Alt");
489  if (accelerator.IsShiftDown())
490    g_variant_builder_add(&builder, "s", "Shift");
491
492  char* name = XKeysymToString(XKeysymForWindowsKeyCode(
493      accelerator.key_code(), false));
494  if (!name) {
495    NOTIMPLEMENTED();
496    return;
497  }
498  g_variant_builder_add(&builder, "s", name);
499
500  GVariant* inside_array = g_variant_builder_end(&builder);
501  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
502  g_variant_builder_add_value(&builder, inside_array);
503  GVariant* outside_array = g_variant_builder_end(&builder);
504
505  menuitem_property_set_variant(item, kPropertyShortcut, outside_array);
506}
507
508GlobalMenuBarX11::HistoryItem* GlobalMenuBarX11::HistoryItemForTab(
509    const TabRestoreService::Tab& entry) {
510  const sessions::SerializedNavigationEntry& current_navigation =
511      entry.navigations.at(entry.current_navigation_index);
512  HistoryItem* item = new HistoryItem();
513  item->title = current_navigation.title();
514  item->url = current_navigation.virtual_url();
515  item->session_id = entry.id;
516
517  return item;
518}
519
520void GlobalMenuBarX11::AddHistoryItemToMenu(HistoryItem* item,
521                                            DbusmenuMenuitem* menu,
522                                            int tag,
523                                            int index) {
524  string16 title = item->title;
525  std::string url_string = item->url.possibly_invalid_spec();
526
527  if (title.empty())
528    title = UTF8ToUTF16(url_string);
529  gfx::ElideString(title, kMaximumMenuWidthInChars, &title);
530
531  DbusmenuMenuitem* menu_item = BuildMenuItem(UTF16ToUTF8(title), tag);
532  g_signal_connect(menu_item, "item-activated",
533                   G_CALLBACK(OnHistoryItemActivatedThunk), this);
534
535  g_object_set_data_full(G_OBJECT(menu_item), kHistoryItem, item,
536                         DeleteHistoryItem);
537  menuitem_child_add_position(menu, menu_item, index);
538  g_object_unref(menu_item);
539}
540
541void GlobalMenuBarX11::GetTopSitesData() {
542  DCHECK(top_sites_);
543
544  top_sites_->GetMostVisitedURLs(
545      base::Bind(&GlobalMenuBarX11::OnTopSitesReceived,
546                 weak_ptr_factory_.GetWeakPtr()));
547}
548
549void GlobalMenuBarX11::OnTopSitesReceived(
550    const history::MostVisitedURLList& visited_list) {
551  ClearMenuSection(history_menu_, TAG_MOST_VISITED);
552
553  int index = GetIndexOfMenuItemWithTag(history_menu_,
554                                        TAG_MOST_VISITED_HEADER) + 1;
555
556  for (size_t i = 0; i < visited_list.size() && i < kMostVisitedCount; ++i) {
557    const history::MostVisitedURL& visited = visited_list[i];
558    if (visited.url.spec().empty())
559      break;  // This is the signal that there are no more real visited sites.
560
561    HistoryItem* item = new HistoryItem();
562    item->title = visited.title;
563    item->url = visited.url;
564
565    AddHistoryItemToMenu(item,
566                         history_menu_,
567                         TAG_MOST_VISITED,
568                         index++);
569  }
570}
571
572void GlobalMenuBarX11::OnBookmarkBarVisibilityChanged() {
573  CommandIDMenuItemMap::iterator it =
574      id_to_menu_item_.find(IDC_SHOW_BOOKMARK_BAR);
575  if (it != id_to_menu_item_.end()) {
576    PrefService* prefs = browser_->profile()->GetPrefs();
577    // Note: Unlike the GTK version, we don't appear to need to do tricks where
578    // we block activation while setting the toggle.
579    menuitem_property_set_int(it->second, kPropertyToggleState,
580                              prefs->GetBoolean(prefs::kShowBookmarkBar));
581  }
582}
583
584int GlobalMenuBarX11::GetIndexOfMenuItemWithTag(DbusmenuMenuitem* menu,
585                                                int tag_id) {
586  GList* childs = menuitem_get_children(menu);
587  int i = 0;
588  for (; childs != NULL; childs = childs->next, i++) {
589    int tag =
590        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(childs->data), kTypeTag));
591    if (tag == tag_id)
592      return i;
593  }
594
595  NOTREACHED();
596  return -1;
597}
598
599void GlobalMenuBarX11::ClearMenuSection(DbusmenuMenuitem* menu, int tag_id) {
600  std::vector<DbusmenuMenuitem*> menuitems_to_delete;
601
602  GList* childs = menuitem_get_children(menu);
603  for (; childs != NULL; childs = childs->next) {
604    DbusmenuMenuitem* current_item = reinterpret_cast<DbusmenuMenuitem*>(
605        childs->data);
606    ClearMenuSection(current_item, tag_id);
607
608    int tag =
609        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(childs->data), kTypeTag));
610    if (tag == tag_id)
611      menuitems_to_delete.push_back(current_item);
612  }
613
614  for (std::vector<DbusmenuMenuitem*>::const_iterator it =
615           menuitems_to_delete.begin(); it != menuitems_to_delete.end(); ++it) {
616    menuitem_child_delete(menu, *it);
617  }
618}
619
620// static
621void GlobalMenuBarX11::DeleteHistoryItem(void* void_item) {
622  HistoryItem* item =
623      reinterpret_cast<GlobalMenuBarX11::HistoryItem*>(void_item);
624  delete item;
625}
626
627void GlobalMenuBarX11::EnabledStateChangedForCommand(int id, bool enabled) {
628  CommandIDMenuItemMap::iterator it = id_to_menu_item_.find(id);
629  if (it != id_to_menu_item_.end())
630    menuitem_property_set_bool(it->second, kPropertyEnabled, enabled);
631}
632
633void GlobalMenuBarX11::Observe(int type,
634                               const content::NotificationSource& source,
635                               const content::NotificationDetails& details) {
636  if (type == chrome::NOTIFICATION_TOP_SITES_CHANGED) {
637    GetTopSitesData();
638  } else {
639    NOTREACHED();
640  }
641}
642
643void GlobalMenuBarX11::TabRestoreServiceChanged(TabRestoreService* service) {
644  const TabRestoreService::Entries& entries = service->entries();
645
646  ClearMenuSection(history_menu_, TAG_RECENTLY_CLOSED);
647
648  // We'll get the index the "Recently Closed" header. (This can vary depending
649  // on the number of "Most Visited" items.
650  int index = GetIndexOfMenuItemWithTag(history_menu_,
651                                        TAG_RECENTLY_CLOSED_HEADER) + 1;
652
653  unsigned int added_count = 0;
654  for (TabRestoreService::Entries::const_iterator it = entries.begin();
655       it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
656    TabRestoreService::Entry* entry = *it;
657
658    if (entry->type == TabRestoreService::WINDOW) {
659      TabRestoreService::Window* entry_win =
660          static_cast<TabRestoreService::Window*>(entry);
661      std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs;
662      if (tabs.empty())
663        continue;
664
665      // Create the item for the parent/window.
666      HistoryItem* item = new HistoryItem();
667      item->session_id = entry_win->id;
668
669      std::string title = item->tabs.size() == 1 ?
670          l10n_util::GetStringUTF8(
671              IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE) :
672          l10n_util::GetStringFUTF8(
673              IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
674              base::IntToString16(item->tabs.size()));
675      DbusmenuMenuitem* parent_item = BuildMenuItem(
676          title, TAG_RECENTLY_CLOSED);
677      menuitem_child_add_position(history_menu_, parent_item, index++);
678      g_object_unref(parent_item);
679
680      // The mac version of this code allows the user to click on the parent
681      // menu item to have the same effect as clicking the restore window
682      // submenu item. GTK+ helpfully activates a menu item when it shows a
683      // submenu so toss that feature out.
684      DbusmenuMenuitem* restore_item = BuildMenuItem(
685          l10n_util::GetStringUTF8(
686              IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX).c_str(),
687          TAG_RECENTLY_CLOSED);
688      g_signal_connect(restore_item, "item-activated",
689                       G_CALLBACK(OnHistoryItemActivatedThunk), this);
690      g_object_set_data_full(G_OBJECT(restore_item), kHistoryItem, item,
691                             DeleteHistoryItem);
692      menuitem_child_append(parent_item, restore_item);
693      g_object_unref(restore_item);
694
695      DbusmenuMenuitem* separator = BuildSeparator();
696      menuitem_child_append(parent_item, separator);
697      g_object_unref(separator);
698
699      // Loop over the window's tabs and add them to the submenu.
700      int subindex = 2;
701      std::vector<TabRestoreService::Tab>::const_iterator iter;
702      for (iter = tabs.begin(); iter != tabs.end(); ++iter) {
703        TabRestoreService::Tab tab = *iter;
704        HistoryItem* tab_item = HistoryItemForTab(tab);
705        item->tabs.push_back(tab_item);
706        AddHistoryItemToMenu(tab_item,
707                             parent_item,
708                             TAG_RECENTLY_CLOSED,
709                             subindex++);
710      }
711
712      ++added_count;
713    } else if (entry->type == TabRestoreService::TAB) {
714      TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
715      HistoryItem* item = HistoryItemForTab(*tab);
716      AddHistoryItemToMenu(item,
717                           history_menu_,
718                           TAG_RECENTLY_CLOSED,
719                           index++);
720      ++added_count;
721    }
722  }
723}
724
725void GlobalMenuBarX11::TabRestoreServiceDestroyed(
726    TabRestoreService* service) {
727  tab_restore_service_ = NULL;
728}
729
730void GlobalMenuBarX11::OnWindowMapped(unsigned long xid) {
731  if (!server_)
732    InitServer(xid);
733
734  GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid);
735}
736
737void GlobalMenuBarX11::OnWindowUnmapped(unsigned long xid) {
738  GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid);
739}
740
741void GlobalMenuBarX11::OnItemActivated(DbusmenuMenuitem* item,
742                                       unsigned int timestamp) {
743  int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "command-id"));
744  chrome::ExecuteCommand(browser_, id);
745}
746
747void GlobalMenuBarX11::OnHistoryItemActivated(DbusmenuMenuitem* sender,
748                                              unsigned int timestamp) {
749  // Note: We don't have access to the event modifiers used to click the menu
750  // item since that happens in a different process.
751  HistoryItem* item = reinterpret_cast<HistoryItem*>(
752      g_object_get_data(G_OBJECT(sender), kHistoryItem));
753
754  // If this item can be restored using TabRestoreService, do so. Otherwise,
755  // just load the URL.
756  TabRestoreService* service =
757      TabRestoreServiceFactory::GetForProfile(profile_);
758  if (item->session_id && service) {
759    service->RestoreEntryById(browser_->tab_restore_service_delegate(),
760                              item->session_id, browser_->host_desktop_type(),
761                              UNKNOWN);
762  } else {
763    DCHECK(item->url.is_valid());
764    browser_->OpenURL(content::OpenURLParams(
765        item->url,
766        content::Referrer(),
767        NEW_FOREGROUND_TAB,
768        content::PAGE_TRANSITION_AUTO_BOOKMARK,
769        false));
770  }
771}
772
773void GlobalMenuBarX11::OnHistoryMenuAboutToShow(DbusmenuMenuitem* item) {
774  if (!tab_restore_service_) {
775    tab_restore_service_ = TabRestoreServiceFactory::GetForProfile(profile_);
776    if (tab_restore_service_) {
777      tab_restore_service_->LoadTabsFromLastSession();
778      tab_restore_service_->AddObserver(this);
779
780      // If LoadTabsFromLastSession doesn't load tabs, it won't call
781      // TabRestoreServiceChanged(). This ensures that all new windows after
782      // the first one will have their menus populated correctly.
783      TabRestoreServiceChanged(tab_restore_service_);
784    }
785  }
786}
787