486 lines
15 KiB
C++
486 lines
15 KiB
C++
/* 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;
|
|
}
|
|
|
|
MOZ_ASSERT(sService == this);
|
|
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);
|
|
}
|
|
}
|