Bug 1734943 [wpt PR 31170] - Correct scrolling contents cull rect, a=testonly
[gecko.git] / widget / gtk / MPRISServiceHandler.cpp
blob22c68c0b8da90556bdd9d4256894eec6fcb1d9a5
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"
9 #include <stdint.h>
10 #include <inttypes.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__))
27 namespace mozilla {
28 namespace widget {
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,
54 gpointer aUserData) {
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,
63 aMethodName);
64 return;
67 MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
68 if (handler->PressKey(key.value())) {
69 g_dbus_method_invocation_return_value(aInvocation, nullptr);
70 } else {
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,
74 aMethodName);
78 enum class Property : uint8_t {
79 eIdentity,
80 eDesktopEntry,
81 eHasTrackList,
82 eCanRaise,
83 eCanQuit,
84 eSupportedUriSchemes,
85 eSupportedMimeTypes,
86 eCanGoNext,
87 eCanGoPrevious,
88 eCanPlay,
89 eCanPause,
90 eCanSeek,
91 eCanControl,
92 eGetPlaybackStatus,
93 eGetMetadata,
96 static inline Maybe<mozilla::dom::MediaControlKey> GetPairedKey(
97 Property aProperty) {
98 switch (aProperty) {
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);
109 default:
110 return Nothing();
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,
151 aPropertyName);
152 return nullptr;
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");
187 return nullptr;
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);
200 return false;
203 static const GDBusInterfaceVTable gInterfaceVTable = {
204 HandleMethodCall, HandleGetProperty, HandleSetProperty};
206 void MPRISServiceHandler::OnNameAcquiredStatic(GDBusConnection* aConnection,
207 const gchar* aName,
208 gpointer aUserData) {
209 MOZ_ASSERT(aUserData);
210 static_cast<MPRISServiceHandler*>(aUserData)->OnNameAcquired(aConnection,
211 aName);
214 void MPRISServiceHandler::OnNameLostStatic(GDBusConnection* aConnection,
215 const gchar* aName,
216 gpointer aUserData) {
217 MOZ_ASSERT(aUserData);
218 static_cast<MPRISServiceHandler*>(aUserData)->OnNameLost(aConnection, aName);
221 void MPRISServiceHandler::OnBusAcquiredStatic(GDBusConnection* aConnection,
222 const gchar* aName,
223 gpointer aUserData) {
224 MOZ_ASSERT(aUserData);
225 static_cast<MPRISServiceHandler*>(aUserData)->OnBusAcquired(aConnection,
226 aName);
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) {
240 return;
243 if (g_dbus_connection_unregister_object(aConnection, mRootRegistrationId)) {
244 mRootRegistrationId = 0;
245 } else {
246 // Note: Most code examples in the internet probably dont't even check the
247 // result here, but
248 // according to the spec it _can_ return false.
249 LOGMPRIS("Unable to unregister root object from within onNameLost!");
252 if (!mPlayerRegistrationId) {
253 return;
256 if (g_dbus_connection_unregister_object(aConnection, mPlayerRegistrationId)) {
257 mPlayerRegistrationId = 0;
258 } else {
259 // Note: Most code examples in the internet probably dont't even check the
260 // result here, but
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");
280 if (error) {
281 g_error_free(error);
283 return;
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");
295 if (error) {
296 g_error_free(error);
301 bool MPRISServiceHandler::Open() {
302 MOZ_ASSERT(!mInitialized);
303 MOZ_ASSERT(NS_IsMainThread());
304 GError* error = nullptr;
305 gchar serviceName[256];
307 InitIdentity();
308 SprintfLiteral(serviceName, DBUS_MPRIS_SERVICE_NAME ".instance%d", getpid());
309 mOwnerId =
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");
322 if (error) {
323 g_error_free(error);
325 return false;
328 mInitialized = true;
329 return true;
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);
342 ClearMetadata();
344 OnNameLost(mConnection, serviceName);
346 if (mOwnerId != 0) {
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() {
360 nsresult rv;
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));
391 return false;
393 LOGMPRIS("Press %s", ToMediaControlKeyStr(aKey));
394 EmitEvent(aKey);
395 return true;
398 void MPRISServiceHandler::SetPlaybackState(
399 dom::MediaSessionPlaybackState aState) {
400 LOGMPRIS("SetPlaybackState");
401 if (mPlaybackState == aState) {
402 return;
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");
427 default:
428 MOZ_ASSERT_UNREACHABLE("Invalid Playback State");
429 return nullptr;
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
440 mNextImageIndex = 0;
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)) {
447 LOGMPRIS(
448 "No need to load MPRIS image. The one being processed is in the "
449 "artwork");
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);
453 return;
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);
459 return;
463 // Set MPRIS without the image first then load the image to MPRIS
464 // asynchronously
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);
484 if (aClearArtUrl) {
485 mMPRISMetadata.mArtUrl.Truncate();
487 EmitMetadataChanged();
490 void MPRISServiceHandler::ClearMetadata() {
491 mMPRISMetadata.Clear();
492 mImageFetchRequest.DisconnectIfExists();
493 RemoveAllLocalImages();
494 mCurrentImageUrl.Truncate();
495 mFetchingUrl.Truncate();
496 mNextImageIndex = 0;
497 mSupportedKeys = 0;
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();
507 return;
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++);
515 return;
518 mImageFetchRequest.DisconnectIfExists();
519 mFetchingUrl = image.mSrc;
521 mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image);
522 RefPtr<MPRISServiceHandler> self = this;
523 mImageFetcher->FetchImage()
524 ->Then(
525 AbstractThread::MainThread(), __func__,
526 [this, self](const nsCOMPtr<imgIContainer>& aImage) {
527 LOGMPRIS("The image is fetched successfully");
528 mImageFetchRequest.Complete();
530 uint32_t size = 0;
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++);
539 return;
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());
546 } else {
547 LOGMPRIS("Failed to set image to MPRIS");
548 mCurrentImageUrl.Truncate();
551 mFetchingUrl.Truncate();
553 [this, self](bool) {
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)) {
565 return false;
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");
583 return false;
586 MOZ_ASSERT(mLocalImageFile);
587 nsCOMPtr<nsIOutputStream> out;
588 NS_NewLocalFileOutputStream(getter_AddRefs(out), mLocalImageFile,
589 PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
590 uint32_t written;
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();
595 return false;
598 return true;
601 static const char* GetImageFileExtension(const char* aMimeType) {
602 MOZ_ASSERT(strcmp(aMimeType, IMAGE_PNG) == 0);
603 return "png";
606 bool MPRISServiceHandler::InitLocalImageFile() {
607 RemoveAllLocalImages();
609 if (!InitLocalImageFolder()) {
610 return false;
613 MOZ_ASSERT(mLocalImageFolder);
614 MOZ_ASSERT(!mLocalImageFile);
615 nsresult rv = mLocalImageFolder->Clone(getter_AddRefs(mLocalImageFile));
616 if (NS_FAILED(rv)) {
617 LOGMPRIS("Failed to get the image folder");
618 return false;
621 auto cleanup =
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
632 char filename[64];
633 SprintfLiteral(filename, "%d_%d.%s", getpid(), gImageNumber++,
634 GetImageFileExtension(mMimeType.get()));
636 rv = mLocalImageFile->Append(NS_ConvertUTF8toUTF16(filename));
637 if (NS_FAILED(rv)) {
638 LOGMPRIS("Failed to create an image filename");
639 return false;
642 rv = mLocalImageFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
643 if (NS_FAILED(rv)) {
644 LOGMPRIS("Failed to create an image file");
645 return false;
648 cleanup.release();
649 return true;
652 bool MPRISServiceHandler::InitLocalImageFolder() {
653 if (mLocalImageFolder && LocalImageFolderExists()) {
654 return true;
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");
661 return false;
664 auto cleanup =
665 MakeScopeExit([this, self = RefPtr<MPRISServiceHandler>(this)] {
666 mLocalImageFolder = nullptr;
669 rv = mLocalImageFolder->Append(u"firefox-mpris"_ns);
670 if (NS_FAILED(rv)) {
671 LOGMPRIS("Failed to name an image folder");
672 return false;
675 if (!LocalImageFolderExists()) {
676 rv = mLocalImageFolder->Create(nsIFile::DIRECTORY_TYPE, 0700);
677 if (NS_FAILED(rv)) {
678 LOGMPRIS("Failed to create an image folder");
679 return false;
683 cleanup.release();
684 return true;
687 void MPRISServiceHandler::RemoveAllLocalImages() {
688 if (!mLocalImageFolder || !LocalImageFolderExists()) {
689 return;
692 nsresult rv = mLocalImageFolder->Remove(/* aRecursive */ true);
693 if (NS_FAILED(rv)) {
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);
709 bool exists;
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(
733 &artistBuilder, "s",
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,
759 InterfaceProperty>
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");
780 return;
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));
808 return false;
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,
819 nullptr);
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 {
828 if (!mConnection) {
829 LOGMPRIS("No D-Bus Connection. Cannot emit properties changed signal");
830 return false;
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,
837 &error)) {
838 LOGMPRIS("Failed to emit MPRIS property changes: %s",
839 error ? error->message : "Unknown Error");
840 if (error) {
841 g_error_free(error);
843 return false;
846 return true;
849 #undef LOGMPRIS
851 } // namespace widget
852 } // namespace mozilla