extern "C" { #include "media.h" #include "common/time.h" #include "common/windows/unicode.h" #include "common/windows/com.h" } #if FF_HAVE_WINRT #include #include #include #include #include #include #include #include using winrt::impl::abi_t; using winrt::Windows::Foundation::IAsyncOperation; using winrt::Windows::Foundation::IAsyncOperationWithProgress; static inline void deleteHstring(HSTRING* pstr) { if (*pstr) { WindowsDeleteString(*pstr); } } static inline void ffStrbufSetHstring(FFstrbuf* destination, HSTRING value) { uint32_t length; const wchar_t* raw = WindowsGetStringRawBuffer(value, &length); ffStrbufSetNWS(destination, length, raw); } template static inline HRESULT ffGetActivationFactory(const wchar_t* className, REFIID iid, Interface** factory) { HSTRING_HEADER header; HSTRING runtimeClass; HRESULT hr = WindowsCreateStringReference(className, (UINT32)::wcslen(className), &header, &runtimeClass); if (FAILED(hr)) { return hr; } return RoGetActivationFactory(runtimeClass, iid, reinterpret_cast(factory)); } template static inline HRESULT ffQueryInterface(SourceAbi* source, abi_t** target) { return source->QueryInterface(winrt::guid_of(), reinterpret_cast(target)); } template static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** result) { IAsyncInfo* FF_AUTO_RELEASE_COM_OBJECT asyncInfo = NULL; HRESULT hr = ffQueryInterface(operation, &asyncInfo); if (FAILED(hr)) { return hr; } AsyncStatus status = AsyncStatus::Started; for (;;) { hr = asyncInfo->get_Status(&status); if (FAILED(hr)) { return hr; } if (status == AsyncStatus::Started) { ffTimeSleep(0); } else { break; } } if (status != AsyncStatus::Completed) { HRESULT errorCode = E_FAIL; asyncInfo->get_ErrorCode(&errorCode); return FAILED(errorCode) ? errorCode : E_FAIL; } return operation->GetResults((void**) result); } template static HRESULT ffRunAndWait(TOperation&& operation, abi_t** result) { abi_t>* FF_AUTO_RELEASE_COM_OBJECT opResult = NULL; HRESULT hr = operation(reinterpret_cast(&opResult)); if (FAILED(hr) || !opResult) { return hr; } return ffWaitForAsyncOperation(opResult, result); } template static HRESULT ffRunAndWait2(TOperation&& operation, abi_t** result) { *result = NULL; abi_t>* FF_AUTO_RELEASE_COM_OBJECT opResult = NULL; HRESULT hr = operation(reinterpret_cast(&opResult)); if (FAILED(hr) || !opResult) { return hr; } return ffWaitForAsyncOperation(opResult, result); } static HRESULT ffSaveThumbnailToTempPath( abi_t* thumbnail, FFstrbuf* destination) { abi_t* FF_AUTO_RELEASE_COM_OBJECT contentStream = NULL; HRESULT hr = ffRunAndWait([=](void** result) { return thumbnail->OpenReadAsync(result); }, &contentStream); if (FAILED(hr) || !contentStream) { return FAILED(hr) ? hr : E_FAIL; } abi_t* FF_AUTO_RELEASE_COM_OBJECT randomAccessStream = NULL; hr = ffQueryInterface(contentStream, &randomAccessStream); if (FAILED(hr)) { return hr; } UINT64 size = 0; hr = randomAccessStream->get_Size(&size); if (FAILED(hr) || size == 0) { return FAILED(hr) ? hr : S_FALSE; } if (size > 0xFFFFFFFFu) { return HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE); } abi_t* FF_AUTO_RELEASE_COM_OBJECT bufferFactory = NULL; hr = ffGetActivationFactory(L"Windows.Storage.Streams.Buffer", winrt::guid_of(), &bufferFactory); if (FAILED(hr)) { return hr; } abi_t* FF_AUTO_RELEASE_COM_OBJECT buffer = NULL; hr = bufferFactory->Create((UINT32) size, reinterpret_cast(&buffer)); if (FAILED(hr) || !buffer) { return FAILED(hr) ? hr : E_FAIL; } abi_t* FF_AUTO_RELEASE_COM_OBJECT inputStream = NULL; hr = ffQueryInterface(contentStream, &inputStream); if (FAILED(hr)) { return hr; } abi_t* FF_AUTO_RELEASE_COM_OBJECT readBuffer = NULL; hr = ffRunAndWait2([=](void** result) { return inputStream->ReadAsync(buffer, (uint32_t) size, (uint32_t) winrt::Windows::Storage::Streams::InputStreamOptions::None, result); }, &readBuffer); if (FAILED(hr) || !readBuffer) { return FAILED(hr) ? hr : E_FAIL; } UINT32 length = 0; hr = readBuffer->get_Length(&length); if (FAILED(hr) || length == 0) { return FAILED(hr) ? hr : S_FALSE; } Windows::Storage::Streams::IBufferByteAccess* FF_AUTO_RELEASE_COM_OBJECT byteAccess = NULL; hr = readBuffer->QueryInterface(IID_PPV_ARGS(&byteAccess)); if (FAILED(hr)) { return hr; } byte* bytes = NULL; hr = byteAccess->Buffer(&bytes); if (FAILED(hr) || !bytes) { return FAILED(hr) ? hr : E_FAIL; } wchar_t tempDirectory[MAX_PATH]; DWORD tempLength = GetTempPathW(MAX_PATH, tempDirectory); if (tempLength == 0 || tempLength >= MAX_PATH) { return HRESULT_FROM_WIN32(GetLastError()); } wchar_t tempFilePath[MAX_PATH]; if (!GetTempFileNameW(tempDirectory, L"fft", 0, tempFilePath)) { return HRESULT_FROM_WIN32(GetLastError()); } HANDLE file = CreateFileW(tempFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (file == INVALID_HANDLE_VALUE) { DWORD writeError = GetLastError(); DeleteFileW(tempFilePath); return HRESULT_FROM_WIN32(writeError); } DWORD written = 0; BOOL writtenOk = WriteFile(file, bytes, length, &written, NULL); NtClose(file); file = NULL; if (!writtenOk || written != length) { DWORD writeError = GetLastError(); DeleteFileW(tempFilePath); return HRESULT_FROM_WIN32(writtenOk ? ERROR_WRITE_FAULT : writeError); } ffStrbufSetWS(destination, tempFilePath); return S_OK; } static const char* getMedia(FFMediaResult* result, bool saveCover) { const char* error = ffInitCom(); if (error) { return error; } do { abi_t* FF_AUTO_RELEASE_COM_OBJECT managerStatics = NULL; HRESULT hr = ffGetActivationFactory(L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager", winrt::guid_of(), &managerStatics); if (FAILED(hr) || !managerStatics) { error = "winrt: RoGetActivationFactory(GlobalSystemMediaTransportControlsSessionManager) failed"; break; } abi_t* FF_AUTO_RELEASE_COM_OBJECT manager = NULL; hr = ffRunAndWait([=](void** result) { return managerStatics->RequestAsync(result); }, &manager); if (FAILED(hr) || !manager) { error = "winrt: RequestAsync().GetResults() failed"; break; } FF_A_CLEANUP(deleteHstring) HSTRING playerId = NULL; abi_t* FF_AUTO_RELEASE_COM_OBJECT session = NULL; if (instance.config.general.playerName.length) { abi_t>* FF_AUTO_RELEASE_COM_OBJECT sessions = NULL; hr = manager->GetSessions(reinterpret_cast(&sessions)); if (FAILED(hr) || !sessions) { error = "winrt: GetSessions() failed"; break; } uint32_t sessionCount = 0; hr = sessions->get_Size(&sessionCount); if (FAILED(hr)) { error = "winrt: GetSessions().get_Size() failed"; break; } for (uint32_t i = 0; i < sessionCount; i++) { abi_t* FF_AUTO_RELEASE_COM_OBJECT currentSession = NULL; hr = sessions->GetAt(i, reinterpret_cast(¤tSession)); if (FAILED(hr) || !currentSession) { continue; } hr = currentSession->get_SourceAppUserModelId(reinterpret_cast(&playerId)); if (FAILED(hr) || !playerId) { continue; } ffStrbufSetHstring(&result->playerId, playerId); if (ffStrbufContainIgnCase(&result->playerId, &instance.config.general.playerName)) { session = currentSession; currentSession = NULL; // Don't release the session object break; } deleteHstring(&playerId); ffStrbufClear(&result->playerId); } if (!session) { error = "winrt: No media session found with the specified player name"; break; } } else { hr = manager->GetCurrentSession(reinterpret_cast(&session)); if (FAILED(hr) || !session) { error = "winrt: GetCurrentSession() failed"; break; } hr = session->get_SourceAppUserModelId(reinterpret_cast(&playerId)); if (FAILED(hr)) { error = "winrt: get_SourceAppUserModelId() failed"; break; } ffStrbufSetHstring(&result->playerId, playerId); } abi_t* FF_AUTO_RELEASE_COM_OBJECT mediaProps = NULL; hr = ffRunAndWait([=](void** result) { return session->TryGetMediaPropertiesAsync(result); }, &mediaProps); if (FAILED(hr) || !mediaProps) { error = "winrt: TryGetMediaPropertiesAsync().GetResults() failed"; break; } abi_t* FF_AUTO_RELEASE_COM_OBJECT playbackInfo = NULL; hr = session->GetPlaybackInfo(reinterpret_cast(&playbackInfo)); bool isPlaying = false; double playbackRate = 1.0; if (SUCCEEDED(hr) && playbackInfo) { int32_t playbackStatusValue = 0; if (SUCCEEDED(playbackInfo->get_PlaybackStatus(&playbackStatusValue))) { isPlaying = playbackStatusValue == static_cast(winrt::Windows::Media::Control::GlobalSystemMediaTransportControlsSessionPlaybackStatus::Playing); switch (static_cast(playbackStatusValue)) { #define FF_MEDIA_SET_STATUS(status_code) \ case winrt::Windows::Media::Control::GlobalSystemMediaTransportControlsSessionPlaybackStatus::status_code: \ ffStrbufSetStatic(&result->status, #status_code); \ break; FF_MEDIA_SET_STATUS(Closed) FF_MEDIA_SET_STATUS(Opened) FF_MEDIA_SET_STATUS(Changing) FF_MEDIA_SET_STATUS(Stopped) FF_MEDIA_SET_STATUS(Playing) FF_MEDIA_SET_STATUS(Paused) #undef FF_MEDIA_SET_STATUS } } abi_t>* FF_AUTO_RELEASE_COM_OBJECT playbackRateRef = NULL; if (SUCCEEDED(playbackInfo->get_PlaybackRate(reinterpret_cast(&playbackRateRef))) && playbackRateRef) { if (SUCCEEDED(playbackRateRef->get_Value(&playbackRate)) && playbackRate < 0.0) { playbackRate = 0.0; } } } FF_A_CLEANUP(deleteHstring) HSTRING title = NULL; if (SUCCEEDED(mediaProps->get_Title(reinterpret_cast(&title)))) { ffStrbufSetHstring(&result->song, title); } FF_A_CLEANUP(deleteHstring) HSTRING artist = NULL; if (SUCCEEDED(mediaProps->get_Artist(reinterpret_cast(&artist)))) { ffStrbufSetHstring(&result->artist, artist); } FF_A_CLEANUP(deleteHstring) HSTRING album = NULL; if (SUCCEEDED(mediaProps->get_AlbumTitle(reinterpret_cast(&album)))) { ffStrbufSetHstring(&result->album, album); } abi_t* FF_AUTO_RELEASE_COM_OBJECT timelineProps = NULL; hr = session->GetTimelineProperties(reinterpret_cast(&timelineProps)); if (SUCCEEDED(hr) && timelineProps) { int64_t duration = 0; if (SUCCEEDED(timelineProps->get_EndTime(&duration)) && duration > 0) { result->length = (uint32_t) (duration / 10000); // Convert from 100-nanosecond units to milliseconds int64_t position = 0; if (SUCCEEDED(timelineProps->get_Position(&position))) { result->position = (uint32_t) (position / 10000); // Convert from 100-nanosecond units to milliseconds int64_t lastUpdatedTime = 0; if (isPlaying && SUCCEEDED(timelineProps->get_LastUpdatedTime(&lastUpdatedTime)) && lastUpdatedTime > 0) { uint64_t lastUpdatedTimeMs = ffFileTimeToUnixMs((uint64_t) lastUpdatedTime); uint64_t nowMs = ffTimeGetNow(); if (nowMs > lastUpdatedTimeMs) { result->position += (uint32_t) (((double) (nowMs - lastUpdatedTimeMs)) * playbackRate); } } } } } abi_t* FF_AUTO_RELEASE_COM_OBJECT appInfoStatics = NULL; hr = ffGetActivationFactory(L"Windows.ApplicationModel.AppInfo", winrt::guid_of(), &appInfoStatics); if (SUCCEEDED(hr) && appInfoStatics) { abi_t* FF_AUTO_RELEASE_COM_OBJECT appInfo = NULL; if (SUCCEEDED(appInfoStatics->GetFromAppUserModelId(reinterpret_cast(playerId), reinterpret_cast(&appInfo))) && appInfo) { abi_t* FF_AUTO_RELEASE_COM_OBJECT displayInfo = NULL; if (SUCCEEDED(appInfo->get_DisplayInfo(reinterpret_cast(&displayInfo))) && displayInfo) { FF_A_CLEANUP(deleteHstring) HSTRING displayName = NULL; if (SUCCEEDED(displayInfo->get_DisplayName(reinterpret_cast(&displayName)))) { ffStrbufSetHstring(&result->player, displayName); } } } } if (result->player.length == 0) { ffStrbufSet(&result->player, &result->playerId); if (ffStrbufEndsWithIgnCaseS(&result->player, ".exe")) { ffStrbufSubstrBefore(&result->player, result->player.length - 4); } } if (saveCover) { abi_t* FF_AUTO_RELEASE_COM_OBJECT thumbnail = NULL; hr = mediaProps->get_Thumbnail(reinterpret_cast(&thumbnail)); if (SUCCEEDED(hr) && thumbnail) { if (SUCCEEDED(ffSaveThumbnailToTempPath(thumbnail, &result->cover)) && result->cover.length > 0) { result->removeCoverAfterUse = true; } } } } while (false); return error; } #else static const char* getMedia(FFMediaResult* media, bool saveCover) { FF_UNUSED(media, saveCover); return "Fastfetch is not compiled with WinRT support"; } #endif // FF_HAVE_WINRT extern "C" void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { const char* error = getMedia(media, saveCover); ffStrbufAppendS(&media->error, error); }