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

Event stream IPC #453

Merged
merged 15 commits into from
Sep 2, 2024
Merged

Event stream IPC #453

merged 15 commits into from
Sep 2, 2024

Conversation

YaLTeR
Copy link
Owner

@YaLTeR YaLTeR commented Jun 20, 2024

Some basic scaffolding for an event stream IPC.

  1. Changed the client to read only a single line worth of response.

    This mirrors how the server works, and will be necessary to tell apart further messages. Events will arrive at one per line.

  2. Added Request::EventStream that replies with Response::Handled and then proceeds to write one Event per line as they occur.

  3. Events will be designed in such a way that the initial burst will synchronize the current state to the client, then subsequent events will modify that state in a consistent way. E.g. the initial burst of events will create all current outputs and workspaces.

  4. Compositor-side, event stream clients have a buffer of events for sending. If this buffer runs too large (the client hanged and isn't reading), this client will be dropped. This is kinda similar to what Wayland itself does.

The events are all subject to change until this PR is merged. Currently only the WindowFocused is actually implemented, and I'm planning to change its type too.

@SidharthArya
Copy link

Hi, when is this planned to be merged?
Can pick up other tasks (except WindowFocused) if needed ?
Would love to see WorkspaceFocused, MonitorFocused, WorkspaceAdded and MonitorAdded.

@YaLTeR
Copy link
Owner Author

YaLTeR commented Jul 9, 2024

No plan yet, this is just scaffolding for now. It's in a too early of a state to get help from others on specific tasks.

@jakesarjeant
Copy link

No plan yet, this is just scaffolding for now. It's in a too early of a state to get help from others on specific tasks.

Has that changed yet? I'd be happy to pick up some work too if needed

@YaLTeR
Copy link
Owner Author

YaLTeR commented Aug 24, 2024

I was fairly busy with uni; I think I am about to have sorted most of it out though so I will look into this more soon.

@YaLTeR
Copy link
Owner Author

YaLTeR commented Aug 28, 2024

Update, I did some more sketching out. Now, the following events are implemented: WorkspacesChanged, WorkspaceSwitched, KeyboardLayoutsChanged, KeyboardLayoutSwitched.

I was able to make a Waybar workspace indicator with the following Python script:

#!/usr/bin/python3

import json
import os
import sys
from socket import AF_UNIX, SHUT_WR, socket

niri_socket = socket(AF_UNIX)
niri_socket.connect(os.environ["NIRI_SOCKET"])
file = niri_socket.makefile("rw")

file.write('"EventStream"')
file.flush()
niri_socket.shutdown(SHUT_WR)

output = None
if len(sys.argv) > 1:
    output = sys.argv[1]

workspaces = {}


def print_workspaces():
    vals = workspaces.values()
    if output is not None:
        vals = filter(lambda ws: ws["output"] == output, vals)

    vals = sorted(vals, key=lambda ws: ws["idx"])
    text = "\n".join(("󰪥" if ws["is_active"] else "󰄰" for ws in vals))
    print(json.dumps({"text": text}), flush=True)


for line in file:
    event = json.loads(line)

    if changed := event.get("WorkspacesChanged"):
        workspaces = {ws["id"]: ws for ws in changed["workspaces"]}
        print_workspaces()
    elif switched := event.get("WorkspaceSwitched"):
        for ws in workspaces.values():
            if ws["output"] == switched["output"]:
                ws["is_active"] = ws["id"] == switched["id"]
        print_workspaces()

(Note the \n: my bar is vertical, so it uses newlines.)

And this is the custom module setup:

    "custom/workspaces": {
        "exec": "~/source/py/niri-ipc/workspaces.py \"$WAYBAR_OUTPUT_NAME\"",
        "return-type": "json"
    },

image

@YaLTeR
Copy link
Owner Author

YaLTeR commented Aug 29, 2024

I started implementing built-in support in Waybar using this event stream draft PR. You can try my fork here: https://github.com/YaLTeR/Waybar/tree/niri

niri-waybar-workspaces.mp4

So far I added niri/workspaces and niri/language with configuration roughly matching some subset of the same from sway/ and hyprland/.

@YaLTeR
Copy link
Owner Author

YaLTeR commented Aug 29, 2024

Pushed a breaking change to the IPC to differentiate between active and focused workspaces, Waybar fork also updated. New Python script:

#!/usr/bin/python3

import json
import os
import sys
from socket import AF_UNIX, SHUT_WR, socket

niri_socket = socket(AF_UNIX)
niri_socket.connect(os.environ["NIRI_SOCKET"])
file = niri_socket.makefile("rw")

file.write('"EventStream"')
file.flush()
niri_socket.shutdown(SHUT_WR)

output = None
if len(sys.argv) > 1:
    output = sys.argv[1]

workspaces = {}


def print_workspaces():
    vals = workspaces.values()
    if output is not None:
        vals = filter(lambda ws: ws["output"] == output, vals)

    vals = sorted(vals, key=lambda ws: ws["idx"])
    text = "\n".join(("󰪥" if ws["is_active"] else "󰄰" for ws in vals))
    print(json.dumps({"text": text}), flush=True)


for line in file:
    event = json.loads(line)

    if changed := event.get("WorkspacesChanged"):
        workspaces = {ws["id"]: ws for ws in changed["workspaces"]}
        print_workspaces()
    elif focused := event.get("WorkspaceFocused"):
        ws_output = workspaces[focused["id"]]["output"]
        for ws in workspaces.values():
            got_focused = ws["id"] == focused["id"]
            if ws["output"] == ws_output:
                ws["is_active"] = got_focused
            ws["is_focused"] = got_focused
        print_workspaces()
    elif activated := event.get("WorkspaceActivated"):
        ws_output = workspaces[activated["id"]]["output"]
        for ws in workspaces.values():
            if ws["output"] == ws_output:
                ws["is_active"] = ws["id"] == activated["id"]
        print_workspaces()

@YaLTeR
Copy link
Owner Author

YaLTeR commented Aug 30, 2024

Pushed another breaking change, along with an implementation of Window events:

  • WorkspaceActiveWindowChanged
  • WindowsChanged
  • WindowOpenedOrChanged
  • WindowClosed
  • WindowFocusChanged

Waybar fork is updated to use this, and also I implemented a niri/window module similar to hyprland/window.

This completes my sketching so far; I'm fairly sure that this is enough IPC to cover the remaining window icon support in the workspaces Waybar module (I'm too lazy to implement that), and anything past that should be fairly straightforward to add without breaking changes.

