Skip to content

Commit

Permalink
[Packages] AudioControl: add tray icon support
Browse files Browse the repository at this point in the history
Signed-off-by: Nikita Bazulin <[email protected]>
  • Loading branch information
baz2142 committed Nov 4, 2024
1 parent b8263bb commit c58bd25
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 44 deletions.
5 changes: 4 additions & 1 deletion packages/ghaf-audio-control/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
stdenv,
cmake,
gtkmm3,
libayatana-appindicator,
libpulseaudio,
ninja,
pkg-config,
Expand All @@ -19,12 +20,14 @@ stdenv.mkDerivation rec {

nativeBuildInputs = [
cmake
gtkmm3
libayatana-appindicator
ninja
pkg-config
gtkmm3
];
buildInputs = [
gtkmm3
libayatana-appindicator
libpulseaudio
];

Expand Down
8 changes: 7 additions & 1 deletion packages/ghaf-audio-control/src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ cmake_minimum_required(VERSION 3.5)

project(GhafAudioControlStandalone LANGUAGES CXX)

find_package(PkgConfig REQUIRED)
pkg_check_modules(AYATANA REQUIRED ayatana-appindicator3-0.1)

include_directories(${AYATANA_INCLUDE_DIRS})
link_directories(${AYATANA_INCLUDE_DIRS})

add_executable(GhafAudioControlStandalone main.cpp)
target_link_libraries(GhafAudioControlStandalone GhafAudioControl)
target_link_libraries(GhafAudioControlStandalone GhafAudioControl ${AYATANA_LIBRARIES})

include(GNUInstallDirs)
install(TARGETS ${PROJECT_NAME})
193 changes: 158 additions & 35 deletions packages/ghaf-audio-control/src/app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@
#include <GhafAudioControl/utils/Logger.hpp>
#include <GhafAudioControl/widgets/AudioControl.hpp>

#include <gtkmm/applicationwindow.h>
#include <gtkmm/menu.h>
#include <gtkmm/menuitem.h>
#include <gtkmm/radiomenuitem.h>
#include <gtkmm/scale.h>

#include <glibmm/main.h>
#include <glibmm/optioncontext.h>
#include <gtkmm/applicationwindow.h>

#include <libayatana-appindicator/app-indicator.h>

#include <format>

Expand All @@ -19,6 +26,15 @@ using namespace ghaf::AudioControl;
namespace
{

constexpr auto AppId = "Ghaf Audio Control";

struct AppArgs
{
Glib::ustring pulseServerAddress;
Glib::ustring indicatorIconPath;
Glib::ustring appVms;
};

std::vector<std::string> GetAppVmsList(const std::string& appVms)
{
std::vector<std::string> result;
Expand All @@ -32,59 +48,166 @@ std::vector<std::string> GetAppVmsList(const std::string& appVms)
return result;
}

int GtkClient(const std::string& pulseAudioServerAddress, const std::string& appVms)
class MyApp : public Gtk::Application
{
auto app = Gtk::Application::create();
Gtk::ApplicationWindow window;
window.set_title("Ghaf Audio Control");
private:
class AppMenu : public Gtk::Menu
{
public:
AppMenu(MyApp& app)
: m_app(app)
, m_openItem("Open/Hide Audio Control")
, m_quitItem("Quit")
{
add(m_openItem);
add(m_quitItem);

const auto onOpen = [this]()
{
auto& window = *m_app.m_window;

Logger::debug(std::format("Indicator has been activated. window.is_visible: {}", window.is_visible()));

if (window.is_visible())
window.hide();
else
{
window.show_all();
window.present();
}
};

m_connections = {m_openItem.signal_activate().connect(onOpen), m_quitItem.signal_activate().connect([this]() { m_app.onQuit(); })};

show_all();
}

~AppMenu() override = default;

private:
MyApp& m_app;

Gtk::MenuItem m_openItem;
Gtk::MenuItem m_quitItem;

ConnectionContainer m_connections;
};

public:
MyApp(int argc, char** argv)
: m_menu(*this)
, m_indicator(createAppIndicator())
{
AppArgs appArgs;

AudioControl audioControl{std::make_unique<Backend::PulseAudio::AudioControlBackend>(pulseAudioServerAddress), GetAppVmsList(appVms)};
Glib::OptionEntry pulseServerOption;
pulseServerOption.set_long_name("pulseaudio_server");
pulseServerOption.set_description("PulseAudio server address");

window.add(audioControl);
window.show_all();
Glib::OptionEntry indicatorIconPathOption;
indicatorIconPathOption.set_long_name("indicator_icon_path");
indicatorIconPathOption.set_description("Tray's icon indicator path");

return app->run(window);
}
Glib::OptionEntry appVmsOption;
appVmsOption.set_long_name("app_vms");
appVmsOption.set_description("AppVMs list");

} // namespace
Glib::OptionGroup options("Main", "Main");
options.add_entry(pulseServerOption, appArgs.pulseServerAddress);
options.add_entry(indicatorIconPathOption, appArgs.indicatorIconPath);
options.add_entry(appVmsOption, appArgs.appVms);

int main(int argc, char** argv)
{
pthread_setname_np(pthread_self(), "main");
Glib::OptionContext context("Application Options");
context.set_main_group(options);

Glib::ustring pulseServerAddress;
Glib::OptionEntry pulseServerOption;
pulseServerOption.set_long_name("pulseaudio_server");
pulseServerOption.set_description("PulseAudio server address");
if (!context.parse(argc, argv))
{
Logger::info(context.get_help().c_str());
throw std::runtime_error{"Couldn't parse the command line arguments"};
}

Glib::ustring appVms;
Glib::OptionEntry appVmsOption;
appVmsOption.set_long_name("app_vms");
appVmsOption.set_description("AppVMs list");
app_indicator_set_icon(m_indicator.get(), appArgs.indicatorIconPath.c_str());

Glib::OptionGroup options("Main", "Main");
options.add_entry(pulseServerOption, pulseServerAddress);
options.add_entry(appVmsOption, appVms);
m_audioControl = std::make_unique<AudioControl>(std::make_unique<Backend::PulseAudio::AudioControlBackend>(appArgs.pulseServerAddress),
GetAppVmsList(appArgs.appVms));
}

Glib::OptionContext context("Application Options");
context.set_main_group(options);
int start(int argc, char** argv)
{
Logger::debug(__PRETTY_FUNCTION__);
return run(argc, argv);
}

try
[[nodiscard]] RaiiWrap<AppIndicator*> createAppIndicator()
{
if (!context.parse(argc, argv))
throw std::runtime_error{"Couldn't parse command line arguments"};
const auto contructor = [this](AppIndicator*& indicator)
{
indicator = app_indicator_new(AppId, "", APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);
app_indicator_set_label(indicator, AppId, AppId);
app_indicator_set_title(indicator, AppId);
app_indicator_set_menu(indicator, GTK_MENU(m_menu.gobj()));
};

return {contructor, {}};
}
catch (const Glib::Error& ex)

protected:
bool onWindowDelete([[maybe_unused]] GdkEventAny* event)
{
Logger::error(std::format("Error: {}", ex.what().c_str()));
Logger::info(context.get_help().c_str());
Logger::debug(__PRETTY_FUNCTION__);

return 1;
m_window->hide();
return true;
}

void onQuit()
{
Logger::debug(__PRETTY_FUNCTION__);

release();
quit();
}

void on_activate() override
{
Logger::debug(__PRETTY_FUNCTION__);

m_window = std::make_unique<Gtk::ApplicationWindow>();
m_window->set_title(AppId);
m_window->add(*m_audioControl);

m_window->signal_delete_event().connect(sigc::mem_fun(*this, &MyApp::onWindowDelete));

hold();
add_window(*m_window);

m_window->show_all();
}

private:
std::unique_ptr<AudioControl> m_audioControl;
std::unique_ptr<Gtk::ApplicationWindow> m_window;

AppMenu m_menu;
RaiiWrap<AppIndicator*> m_indicator;
};

} // namespace

