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
node_modules
yarn.lock
package.json
package.json
compile_commands.json

View File

@ -1,5 +1,6 @@
#pragma once
#include <core/global/objects.hpp>
#include <core/objects/data.hpp>
#include <core/objects/settings.hpp>
#include <string>
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__)
#include <helper/audio/linux/backend.hpp>
#endif
#include "objects.hpp"
#include <core/config/config.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/threads/processing.hpp>
#include <helper/queue/queue.hpp>
#include <helper/ytdl/youtube-dl.hpp>
#include <memory>
#include <ui/ui.hpp>
@ -20,15 +22,15 @@ namespace Soundux
inline Objects::Data gData;
inline Objects::Audio gAudio;
#if defined(__linux__)
inline Objects::IconFetcher gIcons;
inline std::optional<Objects::IconFetcher> gIcons;
inline std::shared_ptr<Objects::AudioBackend> gAudioBackend;
#endif
inline Objects::Queue gQueue;
inline Objects::Config gConfig;
inline Objects::YoutubeDl gYtdl;
inline Objects::Hotkeys gHotKeys;
inline Objects::Settings gSettings;
inline std::unique_ptr<Objects::Window> gGui;
inline Objects::ProcessingQueue<std::uintptr_t> gQueue;
/* 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;

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 "globals.hpp"
#include "data.hpp"
#include <core/global/globals.hpp>
#include <fancy.hpp>
#include <functional>
#include <optional>
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
#include <atomic>
#include <core/global/objects.hpp>
#include <core/objects/objects.hpp>
#include <cstdint>
#include <map>
#include <memory>

View File

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

View File

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

View File

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

View File

@ -9,24 +9,24 @@ namespace Soundux
{
bool setup();
inline pw_core *(*pw_context_connect)(pw_context *, pw_properties *, std::size_t);
inline pw_context *(*pw_context_new)(pw_loop *, pw_properties *, std::size_t);
inline pw_main_loop *(*pw_main_loop_new)(pw_properties *);
inline pw_loop *(*pw_main_loop_get_loop)(pw_main_loop *);
inline void (*pw_proxy_add_listener)(pw_proxy *, spa_hook *, pw_proxy_events *, void *);
inline int (*pw_properties_setf)(pw_properties *, const char *, const char *, ...);
inline int (*pw_properties_set)(pw_properties *, const char *, const char *);
inline pw_properties *(*pw_properties_new)(const char *, ...);
inline void (*pw_main_loop_destroy)(pw_main_loop *);
inline void (*pw_properties_free)(pw_properties *);
inline int (*pw_main_loop_quit)(pw_main_loop *);
inline void (*pw_context_destroy)(pw_context *);
inline int (*pw_main_loop_run)(pw_main_loop *);
inline int (*pw_core_disconnect)(pw_core *);
inline void (*pw_proxy_destroy)(pw_proxy *);
inline void (*pw_init)(int *, char **);
//* We declare function pointers here so that we can use dlsym to assign them later.
inline pw_core *(*context_connect)(pw_context *, pw_properties *, std::size_t);
inline pw_context *(*context_new)(pw_loop *, pw_properties *, std::size_t);
inline pw_main_loop *(*main_loop_new)(pw_properties *);
inline pw_loop *(*main_loop_get_loop)(pw_main_loop *);
inline void (*proxy_add_listener)(pw_proxy *, spa_hook *, pw_proxy_events *, void *);
inline int (*properties_setf)(pw_properties *, const char *, const char *, ...);
inline int (*properties_set)(pw_properties *, const char *, const char *);
inline pw_properties *(*properties_new)(const char *, ...);
inline void (*main_loop_destroy)(pw_main_loop *);
inline void (*properties_free)(pw_properties *);
inline int (*main_loop_quit)(pw_main_loop *);
inline void (*context_destroy)(pw_context *);
inline int (*main_loop_run)(pw_main_loop *);
inline int (*core_disconnect)(pw_core *);
inline void (*proxy_destroy)(pw_proxy *);
inline void (*init)(int *, char **);
} // namespace PipeWireApi
} // namespace Soundux
#endif

View File

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

View File

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

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;
#else
auto *libpulse = dlopen("libpulse.so", RTLD_LAZY);
if (!libpulse)
{
//* For Ubuntu
libpulse = dlopen("/usr/lib/x86_64-linux-gnu/libpulse.so.0", RTLD_LAZY);
}
if (libpulse)
{
try
{
#define stringify(what) #what
#define load(name) loadFunc(libpulse, name, stringify(pa_##name))
load(mainloop_new);
load(mainloop_iterate);
load(mainloop_get_api);
load(context_new);
load(context_connect);
load(context_set_state_callback);
load(context_load_module);
load(context_get_module_info_list);
load(context_get_source_output_info_list);
load(context_get_sink_input_info_list);
load(context_get_server_info);
load(proplist_gets);
load(context_set_default_source);
load(context_move_sink_input_by_name);
load(context_get_server_info);
load(context_move_sink_input_by_index);
load(context_move_source_output_by_name);
load(context_move_source_output_by_index);
load(context_set_sink_input_mute);
load(context_unload_module);
load(context_get_state);
load(operation_get_state);
return true;
load(mainloop_new);
load(mainloop_iterate);
load(mainloop_get_api);
load(context_new);
load(context_connect);
load(context_set_state_callback);
load(context_load_module);
load(context_get_module_info_list);
load(context_get_source_output_info_list);
load(context_get_sink_input_info_list);
load(context_get_server_info);
load(proplist_gets);
load(context_set_default_source);
load(context_move_sink_input_by_name);
load(context_get_server_info);
load(context_move_sink_input_by_index);
load(context_move_source_output_by_name);
load(context_move_source_output_by_index);
load(context_set_sink_input_mute);
load(context_unload_module);
load(context_get_state);
load(operation_get_state);
return true;
}
catch (std::exception &e)
{
Fancy::fancy.logTime().failure() << "Loading Functions failed: " << e.what() << std::endl;
}
}
Fancy::fancy.logTime().failure() << "Failed to load pulseaudio" << std::endl;
Globals::gAudioBackend = std::make_shared<Objects::AudioBackend>();
return false;
#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__)
#include "pulse.hpp"
#include "pulseaudio.hpp"
#include "forward.hpp"
#include <cstring>
#include <exception>
#include <fancy.hpp>
#include <helper/audio/linux/pulse/forward.hpp>
namespace Soundux::Objects
{
void PulseAudio::setup()
bool PulseAudio::setup()
{
if (!PulseApi::setup())
{
return;
return false;
}
mainloop = PulseApi::mainloop_new();
mainloopApi = PulseApi::mainloop_get_api(mainloop);
context = PulseApi::context_new(mainloopApi, "soundux");
PulseApi::context_connect(context, nullptr, 0, nullptr);
PulseApi::context_connect(context, nullptr, pa_context_flags::PA_CONTEXT_NOFLAGS, nullptr);
auto data = std::make_pair(false, false);
PulseApi::context_set_state_callback(
@ -53,13 +53,15 @@ namespace Soundux::Objects
if (!data.second)
{
return;
return false;
}
unloadLeftOvers();
fetchDefaultSource();
return !(defaultSource.empty() || serverName.empty() || isRunningPipeWire());
}
void PulseAudio::loadModules()
bool PulseAudio::loadModules()
{
auto playbackApps = getPlaybackApps();
auto recordingApps = getRecordingApps();
@ -74,7 +76,7 @@ namespace Soundux::Objects
}
else
{
*reinterpret_cast<std::uint32_t *>(userData) = id;
*reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
}
},
&nullSink));
@ -90,7 +92,7 @@ namespace Soundux::Objects
}
else
{
*reinterpret_cast<std::uint32_t *>(userData) = id;
*reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
}
},
&loopBack));
@ -105,7 +107,7 @@ namespace Soundux::Objects
}
else
{
*reinterpret_cast<std::uint32_t *>(userData) = id;
*reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
}
},
&passthrough));
@ -120,7 +122,7 @@ namespace Soundux::Objects
}
else
{
*reinterpret_cast<std::uint32_t *>(userData) = id;
*reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
}
},
&passthroughSink));
@ -134,14 +136,23 @@ namespace Soundux::Objects
}
else
{
*reinterpret_cast<std::uint32_t *>(userData) = id;
*reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
}
},
&passthroughLoopBack));
fetchLoopBackSinkId();
if (!nullSink || !loopBack || !loopBackSink || !passthrough || !passthroughSink || !passthroughLoopBack)
{
unloadLeftOvers();
return false;
}
fixPlaybackApps(playbackApps);
fixRecordingApps(recordingApps);
return true;
}
void PulseAudio::destroy()
{
@ -149,14 +160,19 @@ namespace Soundux::Objects
stopSoundInput();
stopPassthrough();
//* We only have to unload these 3 because the other modules depend on these and will automatically be deleted
await(PulseApi::context_unload_module(context, nullSink, nullptr, nullptr));
await(PulseApi::context_unload_module(context, loopBack, nullptr, nullptr));
await(PulseApi::context_unload_module(context, loopBackSink, nullptr, nullptr));
if (nullSink)
await(PulseApi::context_unload_module(context, *nullSink, nullptr, nullptr));
if (loopBack)
await(PulseApi::context_unload_module(context, *loopBack, nullptr, nullptr));
if (loopBackSink)
await(PulseApi::context_unload_module(context, *loopBackSink, nullptr, nullptr));
await(PulseApi::context_unload_module(context, passthrough, nullptr, nullptr));
await(PulseApi::context_unload_module(context, passthroughSink, nullptr, nullptr));
await(PulseApi::context_unload_module(context, passthroughLoopBack, nullptr, nullptr));
if (passthrough)
await(PulseApi::context_unload_module(context, *passthrough, nullptr, nullptr));
if (passthroughSink)
await(PulseApi::context_unload_module(context, *passthroughSink, nullptr, nullptr));
if (passthroughLoopBack)
await(PulseApi::context_unload_module(context, *passthroughLoopBack, nullptr, nullptr));
}
void PulseAudio::await(pa_operation *operation)
{
@ -174,6 +190,7 @@ namespace Soundux::Objects
if (info)
{
reinterpret_cast<PulseAudio *>(userData)->defaultSource = info->default_source_name;
reinterpret_cast<PulseAudio *>(userData)->serverName = info->server_name;
}
},
this));
@ -267,7 +284,7 @@ namespace Soundux::Objects
if (!defaultSource.empty())
{
await(PulseApi::context_unload_module(context, loopBack, nullptr, nullptr));
await(PulseApi::context_unload_module(context, *loopBack, nullptr, nullptr));
await(PulseApi::context_load_module(
context, "module-loopback", ("rate=44100 source=" + defaultSource + " sink=soundux_sink").c_str(),
@ -275,16 +292,16 @@ namespace Soundux::Objects
if (static_cast<int>(id) < 0)
{
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
{
*reinterpret_cast<std::uint32_t *>(userData) = id;
*reinterpret_cast<std::optional<std::uint32_t> *>(userData) = id;
}
},
&loopBack));
if (loopBack == 0)
if (!loopBack)
{
return false;
}
@ -313,7 +330,7 @@ namespace Soundux::Objects
{
if (!defaultSource.empty())
{
await(PulseApi::context_unload_module(context, loopBack, nullptr, nullptr));
await(PulseApi::context_unload_module(context, *loopBack, nullptr, nullptr));
auto result = std::make_pair(&loopBack, false);
@ -353,12 +370,14 @@ namespace Soundux::Objects
}
bool PulseAudio::passthroughFrom(std::shared_ptr<PlaybackApp> app)
{
if (movedPassthroughApplication && movedPassthroughApplication->name == app->name)
auto movedPassthroughScoped = movedPassthroughApplication.scoped();
if (*movedPassthroughScoped && movedPassthroughScoped->get()->name == app->name)
{
Fancy::fancy.logTime().message()
<< "Ignoring sound passthrough request because requested app is already moved" << std::endl;
return true;
}
movedPassthroughScoped.unlock();
if (!stopPassthrough())
{
Fancy::fancy.logTime().warning() << "Failed to stop current passthrough" << std::endl;
@ -396,23 +415,26 @@ namespace Soundux::Objects
}
}
movedPassthroughApplication = std::dynamic_pointer_cast<PulsePlaybackApp>(app);
movedPassthroughScoped.lock();
movedPassthroughScoped = std::dynamic_pointer_cast<PulsePlaybackApp>(app);
return true;
}
bool PulseAudio::stopPassthrough()
{
if (movedPassthroughApplication)
auto movedPassthroughScoped = movedPassthroughApplication.scoped();
if (*movedPassthroughScoped)
{
bool success = false;
for (const auto &app : getPlaybackApps())
{
auto pulseApp = std::dynamic_pointer_cast<PulsePlaybackApp>(app);
if (app->name == movedPassthroughApplication->name)
if (app->name == movedPassthroughApplication->get()->name)
{
await(PulseApi::context_move_sink_input_by_index(
context, pulseApp->id, movedPassthroughApplication->sink,
context, pulseApp->id, movedPassthroughApplication->get()->sink,
[]([[maybe_unused]] pa_context *ctx, int success, void *userData) {
if (success)
{
@ -421,7 +443,7 @@ namespace Soundux::Objects
},
&success));
}
movedPassthroughApplication.reset();
movedPassthroughApplication->reset();
return success;
}
}
@ -436,15 +458,14 @@ namespace Soundux::Objects
return false;
}
std::unique_lock lock(movedAppMutex);
if (movedApplication && movedApplication->name == app->name)
auto movedAppScoped = movedApplication.scoped();
if (*movedAppScoped && movedAppScoped->get()->name == app->name)
{
return true;
}
lock.unlock();
movedAppScoped.unlock();
stopSoundInput();
lock.lock();
for (const auto &recordingApp : getRecordingApps())
{
@ -471,25 +492,25 @@ namespace Soundux::Objects
}
}
movedApplication = std::dynamic_pointer_cast<PulseRecordingApp>(app);
movedAppScoped.lock();
movedAppScoped = std::dynamic_pointer_cast<PulseRecordingApp>(app);
return true;
}
bool PulseAudio::stopSoundInput()
{
std::unique_lock lock(movedAppMutex);
bool success = true;
if (movedApplication)
auto movedAppScoped = movedApplication.scoped();
if (*movedAppScoped)
{
for (const auto &recordingApp : getRecordingApps())
{
auto pulseApp = std::dynamic_pointer_cast<PulseRecordingApp>(recordingApp);
if (pulseApp->name == movedApplication->name)
if (pulseApp->name == movedAppScoped->get()->name)
{
await(PulseApi::context_move_source_output_by_index(
context, pulseApp->id, movedApplication->source,
context, pulseApp->id, movedAppScoped->get()->source,
[]([[maybe_unused]] pa_context *ctx, int success, void *userData) {
if (!success)
{
@ -506,7 +527,7 @@ namespace Soundux::Objects
}
}
}
movedApplication.reset();
movedAppScoped->reset();
}
return success;
@ -587,7 +608,7 @@ namespace Soundux::Objects
bool success = false;
await(PulseApi::context_set_sink_input_mute(
context, loopBackSink, state,
context, *loopBackSink, state,
+[]([[maybe_unused]] pa_context *ctx, int success, void *userData) {
*reinterpret_cast<bool *>(userData) = success;
},
@ -603,7 +624,7 @@ namespace Soundux::Objects
bool PulseAudio::isCurrentlyPassingThrough()
{
return movedPassthroughApplication != nullptr;
return **movedPassthroughApplication != nullptr;
}
bool PulseAudio::switchOnConnectPresent()
@ -643,5 +664,17 @@ namespace Soundux::Objects
},
nullptr));
}
bool PulseAudio::isRunningPipeWire()
{
//* The stuff we do here does is broken on pipewire-pulse, use the native backend instead.
if (serverName.find("PipeWire") != std::string::npos)
{
Fancy::fancy.logTime().message()
<< "Detected PipeWire-Pulse, please use the native pipewire backend" << std::endl;
return true;
}
return false;
}
} // namespace Soundux::Objects
#endif

View File

@ -2,6 +2,8 @@
#include "../backend.hpp"
#include "forward.hpp"
#include <mutex>
#include <optional>
#include <var_guard.hpp>
namespace Soundux
{
@ -27,25 +29,29 @@ namespace Soundux
class PulseAudio : public AudioBackend
{
friend class AudioBackend;
private:
pa_context *context;
pa_mainloop *mainloop;
pa_mainloop_api *mainloopApi;
//* ~= The modules we create =~
std::uint32_t nullSink;
std::uint32_t loopBack;
std::uint32_t loopBackSink;
std::optional<std::uint32_t> nullSink;
std::optional<std::uint32_t> loopBack;
std::optional<std::uint32_t> loopBackSink;
std::uint32_t passthrough;
std::uint32_t passthroughSink;
std::uint32_t passthroughLoopBack;
std::optional<std::uint32_t> passthrough;
std::optional<std::uint32_t> passthroughSink;
std::optional<std::uint32_t> passthroughLoopBack;
//* ~= ~~~~~~~~~~~~~~~~~~~~~ =~
std::string serverName;
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;
void unloadLeftOvers();
@ -56,12 +62,15 @@ namespace Soundux
void fixPlaybackApps(const std::vector<std::shared_ptr<PlaybackApp>> &);
void fixRecordingApps(const std::vector<std::shared_ptr<RecordingApp>> &);
public:
PulseAudio() = default;
protected:
bool setup() override;
public:
//! Is not ran by default to avoid problems with switch-on-connect
bool loadModules();
void setup() override;
void loadModules(); //! Is not ran by default to avoid problems with switch-on-connect
void destroy() override;
bool isRunningPipeWire();
bool useAsDefault() override;
bool revertDefault() override;

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

View File

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

View File

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

View File

@ -8,18 +8,14 @@ namespace Soundux
namespace Helpers
{
#if defined(_WIN32)
std::wstring widen(const std::string &s);
std::string narrow(const std::wstring &s);
#endif
#if defined(__linux__)
std::optional<int> getPpid(int pid);
std::wstring widen(const std::string &);
std::string narrow(const std::wstring &);
#endif
bool deleteFile(const std::string &, bool = true);
bool deleteFile(const std::string &path, bool trash = true);
bool run(const std::string &command);
std::pair<std::string, bool> getResultCompact(const std::string &command);
std::pair<std::vector<std::string>, bool> getResult(const std::string &command);
bool run(const std::string &);
std::pair<std::string, bool> getResultCompact(const std::string &);
std::pair<std::vector<std::string>, bool> getResult(const std::string &);
} // namespace Helpers
} // 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())
{
Fancy::fancy.logTime().warning() << "Failed to parse youtube-dl information" << std::endl;
Globals::gGui->onError(ErrorCode::YtdlInvalidJson);
Globals::gGui->onError(Enums::ErrorCode::YtdlInvalidJson);
return std::nullopt;
}
@ -66,14 +66,14 @@ namespace Soundux::Objects
}
Fancy::fancy.logTime().warning() << "Failed to get info from youtube-dl" << std::endl;
Globals::gGui->onError(ErrorCode::YtdlInformationUnknown);
Globals::gGui->onError(Enums::ErrorCode::YtdlInformationUnknown);
return std::nullopt;
}
bool YoutubeDl::download(const std::string &url)
{
if (!isAvailable)
{
Globals::gGui->onError(ErrorCode::YtdlNotFound);
Globals::gGui->onError(Enums::ErrorCode::YtdlNotFound);
return false;
}
@ -85,7 +85,7 @@ namespace Soundux::Objects
if (!std::regex_match(url, urlRegex))
{
Fancy::fancy.logTime().warning() << "Bad url " >> url << std::endl;
Globals::gGui->onError(ErrorCode::YtdlInvalidUrl);
Globals::gGui->onError(Enums::ErrorCode::YtdlInvalidUrl);
return false;
}
@ -121,7 +121,7 @@ namespace Soundux::Objects
return rtn;
}
Globals::gGui->onError(ErrorCode::TabDoesNotExist);
Globals::gGui->onError(Enums::ErrorCode::TabDoesNotExist);
return false;
}
void YoutubeDl::killDownload()

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

View File

@ -3,7 +3,7 @@
#include <cstdint>
#include <fancy.hpp>
#include <filesystem>
#include <helper/audio/linux/pulse/pulse.hpp>
#include <helper/audio/linux/pulseaudio/pulseaudio.hpp>
#include <helper/json/bindings.hpp>
#include <helper/systeminfo/systeminfo.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(
"setHotkey", [this](std::uint32_t id, const std::vector<int> &keys) { return setHotkey(id, keys); }));
webview->expose(Webview::Function("getHotkeySequence",
[this](const std::vector<int> &keys) { return getHotkeySequence(keys); }));
webview->expose(Webview::Function("getHotkeySequence", [this](const std::vector<int> &keys) {
return Globals::gHotKeys.getKeySequence(keys);
}));
webview->expose(Webview::Function("removeTab", [this](std::uint32_t id) { return removeTab(id); }));
webview->expose(Webview::Function("refreshTab", [this](std::uint32_t id) { return refreshTab(id); }));
webview->expose(Webview::Function(
"moveTabs", [this](const std::vector<int> &newOrder) { return changeTabOrder(newOrder); }));
webview->expose(Webview::Function("markFavorite", [this](const std::uint32_t &id, bool favourite) {
markFavourite(id, favourite);
return getFavouriteIds();
webview->expose(Webview::Function("markFavorite", [this](const std::uint32_t &id, bool favorite) {
Globals::gData.markFavorite(id, favorite);
return Globals::gData.getFavoriteIds();
}));
webview->expose(Webview::Function("getFavorites", [this] { return getFavouriteIds(); }));
webview->expose(Webview::Function("getFavorites", [this] { return Globals::gData.getFavoriteIds(); }));
webview->expose(Webview::Function("isYoutubeDLAvailable", []() { return Globals::gYtdl.available(); }));
webview->expose(
Webview::AsyncFunction("getYoutubeDLInfo", [this](Webview::Promise promise, const std::string &url) {
@ -131,7 +132,7 @@ namespace Soundux::Objects
webview->expose(Webview::Function("getSystemInfo", []() -> std::string { return SystemInfo::getSummary(); }));
webview->expose(Webview::AsyncFunction(
"updateCheck", [this](Webview::Promise promise) { promise.resolve(VersionCheck::getStatus()); }));
webview->expose(Webview::Function("isOnFavorites", [this](bool state) { isOnFavorites(state); }));
webview->expose(Webview::Function("isOnFavorites", [this](bool state) { setIsOnFavorites(state); }));
webview->expose(Webview::Function("deleteSound", [this](std::uint32_t id) { return deleteSound(id); }));
#if !defined(__linux__)
@ -337,7 +338,7 @@ namespace Soundux::Objects
{
webview->callFunction<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)));
}

View File

@ -22,7 +22,7 @@ namespace Soundux
void onSoundFinished(const PlayingSound &sound) 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 onSoundProgressed(const PlayingSound &sound) override;
void onDownloadProgressed(float progress, const std::string &eta) override;

View File

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

View File

@ -1,5 +1,5 @@
#pragma once
#include <core/global/objects.hpp>
#include <core/objects/settings.hpp>
#include <helper/audio/audio.hpp>
#if defined(__linux__)
#include <helper/audio/linux/backend.hpp>
@ -46,53 +46,53 @@ namespace Soundux
std::string muteDuringPlayback;
} translations;
protected:
virtual void onAllSoundsFinished();
virtual void isOnFavorites(bool);
virtual void stopSounds(bool = false);
virtual bool stopSound(const std::uint32_t &);
protected:
virtual std::vector<Sound> getTabContent(const Tab &) const;
#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::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> pauseSound(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> seekSound(const std::uint32_t &, std::uint64_t);
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:
virtual ~Window();
virtual void setup();
virtual void mainLoop() = 0;
virtual void onError(const ErrorCode &) = 0;
virtual void onSoundPlayed(
const PlayingSound &); //* This will be called when a sound is played through a hotkey. PlaySound
//* will be called before this gets called
virtual void onError(const Enums::ErrorCode &) = 0;
virtual void onSoundPlayed(const PlayingSound &);
virtual void onSoundFinished(const PlayingSound &);
virtual void onHotKeyReceived(const std::vector<int> &);
virtual void onSoundProgressed(const PlayingSound &) = 0;