From 48132d86b9c3e5a85fd23f9a93a0ff934d0ba6d8 Mon Sep 17 00:00:00 2001 From: Curve Date: Sat, 22 May 2021 16:41:36 +0200 Subject: [PATCH] refactor: audiobackend code, ui.hpp, iconfetcher, structure - Changed Structure (Moved Settings, Data and Enums into own header files) - Changed how AudioBackend is created to fix issues (fixes #208) - Made AudioBackend switch to pipewire when pipewire-pulse was detected as server in pulseaudio - Refactored Code for IconFetcher and moved getPpid from misc into IconFetcher - Removed unused code in proccessingqueue, renamed processingqueue to queue. --- .gitignore | 3 +- src/core/config/config.hpp | 3 +- src/core/enums/enums.hpp | 61 ++++ src/core/global/globals.hpp | 10 +- src/core/global/objects.hpp | 144 -------- .../{global/objects.cpp => objects/data.cpp} | 6 +- src/core/objects/data.hpp | 46 +++ src/core/objects/objects.hpp | 32 ++ src/core/objects/settings.hpp | 35 ++ src/helper/audio/audio.hpp | 2 +- src/helper/audio/linux/backend.cpp | 126 +++---- src/helper/audio/linux/backend.hpp | 34 +- src/helper/audio/linux/pipewire/forward.cpp | 48 ++- src/helper/audio/linux/pipewire/forward.hpp | 34 +- src/helper/audio/linux/pipewire/pipewire.cpp | 92 +++-- src/helper/audio/linux/pipewire/pipewire.hpp | 14 +- src/helper/audio/linux/pulse/forward.hpp | 43 --- .../linux/{pulse => pulseaudio}/forward.cpp | 61 ++-- src/helper/audio/linux/pulseaudio/forward.hpp | 39 ++ .../pulse.cpp => pulseaudio/pulseaudio.cpp} | 121 ++++--- .../pulse.hpp => pulseaudio/pulseaudio.hpp} | 35 +- src/helper/icons/forward.cpp | 21 ++ src/helper/icons/forward.hpp | 19 + src/helper/icons/icons.cpp | 93 +++-- src/helper/icons/icons.hpp | 24 +- src/helper/misc/misc.cpp | 34 -- src/helper/misc/misc.hpp | 16 +- src/helper/queue/queue.cpp | 51 +++ src/helper/queue/queue.hpp | 32 ++ src/helper/threads/processing.hpp | 95 ----- src/helper/ytdl/youtube-dl.cpp | 10 +- src/main.cpp | 78 ++-- src/ui/impl/webview/webview.cpp | 19 +- src/ui/impl/webview/webview.hpp | 2 +- src/ui/ui.cpp | 341 +++++++++--------- src/ui/ui.hpp | 64 ++-- 36 files changed, 989 insertions(+), 899 deletions(-) create mode 100644 src/core/enums/enums.hpp delete mode 100644 src/core/global/objects.hpp rename src/core/{global/objects.cpp => objects/data.cpp} (98%) create mode 100644 src/core/objects/data.hpp create mode 100644 src/core/objects/objects.hpp create mode 100644 src/core/objects/settings.hpp delete mode 100644 src/helper/audio/linux/pulse/forward.hpp rename src/helper/audio/linux/{pulse => pulseaudio}/forward.cpp (55%) create mode 100644 src/helper/audio/linux/pulseaudio/forward.hpp rename src/helper/audio/linux/{pulse/pulse.cpp => pulseaudio/pulseaudio.cpp} (85%) rename src/helper/audio/linux/{pulse/pulse.hpp => pulseaudio/pulseaudio.hpp} (72%) create mode 100644 src/helper/icons/forward.cpp create mode 100644 src/helper/icons/forward.hpp create mode 100644 src/helper/queue/queue.cpp create mode 100644 src/helper/queue/queue.hpp delete mode 100644 src/helper/threads/processing.hpp diff --git a/.gitignore b/.gitignore index ecb9c25..76ae98e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ cmake-build-debug analyze node_modules yarn.lock -package.json \ No newline at end of file +package.json +compile_commands.json \ No newline at end of file diff --git a/src/core/config/config.hpp b/src/core/config/config.hpp index 777538d..4aaba06 100644 --- a/src/core/config/config.hpp +++ b/src/core/config/config.hpp @@ -1,5 +1,6 @@ #pragma once -#include +#include +#include #include namespace Soundux diff --git a/src/core/enums/enums.hpp b/src/core/enums/enums.hpp new file mode 100644 index 0000000..95447a6 --- /dev/null +++ b/src/core/enums/enums.hpp @@ -0,0 +1,61 @@ +#pragma once +#include + +namespace Soundux +{ + namespace Enums + { + enum class ErrorCode : std::uint8_t + { + FailedToPlay, + FailedToSeek, + FailedToPause, + FailedToRepeat, + FailedToResume, + FailedToMoveToSink, + SoundNotFound, + FolderDoesNotExist, + TabDoesNotExist, + FailedToSetHotkey, + FailedToStartPassthrough, + FailedToMoveBack, + FailedToMoveBackPassthrough, + FailedToRevertDefaultSource, + FailedToSetDefaultSource, + YtdlNotFound, + YtdlInvalidUrl, + YtdlInvalidJson, + YtdlInformationUnknown, + FailedToDelete + }; + + enum class SortMode : std::uint8_t + { + ModifiedDate_Ascending, + ModifiedDate_Descending, + Alphabetical_Ascending, + Alphabetical_Descending, + }; + + enum class Theme : std::uint8_t + { + System, + Dark, + Light + }; + + enum class ViewMode : std::uint8_t + { + List, + Grid, + EmulatedLaunchpad, + }; + + enum class BackendType : std::uint8_t + { + None, + PipeWire, + PulseAudio, + }; + } // namespace Enums +} // namespace Soundux \ No newline at end of file diff --git a/src/core/global/globals.hpp b/src/core/global/globals.hpp index 8cbb312..2cf12ea 100644 --- a/src/core/global/globals.hpp +++ b/src/core/global/globals.hpp @@ -3,11 +3,13 @@ #if defined(__linux__) #include #endif -#include "objects.hpp" #include #include +#include +#include +#include #include -#include +#include #include #include #include @@ -20,15 +22,15 @@ namespace Soundux inline Objects::Data gData; inline Objects::Audio gAudio; #if defined(__linux__) - inline Objects::IconFetcher gIcons; + inline std::optional gIcons; inline std::shared_ptr gAudioBackend; #endif + 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 Objects::ProcessingQueue gQueue; /* Allows for fast & easy sound access, is populated on start up */ inline sxl::var_guard>> gSounds; diff --git a/src/core/global/objects.hpp b/src/core/global/objects.hpp deleted file mode 100644 index cb3f6cc..0000000 --- a/src/core/global/objects.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -namespace nlohmann -{ - template struct adl_serializer; -} // namespace nlohmann - -namespace Soundux -{ - namespace Objects - { - struct AudioDevice; - - enum class ErrorCode : std::uint8_t - { - FailedToPlay, - FailedToSeek, - FailedToPause, - FailedToRepeat, - FailedToResume, - FailedToMoveToSink, - SoundNotFound, - FolderDoesNotExist, - TabDoesNotExist, - FailedToSetHotkey, - FailedToStartPassthrough, - FailedToMoveBack, - FailedToMoveBackPassthrough, - FailedToRevertDefaultSource, - FailedToSetDefaultSource, - YtdlNotFound, - YtdlInvalidUrl, - YtdlInvalidJson, - YtdlInformationUnknown, - FailedToDelete - }; - - enum class SortMode : std::uint8_t - { - ModifiedDate_Ascending, - ModifiedDate_Descending, - Alphabetical_Ascending, - Alphabetical_Descending, - }; - - enum class Theme : std::uint8_t - { - System, - Dark, - Light - }; - - enum class ViewMode : std::uint8_t - { - List, - Grid, - EmulatedLaunchpad, - }; - - enum class BackendType : std::uint8_t - { - PulseAudio, - PipeWire, - }; - - struct Sound - { - std::uint32_t id; - std::string name; - std::string path; - bool isFavorite = false; - - std::vector hotkeys; - std::uint64_t modifiedDate; - }; - struct Tab - { - std::uint32_t id; //* Equal to index - std::string name; - std::string path; - - std::vector sounds; - }; - - struct Settings - { - SortMode sortMode = SortMode::ModifiedDate_Descending; - BackendType audioBackend = BackendType::PulseAudio; - ViewMode viewMode = ViewMode::List; - Theme theme = Theme::System; - - std::vector pushToTalkKeys; - std::vector stopHotkey; - - std::uint32_t selectedTab = 0; - std::string output; - - float remoteVolume = 1.f; - float localVolume = 0.5f; - bool syncVolumes = false; - - bool useAsDefaultDevice = false; - bool muteDuringPlayback = false; - bool allowOverlapping = true; - bool minimizeToTray = false; - bool tabHotkeysOnly = false; - bool deleteToTrash = true; - }; - class Data - { - template friend struct nlohmann::adl_serializer; - - private: - std::vector tabs; - - public: - bool isOnFavorites = false; - int width = 1280, height = 720; - std::uint32_t soundIdCounter = 0; - - std::vector getTabs() const; - void setTabs(const std::vector &); - std::optional setTab(const std::uint32_t &, const Tab &); - - Tab addTab(Tab); - void removeTabById(const std::uint32_t &); - - std::optional getTab(const std::uint32_t &) const; - std::optional> getSound(const std::uint32_t &); - - std::vector getFavorites(); - std::vector getFavoriteIds(); - void markFavorite(const std::uint32_t &, bool); - - void set(const Data &other); - Data &operator=(const Data &other) = delete; - }; - } // namespace Objects -} // namespace Soundux \ No newline at end of file diff --git a/src/core/global/objects.cpp b/src/core/objects/data.cpp similarity index 98% rename from src/core/global/objects.cpp rename to src/core/objects/data.cpp index 10afd51..0195eae 100644 --- a/src/core/global/objects.cpp +++ b/src/core/objects/data.cpp @@ -1,8 +1,6 @@ -#include "objects.hpp" -#include "globals.hpp" +#include "data.hpp" +#include #include -#include -#include namespace Soundux::Objects { diff --git a/src/core/objects/data.hpp b/src/core/objects/data.hpp new file mode 100644 index 0000000..33d3ab4 --- /dev/null +++ b/src/core/objects/data.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "objects.hpp" +#include +#include +#include + +namespace nlohmann +{ + template struct adl_serializer; +} // namespace nlohmann + +namespace Soundux +{ + namespace Objects + { + class Data + { + template friend struct nlohmann::adl_serializer; + + private: + std::vector tabs; + + public: + bool isOnFavorites = false; + int width = 1280, height = 720; + std::uint32_t soundIdCounter = 0; + + std::vector getTabs() const; + void setTabs(const std::vector &); + std::optional setTab(const std::uint32_t &, const Tab &); + + Tab addTab(Tab); + void removeTabById(const std::uint32_t &); + + std::optional getTab(const std::uint32_t &) const; + std::optional> getSound(const std::uint32_t &); + + std::vector getFavorites(); + std::vector getFavoriteIds(); + void markFavorite(const std::uint32_t &, bool); + + void set(const Data &other); + Data &operator=(const Data &other) = delete; + }; + } // namespace Objects +} // namespace Soundux \ No newline at end of file diff --git a/src/core/objects/objects.hpp b/src/core/objects/objects.hpp new file mode 100644 index 0000000..c675ce3 --- /dev/null +++ b/src/core/objects/objects.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include + +namespace Soundux +{ + namespace Objects + { + struct AudioDevice; + + struct Sound + { + std::uint32_t id; + std::string name; + std::string path; + bool isFavorite = false; + + std::vector hotkeys; + std::uint64_t modifiedDate; + }; + + struct Tab + { + std::uint32_t id; //* Equal to index + std::string name; + std::string path; + + std::vector sounds; + }; + } // namespace Objects +} // namespace Soundux \ No newline at end of file diff --git a/src/core/objects/settings.hpp b/src/core/objects/settings.hpp new file mode 100644 index 0000000..2dd8b0b --- /dev/null +++ b/src/core/objects/settings.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include + +namespace Soundux +{ + namespace Objects + { + struct Settings + { + Enums::SortMode sortMode = Enums::SortMode::ModifiedDate_Descending; + Enums::BackendType audioBackend = Enums::BackendType::PulseAudio; + Enums::ViewMode viewMode = Enums::ViewMode::List; + Enums::Theme theme = Enums::Theme::System; + + std::vector pushToTalkKeys; + std::vector stopHotkey; + + std::uint32_t selectedTab = 0; + std::string output; + + float remoteVolume = 1.f; + float localVolume = 0.5f; + bool syncVolumes = false; + + bool useAsDefaultDevice = false; + bool muteDuringPlayback = false; + bool allowOverlapping = true; + bool minimizeToTray = false; + bool tabHotkeysOnly = false; + bool deleteToTrash = true; + }; + } // namespace Objects +} // namespace Soundux \ No newline at end of file diff --git a/src/helper/audio/audio.hpp b/src/helper/audio/audio.hpp index 6c562b8..415f229 100644 --- a/src/helper/audio/audio.hpp +++ b/src/helper/audio/audio.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include #include #include diff --git a/src/helper/audio/linux/backend.cpp b/src/helper/audio/linux/backend.cpp index 14c87da..3d15c18 100644 --- a/src/helper/audio/linux/backend.cpp +++ b/src/helper/audio/linux/backend.cpp @@ -1,96 +1,54 @@ -#if defined(__linux__) #include "backend.hpp" +#include "pipewire/pipewire.hpp" +#include "pulseaudio/pulseaudio.hpp" +#include +#include #include +#include namespace Soundux::Objects { - void AudioBackend::setup() + std::shared_ptr AudioBackend::createInstance(Enums::BackendType backend) { - Fancy::fancy.logTime().warning() << "setup(): not implemented (Possibly using null-audiobackend)" << std::endl; - } - void AudioBackend::destroy() - { - Fancy::fancy.logTime().warning() << "destroy(): not implemented (Possibly using null-audiobackend)" - << std::endl; - } - bool AudioBackend::useAsDefault() - { - Fancy::fancy.logTime().warning() << "useAsDefault(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return false; - } - bool AudioBackend::revertDefault() - { - Fancy::fancy.logTime().warning() << "revertDefault(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return false; - } - bool AudioBackend::muteInput([[maybe_unused]] bool state) - { - Fancy::fancy.logTime().warning() << "muteInput(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return false; - } + std::shared_ptr instance; + if (backend == Enums::BackendType::PulseAudio) + { + instance = std::shared_ptr(new PulseAudio()); // NOLINT + auto pulseInstance = std::dynamic_pointer_cast(instance); - bool AudioBackend::stopPassthrough() - { - Fancy::fancy.logTime().warning() << "stopPassthrough(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return false; - } - bool AudioBackend::isCurrentlyPassingThrough() - { - Fancy::fancy.logTime().warning() - << "isCurrentlyPassingThrough(): not implemented (Possibly using null-audiobackend)" << std::endl; - return false; - } - bool AudioBackend::passthroughFrom([[maybe_unused]] std::shared_ptr app) // NOLINT - { - Fancy::fancy.logTime().warning() << "passthroughFrom(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return false; - } + if (pulseInstance && pulseInstance->setup()) + { + if (!pulseInstance->switchOnConnectPresent()) + { + if (pulseInstance->loadModules()) + { + return instance; + } + } + else + { + return instance; + } + } - bool AudioBackend::stopSoundInput() - { - Fancy::fancy.logTime().warning() << "stopSoundInput(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return false; - } + if (pulseInstance && pulseInstance->isRunningPipeWire()) + { + backend = Enums::BackendType::PipeWire; + Globals::gSettings.audioBackend = backend; + } + } - bool AudioBackend::inputSoundTo([[maybe_unused]] std::shared_ptr app) // NOLINT - { - Fancy::fancy.logTime().warning() << "inputSoundTo(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return false; - } + if (backend == Enums::BackendType::PipeWire) + { + instance = std::shared_ptr(new PipeWire()); // NOLINT + if (instance->setup()) + { + return instance; + } + } - std::shared_ptr AudioBackend::getPlaybackApp([[maybe_unused]] const std::string &name) - { - Fancy::fancy.logTime().warning() << "getPlaybackApp(): not implemented (Possibly using null-audiobackend)" - << std::endl; + Fancy::fancy.logTime().failure() << "Failed to create AudioBackend instance" << std::endl; + Globals::gSettings.audioBackend = Enums::BackendType::None; return nullptr; } - - std::shared_ptr AudioBackend::getRecordingApp([[maybe_unused]] const std::string &name) - { - - Fancy::fancy.logTime().warning() << "getRecordingApp(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return nullptr; - } - - std::vector> AudioBackend::getPlaybackApps() - { - Fancy::fancy.logTime().warning() << "getPlaybackApps(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return {}; - } - std::vector> AudioBackend::getRecordingApps() - { - Fancy::fancy.logTime().warning() << "getRecordingApps(): not implemented (Possibly using null-audiobackend)" - << std::endl; - return {}; - } -} // namespace Soundux::Objects -#endif \ No newline at end of file +} // namespace Soundux::Objects \ No newline at end of file diff --git a/src/helper/audio/linux/backend.hpp b/src/helper/audio/linux/backend.hpp index d43856b..d587400 100644 --- a/src/helper/audio/linux/backend.hpp +++ b/src/helper/audio/linux/backend.hpp @@ -1,5 +1,6 @@ #pragma once #if defined(__linux__) +#include #include #include #include @@ -25,28 +26,31 @@ namespace Soundux class AudioBackend { - public: + protected: + virtual bool setup() = 0; AudioBackend() = default; - virtual void setup(); - virtual void destroy(); + public: + static std::shared_ptr createInstance(Enums::BackendType); - virtual bool useAsDefault(); - virtual bool revertDefault(); - virtual bool muteInput(bool); + public: + virtual void destroy() = 0; + virtual bool useAsDefault() = 0; + virtual bool revertDefault() = 0; + virtual bool muteInput(bool) = 0; - virtual bool stopPassthrough(); - virtual bool isCurrentlyPassingThrough(); - virtual bool passthroughFrom(std::shared_ptr); + virtual bool stopPassthrough() = 0; + virtual bool isCurrentlyPassingThrough() = 0; + virtual bool passthroughFrom(std::shared_ptr) = 0; - virtual bool stopSoundInput(); - virtual bool inputSoundTo(std::shared_ptr); + virtual bool stopSoundInput() = 0; + virtual bool inputSoundTo(std::shared_ptr) = 0; - virtual std::shared_ptr getPlaybackApp(const std::string &); - virtual std::shared_ptr getRecordingApp(const std::string &); + virtual std::shared_ptr getPlaybackApp(const std::string &) = 0; + virtual std::shared_ptr getRecordingApp(const std::string &) = 0; - virtual std::vector> getPlaybackApps(); - virtual std::vector> getRecordingApps(); + virtual std::vector> getPlaybackApps() = 0; + virtual std::vector> getRecordingApps() = 0; }; } // namespace Objects } // namespace Soundux diff --git a/src/helper/audio/linux/pipewire/forward.cpp b/src/helper/audio/linux/pipewire/forward.cpp index 595deb2..ed3fd8b 100644 --- a/src/helper/audio/linux/pipewire/forward.cpp +++ b/src/helper/audio/linux/pipewire/forward.cpp @@ -2,6 +2,7 @@ #include "forward.hpp" #include #include +#include #include #include @@ -20,34 +21,41 @@ bool Soundux::PipeWireApi::setup() auto *libpulse = dlopen("libpipewire-0.3.so", RTLD_LAZY); if (!libpulse) { - //* For flatpak + //* For Ubuntu libpulse = dlopen("/usr/lib/x86_64-linux-gnu/libpipewire-0.3.so.0", RTLD_LAZY); } if (libpulse) { -#define load(name) loadFunc(libpulse, name, #name) - load(pw_context_connect); - load(pw_context_new); - load(pw_main_loop_new); - load(pw_main_loop_get_loop); - load(pw_proxy_add_listener); - load(pw_properties_setf); - load(pw_properties_set); - load(pw_properties_new); - load(pw_properties_free); - load(pw_main_loop_destroy); - load(pw_main_loop_quit); - load(pw_context_destroy); - load(pw_core_disconnect); - load(pw_main_loop_run); - load(pw_proxy_destroy); - load(pw_init); - return true; + try + { +#define stringify(what) #what +#define load(name) loadFunc(libpulse, name, stringify(pw_##name)) + load(init); + load(context_new); + load(main_loop_run); + load(main_loop_new); + load(proxy_destroy); + load(main_loop_quit); + load(properties_new); + load(properties_set); + load(context_connect); + load(properties_setf); + load(context_destroy); + load(properties_free); + load(core_disconnect); + load(main_loop_destroy); + load(main_loop_get_loop); + load(proxy_add_listener); + return true; + } + catch (std::exception &e) + { + Fancy::fancy.logTime().failure() << "Loading Functions failed: " << e.what() << std::endl; + } } Fancy::fancy.logTime().failure() << "Failed to load pipewire" << std::endl; - Globals::gAudioBackend = std::make_shared(); return false; } diff --git a/src/helper/audio/linux/pipewire/forward.hpp b/src/helper/audio/linux/pipewire/forward.hpp index e660daf..2225aff 100644 --- a/src/helper/audio/linux/pipewire/forward.hpp +++ b/src/helper/audio/linux/pipewire/forward.hpp @@ -9,24 +9,24 @@ namespace Soundux { bool setup(); - inline pw_core *(*pw_context_connect)(pw_context *, pw_properties *, std::size_t); - inline pw_context *(*pw_context_new)(pw_loop *, pw_properties *, std::size_t); - inline pw_main_loop *(*pw_main_loop_new)(pw_properties *); - inline pw_loop *(*pw_main_loop_get_loop)(pw_main_loop *); - - inline void (*pw_proxy_add_listener)(pw_proxy *, spa_hook *, pw_proxy_events *, void *); - inline int (*pw_properties_setf)(pw_properties *, const char *, const char *, ...); - inline int (*pw_properties_set)(pw_properties *, const char *, const char *); - inline pw_properties *(*pw_properties_new)(const char *, ...); - inline void (*pw_main_loop_destroy)(pw_main_loop *); - inline void (*pw_properties_free)(pw_properties *); - inline int (*pw_main_loop_quit)(pw_main_loop *); - inline void (*pw_context_destroy)(pw_context *); - inline int (*pw_main_loop_run)(pw_main_loop *); - inline int (*pw_core_disconnect)(pw_core *); - inline void (*pw_proxy_destroy)(pw_proxy *); - inline void (*pw_init)(int *, char **); + //* We declare function pointers here so that we can use dlsym to assign them later. + inline pw_core *(*context_connect)(pw_context *, pw_properties *, std::size_t); + inline pw_context *(*context_new)(pw_loop *, pw_properties *, std::size_t); + inline pw_main_loop *(*main_loop_new)(pw_properties *); + inline pw_loop *(*main_loop_get_loop)(pw_main_loop *); + inline void (*proxy_add_listener)(pw_proxy *, spa_hook *, pw_proxy_events *, void *); + inline int (*properties_setf)(pw_properties *, const char *, const char *, ...); + inline int (*properties_set)(pw_properties *, const char *, const char *); + inline pw_properties *(*properties_new)(const char *, ...); + inline void (*main_loop_destroy)(pw_main_loop *); + inline void (*properties_free)(pw_properties *); + inline int (*main_loop_quit)(pw_main_loop *); + inline void (*context_destroy)(pw_context *); + inline int (*main_loop_run)(pw_main_loop *); + inline int (*core_disconnect)(pw_core *); + inline void (*proxy_destroy)(pw_proxy *); + inline void (*init)(int *, char **); } // namespace PipeWireApi } // namespace Soundux #endif \ No newline at end of file diff --git a/src/helper/audio/linux/pipewire/pipewire.cpp b/src/helper/audio/linux/pipewire/pipewire.cpp index 3e784c7..9e0067e 100644 --- a/src/helper/audio/linux/pipewire/pipewire.cpp +++ b/src/helper/audio/linux/pipewire/pipewire.cpp @@ -22,18 +22,22 @@ namespace Soundux::Objects if (id == PW_ID_CORE && seq == *info->second) { *info->second = -1; - PipeWireApi::pw_main_loop_quit(info->first->loop); + PipeWireApi::main_loop_quit(info->first->loop); } } }; coreEvents.error = [](void *data, std::uint32_t id, int seq, int res, const char *message) { - Fancy::fancy.logTime() << "Core Failure - Seq " << seq << " - Res " << res << ": " << message << std::endl; auto *info = reinterpret_cast *>(data); - - if (info && id == PW_ID_CORE) + if (info) { - *info->second = -1; - PipeWireApi::pw_main_loop_quit(info->first->loop); + if (id == PW_ID_CORE && seq == *info->second) + { + Fancy::fancy.logTime() + << "Core Failure - Seq " << seq << " - Res " << res << ": " << message << std::endl; + + *info->second = -1; + PipeWireApi::main_loop_quit(info->first->loop); + } } }; @@ -43,7 +47,7 @@ namespace Soundux::Objects pending = pw_core_sync(core, PW_ID_CORE, 0); // NOLINT while (pending != -1) { - PipeWireApi::pw_main_loop_run(loop); + PipeWireApi::main_loop_run(loop); } spa_hook_remove(&coreListener); @@ -135,7 +139,7 @@ namespace Soundux::Objects pw_node_add_listener(boundNode, &listener, &events, thiz); // NOLINT thiz->sync(); spa_hook_remove(&listener); - PipeWireApi::pw_proxy_destroy(reinterpret_cast(boundNode)); + PipeWireApi::proxy_destroy(reinterpret_cast(boundNode)); } } if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0) @@ -164,7 +168,7 @@ namespace Soundux::Objects pw_port_add_listener(boundPort, &listener, &events, thiz); // NOLINT thiz->sync(); spa_hook_remove(&listener); - PipeWireApi::pw_proxy_destroy(reinterpret_cast(boundPort)); + PipeWireApi::proxy_destroy(reinterpret_cast(boundPort)); auto scopedNodes = thiz->nodes.scoped(); auto scopedPorts = thiz->ports.scoped(); @@ -202,34 +206,37 @@ namespace Soundux::Objects } } - void PipeWire::setup() + bool PipeWire::setup() { if (!PipeWireApi::setup()) { - return; + return false; } - PipeWireApi::pw_init(nullptr, nullptr); - loop = PipeWireApi::pw_main_loop_new(nullptr); + PipeWireApi::init(nullptr, nullptr); + loop = PipeWireApi::main_loop_new(nullptr); if (!loop) { - throw std::runtime_error("Failed to create main loop"); + Fancy::fancy.logTime().failure() << "Failed to create main loop" << std::endl; + return false; } - context = PipeWireApi::pw_context_new(PipeWireApi::pw_main_loop_get_loop(loop), nullptr, 0); + context = PipeWireApi::context_new(PipeWireApi::main_loop_get_loop(loop), nullptr, 0); if (!context) { - throw std::runtime_error("Failed to create context"); + Fancy::fancy.logTime().failure() << "Failed to create context" << std::endl; + return false; } - - core = PipeWireApi::pw_context_connect(context, nullptr, 0); + core = PipeWireApi::context_connect(context, nullptr, 0); if (!core) { - throw std::runtime_error("Failed to connect context"); + Fancy::fancy.logTime().failure() << "Failed to connect context" << std::endl; + return false; } registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0); if (!registry) { - throw std::runtime_error("Failed to get registry"); + Fancy::fancy.logTime().failure() << "Failed to get registry" << std::endl; + return false; } registryEvents.global = onGlobalAdded; @@ -239,31 +246,38 @@ namespace Soundux::Objects pw_registry_add_listener(registry, ®istryListener, ®istryEvents, this); // NOLINT sync(); - createNullSink(); + + if (!createNullSink()) + { + Fancy::fancy.logTime().failure() << "Failed to create null sink" << std::endl; + return false; + } + + return true; } void PipeWire::destroy() { - PipeWireApi::pw_proxy_destroy(reinterpret_cast(registry)); - PipeWireApi::pw_core_disconnect(core); - PipeWireApi::pw_context_destroy(context); - PipeWireApi::pw_main_loop_destroy(loop); + PipeWireApi::proxy_destroy(reinterpret_cast(registry)); + PipeWireApi::core_disconnect(core); + PipeWireApi::context_destroy(context); + PipeWireApi::main_loop_destroy(loop); } bool PipeWire::createNullSink() { - pw_properties *props = PipeWireApi::pw_properties_new(nullptr, nullptr); + pw_properties *props = PipeWireApi::properties_new(nullptr, nullptr); - PipeWireApi::pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); - PipeWireApi::pw_properties_set(props, PW_KEY_NODE_NAME, "soundux_sink"); - PipeWireApi::pw_properties_set(props, PW_KEY_FACTORY_NAME, "support.null-audio-sink"); + PipeWireApi::properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); + PipeWireApi::properties_set(props, PW_KEY_NODE_NAME, "soundux_sink"); + PipeWireApi::properties_set(props, PW_KEY_FACTORY_NAME, "support.null-audio-sink"); auto *proxy = reinterpret_cast( pw_core_create_object(core, "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0)); if (!proxy) { - PipeWireApi::pw_properties_free(props); + PipeWireApi::properties_free(props); return false; } @@ -277,11 +291,11 @@ namespace Soundux::Objects *reinterpret_cast(data) = false; }; - PipeWireApi::pw_proxy_add_listener(proxy, &listener, &linkEvent, &success); + PipeWireApi::proxy_add_listener(proxy, &listener, &linkEvent, &success); sync(); spa_hook_remove(&listener); - PipeWireApi::pw_properties_free(props); + PipeWireApi::properties_free(props); return success; } @@ -296,18 +310,18 @@ namespace Soundux::Objects std::optional PipeWire::linkPorts(std::uint32_t in, std::uint32_t out) { - pw_properties *props = PipeWireApi::pw_properties_new(nullptr, nullptr); + pw_properties *props = PipeWireApi::properties_new(nullptr, nullptr); - PipeWireApi::pw_properties_set(props, PW_KEY_APP_NAME, "soundux"); - PipeWireApi::pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", in); - PipeWireApi::pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", out); + PipeWireApi::properties_set(props, PW_KEY_APP_NAME, "soundux"); + PipeWireApi::properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", in); + PipeWireApi::properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", out); auto *proxy = reinterpret_cast( pw_core_create_object(core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0)); if (!proxy) { - PipeWireApi::pw_properties_free(props); + PipeWireApi::properties_free(props); return std::nullopt; } @@ -324,11 +338,11 @@ namespace Soundux::Objects *reinterpret_cast *>(data) = std::nullopt; }; - PipeWireApi::pw_proxy_add_listener(proxy, &listener, &linkEvent, &result); + PipeWireApi::proxy_add_listener(proxy, &listener, &linkEvent, &result); sync(); spa_hook_remove(&listener); - PipeWireApi::pw_properties_free(props); + PipeWireApi::properties_free(props); return result; } diff --git a/src/helper/audio/linux/pipewire/pipewire.hpp b/src/helper/audio/linux/pipewire/pipewire.hpp index 57cbff3..9f55a37 100644 --- a/src/helper/audio/linux/pipewire/pipewire.hpp +++ b/src/helper/audio/linux/pipewire/pipewire.hpp @@ -5,6 +5,13 @@ #include #include +// TODO(pipewire): +//* From the pipewire news of 0.3.26 +//* - The link factory can now also make links between nodes and +//* ports by name so that it can be used in scripts. +//* +//* Maybe we could try to make use of that, it could reduce loc. + namespace Soundux { namespace Objects @@ -43,6 +50,9 @@ namespace Soundux class PipeWire : public AudioBackend { + friend class AudioBackend; + + private: pw_core *core; pw_main_loop *loop; pw_context *context; @@ -72,9 +82,11 @@ namespace Soundux static void onGlobalAdded(void *, std::uint32_t, std::uint32_t, const char *, std::uint32_t, const spa_dict *); + protected: + bool setup() override; + public: PipeWire() = default; - void setup() override; void destroy() override; bool useAsDefault() override; diff --git a/src/helper/audio/linux/pulse/forward.hpp b/src/helper/audio/linux/pulse/forward.hpp deleted file mode 100644 index f6d83d6..0000000 --- a/src/helper/audio/linux/pulse/forward.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once -#if defined(__linux__) -#include -#include - -namespace Soundux -{ - namespace PulseApi - { - bool setup(); - - inline pa_mainloop *(*mainloop_new)(); - inline int (*mainloop_iterate)(pa_mainloop *, int, int *); - inline pa_mainloop_api *(*mainloop_get_api)(pa_mainloop *); - inline pa_context *(*context_new)(pa_mainloop_api *, const char *); - inline int (*context_connect)(pa_context *, const char *, unsigned int, const void *); - inline void (*context_set_state_callback)(pa_context *, pa_context_notify_cb_t, void *); - inline pa_operation *(*context_load_module)(pa_context *, const char *, const char *, pa_context_index_cb_t, - void *); - inline pa_operation *(*context_get_module_info_list)(pa_context *, pa_module_info_cb_t, void *); - inline pa_operation *(*context_get_source_output_info_list)(pa_context *, pa_source_output_info_cb_t, void *); - inline pa_operation *(*context_get_sink_input_info_list)(pa_context *, pa_sink_input_info_cb_t, void *); - inline pa_operation *(*context_get_server_info)(pa_context *, pa_server_info_cb_t, void *); - - inline const char *(*proplist_gets)(const pa_proplist *, const char *); - - inline pa_operation *(*context_set_default_source)(pa_context *, const char *, pa_context_success_cb_t, void *); - inline pa_operation *(*context_move_sink_input_by_name)(pa_context *, std::uint32_t, const char *, - pa_context_success_cb_t, void *); - inline pa_operation *(*context_move_sink_input_by_index)(pa_context *, std::uint32_t, std::uint32_t, - pa_context_success_cb_t, void *); - inline pa_operation *(*context_move_source_output_by_name)(pa_context *, std::uint32_t, const char *, - pa_context_success_cb_t, void *); - inline pa_operation *(*context_move_source_output_by_index)(pa_context *, std::uint32_t, std::uint32_t, - pa_context_success_cb_t, void *); - inline pa_operation *(*context_set_sink_input_mute)(pa_context *, uint32_t, int, pa_context_success_cb_t, - void *); - inline pa_operation *(*context_unload_module)(pa_context *, std::uint32_t, pa_context_success_cb_t, void *); - inline pa_context_state (*context_get_state)(const pa_context *); - inline pa_operation_state (*operation_get_state)(const pa_operation *); - } // namespace PulseApi -} // namespace Soundux -#endif \ No newline at end of file diff --git a/src/helper/audio/linux/pulse/forward.cpp b/src/helper/audio/linux/pulseaudio/forward.cpp similarity index 55% rename from src/helper/audio/linux/pulse/forward.cpp rename to src/helper/audio/linux/pulseaudio/forward.cpp index ecd61b9..9e09978 100644 --- a/src/helper/audio/linux/pulse/forward.cpp +++ b/src/helper/audio/linux/pulseaudio/forward.cpp @@ -44,37 +44,50 @@ bool Soundux::PulseApi::setup() return true; #else auto *libpulse = dlopen("libpulse.so", RTLD_LAZY); + if (!libpulse) + { + //* For Ubuntu + libpulse = dlopen("/usr/lib/x86_64-linux-gnu/libpulse.so.0", RTLD_LAZY); + } + if (libpulse) { + try + { + #define stringify(what) #what #define load(name) loadFunc(libpulse, name, stringify(pa_##name)) - load(mainloop_new); - load(mainloop_iterate); - load(mainloop_get_api); - load(context_new); - load(context_connect); - load(context_set_state_callback); - load(context_load_module); - load(context_get_module_info_list); - load(context_get_source_output_info_list); - load(context_get_sink_input_info_list); - load(context_get_server_info); - load(proplist_gets); - load(context_set_default_source); - load(context_move_sink_input_by_name); - load(context_get_server_info); - load(context_move_sink_input_by_index); - load(context_move_source_output_by_name); - load(context_move_source_output_by_index); - load(context_set_sink_input_mute); - load(context_unload_module); - load(context_get_state); - load(operation_get_state); - return true; + load(mainloop_new); + load(mainloop_iterate); + load(mainloop_get_api); + load(context_new); + load(context_connect); + load(context_set_state_callback); + load(context_load_module); + load(context_get_module_info_list); + load(context_get_source_output_info_list); + load(context_get_sink_input_info_list); + load(context_get_server_info); + load(proplist_gets); + load(context_set_default_source); + load(context_move_sink_input_by_name); + load(context_get_server_info); + load(context_move_sink_input_by_index); + load(context_move_source_output_by_name); + load(context_move_source_output_by_index); + load(context_set_sink_input_mute); + load(context_unload_module); + load(context_get_state); + load(operation_get_state); + return true; + } + catch (std::exception &e) + { + Fancy::fancy.logTime().failure() << "Loading Functions failed: " << e.what() << std::endl; + } } Fancy::fancy.logTime().failure() << "Failed to load pulseaudio" << std::endl; - Globals::gAudioBackend = std::make_shared(); return false; #endif } diff --git a/src/helper/audio/linux/pulseaudio/forward.hpp b/src/helper/audio/linux/pulseaudio/forward.hpp new file mode 100644 index 0000000..a1ab0bc --- /dev/null +++ b/src/helper/audio/linux/pulseaudio/forward.hpp @@ -0,0 +1,39 @@ +#pragma once +#if defined(__linux__) +#include +#include +#include + +namespace Soundux +{ + namespace PulseApi + { + bool setup(); + + //* We declare function pointers here so that we can use dlsym to assign them later. +#define pulse_forward_decl(function) inline std::add_pointer_t function; + + pulse_forward_decl(context_new); + pulse_forward_decl(mainloop_new); + pulse_forward_decl(proplist_gets); + pulse_forward_decl(context_connect); + pulse_forward_decl(mainloop_iterate); + pulse_forward_decl(mainloop_get_api); + pulse_forward_decl(context_get_state); + pulse_forward_decl(operation_get_state); + pulse_forward_decl(context_load_module); + pulse_forward_decl(context_unload_module); + pulse_forward_decl(context_get_server_info); + pulse_forward_decl(context_set_state_callback); + pulse_forward_decl(context_set_default_source); + pulse_forward_decl(context_set_sink_input_mute); + pulse_forward_decl(context_get_module_info_list); + pulse_forward_decl(context_move_sink_input_by_name); + pulse_forward_decl(context_move_sink_input_by_index); + pulse_forward_decl(context_get_sink_input_info_list); + pulse_forward_decl(context_move_source_output_by_name); + pulse_forward_decl(context_get_source_output_info_list); + pulse_forward_decl(context_move_source_output_by_index); + } // namespace PulseApi +} // namespace Soundux +#endif \ No newline at end of file diff --git a/src/helper/audio/linux/pulse/pulse.cpp b/src/helper/audio/linux/pulseaudio/pulseaudio.cpp similarity index 85% rename from src/helper/audio/linux/pulse/pulse.cpp rename to src/helper/audio/linux/pulseaudio/pulseaudio.cpp index a119212..88fed52 100644 --- a/src/helper/audio/linux/pulse/pulse.cpp +++ b/src/helper/audio/linux/pulseaudio/pulseaudio.cpp @@ -1,23 +1,23 @@ #if defined(__linux__) -#include "pulse.hpp" +#include "pulseaudio.hpp" +#include "forward.hpp" #include #include #include -#include namespace Soundux::Objects { - void PulseAudio::setup() + bool PulseAudio::setup() { if (!PulseApi::setup()) { - return; + return false; } mainloop = PulseApi::mainloop_new(); mainloopApi = PulseApi::mainloop_get_api(mainloop); context = PulseApi::context_new(mainloopApi, "soundux"); - PulseApi::context_connect(context, nullptr, 0, nullptr); + PulseApi::context_connect(context, nullptr, pa_context_flags::PA_CONTEXT_NOFLAGS, nullptr); auto data = std::make_pair(false, false); PulseApi::context_set_state_callback( @@ -53,13 +53,15 @@ namespace Soundux::Objects if (!data.second) { - return; + return false; } unloadLeftOvers(); fetchDefaultSource(); + + return !(defaultSource.empty() || serverName.empty() || isRunningPipeWire()); } - void PulseAudio::loadModules() + bool PulseAudio::loadModules() { auto playbackApps = getPlaybackApps(); auto recordingApps = getRecordingApps(); @@ -74,7 +76,7 @@ namespace Soundux::Objects } else { - *reinterpret_cast(userData) = id; + *reinterpret_cast *>(userData) = id; } }, &nullSink)); @@ -90,7 +92,7 @@ namespace Soundux::Objects } else { - *reinterpret_cast(userData) = id; + *reinterpret_cast *>(userData) = id; } }, &loopBack)); @@ -105,7 +107,7 @@ namespace Soundux::Objects } else { - *reinterpret_cast(userData) = id; + *reinterpret_cast *>(userData) = id; } }, &passthrough)); @@ -120,7 +122,7 @@ namespace Soundux::Objects } else { - *reinterpret_cast(userData) = id; + *reinterpret_cast *>(userData) = id; } }, &passthroughSink)); @@ -134,14 +136,23 @@ namespace Soundux::Objects } else { - *reinterpret_cast(userData) = id; + *reinterpret_cast *>(userData) = id; } }, &passthroughLoopBack)); fetchLoopBackSinkId(); + + if (!nullSink || !loopBack || !loopBackSink || !passthrough || !passthroughSink || !passthroughLoopBack) + { + unloadLeftOvers(); + return false; + } + fixPlaybackApps(playbackApps); fixRecordingApps(recordingApps); + + return true; } void PulseAudio::destroy() { @@ -149,14 +160,19 @@ namespace Soundux::Objects stopSoundInput(); stopPassthrough(); - //* We only have to unload these 3 because the other modules depend on these and will automatically be deleted - await(PulseApi::context_unload_module(context, nullSink, nullptr, nullptr)); - await(PulseApi::context_unload_module(context, loopBack, nullptr, nullptr)); - await(PulseApi::context_unload_module(context, loopBackSink, nullptr, nullptr)); + if (nullSink) + await(PulseApi::context_unload_module(context, *nullSink, nullptr, nullptr)); + if (loopBack) + await(PulseApi::context_unload_module(context, *loopBack, nullptr, nullptr)); + if (loopBackSink) + await(PulseApi::context_unload_module(context, *loopBackSink, nullptr, nullptr)); - await(PulseApi::context_unload_module(context, passthrough, nullptr, nullptr)); - await(PulseApi::context_unload_module(context, passthroughSink, nullptr, nullptr)); - await(PulseApi::context_unload_module(context, passthroughLoopBack, nullptr, nullptr)); + if (passthrough) + await(PulseApi::context_unload_module(context, *passthrough, nullptr, nullptr)); + if (passthroughSink) + await(PulseApi::context_unload_module(context, *passthroughSink, nullptr, nullptr)); + if (passthroughLoopBack) + await(PulseApi::context_unload_module(context, *passthroughLoopBack, nullptr, nullptr)); } void PulseAudio::await(pa_operation *operation) { @@ -174,6 +190,7 @@ namespace Soundux::Objects if (info) { reinterpret_cast(userData)->defaultSource = info->default_source_name; + reinterpret_cast(userData)->serverName = info->server_name; } }, this)); @@ -267,7 +284,7 @@ namespace Soundux::Objects if (!defaultSource.empty()) { - await(PulseApi::context_unload_module(context, loopBack, nullptr, nullptr)); + await(PulseApi::context_unload_module(context, *loopBack, nullptr, nullptr)); await(PulseApi::context_load_module( context, "module-loopback", ("rate=44100 source=" + defaultSource + " sink=soundux_sink").c_str(), @@ -275,16 +292,16 @@ namespace Soundux::Objects if (static_cast(id) < 0) { Fancy::fancy.logTime().failure() << "Failed to load loopback" << std::endl; - *reinterpret_cast(userData) = 0; + *reinterpret_cast *>(userData) = std::nullopt; } else { - *reinterpret_cast(userData) = id; + *reinterpret_cast *>(userData) = id; } }, &loopBack)); - if (loopBack == 0) + if (!loopBack) { return false; } @@ -313,7 +330,7 @@ namespace Soundux::Objects { if (!defaultSource.empty()) { - await(PulseApi::context_unload_module(context, loopBack, nullptr, nullptr)); + await(PulseApi::context_unload_module(context, *loopBack, nullptr, nullptr)); auto result = std::make_pair(&loopBack, false); @@ -353,12 +370,14 @@ namespace Soundux::Objects } bool PulseAudio::passthroughFrom(std::shared_ptr app) { - if (movedPassthroughApplication && movedPassthroughApplication->name == app->name) + auto movedPassthroughScoped = movedPassthroughApplication.scoped(); + if (*movedPassthroughScoped && movedPassthroughScoped->get()->name == app->name) { Fancy::fancy.logTime().message() << "Ignoring sound passthrough request because requested app is already moved" << std::endl; return true; } + movedPassthroughScoped.unlock(); if (!stopPassthrough()) { Fancy::fancy.logTime().warning() << "Failed to stop current passthrough" << std::endl; @@ -396,23 +415,26 @@ namespace Soundux::Objects } } - movedPassthroughApplication = std::dynamic_pointer_cast(app); + movedPassthroughScoped.lock(); + movedPassthroughScoped = std::dynamic_pointer_cast(app); + return true; } bool PulseAudio::stopPassthrough() { - if (movedPassthroughApplication) + auto movedPassthroughScoped = movedPassthroughApplication.scoped(); + if (*movedPassthroughScoped) { bool success = false; for (const auto &app : getPlaybackApps()) { auto pulseApp = std::dynamic_pointer_cast(app); - if (app->name == movedPassthroughApplication->name) + if (app->name == movedPassthroughApplication->get()->name) { await(PulseApi::context_move_sink_input_by_index( - context, pulseApp->id, movedPassthroughApplication->sink, + context, pulseApp->id, movedPassthroughApplication->get()->sink, []([[maybe_unused]] pa_context *ctx, int success, void *userData) { if (success) { @@ -421,7 +443,7 @@ namespace Soundux::Objects }, &success)); } - movedPassthroughApplication.reset(); + movedPassthroughApplication->reset(); return success; } } @@ -436,15 +458,14 @@ namespace Soundux::Objects return false; } - std::unique_lock lock(movedAppMutex); - if (movedApplication && movedApplication->name == app->name) + auto movedAppScoped = movedApplication.scoped(); + if (*movedAppScoped && movedAppScoped->get()->name == app->name) { return true; } - lock.unlock(); + movedAppScoped.unlock(); stopSoundInput(); - lock.lock(); for (const auto &recordingApp : getRecordingApps()) { @@ -471,25 +492,25 @@ namespace Soundux::Objects } } - movedApplication = std::dynamic_pointer_cast(app); + movedAppScoped.lock(); + movedAppScoped = std::dynamic_pointer_cast(app); return true; } bool PulseAudio::stopSoundInput() { - std::unique_lock lock(movedAppMutex); - bool success = true; - if (movedApplication) + + auto movedAppScoped = movedApplication.scoped(); + if (*movedAppScoped) { for (const auto &recordingApp : getRecordingApps()) { auto pulseApp = std::dynamic_pointer_cast(recordingApp); - - if (pulseApp->name == movedApplication->name) + if (pulseApp->name == movedAppScoped->get()->name) { await(PulseApi::context_move_source_output_by_index( - context, pulseApp->id, movedApplication->source, + context, pulseApp->id, movedAppScoped->get()->source, []([[maybe_unused]] pa_context *ctx, int success, void *userData) { if (!success) { @@ -506,7 +527,7 @@ namespace Soundux::Objects } } } - movedApplication.reset(); + movedAppScoped->reset(); } return success; @@ -587,7 +608,7 @@ namespace Soundux::Objects bool success = false; await(PulseApi::context_set_sink_input_mute( - context, loopBackSink, state, + context, *loopBackSink, state, +[]([[maybe_unused]] pa_context *ctx, int success, void *userData) { *reinterpret_cast(userData) = success; }, @@ -603,7 +624,7 @@ namespace Soundux::Objects bool PulseAudio::isCurrentlyPassingThrough() { - return movedPassthroughApplication != nullptr; + return **movedPassthroughApplication != nullptr; } bool PulseAudio::switchOnConnectPresent() @@ -643,5 +664,17 @@ namespace Soundux::Objects }, nullptr)); } + bool PulseAudio::isRunningPipeWire() + { + //* The stuff we do here does is broken on pipewire-pulse, use the native backend instead. + if (serverName.find("PipeWire") != std::string::npos) + { + Fancy::fancy.logTime().message() + << "Detected PipeWire-Pulse, please use the native pipewire backend" << std::endl; + return true; + } + + return false; + } } // namespace Soundux::Objects #endif \ No newline at end of file diff --git a/src/helper/audio/linux/pulse/pulse.hpp b/src/helper/audio/linux/pulseaudio/pulseaudio.hpp similarity index 72% rename from src/helper/audio/linux/pulse/pulse.hpp rename to src/helper/audio/linux/pulseaudio/pulseaudio.hpp index 18e77bc..fdda10b 100644 --- a/src/helper/audio/linux/pulse/pulse.hpp +++ b/src/helper/audio/linux/pulseaudio/pulseaudio.hpp @@ -2,6 +2,8 @@ #include "../backend.hpp" #include "forward.hpp" #include +#include +#include namespace Soundux { @@ -27,25 +29,29 @@ namespace Soundux class PulseAudio : public AudioBackend { + friend class AudioBackend; + + private: pa_context *context; pa_mainloop *mainloop; pa_mainloop_api *mainloopApi; //* ~= The modules we create =~ - std::uint32_t nullSink; - std::uint32_t loopBack; - std::uint32_t loopBackSink; + std::optional nullSink; + std::optional loopBack; + std::optional loopBackSink; - std::uint32_t passthrough; - std::uint32_t passthroughSink; - std::uint32_t passthroughLoopBack; + std::optional passthrough; + std::optional passthroughSink; + std::optional passthroughLoopBack; //* ~= ~~~~~~~~~~~~~~~~~~~~~ =~ + std::string serverName; std::string defaultSource; - std::shared_ptr movedApplication; - std::shared_ptr movedPassthroughApplication; - std::mutex movedAppMutex; + sxl::var_guard> movedApplication; + sxl::var_guard> movedPassthroughApplication; + std::mutex operationMutex; void unloadLeftOvers(); @@ -56,12 +62,15 @@ namespace Soundux void fixPlaybackApps(const std::vector> &); void fixRecordingApps(const std::vector> &); - public: - PulseAudio() = default; + protected: + bool setup() override; + + public: + //! Is not ran by default to avoid problems with switch-on-connect + bool loadModules(); - void setup() override; - void loadModules(); //! Is not ran by default to avoid problems with switch-on-connect void destroy() override; + bool isRunningPipeWire(); bool useAsDefault() override; bool revertDefault() override; diff --git a/src/helper/icons/forward.cpp b/src/helper/icons/forward.cpp new file mode 100644 index 0000000..a7ff414 --- /dev/null +++ b/src/helper/icons/forward.cpp @@ -0,0 +1,21 @@ +#include "forward.hpp" +#include +#include + +bool Soundux::LibWnck::setup() +{ + auto *libWnck = dlopen("libwnck-3.so", RTLD_LAZY); + if (libWnck) + { + getWindowPID = reinterpret_cast(dlsym(libWnck, "wnck_window_get_pid")); + getWindowIcon = reinterpret_cast(dlsym(libWnck, "wnck_window_get_icon")); + forceUpdate = reinterpret_cast(dlsym(libWnck, "wnck_screen_force_update")); + getDefaultScreen = reinterpret_cast(dlsym(libWnck, "wnck_screen_get_default")); + getScreenWindows = reinterpret_cast(dlsym(libWnck, "wnck_screen_get_windows")); + + Fancy::fancy.logTime().success() << "LibWnck found - Icon support is enabled" << std::endl; + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/helper/icons/forward.hpp b/src/helper/icons/forward.hpp new file mode 100644 index 0000000..b2fd6f1 --- /dev/null +++ b/src/helper/icons/forward.hpp @@ -0,0 +1,19 @@ +#pragma once +#include + +namespace Soundux +{ + namespace LibWnck + { + struct Screen; + struct Window; + + inline Screen *(*getDefaultScreen)(); + inline void (*forceUpdate)(Screen *); + inline int (*getWindowPID)(Window *); + inline GList *(*getScreenWindows)(Screen *); + inline GdkPixbuf *(*getWindowIcon)(Window *); + + bool setup(); + } // namespace LibWnck +} // namespace Soundux \ No newline at end of file diff --git a/src/helper/icons/icons.cpp b/src/helper/icons/icons.cpp index 5f267b3..c01c7c5 100644 --- a/src/helper/icons/icons.cpp +++ b/src/helper/icons/icons.cpp @@ -2,67 +2,94 @@ #include "icons.hpp" #include #include +#include +#include #include -#include #include +#include namespace Soundux::Objects { - void IconFetcher::setup() + bool IconFetcher::setup() { - auto *libWnck = dlopen("libwnck-3.so.0", RTLD_LAZY); - if (libWnck) - { - isAvailable = true; - Fancy::fancy.logTime().success() << "LibWnck found - Icon support is enabled" << std::endl; - - Lib::wnckGetDefaultScreen = - reinterpret_cast(dlsym(libWnck, "wnck_screen_get_default")); - Lib::wnckForceUpdate = - reinterpret_cast(dlsym(libWnck, "wnck_screen_force_update")); - Lib::wnckGetScreenWindows = - reinterpret_cast(dlsym(libWnck, "wnck_screen_get_windows")); - Lib::wnckGetWindowPID = - reinterpret_cast(dlsym(libWnck, "wnck_window_get_pid")); - Lib::wnckGetWindowIcon = - reinterpret_cast(dlsym(libWnck, "wnck_window_get_icon")); - } - else + if (!LibWnck::setup()) { Fancy::fancy.logTime().message() << "LibWnck was not found - Icon support is not available" << std::endl; - return; + return false; } gdk_init(nullptr, nullptr); - screen = Lib::wnckGetDefaultScreen(); + screen = LibWnck::getDefaultScreen(); + if (!screen) { Fancy::fancy.logTime().warning() << "Failed to get default screen!" << std::endl; + return false; } + + return true; + } + std::optional IconFetcher::createInstance() + { + IconFetcher instance; + if (instance.setup()) + { + return instance; + } + + Fancy::fancy.logTime().failure() << "Could not create IconFetcher instance" << std::endl; + return std::nullopt; + } + std::optional IconFetcher::getPpid(int pid) + { + std::filesystem::path path("/proc/" + std::to_string(pid)); + if (std::filesystem::exists(path)) + { + auto statusFile = path / "status"; + if (std::filesystem::exists(statusFile) && std::filesystem::is_regular_file(statusFile)) + { + static const std::regex pidRegex(R"(PPid:(\ +|\t)(\d+))"); + std::ifstream statusStream(statusFile); + + std::string line; + std::smatch match; + while (std::getline(statusStream, line)) + { + if (std::regex_search(line, match, pidRegex)) + { + if (match[2].matched) + { + return std::stoi(match[2]); + } + } + } + + Fancy::fancy.logTime().warning() << "Failed to find ppid of " >> pid << std::endl; + return std::nullopt; + } + } + + Fancy::fancy.logTime().warning() << "Failed to find ppid of " >> pid << ", process does not exist" << std::endl; + return std::nullopt; } std::optional IconFetcher::getIcon(int pid, bool recursive) { - if (!isAvailable) - { - return std::nullopt; - } - if (cache.find(pid) != cache.end()) { return cache.at(pid); } - Lib::wnckForceUpdate(screen); - auto *windows = Lib::wnckGetScreenWindows(screen); + LibWnck::forceUpdate(screen); + auto *windows = LibWnck::getScreenWindows(screen); for (auto *item = windows; item != nullptr; item = item->next) { - auto *window = reinterpret_cast(item->data); - auto _pid = Lib::wnckGetWindowPID(window); + auto *window = reinterpret_cast(item->data); + auto _pid = LibWnck::getWindowPID(window); if (pid == _pid) { - auto *icon = Lib::wnckGetWindowIcon(window); + auto *icon = LibWnck::getWindowIcon(window); gsize size = 4096; auto *iconBuff = new gchar[size]; @@ -90,7 +117,7 @@ namespace Soundux::Objects if (recursive) { - auto parentProcess = Helpers::getPpid(pid); + auto parentProcess = getPpid(pid); if (parentProcess) { auto recursiveResult = getIcon(*parentProcess, false); diff --git a/src/helper/icons/icons.hpp b/src/helper/icons/icons.hpp index 31c33df..18ec0ce 100644 --- a/src/helper/icons/icons.hpp +++ b/src/helper/icons/icons.hpp @@ -1,32 +1,28 @@ #if defined(__linux__) #pragma once -#include +#include "forward.hpp" #include +#include #include #include namespace Soundux { - namespace Lib - { - struct WnckScreen; - struct WnckWindow; - inline WnckScreen *(*wnckGetDefaultScreen)(); - inline void (*wnckForceUpdate)(WnckScreen *); - inline int (*wnckGetWindowPID)(WnckWindow *); - inline GList *(*wnckGetScreenWindows)(WnckScreen *); - inline GdkPixbuf *(*wnckGetWindowIcon)(WnckWindow *); - } // namespace Lib namespace Objects { class IconFetcher { - Lib::WnckScreen *screen; - bool isAvailable = false; + LibWnck::Screen *screen; std::map cache; + private: + IconFetcher() = default; + + bool setup(); + std::optional getPpid(int pid); + public: - void setup(); + static std::optional createInstance(); std::optional getIcon(int pid, bool recursive = true); }; } // namespace Objects diff --git a/src/helper/misc/misc.cpp b/src/helper/misc/misc.cpp index 4148f37..22999be 100644 --- a/src/helper/misc/misc.cpp +++ b/src/helper/misc/misc.cpp @@ -71,40 +71,6 @@ namespace Soundux WideCharToMultiByte(65001, 0, s.c_str(), -1, &out[0], wsz, nullptr, nullptr); return out; } -#endif -#if defined(__linux__) - std::optional Helpers::getPpid(int pid) - { - std::filesystem::path path("/proc/" + std::to_string(pid)); - if (std::filesystem::exists(path)) - { - auto statusFile = path / "status"; - if (std::filesystem::exists(statusFile) && std::filesystem::is_regular_file(statusFile)) - { - static const std::regex pidRegex(R"(PPid:(\ +|\t)(\d+))"); - std::ifstream statusStream(statusFile); - - std::string line; - std::smatch match; - while (std::getline(statusStream, line)) - { - if (std::regex_search(line, match, pidRegex)) - { - if (match[2].matched) - { - return std::stoi(match[2]); - } - } - } - - Fancy::fancy.logTime().warning() << "Failed to find ppid of " >> pid << std::endl; - return std::nullopt; - } - } - - Fancy::fancy.logTime().warning() << "Failed to find ppid of " >> pid << ", process does not exist" << std::endl; - return std::nullopt; - } #endif bool Helpers::deleteFile(const std::string &path, bool trash) { diff --git a/src/helper/misc/misc.hpp b/src/helper/misc/misc.hpp index 8bf3c57..2b432dd 100644 --- a/src/helper/misc/misc.hpp +++ b/src/helper/misc/misc.hpp @@ -8,18 +8,14 @@ namespace Soundux namespace Helpers { #if defined(_WIN32) - std::wstring widen(const std::string &s); - std::string narrow(const std::wstring &s); -#endif -#if defined(__linux__) - std::optional getPpid(int pid); + std::wstring widen(const std::string &); + std::string narrow(const std::wstring &); #endif + bool deleteFile(const std::string &, bool = true); - bool deleteFile(const std::string &path, bool trash = true); - - bool run(const std::string &command); - std::pair getResultCompact(const std::string &command); - std::pair, bool> getResult(const std::string &command); + bool run(const std::string &); + std::pair getResultCompact(const std::string &); + std::pair, bool> getResult(const std::string &); } // namespace Helpers } // namespace Soundux \ No newline at end of file diff --git a/src/helper/queue/queue.cpp b/src/helper/queue/queue.cpp new file mode 100644 index 0000000..b56e938 --- /dev/null +++ b/src/helper/queue/queue.cpp @@ -0,0 +1,51 @@ +#include "queue.hpp" + +namespace Soundux::Objects +{ + void Queue::handle() + { + std::unique_lock lock(queueMutex); + while (!stop) + { + 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); + } + } + } + + void Queue::push_unique(std::uint64_t id, std::function function) + { + { + std::lock_guard lock(queueMutex); + if (queue.find(id) != queue.end()) + { + return; + } + } + + std::unique_lock lock(queueMutex); + queue.emplace(id, std::move(function)); + lock.unlock(); + + cv.notify_one(); + } + + Queue::Queue() + { + handler = std::thread([this] { handle(); }); + } + Queue::~Queue() + { + stop = true; + cv.notify_all(); + handler.join(); + } +} // namespace Soundux::Objects \ No newline at end of file diff --git a/src/helper/queue/queue.hpp b/src/helper/queue/queue.hpp new file mode 100644 index 0000000..203c475 --- /dev/null +++ b/src/helper/queue/queue.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace Soundux +{ + namespace Objects + { + class Queue + { + std::map> queue; + std::mutex queueMutex; + + std::condition_variable cv; + std::atomic stop; + std::thread handler; + + private: + void handle(); + + public: + Queue(); + ~Queue(); + + void push_unique(std::uint64_t, std::function); + }; + } // namespace Objects +} // namespace Soundux \ No newline at end of file diff --git a/src/helper/threads/processing.hpp b/src/helper/threads/processing.hpp deleted file mode 100644 index 52a8131..0000000 --- a/src/helper/threads/processing.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include - -namespace Soundux -{ - namespace Objects - { - template class ProcessingQueue - { - struct Item - { - std::shared_ptr> handled; - std::function callback; - UID identifier; - }; - - private: - std::queue queue; - std::mutex queueMutex; - - std::vector unhandled; - std::shared_mutex unhandledMutex; - - std::condition_variable cv; - std::atomic stop; - std::thread handler; - - void handle() - { - std::unique_lock lock(queueMutex); - while (!stop) - { - cv.wait(lock, [&]() { return !queue.empty() || stop; }); - while (!queue.empty()) - { - auto front = std::move(queue.front()); - queue.pop(); - - lock.unlock(); - - front.callback(); - if (front.handled) - { - front.handled->store(true); - } - auto found = std::find(unhandled.begin(), unhandled.end(), front.identifier); - if (found != unhandled.end()) - { - unhandled.erase(found); - } - - lock.lock(); - } - } - } - - public: - void push_unique(const UID &identifier, const std::function &item) - { - { - std::shared_lock lock(unhandledMutex); - if (std::find(unhandled.begin(), unhandled.end(), identifier) != unhandled.end()) - { - return; - } - } - - std::unique_lock lock(queueMutex); - std::unique_lock lock_(unhandledMutex); - - unhandled.emplace_back(identifier); - - queue.emplace(Item{nullptr, item, identifier}); - lock.unlock(); - cv.notify_one(); - } - - ProcessingQueue() - { - handler = std::thread([this] { handle(); }); - } - ~ProcessingQueue() - { - stop = true; - cv.notify_all(); - handler.join(); - } - }; - } // namespace Objects -} // namespace Soundux \ No newline at end of file diff --git a/src/helper/ytdl/youtube-dl.cpp b/src/helper/ytdl/youtube-dl.cpp index 231a290..355bac1 100644 --- a/src/helper/ytdl/youtube-dl.cpp +++ b/src/helper/ytdl/youtube-dl.cpp @@ -44,7 +44,7 @@ namespace Soundux::Objects if (json.is_discarded()) { Fancy::fancy.logTime().warning() << "Failed to parse youtube-dl information" << std::endl; - Globals::gGui->onError(ErrorCode::YtdlInvalidJson); + Globals::gGui->onError(Enums::ErrorCode::YtdlInvalidJson); return std::nullopt; } @@ -66,14 +66,14 @@ namespace Soundux::Objects } Fancy::fancy.logTime().warning() << "Failed to get info from youtube-dl" << std::endl; - Globals::gGui->onError(ErrorCode::YtdlInformationUnknown); + Globals::gGui->onError(Enums::ErrorCode::YtdlInformationUnknown); return std::nullopt; } bool YoutubeDl::download(const std::string &url) { if (!isAvailable) { - Globals::gGui->onError(ErrorCode::YtdlNotFound); + Globals::gGui->onError(Enums::ErrorCode::YtdlNotFound); return false; } @@ -85,7 +85,7 @@ namespace Soundux::Objects if (!std::regex_match(url, urlRegex)) { Fancy::fancy.logTime().warning() << "Bad url " >> url << std::endl; - Globals::gGui->onError(ErrorCode::YtdlInvalidUrl); + Globals::gGui->onError(Enums::ErrorCode::YtdlInvalidUrl); return false; } @@ -121,7 +121,7 @@ namespace Soundux::Objects return rtn; } - Globals::gGui->onError(ErrorCode::TabDoesNotExist); + Globals::gGui->onError(Enums::ErrorCode::TabDoesNotExist); return false; } void YoutubeDl::killDownload() diff --git a/src/main.cpp b/src/main.cpp index 7433143..aee3fb5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,10 @@ -#include "core/global/globals.hpp" -#include "helper/exceptions/crashhandler.hpp" -#include "ui/impl/webview/webview.hpp" #include +#include +#include #include - -#if defined(__linux__) -#include -#include -#include -#endif +#include +#include +#include #if defined(_WIN32) #include "../assets/icon.h" @@ -18,6 +14,10 @@ int __stdcall WinMain([[maybe_unused]] HINSTANCE hInstrance, [[maybe_unused]] HI int main() #endif { + using namespace Soundux::Globals; // NOLINT + using namespace Soundux::Objects; // NOLINT + using namespace Soundux::Enums; // NOLINT + #if defined(_WIN32) if (std::getenv("SOUNDUX_DEBUG")) { @@ -46,60 +46,46 @@ int main() return 1; } - Soundux::Globals::gConfig.load(); - Soundux::Globals::gData.set(Soundux::Globals::gConfig.data); - Soundux::Globals::gSettings = Soundux::Globals::gConfig.settings; + gConfig.load(); + gData.set(gConfig.data); + gSettings = gConfig.settings; #if defined(__linux__) - Soundux::Globals::gIcons.setup(); - - if (Soundux::Globals::gSettings.audioBackend == Soundux::Objects::BackendType::PulseAudio) - { - Soundux::Globals::gAudioBackend = std::make_shared(); - Soundux::Globals::gAudioBackend->setup(); - - auto pulseBackend = std::dynamic_pointer_cast(Soundux::Globals::gAudioBackend); - - if (pulseBackend && !pulseBackend->switchOnConnectPresent()) - { - pulseBackend->loadModules(); - } - } - else - { - Soundux::Globals::gSettings.audioBackend = Soundux::Objects::BackendType::PipeWire; - Soundux::Globals::gAudioBackend = std::make_shared(); - Soundux::Globals::gAudioBackend->setup(); - } - + gIcons = gIcons->createInstance(); + gAudioBackend = AudioBackend::createInstance(gSettings.audioBackend); #endif - Soundux::Globals::gAudio.setup(); - Soundux::Globals::gYtdl.setup(); + + gAudio.setup(); + gYtdl.setup(); + #if defined(__linux__) - if (Soundux::Globals::gSettings.audioBackend == Soundux::Objects::BackendType::PulseAudio && - Soundux::Globals::gConfig.settings.useAsDefaultDevice) + if (gAudioBackend && gSettings.audioBackend == BackendType::PulseAudio && gConfig.settings.useAsDefaultDevice) { - Soundux::Globals::gAudioBackend->useAsDefault(); + gAudioBackend->useAsDefault(); } #endif - Soundux::Globals::gGui = std::make_unique(); - Soundux::Globals::gGui->setup(); + gGui = std::make_unique(); + gGui->setup(); #if defined(_WIN32) HICON hIcon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_ICON1)); SendMessage(GetActiveWindow(), WM_SETICON, ICON_SMALL, reinterpret_cast(hIcon)); SendMessage(GetActiveWindow(), WM_SETICON, ICON_BIG, reinterpret_cast(hIcon)); #endif - Soundux::Globals::gGui->mainLoop(); - Soundux::Globals::gAudio.destroy(); + gGui->mainLoop(); + + gAudio.destroy(); #if defined(__linux__) - Soundux::Globals::gAudioBackend->destroy(); + if (gAudioBackend) + { + gAudioBackend->destroy(); + } #endif - Soundux::Globals::gConfig.data.set(Soundux::Globals::gData); - Soundux::Globals::gConfig.settings = Soundux::Globals::gSettings; - Soundux::Globals::gConfig.save(); + gConfig.data.set(gData); + gConfig.settings = gSettings; + gConfig.save(); return 0; } \ No newline at end of file diff --git a/src/ui/impl/webview/webview.cpp b/src/ui/impl/webview/webview.cpp index 02ea1b9..f921e2c 100644 --- a/src/ui/impl/webview/webview.cpp +++ b/src/ui/impl/webview/webview.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -101,17 +101,18 @@ namespace Soundux::Objects webview->expose(Webview::Function("requestHotkey", [](bool state) { Globals::gHotKeys.shouldNotify(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 getHotkeySequence(keys); })); + webview->expose(Webview::Function("getHotkeySequence", [this](const std::vector &keys) { + return Globals::gHotKeys.getKeySequence(keys); + })); 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( "moveTabs", [this](const std::vector &newOrder) { return changeTabOrder(newOrder); })); - webview->expose(Webview::Function("markFavorite", [this](const std::uint32_t &id, bool favourite) { - markFavourite(id, favourite); - return getFavouriteIds(); + webview->expose(Webview::Function("markFavorite", [this](const std::uint32_t &id, bool favorite) { + Globals::gData.markFavorite(id, favorite); + return Globals::gData.getFavoriteIds(); })); - webview->expose(Webview::Function("getFavorites", [this] { return getFavouriteIds(); })); + webview->expose(Webview::Function("getFavorites", [this] { return Globals::gData.getFavoriteIds(); })); webview->expose(Webview::Function("isYoutubeDLAvailable", []() { return Globals::gYtdl.available(); })); webview->expose( Webview::AsyncFunction("getYoutubeDLInfo", [this](Webview::Promise promise, const std::string &url) { @@ -131,7 +132,7 @@ namespace Soundux::Objects webview->expose(Webview::Function("getSystemInfo", []() -> std::string { return SystemInfo::getSummary(); })); webview->expose(Webview::AsyncFunction( "updateCheck", [this](Webview::Promise promise) { promise.resolve(VersionCheck::getStatus()); })); - webview->expose(Webview::Function("isOnFavorites", [this](bool state) { isOnFavorites(state); })); + webview->expose(Webview::Function("isOnFavorites", [this](bool state) { setIsOnFavorites(state); })); webview->expose(Webview::Function("deleteSound", [this](std::uint32_t id) { return deleteSound(id); })); #if !defined(__linux__) @@ -337,7 +338,7 @@ namespace Soundux::Objects { webview->callFunction(Webview::JavaScriptFunction("window.downloadProgressed", progress, eta)); } - void WebView::onError(const ErrorCode &error) + void WebView::onError(const Enums::ErrorCode &error) { webview->callFunction(Webview::JavaScriptFunction("window.onError", static_cast(error))); } diff --git a/src/ui/impl/webview/webview.hpp b/src/ui/impl/webview/webview.hpp index e34690c..81ff785 100644 --- a/src/ui/impl/webview/webview.hpp +++ b/src/ui/impl/webview/webview.hpp @@ -22,7 +22,7 @@ namespace Soundux void onSoundFinished(const PlayingSound &sound) override; void onHotKeyReceived(const std::vector &keys) override; - void onError(const ErrorCode &error) override; + void onError(const Enums::ErrorCode &error) override; void onSoundPlayed(const PlayingSound &sound) override; void onSoundProgressed(const PlayingSound &sound) override; void onDownloadProgressed(float progress, const std::string &eta) override; diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 0ab8c16..20ea6cd 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include @@ -101,7 +101,7 @@ namespace Soundux::Objects Fancy::fancy.logTime().warning() << "Path " >> tab.path << " does not exist" << std::endl; return {}; } - std::optional Window::addTab() // NOLINT + std::optional Window::addTab() { nfdnchar_t *outpath = {}; auto result = NFD::PickFolder(outpath, nullptr); @@ -132,7 +132,7 @@ namespace Soundux::Objects return tab; } Fancy::fancy.logTime().warning() << "Selected Folder does not exist!" << std::endl; - onError(ErrorCode::FolderDoesNotExist); + onError(Enums::ErrorCode::FolderDoesNotExist); } return std::nullopt; } @@ -148,7 +148,10 @@ namespace Soundux::Objects } if (Globals::gSettings.muteDuringPlayback) { - Globals::gAudioBackend->muteInput(true); + if (Globals::gAudioBackend) + { + Globals::gAudioBackend->muteInput(true); + } } if (!Globals::gSettings.pushToTalkKeys.empty()) { @@ -165,7 +168,7 @@ namespace Soundux::Objects { return *playingSound; } - if (!Globals::gSettings.output.empty()) + if (!Globals::gSettings.output.empty() && Globals::gAudioBackend) { auto moveSuccess = Globals::gAudioBackend->inputSoundTo( Globals::gAudioBackend->getRecordingApp(Globals::gSettings.output)); @@ -179,7 +182,7 @@ namespace Soundux::Objects Fancy::fancy.logTime().failure() << "Failed to move Application " << Globals::gSettings.output << " to soundux sink for sound " << id << std::endl; - onError(ErrorCode::FailedToMoveToSink); + onError(Enums::ErrorCode::FailedToMoveToSink); return std::nullopt; } @@ -190,12 +193,12 @@ namespace Soundux::Objects else { Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl; - onError(ErrorCode::SoundNotFound); + onError(Enums::ErrorCode::SoundNotFound); return std::nullopt; } Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl; - onError(ErrorCode::FailedToPlay); + onError(Enums::ErrorCode::FailedToPlay); return std::nullopt; } #else @@ -247,12 +250,12 @@ namespace Soundux::Objects else { Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl; - onError(ErrorCode::SoundNotFound); + onError(Enums::ErrorCode::SoundNotFound); return std::nullopt; } Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl; - onError(ErrorCode::FailedToPlay); + onError(Enums::ErrorCode::FailedToPlay); return std::nullopt; } #endif @@ -286,7 +289,7 @@ namespace Soundux::Objects } Fancy::fancy.logTime().warning() << "Failed to pause sound " << id << std::endl; - onError(ErrorCode::FailedToPause); + onError(Enums::ErrorCode::FailedToPause); return std::nullopt; } std::optional Window::resumeSound(const std::uint32_t &id) @@ -319,7 +322,7 @@ namespace Soundux::Objects } Fancy::fancy.logTime().warning() << "Failed to resume sound " << id << std::endl; - onError(ErrorCode::FailedToResume); + onError(Enums::ErrorCode::FailedToResume); return std::nullopt; } std::optional Window::seekSound(const std::uint32_t &id, std::uint64_t seekTo) @@ -352,7 +355,7 @@ namespace Soundux::Objects } Fancy::fancy.logTime().warning() << "Failed to seek sound " << id << " to " << seekTo << std::endl; - onError(ErrorCode::FailedToSeek); + onError(Enums::ErrorCode::FailedToSeek); return std::nullopt; } std::optional Window::repeatSound(const std::uint32_t &id, bool shouldRepeat) @@ -386,7 +389,7 @@ namespace Soundux::Objects Fancy::fancy.logTime().failure() << "Failed to set repeat-state of sound " << id << " to " << shouldRepeat << std::endl; - onError(ErrorCode::FailedToRepeat); + onError(Enums::ErrorCode::FailedToRepeat); return std::nullopt; } std::vector Window::removeTab(const std::uint32_t &id) @@ -435,15 +438,18 @@ namespace Soundux::Objects onAllSoundsFinished(); #if defined(__linux__) - if (!Globals::gAudioBackend->stopSoundInput()) + if (Globals::gAudioBackend) { - Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; - onError(ErrorCode::FailedToMoveBack); - } - if (!Globals::gAudioBackend->stopPassthrough()) - { - Fancy::fancy.logTime().failure() << "Failed to move back current passthrough application" << std::endl; - onError(ErrorCode::FailedToMoveBackPassthrough); + if (!Globals::gAudioBackend->stopSoundInput()) + { + Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; + onError(Enums::ErrorCode::FailedToMoveBack); + } + if (!Globals::gAudioBackend->stopPassthrough()) + { + Fancy::fancy.logTime().failure() << "Failed to move back current passthrough application" << std::endl; + onError(Enums::ErrorCode::FailedToMoveBackPassthrough); + } } #endif } @@ -453,75 +459,66 @@ namespace Soundux::Objects if (settings.audioBackend != Globals::gSettings.audioBackend) { stopSounds(true); - Globals::gAudioBackend->destroy(); - if (settings.audioBackend == BackendType::PulseAudio) - { - Soundux::Globals::gAudioBackend = std::make_shared(); - Soundux::Globals::gAudioBackend->setup(); - auto pulseBackend = - std::dynamic_pointer_cast(Soundux::Globals::gAudioBackend); - if (pulseBackend) - { - pulseBackend->loadModules(); - } - } - else + if (Globals::gAudioBackend) { - Soundux::Globals::gAudioBackend = std::make_shared(); - Soundux::Globals::gAudioBackend->setup(); + Globals::gAudioBackend->destroy(); } + Globals::gAudioBackend = AudioBackend::createInstance(settings.audioBackend); Globals::gAudio.setup(); } - if (!Globals::gAudio.getPlayingSounds().empty()) + if (Globals::gAudioBackend) { - if (settings.muteDuringPlayback && !Globals::gSettings.muteDuringPlayback) + if (!Globals::gAudio.getPlayingSounds().empty()) { - if (!Globals::gAudioBackend->muteInput(true)) + if (settings.muteDuringPlayback && !Globals::gSettings.muteDuringPlayback) { - Fancy::fancy.logTime().failure() << "Failed to mute input" << std::endl; + if (!Globals::gAudioBackend->muteInput(true)) + { + Fancy::fancy.logTime().failure() << "Failed to mute input" << std::endl; + } + } + else if (!settings.muteDuringPlayback && Globals::gSettings.muteDuringPlayback) + { + if (!Globals::gAudioBackend->muteInput(false)) + { + Fancy::fancy.logTime().failure() << "Failed to un-mute input" << std::endl; + } } } - else if (!settings.muteDuringPlayback && Globals::gSettings.muteDuringPlayback) + if (!settings.useAsDefaultDevice && Globals::gSettings.useAsDefaultDevice) { - if (!Globals::gAudioBackend->muteInput(false)) + if (!Globals::gAudioBackend->revertDefault()) { - Fancy::fancy.logTime().failure() << "Failed to un-mute input" << std::endl; + Fancy::fancy.logTime().failure() << "Failed to move back default source" << std::endl; + onError(Enums::ErrorCode::FailedToRevertDefaultSource); } } - } - if (!settings.useAsDefaultDevice && Globals::gSettings.useAsDefaultDevice) - { - if (!Globals::gAudioBackend->revertDefault()) + else if (settings.useAsDefaultDevice && !Globals::gSettings.useAsDefaultDevice) { - Fancy::fancy.logTime().failure() << "Failed to move back default source" << std::endl; - onError(ErrorCode::FailedToRevertDefaultSource); + Globals::gSettings.output = ""; + if (!Globals::gAudioBackend->stopSoundInput()) + { + Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; + onError(Enums::ErrorCode::FailedToMoveBack); + } + if (!Globals::gAudioBackend->useAsDefault()) + { + onError(Enums::ErrorCode::FailedToSetDefaultSource); + } } - } - else if (settings.useAsDefaultDevice && !Globals::gSettings.useAsDefaultDevice) - { - Globals::gSettings.output = ""; - if (!Globals::gAudioBackend->stopSoundInput()) + if (settings.output != Globals::gSettings.output) { - Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; - onError(ErrorCode::FailedToMoveBack); - } - if (!Globals::gAudioBackend->useAsDefault()) - { - onError(ErrorCode::FailedToSetDefaultSource); - } - } - if (settings.output != Globals::gSettings.output) - { - if (!Globals::gAudioBackend->stopSoundInput()) - { - Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; - onError(ErrorCode::FailedToMoveBack); - } - if (!settings.output.empty() && !Globals::gAudio.getPlayingSounds().empty()) - { - Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(settings.output)); + if (!Globals::gAudioBackend->stopSoundInput()) + { + Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; + onError(Enums::ErrorCode::FailedToMoveBack); + } + if (!settings.output.empty() && !Globals::gAudio.getPlayingSounds().empty()) + { + Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(settings.output)); + } } } #endif @@ -544,7 +541,7 @@ namespace Soundux::Objects } } Fancy::fancy.logTime().failure() << "Failed to refresh tab " << id << " tab does not exist" << std::endl; - onError(ErrorCode::TabDoesNotExist); + onError(Enums::ErrorCode::TabDoesNotExist); return std::nullopt; } std::optional Window::setHotkey(const std::uint32_t &id, const std::vector &hotkeys) @@ -557,13 +554,9 @@ namespace Soundux::Objects } Fancy::fancy.logTime().failure() << "Failed to set hotkey for sound " << id << ", sound does not exist" << std::endl; - onError(ErrorCode::FailedToSetHotkey); + onError(Enums::ErrorCode::FailedToSetHotkey); return std::nullopt; } - std::string Window::getHotkeySequence(const std::vector &hotkeys) - { - return Globals::gHotKeys.getKeySequence(hotkeys); - } std::vector Window::changeTabOrder(const std::vector &newOrder) { std::vector newTabs; @@ -583,33 +576,41 @@ namespace Soundux::Objects //* once. The backend (gPulse.getRecordingStreams()) will work with multiple instances, so we need to filter out //* duplicates here. - auto streams = Globals::gAudioBackend->getRecordingApps(); std::vector> uniqueStreams; - for (auto &stream : streams) - { - auto item = std::find_if(std::begin(uniqueStreams), std::end(uniqueStreams), - [&](const auto &_stream) { return stream->name == _stream->name; }); - if (stream && item == std::end(uniqueStreams)) - { - auto iconStream = std::make_shared(*stream); - if (auto pulseApp = std::dynamic_pointer_cast(stream); pulseApp) - { - auto icon = Soundux::Globals::gIcons.getIcon(static_cast(pulseApp->pid)); - if (icon) - { - iconStream->appIcon = *icon; - } - } - else if (auto pipeWireApp = std::dynamic_pointer_cast(stream); pipeWireApp) - { - auto icon = Soundux::Globals::gIcons.getIcon(static_cast(pipeWireApp->pid)); - if (icon) - { - iconStream->appIcon = *icon; - } - } - uniqueStreams.emplace_back(iconStream); + if (Globals::gAudioBackend) + { + auto streams = Globals::gAudioBackend->getRecordingApps(); + for (auto &stream : streams) + { + auto item = std::find_if(std::begin(uniqueStreams), std::end(uniqueStreams), + [&](const auto &_stream) { return stream->name == _stream->name; }); + if (stream && item == std::end(uniqueStreams)) + { + auto iconStream = std::make_shared(*stream); + if (Globals::gIcons) + { + if (auto pulseApp = std::dynamic_pointer_cast(stream); pulseApp) + { + auto icon = Soundux::Globals::gIcons->getIcon(static_cast(pulseApp->pid)); + if (icon) + { + iconStream->appIcon = *icon; + } + } + else if (auto pipeWireApp = std::dynamic_pointer_cast(stream); + pipeWireApp) + { + auto icon = Soundux::Globals::gIcons->getIcon(static_cast(pipeWireApp->pid)); + if (icon) + { + iconStream->appIcon = *icon; + } + } + } + + uniqueStreams.emplace_back(iconStream); + } } } @@ -617,34 +618,42 @@ namespace Soundux::Objects } std::vector> Window::getPlayback() { - auto streams = Globals::gAudioBackend->getPlaybackApps(); std::vector> uniqueStreams; - for (auto &stream : streams) + if (Globals::gAudioBackend) { - auto item = std::find_if(std::begin(uniqueStreams), std::end(uniqueStreams), - [&](const auto &_stream) { return stream->name == _stream->name; }); - if (stream && item == std::end(uniqueStreams)) - { - auto iconStream = std::make_shared(*stream); - if (auto pulseApp = std::dynamic_pointer_cast(stream); pulseApp) - { - auto icon = Soundux::Globals::gIcons.getIcon(static_cast(pulseApp->pid)); - if (icon) - { - iconStream->appIcon = *icon; - } - } - if (auto pipeWireApp = std::dynamic_pointer_cast(stream); pipeWireApp) - { - auto icon = Soundux::Globals::gIcons.getIcon(static_cast(pipeWireApp->pid)); - if (icon) - { - iconStream->appIcon = *icon; - } - } + auto streams = Globals::gAudioBackend->getPlaybackApps(); - uniqueStreams.emplace_back(iconStream); + for (auto &stream : streams) + { + auto item = std::find_if(std::begin(uniqueStreams), std::end(uniqueStreams), + [&](const auto &_stream) { return stream->name == _stream->name; }); + if (stream && item == std::end(uniqueStreams)) + { + auto iconStream = std::make_shared(*stream); + + if (Globals::gIcons) + { + if (auto pulseApp = std::dynamic_pointer_cast(stream); pulseApp) + { + auto icon = Soundux::Globals::gIcons->getIcon(static_cast(pulseApp->pid)); + if (icon) + { + iconStream->appIcon = *icon; + } + } + if (auto pipeWireApp = std::dynamic_pointer_cast(stream); pipeWireApp) + { + auto icon = Soundux::Globals::gIcons->getIcon(static_cast(pipeWireApp->pid)); + if (icon) + { + iconStream->appIcon = *icon; + } + } + } + + uniqueStreams.emplace_back(iconStream); + } } } @@ -652,37 +661,44 @@ namespace Soundux::Objects } bool Window::startPassthrough(const std::string &name) { - if (Globals::gSettings.output.empty() || - Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(Globals::gSettings.output))) + if (Globals::gAudioBackend) { - if (!Globals::gAudioBackend->passthroughFrom(Globals::gAudioBackend->getPlaybackApp(name))) + if (Globals::gSettings.output.empty() || + Globals::gAudioBackend->inputSoundTo( + Globals::gAudioBackend->getRecordingApp(Globals::gSettings.output))) { - Fancy::fancy.logTime().failure() - << "Failed to move application: " << name << " to passthrough" << std::endl; - onError(ErrorCode::FailedToStartPassthrough); - return false; + if (!Globals::gAudioBackend->passthroughFrom(Globals::gAudioBackend->getPlaybackApp(name))) + { + Fancy::fancy.logTime().failure() + << "Failed to move application: " << name << " to passthrough" << std::endl; + onError(Enums::ErrorCode::FailedToStartPassthrough); + return false; + } + return true; } - return true; } Fancy::fancy.logTime().failure() << "Failed to start passthrough for application: " << name << std::endl; - onError(ErrorCode::FailedToStartPassthrough); + onError(Enums::ErrorCode::FailedToStartPassthrough); return false; } void Window::stopPassthrough() { - if (Globals::gAudio.getPlayingSounds().empty()) + if (Globals::gAudioBackend) { - if (!Globals::gAudioBackend->stopSoundInput()) + if (Globals::gAudio.getPlayingSounds().empty()) { - Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; - onError(ErrorCode::FailedToMoveBack); + if (!Globals::gAudioBackend->stopSoundInput()) + { + Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; + onError(Enums::ErrorCode::FailedToMoveBack); + } + } + if (!Globals::gAudioBackend->stopPassthrough()) + { + Fancy::fancy.logTime().failure() << "Failed to move back current passthrough application" << std::endl; + onError(Enums::ErrorCode::FailedToMoveBackPassthrough); } - } - if (!Globals::gAudioBackend->stopPassthrough()) - { - Fancy::fancy.logTime().failure() << "Failed to move back current passthrough application" << std::endl; - onError(ErrorCode::FailedToMoveBackPassthrough); } } #else @@ -705,14 +721,6 @@ namespace Soundux::Objects onAllSoundsFinished(); } } - std::vector Window::getFavouriteIds() - { - return Globals::gData.getFavoriteIds(); - } - void Window::markFavourite(const std::uint32_t &id, bool favorite) - { - Globals::gData.markFavorite(id, favorite); - } void Window::onAllSoundsFinished() { if (!Globals::gSettings.pushToTalkKeys.empty()) @@ -721,16 +729,19 @@ namespace Soundux::Objects } #if defined(__linux__) - if (Globals::gSettings.muteDuringPlayback) + if (Globals::gAudioBackend) { - Globals::gAudioBackend->muteInput(false); - } - if (!Globals::gAudioBackend->isCurrentlyPassingThrough()) - { - if (!Globals::gAudioBackend->stopSoundInput()) + if (Globals::gSettings.muteDuringPlayback) { - Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; - onError(ErrorCode::FailedToMoveBack); + Globals::gAudioBackend->muteInput(false); + } + if (!Globals::gAudioBackend->isCurrentlyPassingThrough()) + { + if (!Globals::gAudioBackend->stopSoundInput()) + { + Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; + onError(Enums::ErrorCode::FailedToMoveBack); + } } } #endif @@ -742,7 +753,7 @@ namespace Soundux::Objects Globals::gHotKeys.pressKeys(Globals::gSettings.pushToTalkKeys); } } - void Window::isOnFavorites(bool state) + void Window::setIsOnFavorites(bool state) { Globals::gData.isOnFavorites = state; } @@ -753,14 +764,14 @@ namespace Soundux::Objects { if (!Helpers::deleteFile(sound->get().path, Globals::gSettings.deleteToTrash)) { - onError(ErrorCode::FailedToDelete); + onError(Enums::ErrorCode::FailedToDelete); return false; } return true; } Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl; - onError(ErrorCode::SoundNotFound); + onError(Enums::ErrorCode::SoundNotFound); return false; } diff --git a/src/ui/ui.hpp b/src/ui/ui.hpp index 2130989..327dc1e 100644 --- a/src/ui/ui.hpp +++ b/src/ui/ui.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #if defined(__linux__) #include @@ -46,53 +46,53 @@ namespace Soundux std::string muteDuringPlayback; } translations; + protected: virtual void onAllSoundsFinished(); - virtual void isOnFavorites(bool); - virtual void stopSounds(bool = false); - virtual bool stopSound(const std::uint32_t &); + protected: + virtual std::vector getTabContent(const Tab &) const; + +#if defined(__linux__) + virtual std::vector> getOutputs(); + virtual std::vector> getPlayback(); +#else + virtual std::vector getOutputs(); +#endif + + protected: + virtual void setIsOnFavorites(bool); + virtual void changeSettings(const Settings &); + virtual bool deleteSound(const std::uint32_t &); + + void stopPassthrough(); + virtual bool startPassthrough(const std::string &); + + protected: + virtual std::optional addTab(); virtual std::vector removeTab(const std::uint32_t &); virtual std::optional refreshTab(const std::uint32_t &); + virtual std::vector changeTabOrder(const std::vector &); + + protected: + virtual void stopSounds(bool = false); + virtual bool stopSound(const std::uint32_t &); + + protected: virtual std::optional playSound(const std::uint32_t &); virtual std::optional pauseSound(const std::uint32_t &); virtual std::optional resumeSound(const std::uint32_t &); 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::string getHotkeySequence(const std::vector &); - - virtual void changeSettings(const Settings &); - - virtual std::vector getFavouriteIds(); - virtual void markFavourite(const std::uint32_t &, bool); - - virtual std::optional addTab(); - virtual std::vector getTabContent(const Tab &) const; - virtual std::vector changeTabOrder(const std::vector &); - - virtual bool deleteSound(const std::uint32_t &); - -#if defined(__linux__) - virtual std::vector> getOutputs(); - virtual std::vector> getPlayback(); - - void stopPassthrough(); - virtual bool startPassthrough(const std::string &); - -#else - virtual std::vector getOutputs(); -#endif - public: virtual ~Window(); virtual void setup(); virtual void mainLoop() = 0; - virtual void onError(const ErrorCode &) = 0; - virtual void onSoundPlayed( - const PlayingSound &); //* This will be called when a sound is played through a hotkey. PlaySound - //* will be called before this gets called + virtual void onError(const Enums::ErrorCode &) = 0; + virtual void onSoundPlayed(const PlayingSound &); virtual void onSoundFinished(const PlayingSound &); virtual void onHotKeyReceived(const std::vector &); virtual void onSoundProgressed(const PlayingSound &) = 0;