diff --git a/lib/solaar/ui/__init__.py b/lib/solaar/ui/__init__.py
index 290c5b8f80..8b18416673 100644
--- a/lib/solaar/ui/__init__.py
+++ b/lib/solaar/ui/__init__.py
@@ -17,6 +17,7 @@
 
 import logging
 
+from enum import Enum
 from typing import Callable
 
 import gi
@@ -48,6 +49,12 @@
 APP_ID = "io.github.pwr_solaar.solaar"
 
 
+class GtkSignal(Enum):
+    ACTIVATE = "activate"
+    COMMAND_LINE = "command-line"
+    SHUTDOWN = "shutdown"
+
+
 def _startup(app, startup_hook, use_tray, show_window):
     if logger.isEnabledFor(logging.DEBUG):
         logger.debug("startup registered=%s, remote=%s", app.get_is_registered(), app.get_is_remote())
@@ -108,9 +115,9 @@ def run_loop(
         lambda app, startup_hook: _startup(app, startup_hook, use_tray, show_window),
         startup_hook,
     )
-    application.connect("command-line", _command_line)
-    application.connect("activate", _activate)
-    application.connect("shutdown", _shutdown, shutdown_hook)
+    application.connect(GtkSignal.COMMAND_LINE.value, _command_line)
+    application.connect(GtkSignal.ACTIVATE.value, _activate)
+    application.connect(GtkSignal.SHUTDOWN.value, _shutdown, shutdown_hook)
 
     application.register()
     if application.get_is_remote():
diff --git a/lib/solaar/ui/about/view.py b/lib/solaar/ui/about/view.py
index 6670ac4625..74dc0611aa 100644
--- a/lib/solaar/ui/about/view.py
+++ b/lib/solaar/ui/about/view.py
@@ -13,8 +13,7 @@
 ## You should have received a copy of the GNU General Public License along
 ## with this program; if not, write to the Free Software Foundation, Inc.,
 ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-
+from enum import Enum
 from typing import List
 from typing import Tuple
 from typing import Union
@@ -24,6 +23,10 @@
 from solaar import NAME
 
 
+class GtkSignal(Enum):
+    RESPONSE = "response"
+
+
 class AboutView:
     def __init__(self) -> None:
         self.view: Union[Gtk.AboutDialog, None] = None
@@ -34,7 +37,7 @@ def init_ui(self) -> None:
         self.view.set_icon_name(NAME.lower())
         self.view.set_license_type(Gtk.License.GPL_2_0)
 
-        self.view.connect("response", lambda x, y: self.handle_close(x))
+        self.view.connect(GtkSignal.RESPONSE.value, lambda x, y: self.handle_close(x))
 
     def update_version_info(self, version: str) -> None:
         self.view.set_version(version)
diff --git a/lib/solaar/ui/action.py b/lib/solaar/ui/action.py
index 0015f55371..58f6dc1a4b 100644
--- a/lib/solaar/ui/action.py
+++ b/lib/solaar/ui/action.py
@@ -14,6 +14,7 @@
 ## You should have received a copy of the GNU General Public License along
 ## with this program; if not, write to the Free Software Foundation, Inc.,
 ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+from enum import Enum
 
 from gi.repository import Gdk
 from gi.repository import Gtk
@@ -24,6 +25,10 @@
 from . import pair_window
 
 
+class GtkSignal(Enum):
+    ACTIVATE = "activate"
+
+
 def make_image_menu_item(label, icon_name, function, *args):
     box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6)
     label = Gtk.Label(label=label)
@@ -33,7 +38,7 @@ def make_image_menu_item(label, icon_name, function, *args):
     menu_item = Gtk.MenuItem()
     menu_item.add(box)
     menu_item.show_all()
-    menu_item.connect("activate", function, *args)
+    menu_item.connect(GtkSignal.ACTIVATE.value, function, *args)
     menu_item.label = label
     menu_item.icon = icon
     return menu_item
@@ -45,7 +50,7 @@ def make(name, label, function, stock_id=None, *args):
     if stock_id is not None:
         action.set_stock_id(stock_id)
     if function:
-        action.connect("activate", function, *args)
+        action.connect(GtkSignal.ACTIVATE.value, function, *args)
     return action
 
 
@@ -54,7 +59,7 @@ def make_toggle(name, label, function, stock_id=None, *args):
     action.set_icon_name(name)
     if stock_id is not None:
         action.set_stock_id(stock_id)
-    action.connect("activate", function, *args)
+    action.connect(GtkSignal.ACTIVATE.value, function, *args)
     return action
 
 
diff --git a/lib/solaar/ui/config_panel.py b/lib/solaar/ui/config_panel.py
index d4a88f874f..b52ae10cff 100644
--- a/lib/solaar/ui/config_panel.py
+++ b/lib/solaar/ui/config_panel.py
@@ -18,6 +18,7 @@
 import logging
 import traceback
 
+from enum import Enum
 from threading import Timer
 
 import gi
@@ -38,6 +39,16 @@
 logger = logging.getLogger(__name__)
 
 
