blob: 3e6deb9a32f632c2b148b8859286488f02db278a [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/system_media_controls/linux/system_media_controls_linux.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/singleton.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "build/branding_buildflags.h"
#include "components/dbus/properties/dbus_properties.h"
#include "components/dbus/properties/success_barrier_callback.h"
#include "components/dbus/thread_linux/dbus_thread_linux.h"
#include "components/system_media_controls/system_media_controls_observer.h"
#include "dbus/bus.h"
#include "dbus/exported_object.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/property.h"
namespace system_media_controls {
// static
SystemMediaControls* SystemMediaControls::GetInstance() {
internal::SystemMediaControlsLinux* service =
internal::SystemMediaControlsLinux::GetInstance();
service->StartService();
return service;
}
namespace internal {
namespace {
constexpr int kNumMethodsToExport = 11;
} // namespace
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
const char kMprisAPIServiceNamePrefix[] =
"org.mpris.MediaPlayer2.chrome.instance";
#else
const char kMprisAPIServiceNamePrefix[] =
"org.mpris.MediaPlayer2.chromium.instance";
#endif
const char kMprisAPIObjectPath[] = "/org/mpris/MediaPlayer2";
const char kMprisAPIInterfaceName[] = "org.mpris.MediaPlayer2";
const char kMprisAPIPlayerInterfaceName[] = "org.mpris.MediaPlayer2.Player";
// static
SystemMediaControlsLinux* SystemMediaControlsLinux::GetInstance() {
return base::Singleton<SystemMediaControlsLinux>::get();
}
SystemMediaControlsLinux::SystemMediaControlsLinux()
: service_name_(std::string(kMprisAPIServiceNamePrefix) +
base::NumberToString(base::Process::Current().Pid())) {}
SystemMediaControlsLinux::~SystemMediaControlsLinux() {
if (bus_) {
dbus_thread_linux::GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&dbus::Bus::ShutdownAndBlock, bus_));
}
}
void SystemMediaControlsLinux::StartService() {
if (started_)
return;
started_ = true;
InitializeDbusInterface();
}
void SystemMediaControlsLinux::AddObserver(
SystemMediaControlsObserver* observer) {
observers_.AddObserver(observer);
// If the service is already ready, inform the observer.
if (service_ready_)
observer->OnServiceReady();
}
void SystemMediaControlsLinux::RemoveObserver(
SystemMediaControlsObserver* observer) {
observers_.RemoveObserver(observer);
}
void SystemMediaControlsLinux::SetIsNextEnabled(bool value) {
properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanGoNext",
DbusBoolean(value));
}
void SystemMediaControlsLinux::SetIsPreviousEnabled(bool value) {
properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanGoPrevious",
DbusBoolean(value));
}
void SystemMediaControlsLinux::SetIsPlayPauseEnabled(bool value) {
properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanPlay",
DbusBoolean(value));
properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanPause",
DbusBoolean(value));
}
void SystemMediaControlsLinux::SetPlaybackStatus(PlaybackStatus value) {
auto status = [&]() {
switch (value) {
case PlaybackStatus::kPlaying:
return DbusString("Playing");
case PlaybackStatus::kPaused:
return DbusString("Paused");
case PlaybackStatus::kStopped:
return DbusString("Stopped");
}
};
properties_->SetProperty(kMprisAPIPlayerInterfaceName, "PlaybackStatus",
status());
}
void SystemMediaControlsLinux::SetTitle(const base::string16& value) {
SetMetadataPropertyInternal(
"xesam:title", MakeDbusVariant(DbusString(base::UTF16ToUTF8(value))));
}
void SystemMediaControlsLinux::SetArtist(const base::string16& value) {
SetMetadataPropertyInternal(
"xesam:artist",
MakeDbusVariant(MakeDbusArray(DbusString(base::UTF16ToUTF8(value)))));
}
void SystemMediaControlsLinux::SetAlbum(const base::string16& value) {
SetMetadataPropertyInternal(
"xesam:album", MakeDbusVariant(DbusString(base::UTF16ToUTF8(value))));
}
void SystemMediaControlsLinux::ClearMetadata() {
SetTitle(base::string16());
SetArtist(base::string16());
SetAlbum(base::string16());
}
std::string SystemMediaControlsLinux::GetServiceName() const {
return service_name_;
}
void SystemMediaControlsLinux::InitializeProperties() {
// org.mpris.MediaPlayer2 interface properties.
auto set_property = [&](const std::string& property_name, auto&& value) {
properties_->SetProperty(kMprisAPIInterfaceName, property_name,
std::move(value), false);
};
set_property("CanQuit", DbusBoolean(false));
set_property("CanRaise", DbusBoolean(false));
set_property("HasTrackList", DbusBoolean(false));
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
set_property("Identity", DbusString("Chrome"));
#else
set_property("Identity", DbusString("Chromium"));
#endif
set_property("SupportedUriSchemes", DbusArray<DbusString>());
set_property("SupportedMimeTypes", DbusArray<DbusString>());
// org.mpris.MediaPlayer2.Player interface properties.
auto set_player_property = [&](const std::string& property_name,
auto&& value) {
properties_->SetProperty(kMprisAPIPlayerInterfaceName, property_name,
std::move(value), false);
};
set_player_property("PlaybackStatus", DbusString("Stopped"));
set_player_property("Rate", DbusDouble(1.0));
set_player_property("Metadata", DbusDictionary());
set_player_property("Volume", DbusDouble(1.0));
set_player_property("Position", DbusInt64(0));
set_player_property("MinimumRate", DbusDouble(1.0));
set_player_property("MaximumRate", DbusDouble(1.0));
set_player_property("CanGoNext", DbusBoolean(false));
set_player_property("CanGoPrevious", DbusBoolean(false));
set_player_property("CanPlay", DbusBoolean(false));
set_player_property("CanPause", DbusBoolean(false));
set_player_property("CanSeek", DbusBoolean(false));
set_player_property("CanControl", DbusBoolean(false));
}
void SystemMediaControlsLinux::InitializeDbusInterface() {
// Bus may be set for testing.
if (!bus_) {
dbus::Bus::Options bus_options;
bus_options.bus_type = dbus::Bus::SESSION;
bus_options.connection_type = dbus::Bus::PRIVATE;
bus_options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
bus_ = base::MakeRefCounted<dbus::Bus>(bus_options);
}
exported_object_ =
bus_->GetExportedObject(dbus::ObjectPath(kMprisAPIObjectPath));
int num_methods_attempted_to_export = 0;
// kNumMethodsToExport calls for method export, 1 call for properties
// initialization.
barrier_ = SuccessBarrierCallback(
kNumMethodsToExport + 1,
base::BindOnce(&SystemMediaControlsLinux::OnInitialized,
base::Unretained(this)));
properties_ = std::make_unique<DbusProperties>(exported_object_, barrier_);
properties_->RegisterInterface(kMprisAPIInterfaceName);
properties_->RegisterInterface(kMprisAPIPlayerInterfaceName);
InitializeProperties();
// Helper lambdas for exporting methods while keeping track of the number of
// exported methods.
auto export_method =
[&](const std::string& interface_name, const std::string& method_name,
dbus::ExportedObject::MethodCallCallback method_call_callback) {
exported_object_->ExportMethod(
interface_name, method_name, method_call_callback,
base::BindRepeating(&SystemMediaControlsLinux::OnExported,
base::Unretained(this)));
num_methods_attempted_to_export++;
};
auto export_unhandled_method = [&](const std::string& interface_name,
const std::string& method_name) {
export_method(interface_name, method_name,
base::BindRepeating(&SystemMediaControlsLinux::DoNothing,
base::Unretained(this)));
};
// Set up org.mpris.MediaPlayer2 interface.
// https://specifications.freedesktop.org/mpris-spec/2.2/Media_Player.html
export_unhandled_method(kMprisAPIInterfaceName, "Raise");
export_unhandled_method(kMprisAPIInterfaceName, "Quit");
// Set up org.mpris.MediaPlayer2.Player interface.
// https://specifications.freedesktop.org/mpris-spec/2.2/Player_Interface.html
export_method(kMprisAPIPlayerInterfaceName, "Next",
base::BindRepeating(&SystemMediaControlsLinux::Next,
base::Unretained(this)));
export_method(kMprisAPIPlayerInterfaceName, "Previous",
base::BindRepeating(&SystemMediaControlsLinux::Previous,
base::Unretained(this)));
export_method(kMprisAPIPlayerInterfaceName, "Pause",
base::BindRepeating(&SystemMediaControlsLinux::Pause,
base::Unretained(this)));
export_method(kMprisAPIPlayerInterfaceName, "PlayPause",
base::BindRepeating(&SystemMediaControlsLinux::PlayPause,
base::Unretained(this)));
export_method(kMprisAPIPlayerInterfaceName, "Stop",
base::BindRepeating(&SystemMediaControlsLinux::Stop,
base::Unretained(this)));
export_method(kMprisAPIPlayerInterfaceName, "Play",
base::BindRepeating(&SystemMediaControlsLinux::Play,
base::Unretained(this)));
export_unhandled_method(kMprisAPIPlayerInterfaceName, "Seek");
export_unhandled_method(kMprisAPIPlayerInterfaceName, "SetPosition");
export_unhandled_method(kMprisAPIPlayerInterfaceName, "OpenUri");
DCHECK_EQ(kNumMethodsToExport, num_methods_attempted_to_export);
}
void SystemMediaControlsLinux::OnExported(const std::string& interface_name,
const std::string& method_name,
bool success) {
barrier_.Run(success);
}
void SystemMediaControlsLinux::OnInitialized(bool success) {
if (success) {
bus_->RequestOwnership(
service_name_, dbus::Bus::ServiceOwnershipOptions::REQUIRE_PRIMARY,
base::BindRepeating(&SystemMediaControlsLinux::OnOwnership,
base::Unretained(this)));
}
}
void SystemMediaControlsLinux::OnOwnership(const std::string& service_name,
bool success) {
if (!success)
return;
service_ready_ = true;
for (SystemMediaControlsObserver& obs : observers_)
obs.OnServiceReady();
}
void SystemMediaControlsLinux::Next(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
for (SystemMediaControlsObserver& obs : observers_)
obs.OnNext();
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void SystemMediaControlsLinux::Previous(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
for (SystemMediaControlsObserver& obs : observers_)
obs.OnPrevious();
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void SystemMediaControlsLinux::Pause(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
for (SystemMediaControlsObserver& obs : observers_)
obs.OnPause();
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void SystemMediaControlsLinux::PlayPause(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
for (SystemMediaControlsObserver& obs : observers_)
obs.OnPlayPause();
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void SystemMediaControlsLinux::Stop(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
for (SystemMediaControlsObserver& obs : observers_)
obs.OnStop();
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void SystemMediaControlsLinux::Play(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
for (SystemMediaControlsObserver& obs : observers_)
obs.OnPlay();
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void SystemMediaControlsLinux::DoNothing(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void SystemMediaControlsLinux::SetMetadataPropertyInternal(
const std::string& property_name,
DbusVariant&& new_value) {
DbusVariant* dictionary_variant =
properties_->GetProperty(kMprisAPIPlayerInterfaceName, "Metadata");
DCHECK(dictionary_variant);
DbusDictionary* dictionary = dictionary_variant->GetAs<DbusDictionary>();
DCHECK(dictionary);
if (dictionary->Put(property_name, std::move(new_value)))
properties_->PropertyUpdated(kMprisAPIPlayerInterfaceName, "Metadata");
}
} // namespace internal
} // namespace system_media_controls