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:
Marc Streckfuss 2020-02-12 13:10:50 +00:00
Родитель 2731ff1336
Коммит 1eb3d684e4
6 изменённых файлов: 936 добавлений и 2 удалений

Просмотреть файл

@ -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',