+class GtkSignal(Enum):
+    ACTIVATE = "activate"
+    CHANGED = "changed"
+    CLICKED = "clicked"
+    MATCH_SELECTED = "match_selected"
+    NOTIFY_ACTIVE = "notify::active"
+    TOGGLED = "toggled"
+    VALUE_CHANGED = "value-changed"
+
+
 def _read_async(setting, force_read, sbox, device_is_online, sensitive):
     def _do_read(s, force, sb, online, sensitive):
         try:
@@ -116,7 +127,7 @@ class ToggleControl(Gtk.Switch, Control):
     def __init__(self, sbox, delegate=None):
         super().__init__(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER)
         self.init(sbox, delegate)
-        self.connect("notify::active", self.changed)
+        self.connect(GtkSignal.NOTIFY_ACTIVE.value, self.changed)
 
     def set_value(self, value):
         if value is not None:
@@ -135,7 +146,7 @@ def __init__(self, sbox, delegate=None):
         self.set_round_digits(0)
         self.set_digits(0)
         self.set_increments(1, 5)
-        self.connect("value-changed", self.changed)
+        self.connect(GtkSignal.VALUE_CHANGED.value, self.changed)
 
     def get_value(self):
         return int(super().get_value())
@@ -167,7 +178,7 @@ def __init__(self, sbox, delegate=None, choices=None):
         self.choices = choices if choices is not None else sbox.setting.choices
         for entry in self.choices:
             self.append(str(int(entry)), str(entry))
-        self.connect("changed", self.changed)
+        self.connect(GtkSignal.CHANGED.value, self.changed)
 
     def get_value(self):
         return int(self.get_active_id()) if self.get_active_id() is not None else None
@@ -205,9 +216,9 @@ def norm(s):
         completion.set_match_func(lambda completion, key, it: norm(key) in norm(completion.get_model()[it][1]))
         completion.set_text_column(1)
         self.set_completion(completion)
-        self.connect("changed", self.changed)
-        self.connect("activate", self.activate)
-        completion.connect("match_selected", self.select)
+        self.connect(GtkSignal.CHANGED.value, self.changed)
+        self.connect(GtkSignal.ACTIVATE.value, self.activate)
+        completion.connect(GtkSignal.MATCH_SELECTED.value, self.select)
 
     def get_value(self):
         choice = self.get_choice()
@@ -253,7 +264,7 @@ def __init__(self, sbox, delegate=None):
         self.valueBox = _create_choice_control(sbox.setting, choices=self.value_choices, delegate=self)
         self.pack_start(self.keyBox, False, False, 0)
         self.pack_end(self.valueBox, False, False, 0)
-        self.keyBox.connect("changed", self.map_value_notify_key)
+        self.keyBox.connect(GtkSignal.CHANGED.value, self.map_value_notify_key)
 
     def get_value(self):
         key_choice = int(self.keyBox.get_active_id())
@@ -301,7 +312,7 @@ def __init__(self, sbox, change, button_label="...", delegate=None):
         self._showing = True
         self.setup(sbox.setting)  # set up the data and boxes for the sub-controls
         btn = Gtk.Button(label=button_label)
-        btn.connect("clicked", self.toggle_display)
+        btn.connect(GtkSignal.CLICKED.value, self.toggle_display)
         self._button = btn
         hbox = Gtk.HBox(homogeneous=False, spacing=6)
         hbox.pack_end(change, False, False, 0)
@@ -349,7 +360,7 @@ def setup(self, setting):
             h.set_tooltip_text(lbl_tooltip or " ")
             control = Gtk.Switch()
             control._setting_key = int(k)
-            control.connect("notify::active", self.toggle_notify)
+            control.connect(GtkSignal.NOTIFY_ACTIVE.value, self.toggle_notify)
             h.pack_start(lbl, False, False, 0)
             h.pack_end(control, False, False, 0)
             lbl.set_margin_start(30)
@@ -426,7 +437,7 @@ def setup(self, setting):
                     h.pack_end(control, False, False, 0)
                 else:
                     raise NotImplementedError
-                control.connect("value-changed", self.changed, item, sub_item)
+                control.connect(GtkSignal.VALUE_CHANGED.value, self.changed, item, sub_item)
                 item_lb.add(h)
                 h._setting_sub_item = sub_item
                 h._label, h._control = sub_item_lbl, control
@@ -487,7 +498,7 @@ def setup(self, setting):
             control = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, validator.min_value, validator.max_value, 1)
             control.set_round_digits(0)
             control.set_digits(0)
-            control.connect("value-changed", self.changed, validator.keys[item])
+            control.connect(GtkSignal.VALUE_CHANGED.value, self.changed, validator.keys[item])
             h.pack_start(lbl, False, False, 0)
             h.pack_end(control, True, True, 0)
             h._setting_item = validator.keys[item]
@@ -548,7 +559,7 @@ def __init__(self, sbox, delegate=None):
                 for entry in item["choices"]:
                     item_box.append(str(int(entry)), str(entry))
                 item_box.set_active(0)
-                item_box.connect("changed", self.changed)
+                item_box.connect(GtkSignal.CHANGED.value, self.changed)
                 self.pack_start(item_box, False, False, 0)
             elif item["kind"] == settings.Kind.RANGE:
                 item_box = Scale()
@@ -556,7 +567,7 @@ def __init__(self, sbox, delegate=None):
                 item_box.set_round_digits(0)
                 item_box.set_digits(0)
                 item_box.set_increments(1, 5)
