feat: hotkeys rework and MIDI support (#242)

* feat: add midi support
refactor: hotkeys

* feat: add support for knob

* fix: return hotkey instance

* feat: save local/remote VolumeKnob

* refactor: add default values for Key to avoid trash values

* feat: call setLocal/RemoteVolume

* refactor: make remote/localVolumeKnob optional, add optional serialize for bindings

* feat: expose getKeyName

* fix(x11): misplaced sleep

* fix: don't change volume whjen "shouldNotifyKnob" is true

* feat: smooth out volume changes
fix: volume not changing during playback

* chore(deps): bump webviewpp

* chore(deps): bump webviewpp

* fix(queue): don't unlock mutex for function call

* docs: improve contributing and move to root

* feat(windows): rework hotkeys, enable midi support

* chore(deps): update traypp
Fixes issue where a segmentation fault would occur upon closing the program

* fix(windows): undefined UNICODE when including tiny-process-library

* refactor: make sound deduction function constexpr

* chore(deps): bump frontend

* refactor: reorder includes

Co-authored-by: Curve <fynnbwdt@gmail.com>
This commit is contained in:
Nico 2021-06-13 01:42:36 +02:00 committed by GitHub
parent 03ce562a63
commit ad5fa40304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 707 additions and 360 deletions

3
.gitmodules vendored
View File

@ -41,3 +41,6 @@
[submodule "lib/guardpp"] [submodule "lib/guardpp"]
path = lib/guardpp path = lib/guardpp
url = https://github.com/Soundux/guardpp url = https://github.com/Soundux/guardpp
[submodule "lib/libremidi"]
path = lib/libremidi
url = https://github.com/Soundux/libremidi

View File

@ -74,6 +74,7 @@ add_subdirectory(lib/nativefiledialog-extended EXCLUDE_FROM_ALL)
add_subdirectory(lib/tiny-process-library EXCLUDE_FROM_ALL) add_subdirectory(lib/tiny-process-library EXCLUDE_FROM_ALL)
add_subdirectory(lib/backward-cpp EXCLUDE_FROM_ALL) add_subdirectory(lib/backward-cpp EXCLUDE_FROM_ALL)
add_subdirectory(lib/traypp EXCLUDE_FROM_ALL) add_subdirectory(lib/traypp EXCLUDE_FROM_ALL)
add_subdirectory(lib/libremidi)
add_subdirectory(lib/guardpp) add_subdirectory(lib/guardpp)
add_subdirectory(lib/lockpp) add_subdirectory(lib/lockpp)
@ -89,7 +90,7 @@ set(HTTPLIB_REQUIRE_OPENSSL ON)
add_subdirectory(lib/cpp-httplib EXCLUDE_FROM_ALL) add_subdirectory(lib/cpp-httplib EXCLUDE_FROM_ALL)
target_include_directories(soundux SYSTEM PRIVATE "lib/cpp-httplib") 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") if (${EMBED_PATH} STREQUAL "OFF")
message("Web-content will not be embedded") message("Web-content will not be embedded")

1
lib/libremidi Submodule

@ -0,0 +1 @@
Subproject commit e2213b724d8426366235f3de70db25fb231a0b07

@ -1 +1 @@
Subproject commit 179e8d5fadc0626085b25d6fd4f7c89c67a773dc Subproject commit 8ea68626a484de06d48ebcba8db99f8fd393ddd4

View File

@ -33,9 +33,9 @@ namespace Soundux
inline Objects::Queue gQueue; inline Objects::Queue gQueue;
inline Objects::Config gConfig; inline Objects::Config gConfig;
inline Objects::YoutubeDl gYtdl; inline Objects::YoutubeDl gYtdl;
inline Objects::Hotkeys gHotKeys;
inline Objects::Settings gSettings; inline Objects::Settings gSettings;
inline std::unique_ptr<Objects::Window> gGui; inline std::unique_ptr<Objects::Window> gGui;
inline std::shared_ptr<Objects::Hotkeys> gHotKeys;
inline std::shared_ptr<Instance::Guard> gGuard; inline std::shared_ptr<Instance::Guard> gGuard;

View File

@ -1,6 +1,10 @@
#include "hotkeys.hpp" #include "hotkeys.hpp"
#include "keys.hpp"
#include "linux/x11.hpp"
#include "windows/windows.hpp"
#include <core/global/globals.hpp> #include <core/global/globals.hpp>
#include <cstdint> #include <cstdint>
#include <fancy.hpp>
namespace Soundux namespace Soundux
{ {
@ -19,38 +23,117 @@ namespace Soundux
} // namespace traits } // namespace traits
namespace Objects namespace Objects
{ {
void Hotkeys::init() std::shared_ptr<Hotkeys> Hotkeys::createInstance()
{ {
listener = std::thread([this] { listen(); }); std::shared_ptr<Hotkeys> rtn;
#if defined(__linux__)
rtn = std::shared_ptr<X11>(new X11()); // NOLINT
#elif defined(_WIN32)
rtn = std::shared_ptr<WindowsHotkeys>(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<int>((static_cast<float>(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(); pressedKeys.clear();
notify = status; shouldNotify = state;
} }
void Hotkeys::onKeyUp(int key) void Hotkeys::requestKnob(bool state)
{ {
if (notify && !pressedKeys.empty() && shouldNotifyKnob = state;
std::find(pressedKeys.begin(), pressedKeys.end(), key) != pressedKeys.end()) }
void Hotkeys::onKeyUp(const Key &key)
{
if (std::find(pressedKeys.begin(), pressedKeys.end(), key) != pressedKeys.end())
{ {
Globals::gGui->onHotKeyReceived(pressedKeys); if (shouldNotify)
pressedKeys.clear(); {
} Globals::gGui->onHotKeyReceived(pressedKeys);
else pressedKeys.clear();
{ }
pressedKeys.erase(std::remove_if(pressedKeys.begin(), pressedKeys.end(), else
[key](const auto &item) { return key == item; }), {
pressedKeys.end()); pressedKeys.erase(std::remove_if(pressedKeys.begin(), pressedKeys.end(),
[&](const auto &keyItem) { return key == keyItem; }),
pressedKeys.end());
}
} }
} }
bool isCloseMatch(const std::vector<int> &pressedKeys, const std::vector<int> &keys) bool isCloseMatch(const std::vector<Key> &pressedKeys, const std::vector<Key> &keys)
{ {
if (pressedKeys.size() >= keys.size()) if (pressedKeys.size() >= keys.size())
{ {
bool allMatched = true; bool allMatched = true;
for (const auto &key : keys) 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; allMatched = false;
} }
@ -59,13 +142,14 @@ namespace Soundux
} }
return false; return false;
} }
template <typename T> std::optional<Sound> getBestMatch(const T &list, const std::vector<int> &pressedKeys) template <typename T> std::optional<Sound> getBestMatch(const T &list, const std::vector<Key> &pressedKeys)
{ {
std::optional<Sound> rtn; std::optional<Sound> rtn;
for (const auto &_sound : list) for (const auto &_sound : list)
{ {
const auto &sound = [&] { const auto &sound = [&]() constexpr
{
if constexpr (traits::is_pair<std::decay_t<decltype(_sound)>>::value) if constexpr (traits::is_pair<std::decay_t<decltype(_sound)>>::value)
{ {
return _sound.second.get(); return _sound.second.get();
@ -74,12 +158,13 @@ namespace Soundux
{ {
return _sound; return _sound;
} }
}(); }
();
if (sound.hotkeys.empty()) if (sound.hotkeys.empty())
continue; continue;
if (sound.hotkeys == pressedKeys) if (pressedKeys == sound.hotkeys)
{ {
rtn = sound; rtn = sound;
break; break;
@ -97,78 +182,81 @@ namespace Soundux
} }
return rtn; return rtn;
} }
void Hotkeys::onKeyDown(int key) void Hotkeys::onKeyDown(const Key &key)
{ {
if (std::find(keysToPress.begin(), keysToPress.end(), key) != keysToPress.end()) if (std::find(pressedKeys.begin(), pressedKeys.end(), key) != pressedKeys.end())
{
return;
}
if (std::find(pressedKeys.begin(), pressedKeys.end(), key) == pressedKeys.end())
{
pressedKeys.emplace_back(key);
}
else
{ {
return; return;
} }
if (notify) pressedKeys.emplace_back(key);
if (!shouldNotify)
{ {
return; if (!Globals::gSettings.stopHotkey.empty() &&
} (Globals::gSettings.stopHotkey == pressedKeys ||
isCloseMatch(pressedKeys, Globals::gSettings.stopHotkey)))
if (!Globals::gSettings.stopHotkey.empty() && (pressedKeys == Globals::gSettings.stopHotkey ||
isCloseMatch(pressedKeys, Globals::gSettings.stopHotkey)))
{
Globals::gGui->stopSounds();
return;
}
std::optional<Sound> bestMatch;
if (Globals::gSettings.tabHotkeysOnly)
{
if (Globals::gData.isOnFavorites)
{ {
auto sounds = Globals::gData.getFavorites(); Globals::gGui->stopSounds();
bestMatch = getBestMatch(sounds, pressedKeys); return;
}
std::optional<Sound> 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 else
{ {
auto tab = Globals::gData.getTab(Globals::gSettings.selectedTab); auto scopedSounds = Globals::gSounds.scoped();
if (tab) 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(); return "MIDI_" + std::to_string(key.key);
bestMatch = getBestMatch(*scopedSounds, pressedKeys);
} }
if (bestMatch) return "";
{
auto pSound = Globals::gGui->playSound(bestMatch->id);
if (pSound)
{
Globals::gGui->onSoundPlayed(*pSound);
}
}
} }
std::string Hotkeys::getKeySequence(const std::vector<int> &keys) std::string Hotkeys::getKeySequence(const std::vector<Key> &keys)
{ {
std::string rtn; 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;
return rtn.substr(0, rtn.length() - 3);
}
return "";
} }
} // namespace Objects } // namespace Objects
} // namespace Soundux } // namespace Soundux

View File

@ -1,7 +1,15 @@
#pragma once #pragma once
#pragma push_macro("max")
#pragma push_macro("min")
#undef min
#undef max
#include <libremidi/libremidi.hpp>
#pragma pop_macro("min")
#pragma pop_macro("max")
#include "keys.hpp"
#include <atomic> #include <atomic>
#include <string> #include <string>
#include <thread>
#include <vector> #include <vector>
namespace Soundux namespace Soundux
@ -10,33 +18,32 @@ namespace Soundux
{ {
class Hotkeys class Hotkeys
{ {
std::thread listener; libremidi::midi_in midi;
std::atomic<bool> kill = false;
std::atomic<bool> notify = false;
std::vector<int> pressedKeys; protected:
std::vector<int> keysToPress; Hotkeys() = default;
#if defined(_WIN32) virtual void setup();
std::thread keyPressThread;
std::atomic<bool> shouldPressKeys = false;
#endif
private: protected:
void listen(); std::vector<Key> pressedKeys;
std::atomic<bool> shouldNotify = false;
std::atomic<bool> shouldNotifyKnob = false;
public: public:
void init(); static std::shared_ptr<Hotkeys> createInstance();
void stop();
void shouldNotify(bool);
void onKeyUp(int); public:
void onKeyDown(int); virtual void notify(bool);
virtual void requestKnob(bool);
void pressKeys(const std::vector<int> &); virtual void onKeyUp(const Key &);
void releaseKeys(const std::vector<int> &); virtual void onKeyDown(const Key &);
std::string getKeyName(const int &); virtual void pressKeys(const std::vector<Key> &) = 0;
std::string getKeySequence(const std::vector<int> &); virtual void releaseKeys(const std::vector<Key> &) = 0;
virtual std::string getKeyName(const Key &);
virtual std::string getKeySequence(const std::vector<Key> &);
}; };
} // namespace Objects } // namespace Objects
} // namespace Soundux } // namespace Soundux

View File

@ -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

33
src/core/hotkeys/keys.hpp Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
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

View File

@ -1,25 +1,26 @@
#if defined(__linux__) && __has_include(<X11/Xlib.h>) #if defined(__linux__)
#include "../hotkeys.hpp" #include "x11.hpp"
#include <X11/X.h>
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/extensions/XI2.h>
#include <X11/extensions/XInput2.h> #include <X11/extensions/XInput2.h>
#include <X11/extensions/XTest.h> #include <X11/extensions/XTest.h>
#include <chrono> #include <chrono>
#include <cstdlib> #include <core/hotkeys/keys.hpp>
#include <fancy.hpp> #include <fancy.hpp>
#include <thread> #include <thread>
using namespace std::chrono_literals;
namespace Soundux::Objects namespace Soundux::Objects
{ {
Display *display; void X11::setup()
void Hotkeys::listen()
{ {
auto *displayenv = std::getenv("DISPLAY"); // NOLINT Hotkeys::setup();
auto *x11Display = XOpenDisplay(displayenv); listener = std::thread([this] { listen(); });
}
void X11::listen()
{
auto *displayEnv = std::getenv("DISPLAY"); // NOLINT
auto *x11Display = XOpenDisplay(displayEnv);
if (!x11Display) if (!x11Display)
{ {
@ -27,20 +28,18 @@ namespace Soundux::Objects
if (!(x11Display = XOpenDisplay(":0"))) if (!(x11Display = XOpenDisplay(":0")))
{ {
Fancy::fancy.logTime().failure() << "Could not open X11 Display" << std::endl; Fancy::fancy.logTime().failure() << "Could not open X11 Display" << std::endl;
return;
} }
} }
else 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)) if (!XQueryExtension(display, "XInputExtension", &major_op, &event_rtn, &ext_rtn))
{ {
Fancy::fancy.logTime().failure() << "Failed to find XInputExtension" << std::endl; Fancy::fancy.logTime().failure() << "Failed to find XInputExtension" << std::endl;
return;
} }
Window root = DefaultRootWindow(display); // NOLINT Window root = DefaultRootWindow(display); // NOLINT
@ -67,78 +66,106 @@ namespace Soundux::Objects
XNextEvent(display, &event); XNextEvent(display, &event);
auto *cookie = reinterpret_cast<XGenericEventCookie *>(&event.xcookie); auto *cookie = reinterpret_cast<XGenericEventCookie *>(&event.xcookie);
if (XGetEventData(display, cookie) && cookie->type == GenericEvent && cookie->extension == major_op && 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))
{ {
auto *data = reinterpret_cast<XIRawEvent *>(cookie->data); if (cookie->evtype == XI_RawKeyPress || cookie->evtype == XI_RawKeyRelease)
auto key = data->detail;
if (key == 1)
continue;
if (cookie->evtype == XI_RawKeyPress || cookie->evtype == XI_RawButtonPress)
{ {
onKeyDown(key); auto *data = reinterpret_cast<XIRawEvent *>(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<XIRawEvent *>(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 else
{ {
std::this_thread::sleep_for(100ms); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} }
} }
} }
std::string X11::getKeyName(const Key &key)
std::string Hotkeys::getKeyName(const int &key)
{ {
// TODO(curve): There is no Keysym for the mouse buttons and I couldn't find any way to get the name for the if (!Hotkeys::getKeyName(key).empty())
// 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)
{ {
return "KEY_" + std::to_string(key); return Hotkeys::getKeyName(key);
} }
auto *str = XKeysymToString(s); if (key.type == Enums::KeyType::Keyboard)
if (str == nullptr)
{ {
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<Key> &keys)
{
for (const auto &key : keys)
{
XTestFakeKeyEvent(display, key.key, True, 0);
}
}
void X11::releaseKeys(const std::vector<Key> &keys)
{
for (const auto &key : keys)
{
XTestFakeKeyEvent(display, key.key, False, 0);
}
}
X11::~X11()
{ {
kill = true; kill = true;
listener.join(); listener.join();
} }
void Hotkeys::pressKeys(const std::vector<int> &keys)
{
keysToPress = keys;
for (const auto &key : keys)
{
XTestFakeKeyEvent(display, key, True, 0);
}
}
void Hotkeys::releaseKeys(const std::vector<int> &keys)
{
keysToPress.clear();
for (const auto &key : keys)
{
XTestFakeKeyEvent(display, key, False, 0);
}
}
} // namespace Soundux::Objects } // namespace Soundux::Objects
#endif #endif

View File

@ -0,0 +1,33 @@
#pragma once
#if defined(__linux__)
#include <core/hotkeys/hotkeys.hpp>
#include <thread>
struct _XDisplay; // NOLINT
using Display = _XDisplay;
namespace Soundux
{
namespace Objects
{
class X11 : public Hotkeys
{
int major_op;
Display *display;
std::thread listener;
std::atomic<bool> kill = false;
private:
void listen();
void setup() override;
public:
~X11();
std::string getKeyName(const Key &key) override;
void pressKeys(const std::vector<Key> &keys) override;
void releaseKeys(const std::vector<Key> &keys) override;
};
} // namespace Objects
} // namespace Soundux
#endif

View File

@ -1,88 +1,90 @@
#if defined(_WIN32) #if defined(_WIN32)
#include "../hotkeys.hpp" #include "windows.hpp"
#include <Windows.h>
#include <chrono>
#include <core/global/globals.hpp> #include <core/global/globals.hpp>
#include <winuser.h>
using namespace std::chrono_literals;
namespace Soundux::Objects namespace Soundux::Objects
{ {
HHOOK oKeyBoardProc; HHOOK WindowsHotkeys::oMouseProc;
HHOOK 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) if (nCode == HC_ACTION)
{ {
auto *info = reinterpret_cast<PKBDLLHOOKSTRUCT>(lParam); // NOLINT
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{ {
auto *info = reinterpret_cast<PKBDLLHOOKSTRUCT>(lParam); Key key;
Globals::gHotKeys.onKeyDown(info->vkCode); key.type = Enums::KeyType::Keyboard;
key.key = static_cast<int>(info->vkCode);
Globals::gHotKeys->onKeyDown(key);
} }
else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)
{ {
auto *info = reinterpret_cast<PKBDLLHOOKSTRUCT>(lParam); Key key;
Globals::gHotKeys.onKeyUp(info->vkCode); key.type = Enums::KeyType::Keyboard;
key.key = static_cast<int>(info->vkCode);
Globals::gHotKeys->onKeyUp(key);
} }
} }
return CallNextHookEx(oKeyBoardProc, nCode, wParam, lParam); return CallNextHookEx(oKeyboardProc, nCode, wParam, lParam);
} }
LRESULT CALLBACK WindowsHotkeys::mouseProc(int nCode, WPARAM wParam, LPARAM lParam)
LRESULT CALLBACK mouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{ {
if (nCode == HC_ACTION) 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) switch (wParam)
{ {
case WM_RBUTTONUP: case WM_RBUTTONUP: {
Globals::gHotKeys.onKeyUp(VK_RBUTTON); Key key;
break; key.key = VK_RBUTTON;
key.type = Enums::KeyType::Mouse;
case WM_RBUTTONDOWN: Soundux::Globals::gHotKeys->onKeyUp(key);
Globals::gHotKeys.onKeyDown(VK_RBUTTON); }
break; break;
case WM_RBUTTONDOWN: {
case WM_MBUTTONDOWN: Key key;
Globals::gHotKeys.onKeyDown(VK_MBUTTON); key.key = VK_RBUTTON;
break; key.type = Enums::KeyType::Mouse;
Soundux::Globals::gHotKeys->onKeyDown(key);
case WM_MBUTTONUP: }
Globals::gHotKeys.onKeyUp(VK_MBUTTON); break;
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); return CallNextHookEx(oMouseProc, nCode, wParam, lParam);
} }
void WindowsHotkeys::listen()
void Hotkeys::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; MSG message;
while (!GetMessage(&message, nullptr, NULL, NULL)) while (!GetMessage(&message, nullptr, 0, 0))
{ {
if (kill) if (kill)
{ {
@ -92,68 +94,82 @@ namespace Soundux::Objects
DispatchMessage(&message); DispatchMessage(&message);
} }
} }
void WindowsHotkeys::presser()
void Hotkeys::stop()
{ {
kill = true; std::unique_lock lock(keysToPressMutex);
UnhookWindowsHookEx(oMouseProc); while (!kill)
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)
{ {
case VK_LEFT: cv.wait(lock, [&]() { return !keysToPress.empty() || kill; });
case VK_UP: //* Yes, this is absolutely cursed. I tried to implement this by just sending the keydown event once but
case VK_RIGHT: //* it does not work like that on windows, so I have to do this, thank you Microsoft, I hate you.
case VK_DOWN: for (const auto &key : keysToPress)
case VK_RCONTROL: {
case VK_RMENU: keybd_event(key.key, 0, 1, 0);
case VK_LWIN: std::this_thread::sleep_for(std::chrono::milliseconds(10));
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);
} }
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<int> &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<Key> &keys)
{ {
std::unique_lock lock(keysToPressMutex);
keysToPress = keys; keysToPress = keys;
shouldPressKeys = true;
} }
void WindowsHotkeys::releaseKeys(const std::vector<Key> &keys)
void Hotkeys::releaseKeys([[maybe_unused]] const std::vector<int> &keys)
{ {
shouldPressKeys = false; std::unique_lock lock(keysToPressMutex);
keysToPress.clear();
for (const auto &key : keys) 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 } // namespace Soundux::Objects
#endif #endif

View File

@ -0,0 +1,43 @@
#pragma once
#if defined(_WIN32)
#include <core/hotkeys/hotkeys.hpp>
#include <mutex>
#include <thread>
#include <windows.h>
namespace Soundux
{
namespace Objects
{
class WindowsHotkeys : public Hotkeys
{
std::thread listener;
std::thread keyPresser;
std::atomic<bool> kill = false;
std::condition_variable cv;
std::mutex keysToPressMutex;
std::vector<Key> 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<Key> &keys) override;
void releaseKeys(const std::vector<Key> &keys) override;
};
} // namespace Objects
} // namespace Soundux
#endif

View File

@ -8,7 +8,7 @@ namespace Soundux
{ {
namespace Objects namespace Objects
{ {
struct AudioDevice; struct Key;
struct Sound struct Sound
{ {
@ -17,7 +17,7 @@ namespace Soundux
std::string path; std::string path;
bool isFavorite = false; bool isFavorite = false;
std::vector<int> hotkeys; std::vector<Key> hotkeys;
std::uint64_t modifiedDate; std::uint64_t modifiedDate;
std::optional<int> localVolume; std::optional<int> localVolume;

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <core/enums/enums.hpp> #include <core/enums/enums.hpp>
#include <core/hotkeys/keys.hpp>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -13,15 +15,18 @@ namespace Soundux
Enums::ViewMode viewMode = Enums::ViewMode::List; Enums::ViewMode viewMode = Enums::ViewMode::List;
Enums::Theme theme = Enums::Theme::System; Enums::Theme theme = Enums::Theme::System;
std::vector<int> pushToTalkKeys; std::vector<Key> pushToTalkKeys;
std::vector<int> stopHotkey; std::vector<Key> stopHotkey;
std::optional<Key> remoteVolumeKnob;
std::optional<Key> localVolumeKnob;
std::vector<std::string> outputs; std::vector<std::string> outputs;
std::uint32_t selectedTab = 0; std::uint32_t selectedTab = 0;
bool syncVolumes = false;
int remoteVolume = 100; int remoteVolume = 100;
int localVolume = 50; int localVolume = 50;
bool syncVolumes = false;
bool allowMultipleOutputs = false; bool allowMultipleOutputs = false;
bool useAsDefaultDevice = false; bool useAsDefaultDevice = false;

View File

@ -1,11 +1,69 @@
#pragma once #pragma once
#include <core/global/globals.hpp> #include <core/global/globals.hpp>
#include <core/hotkeys/keys.hpp>
#include <helper/audio/windows/winsound.hpp> #include <helper/audio/windows/winsound.hpp>
#include <helper/version/check.hpp> #include <helper/version/check.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
namespace Soundux
{
namespace traits
{
template <typename T> struct is_optional
{
private:
static std::uint8_t test(...);
template <typename O> static auto test(std::optional<O> *) -> std::uint16_t;
public:
static const bool value = sizeof(test(reinterpret_cast<std::decay_t<T> *>(0))) == sizeof(std::uint16_t);
};
} // namespace traits
} // namespace Soundux
namespace nlohmann namespace nlohmann
{ {
template <typename T> struct adl_serializer<std::optional<T>>
{
static void to_json(json &j, const std::optional<T> &obj)
{
if (obj)
{
j = *obj;
}
else
{
j = nullptr;
}
}
static void from_json(const json &j, const std::optional<T> &obj)
{
if (!j.is_null())
{
obj = j.get<T>();
}
}
}; // namespace nlohmann
template <> struct adl_serializer<Soundux::Objects::Key>
{
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<Soundux::Objects::Sound> template <> struct adl_serializer<Soundux::Objects::Sound>
{ {
static void to_json(json &j, const Soundux::Objects::Sound &obj) static void to_json(json &j, const Soundux::Objects::Sound &obj)
@ -14,29 +72,14 @@ namespace nlohmann
{"name", obj.name}, {"name", obj.name},
{"hotkeys", obj.hotkeys}, {"hotkeys", obj.hotkeys},
{"hotkeySequence", {"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}, {"id", obj.id},
{"path", obj.path}, {"path", obj.path},
{"isFavorite", obj.isFavorite}, {"isFavorite", obj.isFavorite},
{"localVolume", obj.localVolume},
{"remoteVolume", obj.remoteVolume},
{"modifiedDate", obj.modifiedDate}, {"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) static void from_json(const json &j, Soundux::Objects::Sound &obj)
{ {
@ -119,6 +162,8 @@ namespace nlohmann
{"pushToTalkKeys", obj.pushToTalkKeys}, {"pushToTalkKeys", obj.pushToTalkKeys},
{"tabHotkeysOnly", obj.tabHotkeysOnly}, {"tabHotkeysOnly", obj.tabHotkeysOnly},
{"minimizeToTray", obj.minimizeToTray}, {"minimizeToTray", obj.minimizeToTray},
{"localVolumeKnob", obj.localVolumeKnob},
{"remoteVolumeKnob", obj.remoteVolumeKnob},
{"allowOverlapping", obj.allowOverlapping}, {"allowOverlapping", obj.allowOverlapping},
{"muteDuringPlayback", obj.muteDuringPlayback}, {"muteDuringPlayback", obj.muteDuringPlayback},
{"useAsDefaultDevice", obj.useAsDefaultDevice}, {"useAsDefaultDevice", obj.useAsDefaultDevice},
@ -130,9 +175,22 @@ namespace nlohmann
{ {
if (j.find(key) != j.end()) if (j.find(key) != j.end())
{ {
if (j.at(key).type_name() == nlohmann::basic_json(T{}).type_name()) if constexpr (Soundux::traits::is_optional<T>::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<typename T::value_type>();
}
}
}
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, "pushToTalkKeys", obj.pushToTalkKeys);
get_to_safe(j, "minimizeToTray", obj.minimizeToTray); get_to_safe(j, "minimizeToTray", obj.minimizeToTray);
get_to_safe(j, "tabHotkeysOnly", obj.tabHotkeysOnly); 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, "allowOverlapping", obj.allowOverlapping);
get_to_safe(j, "useAsDefaultDevice", obj.useAsDefaultDevice); get_to_safe(j, "useAsDefaultDevice", obj.useAsDefaultDevice);
get_to_safe(j, "muteDuringPlayback", obj.muteDuringPlayback); get_to_safe(j, "muteDuringPlayback", obj.muteDuringPlayback);
@ -275,22 +335,5 @@ namespace nlohmann
}; };
} }
}; };
template <> struct adl_serializer<std::optional<Soundux::Objects::RecordingDevice>>
{
static void to_json(json &j, const std::optional<Soundux::Objects::RecordingDevice> &obj)
{
if (obj)
{
j = {
{"name", obj->getName()},
{"guid", obj->getGUID()},
};
}
else
{
j = "null";
}
}
};
#endif #endif
} // namespace nlohmann } // namespace nlohmann

View File

@ -5,7 +5,12 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <optional> #include <optional>
#pragma push_macro("UNICOCDE")
#undef UNICODE
#include <process.hpp> #include <process.hpp>
#pragma pop_macro("UNICOCDE")
#include <regex> #include <regex>
#include <system_error> #include <system_error>

View File

@ -10,29 +10,33 @@ namespace Soundux::Objects
cv.wait(lock, [&]() { return !queue.empty() || stop; }); cv.wait(lock, [&]() { return !queue.empty() || stop; });
while (!queue.empty()) while (!queue.empty())
{ {
auto front = std::move(*queue.begin()); auto front = queue.begin();
front->function();
lock.unlock(); queue.erase(front);
front.second();
lock.lock();
queue.erase(front.first);
} }
} }
} }
void Queue::push_unique(std::uint64_t id, std::function<void()> function) void Queue::push_unique(std::uint64_t id, std::function<void()> function)
{ {
{ {
std::lock_guard lock(queueMutex); 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; return;
} }
} }
std::unique_lock lock(queueMutex); 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<void()> function)
{
std::unique_lock lock(queueMutex);
queue.emplace_back(Call{std::move(function), std::nullopt});
lock.unlock(); lock.unlock();
cv.notify_one(); cv.notify_one();

View File

@ -3,6 +3,7 @@
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
#include <map> #include <map>
#include <optional>
#include <queue> #include <queue>
#include <thread> #include <thread>
@ -12,8 +13,14 @@ namespace Soundux
{ {
class Queue class Queue
{ {
std::map<std::uint64_t, std::function<void()>> queue; struct Call
{
std::function<void()> function;
std::optional<std::uint64_t> id;
};
std::mutex queueMutex; std::mutex queueMutex;
std::vector<Call> queue;
std::condition_variable cv; std::condition_variable cv;
std::atomic<bool> stop; std::atomic<bool> stop;
@ -26,6 +33,7 @@ namespace Soundux
Queue(); Queue();
~Queue(); ~Queue();
void push(std::function<void()>);
void push_unique(std::uint64_t, std::function<void()>); void push_unique(std::uint64_t, std::function<void()>);
}; };
} // namespace Objects } // namespace Objects

View File

@ -1,7 +1,12 @@
#pragma once #pragma once
#include <json.hpp> #include <json.hpp>
#include <optional> #include <optional>
#pragma push_macro("UNICOCDE")
#undef UNICODE
#include <process.hpp> #include <process.hpp>
#pragma pop_macro("UNICOCDE")
#include <regex> #include <regex>
#include <string> #include <string>

@ -1 +1 @@
Subproject commit 612544b744ab667b2d78087ebe36328a7ff5b938 Subproject commit 54f7fceef55071b11a1a4d14dcacc60656cbada3

View File

@ -9,6 +9,7 @@
#include <helper/systeminfo/systeminfo.hpp> #include <helper/systeminfo/systeminfo.hpp>
#include <helper/version/check.hpp> #include <helper/version/check.hpp>
#include <helper/ytdl/youtube-dl.hpp> #include <helper/ytdl/youtube-dl.hpp>
#include <javascript/function.hpp>
#ifdef _WIN32 #ifdef _WIN32
#include "../../assets/icon.h" #include "../../assets/icon.h"
@ -122,12 +123,14 @@ namespace Soundux::Objects
webview->expose(Webview::Function("stopSounds", [this]() { stopSounds(); })); webview->expose(Webview::Function("stopSounds", [this]() { stopSounds(); }));
webview->expose(Webview::Function("changeSettings", webview->expose(Webview::Function("changeSettings",
[this](const Settings &newSettings) { return changeSettings(newSettings); })); [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( webview->expose(Webview::Function(
"setHotkey", [this](std::uint32_t id, const std::vector<int> &keys) { return setHotkey(id, keys); })); "setHotkey", [this](std::uint32_t id, const std::vector<Key> &keys) { return setHotkey(id, keys); }));
webview->expose(Webview::Function("getHotkeySequence", [this](const std::vector<int> &keys) { webview->expose(Webview::Function("getHotkeySequence", [this](const std::vector<Key> &keys) {
return Globals::gHotKeys.getKeySequence(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("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("refreshTab", [this](std::uint32_t id) { return refreshTab(id); }));
webview->expose(Webview::Function( webview->expose(Webview::Function(
@ -169,6 +172,8 @@ namespace Soundux::Objects
return setCustomRemoteVolume(id, volume); return setCustomRemoteVolume(id, volume);
})); }));
webview->expose(Webview::Function("toggleSoundPlayback", [this]() { return toggleSoundPlayback(); })); webview->expose(Webview::Function("toggleSoundPlayback", [this]() { return toggleSoundPlayback(); }));
webview->expose(
Webview::Function("requestKnob", [this](bool state) { Globals::gHotKeys->requestKnob(state); }));
#if !defined(__linux__) #if !defined(__linux__)
webview->expose(Webview::Function("getOutputs", [this]() { return getOutputs(); })); webview->expose(Webview::Function("getOutputs", [this]() { return getOutputs(); }));
@ -387,15 +392,10 @@ namespace Soundux::Objects
} }
Fancy::fancy.logTime().message() << "UI exited" << std::endl; Fancy::fancy.logTime().message() << "UI exited" << std::endl;
} }
void WebView::onHotKeyReceived(const std::vector<int> &keys) void WebView::onHotKeyReceived(const std::vector<Key> &keys)
{ {
std::string hotkeySequence; webview->callFunction<void>(
for (const auto &key : keys) Webview::JavaScriptFunction("window.hotkeyReceived", Globals::gHotKeys->getKeySequence(keys), keys));
{
hotkeySequence += Globals::gHotKeys.getKeyName(key) + " + ";
}
webview->callFunction<void>(Webview::JavaScriptFunction(
"window.hotkeyReceived", hotkeySequence.substr(0, hotkeySequence.length() - 3), keys));
} }
void WebView::onSoundFinished(const PlayingSound &sound) void WebView::onSoundFinished(const PlayingSound &sound)
{ {
@ -448,4 +448,12 @@ namespace Soundux::Objects
webview->callFunction<void>( webview->callFunction<void>(
Webview::JavaScriptFunction("window.getStore().commit", "setAdministrativeModal", true)); Webview::JavaScriptFunction("window.getStore().commit", "setAdministrativeModal", true));
} }
void WebView::onLocalVolumeChanged(int volume)
{
webview->callFunction<void>(Webview::JavaScriptFunction("window.getStore().commit", "setLocalVolume", volume));
}
void WebView::onRemoteVolumeChanged(int volume)
{
webview->callFunction<void>(Webview::JavaScriptFunction("window.getStore().commit", "setRemoteVolume", volume));
}
} // namespace Soundux::Objects } // namespace Soundux::Objects

View File

@ -28,10 +28,12 @@ namespace Soundux
void setup() override; void setup() override;
void mainLoop() override; void mainLoop() override;
void onSoundFinished(const PlayingSound &sound) override; void onSoundFinished(const PlayingSound &sound) override;
void onHotKeyReceived(const std::vector<int> &keys) override; void onHotKeyReceived(const std::vector<Key> &keys) override;
void onAdminRequired() override; void onAdminRequired() override;
void onSettingsChanged() override; void onSettingsChanged() override;
void onLocalVolumeChanged(int volume) override;
void onRemoteVolumeChanged(int volume) override;
void onSwitchOnConnectDetected(bool state) override; void onSwitchOnConnectDetected(bool state) override;
void onError(const Enums::ErrorCode &error) override; void onError(const Enums::ErrorCode &error) override;
void onSoundPlayed(const PlayingSound &sound) override; void onSoundPlayed(const PlayingSound &sound) override;

View File

@ -15,7 +15,8 @@ namespace Soundux::Objects
void Window::setup() void Window::setup()
{ {
NFD::Init(); NFD::Init();
Globals::gHotKeys.init(); Globals::gHotKeys = Hotkeys::createInstance();
for (auto &tab : Globals::gData.getTabs()) for (auto &tab : Globals::gData.getTabs())
{ {
tab.sounds = getTabContent(tab); tab.sounds = getTabContent(tab);
@ -25,7 +26,6 @@ namespace Soundux::Objects
Window::~Window() Window::~Window()
{ {
NFD::Quit(); NFD::Quit();
Globals::gHotKeys.stop();
} }
std::vector<Sound> Window::getTabContent(const Tab &tab) const std::vector<Sound> Window::getTabContent(const Tab &tab) const
{ {
@ -225,7 +225,7 @@ namespace Soundux::Objects
} }
if (!Globals::gSettings.pushToTalkKeys.empty()) if (!Globals::gSettings.pushToTalkKeys.empty())
{ {
Globals::gHotKeys.pressKeys(Globals::gSettings.pushToTalkKeys); Globals::gHotKeys->pressKeys(Globals::gSettings.pushToTalkKeys);
} }
auto playingSound = Globals::gAudio.play(*sound); auto playingSound = Globals::gAudio.play(*sound);
@ -297,7 +297,7 @@ namespace Soundux::Objects
} }
if (!Globals::gSettings.pushToTalkKeys.empty()) 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) if (Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
@ -579,30 +579,33 @@ namespace Soundux::Objects
onError(Enums::ErrorCode::FailedToSetCustomVolume); onError(Enums::ErrorCode::FailedToSetCustomVolume);
return std::nullopt; 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<float>(newVolume) / 100.f;
}
}
Settings Window::changeSettings(Settings settings) Settings Window::changeSettings(Settings settings)
{ {
auto oldSettings = Globals::gSettings; auto oldSettings = Globals::gSettings;
Globals::gSettings = settings; Globals::gSettings = settings;
if ((settings.localVolume != oldSettings.localVolume || settings.remoteVolume != oldSettings.remoteVolume) && if ((settings.localVolume != oldSettings.localVolume || settings.remoteVolume != oldSettings.remoteVolume))
!Globals::gAudio.getPlayingSounds().empty())
{ {
for (const auto &playingSound : Globals::gAudio.getPlayingSounds()) onVolumeChanged();
{
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<float>(newVolume) / 100.f;
}
} }
#if defined(__linux__) #if defined(__linux__)
@ -714,9 +717,9 @@ namespace Soundux::Objects
#endif #endif
return Globals::gSettings; return Globals::gSettings;
} }
void Window::onHotKeyReceived([[maybe_unused]] const std::vector<int> &keys) void Window::onHotKeyReceived([[maybe_unused]] const std::vector<Key> &keys)
{ {
Globals::gHotKeys.shouldNotify(false); Globals::gHotKeys->notify(false);
} }
std::optional<Tab> Window::refreshTab(const std::uint32_t &id) std::optional<Tab> Window::refreshTab(const std::uint32_t &id)
{ {
@ -753,7 +756,7 @@ namespace Soundux::Objects
onError(Enums::ErrorCode::TabDoesNotExist); onError(Enums::ErrorCode::TabDoesNotExist);
return std::nullopt; return std::nullopt;
} }
std::optional<Sound> Window::setHotkey(const std::uint32_t &id, const std::vector<int> &hotkeys) std::optional<Sound> Window::setHotkey(const std::uint32_t &id, const std::vector<Key> &hotkeys)
{ {
auto sound = Globals::gData.getSound(id); auto sound = Globals::gData.getSound(id);
if (sound) if (sound)
@ -941,7 +944,7 @@ namespace Soundux::Objects
{ {
if (!Globals::gSettings.pushToTalkKeys.empty()) if (!Globals::gSettings.pushToTalkKeys.empty())
{ {
Globals::gHotKeys.releaseKeys(Globals::gSettings.pushToTalkKeys); Globals::gHotKeys->releaseKeys(Globals::gSettings.pushToTalkKeys);
} }
#if defined(__linux__) #if defined(__linux__)
@ -979,7 +982,7 @@ namespace Soundux::Objects
{ {
if (!Globals::gSettings.pushToTalkKeys.empty()) if (!Globals::gSettings.pushToTalkKeys.empty())
{ {
Globals::gHotKeys.pressKeys(Globals::gSettings.pushToTalkKeys); Globals::gHotKeys->pressKeys(Globals::gSettings.pushToTalkKeys);
} }
} }
void Window::setIsOnFavorites(bool state) void Window::setIsOnFavorites(bool state)

View File

@ -77,6 +77,7 @@ namespace Soundux
virtual std::optional<Tab> setSortMode(const std::uint32_t &, Enums::SortMode); virtual std::optional<Tab> setSortMode(const std::uint32_t &, Enums::SortMode);
protected: protected:
virtual void onVolumeChanged();
virtual bool toggleSoundPlayback(); virtual bool toggleSoundPlayback();
virtual void stopSounds(bool = false); virtual void stopSounds(bool = false);
virtual bool stopSound(const std::uint32_t &); virtual bool stopSound(const std::uint32_t &);
@ -88,7 +89,7 @@ namespace Soundux
virtual std::optional<PlayingSound> repeatSound(const std::uint32_t &, bool); virtual std::optional<PlayingSound> repeatSound(const std::uint32_t &, bool);
virtual std::optional<PlayingSound> seekSound(const std::uint32_t &, std::uint64_t); virtual std::optional<PlayingSound> seekSound(const std::uint32_t &, std::uint64_t);
virtual std::optional<Sound> setHotkey(const std::uint32_t &, const std::vector<int> &); virtual std::optional<Sound> setHotkey(const std::uint32_t &, const std::vector<Key> &);
virtual std::optional<Sound> setCustomLocalVolume(const std::uint32_t &, const std::optional<int> &); virtual std::optional<Sound> setCustomLocalVolume(const std::uint32_t &, const std::optional<int> &);
virtual std::optional<Sound> setCustomRemoteVolume(const std::uint32_t &, const std::optional<int> &); virtual std::optional<Sound> setCustomRemoteVolume(const std::uint32_t &, const std::optional<int> &);
@ -100,11 +101,13 @@ namespace Soundux
virtual void onAdminRequired() = 0; virtual void onAdminRequired() = 0;
virtual void onSettingsChanged() = 0; virtual void onSettingsChanged() = 0;
virtual void onLocalVolumeChanged(int) = 0;
virtual void onRemoteVolumeChanged(int) = 0;
virtual void onSwitchOnConnectDetected(bool) = 0; virtual void onSwitchOnConnectDetected(bool) = 0;
virtual void onSoundPlayed(const PlayingSound &); virtual void onSoundPlayed(const PlayingSound &);
virtual void onError(const Enums::ErrorCode &) = 0; virtual void onError(const Enums::ErrorCode &) = 0;
virtual void onSoundFinished(const PlayingSound &); virtual void onSoundFinished(const PlayingSound &);
virtual void onHotKeyReceived(const std::vector<int> &); virtual void onHotKeyReceived(const std::vector<Key> &);
virtual void onSoundProgressed(const PlayingSound &) = 0; virtual void onSoundProgressed(const PlayingSound &) = 0;
virtual void onDownloadProgressed(float, const std::string &) = 0; virtual void onDownloadProgressed(float, const std::string &) = 0;
}; };