summaryrefslogtreecommitdiffstats
path: root/widget/gtk/nsNativeMenuService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk/nsNativeMenuService.cpp')
-rw-r--r--widget/gtk/nsNativeMenuService.cpp484
1 files changed, 484 insertions, 0 deletions
diff --git a/widget/gtk/nsNativeMenuService.cpp b/widget/gtk/nsNativeMenuService.cpp
new file mode 100644
index 000000000..7b92e73e8
--- /dev/null
+++ b/widget/gtk/nsNativeMenuService.cpp
@@ -0,0 +1,484 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Move.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsGtkUtils.h"
+#include "nsIContent.h"
+#include "nsIWidget.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWindow.h"
+#include "prlink.h"
+
+#include "nsDbusmenu.h"
+#include "nsMenuBar.h"
+#include "nsNativeMenuAtoms.h"
+#include "nsNativeMenuDocListener.h"
+
+#include <glib-object.h>
+#include <pango/pango.h>
+#include <stdlib.h>
+
+#include "nsNativeMenuService.h"
+
+using namespace mozilla;
+
+nsNativeMenuService* nsNativeMenuService::sService = nullptr;
+
+extern PangoLayout* gPangoLayout;
+extern nsNativeMenuDocListenerTArray* gPendingListeners;
+
+static const nsTArray<nsMenuBar* >::index_type NoIndex = nsTArray<nsMenuBar* >::NoIndex;
+
+#if not GLIB_CHECK_VERSION(2,26,0)
+enum GBusType {
+ G_BUS_TYPE_STARTER = -1,
+ G_BUS_TYPE_NONE = 0,
+ G_BUS_TYPE_SYSTEM = 1,
+ G_BUS_TYPE_SESSION = 2
+};
+
+enum GDBusProxyFlags {
+ G_DBUS_PROXY_FLAGS_NONE = 0,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = 1 << 0,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = 1 << 1,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START = 1 << 2,
+ G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES = 1 << 3
+};
+
+enum GDBusCallFlags {
+ G_DBUS_CALL_FLAGS_NONE = 0,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START = 1 << 0
+};
+
+typedef _GDBusInterfaceInfo GDBusInterfaceInfo;
+typedef _GDBusProxy GDBusProxy;
+typedef _GVariant GVariant;
+#endif
+
+#undef g_dbus_proxy_new_for_bus
+#undef g_dbus_proxy_new_for_bus_finish
+#undef g_dbus_proxy_call
+#undef g_dbus_proxy_call_finish
+#undef g_dbus_proxy_get_name_owner
+
+typedef void (*_g_dbus_proxy_new_for_bus_fn)(GBusType, GDBusProxyFlags,
+ GDBusInterfaceInfo*,
+ const gchar*, const gchar*,
+ const gchar*, GCancellable*,
+ GAsyncReadyCallback, gpointer);
+
+typedef GDBusProxy* (*_g_dbus_proxy_new_for_bus_finish_fn)(GAsyncResult*,
+ GError**);
+typedef void (*_g_dbus_proxy_call_fn)(GDBusProxy*, const gchar*, GVariant*,
+ GDBusCallFlags, gint, GCancellable*,
+ GAsyncReadyCallback, gpointer);
+typedef GVariant* (*_g_dbus_proxy_call_finish_fn)(GDBusProxy*, GAsyncResult*,
+ GError**);
+typedef gchar* (*_g_dbus_proxy_get_name_owner_fn)(GDBusProxy*);
+
+static _g_dbus_proxy_new_for_bus_fn _g_dbus_proxy_new_for_bus;
+static _g_dbus_proxy_new_for_bus_finish_fn _g_dbus_proxy_new_for_bus_finish;
+static _g_dbus_proxy_call_fn _g_dbus_proxy_call;
+static _g_dbus_proxy_call_finish_fn _g_dbus_proxy_call_finish;
+static _g_dbus_proxy_get_name_owner_fn _g_dbus_proxy_get_name_owner;
+
+#define g_dbus_proxy_new_for_bus _g_dbus_proxy_new_for_bus
+#define g_dbus_proxy_new_for_bus_finish _g_dbus_proxy_new_for_bus_finish
+#define g_dbus_proxy_call _g_dbus_proxy_call
+#define g_dbus_proxy_call_finish _g_dbus_proxy_call_finish
+#define g_dbus_proxy_get_name_owner _g_dbus_proxy_get_name_owner
+
+static PRLibrary* gGIOLib = nullptr;
+
+static nsresult
+GDBusInit() {
+ gGIOLib = PR_LoadLibrary("libgio-2.0.so.0");
+ if (!gGIOLib) {
+ return NS_ERROR_FAILURE;
+ }
+
+ g_dbus_proxy_new_for_bus = (_g_dbus_proxy_new_for_bus_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus");
+ g_dbus_proxy_new_for_bus_finish = (_g_dbus_proxy_new_for_bus_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus_finish");
+ g_dbus_proxy_call = (_g_dbus_proxy_call_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call");
+ g_dbus_proxy_call_finish = (_g_dbus_proxy_call_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call_finish");
+ g_dbus_proxy_get_name_owner = (_g_dbus_proxy_get_name_owner_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_get_name_owner");
+
+ if (!g_dbus_proxy_new_for_bus ||
+ !g_dbus_proxy_new_for_bus_finish ||
+ !g_dbus_proxy_call ||
+ !g_dbus_proxy_call_finish ||
+ !g_dbus_proxy_get_name_owner) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsNativeMenuService, nsINativeMenuService)
+
+nsNativeMenuService::nsNativeMenuService() :
+ mCreateProxyCancellable(nullptr), mDbusProxy(nullptr), mOnline(false) {
+}
+
+nsNativeMenuService::~nsNativeMenuService() {
+ SetOnline(false);
+
+ if (mCreateProxyCancellable) {
+ g_cancellable_cancel(mCreateProxyCancellable);
+ g_object_unref(mCreateProxyCancellable);
+ mCreateProxyCancellable = nullptr;
+ }
+
+ // Make sure we disconnect map-event handlers
+ while (mMenuBars.Length() > 0) {
+ NotifyNativeMenuBarDestroyed(mMenuBars[0]);
+ }
+
+ Preferences::UnregisterCallback(PrefChangedCallback,
+ "ui.use_global_menubar");
+
+ if (mDbusProxy) {
+ g_signal_handlers_disconnect_by_func(mDbusProxy,
+ FuncToGpointer(name_owner_changed_cb),
+ NULL);
+ g_object_unref(mDbusProxy);
+ }
+
+ if (gPendingListeners) {
+ delete gPendingListeners;
+ gPendingListeners = nullptr;
+ }
+ if (gPangoLayout) {
+ g_object_unref(gPangoLayout);
+ gPangoLayout = nullptr;
+ }
+
+ sService = nullptr;
+}
+
+nsresult
+nsNativeMenuService::Init() {
+ nsresult rv = nsDbusmenuFunctions::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = GDBusInit();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Preferences::RegisterCallback(PrefChangedCallback,
+ "ui.use_global_menubar");
+
+ mCreateProxyCancellable = g_cancellable_new();
+
+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
+ static_cast<GDBusProxyFlags>(
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
+ nullptr,
+ "com.canonical.AppMenu.Registrar",
+ "/com/canonical/AppMenu/Registrar",
+ "com.canonical.AppMenu.Registrar",
+ mCreateProxyCancellable, proxy_created_cb,
+ nullptr);
+
+ /* We don't technically know that the shell will draw the menubar until
+ * we know whether anybody owns the name of the menubar service on the
+ * session bus. However, discovering this happens asynchronously so
+ * we optimize for the common case here by assuming that the shell will
+ * draw window menubars if we are running inside Unity. This should
+ * mean that we avoid temporarily displaying the window menubar ourselves
+ */
+ const char* desktop = getenv("XDG_CURRENT_DESKTOP");
+ if (nsCRT::strcmp(desktop, "Unity") == 0) {
+ SetOnline(true);
+ }
+
+ return NS_OK;
+}
+
+/* static */ void
+nsNativeMenuService::EnsureInitialized() {
+ if (sService) {
+ return;
+ }
+ nsCOMPtr<nsINativeMenuService> service =
+ do_GetService("@mozilla.org/widget/nativemenuservice;1");
+}
+
+void
+nsNativeMenuService::SetOnline(bool aOnline) {
+ if (!Preferences::GetBool("ui.use_global_menubar", true)) {
+ aOnline = false;
+ }
+
+ mOnline = aOnline;
+ if (aOnline) {
+ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) {
+ RegisterNativeMenuBar(mMenuBars[i]);
+ }
+ } else {
+ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) {
+ mMenuBars[i]->Deactivate();
+ }
+ }
+}
+
+void
+nsNativeMenuService::RegisterNativeMenuBar(nsMenuBar* aMenuBar) {
+ if (!mOnline) {
+ return;
+ }
+
+ // This will effectively create the native menubar for
+ // exporting over the session bus, and hide the XUL menubar
+ aMenuBar->Activate();
+
+ if (!mDbusProxy ||
+ !gtk_widget_get_mapped(aMenuBar->TopLevelWindow()) ||
+ mMenuBarRegistrationCancellables.Get(aMenuBar, nullptr)) {
+ // Don't go further if we don't have a proxy for the shell menu
+ // service, the window isn't mapped or there is a request in progress.
+ return;
+ }
+
+ uint32_t xid = aMenuBar->WindowId();
+ nsAdoptingCString path = aMenuBar->ObjectPath();
+ if (xid == 0 || path.IsEmpty()) {
+ NS_WARNING("Menubar has invalid XID or object path");
+ return;
+ }
+
+ GCancellable* cancellable = g_cancellable_new();
+ mMenuBarRegistrationCancellables.Put(aMenuBar, cancellable);
+
+ // We keep a weak ref because we can't assume that GDBus cancellation
+ // is reliable (see https://launchpad.net/bugs/953562)
+
+ g_dbus_proxy_call(mDbusProxy, "RegisterWindow",
+ g_variant_new("(uo)", xid, path.get()),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ cancellable,
+ register_native_menubar_cb, aMenuBar);
+}
+
+/* static */ void
+nsNativeMenuService::name_owner_changed_cb(GObject* gobject,
+ GParamSpec* pspec,
+ gpointer user_data) {
+ nsNativeMenuService::GetSingleton()->OnNameOwnerChanged();
+}
+
+/* static */ void
+nsNativeMenuService::proxy_created_cb(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data) {
+ GError* error = nullptr;
+ GDBusProxy* proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
+ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free(error);
+ return;
+ }
+
+ if (error) {
+ g_error_free(error);
+ }
+
+ // We need this check because we can't assume that GDBus cancellation
+ // is reliable (see https://launchpad.net/bugs/953562)
+ nsNativeMenuService* self = nsNativeMenuService::GetSingleton();
+ if (!self) {
+ if (proxy) {
+ g_object_unref(proxy);
+ }
+ return;
+ }
+
+ self->OnProxyCreated(proxy);
+}
+
+/* static */ void
+nsNativeMenuService::register_native_menubar_cb(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data) {
+ nsMenuBar* menuBar = static_cast<nsMenuBar* >(user_data);
+
+ GError* error = nullptr;
+ GVariant* results = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object),
+ res, &error);
+ if (results) {
+ // There's nothing useful in the response
+ g_variant_unref(results);
+ }
+
+ bool success = error ? false : true;
+ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free(error);
+ return;
+ }
+
+ if (error) {
+ g_error_free(error);
+ }
+
+ nsNativeMenuService* self = nsNativeMenuService::GetSingleton();
+ if (!self) {
+ return;
+ }
+
+ self->OnNativeMenuBarRegistered(menuBar, success);
+}
+
+/* static */ gboolean
+nsNativeMenuService::map_event_cb(GtkWidget* widget,
+ GdkEvent* event,
+ gpointer user_data) {
+ nsMenuBar* menubar = static_cast<nsMenuBar* >(user_data);
+ nsNativeMenuService::GetSingleton()->RegisterNativeMenuBar(menubar);
+
+ return FALSE;
+}
+
+void
+nsNativeMenuService::OnNameOwnerChanged() {
+ char* owner = g_dbus_proxy_get_name_owner(mDbusProxy);
+ SetOnline(owner ? true : false);
+ g_free(owner);
+}
+
+void
+nsNativeMenuService::OnProxyCreated(GDBusProxy* aProxy) {
+ mDbusProxy = aProxy;
+
+ g_object_unref(mCreateProxyCancellable);
+ mCreateProxyCancellable = nullptr;
+
+ if (!mDbusProxy) {
+ SetOnline(false);
+ return;
+ }
+
+ g_signal_connect(mDbusProxy, "notify::g-name-owner",
+ G_CALLBACK(name_owner_changed_cb), nullptr);
+
+ OnNameOwnerChanged();
+}
+
+void
+nsNativeMenuService::OnNativeMenuBarRegistered(nsMenuBar* aMenuBar,
+ bool aSuccess) {
+ // Don't assume that GDBus cancellation is reliable (ie, |aMenuBar| might
+ // have already been deleted (see https://launchpad.net/bugs/953562)
+ GCancellable* cancellable = nullptr;
+ if (!mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) {
+ return;
+ }
+
+ g_object_unref(cancellable);
+ mMenuBarRegistrationCancellables.Remove(aMenuBar);
+
+ if (!aSuccess) {
+ aMenuBar->Deactivate();
+ }
+}
+
+/* static */ void
+nsNativeMenuService::PrefChangedCallback(const char* aPref,
+ void* aClosure) {
+ nsNativeMenuService::GetSingleton()->PrefChanged();
+}
+
+void
+nsNativeMenuService::PrefChanged() {
+ if (!mDbusProxy) {
+ SetOnline(false);
+ return;
+ }
+
+ OnNameOwnerChanged();
+}
+
+NS_IMETHODIMP
+nsNativeMenuService::CreateNativeMenuBar(nsIWidget* aParent,
+ nsIContent* aMenuBarNode) {
+ NS_ENSURE_ARG(aParent);
+ NS_ENSURE_ARG(aMenuBarNode);
+
+ if (aMenuBarNode->AttrValueIs(kNameSpaceID_None,
+ nsNativeMenuAtoms::_moz_menubarkeeplocal,
+ nsGkAtoms::_true,
+ eCaseMatters)) {
+ return NS_OK;
+ }
+
+ UniquePtr<nsMenuBar> menubar(nsMenuBar::Create(aParent, aMenuBarNode));
+ if (!menubar) {
+ NS_WARNING("Failed to create menubar");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Unity forgets our window if it is unmapped by the application, which
+ // happens with some extensions that add "minimize to tray" type
+ // functionality. We hook on to the MapNotify event to re-register our menu
+ // with Unity
+ g_signal_connect(G_OBJECT(menubar->TopLevelWindow()),
+ "map-event", G_CALLBACK(map_event_cb),
+ menubar.get());
+
+ mMenuBars.AppendElement(menubar.get());
+ RegisterNativeMenuBar(menubar.get());
+
+ static_cast<nsWindow* >(aParent)->SetMenuBar(Move(menubar));
+
+ return NS_OK;
+}
+
+/* static */ already_AddRefed<nsNativeMenuService>
+nsNativeMenuService::GetInstanceForServiceManager() {
+ RefPtr<nsNativeMenuService> service(sService);
+
+ if (service) {
+ return service.forget();
+ }
+
+ service = new nsNativeMenuService();
+
+ if (NS_FAILED(service->Init())) {
+ return nullptr;
+ }
+
+ sService = service.get();
+ return service.forget();
+}
+
+/* static */ nsNativeMenuService*
+nsNativeMenuService::GetSingleton() {
+ EnsureInitialized();
+ return sService;
+}
+
+void
+nsNativeMenuService::NotifyNativeMenuBarDestroyed(nsMenuBar* aMenuBar) {
+ g_signal_handlers_disconnect_by_func(aMenuBar->TopLevelWindow(),
+ FuncToGpointer(map_event_cb),
+ aMenuBar);
+
+ mMenuBars.RemoveElement(aMenuBar);
+
+ GCancellable* cancellable = nullptr;
+ if (mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) {
+ mMenuBarRegistrationCancellables.Remove(aMenuBar);
+ g_cancellable_cancel(cancellable);
+ g_object_unref(cancellable);
+ }
+}