diff --git a/include/factory.hpp b/include/factory.hpp index 43dd2cfd..3855ce22 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -51,6 +51,9 @@ #ifdef HAVE_LIBSNDIO #include "modules/sndio.hpp" #endif +#ifdef HAVE_GIO_UNIX +#include "modules/inhibitor.hpp" +#endif #include "bar.hpp" #include "modules/custom.hpp" #include "modules/temperature.hpp" diff --git a/include/modules/inhibitor.hpp b/include/modules/inhibitor.hpp new file mode 100644 index 00000000..aa2f97d4 --- /dev/null +++ b/include/modules/inhibitor.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include + +#include "ALabel.hpp" +#include "bar.hpp" + +namespace waybar::modules { + +class Inhibitor : public ALabel { + public: + Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&); + ~Inhibitor() override; + auto update() -> void; + auto activated() -> bool; + + private: + auto handleToggle(::GdkEventButton* const& e) -> bool; + + const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_; + const std::string inhibitors_; + int handle_ = -1; +}; + +} // namespace waybar::modules diff --git a/meson.build b/meson.build index 7f2d9562..5e62eecf 100644 --- a/meson.build +++ b/meson.build @@ -86,7 +86,7 @@ wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) -giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) +giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled())) jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') libepoll = dependency('epoll-shim', required: false) @@ -242,6 +242,11 @@ if libsndio.found() src_files += 'src/modules/sndio.cpp' endif +if (giounix.found() and not get_option('logind').disabled()) + add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') + src_files += 'src/modules/inhibitor.cpp' +endif + if get_option('rfkill').enabled() if is_linux add_project_arguments('-DWANT_RFKILL', language: 'cpp') diff --git a/meson_options.txt b/meson_options.txt index f4f60d09..230a53d6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -10,5 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') +option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('tests', type: 'feature', value: 'auto', description: 'Enable tests') option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') diff --git a/src/factory.cpp b/src/factory.cpp index 351aa332..900653b5 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -94,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "sndio") { return new waybar::modules::Sndio(id, config_[name]); } +#endif +#ifdef HAVE_GIO_UNIX + if (ref == "inhibitor") { + return new waybar::modules::Inhibitor(id, bar_, config_[name]); + } #endif if (ref == "temperature") { return new waybar::modules::Temperature(id, config_[name]); diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp new file mode 100644 index 00000000..1e3f2d35 --- /dev/null +++ b/src/modules/inhibitor.cpp @@ -0,0 +1,175 @@ +#include "modules/inhibitor.hpp" + +#include +#include +#include + +namespace { + +using DBus = std::unique_ptr; + +auto dbus() -> DBus { + GError *error = nullptr; + GDBusConnection* connection = + g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + + if (error) { + spdlog::error("g_bus_get_sync() failed: {}", error->message); + g_error_free(error); + connection = nullptr; + } + + auto destructor = [](GDBusConnection* connection) { + GError *error = nullptr; + g_dbus_connection_close_sync(connection, nullptr, &error); + if (error) { + spdlog::error( + "g_bus_connection_close_sync failed(): {}", + error->message); + g_error_free(error); + } + }; + + return DBus{connection, destructor}; +} + +auto getLocks(const DBus& bus, const std::string& inhibitors) -> int { + GError *error = nullptr; + GUnixFDList* fd_list; + int handle; + + auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(), + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "Inhibit", + g_variant_new( + "(ssss)", + inhibitors.c_str(), + "waybar", + "Asked by user", + "block"), + G_VARIANT_TYPE("(h)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &fd_list, + nullptr, + &error); + if (error) { + spdlog::error( + "g_dbus_connection_call_with_unix_fd_list_sync() failed: {}", + error->message); + g_error_free(error); + handle = -1; + } else { + gint index; + g_variant_get(reply, "(h)", &index); + g_variant_unref(reply); + handle = g_unix_fd_list_get(fd_list, index, nullptr); + g_object_unref(fd_list); + } + + return handle; +} + +auto checkInhibitor(const std::string& inhibitor) -> const std::string& { + static const auto inhibitors = std::array{ + "idle", + "shutdown", + "sleep", + "handle-power-key", + "handle-suspend-key", + "handle-hibernate-key", + "handle-lid-switch" + }; + + if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor) + == inhibitors.end()) { + throw std::runtime_error("invalid logind inhibitor " + inhibitor); + } + + return inhibitor; +} + +auto getInhibitors(const Json::Value& config) -> std::string { + std::string inhibitors = "idle"; + + if (config["what"].empty()) { + return inhibitors; + } + + if (config["what"].isString()) { + return checkInhibitor(config["what"].asString()); + } + + if (config["what"].isArray()) { + inhibitors = checkInhibitor(config["what"][0].asString()); + for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { + inhibitors += ":" + checkInhibitor(config["what"][i].asString()); + } + return inhibitors; + } + + return inhibitors; +} + +} + +namespace waybar::modules { + +Inhibitor::Inhibitor(const std::string& id, const Bar& bar, + const Json::Value& config) + : ALabel(config, "inhibitor", id, "{status}", true), + dbus_(::dbus()), + inhibitors_(::getInhibitors(config)) { + event_box_.add_events(Gdk::BUTTON_PRESS_MASK); + event_box_.signal_button_press_event().connect( + sigc::mem_fun(*this, &Inhibitor::handleToggle)); + dp.emit(); +} + +Inhibitor::~Inhibitor() { + if (handle_ != -1) { + ::close(handle_); + } +} + +auto Inhibitor::activated() -> bool { + return handle_ != -1; +} + +auto Inhibitor::update() -> void { + std::string status_text = activated() ? "activated" : "deactivated"; + + label_.get_style_context()->remove_class( + activated() ? "deactivated" : "activated"); + label_.set_markup( + fmt::format(format_, fmt::arg("status", status_text), + fmt::arg("icon", getIcon(0, status_text)))); + label_.get_style_context()->add_class(status_text); + + if (tooltipEnabled()) { + label_.set_tooltip_text(status_text); + } + + return ALabel::update(); +} + +auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { + if (e->button == 1) { + if (activated()) { + ::close(handle_); + handle_ = -1; + } else { + handle_ = ::getLocks(dbus_, inhibitors_); + if (handle_ == -1) { + spdlog::error("cannot get inhibitor locks"); + } + } + } + + return ALabel::handleToggle(e); +} + +} // waybar::modules