26Q_LOGGING_CATEGORY(Qi3pcLogger,
"qi3pc", QtMsgType::QtDebugMsg)
37 m_barConfigs = std::make_pair(QJsonObject(), QDateTime::currentMSecsSinceEpoch());
39 throw std::invalid_argument(
"The provided server path must not be empty.");
65 auto [data, type] = m.value();
67 if (type & (1u << 31)) {
68 switch (
static_cast<IpcEvent>(type)) {
69 case IpcEvent::Workspace:
72 case IpcEvent::Output:
78 case IpcEvent::Window:
81 case IpcEvent::BarUpdate:
84 case IpcEvent::Binding:
87 case IpcEvent::Shutdown:
94 std::string log =
"Received event of unsupported type IpcType::%u";
95 qCWarning(Qi3pcLogger, log.c_str(), type, IpcType::Subscribe);
99 switch (
static_cast<IpcType>(type)) {
100 case IpcType::Subscribe:
107 std::string log =
"Received message IpcType::%u while expecting IpcEvent, IpcType::Subscribe (IpcType::%u),"
108 "or IpcType::Tick (IpcType::%u)";
109 qCWarning(Qi3pcLogger, log.c_str(), type, IpcType::Subscribe, IpcType::Tick);
123 auto [data, type] = m.value();
126 switch (
static_cast<IpcType>(type)) {
127 case IpcType::Command:
130 case IpcType::Workspaces:
133 case IpcType::Subscribe:
134 log =
"Received reply of type IpcType::Subscribe (IpcType::%u): unexpected.";
135 qCWarning(Qi3pcLogger, log.c_str(), IpcType::Subscribe);
137 case IpcType::Outputs:
146 case IpcType::BarConfig:
149 case IpcType::Version:
152 case IpcType::BindingModes:
155 case IpcType::Config:
159 log =
"Received reply of type IpcType::Tick (IpcType::%u): unexpected.";
160 qCWarning(Qi3pcLogger, log.c_str(), IpcType::Tick);
165 case IpcType::BindingState:
169 log =
"Received reply of unsupported type IpcType::%u.";
170 qCWarning(Qi3pcLogger, log.c_str(), type);
178 auto results = doc.array();
179 if (results.empty()) {
180 qCWarning(Qi3pcLogger) <<
"Received empty command reply:" << doc.toJson();
185 for (
auto jsonObject: std::as_const(results)) {
186 auto object = jsonObject.toObject();
187 if (!
object.contains(
"success")) {
188 qCWarning(Qi3pcLogger) <<
"Command reply does not contain success key. Skipping:" << object;
192 if (
auto success =
object[
"success"].toBool(); success) {
193 result.push_back({
true, {}});
205 m_workspaces = std::make_pair(doc.array(), QDateTime::currentMSecsSinceEpoch());
212 m_outputs = std::make_pair(doc.array(), QDateTime::currentMSecsSinceEpoch());
219 m_tree = std::make_pair(doc.object(), QDateTime::currentMSecsSinceEpoch());
226 m_marks = std::make_pair(doc.array(), QDateTime::currentMSecsSinceEpoch());
233 if (
const auto& ids = doc.array(); !ids.isEmpty()) {
234 for (
auto ref : ids) {
235 if (
auto id = ref.toString();
241 auto id = doc[
"id"].toString();
242 auto config = doc.object();
244 m_barConfigs->second = QDateTime::currentMSecsSinceEpoch();
252 m_version = std::make_pair(doc.object(), QDateTime::currentMSecsSinceEpoch());
259 m_bindingModes = std::make_pair(doc.array(), QDateTime::currentMSecsSinceEpoch());
266 m_config = std::make_pair(doc.object(), QDateTime::currentMSecsSinceEpoch());
273 emit
tickSent(doc[
"success"].toBool());
279 emit
synced(doc[
"success"].toBool());
285 m_bindingState = std::make_pair(doc[
"name"].toString(), QDateTime::currentMSecsSinceEpoch());
295 qCWarning(Qi3pcLogger) <<
"Received workspace event with unknown change string" << doc[
"change"].toString();
299 auto old = doc[
"old"].toObject();
300 auto current = doc[
"current"].toObject();
310 qCWarning(Qi3pcLogger) <<
"Received output event with unknown change string" << data[
"change"].toString();
320 emit
modeEvent(doc[
"change"].toString(), doc[
"pango_markup"].toBool());
329 qCWarning(Qi3pcLogger) <<
"Received window event with unknown change string" << doc[
"change"].toString();
333 emit
windowEvent(change, doc[
"container"].toObject());
348 qCWarning(Qi3pcLogger) <<
"Received binding event with unknown change string" << doc[
"change"].toString();
352 emit
bindingEvent(change, doc[
"binding"].toObject(), doc[
"mode"].toString());
361 qCWarning(Qi3pcLogger) <<
"Received shutdown event with unknown change string" << doc[
"change"].toString();
371 if(
auto first = doc[
"first"].toBool(); !first) {
372 emit
tickEvent(doc[
"payload"].toString());
381 }
else if (s ==
"init") {
383 }
else if (s ==
"empty") {
385 }
else if (s ==
"urgent") {
387 }
else if (s ==
"reload") {
389 }
else if (s ==
"rename") {
391 }
else if (s ==
"restored") {
393 }
else if (s ==
"move") {
405 }
else if (s ==
"close") {
407 }
else if (s ==
"focus") {
409 }
else if (s ==
"title") {
411 }
else if (s ==
"fullscreen_mode") {
413 }
else if (s ==
"move") {
415 }
else if (s ==
"floating") {
417 }
else if (s ==
"urgent") {
419 }
else if (s ==
"mark") {
429 if (s ==
"restart") {
431 }
else if (s ==
"exit") {
441 if (s ==
"unspecified") {
464 qCWarning(Qi3pcLogger) <<
"Insufficient data read for IPC magic string. Seeked"
470 qCWarning(Qi3pcLogger) <<
"Unexpected magic string in message. Expected"
476 if (
auto read_size = socket.read(
reinterpret_cast<char*
>(&size),
sizeof size);
477 read_size !=
sizeof size) {
478 qCWarning(Qi3pcLogger) <<
"Insufficient data read for message size. Seeked"
479 <<
sizeof size <<
"bytes. - Read" << read_size <<
"bytes.";
484 if (
auto read_size = socket.read(
reinterpret_cast<char*
>(&type),
sizeof type);
485 read_size !=
sizeof type) {
486 qCWarning(Qi3pcLogger) <<
"Insufficient data read for message type. Seeked"
487 <<
sizeof type <<
"bytes. - Read" << read_size <<
"bytes.";
491 QJsonParseError parseError;
492 auto payload = socket.read(size);
494 if (socket.bytesAvailable() > 0) {
495 emit socket.readyRead();
498 QJsonDocument data = QJsonDocument::fromJson(payload, &parseError);
499 if (parseError.error != QJsonParseError::NoError) {
500 qCWarning(Qi3pcLogger) <<
"Parsing message body failed - JSON parse error:"
501 << parseError.errorString() <<
"in:" << payload;
505 return std::make_pair(data, type);
511 if (
auto path = QProcessEnvironment::systemEnvironment().value(
"I3SOCK");
524 process.start(
"i3", QStringList(
"--get-socketpath"));
525 process.waitForReadyRead();
526 auto path = QString(process.readAllStandardOutput()).trimmed();
528 process.waitForFinished();
664 if (events.isEmpty()) {
665 qCWarning(Qi3pcLogger) <<
"Requested to subscibe to empty event list. Nothing to do.";
671 QJsonDocument(QJsonArray::fromStringList(events)).toJson(),
680 QDataStream stream(&message, QIODevice::WriteOnly);
684 qint32 size = payload.size();
685 stream.writeRawData(
reinterpret_cast<const char*
>(&size),
sizeof size);
686 stream.writeRawData(
reinterpret_cast<const char*
>(&type),
sizeof type);
689 stream.writeRawData(payload.constData(), size);
692 socket.write(message);
747 payload.append(
id.toStdString().c_str());
760 if(!json.contains(
"parse_error")) {
765 if (json.contains(name)) {
766 ret.*member = json[name].toString();
783 auto str = QString(
"{");
785 str += QString(name) +
":'" + this->*member +
"',";
ShutdownChange
Types of change a shutdown event can have.
void configUpdated(const qi3pc::DataObject &config)
Signal emitted when the (cached) config have been updated.
void treeUpdated(const qi3pc::DataObject &tree)
Signal emitted when the layout tree cache have been updated.
WindowChange
Types of change a window event can have.
void processOutputEvent(const QJsonDocument &doc)
Handle data received from an output event.
void barConfigUpdated(const QJsonObject &config)
Signal emitted when a specific bar's (cached) config have been updated. At this point the configurati...
void fetchConfig()
Signal to emit to trigger an update of the (cached) config.
void processBindingEvent(const QJsonDocument &doc)
Handle data received from a binding event.
void sendMessage(const QByteArray &payload=QByteArray())
Send a message with the specified type and payload to i3.
void fetchBarConfigs()
Signal to emit to update the list of bar configurations.
void fetchBindingModes()
Signal to emit to trigger an update of the (cached) list of modes.
std::vector< std::pair< bool, Error > > CommandResults
Pairs of qi3pc::Error and boolean.
std::optional< std::pair< QJsonDocument, quint32 > > Message
Optional pair of a JSON document with a qi3pc::IpcType received with a message or an event before it ...
void barUpdateEvent(const QJsonObject &doc)
Signal emitted when a bar's configuration have been updated.
QLocalSocket m_messageSocket
void workspacesUpdated(const qi3pc::DataArray &workspaces)
Signal emitted when the (cached) list of workspaces have been updated.
void synced(bool success)
Signal emitted when a sync message have been replied to by i3.
std::optional< ParseError > Error
Optional qi3pc::ParseError. The optional is empty when the error could not be parsed.
void tickSent(bool success)
Signal emitted when a tick have been processed by i3.
void processBindingModesReply(const QJsonDocument &doc)
Handle data received in a binding mode reply.
QString socketPath() const
Get the socket path selected at construction.
const DataArray & marks() const
Get the (cached) list of set marks.
void subscribed(bool success)
Signal emitted when a subscribe message have been replied to.
void processSyncReply(const QJsonDocument &doc)
Handle data received from a sync reply.
DataString m_bindingState
void fetchBarConfig(const QString &id)
Signal to emit to update the (cached) configuration of a certain bar.
void processBarUpdateEvent(const QJsonDocument &doc)
Handle data received from a bar update event.
void bindingStateUpdated(const qi3pc::DataString &state)
Signal emitted when the (cached) current binding state have been updated.
void outputEvent(qi3pc::OutputChange change)
Signal emitted when the output(s) change.
void processBarConfigReply(const QJsonDocument &doc)
Handle data received from a bar config reply.
void fetchTree()
Signal to emit to trigger an update of the (cached) layout tree.
void processReply()
Read data from the message socket.
void fetchOutputs()
Signal to emit to trigger an update of the (cached) outputs.
void versionUpdated(const qi3pc::DataObject &version)
Signal emitted when the (cached) i3 version have been updated.
void outputsUpdated(const qi3pc::DataArray &outputs)
Signal emitted when (cached) outputs have been updated.
bool connect()
Start listening to messages and events from the window manager.
void modeEvent(QString change, bool pango)
Signal emitted when the binding mode changes.
static ShutdownChange ShutdownChangeFromString(const QString &s)
Convert a string into a shutdown change object.
void processMarkReply(const QJsonDocument &doc)
Handle data received from a mark reply.
static constexpr auto IpcMagicLength
void fetchBindingState()
Request update of the (cached) binding state.
void windowEvent(qi3pc::WindowChange change, const QJsonObject &container)
Signal emitted when a window changes.
static WorkspaceChange WorkspaceChangeFromString(const QString &s)
Convert a string into a workspace change object.
QLocalSocket m_eventSocket
static BindingChange BindingChangeFromString(const QString &s)
Convert a string into a binding change object.
void processWindowEvent(const QJsonDocument &doc)
Handle data received from a window event.
void subscribe(const QStringList &events)
Subscribe to a list of events.
void fetchMarks()
Signal to emit to trigger an update of the (cached) list of marks.
void processVersionReply(const QJsonDocument &doc)
Handle data received from a version reply.
static WindowChange WindowChangeFromString(const QString &s)
Convert a string into a window change object.
const DataArray & workspaces() const
Get the list of (cached) workspaces.
void newBarConfig(const QString &id)
Signal emitted when a new bar config have been added to the cache.
void processWorkspaceReply(const QJsonDocument &doc)
Handle data received from a workspace reply.
void fetchVersion()
Signal to emit to trigger a cache update for the i3wm version.
bool disconnect()
Stop listening to messages and events from the window manager.
static QString FindSocketPath()
Find the path to the i3 ipc local unix socket.
void processConfigReply(const QJsonDocument &doc)
Handle data received from a config reply.
const DataString & bindingState() const
Get the (cached) binding state.
static void WritePayload(QLocalSocket &socket, const QByteArray &payload, IpcType type)
Send a message with the specified type and payload to i3 using the specified socket.
void processTickReply(const QJsonDocument &doc)
Handle data received from a tick reply.
void processTickEvent(const QJsonDocument &doc)
Handle data received from a tick event.
void processBindingStateReply(const QJsonDocument &doc)
Handle data received from a binding state event reply.
void sendTick(const QByteArray &payload=QByteArray())
Send a tick message with the spoecified payload.
IpcType
Types of message/replies the API send/expect to/from i3wm.
const DataArray & bindingModes() const
Get the (cached) list of binding modes.
static constexpr auto IpcMagicString
void processTreeReply(const QJsonDocument &doc)
Handle data received from a tree reply.
void processShutdownEvent(const QJsonDocument &doc)
Handle data received from a shutdowm event.
virtual ~qi3pc()
Simple destructor for the qi3pc class.
void tickEvent(const QString &payload)
Signal emitted when subscribing to tick events or when a tick message have been sent to the ipc conne...
BindingChange
Types of change a binding event can have.
void processModeEvent(const QJsonDocument &doc)
Handle data received from a mode event.
Message processMessage(QLocalSocket &socket)
Read one message using the socket parameter.
void marksUpdated(const qi3pc::DataArray &marks)
Signal emitted when the (cached) list of marks have been updated.
void commandRan(CommandResults result)
Signal emitted when a command have been ran by i3.
void processEvent()
Read data from the event socket.
OutputChange
Types of change an output event can have.
std::optional< std::pair< QJsonObject, qint64 > > DataObject
Optional pair of a JSON object with its last update time.
WorkspaceChange
Types of change a workspace event can have.
void processCommandReply(const QJsonDocument &doc)
Handle data received from a run command reply.
qi3pc(QObject *parent=nullptr)
Construct a qi3pc object.
void bindingEvent(qi3pc::BindingChange change, const QJsonObject &binding, const QString &mode)
Signal emitteed when a binding have been triggered to run a command.
const DataObject & tree() const
Get the (cached) i3 layout tree.
void fetchWorkspaces()
Signal to emit to trigger an update of the list of workspace cache.
void processOutputReply(const QJsonDocument &doc)
Handle data received from an output reply.
void shutdownEvent(qi3pc::ShutdownChange change)
Signal emitted when the ipc socket is about to shutdown.
const DataObject & config() const
Get the (cached) data read from the config file.
void workspaceEvent(qi3pc::WorkspaceChange change, const QJsonObject ¤t, const QJsonObject &old)
Signal emitted with a workspace event's data preprocessed.
std::optional< std::pair< QString, qint64 > > DataString
Optional pair of a string with its last update time.
static OutputChange OutputChangeFromString(const QString &s)
Convert a string into an output change object.
static QString FindSocketPathFromI3Binary()
Find the path to the i3 ipc local unix socket using the i3 binary.
void bindingModesUpdated(const qi3pc::DataArray &modes)
Signal emitted when the (cached) list of modes have been updated.
std::optional< std::pair< QJsonArray, qint64 > > DataArray
Optional pair of a JSON array with its last update time.
const DataObject & version() const
Get the (cached) i3 version object.
bool isConnected()
Check if the connection to the ipc socket is established.
const DataObject & barConfigs() const
Get the (cached) list of all bar configurations.
const DataArray & outputs() const
Get the (cached) list of outputs.
void processWorkspaceEvent(const QJsonDocument &doc)
Handle data received from a workspace event.
IpcEvent
Types of events offered by i3wm's IPC API.
Container for the attributes of a parsing error from i3wm when trying to run an unparsable command;.
static constexpr auto _MEMBERS
Mapping of i3wm's parse error reply's attributes to members of ParseError.
QString errorPosition
The position where the error was detected in the input.
QString input
The command that failed to run.
QString toString() const
Convert to a json like string.
QString error
Human readble error message.
bool operator==(const ParseError &other) const
Memberwise comparison of this ParseError and another.
static std::optional< ParseError > FromJSON(const QJsonObject &json)
Build an optional ParseError from a QJsonObject.