int main(int argc, char** argv)
{
pthread_setname_np(pthread_self(), "main");

try
{
return GtkClient(pulseServerAddress, appVms);
MyApp app(argc, argv);
return app.start(argc, argv);
}
catch (const Glib::Error& ex)
{
Logger::error(std::format("Error: {}", ex.what().c_str()));
return 1;
}
catch (const std::exception& e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ void AudioControlBackend::cardInfoCallback(pa_context* context, const pa_card_in
if (info == nullptr)
return;

Logger::debug("\n###############################################");
Logger::debug("###############################################");
Logger::debug(std::format("Card. index: {}, name: {}", info->index, info->name));

for (size_t i = 0; i < info->n_ports; ++i)
Expand All @@ -345,25 +345,21 @@ void AudioControlBackend::cardInfoCallback(pa_context* context, const pa_card_in

auto* self = static_cast<AudioControlBackend*>(data);

auto sinkPredicate = [&info](const ISink& sink) -> bool
const auto sinkPredicate = [&info](const ISink& sink)
{
return dynamic_cast<const Sink&>(sink).getCardIndex() == info->index;
};

for (auto it : self->m_sinks.findByPredicate(sinkPredicate))
{
self->m_sinks.update(it, [&info](ISink& sink) { dynamic_cast<Sink&>(sink).update(*info); });
}

auto sourcePredicate = [&info](const ISource& source)
const auto sourcePredicate = [&info](const ISource& source)
{
return dynamic_cast<const Source&>(source).getCardIndex() == info->index;
};

for (auto it : self->m_sources.findByPredicate(sourcePredicate))
{
self->m_sources.update(it, [&info](ISource& source) { dynamic_cast<Source&>(source).update(*info); });
}
}

} // namespace ghaf::AudioControl::Backend::PulseAudio

0 comments on commit c58bd25

Please sign in to comment.