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.
This commit is contained in:
Curve 2021-05-22 16:41:36 +02:00
parent bcfccf14ad
commit 48132d86b9
No known key found for this signature in database
GPG Key ID: 460F6C466BD35813
36 changed files with 989 additions and 899 deletions

3
.gitignore vendored
View File

@ -7,4 +7,5 @@ cmake-build-debug
analyze analyze
node_modules node_modules
yarn.lock yarn.lock
package.json package.json
compile_commands.json

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <core/global/objects.hpp> #include <core/objects/data.hpp>
#include <core/objects/settings.hpp>
#include <string> #include <string>
namespace Soundux namespace Soundux

61
src/core/enums/enums.hpp Normal file
View File

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

View File

@ -3,11 +3,13 @@
#if defined(__linux__) #if defined(__linux__)
#include <helper/audio/linux/backend.hpp> #include <helper/audio/linux/backend.hpp>
#endif #endif
#include "objects.hpp"
#include <core/config/config.hpp> #include <core/config/config.hpp>
#include <core/hotkeys/hotkeys.hpp> #include <core/hotkeys/hotkeys.hpp>
#include <core/objects/data.hpp>
#include <core/objects/objects.hpp>
#include <core/objects/settings.hpp>
#include <helper/icons/icons.hpp> #include <helper/icons/icons.hpp>
#include <helper/threads/processing.hpp> #include <helper/queue/queue.hpp>
#include <helper/ytdl/youtube-dl.hpp> #include <helper/ytdl/youtube-dl.hpp>
#include <memory> #include <memory>
#include <ui/ui.hpp> #include <ui/ui.hpp>
@ -20,15 +22,15 @@ namespace Soundux
inline Objects::Data gData; inline Objects::Data gData;
inline Objects::Audio gAudio; inline Objects::Audio gAudio;
#if defined(__linux__) #if defined(__linux__)
inline Objects::IconFetcher gIcons; inline std::optional<Objects::IconFetcher> gIcons;
inline std::shared_ptr<Objects::AudioBackend> gAudioBackend; inline std::shared_ptr<Objects::AudioBackend> gAudioBackend;
#endif #endif
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::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 Objects::ProcessingQueue<std::uintptr_t> gQueue;
/* Allows for fast & easy sound access, is populated on start up */ /* Allows for fast & easy sound access, is populated on start up */
inline sxl::var_guard<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gSounds; inline sxl::var_guard<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gSounds;

View File

@ -1,144 +0,0 @@
#pragma once
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <vector>
namespace nlohmann
{
template <typename, typename> 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<int> hotkeys;
std::uint64_t modifiedDate;
};
struct Tab
{
std::uint32_t id; //* Equal to index
std::string name;
std::string path;
std::vector<Sound> sounds;
};
struct Settings
{
SortMode sortMode = SortMode::ModifiedDate_Descending;
BackendType audioBackend = BackendType::PulseAudio;
ViewMode viewMode = ViewMode::List;
Theme theme = Theme::System;
std::vector<int> pushToTalkKeys;
std::vector<int> 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 <typename, typename> friend struct nlohmann::adl_serializer;
private:
std::vector<Tab> tabs;
public:
bool isOnFavorites = false;
int width = 1280, height = 720;
std::uint32_t soundIdCounter = 0;
std::vector<Tab> getTabs() const;
void setTabs(const std::vector<Tab> &);
std::optional<Tab> setTab(const std::uint32_t &, const Tab &);
Tab addTab(Tab);
void removeTabById(const std::uint32_t &);
std::optional<Tab> getTab(const std::uint32_t &) const;
std::optional<std::reference_wrapper<Sound>> getSound(const std::uint32_t &);
std::vector<Sound> getFavorites();
std::vector<std::uint32_t> getFavoriteIds();
void markFavorite(const std::uint32_t &, bool);
void set(const Data &other);
Data &operator=(const Data &other) = delete;
};
} // namespace Objects
} // namespace Soundux

View File