-                item_box.connect("value-changed", self.changed)
+                item_box.connect(GtkSignal.VALUE_CHANGED.value, self.changed)
                 self.pack_start(item_box, True, True, 0)
             item_box.set_visible(False)
             self._items[str(item["name"])] = (item_lblbox, item_box)
@@ -664,7 +675,7 @@ def _create_sbox(s, _device):
     change.set_relief(Gtk.ReliefStyle.NONE)
     change.add(change_icon)
     change.set_sensitive(True)
-    change.connect("clicked", _change_click, sbox)
+    change.connect(GtkSignal.CLICKED.value, _change_click, sbox)
 
     if s.kind == settings.Kind.TOGGLE:
         control = ToggleControl(sbox)
diff --git a/lib/solaar/ui/diversion_rules.py b/lib/solaar/ui/diversion_rules.py
index 788eab241b..2e3b90de92 100644
--- a/lib/solaar/ui/diversion_rules.py
+++ b/lib/solaar/ui/diversion_rules.py
@@ -56,6 +56,18 @@
 _rule_component_clipboard = None
 
 
+class GtkSignal(Enum):
+    ACTIVATE = "activate"
+    BUTTON_RELEASE_EVENT = "button-release-event"
+    CHANGED = "changed"
+    CLICKED = "clicked"
+    DELETE_EVENT = "delete-event"
+    KEY_PRESS_EVENT = "key-press-event"
+    NOTIFY_ACTIVE = "notify::active"
+    TOGGLED = "toggled"
+    VALUE_CHANGED = "value_changed"
+
+
 def create_all_settings(all_settings: list[Setting]) -> dict[str, list[Setting]]:
     settings = {}
     for s in sorted(all_settings, key=lambda setting: setting.label):
@@ -333,7 +345,7 @@ def menu_do_flatten(self, _mitem, m, it):
 
     def _menu_flatten(self, m, it):
         menu_flatten = Gtk.MenuItem(_("Flatten"))
-        menu_flatten.connect("activate", self.menu_do_flatten, m, it)
+        menu_flatten.connect(GtkSignal.ACTIVATE.value, self.menu_do_flatten, m, it)
         menu_flatten.show()
         return menu_flatten
 
@@ -416,7 +428,7 @@ def build(spec):
                 label, feature, *args = spec
                 item = Gtk.MenuItem(label)
                 args = [a.copy() if isinstance(a, list) else a for a in args]
-                item.connect("activate", self._menu_do_insert_new, m, it, feature, *args, below)
+                item.connect(GtkSignal.ACTIVATE.value, self._menu_do_insert_new, m, it, feature, *args, below)
                 return item
             else:
                 return None
@@ -427,7 +439,7 @@ def build(spec):
 
     def _menu_create_rule(self, m, it, below=False) -> Gtk.MenuItem:
         menu_create_rule = Gtk.MenuItem(_("Insert new rule"))
-        menu_create_rule.connect("activate", self._menu_do_insert_new, m, it, diversion.Rule, [], below)
+        menu_create_rule.connect(GtkSignal.ACTIVATE.value, self._menu_do_insert_new, m, it, diversion.Rule, [], below)
         menu_create_rule.show()
         return menu_create_rule
 
@@ -447,7 +459,7 @@ def menu_do_delete(self, _mitem, m, it):
 
     def _menu_delete(self, m, it) -> Gtk.MenuItem:
         menu_delete = Gtk.MenuItem(_("Delete"))
-        menu_delete.connect("activate", self.menu_do_delete, m, it)
+        menu_delete.connect(GtkSignal.ACTIVATE.value, self.menu_do_delete, m, it)
         menu_delete.show()
         return menu_delete
 
@@ -469,7 +481,7 @@ def menu_do_negate(self, _mitem, m, it):
 
     def _menu_negate(self, m, it) -> Gtk.MenuItem:
         menu_negate = Gtk.MenuItem(_("Negate"))
-        menu_negate.connect("activate", self.menu_do_negate, m, it)
+        menu_negate.connect(GtkSignal.ACTIVATE.value, self.menu_do_negate, m, it)
         menu_negate.show()
         return menu_negate
 
@@ -497,9 +509,9 @@ def _menu_wrap(self, m, it) -> Gtk.MenuItem:
         menu_sub_rule = Gtk.MenuItem(_("Sub-rule"))
         menu_and = Gtk.MenuItem(_("And"))
         menu_or = Gtk.MenuItem(_("Or"))
-        menu_sub_rule.connect("activate", self.menu_do_wrap, m, it, diversion.Rule)
-        menu_and.connect("activate", self.menu_do_wrap, m, it, diversion.And)
-        menu_or.connect("activate", self.menu_do_wrap, m, it, diversion.Or)
+        menu_sub_rule.connect(GtkSignal.ACTIVATE.value, self.menu_do_wrap, m, it, diversion.Rule)
+        menu_and.connect(GtkSignal.ACTIVATE.value, self.menu_do_wrap, m, it, diversion.And)
+        menu_or.connect(GtkSignal.ACTIVATE.value, self.menu_do_wrap, m, it, diversion.Or)
         submenu_wrap.append(menu_sub_rule)
         submenu_wrap.append(menu_and)
         submenu_wrap.append(menu_or)
