Skip to content

Conversation

nydragon
Copy link
Contributor

@nydragon nydragon commented Nov 2, 2024

This pr adds support for retrieving data from Sway's IPC.

Features:

  • Get last IPC object
  • Dispatch Sway commands
  • Workspaces
    • Retrieve all
    • Listen to data changes via signals
    • Force full refresh of workspaces
    • Get active
    • Live update
  • Monitors
    • Retrieve all
    • Listen to data changes via signals
    • Force full refresh of monitors
    • Get active
    • Live update

Additional Todo:

  • documentation
@outfoxxed
Copy link
Member

Looks largely copied from hyprland ipc at this point so I figured I'd mention a few things:

  • Make sure you only copy what makes sense to copy, don't keep what doesn't fit.
  • If you can leave the dispatch socket open you should. You can't in hyprland, idk about i3/sway
  • If you aren't implementing any sway specific features, this should work with i3 and be named as such.
@nydragon
Copy link
Contributor Author

nydragon commented Nov 3, 2024

Thanks for the feedback, I wanted to stay mainly in line with the existing code, but I do have some questions:

  1. Sway calls monitors "outputs", should that be reflected in Quickshell or is it better to be have uniform naming across different compositors ? Or "dispatch" is called "run commands"
  2. Should I move the x11 and wayland folders into a compositor folder and create a shared folder in there? Or where would I put the sway-i3 code ?
  3. We could abstract a part of the logic, namely the workspace/monitors refresh functions, across Hyprland & Sway/i3, does that make sense ?
@outfoxxed
Copy link
Member

  1. Monitor. Dispatch under hyprland is only for commands with no response as we don't currently have a promise type.
  2. For now src/x11/i3/ipc is fine. Hyprland is under the wayland folder because it has to use cmake functions defined in the wayland folder.
  3. Yes. This should happen, but not in this PR.

Also make sure you have a way to turn a sway monitor into a qs monitor.

@nydragon nydragon force-pushed the master branch 3 times, most recently from c061ef6 to e899eb4 Compare November 5, 2024 13:38
@nydragon nydragon changed the title Add Sway support Add i3/Sway support Nov 5, 2024
@nydragon nydragon force-pushed the master branch 5 times, most recently from 4a93776 to 30aa969 Compare November 5, 2024 23:33
@outfoxxed
Copy link
Member

You've force pushed a lot, this ready for review?

@nydragon
Copy link
Contributor Author

nydragon commented Nov 6, 2024

Yep, you could review it,

I would still want to implement more i3/Sway IPC features, but I think that will be for a later PR

@nydragon nydragon marked this pull request as ready for review November 8, 2024 15:36
Copy link
Member

@outfoxxed outfoxxed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bunch of nitpicks. I assume you've gone through and tested everything manually as well, run the lints, etc. If you have a tester file send that along as well and I'll give it a try before merging.

@nydragon
Copy link
Contributor Author

nydragon commented Nov 10, 2024

I have just noticed a crash related to those changes, don't know how to replicate it yet but I would need to fix that before this PR can progress

edit: fixed, was a lot more straightforward than I thought

@outfoxxed
Copy link
Member

outfoxxed commented Nov 10, 2024

Consider using QDataStream transactions instead. As is, if you break halfway through an event then the following event will be invalid and so on. check ipc.cpp for an example

@nydragon
Copy link
Contributor Author

Consider using QDataStream transactions instead. As is, if you break halfway through an event then the following event will be invalid and so on. check ipc.cpp for an example

I ended up doing that with QByteArrays instead, if I encounter a broken message i try to find the next occurrence of the magic sequence and skip to there. However this is under the assumption that the Sway IPC does not write partial messages to the socket, and it does not seem to do so.

@outfoxxed
Copy link
Member

outfoxxed commented Nov 16, 2024

Do we ever encounter broken messages? i3 shouldn't be sending those at all. I think the best solution to read the header would be a QDataStream transaction, which lets you do a partial read pretty easily.

Basically:

