qi3pc 1.0.0.1
Qt bindings for i3wm's IPC interface
Loading...
Searching...
No Matches

builds.sr.ht status

qi3pc is a simple C++ library that provides idiomatic Qt bindings for i3wm's IPC interface.

qi3pc is hosted on Sourcehut.

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.

Building

qi3pc is a modern C++ library. It uses C++20. It depends only on Qt6 and i3wm itself. cmake is necessary for building.

git clone https://git.sr.ht/~hantz/qi3pc
cd qi3pc
cmake -S . -B build
cmake --build build
cmake --install build

For more inspiration, see the CI build recipe.

Documentation

Documentation is available here. Once installed, the docs are also available in the man pages qi3pc(3): man qi3pc. Staging docs are available here.

Getting Started

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

Code examples

The code examples below assume some familiarity with Qt development as well as i3wm's IPC idioms.

Listening to the window manager

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.

auto ipc = qi3pc();
Qt bindings for i3wm's IPC interface.
Definition qi3pc.h:53

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.

QObject::connect(&ipc,
[](qi3pc::WindowChange change, const QJsonObject& container){
qDebug() << "Window event!";
if (change == qi3pc::WindowChange::Focus) {
qDebug() << "Window focus changed!";
}
});
WindowChange
Types of change a window event can have.
Definition qi3pc.h:140
void windowEvent(qi3pc::WindowChange change, const QJsonObject &container)
Signal emitted when a window changes.

Next, connect to the i3wm's IPC server.

ipc.connect();

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.

QStringList events("window");
ipc.subscribe(events);

Talking to the window manager

Below is an example showing how to automatically move to a newly created workspace.

MyClass::MyClass() {
QObject::connect(&m_ipc,
&qip3c::workspaceEvent,
this,
&MyClass::handleWorkspaceEvent);
m_ipc.connect();
QStringList events("workspace");
m_ipc.subscribe(events);
}
void
MyClass::handleWorkspaceEvent(qi3pc::WorkspaceChange change,
const QJsonObject& current,
const QJsonObject& old)
{
Q_UNUSED(old)
if (auto jsonStr = current["name"]; !jsonStr.isUndefined()) {
QString name = jsonStr.toString();
QByteArray payload;
QString i3Command = "workspace " + name;
payload.append(i3Command.toStdString().c_str());
m_ipc.sendMessage<qi3pc::IpcType::Command>(payload);
}
}
}
WorkspaceChange
Types of change a workspace event can have.
Definition qi3pc.h:109

When a new workspace is created, a command is sent to switch to it. As simple as that!

A conversation with the window manager

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.

MyClass::MyClass() {
m_ipc.connect();
QObject::connect(&m_ipc,
this,
&MyClass::handleWorkspaceReply);
// Query up-to-date workspace information from i3wm
m_ipc.fetchWorkspaces();
}
void
MyClass::handleWorkspaceReply(const qi3pc::DataArray& data)
{
qDebug() << "List of workspaces";
qDebug() << data->first;
qDebug() << "Cached list of workspaces";
qDebug() << ipc.workspaces().first();
}
void workspacesUpdated(const qi3pc::DataArray &workspaces)
Signal emitted when the (cached) list of workspaces have been updated.
std::optional< std::pair< QJsonArray, qint64 > > DataArray
Optional pair of a JSON array with its last update time.
Definition qi3pc.h:182

Note that in this case there was no need to subscribe to workspace events with qi3pc::subscribe.

More examples

Other examples can be found in the repository.

buffalo

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.

Replies are not events

In qi3pc there is a conceptual difference between event signals and reply signals.

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

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.