@@ -523,7 +535,7 @@ def menu_do_cut(self, _mitem, m, it):
 
     def _menu_cut(self, m, it):
         menu_cut = Gtk.MenuItem(_("Cut"))
-        menu_cut.connect("activate", self.menu_do_cut, m, it)
+        menu_cut.connect(GtkSignal.ACTIVATE.value, self.menu_do_cut, m, it)
         menu_cut.show()
         return menu_cut
 
@@ -539,13 +551,13 @@ def menu_do_paste(self, _mitem, m, it, below=False):
 
     def _menu_paste(self, m, it, below=False):
         menu_paste = Gtk.MenuItem(_("Paste"))
-        menu_paste.connect("activate", self.menu_do_paste, m, it, below)
+        menu_paste.connect(GtkSignal.ACTIVATE.value, self.menu_do_paste, m, it, below)
         menu_paste.show()
         return menu_paste
 
     def _menu_copy(self, m, it):
         menu_copy = Gtk.MenuItem(_("Copy"))
-        menu_copy.connect("activate", self.menu_do_copy, m, it)
+        menu_copy.connect(GtkSignal.ACTIVATE.value, self.menu_do_copy, m, it)
         menu_copy.show()
         return menu_copy
 
@@ -554,7 +566,7 @@ class DiversionDialog:
     def __init__(self, action_menu):
         window = Gtk.Window()
         window.set_title(_("Solaar Rule Editor"))
-        window.connect("delete-event", self._closing)
+        window.connect(GtkSignal.DELETE_EVENT.value, self._closing)
         vbox = Gtk.VBox()
 
         self.top_panel, self.view = self._create_top_panel()
@@ -597,7 +609,7 @@ def __init__(self, action_menu):
 
         window.show_all()
 
-        window.connect("delete-event", lambda w, e: w.hide_on_delete() or True)
+        window.connect(GtkSignal.DELETE_EVENT.value, lambda w, e: w.hide_on_delete() or True)
 
         style = window.get_style_context()
         style.add_class("solaar")
@@ -645,9 +657,9 @@ def _create_top_panel(self):
         view.set_enable_tree_lines(True)
         view.set_reorderable(False)
 
-        view.connect("key-press-event", self._event_key_pressed)
-        view.connect("button-release-event", self._event_button_released)
-        view.get_selection().connect("changed", self._selection_changed)
+        view.connect(GtkSignal.KEY_PRESS_EVENT.value, self._event_key_pressed)
+        view.connect(GtkSignal.BUTTON_RELEASE_EVENT.value, self._event_button_released)
+        view.get_selection().connect(GtkSignal.CHANGED.value, self._selection_changed)
         sw.add(view)
         sw.set_size_request(0, 300)  # don't ask for so much height
 
@@ -662,8 +674,8 @@ def _create_top_panel(self):
         self.discard_btn.set_always_show_image(True)
         self.discard_btn.set_sensitive(False)
         self.discard_btn.set_valign(Gtk.Align.CENTER)
-        self.save_btn.connect("clicked", lambda *_args: self._save_yaml_file())
-        self.discard_btn.connect("clicked", lambda *_args: self._reload_yaml_file())
+        self.save_btn.connect(GtkSignal.CLICKED.value, lambda *_args: self._save_yaml_file())
+        self.discard_btn.connect(GtkSignal.CLICKED.value, lambda *_args: self._reload_yaml_file())
         button_box.pack_start(self.save_btn, False, False, 0)
         button_box.pack_start(self.discard_btn, False, False, 0)
         button_box.set_halign(Gtk.Align.CENTER)
@@ -869,7 +881,7 @@ def replace_with(value):
                 if name != self.get_child().get_text():
                     self.get_child().set_text(name)
 
-        self.connect("changed", lambda *a: replace_with(self.get_value(invalid_as_str=False)))
+        self.connect(GtkSignal.CHANGED.value, lambda *a: replace_with(self.get_value(invalid_as_str=False)))
 
         self.set_id_column(0)
         if self.get_has_entry():
@@ -1158,7 +1170,7 @@ def create_widgets(self):
         self.field.set_halign(Gtk.Align.CENTER)
         self.field.set_valign(Gtk.Align.CENTER)
         self.field.set_hexpand(True)
-        self.field.connect("value-changed", self._on_update)
+        self.field.connect(GtkSignal.VALUE_CHANGED.value, self._on_update)
         self.widgets[self.field] = (0, 1, 1, 1)
 
     def show(self, component, editable):
@@ -1226,15 +1238,15 @@ def __init__(self, on_change, *args, accept_toggle=True, **kwargs):
             ],
             case_insensitive=True,
         )
-        self.toggle_widget.connect("changed", self._changed)
+        self.toggle_widget.connect(GtkSignal.CHANGED.value, self._changed)
         self.range_widget = Gtk.SpinButton.new_with_range(0, 0xFFFF, 1)
-        self.range_widget.connect("value-changed", self._changed)
+        self.range_widget.connect(GtkSignal.VALUE_CHANGED.value, self._changed)
         self.choice_widget = SmartComboBox(
             [], completion=True, has_entry=True, case_insensitive=True, replace_with_default_name=True
         )
-        self.choice_widget.connect("changed", self._changed)
+        self.choice_widget.connect(GtkSignal.CHANGED.value, self._changed)
         self.sub_key_widget = SmartComboBox([])
-        self.sub_key_widget.connect("changed", self._changed)
+        self.sub_key_widget.connect(GtkSignal.CHANGED.value, self._changed)
         self.unsupported_label = Gtk.Label(label=_("Unsupported setting"))
         self.pack_start(self.sub_key_widget, False, False, 0)
         self.sub_key_widget.set_hexpand(False)
@@ -1385,7 +1397,7 @@ def create_widgets(self):
         self.device_field.set_value("")
         self.device_field.set_valign(Gtk.Align.CENTER)
         self.device_field.set_size_request(400, 0)
-        self.device_field.connect("changed", self._on_update)
+        self.device_field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.device_field] = (1, 1, 1, 1)
 
     def update_devices(self):
@@ -1436,7 +1448,7 @@ def create_widgets(self):
         self.widgets[self.label] = (0, 0, 1, 1)
         self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True)
         self.field.set_size_request(600, 0)
-        self.field.connect("changed", self._on_update)
+        self.field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.field] = (0, 1, 1, 1)
 
     def show(self, component, editable):
@@ -1483,8 +1495,8 @@ def create_widgets(self):
         self.device_field.set_valign(Gtk.Align.CENTER)
         self.device_field.set_size_request(400, 0)
         self.device_field.set_margin_top(m)
-        self.device_field.connect("changed", self._changed_device)
-        self.device_field.connect("changed", self._on_update)
+        self.device_field.connect(GtkSignal.CHANGED.value, self._changed_device)
+        self.device_field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.device_field] = (1, 1, 1, 1)
 
         lbl = Gtk.Label(
@@ -1497,8 +1509,8 @@ def create_widgets(self):
         self.widgets[lbl] = (0, 2, 1, 1)
         self.setting_field = SmartComboBox([(s[0].name, s[0].label) for s in ALL_SETTINGS.values()])
         self.setting_field.set_valign(Gtk.Align.CENTER)
-        self.setting_field.connect("changed", self._changed_setting)
-        self.setting_field.connect("changed", self._on_update)
+        self.setting_field.connect(GtkSignal.CHANGED.value, self._changed_setting)
+        self.setting_field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.setting_field] = (1, 2, 1, 1)
 
         self.value_lbl = Gtk.Label(
@@ -1521,8 +1533,8 @@ def create_widgets(self):
         self.key_field.set_margin_top(m)
         self.key_field.hide()
         self.key_field.set_valign(Gtk.Align.CENTER)
-        self.key_field.connect("changed", self._changed_key)
-        self.key_field.connect("changed", self._on_update)
+        self.key_field.connect(GtkSignal.CHANGED.value, self._changed_key)
+        self.key_field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.key_field] = (3, 1, 1, 1)
 
     @classmethod
diff --git a/lib/solaar/ui/pair_window.py b/lib/solaar/ui/pair_window.py
index 84b6353993..9e9eb27316 100644
--- a/lib/solaar/ui/pair_window.py
+++ b/lib/solaar/ui/pair_window.py
@@ -17,6 +17,8 @@
 
 import logging
 
+from enum import Enum
+
 from gi.repository import GLib
 from gi.repository import Gtk
 from logitech_receiver import hidpp10_constants
@@ -32,6 +34,11 @@
 _STATUS_CHECK = 500  # milliseconds
 
 
+class GtkSignal(Enum):
+    CANCEL = "cancel"
+    CLOSE = "close"
+
+
 def create(receiver):
     receiver.reset_pairing()  # clear out any information on previous pairing
     title = _("%(receiver_name)s: pair new device") % {"receiver_name": receiver.name}
@@ -207,8 +214,8 @@ def _create_assistant(receiver, ok, finish, title, text):
         assistant.set_page_complete(page_intro, True)
     else:
         page_intro = _create_failure_page(assistant, receiver.pairing.error)
-    assistant.connect("cancel", finish, receiver)
-    assistant.connect("close", finish, receiver)
+    assistant.connect(GtkSignal.CANCEL.value, finish, receiver)
+    assistant.connect(GtkSignal.CLOSE.value, finish, receiver)
     return assistant
 
 
diff --git a/lib/solaar/ui/rule_actions.py b/lib/solaar/ui/rule_actions.py
index 347947b7a1..0a738a6787 100644
--- a/lib/solaar/ui/rule_actions.py
+++ b/lib/solaar/ui/rule_actions.py
@@ -13,7 +13,7 @@
 ## You should have received a copy of the GNU General Public License along
 ## with this program; if not, write to the Free Software Foundation, Inc.,
 ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
+from enum import Enum
 from shlex import quote as shlex_quote
 
 from gi.repository import Gtk
@@ -29,6 +29,12 @@
 from solaar.ui.rule_base import RuleComponentUI
 
 
+class GtkSignal(Enum):
+    CHANGED = "changed"
+    CLICKED = "clicked"
+    TOGGLED = "toggled"
+
+
 class ActionUI(RuleComponentUI):
     CLASS = diversion.Action
 