stream.startTransaction() stream.startTransaction() stream >> magic >> header bits >> size; if (!stream.commitTransaction()) return; stream >> data; // assuming you need size to find data. nested commitTransaction works as a barrier for the integrity of values read previosuly if (!stream.commitTransaction()) return;

You can nest transactions and it works pretty well. This should be more reliable than skipping events. Failing commits roll back the stream, and if there's enough data on the next readyread it should work.

@nydragon nydragon force-pushed the master branch 2 times, most recently from de9da8d to 35191b1 Compare November 17, 2024 02:30
@nydragon
Copy link
Contributor Author

Alrighty, that should be implemented now, here is also a file to test the IPC impl:

Details

import QtQuick import Quickshell import Quickshell.I3 ShellRoot { PanelWindow { id: panel Timer { id: timer } Component.onCompleted: { const program = "foot"; // change to an installed program const longLog = false; // toggle logging of workspace & monitor objects const stringify = v => JSON.stringify(v, null, 2); console.log("Connecting to", I3.socketPath); I3.rawEvent.connect(e => { console.log("Event:", e.type); }); I3.focusedWorkspaceChanged.connect(() => { console.log("Changed to workspace", I3.focusedWorkspace.name, I3.focusedMonitor.focusedWorkspace.name); console.log("Focused monitor's focused workspace and actual focused workspace should be the same:"); console.log(`${I3.focusedMonitor.focusedWorkspace.name} and ${I3.focusedWorkspace.name}`); }); I3.focusedMonitorChanged.connect(() => { console.log("Changed to monitor", I3.focusedMonitor.name); }); for (const monitor of I3.monitors.values) { console.log("Monitor:", monitor.name); longLog && console.log(stringify(monitor)); } for (const workspace of I3.workspaces.values) { console.log("Workspace:", workspace.name); longLog && console.log(stringify(workspace)); I3.dispatch(`workspace ${workspace.num}`); } I3.dispatch(`workspace 13`); console.log(`Changing workspace response: ${stringify(I3.dispatch(`exec ${program}`))}`); console.log(`Bad request response: ${stringify(I3.dispatch(`quickshell`))}`); I3.dispatch(`workspace 1`); I3.refreshWorkspaces(); I3.refreshMonitors(); console.log(stringify(I3.monitorFor(panel.screen))); } } }

@outfoxxed
Copy link
Member

outfoxxed commented Nov 17, 2024

Until you commit the transaction you shouldn't access data that is part of it, as it might not have been read. Also, rolling back the transaction to read again when you hit something invalid will just hang. On my phone rn, will do a proper code review again later.

@outfoxxed
Copy link
Member

While reviewing, I was going to test it and it crashed instantly under hyprland. It shouldn't work but it shouldn't crash either.
image

Copy link
Member

@outfoxxed outfoxxed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might've missed things again, but this is what I saw this time. Apologies for the repeated re-reviews but I want to be sure quickshell's integrations are stable.

@nydragon
Copy link
Contributor Author

Should be all fixed now

Copy link
Member

@outfoxxed outfoxxed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't see anything else at a glance, will give it a more thorough lookover again shortly.

@outfoxxed
Copy link
Member

We shouldn't be trying to write to the socket unless we're actually connected. I think hyprland IPC also does this though, now that I think about it. Will fix that later.

build/src/quickshell -p testi3.qml INFO: Launching config: "/home/admin/programming/outfoxxed/quickshell/i3/testi3.qml" INFO: Shell ID: "c4c77744f223921c7870881fbb46b0a8" Path ID "c4c77744f223921c7870881fbb46b0a8" INFO: Saving logs to "/run/user/1000/quickshell/by-id/jij3guvdns/log.log" WARN quickshell.I3.ipc: $I3SOCK is unset. Trying $SWAYSOCK. WARN quickshell.I3.ipc: $SWAYSOCK and I3SOCK are unset. Cannot connect to socket. DEBUG qml: Connecting to WARN: QIODevice::write (QLocalSocket): device not open WARN: QIODevice::write (QLocalSocket): device not open DEBUG qml: Changing workspace response: undefined WARN: QIODevice::write (QLocalSocket): device not open DEBUG qml: Bad request response: undefined WARN: QIODevice::write (QLocalSocket): device not open WARN: QIODevice::write (QLocalSocket): device not open WARN: QIODevice::write (QLocalSocket): device not open DEBUG qml: null INFO: Configuration Loaded 
@outfoxxed
Copy link
Member

Failing lints

/home/admin/programming/outfoxxed/quickshell/i3/src/x11/i3/ipc/qml.cpp:3:1: error: included header qjsonarray.h is not used directly [misc-include-cleaner,-warnings-as-errors] 3 | #include <qjsonarray.h> | ^~~~~~~~~~~~~~~~~~~~~~~ 4 | #include <qobject.h> 
/home/admin/programming/outfoxxed/quickshell/i3/src/x11/i3/ipc/connection.cpp:96:34: error: class member accesses must explicitly use thisptr [tidyfox-explicit-thisptr,-warnings-as-errors] 96 | for (auto& [type, data]: I3Ipc::parseResponse()) { | ^ | this-> /home/admin/programming/outfoxxed/quickshell/i3/src/x11/i3/ipc/connection.cpp:135:7: error: no header providing "strncmp" is directly included [misc-include-cleaner,-warnings-as-errors] 3 | if (strncmp(buffer.data(), MAGIC.data(), 6) != 0) { | ^ /home/admin/programming/outfoxxed/quickshell/i3/src/x11/i3/ipc/connection.cpp:377:13: error: method 'handleRunCommand' can be made static [readability-convert-member-functions-to-static,-warnings-as-errors] 377 | void I3Ipc::handleRunCommand(I3IpcEvent* event) { | ^ /home/admin/programming/outfoxxed/quickshell/i3/src/x11/i3/ipc/connection.cpp:380:3: error: variable 'success' of type 'bool' can be declared 'const' [misc-const-correctness,-warnings-as-errors] 380 | bool success = obj["success"].toBool(); | ^ | const /home/admin/programming/outfoxxed/quickshell/i3/src/x11/i3/ipc/connection.cpp:383:4: error: variable 'error' of type 'QString' can be declared 'const' [misc-const-correctness,-warnings-as-errors] 383 | QString error = obj["error"].toString(); | ^ | const 
@nydragon
Copy link
Contributor Author

Fixed

sway: add urgent and focused dispatchers to workspaces flake: add sway toggle WIP sway: add monitor status sway: handle multiple ipc events in one line sway: reuse socket connection for dispatches & better command type handling WIP sway: add associated monitor to a workspace i3/sway: update to allow for i3 compatibility i3/sway: manage setting the focused monitors i3/sway: fix multi monitor crash i3/sway: fix linting errors i3/sway: update nix package flag naming to i3 i3/sway: add documentation, fix module.md and impl monitorFor i3/sway: handle more workspace ipc events i3/sway: fix review i3/sway: fix crash due to newline breaking up an IPC message i3/sway: handle broken messages by forwarding to the next magic sequence i3/sway: break loop when buffer is empty i3/sway: fix monitor focus & focused monitor signal not being emitted i3/sway: use datastreams instead of qbytearrays for socket reading i3/sway: fix lint issues i3/sway: drop second socket connection, remove dispatch return value, recreate IPC connection on fatal error i3/sway: handle run_command responses i3/sway: remove reconnection on unknown event i3/sway: fix formatting, lint & avoid writing to socket if connection is not open
Copy link
Member

@outfoxxed outfoxxed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@outfoxxed outfoxxed merged commit 31adcaa into quickshell-mirror:master Nov 24, 2024
20 checks passed
@Actro25 Actro25 mentioned this pull request Sep 18, 2025
da-x added a commit to da-x/quickshell that referenced this pull request Oct 3, 2025
This is because QTextStream is not thread safe, as witnessed by this ASAN catch: | #0 0x7ffff78fb648 in realloc.part.0 (/lib64/libasan.so.8+0xfb648) (BuildId: 62576d6cef8744b024a915995656ecd4afeb4351) | quickshell-mirror#1 0x7ffff44fa2c7 in QArrayData::reallocateUnaligned(QArrayData*, void*, long long, long long, QArrayData::AllocationOption) (/lib64/libQt6Core.so.6+0x2fa2c7) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#2 0x7ffff44d025c in QArrayDataPointer<char16_t>::reallocateAndGrow(QArrayData::GrowthPosition, long long, QArrayDataPointer<char16_t>*) (/lib64/libQt6Core.so.6+0x2d025c) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#3 0x7ffff44ca21e in QString::append(QLatin1String) (/lib64/libQt6Core.so.6+0x2ca21e) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#4 0x7ffff4484bec in QTextStream::operator<<(QLatin1String) (/lib64/libQt6Core.so.6+0x284bec) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#5 0x6dec0e in qs::log::LogMessage::formatMessage(QTextStream&, qs::log::LogMessage const&, bool, bool, QString const&) /home/dan/dev/gh/da-x/quickshell/src/core/logging.cpp:101 | quickshell-mirror#6 0x6dfaa3 in qs::log::LogManager::messageHandler(QtMsgType, QMessageLogContext const&, QString const&) /home/dan/dev/gh/da-x/quickshell/src/core/logging.cpp:166 | quickshell-mirror#7 0x7ffff4326319 in qt_message_print(QtMsgType, QMessageLogContext const&, QString const&) (/lib64/libQt6Core.so.6+0x126319) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#8 0x7ffff432c2dd in qt_message_output(QtMsgType, QMessageLogContext const&, QString const&) (/lib64/libQt6Core.so.6+0x12c2dd) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#9 0x7ffff433e384 in QDebug::~QDebug() (/lib64/libQt6Core.so.6+0x13e384) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#10 0x7ffff44dfd55 in QScopedScopeLevelCounter::QScopedScopeLevelCounter(QThreadData*) (/lib64/libQt6Core.so.6+0x2dfd55) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#11 0x7ffff43af631 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (/lib64/libQt6Core.so.6+0x1af631) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#12 0x7ffff43b344d in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (/lib64/libQt6Core.so.6+0x1b344d) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#13 0x7ffff46e2b86 in postEventSourceDispatch(_GSource*, int (*)(void*), void*) (/lib64/libQt6Core.so.6+0x4e2b86) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#14 0x7ffff130cf80 in g_main_context_dispatch_unlocked.lto_priv.0 (/lib64/libglib-2.0.so.0+0x5bf80) (BuildId: 8eb8a9a2c6a8fd0b4f3a00d75e20274c6c52e805) | quickshell-mirror#15 0x7ffff136cc67 in g_main_context_iterate_unlocked.isra.0 (/lib64/libglib-2.0.so.0+0xbbc67) (BuildId: 8eb8a9a2c6a8fd0b4f3a00d75e20274c6c52e805) | quickshell-mirror#16 0x7ffff130e35f in g_main_context_iteration (/lib64/libglib-2.0.so.0+0x5d35f) (BuildId: 8eb8a9a2c6a8fd0b4f3a00d75e20274c6c52e805) | quickshell-mirror#17 0x7ffff46e22f2 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (/lib64/libQt6Core.so.6+0x4e22f2) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#18 0x7ffff43bdcc9 in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (/lib64/libQt6Core.so.6+0x1bdcc9) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#19 0x7ffff44e0c54 in QThread::exec() (/lib64/libQt6Core.so.6+0x2e0c54) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#20 0x7ffff458725e in QThreadPrivate::start(void*) (/lib64/libQt6Core.so.6+0x38725e) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#21 0x7ffff785e259 in asan_thread_start(void*) (/lib64/libasan.so.8+0x5e259) (BuildId: 62576d6cef8744b024a915995656ecd4afeb4351) | quickshell-mirror#22 0x7ffff4cbbb67 in start_thread (/lib64/libc.so.6+0x94b67) (BuildId: 9488b7bc91c299f62d05325020fbee9cfaf229bc) | quickshell-mirror#23 0x7ffff4d2c6bb in __clone3 (/lib64/libc.so.6+0x1056bb) (BuildId: 9488b7bc91c299f62d05325020fbee9cfaf229bc) | |0x50c00008fe00 is located 0 bytes inside of 128-byte region [0x50c00008fe00,0x50c00008fe80) |freed by thread T2 (QDBusConnection) here: | #0 0x7ffff78fb648 in realloc.part.0 (/lib64/libasan.so.8+0xfb648) (BuildId: 62576d6cef8744b024a915995656ecd4afeb4351) | quickshell-mirror#1 0x7ffff44fa2c7 in QArrayData::reallocateUnaligned(QArrayData*, void*, long long, long long, QArrayData::AllocationOption) (/lib64/libQt6Core.so.6+0x2fa2c7) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | |previously allocated by thread T1 (QThread) here: | #0 0x7ffff78fb648 in realloc.part.0 (/lib64/libasan.so.8+0xfb648) (BuildId: 62576d6cef8744b024a915995656ecd4afeb4351) | quickshell-mirror#1 0x7ffff44fa2c7 in QArrayData::reallocateUnaligned(QArrayData*, void*, long long, long long, QArrayData::AllocationOption) (/lib64/libQt6Core.so.6+0x2fa2c7) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | |Thread T1 (QThread) created by T0 here: | #0 0x7ffff78f40a3 in pthread_create (/lib64/libasan.so.8+0xf40a3) (BuildId: 62576d6cef8744b024a915995656ecd4afeb4351) | quickshell-mirror#1 0x7ffff45868e8 in QThread::start(QThread::Priority) (/lib64/libQt6Core.so.6+0x3868e8) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#2 0x6e1049 in qs::log::LogManager::init(bool, bool, bool, QtMsgType, QString const&, QString const&) /home/dan/dev/gh/da-x/quickshell/src/core/logging.cpp:261 | quickshell-mirror#3 0x46a94f in qs::launch::runCommand(int, char**, QCoreApplication*) /home/dan/dev/gh/da-x/quickshell/src/launch/command.cpp:500 | quickshell-mirror#4 0x445373 in qs::launch::main(int, char**) /home/dan/dev/gh/da-x/quickshell/src/launch/main.cpp:112 | quickshell-mirror#5 0x4374b5 in main /home/dan/dev/gh/da-x/quickshell/src/main.cpp:3 | quickshell-mirror#6 0x7ffff4c5130d in __libc_start_call_main (/lib64/libc.so.6+0x2a30d) (BuildId: 9488b7bc91c299f62d05325020fbee9cfaf229bc) | |Thread T2 (QDBusConnection) created by T0 here: | #0 0x7ffff78f40a3 in pthread_create (/lib64/libasan.so.8+0xf40a3) (BuildId: 62576d6cef8744b024a915995656ecd4afeb4351) | quickshell-mirror#1 0x7ffff45868e8 in QThread::start(QThread::Priority) (/lib64/libQt6Core.so.6+0x3868e8) (BuildId: c21ff2de250ed0aba68ae0250f9a0d1c455f9fd3) | quickshell-mirror#2 0x7ffff613a166 in QDBusConnectionManager::instance() (/lib64/libQt6DBus.so.6+0x3d166) (BuildId: 35a23e05853374dafc6df118df20e91852bc6344) | |SUMMARY: AddressSanitizer: double-free (/lib64/libasan.so.8+0xfb648) (BuildId: 62576d6cef8744b024a915995656ecd4afeb4351) in realloc.part.0 |==3529858==ABORTING |[Thread 0x7fffd40f56c0 (LWP 3529873) exited] |[Thread 0x7fffd53ff6c0 (LWP 3529872) exited] |[Inferior 1 (process 3529858) exited with code 01]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants