-
-
Notifications
You must be signed in to change notification settings - Fork 662
Hidden workspaces #2997
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
base: main
Are you sure you want to change the base?
Hidden workspaces #2997
Conversation
|
Glad to see some progress here! Looking ahead at indexing implementation, I think negative numbers would make sense in this context? Especially as these hidden workspaces would be un-switchable. |
that was my thought, but i think WorkspaceId wraps a u64 |
|
Hmm yeah if that's a rigid implementation then that seems like an annoying workaround. In theory, you could wrap around and index backwards from 0xFFFFFFFFFFFFFFFF, then find a way to discount hidden workspaces from being detected & interpolated between. I'd say a swap to i64 would be theoretically cleaner but that's definitely something for YaLTeR to comment on. |
That's not a bad method. If we force the hidden workspaces at the end of u64 we shouldn't need to worry about interpolation. But ill have to play with that. As for larger changes like changing types on workspace id. Yeah, my hope is to step on as few as toes as possible with this, haha. |
|
Every workspace is that's not the active one is already hidden (not visible) outside of the overview, while the overview is useful for reviewing what can't normally be seen. What's the benefit completely hiding workspaces even from the overview? I don't quite see the use over a normal workspace named "stash" that you perhaps treat differently from others using different keybindings for it. |
It declutters the interface for those using those stash workspaces. It's a concession every implementation of scratchpad has to make because niri has no hidden workspace. Workspaces that are "hidden" by virtue of not being in the current view can still be erroneously navigated to and also display through different protocols. Those using the workspace in this way will never need to see the workspace their scratchpads go to. |
The scratchpad use-case might be too incidental to be the main selling point of this feature. But I have a direct use-case. I work on a large project made up of subprojects that I open at the start of my work day.
Another potential solution would be some sort of virtual output that holds workspaces offscreen. |
I like this idea. I was also worried that this story would be pointless without a third party tie that sends things to hidden. So would this entail keybinds that toggle hidden on workspaces by their id, name, etc? EDIT: i've gone ahead and taken a shot at this req. |
fac4963 to
990620d
Compare
| let output = self.output.as_ref().unwrap(); | ||
| let Some(output) = self.output.as_ref() else { | ||
| return; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the case of updating the output size of the ws a hidden workspace will not be rendered so we need to exit early if there is none. At least that seems to be the need, as otherwise i'd get core dump on the initial hide toggle.
|
|
||
| /// Hidden workspaces need to track their original idx | ||
| /// | ||
| pub original_idx: Option<usize>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, I'd like a hidden workspace to try and keep it's idx so it can be at an intuitive place when you unhide it, similar to workspace movement on monitor connect/disconnect.
|
Currently, for my usecase this is functional for me, and I encourage anyone willing to experiment to try this PR and let me know if there are issues with it, or edgecases I didn't test. I have also added a separate branch on my scratchpad project that makes use of this PR's new niri_ipc WorkspacesWithHidden functionality to summon and stash windows bound to hidden workspaces. |
|
An alternative approach I've been thinking about: So, we hide a workspace. We remove the workspace from the monitors workspace vec ( but retain its output. ). Then insert that workspace into a hidden_workspaces vec on that monitor. Because it's in a different vec we shouldn't need extra logic for managing idxs of the visible workspaces. When we unhide that workspace, we simply insert it by its idx into the main workspace vec at its desired position. My hope is this method will require far fewer changes, less extra struct fields, less complexity for the same functionality. main...argosnothing:niri:hidden-workspaces-2 an attempt was made. I'm not really convinced this is a better route after all. It seems to be even more code add than simply keeping hidden workspaces in the same vec and adjusting indexes to keep out of the visible idx range. |
6ce2667 to
0333fd0
Compare
0333fd0 to
2a10e2b
Compare
| println!("The change will apply when it is connected."); | ||
| } | ||
| } | ||
| Msg::WorkspacesWithHidden => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially I had added options to Workspaces for different filtering options with Hidden workspaces, but this method seems better since it won't break projects that use the rust_ipc crate as it's opt in.
| Response::Outputs(outputs.collect()) | ||
| } | ||
| Request::Workspaces => { | ||
| let state = ctx.event_stream_state.borrow(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not include hidden workspaces in normal workspace requests. If consumers want to access hidden workspaces, they'll need to use the new endpoint.
| state.apply(event.clone()); | ||
| server.send_event(event); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I played around with different methods to keep hidden workspaces out of event stream while still keeping it for the state, this just ended up being the only one I could get to work without horrible things happening.
src/layout/monitor.rs
Outdated
| self.workspaces[idx].hidden = true; | ||
| let mut ws = self.remove_workspace_by_idx(idx); | ||
| ws.original_idx = Some(idx); | ||
| self.workspaces.insert(self.workspaces.len(), ws); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the main "trick" of how im doing these hidden workspaces. If it's hidden, we make sure it's at the end of the vec, and that empty workspace is shown right before, creating the illusion that the empty workspace is the last workspace, and the hidden ones arn't behind it.
|
Hi, I don't want to add complexity, but may I ask if this method of hiding workspaces supports also hidding named workspaces? I have a couple of usecases that would be great to have named workspaces (which have specific layouts) be hidden! Thanks! |
Yes, in fact it is designed to only work on named workspaces. Currently I have a workspace called stash that i send scratchpad windows to. This is usually hidden, but i can toggle it with a keybind. I have another workspace called "work" that Is unhidden but I toggle to hidden when im not working. |
Right, sorry, dumb question! I tried your branch Some things I missed, and wondering if it is in scope of features:
I understand if these are not wanted features, since I think I am trying to use this in a different usecase. Instead of one hidden "stash" workspace, I am trying to have several workspaces that I not always use, configured.
Mentioning again, totally understand if this usecase it outside of scope! Thanks! |
1 and 2 seem like good ideas and would be fun to implement. The issue with the third, and why I only have it setup for named workspaces ( although i believe i still need to properly represent this in the config )
Your usecase makes sense though, and for the first two i'll add that to my todo on this PR. EDIT: I will need to support dynamically assigned and unassigned workspace as well, so this might suit your first requirement after all once implemented. |
Great to hear! I, in my own use case, don't see much issue in the third not being implemented if the first 2 are. Because if it "auto hides" based on having windows or not, I don't need much of "manual hidding". For the manual hidding (and really hide windows), I can set a proper static scratchpad workspace, like the use case of this pull request. Thanks! |
|
Just tested. Yeah, as you said exactly :) It is interesting, maybe for temporary apps/windows, that I don't mind losing when closing Niri. I am testing with Dank shell, which allows to give special icons for named desktops in Niri. (one issue with Dank shell, is if I click on the taskbar, the icon of a window on a hidden workspace, it does not change to it) Would be great to have an option to leave them visible if they have windows, but, this way is also interesting! (already have a use case in mind... to use llama-serve, which I want running but it would be handy to be hidden when not on it, and I would not care to close it before exiting Niri). Thanks, looking good! :) |
Next time i do a rebuild i'll bring in these changes myself. When testing niri it actually crashed my scratchpad daemon so i need to retag my windows in my stash workspace to pick them back up after i restart the daemon. Would save me a keypress as I never need the workspace to stay unhidden when im done with it. As for the other stuff i'll probably hold off until I get some additional feedback on what things other people would want/expect for a PR like this. Your suggestion of auto unhiding when explicitly focused makes intuitive sense to me though. |
|
I'm trying out this branch, and things have been pretty smooth so far. I'm also using https://github.com/argosnothing/niri-scratchpad-rs/tree/hidden-workspaces and it's working as expected. I just did some basic sanity tests, but that's a feature I'm really looking forward to, I'll keep using it to see if something comes up. Good job! Something I did notice, if you create a hidden workspace while niri is running, niri crashes. I mean, adding and saving to hot reload, it causes a crash. Creating a |
Ah i forgot to add that bug to my todos! I did run into this. I'll add it to the list. I try to get a few things knocked out on this PR a week and do my best to keep it up to date with master. I appreciate the feedback! |
|
I've added the fix to One note is that hidden workspaces "want" to be sent to where they were previously when you hide and unhide. I use the idx for this. On a newly added hidden workspace that idx will be where that workspace would have been sent to if it were not hidden. So lets say you have non hidden workspaces like on hidden workspace add it's original_idx ( where it wants to go when unhidden ) will be set to 0, pushing the idx of other workspaces up, it's actual position on add will be at |3| (at the end of the vec, and hidden, inaccessible), once it gets unhid it will go to it's 0 position. |
|
After using this feature daily for nearly a month, I find it to be sufficiently reliable and very practical. Are there any plans to convert this PR from a draft to a formal PR? |
I would like some input from contributors and yalter first. There are currently many Issues in the pipeline so I'd rather get input before I add to the pile. There's also several edge cases I know exist like travelling hidden workspaces that I need to brainstorm workarounds for. Prs to this are welcome as well, my plan is to keep this updated with niri major releases. |
|
Hello, great work! I am using this, and I am having a problem: as soon as I open the application I get the workspace ID of the current workspace even though it was supposed to open in the stash workspace: It should show workspace ID 3: ❯ niri msg workspaces-with-hidden
Output "DP-1":
* 1
2
Output "HDMI-A-2":
* 1
2
3 "stash"I don't know if this is related to having 2 monitors. But I also can't see the window but know it is running because of |
As a reference, I solved this by keeping track of when the windows is hidden by using a file in the tmp dir. |
Interesting. I haven't tested with against window rules. I'll add that to my todos. Thanks for letting me know. |
|
@argosnothing Something that is more of a QOL tip, would it be possible to just show some symbol or something in the normal workspace listing command to say that the workspace is hidden? Maybe it is something like: 3 " |
I think that most scripts use json, so it shouldn't break a lot of stuff to unify the commands and add a flag. But, maybe you just created the command to make it more clear while testing the PR. I will let you know if I find anything else. I plan to keep using it. I will also soon put the scripts I use to make the complete scratchpad setup work even with that issue. Thank you for the awesome work! I don't have the time to help with code as I am not very proficient in Rust, but I hope I can help with reproducible bug reports. |
Yes, the initial intention was to never touch previous IPC code. If i remember correctly, I initially put the hidden in normal workspace list calls, with an additional hidden flag, but the issue is shells like noctalia do not know about this flag, and will simply include workspaces by default. I initially added a flag to that endpoint, and this does work. The issue is mainly on applications that hook into the niri_ipc rust crate would also need to be updated for that version. In the end I think it does make more sense to pass this along any custom ipcs niri exposes, but since I also use this daily and make use of noctalia ( and also don't want to modify noctalias own dependencies to match ), I went ahead and just made a special endpoint to grab hidden. |
Had to revert this as it caused a regression. I also updated against latest niri main so might still be worth the build. |
…regression for moving to workspace ) This reverts commit 8c972b8.


Hidden Workspaces
Justification
this thread proposes several implementations of a niri-scratchpad system. These implementations tend to rely on a declared
stashworkspace. Currently these workspaces are visible and treated by the compositor like any other workspace. My proposol is to introduce a flag to workspaces through the Workspace { hide true } option that effectively hides that workspace in the overview, drawn windows inside the overview, and socket.In addition, there are workflows that will benefit from being able to dynamically "hide" a workspace in this fashion, consider a workflow in which you have a workspace you dedicate to social media, or any category that you have no reason to navigate to or see information from, you could add windows to that workspace, "hide" it, and then bring it back later when you actually need to use it in the same session. ( #2997 (comment) )
Implementation
Here is what I did:
hiddenoption to workspace configToggleWorkspaceVisibilityto bindshiddenandoriginal_idx fieldsJustification for Implementation
My goal is to try and touch as little of the workspace logic as possible, with minimal additions to the code. Having a nullable idx, for example would require substantial changes to everything that works with workspaces to handle variants.
Update in workspace logic.
niri msg workspaces-with-hidden). I have omitted hidden workspaces from the event stream, but keep them in the state forworkspaces-with-hidden.AllConfig add
Other
If anything I would just like to use this to start a dialogue about how we could go about implementing hidden workspaces. I believe there are many different ways to go about a scratchpad implementation, and it's understandable that it will take some time. In the meantime I think having more flexibility for how the user regards workspaces can only be a good thing and allow third party implementations to act as a temporary stopgap.