@@ -51,21 +57,21 @@ def create_widgets(self):
         self.widgets[self.label] = (0, 0, 5, 1)
         self.del_btns = []
         self.add_btn = Gtk.Button(label=_("Add key"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True)
-        self.add_btn.connect("clicked", self._clicked_add)
+        self.add_btn.connect(GtkSignal.CLICKED.value, self._clicked_add)
         self.widgets[self.add_btn] = (1, 1, 1, 1)
         self.action_clicked_radio = Gtk.RadioButton.new_with_label_from_widget(None, _("Click"))
-        self.action_clicked_radio.connect("toggled", self._on_update, CLICK)
+        self.action_clicked_radio.connect(GtkSignal.TOGGLED.value, self._on_update, CLICK)
         self.widgets[self.action_clicked_radio] = (0, 3, 1, 1)
         self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_clicked_radio, _("Depress"))
-        self.action_pressed_radio.connect("toggled", self._on_update, DEPRESS)
+        self.action_pressed_radio.connect(GtkSignal.TOGGLED.value, self._on_update, DEPRESS)
         self.widgets[self.action_pressed_radio] = (1, 3, 1, 1)
         self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _("Release"))
-        self.action_released_radio.connect("toggled", self._on_update, RELEASE)
+        self.action_released_radio.connect(GtkSignal.TOGGLED.value, self._on_update, RELEASE)
         self.widgets[self.action_released_radio] = (2, 3, 1, 1)
 
     def _create_field(self):
         field_entry = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True)
-        field_entry.connect("changed", self._on_update)
+        field_entry.connect(GtkSignal.CHANGED.value, self._on_update)
         self.fields.append(field_entry)
         self.widgets[field_entry] = (len(self.fields) - 1, 1, 1, 1)
         return field_entry
@@ -74,7 +80,7 @@ def _create_del_btn(self):
         btn = Gtk.Button(label=_("Delete"), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True)
         self.del_btns.append(btn)
         self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1)
-        btn.connect("clicked", self._clicked_del, len(self.del_btns) - 1)
+        btn.connect(GtkSignal.CLICKED.value, self._clicked_del, len(self.del_btns) - 1)
         return btn
 
     def _clicked_add(self, _btn):
@@ -153,8 +159,8 @@ def create_widgets(self):
         for f in [self.field_x, self.field_y]:
             f.set_halign(Gtk.Align.CENTER)
             f.set_valign(Gtk.Align.START)
-        self.field_x.connect("changed", self._on_update)
-        self.field_y.connect("changed", self._on_update)
+        self.field_x.connect(GtkSignal.CHANGED.value, self._on_update)
+        self.field_y.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.label_x] = (0, 1, 1, 1)
         self.widgets[self.field_x] = (1, 1, 1, 1)
         self.widgets[self.label_y] = (2, 1, 1, 1)
@@ -210,9 +216,9 @@ def create_widgets(self):
         for f in [self.field_b, self.field_c]:
             f.set_halign(Gtk.Align.CENTER)
             f.set_valign(Gtk.Align.START)
-        self.field_b.connect("changed", self._on_update)
-        self.field_c.connect("changed", self._on_update)
-        self.field_d.connect("changed", self._on_update)
+        self.field_b.connect(GtkSignal.CHANGED.value, self._on_update)
+        self.field_c.connect(GtkSignal.CHANGED.value, self._on_update)
+        self.field_d.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.label_b] = (0, 1, 1, 1)
         self.widgets[self.field_b] = (1, 1, 1, 1)
         self.widgets[self.label_c] = (2, 1, 1, 1)
@@ -257,13 +263,13 @@ def create_widgets(self):
         self.fields = []
         self.add_btn = Gtk.Button(label=_("Add argument"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True)
         self.del_btns = []
-        self.add_btn.connect("clicked", self._clicked_add)
+        self.add_btn.connect(GtkSignal.CLICKED.value, self._clicked_add)
         self.widgets[self.add_btn] = (1, 1, 1, 1)
 
     def _create_field(self):
         field_entry = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True)
         field_entry.set_size_request(150, 0)
-        field_entry.connect("changed", self._on_update)
+        field_entry.connect(GtkSignal.CHANGED.value, self._on_update)
         self.fields.append(field_entry)
         self.widgets[field_entry] = (len(self.fields) - 1, 1, 1, 1)
         return field_entry
@@ -273,7 +279,7 @@ def _create_del_btn(self):
         btn.set_size_request(150, 0)
         self.del_btns.append(btn)
         self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1)
-        btn.connect("clicked", self._clicked_del, len(self.del_btns) - 1)
+        btn.connect(GtkSignal.CLICKED.value, self._clicked_del, len(self.del_btns) - 1)
         return btn
 
     def _clicked_add(self, *_args):
diff --git a/lib/solaar/ui/rule_conditions.py b/lib/solaar/ui/rule_conditions.py
index 82fb8429e5..5ef5133514 100644
--- a/lib/solaar/ui/rule_conditions.py
+++ b/lib/solaar/ui/rule_conditions.py
@@ -14,6 +14,7 @@
 ## with this program; if not, write to the Free Software Foundation, Inc.,
 ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 from dataclasses import dataclass
+from enum import Enum
 
 from gi.repository import Gtk
 from logitech_receiver import diversion
@@ -26,6 +27,14 @@
 from solaar.ui.rule_base import RuleComponentUI
 
 
