Help setting custom keybindings and modifiers


On one of my devices, I would like to change my Meta modifier so that it uses keycode 68 instead of keysym Alt because for other reasons specific to the setup on that device, Alt and AltGr are not distinguished (yet they have different keycodes). I know Nyxt supports mapping keycodes, but I am new to it and to Lisp, meaning I’m right in the steepest part of the learning curve right now.

My understanding is I need a ~/.nyxt/init.lisp file for my custom settings. I looked at the example given in the modifier-translater (gtk-browser) slot and came up with this:

(defun my-translate-modifiers (modifier-state &optional event)
  "Replace Alt by keycode 64"
  (declare (ignore event))
  (let ((plist '(:mod1-mask "64")))
    (delete nil (mapcar (lambda (mod) (getf plist mod)) modifier-state))))

From there, three questions:

  1. Would that be the correct way to do it?
  2. Do I also need to explicitly describe other modifier, or can I just do the above and assume that Nyxt will use the default values for the other modifiers?
  3. What is the exact way to set individual keybindings for given command? Can I for instance merge all changes in a single code chunk for several keybindings, or would that be a different code block for each keybinding, e.g. copy the Lisp code found in describe-key (C-h k) followed by a keybinding, paste it in my init.lisp and edit just the keybinding line, for each custom change?

Thanks a lot for this browser!

After some tests I realized the above code disabled other modifiers, so that replies to question 2. I therefore added other modifiers in my init.lisp, but I still can’t use meta on that particular device:

(defun my-translate-modifiers (modifier-state &optional event)
  "Replace Alt by keycode 64"
  (declare (ignore event))
  (let ((plist '(:control-mask "control"
                 :mod1-mask "64"
                 :shift-mask "shift"
                 :super-mask "super"
                 :hyper-mask "hyper")))
    (delete nil (mapcar (lambda (mod) (getf plist mod)) modifier-state))))

(define-configuration browser
  ((modifier-translator #'my-translate-modifiers)))

I think I need to post more details about that peculiar device setup. This is a Debian LXC container running on a Sailfish phone, with a 64-key hardware keyboard. The container runs in an XWayland window, which brings some limitations as to what I can set up with setxkbmap or xmodmap (I believe xmodmap changes are simply ignored in XWayland), and some limitations with having a 64-key keyboard too. Long story short, I have to use mod1 as my i3wm modifier, which encompasses both my AltGr and my Alt keys, despite their different keycodes (108 and 64, respectively). This means I can’t use meta in Nyxt, unless I can find a way to distinguish AltGr and Alt by their keycodes.

So if I still cannot use meta in Nyxt with the above init.lisp, tt may suggest that either Nyxt doesn’t understand my keycode 64 and my config is wrong, or i3wm takes priority and captures mod1 (which somehow includes both keycodes 64 and 108) before Nyxt has a chance to capture keycode 64. The more I think about it, the more I believe I would need a way to tell i3wm to just use keycode 108 instead of mod1, but I don’t think i3wm supports keycodes.

Hello, thank you for the kind words! I will try my best to explain how we can get this functionality!

Firstly, the plist is a map of input -> output in the example. So in your case we want keycode 68 to produce a “meta” call. Therefore, the plist would have to look something like this:

(plist '(:control-mask "control"
                 68 "meta"
                 :shift-mask "shift"
                 :super-mask "super"
                 :hyper-mask "hyper"))

Unfortunately, the problem is that you will never receive a value of 68 within the modifier-translator and will not get the ability to pass a meta. As it is now set-up, the modifier-translators slot can only translate between modifiers, it lacks the ability to make ANY key a modifier. It receives a list of modifiers from GTK and translates them based on your specification. This is a limitation of our current design, luckily it is possible for us to fix it at some point in the future :slight_smile:

Here is where the modifier-translator slot is called in the codebase:

(modifiers (funcall (modifier-translator *browser*)
                             (key-event-modifiers event)

As you can see, the modifier-translator is only invoked with the modifiers of the event, meaning that if keycode 68 is not a modifier, then it will not be in the list of of (key-event-modifiers event). In order to overcome this we would need to send the complete key-event to the function, which would complicate the typical use case.

All of this said, it is still possible for you to use key code 68 as a modifier in Nyxt, it would involve you overriding the following method in renderer-gtk.lisp:

(define-ffi-method on-signal-key-press-event ((sender gtk-window) event)
  (let* ((keycode (gdk:gdk-event-key-hardware-keycode event))
         (keyval (gdk:gdk-event-key-keyval event))
         (keyval-name (gdk:gdk-keyval-name keyval))
         (character (gdk:gdk-keyval-to-unicode keyval))
         (printable-value (printable-p sender event))
         (key-string (or printable-value
                         (derive-key-string keyval-name character)))
         (modifiers (funcall (modifier-translator *browser*)
                             (key-event-modifiers event)
    (if modifiers
        (log:debug key-string keycode character keyval-name modifiers)
        (log:debug key-string keycode character keyval-name))
    (if key-string
          (alex:appendf (key-stack sender)
                        (list (keymap:make-key :code keycode
                                               :value key-string
                                               :modifiers modifiers
                                               :status :pressed)))
          (funcall (input-dispatcher sender) event (active-buffer sender) sender printable-value))
        ;; Do not forward modifier-only to renderer.

Complex, but doable :smiley:
Hopefully that provides some more background!



1 Like

Ah, I just noticed a silly mistake! I just realized that we do pass a optional event to the modifier translator, however, because it is &optional, it is not well enforced by the specification! I think we should make this a required argument, and then you could confidently bind any key as a modifier. @ambrevar what do you think of this idea?

Long time I have played with modifier translation, but from what I can remember, I think yes, that would work, and if I’m not mistaken “full customizability” is what I had in mind when I decided to pass the event parameter :wink:

Thank you for this detailed answer, it took me some time to reply because this seemed a bit overwhelming! My understanding from your latest messages is this cannot work right now and would require some changes on the Nyxt code, is that correct?

Sorry for my late reply, this fell under my radar :frowning: To clarify my answer: I’m not sure! I’d need to do some testing. I’ll come back to it when I’m done with the minibuffer overhaul and come back to polishing the renderer.