diff --git a/application/basilisk/base/content/browser.js b/application/basilisk/base/content/browser.js index 0769d286ad..64bab110d0 100644 --- a/application/basilisk/base/content/browser.js +++ b/application/basilisk/base/content/browser.js @@ -1795,12 +1795,16 @@ function HandleMediaControlAppCommand(action) { } function getWindowsUIUtils() { - if (AppConstants.platform != "win") { - return null; - } try { - return Cc["@mozilla.org/windows-ui-utils;1"] - .getService(Ci.nsIWindowsUIUtils); + if (AppConstants.platform == "win") { + return Cc["@mozilla.org/windows-ui-utils;1"] + .getService(Ci.nsIWindowsUIUtils); + } + if (AppConstants.platform == "macosx") { + return Cc["@mozilla.org/mac-media-control;1"] + .getService(Ci.nsIMacMediaControl); + } + return null; } catch (e) { return null; } diff --git a/widget/cocoa/MacMediaControlUtils.h b/widget/cocoa/MacMediaControlUtils.h new file mode 100644 index 0000000000..25160a824d --- /dev/null +++ b/widget/cocoa/MacMediaControlUtils.h @@ -0,0 +1,23 @@ +/* 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 mozilla_widget_MacMediaControlUtils_h__ +#define mozilla_widget_MacMediaControlUtils_h__ + +#include "nsIMacMediaControl.h" + +#define NS_MAC_MEDIACONTROL_CONTRACTID "@mozilla.org/mac-media-control;1" + +class MacMediaControlUtils final : public nsIMacMediaControl { +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMACMEDIACONTROL + + MacMediaControlUtils() = default; + +private: + ~MacMediaControlUtils() = default; +}; + +#endif // mozilla_widget_MacMediaControlUtils_h__ diff --git a/widget/cocoa/MacMediaControlUtils.mm b/widget/cocoa/MacMediaControlUtils.mm new file mode 100644 index 0000000000..a357fac7f2 --- /dev/null +++ b/widget/cocoa/MacMediaControlUtils.mm @@ -0,0 +1,243 @@ +/* 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/. */ + +#import +#import +#include + +#include "MacMediaControlUtils.h" +#include "mozilla/Unused.h" +#include "nsCocoaUtils.h" + +// This must be included last: +#include "nsObjCExceptions.h" + +NS_IMPL_ISUPPORTS(MacMediaControlUtils, nsIMacMediaControl) + +namespace { + +static NSMutableDictionary* sNowPlayingInfo = nil; +static bool sTriedLoadMediaPlayerFramework = false; +static void* sMediaPlayerHandle = nullptr; + +static void EnsureMediaPlayerFrameworkLoaded(); + +static NSString* +GetMediaPlayerStringConstant(const char* aSymbolName, NSString* aFallback) +{ + EnsureMediaPlayerFrameworkLoaded(); + + if (!sMediaPlayerHandle) { + sMediaPlayerHandle = dlopen("/System/Library/Frameworks/MediaPlayer.framework/MediaPlayer", + RTLD_LAZY | RTLD_NOLOAD); + if (!sMediaPlayerHandle) { + sMediaPlayerHandle = dlopen("/System/Library/Frameworks/MediaPlayer.framework/MediaPlayer", + RTLD_LAZY); + } + } + + if (!sMediaPlayerHandle) { + return aFallback; + } + + NSString* const* key = static_cast(dlsym(sMediaPlayerHandle, aSymbolName)); + return (key && *key) ? *key : aFallback; +} + +static NSString* +NowPlayingPlaybackRateKey() +{ + static NSString* key = GetMediaPlayerStringConstant("MPNowPlayingInfoPropertyPlaybackRate", + @"playbackRate"); + return key; +} + +static NSString* +NowPlayingElapsedTimeKey() +{ + static NSString* key = GetMediaPlayerStringConstant("MPNowPlayingInfoPropertyElapsedPlaybackTime", + @"elapsedPlaybackTime"); + return key; +} + +static NSString* +MediaItemTitleKey() +{ + static NSString* key = GetMediaPlayerStringConstant("MPMediaItemPropertyTitle", + @"title"); + return key; +} + +static NSString* +MediaItemArtistKey() +{ + static NSString* key = GetMediaPlayerStringConstant("MPMediaItemPropertyArtist", + @"artist"); + return key; +} + +static NSString* +MediaItemAlbumTitleKey() +{ + static NSString* key = GetMediaPlayerStringConstant("MPMediaItemPropertyAlbumTitle", + @"albumTitle"); + return key; +} + +static void +EnsureMediaPlayerFrameworkLoaded() +{ + if (sTriedLoadMediaPlayerFramework) { + return; + } + sTriedLoadMediaPlayerFramework = true; + + NSString* frameworkPath = @"/System/Library/Frameworks/MediaPlayer.framework"; + NSBundle* mediaPlayerBundle = [NSBundle bundleWithPath:frameworkPath]; + if (mediaPlayerBundle) { + [mediaPlayerBundle load]; + } +} + +static id +GetNowPlayingInfoCenter() +{ + EnsureMediaPlayerFrameworkLoaded(); + + Class centerClass = NSClassFromString(@"MPNowPlayingInfoCenter"); + if (!centerClass) { + return nil; + } + + SEL defaultCenterSel = NSSelectorFromString(@"defaultCenter"); + if (![centerClass respondsToSelector:defaultCenterSel]) { + return nil; + } + + return ((id (*)(id, SEL))objc_msgSend)(centerClass, defaultCenterSel); +} + +static void +ApplyNowPlayingInfo() +{ + id center = GetNowPlayingInfoCenter(); + if (!center) { + return; + } + + SEL setInfoSel = NSSelectorFromString(@"setNowPlayingInfo:"); + if (![center respondsToSelector:setInfoSel]) { + return; + } + + NSDictionary* info = (sNowPlayingInfo && [sNowPlayingInfo count] > 0) + ? [NSDictionary dictionaryWithDictionary:sNowPlayingInfo] + : nil; + + ((void (*)(id, SEL, id))objc_msgSend)(center, setInfoSel, info); +} + +static void +EnsureNowPlayingInfo() +{ + if (!sNowPlayingInfo) { + sNowPlayingInfo = [[NSMutableDictionary alloc] init]; + } +} + +} // namespace + +NS_IMETHODIMP +MacMediaControlUtils::SetSMTCPlaybackState(bool aPlaying) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + EnsureNowPlayingInfo(); + [sNowPlayingInfo setObject:[NSNumber numberWithDouble:(aPlaying ? 1.0 : 0.0)] + forKey:NowPlayingPlaybackRateKey()]; + [sNowPlayingInfo setObject:[NSNumber numberWithDouble:0.0] + forKey:NowPlayingElapsedTimeKey()]; + + id center = GetNowPlayingInfoCenter(); + if (center) { + SEL setPlaybackStateSel = NSSelectorFromString(@"setPlaybackState:"); + if ([center respondsToSelector:setPlaybackStateSel]) { + // MPNowPlayingPlaybackState: 1 = playing, 2 = paused + NSInteger state = aPlaying ? 1 : 2; + ((void (*)(id, SEL, NSInteger))objc_msgSend)(center, setPlaybackStateSel, state); + } + } + + ApplyNowPlayingInfo(); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +MacMediaControlUtils::SetSMTCMetadata(const nsAString& aTitle, + const nsAString& aArtist, + const nsAString& aAlbum) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + EnsureNowPlayingInfo(); + + NSString* title = nsCocoaUtils::ToNSString(aTitle); + NSString* artist = nsCocoaUtils::ToNSString(aArtist); + NSString* album = nsCocoaUtils::ToNSString(aAlbum); + NSString* fallbackTitle = [[NSProcessInfo processInfo] processName]; + + if (title && [title length] > 0) { + [sNowPlayingInfo setObject:title forKey:MediaItemTitleKey()]; + } else if (fallbackTitle && [fallbackTitle length] > 0) { + [sNowPlayingInfo setObject:fallbackTitle forKey:MediaItemTitleKey()]; + } else { + [sNowPlayingInfo removeObjectForKey:MediaItemTitleKey()]; + } + + if (artist && [artist length] > 0) { + [sNowPlayingInfo setObject:artist forKey:MediaItemArtistKey()]; + } else { + [sNowPlayingInfo removeObjectForKey:MediaItemArtistKey()]; + } + + if (album && [album length] > 0) { + [sNowPlayingInfo setObject:album forKey:MediaItemAlbumTitleKey()]; + } else { + [sNowPlayingInfo removeObjectForKey:MediaItemAlbumTitleKey()]; + } + + ApplyNowPlayingInfo(); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +MacMediaControlUtils::SetSMTCThumbnailURL(const nsAString& aThumbnailURL) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // Best effort: Artwork publishing requires MediaPlayer artwork classes that + // vary by macOS version. Keep this no-op for compatibility. + Unused << aThumbnailURL; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +MacMediaControlUtils::ClearSMTCMetadata() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (sNowPlayingInfo) { + [sNowPlayingInfo removeAllObjects]; + } + ApplyNowPlayingInfo(); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build index c4f887297d..364dedcfb8 100644 --- a/widget/cocoa/moz.build +++ b/widget/cocoa/moz.build @@ -21,6 +21,7 @@ EXPORTS += [ UNIFIED_SOURCES += [ 'ComplexTextInputPanel.mm', 'GfxInfo.mm', + 'MacMediaControlUtils.mm', 'NativeKeyBindings.mm', 'nsAppShell.mm', 'nsBidiKeyboard.mm', diff --git a/widget/cocoa/nsPrintOptionsX.mm b/widget/cocoa/nsPrintOptionsX.mm index d9aa17b42e..e15674b15b 100644 --- a/widget/cocoa/nsPrintOptionsX.mm +++ b/widget/cocoa/nsPrintOptionsX.mm @@ -8,6 +8,8 @@ #include "nsIServiceManager.h" #include "nsPrintOptionsX.h" #include "nsPrintSettingsX.h" +#include "nsCocoaUtils.h" +#include "nsIWebBrowserPrint.h" // The constants for paper orientation were renamed in 10.9. __MAC_10_9 is // defined on OS X 10.9 and later. Although 10.8 and earlier are not supported diff --git a/widget/cocoa/nsWidgetFactory.mm b/widget/cocoa/nsWidgetFactory.mm index 3bddaf95ce..b5c5286219 100644 --- a/widget/cocoa/nsWidgetFactory.mm +++ b/widget/cocoa/nsWidgetFactory.mm @@ -72,6 +72,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeThemeCocoa) #include "nsMacDockSupport.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacDockSupport) +#include "MacMediaControlUtils.h" +NS_GENERIC_FACTORY_CONSTRUCTOR(MacMediaControlUtils) + #include "nsMacWebAppUtils.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacWebAppUtils) @@ -112,6 +115,7 @@ NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID); NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID); NS_DEFINE_NAMED_CID(NS_MACDOCKSUPPORT_CID); +NS_DEFINE_NAMED_CID(NS_MAC_MEDIA_CONTROL_CID); NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID); NS_DEFINE_NAMED_CID(NS_STANDALONENATIVEMENU_CID); NS_DEFINE_NAMED_CID(NS_MACSYSTEMSTATUSBAR_CID); @@ -148,6 +152,7 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = { { &kNS_SYSTEMALERTSSERVICE_CID, false, NULL, OSXNotificationCenterConstructor }, { &kNS_NATIVEMENUSERVICE_CID, false, NULL, nsNativeMenuServiceXConstructor }, { &kNS_MACDOCKSUPPORT_CID, false, NULL, nsMacDockSupportConstructor }, + { &kNS_MAC_MEDIA_CONTROL_CID, false, NULL, MacMediaControlUtilsConstructor }, { &kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor }, { &kNS_STANDALONENATIVEMENU_CID, false, NULL, nsStandaloneNativeMenuConstructor }, { &kNS_MACSYSTEMSTATUSBAR_CID, false, NULL, nsSystemStatusBarCocoaConstructor }, @@ -186,6 +191,7 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { { "@mozilla.org/system-alerts-service;1", &kNS_SYSTEMALERTSSERVICE_CID }, { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID }, { "@mozilla.org/widget/macdocksupport;1", &kNS_MACDOCKSUPPORT_CID }, + { "@mozilla.org/mac-media-control;1", &kNS_MAC_MEDIA_CONTROL_CID }, { "@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID }, { "@mozilla.org/widget/standalonenativemenu;1", &kNS_STANDALONENATIVEMENU_CID }, { "@mozilla.org/widget/macsystemstatusbar;1", &kNS_MACSYSTEMSTATUSBAR_CID }, diff --git a/widget/moz.build b/widget/moz.build index c72b2066c3..ebf854a478 100644 --- a/widget/moz.build +++ b/widget/moz.build @@ -31,6 +31,7 @@ if toolkit == 'windows': elif toolkit == 'cocoa': XPIDL_SOURCES += [ 'nsIMacDockSupport.idl', + 'nsIMacMediaControl.idl', 'nsIMacWebAppUtils.idl', 'nsIStandaloneNativeMenu.idl', 'nsISystemStatusBar.idl', diff --git a/widget/nsIMacMediaControl.idl b/widget/nsIMacMediaControl.idl new file mode 100644 index 0000000000..eadf2ba236 --- /dev/null +++ b/widget/nsIMacMediaControl.idl @@ -0,0 +1,31 @@ +/* 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 "nsISupports.idl" + +[scriptable, uuid(9d16f8ac-9d47-4f16-95cf-5ec9b42d3e11)] +interface nsIMacMediaControl : nsISupports +{ + /** + * Publish playback state to macOS now playing surfaces. + */ + void setSMTCPlaybackState(in boolean aPlaying); + + /** + * Publish media metadata to macOS now playing surfaces. + */ + void setSMTCMetadata(in AString aTitle, + in AString aArtist, + in AString aAlbum); + + /** + * Publish media artwork URL. Best effort; may be ignored by implementation. + */ + void setSMTCThumbnailURL(in AString aThumbnailURL); + + /** + * Clear published media metadata. + */ + void clearSMTCMetadata(); +}; diff --git a/widget/nsWidgetsCID.h b/widget/nsWidgetsCID.h index 54ebe63abc..ef820e088a 100644 --- a/widget/nsWidgetsCID.h +++ b/widget/nsWidgetsCID.h @@ -155,6 +155,10 @@ #define NS_MACWEBAPPUTILS_CID \ { 0xe9096367, 0xddd9, 0x45e4, { 0xb7, 0x62, 0x49, 0xc0, 0xc1, 0x8b, 0x71, 0x19 } } +// {7e3bd1b8-e2f4-4fbe-9f1c-d6f7b98a215e} +#define NS_MAC_MEDIA_CONTROL_CID \ +{ 0x7e3bd1b8, 0xe2f4, 0x4fbe, { 0x9f, 0x1c, 0xd6, 0xf7, 0xb9, 0x8a, 0x21, 0x5e } } + //----------------------------------------------------------- //Printing //-----------------------------------------------------------