Skip to content

Conversation

anriha
Copy link
Contributor

@anriha anriha commented Oct 3, 2025

This PR adds signals to track when windows enter or exit different layout modes (tiled, floating, maximized, fullscreen, and spilled).
When a window changes from one layout mode to another, the appropriate "unset" signal is fired for the previous state, followed by the "set" signal for the new state. This allows configurations to react to both entering and exiting specific window states. The signal is also fired when the window first spawns.

This PR adds signals for window creation and destruction. It also adds a layout mode change signal. This should address all the suggestions in this PR. I am not completely sure if the destroyed signal is correct like this in regard to unmap flag. But from my testing, this gave me the results I wanted.

I would also really appreciate it if someone checked my lua changes, I have basically no knowledge of lua, so I am just copying and pasting. I think it is right, but I am not sure how to actually check.

resolves #365

@Ottatop
Copy link
Collaborator

Ottatop commented Oct 3, 2025

I think it's simpler if we have just one signal, LayoutModeChanged, that sends the new layout mode for a window when it changes.

@anriha
Copy link
Contributor Author

anriha commented Oct 3, 2025

You are right, that would be simpler. But I still think it makes sense to add the information about what layout mode it went from. One of my use cases that I plan for this is to add idle inhibitors to windows. Currently, when you are playing wine game via xwayland there is no way to get idle inhibitor on the window. This is usually fine as you are using mouse/keyboard. But if you play a game with just controller, that doesn't register as any activity. So I want to be able to add when window goes fullscreen add idle inhibitor, when it leaves remove it.

