add tray icons

This commit is contained in:
2025-12-10 01:35:39 +01:00
parent 7bd4c72763
commit 70a271fb8b
6 changed files with 677 additions and 8 deletions

View File

@@ -1,6 +1,11 @@
#include "widgets/tray.hpp"
#include <cstdint>
#include <gtk/gtk.h>
#include <gdkmm/rectangle.h>
#include <gio/gmenu.h>
#include <utility>
#include <iostream>
TrayIconWidget::TrayIconWidget(TrayService &service, std::string id)
: m_service(service), m_id(std::move(id)), m_container(Gtk::Orientation::HORIZONTAL)
@@ -41,6 +46,24 @@ TrayIconWidget::TrayIconWidget(TrayService &service, std::string id)
void TrayIconWidget::update(const TrayService::Item &item)
{
if (!item.menuAvailable)
{
m_menuModel.reset();
m_menuActions.reset();
m_menuPopupPending = false;
if (m_menuChangedConnection.connected())
{
m_menuChangedConnection.disconnect();
}
if (m_menuPopover)
{
m_menuPopover->insert_action_group("dbusmenu", Glib::RefPtr<Gio::ActionGroup>());
m_menuPopover->set_menu_model({});
m_menuPopover->unparent();
m_menuPopover.reset();
}
}
if (item.iconPaintable)
{
m_picture.set_paintable(item.iconPaintable);
@@ -78,15 +101,186 @@ void TrayIconWidget::on_primary_clicked()
m_service.activate(m_id, 0, 0);
}
void TrayIconWidget::on_secondary_released(int /*n_press*/, double /*x*/, double /*y*/)
void TrayIconWidget::on_secondary_released(int /*n_press*/, double x, double y)
{
m_service.contextMenu(m_id, 0, 0);
m_service.contextMenu(m_id, static_cast<int32_t>(x), static_cast<int32_t>(y));
if (!ensure_menu())
{
return;
}
m_pendingX = x;
m_pendingY = y;
m_menuPopupPending = true;
try_popup();
}
bool TrayIconWidget::ensure_menu()
{
auto layoutOpt = m_service.get_menu_layout(m_id);
if (!layoutOpt)
{
m_menuModel.reset();
m_menuActions.reset();
m_menuPopupPending = false;
if (m_menuChangedConnection.connected())
{
m_menuChangedConnection.disconnect();
}
if (m_menuPopover)
{
remove_action_group("dbusmenu");
m_menuPopover->set_menu_model({});
m_menuPopover->unparent();
m_menuPopover.reset();
}
return false;
}
const auto &layout = *layoutOpt;
auto menu = Gio::Menu::create();
auto actions = Gio::SimpleActionGroup::create();
populate_menu_items(layout.children, menu, actions);
const auto itemCount = menu->get_n_items();
std::cout << "[TrayIconWidget] menu update for " << m_id << ", items: " << itemCount << std::endl;
if (itemCount == 0)
{
m_service.debug_dump_menu_layout(m_id);
return false;
}
m_menuModel = menu;
m_menuActions = actions;
if (!m_menuPopover)
{
auto *rawPopover = Gtk::make_managed<Gtk::PopoverMenu>();
m_menuPopover = Glib::make_refptr_for_instance<Gtk::PopoverMenu>(rawPopover);
if (!m_menuPopover)
{
return false;
}
m_menuPopover->set_has_arrow(false);
m_menuPopover->set_autohide(true);
m_menuPopover->set_parent(*this);
}
m_menuPopover->remove_action_group("dbusmenu");
m_menuPopover->insert_action_group("dbusmenu", m_menuActions);
if (m_menuChangedConnection.connected())
{
m_menuChangedConnection.disconnect();
}
m_menuChangedConnection = m_menuModel->signal_items_changed().connect(sigc::mem_fun(*this, &TrayIconWidget::on_menu_items_changed));
m_menuPopover->set_menu_model(m_menuModel);
return true;
}
void TrayIconWidget::on_menu_items_changed(guint /*position*/, guint /*removed*/, guint /*added*/)
{
if (!m_menuModel)
{
return;
}
const auto count = m_menuModel->get_n_items();
std::cout << "[TrayIconWidget] items changed for " << m_id << ": " << count << " entries" << std::endl;
try_popup();
}
void TrayIconWidget::try_popup()
{
if (!m_menuPopupPending || !m_menuPopover || !m_menuModel)
{
return;
}
if (m_menuModel->get_n_items() == 0)
{
return;
}
Gdk::Rectangle rect(static_cast<int>(m_pendingX), static_cast<int>(m_pendingY), 1, 1);
m_menuPopover->set_pointing_to(rect);
m_menuPopover->popup();
m_menuPopupPending = false;
}
void TrayIconWidget::populate_menu_items(const std::vector<TrayService::MenuNode> &nodes,
const Glib::RefPtr<Gio::Menu> &menu,
const Glib::RefPtr<Gio::SimpleActionGroup> &actions)
{
for (const auto &node : nodes)
{
if (!node.visible)
{
continue;
}
if (node.separator)
{
auto section = Gio::Menu::create();
menu->append_section("", section);
continue;
}
if (!node.children.empty())
{
auto submenu = Gio::Menu::create();
populate_menu_items(node.children, submenu, actions);
auto submenuItem = Gio::MenuItem::create(node.label, Glib::ustring());
submenuItem->set_submenu(submenu);
if (!node.enabled)
{
submenuItem->set_attribute_value("enabled", Glib::Variant<bool>::create(false));
}
menu->append_item(submenuItem);
continue;
}
const std::string actionName = "item" + std::to_string(node.id);
auto menuItem = Gio::MenuItem::create(node.label, "dbusmenu." + actionName);
if (!node.enabled)
{
menuItem->set_attribute_value("enabled", Glib::Variant<bool>::create(false));
}
auto action = Gio::SimpleAction::create(actionName);
action->set_enabled(node.enabled);
action->signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &TrayIconWidget::on_menu_action), node.id));
actions->add_action(action);
menu->append_item(menuItem);
}
}
void TrayIconWidget::on_menu_action(const Glib::VariantBase & /*parameter*/, int itemId)
{
m_service.activate_menu_item(m_id, itemId);
if (m_menuPopover)
{
m_menuPopover->popdown();
}
}
TrayWidget::TrayWidget(TrayService &service)
: Gtk::Box(Gtk::Orientation::HORIZONTAL), m_service(service)
{
set_spacing(6);
set_spacing(4);
set_valign(Gtk::Align::CENTER);
set_halign(Gtk::Align::CENTER);
set_visible(false);