diff --git a/.gitmodules b/.gitmodules index 9c7cf0c..b1dda15 100644 --- a/.gitmodules +++ b/.gitmodules @@ -41,3 +41,6 @@ [submodule "lib/guardpp"] path = lib/guardpp url = https://github.com/Soundux/guardpp +[submodule "lib/libremidi"] + path = lib/libremidi + url = https://github.com/Soundux/libremidi diff --git a/CMakeLists.txt b/CMakeLists.txt index acfb557..18fce98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ add_subdirectory(lib/nativefiledialog-extended EXCLUDE_FROM_ALL) add_subdirectory(lib/tiny-process-library EXCLUDE_FROM_ALL) add_subdirectory(lib/backward-cpp EXCLUDE_FROM_ALL) add_subdirectory(lib/traypp EXCLUDE_FROM_ALL) +add_subdirectory(lib/libremidi) add_subdirectory(lib/guardpp) add_subdirectory(lib/lockpp) @@ -89,7 +90,7 @@ set(HTTPLIB_REQUIRE_OPENSSL ON) add_subdirectory(lib/cpp-httplib EXCLUDE_FROM_ALL) target_include_directories(soundux SYSTEM PRIVATE "lib/cpp-httplib") -target_link_libraries(soundux PRIVATE webview nfd tiny-process-library tray guard httplib lockpp) +target_link_libraries(soundux PRIVATE webview nfd tiny-process-library tray guard httplib lockpp libremidi) if (${EMBED_PATH} STREQUAL "OFF") message("Web-content will not be embedded") diff --git a/lib/libremidi b/lib/libremidi new file mode 160000 index 0000000..e2213b7 --- /dev/null +++ b/lib/libremidi @@ -0,0 +1 @@ +Subproject commit e2213b724d8426366235f3de70db25fb231a0b07 diff --git a/lib/traypp b/lib/traypp index 179e8d5..8ea6862 160000 --- a/lib/traypp +++ b/lib/traypp @@ -1 +1 @@ -Subproject commit 179e8d5fadc0626085b25d6fd4f7c89c67a773dc +Subproject commit 8ea68626a484de06d48ebcba8db99f8fd393ddd4 diff --git a/src/core/global/globals.hpp b/src/core/global/globals.hpp index f4ae5f7..6978bcc 100644 --- a/src/core/global/globals.hpp +++ b/src/core/global/globals.hpp @@ -33,9 +33,9 @@ namespace Soundux inline Objects::Queue gQueue; inline Objects::Config gConfig; inline Objects::YoutubeDl gYtdl; - inline Objects::Hotkeys gHotKeys; inline Objects::Settings gSettings; inline std::unique_ptr gGui; + inline std::shared_ptr gHotKeys; inline std::shared_ptr gGuard; diff --git a/src/core/hotkeys/hotkeys.cpp b/src/core/hotkeys/hotkeys.cpp index 6a07f70..b135f97 100644 --- a/src/core/hotkeys/hotkeys.cpp +++ b/src/core/hotkeys/hotkeys.cpp @@ -1,6 +1,10 @@ #include "hotkeys.hpp" +#include "keys.hpp" +#include "linux/x11.hpp" +#include "windows/windows.hpp" #include #include +#include namespace Soundux { @@ -19,38 +23,117 @@ namespace Soundux } // namespace traits namespace Objects { - void Hotkeys::init() + std::shared_ptr Hotkeys::createInstance() { - listener = std::thread([this] { listen(); }); + std::shared_ptr rtn; +#if defined(__linux__) + rtn = std::shared_ptr(new X11()); // NOLINT +#elif defined(_WIN32) + rtn = std::shared_ptr(new WindowsHotkeys()); // NOLINT +#endif + rtn->setup(); + return rtn; } - void Hotkeys::shouldNotify(bool status) + + void Hotkeys::setup() + { + try + { + midi.open_port(); + midi.set_callback([this](const libremidi::message &message) { + if (message.size() < 3) + { + Fancy::fancy.logTime().failure() + << "Midi Message contains less than 3 bytes, can't parse information"; + return; + } + + auto byte0 = message[0]; + auto byte1 = message[1]; + auto byte2 = message[2]; + + MidiKey key; + key.byte0 = byte0; + key.key = byte1; + key.byte2 = byte2; + key.type = Enums::KeyType::Midi; + + if (byte0 == 144) + { + onKeyDown(key); + } + else if (byte0 == 128) + { + onKeyUp(key); + } + else if (byte0 == 176) + { + if (shouldNotifyKnob) + { + Globals::gGui->onHotKeyReceived({key}); // NOLINT + } + else + { + auto newVolume = static_cast((static_cast(byte2) / 127.f) * 100); + + if (Globals::gSettings.localVolumeKnob && key == Globals::gSettings.localVolumeKnob) + { + Globals::gSettings.localVolume = newVolume; + Globals::gGui->onVolumeChanged(); + + Globals::gQueue.push([=]() { Globals::gGui->onLocalVolumeChanged(newVolume); }); + } + else if (Globals::gSettings.remoteVolumeKnob && key == Globals::gSettings.remoteVolumeKnob) + { + Globals::gSettings.remoteVolume = newVolume; + Globals::gGui->onVolumeChanged(); + + Globals::gQueue.push([=]() { Globals::gGui->onRemoteVolumeChanged(newVolume); }); + } + } + } + }); + midi.ignore_types(false, false, false); + } + catch (const libremidi::midi_exception &e) + { + Fancy::fancy.logTime().failure() << "Failed to initialize libremidi: " << e.what() << std::endl; + } + } + void Hotkeys::notify(bool state) { pressedKeys.clear(); - notify = status; + shouldNotify = state; } - void Hotkeys::onKeyUp(int key) + void Hotkeys::requestKnob(bool state) { - if (notify && !pressedKeys.empty() && - std::find(pressedKeys.begin(), pressedKeys.end(), key) != pressedKeys.end()) + shouldNotifyKnob = state; + } + void Hotkeys::onKeyUp(const Key &key) + { + if (std::find(pressedKeys.begin(), pressedKeys.end(), key) != pressedKeys.end()) { - Globals::gGui->onHotKeyReceived(pressedKeys); - pressedKeys.clear(); - } - else - { - pressedKeys.erase(std::remove_if(pressedKeys.begin(), pressedKeys.end(), - [key](const auto &item) { return key == item; }), - pressedKeys.end()); + if (shouldNotify) + { + Globals::gGui->onHotKeyReceived(pressedKeys); + pressedKeys.clear(); + } + else + { + pressedKeys.erase(std::remove_if(pressedKeys.begin(), pressedKeys.end(), + [&](const auto &keyItem) { return key == keyItem; }), + pressedKeys.end()); + } } } - bool isCloseMatch(const std::vector &pressedKeys, const std::vector &keys) + bool isCloseMatch(const std::vector &pressedKeys, const std::vector &keys) { if (pressedKeys.size() >= keys.size()) { bool allMatched = true; for (const auto &key : keys) { - if (std::find(pressedKeys.begin(), pressedKeys.end(), key) == pressedKeys.end()) + if (std::find(pressedKeys.begin(), pressedKeys.end(), key) == pressedKeys.end()) // NOLINT { allMatched = false; } @@ -59,13 +142,14 @@ namespace Soundux } return false; } - template std::optional getBestMatch(const T &list, const std::vector &pressedKeys) + template std::optional getBestMatch(const T &list, const std::vector &pressedKeys) { std::optional rtn; for (const auto &_sound : list) { - const auto &sound = [&] { + const auto &sound = [&]() constexpr + { if constexpr (traits::is_pair>::value) { return _sound.second.get(); @@ -74,12 +158,13 @@ namespace Soundux { return _sound; } - }(); + } + (); if (sound.hotkeys.empty()) continue; - if (sound.hotkeys == pressedKeys) + if (pressedKeys == sound.hotkeys) { rtn = sound; break; @@ -97,78 +182,81 @@ namespace Soundux } return rtn; } - void Hotkeys::onKeyDown(int key) + void Hotkeys::onKeyDown(const Key &key) { - if (std::find(keysToPress.begin(), keysToPress.end(), key) != keysToPress.end()) - { - return; - } - if (std::find(pressedKeys.begin(), pressedKeys.end(), key) == pressedKeys.end()) - { - pressedKeys.emplace_back(key); - } - else + if (std::find(pressedKeys.begin(), pressedKeys.end(), key) != pressedKeys.end()) { return; } - if (notify) + pressedKeys.emplace_back(key); + if (!shouldNotify) { - return; - } - - if (!Globals::gSettings.stopHotkey.empty() && (pressedKeys == Globals::gSettings.stopHotkey || - isCloseMatch(pressedKeys, Globals::gSettings.stopHotkey))) - { - Globals::gGui->stopSounds(); - return; - } - - std::optional bestMatch; - - if (Globals::gSettings.tabHotkeysOnly) - { - if (Globals::gData.isOnFavorites) + if (!Globals::gSettings.stopHotkey.empty() && + (Globals::gSettings.stopHotkey == pressedKeys || + isCloseMatch(pressedKeys, Globals::gSettings.stopHotkey))) { - auto sounds = Globals::gData.getFavorites(); - bestMatch = getBestMatch(sounds, pressedKeys); + Globals::gGui->stopSounds(); + return; + } + + std::optional bestMatch; + + if (Globals::gSettings.tabHotkeysOnly) + { + if (Globals::gData.isOnFavorites) + { + auto sounds = Globals::gData.getFavorites(); + bestMatch = getBestMatch(sounds, pressedKeys); + } + else + { + auto tab = Globals::gData.getTab(Globals::gSettings.selectedTab); + if (tab) + { + bestMatch = getBestMatch(tab->sounds, pressedKeys); + } + } } else { - auto tab = Globals::gData.getTab(Globals::gSettings.selectedTab); - if (tab) + auto scopedSounds = Globals::gSounds.scoped(); + bestMatch = getBestMatch(*scopedSounds, pressedKeys); + } + + if (bestMatch) + { + auto pSound = Globals::gGui->playSound(bestMatch->id); + if (pSound) { - bestMatch = getBestMatch(tab->sounds, pressedKeys); + Globals::gGui->onSoundPlayed(*pSound); } } } - else + } + std::string Hotkeys::getKeyName(const Key &key) + { + if (key.type == Enums::KeyType::Midi) { - auto scopedSounds = Globals::gSounds.scoped(); - bestMatch = getBestMatch(*scopedSounds, pressedKeys); + return "MIDI_" + std::to_string(key.key); } - if (bestMatch) - { - auto pSound = Globals::gGui->playSound(bestMatch->id); - if (pSound) - { - Globals::gGui->onSoundPlayed(*pSound); - } - } + return ""; } - std::string Hotkeys::getKeySequence(const std::vector &keys) + std::string Hotkeys::getKeySequence(const std::vector &keys) { std::string rtn; - for (const auto &key : keys) + + for (auto it = keys.begin(); it != keys.end(); ++it) { - rtn += getKeyName(key) + " + "; + rtn += getKeyName(*it); + if (std::distance(it, keys.end()) > 1) + { + rtn += " + "; + } } - if (!rtn.empty()) - { - return rtn.substr(0, rtn.length() - 3); - } - return ""; + + return rtn; } } // namespace Objects } // namespace Soundux \ No newline at end of file diff --git a/src/core/hotkeys/hotkeys.hpp b/src/core/hotkeys/hotkeys.hpp index 6bafa1d..d891898 100644 --- a/src/core/hotkeys/hotkeys.hpp +++ b/src/core/hotkeys/hotkeys.hpp @@ -1,7 +1,15 @@ #pragma once +#pragma push_macro("max") +#pragma push_macro("min") +#undef min +#undef max +#include +#pragma pop_macro("min") +#pragma pop_macro("max") + +#include "keys.hpp" #include #include -#include #include namespace Soundux @@ -10,33 +18,32 @@ namespace Soundux { class Hotkeys { - std::thread listener; - std::atomic kill = false; - std::atomic notify = false; + libremidi::midi_in midi; - std::vector pressedKeys; - std::vector keysToPress; -#if defined(_WIN32) - std::thread keyPressThread; - std::atomic shouldPressKeys = false; -#endif + protected: + Hotkeys() = default; + virtual void setup(); - private: - void listen(); + protected: + std::vector pressedKeys; + std::atomic shouldNotify = false; + std::atomic shouldNotifyKnob = false; public: - void init(); - void stop(); - void shouldNotify(bool); + static std::shared_ptr createInstance(); - void onKeyUp(int); - void onKeyDown(int); + public: + virtual void notify(bool); + virtual void requestKnob(bool); - void pressKeys(const std::vector &); - void releaseKeys(const std::vector &); + virtual void onKeyUp(const Key &); + virtual void onKeyDown(const Key &); - std::string getKeyName(const int &); - std::string getKeySequence(const std::vector &); + virtual void pressKeys(const std::vector &) = 0; + virtual void releaseKeys(const std::vector &) = 0; + + virtual std::string getKeyName(const Key &); + virtual std::string getKeySequence(const std::vector &); }; } // namespace Objects } // namespace Soundux \ No newline at end of file diff --git a/src/core/hotkeys/keys.cpp b/src/core/hotkeys/keys.cpp new file mode 100644 index 0000000..2fddcab --- /dev/null +++ b/src/core/hotkeys/keys.cpp @@ -0,0 +1,9 @@ +#include "keys.hpp" + +namespace Soundux::Objects +{ + bool Key::operator==(const Key &other) const + { + return other.key == key && other.type == type; + } +} // namespace Soundux::Objects \ No newline at end of file diff --git a/src/core/hotkeys/keys.hpp b/src/core/hotkeys/keys.hpp new file mode 100644 index 0000000..edf15ed --- /dev/null +++ b/src/core/hotkeys/keys.hpp @@ -0,0 +1,33 @@ +#pragma once +#include + +namespace Soundux +{ + namespace Enums + { + enum class KeyType : std::uint8_t + { + Keyboard, + Mouse, + Midi + }; + } // namespace Enums + + namespace Objects + { + struct Key + { + int key; + Enums::KeyType type; + + virtual ~Key() = default; + bool operator==(const Key &) const; + }; + struct MidiKey : public Key + { + int byte0; // Action + int byte2; // Knob value / Press strength + ~MidiKey() override = default; + }; + } // namespace Objects +} // namespace Soundux \ No newline at end of file diff --git a/src/core/hotkeys/linux/x11.cpp b/src/core/hotkeys/linux/x11.cpp index 700811f..d0f6071 100644 --- a/src/core/hotkeys/linux/x11.cpp +++ b/src/core/hotkeys/linux/x11.cpp @@ -1,25 +1,26 @@ -#if defined(__linux__) && __has_include() -#include "../hotkeys.hpp" -#include +#if defined(__linux__) +#include "x11.hpp" #include #include -#include #include #include #include -#include +#include #include #include -using namespace std::chrono_literals; - namespace Soundux::Objects { - Display *display; - void Hotkeys::listen() + void X11::setup() { - auto *displayenv = std::getenv("DISPLAY"); // NOLINT - auto *x11Display = XOpenDisplay(displayenv); + Hotkeys::setup(); + listener = std::thread([this] { listen(); }); + } + + void X11::listen() + { + auto *displayEnv = std::getenv("DISPLAY"); // NOLINT + auto *x11Display = XOpenDisplay(displayEnv); if (!x11Display) { @@ -27,20 +28,18 @@ namespace Soundux::Objects if (!(x11Display = XOpenDisplay(":0"))) { Fancy::fancy.logTime().failure() << "Could not open X11 Display" << std::endl; - return; } } else { - Fancy::fancy.logTime().message() << "Using DISPLAY " << displayenv << std::endl; + Fancy::fancy.logTime().message() << "Using DISPLAY " << displayEnv << std::endl; } - display = x11Display; - int major_op = 0, event_rtn = 0, ext_rtn = 0; + display = x11Display; + int event_rtn = 0, ext_rtn = 0; if (!XQueryExtension(display, "XInputExtension", &major_op, &event_rtn, &ext_rtn)) { Fancy::fancy.logTime().failure() << "Failed to find XInputExtension" << std::endl; - return; } Window root = DefaultRootWindow(display); // NOLINT @@ -67,78 +66,106 @@ namespace Soundux::Objects XNextEvent(display, &event); auto *cookie = reinterpret_cast(&event.xcookie); - if (XGetEventData(display, cookie) && cookie->type == GenericEvent && cookie->extension == major_op && - (cookie->evtype == XI_RawKeyPress || cookie->evtype == XI_RawKeyRelease || - cookie->evtype == XI_RawButtonPress || cookie->evtype == XI_RawButtonRelease)) + if (XGetEventData(display, cookie) && cookie->type == GenericEvent && cookie->extension == major_op) { - auto *data = reinterpret_cast(cookie->data); - auto key = data->detail; - - if (key == 1) - continue; - - if (cookie->evtype == XI_RawKeyPress || cookie->evtype == XI_RawButtonPress) + if (cookie->evtype == XI_RawKeyPress || cookie->evtype == XI_RawKeyRelease) { - onKeyDown(key); + auto *data = reinterpret_cast(cookie->data); + auto key = data->detail; + + Key pressedKey; + pressedKey.key = key; + pressedKey.type = Enums::KeyType::Keyboard; + + if (cookie->evtype == XI_RawKeyPress) + { + onKeyDown(pressedKey); + } + else + { + onKeyUp(pressedKey); + } } - else if (cookie->evtype == XI_RawKeyRelease || cookie->evtype == XI_RawButtonRelease) + else if (cookie->evtype == XI_RawButtonPress || cookie->evtype == XI_RawButtonRelease) { - onKeyUp(key); + auto *data = reinterpret_cast(cookie->data); + auto button = data->detail; + + if (button != 1) + { + Key pressedButton; + pressedButton.key = button; + pressedButton.type = Enums::KeyType::Mouse; + + if (cookie->evtype == XI_RawButtonPress) + { + onKeyDown(pressedButton); + } + else + { + onKeyUp(pressedButton); + } + } } } } else { - std::this_thread::sleep_for(100ms); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } } - - std::string Hotkeys::getKeyName(const int &key) + std::string X11::getKeyName(const Key &key) { - // TODO(curve): There is no Keysym for the mouse buttons and I couldn't find any way to get the name for the - // mouse buttons so they'll just be named KEY_1 (1 is the Keycode). Maybe someone will be able to help me but I - // just can't figure it out - - KeySym s = XkbKeycodeToKeysym(display, key, 0, 0); - - if (s == NoSymbol) + if (!Hotkeys::getKeyName(key).empty()) { - return "KEY_" + std::to_string(key); + return Hotkeys::getKeyName(key); } - auto *str = XKeysymToString(s); - if (str == nullptr) + if (key.type == Enums::KeyType::Keyboard) { - return "KEY_" + std::to_string(key); + KeySym keySym = XkbKeycodeToKeysym(display, key.key, 0, 0); + + if (keySym == NoSymbol) + { + return "KEY_" + std::to_string(key.key); + } + + auto *str = XKeysymToString(keySym); + if (!str) + { + return "KEY_" + std::to_string(key.key); + } + + return str; } - return str; + if (key.type == Enums::KeyType::Mouse) + { + return "MOUSE_" + std::to_string(key.key); + } + + return ""; } - void Hotkeys::stop() + void X11::pressKeys(const std::vector &keys) + { + for (const auto &key : keys) + { + XTestFakeKeyEvent(display, key.key, True, 0); + } + } + void X11::releaseKeys(const std::vector &keys) + { + for (const auto &key : keys) + { + XTestFakeKeyEvent(display, key.key, False, 0); + } + } + X11::~X11() { kill = true; listener.join(); } - - void Hotkeys::pressKeys(const std::vector &keys) - { - keysToPress = keys; - for (const auto &key : keys) - { - XTestFakeKeyEvent(display, key, True, 0); - } - } - - void Hotkeys::releaseKeys(const std::vector &keys) - { - keysToPress.clear(); - for (const auto &key : keys) - { - XTestFakeKeyEvent(display, key, False, 0); - } - } } // namespace Soundux::Objects - #endif \ No newline at end of file diff --git a/src/core/hotkeys/linux/x11.hpp b/src/core/hotkeys/linux/x11.hpp new file mode 100644 index 0000000..d663262 --- /dev/null +++ b/src/core/hotkeys/linux/x11.hpp @@ -0,0 +1,33 @@ +#pragma once +#if defined(__linux__) +#include +#include + +struct _XDisplay; // NOLINT +using Display = _XDisplay; + +namespace Soundux +{ + namespace Objects + { + class X11 : public Hotkeys + { + int major_op; + Display *display; + std::thread listener; + std::atomic kill = false; + + private: + void listen(); + void setup() override; + + public: + ~X11(); + std::string getKeyName(const Key &key) override; + void pressKeys(const std::vector &keys) override; + void releaseKeys(const std::vector &keys) override; + }; + } // namespace Objects +} // namespace Soundux + +#endif \ No newline at end of file diff --git a/src/core/hotkeys/windows/windows.cpp b/src/core/hotkeys/windows/windows.cpp index f211c8a..8ee5fad 100644 --- a/src/core/hotkeys/windows/windows.cpp +++ b/src/core/hotkeys/windows/windows.cpp @@ -1,88 +1,90 @@ #if defined(_WIN32) -#include "../hotkeys.hpp" -#include -#include +#include "windows.hpp" #include - -using namespace std::chrono_literals; +#include namespace Soundux::Objects { - HHOOK oKeyBoardProc; - HHOOK oMouseProc; + HHOOK WindowsHotkeys::oMouseProc; + HHOOK WindowsHotkeys::oKeyboardProc; - LRESULT CALLBACK keyBoardProc(int nCode, WPARAM wParam, LPARAM lParam) + void WindowsHotkeys::setup() + { + Hotkeys::setup(); + + oMouseProc = SetWindowsHookEx(WH_MOUSE_LL, mouseProc, nullptr, NULL); + oKeyboardProc = SetWindowsHookEx(WH_KEYBOARD_LL, keyBoardProc, nullptr, NULL); + + listener = std::thread([this] { listen(); }); + keyPresser = std::thread([this] { presser(); }); + } + LRESULT CALLBACK WindowsHotkeys::keyBoardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { + auto *info = reinterpret_cast(lParam); // NOLINT + if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { - auto *info = reinterpret_cast(lParam); - Globals::gHotKeys.onKeyDown(info->vkCode); + Key key; + key.type = Enums::KeyType::Keyboard; + key.key = static_cast(info->vkCode); + + Globals::gHotKeys->onKeyDown(key); } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) { - auto *info = reinterpret_cast(lParam); - Globals::gHotKeys.onKeyUp(info->vkCode); + Key key; + key.type = Enums::KeyType::Keyboard; + key.key = static_cast(info->vkCode); + + Globals::gHotKeys->onKeyUp(key); } } - return CallNextHookEx(oKeyBoardProc, nCode, wParam, lParam); + return CallNextHookEx(oKeyboardProc, nCode, wParam, lParam); } - - LRESULT CALLBACK mouseProc(int nCode, WPARAM wParam, LPARAM lParam) + LRESULT CALLBACK WindowsHotkeys::mouseProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { - // TODO(curve): How would I tell if XButton1 or XButton2 is pressed? Is there a nicer way to do this? - switch (wParam) { - case WM_RBUTTONUP: - Globals::gHotKeys.onKeyUp(VK_RBUTTON); - break; - - case WM_RBUTTONDOWN: - Globals::gHotKeys.onKeyDown(VK_RBUTTON); - break; - - case WM_MBUTTONDOWN: - Globals::gHotKeys.onKeyDown(VK_MBUTTON); - break; - - case WM_MBUTTONUP: - Globals::gHotKeys.onKeyUp(VK_MBUTTON); - break; + case WM_RBUTTONUP: { + Key key; + key.key = VK_RBUTTON; + key.type = Enums::KeyType::Mouse; + Soundux::Globals::gHotKeys->onKeyUp(key); + } + break; + case WM_RBUTTONDOWN: { + Key key; + key.key = VK_RBUTTON; + key.type = Enums::KeyType::Mouse; + Soundux::Globals::gHotKeys->onKeyDown(key); + } + break; + case WM_MBUTTONUP: { + Key key; + key.key = VK_MBUTTON; + key.type = Enums::KeyType::Mouse; + Soundux::Globals::gHotKeys->onKeyUp(key); + } + break; + case WM_MBUTTONDOWN: { + Key key; + key.key = VK_RBUTTON; + key.type = Enums::KeyType::Mouse; + Soundux::Globals::gHotKeys->onKeyDown(key); + } + break; } } return CallNextHookEx(oMouseProc, nCode, wParam, lParam); } - - void Hotkeys::listen() + void WindowsHotkeys::listen() { - oKeyBoardProc = SetWindowsHookEx(WH_KEYBOARD_LL, keyBoardProc, GetModuleHandle(nullptr), NULL); - oMouseProc = SetWindowsHookEx(WH_MOUSE_LL, mouseProc, GetModuleHandle(nullptr), NULL); - keyPressThread = std::thread([this] { - while (!kill) - { - //* Yes, this is absolutely cursed. I tried to implement this by just sending the keydown event once but - //* it does not work like that on windows, so I have to do this, thank you Microsoft, I hate you. - if (shouldPressKeys) - { - for (const auto &key : keysToPress) - { - keybd_event(key, 0, 1, 0); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - } - else - { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - } - }); - MSG message; - while (!GetMessage(&message, nullptr, NULL, NULL)) + while (!GetMessage(&message, nullptr, 0, 0)) { if (kill) { @@ -92,68 +94,82 @@ namespace Soundux::Objects DispatchMessage(&message); } } - - void Hotkeys::stop() + void WindowsHotkeys::presser() { - kill = true; - UnhookWindowsHookEx(oMouseProc); - UnhookWindowsHookEx(oKeyBoardProc); - PostThreadMessage(GetThreadId(listener.native_handle()), WM_QUIT, 0, 0); - listener.join(); - keyPressThread.join(); - } - - std::string Hotkeys::getKeyName(const int &key) - { - auto scanCode = MapVirtualKey(key, MAPVK_VK_TO_VSC); - - CHAR name[128]; - int result = 0; - switch (key) + std::unique_lock lock(keysToPressMutex); + while (!kill) { - case VK_LEFT: - case VK_UP: - case VK_RIGHT: - case VK_DOWN: - case VK_RCONTROL: - case VK_RMENU: - case VK_LWIN: - case VK_RWIN: - case VK_APPS: - case VK_PRIOR: - case VK_NEXT: - case VK_END: - case VK_HOME: - case VK_INSERT: - case VK_DELETE: - case VK_DIVIDE: - case VK_NUMLOCK: - scanCode |= KF_EXTENDED; - default: - result = GetKeyNameTextA(scanCode << 16, name, 128); + cv.wait(lock, [&]() { return !keysToPress.empty() || kill; }); + //* Yes, this is absolutely cursed. I tried to implement this by just sending the keydown event once but + //* it does not work like that on windows, so I have to do this, thank you Microsoft, I hate you. + for (const auto &key : keysToPress) + { + keybd_event(key.key, 0, 1, 0); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } } - if (result == 0) + } + std::string WindowsHotkeys::getKeyName(const Key &key) + { + if (!Hotkeys::getKeyName(key).empty()) { - return "KEY_" + std::to_string(key); + return Hotkeys::getKeyName(key); } - return name; - } + if (key.type == Enums::KeyType::Keyboard) + { + char name[128]; + auto result = GetKeyNameTextA(MapVirtualKey(key.key, MAPVK_VK_TO_VSC) << 16, name, 128); - void Hotkeys::pressKeys(const std::vector &keys) + if (result == 0) + { + return "KEY_" + std::to_string(key.key); + } + + return name; + } + + if (key.type == Enums::KeyType::Mouse) + { + return "MOUSE_" + std::to_string(key.key); + } + + return ""; + } + void WindowsHotkeys::pressKeys(const std::vector &keys) { + std::unique_lock lock(keysToPressMutex); keysToPress = keys; - shouldPressKeys = true; } - - void Hotkeys::releaseKeys([[maybe_unused]] const std::vector &keys) + void WindowsHotkeys::releaseKeys(const std::vector &keys) { - shouldPressKeys = false; - keysToPress.clear(); + std::unique_lock lock(keysToPressMutex); for (const auto &key : keys) { - keybd_event(key, 0, 2, 0); + for (auto it = keysToPress.begin(); it != keysToPress.end();) + { + if (*it == key) + { + it = keysToPress.erase(it); + } + else + { + ++it; + } + } } } + WindowsHotkeys::~WindowsHotkeys() + { + kill = true; + PostThreadMessage(GetThreadId(listener.native_handle()), WM_QUIT, 0, 0); + + listener.join(); + cv.notify_all(); + keyPresser.join(); + + UnhookWindowsHookEx(oMouseProc); + UnhookWindowsHookEx(oKeyboardProc); + } } // namespace Soundux::Objects #endif \ No newline at end of file diff --git a/src/core/hotkeys/windows/windows.hpp b/src/core/hotkeys/windows/windows.hpp new file mode 100644 index 0000000..5b61530 --- /dev/null +++ b/src/core/hotkeys/windows/windows.hpp @@ -0,0 +1,43 @@ +#pragma once +#if defined(_WIN32) +#include +#include +#include +#include + +namespace Soundux +{ + namespace Objects + { + class WindowsHotkeys : public Hotkeys + { + std::thread listener; + std::thread keyPresser; + std::atomic kill = false; + + std::condition_variable cv; + std::mutex keysToPressMutex; + std::vector keysToPress; + + private: + void listen(); + void presser(); + void setup() override; + + private: + static HHOOK oMouseProc; + static HHOOK oKeyboardProc; + + static LRESULT CALLBACK mouseProc(int, WPARAM, LPARAM); + static LRESULT CALLBACK keyBoardProc(int, WPARAM, LPARAM); + + public: + ~WindowsHotkeys(); + std::string getKeyName(const Key &key) override; + void pressKeys(const std::vector &keys) override; + void releaseKeys(const std::vector &keys) override; + }; + } // namespace Objects +} // namespace Soundux + +#endif \ No newline at end of file diff --git a/src/core/objects/objects.hpp b/src/core/objects/objects.hpp index 909ee7a..ef2b5a7 100644 --- a/src/core/objects/objects.hpp +++ b/src/core/objects/objects.hpp @@ -8,7 +8,7 @@ namespace Soundux { namespace Objects { - struct AudioDevice; + struct Key; struct Sound { @@ -17,7 +17,7 @@ namespace Soundux std::string path; bool isFavorite = false; - std::vector hotkeys; + std::vector hotkeys; std::uint64_t modifiedDate; std::optional localVolume; diff --git a/src/core/objects/settings.hpp b/src/core/objects/settings.hpp index 32f1acd..7144bab 100644 --- a/src/core/objects/settings.hpp +++ b/src/core/objects/settings.hpp @@ -1,5 +1,7 @@ #pragma once #include +#include +#include #include #include @@ -13,15 +15,18 @@ namespace Soundux Enums::ViewMode viewMode = Enums::ViewMode::List; Enums::Theme theme = Enums::Theme::System; - std::vector pushToTalkKeys; - std::vector stopHotkey; + std::vector pushToTalkKeys; + std::vector stopHotkey; + + std::optional remoteVolumeKnob; + std::optional localVolumeKnob; std::vector outputs; std::uint32_t selectedTab = 0; + bool syncVolumes = false; int remoteVolume = 100; int localVolume = 50; - bool syncVolumes = false; bool allowMultipleOutputs = false; bool useAsDefaultDevice = false; diff --git a/src/helper/json/bindings.hpp b/src/helper/json/bindings.hpp index 5d2ee25..7e28635 100644 --- a/src/helper/json/bindings.hpp +++ b/src/helper/json/bindings.hpp @@ -1,11 +1,69 @@ #pragma once #include +#include #include #include #include +namespace Soundux +{ + namespace traits + { + template struct is_optional + { + private: + static std::uint8_t test(...); + template static auto test(std::optional *) -> std::uint16_t; + + public: + static const bool value = sizeof(test(reinterpret_cast *>(0))) == sizeof(std::uint16_t); + }; + } // namespace traits +} // namespace Soundux + namespace nlohmann { + template struct adl_serializer> + { + static void to_json(json &j, const std::optional &obj) + { + if (obj) + { + j = *obj; + } + else + { + j = nullptr; + } + } + static void from_json(const json &j, const std::optional &obj) + { + if (!j.is_null()) + { + obj = j.get(); + } + } + }; // namespace nlohmann + template <> struct adl_serializer + { + static void to_json(json &j, const Soundux::Objects::Key &obj) + { + j = {{"key", obj.key}, {"type", obj.type}}; + } + static void from_json(const json &j, Soundux::Objects::Key &obj) + { + if (j.find("type") != j.end()) + { + j.at("key").get_to(obj.key); + j.at("type").get_to(obj.type); + } + else + { + j.get_to(obj.key); + obj.type = Soundux::Enums::KeyType::Keyboard; + } + } + }; // namespace nlohmann template <> struct adl_serializer { static void to_json(json &j, const Soundux::Objects::Sound &obj) @@ -14,29 +72,14 @@ namespace nlohmann {"name", obj.name}, {"hotkeys", obj.hotkeys}, {"hotkeySequence", - Soundux::Globals::gHotKeys.getKeySequence(obj.hotkeys)}, //* For frontend and config readability + Soundux::Globals::gHotKeys->getKeySequence(obj.hotkeys)}, //* For frontend and config readability {"id", obj.id}, {"path", obj.path}, {"isFavorite", obj.isFavorite}, + {"localVolume", obj.localVolume}, + {"remoteVolume", obj.remoteVolume}, {"modifiedDate", obj.modifiedDate}, }; - - if (obj.localVolume) - { - j["localVolume"] = *obj.localVolume; - } - else - { - j["localVolume"] = nullptr; - } - if (obj.remoteVolume) - { - j["remoteVolume"] = *obj.remoteVolume; - } - else - { - j["remoteVolume"] = nullptr; - } } static void from_json(const json &j, Soundux::Objects::Sound &obj) { @@ -119,6 +162,8 @@ namespace nlohmann {"pushToTalkKeys", obj.pushToTalkKeys}, {"tabHotkeysOnly", obj.tabHotkeysOnly}, {"minimizeToTray", obj.minimizeToTray}, + {"localVolumeKnob", obj.localVolumeKnob}, + {"remoteVolumeKnob", obj.remoteVolumeKnob}, {"allowOverlapping", obj.allowOverlapping}, {"muteDuringPlayback", obj.muteDuringPlayback}, {"useAsDefaultDevice", obj.useAsDefaultDevice}, @@ -130,9 +175,22 @@ namespace nlohmann { if (j.find(key) != j.end()) { - if (j.at(key).type_name() == nlohmann::basic_json(T{}).type_name()) + if constexpr (Soundux::traits::is_optional::value) { - j.at(key).get_to(member); + if (j.at(key).type_name() == nlohmann::basic_json(typename T::value_type{}).type_name()) + { + if (!j.at(key).is_null()) + { + member = j.at(key).get(); + } + } + } + else + { + if (j.at(key).type_name() == nlohmann::basic_json(T{}).type_name()) + { + j.at(key).get_to(member); + } } } } @@ -152,6 +210,8 @@ namespace nlohmann get_to_safe(j, "pushToTalkKeys", obj.pushToTalkKeys); get_to_safe(j, "minimizeToTray", obj.minimizeToTray); get_to_safe(j, "tabHotkeysOnly", obj.tabHotkeysOnly); + get_to_safe(j, "localVolumeKnob", obj.localVolumeKnob); + get_to_safe(j, "remoteVolumeKnob", obj.remoteVolumeKnob); get_to_safe(j, "allowOverlapping", obj.allowOverlapping); get_to_safe(j, "useAsDefaultDevice", obj.useAsDefaultDevice); get_to_safe(j, "muteDuringPlayback", obj.muteDuringPlayback); @@ -275,22 +335,5 @@ namespace nlohmann }; } }; - template <> struct adl_serializer> - { - static void to_json(json &j, const std::optional &obj) - { - if (obj) - { - j = { - {"name", obj->getName()}, - {"guid", obj->getGUID()}, - }; - } - else - { - j = "null"; - } - } - }; #endif } // namespace nlohmann \ No newline at end of file diff --git a/src/helper/misc/misc.cpp b/src/helper/misc/misc.cpp index 11ed51e..399bc8e 100644 --- a/src/helper/misc/misc.cpp +++ b/src/helper/misc/misc.cpp @@ -5,7 +5,12 @@ #include #include #include + +#pragma push_macro("UNICOCDE") +#undef UNICODE #include +#pragma pop_macro("UNICOCDE") + #include #include diff --git a/src/helper/queue/queue.cpp b/src/helper/queue/queue.cpp index b56e938..257b482 100644 --- a/src/helper/queue/queue.cpp +++ b/src/helper/queue/queue.cpp @@ -10,29 +10,33 @@ namespace Soundux::Objects cv.wait(lock, [&]() { return !queue.empty() || stop; }); while (!queue.empty()) { - auto front = std::move(*queue.begin()); - - lock.unlock(); - front.second(); - lock.lock(); - - queue.erase(front.first); + auto front = queue.begin(); + front->function(); + queue.erase(front); } } } - void Queue::push_unique(std::uint64_t id, std::function function) { { std::lock_guard lock(queueMutex); - if (queue.find(id) != queue.end()) + if (std::find_if(queue.begin(), queue.end(), + [&id](const auto &entry) { return entry.id && *entry.id == id; }) != queue.end()) { return; } } std::unique_lock lock(queueMutex); - queue.emplace(id, std::move(function)); + queue.emplace_back(Call{std::move(function), id}); + lock.unlock(); + + cv.notify_one(); + } + void Queue::push(std::function function) + { + std::unique_lock lock(queueMutex); + queue.emplace_back(Call{std::move(function), std::nullopt}); lock.unlock(); cv.notify_one(); diff --git a/src/helper/queue/queue.hpp b/src/helper/queue/queue.hpp index 203c475..33ca651 100644 --- a/src/helper/queue/queue.hpp +++ b/src/helper/queue/queue.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -12,8 +13,14 @@ namespace Soundux { class Queue { - std::map> queue; + struct Call + { + std::function function; + std::optional id; + }; + std::mutex queueMutex; + std::vector queue; std::condition_variable cv; std::atomic stop; @@ -26,6 +33,7 @@ namespace Soundux Queue(); ~Queue(); + void push(std::function); void push_unique(std::uint64_t, std::function); }; } // namespace Objects diff --git a/src/helper/ytdl/youtube-dl.hpp b/src/helper/ytdl/youtube-dl.hpp index 12b05c1..836ffd6 100644 --- a/src/helper/ytdl/youtube-dl.hpp +++ b/src/helper/ytdl/youtube-dl.hpp @@ -1,7 +1,12 @@ #pragma once #include #include + +#pragma push_macro("UNICOCDE") +#undef UNICODE #include +#pragma pop_macro("UNICOCDE") + #include #include diff --git a/src/ui/impl/webview/lib/soundux-ui b/src/ui/impl/webview/lib/soundux-ui index 612544b..54f7fce 160000 --- a/src/ui/impl/webview/lib/soundux-ui +++ b/src/ui/impl/webview/lib/soundux-ui @@ -1 +1 @@ -Subproject commit 612544b744ab667b2d78087ebe36328a7ff5b938 +Subproject commit 54f7fceef55071b11a1a4d14dcacc60656cbada3 diff --git a/src/ui/impl/webview/webview.cpp b/src/ui/impl/webview/webview.cpp index 575bcd6..645e803 100644 --- a/src/ui/impl/webview/webview.cpp +++ b/src/ui/impl/webview/webview.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef _WIN32 #include "../../assets/icon.h" @@ -122,12 +123,14 @@ namespace Soundux::Objects webview->expose(Webview::Function("stopSounds", [this]() { stopSounds(); })); webview->expose(Webview::Function("changeSettings", [this](const Settings &newSettings) { return changeSettings(newSettings); })); - webview->expose(Webview::Function("requestHotkey", [](bool state) { Globals::gHotKeys.shouldNotify(state); })); + webview->expose(Webview::Function("requestHotkey", [](bool state) { Globals::gHotKeys->notify(state); })); webview->expose(Webview::Function( - "setHotkey", [this](std::uint32_t id, const std::vector &keys) { return setHotkey(id, keys); })); - webview->expose(Webview::Function("getHotkeySequence", [this](const std::vector &keys) { - return Globals::gHotKeys.getKeySequence(keys); + "setHotkey", [this](std::uint32_t id, const std::vector &keys) { return setHotkey(id, keys); })); + webview->expose(Webview::Function("getHotkeySequence", [this](const std::vector &keys) { + return Globals::gHotKeys->getKeySequence(keys); })); + webview->expose( + Webview::Function("getKeyName", [this](const Key &key) { return Globals::gHotKeys->getKeyName(key); })); webview->expose(Webview::Function("removeTab", [this](std::uint32_t id) { return removeTab(id); })); webview->expose(Webview::Function("refreshTab", [this](std::uint32_t id) { return refreshTab(id); })); webview->expose(Webview::Function( @@ -169,6 +172,8 @@ namespace Soundux::Objects return setCustomRemoteVolume(id, volume); })); webview->expose(Webview::Function("toggleSoundPlayback", [this]() { return toggleSoundPlayback(); })); + webview->expose( + Webview::Function("requestKnob", [this](bool state) { Globals::gHotKeys->requestKnob(state); })); #if !defined(__linux__) webview->expose(Webview::Function("getOutputs", [this]() { return getOutputs(); })); @@ -387,15 +392,10 @@ namespace Soundux::Objects } Fancy::fancy.logTime().message() << "UI exited" << std::endl; } - void WebView::onHotKeyReceived(const std::vector &keys) + void WebView::onHotKeyReceived(const std::vector &keys) { - std::string hotkeySequence; - for (const auto &key : keys) - { - hotkeySequence += Globals::gHotKeys.getKeyName(key) + " + "; - } - webview->callFunction(Webview::JavaScriptFunction( - "window.hotkeyReceived", hotkeySequence.substr(0, hotkeySequence.length() - 3), keys)); + webview->callFunction( + Webview::JavaScriptFunction("window.hotkeyReceived", Globals::gHotKeys->getKeySequence(keys), keys)); } void WebView::onSoundFinished(const PlayingSound &sound) { @@ -448,4 +448,12 @@ namespace Soundux::Objects webview->callFunction( Webview::JavaScriptFunction("window.getStore().commit", "setAdministrativeModal", true)); } + void WebView::onLocalVolumeChanged(int volume) + { + webview->callFunction(Webview::JavaScriptFunction("window.getStore().commit", "setLocalVolume", volume)); + } + void WebView::onRemoteVolumeChanged(int volume) + { + webview->callFunction(Webview::JavaScriptFunction("window.getStore().commit", "setRemoteVolume", volume)); + } } // namespace Soundux::Objects \ No newline at end of file diff --git a/src/ui/impl/webview/webview.hpp b/src/ui/impl/webview/webview.hpp index 95a2f73..6f74f5c 100644 --- a/src/ui/impl/webview/webview.hpp +++ b/src/ui/impl/webview/webview.hpp @@ -28,10 +28,12 @@ namespace Soundux void setup() override; void mainLoop() override; void onSoundFinished(const PlayingSound &sound) override; - void onHotKeyReceived(const std::vector &keys) override; + void onHotKeyReceived(const std::vector &keys) override; void onAdminRequired() override; void onSettingsChanged() override; + void onLocalVolumeChanged(int volume) override; + void onRemoteVolumeChanged(int volume) override; void onSwitchOnConnectDetected(bool state) override; void onError(const Enums::ErrorCode &error) override; void onSoundPlayed(const PlayingSound &sound) override; diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index b6576b6..334659b 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -15,7 +15,8 @@ namespace Soundux::Objects void Window::setup() { NFD::Init(); - Globals::gHotKeys.init(); + Globals::gHotKeys = Hotkeys::createInstance(); + for (auto &tab : Globals::gData.getTabs()) { tab.sounds = getTabContent(tab); @@ -25,7 +26,6 @@ namespace Soundux::Objects Window::~Window() { NFD::Quit(); - Globals::gHotKeys.stop(); } std::vector Window::getTabContent(const Tab &tab) const { @@ -225,7 +225,7 @@ namespace Soundux::Objects } if (!Globals::gSettings.pushToTalkKeys.empty()) { - Globals::gHotKeys.pressKeys(Globals::gSettings.pushToTalkKeys); + Globals::gHotKeys->pressKeys(Globals::gSettings.pushToTalkKeys); } auto playingSound = Globals::gAudio.play(*sound); @@ -297,7 +297,7 @@ namespace Soundux::Objects } if (!Globals::gSettings.pushToTalkKeys.empty()) { - Globals::gHotKeys.pressKeys(Globals::gSettings.pushToTalkKeys); + Globals::gHotKeys->pressKeys(Globals::gSettings.pushToTalkKeys); } if (Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice) @@ -579,30 +579,33 @@ namespace Soundux::Objects onError(Enums::ErrorCode::FailedToSetCustomVolume); return std::nullopt; } + void Window::onVolumeChanged() + { + for (const auto &playingSound : Globals::gAudio.getPlayingSounds()) + { + int newVolume = 0; + const auto &sound = playingSound.sound; + + if (playingSound.playbackDevice.isDefault) + { + newVolume = sound.localVolume ? *sound.localVolume : Globals::gSettings.localVolume; + } + else + { + newVolume = sound.remoteVolume ? *sound.remoteVolume : Globals::gSettings.remoteVolume; + } + + playingSound.raw.device.load()->masterVolumeFactor = static_cast(newVolume) / 100.f; + } + } Settings Window::changeSettings(Settings settings) { auto oldSettings = Globals::gSettings; Globals::gSettings = settings; - if ((settings.localVolume != oldSettings.localVolume || settings.remoteVolume != oldSettings.remoteVolume) && - !Globals::gAudio.getPlayingSounds().empty()) + if ((settings.localVolume != oldSettings.localVolume || settings.remoteVolume != oldSettings.remoteVolume)) { - for (const auto &playingSound : Globals::gAudio.getPlayingSounds()) - { - int newVolume = 0; - const auto &sound = playingSound.sound; - - if (playingSound.playbackDevice.isDefault) - { - newVolume = sound.localVolume ? *sound.localVolume : Globals::gSettings.localVolume; - } - else - { - newVolume = sound.remoteVolume ? *sound.remoteVolume : Globals::gSettings.remoteVolume; - } - - playingSound.raw.device.load()->masterVolumeFactor = static_cast(newVolume) / 100.f; - } + onVolumeChanged(); } #if defined(__linux__) @@ -714,9 +717,9 @@ namespace Soundux::Objects #endif return Globals::gSettings; } - void Window::onHotKeyReceived([[maybe_unused]] const std::vector &keys) + void Window::onHotKeyReceived([[maybe_unused]] const std::vector &keys) { - Globals::gHotKeys.shouldNotify(false); + Globals::gHotKeys->notify(false); } std::optional Window::refreshTab(const std::uint32_t &id) { @@ -753,7 +756,7 @@ namespace Soundux::Objects onError(Enums::ErrorCode::TabDoesNotExist); return std::nullopt; } - std::optional Window::setHotkey(const std::uint32_t &id, const std::vector &hotkeys) + std::optional Window::setHotkey(const std::uint32_t &id, const std::vector &hotkeys) { auto sound = Globals::gData.getSound(id); if (sound) @@ -941,7 +944,7 @@ namespace Soundux::Objects { if (!Globals::gSettings.pushToTalkKeys.empty()) { - Globals::gHotKeys.releaseKeys(Globals::gSettings.pushToTalkKeys); + Globals::gHotKeys->releaseKeys(Globals::gSettings.pushToTalkKeys); } #if defined(__linux__) @@ -979,7 +982,7 @@ namespace Soundux::Objects { if (!Globals::gSettings.pushToTalkKeys.empty()) { - Globals::gHotKeys.pressKeys(Globals::gSettings.pushToTalkKeys); + Globals::gHotKeys->pressKeys(Globals::gSettings.pushToTalkKeys); } } void Window::setIsOnFavorites(bool state) diff --git a/src/ui/ui.hpp b/src/ui/ui.hpp index a917b31..91ba32c 100644 --- a/src/ui/ui.hpp +++ b/src/ui/ui.hpp @@ -77,6 +77,7 @@ namespace Soundux virtual std::optional setSortMode(const std::uint32_t &, Enums::SortMode); protected: + virtual void onVolumeChanged(); virtual bool toggleSoundPlayback(); virtual void stopSounds(bool = false); virtual bool stopSound(const std::uint32_t &); @@ -88,7 +89,7 @@ namespace Soundux virtual std::optional repeatSound(const std::uint32_t &, bool); virtual std::optional seekSound(const std::uint32_t &, std::uint64_t); - virtual std::optional setHotkey(const std::uint32_t &, const std::vector &); + virtual std::optional setHotkey(const std::uint32_t &, const std::vector &); virtual std::optional setCustomLocalVolume(const std::uint32_t &, const std::optional &); virtual std::optional setCustomRemoteVolume(const std::uint32_t &, const std::optional &); @@ -100,11 +101,13 @@ namespace Soundux virtual void onAdminRequired() = 0; virtual void onSettingsChanged() = 0; + virtual void onLocalVolumeChanged(int) = 0; + virtual void onRemoteVolumeChanged(int) = 0; virtual void onSwitchOnConnectDetected(bool) = 0; virtual void onSoundPlayed(const PlayingSound &); virtual void onError(const Enums::ErrorCode &) = 0; virtual void onSoundFinished(const PlayingSound &); - virtual void onHotKeyReceived(const std::vector &); + virtual void onHotKeyReceived(const std::vector &); virtual void onSoundProgressed(const PlayingSound &) = 0; virtual void onDownloadProgressed(float, const std::string &) = 0; };