So I think it might make sense to change it to one signal, something like WindowLayoutModeChanged which would get the old_mode: Option<LayoutModeKind>, new_mode: Option<LayoutModeKind>. If the window was new the old_mode would be None. And if the window was closing (which I didn't handle at all thinking about it) the new_mode would be None.

@Ph4ntomas
Copy link
Contributor

Ph4ntomas commented Oct 3, 2025

So I want to be able to add when window goes fullscreen add idle inhibitor, when it leaves remove it.

You don't need to know the old mode for this to work (that doesn't mean it won't be useful for other stuff).

So I think it might make sense to change it to one signal, something like WindowLayoutModeChanged which would get the old_mode: Option, new_mode: Option. If the window was new the old_mode would be None. And if the window was closing (which I didn't handle at all thinking about it) the new_mode would be None.

I think the Close should have its own signal, too. But sending one with a empty/None new_mode is still a good idea to allow for cleanup

Comment on lines 221 to 256
match old_mode.current() {
crate::window::window_state::LayoutModeKind::Floating => {
self.signal_state.window_unset_floating.signal(window);
}
crate::window::window_state::LayoutModeKind::Tiled => {
self.signal_state.window_unset_tiled.signal(window);
}
crate::window::window_state::LayoutModeKind::Maximized => {
self.signal_state.window_unset_maximized.signal(window);
}
crate::window::window_state::LayoutModeKind::Fullscreen => {
self.signal_state.window_unset_fullscreen.signal(window);
}
crate::window::window_state::LayoutModeKind::Spilled => {
self.signal_state.window_unset_spilled.signal(window);
}
}

match new_mode.current() {
crate::window::window_state::LayoutModeKind::Floating => {
self.signal_state.window_set_floating.signal(window);
}
crate::window::window_state::LayoutModeKind::Tiled => {
self.signal_state.window_set_tiled.signal(window);
}
crate::window::window_state::LayoutModeKind::Maximized => {
self.signal_state.window_set_maximized.signal(window);
}
crate::window::window_state::LayoutModeKind::Fullscreen => {
self.signal_state.window_set_fullscreen.signal(window);
}
crate::window::window_state::LayoutModeKind::Spilled => {
self.signal_state.window_set_spilled.signal(window);
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

This is not the only place the layout can change.

For example (there might be other instances):

  • src/layout.rs:108
  • src/api/window.rs:{443, 453, 459, 469}

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, it's better to handle this from State::on_event_loop_cycle_completion() rather than sprinkle the call to self.signal_state.window_*.

If you need the old value, it could be held in the window LayoutMode until the end of the loop, then from the aforementioned function, you'd iterate over windows to clear this value and send the old/new pair.
If you don't need the old value, just a boolean would work.

This should prevent missing layout changes.

(You may want to wait for Ottatop input on that first)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yea I think having an old_layout_mode: LayoutMode, checking to see if the current mode != the old mode to send the signal, and copying the current mode to the old mode in on_event_loop_cycle_completion is the simplest solution.

@anriha
Copy link
Contributor Author

anriha commented Oct 3, 2025

You don't need to know the old mode for this to work (that doesn't mean it won't be useful for other stuff).

You do need to know when the window stops being fullscreen, so you can remove the idle inhibitor. You could track it internally in your configuration, but that would be more complicated.

@Ph4ntomas
Copy link
Contributor

Ph4ntomas commented Oct 3, 2025

You don't need to know the old mode for this to work (that doesn't mean it won't be useful for other stuff).

You do need to know when the window stops being fullscreen, so you can remove the idle inhibitor. You could track it internally in your configuration, but that would be more complicated.

I already said that it might still be useful, but I still don't see the need for that use-case.

EDIT: Just to be clear, I'm not saying the old_mode shouldn't be sent. This is not up to me anyway.

You could track it internally in your configuration, but that would be more complicated

Just having the information doesn't solves your issue cleanly, so you'd still need to track some state in your config:

Let say for the sake of argument you want to use one global inhibitor if a window is full-screen, and you have this initial state:

window A { mode = Fullscreen }
window B { mode = Tiled }
Inhibitor { active = true }

You swap WindowA & WindowB (or fullscreen B then un-fullscreen A).

The following signal will be emitted:

WindowA::LayoutChanged( old = Fullscreen, new = Tiled }
WindowB::LayoutChanged( old = Tiled, new = Fullscreen }

There is no guarantee which signal will arrive first. If WindowB signal arrive first, the following sequence will happen:

WindowB::LayoutChanged( old = Tiled, new = Fullscreen }
Inhibitor::setActive() // ok, was already active
WindowA::LayoutChanged( old = Fullscreen, new = Tiled }
Inhibitor::setInactive() // there is still an active window, but the inhibitor was deactivated.

You can solve this by:

  • Reference counting Inhibitor activation/de-activation -> you do need to know the window previous mode
  • Having the inhibitor track which window is full-screen -> you don't need to know the window previous mode
  • Maintaining a set of fullscreened window -> you don't need to know the window previous mode
  • Having an inhibitor instance per window -> you don't need to know the window previous mode

@anriha
Copy link
Contributor Author

anriha commented Oct 3, 2025

EDIT: Just to be clear, I'm not saying the old_mode shouldn't be sent. This is not up to me anyway.

Don't worry, I don't mind discussing this.

What I plan to do is to add a way to manually insert idle inhibitor for the window itself. I think that would be cleaner than using one global idle inhibitor. Plus, it would work better if the fullscreen surface is not visible. At that point, the idle inhibitor shouldn't probably be active.

@Ottatop
Copy link
Collaborator

Ottatop commented Oct 4, 2025

I think it's best to just send the new mode for now. If we send both modes then the old has to be an Option and is only None for the first signal, which I don't like.

@anriha
Copy link
Contributor Author

anriha commented Oct 4, 2025

Ok, in that case, how about this approach. I do need to know what is the window initial layout mode when it spawns, I need to know when it changes, and when it closes. So how about I add new signals, something like window created/destroyed. These would just give you window_id and you can then use API to get its layout mode. And I do just add WindowLayoutModeChanged.

For my use case, this would make it a bit more complicated to keep track of everything in my config, but it would be doable and it would address the issue.

Theoretically we wouldn't even need window created signal, as add_window_rules could be used for this, but I think adding new signal for this is cleaner. As the add_window_rules shouldn't be used for this.

@Ottatop
Copy link
Collaborator

Ottatop commented Oct 6, 2025

Yea, an open and closed signal is good.

@anriha anriha changed the title Add window layout state change signals Window created/destroyed/layout_mode_change signals Oct 16, 2025
@anriha
Copy link
Contributor Author

anriha commented Oct 16, 2025

I changed PR to hopefully address all the comments here.

- Add new signals: WindowLayoutModeChanged, WindowCreated, WindowDestroyed
- Move LayoutMode enum to util.proto for shared usage
- Add tracking of window layout mode changes in on_event_loop_cycle_completion
Copy link
Contributor

@Ph4ntomas Ph4ntomas left a comment

Choose a reason for hiding this comment

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

You missed a few types when moving LayoutMode to util, but the lua changes are fine as far as I can tell

- Replace unwrap_or with if let for safer conversion in signal.rs
- Move window layout mode change check to dedicated method in window.rs
- Remove redundant window_created signal in xwayland.rs
Copy link
Collaborator

@Ottatop Ottatop left a comment

Choose a reason for hiding this comment

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

Thanks!

@Ottatop Ottatop merged commit 49c874f into pinnacle-comp:main Oct 22, 2025
4 checks passed
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.

Better window layering manipulation/always-on-top

3 participants