// gKeyMapper - the tray icon, spoken as a StatusNotifierItem. #ifndef GKEYMAPPER_TRAYICON_H #define GKEYMAPPER_TRAYICON_H #include #include #include #include namespace gkeymapper { // The tray icon. GTK4 has no tray widget (GtkStatusIcon was GTK3 and is gone), so // the icon is exported on the session bus as a StatusNotifierItem plus a // com.canonical.dbusmenu menu - exactly the protocol libxapp and libappindicator // wrap, only without their GTK3 dependency (a GTK4 process must not load GTK3). // The panel hosts it: Cinnamon's xapp-status applet, KDE, Unity, or the GNOME // AppIndicator extension. // // The panel is a different process, so the icon travels as a pixmap: it cannot // read the icons compiled into this binary. class TrayIcon { public: using Callback = std::function; TrayIcon() = default; ~TrayIcon(); TrayIcon(const TrayIcon&) = delete; TrayIcon& operator=(const TrayIcon&) = delete; // Owns the item's bus name, exports the item and its menu and registers with // the panel's watcher - and again whenever the panel restarts (the watcher // owns the icon list, a restart loses it). `icon` is ARGB32 in network byte // order, which is the layout the protocol asks for. void start(std::vector icon, int width, int height, Callback onShow, Callback onQuit); // True once a panel hosts the icon, i.e. the icon is really on screen. Only // then hiding the window is safe: without a tray there is no way back. bool ready() const { return registered_; } // The panel mirrors the menu once, so it has to be told when the texts in it // changed - a language switch. Nothing else about the icon changes. void refresh_labels(); private: static void on_bus_acquired(GDBusConnection* connection, const gchar* name, gpointer userData); static void on_name_lost(GDBusConnection* connection, const gchar* name, gpointer userData); static void on_watcher_appeared(GDBusConnection* connection, const gchar* name, const gchar* nameOwner, gpointer userData); static void on_watcher_vanished(GDBusConnection* connection, const gchar* name, gpointer userData); static void on_register_reply(GObject* source, GAsyncResult* result, gpointer userData); static void on_item_method_call(GDBusConnection* connection, const gchar* sender, const gchar* objectPath, const gchar* interfaceName, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData); static GVariant* on_item_get_property(GDBusConnection* connection, const gchar* sender, const gchar* objectPath, const gchar* interfaceName, const gchar* propertyName, GError** error, gpointer userData); static void on_menu_method_call(GDBusConnection* connection, const gchar* sender, const gchar* objectPath, const gchar* interfaceName, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData); static GVariant* on_menu_get_property(GDBusConnection* connection, const gchar* sender, const gchar* objectPath, const gchar* interfaceName, const gchar* propertyName, GError** error, gpointer userData); void export_objects(GDBusConnection* connection); // Runs what a click on a menu entry means ("clicked" on Show or Close). void handle_event(int id, const char* eventId); // Notes which of the two watcher names is on the bus and registers there. void set_watcher_available(const char* name, bool available); void register_with_watcher(); GVariant* icon_pixmaps() const; GVariant* tooltip() const; GVariant* layout(int parentId, int depth) const; GVariant* node(int id, int depth) const; GVariant* group_properties(GVariant* ids) const; GVariant* menu_property(int id, const char* name) const; GDBusConnection* connection_ = nullptr; guint itemRegistration_ = 0; guint menuRegistration_ = 0; guint ownerId_ = 0; guint watcherId_ = 0; guint watcherIdX_ = 0; std::string serviceName_; std::vector icon_; int iconWidth_ = 0; int iconHeight_ = 0; bool watcher_ = false; bool watcherAlias_ = false; bool registering_ = false; bool registered_ = false; const char* registeringWith_ = nullptr; std::string registeredWith_; // Bumped whenever the menu texts change; the panel compares it to know that // what it mirrored is stale. guint menuRevision_ = 1; Callback onShow_; Callback onQuit_; }; } // namespace gkeymapper #endif // GKEYMAPPER_TRAYICON_H