A quick update on this:
I haven’t managed to solve the core issue of intercepting all input, but for UMG I’ve found a workaround.
What I’m doing is that as soon as I want to intercept the user input, I create a new Widget (as a child of the viewport) which contains only a single “Safe Zone” box that covers the whole screen. Then, when the user clicks (or presses a key) the input is rerouted to the Controller/GameViewport and I can notify the widgets from code. When the widget receives the notification I remove the “Safe Zone” widget.
It’s an absolute hack, but it works.
I’m not marking this as an answer as it doesn’t really address the original question, and I’m still interested in doing it properly.