That said, I'd like others to take a look at this before merging, give it a try with Waybar or by implementing their own scripts, in case I missed something important.

Newest Python workspaces script:

#!/usr/bin/python3

import json
import os
import sys
from socket import AF_UNIX, SHUT_WR, socket

niri_socket = socket(AF_UNIX)
niri_socket.connect(os.environ["NIRI_SOCKET"])
file = niri_socket.makefile("rw")

file.write('"EventStream"')
file.flush()
niri_socket.shutdown(SHUT_WR)

output = None
if len(sys.argv) > 1:
    output = sys.argv[1]

workspaces = {}


def print_workspaces():
    vals = workspaces.values()
    if output is not None:
        vals = filter(lambda ws: ws["output"] == output, vals)

    vals = sorted(vals, key=lambda ws: ws["idx"])
    text = "\n".join(("󰪥" if ws["is_active"] else "󰄰" for ws in vals))
    print(json.dumps({"text": text}), flush=True)


for line in file:
    event = json.loads(line)

    if changed := event.get("WorkspacesChanged"):
        workspaces = {ws["id"]: ws for ws in changed["workspaces"]}
        print_workspaces()
    elif activated := event.get("WorkspaceActivated"):
        focused = activated["focused"]
        ws_output = workspaces[activated["id"]]["output"]
        for ws in workspaces.values():
            got_activated = ws["id"] == activated["id"]
            if ws["output"] == ws_output:
                ws["is_active"] = got_activated
            if focused:
                ws["is_focused"] = got_activated
        print_workspaces()

@YaLTeR YaLTeR changed the title Draft: Event stream IPC Event stream IPC Aug 30, 2024
@YaLTeR YaLTeR marked this pull request as ready for review August 30, 2024 15:51
Deleting the test because it only made sense when no-animation was
special cased.
Let's see if anyone complains.
It's not added to clap because there's no convenient mutually-exclusive
argument enum derive yet (to have either the current <REFERENCE> or an
--id <ID>). It's not added to config parsing because I don't see how it
could be useful there. As such, it's only accessible through raw IPC.
@YaLTeR YaLTeR enabled auto-merge (rebase) September 2, 2024 06:41
@YaLTeR YaLTeR merged commit 12b16a9 into main Sep 2, 2024
18 checks passed
@YaLTeR YaLTeR deleted the event-stream branch September 2, 2024 06:49
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

Successfully merging this pull request may close these issues.

3 participants