Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

D-Bus implementation of system tray support #11703

Open
slouken opened this issue Dec 24, 2024 · 6 comments
Open

D-Bus implementation of system tray support #11703

slouken opened this issue Dec 24, 2024 · 6 comments
Milestone

Comments

@slouken
Copy link
Collaborator

slouken commented Dec 24, 2024

Given that I've never implemented that specific D-Bus API using PyQt5 (my API of choice for my own creations at the moment), I got nerd-sniped and decided to implement a Python proof of concept for practice with the API.

It's bedtime, but I've successfully exercised most of the functionality on offer. Granted, two of the parts that are left are important to what SDL would want, but they're more things I don't have time for tonight than things I anticipate having trouble with:

  • Passing icons as pixmaps rather than names to be looked up in the system icon theme (There isn't a ton of example code for how QtDBus's interaction with Qt's type meta-system translates from C++ to Python and I'll need to spend more time reading how it expects me to specify compound data types for D-Bus properties.)
  • Defining menus to be rendered by the desktop rather than just having D-Bus call the ContextMenu(x, y) method. (Shouldn't be difficult... just a bit time-consuming since I don't have a "D-Bus XML schema to PyQt5 QDBus" boilerplate generator, so I need to transcribe the relevant bits of the com.canonical.dbusmenu schema by hand first.)

(According to ubuntu/gnome-shell-extension-appindicator, rich tooltips, which also need compound data types, were never implemented by libappindicator or Unity to begin with and I doubt SDL has a pressing need for overlay icons or custom-animated "needs attention" tray icon states.)

I have, however, already discovered something relevant. It looks like the FreeDesktop version of the spec stalled once it became clear that GNOME had no interest in implementing tray icons of any kind.

My Kubuntu Linux 22.04 LTS doesn't implement org.freedesktop.StatusNotifierItem and does implement org.kde.StatusNotifierItem and the interface definition in ubuntu/gnome-shell-extension-appindicator also uses that interface name though, so far, aside from org.kde instead of org.freedesktop, I have produced interfaces my Plasma 5.x is OK with by following what is described on FreeDesktop.org.

...so, yeah. If anyone has any questions about implementing the StatusNotifierItem API, I'm now in a better position to answer them.

I'm not sure what details you consider significant, but I'm certainly willing to give it a shot.

At the highest level, the gist of how it works is:

  1. You register a service on the session bus which implements the org.kde.StatusNotifierItem interface, with properties like Category (set it to "ApplicationStatus") to define the icon's appearance and behaviour, and with methods like Action and Scroll which will be called by the SNI host when the user interacts with it.
  2. You then call the org.kde.StatusNotifierWatcher.RegisterStatusNotifierItem method under /StatusNotifierWatcher on org.kde.StatusNotifierWatcher (the name the active SNI host will claim) and it takes the service name you registered in step 1 as a string as its only argument.
  3. The SNI host will then use the D-Bus introspection interface to retrieve the contents of all of the org.kde.StatusNotifierItem.* properties you implemented and to get a listing of which methods have been implemented and will create an icon based on what it finds. (eg. whether left-clicking and right-clicking do the same thing depends on whether you've registered separate handlers for Action and ContextMenu and whether the ItemIsMenu boolean property is true or false.)
  4. When the program disconnects from the bus, the host will automatically remove the icon.

This XML schema is what ubuntu/gnome-shell-extension-appindicator is feeding to GDBusProxy to describe what it expects to find while the FreeDesktop document goes into more detail on what the argument values should be but doesn't touch on "not standard, but widely implemented" extensions to the API.

As far as tooling goes, here are the tools I used which I'd highly recommend:

  • qdbusviewer from Qt's development tools (eg. qttools5-dev-tools for Qt 5 on *buntu Linux) provides a great interactive browser for D-Bus introspection, as well as being a GUI for interactively querying properties and calling methods with sufficiently simple type signatures. The dialog that pops up when double-clicking a method is also a great way to check what type signature you're exporting. (I made good use of it to verify that I was exporting the API that I thought I was.)
  • dbus-monitor is a non-graphical tool for logging what's flowing over the bus to diagnose things which tends to be part of the same package as the D-Bus daemon itself. (eg. Very useful for stuff like seeing which error messages the D-Bus daemon is returning to the SNI Host when your type signatures aren't quite right on stuff it's supposed to call.)
  • Bustle is a graphical frontend for dbus-monitor which is much more amenable to exclusion-based filtering than bare dbus-monitor if you want to look at "everything except the irrelevant cruft that Workrave, Yakuake, and Pidgin are flooding the bus with". It also, in my experience, presents the argument lists for method calls in a format that's cleaner for lining up and comparing when you have a working SNI implementation and a buggy SNI implementation side-by-side. (D-Bus will tell you there's no such method if the signatures don't match. I haven't checked if that's because it supports method overloading, but I've never seen any APIs use that if it does.)

Also, in case you want to poke at a working implementation for basically everything relevant except for the IconThemePath property (a non-standard-but-commonly-implemented extension for amending the search path for IconName), the IconPixmap property, and the Menu property (For describing a menu for the panel host to render, as opposed to the ContextMenu method where you draw it yourself at the specified x,y coordinates), here's the current state of my PyQt5+QtDBus practice piece as I left it last night:

https://gist.github.com/ssokolow/1cbe7d9341d6b4401b17c0f7eebbd2b8

