Skip to content

Commit

Permalink
[Packages] AudioControl: add DBus service
Browse files Browse the repository at this point in the history
And implement Open() method to be able
to raise the app's window from another process

Signed-off-by: Nikita Bazulin <[email protected]>
  • Loading branch information
baz2142 committed Nov 6, 2024
1 parent c32d358 commit e3a93d0
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 189 deletions.
158 changes: 158 additions & 0 deletions packages/ghaf-audio-control/src/app/App.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
* SPDX-License-Identifier: Apache-2.0
*/

#include "App.hpp"

#include <glibmm/optioncontext.h>

#include <format>

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;

std::istringstream iss(appVms);
std::string buf;

while (getline(iss, buf, ','))
result.push_back(buf);

return result;
}

} // namespace

App::AppMenu::AppMenu(App& app)
: m_app(app)
, m_openItem("Open/Hide Audio Control")
, m_quitItem("Quit")
, m_connections{m_openItem.signal_activate().connect([this]() { m_app.toggleWindow(); }), m_quitItem.signal_activate().connect([this]() { m_app.onQuit(); })}
{
add(m_openItem);
add(m_quitItem);

show_all();
}

App::App(int argc, char** argv)
: m_menu(*this)
, m_indicator(createAppIndicator())
, m_connections{m_dbusService.openSignal().connect(sigc::mem_fun(*this, &App::openWindow))}
{
AppArgs appArgs;

Glib::OptionEntry pulseServerOption;
pulseServerOption.set_long_name("pulseaudio_server");
pulseServerOption.set_description("PulseAudio server address");

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

Glib::OptionEntry appVmsOption;
appVmsOption.set_long_name("app_vms");
appVmsOption.set_description("AppVMs list");

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

Glib::OptionContext context("Application Options");
context.set_main_group(options);

if (!context.parse(argc, argv))
{
Logger::info(context.get_help().c_str());
throw std::runtime_error{"Couldn't parse the command line arguments"};
}

app_indicator_set_icon(m_indicator.get(), appArgs.indicatorIconPath.c_str());

m_audioControl = std::make_unique<AudioControl>(std::make_unique<Backend::PulseAudio::AudioControlBackend>(appArgs.pulseServerAddress),
GetAppVmsList(appArgs.appVms));
}

int App::start(int argc, char** argv)
{
Logger::debug(__PRETTY_FUNCTION__);
return run(argc, argv);
}

bool App::onWindowDelete([[maybe_unused]] GdkEventAny* event)
{
Logger::debug(__PRETTY_FUNCTION__);

m_window->hide();
return true;
}

void App::openWindow()
{
m_window->show_all();
m_window->present();
}

void App::toggleWindow()
{
auto& window = *m_window;

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

if (window.is_visible())
window.hide();
else
openWindow();
}

void App::onQuit()
{
release();
quit();
}

void App::on_activate()
{
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, &App::onWindowDelete));

hold();
add_window(*m_window);

m_window->show_all();
}

RaiiWrap<AppIndicator*> App::createAppIndicator()
{
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, {}};
}
64 changes: 64 additions & 0 deletions packages/ghaf-audio-control/src/app/App.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <GhafAudioControl/Backends/PulseAudio/AudioControlBackend.hpp>
#include <GhafAudioControl/utils/Debug.hpp>
#include <GhafAudioControl/utils/Logger.hpp>
#include <GhafAudioControl/widgets/AudioControl.hpp>

#include "DBusService.hpp"

#include <gtkmm/application.h>
#include <gtkmm/applicationwindow.h>

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

class App : public Gtk::Application
{
private:
class AppMenu : public Gtk::Menu
{
public:
AppMenu(App& app);
~AppMenu() override = default;

private:
App& m_app;

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

ghaf::AudioControl::ConnectionContainer m_connections;
};

public:
App(int argc, char** argv);

int start(int argc, char** argv);

[[nodiscard]] ghaf::AudioControl::RaiiWrap<AppIndicator*> createAppIndicator();

void openWindow();
void toggleWindow();

private:
bool onWindowDelete([[maybe_unused]] GdkEventAny* event);
void onQuit();

void on_activate() override;

private:
DBusService m_dbusService;

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

AppMenu m_menu;
ghaf::AudioControl::RaiiWrap<AppIndicator*> m_indicator;

ghaf::AudioControl::ConnectionContainer m_connections;
};
9 changes: 8 additions & 1 deletion packages/ghaf-audio-control/src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ pkg_check_modules(AYATANA REQUIRED ayatana-appindicator3-0.1)
include_directories(${AYATANA_INCLUDE_DIRS})
link_directories(${AYATANA_INCLUDE_DIRS})