@ -1,8 +1,6 @@
#include "objects.hpp" #include "data.hpp"
#include "globals.hpp" #include <core/global/globals.hpp>
#include <fancy.hpp> #include <fancy.hpp>
#include <functional>
#include <optional>
namespace Soundux::Objects namespace Soundux::Objects
{ {

46
src/core/objects/data.hpp Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include "objects.hpp"
#include <cstdint>
#include <optional>
#include <vector>
namespace nlohmann
{
template <typename, typename> struct adl_serializer;
} // namespace nlohmann
namespace Soundux
{
namespace Objects
{
class Data
{
template <typename, typename> friend struct nlohmann::adl_serializer;
private:
std::vector<Tab> tabs;
public:
bool isOnFavorites = false;
int width = 1280, height = 720;
std::uint32_t soundIdCounter = 0;
std::vector<Tab> getTabs() const;
void setTabs(const std::vector<Tab> &);
std::optional<Tab> setTab(const std::uint32_t &, const Tab &);
Tab addTab(Tab);
void removeTabById(const std::uint32_t &);
std::optional<Tab> getTab(const std::uint32_t &) const;
std::optional<std::reference_wrapper<Sound>> getSound(const std::uint32_t &);
std::vector<Sound> getFavorites();
std::vector<std::uint32_t> getFavoriteIds();
void markFavorite(const std::uint32_t &, bool);
void set(const Data &other);
Data &operator=(const Data &other) = delete;
};
} // namespace Objects
} // namespace Soundux

View File

@ -0,0 +1,32 @@
#pragma once
#include <core/enums/enums.hpp>
#include <string>
#include <vector>
namespace Soundux
{
namespace Objects
{
struct AudioDevice;
struct Sound
{
std::uint32_t id;
std::string name;
std::string path;
bool isFavorite = false;
std::vector<int> hotkeys;
std::uint64_t modifiedDate;
};
struct Tab
{
std::uint32_t id; //* Equal to index
std::string name;
std::string path;
std::vector<Sound> sounds;
};
} // namespace Objects
} // namespace Soundux

View File

@ -0,0 +1,35 @@
#pragma once
#include <core/enums/enums.hpp>
#include <string>
#include <vector>
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<int> pushToTalkKeys;
std::vector<int> 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

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <core/global/objects.hpp> #include <core/objects/objects.hpp>
#include <cstdint> #include <cstdint>
#include <map> #include <map>
#include <memory> #include <memory>

View File

@ -1,96 +1,54 @@
#if defined(__linux__)
#include "backend.hpp" #include "backend.hpp"
#include "pipewire/pipewire.hpp"
#include "pulseaudio/pulseaudio.hpp"
#include <core/enums/enums.hpp>
#include <core/global/globals.hpp>
#include <fancy.hpp> #include <fancy.hpp>
#include <memory>
namespace Soundux::Objects namespace Soundux::Objects
{ {
void AudioBackend::setup() std::shared_ptr<AudioBackend> AudioBackend::createInstance(Enums::BackendType backend)
{ {
Fancy::fancy.logTime().warning() << "setup(): not implemented (Possibly using null-audiobackend)" << std::endl; std::shared_ptr<AudioBackend> instance;
} if (backend == Enums::BackendType::PulseAudio)
void AudioBackend::destroy() {
{ instance = std::shared_ptr<PulseAudio>(new PulseAudio()); // NOLINT
Fancy::fancy.logTime().warning() << "destroy(): not implemented (Possibly using null-audiobackend)" auto pulseInstance = std::dynamic_pointer_cast<PulseAudio>(instance);
<< 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;
}
bool AudioBackend::stopPassthrough() if (pulseInstance && pulseInstance->setup())
{ {
Fancy::fancy.logTime().warning() << "stopPassthrough(): not implemented (Possibly using null-audiobackend)" if (!pulseInstance->switchOnConnectPresent())
<< std::endl; {
return false; if (pulseInstance->loadModules())
} {
bool AudioBackend::isCurrentlyPassingThrough() return instance;
{ }
Fancy::fancy.logTime().warning() }
<< "isCurrentlyPassingThrough(): not implemented (Possibly using null-audiobackend)" << std::endl; else
return false; {
} return instance;
bool AudioBackend::passthroughFrom([[maybe_unused]] std::shared_ptr<PlaybackApp> app) // NOLINT }
{ }
Fancy::fancy.logTime().warning() << "passthroughFrom(): not implemented (Possibly using null-audiobackend)"
<< std::endl;
return false;
}
bool AudioBackend::stopSoundInput() if (pulseInstance && pulseInstance->isRunningPipeWire())
{ {
Fancy::fancy.logTime().warning() << "stopSoundInput(): not implemented (Possibly using null-audiobackend)" backend = Enums::BackendType::PipeWire;
<< std::endl; Globals::gSettings.audioBackend = backend;
return false; }
} }
bool AudioBackend::inputSoundTo([[maybe_unused]] std::shared_ptr<RecordingApp> app) // NOLINT if (backend == Enums::BackendType::PipeWire)
{ {
Fancy::fancy.logTime().warning() << "inputSoundTo(): not implemented (Possibly using null-audiobackend)" instance = std::shared_ptr<PipeWire>(new PipeWire()); // NOLINT
<< std::endl; if (instance->setup())
return false; {
} return instance;
}
}
std::shared_ptr<PlaybackApp> AudioBackend::getPlaybackApp([[maybe_unused]] const std::string &name) Fancy::fancy.logTime().failure() << "Failed to create AudioBackend instance" << std::endl;
{ Globals::gSettings.audioBackend = Enums::BackendType::None;
Fancy::fancy.logTime().warning() << "getPlaybackApp(): not implemented (Possibly using null-audiobackend)"
<< std::endl;
return nullptr; return nullptr;
} }
} // namespace Soundux::Objects
std::shared_ptr<RecordingApp> 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<std::shared_ptr<PlaybackApp>> AudioBackend::getPlaybackApps()
{
Fancy::fancy.logTime().warning() << "getPlaybackApps(): not implemented (Possibly using null-audiobackend)"
<< std::endl;
return {};
}
std::vector<std::shared_ptr<RecordingApp>> AudioBackend::getRecordingApps()
{
Fancy::fancy.logTime().warning() << "getRecordingApps(): not implemented (Possibly using null-audiobackend)"
<< std::endl;
return {};
}
} // namespace Soundux::Objects
#endif

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#if defined(__linux__) #if defined(__linux__)
#include <core/enums/enums.hpp>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@ -25,28 +26,31 @@ namespace Soundux
class AudioBackend class AudioBackend
{ {
public: protected:
virtual bool setup() = 0;
AudioBackend() = default; AudioBackend() = default;
virtual void setup(); public:
virtual void destroy(); static std::shared_ptr<AudioBackend> createInstance(Enums::BackendType);
virtual bool useAsDefault(); public:
virtual bool revertDefault(); virtual void destroy() = 0;
virtual bool muteInput(bool); virtual bool useAsDefault() = 0;
virtual bool revertDefault() = 0;
virtual bool muteInput(bool) = 0;
virtual bool stopPassthrough(); virtual bool stopPassthrough() = 0;
virtual bool isCurrentlyPassingThrough(); virtual bool isCurrentlyPassingThrough() = 0;
virtual bool passthroughFrom(std::shared_ptr<PlaybackApp>); virtual bool passthroughFrom(std::shared_ptr<PlaybackApp>) = 0;
virtual bool stopSoundInput(); virtual bool stopSoundInput() = 0;
virtual bool inputSoundTo(std::shared_ptr<RecordingApp>); virtual bool inputSoundTo(std::shared_ptr<RecordingApp>) = 0;
virtual std::shared_ptr<PlaybackApp> getPlaybackApp(const std::string &); virtual std::shared_ptr<PlaybackApp> getPlaybackApp(const std::string &) = 0;
virtual std::shared_ptr<RecordingApp> getRecordingApp(const std::string &); virtual std::shared_ptr<RecordingApp> getRecordingApp(const std::string &) = 0;
virtual std::vector<std::shared_ptr<PlaybackApp>> getPlaybackApps(); virtual std::vector<std::shared_ptr<PlaybackApp>> getPlaybackApps() = 0;
virtual std::vector<std::shared_ptr<RecordingApp>> getRecordingApps(); virtual std::vector<std::shared_ptr<RecordingApp>> getRecordingApps() = 0;
}; };
} // namespace Objects } // namespace Objects
} // namespace Soundux } // namespace Soundux

View File

@ -2,6 +2,7 @@
#include "forward.hpp" #include "forward.hpp"
#include <core/global/globals.hpp> #include <core/global/globals.hpp>
#include <dlfcn.h> #include <dlfcn.h>
#include <exception>
#include <fancy.hpp> #include <fancy.hpp>
#include <stdexcept> #include <stdexcept>
@ -20,34 +21,41 @@ bool Soundux::PipeWireApi::setup()
auto *libpulse = dlopen("libpipewire-0.3.so", RTLD_LAZY); auto *libpulse = dlopen("libpipewire-0.3.so", RTLD_LAZY);
if (!libpulse) if (!libpulse)
{ {
//* For flatpak //* For Ubuntu
libpulse = dlopen("/usr/lib/x86_64-linux-gnu/libpipewire-0.3.so.0", RTLD_LAZY); libpulse = dlopen("/usr/lib/x86_64-linux-gnu/libpipewire-0.3.so.0", RTLD_LAZY);
} }
if (libpulse) if (libpulse)
{ {
#define load(name) loadFunc(libpulse, name, #name) try
load(pw_context_connect); {
load(pw_context_new); #define stringify(what) #what
load(pw_main_loop_new); #define load(name) loadFunc(libpulse, name, stringify(pw_##name))
load(pw_main_loop_get_loop); load(init);
load(pw_proxy_add_listener); load(context_new);
load(pw_properties_setf); load(main_loop_run);
load(pw_properties_set); load(main_loop_new);
load(pw_properties_new); load(proxy_destroy);
load(pw_properties_free); load(main_loop_quit);
load(pw_main_loop_destroy); load(properties_new);
load(pw_main_loop_quit); load(properties_set);
load(pw_context_destroy); load(context_connect);
load(pw_core_disconnect); load(properties_setf);
load(pw_main_loop_run); load(context_destroy);
load(pw_proxy_destroy); load(properties_free);
load(pw_init); load(core_disconnect);
return true; 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; Fancy::fancy.logTime().failure() << "Failed to load pipewire" << std::endl;
Globals::gAudioBackend = std::make_shared<Objects::AudioBackend>();
return false; return false;
} }

View File

@ -9,24 +9,24 @@ namespace Soundux
{ {
bool setup(); bool setup();
inline pw_core *(*pw_context_connect)(pw_context *, pw_properties *, std::size_t); //* We declare function pointers here so that we can use dlsym to assign them later.
inline pw_context *(*pw_context_new)(pw_loop *, pw_properties *, std::size_t); inline pw_core *(*context_connect)(pw_context *, pw_properties *, std::size_t);
inline pw_main_loop *(*pw_main_loop_new)(pw_properties *); inline pw_context *(*context_new)(pw_loop *, pw_properties *, std::size_t);
inline pw_loop *(*pw_main_loop_get_loop)(pw_main_loop *); inline pw_main_loop *(*main_loop_new)(pw_properties *);
inline pw_loop *(*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 **);
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 PipeWireApi
} // namespace Soundux } // namespace Soundux
#endif #endif

View File

@ -22,18 +22,22 @@ namespace Soundux::Objects
if (id == PW_ID_CORE && seq == *info->second) if (id == PW_ID_CORE && seq == *info->second)
{ {
*info->second = -1; *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) { 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<std::pair<PipeWire *, int *> *>(data); auto *info = reinterpret_cast<std::pair<PipeWire *, int *> *>(data);
if (info)
if (info && id == PW_ID_CORE)
{ {
*info->second = -1; if (id == PW_ID_CORE && seq == *info->second)
PipeWireApi::pw_main_loop_quit(info->first->loop); {
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 pending = pw_core_sync(core, PW_ID_CORE, 0); // NOLINT
while (pending != -1) while (pending != -1)
{ {
PipeWireApi::pw_main_loop_run(loop); PipeWireApi::main_loop_run(loop);
} }
spa_hook_remove(&coreListener); spa_hook_remove(&coreListener);
@ -135,7 +139,7 @@ namespace Soundux::Objects
pw_node_add_listener(boundNode, &listener, &events, thiz); // NOLINT pw_node_add_listener(boundNode, &listener, &events, thiz); // NOLINT
thiz->sync(); thiz->sync();
spa_hook_remove(&listener); spa_hook_remove(&listener);
PipeWireApi::pw_proxy_destroy(reinterpret_cast<pw_proxy *>(boundNode)); PipeWireApi::proxy_destroy(reinterpret_cast<pw_proxy *>(boundNode));
} }
} }
if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0) if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
@ -164,7 +168,7 @@ namespace Soundux::Objects
pw_port_add_listener(boundPort, &listener, &events, thiz); // NOLINT pw_port_add_listener(boundPort, &listener, &events, thiz); // NOLINT
thiz->sync(); thiz->sync();
spa_hook_remove(&listener); spa_hook_remove(&listener);
PipeWireApi::pw_proxy_destroy(reinterpret_cast<pw_proxy *>(boundPort)); PipeWireApi::proxy_destroy(reinterpret_cast<pw_proxy *>(boundPort));
auto scopedNodes = thiz->nodes.scoped(); auto scopedNodes = thiz->nodes.scoped();
auto scopedPorts = thiz->ports.scoped(); auto scopedPorts = thiz->ports.scoped();
@ -202,34 +206,37 @@ namespace Soundux::Objects
} }
} }
void PipeWire::setup() bool PipeWire::setup()
{ {
if (!PipeWireApi::setup()) if (!PipeWireApi::setup())
{ {
return; return false;
} }
PipeWireApi::pw_init(nullptr, nullptr); PipeWireApi::init(nullptr, nullptr);
loop = PipeWireApi::pw_main_loop_new(nullptr); loop = PipeWireApi::main_loop_new(nullptr);
if (!loop) 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) if (!context)
{ {
throw std::runtime_error("Failed to create context"); Fancy::fancy.logTime().failure() << "Failed to create context" << std::endl;
return false;
} }
core = PipeWireApi::context_connect(context, nullptr, 0);
core = PipeWireApi::pw_context_connect(context, nullptr, 0);
if (!core) 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); registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0);
if (!registry) 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; registryEvents.global = onGlobalAdded;
@ -239,31 +246,38 @@ namespace Soundux::Objects
pw_registry_add_listener(registry, &registryListener, &registryEvents, this); // NOLINT pw_registry_add_listener(registry, &registryListener, &registryEvents, this); // NOLINT
sync(); sync();
createNullSink();
if (!createNullSink())
{
Fancy::fancy.logTime().failure() << "Failed to create null sink" << std::endl;
return false;
}
return true;
} }
void PipeWire::destroy() void PipeWire::destroy()
{ {
PipeWireApi::pw_proxy_destroy(reinterpret_cast<pw_proxy *>(registry)); PipeWireApi::proxy_destroy(reinterpret_cast<pw_proxy *>(registry));
PipeWireApi::pw_core_disconnect(core); PipeWireApi::core_disconnect(core);
PipeWireApi::pw_context_destroy(context); PipeWireApi::context_destroy(context);
PipeWireApi::pw_main_loop_destroy(loop); PipeWireApi::main_loop_destroy(loop);
} }
bool PipeWire::createNullSink() 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::properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
PipeWireApi::pw_properties_set(props, PW_KEY_NODE_NAME, "soundux_sink"); PipeWireApi::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_FACTORY_NAME, "support.null-audio-sink");
auto *proxy = reinterpret_cast<pw_proxy *>( auto *proxy = reinterpret_cast<pw_proxy *>(
pw_core_create_object(core, "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0)); pw_core_create_object(core, "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0));
if (!proxy) if (!proxy)
{ {
PipeWireApi::pw_properties_free(props); PipeWireApi::properties_free(props);
return false; return false;
} }
@ -277,11 +291,11 @@ namespace Soundux::Objects
*reinterpret_cast<bool *>(data) = false; *reinterpret_cast<bool *>(data) = false;
}; };
PipeWireApi::pw_proxy_add_listener(proxy, &listener, &linkEvent, &success); PipeWireApi::proxy_add_listener(proxy, &listener, &linkEvent, &success);
sync(); sync();
spa_hook_remove(&listener); spa_hook_remove(&listener);
PipeWireApi::pw_properties_free(props); PipeWireApi::properties_free(props);
return success; return success;
} }
@ -296,18 +310,18 @@ namespace Soundux::Objects
std::optional<int> PipeWire::linkPorts(std::uint32_t in, std::uint32_t out) std::optional<int> 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::properties_set(props, PW_KEY_APP_NAME, "soundux");
PipeWireApi::pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", in); PipeWireApi::properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", in);
PipeWireApi::pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", out); PipeWireApi::properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", out);
auto *proxy = reinterpret_cast<pw_proxy *>( auto *proxy = reinterpret_cast<pw_proxy *>(
pw_core_create_object(core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0)); pw_core_create_object(core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0));
if (!proxy) if (!proxy)
{ {
PipeWireApi::pw_properties_free(props); PipeWireApi::properties_free(props);
return std::nullopt; return std::nullopt;
} }
@ -324,11 +338,11 @@ namespace Soundux::Objects
*reinterpret_cast<std::optional<std::uint32_t> *>(data) = std::nullopt; *reinterpret_cast<std::optional<std::uint32_t> *>(data) = std::nullopt;
}; };
PipeWireApi::pw_proxy_add_listener(proxy, &listener, &linkEvent, &result); PipeWireApi::proxy_add_listener(proxy, &listener, &linkEvent, &result);
sync(); sync();
spa_hook_remove(&listener); spa_hook_remove(&listener);
PipeWireApi::pw_properties_free(props); PipeWireApi::properties_free(props);
return result; return result;
} }

