Compare commits
44 Commits
Author | SHA1 | Date |
---|---|---|
|
7d4bc6dd89 | |
|
fa29020cea | |
|
36be257462 | |
|
958def7527 | |
|
28a198983c | |
|
374a95e68d | |
|
49e1bb0262 | |
|
cfecd99858 | |
|
c805dfe829 | |
|
c3bf12a997 | |
|
5428f57891 | |
|
66a25e62f2 | |
|
6807fe00fa | |
|
4e57f272a1 | |
|
7fc1ac5d5d | |
|
a6f5d3277b | |
|
563080643f | |
|
90607d2f5d | |
|
cd5656de63 | |
|
c53f02b35c | |
|
dbbec3b027 | |
|
bccf4c6100 | |
|
349058c32d | |
|
9821b6a294 | |
|
8fb4af3a49 | |
|
191fd2c375 | |
|
c35416c928 | |
|
be3a50d42a | |
|
aa734cf45a | |
|
cbc96614d0 | |
|
e8ac296f35 | |
|
a6c77e256f | |
|
3e16670a70 | |
|
017b21dbeb | |
|
d13231e84e | |
|
4a68eba106 | |
|
6f29111c40 | |
|
35d0ce747b | |
|
bb093cdefe | |
|
26af0323f2 | |
|
7b5666a906 | |
|
322ceeaddb | |
|
0c274c55c5 | |
|
31afe3372d |
|
@ -1,10 +1,11 @@
|
||||||
BasedOnStyle: Google
|
BasedOnStyle: Google
|
||||||
UseTab: Never
|
UseTab: Never
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
TabWidth: 4
|
TabWidth: 4
|
||||||
BreakBeforeBraces: Attach
|
BreakBeforeBraces: Attach
|
||||||
AllowShortIfStatementsOnASingleLine: false
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
IndentCaseLabels: false
|
IndentCaseLabels: false
|
||||||
AccessModifierOffset: -4
|
AccessModifierOffset: -4
|
||||||
ColumnLimit: 120
|
ColumnLimit: 120
|
||||||
NamespaceIndentation: All
|
NamespaceIndentation: All
|
||||||
|
SortIncludes: false
|
|
@ -0,0 +1,292 @@
|
||||||
|
name: Build and Package AppImage, Windows Executable, and macOS DMG
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-22.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 --target PlayerLink --parallel $(nproc)
|
||||||
|
|
||||||
|
- 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-linux-arm64:
|
||||||
|
runs-on: ubuntu-22.04-arm
|
||||||
|
|
||||||
|
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 --target PlayerLink --parallel $(nproc)
|
||||||
|
|
||||||
|
- name: Download linuxdeploy
|
||||||
|
run: |
|
||||||
|
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage"
|
||||||
|
wget "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"
|
||||||
|
chmod +x linuxdeploy-aarch64.AppImage linuxdeploy-plugin-gtk.sh
|
||||||
|
sudo mv linuxdeploy-aarch64.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-ARM64
|
||||||
|
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 --target PlayerLink
|
||||||
|
|
||||||
|
- name: Upload Windows artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PlayerLink-Windows-AMD64
|
||||||
|
path: build/Release/*
|
||||||
|
|
||||||
|
build-windows-arm64:
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup right windows sdk
|
||||||
|
uses: GuillaumeFalourd/setup-windows10-sdk-action@v2.4
|
||||||
|
with:
|
||||||
|
sdk-version: 26100
|
||||||
|
|
||||||
|
- name: Set up CMake
|
||||||
|
uses: lukka/get-cmake@latest
|
||||||
|
with:
|
||||||
|
cmakeVersion: '3.22.0'
|
||||||
|
|
||||||
|
- name: Configure with CMake for ARM64
|
||||||
|
run: cmake -B build -S . -DCMAKE_SYSTEM_VERSION="10.0.26100.0" -DCMAKE_BUILD_TYPE=Release -A ARM64
|
||||||
|
|
||||||
|
- name: Build the project for ARM64
|
||||||
|
run: cmake --build build --config Release --target PlayerLink
|
||||||
|
|
||||||
|
- name: Rename ARM64 executable
|
||||||
|
run: Rename-Item build\Release\PlayerLink.exe PlayerLink-arm64.exe
|
||||||
|
|
||||||
|
- name: Upload Windows ARM64 artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PlayerLink-Windows-ARM64
|
||||||
|
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 --target PlayerLink --parallel $(sysctl -n hw.physicalcpu)
|
||||||
|
|
||||||
|
- 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-linux-arm64, build-windows, build-windows-arm64, 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 ARM64 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-aarch64.AppImage
|
||||||
|
asset_name: PlayerLink-aarch64.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 Windows ARM64 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-arm64.exe
|
||||||
|
asset_name: PlayerLink-arm64.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
|
|
@ -3,4 +3,10 @@ build/*
|
||||||
.cache/*
|
.cache/*
|
||||||
src/rsrc.hpp
|
src/rsrc.hpp
|
||||||
PlayerLink.exe
|
PlayerLink.exe
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
linux/AppDir/usr/share/doc/*
|
||||||
|
linux/AppDir/usr/lib/*
|
||||||
|
linux/AppDir/usr/bin/*
|
||||||
|
linux/AppDir/*
|
||||||
|
!linux/AppDir/usr
|
||||||
|
!.gitkeep
|
|
@ -1,13 +1,13 @@
|
||||||
cmake_minimum_required (VERSION 3.8)
|
cmake_minimum_required (VERSION 3.12)
|
||||||
include("cmake/create_resources.cmake")
|
include("cmake/create_resources.cmake")
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||||
|
|
||||||
#enable objective c support on mac os, needed for wxwidgets and compile for both intel macs and apple sillicon macs
|
#enable objective c support on mac os, needed for wxwidgets and compile for both intel macs and apple sillicon macs
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
list(APPEND SOURCES "src/backends/darwin.mm" ${CMAKE_SOURCE_DIR}/osx/icon.icns)
|
list(APPEND SOURCES "src/backends/darwin.mm" ${CMAKE_SOURCE_DIR}/osx/icon.icns ${CMAKE_SOURCE_DIR}/osx/MediaRemote.js)
|
||||||
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
|
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)
|
project ("PlayerLink" LANGUAGES C CXX OBJCXX)
|
||||||
else()
|
else()
|
||||||
project ("PlayerLink" LANGUAGES C CXX)
|
project ("PlayerLink" LANGUAGES C CXX)
|
||||||
|
@ -40,6 +40,7 @@ elseif(APPLE)
|
||||||
endif()
|
endif()
|
||||||
set_target_properties(PlayerLink PROPERTIES MACOSX_BUNDLE TRUE)
|
set_target_properties(PlayerLink PROPERTIES MACOSX_BUNDLE TRUE)
|
||||||
set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||||
|
set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/MediaRemote.js PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||||
set_target_properties(PlayerLink PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/osx/Info.plist)
|
set_target_properties(PlayerLink PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/osx/Info.plist)
|
||||||
elseif(UNIX AND NOT APPLE)
|
elseif(UNIX AND NOT APPLE)
|
||||||
list(APPEND LIBRARIES dbus)
|
list(APPEND LIBRARIES dbus)
|
||||||
|
@ -58,4 +59,4 @@ if(wx_setup_dir)
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "wx/setup.h not found. Please check your wxWidgets build configuration.")
|
message(FATAL_ERROR "wx/setup.h not found. Please check your wxWidgets build configuration.")
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(PlayerLink PUBLIC ${LIBRARIES})
|
target_link_libraries(PlayerLink PUBLIC ${LIBRARIES})
|
||||||
|
|
19
README.md
|
@ -7,7 +7,7 @@ Cross platform, universal discord rich presence for media players.
|
||||||
- Pretty much any linux distribution with gtk3 and dbus support
|
- Pretty much any linux distribution with gtk3 and dbus support
|
||||||
|
|
||||||
## Showcase
|
## 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%">
|
<p align="center" width="100%">
|
||||||
<img src="img/showcase.png" alt="rich presence" />
|
<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
|
### 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.
|
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
|
## Config
|
||||||
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.
|
**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
|
## Building
|
||||||
|
|
||||||
|
@ -73,13 +80,13 @@ The config is currently located in the same folder as PlayerLink, this will be c
|
||||||
4. Build the project :)
|
4. Build the project :)
|
||||||
```bash
|
```bash
|
||||||
# for a release build
|
# for a release build
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release --target PlayerLink
|
||||||
# for a debug build
|
# for a debug build
|
||||||
cmake --build build
|
cmake --build build --target PlayerLink
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
This repository is open for contributions. You can view the current roadmap [here](https://github.com/EinTim23/PlayerLink/projects) or implement your own features and then open a pull request. Please keep your code as consistent and clean as possible.
|
This repository is open for contributions. You can view the current roadmap [here](https://github.com/EinTim23/PlayerLink/projects) or implement your own features and then open a pull request. Please keep your code as consistent and clean as possible.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
This project was heavily inspired by [Alexandra Aurora's MusicRPC](https://github.com/AlexandraAurora/MusicRPC) and her project may provide a better experience when being on Mac OS only.
|
This project was heavily inspired by [Alexandra Göttlicher's MusicRPC](https://github.com/kaethchen/MusicRPC) and her project may provide a better experience when being on Mac OS only.
|
BIN
img/linux.png
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 263 KiB |
BIN
img/macos.png
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 692 KiB |
BIN
img/windows.png
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 39 KiB |
|
@ -0,0 +1,6 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=PlayerLink
|
||||||
|
Exec=PlayerLink
|
||||||
|
Icon=PlayerLink
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;GTK;
|
After Width: | Height: | Size: 41 KiB |
|
@ -0,0 +1,33 @@
|
||||||
|
ObjC.import('Foundation');
|
||||||
|
try {
|
||||||
|
const frameworkPath = '/System/Library/PrivateFrameworks/MediaRemote.framework';
|
||||||
|
const framework = $.NSBundle.bundleWithPath($(frameworkPath));
|
||||||
|
framework.load
|
||||||
|
|
||||||
|
const MRNowPlayingRequest = $.NSClassFromString('MRNowPlayingRequest');
|
||||||
|
|
||||||
|
const playerPath = MRNowPlayingRequest.localNowPlayingPlayerPath;
|
||||||
|
const bundleID = ObjC.unwrap(playerPath.client.bundleIdentifier);
|
||||||
|
|
||||||
|
const nowPlayingItem = MRNowPlayingRequest.localNowPlayingItem;
|
||||||
|
const info = nowPlayingItem.nowPlayingInfo;
|
||||||
|
|
||||||
|
const title = info.valueForKey('kMRMediaRemoteNowPlayingInfoTitle');
|
||||||
|
const album = info.valueForKey('kMRMediaRemoteNowPlayingInfoAlbum');
|
||||||
|
const artist = info.valueForKey('kMRMediaRemoteNowPlayingInfoArtist');
|
||||||
|
const duration = info.valueForKey('kMRMediaRemoteNowPlayingInfoDuration');
|
||||||
|
const playbackStatus = info.valueForKey('kMRMediaRemoteNowPlayingInfoPlaybackRate');
|
||||||
|
const elapsed = info.valueForKey('kMRMediaRemoteNowPlayingInfoElapsedTime');
|
||||||
|
|
||||||
|
JSON.stringify({
|
||||||
|
title: ObjC.unwrap(title),
|
||||||
|
album: ObjC.unwrap(album),
|
||||||
|
artist: ObjC.unwrap(artist),
|
||||||
|
duration: ObjC.unwrap(duration),
|
||||||
|
playbackStatus: ObjC.unwrap(playbackStatus),
|
||||||
|
elapsed: ObjC.unwrap(elapsed),
|
||||||
|
player: ObjC.unwrap(bundleID)
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
JSON.stringify({ player: 'none', error: error.toString() });
|
||||||
|
}
|
BIN
osx/icon.icns
BIN
rsrc/icon.png
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 817 KiB |
After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path d="M21.731 2.269a2.625 2.625 0 0 0-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 0 0 0-3.712ZM19.513 8.199l-3.712-3.712-12.15 12.15a5.25 5.25 0 0 0-1.32 2.214l-.8 2.685a.75.75 0 0 0 .933.933l2.685-.8a5.25 5.25 0 0 0 2.214-1.32L19.513 8.2Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 367 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path fill-rule="evenodd" d="M14 3a2 2 0 1 0-4 0v7H3a2 2 0 1 0 0 4h7v7a2 2 0 1 0 4 0v-7h7a2 2 0 1 0 0-4h-7V3z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 239 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||||
|
<path fill-rule="evenodd" d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 751 B |
|
@ -5,13 +5,34 @@
|
||||||
"client_id": "1245257414715113573",
|
"client_id": "1245257414715113573",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"name": "Spotify",
|
"name": "Spotify",
|
||||||
|
"type": 2,
|
||||||
"process_names": [
|
"process_names": [
|
||||||
"org.mpris.MediaPlayer2.spotify",
|
"org.mpris.MediaPlayer2.spotify",
|
||||||
"com.spotify.client",
|
"com.spotify.client",
|
||||||
"Spotify.exe"
|
"Spotify.exe"
|
||||||
],
|
],
|
||||||
"search_endpoint": "https://open.spotify.com/search/"
|
"search_endpoint": "https://open.spotify.com/search/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "1337188104829665340",
|
||||||
|
"enabled": true,
|
||||||
|
"name": "Apple Music",
|
||||||
|
"type": 2,
|
||||||
|
"process_names": [
|
||||||
|
"com.apple.Music",
|
||||||
|
"AppleInc.AppleMusicWin_nzyj5cx40ttqa!APP",
|
||||||
|
"AppleMusic.exe"
|
||||||
|
],
|
||||||
|
"search_endpoint": "https://music.apple.com/search?term="
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"lastfm": {
|
||||||
|
"api_key": "",
|
||||||
|
"api_secret": "",
|
||||||
|
"enabled": false,
|
||||||
|
"password": "",
|
||||||
|
"username": ""
|
||||||
|
},
|
||||||
|
"odesli": true,
|
||||||
"autostart": false
|
"autostart": false
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
struct MediaInfo {
|
struct MediaInfo {
|
||||||
bool paused;
|
bool paused;
|
||||||
|
@ -30,6 +31,7 @@ struct MediaInfo {
|
||||||
namespace backend {
|
namespace backend {
|
||||||
bool init();
|
bool init();
|
||||||
bool toggleAutostart(bool enabled);
|
bool toggleAutostart(bool enabled);
|
||||||
|
std::filesystem::path getConfigDirectory();
|
||||||
std::shared_ptr<MediaInfo> getMediaInformation();
|
std::shared_ptr<MediaInfo> getMediaInformation();
|
||||||
} // namespace backend
|
} // namespace backend
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
#include <Foundation/NSObjCRuntime.h>
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <AppKit/AppKit.h>
|
#include <AppKit/AppKit.h>
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
#include <Foundation/Foundation.h>
|
#include <Foundation/Foundation.h>
|
||||||
#include <dispatch/dispatch.h>
|
#include <dispatch/dispatch.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <nlohmann-json/single_include/nlohmann/json.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include "../MediaRemote.hpp"
|
#include "../MediaRemote.hpp"
|
||||||
|
@ -16,77 +18,141 @@ void hideDockIcon(bool shouldHide) {
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NSString *getFilePathFromBundle(NSString *fileName, NSString *fileType) {
|
||||||
|
NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:fileType];
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *executeCommand(NSString *command, NSArray *arguments) {
|
||||||
|
NSTask *task = [[NSTask alloc] init];
|
||||||
|
task.launchPath = command;
|
||||||
|
task.arguments = arguments;
|
||||||
|
|
||||||
|
NSPipe *pipe = [NSPipe pipe];
|
||||||
|
task.standardOutput = pipe;
|
||||||
|
task.standardError = [NSPipe pipe];
|
||||||
|
[task launch];
|
||||||
|
|
||||||
|
NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];
|
||||||
|
NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||||
|
[task waitUntilExit];
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<MediaInfo> backend::getMediaInformation() {
|
std::shared_ptr<MediaInfo> backend::getMediaInformation() {
|
||||||
__block NSString *appName = nil;
|
// apple decided to prevent apps not signed by them to use media remote, so we use an apple script instead. But that script only works on Sonoma or newer and the other one is arguably better, so keep the old method as well
|
||||||
__block NSDictionary *playingInfo = nil;
|
if (@available(macOS 15.0, *)) {
|
||||||
|
static NSString *script = getFilePathFromBundle(@"MediaRemote", @"js");
|
||||||
|
NSString *output = executeCommand(@"/usr/bin/osascript", @[ @"-l", @"JavaScript", script ]);
|
||||||
|
nlohmann::json j = nlohmann::json::parse([output UTF8String]);
|
||||||
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
std::string appName = j["player"].get<std::string>();
|
||||||
|
if (appName == "none")
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
dispatch_group_enter(group);
|
bool paused = j["playbackStatus"].get<int>() == 0;
|
||||||
MRMediaRemoteGetNowPlayingApplicationPID(dispatch_get_main_queue(), ^(pid_t pid) {
|
|
||||||
if (pid > 0) {
|
|
||||||
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid];
|
|
||||||
if (app)
|
|
||||||
appName = [[app.bundleIdentifier copy] retain];
|
|
||||||
}
|
|
||||||
dispatch_group_leave(group);
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch_group_enter(group);
|
std::string songTitle = j["title"].get<std::string>();
|
||||||
MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef result) {
|
|
||||||
if (result)
|
|
||||||
playingInfo = [[(__bridge NSDictionary *)result copy] retain];
|
|
||||||
dispatch_group_leave(group);
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
std::string songAlbum = j["album"].get<std::string>();
|
||||||
dispatch_release(group);
|
|
||||||
if (appName == nil || playingInfo == nil)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
bool paused = [playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoPlaybackRate] intValue] == 0;
|
std::string songArtist = j["artist"].get<std::string>();
|
||||||
|
|
||||||
NSString *title = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoTitle];
|
int64_t elapsedTimeMs = 0;
|
||||||
std::string songTitle = title ? [title UTF8String] : "";
|
int64_t durationMs = 0;
|
||||||
|
try {
|
||||||
|
double durationNumber = j["duration"].get<double>();
|
||||||
|
durationMs = static_cast<int64_t>(durationNumber * 1000);
|
||||||
|
|
||||||
NSString *album = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoAlbum];
|
double elapsedTimeNumber = j["elapsed"].get<double>();
|
||||||
std::string songAlbum = album ? [album UTF8String] : "";
|
elapsedTimeMs = static_cast<int64_t>(elapsedTimeNumber * 1000);
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
|
||||||
NSString *artist = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtist];
|
return std::make_shared<MediaInfo>(paused, songTitle, songArtist, songAlbum, appName, "", durationMs,
|
||||||
std::string songArtist = artist ? [artist UTF8String] : "";
|
elapsedTimeMs);
|
||||||
|
} else {
|
||||||
|
__block NSString *appName = nil;
|
||||||
|
__block NSDictionary *playingInfo = nil;
|
||||||
|
|
||||||
NSData *artworkData = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtworkData];
|
dispatch_group_t group = dispatch_group_create();
|
||||||
|
|
||||||
std::string thumbnailData;
|
dispatch_group_enter(group);
|
||||||
if (artworkData)
|
MRMediaRemoteGetNowPlayingApplicationPID(dispatch_get_main_queue(), ^(pid_t pid) {
|
||||||
thumbnailData = std::string((const char *)[artworkData bytes], [artworkData length]);
|
if (pid > 0) {
|
||||||
|
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid];
|
||||||
|
if (app)
|
||||||
|
appName = [[app.bundleIdentifier copy] retain];
|
||||||
|
}
|
||||||
|
dispatch_group_leave(group);
|
||||||
|
});
|
||||||
|
|
||||||
NSNumber *elapsedTimeNumber = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoElapsedTime];
|
dispatch_group_enter(group);
|
||||||
|
MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef result) {
|
||||||
|
if (result)
|
||||||
|
playingInfo = [[(__bridge NSDictionary *)result copy] retain];
|
||||||
|
dispatch_group_leave(group);
|
||||||
|
});
|
||||||
|
|
||||||
NSNumber *durationNumber = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoDuration];
|
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||||
|
dispatch_release(group);
|
||||||
|
if (appName == nil || playingInfo == nil)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
int64_t elapsedTimeMs = elapsedTimeNumber ? static_cast<int64_t>([elapsedTimeNumber doubleValue] * 1000) : 0;
|
bool paused = [playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoPlaybackRate] intValue] == 0;
|
||||||
|
|
||||||
int64_t durationMs = durationNumber ? static_cast<int64_t>([durationNumber doubleValue] * 1000) : 0;
|
NSString *title = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoTitle];
|
||||||
|
std::string songTitle = title ? [title UTF8String] : "";
|
||||||
|
|
||||||
std::string appNameString = appName.UTF8String;
|
NSString *album = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoAlbum];
|
||||||
|
std::string songAlbum = album ? [album UTF8String] : "";
|
||||||
|
|
||||||
[appName release];
|
NSString *artist = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtist];
|
||||||
[playingInfo release];
|
std::string songArtist = artist ? [artist UTF8String] : "";
|
||||||
return std::make_shared<MediaInfo>(paused, songTitle, songArtist, songAlbum, appNameString, thumbnailData,
|
|
||||||
durationMs, elapsedTimeMs);
|
NSData *artworkData = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtworkData];
|
||||||
|
|
||||||
|
std::string thumbnailData;
|
||||||
|
if (artworkData)
|
||||||
|
thumbnailData = std::string((const char *)[artworkData bytes], [artworkData length]);
|
||||||
|
|
||||||
|
NSNumber *elapsedTimeNumber = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoElapsedTime];
|
||||||
|
|
||||||
|
NSNumber *durationNumber = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoDuration];
|
||||||
|
|
||||||
|
int64_t elapsedTimeMs = elapsedTimeNumber ? static_cast<int64_t>([elapsedTimeNumber doubleValue] * 1000) : 0;
|
||||||
|
|
||||||
|
int64_t durationMs = durationNumber ? static_cast<int64_t>([durationNumber doubleValue] * 1000) : 0;
|
||||||
|
|
||||||
|
std::string appNameString = appName.UTF8String;
|
||||||
|
|
||||||
|
[appName release];
|
||||||
|
[playingInfo release];
|
||||||
|
return std::make_shared<MediaInfo>(paused, songTitle, songArtist, songAlbum, appNameString, thumbnailData,
|
||||||
|
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) {
|
bool backend::toggleAutostart(bool enabled) {
|
||||||
std::filesystem::path launchAgentPath = std::getenv("HOME");
|
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)) {
|
if (!enabled && std::filesystem::exists(launchAgentPath)) {
|
||||||
std::filesystem::remove(launchAgentPath);
|
std::filesystem::remove(launchAgentPath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
NSString *binaryPath = [[[NSProcessInfo processInfo] arguments][0] stringByStandardizingPath];
|
NSString *binaryPath = [[[NSProcessInfo processInfo] arguments][0] stringByStandardizingPath];
|
||||||
|
|
||||||
//I would also like to use std::format here, but well I also want to support older mac os versions.
|
// I would also like to use std::format here, but well I also want to support older mac os versions.
|
||||||
std::string formattedPlist =
|
std::string formattedPlist =
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
|
||||||
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n <dict>\n\n "
|
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n <dict>\n\n "
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
DBusConnection* conn = nullptr;
|
DBusConnection* conn = nullptr;
|
||||||
|
|
||||||
std::string getExecutablePath() {
|
std::string getExecutablePath() {
|
||||||
|
if (const char* appImagePath = std::getenv("APPIMAGE"))
|
||||||
|
return std::string(appImagePath);
|
||||||
|
|
||||||
char result[PATH_MAX]{};
|
char result[PATH_MAX]{};
|
||||||
ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
|
ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
|
||||||
return (count != -1) ? std::string(result, count) : std::string();
|
return (count != -1) ? std::string(result, count) : std::string();
|
||||||
|
@ -189,8 +192,17 @@ bool backend::init() {
|
||||||
|
|
||||||
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
||||||
if (!conn) {
|
if (!conn) {
|
||||||
dbus_error_free(&err);
|
if (dbus_error_is_set(&err))
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -206,6 +218,12 @@ std::shared_ptr<MediaInfo> backend::getMediaInformation() {
|
||||||
return std::make_shared<MediaInfo>(ret);
|
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) {
|
bool backend::toggleAutostart(bool enabled) {
|
||||||
const char* xdgHome = std::getenv("XDG_CONFIG_HOME");
|
const char* xdgHome = std::getenv("XDG_CONFIG_HOME");
|
||||||
|
|
||||||
|
|
|
@ -1,127 +1,152 @@
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <objbase.h>
|
#include <objbase.h>
|
||||||
#include <shlobj.h>
|
#include <psapi.h>
|
||||||
#include <windows.h>
|
#include <shlobj.h>
|
||||||
#include <winrt/windows.foundation.h>
|
#include <windows.h>
|
||||||
#include <winrt/windows.foundation.metadata.h>
|
#include <appmodel.h>
|
||||||
#include <winrt/windows.media.control.h>
|
#include <winrt/windows.foundation.h>
|
||||||
#include <winrt/windows.storage.streams.h>
|
#include <winrt/windows.foundation.metadata.h>
|
||||||
|
#include <winrt/windows.media.control.h>
|
||||||
#include <chrono>
|
#include <winrt/windows.storage.streams.h>
|
||||||
#include <codecvt>
|
|
||||||
#include <filesystem>
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
#include "../backend.hpp"
|
|
||||||
#include "../utils.hpp"
|
#include "../backend.hpp"
|
||||||
|
#include "../utils.hpp"
|
||||||
using namespace winrt;
|
|
||||||
using namespace Windows::Media::Control;
|
using namespace winrt;
|
||||||
using namespace Windows::Storage::Streams;
|
using namespace Windows::Media::Control;
|
||||||
#define EM_DASH "\xE2\x80\x94"
|
using namespace Windows::Storage::Streams;
|
||||||
// codecvt is deprecated, but there is no good portable way to do this, I could technically use the winapi as this is
|
#define EM_DASH "\xE2\x80\x94"
|
||||||
// the windows backend tho
|
|
||||||
std::string toStdString(winrt::hstring in) {
|
std::string toStdString(winrt::hstring& in) {
|
||||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
|
const wchar_t* wideStr = in.c_str();
|
||||||
return converter.to_bytes(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)
|
||||||
bool CreateShortcut(std::string source, std::string target) {
|
return "";
|
||||||
CoInitialize(nullptr);
|
|
||||||
WCHAR src[MAX_PATH];
|
std::string result(bufferSize, 0);
|
||||||
IShellLinkW* pShellLink = nullptr;
|
|
||||||
HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
WideCharToMultiByte(CP_UTF8, 0, wideStr, wideStrLen, result.data(), bufferSize, nullptr, nullptr);
|
||||||
reinterpret_cast<void**>(&pShellLink));
|
|
||||||
|
return result;
|
||||||
if (SUCCEEDED(hr) && pShellLink) {
|
}
|
||||||
MultiByteToWideChar(CP_ACP, 0, source.c_str(), -1, src, MAX_PATH);
|
|
||||||
pShellLink->SetPath(src);
|
bool CreateShortcut(std::string source, std::string target) {
|
||||||
|
CoInitialize(nullptr);
|
||||||
IPersistFile* pPersistFile = nullptr;
|
WCHAR src[MAX_PATH];
|
||||||
hr = pShellLink->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&pPersistFile));
|
IShellLinkW* pShellLink = nullptr;
|
||||||
|
HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
||||||
if (SUCCEEDED(hr) && pPersistFile) {
|
reinterpret_cast<void**>(&pShellLink));
|
||||||
WCHAR dst[MAX_PATH];
|
|
||||||
MultiByteToWideChar(CP_ACP, 0, target.c_str(), -1, dst, MAX_PATH);
|
if (SUCCEEDED(hr) && pShellLink) {
|
||||||
hr = pPersistFile->Save(dst, TRUE);
|
MultiByteToWideChar(CP_ACP, 0, source.c_str(), -1, src, MAX_PATH);
|
||||||
pPersistFile->Release();
|
pShellLink->SetPath(src);
|
||||||
}
|
|
||||||
|
IPersistFile* pPersistFile = nullptr;
|
||||||
pShellLink->Release();
|
hr = pShellLink->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&pPersistFile));
|
||||||
}
|
|
||||||
|
if (SUCCEEDED(hr) && pPersistFile) {
|
||||||
CoUninitialize();
|
WCHAR dst[MAX_PATH];
|
||||||
return SUCCEEDED(hr);
|
MultiByteToWideChar(CP_ACP, 0, target.c_str(), -1, dst, MAX_PATH);
|
||||||
}
|
hr = pPersistFile->Save(dst, TRUE);
|
||||||
|
pPersistFile->Release();
|
||||||
bool backend::toggleAutostart(bool enabled) {
|
}
|
||||||
std::filesystem::path shortcutPath = std::getenv("APPDATA");
|
|
||||||
shortcutPath = shortcutPath / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup" / "PlayerLink.lnk";
|
pShellLink->Release();
|
||||||
if (!enabled && std::filesystem::exists(shortcutPath)) {
|
}
|
||||||
std::filesystem::remove(shortcutPath);
|
|
||||||
return true;
|
CoUninitialize();
|
||||||
}
|
return SUCCEEDED(hr);
|
||||||
char binaryPath[MAX_PATH]{};
|
}
|
||||||
GetModuleFileNameA(NULL, binaryPath, MAX_PATH);
|
|
||||||
bool result = CreateShortcut(binaryPath, shortcutPath.string());
|
std::filesystem::path backend::getConfigDirectory() {
|
||||||
return result;
|
std::filesystem::path configDirectoryPath = std::getenv("APPDATA");
|
||||||
}
|
configDirectoryPath = configDirectoryPath / "PlayerLink";
|
||||||
|
return configDirectoryPath;
|
||||||
std::shared_ptr<MediaInfo> backend::getMediaInformation() {
|
}
|
||||||
auto sessionManager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
|
|
||||||
auto currentSession = sessionManager.GetCurrentSession();
|
bool backend::toggleAutostart(bool enabled) {
|
||||||
if (!currentSession)
|
std::filesystem::path shortcutPath = std::getenv("APPDATA");
|
||||||
return nullptr;
|
shortcutPath = shortcutPath / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup";
|
||||||
|
std::filesystem::create_directories(shortcutPath);
|
||||||
auto playbackInfo = currentSession.GetPlaybackInfo();
|
shortcutPath = shortcutPath / "PlayerLink.lnk";
|
||||||
auto mediaProperties = currentSession.TryGetMediaPropertiesAsync().get();
|
|
||||||
auto timelineInformation = currentSession.GetTimelineProperties();
|
if (!enabled && std::filesystem::exists(shortcutPath)) {
|
||||||
if (!mediaProperties)
|
std::filesystem::remove(shortcutPath);
|
||||||
return nullptr;
|
return true;
|
||||||
|
}
|
||||||
auto endTime = std::chrono::duration_cast<std::chrono::milliseconds>(timelineInformation.EndTime()).count();
|
char binaryPath[MAX_PATH]{};
|
||||||
auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(timelineInformation.Position()).count();
|
GetModuleFileNameA(NULL, binaryPath, MAX_PATH);
|
||||||
|
bool result = CreateShortcut(binaryPath, shortcutPath.string());
|
||||||
auto thumbnail = mediaProperties.Thumbnail();
|
return result;
|
||||||
std::string thumbnailData = "";
|
}
|
||||||
|
|
||||||
if (thumbnail) {
|
std::shared_ptr<MediaInfo> backend::getMediaInformation() {
|
||||||
auto stream = thumbnail.OpenReadAsync().get();
|
static auto sessionManager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
|
||||||
size_t size = static_cast<size_t>(stream.Size());
|
auto currentSession = sessionManager.GetCurrentSession();
|
||||||
|
if (!currentSession)
|
||||||
DataReader reader(stream);
|
return nullptr;
|
||||||
reader.LoadAsync(static_cast<uint32_t>(size)).get();
|
|
||||||
|
auto playbackInfo = currentSession.GetPlaybackInfo();
|
||||||
std::vector<uint8_t> buffer(size);
|
try {
|
||||||
reader.ReadBytes(buffer);
|
auto mediaProperties = currentSession.TryGetMediaPropertiesAsync().get();
|
||||||
reader.Close();
|
auto timelineInformation = currentSession.GetTimelineProperties();
|
||||||
|
if (!mediaProperties)
|
||||||
thumbnailData = std::string(buffer.begin(), buffer.end());
|
return nullptr;
|
||||||
stream.Close();
|
|
||||||
}
|
auto endTime = std::chrono::duration_cast<std::chrono::milliseconds>(timelineInformation.EndTime()).count();
|
||||||
|
auto elapsedTime =
|
||||||
std::string artist = toStdString(mediaProperties.Artist());
|
std::chrono::duration_cast<std::chrono::milliseconds>(timelineInformation.Position()).count();
|
||||||
std::string albumName = toStdString(mediaProperties.AlbumTitle());
|
|
||||||
if (artist == "")
|
auto thumbnail = mediaProperties.Thumbnail();
|
||||||
artist = toStdString(mediaProperties.AlbumArtist()); // Needed for some apps
|
std::string thumbnailData = "";
|
||||||
|
|
||||||
if (artist.find(EM_DASH) != std::string::npos) {
|
if (thumbnail) {
|
||||||
albumName = artist.substr(artist.find(EM_DASH) + 3);
|
auto stream = thumbnail.OpenReadAsync().get();
|
||||||
artist = artist.substr(0, artist.find(EM_DASH));
|
size_t size = static_cast<size_t>(stream.Size());
|
||||||
utils::trim(artist);
|
|
||||||
utils::trim(albumName);
|
DataReader reader(stream);
|
||||||
}
|
reader.LoadAsync(static_cast<uint32_t>(size)).get();
|
||||||
|
|
||||||
return std::make_shared<MediaInfo>(
|
std::vector<uint8_t> buffer(size);
|
||||||
playbackInfo.PlaybackStatus() == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Paused,
|
reader.ReadBytes(buffer);
|
||||||
toStdString(mediaProperties.Title()), artist, albumName, toStdString(currentSession.SourceAppUserModelId()),
|
reader.Close();
|
||||||
thumbnailData, endTime, elapsedTime);
|
|
||||||
}
|
thumbnailData = std::string(buffer.begin(), buffer.end());
|
||||||
|
stream.Close();
|
||||||
bool backend::init() {
|
}
|
||||||
return winrt::Windows::Foundation::Metadata::ApiInformation::IsTypePresent(
|
|
||||||
L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager");
|
std::string artist = toStdString(mediaProperties.Artist());
|
||||||
}
|
std::string albumName = toStdString(mediaProperties.AlbumTitle());
|
||||||
|
if (artist == "")
|
||||||
#undef EM_DASH
|
artist = toStdString(mediaProperties.AlbumArtist()); // Needed for some apps
|
||||||
#endif
|
|
||||||
|
if (artist.find(EM_DASH) != std::string::npos) {
|
||||||
|
albumName = artist.substr(artist.find(EM_DASH) + 3);
|
||||||
|
artist = artist.substr(0, artist.find(EM_DASH));
|
||||||
|
utils::trim(artist);
|
||||||
|
utils::trim(albumName);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string modelId = toStdString(currentSession.SourceAppUserModelId());
|
||||||
|
|
||||||
|
return std::make_shared<MediaInfo>(
|
||||||
|
playbackInfo.PlaybackStatus() == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Paused,
|
||||||
|
toStdString(mediaProperties.Title()), std::move(artist), std::move(albumName), std::move(modelId),
|
||||||
|
std::move(thumbnailData), endTime, elapsedTime);
|
||||||
|
} catch (...) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool backend::init() {
|
||||||
|
return winrt::Windows::Foundation::Metadata::ApiInformation::IsTypePresent(
|
||||||
|
L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager");
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef EM_DASH
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
#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);
|
||||||
|
try {
|
||||||
|
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;
|
||||||
|
} catch (...) {
|
||||||
|
return LASTFM_STATUS::UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
1003
src/main.cpp
162
src/utils.hpp
|
@ -3,7 +3,7 @@
|
||||||
#include <curl/include/curl/curl.h>
|
#include <curl/include/curl/curl.h>
|
||||||
#include <wx/mstream.h>
|
#include <wx/mstream.h>
|
||||||
#include <wx/wx.h>
|
#include <wx/wx.h>
|
||||||
|
#include <wx/clipbrd.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <nlohmann-json/single_include/nlohmann/json.hpp>
|
#include <nlohmann-json/single_include/nlohmann/json.hpp>
|
||||||
|
@ -11,37 +11,105 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "backend.hpp"
|
||||||
|
|
||||||
#define DEFAULT_CLIENT_ID "1301849203378622545"
|
#define DEFAULT_CLIENT_ID "1301849203378622545"
|
||||||
#define DEFAULT_APP_NAME "Music"
|
#define DEFAULT_APP_NAME "Music"
|
||||||
#define CONFIG_FILENAME "settings.json"
|
#define CONFIG_FILENAME backend::getConfigDirectory() / "settings.json"
|
||||||
|
|
||||||
namespace utils {
|
namespace utils {
|
||||||
struct App {
|
struct App {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
int type;
|
||||||
std::string appName;
|
std::string appName;
|
||||||
std::string clientId;
|
std::string clientId;
|
||||||
std::string searchEndpoint;
|
std::string searchEndpoint;
|
||||||
std::vector<std::string> processNames;
|
std::vector<std::string> processNames;
|
||||||
|
bool operator==(const App& other) const {
|
||||||
|
return appName == other.appName && clientId == other.clientId && type == other.type;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LastFMSettings {
|
||||||
|
bool enabled;
|
||||||
|
std::string username;
|
||||||
|
std::string password;
|
||||||
|
std::string api_key;
|
||||||
|
std::string api_secret;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
|
bool odesli;
|
||||||
bool autoStart;
|
bool autoStart;
|
||||||
bool anyOtherEnabled;
|
bool anyOtherEnabled;
|
||||||
|
LastFMSettings lastfm;
|
||||||
std::vector<App> apps;
|
std::vector<App> apps;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline wxIcon loadIconFromMemory(const unsigned char* data, size_t size) {
|
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 wxBitmap loadImageFromMemory(const unsigned char* data, size_t size, int width = 0, int height = 0) {
|
||||||
wxMemoryInputStream stream(data, size);
|
wxMemoryInputStream stream(data, size);
|
||||||
wxImage img(stream, wxBITMAP_TYPE_PNG);
|
wxImage img(stream, wxBITMAP_TYPE_PNG);
|
||||||
if (img.IsOk()) {
|
if (img.IsOk()) {
|
||||||
|
if (width != 0 || height != 0)
|
||||||
|
img.Rescale(width, height, wxIMAGE_QUALITY_HIGH);
|
||||||
wxBitmap bmp(img);
|
wxBitmap bmp(img);
|
||||||
wxIcon icon;
|
return bmp;
|
||||||
icon.CopyFromBitmap(bmp);
|
|
||||||
return icon;
|
|
||||||
}
|
}
|
||||||
return wxNullIcon;
|
return wxNullBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline wxIcon loadIconFromMemory(const unsigned char* data, size_t size, int width = 0, int height = 0) {
|
||||||
|
wxIcon icn{};
|
||||||
|
icn.CopyFromBitmap(loadImageFromMemory(data, size, width, height));
|
||||||
|
return icn;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline wxBitmap loadColoredSVG(const unsigned char* svg, const unsigned int svg_size, const wxSize& size,
|
||||||
|
const wxColour& color) {
|
||||||
|
const std::string defaultColor = "currentColor";
|
||||||
|
std::string svg_data = std::string((const char*)svg, svg_size);
|
||||||
|
size_t start_pos = svg_data.find(defaultColor);
|
||||||
|
if (start_pos != std::string::npos)
|
||||||
|
svg_data.replace(start_pos, defaultColor.length(), color.GetAsString(wxC2S_HTML_SYNTAX));
|
||||||
|
|
||||||
|
wxBitmapBundle bundle = wxBitmapBundle::FromSVG(svg_data.c_str(), size);
|
||||||
|
if (!bundle.IsOk())
|
||||||
|
return wxNullBitmap;
|
||||||
|
wxBitmap bmp = bundle.GetBitmap(size);
|
||||||
|
if (!bmp.IsOk())
|
||||||
|
return wxNullBitmap;
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline wxBitmap loadSettingsIcon(const unsigned char* svg, const unsigned int svg_size,
|
||||||
|
const wxSize& size = wxSize(16, 16)) {
|
||||||
|
return loadColoredSVG(
|
||||||
|
svg, svg_size, size,
|
||||||
|
wxSystemSettings::GetAppearance().IsSystemDark() ? wxColor(255, 255, 255, 255) : wxColor(0, 0, 0, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string toLower(const std::string& str) {
|
||||||
|
std::string lowerStr = str;
|
||||||
|
std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
return lowerStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool caseInsensitiveMatch(const std::string& a, const std::string& b) { return toLower(a) == toLower(b); }
|
||||||
|
|
||||||
inline std::string ltrim(std::string& s) {
|
inline std::string ltrim(std::string& s) {
|
||||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
|
||||||
return s;
|
return s;
|
||||||
|
@ -78,7 +146,21 @@ namespace utils {
|
||||||
return size * nmemb;
|
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;
|
CURL* curl;
|
||||||
CURLcode res;
|
CURLcode res;
|
||||||
std::string buf;
|
std::string buf;
|
||||||
|
@ -86,6 +168,10 @@ namespace utils {
|
||||||
curl = curl_easy_init();
|
curl = curl_easy_init();
|
||||||
if (curl) {
|
if (curl) {
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
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_WRITEFUNCTION, curlWriteCallback);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
||||||
|
@ -96,16 +182,27 @@ namespace utils {
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string getArtworkURL(std::string query) {
|
inline SongInfo getSongInfo(std::string query) {
|
||||||
|
SongInfo ret{};
|
||||||
std::string response =
|
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);
|
try {
|
||||||
auto results = j["results"];
|
nlohmann::json j = nlohmann::json::parse(response);
|
||||||
if (results.size() > 0) {
|
auto results = j["results"];
|
||||||
return results[0]["artworkUrl100"].get<std::string>();
|
if (results.size() > 0) {
|
||||||
|
ret.artworkURL = results[0]["artworkUrl100"].get<std::string>();
|
||||||
|
ret.trackId = results[0]["trackId"].get<int64_t>();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} catch (...) {
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string getOdesliURL(SongInfo& song) {
|
||||||
|
return std::string("https://song.link/i/" + std::to_string(song.trackId));
|
||||||
|
}
|
||||||
|
|
||||||
inline void saveSettings(const App* newApp) {
|
inline void saveSettings(const App* newApp) {
|
||||||
nlohmann::json j;
|
nlohmann::json j;
|
||||||
|
|
||||||
|
@ -159,6 +256,13 @@ namespace utils {
|
||||||
nlohmann::json j;
|
nlohmann::json j;
|
||||||
j["autostart"] = settings.autoStart;
|
j["autostart"] = settings.autoStart;
|
||||||
j["any_other"] = settings.anyOtherEnabled;
|
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) {
|
for (const auto& app : settings.apps) {
|
||||||
nlohmann::json appJson;
|
nlohmann::json appJson;
|
||||||
|
@ -166,7 +270,7 @@ namespace utils {
|
||||||
appJson["client_id"] = app.clientId;
|
appJson["client_id"] = app.clientId;
|
||||||
appJson["search_endpoint"] = app.searchEndpoint;
|
appJson["search_endpoint"] = app.searchEndpoint;
|
||||||
appJson["enabled"] = app.enabled;
|
appJson["enabled"] = app.enabled;
|
||||||
|
appJson["type"] = app.type;
|
||||||
for (const auto& processName : app.processNames) appJson["process_names"].push_back(processName);
|
for (const auto& processName : app.processNames) appJson["process_names"].push_back(processName);
|
||||||
|
|
||||||
j["apps"].push_back(appJson);
|
j["apps"].push_back(appJson);
|
||||||
|
@ -177,9 +281,15 @@ namespace utils {
|
||||||
o.close();
|
o.close();
|
||||||
}
|
}
|
||||||
inline Settings getSettings() {
|
inline Settings getSettings() {
|
||||||
|
std::filesystem::create_directories(backend::getConfigDirectory());
|
||||||
Settings ret;
|
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;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::ifstream i(CONFIG_FILENAME);
|
std::ifstream i(CONFIG_FILENAME);
|
||||||
|
@ -188,6 +298,16 @@ namespace utils {
|
||||||
|
|
||||||
ret.autoStart = j.value("autostart", false);
|
ret.autoStart = j.value("autostart", false);
|
||||||
ret.anyOtherEnabled = j.value("any_other", 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"]) {
|
for (const auto& app : j["apps"]) {
|
||||||
App a;
|
App a;
|
||||||
|
@ -195,13 +315,14 @@ namespace utils {
|
||||||
a.clientId = app.value("client_id", "");
|
a.clientId = app.value("client_id", "");
|
||||||
a.searchEndpoint = app.value("search_endpoint", "");
|
a.searchEndpoint = app.value("search_endpoint", "");
|
||||||
a.enabled = app.value("enabled", false);
|
a.enabled = app.value("enabled", false);
|
||||||
|
a.type = app.value("type", 2);
|
||||||
|
|
||||||
for (const auto& process : app["process_names"]) a.processNames.push_back(process.get<std::string>());
|
for (const auto& process : app.value("process_names", nlohmann::json())) a.processNames.push_back(process.get<std::string>());
|
||||||
|
|
||||||
ret.apps.push_back(a);
|
ret.apps.push_back(a);
|
||||||
}
|
}
|
||||||
} catch (const nlohmann::json::parse_error&) {
|
} catch (const nlohmann::json::parse_error&) {
|
||||||
} // TODO: handle error
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +330,7 @@ namespace utils {
|
||||||
auto settings = getSettings();
|
auto settings = getSettings();
|
||||||
for (auto app : settings.apps) {
|
for (auto app : settings.apps) {
|
||||||
for (auto procName : app.processNames) {
|
for (auto procName : app.processNames) {
|
||||||
if (procName == processName)
|
if (caseInsensitiveMatch(procName, processName))
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,6 +338,7 @@ namespace utils {
|
||||||
a.clientId = DEFAULT_CLIENT_ID;
|
a.clientId = DEFAULT_CLIENT_ID;
|
||||||
a.appName = DEFAULT_APP_NAME;
|
a.appName = DEFAULT_APP_NAME;
|
||||||
a.enabled = settings.anyOtherEnabled;
|
a.enabled = settings.anyOtherEnabled;
|
||||||
|
a.type = 2; // Default to listening
|
||||||
a.searchEndpoint = "";
|
a.searchEndpoint = "";
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,16 @@ SET(BUILD_STATIC_LIBS ON)
|
||||||
SET(BUILD_SHARED_LIBS OFF)
|
SET(BUILD_SHARED_LIBS OFF)
|
||||||
SET(BUILD_CURL_EXE OFF)
|
SET(BUILD_CURL_EXE OFF)
|
||||||
SET(MBEDTLS_INCLUDE_DIRS ../mbedtls/include)
|
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})
|
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dummy ${CMAKE_MODULE_PATH})
|
||||||
add_subdirectory("curl")
|
add_subdirectory("curl")
|
||||||
|
file(RENAME curl/CMake/FindMbedTLS.cmake.bak curl/CMake/FindMbedTLS.cmake)
|
||||||
set(wxBUILD_SHARED OFF)
|
set(wxBUILD_SHARED OFF)
|
||||||
set(wxBUILD_MONOLITHIC ON)
|
set(wxBUILD_MONOLITHIC ON)
|
||||||
set(wxUSE_GUI ON)
|
set(wxUSE_GUI ON)
|
||||||
set(wxUSE_WEBVIEW OFF)
|
set(wxUSE_WEBVIEW OFF)
|
||||||
|
set(wxUSE_UNICODE_UTF8 ON)
|
||||||
|
set(wxUSE_UTF8_LOCALE_ONLY ON)
|
||||||
add_subdirectory("wxWidgets")
|
add_subdirectory("wxWidgets")
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
add_subdirectory("dbus")
|
add_subdirectory("dbus")
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit e86d7a81de7a33323e2038182ab53a26c69f7880
|
Subproject commit 6bc42071f109083178ae824898ca4b3cb40a8307
|
|
@ -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
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 12b09a5e5ea76a1a0c27b769e821b37d803a4cb7
|
Subproject commit d7a696de4c301948f02dc5d7979f867f07f9d684
|
BIN
win/icon.ico
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 156 KiB |