8000 feat!(NiriService): move to GObject-based objects by regenman · Pull Request #181 · linkfrg/ignis · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat!(NiriService): move to GObject-based objects #181

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

Merged
merged 41 commits into from
Mar 25, 2025

Conversation

regenman
Copy link
Contributor
@regenman regenman commented Mar 3, 2025

Along the lines of #171, this is a refactor introducing GObjects for the Niri Service.

New classes:

  • NiriKeyboardLayouts
  • NiriOutput
  • NiriWindow
  • NiriWorkspace

In contrast to #171, I've strived to more comprehensively use the data of events received in the IPC event stream to update the GObjects, instead of triggering an additional IPC request to synchronize the state, thus reducing overhead. This is possible since Niri's IPC makes the promise to be verbose about it's state.

Some parts of the logic are inspired by the Niri Waybar module.

A few notes:

  • NiriService.kb_layout is removed, instead NiriService.keyboard_layouts exposes a list of configured layouts (NiriService.keyboard_layouts.names) and the id of the currently active layout (NiriService.keyboard_layouts.current_idx)
    use NiriService.keyboard_layouts.current_name instead.
  • NiriService.active_workspaces has been dropped, to avoid introducing a separate data structure (list) that needs to be kept up to date. Active workspaces can still be extracted from NiriService.workspaces (each NiriWorkspace object exposes the is_active property). I'm open for discussion on this point, and would appreciate any feedback, it does potentially add a burden of complexity for the user.
  • NiriService.send_command now performs json serialization of the cmd argument and adds a trailing newline ("\n") to the resulting string, thus eliminating the need to pre-process any argument passed to this method.

The PR is in a working state already, I've labeled it WIP since it's a big refactor and minor changes might still be needed.

@linkfrg
Copy link
Owner
linkfrg commented Mar 3, 2025

Hmmm, as I see it, NiriObject and HyprlandObject are no different.
How about adding a general class named DataGObject (in the main branch) and then using it in both HyprlandService and NiriService?

@regenman
Copy link
Contributor Author
regenman commented Mar 3, 2025

Hmmm, as I see it, NiriObject and HyprlandObject are no different. How about adding a general class named DataGObject (in the main branch) and then using it in both HyprlandService and NiriService?

Good idea, I think it would make sense to generalise it.

@linkfrg
Copy link
Owner
linkfrg commented Mar 3, 2025

DataGObject has been added in 1dccb2b

@regenman
Copy link
Contributor Author
regenman commented Mar 3, 2025

DataGObject has been added in 1dccb2b

Thanks 👍 I've pushed a commit that implements this for NiriService

@linkfrg
Copy link
Owner
linkfrg commented Mar 4, 2025

I guess you forgot to mention the new classes in docs.
Add this to docs/api/services/niri.rst:

.. autoclass:: ignis.services.niri.NiriKeyboardLayouts
    :members:

.. autoclass:: ignis.services.niri.NiriOutput
    :members:

.. autoclass:: ignis.services.niri.NiriWindow
    :members:

.. autoclass:: ignis.services.niri.NiriWorkspace
    :members:

@regenman
Copy link
Contributor Author
regenman commented Mar 4, 2025

I guess you forgot to mention the new classes in docs. Add this to docs/api/services/niri.rst:

Thanks, added now.

@regenman
Copy link
Contributor Author
regenman commented Mar 5, 2025

Any idea why the mypy check keeps failing? I can't see anything wrong here.

ignis/services/niri/window.py:86: error: Signature of "is_floating" incompatible with supertype "Object"  [override]
ignis/services/niri/window.py:86: note:      Superclass:
ignis/services/niri/window.py:86: note:          def is_floating(self) -> bool
ignis/services/niri/window.py:86: note:      Subclass:
ignis/services/niri/window.py:86: note:          IgnisProperty

@linkfrg
Copy link
Owner
linkfrg commented Mar 5, 2025

Any idea why the mypy check keeps failing? I can't see anything wrong here.

It happens because GObject.Object already has is_floating() method. You override it, but the signature is incompatible with the parent class.
There are two solutions for this:

  1. Simply add # type: ignore to the problematic line.
  2. Rename the method (property).

@linkfrg linkfrg added this to the v0.5 milestone Mar 7, 2025
@regenman
Copy link
Contributor Author

I guess this is ready to be reviewed.

