diff --git a/include/factory.hpp b/include/factory.hpp index 2d07b4bc..1d0f147f 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -42,6 +42,9 @@ #ifdef HAVE_LIBEVDEV #include "modules/keyboard_state.hpp" #endif +#ifdef HAVE_GAMEMODE +#include "modules/gamemode.hpp" +#endif #ifdef HAVE_UPOWER #include "modules/upower/upower.hpp" #endif diff --git a/include/modules/gamemode.hpp b/include/modules/gamemode.hpp new file mode 100644 index 00000000..b027393f --- /dev/null +++ b/include/modules/gamemode.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +#include "ALabel.hpp" +#include "giomm/dbusconnection.h" +#include "giomm/dbusproxy.h" +#include "glibconfig.h" +#include "gtkmm/box.h" +#include "gtkmm/image.h" +#include "gtkmm/label.h" +#include "gtkmm/overlay.h" + +namespace waybar::modules { + +class Gamemode : public AModule { + public: + Gamemode(const std::string &, const Json::Value &); + ~Gamemode(); + auto update() -> void; + + private: + const std::string DEFAULT_ICON_NAME = "input-gaming-symbolic"; + const std::string DEFAULT_FORMAT = "{glyph}"; + const std::string DEFAULT_FORMAT_ALT = "{glyph} {count}"; + const std::string DEFAULT_TOOLTIP_FORMAT = "Games running: {count}"; + const std::string DEFAULT_GLYPH = ""; + + void appear(const Glib::RefPtr &connection, const Glib::ustring &name, + const Glib::ustring &name_owner); + void disappear(const Glib::RefPtr &connection, const Glib::ustring &name); + void prepareForSleep_cb(const Glib::RefPtr &connection, + const Glib::ustring &sender_name, const Glib::ustring &object_path, + const Glib::ustring &interface_name, const Glib::ustring &signal_name, + const Glib::VariantContainerBase ¶meters); + void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name, + const Glib::VariantContainerBase &arguments); + + void getData(); + bool handleToggle(GdkEventButton *const &); + + // Config + std::string format = DEFAULT_FORMAT; + std::string format_alt = DEFAULT_FORMAT_ALT; + std::string tooltip_format = DEFAULT_TOOLTIP_FORMAT; + std::string glyph = DEFAULT_GLYPH; + bool tooltip = true; + bool hideNotRunning = true; + bool useIcon = true; + uint iconSize = 20; + uint iconSpacing = 4; + std::string iconName = DEFAULT_ICON_NAME; + + Gtk::Box box_; + Gtk::Image icon_; + Gtk::Label label_; + + const std::string dbus_name = "com.feralinteractive.GameMode"; + const std::string dbus_obj_path = "/com/feralinteractive/GameMode"; + const std::string dbus_interface = "org.freedesktop.DBus.Properties"; + const std::string dbus_get_interface = "com.feralinteractive.GameMode"; + + uint gameCount = 0; + + std::string lastStatus; + bool showAltText = false; + + guint login1_id; + Glib::RefPtr gamemode_proxy; + Glib::RefPtr system_connection; + bool gamemodeRunning; + guint gamemodeWatcher_id; +}; + +} // namespace waybar::modules diff --git a/man/waybar-gamemode.5.scd b/man/waybar-gamemode.5.scd new file mode 100644 index 00000000..f7847bd9 --- /dev/null +++ b/man/waybar-gamemode.5.scd @@ -0,0 +1,95 @@ +waybar-gamemode(5) + +# NAME + +waybar - gamemode module + +# DESCRIPTION + +The *gamemode* module displays if any game or application is running with ++ +Feral Gamemode optimizations. + +# CONFIGURATION + +*format*: ++ + typeof: string ++ + default: {glyph} ++ + The text format. + +*format-alt*: ++ + typeof: string ++ + default: {glyph} {count} ++ + The text format when toggled. + +*tooltip*: ++ + typeof: bool ++ + defualt: true ++ + Option to disable tooltip on hover. + +*tooltip-format*: ++ + typeof: string ++ + default: Games running: {glyph} ++ + The text format of the tooltip. + +*hide-not-running*: ++ + typeof: bool ++ + default: true ++ + Defines if the module should be hidden if no games are running. + +*use-icon*: ++ + typeof: bool ++ + default: true ++ + Defines if the module should display a GTK icon instead of the specified *glyph* + +*glyph*: ++ + typeof: string ++ + default:  ++ + The string icon to display. Only visible if *use-icon* is set to false. + +*icon-name*: ++ + typeof: string ++ + default: input-gaming-symbolic ++ + The GTK icon to display. Only visible if *use-icon* is set to true. + +*icon-size*: ++ + typeof: unsigned integer ++ + default: 20 ++ + Defines the size of the icons. + +*icon-spacing*: ++ + typeof: unsigned integer ++ + default: 4 ++ + Defines the spacing between the icon and the text. + +# FORMAT REPLACEMENTS + +*{glyph}*: The string icon glyph to use instead. + +*{count}*: The amount of games running with gamemode optimizations. + +# TOOLTIP FORMAT REPLACEMENTS + +*{count}*: The amount of games running with gamemode optimizations. + +# EXAMPLES + +``` +"gamemode": { + "format": "{glyph}", + "format-alt": "{glyph} {count}", + "glyph": "", + "hide-not-running": true, + "use-icon": true, + "icon-name": "input-gaming-symbolic", + "icon-spacing": 4, + "icon-size": 20, + "tooltip": true, + "tooltip-format": "Games running: {count}" +} + +``` + +# STYLE + +- *#gamemode* +- *#gamemode.running* diff --git a/meson.build b/meson.build index b3a4a4cb..ea63c902 100644 --- a/meson.build +++ b/meson.build @@ -204,6 +204,11 @@ if libnl.found() and libnlgen.found() src_files += 'src/modules/network.cpp' endif +if (giounix.found() and not get_option('logind').disabled()) + add_project_arguments('-DHAVE_GAMEMODE', language: 'cpp') + src_files += 'src/modules/gamemode.cpp' +endif + if (upower_glib.found() and giounix.found() and not get_option('logind').disabled()) add_project_arguments('-DHAVE_UPOWER', language: 'cpp') src_files += 'src/modules/upower/upower.cpp' diff --git a/src/factory.cpp b/src/factory.cpp index 5f2d755d..9defb4e7 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -12,6 +12,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { return new waybar::modules::Battery(id, config_[name]); } #endif +#ifdef HAVE_GAMEMODE + if (ref == "gamemode") { + return new waybar::modules::Gamemode(id, config_[name]); + } +#endif #ifdef HAVE_UPOWER if (ref == "upower") { return new waybar::modules::upower::UPower(id, config_[name]); diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp new file mode 100644 index 00000000..c7accd58 --- /dev/null +++ b/src/modules/gamemode.cpp @@ -0,0 +1,237 @@ +#include "modules/gamemode.hpp" + +#include +#include + +#include +#include +#include + +#include "AModule.hpp" +#include "giomm/dbusconnection.h" +#include "giomm/dbusinterface.h" +#include "giomm/dbusproxy.h" +#include "giomm/dbuswatchname.h" +#include "glibmm/error.h" +#include "glibmm/ustring.h" +#include "glibmm/variant.h" +#include "glibmm/varianttype.h" +#include "gtkmm/icontheme.h" +#include "gtkmm/label.h" +#include "gtkmm/tooltip.h" + +namespace waybar::modules { +Gamemode::Gamemode(const std::string& id, const Json::Value& config) + : AModule(config, "gamemode", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0), icon_(), label_() { + box_.pack_start(icon_); + box_.pack_start(label_); + box_.set_name(name_); + event_box_.add(box_); + + // Tooltip + if (config_["tooltip"].isBool()) { + tooltip = config_["tooltip"].asBool(); + } + box_.set_has_tooltip(tooltip); + + // Tooltip Format + if (config_["tooltip-format"].isString()) { + tooltip_format = config_["tooltip-format"].asString(); + } + + // Hide when game count is 0 + if (config_["hide-not-running"].isBool()) { + hideNotRunning = config_["hide-not-running"].asBool(); + } + + // Icon Name + if (config_["icon-name"].isString()) { + iconName = config_["icon-name"].asString(); + } + + // Icon Spacing + if (config_["icon-spacing"].isUInt()) { + iconSpacing = config_["icon-spacing"].asUInt(); + } + box_.set_spacing(iconSpacing); + + // Wether to use icon or not + if (config_["use-icon"].isBool()) { + useIcon = config_["use-icon"].asBool(); + } + + // Icon Size + if (config_["icon-size"].isUInt()) { + iconSize = config_["icon-size"].asUInt(); + } + icon_.set_pixel_size(iconSize); + + // Format + if (config_["format"].isString()) { + format = config_["format"].asString(); + } + + // Format Alt + if (config_["format-alt"].isString()) { + format_alt = config_["format-alt"].asString(); + } + + // Glyph + if (config_["glyph"].isString()) { + glyph = config_["glyph"].asString(); + } + + gamemodeWatcher_id = Gio::DBus::watch_name( + Gio::DBus::BUS_TYPE_SESSION, dbus_name, sigc::mem_fun(*this, &Gamemode::appear), + sigc::mem_fun(*this, &Gamemode::disappear), + Gio::DBus::BusNameWatcherFlags::BUS_NAME_WATCHER_FLAGS_AUTO_START); + + // Connect to gamemode + gamemode_proxy = Gio::DBus::Proxy::create_for_bus_sync(Gio::DBus::BusType::BUS_TYPE_SESSION, + dbus_name, dbus_obj_path, dbus_interface); + if (!gamemode_proxy) { + throw std::runtime_error("Unable to connect to gamemode DBus!..."); + } else { + gamemode_proxy->signal_signal().connect(sigc::mem_fun(*this, &Gamemode::notify_cb)); + } + + // Connect to Login1 PrepareForSleep signal + system_connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SYSTEM); + if (!system_connection) { + throw std::runtime_error("Unable to connect to the SYSTEM Bus!..."); + } else { + login1_id = system_connection->signal_subscribe( + sigc::mem_fun(*this, &Gamemode::prepareForSleep_cb), "org.freedesktop.login1", + "org.freedesktop.login1.Manager", "PrepareForSleep", "/org/freedesktop/login1"); + } + + event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Gamemode::handleToggle)); +} + +Gamemode::~Gamemode() { + if (gamemode_proxy) gamemode_proxy->unreference(); + if (gamemodeWatcher_id > 0) { + Gio::DBus::unwatch_name(gamemodeWatcher_id); + gamemodeWatcher_id = 0; + } + if (login1_id > 0) { + system_connection->signal_unsubscribe(login1_id); + login1_id = 0; + } +} + +// Gets the DBus ClientCount +void Gamemode::getData() { + if (gamemodeRunning && gamemode_proxy) { + try { + // Get game count + auto parameters = Glib::VariantContainerBase( + g_variant_new("(ss)", dbus_get_interface.c_str(), "ClientCount")); + Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters); + if (data && data.is_of_type(Glib::VariantType("(v)"))) { + Glib::VariantBase variant; + g_variant_get(data.gobj_copy(), "(v)", &variant); + if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) { + g_variant_get(variant.gobj_copy(), "i", &gameCount); + return; + } + } + } catch (Glib::Error& e) { + spdlog::error("Gamemode Error {}", e.what().c_str()); + } + } + gameCount = 0; +} + +// Whenever the DBus ClientCount changes +void Gamemode::notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, + const Glib::VariantContainerBase& arguments) { + if (signal_name == "PropertiesChanged") { + getData(); + dp.emit(); + } +} + +void Gamemode::prepareForSleep_cb(const Glib::RefPtr& connection, + const Glib::ustring& sender_name, + const Glib::ustring& object_path, + const Glib::ustring& interface_name, + const Glib::ustring& signal_name, + const Glib::VariantContainerBase& parameters) { + if (parameters.is_of_type(Glib::VariantType("(b)"))) { + gboolean sleeping; + g_variant_get(parameters.gobj_copy(), "(b)", &sleeping); + if (!sleeping) { + getData(); + dp.emit(); + } + } +} + +// When the gamemode name appears +void Gamemode::appear(const Glib::RefPtr& connection, + const Glib::ustring& name, const Glib::ustring& name_owner) { + gamemodeRunning = true; + event_box_.set_visible(true); + getData(); + dp.emit(); +} +// When the gamemode name disappears +void Gamemode::disappear(const Glib::RefPtr& connection, + const Glib::ustring& name) { + gamemodeRunning = false; + event_box_.set_visible(false); +} + +bool Gamemode::handleToggle(GdkEventButton* const& event) { + showAltText = !showAltText; + dp.emit(); + return true; +} + +auto Gamemode::update() -> void { + // Don't update widget if the Gamemode service isn't running + if (!gamemodeRunning || (gameCount <= 0 && hideNotRunning)) { + event_box_.set_visible(false); + return; + } + + // Show the module + if (!event_box_.get_visible()) event_box_.set_visible(true); + + // CSS status class + const std::string status = gamemodeRunning && gameCount > 0 ? "running" : ""; + // Remove last status if it exists + if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) { + box_.get_style_context()->remove_class(lastStatus); + } + // Add the new status class to the Box + if (!status.empty() && !box_.get_style_context()->has_class(status)) { + box_.get_style_context()->add_class(status); + } + lastStatus = status; + + // Tooltip + if (tooltip) { + std::string text = fmt::format(tooltip_format, fmt::arg("count", gameCount)); + box_.set_tooltip_text(text); + } + + // Label format + std::string str = + fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph), + fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); + label_.set_markup(str); + + if (useIcon) { + if (!Gtk::IconTheme::get_default()->has_icon(iconName)) { + iconName = DEFAULT_ICON_NAME; + } + icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID); + } + + // Call parent update + AModule::update(); +} + +} // namespace waybar::modules