View File

@ -5,6 +5,13 @@
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <var_guard.hpp> #include <var_guard.hpp>
// 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 Soundux
{ {
namespace Objects namespace Objects
@ -43,6 +50,9 @@ namespace Soundux
class PipeWire : public AudioBackend class PipeWire : public AudioBackend
{ {
friend class AudioBackend;
private:
pw_core *core; pw_core *core;
pw_main_loop *loop; pw_main_loop *loop;
pw_context *context; pw_context *context;
@ -72,9 +82,11 @@ namespace Soundux
static void onGlobalAdded(void *, std::uint32_t, std::uint32_t, const char *, std::uint32_t, static void onGlobalAdded(void *, std::uint32_t, std::uint32_t, const char *, std::uint32_t,
const spa_dict *); const spa_dict *);
protected:
bool setup() override;
public: public:
PipeWire() = default; PipeWire() = default;
void setup() override;
void destroy() override; void destroy() override;
bool useAsDefault() override; bool useAsDefault() override;

View File

@ -1,43 +0,0 @@
#pragma once
#if defined(__linux__)
#include <cstdint>
#include <pulse/pulseaudio.h>
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

View File

@ -44,37 +44,50 @@ bool Soundux::PulseApi::setup()
return true; return true;
#else #else
auto *libpulse = dlopen("libpulse.so", RTLD_LAZY); 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) if (libpulse)
{ {
try
{
#define stringify(what) #what #define stringify(what) #what
#define load(name) loadFunc(libpulse, name, stringify(pa_##name)) #define load(name) loadFunc(libpulse, name, stringify(pa_##name))
load(mainloop_new); load(mainloop_new);
load(mainloop_iterate); load(mainloop_iterate);
load(mainloop_get_api); load(mainloop_get_api);
load(context_new); load(context_new);
load(context_connect); load(context_connect);
load(context_set_state_callback); load(context_set_state_callback);
load(context_load_module); load(context_load_module);
load(context_get_module_info_list); load(context_get_module_info_list);
load(context_get_source_output_info_list); load(context_get_source_output_info_list);
load(context_get_sink_input_info_list); load(context_get_sink_input_info_list);
load(context_get_server_info); load(context_get_server_info);
load(proplist_gets); load(proplist_gets);
load(context_set_default_source); load(context_set_default_source);
load(context_move_sink_input_by_name); load(context_move_sink_input_by_name);
load(context_get_server_info); load(context_get_server_info);
load(context_move_sink_input_by_index); load(context_move_sink_input_by_index);
load(context_move_source_output_by_name); load(context_move_source_output_by_name);
load(context_move_source_output_by_index); load(context_move_source_output_by_index);
load(context_set_sink_input_mute); load(context_set_sink_input_mute);
load(context_unload_module); load(context_unload_module);
load(context_get_state); load(context_get_state);
load(operation_get_state); load(operation_get_state);
return true; 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; Fancy::fancy.logTime().failure() << "Failed to load pulseaudio" << std::endl;
Globals::gAudioBackend = std::make_shared<Objects::AudioBackend>();
return false; return false;
#endif #endif
} }

View File

@ -0,0 +1,39 @@
#pragma once
#if defined(__linux__)
#include <cstdint>
#include <pulse/pulseaudio.h>
#include <type_traits>
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<decltype(pa_##function)> 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

View File

@ -1,23 +1,23 @@
#if defined(__linux__) #if defined(__linux__)
#include "pulse.hpp" #include "pulseaudio.hpp"
#include "forward.hpp"
#include <cstring> #include <cstring>
#include <exception> #include <exception>
#include <fancy.hpp> #include <fancy.hpp>
#include <helper/audio/linux/pulse/forward.hpp>
namespace Soundux::Objects namespace Soundux::Objects
{ {
void PulseAudio::setup() bool PulseAudio::setup()
{ {
if (!PulseApi::setup()) if (!PulseApi::setup())
{ {
return; return false;
} }
mainloop = PulseApi::mainloop_new(); mainloop = PulseApi::mainloop_new();
mainloopApi = PulseApi::mainloop_get_api(mainloop); mainloopApi = PulseApi::mainloop_get_api(mainloop);
context = PulseApi::context_new(mainloopApi, "soundux"); 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); auto data = std::make_pair(false, false);
PulseApi::context_set_state_callback( PulseApi::context_set_state_callback(
@ -53,13 +53,15 @@ namespace Soundux::Objects
if (!data.second) if (!data.second)
{ {
return; return false;
} }
unloadLeftOvers(); unloadLeftOvers();
fetchDefaultSource(); fetchDefaultSource();
return !(defaultSource.empty() || serverName.empty() || isRunningPipeWire());
} }
void PulseAudio::loadModules() bool PulseAudio::loadModules()
{ {
auto playbackApps = getPlaybackApps(); auto playbackApps = getPlaybackApps();
auto recordingApps = getRecordingApps(); auto recordingApps = getRecordingApps();
@ -74,7 +76,7 @@ namespace Soundux::Objects
} }
else else
{ {
*reinterpret_cast<std::uint32_t *>(userData) = id; *reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
} }
}, },
&nullSink)); &nullSink));
@ -90,7 +92,7 @@ namespace Soundux::Objects
} }
else else
{ {
*reinterpret_cast<std::uint32_t *>(userData) = id; *reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
} }
}, },
&loopBack)); &loopBack));
@ -105,7 +107,7 @@ namespace Soundux::Objects
} }
else else
{ {
*reinterpret_cast<std::uint32_t *>(userData) = id; *reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
} }
}, },
&passthrough)); &passthrough));
@ -120,7 +122,7 @@ namespace Soundux::Objects
} }
else else
{ {
*reinterpret_cast<std::uint32_t *>(userData) = id; *reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
} }
}, },
&passthroughSink)); &passthroughSink));
@ -134,14 +136,23 @@ namespace Soundux::Objects
} }
else else
{ {
*reinterpret_cast<std::uint32_t *>(userData) = id; *reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
} }
}, },
&passthroughLoopBack)); &passthroughLoopBack));
fetchLoopBackSinkId(); fetchLoopBackSinkId();
if (!nullSink || !loopBack || !loopBackSink || !passthrough || !passthroughSink || !passthroughLoopBack)
{
unloadLeftOvers();
return false;
}
fixPlaybackApps(playbackApps); fixPlaybackApps(playbackApps);
fixRecordingApps(recordingApps); fixRecordingApps(recordingApps);
return true;
} }
void PulseAudio::destroy() void PulseAudio::destroy()
{ {
@ -149,14 +160,19 @@ namespace Soundux::Objects
stopSoundInput(); stopSoundInput();
stopPassthrough(); stopPassthrough();
//* We only have to unload these 3 because the other modules depend on these and will automatically be deleted if (nullSink)
await(PulseApi::context_unload_module(context, nullSink, nullptr, nullptr)); await(PulseApi::context_unload_module(context, *nullSink, nullptr, nullptr));
await(PulseApi::context_unload_module(context, loopBack, nullptr, nullptr)); if (loopBack)
await(PulseApi::context_unload_module(context, loopBackSink, nullptr, nullptr)); 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)); if (passthrough)
await(PulseApi::context_unload_module(context, passthroughSink, nullptr, nullptr)); await(PulseApi::context_unload_module(context, *passthrough, nullptr, nullptr));
await(PulseApi::context_unload_module(context, passthroughLoopBack, 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) void PulseAudio::await(pa_operation *operation)
{ {
@ -174,6 +190,7 @@ namespace Soundux::Objects
if (info) if (info)
{ {
reinterpret_cast<PulseAudio *>(userData)->defaultSource = info->default_source_name; reinterpret_cast<PulseAudio *>(userData)->defaultSource = info->default_source_name;
reinterpret_cast<PulseAudio *>(userData)->serverName = info->server_name;
} }
}, },
this)); this));
@ -267,7 +284,7 @@ namespace Soundux::Objects
if (!defaultSource.empty()) 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( await(PulseApi::context_load_module(
context, "module-loopback", ("rate=44100 source=" + defaultSource + " sink=soundux_sink").c_str(), context, "module-loopback", ("rate=44100 source=" + defaultSource + " sink=soundux_sink").c_str(),
@ -275,16 +292,16 @@ namespace Soundux::Objects
if (static_cast<int>(id) < 0) if (static_cast<int>(id) < 0)
{ {
Fancy::fancy.logTime().failure() << "Failed to load loopback" << std::endl; Fancy::fancy.logTime().failure() << "Failed to load loopback" << std::endl;
*reinterpret_cast<std::uint32_t *>(userData) = 0; *reinterpret_cast<std::optional<std::uint32_t> *>(userData) = std::nullopt;
} }
else else
{ {
*reinterpret_cast<std::uint32_t *>(userData) = id; *reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
} }
}, },
&loopBack)); &loopBack));
if (loopBack == 0) if (!loopBack)
{ {
return false; return false;
} }
@ -313,7 +330,7 @@ namespace Soundux::Objects
{ {
if (!defaultSource.empty()) 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); auto result = std::make_pair(&loopBack, false);
@ -353,12 +370,14 @@ namespace Soundux::Objects
} }
bool PulseAudio::passthroughFrom(std::shared_ptr<PlaybackApp> app) bool PulseAudio::passthroughFrom(std::shared_ptr<PlaybackApp> app)
{ {
if (movedPassthroughApplication && movedPassthroughApplication->name == app->name) auto movedPassthroughScoped = movedPassthroughApplication.scoped();
if (*movedPassthroughScoped && movedPassthroughScoped->get()->name == app->name)
{ {
Fancy::fancy.logTime().message() Fancy::fancy.logTime().message()
<< "Ignoring sound passthrough request because requested app is already moved" << std::endl; << "Ignoring sound passthrough request because requested app is already moved" << std::endl;
return true; return true;
} }
movedPassthroughScoped.unlock();
if (!stopPassthrough()) if (!stopPassthrough())
{ {
Fancy::fancy.logTime().warning() << "Failed to stop current passthrough" << std::endl; Fancy::fancy.logTime().warning() << "Failed to stop current passthrough" << std::endl;
@ -396,23 +415,26 @@ namespace Soundux::Objects
} }
} }
movedPassthroughApplication = std::dynamic_pointer_cast<PulsePlaybackApp>(app); movedPassthroughScoped.lock();
movedPassthroughScoped = std::dynamic_pointer_cast<PulsePlaybackApp>(app);
return true; return true;
} }
bool PulseAudio::stopPassthrough() bool PulseAudio::stopPassthrough()
{ {
if (movedPassthroughApplication) auto movedPassthroughScoped = movedPassthroughApplication.scoped();
if (*movedPassthroughScoped)
{ {
bool success = false; bool success = false;
for (const auto &app : getPlaybackApps()) for (const auto &app : getPlaybackApps())
{ {
auto pulseApp = std::dynamic_pointer_cast<PulsePlaybackApp>(app); auto pulseApp = std::dynamic_pointer_cast<PulsePlaybackApp>(app);
if (app->name == movedPassthroughApplication->name) if (app->name == movedPassthroughApplication->get()->name)
{ {
await(PulseApi::context_move_sink_input_by_index( 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) { []([[maybe_unused]] pa_context *ctx, int success, void *userData) {
if (success) if (success)
{ {
@ -421,7 +443,7 @@ namespace Soundux::Objects
}, },
&success)); &success));
} }
movedPassthroughApplication.reset(); movedPassthroughApplication->reset();
return success; return success;
} }
} }
@ -436,15 +458,14 @@ namespace Soundux::Objects
return false; return false;
} }
std::unique_lock lock(movedAppMutex); auto movedAppScoped = movedApplication.scoped();
if (movedApplication && movedApplication->name == app->name) if (*movedAppScoped && movedAppScoped->get()->name == app->name)
{ {
return true; return true;
} }
lock.unlock(); movedAppScoped.unlock();
stopSoundInput(); stopSoundInput();
lock.lock();
for (const auto &recordingApp : getRecordingApps()) for (const auto &recordingApp : getRecordingApps())
{ {
@ -471,25 +492,25 @@ namespace Soundux::Objects
} }
} }
movedApplication = std::dynamic_pointer_cast<PulseRecordingApp>(app); movedAppScoped.lock();
movedAppScoped = std::dynamic_pointer_cast<PulseRecordingApp>(app);
return true; return true;
} }
bool PulseAudio::stopSoundInput() bool PulseAudio::stopSoundInput()
{ {
std::unique_lock lock(movedAppMutex);
bool success = true; bool success = true;
if (movedApplication)
auto movedAppScoped = movedApplication.scoped();
if (*movedAppScoped)
{ {
for (const auto &recordingApp : getRecordingApps()) for (const auto &recordingApp : getRecordingApps())
{ {
auto pulseApp = std::dynamic_pointer_cast<PulseRecordingApp>(recordingApp); auto pulseApp = std::dynamic_pointer_cast<PulseRecordingApp>(recordingApp);
if (pulseApp->name == movedAppScoped->get()->name)
if (pulseApp->name == movedApplication->name)
{ {
await(PulseApi::context_move_source_output_by_index( 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) { []([[maybe_unused]] pa_context *ctx, int success, void *userData) {
if (!success) if (!success)
{ {
@ -506,7 +527,7 @@ namespace Soundux::Objects
} }
} }
} }
movedApplication.reset(); movedAppScoped->reset();
} }
return success; return success;
@ -587,7 +608,7 @@ namespace Soundux::Objects
bool success = false; bool success = false;
await(PulseApi::context_set_sink_input_mute( await(PulseApi::context_set_sink_input_mute(
context, loopBackSink, state, context, *loopBackSink, state,
+[]([[maybe_unused]] pa_context *ctx, int success, void *userData) { +[]([[maybe_unused]] pa_context *ctx, int success, void *userData) {
*reinterpret_cast<bool *>(userData) = success; *reinterpret_cast<bool *>(userData) = success;
}, },
@ -603,7 +624,7 @@ namespace Soundux::Objects
bool PulseAudio::isCurrentlyPassingThrough() bool PulseAudio::isCurrentlyPassingThrough()
{ {
return movedPassthroughApplication != nullptr; return **movedPassthroughApplication != nullptr;
} }
bool PulseAudio::switchOnConnectPresent() bool PulseAudio::switchOnConnectPresent()
@ -643,5 +664,17 @@ namespace Soundux::Objects
}, },
nullptr)); 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 } // namespace Soundux::Objects
#endif #endif

View File

@ -2,6 +2,8 @@
#include "../backend.hpp" #include "../backend.hpp"
#include "forward.hpp" #include "forward.hpp"
#include <mutex> #include <mutex>
#include <optional>
#include <var_guard.hpp>
namespace Soundux namespace Soundux
{ {
@ -27,25 +29,29 @@ namespace Soundux
class PulseAudio : public AudioBackend class PulseAudio : public AudioBackend
{ {
friend class AudioBackend;
private:
pa_context *context; pa_context *context;
pa_mainloop *mainloop; pa_mainloop *mainloop;
pa_mainloop_api *mainloopApi; pa_mainloop_api *mainloopApi;
//* ~= The modules we create =~ //* ~= The modules we create =~
std::uint32_t nullSink; std::optional<std::uint32_t> nullSink;
std::uint32_t loopBack; std::optional<std::uint32_t> loopBack;
std::uint32_t loopBackSink; std::optional<std::uint32_t> loopBackSink;
std::uint32_t passthrough; std::optional<std::uint32_t> passthrough;
std::uint32_t passthroughSink; std::optional<std::uint32_t> passthroughSink;
std::uint32_t passthroughLoopBack; std::optional<std::uint32_t> passthroughLoopBack;
//* ~= ~~~~~~~~~~~~~~~~~~~~~ =~ //* ~= ~~~~~~~~~~~~~~~~~~~~~ =~
std::string serverName;
std::string defaultSource; std::string defaultSource;
std::shared_ptr<PulseRecordingApp> movedApplication;
std::shared_ptr<PulsePlaybackApp> movedPassthroughApplication;
std::mutex movedAppMutex; sxl::var_guard<std::shared_ptr<PulseRecordingApp>> movedApplication;
sxl::var_guard<std::shared_ptr<PulsePlaybackApp>> movedPassthroughApplication;
std::mutex operationMutex; std::mutex operationMutex;
void unloadLeftOvers(); void unloadLeftOvers();
@ -56,12 +62,15 @@ namespace Soundux
void fixPlaybackApps(const std::vector<std::shared_ptr<PlaybackApp>> &); void fixPlaybackApps(const std::vector<std::shared_ptr<PlaybackApp>> &);
void fixRecordingApps(const std::vector<std::shared_ptr<RecordingApp>> &); void fixRecordingApps(const std::vector<std::shared_ptr<RecordingApp>> &);
public: protected:
PulseAudio() = default; 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; void destroy() override;
bool isRunningPipeWire();
bool useAsDefault() override; bool useAsDefault() override;
bool revertDefault() override; bool revertDefault() override;

View File

@ -0,0 +1,21 @@
#include "forward.hpp"
#include <dlfcn.h>
#include <fancy.hpp>
bool Soundux::LibWnck::setup()
{
auto *libWnck = dlopen("libwnck-3.so", RTLD_LAZY);
if (libWnck)
{
getWindowPID = reinterpret_cast<decltype(getWindowPID)>(dlsym(libWnck, "wnck_window_get_pid"));
getWindowIcon = reinterpret_cast<decltype(getWindowIcon)>(dlsym(libWnck, "wnck_window_get_icon"));
forceUpdate = reinterpret_cast<decltype(forceUpdate)>(dlsym(libWnck, "wnck_screen_force_update"));
getDefaultScreen = reinterpret_cast<decltype(getDefaultScreen)>(dlsym(libWnck, "wnck_screen_get_default"));
getScreenWindows = reinterpret_cast<decltype(getScreenWindows)>(dlsym(libWnck, "wnck_screen_get_windows"));
Fancy::fancy.logTime().success() << "LibWnck found - Icon support is enabled" << std::endl;
return true;
}
return false;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <gdk/gdk.h>
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

View File

@ -2,67 +2,94 @@
#include "icons.hpp" #include "icons.hpp"
#include <dlfcn.h> #include <dlfcn.h>
#include <fancy.hpp> #include <fancy.hpp>
#include <filesystem>
#include <fstream>
#include <helper/base64/base64.hpp> #include <helper/base64/base64.hpp>
#include <helper/misc/misc.hpp>
#include <optional> #include <optional>
#include <regex>
namespace Soundux::Objects namespace Soundux::Objects
{ {
void IconFetcher::setup() bool IconFetcher::setup()
{ {
auto *libWnck = dlopen("libwnck-3.so.0", RTLD_LAZY); if (!LibWnck::setup())
if (libWnck)
{
isAvailable = true;
Fancy::fancy.logTime().success() << "LibWnck found - Icon support is enabled" << std::endl;
Lib::wnckGetDefaultScreen =
reinterpret_cast<decltype(Lib::wnckGetDefaultScreen)>(dlsym(libWnck, "wnck_screen_get_default"));
Lib::wnckForceUpdate =
reinterpret_cast<decltype(Lib::wnckForceUpdate)>(dlsym(libWnck, "wnck_screen_force_update"));
Lib::wnckGetScreenWindows =
reinterpret_cast<decltype(Lib::wnckGetScreenWindows)>(dlsym(libWnck, "wnck_screen_get_windows"));
Lib::wnckGetWindowPID =
reinterpret_cast<decltype(Lib::wnckGetWindowPID)>(dlsym(libWnck, "wnck_window_get_pid"));
Lib::wnckGetWindowIcon =
reinterpret_cast<decltype(Lib::wnckGetWindowIcon)>(dlsym(libWnck, "wnck_window_get_icon"));
}
else
{ {
Fancy::fancy.logTime().message() << "LibWnck was not found - Icon support is not available" << std::endl; Fancy::fancy.logTime().message() << "LibWnck was not found - Icon support is not available" << std::endl;
return; return false;
} }
gdk_init(nullptr, nullptr); gdk_init(nullptr, nullptr);
screen = Lib::wnckGetDefaultScreen(); screen = LibWnck::getDefaultScreen();
if (!screen) if (!screen)
{ {
Fancy::fancy.logTime().warning() << "Failed to get default screen!" << std::endl; Fancy::fancy.logTime().warning() << "Failed to get default screen!" << std::endl;
return false;
} }
return true;
}
std::optional<IconFetcher> 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<int> 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<std::string> IconFetcher::getIcon(int pid, bool recursive) std::optional<std::string> IconFetcher::getIcon(int pid, bool recursive)
{ {
if (!isAvailable)
{
return std::nullopt;
}
if (cache.find(pid) != cache.end()) if (cache.find(pid) != cache.end())
{ {
return cache.at(pid); return cache.at(pid);
} }
Lib::wnckForceUpdate(screen); LibWnck::forceUpdate(screen);
auto *windows = Lib::wnckGetScreenWindows(screen); auto *windows = LibWnck::getScreenWindows(screen);
for (auto *item = windows; item != nullptr; item = item->next) for (auto *item = windows; item != nullptr; item = item->next)
{ {
auto *window = reinterpret_cast<Lib::WnckWindow *>(item->data); auto *window = reinterpret_cast<LibWnck::Window *>(item->data);
auto _pid = Lib::wnckGetWindowPID(window); auto _pid = LibWnck::getWindowPID(window);
if (pid == _pid) if (pid == _pid)
{ {
auto *icon = Lib::wnckGetWindowIcon(window); auto *icon = LibWnck::getWindowIcon(window);
gsize size = 4096; gsize size = 4096;
auto *iconBuff = new gchar[size]; auto *iconBuff = new gchar[size];
@ -90,7 +117,7 @@ namespace Soundux::Objects
if (recursive) if (recursive)
{ {
auto parentProcess = Helpers::getPpid(pid); auto parentProcess = getPpid(pid);
if (parentProcess) if (parentProcess)
{ {
auto recursiveResult = getIcon(*parentProcess, false); auto recursiveResult = getIcon(*parentProcess, false);

View File

@ -1,32 +1,28 @@
#if defined(__linux__) #if defined(__linux__)
#pragma once #pragma once
#include <gdk/gdk.h> #include "forward.hpp"
#include <map> #include <map>
#include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
namespace Soundux 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 namespace Objects
{ {
class IconFetcher class IconFetcher
{ {
Lib::WnckScreen *screen; LibWnck::Screen *screen;
bool isAvailable = false;
std::map<int, std::string> cache; std::map<int, std::string> cache;
private:
IconFetcher() = default;
bool setup();
std::optional<int> getPpid(int pid);
public: public:
void setup(); static std::optional<IconFetcher> createInstance();
std::optional<std::string> getIcon(int pid, bool recursive = true); std::optional<std::string> getIcon(int pid, bool recursive = true);
}; };
} // namespace Objects } // namespace Objects

View File

@ -71,40 +71,6 @@ namespace Soundux
WideCharToMultiByte(65001, 0, s.c_str(), -1, &out[0], wsz, nullptr, nullptr); WideCharToMultiByte(65001, 0, s.c_str(), -1, &out[0], wsz, nullptr, nullptr);
return out; return out;
} }
#endif
#if defined(__linux__)
std::optional<int> 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 #endif
bool Helpers::deleteFile(const std::string &path, bool trash) bool Helpers::deleteFile(const std::string &path, bool trash)
{ {

View File

@ -8,18 +8,14 @@ namespace Soundux
namespace Helpers namespace Helpers
{ {
#if defined(_WIN32) #if defined(_WIN32)
std::wstring widen(const std::string &s); std::wstring widen(const std::string &);
std::string narrow(const std::wstring &s); std::string narrow(const std::wstring &);
#endif
#if defined(__linux__)
std::optional<int> getPpid(int pid);
#endif #endif
bool deleteFile(const std::string &, bool = true);
bool deleteFile(const std::string &path, bool trash = true); bool run(const std::string &);
std::pair<std::string, bool> getResultCompact(const std::string &);
bool run(const std::string &command); std::pair<std::vector<std::string>, bool> getResult(const std::string &);
std::pair<std::string, bool> getResultCompact(const std::string &command);
std::pair<std::vector<std::string>, bool> getResult(const std::string &command);
} // namespace Helpers } // namespace Helpers
} // namespace Soundux } // namespace Soundux

View File

@ -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<void()> 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

View File

@ -0,0 +1,32 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <functional>
#include <map>
#include <queue>
#include <thread>
namespace Soundux
{
namespace Objects
{
class Queue
{
std::map<std::uint64_t, std::function<void()>> queue;
std::mutex queueMutex;
std::condition_variable cv;
std::atomic<bool> stop;
std::thread handler;
private:
void handle();
public:
Queue();
~Queue();
void push_unique(std::uint64_t, std::function<void()>);
};
} // namespace Objects
} // namespace Soundux

View File

@ -1,95 +0,0 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <functional>
#include <queue>
#include <shared_mutex>
#include <thread>
namespace Soundux
{
namespace Objects
{
template <typename UID = int> class ProcessingQueue
{
struct Item
{
std::shared_ptr<std::atomic<bool>> handled;
std::function<void()> callback;
UID identifier;
};
private:
std::queue<Item> queue;
std::mutex queueMutex;
std::vector<UID> unhandled;
std::shared_mutex unhandledMutex;
std::condition_variable cv;
std::atomic<bool> 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<void()> &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

View File

@ -44,7 +44,7 @@ namespace Soundux::Objects
if (json.is_discarded()) if (json.is_discarded())
{ {
Fancy::fancy.logTime().warning() << "Failed to parse youtube-dl information" << std::endl; 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; return std::nullopt;
} }
@ -66,14 +66,14 @@ namespace Soundux::Objects
} }
Fancy::fancy.logTime().warning() << "Failed to get info from youtube-dl" << std::endl; 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; return std::nullopt;
} }
bool YoutubeDl::download(const std::string &url) bool YoutubeDl::download(const std::string &url)
{ {
if (!isAvailable) if (!isAvailable)
{ {
Globals::gGui->onError(ErrorCode::YtdlNotFound); Globals::gGui->onError(Enums::ErrorCode::YtdlNotFound);
return false; return false;
} }
@ -85,7 +85,7 @@ namespace Soundux::Objects
if (!std::regex_match(url, urlRegex)) if (!std::regex_match(url, urlRegex))
{ {
Fancy::fancy.logTime().warning() << "Bad url " >> url << std::endl; Fancy::fancy.logTime().warning() << "Bad url " >> url << std::endl;
Globals::gGui->onError(ErrorCode::YtdlInvalidUrl); Globals::gGui->onError(Enums::ErrorCode::YtdlInvalidUrl);
return false; return false;
} }
@ -121,7 +121,7 @@ namespace Soundux::Objects
return rtn; return rtn;
} }
Globals::gGui->onError(ErrorCode::TabDoesNotExist); Globals::gGui->onError(Enums::ErrorCode::TabDoesNotExist);
return false; return false;
} }
void YoutubeDl::killDownload() void YoutubeDl::killDownload()

View File

@ -1,14 +1,10 @@
#include "core/global/globals.hpp"
#include "helper/exceptions/crashhandler.hpp"
#include "ui/impl/webview/webview.hpp"
#include <InstanceGuard.hpp> #include <InstanceGuard.hpp>
#include <core/enums/enums.hpp>
#include <core/global/globals.hpp>
#include <fancy.hpp> #include <fancy.hpp>
#include <helper/audio/linux/backend.hpp>
#if defined(__linux__) #include <helper/exceptions/crashhandler.hpp>
#include <dlfcn.h> #include <ui/impl/webview/webview.hpp>
#include <helper/audio/linux/pipewire/pipewire.hpp>
#include <helper/audio/linux/pulse/pulse.hpp>
#endif
#if defined(_WIN32) #if defined(_WIN32)
#include "../assets/icon.h" #include "../assets/icon.h"
@ -18,6 +14,10 @@ int __stdcall WinMain([[maybe_unused]] HINSTANCE hInstrance, [[maybe_unused]] HI
int main() int main()
#endif #endif
{ {
using namespace Soundux::Globals; // NOLINT
using namespace Soundux::Objects; // NOLINT
using namespace Soundux::Enums; // NOLINT
#if defined(_WIN32) #if defined(_WIN32)
if (std::getenv("SOUNDUX_DEBUG")) if (std::getenv("SOUNDUX_DEBUG"))
{ {
@ -46,60 +46,46 @@ int main()
return 1; return 1;
} }
Soundux::Globals::gConfig.load(); gConfig.load();
Soundux::Globals::gData.set(Soundux::Globals::gConfig.data); gData.set(gConfig.data);
Soundux::Globals::gSettings = Soundux::Globals::gConfig.settings; gSettings = gConfig.settings;
#if defined(__linux__) #if defined(__linux__)
Soundux::Globals::gIcons.setup(); gIcons = gIcons->createInstance();
gAudioBackend = AudioBackend::createInstance(gSettings.audioBackend);
if (Soundux::Globals::gSettings.audioBackend == Soundux::Objects::BackendType::PulseAudio)
{
Soundux::Globals::gAudioBackend = std::make_shared<Soundux::Objects::PulseAudio>();
Soundux::Globals::gAudioBackend->setup();
auto pulseBackend = std::dynamic_pointer_cast<Soundux::Objects::PulseAudio>(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::Objects::PipeWire>();
Soundux::Globals::gAudioBackend->setup();
}
#endif #endif
Soundux::Globals::gAudio.setup();
Soundux::Globals::gYtdl.setup(); gAudio.setup();
gYtdl.setup();
#if defined(__linux__) #if defined(__linux__)
if (Soundux::Globals::gSettings.audioBackend == Soundux::Objects::BackendType::PulseAudio && if (gAudioBackend && gSettings.audioBackend == BackendType::PulseAudio && gConfig.settings.useAsDefaultDevice)
Soundux::Globals::gConfig.settings.useAsDefaultDevice)
{ {
Soundux::Globals::gAudioBackend->useAsDefault(); gAudioBackend->useAsDefault();
} }
#endif #endif
Soundux::Globals::gGui = std::make_unique<Soundux::Objects::WebView>(); gGui = std::make_unique<Soundux::Objects::WebView>();
Soundux::Globals::gGui->setup(); gGui->setup();
#if defined(_WIN32) #if defined(_WIN32)
HICON hIcon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_ICON1)); HICON hIcon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_ICON1));
SendMessage(GetActiveWindow(), WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon)); SendMessage(GetActiveWindow(), WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon));
SendMessage(GetActiveWindow(), WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon)); SendMessage(GetActiveWindow(), WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon));
#endif #endif
Soundux::Globals::gGui->mainLoop();
Soundux::Globals::gAudio.destroy(); gGui->mainLoop();
gAudio.destroy();
#if defined(__linux__) #if defined(__linux__)
Soundux::Globals::gAudioBackend->destroy(); if (gAudioBackend)
{
gAudioBackend->destroy();
}
#endif #endif
Soundux::Globals::gConfig.data.set(Soundux::Globals::gData); gConfig.data.set(gData);
Soundux::Globals::gConfig.settings = Soundux::Globals::gSettings; gConfig.settings = gSettings;
Soundux::Globals::gConfig.save(); gConfig.save();
return 0; return 0;
} }

View File

@ -3,7 +3,7 @@
#include <cstdint> #include <cstdint>
#include <fancy.hpp> #include <fancy.hpp>
#include <filesystem> #include <filesystem>
#include <helper/audio/linux/pulse/pulse.hpp> #include <helper/audio/linux/pulseaudio/pulseaudio.hpp>
#include <helper/json/bindings.hpp> #include <helper/json/bindings.hpp>
#include <helper/systeminfo/systeminfo.hpp> #include <helper/systeminfo/systeminfo.hpp>
#include <helper/version/check.hpp> #include <helper/version/check.hpp>
@ -101,17 +101,18 @@ namespace Soundux::Objects
webview->expose(Webview::Function("requestHotkey", [](bool state) { Globals::gHotKeys.shouldNotify(state); })); webview->expose(Webview::Function("requestHotkey", [](bool state) { Globals::gHotKeys.shouldNotify(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<int> &keys) { return setHotkey(id, keys); }));
webview->expose(Webview::Function("getHotkeySequence", webview->expose(Webview::Function("getHotkeySequence", [this](const std::vector<int> &keys) {
[this](const std::vector<int> &keys) { return getHotkeySequence(keys); })); return Globals::gHotKeys.getKeySequence(keys);
}));
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(
"moveTabs", [this](const std::vector<int> &newOrder) { return changeTabOrder(newOrder); })); "moveTabs", [this](const std::vector<int> &newOrder) { return changeTabOrder(newOrder); }));
webview->expose(Webview::Function("markFavorite", [this](const std::uint32_t &id, bool favourite) { webview->expose(Webview::Function("markFavorite", [this](const std::uint32_t &id, bool favorite) {
markFavourite(id, favourite); Globals::gData.markFavorite(id, favorite);
return getFavouriteIds(); 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::Function("isYoutubeDLAvailable", []() { return Globals::gYtdl.available(); }));
webview->expose( webview->expose(
Webview::AsyncFunction("getYoutubeDLInfo", [this](Webview::Promise promise, const std::string &url) { 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::Function("getSystemInfo", []() -> std::string { return SystemInfo::getSummary(); }));
webview->expose(Webview::AsyncFunction( webview->expose(Webview::AsyncFunction(
"updateCheck", [this](Webview::Promise promise) { promise.resolve(VersionCheck::getStatus()); })); "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); })); webview->expose(Webview::Function("deleteSound", [this](std::uint32_t id) { return deleteSound(id); }));
#if !defined(__linux__) #if !defined(__linux__)
@ -337,7 +338,7 @@ namespace Soundux::Objects
{ {
webview->callFunction<void>(Webview::JavaScriptFunction("window.downloadProgressed", progress, eta)); webview->callFunction<void>(Webview::JavaScriptFunction("window.downloadProgressed", progress, eta));
} }
void WebView::onError(const ErrorCode &error) void WebView::onError(const Enums::ErrorCode &error)
{ {
webview->callFunction<void>(Webview::JavaScriptFunction("window.onError", static_cast<std::uint8_t>(error))); webview->callFunction<void>(Webview::JavaScriptFunction("window.onError", static_cast<std::uint8_t>(error)));
} }

View File

@ -22,7 +22,7 @@ namespace Soundux
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<int> &keys) override;
void onError(const ErrorCode &error) override; void onError(const Enums::ErrorCode &error) override;
void onSoundPlayed(const PlayingSound &sound) override; void onSoundPlayed(const PlayingSound &sound) override;
void onSoundProgressed(const PlayingSound &sound) override; void onSoundProgressed(const PlayingSound &sound) override;
void onDownloadProgressed(float progress, const std::string &eta) override; void onDownloadProgressed(float progress, const std::string &eta) override;

View File

@ -5,7 +5,7 @@
#include <filesystem> #include <filesystem>
#include <helper/audio/linux/backend.hpp> #include <helper/audio/linux/backend.hpp>
#include <helper/audio/linux/pipewire/pipewire.hpp> #include <helper/audio/linux/pipewire/pipewire.hpp>
#include <helper/audio/linux/pulse/pulse.hpp> #include <helper/audio/linux/pulseaudio/pulseaudio.hpp>
#include <helper/misc/misc.hpp> #include <helper/misc/misc.hpp>
#include <nfd.hpp> #include <nfd.hpp>
#include <optional> #include <optional>
@ -101,7 +101,7 @@ namespace Soundux::Objects
Fancy::fancy.logTime().warning() << "Path " >> tab.path << " does not exist" << std::endl; Fancy::fancy.logTime().warning() << "Path " >> tab.path << " does not exist" << std::endl;
return {}; return {};
} }
std::optional<Tab> Window::addTab() // NOLINT std::optional<Tab> Window::addTab()
{ {
nfdnchar_t *outpath = {}; nfdnchar_t *outpath = {};
auto result = NFD::PickFolder(outpath, nullptr); auto result = NFD::PickFolder(outpath, nullptr);
@ -132,7 +132,7 @@ namespace Soundux::Objects
return tab; return tab;
} }
Fancy::fancy.logTime().warning() << "Selected Folder does not exist!" << std::endl; Fancy::fancy.logTime().warning() << "Selected Folder does not exist!" << std::endl;
onError(ErrorCode::FolderDoesNotExist); onError(Enums::ErrorCode::FolderDoesNotExist);
} }
return std::nullopt; return std::nullopt;
} }
@ -148,7 +148,10 @@ namespace Soundux::Objects
} }
if (Globals::gSettings.muteDuringPlayback) if (Globals::gSettings.muteDuringPlayback)
{ {
Globals::gAudioBackend->muteInput(true); if (Globals::gAudioBackend)
{
Globals::gAudioBackend->muteInput(true);
}
} }
if (!Globals::gSettings.pushToTalkKeys.empty()) if (!Globals::gSettings.pushToTalkKeys.empty())
{ {
@ -165,7 +168,7 @@ namespace Soundux::Objects
{ {
return *playingSound; return *playingSound;
} }
if (!Globals::gSettings.output.empty()) if (!Globals::gSettings.output.empty() && Globals::gAudioBackend)
{ {
auto moveSuccess = Globals::gAudioBackend->inputSoundTo( auto moveSuccess = Globals::gAudioBackend->inputSoundTo(
Globals::gAudioBackend->getRecordingApp(Globals::gSettings.output)); Globals::gAudioBackend->getRecordingApp(Globals::gSettings.output));
@ -179,7 +182,7 @@ namespace Soundux::Objects
Fancy::fancy.logTime().failure() << "Failed to move Application " << Globals::gSettings.output Fancy::fancy.logTime().failure() << "Failed to move Application " << Globals::gSettings.output
<< " to soundux sink for sound " << id << std::endl; << " to soundux sink for sound " << id << std::endl;
onError(ErrorCode::FailedToMoveToSink); onError(Enums::ErrorCode::FailedToMoveToSink);
return std::nullopt; return std::nullopt;
} }
@ -190,12 +193,12 @@ namespace Soundux::Objects
else else
{ {
Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl; Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl;
onError(ErrorCode::SoundNotFound); onError(Enums::ErrorCode::SoundNotFound);
return std::nullopt; return std::nullopt;
} }
Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl; Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl;
onError(ErrorCode::FailedToPlay); onError(Enums::ErrorCode::FailedToPlay);
return std::nullopt; return std::nullopt;
} }
#else #else
@ -247,12 +250,12 @@ namespace Soundux::Objects
else else
{ {
Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl; Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl;
onError(ErrorCode::SoundNotFound); onError(Enums::ErrorCode::SoundNotFound);
return std::nullopt; return std::nullopt;
} }
Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl; Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl;
onError(ErrorCode::FailedToPlay); onError(Enums::ErrorCode::FailedToPlay);
return std::nullopt; return std::nullopt;
} }
#endif #endif
@ -286,7 +289,7 @@ namespace Soundux::Objects
} }
Fancy::fancy.logTime().warning() << "Failed to pause sound " << id << std::endl; Fancy::fancy.logTime().warning() << "Failed to pause sound " << id << std::endl;
onError(ErrorCode::FailedToPause); onError(Enums::ErrorCode::FailedToPause);
return std::nullopt; return std::nullopt;
} }
std::optional<PlayingSound> Window::resumeSound(const std::uint32_t &id) std::optional<PlayingSound> 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; Fancy::fancy.logTime().warning() << "Failed to resume sound " << id << std::endl;
onError(ErrorCode::FailedToResume); onError(Enums::ErrorCode::FailedToResume);
return std::nullopt; return std::nullopt;
} }
std::optional<PlayingSound> Window::seekSound(const std::uint32_t &id, std::uint64_t seekTo) std::optional<PlayingSound> 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; Fancy::fancy.logTime().warning() << "Failed to seek sound " << id << " to " << seekTo << std::endl;
onError(ErrorCode::FailedToSeek); onError(Enums::ErrorCode::FailedToSeek);
return std::nullopt; return std::nullopt;
} }
std::optional<PlayingSound> Window::repeatSound(const std::uint32_t &id, bool shouldRepeat) std::optional<PlayingSound> 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 Fancy::fancy.logTime().failure() << "Failed to set repeat-state of sound " << id << " to " << shouldRepeat
<< std::endl; << std::endl;
onError(ErrorCode::FailedToRepeat); onError(Enums::ErrorCode::FailedToRepeat);
return std::nullopt; return std::nullopt;
} }
std::vector<Tab> Window::removeTab(const std::uint32_t &id) std::vector<Tab> Window::removeTab(const std::uint32_t &id)
@ -435,15 +438,18 @@ namespace Soundux::Objects
onAllSoundsFinished(); onAllSoundsFinished();
#if defined(__linux__) #if defined(__linux__)
if (!Globals::gAudioBackend->stopSoundInput()) if (Globals::gAudioBackend)
{ {
Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; if (!Globals::gAudioBackend->stopSoundInput())
onError(ErrorCode::FailedToMoveBack); {
} Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl;
if (!Globals::gAudioBackend->stopPassthrough()) onError(Enums::ErrorCode::FailedToMoveBack);
{ }
Fancy::fancy.logTime().failure() << "Failed to move back current passthrough application" << std::endl; if (!Globals::gAudioBackend->stopPassthrough())
onError(ErrorCode::FailedToMoveBackPassthrough); {
Fancy::fancy.logTime().failure() << "Failed to move back current passthrough application" << std::endl;
onError(Enums::ErrorCode::FailedToMoveBackPassthrough);
}
} }
#endif #endif
} }
@ -453,75 +459,66 @@ namespace Soundux::Objects
if (settings.audioBackend != Globals::gSettings.audioBackend) if (settings.audioBackend != Globals::gSettings.audioBackend)
{ {
stopSounds(true); stopSounds(true);
Globals::gAudioBackend->destroy();
if (settings.audioBackend == BackendType::PulseAudio)
{
Soundux::Globals::gAudioBackend = std::make_shared<Soundux::Objects::PulseAudio>();
Soundux::Globals::gAudioBackend->setup();
auto pulseBackend = if (Globals::gAudioBackend)
std::dynamic_pointer_cast<Soundux::Objects::PulseAudio>(Soundux::Globals::gAudioBackend);
if (pulseBackend)
{
pulseBackend->loadModules();
}
}
else
{ {
Soundux::Globals::gAudioBackend = std::make_shared<Soundux::Objects::PipeWire>(); Globals::gAudioBackend->destroy();
Soundux::Globals::gAudioBackend->setup();
} }
Globals::gAudioBackend = AudioBackend::createInstance(settings.audioBackend);
Globals::gAudio.setup(); 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);
} }
} }
} else if (settings.useAsDefaultDevice && !Globals::gSettings.useAsDefaultDevice)
if (!settings.useAsDefaultDevice && Globals::gSettings.useAsDefaultDevice)
{
if (!Globals::gAudioBackend->revertDefault())
{ {
Fancy::fancy.logTime().failure() << "Failed to move back default source" << std::endl; Globals::gSettings.output = "";
onError(ErrorCode::FailedToRevertDefaultSource); 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);
}
} }
} if (settings.output != Globals::gSettings.output)
else if (settings.useAsDefaultDevice && !Globals::gSettings.useAsDefaultDevice)
{
Globals::gSettings.output = "";
if (!Globals::gAudioBackend->stopSoundInput())
{ {
Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; if (!Globals::gAudioBackend->stopSoundInput())
onError(ErrorCode::FailedToMoveBack); {
} Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl;
if (!Globals::gAudioBackend->useAsDefault()) onError(Enums::ErrorCode::FailedToMoveBack);
{ }
onError(ErrorCode::FailedToSetDefaultSource); if (!settings.output.empty() && !Globals::gAudio.getPlayingSounds().empty())
} {
} Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(settings.output));
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));
} }
} }
#endif #endif
@ -544,7 +541,7 @@ namespace Soundux::Objects
} }
} }
Fancy::fancy.logTime().failure() << "Failed to refresh tab " << id << " tab does not exist" << std::endl; 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; 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<int> &hotkeys)
@ -557,13 +554,9 @@ namespace Soundux::Objects
} }
Fancy::fancy.logTime().failure() << "Failed to set hotkey for sound " << id << ", sound does not exist" Fancy::fancy.logTime().failure() << "Failed to set hotkey for sound " << id << ", sound does not exist"
<< std::endl; << std::endl;
onError(ErrorCode::FailedToSetHotkey); onError(Enums::ErrorCode::FailedToSetHotkey);
return std::nullopt; return std::nullopt;
} }
std::string Window::getHotkeySequence(const std::vector<int> &hotkeys)
{
return Globals::gHotKeys.getKeySequence(hotkeys);
}
std::vector<Tab> Window::changeTabOrder(const std::vector<int> &newOrder) std::vector<Tab> Window::changeTabOrder(const std::vector<int> &newOrder)
{ {
std::vector<Tab> newTabs; std::vector<Tab> newTabs;
@ -583,33 +576,41 @@ namespace Soundux::Objects
//* once. The backend (gPulse.getRecordingStreams()) will work with multiple instances, so we need to filter out //* once. The backend (gPulse.getRecordingStreams()) will work with multiple instances, so we need to filter out
//* duplicates here. //* duplicates here.
auto streams = Globals::gAudioBackend->getRecordingApps();
std::vector<std::shared_ptr<IconRecordingApp>> uniqueStreams; std::vector<std::shared_ptr<IconRecordingApp>> 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<IconRecordingApp>(*stream);
if (auto pulseApp = std::dynamic_pointer_cast<PulseRecordingApp>(stream); pulseApp)
{
auto icon = Soundux::Globals::gIcons.getIcon(static_cast<int>(pulseApp->pid));
if (icon)
{
iconStream->appIcon = *icon;
}
}
else if (auto pipeWireApp = std::dynamic_pointer_cast<PipeWireRecordingApp>(stream); pipeWireApp)
{
auto icon = Soundux::Globals::gIcons.getIcon(static_cast<int>(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<IconRecordingApp>(*stream);
if (Globals::gIcons)
{
if (auto pulseApp = std::dynamic_pointer_cast<PulseRecordingApp>(stream); pulseApp)
{
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pulseApp->pid));
if (icon)
{
iconStream->appIcon = *icon;
}
}
else if (auto pipeWireApp = std::dynamic_pointer_cast<PipeWireRecordingApp>(stream);
pipeWireApp)
{
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->pid));
if (icon)
{
iconStream->appIcon = *icon;
}
}
}
uniqueStreams.emplace_back(iconStream);
}
} }
} }
@ -617,34 +618,42 @@ namespace Soundux::Objects
} }
std::vector<std::shared_ptr<IconPlaybackApp>> Window::getPlayback() std::vector<std::shared_ptr<IconPlaybackApp>> Window::getPlayback()
{ {
auto streams = Globals::gAudioBackend->getPlaybackApps();
std::vector<std::shared_ptr<IconPlaybackApp>> uniqueStreams; std::vector<std::shared_ptr<IconPlaybackApp>> uniqueStreams;
for (auto &stream : streams) if (Globals::gAudioBackend)
{ {
auto item = std::find_if(std::begin(uniqueStreams), std::end(uniqueStreams), auto streams = Globals::gAudioBackend->getPlaybackApps();
[&](const auto &_stream) { return stream->name == _stream->name; });
if (stream && item == std::end(uniqueStreams))
{
auto iconStream = std::make_shared<IconPlaybackApp>(*stream);
if (auto pulseApp = std::dynamic_pointer_cast<PulsePlaybackApp>(stream); pulseApp)
{
auto icon = Soundux::Globals::gIcons.getIcon(static_cast<int>(pulseApp->pid));
if (icon)
{
iconStream->appIcon = *icon;
}
}
if (auto pipeWireApp = std::dynamic_pointer_cast<PipeWirePlaybackApp>(stream); pipeWireApp)
{
auto icon = Soundux::Globals::gIcons.getIcon(static_cast<int>(pipeWireApp->pid));
if (icon)
{
iconStream->appIcon = *icon;
}
}
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<IconPlaybackApp>(*stream);
if (Globals::gIcons)
{
if (auto pulseApp = std::dynamic_pointer_cast<PulsePlaybackApp>(stream); pulseApp)
{
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pulseApp->pid));
if (icon)
{
iconStream->appIcon = *icon;
}
}
if (auto pipeWireApp = std::dynamic_pointer_cast<PipeWirePlaybackApp>(stream); pipeWireApp)
{
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(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) bool Window::startPassthrough(const std::string &name)
{ {
if (Globals::gSettings.output.empty() || if (Globals::gAudioBackend)
Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(Globals::gSettings.output)))
{ {
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() if (!Globals::gAudioBackend->passthroughFrom(Globals::gAudioBackend->getPlaybackApp(name)))
<< "Failed to move application: " << name << " to passthrough" << std::endl; {
onError(ErrorCode::FailedToStartPassthrough); Fancy::fancy.logTime().failure()
return false; << "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; Fancy::fancy.logTime().failure() << "Failed to start passthrough for application: " << name << std::endl;
onError(ErrorCode::FailedToStartPassthrough); onError(Enums::ErrorCode::FailedToStartPassthrough);
return false; return false;
} }
void Window::stopPassthrough() 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; if (!Globals::gAudioBackend->stopSoundInput())
onError(ErrorCode::FailedToMoveBack); {
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 #else
@ -705,14 +721,6 @@ namespace Soundux::Objects
onAllSoundsFinished(); onAllSoundsFinished();
} }
} }
std::vector<std::uint32_t> Window::getFavouriteIds()
{
return Globals::gData.getFavoriteIds();
}
void Window::markFavourite(const std::uint32_t &id, bool favorite)
{
Globals::gData.markFavorite(id, favorite);
}
void Window::onAllSoundsFinished() void Window::onAllSoundsFinished()
{ {
if (!Globals::gSettings.pushToTalkKeys.empty()) if (!Globals::gSettings.pushToTalkKeys.empty())
@ -721,16 +729,19 @@ namespace Soundux::Objects
} }
#if defined(__linux__) #if defined(__linux__)
if (Globals::gSettings.muteDuringPlayback) if (Globals::gAudioBackend)
{ {
Globals::gAudioBackend->muteInput(false); if (Globals::gSettings.muteDuringPlayback)
}
if (!Globals::gAudioBackend->isCurrentlyPassingThrough())
{
if (!Globals::gAudioBackend->stopSoundInput())
{ {
Fancy::fancy.logTime().failure() << "Failed to move back current application" << std::endl; Globals::gAudioBackend->muteInput(false);
onError(ErrorCode::FailedToMoveBack); }
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 #endif
@ -742,7 +753,7 @@ namespace Soundux::Objects
Globals::gHotKeys.pressKeys(Globals::gSettings.pushToTalkKeys); Globals::gHotKeys.pressKeys(Globals::gSettings.pushToTalkKeys);
} }
} }
void Window::isOnFavorites(bool state) void Window::setIsOnFavorites(bool state)
{ {
Globals::gData.isOnFavorites = state; Globals::gData.isOnFavorites = state;
} }
@ -753,14 +764,14 @@ namespace Soundux::Objects
{ {
if (!Helpers::deleteFile(sound->get().path, Globals::gSettings.deleteToTrash)) if (!Helpers::deleteFile(sound->get().path, Globals::gSettings.deleteToTrash))
{ {
onError(ErrorCode::FailedToDelete); onError(Enums::ErrorCode::FailedToDelete);
return false; return false;
} }
return true; return true;
} }
Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl; Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl;
onError(ErrorCode::SoundNotFound); onError(Enums::ErrorCode::SoundNotFound);
return false; return false;
} }

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
#include <core/global/objects.hpp> #include <core/objects/settings.hpp>
#include <helper/audio/audio.hpp> #include <helper/audio/audio.hpp>
#if defined(__linux__) #if defined(__linux__)
#include <helper/audio/linux/backend.hpp> #include <helper/audio/linux/backend.hpp>
@ -46,53 +46,53 @@ namespace Soundux
std::string muteDuringPlayback; std::string muteDuringPlayback;
} translations; } translations;
protected:
virtual void onAllSoundsFinished(); virtual void onAllSoundsFinished();
virtual void isOnFavorites(bool); protected:
virtual void stopSounds(bool = false); virtual std::vector<Sound> getTabContent(const Tab &) const;
virtual bool stopSound(const std::uint32_t &);
#if defined(__linux__)
virtual std::vector<std::shared_ptr<IconRecordingApp>> getOutputs();
virtual std::vector<std::shared_ptr<IconPlaybackApp>> getPlayback();
#else
virtual std::vector<AudioDevice> 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<Tab> addTab();
virtual std::vector<Tab> removeTab(const std::uint32_t &); virtual std::vector<Tab> removeTab(const std::uint32_t &);
virtual std::optional<Tab> refreshTab(const std::uint32_t &); virtual std::optional<Tab> refreshTab(const std::uint32_t &);
virtual std::vector<Tab> changeTabOrder(const std::vector<int> &);
protected:
virtual void stopSounds(bool = false);
virtual bool stopSound(const std::uint32_t &);
protected:
virtual std::optional<PlayingSound> playSound(const std::uint32_t &); virtual std::optional<PlayingSound> playSound(const std::uint32_t &);
virtual std::optional<PlayingSound> pauseSound(const std::uint32_t &); virtual std::optional<PlayingSound> pauseSound(const std::uint32_t &);
virtual std::optional<PlayingSound> resumeSound(const std::uint32_t &); virtual std::optional<PlayingSound> resumeSound(const std::uint32_t &);
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<int> &);
virtual std::string getHotkeySequence(const std::vector<int> &);
virtual void changeSettings(const Settings &);
virtual std::vector<std::uint32_t> getFavouriteIds();
virtual void markFavourite(const std::uint32_t &, bool);
virtual std::optional<Tab> addTab();
virtual std::vector<Sound> getTabContent(const Tab &) const;
virtual std::vector<Tab> changeTabOrder(const std::vector<int> &);
virtual bool deleteSound(const std::uint32_t &);
#if defined(__linux__)
virtual std::vector<std::shared_ptr<IconRecordingApp>> getOutputs();
virtual std::vector<std::shared_ptr<IconPlaybackApp>> getPlayback();
void stopPassthrough();
virtual bool startPassthrough(const std::string &);
#else
virtual std::vector<AudioDevice> getOutputs();
#endif
public: public:
virtual ~Window(); virtual ~Window();
virtual void setup(); virtual void setup();
virtual void mainLoop() = 0; virtual void mainLoop() = 0;
virtual void onError(const ErrorCode &) = 0; virtual void onError(const Enums::ErrorCode &) = 0;
virtual void onSoundPlayed( virtual void onSoundPlayed(const PlayingSound &);
const PlayingSound &); //* This will be called when a sound is played through a hotkey. PlaySound
//* will be called before this gets called
virtual void onSoundFinished(const PlayingSound &); virtual void onSoundFinished(const PlayingSound &);
virtual void onHotKeyReceived(const std::vector<int> &); virtual void onHotKeyReceived(const std::vector<int> &);
virtual void onSoundProgressed(const PlayingSound &) = 0; virtual void onSoundProgressed(const PlayingSound &) = 0;