add_executable(GhafAudioControlStandalone main.cpp)
add_executable(GhafAudioControlStandalone
App.hpp
DBusService.hpp

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

include(GNUInstallDirs)
Expand Down
68 changes: 68 additions & 0 deletions packages/ghaf-audio-control/src/app/DBusService.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
* SPDX-License-Identifier: Apache-2.0
*/

#include "DBusService.hpp"

#include <GhafAudioControl/utils/Logger.hpp>

#include <giomm/dbusownname.h>

#include <format>
#include <iostream>

using namespace ghaf::AudioControl;

namespace
{

constexpr auto IntrospectionXml = "<node>"
" <interface name='org.ghaf.Audio'>"
" <method name='Open'/>"
" </interface>"
"</node>";

}

DBusService::DBusService()
: m_interfaceVtable(sigc::mem_fun(*this, &DBusService::onMethodCall))
, m_introspectionData(Gio::DBus::NodeInfo::create_for_xml(IntrospectionXml))
, m_connectionId(Gio::DBus::own_name(Gio::DBus::BUS_TYPE_SESSION, "org.ghaf.Audio", sigc::mem_fun(*this, &DBusService::onBusAcquired),
sigc::mem_fun(*this, &DBusService::onNameAcquired), sigc::mem_fun(*this, &DBusService::onNameLost)))
{
}

DBusService::~DBusService()
{
Gio::DBus::unown_name(m_connectionId);
}

void DBusService::onBusAcquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring&)
{
Logger::debug("The bus is acquired, registering...");
connection->register_object("/org/ghaf/Audio", m_introspectionData->lookup_interface("org.ghaf.Audio"), m_interfaceVtable);
}

void DBusService::onNameAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, const Glib::ustring&)
{
Logger::debug("The DBus service org.ghaf.Audio has been registered");
}

void DBusService::onNameLost(const Glib::RefPtr<Gio::DBus::Connection>&, const Glib::ustring&)
{
Logger::error("Couldn't register service for org.ghaf.Audio");
}

void DBusService::onMethodCall(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& sender, const Glib::ustring& objectPath,
const Glib::ustring& interfaceName, const Glib::ustring& methodName, const Glib::VariantContainerBase& parameters,
const Glib::RefPtr<Gio::DBus::MethodInvocation>& invocation)
{
Logger::debug(std::format("Invokated method: {}", methodName.c_str()));

if (methodName == "Open")
{
m_openSignal();
invocation->return_value(parameters);
}
}
43 changes: 43 additions & 0 deletions packages/ghaf-audio-control/src/app/DBusService.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <giomm/dbusinterfacevtable.h>
#include <giomm/dbusintrospection.h>
#include <giomm/dbusmethodinvocation.h>

class DBusService
{
public:
using OpenSignalSignature = sigc::signal<void()>;

public:
DBusService();
~DBusService();

public:
OpenSignalSignature openSignal() const noexcept
{
return m_openSignal;
}

private:
void onBusAcquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
void onNameAcquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
void onNameLost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);

void onMethodCall(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& sender, const Glib::ustring& objectPath,
const Glib::ustring& interfaceName, const Glib::ustring& methodName, const Glib::VariantContainerBase& parameters,
const Glib::RefPtr<Gio::DBus::MethodInvocation>& invocation);

private:
OpenSignalSignature m_openSignal;

Gio::DBus::InterfaceVTable m_interfaceVtable;
Glib::RefPtr<Gio::DBus::NodeInfo> m_introspectionData;

guint m_connectionId;
};
Loading

0 comments on commit e3a93d0

Please sign in to comment.