зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1353652 - Initial Draft of MPRIS API Provider (Media API on Linux) r=alwu
Differential Revision: https://phabricator.services.mozilla.com/D47999 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
2731ff1336
Коммит
1eb3d684e4
|
@ -59,6 +59,9 @@ tools/clang-tidy/test/.*
|
|||
# We are testing the incorrect formatting.
|
||||
tools/lint/test/files/file-whitespace/
|
||||
|
||||
# Contains an XML definition and formatting would break the layout
|
||||
widget/gtk/MPRISInterfaceDescription.h
|
||||
|
||||
# The XPTCall stubs files have some inline assembly macros
|
||||
# that get reformatted badly. See bug 1510781.
|
||||
xpcom/reflect/xptcall/md/win32/.*
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
*
|
||||
* 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/. */
|
||||
|
||||
#ifndef WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
|
||||
#define WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
extern const gchar introspection_xml[] =
|
||||
// adopted from https://github.com/freedesktop/mpris-spec/blob/master/spec/org.mpris.MediaPlayer2.xml
|
||||
// everything starting with tp can be removed, as it is used for HTML Spec Documentation Generation
|
||||
"<node>"
|
||||
"<interface name=\"org.mpris.MediaPlayer2\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"<method name=\"Raise\"/>"
|
||||
"<method name=\"Quit\"/>"
|
||||
"<property name=\"CanQuit\" type=\"b\" access=\"read\"/>"
|
||||
#ifdef MPRIS_FULLSCREEN
|
||||
"<property name=\"Fullscreen\" type=\"b\" access=\"readwrite\">"
|
||||
"<annotation name=\"org.mpris.MediaPlayer2.property.optional\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"CanSetFullscreen\" type=\"b\" access=\"read\">"
|
||||
"<annotation name=\"org.mpris.MediaPlayer2.property.optional\" value=\"true\"/>"
|
||||
"</property>"
|
||||
#endif
|
||||
"<property name=\"CanRaise\" type=\"b\" access=\"read\"/>"
|
||||
"<property name=\"HasTrackList\" type=\"b\" access=\"read\"/>"
|
||||
"<property name=\"Identity\" type=\"s\" access=\"read\"/>"
|
||||
#ifdef MPRIS_DESKTOP_ENTRY
|
||||
"<property name=\"DesktopEntry\" type=\"s\" access=\"read\">"
|
||||
"<annotation name=\"org.mpris.MediaPlayer2.property.optional\" value=\"true\"/>"
|
||||
"</property>"
|
||||
#endif
|
||||
"<property name=\"SupportedUriSchemes\" type=\"as\" access=\"read\"/>"
|
||||
"<property name=\"SupportedMimeTypes\" type=\"as\" access=\"read\"/>"
|
||||
"</interface>"
|
||||
// Note that every property emits a changed signal (which is default) apart from Position.
|
||||
"<interface name=\"org.mpris.MediaPlayer2.Player\">"
|
||||
"<method name=\"Next\"/>"
|
||||
"<method name=\"Previous\"/>"
|
||||
"<method name=\"Pause\"/>"
|
||||
"<method name=\"PlayPause\"/>"
|
||||
"<method name=\"Stop\"/>"
|
||||
"<method name=\"Play\"/>"
|
||||
"<method name=\"Seek\">"
|
||||
"<arg direction=\"in\" type=\"x\" name=\"Offset\"/>"
|
||||
"</method>"
|
||||
"<method name=\"SetPosition\">"
|
||||
"<arg direction=\"in\" type=\"o\" name=\"TrackId\"/>"
|
||||
"<arg direction=\"in\" type=\"x\" name=\"Position\"/>"
|
||||
"</method>"
|
||||
"<method name=\"OpenUri\">"
|
||||
"<arg direction=\"in\" type=\"s\" name=\"Uri\"/>"
|
||||
"</method>"
|
||||
"<property name=\"PlaybackStatus\" type=\"s\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
#ifdef MPRIS_LOOP_STATUS
|
||||
"<property name=\"LoopStatus\" type=\"s\" access=\"readwrite\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"<annotation name=\"org.mpris.MediaPlayer2.property.optional\" value=\"true\"/>"
|
||||
"</property>"
|
||||
#endif
|
||||
"<property name=\"Rate\" type=\"d\" access=\"readwrite\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
#ifdef MRPIS_SHUFFLE
|
||||
"<property name=\"Shuffle\" type=\"b\" access=\"readwrite\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"<annotation name=\"org.mpris.MediaPlayer2.property.optional\" value=\"true\"/>"
|
||||
"</property>"
|
||||
#endif
|
||||
"<property name=\"Metadata\" type=\"a{sv}\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"Volume\" type=\"d\" access=\"readwrite\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"Position\" type=\"x\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>"
|
||||
"</property>"
|
||||
"<property name=\"MinimumRate\" type=\"d\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"MaximumRate\" type=\"d\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"CanGoNext\" type=\"b\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"CanGoPrevious\" type=\"b\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"CanPlay\" type=\"b\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"CanPause\" type=\"b\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"CanSeek\" type=\"b\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
||||
"</property>"
|
||||
"<property name=\"CanControl\" type=\"b\" access=\"read\">"
|
||||
"<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>"
|
||||
"</property>"
|
||||
"<signal name=\"Seeked\">"
|
||||
"<arg name=\"Position\" type=\"x\"/>"
|
||||
"</signal>"
|
||||
"</interface>"
|
||||
"</node>";
|
||||
|
||||
#endif // WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
|
|
@ -0,0 +1,653 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
*
|
||||
* 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 "MPRISServiceHandler.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "MPRISInterfaceDescription.h"
|
||||
#include "mozilla/dom/MediaControlUtils.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
// avoid redefined macro in unified build
|
||||
#undef LOG
|
||||
#define LOG(msg, ...) \
|
||||
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
|
||||
("MPRISServiceHandler=%p, " msg, this, ##__VA_ARGS__))
|
||||
|
||||
namespace mozilla {
|
||||
namespace widget {
|
||||
|
||||
enum class Method : uint8_t {
|
||||
eQuit,
|
||||
eRaise,
|
||||
eNext,
|
||||
ePrevious,
|
||||
ePause,
|
||||
ePlayPause,
|
||||
eStop,
|
||||
ePlay,
|
||||
eSeek,
|
||||
eSetPosition,
|
||||
eOpenUri,
|
||||
eUnknown
|
||||
};
|
||||
|
||||
static inline Method GetMethod(const gchar* aMethodName) {
|
||||
const std::unordered_map<std::string, Method> map = {
|
||||
{"Quit", Method::eQuit}, {"Raise", Method::eRaise},
|
||||
{"Next", Method::eNext}, {"Previous", Method::ePrevious},
|
||||
{"Pause", Method::ePause}, {"PlayPause", Method::ePlayPause},
|
||||
{"Stop", Method::eStop}, {"Play", Method::ePlay},
|
||||
{"Seek", Method::eSeek}, {"SetPosition", Method::eSetPosition},
|
||||
{"OpenUri", Method::eOpenUri}};
|
||||
|
||||
auto it = map.find(aMethodName);
|
||||
return (it == map.end() ? Method::eUnknown : it->second);
|
||||
}
|
||||
|
||||
static void HandleMethodCall(GDBusConnection* aConnection, const gchar* aSender,
|
||||
const gchar* aObjectPath,
|
||||
const gchar* aInterfaceName,
|
||||
const gchar* aMethodName, GVariant* aParameters,
|
||||
GDBusMethodInvocation* aInvocation,
|
||||
gpointer aUserData) {
|
||||
MOZ_ASSERT(aUserData);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
|
||||
std::string error;
|
||||
|
||||
switch (GetMethod(aMethodName)) {
|
||||
case Method::eUnknown:
|
||||
g_dbus_method_invocation_return_error(
|
||||
aInvocation, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid Method");
|
||||
return;
|
||||
case Method::eQuit:
|
||||
if (handler->CanQuit()) {
|
||||
handler->Quit();
|
||||
} else {
|
||||
error = "Cannot invoke Quit() when CanQuit() returns false";
|
||||
}
|
||||
break;
|
||||
case Method::eRaise:
|
||||
if (handler->CanRaise()) {
|
||||
handler->Raise();
|
||||
} else {
|
||||
error = "Cannot invoke Raise() when CanRaise() returns false";
|
||||
}
|
||||
break;
|
||||
case Method::eNext:
|
||||
if (handler->CanGoNext()) {
|
||||
handler->Next();
|
||||
} else {
|
||||
error = "Cannot invoke Next() when CanGoNext() returns false";
|
||||
}
|
||||
break;
|
||||
case Method::ePrevious:
|
||||
if (handler->CanGoPrevious()) {
|
||||
handler->Previous();
|
||||
} else {
|
||||
error = "Cannot invoke Previous() when CanGoPrevious() returns false";
|
||||
}
|
||||
break;
|
||||
case Method::ePause:
|
||||
if (handler->CanPause()) {
|
||||
handler->Pause();
|
||||
} else {
|
||||
error = "Cannot invoke Pause() when CanPause() returns false";
|
||||
}
|
||||
break;
|
||||
case Method::ePlayPause:
|
||||
// According to Spec this should only fail if canPause is false, but Play
|
||||
// may be forbidden due to CanPlay. This means in theory even though
|
||||
// CanPlay is false, this method would be able to Play something which
|
||||
// means when CanPause is false, CanPlay _has to be_ false as well.
|
||||
if (handler->CanPlay() && handler->CanPause()) {
|
||||
handler->PlayPause();
|
||||
} else {
|
||||
error =
|
||||
"Cannot invoke PlayPause() when either CanPlay() or CanPause() "
|
||||
"returns false";
|
||||
}
|
||||
break;
|
||||
case Method::eStop:
|
||||
handler->Stop(); // Stop is mandatory
|
||||
break;
|
||||
case Method::ePlay:
|
||||
if (handler->CanPlay()) {
|
||||
handler->Play();
|
||||
} else {
|
||||
error = "Cannot invoke Play() when CanPlay() returns false";
|
||||
}
|
||||
break;
|
||||
case Method::eSeek:
|
||||
if (handler->CanSeek()) {
|
||||
gint64 position;
|
||||
g_variant_get(aParameters, "(x)", &position);
|
||||
handler->Seek(position);
|
||||
} else {
|
||||
error = "Cannot invoke Seek() when CanSeek() returns false";
|
||||
}
|
||||
break;
|
||||
case Method::eSetPosition:
|
||||
if (handler->CanSeek()) {
|
||||
gchar* trackId;
|
||||
gint64 position;
|
||||
g_variant_get(aParameters, "(ox)", &trackId, &position);
|
||||
handler->SetPosition(trackId, position);
|
||||
} else {
|
||||
error = "Cannot invoke SetPosition() when CanSeek() returns false";
|
||||
}
|
||||
break;
|
||||
case Method::eOpenUri:
|
||||
gchar* uri;
|
||||
g_variant_get(aParameters, "(s)", &uri);
|
||||
if (!handler->OpenUri(uri)) {
|
||||
error = "Could not open URI";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!error.empty()) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
aInvocation, G_IO_ERROR, G_IO_ERROR_READ_ONLY, "%s", error.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
enum class Property : uint8_t {
|
||||
eIdentity,
|
||||
eHasTrackList,
|
||||
eCanRaise,
|
||||
eCanQuit,
|
||||
eSupportedUriSchemes,
|
||||
eSupportedMimeTypes,
|
||||
eCanGoNext,
|
||||
eCanGoPrevious,
|
||||
eCanPlay,
|
||||
eCanPause,
|
||||
eCanSeek,
|
||||
eCanControl,
|
||||
eGetVolume,
|
||||
eGetPosition,
|
||||
eGetMinimumRate,
|
||||
eGetMaximumRate,
|
||||
eGetRate,
|
||||
eGetPlaybackStatus,
|
||||
eGetMetadata,
|
||||
eUnknown
|
||||
};
|
||||
|
||||
static inline Property GetProperty(const gchar* aPropertyName) {
|
||||
const std::unordered_map<std::string, Property> map = {
|
||||
{"Identity", Property::eIdentity},
|
||||
{"HasTrackList", Property::eHasTrackList},
|
||||
{"CanRaise", Property::eCanRaise},
|
||||
{"CanQuit", Property::eCanQuit},
|
||||
{"SupportedUriSchemes", Property::eSupportedUriSchemes},
|
||||
{"SupportedMimeTypes", Property::eSupportedMimeTypes},
|
||||
{"CanGoNext", Property::eCanGoNext},
|
||||
{"CanGoPrevious", Property::eCanGoPrevious},
|
||||
{"CanPlay", Property::eCanPlay},
|
||||
{"CanPause", Property::eCanPause},
|
||||
{"CanSeek", Property::eCanSeek},
|
||||
{"CanControl", Property::eCanControl},
|
||||
{"Volume", Property::eGetVolume},
|
||||
{"Position", Property::eGetPosition},
|
||||
{"MinimumRate", Property::eGetMinimumRate},
|
||||
{"MaximumRate", Property::eGetMaximumRate},
|
||||
{"Rate", Property::eGetRate},
|
||||
{"PlaybackStatus", Property::eGetPlaybackStatus},
|
||||
{"Metadata", Property::eGetMetadata}};
|
||||
|
||||
auto it = map.find(aPropertyName);
|
||||
return (it == map.end() ? Property::eUnknown : it->second);
|
||||
}
|
||||
|
||||
static GVariant* HandleGetProperty(GDBusConnection* aConnection,
|
||||
const gchar* aSender,
|
||||
const gchar* aObjectPath,
|
||||
const gchar* aInterfaceName,
|
||||
const gchar* aPropertyName, GError** aError,
|
||||
gpointer aUserData) {
|
||||
MOZ_ASSERT(aUserData);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
|
||||
|
||||
switch (GetProperty(aPropertyName)) {
|
||||
case Property::eUnknown:
|
||||
g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown Property");
|
||||
return nullptr;
|
||||
case Property::eIdentity:
|
||||
return g_variant_new_string(handler->Identity());
|
||||
case Property::eHasTrackList:
|
||||
return g_variant_new_boolean(handler->HasTrackList());
|
||||
case Property::eCanRaise:
|
||||
return g_variant_new_boolean(handler->CanRaise());
|
||||
case Property::eCanQuit:
|
||||
return g_variant_new_boolean(handler->CanQuit());
|
||||
case Property::eSupportedUriSchemes:
|
||||
return handler->SupportedUriSchemes();
|
||||
case Property::eSupportedMimeTypes:
|
||||
return handler->SupportedMimeTypes();
|
||||
case Property::eCanGoNext:
|
||||
return g_variant_new_boolean(handler->CanGoNext());
|
||||
case Property::eCanGoPrevious:
|
||||
return g_variant_new_boolean(handler->CanGoPrevious());
|
||||
case Property::eCanPlay:
|
||||
return g_variant_new_boolean(handler->CanPlay());
|
||||
case Property::eCanPause:
|
||||
return g_variant_new_boolean(handler->CanPause());
|
||||
case Property::eCanSeek:
|
||||
return g_variant_new_boolean(handler->CanSeek());
|
||||
case Property::eCanControl:
|
||||
return g_variant_new_boolean(handler->CanControl());
|
||||
case Property::eGetVolume:
|
||||
return g_variant_new_double(handler->GetVolume());
|
||||
case Property::eGetPosition:
|
||||
return g_variant_new_int64(handler->GetPosition());
|
||||
case Property::eGetMinimumRate:
|
||||
return g_variant_new_double(handler->GetMinimumRate());
|
||||
case Property::eGetMaximumRate:
|
||||
return g_variant_new_double(handler->GetMaximumRate());
|
||||
case Property::eGetRate:
|
||||
return g_variant_new_double(handler->GetRate());
|
||||
case Property::eGetPlaybackStatus:
|
||||
if (GVariant* state = handler->GetPlaybackStatus()) {
|
||||
return state;
|
||||
}
|
||||
g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Invalid Playback Status");
|
||||
return nullptr;
|
||||
case Property::eGetMetadata:
|
||||
std::vector<struct MPRISMetadata> list = handler->GetDefaultMetadata();
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
|
||||
for (auto const& data : list) {
|
||||
g_variant_builder_add(&builder, "{sv}", data.mKey, data.mValue);
|
||||
}
|
||||
return g_variant_builder_end(&builder);
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("Switch Statement incomplete");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static gboolean HandleSetProperty(GDBusConnection* aConnection,
|
||||
const gchar* aSender,
|
||||
const gchar* aObjectPath,
|
||||
const gchar* aInterfaceName,
|
||||
const gchar* aPropertyName, GVariant* aValue,
|
||||
GError** aError, gpointer aUserData) {
|
||||
MOZ_ASSERT(aUserData);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
|
||||
|
||||
if (g_strcmp0(aPropertyName, "Volume") == 0) {
|
||||
if (!handler->SetVolume(g_variant_get_double(aValue))) {
|
||||
g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Could not set the Volume");
|
||||
return false;
|
||||
}
|
||||
} else if (g_strcmp0(aPropertyName, "Rate") == 0) {
|
||||
if (!handler->SetRate(g_variant_get_double(aValue))) {
|
||||
g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Could not set the Rate");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown Property");
|
||||
return false;
|
||||
}
|
||||
|
||||
GVariantBuilder
|
||||
propertiesBuilder; // a builder for the list of changed properties
|
||||
g_variant_builder_init(&propertiesBuilder, G_VARIANT_TYPE_VARDICT);
|
||||
g_variant_builder_add(&propertiesBuilder, "{sv}", aPropertyName, aValue);
|
||||
|
||||
return g_dbus_connection_emit_signal(
|
||||
aConnection, nullptr, aObjectPath, "org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
g_variant_new("(sa{sv}as)", aInterfaceName, &propertiesBuilder, nullptr),
|
||||
aError);
|
||||
}
|
||||
|
||||
static const GDBusInterfaceVTable gInterfaceVTable = {
|
||||
HandleMethodCall, HandleGetProperty, HandleSetProperty};
|
||||
|
||||
void MPRISServiceHandler::OnNameAcquiredStatic(GDBusConnection* aConnection,
|
||||
const gchar* aName,
|
||||
gpointer aUserData) {
|
||||
MOZ_ASSERT(aUserData);
|
||||
static_cast<MPRISServiceHandler*>(aUserData)->OnNameAcquired(aConnection,
|
||||
aName);
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::OnNameLostStatic(GDBusConnection* aConnection,
|
||||
const gchar* aName,
|
||||
gpointer aUserData) {
|
||||
MOZ_ASSERT(aUserData);
|
||||
static_cast<MPRISServiceHandler*>(aUserData)->OnNameLost(aConnection, aName);
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::OnBusAcquiredStatic(GDBusConnection* aConnection,
|
||||
const gchar* aName,
|
||||
gpointer aUserData) {
|
||||
MOZ_ASSERT(aUserData);
|
||||
static_cast<MPRISServiceHandler*>(aUserData)->OnBusAcquired(aConnection,
|
||||
aName);
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::OnNameAcquired(GDBusConnection* aConnection,
|
||||
const gchar* aName) {
|
||||
LOG("OnNameAcquired: %s", aName);
|
||||
mConnection = aConnection;
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::OnNameLost(GDBusConnection* aConnection,
|
||||
const gchar* aName) {
|
||||
LOG("OnNameLost: %s", aName);
|
||||
mConnection = nullptr;
|
||||
if (!mRootRegistrationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_dbus_connection_unregister_object(aConnection, mRootRegistrationId)) {
|
||||
mRootRegistrationId = 0;
|
||||
} else {
|
||||
// Note: Most code examples in the internet probably dont't even check the
|
||||
// result here, but
|
||||
// according to the spec it _can_ return false.
|
||||
LOG("Unable to unregister root object from within onNameLost!");
|
||||
}
|
||||
|
||||
if (!mPlayerRegistrationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_dbus_connection_unregister_object(aConnection, mPlayerRegistrationId)) {
|
||||
mPlayerRegistrationId = 0;
|
||||
} else {
|
||||
// Note: Most code examples in the internet probably dont't even check the
|
||||
// result here, but
|
||||
// according to the spec it _can_ return false.
|
||||
LOG("Unable to unregister object from within onNameLost!");
|
||||
}
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::OnBusAcquired(GDBusConnection* aConnection,
|
||||
const gchar* aName) {
|
||||
GError* error = nullptr;
|
||||
LOG("OnBusAcquired: %s", aName);
|
||||
|
||||
mRootRegistrationId = g_dbus_connection_register_object(
|
||||
aConnection, DBUS_MPRIS_OBJECT_PATH, mIntrospectionData->interfaces[0],
|
||||
&gInterfaceVTable, this, /* user_data */
|
||||
nullptr, /* user_data_free_func */
|
||||
&error); /* GError** */
|
||||
|
||||
if (mRootRegistrationId == 0) {
|
||||
LOG("Failed at root registration: %s",
|
||||
error ? error->message : "Unknown Error");
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mPlayerRegistrationId = g_dbus_connection_register_object(
|
||||
aConnection, DBUS_MPRIS_OBJECT_PATH, mIntrospectionData->interfaces[1],
|
||||
&gInterfaceVTable, this, /* user_data */
|
||||
nullptr, /* user_data_free_func */
|
||||
&error); /* GError** */
|
||||
|
||||
if (mPlayerRegistrationId == 0) {
|
||||
LOG("Failed at object registration %s",
|
||||
error ? error->message : "Unknown Error");
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MPRISServiceHandler::Open() {
|
||||
MOZ_ASSERT(!mInitialized);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
GError* error = nullptr;
|
||||
gchar serviceName[256];
|
||||
SprintfLiteral(serviceName, DBUS_MRPIS_SERVICE_NAME ".instance%d", getpid());
|
||||
mOwnerId =
|
||||
g_bus_own_name(G_BUS_TYPE_SESSION, serviceName,
|
||||
// Enter a waiting queue until this service name is free
|
||||
// (likely another FF instance is running/has been crashed)
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE, OnBusAcquiredStatic,
|
||||
OnNameAcquiredStatic, OnNameLostStatic, this, nullptr);
|
||||
|
||||
/* parse introspection data */
|
||||
mIntrospectionData = g_dbus_node_info_new_for_xml(introspection_xml, &error);
|
||||
|
||||
if (!mIntrospectionData) {
|
||||
LOG("Failed at parsing XML Interface definition %s",
|
||||
error ? error->message : "Unknown Error");
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
MPRISServiceHandler::~MPRISServiceHandler() {
|
||||
MOZ_ASSERT(!mInitialized); // Close hasn't been called!
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::Close() {
|
||||
gchar serviceName[256];
|
||||
SprintfLiteral(serviceName, DBUS_MRPIS_SERVICE_NAME ".instance%" PRId32,
|
||||
getpid());
|
||||
|
||||
OnNameLost(mConnection, serviceName);
|
||||
|
||||
if (mOwnerId != 0) {
|
||||
g_bus_unown_name(mOwnerId);
|
||||
}
|
||||
if (mIntrospectionData) {
|
||||
g_dbus_node_info_unref(mIntrospectionData);
|
||||
}
|
||||
|
||||
mInitialized = false;
|
||||
MediaControlKeysEventSource::Close();
|
||||
}
|
||||
|
||||
bool MPRISServiceHandler::IsOpened() const { return mInitialized; }
|
||||
|
||||
bool MPRISServiceHandler::HasTrackList() { return false; }
|
||||
|
||||
const char* MPRISServiceHandler::Identity() { return "Mozilla Firefox"; }
|
||||
|
||||
GVariant* MPRISServiceHandler::SupportedUriSchemes() {
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("as"));
|
||||
return g_variant_builder_end(&builder);
|
||||
}
|
||||
|
||||
GVariant* MPRISServiceHandler::SupportedMimeTypes() {
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("as"));
|
||||
return g_variant_builder_end(&builder);
|
||||
}
|
||||
|
||||
constexpr bool MPRISServiceHandler::CanRaise() { return false; }
|
||||
|
||||
void MPRISServiceHandler::Raise() {
|
||||
MOZ_ASSERT_UNREACHABLE("CanRaise is false, this method is not implemented");
|
||||
}
|
||||
|
||||
constexpr bool MPRISServiceHandler::CanQuit() { return false; }
|
||||
|
||||
void MPRISServiceHandler::Quit() {
|
||||
MOZ_ASSERT_UNREACHABLE("CanQuit is false, this method is not implemented");
|
||||
}
|
||||
|
||||
bool MPRISServiceHandler::CanGoNext() const { return true; }
|
||||
|
||||
bool MPRISServiceHandler::CanGoPrevious() const { return true; }
|
||||
|
||||
bool MPRISServiceHandler::CanPlay() const { return true; }
|
||||
|
||||
bool MPRISServiceHandler::CanPause() const { return true; }
|
||||
|
||||
// We don't support Seeking or Setting/Getting the Position yet
|
||||
bool MPRISServiceHandler::CanSeek() const { return false; }
|
||||
|
||||
bool MPRISServiceHandler::CanControl() const {
|
||||
return true; // we don't support LoopStatus, Shuffle, Rate or Volume, but at
|
||||
// least KDE blocks Play/Pause when CanControl is false.
|
||||
}
|
||||
|
||||
// We don't support getting the volume (yet) so return a dummy value.
|
||||
double MPRISServiceHandler::GetVolume() const { return 1.0f; }
|
||||
|
||||
// we don't support setting the volume yet, so this is a no-op
|
||||
bool MPRISServiceHandler::SetVolume(double aVolume) {
|
||||
if (aVolume > 1.0f || aVolume < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
LOG("Volume set to %f", aVolume);
|
||||
return true;
|
||||
}
|
||||
int64_t MPRISServiceHandler::GetPosition() const { return 0; }
|
||||
|
||||
constexpr double MPRISServiceHandler::GetMinimumRate() { return 1.0f; }
|
||||
|
||||
constexpr double MPRISServiceHandler::GetMaximumRate() { return 1.0f; }
|
||||
|
||||
// Getting and Setting the Rate doesn't work yet, so it will be locked to 1.0
|
||||
double MPRISServiceHandler::GetRate() const { return 1.0f; }
|
||||
|
||||
bool MPRISServiceHandler::SetRate(double aRate) {
|
||||
if (aRate > GetMaximumRate() || aRate < GetMinimumRate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG("Set Playback Rate to %f", aRate);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::SetPlaybackState(dom::PlaybackState aState) {
|
||||
LOG("SetPlaybackState");
|
||||
if (mPlaybackState == aState) {
|
||||
return;
|
||||
}
|
||||
|
||||
MediaControlKeysEventSource::SetPlaybackState(aState);
|
||||
|
||||
if (!mConnection) {
|
||||
return; // No D-Bus Connection, no event
|
||||
}
|
||||
|
||||
GVariant* state = GetPlaybackStatus();
|
||||
if (!state) {
|
||||
return; // Invalid state
|
||||
}
|
||||
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
|
||||
g_variant_builder_add(&builder, "{sv}", "PlaybackStatus", state);
|
||||
g_dbus_connection_emit_signal(
|
||||
mConnection, nullptr, DBUS_MPRIS_OBJECT_PATH,
|
||||
"org.freedesktop.DBus.Properties", "PropertiesChanged",
|
||||
g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2", &builder, nullptr),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
GVariant* MPRISServiceHandler::GetPlaybackStatus() const {
|
||||
switch (GetPlaybackState()) {
|
||||
case dom::PlaybackState::ePlaying:
|
||||
return g_variant_new_string("Playing");
|
||||
case dom::PlaybackState::ePaused:
|
||||
return g_variant_new_string("Paused");
|
||||
case dom::PlaybackState::eStopped:
|
||||
return g_variant_new_string("Stopped");
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Invalid Playback State");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::EmitEvent(mozilla::dom::MediaControlKeysEvent event) {
|
||||
for (auto& listener : mListeners) {
|
||||
listener->OnKeyPressed(event);
|
||||
}
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::Next() {
|
||||
LOG("Next");
|
||||
EmitEvent(mozilla::dom::MediaControlKeysEvent::eNextTrack);
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::Previous() {
|
||||
LOG("Previous");
|
||||
EmitEvent(mozilla::dom::MediaControlKeysEvent::ePrevTrack);
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::Pause() {
|
||||
LOG("Pause");
|
||||
EmitEvent(mozilla::dom::MediaControlKeysEvent::ePause);
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::PlayPause() {
|
||||
LOG("PlayPause");
|
||||
EmitEvent(mozilla::dom::MediaControlKeysEvent::ePlayPause);
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::Stop() {
|
||||
LOG("Stop");
|
||||
EmitEvent(mozilla::dom::MediaControlKeysEvent::eStop);
|
||||
}
|
||||
|
||||
void MPRISServiceHandler::Play() {
|
||||
LOG("Play");
|
||||
EmitEvent(mozilla::dom::MediaControlKeysEvent::ePlay);
|
||||
}
|
||||
|
||||
// Caution, Seek can really be negative, like -1000000 during testing
|
||||
void MPRISServiceHandler::Seek(int64_t aOffset) {
|
||||
LOG("Seek(%" PRId64 ")", aOffset);
|
||||
}
|
||||
|
||||
// The following two methods are untested as my Desktop Widget didn't issue
|
||||
// these calls.
|
||||
void MPRISServiceHandler::SetPosition(char* aTrackId, int64_t aPosition) {
|
||||
LOG("SetPosition(%s, %" PRId64 ")", aTrackId, aPosition);
|
||||
}
|
||||
|
||||
bool MPRISServiceHandler::OpenUri(char* aUri) {
|
||||
LOG("OpenUri(%s)", aUri);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<struct MPRISMetadata> MPRISServiceHandler::GetDefaultMetadata() {
|
||||
std::vector<struct MPRISMetadata> list;
|
||||
|
||||
list.push_back({"mpris:trackid", g_variant_new("o", "/valid/path")});
|
||||
list.push_back({"xesam:title", g_variant_new_string("Firefox")});
|
||||
|
||||
GVariantBuilder artistBuilder; // Artists is a list.
|
||||
g_variant_builder_init(&artistBuilder, G_VARIANT_TYPE("as"));
|
||||
g_variant_builder_add(&artistBuilder, "s", "Mozilla");
|
||||
GVariant* artists = g_variant_builder_end(&artistBuilder);
|
||||
|
||||
list.push_back({"xesam:artist", artists});
|
||||
return list;
|
||||
}
|
||||
|
||||
} // namespace widget
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,162 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
*
|
||||
* 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/. */
|
||||
|
||||
#ifndef WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
|
||||
#define WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include "mozilla/dom/MediaControlKeysEvent.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#define DBUS_MRPIS_SERVICE_NAME "org.mpris.MediaPlayer2.firefox"
|
||||
#define DBUS_MPRIS_OBJECT_PATH "/org/mpris/MediaPlayer2"
|
||||
|
||||
namespace mozilla {
|
||||
namespace widget {
|
||||
|
||||
struct MPRISMetadata {
|
||||
const char* mKey;
|
||||
GVariant* mValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements the "MPRIS" D-Bus Service
|
||||
* (https://specifications.freedesktop.org/mpris-spec/2.2),
|
||||
* which is used to communicate with the Desktop Environment about the
|
||||
* Multimedia playing in Gecko.
|
||||
* Note that this interface requires many methods which may not be supported by
|
||||
* Gecko, the interface
|
||||
* however provides CanXYZ properties for these methods, so the method is
|
||||
* defined but won't be executed.
|
||||
*
|
||||
* Also note that the following defines are for parts that the MPRIS Spec
|
||||
* defines optional. The code won't
|
||||
* compile with any of the defines set, yet, as those aren't implemented yet and
|
||||
* probably never will be of
|
||||
* use for gecko. For sake of completeness, they have been added until the
|
||||
* decision about their implementation
|
||||
* is finally made.
|
||||
*
|
||||
* The constexpr'ed methods are capabilities of the user agent known at compile
|
||||
* time, e.g. we decided at
|
||||
* compile time whether we ever want to support closing the user agent via MPRIS
|
||||
* (Quit() and CanQuit()).
|
||||
*
|
||||
* Other properties like CanPlay() might depend on the runtime state (is there
|
||||
* media available for playback?)
|
||||
* and thus aren't a constexpr but merely a const method.
|
||||
*/
|
||||
class MPRISServiceHandler final : public dom::MediaControlKeysEventSource {
|
||||
NS_INLINE_DECL_REFCOUNTING(MPRISServiceHandler, override)
|
||||
public:
|
||||
// Note that this constructor does NOT initialize the MPRIS Service but only
|
||||
// this class. The method Open() is responsible for registering and MAY FAIL.
|
||||
MPRISServiceHandler() = default;
|
||||
bool Open() override;
|
||||
void Close() override;
|
||||
bool IsOpened() const override;
|
||||
|
||||
// From the EventSource.
|
||||
void SetPlaybackState(dom::PlaybackState aState) override;
|
||||
|
||||
// GetPlaybackState returns dom::PlaybackState. GetPlaybackStatus returns this
|
||||
// state converted into d-bus variants.
|
||||
GVariant* GetPlaybackStatus() const;
|
||||
|
||||
// Implementations of the MPRIS API Methods/Properties. constexpr'ed properties
|
||||
// will be what the user agent doesn't support and thus they are known at
|
||||
// compile time.
|
||||
#ifdef MPRIS_FULLSCREEN
|
||||
bool GetFullscreen();
|
||||
void SetFullscreen(bool aFullscreen);
|
||||
bool CanSetFullscreen();
|
||||
#endif
|
||||
bool HasTrackList();
|
||||
const char* Identity();
|
||||
#ifdef MPRIS_DESKTOP_ENTRY
|
||||
const char* DesktopEntry();
|
||||
#endif
|
||||
GVariant* SupportedUriSchemes();
|
||||
GVariant* SupportedMimeTypes();
|
||||
constexpr bool CanRaise();
|
||||
void Raise();
|
||||
constexpr bool CanQuit();
|
||||
void Quit();
|
||||
|
||||
// :Player::Methods
|
||||
void Next();
|
||||
void Previous();
|
||||
void Pause();
|
||||
void PlayPause();
|
||||
void Stop();
|
||||
void Play();
|
||||
void Seek(int64_t aOffset);
|
||||
void SetPosition(char* aTrackId, int64_t aPosition);
|
||||
// bool is our custom addition: return false whether opening fails/is not
|
||||
// supported for that URI it will raise a DBUS Error
|
||||
bool OpenUri(char* aUri);
|
||||
|
||||
#ifdef MPRIS_LOOP_STATUS
|
||||
MPRISLoopStatus GetLoopStatus();
|
||||
#endif
|
||||
|
||||
double GetRate() const;
|
||||
bool SetRate(double aRate);
|
||||
constexpr double GetMinimumRate();
|
||||
constexpr double GetMaximumRate();
|
||||
|
||||
#ifdef MPRIS_SHUFFLE
|
||||
bool GetShuffle() const;
|
||||
void SetShuffle(bool aShuffle);
|
||||
#endif
|
||||
|
||||
std::vector<struct MPRISMetadata> GetDefaultMetadata();
|
||||
double GetVolume() const;
|
||||
bool SetVolume(double aVolume);
|
||||
int64_t GetPosition() const;
|
||||
|
||||
bool CanGoNext() const;
|
||||
bool CanGoPrevious() const;
|
||||
bool CanPlay() const;
|
||||
bool CanPause() const;
|
||||
bool CanSeek() const;
|
||||
bool CanControl() const;
|
||||
|
||||
private:
|
||||
~MPRISServiceHandler();
|
||||
|
||||
// Note: Registration Ids for the D-Bus start with 1, so a value of 0
|
||||
// indicates an error (or an object which wasn't initialized yet)
|
||||
|
||||
// a handle to our bus registration/ownership
|
||||
guint mOwnerId = 0;
|
||||
// This is for the interface org.mpris.MediaPlayer2
|
||||
guint mRootRegistrationId = 0;
|
||||
// This is for the interface org.mpris.MediaPlayer2.Player
|
||||
guint mPlayerRegistrationId = 0;
|
||||
GDBusNodeInfo* mIntrospectionData = nullptr;
|
||||
GDBusConnection* mConnection = nullptr;
|
||||
bool mInitialized = false;
|
||||
|
||||
// non-public API, called from events
|
||||
void OnNameAcquired(GDBusConnection* aConnection, const gchar* aName);
|
||||
void OnNameLost(GDBusConnection* aConnection, const gchar* aName);
|
||||
void OnBusAcquired(GDBusConnection* aConnection, const gchar* aName);
|
||||
|
||||
static void OnNameAcquiredStatic(GDBusConnection* aConnection,
|
||||
const gchar* aName, gpointer aUserData);
|
||||
static void OnNameLostStatic(GDBusConnection* aConnection, const gchar* aName,
|
||||
gpointer aUserData);
|
||||
static void OnBusAcquiredStatic(GDBusConnection* aConnection,
|
||||
const gchar* aName, gpointer aUserData);
|
||||
|
||||
void EmitEvent(mozilla::dom::MediaControlKeysEvent event);
|
||||
};
|
||||
|
||||
} // namespace widget
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
|
|
@ -3,12 +3,12 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "MediaKeysEventSourceFactory.h"
|
||||
#include "MPRISServiceHandler.h"
|
||||
|
||||
namespace mozilla::widget {
|
||||
|
||||
mozilla::dom::MediaControlKeysEventSource* CreateMediaControlKeysEventSource() {
|
||||
// TODO : will implement this in bug 1353652.
|
||||
return nullptr;
|
||||
return new MPRISServiceHandler();
|
||||
}
|
||||
|
||||
} // namespace mozilla::widget
|
||||
|
|
|
@ -32,6 +32,7 @@ EXPORTS.mozilla += [
|
|||
UNIFIED_SOURCES += [
|
||||
'IMContextWrapper.cpp',
|
||||
'mozcontainer.cpp',
|
||||
'MPRISServiceHandler.cpp',
|
||||
'NativeKeyBindings.cpp',
|
||||
'nsAppShell.cpp',
|
||||
'nsBidiKeyboard.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче