Compare commits

...

18 Commits
v1.0 ... main

Author SHA1 Message Date
EinTim23 c35416c928 feat: odesli/song.link integration 2025-01-16 13:56:29 +01:00
EinTim23 be3a50d42a updated readme images 2025-01-12 21:31:18 +01:00
EinTim23 aa734cf45a fixed linux app image build 2025-01-12 20:54:26 +01:00
EinTim23 cbc96614d0 fixed placeholder text on password inputs 2025-01-12 20:17:00 +01:00
EinTim23 e8ac296f35 automatic release creation 2025-01-12 15:02:15 +01:00
EinTim23 a6c77e256f add automated mac os build 2025-01-12 14:02:56 +01:00
EinTim23 3e16670a70 add automated windows build 2025-01-12 13:22:02 +01:00
EinTim23 017b21dbeb automated linux build 2025-01-12 12:40:29 +01:00
EinTim23 d13231e84e - Fix: settings window not auto focussing on mac os
- Optimized the windows backend
- Updated dependencies
2025-01-11 15:50:39 +01:00
EinTim23 4a68eba106 cleaned up the checkboxes 2024-11-25 21:21:21 +01:00
EinTim23 6f29111c40 lastfm gui 2024-11-25 20:21:05 +01:00
EinTim23 35d0ce747b feat: lastfm support (still missing gui) 2024-11-20 22:51:15 +01:00
EinTim23 bb093cdefe fix process detection on newer windows 11 builds 2024-11-20 16:44:21 +01:00
EinTim23 26af0323f2 Save config in a proper location 2024-11-08 19:57:34 +01:00
EinTim 7b5666a906
Remove doubled semicolon 2024-11-07 00:28:32 +01:00
EinTim23 322ceeaddb ensure that the autostart directories actually exist 2024-11-07 00:27:20 +01:00
EinTim 0c274c55c5
MACOS: fix set CMAKE_OSX_DEPLOYMENT_TARGET 2024-11-06 22:28:59 +01:00
EinTim23 31afe3372d LINUX: add system bus support 2024-11-06 19:03:01 +01:00
22 changed files with 1552 additions and 453 deletions

View File

@ -8,3 +8,4 @@ IndentCaseLabels: false
AccessModifierOffset: -4
ColumnLimit: 120
NamespaceIndentation: All
SortIncludes: false

179
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,179 @@
name: Build and Package AppImage, Windows Executable, and macOS DMG
on:
push:
branches:
- main
tags:
- 'v*'
pull_request:
jobs:
build-linux:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Set up CMake
uses: lukka/get-cmake@latest
with:
cmakeVersion: '3.22.0'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
libssl-dev \
libx11-dev \
libxext-dev \
libxrandr-dev \
libxinerama-dev \
libxcursor-dev \
zlib1g-dev \
libglu1-mesa-dev \
libgtk-3-dev \
libwayland-dev \
fuse
sudo modprobe fuse
- name: Configure with CMake
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
- name: Build the project
run: cmake --build build --config Release
- name: Download linuxdeploy
run: |
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
wget "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"
chmod +x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-gtk.sh
sudo mv linuxdeploy-x86_64.AppImage /usr/local/bin/linuxdeploy
- name: Create AppImage
run: |
cp -r build/PlayerLink linux/AppDir/usr/bin/
linuxdeploy --appdir=linux/AppDir --plugin gtk --output appimage
- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: PlayerLink-AppImage
path: ./*.AppImage
build-windows:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Set up CMake
uses: lukka/get-cmake@latest
with:
cmakeVersion: '3.22.0'
- name: Configure with CMake
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
- name: Build the project
run: cmake --build build --config Release
- name: Upload Windows artifact
uses: actions/upload-artifact@v4
with:
name: PlayerLink-Windows-Executable
path: build/Release/*
build-macos:
runs-on: macos-15
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Set up CMake
uses: lukka/get-cmake@latest
with:
cmakeVersion: '3.22.0'
- name: Configure with CMake
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
- name: Build the project
run: cmake --build build --config Release
- name: Create DMG package
run: |
mkdir -p dmg/PlayerLink
cp -r build/PlayerLink.app dmg/PlayerLink/
hdiutil create -volname "PlayerLink" -srcfolder dmg/PlayerLink -ov -format UDZO PlayerLink.dmg
- name: Upload macOS DMG artifact
uses: actions/upload-artifact@v4
with:
name: PlayerLink-macOS-DMG
path: PlayerLink.dmg
create-release:
if: startsWith(github.ref, 'refs/tags/')
needs: [build-linux, build-windows, build-macos]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: ./release-assets
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R ./release-assets
- name: Create GitHub Release
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: Release ${{ github.ref_name }}
draft: false
prerelease: false
- name: Upload AppImage
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./release-assets/PlayerLink-x86_64.AppImage
asset_name: PlayerLink-x86_64.AppImage
asset_content_type: application/octet-stream
- name: Upload Windows Executable
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./release-assets/PlayerLink.exe
asset_name: PlayerLink.exe
asset_content_type: application/octet-stream
- name: Upload macOS DMG
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./release-assets/PlayerLink.dmg
asset_name: PlayerLink.dmg
asset_content_type: application/octet-stream

6
.gitignore vendored
View File

@ -4,3 +4,9 @@ build/*
src/rsrc.hpp
PlayerLink.exe
.vscode/*
linux/AppDir/usr/share/doc/*
linux/AppDir/usr/lib/*
linux/AppDir/usr/bin/*
linux/AppDir/*
!linux/AppDir/usr
!.gitkeep

View File

@ -7,7 +7,7 @@ file(GLOB_RECURSE SOURCES "src/*.cpp")
if(APPLE)
list(APPEND SOURCES "src/backends/darwin.mm" ${CMAKE_SOURCE_DIR}/osx/icon.icns)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "" FORCE)
project ("PlayerLink" LANGUAGES C CXX OBJCXX)
else()
project ("PlayerLink" LANGUAGES C CXX)

View File

@ -7,7 +7,7 @@ Cross platform, universal discord rich presence for media players.
- Pretty much any linux distribution with gtk3 and dbus support
## Showcase
You can add predefined players to the settings.json to customise the name it shows in discord, edit the search button base url, and app icon. By default it will just display as "Music" without a search button or app icon. In the future I want to add an option to the ui to add custom apps.
You can add predefined players to the settings.json to customise the name it shows in discord, edit the search button base url, and app icon. By default it will just display as "Music" without a search button or app icon.
<p align="center" width="100%">
<img src="img/showcase.png" alt="rich presence" />
@ -32,8 +32,15 @@ The Mac OS backend is powered by the private MediaRemote framework. It provides
### Linux
The linux backend is powered by [MPRIS](https://specifications.freedesktop.org/mpris-spec/latest/). It allows to query the system wide media information via dbus.
## Adding custom apps to the settings.json
The config is currently located in the same folder as PlayerLink, this will be changed in a future release. An example on how to add custom apps to the json can be found [here](./settings.example.json). In the future there will be a UI to configure custom apps in a more user friendly way.
## Config
**Mac OS**
`~/Library/Application Support/PlayerLink`
**Linux**
`~/.config/PlayerLink`
**Windows**
`%appdata%\PlayerLink`
An example on how to add custom apps to the config can be found [here](./settings.example.json). In the future there will be a UI to configure custom apps in a more user friendly way.
## Building

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

View File

View File

@ -0,0 +1,6 @@
[Desktop Entry]
Name=PlayerLink
Exec=PlayerLink
Icon=PlayerLink
Type=Application
Categories=Utility;GTK;

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

View File

@ -4,6 +4,7 @@
#include <memory>
#include <string>
#include <filesystem>
struct MediaInfo {
bool paused;
@ -30,6 +31,7 @@ struct MediaInfo {
namespace backend {
bool init();
bool toggleAutostart(bool enabled);
std::filesystem::path getConfigDirectory();
std::shared_ptr<MediaInfo> getMediaInformation();
} // namespace backend

View File

@ -77,9 +77,18 @@ std::shared_ptr<MediaInfo> backend::getMediaInformation() {
durationMs, elapsedTimeMs);
}
std::filesystem::path backend::getConfigDirectory() {
std::filesystem::path configDirectoryPath = std::getenv("HOME");
configDirectoryPath = configDirectoryPath / "Library" / "Application Support" / "PlayerLink";
return configDirectoryPath;
}
bool backend::toggleAutostart(bool enabled) {
std::filesystem::path launchAgentPath = std::getenv("HOME");
launchAgentPath = launchAgentPath / "Library" / "LaunchAgents" / "PlayerLink.plist";
launchAgentPath = launchAgentPath / "Library" / "LaunchAgents";
std::filesystem::create_directories(launchAgentPath);
launchAgentPath = launchAgentPath / "PlayerLink.plist";
if (!enabled && std::filesystem::exists(launchAgentPath)) {
std::filesystem::remove(launchAgentPath);
return true;

View File

@ -12,6 +12,9 @@
DBusConnection* conn = nullptr;
std::string getExecutablePath() {
if (const char* appImagePath = std::getenv("APPIMAGE"))
return std::string(appImagePath);
char result[PATH_MAX]{};
ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
return (count != -1) ? std::string(result, count) : std::string();
@ -189,8 +192,17 @@ bool backend::init() {
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (!conn) {
dbus_error_free(&err);
return false;
if (dbus_error_is_set(&err))
dbus_error_free(&err);
//fallback to system bus if user doesn't have a session specific bus
conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if(!conn) {
if (dbus_error_is_set(&err))
dbus_error_free(&err);
return false;
}
}
return true;
}
@ -206,6 +218,12 @@ std::shared_ptr<MediaInfo> backend::getMediaInformation() {
return std::make_shared<MediaInfo>(ret);
}
std::filesystem::path backend::getConfigDirectory() {
std::filesystem::path configDirectoryPath = std::getenv("HOME");
configDirectoryPath = configDirectoryPath / ".config" / "PlayerLink";
return configDirectoryPath;
}
bool backend::toggleAutostart(bool enabled) {
const char* xdgHome = std::getenv("XDG_CONFIG_HOME");

View File

@ -1,15 +1,16 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <objbase.h>
#include <psapi.h>
#include <shlobj.h>
#include <windows.h>
#include <appmodel.h>
#include <winrt/windows.foundation.h>
#include <winrt/windows.foundation.metadata.h>
#include <winrt/windows.media.control.h>
#include <winrt/windows.storage.streams.h>
#include <chrono>
#include <codecvt>
#include <filesystem>
#include "../backend.hpp"
@ -19,11 +20,72 @@ using namespace winrt;
using namespace Windows::Media::Control;
using namespace Windows::Storage::Streams;
#define EM_DASH "\xE2\x80\x94"
// 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
std::string toStdString(winrt::hstring in) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
return converter.to_bytes(in.c_str());
std::string toStdString(winrt::hstring& in) {
const wchar_t* wideStr = in.c_str();
int wideStrLen = static_cast<int>(in.size());
int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wideStr, wideStrLen, nullptr, 0, nullptr, nullptr);
if (bufferSize <= 0)
return "";
std::string result(bufferSize, 0);
WideCharToMultiByte(CP_UTF8, 0, wideStr, wideStrLen, result.data(), bufferSize, nullptr, nullptr);
return result;
}
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 "";
}
winrt::hstring wideName = fullName;
std::string name = toStdString(wideName);
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 "";
}
bool CreateShortcut(std::string source, std::string target) {
@ -54,9 +116,18 @@ bool CreateShortcut(std::string source, std::string target) {
return SUCCEEDED(hr);
}
std::filesystem::path backend::getConfigDirectory() {
std::filesystem::path configDirectoryPath = std::getenv("APPDATA");
configDirectoryPath = configDirectoryPath / "PlayerLink";
return configDirectoryPath;
}
bool backend::toggleAutostart(bool enabled) {
std::filesystem::path shortcutPath = std::getenv("APPDATA");
shortcutPath = shortcutPath / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup" / "PlayerLink.lnk";
shortcutPath = shortcutPath / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup";
std::filesystem::create_directories(shortcutPath);
shortcutPath = shortcutPath / "PlayerLink.lnk";
if (!enabled && std::filesystem::exists(shortcutPath)) {
std::filesystem::remove(shortcutPath);
return true;
@ -68,7 +139,7 @@ bool backend::toggleAutostart(bool enabled) {
}
std::shared_ptr<MediaInfo> backend::getMediaInformation() {
auto sessionManager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
static auto sessionManager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
auto currentSession = sessionManager.GetCurrentSession();
if (!currentSession)
return nullptr;
@ -112,10 +183,19 @@ std::shared_ptr<MediaInfo> backend::getMediaInformation() {
utils::trim(albumName);
}
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);
return std::make_shared<MediaInfo>(
playbackInfo.PlaybackStatus() == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Paused,
toStdString(mediaProperties.Title()), artist, albumName, toStdString(currentSession.SourceAppUserModelId()),
thumbnailData, endTime, elapsedTime);
toStdString(mediaProperties.Title()), std::move(artist), std::move(albumName), std::move(modelId),
std::move(thumbnailData), endTime, elapsedTime);
}
bool backend::init() {

84
src/lastfm.hpp Normal file
View File

@ -0,0 +1,84 @@
#ifndef _LASTFM_
#define _LASTFM_
#include <md5.hpp>
#include <string>
#include "utils.hpp"
class LastFM {
public:
enum LASTFM_STATUS {
SUCCESS = 0,
AUTHENTICATION_FAILED = 4,
INVALID_API_KEY = 10,
RATE_LIMIT_REACHED = 29,
API_KEY_SUSPENDED = 26,
UNKNOWN_ERROR = 16,
INVALID_SESSION_KEY = 9,
SERVICE_TEMPORARILY_UNAVAILABLE = 13,
};
LastFM(std::string u, std::string p, std::string ak, std::string as)
: username(u), password(p), api_key(ak), api_secret(as), authenticated(false) {}
std::string getApiSignature(const std::map<std::string, std::string>& parameters) {
std::string unhashedSignature = "";
std::map<std::string, std::string> sortedParameters = parameters;
for (const auto& parameter : sortedParameters) {
if (parameter.first == "format" || parameter.first == "callback")
continue;
unhashedSignature += parameter.first + parameter.second;
}
unhashedSignature += api_secret;
return md5::md5_hash_hex(unhashedSignature);
}
LASTFM_STATUS authenticate() {
std::map<std::string, std::string> parameters = {{"api_key", api_key},
{"password", password},
{"username", username},
{"method", "auth.getMobileSession"},
{"format", "json"}};
parameters["api_sig"] = getApiSignature(parameters);
std::string postBody = utils::getURLEncodedPostBody(parameters);
std::string response = utils::httpRequest(api_base, "POST", postBody);
auto j = nlohmann::json::parse(response);
if (j.contains("error"))
return j["error"].get<LASTFM_STATUS>();
session_token = j["session"]["key"].get<std::string>();
authenticated = true;
return LASTFM_STATUS::SUCCESS;
}
LASTFM_STATUS scrobble(std::string artist, std::string track) {
if (!authenticated)
return LASTFM_STATUS::AUTHENTICATION_FAILED;
std::map<std::string, std::string> parameters = {
{"api_key", api_key}, {"method", "track.scrobble"},
{"sk", session_token}, {"artist", artist},
{"track", track}, {"timestamp", std::to_string(time(NULL))},
{"format", "json"}};
parameters["api_sig"] = getApiSignature(parameters);
std::string postBody = utils::getURLEncodedPostBody(parameters);
std::string response = utils::httpRequest(api_base, "POST", postBody);
return LASTFM_STATUS::SUCCESS;
}
private:
bool authenticated;
std::string session_token;
std::string username;
std::string password;
std::string api_key;
std::string api_secret;
const std::string api_base = "https://ws.audioscrobbler.com/2.0/";
};
#endif

View File

@ -9,12 +9,15 @@
#include <thread>
#include "backend.hpp"
#include "lastfm.hpp"
#include "rsrc.hpp"
#include "utils.hpp"
std::string lastPlayingSong = "";
std::string lastMediaSource = "";
std::string currentSongTitle = "";
utils::SongInfo songInfo;
LastFM* lastfm = nullptr;
void handleRPCTasks() {
while (true) {
@ -36,11 +39,28 @@ void handleRPCTasks() {
}
}
void initLastFM(bool checkMode = false) {
if (lastfm)
delete lastfm;
auto settings = utils::getSettings();
if (!settings.lastfm.enabled && !checkMode)
return;
lastfm = new LastFM(settings.lastfm.username, settings.lastfm.password, settings.lastfm.api_key,
settings.lastfm.api_secret);
LastFM::LASTFM_STATUS status = lastfm->authenticate();
if (status)
wxMessageBox(_("Error authenticating at LastFM!"), _("PlayerLink"), wxOK | wxICON_ERROR);
else if (checkMode)
wxMessageBox(_("The LastFM authentication was successful."), _("PlayerLink"), wxOK | wxICON_INFORMATION);
}
void handleMediaTasks() {
initLastFM();
int64_t lastMs = 0;
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
auto mediaInformation = backend::getMediaInformation();
auto settings = utils::getSettings();
if (!mediaInformation) {
currentSongTitle = "";
Discord_ClearPresence(); // Nothing is playing rn, clear presence
@ -49,7 +69,6 @@ void handleMediaTasks() {
if (mediaInformation->paused) {
lastMs = 0;
lastPlayingSong = "";
currentSongTitle = "";
Discord_ClearPresence();
continue;
@ -66,6 +85,11 @@ void handleMediaTasks() {
if (shouldContinue)
continue;
if (lastPlayingSong.find(mediaInformation->songTitle + mediaInformation->songArtist +
mediaInformation->songAlbum) == std::string::npos &&
lastfm)
lastfm->scrobble(mediaInformation->songArtist, mediaInformation->songTitle);
lastPlayingSong = currentlyPlayingSong;
currentSongTitle = mediaInformation->songArtist + " - " + mediaInformation->songTitle;
@ -90,15 +114,15 @@ void handleMediaTasks() {
activity.details = mediaInformation->songTitle.c_str();
activity.state = activityState.c_str();
activity.smallImageText = serviceName.c_str();
std::string artworkURL = utils::getArtworkURL(mediaInformation->songTitle + " " + mediaInformation->songArtist +
" " + mediaInformation->songAlbum);
songInfo = utils::getSongInfo(mediaInformation->songTitle + " " + mediaInformation->songArtist + " " +
mediaInformation->songAlbum);
activity.smallImageKey = "appicon";
if (artworkURL == "") {
if (songInfo.artworkURL == "") {
activity.smallImageKey = "";
activity.largeImageKey = "appicon";
} else {
activity.largeImageKey = artworkURL.c_str();
activity.largeImageKey = songInfo.artworkURL.c_str();
}
activity.largeImageText = mediaInformation->songAlbum.c_str();
@ -118,6 +142,12 @@ void handleMediaTasks() {
activity.button1link = buttonText.c_str();
}
std::string odesliUrl = utils::getOdesliURL(songInfo);
if(settings.odesli && songInfo.artworkURL != "") {
activity.button2name = "Show on Song.link";
activity.button2link = odesliUrl.c_str();
}
Discord_UpdatePresence(&activity);
}
}
@ -125,7 +155,12 @@ class PlayerLinkIcon : public wxTaskBarIcon {
public:
PlayerLinkIcon(wxFrame* s) : settingsFrame(s) {}
void OnMenuOpen(wxCommandEvent& evt) { settingsFrame->Show(true); }
void OnMenuOpen(wxCommandEvent& evt) {
settingsFrame->Show(true);
settingsFrame->Raise();
}
void OnCopyOdesliURL(wxCommandEvent& evt) { utils::copyToClipboard(utils::getOdesliURL(songInfo)); }
void OnMenuExit(wxCommandEvent& evt) { settingsFrame->Close(true); }
@ -139,10 +174,14 @@ protected:
menu->Append(10004, currentSongTitle == "" ? _("Not Playing") : wxString::FromUTF8(currentSongTitle));
menu->Enable(10004, false);
menu->AppendSeparator();
menu->Append(10005, _("Copy Odesli URL"));
if (songInfo.artworkURL == "" || currentSongTitle == "")
menu->Enable(10005, false);
menu->Append(10001, _("Settings"));
menu->Append(10003, _("About PlayerLink"));
menu->AppendSeparator();
menu->Append(10002, _("Quit PlayerLink..."));
Bind(wxEVT_MENU, &PlayerLinkIcon::OnCopyOdesliURL, this, 10005);
Bind(wxEVT_MENU, &PlayerLinkIcon::OnMenuOpen, this, 10001);
Bind(wxEVT_MENU, &PlayerLinkIcon::OnMenuExit, this, 10002);
Bind(wxEVT_MENU, &PlayerLinkIcon::OnMenuAbout, this, 10003);
@ -153,16 +192,63 @@ private:
wxFrame* settingsFrame;
};
class PlayerLinkFrame : public wxFrame {
protected:
wxStaticText* settingsText;
wxStaticLine* settingsDivider;
wxStaticText* enabledAppsText;
wxCheckBox* anyOtherCheckbox;
wxStaticLine* appsDivider;
wxStaticText* startupText;
wxCheckBox* autostartCheckbox;
class wxTextCtrlWithPlaceholder : public wxTextCtrl {
public:
wxTextCtrlWithPlaceholder(wxWindow* parent, wxWindowID id, const wxString& value,
const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
long style = 0, const wxValidator& validator = wxDefaultValidator,
const wxString& name = "textCtrl")
: wxTextCtrl(parent, id, value, pos, size, style, validator, name),
placeholder(""),
showPlaceholder(true),
isPassword((style & wxTE_PASSWORD) != 0) {
Bind(wxEVT_SET_FOCUS, &wxTextCtrlWithPlaceholder::OnFocus, this);
Bind(wxEVT_KILL_FOCUS, &wxTextCtrlWithPlaceholder::OnBlur, this);
}
void SetPlaceholderText(const wxString& p) {
placeholder = p;
if (GetValue().IsEmpty() || showPlaceholder)
UpdatePlaceholder();
}
protected:
void OnFocus(wxFocusEvent& event) {
if (showPlaceholder && GetValue() == placeholder) {
Clear();
if (isPassword)
SetStyleToPassword();
}
showPlaceholder = false;
event.Skip();
}
void OnBlur(wxFocusEvent& event) {
if (GetValue().IsEmpty()) {
showPlaceholder = true;
UpdatePlaceholder();
}
event.Skip();
}
private:
wxString placeholder;
bool showPlaceholder;
bool isPassword;
void UpdatePlaceholder() {
if (isPassword)
SetStyleToNormal();
SetValue(placeholder);
}
void SetStyleToPassword() { SetWindowStyle(GetWindowStyle() | wxTE_PASSWORD); }
void SetStyleToNormal() { SetWindowStyle(GetWindowStyle() & ~wxTE_PASSWORD); }
};
class PlayerLinkFrame : public wxFrame {
public:
PlayerLinkFrame(wxWindow* parent, wxIcon& icon, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString,
const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(300, 200),
@ -171,20 +257,22 @@ public:
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
this->SetIcon(icon);
wxBoxSizer* mainContainer;
mainContainer = new wxBoxSizer(wxVERTICAL);
settingsText = new wxStaticText(this, wxID_ANY, _("Settings"), wxDefaultPosition, wxDefaultSize, 0);
auto mainContainer = new wxBoxSizer(wxVERTICAL);
// header start
auto settingsText = new wxStaticText(this, wxID_ANY, _("Settings"), wxDefaultPosition, wxDefaultSize, 0);
settingsText->Wrap(-1);
mainContainer->Add(settingsText, 0, wxALIGN_CENTER | wxALL, 5);
// header end
settingsDivider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
// enabled apps start
auto settingsDivider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
mainContainer->Add(settingsDivider, 0, wxEXPAND | wxALL, 5);
wxBoxSizer* enabledAppsContainer;
enabledAppsContainer = new wxBoxSizer(wxHORIZONTAL);
enabledAppsText = new wxStaticText(this, wxID_ANY, _("Enabled Apps:"), wxDefaultPosition, wxDefaultSize, 0);
auto enabledAppsText =
new wxStaticText(this, wxID_ANY, _("Enabled Apps:"), wxDefaultPosition, wxDefaultSize, 0);
enabledAppsText->Wrap(-1);
enabledAppsContainer->Add(enabledAppsText, 0, wxALL, 5);
@ -194,7 +282,7 @@ public:
auto settings = utils::getSettings();
for (auto app : settings.apps) {
auto checkbox = new wxCheckBox(this, wxID_ANY, _(app.appName), wxDefaultPosition, wxDefaultSize, 0);
auto checkbox = new wxCheckBox(this, wxID_ANY, app.appName, wxDefaultPosition, wxDefaultSize, 0);
checkbox->SetValue(app.enabled);
checkbox->SetClientData(new utils::App(app));
checkbox->Bind(wxEVT_CHECKBOX, [checkbox](wxCommandEvent& event) {
@ -209,10 +297,10 @@ public:
appCheckboxContainer->Add(checkbox, 0, wxALL, 5);
}
anyOtherCheckbox = new wxCheckBox(this, wxID_ANY, _("Any other"), wxDefaultPosition, wxDefaultSize, 0);
auto anyOtherCheckbox = new wxCheckBox(this, wxID_ANY, _("Any other"), wxDefaultPosition, wxDefaultSize, 0);
anyOtherCheckbox->SetValue(settings.anyOtherEnabled);
anyOtherCheckbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) {
bool isChecked = this->anyOtherCheckbox->IsChecked();
anyOtherCheckbox->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) {
bool isChecked = event.IsChecked();
auto settings = utils::getSettings();
settings.anyOtherEnabled = isChecked;
utils::saveSettings(settings);
@ -223,30 +311,127 @@ public:
enabledAppsContainer->Add(appCheckboxContainer, 1, wxEXPAND, 5);
mainContainer->Add(enabledAppsContainer, 0, 0, 5);
// enabled apps end
appsDivider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
// LastFM start
auto lastfmDivider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
mainContainer->Add(lastfmDivider, 0, wxEXPAND | wxALL, 5);
wxBoxSizer* lastFMContainer;
lastFMContainer = new wxBoxSizer(wxHORIZONTAL);
auto lastfmText = new wxStaticText(this, wxID_ANY, _("LastFM:"), wxDefaultPosition, wxDefaultSize, 0);
lastfmText->Wrap(-1);
lastFMContainer->Add(lastfmText, 0, wxALL, 5);
wxBoxSizer* lastfmSettingsContainer;
lastfmSettingsContainer = new wxBoxSizer(wxVERTICAL);
auto lastfmEnabledCheckbox = new wxCheckBox(this, wxID_ANY, _("Enabled"), wxDefaultPosition, wxDefaultSize, 0);
lastfmEnabledCheckbox->SetValue(settings.lastfm.enabled);
lastfmEnabledCheckbox->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) {
bool isChecked = event.IsChecked();
auto settings = utils::getSettings();
settings.lastfm.enabled = isChecked;
utils::saveSettings(settings);
});
lastfmSettingsContainer->Add(lastfmEnabledCheckbox, 0, wxALIGN_CENTER | wxALL, 5);
lastFMContainer->Add(lastfmSettingsContainer, 1, wxEXPAND, 5);
auto usernameInput =
new wxTextCtrlWithPlaceholder(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
usernameInput->SetPlaceholderText(_("Username"));
usernameInput->SetValue(settings.lastfm.username);
usernameInput->Bind(wxEVT_TEXT, [this](wxCommandEvent& event) {
auto settings = utils::getSettings();
std::string data = event.GetString().ToStdString();
settings.lastfm.username = data;
utils::saveSettings(settings);
});
auto passwordInput = new wxTextCtrlWithPlaceholder(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDefaultSize, wxTE_PASSWORD);
passwordInput->SetPlaceholderText(_("Password"));
passwordInput->SetValue(settings.lastfm.password);
passwordInput->Bind(wxEVT_TEXT, [this](wxCommandEvent& event) {
auto settings = utils::getSettings();
std::string data = event.GetString().ToStdString();
settings.lastfm.password = data;
utils::saveSettings(settings);
});
auto apikeyInput =
new wxTextCtrlWithPlaceholder(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
apikeyInput->SetPlaceholderText(_("API-Key"));
apikeyInput->SetValue(settings.lastfm.api_key);
apikeyInput->Bind(wxEVT_TEXT, [this](wxCommandEvent& event) {
auto settings = utils::getSettings();
std::string data = event.GetString().ToStdString();
settings.lastfm.api_key = data;
utils::saveSettings(settings);
});
auto apisecretInput = new wxTextCtrlWithPlaceholder(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDefaultSize, wxTE_PASSWORD);
apisecretInput->SetPlaceholderText(_("API-Secret"));
apisecretInput->SetValue(settings.lastfm.api_secret);
apisecretInput->Bind(wxEVT_TEXT, [this](wxCommandEvent& event) {
auto settings = utils::getSettings();
std::string data = event.GetString().ToStdString();
settings.lastfm.api_secret = data;
utils::saveSettings(settings);
});
auto checkButton = new wxButton(this, wxID_ANY, _("Check credentials"));
checkButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { initLastFM(true); });
mainContainer->Add(lastFMContainer, 0, 0, 5);
mainContainer->Add(usernameInput, 0, wxEXPAND | wxALL, 5);
mainContainer->Add(passwordInput, 0, wxEXPAND | wxALL, 5);
mainContainer->Add(apikeyInput, 0, wxEXPAND | wxALL, 5);
mainContainer->Add(apisecretInput, 0, wxEXPAND | wxALL, 5);
mainContainer->Add(checkButton, 0, wxEXPAND | wxALL, 10);
// Last FM End
// settings start
auto appsDivider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
mainContainer->Add(appsDivider, 0, wxEXPAND | wxALL, 5);
wxBoxSizer* settingsContainer;
settingsContainer = new wxBoxSizer(wxHORIZONTAL);
settingsContainer = new wxBoxSizer(wxVERTICAL);
startupText = new wxStaticText(this, wxID_ANY, _("Startup:"), wxDefaultPosition, wxDefaultSize, 0);
wxBoxSizer* startupContainer;
startupContainer = new wxBoxSizer(wxHORIZONTAL);
auto startupText = new wxStaticText(this, wxID_ANY, _("Startup:"), wxDefaultPosition, wxDefaultSize, 0);
startupText->Wrap(-1);
settingsContainer->Add(startupText, 0, wxALL, 5);
startupContainer->Add(startupText, 0, wxALL, 5);
autostartCheckbox = new wxCheckBox(this, wxID_ANY, _("Launch at login"), wxDefaultPosition, wxDefaultSize, 0);
auto autostartCheckbox =
new wxCheckBox(this, wxID_ANY, _("Launch at login"), wxDefaultPosition, wxDefaultSize, 0);
autostartCheckbox->SetValue(settings.autoStart);
autostartCheckbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) {
bool isChecked = this->autostartCheckbox->IsChecked();
autostartCheckbox->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) {
bool isChecked = event.IsChecked();
auto settings = utils::getSettings();
settings.autoStart = isChecked;
backend::toggleAutostart(isChecked);
utils::saveSettings(settings);
});
settingsContainer->Add(autostartCheckbox, 0, wxALL, 5);
mainContainer->Add(settingsContainer, 0, wxEXPAND, 5);
auto odesliCheckbox =
new wxCheckBox(this, wxID_ANY, _("Odesli integration"), wxDefaultPosition, wxDefaultSize, 0);
odesliCheckbox->SetValue(settings.odesli);
odesliCheckbox->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) {
bool isChecked = event.IsChecked();
auto settings = utils::getSettings();
settings.odesli = isChecked;
utils::saveSettings(settings);
});
settingsContainer->Add(autostartCheckbox, 0, wxALL, 5);
settingsContainer->Add(odesliCheckbox, 0, wxALL, 5);
startupContainer->Add(settingsContainer);
mainContainer->Add(startupContainer, 0, wxEXPAND, 5);
// settings end
this->SetSizerAndFit(mainContainer);
wxSize currentSize = this->GetSize();

View File

@ -3,6 +3,7 @@
#include <curl/include/curl/curl.h>
#include <wx/mstream.h>
#include <wx/wx.h>
#include <wx/clipbrd.h>
#include <filesystem>
#include <fstream>
@ -11,9 +12,11 @@
#include <string>
#include <vector>
#include "backend.hpp"
#define DEFAULT_CLIENT_ID "1301849203378622545"
#define DEFAULT_APP_NAME "Music"
#define CONFIG_FILENAME "settings.json"
#define CONFIG_FILENAME backend::getConfigDirectory() / "settings.json"
namespace utils {
struct App {
@ -24,12 +27,35 @@ namespace utils {
std::vector<std::string> processNames;
};
struct LastFMSettings {
bool enabled;
std::string username;
std::string password;
std::string api_key;
std::string api_secret;
};
struct Settings {
bool odesli;
bool autoStart;
bool anyOtherEnabled;
LastFMSettings lastfm;
std::vector<App> apps;
};
struct SongInfo {
std::string artworkURL;
int64_t trackId;
};
inline void copyToClipboard(const wxString& text) {
if (wxTheClipboard->Open()) {
wxTheClipboard->Clear();
wxTheClipboard->SetData(new wxTextDataObject(text));
wxTheClipboard->Close();
}
}
inline wxIcon loadIconFromMemory(const unsigned char* data, size_t size) {
wxMemoryInputStream stream(data, size);
wxImage img(stream, wxBITMAP_TYPE_PNG);
@ -78,7 +104,21 @@ namespace utils {
return size * nmemb;
}
inline std::string getRequest(std::string url) {
inline std::string getURLEncodedPostBody(const std::map<std::string, std::string>& parameters) {
if (parameters.empty())
return "";
std::string encodedPostBody = "";
for (const auto& parameter : parameters) {
encodedPostBody += parameter.first;
encodedPostBody += "=";
encodedPostBody += urlEncode(parameter.second);
encodedPostBody += "&";
}
return encodedPostBody.erase(encodedPostBody.length() - 1);
}
inline std::string httpRequest(std::string url, std::string requestType = "GET", std::string postData = "") {
CURL* curl;
CURLcode res;
std::string buf;
@ -86,6 +126,10 @@ namespace utils {
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, requestType.c_str());
if (requestType != "GET" && requestType != "DELETE" && postData.length() > 0)
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
@ -96,16 +140,23 @@ namespace utils {
return buf;
}
inline std::string getArtworkURL(std::string query) {
inline SongInfo getSongInfo(std::string query) {
SongInfo ret{};
std::string response =
getRequest("https://itunes.apple.com/search?media=music&entity=song&term=" + urlEncode(query));
httpRequest("https://itunes.apple.com/search?media=music&entity=song&term=" + urlEncode(query));
nlohmann::json j = nlohmann::json::parse(response);
auto results = j["results"];
if (results.size() > 0) {
return results[0]["artworkUrl100"].get<std::string>();
ret.artworkURL = results[0]["artworkUrl100"].get<std::string>();
ret.trackId = results[0]["trackId"].get<int64_t>();
}
return "";
return ret;
}
inline std::string getOdesliURL(SongInfo& song) {
return std::string("https://song.link/i/" + std::to_string(song.trackId));
}
inline void saveSettings(const App* newApp) {
nlohmann::json j;
@ -159,6 +210,13 @@ namespace utils {
nlohmann::json j;
j["autostart"] = settings.autoStart;
j["any_other"] = settings.anyOtherEnabled;
j["odesli"] = settings.odesli;
j["lastfm"]["enabled"] = settings.lastfm.enabled;
j["lastfm"]["api_key"] = settings.lastfm.api_key;
j["lastfm"]["api_secret"] = settings.lastfm.api_secret;
j["lastfm"]["username"] = settings.lastfm.username;
j["lastfm"]["password"] = settings.lastfm.password;
for (const auto& app : settings.apps) {
nlohmann::json appJson;
@ -177,9 +235,15 @@ namespace utils {
o.close();
}
inline Settings getSettings() {
std::filesystem::create_directories(backend::getConfigDirectory());
Settings ret;
if (!std::filesystem::exists(CONFIG_FILENAME))
if (!std::filesystem::exists(CONFIG_FILENAME)) {
ret.anyOtherEnabled = true;
ret.autoStart = false;
ret.odesli = false;
saveSettings(ret);
return ret;
}
try {
std::ifstream i(CONFIG_FILENAME);
@ -188,6 +252,16 @@ namespace utils {
ret.autoStart = j.value("autostart", false);
ret.anyOtherEnabled = j.value("any_other", false);
ret.odesli = j.value("odesli", false);
if (j.contains("lastfm")) {
auto lastfm = j["lastfm"];
ret.lastfm.enabled = lastfm.value("enabled", false);
ret.lastfm.api_key = lastfm.value("api_key", "");
ret.lastfm.api_secret = lastfm.value("api_secret", "");
ret.lastfm.username = lastfm.value("username", "");
ret.lastfm.password = lastfm.value("password", "");
}
for (const auto& app : j["apps"]) {
App a;
@ -201,7 +275,7 @@ namespace utils {
ret.apps.push_back(a);
}
} catch (const nlohmann::json::parse_error&) {
} // TODO: handle error
}
return ret;
}

View File

@ -11,9 +11,10 @@ SET(BUILD_STATIC_LIBS ON)
SET(BUILD_SHARED_LIBS OFF)
SET(BUILD_CURL_EXE OFF)
SET(MBEDTLS_INCLUDE_DIRS ../mbedtls/include)
file(REMOVE curl/CMake/FindMbedTLS.cmake) #replace curls FindMbedTLS that expects mbedtls to be prebuilt with a dummy
file(RENAME curl/CMake/FindMbedTLS.cmake curl/CMake/FindMbedTLS.cmake.bak) #replace curls FindMbedTLS that expects mbedtls to be prebuilt with a dummy
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dummy ${CMAKE_MODULE_PATH})
add_subdirectory("curl")
file(RENAME curl/CMake/FindMbedTLS.cmake.bak curl/CMake/FindMbedTLS.cmake)
set(wxBUILD_SHARED OFF)
set(wxBUILD_MONOLITHIC ON)
set(wxUSE_GUI ON)

447
vendor/md5.hpp vendored Normal file
View File

@ -0,0 +1,447 @@
/*
md5.hpp is a reformulation of the md5.h and md5.c code from
http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to
function as a component of a header only library. This conversion was done by
Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The
changes are released under the same license as the original (listed below)
*/
/*
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.h is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Removed support for non-ANSI compilers; removed
references to Ghostscript; clarified derivation from RFC 1321;
now handles byte order either statically or dynamically.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
added conditionalization for C++ compilation from Martin
Purschke <purschke@bnl.gov>.
1999-05-03 lpd Original version.
*/
#ifndef WEBSOCKETPP_COMMON_MD5_HPP
#define WEBSOCKETPP_COMMON_MD5_HPP
/*
* This package supports both compile-time and run-time determination of CPU
* byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
* compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
* defined as non-zero, the code will be compiled to run only on big-endian
* CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
* run on either big- or little-endian CPUs, but will run slightly less
* efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
*/
#include <stddef.h>
#include <string>
#include <cstring>
/// Provides MD5 hashing functionality
namespace md5 {
typedef unsigned char md5_byte_t; /* 8-bit byte */
typedef unsigned int md5_word_t; /* 32-bit word */
/* Define the state of the MD5 Algorithm. */
typedef struct md5_state_s {
md5_word_t count[2]; /* message length in bits, lsw first */
md5_word_t abcd[4]; /* digest buffer */
md5_byte_t buf[64]; /* accumulate block */
} md5_state_t;
/* Initialize the algorithm. */
inline void md5_init(md5_state_t *pms);
/* Append a string to the message. */
inline void md5_append(md5_state_t *pms, md5_byte_t const * data, size_t nbytes);
/* Finish the message and return the digest. */
inline void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
#undef ZSW_MD5_BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
#ifdef ARCH_IS_BIG_ENDIAN
# define ZSW_MD5_BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
#else
# define ZSW_MD5_BYTE_ORDER 0
#endif
#define ZSW_MD5_T_MASK ((md5_word_t)~0)
#define ZSW_MD5_T1 /* 0xd76aa478 */ (ZSW_MD5_T_MASK ^ 0x28955b87)
#define ZSW_MD5_T2 /* 0xe8c7b756 */ (ZSW_MD5_T_MASK ^ 0x173848a9)
#define ZSW_MD5_T3 0x242070db
#define ZSW_MD5_T4 /* 0xc1bdceee */ (ZSW_MD5_T_MASK ^ 0x3e423111)
#define ZSW_MD5_T5 /* 0xf57c0faf */ (ZSW_MD5_T_MASK ^ 0x0a83f050)
#define ZSW_MD5_T6 0x4787c62a
#define ZSW_MD5_T7 /* 0xa8304613 */ (ZSW_MD5_T_MASK ^ 0x57cfb9ec)
#define ZSW_MD5_T8 /* 0xfd469501 */ (ZSW_MD5_T_MASK ^ 0x02b96afe)
#define ZSW_MD5_T9 0x698098d8
#define ZSW_MD5_T10 /* 0x8b44f7af */ (ZSW_MD5_T_MASK ^ 0x74bb0850)
#define ZSW_MD5_T11 /* 0xffff5bb1 */ (ZSW_MD5_T_MASK ^ 0x0000a44e)
#define ZSW_MD5_T12 /* 0x895cd7be */ (ZSW_MD5_T_MASK ^ 0x76a32841)
#define ZSW_MD5_T13 0x6b901122
#define ZSW_MD5_T14 /* 0xfd987193 */ (ZSW_MD5_T_MASK ^ 0x02678e6c)
#define ZSW_MD5_T15 /* 0xa679438e */ (ZSW_MD5_T_MASK ^ 0x5986bc71)
#define ZSW_MD5_T16 0x49b40821
#define ZSW_MD5_T17 /* 0xf61e2562 */ (ZSW_MD5_T_MASK ^ 0x09e1da9d)
#define ZSW_MD5_T18 /* 0xc040b340 */ (ZSW_MD5_T_MASK ^ 0x3fbf4cbf)
#define ZSW_MD5_T19 0x265e5a51
#define ZSW_MD5_T20 /* 0xe9b6c7aa */ (ZSW_MD5_T_MASK ^ 0x16493855)
#define ZSW_MD5_T21 /* 0xd62f105d */ (ZSW_MD5_T_MASK ^ 0x29d0efa2)
#define ZSW_MD5_T22 0x02441453
#define ZSW_MD5_T23 /* 0xd8a1e681 */ (ZSW_MD5_T_MASK ^ 0x275e197e)
#define ZSW_MD5_T24 /* 0xe7d3fbc8 */ (ZSW_MD5_T_MASK ^ 0x182c0437)
#define ZSW_MD5_T25 0x21e1cde6
#define ZSW_MD5_T26 /* 0xc33707d6 */ (ZSW_MD5_T_MASK ^ 0x3cc8f829)
#define ZSW_MD5_T27 /* 0xf4d50d87 */ (ZSW_MD5_T_MASK ^ 0x0b2af278)
#define ZSW_MD5_T28 0x455a14ed
#define ZSW_MD5_T29 /* 0xa9e3e905 */ (ZSW_MD5_T_MASK ^ 0x561c16fa)
#define ZSW_MD5_T30 /* 0xfcefa3f8 */ (ZSW_MD5_T_MASK ^ 0x03105c07)
#define ZSW_MD5_T31 0x676f02d9
#define ZSW_MD5_T32 /* 0x8d2a4c8a */ (ZSW_MD5_T_MASK ^ 0x72d5b375)
#define ZSW_MD5_T33 /* 0xfffa3942 */ (ZSW_MD5_T_MASK ^ 0x0005c6bd)
#define ZSW_MD5_T34 /* 0x8771f681 */ (ZSW_MD5_T_MASK ^ 0x788e097e)
#define ZSW_MD5_T35 0x6d9d6122
#define ZSW_MD5_T36 /* 0xfde5380c */ (ZSW_MD5_T_MASK ^ 0x021ac7f3)
#define ZSW_MD5_T37 /* 0xa4beea44 */ (ZSW_MD5_T_MASK ^ 0x5b4115bb)
#define ZSW_MD5_T38 0x4bdecfa9
#define ZSW_MD5_T39 /* 0xf6bb4b60 */ (ZSW_MD5_T_MASK ^ 0x0944b49f)
#define ZSW_MD5_T40 /* 0xbebfbc70 */ (ZSW_MD5_T_MASK ^ 0x4140438f)
#define ZSW_MD5_T41 0x289b7ec6
#define ZSW_MD5_T42 /* 0xeaa127fa */ (ZSW_MD5_T_MASK ^ 0x155ed805)
#define ZSW_MD5_T43 /* 0xd4ef3085 */ (ZSW_MD5_T_MASK ^ 0x2b10cf7a)
#define ZSW_MD5_T44 0x04881d05
#define ZSW_MD5_T45 /* 0xd9d4d039 */ (ZSW_MD5_T_MASK ^ 0x262b2fc6)
#define ZSW_MD5_T46 /* 0xe6db99e5 */ (ZSW_MD5_T_MASK ^ 0x1924661a)
#define ZSW_MD5_T47 0x1fa27cf8
#define ZSW_MD5_T48 /* 0xc4ac5665 */ (ZSW_MD5_T_MASK ^ 0x3b53a99a)
#define ZSW_MD5_T49 /* 0xf4292244 */ (ZSW_MD5_T_MASK ^ 0x0bd6ddbb)
#define ZSW_MD5_T50 0x432aff97
#define ZSW_MD5_T51 /* 0xab9423a7 */ (ZSW_MD5_T_MASK ^ 0x546bdc58)
#define ZSW_MD5_T52 /* 0xfc93a039 */ (ZSW_MD5_T_MASK ^ 0x036c5fc6)
#define ZSW_MD5_T53 0x655b59c3
#define ZSW_MD5_T54 /* 0x8f0ccc92 */ (ZSW_MD5_T_MASK ^ 0x70f3336d)
#define ZSW_MD5_T55 /* 0xffeff47d */ (ZSW_MD5_T_MASK ^ 0x00100b82)
#define ZSW_MD5_T56 /* 0x85845dd1 */ (ZSW_MD5_T_MASK ^ 0x7a7ba22e)
#define ZSW_MD5_T57 0x6fa87e4f
#define ZSW_MD5_T58 /* 0xfe2ce6e0 */ (ZSW_MD5_T_MASK ^ 0x01d3191f)
#define ZSW_MD5_T59 /* 0xa3014314 */ (ZSW_MD5_T_MASK ^ 0x5cfebceb)
#define ZSW_MD5_T60 0x4e0811a1
#define ZSW_MD5_T61 /* 0xf7537e82 */ (ZSW_MD5_T_MASK ^ 0x08ac817d)
#define ZSW_MD5_T62 /* 0xbd3af235 */ (ZSW_MD5_T_MASK ^ 0x42c50dca)
#define ZSW_MD5_T63 0x2ad7d2bb
#define ZSW_MD5_T64 /* 0xeb86d391 */ (ZSW_MD5_T_MASK ^ 0x14792c6e)
static void md5_process(md5_state_t *pms, md5_byte_t const * data /*[64]*/) {
md5_word_t
a = pms->abcd[0], b = pms->abcd[1],
c = pms->abcd[2], d = pms->abcd[3];
md5_word_t t;
#if ZSW_MD5_BYTE_ORDER > 0
/* Define storage only for big-endian CPUs. */
md5_word_t X[16];
#else
/* Define storage for little-endian or both types of CPUs. */
md5_word_t xbuf[16];
md5_word_t const * X;
#endif
{
#if ZSW_MD5_BYTE_ORDER == 0
/*
* Determine dynamically whether this is a big-endian or
* little-endian machine, since we can use a more efficient
* algorithm on the latter.
*/
static int const w = 1;
if (*((md5_byte_t const *)&w)) /* dynamic little-endian */
#endif
#if ZSW_MD5_BYTE_ORDER <= 0 /* little-endian */
{
/*
* On little-endian machines, we can process properly aligned
* data without copying it.
*/
if (!((data - (md5_byte_t const *)0) & 3)) {
/* data are properly aligned */
X = (md5_word_t const *)data;
} else {
/* not aligned */
std::memcpy(xbuf, data, 64);
X = xbuf;
}
}
#endif
#if ZSW_MD5_BYTE_ORDER == 0
else /* dynamic big-endian */
#endif
#if ZSW_MD5_BYTE_ORDER >= 0 /* big-endian */
{
/*
* On big-endian machines, we must arrange the bytes in the
* right order.
*/
const md5_byte_t *xp = data;
int i;
# if ZSW_MD5_BYTE_ORDER == 0
X = xbuf; /* (dynamic only) */
# else
# define xbuf X /* (static only) */
# endif
for (i = 0; i < 16; ++i, xp += 4)
xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
}
#endif
}
#define ZSW_MD5_ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
/* Round 1. */
/* Let [abcd k s i] denote the operation
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define ZSW_MD5_F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + ZSW_MD5_F(b,c,d) + X[k] + Ti;\
a = ZSW_MD5_ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 7, ZSW_MD5_T1);
SET(d, a, b, c, 1, 12, ZSW_MD5_T2);
SET(c, d, a, b, 2, 17, ZSW_MD5_T3);
SET(b, c, d, a, 3, 22, ZSW_MD5_T4);
SET(a, b, c, d, 4, 7, ZSW_MD5_T5);
SET(d, a, b, c, 5, 12, ZSW_MD5_T6);
SET(c, d, a, b, 6, 17, ZSW_MD5_T7);
SET(b, c, d, a, 7, 22, ZSW_MD5_T8);
SET(a, b, c, d, 8, 7, ZSW_MD5_T9);
SET(d, a, b, c, 9, 12, ZSW_MD5_T10);
SET(c, d, a, b, 10, 17, ZSW_MD5_T11);
SET(b, c, d, a, 11, 22, ZSW_MD5_T12);
SET(a, b, c, d, 12, 7, ZSW_MD5_T13);
SET(d, a, b, c, 13, 12, ZSW_MD5_T14);
SET(c, d, a, b, 14, 17, ZSW_MD5_T15);
SET(b, c, d, a, 15, 22, ZSW_MD5_T16);
#undef SET
/* Round 2. */
/* Let [abcd k s i] denote the operation
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define ZSW_MD5_G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + ZSW_MD5_G(b,c,d) + X[k] + Ti;\
a = ZSW_MD5_ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 1, 5, ZSW_MD5_T17);
SET(d, a, b, c, 6, 9, ZSW_MD5_T18);
SET(c, d, a, b, 11, 14, ZSW_MD5_T19);
SET(b, c, d, a, 0, 20, ZSW_MD5_T20);
SET(a, b, c, d, 5, 5, ZSW_MD5_T21);
SET(d, a, b, c, 10, 9, ZSW_MD5_T22);
SET(c, d, a, b, 15, 14, ZSW_MD5_T23);
SET(b, c, d, a, 4, 20, ZSW_MD5_T24);
SET(a, b, c, d, 9, 5, ZSW_MD5_T25);
SET(d, a, b, c, 14, 9, ZSW_MD5_T26);
SET(c, d, a, b, 3, 14, ZSW_MD5_T27);
SET(b, c, d, a, 8, 20, ZSW_MD5_T28);
SET(a, b, c, d, 13, 5, ZSW_MD5_T29);
SET(d, a, b, c, 2, 9, ZSW_MD5_T30);
SET(c, d, a, b, 7, 14, ZSW_MD5_T31);
SET(b, c, d, a, 12, 20, ZSW_MD5_T32);
#undef SET
/* Round 3. */
/* Let [abcd k s t] denote the operation
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define ZSW_MD5_H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)\
t = a + ZSW_MD5_H(b,c,d) + X[k] + Ti;\
a = ZSW_MD5_ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 5, 4, ZSW_MD5_T33);
SET(d, a, b, c, 8, 11, ZSW_MD5_T34);
SET(c, d, a, b, 11, 16, ZSW_MD5_T35);
SET(b, c, d, a, 14, 23, ZSW_MD5_T36);
SET(a, b, c, d, 1, 4, ZSW_MD5_T37);
SET(d, a, b, c, 4, 11, ZSW_MD5_T38);
SET(c, d, a, b, 7, 16, ZSW_MD5_T39);
SET(b, c, d, a, 10, 23, ZSW_MD5_T40);
SET(a, b, c, d, 13, 4, ZSW_MD5_T41);
SET(d, a, b, c, 0, 11, ZSW_MD5_T42);
SET(c, d, a, b, 3, 16, ZSW_MD5_T43);
SET(b, c, d, a, 6, 23, ZSW_MD5_T44);
SET(a, b, c, d, 9, 4, ZSW_MD5_T45);
SET(d, a, b, c, 12, 11, ZSW_MD5_T46);
SET(c, d, a, b, 15, 16, ZSW_MD5_T47);
SET(b, c, d, a, 2, 23, ZSW_MD5_T48);
#undef SET
/* Round 4. */
/* Let [abcd k s t] denote the operation
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define ZSW_MD5_I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + ZSW_MD5_I(b,c,d) + X[k] + Ti;\
a = ZSW_MD5_ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 6, ZSW_MD5_T49);
SET(d, a, b, c, 7, 10, ZSW_MD5_T50);
SET(c, d, a, b, 14, 15, ZSW_MD5_T51);
SET(b, c, d, a, 5, 21, ZSW_MD5_T52);
SET(a, b, c, d, 12, 6, ZSW_MD5_T53);
SET(d, a, b, c, 3, 10, ZSW_MD5_T54);
SET(c, d, a, b, 10, 15, ZSW_MD5_T55);
SET(b, c, d, a, 1, 21, ZSW_MD5_T56);
SET(a, b, c, d, 8, 6, ZSW_MD5_T57);
SET(d, a, b, c, 15, 10, ZSW_MD5_T58);
SET(c, d, a, b, 6, 15, ZSW_MD5_T59);
SET(b, c, d, a, 13, 21, ZSW_MD5_T60);
SET(a, b, c, d, 4, 6, ZSW_MD5_T61);
SET(d, a, b, c, 11, 10, ZSW_MD5_T62);
SET(c, d, a, b, 2, 15, ZSW_MD5_T63);
SET(b, c, d, a, 9, 21, ZSW_MD5_T64);
#undef SET
/* Then perform the following additions. (That is increment each
of the four registers by the value it had before this block
was started.) */
pms->abcd[0] += a;
pms->abcd[1] += b;
pms->abcd[2] += c;
pms->abcd[3] += d;
}
void md5_init(md5_state_t *pms) {
pms->count[0] = pms->count[1] = 0;
pms->abcd[0] = 0x67452301;
pms->abcd[1] = /*0xefcdab89*/ ZSW_MD5_T_MASK ^ 0x10325476;
pms->abcd[2] = /*0x98badcfe*/ ZSW_MD5_T_MASK ^ 0x67452301;
pms->abcd[3] = 0x10325476;
}
void md5_append(md5_state_t *pms, md5_byte_t const * data, size_t nbytes) {
md5_byte_t const * p = data;
size_t left = nbytes;
int offset = (pms->count[0] >> 3) & 63;
md5_word_t nbits = (md5_word_t)(nbytes << 3);
if (nbytes <= 0)
return;
/* Update the message length. */
pms->count[1] += nbytes >> 29;
pms->count[0] += nbits;
if (pms->count[0] < nbits)
pms->count[1]++;
/* Process an initial partial block. */
if (offset) {
int copy = (offset + nbytes > 64 ? 64 - offset : static_cast<int>(nbytes));
std::memcpy(pms->buf + offset, p, copy);
if (offset + copy < 64)
return;
p += copy;
left -= copy;
md5_process(pms, pms->buf);
}
/* Process full blocks. */
for (; left >= 64; p += 64, left -= 64)
md5_process(pms, p);
/* Process a final partial block. */
if (left)
std::memcpy(pms->buf, p, left);
}
void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) {
static md5_byte_t const pad[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
md5_byte_t data[8];
int i;
/* Save the length before padding. */
for (i = 0; i < 8; ++i)
data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
/* Pad to 56 bytes mod 64. */
md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
/* Append the length. */
md5_append(pms, data, 8);
for (i = 0; i < 16; ++i)
digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}
// some convenience c++ functions
inline std::string md5_hash_string(std::string const & s) {
char digest[16];
md5_state_t state;
md5_init(&state);
md5_append(&state, (md5_byte_t const *)s.c_str(), s.size());
md5_finish(&state, (md5_byte_t *)digest);
std::string ret;
ret.resize(16);
std::copy(digest,digest+16,ret.begin());
return ret;
}
const char hexval[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
inline std::string md5_hash_hex(std::string const & input) {
std::string hash = md5_hash_string(input);
std::string hex;
for (size_t i = 0; i < hash.size(); i++) {
hex.push_back(hexval[((hash[i] >> 4) & 0xF)]);
hex.push_back(hexval[(hash[i]) & 0x0F]);
}
return hex;
}
} // md5
#endif // WEBSOCKETPP_COMMON_MD5_HPP

2
vendor/wxWidgets vendored

@ -1 +1 @@
Subproject commit 12b09a5e5ea76a1a0c27b769e821b37d803a4cb7
Subproject commit 138937b7775c117b57f55374a0c507a35a1102f6