Skip to content

Conversation

@Atan-D-RP4
Copy link

@Atan-D-RP4 Atan-D-RP4 commented Jan 13, 2026

Adds a new ipc action set-cursor-zoom that takes a floating point value and applies a zoom transform to the entire compositor.
Tracking issue #1024

@Atan-D-RP4
Copy link
Author

Atan-D-RP4 commented Jan 15, 2026

Also, I'm unsure which of the relocated variants are completely neccessary for this in the niri_render_elements! macro.

@Atan-D-RP4 Atan-D-RP4 mentioned this pull request Jan 15, 2026
@miku4k
Copy link
Contributor

miku4k commented Jan 15, 2026

Thank you so much for doing this!

Notes from testing:

  • Zoom interacts with the overview as I expected (though it is slightly weird to be zoomed in and out at once)
  • The background and background layer-shell surfaces (but not other layers) don't get zoomed in
  • Built-in UI elements don't get zoomed in either
  • It gets pretty blurry. I think other desktops (Windows, Kwin, Hyprland) use some sort of specialized algorithm to get sharp edges (not nearest neighbor, but I think that's fine too), which makes things much easier to read. Looking at the implementation, this could be a side effect of RescaleRenderElement

For usability, actions to zoom in, zoom out, and reset zoom would be good, an IPC request to query the current zoom factor too, maybe. I can do those later in a follow-up PR if you want.

@Atan-D-RP4
Copy link
Author

Thanks for the feedback @miku4k!

Notes from testing:

* Zoom interacts with the overview as I expected (though it is slightly weird to be zoomed in and out at once)

Yeah, I really didn't expect to be able to just get it work so simply with the overview. Niri's render pipeline seems insanely well architected! On the flip side, it's also so... obscure? complicated? or something. Figuring out what the relevant parts were took a while. Then I just gave up and looked at what overview was doing, did some copy paste and it worked!!??

* The background and background layer-shell surfaces (but not other layers) don't get zoomed in

I use DMS with everything thrown into the overlay layer so it worked out-of-the-box for me. Will try to see if I can get background layer-shell surfaces to co-operate.

* Built-in UI elements don't get zoomed in either

Yeah that was a deliberate choice. Since how you interact with them doesn't change based on screen magnification right? I am open to feedback on changing that though. Personally though, quite happy with the feel of it for now.

* It gets pretty blurry. I think other desktops (Windows, Kwin, Hyprland) use some sort of specialized algorithm to get sharp edges (not nearest neighbor, but I think that's fine too), which makes things much easier to read. Looking at the implementation, this could be a side effect of `RescaleRenderElement`

Will actually look into that. I thought that would just be a limitation of zoom straight up. I didn't other DE's did anything to sharpen or improve resolution of magnified render elements.

For usability, actions to zoom in, zoom out, and reset zoom would be good, an IPC request to query the current zoom factor too, maybe. I can do those later in a follow-up PR if you want.

I didn't want to change IPC too much, since I was torn on making it a simple Action variant as it is, or making it a Request variant. Decided to make it work first then worry about interface stuff with maintainer feedback.
Considering your request now, and some other scripting stuff I want to work on too, it seems best to add it as a variant to the request enum. Will do that.

@Atan-D-RP4
Copy link
Author

Atan-D-RP4 commented Jan 15, 2026

To note, in it's current implementation this probably applies the same zoom/magnification across all outputs/monitors.
I don't have a multi-monitor setup to test that though. I'm still pretty confident about this though since the zoom state is in global state instead of being part of output state
I was reminded why I chose to deal with IPC stuff later. This should probably be applied on a per output basis.
Also, I'm blind! How did it take me so long to notice wallpapers don't zoom?!

@zgibberish
Copy link
Contributor

I saw the PR and tested it right away, it's a great feature, thank you so much for working on it, I have some feedback + things I would like to see in the final version, hope this can be useful

  • Add decrease/increase ipc actions, not just absolute zoom factor (makes setting up keybinds a lot easier, and I won't have to use a script to adjust zoom relatively)
  • Animated zoom (that preferably doesn't block zoom adjustment actions, otherwise it would feel slow)
  • Can we have an option to make the zoomed view not actively follow the cursor, but gets pushed around when the mouse cursor moves near/touches the edge? The current method of panning is not uncommon to see, but I find it a bit difficult to interact with UIs in the edges/corners of my screen while also reading them.

@ayosec
Copy link

ayosec commented Jan 15, 2026

  • It gets pretty blurry. I think other desktops (Windows, Kwin, Hyprland) use some sort of specialized algorithm to get sharp edges (not nearest neighbor, but I think that's fine too)

I don't know about other desktops, but I have seen some image programs using both linear/nearest algorithms, depending on the zoom factor. Something like:

  • If zoom factor is < 200%, use linear.
  • If zoom factor is ≥ 200%, use nearest neighbor.

In my experience this is what works better most of the times.

Ideally, the threshold should be configurable.

@miku4k
Copy link
Contributor

miku4k commented Jan 15, 2026

@Atan-D-RP4

I use DMS with everything thrown into the overlay layer so it worked out-of-the-box for me. Will try to see if I can get background layer-shell surfaces to co-operate.

Slight correction to my previous statement, I forgot what layer my clock widget is on, the bottom layer is affected too. I guess they're separate because one's above and one's below windows.

Yeah that was a deliberate choice. Since how you interact with them doesn't change based on screen magnification right? I am open to feedback on changing that though. Personally though, quite happy with the feel of it for now.

I suppose it makes some sense for the screenshot UI, and the MRU, but zoom is primarily an accessibility feature, and someone who needs zoom likely won't be able to read the small text in those UIs. I think zooming the screenshot UI is kinda problematic implementation-wise, as far as I know, it screenshots whats finally rendered, so the selection would be inaccurate.

I didn't other DE's did anything to sharpen or improve resolution of magnified render elements.

I just researched a bit, and StackOverflow says Windows seems to use a pixel art scaling algorithm based on xbr. I might have just hallucinated Hyprland using a special algorithm, because I couldn't actually find any evidence of that, I'll admit it's been a while. I'm more certain about Kwin because it's part of a full desktop environment. Also saying that, you could check what cosmic-comp does.

@zgibberish

  • Animated zoom (that preferably doesn't block zoom adjustment actions, otherwise it would feel slow)

+1, I'm thinking like something linear, 0.1 seconds.

  • Can we have an option to make the zoomed view not actively follow the cursor, but gets pushed around when the mouse cursor moves near/touches the edge?

+1, It would require keeping the center position independent of the cursor; the calculations currently directly use the cursor position. Still sounds straightforward I think.

@ayosec

I think that's overcomplicating it a bit.

@bertbesser
Copy link

This is awesome! 🚀🚀🚀

Let me ask, b/c I could not see exactly from the recording over in this thread and also not from the comments above:

  • can I click/type/interact while zoomed in (at least I saw animation/changes going on while zoomed in, i.e. no "screenshot zoom")
  • with the current "pointer follow" (not the "edge push" indicated above), can i move the entire space of the screen/monitor, or will i have to zoom out and in again to reach "far-away" parts of the screen (say from top left quadrant to bottom right quadrant)

Thanks!

@zgibberish
Copy link
Contributor

  • can I click/type/interact while zoomed in (at least I saw animation/changes going on while zoomed in, i.e. no "screenshot zoom")

@bertbesser Yes, you can interact while zoomed in

@Atan-D-RP4
Copy link
Author

Atan-D-RP4 commented Jan 15, 2026

Thank you all for the amazing response and feedback! I'll be bothering you all for more.

I just pushed a patch to address some of it.

But also, has anyone tested it on multiple monitors yet?

Slight correction to my previous statement, I forgot what layer my clock widget is on, the bottom layer is affected too. I guess they're separate because one's above and one's below windows.

Both background and bottom layer should work now. Do tell if anything else is not working in that vein.

I suppose it makes some sense for the screenshot UI, and the MRU, but zoom is primarily an accessibility feature, and someone who needs zoom likely won't be able to read the small text in those UIs. I think zooming the screenshot UI is kinda problematic implementation-wise, as far as I know, it screenshots whats finally rendered, so the selection would be inaccurate.

Ah yes, I didn't fully consider that. Yeah, I've read the discussion over on cosmic about this.
The latest patch does address this. If you want, I can push another one for testing out MRU and Screenshot UI's so testers can get a feel for it. Or you can do so yourself now, by just adding the Relocated* variants at the last niri_render_elements! macro, and adding the appropriate OutputRenderElements variant to process_zoom!.

I just researched a bit, and StackOverflow says Windows seems to use a pixel art scaling algorithm based on xbr. I might have just hallucinated Hyprland using a special algorithm, because I couldn't actually find any evidence of that, I'll admit it's been a while. I'm more certain about Kwin because it's part of a full desktop environment. Also saying that, you could check what cosmic-comp does.

Will do. I was actually moved into working on this because I wanted Hyprland's cursor:zoom_factor's effect in Niri! The script I used in the showcase I posted on #1024 was something I built for Hyprland. So when you mentioned it using a sharpening effect really tripped me up, since I'd never noticed anything of the like there.

Can you link the stack overflow page and any other references I can use for this?

@zgibberish

  • Animated zoom (that preferably doesn't block zoom adjustment actions, otherwise it would feel slow)

+1, I'm thinking like something linear, 0.1 seconds.

I've was actually looking into that before I decided to checkpoint and make the PR for getting feedback and approval. It's in the works.

  • Can we have an option to make the zoomed view not actively follow the cursor, but gets pushed around when the mouse cursor moves near/touches the edge?

+1, It would require keeping the center position independent of the cursor; the calculations currently directly use the cursor position. Still sounds straightforward I think.

Actually this would need some refactoring on my part, since right now I don't actually store any state other than the zoom factor. This would require having an independent zoom_center variable that would be passed into apply_cursor_zoom instead of the cursor_pos.

Of course I would need to do it anyway, for better IPC and supporting other features in the future, but I don't want to commit to that yet before I get some feedback on multi-monitor behavior.

@ayosec

I think that's overcomplicating it a bit.

Yeah having two algorithms just for differing zoom_values seems a bit much.

@zgibberish
Copy link
Contributor

Also I just had an idea, would be great if this uses logarithmic scaling, so zooming steps feel smoother.

@Atan-D-RP4
Copy link
Author

Also I just had an idea, would be great if this uses logarithmic scaling, so zooming steps feel smoother.

I feel like we should leave that up to user scripting, and only provide the setter, getter, increment and decrement options.
Also, would you find any value in being able to attach and detach the zoom center to the cursor position?

@zgibberish
Copy link
Contributor

Also I just had an idea, would be great if this uses logarithmic scaling, so zooming steps feel smoother.

I feel like we should leave that up to user scripting, and only provide the setter, getter, increment and decrement options.
Also, would you find any value in being able to attach and detach the zoom center to the cursor position?

  • Ah true, I'm fine with implementing it with user scripts.
  • If you mean toggling between attached/detached zoom center on the fly, then no, I don't think I would ever need that. Would be very useful if it supports the detached center view though, that was what i meant in the previous comments 👍

@garfunkel
Copy link

Wow, this is excellent, I'll give it a go today. I had started on my own implementation of this but you're far further along than I was.

Can we have the "mouse follows cursor" / "mouse pushes cursor when at edge" be switchable? I much prefer the former, but some prefer the latter.

@garfunkel
Copy link

Just gave it a sneaky test - works rather well, very smooth performance wise. A little blurry (as mentioned). I like how it handles when the mouse approaches the edge of the screen, where the cursor is allowed to drift away from the center of the screen to compensate, functioning in a similar way to Gnome and Kwin does, which I think is a good way to go.

The interaction with the overview is a little weird. Gnome 3 for the longest time had the exact same issue with their overview/app dashboard. The handling there may need some more thought.

Unfortunately I don't have a second monitor to test multi-monitor use.

@MichaelGame-Dev
Copy link

I have literally been holding off on Niri due to missing a feature like this.
My apologies but I'm a bit unsure how to use this pull request directly in arch. I"m guessing I need to compile from source with this pull request branch as what I clone in?

I have multiple monitors including a 32:9 ultrawide, I'd be glad to take a look at this.

@garfunkel
Copy link

I have literally been holding off on Niri due to missing a feature like this. My apologies but I'm a bit unsure how to use this pull request directly in arch. I"m guessing I need to compile from source with this pull request branch as what I clone in?

I have multiple monitors including a 32:9 ultrawide, I'd be glad to take a look at this.

Yes, clone this fork and pull the "feat/cursor-zoom" branch. Then compile using:
cargo build --release

After which, the target/release directory will contain the niri binary. At that point I just replaced the normal binary on my machine with the new one, and ran it from a TTY with niri-session.

Once logged in, you can then use IPC commands to change the zoom factor, eg:
niri msg action set-cursor-zoom 2.0

@MichaelGame-Dev
Copy link

I have literally been holding off on Niri due to missing a feature like this. My apologies but I'm a bit unsure how to use this pull request directly in arch. I"m guessing I need to compile from source with this pull request branch as what I clone in?
I have multiple monitors including a 32:9 ultrawide, I'd be glad to take a look at this.

Yes, clone this fork and pull the "feat/cursor-zoom" branch. Then compile using: cargo build --release

After which, the target/release directory will contain the niri binary. At that point I just replaced the normal binary on my machine with the new one, and ran it from a TTY with niri-session.

Once logged in, you can then use IPC commands to change the zoom factor, eg: niri msg action set-cursor-zoom 2.0

Thank you. I'll try to put this PR through it's paces tonight/tomorrow and see what I can break.

@zgibberish
Copy link
Contributor

I've been testing the latest build for a bit, the background layer zoom works now, but I also noticed that the background doesn't move when switching between workspaces, to be honest I like it this way more 😆, but I don't think it is intended behavior.

Uploading 2026-01-16 16-33-17 niri mag pr weird background issue.mp4…

@Atan-D-RP4
Copy link
Author

Atan-D-RP4 commented Jan 16, 2026

I've been testing the latest build for a bit, the background layer zoom works now, but I also noticed that the background doesn't move when switching between workspaces, to be honest I like it this way more 😆, but I don't think it is intended behavior.

I tested that right now, and wow it's weird. Should I do something about that? Or would people prefer it that way...?

Also general question: Do we want the sharpened scaling with nearest neighbor or linear scaling to be configurable? Or just have whichever looks best stick with only that?

@Axlefublr
Copy link
Contributor

I prefer it personally, but it is extra side effects / behavior on top of a feature. pr baggage, basically. so better to fix if it's viable to do so

@my4ng
Copy link
Contributor

my4ng commented Jan 16, 2026

But also, has anyone tested it on multiple monitors yet?

Works as expected on two monitor side-by-side setup. One small issue is that when screenshotting, the current output is frozen but the other output is not, and moves with the zoomed in cursor.

Some weird behaviours with overview (1.25x):

Screenshot from 2026-01-16 21-36-14

Screenshot taken from the left monitor, with transparent(?) bar on the far right.

I think the easiest solution is not to apply any zoom on overview.

I feel like we should leave that up to user scripting, and only provide the setter, getter, increment and decrement options.
Also, would you find any value in being able to attach and detach the zoom center to the cursor position?

I think it makes sense to only have getter and setter functions, with zoom-level and is_detached fields. Any update (inc/dec) should be user script logic only.

Minor code suggestions:

  • preallocate zoomed_elements with with_capacity(elements.len())
  • use into_iter() instead of drain(..)

@Axlefublr
Copy link
Contributor

agreed! I think increment/decrement is in the territory of “too nice”. just get and set is sufficient.

@Atan-D-RP4
Copy link
Author

Atan-D-RP4 commented Jan 16, 2026

Thanks @my4ng! I don't really know what 'as expected' means here, so would you mind clarifying?

  • I only have one global zoom_factor field on the Niri struct which is modified by the ipc command.
  • Does calling set-cursor-zoom in one monitor not change zoom in another monitor (assuming it's not a wide multi-monitor setup)?
  • Does it play well with the wide multi-monitor setup?

The weird behavior with overview is something @zgibberish already mentioned. I pushed a patch for that. See if that fixes it. Thanks for the code suggestion!

I just check the IPC again, and I have to agree with you and @Axlefublr. Niri usually never provides any incr/decr actions only getters/setters.

@garfunkel
Copy link

Yeah I agree, a "user-land" program should be deployed that can handle stuff like scrolling with the mouse wheel and applying whatever increment factor is desired etc. Niri should just provide a robust solution for the actual magnification.

@zgibberish
Copy link
Contributor

Hi again, I'm running on the latest commit, the background sticking when switching workspaces behavior is still here

2026-01-16.19-19-52.niri.mag.pr.video.2.mp4

@my4ng
Copy link
Contributor

my4ng commented Jan 16, 2026

I only have one global zoom_factor field on the Niri struct which is modified by the ipc command.
Does calling set-cursor-zoom in one monitor not change zoom in another monitor (assuming it's not a wide multi-monitor setup)?
Does it play well with the wide multi-monitor setup?

Yes so when the zoom is set, both monitors are zoomed in, centred around the cursor position at the moment of the IPC action. For example, if the cursor was at the top left corner of the left monitor when a zoom level of 2.0 is set, assuming equal output size, then the right monitor will display what was the top right quarter of the left monitor. But also the zoom is partially clipped: the left monitor is clamped to never show the content of what was in the right monitor, but the right can show what was on the left monitor.

IMG_9088.mov

Personally, I am not sure if this cross-monitor zoom is the desired behaviour, as niri has quite a strong sense of isolation between each monitor, e.g. you cannot have a floating window spanning two monitors. It doesn't make much sense from an accessibility point of view either.

@garfunkel
Copy link

garfunkel commented Jan 19, 2026

Just tested the latest revision again, with it now set up as a shortcut. I think logarithmic zooming would definitely be very useful. Once you zoom in by more than 3x or so, a change of 0.2 becomes very minimal and so it's unnaturally 'slow' to adjust in/out once zoomed in. Logarithmic would feel much more natural. Either that, or perhaps allow deltas of a percentage (+/- 10% for example).

Fantastic work though, the nearest neighbour implementation works great, and it already lacks a few bugs that Gnome's implementation has always (and continues to have).

I'll leave the IPC discussion for others for now as I'm quite to Niri.

@zgibberish
Copy link
Contributor

zgibberish commented Jan 19, 2026

I was trolling here a bit with that binding.

GAH i love it that was pretty funny

P.S: This is the hundredth comment on this PR! Isn't that... too much?

yeah.. I this kinda turned into a full on discussion rather than the average PR thread, which can make it harder for future participants to follow, I think this was partly because people started to notice flaws and discuss about optimal designs more, since we had a working implementation to try now.

I think logarithmic zooming would definitely be very useful.

@garfunkel this can be done with user scripts, I made this nushell script for logarithmic zooming, you can try it

https://gist.github.com/zgibberish/4a19e0829ac17b9e89fa9344ef143e86

Note

I haven't updated it to use the latest IPC impl tho, it should work on 14ae109

@Axlefublr
Copy link
Contributor

Big agree with @miku4k. Being required to call niri msg through shell, instead of just using some sort of set-zoom action in your bindings, like you can with most other things, is a pretty clear “smell” to me.
If I remember correctly, you yourself mentioned that the per-monitor approach may get axed, if yalter deems it unfitting niri.
Calling the niri ipc from a mapping instead of the action directly, I can't see not being axed. Usually niri is nicer to us, in that regard.
This ↓

niri msg action set-zoom-factor <change> <output?>
niri msg action get-zoom-factor <change> <output?>

indeed would be wonderful. I'm very partial (maybe even whole!) to this api.

As for the overall toggle-zoom feature. I remember interpreting it as an example, rather than an actual desire, by the person who suggested it. And even regardless, it seems too niche, while also being easily implementable in userscript land. I believe it should be removed.
Hopefully, blammoing animations onto the set-zoom-factor action will be similarly simple? Where it doesn't become a detriment to remove toggle-zoom.

Isn't that... too much?

Yeah I can understand if it feels a bit exhausting! We've even overtaken the infamous blur pr at this point. But I think this just goes to show that this is a complicated enough feature that it warrants so much discussion. Our united goal, after all, is to make niri the best it can be :D
I heavily appreciate all the work you've done. Despite my criticisms, I am in awe at this feature finally existing, and am deeply grateful to you. It's one thing to make a personal patch with the feature; it takes many times more effort to convert it into a proper pr, with all the bikeshedding that ultimately needs to happen.

@Atan-D-RP4
Copy link
Author

Atan-D-RP4 commented Jan 19, 2026

@miku4k Thank you for the kinds words.
The aforementioned bikeshedding has really not been the difficult part here. You all have been amazing with feedback, testing and feature ideas!

Getting the edge-pushing to work though, was real head-scratcher, since I'm not really well-versed in graphics math.
That and the rendering itself to figure out where and how to apply the zoom transform, while the transform itself ended up being fairly simple. Just add the right variants to a macro, construct them and then hand over to renderer. I'm actually in bit of awe of how Smithay (I assume) abstracted the render pipeline and how Niri uses it.

About the IPC actions, I'll add back those two actions then since it's preferred. Also get rid of the toggle-zoom action in favor of just letting niri msg output <name> zoom perform the toggle action. I'll leave it in for now, but removing it in the future should still be trivial.

Looking back on my notes, the animations really only need defined start and end points, so integration with that would only work for the toggle, i.e, significant changes to the zoom factor. Like overview and window movements. Using them for large amounts of continous changes like with pinch updates (again, my script on the tracking issue) would be... not great. I don't think so at least.

I'm working on locking the zoomed viewport right now.

P.S: I created #3285 for futher discussion of this. We can bikeshed further over there!
This will let the any further comments here be for maintainer review.

@Atan-D-RP4 Atan-D-RP4 force-pushed the feat/cursor-zoom branch 3 times, most recently from 2234551 to 5def8d7 Compare January 20, 2026 19:53
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.

10 participants