2025, Dec 24 07:00

Fix 'unknown signal' on Gtk.ApplicationWindow: handle pointer enter/leave in Gtk4 Python with EventControllerMotion

Learn how to track pointer hover in Gtk4 Python on Gtk.ApplicationWindow: use Gtk.EventControllerMotion to handle enter/leave and toggle UI state reliably.

Tracking pointer hover over a Gtk.ApplicationWindow in Gtk4 with Python can be confusing if you try to wire classic mouse-enter signals straight onto the window. The result is an unknown signal error instead of the expected state change. Below is a compact explanation of why that happens and a clean way to implement hover behavior using the Gtk4 API without guesswork.

The goal

Switch the UI state when the pointer enters or leaves the window area. For demonstration, a pair of buttons toggles visibility based on hover: one shows when the pointer is away, the other appears when it’s inside the window.

Broken approach: connecting mouse-enter signals directly to ApplicationWindow

The following minimal example demonstrates the error: it connects enter-notify-event and leave-notify-event to the window and fails at runtime with an “unknown signal name”.

#!/usr/bin/env python3
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk
class HoverApp(Gtk.Application):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.connect("activate", self.when_activated)
        self.main_win: "RootWindow"
    def when_activated(self, app):
        self.main_win = RootWindow(application=app)
        self.main_win.present()
class RootWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_default_size(200, 200)
        box_wrap = Gtk.Box()
        self.btn_out = Gtk.Button(label="mouse away", hexpand=True)
        self.btn_in = Gtk.Button(label="mouse here", hexpand=True, visible=False)
        box_wrap.append(self.btn_out)
        box_wrap.append(self.btn_in)
        self.set_child(box_wrap)
        # These lines cause: unknown signal name
        self.connect("enter-notify-event", self.handle_enter)
        self.connect("leave-notify-event", self.handle_leave)
    def handle_enter(self, widget) -> bool:
        self.btn_out.set_visible(False)
        self.btn_in.set_visible(True)
        return True
    def handle_leave(self, widget) -> bool:
        self.btn_out.set_visible(True)
        self.btn_in.set_visible(False)
        return True
app = HoverApp(application_id="com.example.hovertest")
app.run()

What actually happens

The window does not recognize enter-notify-event and leave-notify-event in this setup, so connecting to these names produces the “unknown signal name” error. Instead of attaching those signals to the window itself, Gtk4 expects you to use an input controller designed for pointer motion. That controller exposes the signals you need.

Fix: use Gtk.EventControllerMotion and connect to its signals

The correct approach is to instantiate Gtk.EventControllerMotion, connect to its enter and leave signals, and then add the controller to the window. The handlers must accept the controller instance and, for enter, the pointer coordinates.

event_ctl = Gtk.EventControllerMotion.new()
event_ctl.connect("enter", self.handle_enter)
event_ctl.connect("leave", self.handle_leave)
self.add_controller(event_ctl)

Here is a fully working version of the earlier example, using EventControllerMotion and updated handler signatures:

#!/usr/bin/env python3
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk
class HoverApp(Gtk.Application):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.connect("activate", self.when_activated)
        self.main_win: "RootWindow"
    def when_activated(self, app):
        self.main_win = RootWindow(application=app)
        self.main_win.present()
class RootWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_default_size(200, 200)
        box_wrap = Gtk.Box()
        self.btn_out = Gtk.Button(label="mouse away", hexpand=True)
        self.btn_in = Gtk.Button(label="mouse here", hexpand=True, visible=False)
        box_wrap.append(self.btn_out)
        box_wrap.append(self.btn_in)
        self.set_child(box_wrap)
        # Attach a motion controller to the window and connect to its signals
        motion_ctl = Gtk.EventControllerMotion.new()
        motion_ctl.connect("enter", self.handle_enter)
        motion_ctl.connect("leave", self.handle_leave)
        self.add_controller(motion_ctl)
    def handle_enter(self, controller, x, y) -> bool:
        self.btn_out.set_visible(False)
        self.btn_in.set_visible(True)
        return True
    def handle_leave(self, controller) -> bool:
        self.btn_out.set_visible(True)
        self.btn_in.set_visible(False)
        return True
app = HoverApp(application_id="com.example.hovertest")
app.run()

Why this detail matters

Using the controller’s enter and leave signals prevents the unknown signal error and gives you reliable pointer entry and exit notifications on the window. It also matches the Gtk4 Python API you’re working with and keeps your event handling wired to the right abstraction.

Takeaways

If you need hover behavior on a Gtk.ApplicationWindow in Gtk4 with Python, don’t connect enter-notify-event or leave-notify-event directly to the window. Create a Gtk.EventControllerMotion instance, connect to its enter and leave signals, add the controller to the window, and make sure your handlers accept the controller object and any parameters the signal provides. With that in place, toggling UI state on pointer entry and exit becomes straightforward and predictable.