diff --git a/README.md b/README.md
index 77fcffe..9eb0e16 100644
--- a/README.md
+++ b/README.md
@@ -89,4 +89,4 @@ An example on how to add custom apps to the config can be found [here](./setting
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
-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.
\ No newline at end of file
+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.
\ No newline at end of file
diff --git a/rsrc/pencil.svg b/rsrc/pencil.svg
new file mode 100644
index 0000000..e54d9fa
--- /dev/null
+++ b/rsrc/pencil.svg
@@ -0,0 +1,3 @@
+
diff --git a/rsrc/plus.svg b/rsrc/plus.svg
new file mode 100644
index 0000000..65617e4
--- /dev/null
+++ b/rsrc/plus.svg
@@ -0,0 +1,3 @@
+
diff --git a/rsrc/trash.svg b/rsrc/trash.svg
new file mode 100644
index 0000000..27913f7
--- /dev/null
+++ b/rsrc/trash.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/main.cpp b/src/main.cpp
index 4d155be..221dd71 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#include
@@ -143,7 +144,7 @@ void handleMediaTasks() {
}
std::string odesliUrl = utils::getOdesliURL(songInfo);
- if(settings.odesli && songInfo.artworkURL != "") {
+ if (settings.odesli && songInfo.artworkURL != "") {
activity.button2name = "Show on Song.link";
activity.button2link = odesliUrl.c_str();
}
@@ -151,9 +152,43 @@ void handleMediaTasks() {
Discord_UpdatePresence(&activity);
}
}
+
+class AboutDialog : public wxDialog {
+public:
+ AboutDialog(wxWindow* parent)
+ : wxDialog(parent, wxID_ANY, _("About PlayerLink"), wxDefaultPosition, wxDefaultSize,
+ wxDEFAULT_DIALOG_STYLE & ~wxRESIZE_BORDER) {
+ wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
+ wxStaticText* label = new wxStaticText(this, wxID_ANY, _("Made with <3 by EinTim"));
+ label->Wrap(300);
+ mainSizer->Add(label, 0, wxALL | wxALIGN_CENTER, 10);
+
+ wxStaticText* copyrightText = new wxStaticText(this, wxID_ANY, "© 2024-2025 EinTim. All rights reserved.");
+ copyrightText->Wrap(300);
+ mainSizer->Add(copyrightText, 0, wxALL | wxALIGN_CENTER, 10);
+
+ wxStaticText* creditsText =
+ new wxStaticText(this, wxID_ANY,
+ _("Credits:\n- Developer: EinTim\n- Inspiration: Alexandra Göttlicher\n- Icons from: "
+ "heroicons.com\n- Open source "
+ "projects used in this:\n wxWidgets, libcurl, libdbus, mbedtls, nlohmann-json."));
+ creditsText->Wrap(300);
+ mainSizer->Add(creditsText, 0, wxALL | wxALIGN_CENTER, 10);
+
+ wxHyperlinkCtrl* link = new wxHyperlinkCtrl(this, wxID_ANY, _("Visit my website"), _("https://eintim.dev"));
+ mainSizer->Add(link, 0, wxALL | wxALIGN_CENTER, 10);
+
+ wxButton* okButton = new wxButton(this, wxID_OK, _("OK"));
+ mainSizer->Add(okButton, 0, wxALL | wxALIGN_CENTER, 10);
+
+ this->SetSizerAndFit(mainSizer);
+ this->CentreOnScreen();
+ }
+};
+
class PlayerLinkIcon : public wxTaskBarIcon {
public:
- PlayerLinkIcon(wxFrame* s) : settingsFrame(s) {}
+ PlayerLinkIcon(wxFrame* s) : settingsFrame(s), aboutDlg(nullptr) {}
void OnMenuOpen(wxCommandEvent& evt) {
settingsFrame->Show(true);
@@ -165,13 +200,14 @@ public:
void OnMenuExit(wxCommandEvent& evt) { settingsFrame->Close(true); }
void OnMenuAbout(wxCommandEvent& evt) {
- wxMessageBox(_("Made with <3 by EinTim"), _("PlayerLink"), wxOK | wxICON_INFORMATION);
+ aboutDlg.Show(true);
+ aboutDlg.Raise();
}
protected:
virtual wxMenu* CreatePopupMenu() override {
wxMenu* menu = new wxMenu;
- menu->Append(10004, currentSongTitle == "" ? _("Not Playing") : wxString::FromUTF8(currentSongTitle));
+ menu->Append(10004, currentSongTitle == "" ? _("Not Playing") : currentSongTitle);
menu->Enable(10004, false);
menu->AppendSeparator();
menu->Append(10005, _("Copy Odesli URL"));
@@ -190,6 +226,7 @@ protected:
private:
wxFrame* settingsFrame;
+ AboutDialog aboutDlg;
};
class wxTextCtrlWithPlaceholder : public wxTextCtrl {
@@ -258,31 +295,49 @@ public:
this->SetIcon(icon);
auto mainContainer = new wxBoxSizer(wxVERTICAL);
+ wxPanel* panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
+ panel->SetSizer(mainContainer);
// header start
- auto settingsText = new wxStaticText(this, wxID_ANY, _("Settings"), wxDefaultPosition, wxDefaultSize, 0);
+ auto settingsText = new wxStaticText(panel, wxID_ANY, _("Settings"), wxDefaultPosition, wxDefaultSize, 0);
settingsText->Wrap(-1);
mainContainer->Add(settingsText, 0, wxALIGN_CENTER | wxALL, 5);
// header end
// enabled apps start
- auto settingsDivider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
+ auto settingsDivider = new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
mainContainer->Add(settingsDivider, 0, wxEXPAND | wxALL, 5);
wxBoxSizer* enabledAppsContainer;
enabledAppsContainer = new wxBoxSizer(wxHORIZONTAL);
auto enabledAppsText =
- new wxStaticText(this, wxID_ANY, _("Enabled Apps:"), wxDefaultPosition, wxDefaultSize, 0);
+ new wxStaticText(panel, wxID_ANY, _("Enabled Apps:"), wxDefaultPosition, wxDefaultSize, 0);
enabledAppsText->Wrap(-1);
enabledAppsContainer->Add(enabledAppsText, 0, wxALL, 5);
- wxBoxSizer* appCheckboxContainer;
- appCheckboxContainer = new wxBoxSizer(wxVERTICAL);
+ const auto edit_button_texture = utils::loadSettingsIcon(pencil_svg, pencil_svg_size);
+ const auto delete_button_texture = utils::loadSettingsIcon(trash_svg, trash_svg_size);
+ const auto add_button_texture = utils::loadSettingsIcon(plus_svg, plus_svg_size);
+ wxBoxSizer* appCheckboxContainer = new wxBoxSizer(wxVERTICAL);
+
+ wxBoxSizer* rowSizer = new wxBoxSizer(wxHORIZONTAL);
+
+ wxBitmapButton* addButton = new wxBitmapButton(panel, wxID_ANY, add_button_texture);
+ addButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
+
+ });
+ rowSizer->Add(addButton, 0, wxALL | wxALIGN_CENTER_VERTICAL);
+
+ wxBoxSizer* vSizer = new wxBoxSizer(wxVERTICAL);
+ vSizer->Add(rowSizer, 0, wxALL, 5);
+ enabledAppsContainer->Add(vSizer, 0, wxEXPAND);
auto settings = utils::getSettings();
for (auto app : settings.apps) {
- auto checkbox = new wxCheckBox(this, wxID_ANY, app.appName, wxDefaultPosition, wxDefaultSize, 0);
+ wxBoxSizer* checkboxRowSizer = new wxBoxSizer(wxHORIZONTAL);
+
+ auto checkbox = new wxCheckBox(panel, wxID_ANY, app.appName, wxDefaultPosition, wxDefaultSize, 0);
checkbox->SetValue(app.enabled);
checkbox->SetClientData(new utils::App(app));
checkbox->Bind(wxEVT_CHECKBOX, [checkbox](wxCommandEvent& event) {
@@ -294,10 +349,35 @@ public:
checkbox->Bind(wxEVT_DESTROY, [checkbox](wxWindowDestroyEvent&) {
delete static_cast(checkbox->GetClientData());
});
- appCheckboxContainer->Add(checkbox, 0, wxALL, 5);
+
+ wxBitmapButton* editButton = new wxBitmapButton(panel, wxID_ANY, edit_button_texture);
+ editButton->Bind(wxEVT_BUTTON, [checkbox](wxCommandEvent& event) {
+
+ });
+
+ wxBitmapButton* deleteButton = new wxBitmapButton(panel, wxID_ANY, delete_button_texture);
+ deleteButton->Bind(wxEVT_BUTTON, [this, checkbox, editButton, deleteButton, checkboxRowSizer,
+ appCheckboxContainer](wxCommandEvent& event) {
+ utils::App* appData = static_cast(checkbox->GetClientData());
+ auto settings = utils::getSettings();
+ settings.apps.erase(std::find(settings.apps.begin(), settings.apps.end(), *appData));
+ utils::saveSettings(settings);
+ appCheckboxContainer->Detach(checkboxRowSizer);
+
+ this->CallAfter([this, checkboxRowSizer]() {
+ checkboxRowSizer->Clear(true);
+ this->Layout();
+ });
+ });
+
+ checkboxRowSizer->Add(checkbox, 1, wxALL | wxALIGN_CENTER_VERTICAL);
+ checkboxRowSizer->Add(editButton, 0, wxALL | wxALIGN_CENTER_VERTICAL);
+ checkboxRowSizer->Add(deleteButton, 0, wxALL | wxALIGN_CENTER_VERTICAL);
+
+ appCheckboxContainer->Add(checkboxRowSizer, 0, wxALL, 5);
}
- auto anyOtherCheckbox = new wxCheckBox(this, wxID_ANY, _("Any other"), wxDefaultPosition, wxDefaultSize, 0);
+ auto anyOtherCheckbox = new wxCheckBox(panel, wxID_ANY, _("Any other"), wxDefaultPosition, wxDefaultSize, 0);
anyOtherCheckbox->SetValue(settings.anyOtherEnabled);
anyOtherCheckbox->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) {
bool isChecked = event.IsChecked();
@@ -308,26 +388,26 @@ public:
appCheckboxContainer->Add(anyOtherCheckbox, 0, wxALL, 5);
- enabledAppsContainer->Add(appCheckboxContainer, 1, wxEXPAND, 5);
+ vSizer->Add(appCheckboxContainer, 1, wxEXPAND, 5);
mainContainer->Add(enabledAppsContainer, 0, 0, 5);
// enabled apps end
// LastFM start
- auto lastfmDivider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
+ auto lastfmDivider = new wxStaticLine(panel, 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);
+ auto lastfmText = new wxStaticText(panel, 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);
+ auto lastfmEnabledCheckbox = new wxCheckBox(panel, wxID_ANY, _("Enabled"), wxDefaultPosition, wxDefaultSize, 0);
lastfmEnabledCheckbox->SetValue(settings.lastfm.enabled);
lastfmEnabledCheckbox->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) {
bool isChecked = event.IsChecked();
@@ -339,7 +419,7 @@ public:
lastFMContainer->Add(lastfmSettingsContainer, 1, wxEXPAND, 5);
auto usernameInput =
- new wxTextCtrlWithPlaceholder(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
+ new wxTextCtrlWithPlaceholder(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
usernameInput->SetPlaceholderText(_("Username"));
usernameInput->SetValue(settings.lastfm.username);
usernameInput->Bind(wxEVT_TEXT, [this](wxCommandEvent& event) {
@@ -348,7 +428,7 @@ public:
settings.lastfm.username = data;
utils::saveSettings(settings);
});
- auto passwordInput = new wxTextCtrlWithPlaceholder(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
+ auto passwordInput = new wxTextCtrlWithPlaceholder(panel, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDefaultSize, wxTE_PASSWORD);
passwordInput->SetPlaceholderText(_("Password"));
passwordInput->SetValue(settings.lastfm.password);
@@ -359,7 +439,7 @@ public:
utils::saveSettings(settings);
});
auto apikeyInput =
- new wxTextCtrlWithPlaceholder(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
+ new wxTextCtrlWithPlaceholder(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
apikeyInput->SetPlaceholderText(_("API-Key"));
apikeyInput->SetValue(settings.lastfm.api_key);
apikeyInput->Bind(wxEVT_TEXT, [this](wxCommandEvent& event) {
@@ -368,7 +448,7 @@ public:
settings.lastfm.api_key = data;
utils::saveSettings(settings);
});
- auto apisecretInput = new wxTextCtrlWithPlaceholder(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
+ auto apisecretInput = new wxTextCtrlWithPlaceholder(panel, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDefaultSize, wxTE_PASSWORD);
apisecretInput->SetPlaceholderText(_("API-Secret"));
apisecretInput->SetValue(settings.lastfm.api_secret);
@@ -379,7 +459,7 @@ public:
utils::saveSettings(settings);
});
- auto checkButton = new wxButton(this, wxID_ANY, _("Check credentials"));
+ auto checkButton = new wxButton(panel, wxID_ANY, _("Check credentials"));
checkButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { initLastFM(true); });
mainContainer->Add(lastFMContainer, 0, 0, 5);
@@ -392,7 +472,7 @@ public:
// Last FM End
// settings start
- auto appsDivider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
+ auto appsDivider = new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
mainContainer->Add(appsDivider, 0, wxEXPAND | wxALL, 5);
wxBoxSizer* settingsContainer;
@@ -401,12 +481,12 @@ public:
wxBoxSizer* startupContainer;
startupContainer = new wxBoxSizer(wxHORIZONTAL);
- auto startupText = new wxStaticText(this, wxID_ANY, _("Startup:"), wxDefaultPosition, wxDefaultSize, 0);
+ auto startupText = new wxStaticText(panel, wxID_ANY, _("Startup:"), wxDefaultPosition, wxDefaultSize, 0);
startupText->Wrap(-1);
startupContainer->Add(startupText, 0, wxALL, 5);
auto autostartCheckbox =
- new wxCheckBox(this, wxID_ANY, _("Launch at login"), wxDefaultPosition, wxDefaultSize, 0);
+ new wxCheckBox(panel, wxID_ANY, _("Launch at login"), wxDefaultPosition, wxDefaultSize, 0);
autostartCheckbox->SetValue(settings.autoStart);
autostartCheckbox->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) {
bool isChecked = event.IsChecked();
@@ -417,7 +497,7 @@ public:
});
auto odesliCheckbox =
- new wxCheckBox(this, wxID_ANY, _("Odesli integration"), wxDefaultPosition, wxDefaultSize, 0);
+ new wxCheckBox(panel, wxID_ANY, _("Odesli integration"), wxDefaultPosition, wxDefaultSize, 0);
odesliCheckbox->SetValue(settings.odesli);
odesliCheckbox->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) {
bool isChecked = event.IsChecked();
@@ -430,17 +510,20 @@ public:
settingsContainer->Add(odesliCheckbox, 0, wxALL, 5);
startupContainer->Add(settingsContainer);
mainContainer->Add(startupContainer, 0, wxEXPAND, 5);
-
// settings end
- this->SetSizerAndFit(mainContainer);
+ wxBoxSizer* frameSizer = new wxBoxSizer(wxVERTICAL);
+ frameSizer->Add(panel, 1, wxEXPAND);
+ this->SetSizerAndFit(frameSizer);
wxSize currentSize = this->GetSize();
this->SetSize(size.GetWidth(), currentSize.GetHeight());
this->Layout();
this->Centre(wxBOTH);
+ panel->SetFocus();
}
};
+
class PlayerLink : public wxApp {
public:
virtual bool OnInit() override {
diff --git a/src/utils.hpp b/src/utils.hpp
index defcddc..9c809d7 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -4,7 +4,6 @@
#include
#include
#include
-
#include
#include
#include
@@ -26,6 +25,9 @@ namespace utils {
std::string clientId;
std::string searchEndpoint;
std::vector processNames;
+ bool operator==(const App& other) const {
+ return appName == other.appName && clientId == other.clientId && type == other.type;
+ }
};
struct LastFMSettings {
@@ -57,16 +59,46 @@ namespace utils {
}
}
- inline wxIcon loadIconFromMemory(const unsigned char* data, size_t size) {
+ inline wxBitmap loadImageFromMemory(const unsigned char* data, size_t size, int width = 0, int height = 0) {
wxMemoryInputStream stream(data, size);
wxImage img(stream, wxBITMAP_TYPE_PNG);
if (img.IsOk()) {
+ if (width != 0 || height != 0)
+ img.Rescale(width, height, wxIMAGE_QUALITY_HIGH);
wxBitmap bmp(img);
- wxIcon icon;
- icon.CopyFromBitmap(bmp);
- return icon;
+ return bmp;
}
- 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) {
diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt
index 6470b68..ecca1a5 100644
--- a/vendor/CMakeLists.txt
+++ b/vendor/CMakeLists.txt
@@ -19,6 +19,8 @@ set(wxBUILD_SHARED OFF)
set(wxBUILD_MONOLITHIC ON)
set(wxUSE_GUI ON)
set(wxUSE_WEBVIEW OFF)
+set(wxUSE_UNICODE_UTF8 ON)
+set(wxUSE_UTF8_LOCALE_ONLY ON)
add_subdirectory("wxWidgets")
if(UNIX AND NOT APPLE)
add_subdirectory("dbus")