2024-11-01 14:10:00 +01:00
|
|
|
#ifdef _WIN32
|
2024-11-04 18:29:25 +01:00
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#include <objbase.h>
|
2024-11-20 16:44:21 +01:00
|
|
|
#include <psapi.h>
|
2024-11-04 18:29:25 +01:00
|
|
|
#include <shlobj.h>
|
|
|
|
#include <windows.h>
|
2024-11-20 16:44:21 +01:00
|
|
|
#include <appmodel.h>
|
2024-11-01 14:10:00 +01:00
|
|
|
#include <winrt/windows.foundation.h>
|
2024-11-06 11:36:32 +01:00
|
|
|
#include <winrt/windows.foundation.metadata.h>
|
2024-11-01 14:10:00 +01:00
|
|
|
#include <winrt/windows.media.control.h>
|
|
|
|
#include <winrt/windows.storage.streams.h>
|
|
|
|
|
|
|
|
#include <chrono>
|
2024-11-04 15:32:46 +01:00
|
|
|
#include <codecvt>
|
2024-11-04 18:29:25 +01:00
|
|
|
#include <filesystem>
|
2024-11-01 14:10:00 +01:00
|
|
|
|
|
|
|
#include "../backend.hpp"
|
2024-11-01 16:49:48 +01:00
|
|
|
#include "../utils.hpp"
|
2024-11-01 14:10:00 +01:00
|
|
|
|
|
|
|
using namespace winrt;
|
|
|
|
using namespace Windows::Media::Control;
|
|
|
|
using namespace Windows::Storage::Streams;
|
2024-11-04 15:32:46 +01:00
|
|
|
#define EM_DASH "\xE2\x80\x94"
|
2024-11-04 18:29:25 +01:00
|
|
|
// codecvt is deprecated, but there is no good portable way to do this, I could technically use the winapi as this is
|
|
|
|
// the windows backend tho
|
2024-11-01 14:10:00 +01:00
|
|
|
std::string toStdString(winrt::hstring in) {
|
2024-11-04 15:32:46 +01:00
|
|
|
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
|
|
|
|
return converter.to_bytes(in.c_str());
|
2024-11-01 14:10:00 +01:00
|
|
|
}
|
|
|
|
|
2024-11-20 16:44:21 +01:00
|
|
|
std::string getAppModelIdOfProcess(HANDLE hProc) {
|
|
|
|
UINT32 length = 0;
|
|
|
|
LONG rc = GetApplicationUserModelId(hProc, &length, NULL);
|
|
|
|
if (rc != ERROR_INSUFFICIENT_BUFFER)
|
|
|
|
return "";
|
|
|
|
|
|
|
|
PWSTR fullName = (PWSTR)malloc(length * sizeof(*fullName));
|
|
|
|
if (!fullName)
|
|
|
|
return "";
|
|
|
|
|
|
|
|
rc = GetApplicationUserModelId(hProc, &length, fullName);
|
|
|
|
if (rc != ERROR_SUCCESS) {
|
|
|
|
free(fullName);
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string name = toStdString(fullName);
|
|
|
|
free(fullName);
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string getProcessNameFromAppModelId(std::string appModelId) {
|
|
|
|
DWORD processes[1024];
|
|
|
|
DWORD cbNeeded;
|
|
|
|
|
|
|
|
if (!EnumProcesses(processes, sizeof(processes), &cbNeeded))
|
|
|
|
return "";
|
|
|
|
|
|
|
|
unsigned int processCount = cbNeeded / sizeof(DWORD);
|
|
|
|
|
|
|
|
for (DWORD i = 0; i < processCount; i++) {
|
|
|
|
DWORD processID = processes[i];
|
|
|
|
|
|
|
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processID);
|
|
|
|
if (hProcess) {
|
|
|
|
std::string modelid = getAppModelIdOfProcess(hProcess);
|
|
|
|
|
|
|
|
if (modelid != appModelId) {
|
|
|
|
CloseHandle(hProcess);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
char exeName[MAX_PATH]{};
|
|
|
|
DWORD size = MAX_PATH;
|
|
|
|
QueryFullProcessImageNameA(hProcess, 0, exeName, &size);
|
|
|
|
std::filesystem::path exePath = exeName;
|
|
|
|
CloseHandle(hProcess);
|
|
|
|
return exePath.filename().string();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2024-11-04 18:29:25 +01:00
|
|
|
bool CreateShortcut(std::string source, std::string target) {
|
|
|
|
CoInitialize(nullptr);
|
|
|
|
WCHAR src[MAX_PATH];
|
|
|
|
IShellLinkW* pShellLink = nullptr;
|
|
|
|
HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
|
|
|
reinterpret_cast<void**>(&pShellLink));
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr) && pShellLink) {
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, source.c_str(), -1, src, MAX_PATH);
|
|
|
|
pShellLink->SetPath(src);
|
|
|
|
|
|
|
|
IPersistFile* pPersistFile = nullptr;
|
|
|
|
hr = pShellLink->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&pPersistFile));
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr) && pPersistFile) {
|
|
|
|
WCHAR dst[MAX_PATH];
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, target.c_str(), -1, dst, MAX_PATH);
|
|
|
|
hr = pPersistFile->Save(dst, TRUE);
|
|
|
|
pPersistFile->Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
pShellLink->Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
CoUninitialize();
|
|
|
|
return SUCCEEDED(hr);
|
|
|
|
}
|
|
|
|
|
2024-11-08 19:57:34 +01:00
|
|
|
std::filesystem::path backend::getConfigDirectory() {
|
|
|
|
std::filesystem::path configDirectoryPath = std::getenv("APPDATA");
|
|
|
|
configDirectoryPath = configDirectoryPath / "PlayerLink";
|
|
|
|
return configDirectoryPath;
|
|
|
|
}
|
|
|
|
|
2024-11-04 18:29:25 +01:00
|
|
|
bool backend::toggleAutostart(bool enabled) {
|
|
|
|
std::filesystem::path shortcutPath = std::getenv("APPDATA");
|
2024-11-07 00:27:20 +01:00
|
|
|
shortcutPath = shortcutPath / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup";
|
|
|
|
std::filesystem::create_directories(shortcutPath);
|
2024-11-07 00:28:32 +01:00
|
|
|
shortcutPath = shortcutPath / "PlayerLink.lnk";
|
2024-11-08 19:57:34 +01:00
|
|
|
|
2024-11-04 18:29:25 +01:00
|
|
|
if (!enabled && std::filesystem::exists(shortcutPath)) {
|
|
|
|
std::filesystem::remove(shortcutPath);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
char binaryPath[MAX_PATH]{};
|
|
|
|
GetModuleFileNameA(NULL, binaryPath, MAX_PATH);
|
|
|
|
bool result = CreateShortcut(binaryPath, shortcutPath.string());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-11-01 14:10:00 +01:00
|
|
|
std::shared_ptr<MediaInfo> backend::getMediaInformation() {
|
|
|
|
auto sessionManager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
|
|
|
|
auto currentSession = sessionManager.GetCurrentSession();
|
|
|
|
if (!currentSession)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
auto playbackInfo = currentSession.GetPlaybackInfo();
|
|
|
|
auto mediaProperties = currentSession.TryGetMediaPropertiesAsync().get();
|
|
|
|
auto timelineInformation = currentSession.GetTimelineProperties();
|
|
|
|
if (!mediaProperties)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
auto endTime = std::chrono::duration_cast<std::chrono::milliseconds>(timelineInformation.EndTime()).count();
|
|
|
|
auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(timelineInformation.Position()).count();
|
|
|
|
|
|
|
|
auto thumbnail = mediaProperties.Thumbnail();
|
|
|
|
std::string thumbnailData = "";
|
|
|
|
|
|
|
|
if (thumbnail) {
|
|
|
|
auto stream = thumbnail.OpenReadAsync().get();
|
|
|
|
size_t size = static_cast<size_t>(stream.Size());
|
|
|
|
|
|
|
|
DataReader reader(stream);
|
|
|
|
reader.LoadAsync(static_cast<uint32_t>(size)).get();
|
|
|
|
|
|
|
|
std::vector<uint8_t> buffer(size);
|
|
|
|
reader.ReadBytes(buffer);
|
2024-11-04 23:11:50 +01:00
|
|
|
reader.Close();
|
2024-11-06 11:36:32 +01:00
|
|
|
|
2024-11-01 14:10:00 +01:00
|
|
|
thumbnailData = std::string(buffer.begin(), buffer.end());
|
2024-11-01 16:49:48 +01:00
|
|
|
stream.Close();
|
2024-11-01 14:10:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string artist = toStdString(mediaProperties.Artist());
|
2024-11-01 16:49:48 +01:00
|
|
|
std::string albumName = toStdString(mediaProperties.AlbumTitle());
|
2024-11-01 14:10:00 +01:00
|
|
|
if (artist == "")
|
|
|
|
artist = toStdString(mediaProperties.AlbumArtist()); // Needed for some apps
|
|
|
|
|
2024-11-04 15:32:46 +01:00
|
|
|
if (artist.find(EM_DASH) != std::string::npos) {
|
|
|
|
albumName = artist.substr(artist.find(EM_DASH) + 3);
|
|
|
|
artist = artist.substr(0, artist.find(EM_DASH));
|
2024-11-01 16:49:48 +01:00
|
|
|
utils::trim(artist);
|
|
|
|
utils::trim(albumName);
|
|
|
|
}
|
|
|
|
|
2024-11-20 16:44:21 +01:00
|
|
|
std::string modelId = toStdString(currentSession.SourceAppUserModelId());
|
|
|
|
|
|
|
|
// I do know that this is disgusting, but for some reason microsoft decided to switch out the exe name with the
|
|
|
|
// ApplicationUserModelId in some version of windows 11. So we check if it's an exe name, if not we are on some
|
|
|
|
// newer windows version and need to get the exe name from the model id. We cannot directly work with the model id
|
|
|
|
// because it's unique per machine and therefore would mess up configs with preconfigured apps.
|
|
|
|
if (modelId.find(".exe") == std::string::npos)
|
|
|
|
modelId = getProcessNameFromAppModelId(modelId);
|
|
|
|
|
2024-11-01 14:10:00 +01:00
|
|
|
return std::make_shared<MediaInfo>(
|
|
|
|
playbackInfo.PlaybackStatus() == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Paused,
|
2024-11-20 16:44:21 +01:00
|
|
|
toStdString(mediaProperties.Title()), artist, albumName, modelId, thumbnailData, endTime, elapsedTime);
|
2024-11-01 14:10:00 +01:00
|
|
|
}
|
2024-11-05 22:53:43 +01:00
|
|
|
|
2024-11-06 11:36:32 +01:00
|
|
|
bool backend::init() {
|
|
|
|
return winrt::Windows::Foundation::Metadata::ApiInformation::IsTypePresent(
|
|
|
|
L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager");
|
|
|
|
}
|
2024-11-05 22:53:43 +01:00
|
|
|
|
2024-11-04 15:32:46 +01:00
|
|
|
#undef EM_DASH
|
2024-11-07 00:28:32 +01:00
|
|
|
#endif
|