Of course it's still possible to add more features, in particular I considered adding a NiriAction class, which would contain methods that wrap each available action in niri_ipc, for increased convenience over NiriService.send_command. (example: NiriAction.focus_column_left())
I think it won't fit in this PR - it turns out there are quite a few actions to cover... Don't know if it would make sense at all, perhaps if I can figure out a way to bulk-create methods from a dict that holds all actions and their respective arguments, in order to reduce the labor cost of staying up to date with the ipc.

regenman and others added 4 commits March 22, 2025 21:51
Co-authored-by: Link <127321188+linkfrg@users.noreply.github.com>
Co-authored-by: Link <127321188+linkfrg@users.noreply.github.com>
Co-authored-by: Link <127321188+linkfrg@users.noreply.github.com>
linkfrg and others added 2 commits March 23, 2025 02:15
fix: use type instead of instances for `obj_type` type hint in `__update_niri_obj`
@linkfrg
Copy link
Owner
linkfrg commented Mar 23, 2025

I think there is some issue caused by relying on event stream during initialization.
__listen_events() launches in a separate thread and it causes the end of service __init__ immediately.
So, the first data synchronization is performed in the background. And although the service is initialized, its objects may not have all required data.
For example, running this code (only it, without other parts of configuration) will raise IndexError: list index out of range:

from ignis.services.niri import NiriService
niri = NiriService.get_default()

print(niri.keyboard_layouts.current_name)

The example bar works great, because other services/widgets/etc are initialized before niri objects are needed, therefore __listen_events() has time to synchronize data.

@regenman
Copy link
Contributor Author

I think there is some issue caused by relying on event stream during initialization. __listen_events() launches in a separate thread and it causes the end of service __init__ immediately. So, the first data synchronization is performed in the background. And although the service is initialized, its objects may not have all required data.

Right, good catch. It looks like KeyboardLayouts is actually always the last event to be reported during initialization of the Niri event stream. What about adding something like this after __listen_events()?

while self._keyboard_layouts.current_idx == -1:
    time.sleep(0.01)

@linkfrg
Copy link
Owner
linkfrg commented Mar 23, 2025

Right, good catch. It looks like KeyboardLayouts is actually always the last event to be reported during initialization of the Niri event stream. What about adding something like this after __listen_events()?

while self._keyboard_layouts.current_idx == -1:
    time.sleep(0.01)

Looks kinda hacky with that time.sleep() and tying to self._keyboard_layouts.current_idx ...

Hmm, if KeyboardLayouts is always the last event, maybe it's better to launch self.__listen_events() without thread, catch when KeyboardLayouts is emitted, run self.__listen_events() but in a separate thread, and return (to unblock execution)?

@regenman
Copy link
Contributor Author

Looks kinda hacky with that time.sleep() and tying to self._keyboard_layouts.current_idx ...

I agree, it was more intended as a starting point for a discussion :)

Hmm, if KeyboardLayouts is always the last event, maybe it's better to launch self.__listen_events() without thread, catch when KeyboardLayouts is emitted, run self.__listen_events() but in a separate thread, and return (to unblock execution)?

That sounds much better. I'll see what I can come up with, in a way that will minimize code duplication

@regenman
Copy link
Contributor Author
regenman commented Mar 23, 2025

Have a look at 7d6751d
EDIT: made a small mistake, corrected in 40d7d63
EDIT2: more improvements in d6f48dd

@linkfrg
Copy link
Owner
linkfrg commented Mar 25, 2025

Hmm, looks good for me. @regenman is this ready to be merged?

@regenman
Copy link
Contributor Author

Hmm, looks good for me. @regenman is it ready to be merged?

Great. It's working well on my side, and I don't have more to add at the moment, so I'd say go ahead and merge.
Thanks for all the feedback!

Copy link
Owner
@linkfrg linkfrg left a comment

Choose a reason for hiding this comment

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

Thank you very much for your work!

@linkfrg linkfrg merged commit 84c0569 into linkfrg:main Mar 25, 2025
3 checks passed
linkfrg added a commit that referenced this pull request Mar 25, 2025
@regenman regenman deleted the niri-gobject branch March 25, 2025 15:43
@linkfrg linkfrg changed the title [WIP] feat!(NiriService): move to GObject-based objects feat!(NiriService): move to GObject-based objects Mar 25, 2025
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.

Move to GObject-based objects for Niri Workspaces
2 participants
0