+class GtkSignal(Enum):
+    CHANGED = "changed"
+    CLICKED = "clicked"
+    NOTIFY_ACTIVE = "notify::active"
+    TOGGLED = "toggled"
+    VALUE_CHANGED = "value-changed"
+
+
 class ConditionUI(RuleComponentUI):
     CLASS = diversion.Condition
 
@@ -44,7 +53,7 @@ def create_widgets(self):
         self.widgets[self.label] = (0, 0, 1, 1)
         self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True)
         self.field.set_size_request(600, 0)
-        self.field.connect("changed", self._on_update)
+        self.field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.field] = (0, 1, 1, 1)
 
     def show(self, component, editable=True):
@@ -74,7 +83,7 @@ def create_widgets(self):
         self.widgets[self.label] = (0, 0, 1, 1)
         self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True)
         self.field.set_size_request(600, 0)
-        self.field.connect("changed", self._on_update)
+        self.field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.field] = (0, 1, 1, 1)
 
     def show(self, component, editable=True):
@@ -119,7 +128,7 @@ def create_widgets(self):
             self.field.append(feature, feature)
         self.field.set_valign(Gtk.Align.CENTER)
         self.field.set_size_request(600, 0)
-        self.field.connect("changed", self._on_update)
+        self.field.connect(GtkSignal.CHANGED.value, self._on_update)
         all_features = [str(f) for f in SupportedFeature]
         CompletionEntry.add_completion_to_entry(self.field.get_child(), all_features)
         self.widgets[self.field] = (0, 1, 1, 1)
@@ -163,7 +172,7 @@ def create_widgets(self):
         self.field.set_halign(Gtk.Align.CENTER)
         self.field.set_valign(Gtk.Align.CENTER)
         self.field.set_hexpand(True)
-        self.field.connect("changed", self._on_update)
+        self.field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.field] = (0, 1, 1, 1)
 
     def show(self, component, editable=True):
@@ -200,7 +209,7 @@ def create_widgets(self):
             self.widgets[switch] = (i, 2, 1, 1)
             self.labels[m] = label
             self.switches[m] = switch
-            switch.connect("notify::active", self._on_update)
+            switch.connect(GtkSignal.NOTIFY_ACTIVE.value, self._on_update)
 
     def show(self, component, editable=True):
         super().show(component, editable)
@@ -236,13 +245,13 @@ def create_widgets(self):
         self.widgets[self.label] = (0, 0, 5, 1)
         self.key_field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True)
         self.key_field.set_size_request(600, 0)
-        self.key_field.connect("changed", self._on_update)
+        self.key_field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.key_field] = (0, 1, 2, 1)
         self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(None, _("Key down"))
-        self.action_pressed_radio.connect("toggled", self._on_update, Key.DOWN)
+        self.action_pressed_radio.connect(GtkSignal.TOGGLED.value, self._on_update, Key.DOWN)
         self.widgets[self.action_pressed_radio] = (2, 1, 1, 1)
         self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _("Key up"))
-        self.action_released_radio.connect("toggled", self._on_update, Key.UP)
+        self.action_released_radio.connect(GtkSignal.TOGGLED.value, self._on_update, Key.UP)
         self.widgets[self.action_released_radio] = (3, 1, 1, 1)
 
     def show(self, component, editable=True):
@@ -288,7 +297,7 @@ def create_widgets(self):
         self.widgets[self.label] = (0, 0, 5, 1)
         self.key_field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True)
         self.key_field.set_size_request(600, 0)
-        self.key_field.connect("changed", self._on_update)
+        self.key_field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.key_field] = (0, 1, 1, 1)
 
     def show(self, component, editable=True):
@@ -335,12 +344,12 @@ def create_widgets(self):
         self.test.set_hexpand(False)
         self.test.set_size_request(300, 0)
         CompletionEntry.add_completion_to_entry(self.test.get_child(), diversion.TESTS)
-        self.test.connect("changed", self._on_update)
+        self.test.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.test] = (1, 1, 1, 1)
 
         self.parameter = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True)
         self.parameter.set_size_request(150, 0)
-        self.parameter.connect("changed", self._on_update)
+        self.parameter.connect(GtkSignal.CHANGED.value, self._on_update)
         self.widgets[self.parameter] = (3, 1, 1, 1)
 
     def show(self, component, editable=True):
@@ -444,14 +453,14 @@ def create_widgets(self):
                     field = Gtk.SpinButton.new_with_range(element.min, element.max, 1)
                     field.set_value(0)
                     field.set_size_request(150, 0)
-                    field.connect("value-changed", self._on_update)
+                    field.connect(GtkSignal.VALUE_CHANGED.value, self._on_update)
                     label = Gtk.Label(label=element.label, margin_top=20)
                     self.fields[element.id] = field
                     self.field_labels[element.id] = label
                     self.widgets[label] = (col, 1, 1, 1)
                     self.widgets[field] = (col, 2, 1, 1)
                     col += 1 if col != mode_col - 1 else 2
-        self.mode_field.connect("changed", lambda cb: (self._on_update(), self._only_mode(cb.get_active_id())))
+        self.mode_field.connect(GtkSignal.CHANGED.value, lambda cb: (self._on_update(), self._only_mode(cb.get_active_id())))
         self.mode_field.set_active_id("range")
 
     def show(self, component, editable=True):
@@ -529,7 +538,7 @@ def create_widgets(self):
         self.widgets[self.label] = (0, 0, 5, 1)
         self.del_btns = []
         self.add_btn = Gtk.Button(label=_("Add movement"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True)
-        self.add_btn.connect("clicked", self._clicked_add)
+        self.add_btn.connect(GtkSignal.CLICKED.value, self._clicked_add)
         self.widgets[self.add_btn] = (1, 1, 1, 1)
 
     def _create_field(self):
@@ -537,7 +546,7 @@ def _create_field(self):
         for g in self.MOUSE_GESTURE_NAMES:
             field.append(g, g)
         CompletionEntry.add_completion_to_entry(field.get_child(), self.MOVE_NAMES)
-        field.connect("changed", self._on_update)
+        field.connect(GtkSignal.CHANGED.value, self._on_update)
         self.fields.append(field)
         self.widgets[field] = (len(self.fields) - 1, 1, 1, 1)
         return field
@@ -546,7 +555,7 @@ def _create_del_btn(self):
         btn = Gtk.Button(label=_("Delete"), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True)
         self.del_btns.append(btn)
         self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1)
-        btn.connect("clicked", self._clicked_del, len(self.del_btns) - 1)
+        btn.connect(GtkSignal.CLICKED.value, self._clicked_del, len(self.del_btns) - 1)
         return btn
 
     def _clicked_add(self, _btn):
diff --git a/lib/solaar/ui/tray.py b/lib/solaar/ui/tray.py
index f89855c749..ef08a94c96 100644
--- a/lib/solaar/ui/tray.py
+++ b/lib/solaar/ui/tray.py
@@ -18,6 +18,7 @@
 import logging
 import os
 
+from enum import Enum
 from time import time
 
 import gi
@@ -42,6 +43,11 @@
 _MENU_ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR
 
 
+class GtkSignal(Enum):
+    ACTIVATE = "activate"
+    SCROLL_EVENT = "scroll-event"
+
+
 def _create_menu(quit_handler):
     # per-device menu entries will be generated as-needed
     menu = Gtk.Menu()
@@ -172,7 +178,7 @@ def _create(menu):
         # ind.set_label(NAME.lower(), NAME.lower())
 
         ind.set_menu(menu)
-        ind.connect("scroll-event", _scroll)
+        ind.connect(GtkSignal.SCROLL_EVENT.value, _scroll)
 
         return ind
 
@@ -214,8 +220,8 @@ def _create(menu):
         icon.set_name(NAME.lower())
         icon.set_title(NAME)
         icon.set_tooltip_text(NAME)
-        icon.connect("activate", window.toggle)
-        icon.connect("scroll-event", _scroll)
+        icon.connect(GtkSignal.ACTIVATE.value, window.toggle)
+        icon.connect(GtkSignal.SCROLL_EVENT.value, _scroll)
         icon.connect(
             "popup-menu",
             lambda icon, button, time: menu.popup(None, None, icon.position_menu, icon, button, time),
diff --git a/lib/solaar/ui/window.py b/lib/solaar/ui/window.py
index 594a529a6d..e26cb7af00 100644
--- a/lib/solaar/ui/window.py
+++ b/lib/solaar/ui/window.py
@@ -17,6 +17,7 @@
 
 import logging
 
+from enum import Enum
 from enum import IntEnum
 
 import gi
@@ -75,6 +76,12 @@ class Column(IntEnum):
 assert len(_COLUMN_TYPES) == len(Column)
 
 
+class GtkSignal(Enum):
+    CHANGED = "changed"
+    CLICKED = "clicked"
+    DELETE_EVENT = "delete-event"
+
+
 def _new_button(label, icon_name=None, icon_size=_NORMAL_BUTTON_ICON_SIZE, tooltip=None, toggle=False, clicked=None):
     b = Gtk.ToggleButton() if toggle else Gtk.Button()
     c = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
@@ -84,7 +91,7 @@ def _new_button(label, icon_name=None, icon_size=_NORMAL_BUTTON_ICON_SIZE, toolt
         c.pack_start(Gtk.Label(label=label), True, True, 0)
     b.add(c)
     if clicked is not None:
-        b.connect("clicked", clicked)
+        b.connect(GtkSignal.CLICKED.value, clicked)
     if tooltip:
         b.set_tooltip_text(tooltip)
     if not label and icon_size < _NORMAL_BUTTON_ICON_SIZE:
@@ -296,7 +303,7 @@ def _create_window_layout():
     assert _empty is not None
 
     assert _tree.get_selection().get_mode() == Gtk.SelectionMode.SINGLE
-    _tree.get_selection().connect("changed", _device_selected)
+    _tree.get_selection().connect(GtkSignal.CHANGED.value, _device_selected)
 
     tree_scroll = Gtk.ScrolledWindow()
     tree_scroll.add(_tree)
@@ -341,7 +348,7 @@ def _create(delete_action):
     window = Gtk.Window()
     window.set_title(NAME)
     window.set_role("status-window")
-    window.connect("delete-event", delete_action)
+    window.connect(GtkSignal.DELETE_EVENT.value, delete_action)
 
     vbox = _create_window_layout()
     window.add(vbox)