Is there a way to completely ignore or discard events?

Godot Version

Godot_v4.4.1-stable_win64

Question

As title says, I would like a way to completely ignore certain input events.

I am making my own input system because of certain necessities with my game, this requires my hardware input to be completely ignored after being first read and processed by at least one script, which will convert it to my virtual input system (which seems to be the hard part).

After that, the original hardware input need to be ignored even by control nodes, and only for my emulated mouse events to be registered at all.

I am aware of get_viewport().set_input_as_handled(), but as much as it claims to "[Stop] the input from propagating further down the SceneTree ", it simply doesn’t work as intended, the nodes further down will still receive the events, but they will be marked as handled, which doesn’t let them be picked up by _unhandled_event(event).

The only way I got even remotely close to the effect I wanted was with windows.
Windows do hog up events, and windows can pass events to each other, but I’d need a fully transparent window to sit on top of my game at all times, and that doesn’t sound like the best idea.

Thanks in advance.

What type of events are we talking about? Are you using actions defined in the InputMap? Because then you can use InputMap.erase_action() to get rid of the action and it will never trigger again.

Can you share some specific examples from your code how you currently handle the events that you want to later ignore?

Unfortunately I’m not working with InputMaps, but rather with InputEventMouse events directly.
How I’m currently set up is something like this:

I have a node right below the root that manages the emulated input like so

extends Node2D

var captured_events: Array[InputEventMouse]

func _ready():
    Input.use_accumulated_input = false

func _input(event):
    if event is InputEventMouse and event.device != -2:
        var new_event: InputEventMouse = event.duplicate()
        new_event.position = Vector2(100, 100)
        new_event.device = -2
        captured_events.append(new_event)
        get_viewport().set_input_as_handled()

func _process(delta):
    if !captured_events.is_empty():
        for event in captured_events:
            Input.parse_input_event(event)
        captured_events.clear()

Now, this example is not very useful, all it does is grab the mouse events, change their device and and simply move them to (100, 100), in my current project they would be moved to where a simulated cursor would be, but this should be enough for demonstration purposes.

new_event.device being set to -2 is simply to avoid infinite recursion.

This works just fine, if I place a button where the events are being moved to, I can click it remotely, and clicking on a button that’s not in the correct position will not respond to mouse inputs, so it seems like mouse inputs are now ignored anywhere but on (100, 100)

Problem arises with things like the PopupMenu, the code above will let the popup menu appear on right click on a LineEdit node, for example, but interacting with the menu itself will still have to be done with the system cursor, otherwise the menu will simply stay unresponsive.

I am aware that popup menus are different windows, and to mitigate that I tried this:

Inside the LineEdit

func _ready():
	get_menu().window_input.connect(parent._input) #parent holds the input logic above

This, at least, fixes the “fake mouse” not even hovering over the menu options, they will darken where the input script is sending events.

However, selecting an option will still be done with the mouse itself, matter of fact, the “fake mouse” will not even cause a on hover effect unless the real mouse is moving to begin with.

So I thought that maybe, somehow, completely disabling the real mouse input except for the main node that creates the simulated mouse events would help, since the real mouse events are still being sent and could be handled by any node that implements _input(event), which is undesirable since I don’t plan on using the real inputs for anything other than handling my virtual cursor, they just cause undesired effects and bugs.

Hope this was descriptive enough, I’m not too well versed in godot yet.

Interesting project.
I’ve never done anything like that before, but I dug around and the most promising seems to be variable Viewport.gui_disable_input which you can set to false in your main node, which should disable all the input completely on this viewport. Or you can disable input on a specific other viewport. This seems like what you’re looking for.

func _ready() -> void:
	get_viewport().gui_disable_input = true

If this doesn’t work for you, you can also consider using Input.should_ignore_device() method, but that would required to identify and remove specific devices.

And finally there is Node.set_process_input() which you can use to disable input on the Node, but I think it won’t propagate to the child nodes, so you’d need to do that on each node separately.

Let me know if any of these will work for you.

Those are amazing suggestions! Don’t know how they slipped when I searched for a solution.

Unfortunately, I’m still unable to make popup menus work, I’m starting to think I should just ignore them and work around them for my project, there might be a hard limitation on input between windows.

Here’s what I tried:

I added a popup menu in my scene, about where the emulated cursor would be.
On ready, I set up the menu like so:

func _ready():
	Input.use_accumulated_input = false
	_popup.window_input.connect(_input)
	_popup.id_pressed.connect(foo)
	_popup.set_process_input(false)
	_popup.gui_disable_input = true

foo() is a simple function that prints what has been pressed

func foo(id):
	if id == 0:
		print("first")
	else:
		print("second")

That’s pretty much it, other than some controls to show the popup, video attached to show behaviour


As shown, the first option is selected, but the one under the cursor will (almost ) always have priority.

I say “almost” because as shown later in the video, clicking fast enough will cause the first option to be clicked instead of the second one.

Also the behaviour is EXACTLY the same whether Node.set_process_input() is set to false, or Viewport.gui_disable_input is set to true, which is odd, in the main Viewport these do work in disabling the input either entirely or partly.

Thanks for the help, unfortunately it might just be that popup menus work differently and having a universal custom input system is a bit too ambitious.

Would you mind if I ask why do you use window_input signal if you disabled the input completely in that window? Wouldn’t that signal be always silent?
And do you use Viewport.push_input() method anywhere in your code?

That’s where my knowledge kinda stops, and I start plugging things randomly and hope something sticks.
I don’t fully know if it’s counter-productive, but I need window_input to be connected to my main input manager because otherwise my simulated input will not be transferred to the popup menu, the only input accepted will be the one sent via the mouse itself, it’s as if the window in the background stops processing input entirely.
This is just a way for me to hot-wire the input system to the window anyways.

Also you’re absolutely right about disabling input then using window_input being weird, though, It’s still odd how even with both Node.set_process_input(false) and Viewport.gui_disable_input = true, the popup still accepts mouse input, even with window_input disconnected.

One thing that does happen, is if I attach a script to the popup menu like so

extends PopupMenu

func _input(event):
	print("popup menu got input")

then yes, this script will not run it’s _input(), ever, but the mouse can still interact with the menu.

I didn’t use Viewport.push_input(), I used Input.parse_input_event() in my example.
I just tested both, they do the exact same thing in my test project, don’t know if there are some lower level nuances but they both have the same exact problems.

Side note, I have tried straight up moving the whole node that hosts the input system as a child to the popup menu, and that, too, yielded the exact same results.
It’s as if the popup menu straight up doesn’t care about events, and uses some display manager shenanigans to decide what the user pressed, yet if I press left click fast enough it will misfire and use the custom input events, as shown in the video.

How do you parse the events to the popup window then? With Input.parse_input_event() you can’t specify which viewport you’re feeding the events, so in your case I think the Viewport.push_input() should work better, as you can specify to feed it to the popup window.
You also should translate the event to the Viewport coordinate system, not sure if using in_local_coords = true will be enough in your case, maybe add some print statements to see what positions your events are when you move the mouse and click on stuff.
And I would disable the window_input signal completely, as it might recursively feed your events around. Seems to me like it’s not needed, but I don’t really understand what you use it for.