(I'll update it when I make more time to keep working on it.)

Beyond that, I'll wait for you to ask further questions.

Originally posted by @ssokolow in #10873 (comment)

@slouken slouken added this to the 3.2.0 milestone Dec 24, 2024
@Semphriss
Copy link
Contributor

Leaving a note here that I'm currently in the process of learning DBus, and I plan to at least try to implement the system tray with it. I'm not sure how much time it'll take me; if someone more familiar with DBus than me would like to take the torch, just let me know so I can shift my efforts on something else :)

Also, shouldn't the title of the issue be "DBus implementation" rather than "AppIndicator"?

@slouken slouken changed the title Appindicator implementation of system tray support D-Bus implementation of system tray support Dec 24, 2024
@deltragon
Copy link

We've had to recently switch from libappindicator to a manual DBus implementation due to the GTK4 migration, as you can't load both GTK3 and 4 in a single process. This was implemented here: slgobinath/SafeEyes#558, but needed to be adjusted to also include some non-standard attributes like slgobinath/SafeEyes#646 since desktop environments expect it, it seems.
In short, our implementation seems to work well at this point, even if the missing standardization is somewhat of a mess.

@smcv
Copy link
Contributor

smcv commented Jan 7, 2025

shouldn't the title of the issue be "DBus implementation" rather than "AppIndicator"?

Developers could implement an unlimited number of incompatible system-tray protocols over D-Bus if they wanted to (and perhaps they already have). D-Bus is just a message delivery protocol and some conventions for how to use it, and the only protocols that are standardized at the D-Bus level are features of the message bus itself, and a few basic building blocks like Properties and ObjectManager. Anything higher-level is outside D-Bus' scope, the same way that the Standard C committee probably don't know anything about SDL.

If the desired feature is something like "be compatible with Ubuntu AppIndicator" or "be compatible with KDE StatusNotifierItem", it would be worthwhile to mention one of those in the title.

Something compatible with both ubuntu/gnome-shell-extension-appindicator and KDE Plasma is probably the closest to standardization that we're going to see any time soon.

D-Bus will tell you there's no such method if the signatures don't match. I haven't checked if that's because it supports method overloading, but I've never seen any APIs use that if it does.

D-Bus, the protocol, has no guarantees either way on this. The message bus (usually dbus-daemon or dbus-broker, dbus-daemon is the reference implementation) just passes methods to the service and passes replies back. So this is a fact about the D-Bus library you used to implement the service (QtDBus?) rather than a fact about the D-Bus protocol.

The wire protocol does technically allow overloading (and lots of other inadvisable things), but that isn't part of the interoperable subset of the protocol and not all client libraries will allow it. It's conventional to behave as though overloading isn't possible, and give methods with different arguments different names if it becomes necessary (e.g. DoSomething, DoSomethingWithFlags).

@ssokolow
Copy link

ssokolow commented Jan 7, 2025

Developers could implement an unlimited number of incompatible system-tray protocols over D-Bus if they wanted to (and perhaps they already have). D-Bus is just a message delivery protocol and some conventions for how to use it, and the only protocols that are standardized at the D-Bus level are features of the message bus itself, and a few basic building blocks like Properties and ObjectManager. Anything higher-level is outside D-Bus' scope, the same way that the Standard C committee probably don't know anything about SDL.

If the desired feature is something like "be compatible with Ubuntu AppIndicator" or "be compatible with KDE StatusNotifierItem", it would be worthwhile to mention one of those in the title.

The intent of the title was more "Stop depending on libappindicator to support KStatusNotifierItem". That's why it was split out of #10873 where the initial libappindicator implementation was added.

libappindicator's Launchpad page flat-out says "Based on KSNI" and ubuntu/gnome-shell-extension-appindicator also implements the same thing as evidenced by the KSNI IDL file in their repo.

The wire protocol does technically allow overloading (and lots of other inadvisable things), but that isn't part of the interoperable subset of the protocol and not all client libraries will allow it. It's conventional to behave as though overloading isn't possible, and give methods with different arguments different names if it becomes necessary (e.g. DoSomething, DoSomethingWithFlags).

Thanks. TIL.

So this is a fact about the D-Bus library you used to implement the service (QtDBus?) rather than a fact about the D-Bus protocol.

IIRC, the overloading-implying error messages I was referring to were being emitted by dbus-daemon as plaintext strings in message bodies and swallowed up by QtDBus, only visible because I was using dbus-monitor to snoop the bus to debug whatever "no such method" exceptions PyQt5 was raising. (It turned out to be that I'd accidentally passed [] where {} was expected for one of the arguments.)

@slouken slouken modified the milestones: 3.2.0, 3.x Jan 10, 2025
@adamkewley
Copy link
Contributor

adamkewley commented Jan 27, 2025

@ssokolow : just wanted to say that I'm excited with your intention here 👍

I just had a nightmare gdb, objdump, etc. session related to the fact that I'm linking GTK3 differently than SDL3's tray implementation because my project also uses nativefiledialog:

Effectively, one or the other library ended up overwriting the gtk_init_check function symbol, which popped up as a runtime segfault. It was a little tricky for me to figure it out at first glance - it's been a while since I've had to trawl function addresses etc. in gdb. Converting a library dependency into a bus dependency would be nice for larger desktop applications like mine.

That said, though, I plan on porting all of opensim-creator's platform code (i.e. everything that isn't CPU/memory/OpenGL) to SDL3 eventually, which puts nativefiledialog in my crosshairs! Keep up the good work ❤

@ssokolow
Copy link

ssokolow commented Jan 27, 2025

:)

Here's hoping I can find some time to extend my PyQt QtDBus demonstration/proof of concept/exploratory piece with the remaining bits and pieces (mainly dbusmenu) soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants