Search code examples
python-3.xgtk3gnome

Python and GTK+3: widget for choosing a keyboard shortcut


I'm looking for a way to add a shortcut chooser widget on a dialog with Python and GTK+3.

I tried to search through all available widgets and don't seem to find any out-of-the-box solution. What would be my best call in that respect? Should I use a GtkEntry and intercept a key press?

Even though it seems like a pretty common use case, I failed to find any working example of that.


Solution

  • I have implemented this myself using a separate dialog. There's a regular button displaying the current assignment, which, when clicked, opens a KeyboardShortcutDialog implemented as follows:

    class KeyboardShortcutDialog(Gtk.Dialog):
        """Dialog that allows to grab a keyboard shortcut."""
    
        def __init__(self, parent):
            Gtk.Dialog.__init__(self, _("Keyboard shortcut"), parent, 0)
            self.shortcut = None
            self.set_border_width(32)
    
            # Add a label
            label = Gtk.Label(xalign=0.5, yalign=0.5)
            label.set_markup(
                _('Press the desired key combination, <b>Backspace</b> to remove any shortcut, or <b>Esc</b> to cancel.'))
            self.get_content_area().pack_start(label, True, True, 0)
            self.connect('key-press-event', self.on_key_press)
            self.show_all()
    
        def on_key_press(self, widget, event: Gdk.EventKey):
            """Signal handler: key pressed."""
            keyval = event.get_keyval()[1]
            name = Gdk.keyval_name(keyval)
    
            # For some reason event.is_modifier == 0 here, even for modifier keys, so we need to resort to checking by name
            if name not in [
                    'Shift_L', 'Shift_R', 'Control_L', 'Control_R', 'Meta_L', 'Meta_R', 'Alt_L', 'Alt_R', 'Super_L',
                    'Super_R', 'Hyper_L', 'Hyper_R']:
                logging.debug('Key pressed: state=%s, keyval=%d', event.state, keyval)
                self.shortcut = (
                    keyval,
                    event.state &
                    (Gdk.ModifierType.META_MASK | Gdk.ModifierType.SUPER_MASK | Gdk.ModifierType.HYPER_MASK |
                     Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK))
                self.response(Gtk.ResponseType.ACCEPT)
            return True
    
        def run(self):
            """Show the dialog and block until it's closed.
            :return: tuple (keyval, state) of the key captured or None if the dialog has been closed."""
            super().run()
            return self.shortcut
    

    The dialog's run() method returns a tuple specifying the pressed key combination.