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/GRefPtr.h"
16 #include "mozilla/GUniquePtr.h"
17 #include "mozilla/UniquePtrExtensions.h"
18 #include "mozilla/Maybe.h"
19 #include "mozilla/ScopeExit.h"
20 #include "mozilla/Sprintf.h"
21 #include "nsXULAppAPI.h"
22 #include "nsIXULAppInfo.h"
23 #include "nsIOutputStream.h"
24 #include "nsNetUtil.h"
25 #include "nsServiceManagerUtils.h"
26 #include "WidgetUtilsGtk.h"
29 #define LOGMPRIS(msg, ...) \
30 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
31 ("MPRISServiceHandler=%p, " msg, this, ##__VA_ARGS__))
36 // A global counter tracking the total images saved in the system and it will be
37 // used to form a unique image file name.
38 static uint32_t gImageNumber
= 0;
40 static inline Maybe
<dom::MediaControlKey
> GetMediaControlKey(
41 const gchar
* aMethodName
) {
42 const std::unordered_map
<std::string
, dom::MediaControlKey
> map
= {
43 {"Raise", dom::MediaControlKey::Focus
},
44 {"Next", dom::MediaControlKey::Nexttrack
},
45 {"Previous", dom::MediaControlKey::Previoustrack
},
46 {"Pause", dom::MediaControlKey::Pause
},
47 {"PlayPause", dom::MediaControlKey::Playpause
},
48 {"Stop", dom::MediaControlKey::Stop
},
49 {"Play", dom::MediaControlKey::Play
}};
51 auto it
= map
.find(aMethodName
);
52 return it
== map
.end() ? Nothing() : Some(it
->second
);
55 static void HandleMethodCall(GDBusConnection
* aConnection
, const gchar
* aSender
,
56 const gchar
* aObjectPath
,
57 const gchar
* aInterfaceName
,
58 const gchar
* aMethodName
, GVariant
* aParameters
,
59 GDBusMethodInvocation
* aInvocation
,
61 MOZ_ASSERT(aUserData
);
62 MOZ_ASSERT(NS_IsMainThread());
64 Maybe
<dom::MediaControlKey
> key
= GetMediaControlKey(aMethodName
);
65 if (key
.isNothing()) {
66 g_dbus_method_invocation_return_error(
67 aInvocation
, G_DBUS_ERROR
, G_DBUS_ERROR_NOT_SUPPORTED
,
68 "Method %s.%s.%s not supported", aObjectPath
, aInterfaceName
,
73 MPRISServiceHandler
* handler
= static_cast<MPRISServiceHandler
*>(aUserData
);
74 if (handler
->PressKey(key
.value())) {
75 g_dbus_method_invocation_return_value(aInvocation
, nullptr);
77 g_dbus_method_invocation_return_error(
78 aInvocation
, G_DBUS_ERROR
, G_DBUS_ERROR_FAILED
,
79 "%s.%s.%s is not available now", aObjectPath
, aInterfaceName
,
84 enum class Property
: uint8_t {
102 static inline Maybe
<dom::MediaControlKey
> GetPairedKey(Property aProperty
) {
104 case Property::eCanRaise
:
105 return Some(dom::MediaControlKey::Focus
);
106 case Property::eCanGoNext
:
107 return Some(dom::MediaControlKey::Nexttrack
);
108 case Property::eCanGoPrevious
:
109 return Some(dom::MediaControlKey::Previoustrack
);
110 case Property::eCanPlay
:
111 return Some(dom::MediaControlKey::Play
);
112 case Property::eCanPause
:
113 return Some(dom::MediaControlKey::Pause
);
119 static inline Maybe
<Property
> GetProperty(const gchar
* aPropertyName
) {
120 const std::unordered_map
<std::string
, Property
> map
= {
121 // org.mpris.MediaPlayer2 properties
122 {"Identity", Property::eIdentity
},
123 {"DesktopEntry", Property::eDesktopEntry
},
124 {"HasTrackList", Property::eHasTrackList
},
125 {"CanRaise", Property::eCanRaise
},
126 {"CanQuit", Property::eCanQuit
},
127 {"SupportedUriSchemes", Property::eSupportedUriSchemes
},
128 {"SupportedMimeTypes", Property::eSupportedMimeTypes
},
129 // org.mpris.MediaPlayer2.Player properties
130 {"CanGoNext", Property::eCanGoNext
},
131 {"CanGoPrevious", Property::eCanGoPrevious
},
132 {"CanPlay", Property::eCanPlay
},
133 {"CanPause", Property::eCanPause
},
134 {"CanSeek", Property::eCanSeek
},
135 {"CanControl", Property::eCanControl
},
136 {"PlaybackStatus", Property::eGetPlaybackStatus
},
137 {"Metadata", Property::eGetMetadata
}};
139 auto it
= map
.find(aPropertyName
);
140 return (it
== map
.end() ? Nothing() : Some(it
->second
));
143 static GVariant
* HandleGetProperty(GDBusConnection
* aConnection
,
144 const gchar
* aSender
,
145 const gchar
* aObjectPath
,
146 const gchar
* aInterfaceName
,
147 const gchar
* aPropertyName
, GError
** aError
,
148 gpointer aUserData
) {
149 MOZ_ASSERT(aUserData
);
150 MOZ_ASSERT(NS_IsMainThread());
152 Maybe
<Property
> property
= GetProperty(aPropertyName
);
153 if (property
.isNothing()) {
154 g_set_error(aError
, G_DBUS_ERROR
, G_DBUS_ERROR_NOT_SUPPORTED
,
155 "%s.%s %s is not supported", aObjectPath
, aInterfaceName
,
160 MPRISServiceHandler
* handler
= static_cast<MPRISServiceHandler
*>(aUserData
);
161 switch (property
.value()) {
162 case Property::eSupportedUriSchemes
:
163 case Property::eSupportedMimeTypes
:
164 // No plan to implement OpenUri for now
165 return g_variant_new_strv(nullptr, 0);
166 case Property::eGetPlaybackStatus
:
167 return handler
->GetPlaybackStatus();
168 case Property::eGetMetadata
:
169 return handler
->GetMetadataAsGVariant();
170 case Property::eIdentity
:
171 return g_variant_new_string(handler
->Identity());
172 case Property::eDesktopEntry
:
173 return g_variant_new_string(handler
->DesktopEntry());
174 case Property::eHasTrackList
:
175 case Property::eCanQuit
:
176 case Property::eCanSeek
:
177 return g_variant_new_boolean(false);
178 // Play/Pause would be blocked if CanControl is false
179 case Property::eCanControl
:
180 return g_variant_new_boolean(true);
181 case Property::eCanRaise
:
182 case Property::eCanGoNext
:
183 case Property::eCanGoPrevious
:
184 case Property::eCanPlay
:
185 case Property::eCanPause
:
186 Maybe
<dom::MediaControlKey
> key
= GetPairedKey(property
.value());
187 MOZ_ASSERT(key
.isSome());
188 return g_variant_new_boolean(handler
->IsMediaKeySupported(key
.value()));
191 MOZ_ASSERT_UNREACHABLE("Switch statement is incomplete");
195 static gboolean
HandleSetProperty(GDBusConnection
* aConnection
,
196 const gchar
* aSender
,
197 const gchar
* aObjectPath
,
198 const gchar
* aInterfaceName
,
199 const gchar
* aPropertyName
, GVariant
* aValue
,
200 GError
** aError
, gpointer aUserData
) {
201 MOZ_ASSERT(aUserData
);
202 MOZ_ASSERT(NS_IsMainThread());
203 g_set_error(aError
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
204 "%s:%s setting is not supported", aInterfaceName
, aPropertyName
);
208 static const GDBusInterfaceVTable gInterfaceVTable
= {
209 HandleMethodCall
, HandleGetProperty
, HandleSetProperty
};
211 void MPRISServiceHandler::OnNameAcquiredStatic(GDBusConnection
* aConnection
,
213 gpointer aUserData
) {
214 MOZ_ASSERT(aUserData
);
215 static_cast<MPRISServiceHandler
*>(aUserData
)->OnNameAcquired(aConnection
,
219 void MPRISServiceHandler::OnNameLostStatic(GDBusConnection
* aConnection
,
221 gpointer aUserData
) {
222 MOZ_ASSERT(aUserData
);
223 static_cast<MPRISServiceHandler
*>(aUserData
)->OnNameLost(aConnection
, aName
);
226 void MPRISServiceHandler::OnBusAcquiredStatic(GDBusConnection
* aConnection
,
228 gpointer aUserData
) {
229 MOZ_ASSERT(aUserData
);
230 static_cast<MPRISServiceHandler
*>(aUserData
)->OnBusAcquired(aConnection
,
234 void MPRISServiceHandler::OnNameAcquired(GDBusConnection
* aConnection
,
235 const gchar
* aName
) {
236 LOGMPRIS("OnNameAcquired: %s", aName
);
237 mConnection
= aConnection
;
240 void MPRISServiceHandler::OnNameLost(GDBusConnection
* aConnection
,
241 const gchar
* aName
) {
242 LOGMPRIS("OnNameLost: %s", aName
);
243 mConnection
= nullptr;
244 if (!mRootRegistrationId
) {
248 if (g_dbus_connection_unregister_object(aConnection
, mRootRegistrationId
)) {
249 mRootRegistrationId
= 0;
251 // Note: Most code examples in the internet probably dont't even check the
253 // according to the spec it _can_ return false.
254 LOGMPRIS("Unable to unregister root object from within onNameLost!");
257 if (!mPlayerRegistrationId
) {
261 if (g_dbus_connection_unregister_object(aConnection
, mPlayerRegistrationId
)) {
262 mPlayerRegistrationId
= 0;
264 // Note: Most code examples in the internet probably dont't even check the
266 // according to the spec it _can_ return false.
267 LOGMPRIS("Unable to unregister object from within onNameLost!");
271 void MPRISServiceHandler::OnBusAcquired(GDBusConnection
* aConnection
,
272 const gchar
* aName
) {
273 GUniquePtr
<GError
> error
;
274 LOGMPRIS("OnBusAcquired: %s", aName
);
276 mRootRegistrationId
= g_dbus_connection_register_object(
277 aConnection
, DBUS_MPRIS_OBJECT_PATH
, mIntrospectionData
->interfaces
[0],
278 &gInterfaceVTable
, this, /* user_data */
279 nullptr, /* user_data_free_func */
280 getter_Transfers(error
)); /* GError** */
282 if (mRootRegistrationId
== 0) {
283 LOGMPRIS("Failed at root registration: %s",
284 error
? error
->message
: "Unknown Error");
288 mPlayerRegistrationId
= g_dbus_connection_register_object(
289 aConnection
, DBUS_MPRIS_OBJECT_PATH
, mIntrospectionData
->interfaces
[1],
290 &gInterfaceVTable
, this, /* user_data */
291 nullptr, /* user_data_free_func */
292 getter_Transfers(error
)); /* GError** */
294 if (mPlayerRegistrationId
== 0) {
295 LOGMPRIS("Failed at object registration: %s",
296 error
? error
->message
: "Unknown Error");
300 bool MPRISServiceHandler::Open() {
301 MOZ_ASSERT(!mInitialized
);
302 MOZ_ASSERT(NS_IsMainThread());
303 GUniquePtr
<GError
> error
;
304 gchar serviceName
[256];
307 SprintfLiteral(serviceName
, DBUS_MPRIS_SERVICE_NAME
".instance%d", getpid());
309 g_bus_own_name(G_BUS_TYPE_SESSION
, serviceName
,
310 // Enter a waiting queue until this service name is free
311 // (likely another FF instance is running/has been crashed)
312 G_BUS_NAME_OWNER_FLAGS_NONE
, OnBusAcquiredStatic
,
313 OnNameAcquiredStatic
, OnNameLostStatic
, this, nullptr);
315 /* parse introspection data */
316 mIntrospectionData
= dont_AddRef(
317 g_dbus_node_info_new_for_xml(introspection_xml
, getter_Transfers(error
)));
319 if (!mIntrospectionData
) {
320 LOGMPRIS("Failed at parsing XML Interface definition: %s",
321 error
? error
->message
: "Unknown Error");
329 MPRISServiceHandler::MPRISServiceHandler() = default;
330 MPRISServiceHandler::~MPRISServiceHandler() {
331 MOZ_ASSERT(!mInitialized
, "Close hasn't been called!");
334 void MPRISServiceHandler::Close() {
335 gchar serviceName
[256];
336 SprintfLiteral(serviceName
, DBUS_MPRIS_SERVICE_NAME
".instance%d", getpid());
338 // Reset playback state and metadata before disconnect from dbus.
339 SetPlaybackState(dom::MediaSessionPlaybackState::None
);
342 OnNameLost(mConnection
, serviceName
);
345 g_bus_unown_name(mOwnerId
);
348 mIntrospectionData
= nullptr;
350 mInitialized
= false;
351 MediaControlKeySource::Close();
354 bool MPRISServiceHandler::IsOpened() const { return mInitialized
; }
356 void MPRISServiceHandler::InitIdentity() {
358 nsCOMPtr
<nsIXULAppInfo
> appInfo
=
359 do_GetService("@mozilla.org/xre/app-info;1", &rv
);
361 MOZ_ASSERT(NS_SUCCEEDED(rv
));
362 rv
= appInfo
->GetVendor(mIdentity
);
363 MOZ_ASSERT(NS_SUCCEEDED(rv
));
364 rv
= appInfo
->GetName(mDesktopEntry
);
365 MOZ_ASSERT(NS_SUCCEEDED(rv
));
367 mIdentity
.Append(' ');
368 mIdentity
.Append(mDesktopEntry
);
370 // Compute the desktop entry name like nsAppRunner does for g_set_prgname
371 ToLowerCase(mDesktopEntry
);
374 const char* MPRISServiceHandler::Identity() const {
375 NS_WARNING_ASSERTION(mInitialized
,
376 "MPRISServiceHandler should have been initialized.");
377 return mIdentity
.get();
380 const char* MPRISServiceHandler::DesktopEntry() const {
381 NS_WARNING_ASSERTION(mInitialized
,
382 "MPRISServiceHandler should have been initialized.");
383 return mDesktopEntry
.get();
386 bool MPRISServiceHandler::PressKey(dom::MediaControlKey aKey
) const {
387 MOZ_ASSERT(mInitialized
);
388 if (!IsMediaKeySupported(aKey
)) {
389 LOGMPRIS("%s is not supported", ToMediaControlKeyStr(aKey
));
392 LOGMPRIS("Press %s", ToMediaControlKeyStr(aKey
));
397 void MPRISServiceHandler::SetPlaybackState(
398 dom::MediaSessionPlaybackState aState
) {
399 LOGMPRIS("SetPlaybackState");
400 if (mPlaybackState
== aState
) {
404 MediaControlKeySource::SetPlaybackState(aState
);
406 GVariant
* state
= GetPlaybackStatus();
407 GVariantBuilder builder
;
408 g_variant_builder_init(&builder
, G_VARIANT_TYPE("a{sv}"));
409 g_variant_builder_add(&builder
, "{sv}", "PlaybackStatus", state
);
411 GVariant
* parameters
= g_variant_new(
412 "(sa{sv}as)", DBUS_MPRIS_PLAYER_INTERFACE
, &builder
, nullptr);
414 LOGMPRIS("Emitting MPRIS property changes for 'PlaybackStatus'");
415 Unused
<< EmitPropertiesChangedSignal(parameters
);
418 GVariant
* MPRISServiceHandler::GetPlaybackStatus() const {
419 switch (GetPlaybackState()) {
420 case dom::MediaSessionPlaybackState::Playing
:
421 return g_variant_new_string("Playing");
422 case dom::MediaSessionPlaybackState::Paused
:
423 return g_variant_new_string("Paused");
424 case dom::MediaSessionPlaybackState::None
:
425 return g_variant_new_string("Stopped");
427 MOZ_ASSERT_UNREACHABLE("Invalid Playback State");
432 void MPRISServiceHandler::SetMediaMetadata(
433 const dom::MediaMetadataBase
& aMetadata
) {
434 // Reset the index of the next available image to be fetched in the artwork,
435 // before checking the fetching process should be started or not. The image
436 // fetching process could be skipped if the image being fetching currently is
437 // in the artwork. If the current image fetching fails, the next availabe
438 // candidate should be the first image in the latest artwork
441 // No need to fetch a MPRIS image if
442 // 1) MPRIS image is being fetched, and the one in fetching is in the artwork
443 // 2) MPRIS image is not being fetched, and the one in use is in the artwork
444 if (!mFetchingUrl
.IsEmpty()) {
445 if (dom::IsImageIn(aMetadata
.mArtwork
, mFetchingUrl
)) {
447 "No need to load MPRIS image. The one being processed is in the "
449 // Set MPRIS without the image first. The image will be loaded to MPRIS
450 // asynchronously once it's fetched and saved into a local file
451 SetMediaMetadataInternal(aMetadata
);
454 } else if (!mCurrentImageUrl
.IsEmpty()) {
455 if (dom::IsImageIn(aMetadata
.mArtwork
, mCurrentImageUrl
)) {
456 LOGMPRIS("No need to load MPRIS image. The one in use is in the artwork");
457 SetMediaMetadataInternal(aMetadata
, false);
462 // Set MPRIS without the image first then load the image to MPRIS
464 SetMediaMetadataInternal(aMetadata
);
465 LoadImageAtIndex(mNextImageIndex
++);
468 bool MPRISServiceHandler::EmitMetadataChanged() const {
469 GVariantBuilder builder
;
470 g_variant_builder_init(&builder
, G_VARIANT_TYPE("a{sv}"));
471 g_variant_builder_add(&builder
, "{sv}", "Metadata", GetMetadataAsGVariant());
473 GVariant
* parameters
= g_variant_new(
474 "(sa{sv}as)", DBUS_MPRIS_PLAYER_INTERFACE
, &builder
, nullptr);
476 LOGMPRIS("Emit MPRIS property changes for 'Metadata'");
477 return EmitPropertiesChangedSignal(parameters
);
480 void MPRISServiceHandler::SetMediaMetadataInternal(
481 const dom::MediaMetadataBase
& aMetadata
, bool aClearArtUrl
) {
482 mMPRISMetadata
.UpdateFromMetadataBase(aMetadata
);
484 mMPRISMetadata
.mArtUrl
.Truncate();
486 EmitMetadataChanged();
489 void MPRISServiceHandler::ClearMetadata() {
490 mMPRISMetadata
.Clear();
491 mImageFetchRequest
.DisconnectIfExists();
492 RemoveAllLocalImages();
493 mCurrentImageUrl
.Truncate();
494 mFetchingUrl
.Truncate();
497 EmitMetadataChanged();
500 void MPRISServiceHandler::LoadImageAtIndex(const size_t aIndex
) {
501 MOZ_ASSERT(NS_IsMainThread());
503 if (aIndex
>= mMPRISMetadata
.mArtwork
.Length()) {
504 LOGMPRIS("Stop loading image to MPRIS. No available image");
505 mImageFetchRequest
.DisconnectIfExists();
509 const dom::MediaImage
& image
= mMPRISMetadata
.mArtwork
[aIndex
];
511 if (!dom::IsValidImageUrl(image
.mSrc
)) {
512 LOGMPRIS("Skip the image with invalid URL. Try next image");
513 LoadImageAtIndex(mNextImageIndex
++);
517 mImageFetchRequest
.DisconnectIfExists();
518 mFetchingUrl
= image
.mSrc
;
520 mImageFetcher
= MakeUnique
<dom::FetchImageHelper
>(image
);
521 RefPtr
<MPRISServiceHandler
> self
= this;
522 mImageFetcher
->FetchImage()
524 AbstractThread::MainThread(), __func__
,
525 [this, self
](const nsCOMPtr
<imgIContainer
>& aImage
) {
526 LOGMPRIS("The image is fetched successfully");
527 mImageFetchRequest
.Complete();
530 char* data
= nullptr;
531 // Only used to hold the image data
532 nsCOMPtr
<nsIInputStream
> inputStream
;
533 nsresult rv
= dom::GetEncodedImageBuffer(
534 aImage
, mMimeType
, getter_AddRefs(inputStream
), &size
, &data
);
535 if (NS_FAILED(rv
) || !inputStream
|| size
== 0 || !data
) {
536 LOGMPRIS("Failed to get the image buffer info. Try next image");
537 LoadImageAtIndex(mNextImageIndex
++);
541 if (SetImageToDisplay(data
, size
)) {
542 mCurrentImageUrl
= mFetchingUrl
;
543 LOGMPRIS("The MPRIS image is updated to the image from: %s",
544 NS_ConvertUTF16toUTF8(mCurrentImageUrl
).get());
546 LOGMPRIS("Failed to set image to MPRIS");
547 mCurrentImageUrl
.Truncate();
550 mFetchingUrl
.Truncate();
553 LOGMPRIS("Failed to fetch image. Try next image");
554 mImageFetchRequest
.Complete();
555 mFetchingUrl
.Truncate();
556 LoadImageAtIndex(mNextImageIndex
++);
558 ->Track(mImageFetchRequest
);
561 bool MPRISServiceHandler::SetImageToDisplay(const char* aImageData
,
562 uint32_t aDataSize
) {
563 if (!RenewLocalImageFile(aImageData
, aDataSize
)) {
566 MOZ_ASSERT(mLocalImageFile
);
568 mMPRISMetadata
.mArtUrl
= nsCString("file://");
569 mMPRISMetadata
.mArtUrl
.Append(mLocalImageFile
->NativePath());
571 LOGMPRIS("The image file is created at %s", mMPRISMetadata
.mArtUrl
.get());
572 return EmitMetadataChanged();
575 bool MPRISServiceHandler::RenewLocalImageFile(const char* aImageData
,
576 uint32_t aDataSize
) {
577 MOZ_ASSERT(aImageData
);
578 MOZ_ASSERT(aDataSize
!= 0);
580 if (!InitLocalImageFile()) {
581 LOGMPRIS("Failed to create a new image");
585 MOZ_ASSERT(mLocalImageFile
);
586 nsCOMPtr
<nsIOutputStream
> out
;
587 NS_NewLocalFileOutputStream(getter_AddRefs(out
), mLocalImageFile
,
588 PR_RDWR
| PR_CREATE_FILE
| PR_TRUNCATE
);
590 nsresult rv
= out
->Write(aImageData
, aDataSize
, &written
);
591 if (NS_FAILED(rv
) || written
!= aDataSize
) {
592 LOGMPRIS("Failed to write an image file");
593 RemoveAllLocalImages();
600 static const char* GetImageFileExtension(const char* aMimeType
) {
601 MOZ_ASSERT(strcmp(aMimeType
, IMAGE_PNG
) == 0);
605 bool MPRISServiceHandler::InitLocalImageFile() {
606 RemoveAllLocalImages();
608 if (!InitLocalImageFolder()) {
612 MOZ_ASSERT(mLocalImageFolder
);
613 MOZ_ASSERT(!mLocalImageFile
);
614 nsresult rv
= mLocalImageFolder
->Clone(getter_AddRefs(mLocalImageFile
));
616 LOGMPRIS("Failed to get the image folder");
621 MakeScopeExit([this, self
= RefPtr
<MPRISServiceHandler
>(this)] {
622 mLocalImageFile
= nullptr;
625 // Create an unique file name to work around the file caching mechanism in the
626 // Ubuntu. Once the image X specified by the filename Y is used in Ubuntu's
627 // MPRIS, this pair will be cached. As long as the filename is same, even the
628 // file content specified by Y is changed to Z, the image will stay unchanged.
629 // The image shown in the Ubuntu's notification is still X instead of Z.
630 // Changing the filename constantly works around this problem
632 SprintfLiteral(filename
, "%d_%d.%s", getpid(), gImageNumber
++,
633 GetImageFileExtension(mMimeType
.get()));
635 rv
= mLocalImageFile
->Append(NS_ConvertUTF8toUTF16(filename
));
637 LOGMPRIS("Failed to create an image filename");
641 rv
= mLocalImageFile
->Create(nsIFile::NORMAL_FILE_TYPE
, 0600);
643 LOGMPRIS("Failed to create an image file");
651 bool MPRISServiceHandler::InitLocalImageFolder() {
652 if (mLocalImageFolder
&& LocalImageFolderExists()) {
656 nsresult rv
= NS_ERROR_FAILURE
;
657 if (IsRunningUnderFlatpak()) {
658 // The XDG_DATA_HOME points to the same location in the host and guest
660 if (const auto* xdgDataHome
= g_getenv("XDG_DATA_HOME")) {
661 rv
= NS_NewNativeLocalFile(nsDependentCString(xdgDataHome
), true,
662 getter_AddRefs(mLocalImageFolder
));
665 rv
= NS_GetSpecialDirectory(XRE_USER_APP_DATA_DIR
,
666 getter_AddRefs(mLocalImageFolder
));
669 if (NS_FAILED(rv
) || !mLocalImageFolder
) {
670 LOGMPRIS("Failed to get the image folder");
674 auto cleanup
= MakeScopeExit([&] { mLocalImageFolder
= nullptr; });
676 rv
= mLocalImageFolder
->Append(u
"firefox-mpris"_ns
);
678 LOGMPRIS("Failed to name an image folder");
682 if (!LocalImageFolderExists()) {
683 rv
= mLocalImageFolder
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
685 LOGMPRIS("Failed to create an image folder");
694 void MPRISServiceHandler::RemoveAllLocalImages() {
695 if (!mLocalImageFolder
|| !LocalImageFolderExists()) {
699 nsresult rv
= mLocalImageFolder
->Remove(/* aRecursive */ true);
701 // It's ok to fail. The next removal is called when updating the
702 // media-session image, or closing the MPRIS.
703 LOGMPRIS("Failed to remove images");
706 LOGMPRIS("Abandon %s",
707 mLocalImageFile
? mLocalImageFile
->NativePath().get() : "nothing");
708 mMPRISMetadata
.mArtUrl
.Truncate();
709 mLocalImageFile
= nullptr;
710 mLocalImageFolder
= nullptr;
713 bool MPRISServiceHandler::LocalImageFolderExists() {
714 MOZ_ASSERT(mLocalImageFolder
);
717 nsresult rv
= mLocalImageFolder
->Exists(&exists
);
718 return NS_SUCCEEDED(rv
) && exists
;
721 GVariant
* MPRISServiceHandler::GetMetadataAsGVariant() const {
722 GVariantBuilder builder
;
723 g_variant_builder_init(&builder
, G_VARIANT_TYPE("a{sv}"));
724 g_variant_builder_add(&builder
, "{sv}", "mpris:trackid",
725 g_variant_new("o", DBUS_MPRIS_TRACK_PATH
));
727 g_variant_builder_add(
728 &builder
, "{sv}", "xesam:title",
729 g_variant_new_string(static_cast<const gchar
*>(
730 NS_ConvertUTF16toUTF8(mMPRISMetadata
.mTitle
).get())));
732 g_variant_builder_add(
733 &builder
, "{sv}", "xesam:album",
734 g_variant_new_string(static_cast<const gchar
*>(
735 NS_ConvertUTF16toUTF8(mMPRISMetadata
.mAlbum
).get())));
737 GVariantBuilder artistBuilder
;
738 g_variant_builder_init(&artistBuilder
, G_VARIANT_TYPE("as"));
739 g_variant_builder_add(
741 static_cast<const gchar
*>(
742 NS_ConvertUTF16toUTF8(mMPRISMetadata
.mArtist
).get()));
743 g_variant_builder_add(&builder
, "{sv}", "xesam:artist",
744 g_variant_builder_end(&artistBuilder
));
746 if (!mMPRISMetadata
.mArtUrl
.IsEmpty()) {
747 g_variant_builder_add(&builder
, "{sv}", "mpris:artUrl",
748 g_variant_new_string(static_cast<const gchar
*>(
749 mMPRISMetadata
.mArtUrl
.get())));
752 return g_variant_builder_end(&builder
);
755 void MPRISServiceHandler::EmitEvent(dom::MediaControlKey aKey
) const {
756 for (const auto& listener
: mListeners
) {
757 listener
->OnActionPerformed(dom::MediaControlAction(aKey
));
761 struct InterfaceProperty
{
762 const char* interface
;
763 const char* property
;
765 static const std::unordered_map
<dom::MediaControlKey
, InterfaceProperty
>
767 {dom::MediaControlKey::Focus
, {DBUS_MPRIS_INTERFACE
, "CanRaise"}},
768 {dom::MediaControlKey::Nexttrack
,
769 {DBUS_MPRIS_PLAYER_INTERFACE
, "CanGoNext"}},
770 {dom::MediaControlKey::Previoustrack
,
771 {DBUS_MPRIS_PLAYER_INTERFACE
, "CanGoPrevious"}},
772 {dom::MediaControlKey::Play
, {DBUS_MPRIS_PLAYER_INTERFACE
, "CanPlay"}},
773 {dom::MediaControlKey::Pause
,
774 {DBUS_MPRIS_PLAYER_INTERFACE
, "CanPause"}}};
776 void MPRISServiceHandler::SetSupportedMediaKeys(
777 const MediaKeysArray
& aSupportedKeys
) {
778 uint32_t supportedKeys
= 0;
779 for (const dom::MediaControlKey
& key
: aSupportedKeys
) {
780 supportedKeys
|= GetMediaKeyMask(key
);
783 if (mSupportedKeys
== supportedKeys
) {
784 LOGMPRIS("Supported keys stay the same");
788 uint32_t oldSupportedKeys
= mSupportedKeys
;
789 mSupportedKeys
= supportedKeys
;
791 // Emit related property changes
792 for (auto it
: gKeyProperty
) {
793 bool keyWasSupported
= oldSupportedKeys
& GetMediaKeyMask(it
.first
);
794 bool keyIsSupported
= mSupportedKeys
& GetMediaKeyMask(it
.first
);
795 if (keyWasSupported
!= keyIsSupported
) {
796 LOGMPRIS("Emit PropertiesChanged signal: %s.%s=%s", it
.second
.interface
,
797 it
.second
.property
, keyIsSupported
? "true" : "false");
798 EmitSupportedKeyChanged(it
.first
, keyIsSupported
);
803 bool MPRISServiceHandler::IsMediaKeySupported(dom::MediaControlKey aKey
) const {
804 return mSupportedKeys
& GetMediaKeyMask(aKey
);
807 bool MPRISServiceHandler::EmitSupportedKeyChanged(dom::MediaControlKey aKey
,
808 bool aSupported
) const {
809 auto it
= gKeyProperty
.find(aKey
);
810 if (it
== gKeyProperty
.end()) {
811 LOGMPRIS("No property for %s", ToMediaControlKeyStr(aKey
));
815 GVariantBuilder builder
;
816 g_variant_builder_init(&builder
, G_VARIANT_TYPE("a{sv}"));
817 g_variant_builder_add(&builder
, "{sv}",
818 static_cast<const gchar
*>(it
->second
.property
),
819 g_variant_new_boolean(aSupported
));
821 GVariant
* parameters
= g_variant_new(
822 "(sa{sv}as)", static_cast<const gchar
*>(it
->second
.interface
), &builder
,
825 LOGMPRIS("Emit MPRIS property changes for '%s.%s'", it
->second
.interface
,
826 it
->second
.property
);
827 return EmitPropertiesChangedSignal(parameters
);
830 bool MPRISServiceHandler::EmitPropertiesChangedSignal(
831 GVariant
* aParameters
) const {
833 LOGMPRIS("No D-Bus Connection. Cannot emit properties changed signal");
837 GError
* error
= nullptr;
838 if (!g_dbus_connection_emit_signal(
839 mConnection
, nullptr, DBUS_MPRIS_OBJECT_PATH
,
840 "org.freedesktop.DBus.Properties", "PropertiesChanged", aParameters
,
842 LOGMPRIS("Failed to emit MPRIS property changes: %s",
843 error
? error
->message
: "Unknown Error");
855 } // namespace widget
856 } // namespace mozilla