1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MPRISServiceHandler.h"
11 #include <unordered_map>
13 #include "MPRISInterfaceDescription.h"
14 #include "mozilla/dom/MediaControlUtils.h"
15 #include "mozilla/Maybe.h"
16 #include "mozilla/ScopeExit.h"
17 #include "mozilla/Sprintf.h"
18 #include "nsIXULAppInfo.h"
19 #include "nsIOutputStream.h"
20 #include "nsNetUtil.h"
21 #include "nsServiceManagerUtils.h"
23 #define LOGMPRIS(msg, ...) \
24 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
25 ("MPRISServiceHandler=%p, " msg, this, ##__VA_ARGS__))
30 // A global counter tracking the total images saved in the system and it will be
31 // used to form a unique image file name.
32 static uint32_t gImageNumber
= 0;
34 static inline Maybe
<mozilla::dom::MediaControlKey
> GetMediaControlKey(
35 const gchar
* aMethodName
) {
36 const std::unordered_map
<std::string
, mozilla::dom::MediaControlKey
> map
= {
37 {"Raise", mozilla::dom::MediaControlKey::Focus
},
38 {"Next", mozilla::dom::MediaControlKey::Nexttrack
},
39 {"Previous", mozilla::dom::MediaControlKey::Previoustrack
},
40 {"Pause", mozilla::dom::MediaControlKey::Pause
},
41 {"PlayPause", mozilla::dom::MediaControlKey::Playpause
},
42 {"Stop", mozilla::dom::MediaControlKey::Stop
},
43 {"Play", mozilla::dom::MediaControlKey::Play
}};
45 auto it
= map
.find(aMethodName
);
46 return (it
== map
.end() ? Nothing() : Some(it
->second
));
49 static void HandleMethodCall(GDBusConnection
* aConnection
, const gchar
* aSender
,
50 const gchar
* aObjectPath
,
51 const gchar
* aInterfaceName
,
52 const gchar
* aMethodName
, GVariant
* aParameters
,
53 GDBusMethodInvocation
* aInvocation
,
55 MOZ_ASSERT(aUserData
);
56 MOZ_ASSERT(NS_IsMainThread());
58 Maybe
<mozilla::dom::MediaControlKey
> key
= GetMediaControlKey(aMethodName
);
59 if (key
.isNothing()) {
60 g_dbus_method_invocation_return_error(
61 aInvocation
, G_DBUS_ERROR
, G_DBUS_ERROR_NOT_SUPPORTED
,
62 "Method %s.%s.%s not supported", aObjectPath
, aInterfaceName
,
67 MPRISServiceHandler
* handler
= static_cast<MPRISServiceHandler
*>(aUserData
);
68 if (handler
->PressKey(key
.value())) {
69 g_dbus_method_invocation_return_value(aInvocation
, nullptr);
71 g_dbus_method_invocation_return_error(
72 aInvocation
, G_DBUS_ERROR
, G_DBUS_ERROR_FAILED
,
73 "%s.%s.%s is not available now", aObjectPath
, aInterfaceName
,
78 enum class Property
: uint8_t {
96 static inline Maybe
<mozilla::dom::MediaControlKey
> GetPairedKey(
99 case Property::eCanRaise
:
100 return Some(mozilla::dom::MediaControlKey::Focus
);
101 case Property::eCanGoNext
:
102 return Some(mozilla::dom::MediaControlKey::Nexttrack
);
103 case Property::eCanGoPrevious
:
104 return Some(mozilla::dom::MediaControlKey::Previoustrack
);
105 case Property::eCanPlay
:
106 return Some(mozilla::dom::MediaControlKey::Play
);
107 case Property::eCanPause
:
108 return Some(mozilla::dom::MediaControlKey::Pause
);
114 static inline Maybe
<Property
> GetProperty(const gchar
* aPropertyName
) {
115 const std::unordered_map
<std::string
, Property
> map
= {
116 // org.mpris.MediaPlayer2 properties
117 {"Identity", Property::eIdentity
},
118 {"DesktopEntry", Property::eDesktopEntry
},
119 {"HasTrackList", Property::eHasTrackList
},
120 {"CanRaise", Property::eCanRaise
},
121 {"CanQuit", Property::eCanQuit
},
122 {"SupportedUriSchemes", Property::eSupportedUriSchemes
},
123 {"SupportedMimeTypes", Property::eSupportedMimeTypes
},
124 // org.mpris.MediaPlayer2.Player properties
125 {"CanGoNext", Property::eCanGoNext
},
126 {"CanGoPrevious", Property::eCanGoPrevious
},
127 {"CanPlay", Property::eCanPlay
},
128 {"CanPause", Property::eCanPause
},
129 {"CanSeek", Property::eCanSeek
},
130 {"CanControl", Property::eCanControl
},
131 {"PlaybackStatus", Property::eGetPlaybackStatus
},
132 {"Metadata", Property::eGetMetadata
}};
134 auto it
= map
.find(aPropertyName
);
135 return (it
== map
.end() ? Nothing() : Some(it
->second
));
138 static GVariant
* HandleGetProperty(GDBusConnection
* aConnection
,
139 const gchar
* aSender
,
140 const gchar
* aObjectPath
,
141 const gchar
* aInterfaceName
,
142 const gchar
* aPropertyName
, GError
** aError
,
143 gpointer aUserData
) {
144 MOZ_ASSERT(aUserData
);
145 MOZ_ASSERT(NS_IsMainThread());
147 Maybe
<Property
> property
= GetProperty(aPropertyName
);
148 if (property
.isNothing()) {
149 g_set_error(aError
, G_DBUS_ERROR
, G_DBUS_ERROR_NOT_SUPPORTED
,
150 "%s.%s %s is not supported", aObjectPath
, aInterfaceName
,
155 MPRISServiceHandler
* handler
= static_cast<MPRISServiceHandler
*>(aUserData
);
156 switch (property
.value()) {
157 case Property::eSupportedUriSchemes
:
158 case Property::eSupportedMimeTypes
:
159 // No plan to implement OpenUri for now
160 return g_variant_new_strv(nullptr, 0);
161 case Property::eGetPlaybackStatus
:
162 return handler
->GetPlaybackStatus();
163 case Property::eGetMetadata
:
164 return handler
->GetMetadataAsGVariant();
165 case Property::eIdentity
:
166 return g_variant_new_string(handler
->Identity());
167 case Property::eDesktopEntry
:
168 return g_variant_new_string(handler
->DesktopEntry());
169 case Property::eHasTrackList
:
170 case Property::eCanQuit
:
171 case Property::eCanSeek
:
172 return g_variant_new_boolean(false);
173 // Play/Pause would be blocked if CanControl is false
174 case Property::eCanControl
:
175 return g_variant_new_boolean(true);
176 case Property::eCanRaise
:
177 case Property::eCanGoNext
:
178 case Property::eCanGoPrevious
:
179 case Property::eCanPlay
:
180 case Property::eCanPause
:
181 Maybe
<mozilla::dom::MediaControlKey
> key
= GetPairedKey(property
.value());
182 MOZ_ASSERT(key
.isSome());
183 return g_variant_new_boolean(handler
->IsMediaKeySupported(key
.value()));
186 MOZ_ASSERT_UNREACHABLE("Switch statement is incomplete");
190 static gboolean
HandleSetProperty(GDBusConnection
* aConnection
,
191 const gchar
* aSender
,
192 const gchar
* aObjectPath
,
193 const gchar
* aInterfaceName
,
194 const gchar
* aPropertyName
, GVariant
* aValue
,
195 GError
** aError
, gpointer aUserData
) {
196 MOZ_ASSERT(aUserData
);
197 MOZ_ASSERT(NS_IsMainThread());
198 g_set_error(aError
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
199 "%s:%s setting is not supported", aInterfaceName
, aPropertyName
);
203 static const GDBusInterfaceVTable gInterfaceVTable
= {
204 HandleMethodCall
, HandleGetProperty
, HandleSetProperty
};
206 void MPRISServiceHandler::OnNameAcquiredStatic(GDBusConnection
* aConnection
,
208 gpointer aUserData
) {
209 MOZ_ASSERT(aUserData
);
210 static_cast<MPRISServiceHandler
*>(aUserData
)->OnNameAcquired(aConnection
,
214 void MPRISServiceHandler::OnNameLostStatic(GDBusConnection
* aConnection
,
216 gpointer aUserData
) {
217 MOZ_ASSERT(aUserData
);
218 static_cast<MPRISServiceHandler
*>(aUserData
)->OnNameLost(aConnection
, aName
);
221 void MPRISServiceHandler::OnBusAcquiredStatic(GDBusConnection
* aConnection
,
223 gpointer aUserData
) {
224 MOZ_ASSERT(aUserData
);
225 static_cast<MPRISServiceHandler
*>(aUserData
)->OnBusAcquired(aConnection
,
229 void MPRISServiceHandler::OnNameAcquired(GDBusConnection
* aConnection
,
230 const gchar
* aName
) {
231 LOGMPRIS("OnNameAcquired: %s", aName
);
232 mConnection
= aConnection
;
235 void MPRISServiceHandler::OnNameLost(GDBusConnection
* aConnection
,
236 const gchar
* aName
) {
237 LOGMPRIS("OnNameLost: %s", aName
);
238 mConnection
= nullptr;
239 if (!mRootRegistrationId
) {
243 if (g_dbus_connection_unregister_object(aConnection
, mRootRegistrationId
)) {
244 mRootRegistrationId
= 0;
246 // Note: Most code examples in the internet probably dont't even check the
248 // according to the spec it _can_ return false.
249 LOGMPRIS("Unable to unregister root object from within onNameLost!");
252 if (!mPlayerRegistrationId
) {
256 if (g_dbus_connection_unregister_object(aConnection
, mPlayerRegistrationId
)) {
257 mPlayerRegistrationId
= 0;
259 // Note: Most code examples in the internet probably dont't even check the
261 // according to the spec it _can_ return false.
262 LOGMPRIS("Unable to unregister object from within onNameLost!");
266 void MPRISServiceHandler::OnBusAcquired(GDBusConnection
* aConnection
,
267 const gchar
* aName
) {
268 GError
* error
= nullptr;
269 LOGMPRIS("OnBusAcquired: %s", aName
);
271 mRootRegistrationId
= g_dbus_connection_register_object(
272 aConnection
, DBUS_MPRIS_OBJECT_PATH
, mIntrospectionData
->interfaces
[0],
273 &gInterfaceVTable
, this, /* user_data */
274 nullptr, /* user_data_free_func */
275 &error
); /* GError** */
277 if (mRootRegistrationId
== 0) {
278 LOGMPRIS("Failed at root registration: %s",
279 error
? error
->message
: "Unknown Error");
286 mPlayerRegistrationId
= g_dbus_connection_register_object(
287 aConnection
, DBUS_MPRIS_OBJECT_PATH
, mIntrospectionData
->interfaces
[1],
288 &gInterfaceVTable
, this, /* user_data */
289 nullptr, /* user_data_free_func */
290 &error
); /* GError** */
292 if (mPlayerRegistrationId
== 0) {
293 LOGMPRIS("Failed at object registration: %s",
294 error
? error
->message
: "Unknown Error");
301 bool MPRISServiceHandler::Open() {
302 MOZ_ASSERT(!mInitialized
);
303 MOZ_ASSERT(NS_IsMainThread());
304 GError
* error
= nullptr;
305 gchar serviceName
[256];
308 SprintfLiteral(serviceName
, DBUS_MPRIS_SERVICE_NAME
".instance%d", getpid());
310 g_bus_own_name(G_BUS_TYPE_SESSION
, serviceName
,
311 // Enter a waiting queue until this service name is free
312 // (likely another FF instance is running/has been crashed)
313 G_BUS_NAME_OWNER_FLAGS_NONE
, OnBusAcquiredStatic
,
314 OnNameAcquiredStatic
, OnNameLostStatic
, this, nullptr);
316 /* parse introspection data */
317 mIntrospectionData
= g_dbus_node_info_new_for_xml(introspection_xml
, &error
);
319 if (!mIntrospectionData
) {
320 LOGMPRIS("Failed at parsing XML Interface definition: %s",
321 error
? error
->message
: "Unknown Error");
332 MPRISServiceHandler::~MPRISServiceHandler() {
333 MOZ_ASSERT(!mInitialized
); // Close hasn't been called!
336 void MPRISServiceHandler::Close() {
337 gchar serviceName
[256];
338 SprintfLiteral(serviceName
, DBUS_MPRIS_SERVICE_NAME
".instance%d", getpid());
340 // Reset playback state and metadata before disconnect from dbus.
341 SetPlaybackState(dom::MediaSessionPlaybackState::None
);
344 OnNameLost(mConnection
, serviceName
);
347 g_bus_unown_name(mOwnerId
);
349 if (mIntrospectionData
) {
350 g_dbus_node_info_unref(mIntrospectionData
);
353 mInitialized
= false;
354 MediaControlKeySource::Close();
357 bool MPRISServiceHandler::IsOpened() const { return mInitialized
; }
359 void MPRISServiceHandler::InitIdentity() {
361 nsCOMPtr
<nsIXULAppInfo
> appInfo
=
362 do_GetService("@mozilla.org/xre/app-info;1", &rv
);
364 MOZ_ASSERT(NS_SUCCEEDED(rv
));
365 rv
= appInfo
->GetVendor(mIdentity
);
366 MOZ_ASSERT(NS_SUCCEEDED(rv
));
367 rv
= appInfo
->GetName(mDesktopEntry
);
368 MOZ_ASSERT(NS_SUCCEEDED(rv
));
370 mIdentity
.Append(' ');
371 mIdentity
.Append(mDesktopEntry
);
373 // Compute the desktop entry name like nsAppRunner does for g_set_prgname
374 ToLowerCase(mDesktopEntry
);
377 const char* MPRISServiceHandler::Identity() const {
378 MOZ_ASSERT(mInitialized
);
379 return mIdentity
.get();
382 const char* MPRISServiceHandler::DesktopEntry() const {
383 MOZ_ASSERT(mInitialized
);
384 return mDesktopEntry
.get();
387 bool MPRISServiceHandler::PressKey(mozilla::dom::MediaControlKey aKey
) const {
388 MOZ_ASSERT(mInitialized
);
389 if (!IsMediaKeySupported(aKey
)) {
390 LOGMPRIS("%s is not supported", ToMediaControlKeyStr(aKey
));
393 LOGMPRIS("Press %s", ToMediaControlKeyStr(aKey
));
398 void MPRISServiceHandler::SetPlaybackState(
399 dom::MediaSessionPlaybackState aState
) {
400 LOGMPRIS("SetPlaybackState");
401 if (mPlaybackState
== aState
) {
405 MediaControlKeySource::SetPlaybackState(aState
);
407 GVariant
* state
= GetPlaybackStatus();
408 GVariantBuilder builder
;
409 g_variant_builder_init(&builder
, G_VARIANT_TYPE("a{sv}"));
410 g_variant_builder_add(&builder
, "{sv}", "PlaybackStatus", state
);
412 GVariant
* parameters
= g_variant_new(
413 "(sa{sv}as)", DBUS_MPRIS_PLAYER_INTERFACE
, &builder
, nullptr);
415 LOGMPRIS("Emitting MPRIS property changes for 'PlaybackStatus'");
416 Unused
<< EmitPropertiesChangedSignal(parameters
);
419 GVariant
* MPRISServiceHandler::GetPlaybackStatus() const {
420 switch (GetPlaybackState()) {
421 case dom::MediaSessionPlaybackState::Playing
:
422 return g_variant_new_string("Playing");
423 case dom::MediaSessionPlaybackState::Paused
:
424 return g_variant_new_string("Paused");
425 case dom::MediaSessionPlaybackState::None
:
426 return g_variant_new_string("Stopped");
428 MOZ_ASSERT_UNREACHABLE("Invalid Playback State");
433 void MPRISServiceHandler::SetMediaMetadata(
434 const dom::MediaMetadataBase
& aMetadata
) {
435 // Reset the index of the next available image to be fetched in the artwork,
436 // before checking the fetching process should be started or not. The image
437 // fetching process could be skipped if the image being fetching currently is
438 // in the artwork. If the current image fetching fails, the next availabe
439 // candidate should be the first image in the latest artwork
442 // No need to fetch a MPRIS image if
443 // 1) MPRIS image is being fetched, and the one in fetching is in the artwork
444 // 2) MPRIS image is not being fetched, and the one in use is in the artwork
445 if (!mFetchingUrl
.IsEmpty()) {
446 if (mozilla::dom::IsImageIn(aMetadata
.mArtwork
, mFetchingUrl
)) {
448 "No need to load MPRIS image. The one being processed is in the "
450 // Set MPRIS without the image first. The image will be loaded to MPRIS
451 // asynchronously once it's fetched and saved into a local file
452 SetMediaMetadataInternal(aMetadata
);
455 } else if (!mCurrentImageUrl
.IsEmpty()) {
456 if (mozilla::dom::IsImageIn(aMetadata
.mArtwork
, mCurrentImageUrl
)) {
457 LOGMPRIS("No need to load MPRIS image. The one in use is in the artwork");
458 SetMediaMetadataInternal(aMetadata
, false);
463 // Set MPRIS without the image first then load the image to MPRIS
465 SetMediaMetadataInternal(aMetadata
);
466 LoadImageAtIndex(mNextImageIndex
++);
469 bool MPRISServiceHandler::EmitMetadataChanged() const {
470 GVariantBuilder builder
;
471 g_variant_builder_init(&builder
, G_VARIANT_TYPE("a{sv}"));
472 g_variant_builder_add(&builder
, "{sv}", "Metadata", GetMetadataAsGVariant());
474 GVariant
* parameters
= g_variant_new(
475 "(sa{sv}as)", DBUS_MPRIS_PLAYER_INTERFACE
, &builder
, nullptr);
477 LOGMPRIS("Emit MPRIS property changes for 'Metadata'");
478 return EmitPropertiesChangedSignal(parameters
);
481 void MPRISServiceHandler::SetMediaMetadataInternal(
482 const dom::MediaMetadataBase
& aMetadata
, bool aClearArtUrl
) {
483 mMPRISMetadata
.UpdateFromMetadataBase(aMetadata
);
485 mMPRISMetadata
.mArtUrl
.Truncate();
487 EmitMetadataChanged();
490 void MPRISServiceHandler::ClearMetadata() {
491 mMPRISMetadata
.Clear();
492 mImageFetchRequest
.DisconnectIfExists();
493 RemoveAllLocalImages();
494 mCurrentImageUrl
.Truncate();
495 mFetchingUrl
.Truncate();
498 EmitMetadataChanged();
501 void MPRISServiceHandler::LoadImageAtIndex(const size_t aIndex
) {
502 MOZ_ASSERT(NS_IsMainThread());
504 if (aIndex
>= mMPRISMetadata
.mArtwork
.Length()) {
505 LOGMPRIS("Stop loading image to MPRIS. No available image");
506 mImageFetchRequest
.DisconnectIfExists();
510 const mozilla::dom::MediaImage
& image
= mMPRISMetadata
.mArtwork
[aIndex
];
512 if (!mozilla::dom::IsValidImageUrl(image
.mSrc
)) {
513 LOGMPRIS("Skip the image with invalid URL. Try next image");
514 LoadImageAtIndex(mNextImageIndex
++);
518 mImageFetchRequest
.DisconnectIfExists();
519 mFetchingUrl
= image
.mSrc
;
521 mImageFetcher
= mozilla::MakeUnique
<mozilla::dom::FetchImageHelper
>(image
);
522 RefPtr
<MPRISServiceHandler
> self
= this;
523 mImageFetcher
->FetchImage()
525 AbstractThread::MainThread(), __func__
,
526 [this, self
](const nsCOMPtr
<imgIContainer
>& aImage
) {
527 LOGMPRIS("The image is fetched successfully");
528 mImageFetchRequest
.Complete();
531 char* data
= nullptr;
532 // Only used to hold the image data
533 nsCOMPtr
<nsIInputStream
> inputStream
;
534 nsresult rv
= mozilla::dom::GetEncodedImageBuffer(
535 aImage
, mMimeType
, getter_AddRefs(inputStream
), &size
, &data
);
536 if (NS_FAILED(rv
) || !inputStream
|| size
== 0 || !data
) {
537 LOGMPRIS("Failed to get the image buffer info. Try next image");
538 LoadImageAtIndex(mNextImageIndex
++);
542 if (SetImageToDisplay(data
, size
)) {
543 mCurrentImageUrl
= mFetchingUrl
;
544 LOGMPRIS("The MPRIS image is updated to the image from: %s",
545 NS_ConvertUTF16toUTF8(mCurrentImageUrl
).get());
547 LOGMPRIS("Failed to set image to MPRIS");
548 mCurrentImageUrl
.Truncate();
551 mFetchingUrl
.Truncate();
554 LOGMPRIS("Failed to fetch image. Try next image");
555 mImageFetchRequest
.Complete();
556 mFetchingUrl
.Truncate();
557 LoadImageAtIndex(mNextImageIndex
++);
559 ->Track(mImageFetchRequest
);
562 bool MPRISServiceHandler::SetImageToDisplay(const char* aImageData
,
563 uint32_t aDataSize
) {
564 if (!RenewLocalImageFile(aImageData
, aDataSize
)) {
567 MOZ_ASSERT(mLocalImageFile
);
569 mMPRISMetadata
.mArtUrl
= nsCString("file://");
570 mMPRISMetadata
.mArtUrl
.Append(mLocalImageFile
->NativePath());
572 LOGMPRIS("The image file is created at %s", mMPRISMetadata
.mArtUrl
.get());
573 return EmitMetadataChanged();
576 bool MPRISServiceHandler::RenewLocalImageFile(const char* aImageData
,
577 uint32_t aDataSize
) {
578 MOZ_ASSERT(aImageData
);
579 MOZ_ASSERT(aDataSize
!= 0);
581 if (!InitLocalImageFile()) {
582 LOGMPRIS("Failed to create a new image");
586 MOZ_ASSERT(mLocalImageFile
);
587 nsCOMPtr
<nsIOutputStream
> out
;
588 NS_NewLocalFileOutputStream(getter_AddRefs(out
), mLocalImageFile
,
589 PR_RDWR
| PR_CREATE_FILE
| PR_TRUNCATE
);
591 nsresult rv
= out
->Write(aImageData
, aDataSize
, &written
);
592 if (NS_FAILED(rv
) || written
!= aDataSize
) {
593 LOGMPRIS("Failed to write an image file");
594 RemoveAllLocalImages();
601 static const char* GetImageFileExtension(const char* aMimeType
) {
602 MOZ_ASSERT(strcmp(aMimeType
, IMAGE_PNG
) == 0);
606 bool MPRISServiceHandler::InitLocalImageFile() {
607 RemoveAllLocalImages();
609 if (!InitLocalImageFolder()) {
613 MOZ_ASSERT(mLocalImageFolder
);
614 MOZ_ASSERT(!mLocalImageFile
);
615 nsresult rv
= mLocalImageFolder
->Clone(getter_AddRefs(mLocalImageFile
));
617 LOGMPRIS("Failed to get the image folder");
622 MakeScopeExit([this, self
= RefPtr
<MPRISServiceHandler
>(this)] {
623 mLocalImageFile
= nullptr;
626 // Create an unique file name to work around the file caching mechanism in the
627 // Ubuntu. Once the image X specified by the filename Y is used in Ubuntu's
628 // MPRIS, this pair will be cached. As long as the filename is same, even the
629 // file content specified by Y is changed to Z, the image will stay unchanged.
630 // The image shown in the Ubuntu's notification is still X instead of Z.
631 // Changing the filename constantly works around this problem
633 SprintfLiteral(filename
, "%d_%d.%s", getpid(), gImageNumber
++,
634 GetImageFileExtension(mMimeType
.get()));
636 rv
= mLocalImageFile
->Append(NS_ConvertUTF8toUTF16(filename
));
638 LOGMPRIS("Failed to create an image filename");
642 rv
= mLocalImageFile
->Create(nsIFile::NORMAL_FILE_TYPE
, 0600);
644 LOGMPRIS("Failed to create an image file");
652 bool MPRISServiceHandler::InitLocalImageFolder() {
653 if (mLocalImageFolder
&& LocalImageFolderExists()) {
657 nsresult rv
= NS_GetSpecialDirectory(XRE_USER_APP_DATA_DIR
,
658 getter_AddRefs(mLocalImageFolder
));
659 if (NS_FAILED(rv
) || !mLocalImageFolder
) {
660 LOGMPRIS("Failed to get the image folder");
665 MakeScopeExit([this, self
= RefPtr
<MPRISServiceHandler
>(this)] {
666 mLocalImageFolder
= nullptr;
669 rv
= mLocalImageFolder
->Append(u
"firefox-mpris"_ns
);
671 LOGMPRIS("Failed to name an image folder");
675 if (!LocalImageFolderExists()) {
676 rv
= mLocalImageFolder
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
678 LOGMPRIS("Failed to create an image folder");
687 void MPRISServiceHandler::RemoveAllLocalImages() {
688 if (!mLocalImageFolder
|| !LocalImageFolderExists()) {
692 nsresult rv
= mLocalImageFolder
->Remove(/* aRecursive */ true);
694 // It's ok to fail. The next removal is called when updating the
695 // media-session image, or closing the MPRIS.
696 LOGMPRIS("Failed to remove images");
699 LOGMPRIS("Abandon %s",
700 mLocalImageFile
? mLocalImageFile
->NativePath().get() : "nothing");
701 mMPRISMetadata
.mArtUrl
.Truncate();
702 mLocalImageFile
= nullptr;
703 mLocalImageFolder
= nullptr;
706 bool MPRISServiceHandler::LocalImageFolderExists() {
707 MOZ_ASSERT(mLocalImageFolder
);
710 nsresult rv
= mLocalImageFolder
->Exists(&exists
);
711 return NS_SUCCEEDED(rv
) && exists
;
714 GVariant
* MPRISServiceHandler::GetMetadataAsGVariant() const {
715 GVariantBuilder builder
;
716 g_variant_builder_init(&builder
, G_VARIANT_TYPE("a{sv}"));
717 g_variant_builder_add(&builder
, "{sv}", "mpris:trackid",
718 g_variant_new("o", DBUS_MPRIS_TRACK_PATH
));
720 g_variant_builder_add(
721 &builder
, "{sv}", "xesam:title",
722 g_variant_new_string(static_cast<const gchar
*>(
723 NS_ConvertUTF16toUTF8(mMPRISMetadata
.mTitle
).get())));
725 g_variant_builder_add(
726 &builder
, "{sv}", "xesam:album",
727 g_variant_new_string(static_cast<const gchar
*>(
728 NS_ConvertUTF16toUTF8(mMPRISMetadata
.mAlbum
).get())));
730 GVariantBuilder artistBuilder
;
731 g_variant_builder_init(&artistBuilder
, G_VARIANT_TYPE("as"));
732 g_variant_builder_add(
734 static_cast<const gchar
*>(
735 NS_ConvertUTF16toUTF8(mMPRISMetadata
.mArtist
).get()));
736 g_variant_builder_add(&builder
, "{sv}", "xesam:artist",
737 g_variant_builder_end(&artistBuilder
));
739 if (!mMPRISMetadata
.mArtUrl
.IsEmpty()) {
740 g_variant_builder_add(&builder
, "{sv}", "mpris:artUrl",
741 g_variant_new_string(static_cast<const gchar
*>(
742 mMPRISMetadata
.mArtUrl
.get())));
745 return g_variant_builder_end(&builder
);
748 void MPRISServiceHandler::EmitEvent(mozilla::dom::MediaControlKey aKey
) const {
749 for (const auto& listener
: mListeners
) {
750 listener
->OnActionPerformed(mozilla::dom::MediaControlAction(aKey
));
754 struct InterfaceProperty
{
755 const char* interface
;
756 const char* property
;
758 static const std::unordered_map
<mozilla::dom::MediaControlKey
,
760 gKeyProperty
= {{mozilla::dom::MediaControlKey::Focus
,
761 {DBUS_MPRIS_INTERFACE
, "CanRaise"}},
762 {mozilla::dom::MediaControlKey::Nexttrack
,
763 {DBUS_MPRIS_PLAYER_INTERFACE
, "CanGoNext"}},
764 {mozilla::dom::MediaControlKey::Previoustrack
,
765 {DBUS_MPRIS_PLAYER_INTERFACE
, "CanGoPrevious"}},
766 {mozilla::dom::MediaControlKey::Play
,
767 {DBUS_MPRIS_PLAYER_INTERFACE
, "CanPlay"}},
768 {mozilla::dom::MediaControlKey::Pause
,
769 {DBUS_MPRIS_PLAYER_INTERFACE
, "CanPause"}}};
771 void MPRISServiceHandler::SetSupportedMediaKeys(
772 const MediaKeysArray
& aSupportedKeys
) {
773 uint32_t supportedKeys
= 0;
774 for (const mozilla::dom::MediaControlKey
& key
: aSupportedKeys
) {
775 supportedKeys
|= GetMediaKeyMask(key
);
778 if (mSupportedKeys
== supportedKeys
) {
779 LOGMPRIS("Supported keys stay the same");
783 uint32_t oldSupportedKeys
= mSupportedKeys
;
784 mSupportedKeys
= supportedKeys
;
786 // Emit related property changes
787 for (auto it
: gKeyProperty
) {
788 bool keyWasSupported
= oldSupportedKeys
& GetMediaKeyMask(it
.first
);
789 bool keyIsSupported
= mSupportedKeys
& GetMediaKeyMask(it
.first
);
790 if (keyWasSupported
!= keyIsSupported
) {
791 LOGMPRIS("Emit PropertiesChanged signal: %s.%s=%s", it
.second
.interface
,
792 it
.second
.property
, keyIsSupported
? "true" : "false");
793 EmitSupportedKeyChanged(it
.first
, keyIsSupported
);
798 bool MPRISServiceHandler::IsMediaKeySupported(
799 mozilla::dom::MediaControlKey aKey
) const {
800 return mSupportedKeys
& GetMediaKeyMask(aKey
);
803 bool MPRISServiceHandler::EmitSupportedKeyChanged(
804 mozilla::dom::MediaControlKey aKey
, bool aSupported
) const {
805 auto it
= gKeyProperty
.find(aKey
);
806 if (it
== gKeyProperty
.end()) {
807 LOGMPRIS("No property for %s", ToMediaControlKeyStr(aKey
));
811 GVariantBuilder builder
;
812 g_variant_builder_init(&builder
, G_VARIANT_TYPE("a{sv}"));
813 g_variant_builder_add(&builder
, "{sv}",
814 static_cast<const gchar
*>(it
->second
.property
),
815 g_variant_new_boolean(aSupported
));
817 GVariant
* parameters
= g_variant_new(
818 "(sa{sv}as)", static_cast<const gchar
*>(it
->second
.interface
), &builder
,
821 LOGMPRIS("Emit MPRIS property changes for '%s.%s'", it
->second
.interface
,
822 it
->second
.property
);
823 return EmitPropertiesChangedSignal(parameters
);
826 bool MPRISServiceHandler::EmitPropertiesChangedSignal(
827 GVariant
* aParameters
) const {
829 LOGMPRIS("No D-Bus Connection. Cannot emit properties changed signal");
833 GError
* error
= nullptr;
834 if (!g_dbus_connection_emit_signal(
835 mConnection
, nullptr, DBUS_MPRIS_OBJECT_PATH
,
836 "org.freedesktop.DBus.Properties", "PropertiesChanged", aParameters
,
838 LOGMPRIS("Failed to emit MPRIS property changes: %s",
839 error
? error
->message
: "Unknown Error");
851 } // namespace widget
852 } // namespace mozilla