|
qi3pc 1.0.0.1
Qt bindings for i3wm's IPC interface
|
qi3pc is a simple C++ library that provides idiomatic Qt bindings for i3wm's IPC interface.
A long time ago (in early 2019, according to the commit log), I took up the quixotic task of building a custom desktop environment based on Qt.
What I have in mind is still vague, but essentially I want a thin, beautiful, customizable, wrapper around i3wm. Think "KDE window manager". Yes, I know, a window manager is not a desktop environment is not a window manager.
A necessary building block for such a project is obviously integration of i3wm's IPC with Qt. A handful of C++ libraries already exist for interprocess communication with i3wm. However, integrating them with Qt tends to require quite a lot of boilerplate. None of them could be integrated in a way that feels clean enough to my taste, so I decided to build my own library to do just that.
qi3pc is a modern C++ library. It uses C++20. It depends only on Qt6 and i3wm itself. cmake is necessary for building.
For more inspiration, see the CI build recipe.
Documentation is available here. Once installed, the docs are also available in the man pages qi3pc(3): man qi3pc. Staging docs are available here.
The messages, replies and events that i3wm's IPC API provides are accessible through methods and signals with obvious names. Getting information from i3 is as simple as sending a message and waiting for a reply or subscribing to events and waiting for them to be triggered. This is done with the signal/slot mechanisms offered by Qt and normal C++ functions.
The signals and methods return objects (arrays, dictionaries, strings, etc.) matching the specifications of i3wm's IPC protocol
The code examples below assume some familiarity with Qt development as well as i3wm's IPC idioms.
Below is an example that monitors window events from i3wm.
There are no free functions. All functionality is through the qi3pc class. So first, an object of that class has to be constructed.
Events from the window manager are available as Qt signals. To monitor those events, simply subscribe to them as to any other Qt signal. Window events include creation of new window, focus change, window move, etc.
Next, connect to the i3wm's IPC server.
Multiple qi3pc objects can be instantiated in the same application. Each object is only notified of the event it cares about. These events have to be explicitly subscribed to. Without this, the qi3pc::windowEvent signal will never fire on this object, and the lambda in the connection above will never run.
Below is an example showing how to automatically move to a newly created workspace.
When a new workspace is created, a command is sent to switch to it. As simple as that!
In the example above, after sending the workspace change request to i3wm, the response is not monitored, simply because the interesting side effect is the wokspace change itself (and also because it was just a simple example). However, sometimes we are interested in the reply from the wm. For those cases, message replies are also published with Qt signals.
To actively fetch information from i3wm, the fetch methods can be used (e.g. qi3pc::fetchWorkspaces). When called, a message is sent to i3wm, and the reply is published through a reply signal. In the case of workspaces, through qi3pc::workspacesUpdated.
Additionally, the reply is cached along with its last update time. This cached data is available through another member function, in this case qi3pc::workspaces.
The following is an example of using that workflow.
Note that in this case there was no need to subscribe to workspace events with qi3pc::subscribe.
Other examples can be found in the repository.
qi3pc is currently used in my (unreleased) bar, buffalo. The current implementation of buffalo covers what was necessary to replace my previous polybar setup that I ditched for instability. So it's quite barebones. But the i3 module provides the best existing usage examples.
For a quick preview of buffalo's capabilities, check out this message I sent to the i3wm mailing list a little while back.
If you use qi3pc in a project, reach out to me to have it listed here as an example.
In qi3pc there is a conceptual difference between event signals and reply signals.
Reply signals names are usually in the past participle, e.g. qi3pc::workspacesUpdated. These signals are fired as the result of a reply to a message sent to the window manager, for example a fetch message sent by calling qi3pc::fetchWorkspaces.
In Qt lingo, these fetch messages are known as slots. They usually indicate an asynchronous work request. In some other frameworks and languages, the scheduler can stop and resume execution with async/await or some other mechanism. However, in Qt there are no keywords matching those. Instead, the reply to that async request is provided through a signal, as has been demonstrated so far.
The parameters supplied with those signals are cached by the qi3pc object. Their values can be retrieved synchronously through the appropriate methods, e.g. qi3pc::workspaces.
These cached values can however be out-of-date. They are only updated when the window manager replies to a fetch message. The last update time is stored with the cached values. For example, qi3pc::workspaces returns a qi3pc::DataArray object, which is an optional pair of the cached value as a QJsonArray and its last update time in milliseconds since the unix epoch as a qint64.
Event signals are usually suffixed with Event, e.g. qi3pc::workspaceEvent. Those signals are fired as a result of some independent change happening in the window manager, like a new workspace or window being created.
The parameters supplied with those signals are not cached by the qi3pc object. The user of the library should save them themself if necessary for later use. Otherwise, they have to be queried from the window manager asynchronously with a fetch message.