better working tray
This commit is contained in:
@@ -3,10 +3,145 @@
|
||||
#include <gdkmm/rectangle.h>
|
||||
#include <gio/gmenu.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <graphene.h>
|
||||
#include <utility>
|
||||
#include "components/base/button.hpp"
|
||||
|
||||
namespace {
|
||||
bool is_wayland_display(GtkWidget *widget) {
|
||||
if (!widget) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GtkNative *native = gtk_widget_get_native(widget);
|
||||
if (!native) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GdkSurface *surface = gtk_native_get_surface(native);
|
||||
if (!surface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GdkDisplay *display = gdk_surface_get_display(surface);
|
||||
if (!display) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *typeName = G_OBJECT_TYPE_NAME(display);
|
||||
if (!typeName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return std::string(typeName).find("Wayland") != std::string::npos;
|
||||
}
|
||||
|
||||
bool try_get_monitor_geometry(GtkWidget *widget, GdkRectangle &outGeom) {
|
||||
if (!widget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GtkNative *native = gtk_widget_get_native(widget);
|
||||
if (!native) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkSurface *surface = gtk_native_get_surface(native);
|
||||
if (!surface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkDisplay *display = gdk_surface_get_display(surface);
|
||||
if (!display) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkMonitor *monitor = gdk_display_get_monitor_at_surface(display, surface);
|
||||
if (!monitor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gdk_monitor_get_geometry(monitor, &outGeom);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_get_global_click_coords(GtkWidget *widget, double x, double y,
|
||||
int32_t &outX, int32_t &outY) {
|
||||
if (!widget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GtkNative *native = gtk_widget_get_native(widget);
|
||||
if (!native) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GtkWidget *nativeWidget = GTK_WIDGET(native);
|
||||
graphene_point_t src{static_cast<float>(x), static_cast<float>(y)};
|
||||
graphene_point_t dst{0.0f, 0.0f};
|
||||
if (!gtk_widget_compute_point(widget, nativeWidget, &src, &dst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkRectangle geom;
|
||||
if (!try_get_monitor_geometry(widget, geom)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outX = static_cast<int32_t>(geom.x + std::lround(dst.x));
|
||||
outY = static_cast<int32_t>(geom.y + std::lround(dst.y));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_get_global_pointer_coords(GtkWidget *widget, int32_t &outX,
|
||||
int32_t &outY) {
|
||||
if (!widget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GtkNative *native = gtk_widget_get_native(widget);
|
||||
if (!native) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkSurface *surface = gtk_native_get_surface(native);
|
||||
if (!surface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkDisplay *display = gdk_surface_get_display(surface);
|
||||
if (!display) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkSeat *seat = gdk_display_get_default_seat(display);
|
||||
if (!seat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkDevice *pointer = gdk_seat_get_pointer(seat);
|
||||
if (!pointer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double sx = 0.0;
|
||||
double sy = 0.0;
|
||||
if (!gdk_surface_get_device_position(surface, pointer, &sx, &sy, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkRectangle geom;
|
||||
if (!try_get_monitor_geometry(widget, geom)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outX = static_cast<int32_t>(geom.x + std::lround(sx));
|
||||
outY = static_cast<int32_t>(geom.y + std::lround(sy));
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TrayIconWidget::TrayIconWidget( std::string id)
|
||||
: Button(id), id(std::move(id)),
|
||||
container(Gtk::Orientation::HORIZONTAL) {
|
||||
@@ -48,16 +183,16 @@ TrayIconWidget::TrayIconWidget( std::string id)
|
||||
}
|
||||
|
||||
void TrayIconWidget::update(const TrayService::Item &item) {
|
||||
hasRemoteMenu = item.menuAvailable;
|
||||
menuPopupPending = false;
|
||||
menuRequestInFlight = false;
|
||||
|
||||
if (!item.menuAvailable) {
|
||||
menuModel.reset();
|
||||
menuActions.reset();
|
||||
menuPopupPending = false;
|
||||
if (menuChangedConnection.connected()) {
|
||||
menuChangedConnection.disconnect();
|
||||
}
|
||||
if (menuPopover) {
|
||||
menuPopover->insert_action_group("dbusmenu",
|
||||
Glib::RefPtr<Gio::ActionGroup>());
|
||||
menuPopover->insert_action_group(
|
||||
"dbusmenu", Glib::RefPtr<Gio::ActionGroup>());
|
||||
menuPopover->set_menu_model({});
|
||||
menuPopover->unparent();
|
||||
menuPopover.reset();
|
||||
@@ -89,52 +224,81 @@ void TrayIconWidget::update(const TrayService::Item &item) {
|
||||
}
|
||||
|
||||
void TrayIconWidget::on_primary_released(int /*n_press*/, double x, double y) {
|
||||
service.activate(id, -1, -1);
|
||||
// Intentionally no-op: some tray items (e.g. Spotify) misbehave when the
|
||||
// host forwards primary clicks.
|
||||
(void)x;
|
||||
(void)y;
|
||||
}
|
||||
|
||||
void TrayIconWidget::on_secondary_released(int /*n_press*/, double x,
|
||||
double y) {
|
||||
service.contextMenu(id, -1, -1);
|
||||
pendingX = x;
|
||||
pendingY = y;
|
||||
|
||||
if (!ensure_menu()) {
|
||||
// Prefer dbusmenu popover when available.
|
||||
if (hasRemoteMenu) {
|
||||
menuPopupPending = true;
|
||||
if (menuRequestInFlight) {
|
||||
return;
|
||||
}
|
||||
|
||||
menuRequestInFlight = true;
|
||||
service.request_menu_layout(
|
||||
id, sigc::mem_fun(*this, &TrayIconWidget::on_menu_layout_ready));
|
||||
return;
|
||||
}
|
||||
|
||||
pendingX = x;
|
||||
pendingY = y;
|
||||
menuPopupPending = true;
|
||||
try_popup();
|
||||
// No dbusmenu: defer to the item's own ContextMenu.
|
||||
if (is_wayland_display(GTK_WIDGET(gobj()))) {
|
||||
service.contextMenu(id, -1, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t sendX = static_cast<int32_t>(std::lround(x));
|
||||
int32_t sendY = static_cast<int32_t>(std::lround(y));
|
||||
if (!try_get_global_click_coords(GTK_WIDGET(gobj()), x, y, sendX, sendY)) {
|
||||
(void)try_get_global_pointer_coords(GTK_WIDGET(gobj()), sendX, sendY);
|
||||
}
|
||||
service.contextMenu(id, sendX, sendY);
|
||||
}
|
||||
|
||||
bool TrayIconWidget::ensure_menu() {
|
||||
auto layoutOpt = service.get_menu_layout(id);
|
||||
void TrayIconWidget::on_menu_layout_ready(
|
||||
std::optional<TrayService::MenuNode> layoutOpt) {
|
||||
menuRequestInFlight = false;
|
||||
|
||||
if (!menuPopupPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!layoutOpt) {
|
||||
menuPopupPending = false;
|
||||
// If dbusmenu layout fetch failed, fall back to ContextMenu.
|
||||
if (is_wayland_display(GTK_WIDGET(gobj()))) {
|
||||
service.contextMenu(id, -1, -1);
|
||||
} else {
|
||||
service.contextMenu(id, static_cast<int32_t>(std::lround(pendingX)),
|
||||
static_cast<int32_t>(std::lround(pendingY)));
|
||||
}
|
||||
menuModel.reset();
|
||||
menuActions.reset();
|
||||
menuPopupPending = false;
|
||||
if (menuChangedConnection.connected()) {
|
||||
menuChangedConnection.disconnect();
|
||||
}
|
||||
if (menuPopover) {
|
||||
remove_action_group("dbusmenu");
|
||||
menuPopover->remove_action_group("dbusmenu");
|
||||
menuPopover->set_menu_model({});
|
||||
menuPopover->unparent();
|
||||
menuPopover.reset();
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &layout = *layoutOpt;
|
||||
|
||||
auto menu = Gio::Menu::create();
|
||||
auto actions = Gio::SimpleActionGroup::create();
|
||||
auto menu = Gio::Menu::create();
|
||||
auto actions = Gio::SimpleActionGroup::create();
|
||||
|
||||
populate_menu_items(layout.children, menu, actions);
|
||||
|
||||
const auto itemCount = menu->get_n_items();
|
||||
|
||||
if (itemCount == 0) {
|
||||
return false;
|
||||
if (menu->get_n_items() == 0) {
|
||||
menuModel.reset();
|
||||
menuActions.reset();
|
||||
menuPopupPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
menuModel = menu;
|
||||
@@ -145,7 +309,8 @@ bool TrayIconWidget::ensure_menu() {
|
||||
menuPopover =
|
||||
Glib::make_refptr_for_instance<Gtk::PopoverMenu>(rawPopover);
|
||||
if (!menuPopover) {
|
||||
return false;
|
||||
menuPopupPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
menuPopover->set_has_arrow(false);
|
||||
@@ -155,36 +320,8 @@ bool TrayIconWidget::ensure_menu() {
|
||||
|
||||
menuPopover->remove_action_group("dbusmenu");
|
||||
menuPopover->insert_action_group("dbusmenu", menuActions);
|
||||
|
||||
if (menuChangedConnection.connected()) {
|
||||
menuChangedConnection.disconnect();
|
||||
}
|
||||
menuChangedConnection = menuModel->signal_items_changed().connect(
|
||||
sigc::mem_fun(*this, &TrayIconWidget::on_menu_items_changed));
|
||||
|
||||
menuPopover->set_menu_model(menuModel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TrayIconWidget::on_menu_items_changed(guint /*position*/,
|
||||
guint /*removed*/, guint /*added*/) {
|
||||
if (!menuModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
try_popup();
|
||||
}
|
||||
|
||||
void TrayIconWidget::try_popup() {
|
||||
if (!menuPopupPending || !menuPopover || !menuModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (menuModel->get_n_items() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Gdk::Rectangle rect(static_cast<int>(pendingX), static_cast<int>(pendingY),
|
||||
1, 1);
|
||||
menuPopover->set_pointing_to(rect);
|
||||
|
||||
Reference in New Issue
Block a user