Consumed modifiers not handled correctly #376

Closed
opened 8 months ago by dnkl · 29 comments
dnkl commented 8 months ago
Owner

The input handling logic in foot currently does:

    xkb_mod_mask_t mods = xkb_state_serialize_mods(
        seat->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED);
    //xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods(seat->kbd.xkb_state, key);
    xkb_mod_mask_t consumed = 0x0;
    xkb_mod_mask_t significant = ctrl | alt | shift | meta;
    xkb_mod_mask_t effective_mods = mods & ~consumed & significant;

All modifier logic (matching against key bindings, and determining what key codes to send to the client application) then uses effective_mods.

As can be seen, we don't "consume" any modifiers. The most commonly consumed modifier is shift. I.e. if the user presses shift+a, the final key is upper case A, with no modifiers. I.e. shift has been consumed.

But this isn't happening in foot at the moment. What this means is if the user's keyboard layout requires her/him to hold shift to produce e.g. the 0 in ctrl+0, foot will not match that against the font-reset action.

It also means foot will send shift and A to the client application when the user presses shift+a. Or would have, if that had been a key combo that generated a key code with modifiers (like shift+F1).

I'm not quite sure why we're doing it this way; the code suggests we did consume modifiers before.

It may be related to handling key bindings like ctrl+shift+u. If we did consume modifiers, Control+U (in key bindings in foot.ini) would mean ctrl+shift+u, while Control+u would mean ctrl+u. And, Control+Shift+U would never trigger.

I'm pretty sure the key codes we're sending to the client application should be considered buggy; i.e. we should consume modifiers there.

But what about foot key bindings? Consuming modifiers would mean key bindings will be more confusing, and we'd also break backward compatibility.

Or should we force users to configure their own key bindings if they cannot produce the default key binding without pressing e.g. shift?

Or, third option, should we try to match key binding modifiers twice? Once without consuming modifiers, and then again with modifiers consumed?

@lechner

The input handling logic in foot currently does: ```c xkb_mod_mask_t mods = xkb_state_serialize_mods( seat->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED); //xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods(seat->kbd.xkb_state, key); xkb_mod_mask_t consumed = 0x0; xkb_mod_mask_t significant = ctrl | alt | shift | meta; xkb_mod_mask_t effective_mods = mods & ~consumed & significant; ``` All modifier logic (matching against key bindings, and determining what key codes to send to the client application) then uses `effective_mods`. As can be seen, we don't "consume" any modifiers. The most commonly consumed modifier is <kbd>shift</kbd>. I.e. if the user presses <kbd>shift</kbd>+<kbd>a</kbd>, the final key is upper case <kbd>A</kbd>, with **no** modifiers. I.e. <kbd>shift</kbd> has been consumed. But this isn't happening in foot at the moment. What this means is if the user's keyboard layout requires her/him to hold <kbd>shift</kbd> to produce e.g. the <kbd>0</kbd> in <kbd>ctrl</kbd>+<kbd>0</kbd>, foot will **not** match that against the `font-reset` action. It also means foot will send <kbd>shift</kbd> **and** <kbd>A</kbd> to the client application when the user presses <kbd>shift</kbd>+<kbd>a</kbd>. Or would have, if that had been a key combo that generated a key code with modifiers (like <kbd>shift</kbd>+<kbd>F1</kbd>). I'm not quite sure _why_ we're doing it this way; the code suggests we _did_ consume modifiers before. It _may_ be related to handling key bindings like <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>. If we did consume modifiers, `Control+U` (in key bindings in `foot.ini`) would mean <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>, while `Control+u` would mean <kbd>ctrl</kbd>+<kbd>u</kbd>. And, `Control+Shift+U` would never trigger. I'm pretty sure the key codes we're sending to the client application should be considered buggy; i.e. we **should** consume modifiers there. But what about foot key bindings? Consuming modifiers would mean key bindings will be more confusing, and we'd also break backward compatibility. Or should we force users to configure their own key bindings if they cannot produce the default key binding without pressing e.g. <kbd>shift</kbd>? Or, third option, should we try to match key binding modifiers twice? Once without consuming modifiers, and then again with modifiers consumed? @lechner
dnkl added the
bug
what do you think?
labels 8 months ago

Hi, isn't the issue located in the layer above foot—i.e. the program passing a capital A to foot should have consumed the Shift modifier if it was needed to produce the uppercase letter?

Hi, isn't the issue located in the layer *above* `foot`—i.e. the program passing a capital <kbd>A</kbd> to `foot` should have consumed the <kbd>Shift</kbd> modifier if it was needed to produce the uppercase letter?
Poster
Owner

No, foot talks Wayland directly to the compositor, which means there is no program "above" foot. The compositor passes more or less raw XKB events to foot, which must decode them by itself (like all other Wayland clients).

XKB is telling us which modifiers should be consumed. It's just that foot currently ignores that, for the reasons mentioned above.

No, foot talks Wayland directly to the compositor, which means there **is** no program "above" foot. The compositor passes more or less raw XKB events to foot, which must decode them by itself (like all other Wayland clients). XKB _is_ telling us which modifiers should be consumed. It's just that foot currently ignores that, for the reasons mentioned above.

Seems like sway does something similar, but they use raw key codes otherwise.

Seems like `sway` [does something similar](https://github.com/swaywm/sway/blob/master/sway/input/keyboard.c#L282-L303), but they use raw key codes otherwise.

Also, the discussion here may be helpful.

Also, [the discussion here](https://github.com/swaywm/sway/pull/3058) may be helpful.
Poster
Owner

FWIW, foot alreay tries to match bindings using both the key symbol and the raw keycode. Though there's no way to manually specify a raw keycode in foot.ini (what foot does, is take the key symbols in the foot.ini key bindings, and look for all keys that procudes the wanted symbol(s). This heuristic isn't perfect, as it ignores modifiers. But it isn't relevant in this case.

The problem is the modifier list - modifiers are, in XKB, something completely different from "regular" keys, and regardless of whether matching XKB key symbol, or raw keycode, we need to get shift off the modifier list when it has been consumed. Or alternatively, remove it from the modifier list in the key binding definition.

FWIW, foot alreay tries to match bindings using both the key _symbol_ and the raw keycode. Though there's no way to manually specify a raw keycode in `foot.ini` (what foot does, is take the key _symbols_ in the `foot.ini` key bindings, and look for all keys that procudes the wanted symbol(s). This heuristic isn't perfect, as it ignores modifiers. But it isn't relevant in this case. The problem is the modifier list - modifiers are, in XKB, something completely different from "regular" keys, and regardless of whether matching XKB key symbol, or raw keycode, we need to get <kbd>shift</kbd> off the modifier list when it has been consumed. Or alternatively, remove it from the modifier list in the key binding definition.

and the raw keycode.

What is the reason for doing that? (Over at sway the reason was compatibility with i3.)

> and the raw keycode. What is the reason for doing that? (Over at `sway` the reason was compatibility with `i3`.)
Poster
Owner

It makes the default bindings work with e.g. russian layouts.

It makes the default bindings work with e.g. russian layouts.
Poster
Owner

To expand on the above; if we only match symbols, then pressing ctrl+shift+u while in russian "mode" will generate the symbol "г" (I think!), which wont match the "u" in the key binding.

To expand on the above; if we only match symbols, then pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd> while in russian "mode" will generate the symbol "г" (I think!), which wont match the "u" in the key binding.

That seems to be peculiar to the Russian layout. Should they not specify the Cyrillic key symbol (or temporarily switch to a US-based layout via XKB when seeking to have the keys interpreted as such)?

That seems to be [peculiar to the Russian layout](https://github.com/swaywm/sway/pull/3058#issuecomment-457808630). Should they not specify the Cyrillic key *symbol* (or temporarily switch to a US-based layout via XKB when seeking to have the keys interpreted as such)?
Poster
Owner

I'm pretty sure they, and other non-latin users, expect the current behavior. You don't want to have to customize every program to work in both layouts - it should just work.

Both alternatives, adding the non-latin symmbol, or switching back and forth between layouts just to be able use a key binding, is a usability nightmare.

I'm pretty sure they, and other non-latin users, expect the current behavior. You don't want to have to customize every program to work in both layouts - it should just work. Both alternatives, adding the non-latin symmbol, or switching back and forth between layouts just to be able use a key binding, is a usability nightmare.
Poster
Owner

You don't want to have to customize every program to work in both layouts - it should just work.

That said, this auto-detection of key codes isn't compatible with consumed modifiers. With auto-detection of key codes enabled, Control-U (which should require shift) can be activated with just ctrl+u.

> You don't want to have to customize every program to work in both layouts - it should just work. That said, this auto-detection of key codes isn't compatible with consumed modifiers. With auto-detection of key codes enabled, `Control-U` (which **should** require <kbd>shift</kbd>) can be activated with just <kbd>ctrl</kbd>+<kbd>u</kbd>.
Poster
Owner

I guess we could go the Sway route and make key code lookup optional, ala --to-code. Not sure how we'd fit that into the key binding syntax in foot.ini though.

With that in place, we can start consuming modifiers.

Then, the remaining question is whether Control+Shift+U should be allowed or not. I don't see it causing any issues, so might be best to keep allowing it, for backward compatibility.

I guess we could go the Sway route and make key code lookup optional, ala `--to-code`. Not sure how we'd fit that into the key binding syntax in `foot.ini` though. With that in place, we can start consuming modifiers. Then, the remaining question is whether `Control+Shift+U` should be allowed or not. I don't see it causing any issues, so might be best to keep allowing it, for backward compatibility.

whether Control+Shift+U should be allowed

I'm so confused! Should that be Control+Shift+u instead (lowercase)?

> whether `Control+Shift+U` should be allowed I'm so confused! Should that be `Control+Shift+u` instead (lowercase)?
Poster
Owner

Doesn't matter. If we consume modifiers, none of those two will actually trigger, because shift will have been removed from the modifier list.

If we do allow Control+Shift+U, then Control+Shift+u will still not trigger, since we're getting a U from XKB, not u.

However, with the current auto-detection of raw key codes, Control+Shift+u actually does trigger, because we will "find" the raw key code for u (which is the same as U) and match against that, instead of the XKB symbol.

Confusing? Yes :)

Doesn't matter. If we consume modifiers, none of those two will actually trigger, because <kbd>shift</kbd> will have been removed from the modifier list. If we do allow `Control+Shift+U`, then `Control+Shift+u` will still not trigger, since we're getting a `U` from XKB, not `u`. However, with the current auto-detection of raw key codes, `Control+Shift+u` actually **does** trigger, because we will "find" the raw key code for `u` (which is the same as `U`) and match against that, instead of the XKB symbol. Confusing? Yes :)
Poster
Owner

What I think needs to happen in foot:

Don't auto-detect raw key codes.

Either add a global option to do this, or allow per-binding configuration.

Note that the per-binding variant means the same amount of work as manually adding the corresponding alternative-layout symbol for non-latin users. I.e. you either add --to-code for each binding, or you add Control+ж. Same amount of work for the user, but one variant requires extra code in foot while the other doesn't.

Note that removing raw key codes also means key bindings will be case sensitive. This is the correct thing to do, and is even documented in the foot.ini man page. However, we might break someones config if they incorrectly added a Control+Shift+u (lower case) binding.

Consume modifiers for key codes sent to application

This is a no-brainer. Key escapes with modifier codes sent to the client application should have "consumed" modifiers removed.

Consume modifiers for key bindings

Do not include consumed modifiers when matching key bindings. This means Control+U matches ctrl+shift+u, and Control-u matches ctrl+u.

Decide how to handle Control+Shift+U style bindings

If/when we remove consumed modifiers from the matching logic, bindings like Control+Shift+U will stop working; the binding will have shift in its list, but we will have removed shift from the set we're comparing with. Hence no match.

We can easily add code that handles this (by "adding back" the consumed modifiers to the set we're comparing with). I.e. the comparison logic would be something like:

if (binding.mods == (mods & ~consumed) || binding.mods == mods) {
    /* Binding matched */
}

I don't think this will cause any problems.

What I think needs to happen in foot: ## Don't auto-detect raw key codes. Either add a global option to do this, or allow per-binding configuration. Note that the per-binding variant means the same amount of work as manually adding the corresponding alternative-layout symbol for non-latin users. I.e. you either add `--to-code` for each binding, or you add `Control+ж`. Same amount of work for the user, but one variant requires extra code in foot while the other doesn't. Note that removing raw key codes also means key bindings will be case sensitive. This is the correct thing to do, and is even documented in the `foot.ini` man page. However, we _might_ break someones config if they incorrectly added a `Control+Shift+u` (lower case) binding. ## Consume modifiers for key codes sent to application This is a no-brainer. Key escapes with modifier codes sent to the client application should have "consumed" modifiers removed. ## Consume modifiers for key bindings Do not include consumed modifiers when matching key bindings. This means `Control+U` matches <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>, and `Control-u` matches <kbd>ctrl</kbd>+<kbd>u</kbd>. ## Decide how to handle `Control+Shift+U` style bindings If/when we remove consumed modifiers from the matching logic, bindings like `Control+Shift+U` will stop working; the binding will have <kbd>shift</kbd> in its list, but we will have removed <kbd>shift</kbd> from the set we're comparing with. Hence no match. We can easily add code that handles this (by "adding back" the consumed modifiers to the set we're comparing with). I.e. the comparison logic would be something like: ```c if (binding.mods == (mods & ~consumed) || binding.mods == mods) { /* Binding matched */ } ``` I don't _think_ this will cause any problems.

What if I use a keyboard layout based on the great Halmak design? (The letter l is located where u is found in a QUERTY layout.) Can I then activate hyperlinks with Ctrl Shift l?

What if I use [a keyboard layout](https://salsa.debian.org/lechner/rocket-layout) based on the [great Halmak design](https://github.com/MadRabbit/halmak)? (The letter <kbd>l</kbd> is located where <kbd>u</kbd> is found in a QUERTY layout.) Can I then activate hyperlinks with <kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>l</kbd>?
Poster
Owner

Yes. Maybe. It depends on how we select the "default" layout when detecting raw key codes. Since you don't configure keyboard layout in foot, foot uses the system default layout when detecting key codes.

So, if you have a qwerty-like default layout, then foot's current auto-detection of key codes should map the Halmak l to u. I.e. it should work.

If we remove support for raw key codes it will not work; you'd have to manually add Control+Shift+l to the key binding in foot.ini.

If we add a per-binding option (--to-code), you'd have to manually add it to the hyperlink binding.

If we add a global option to enable raw key codes, you'd have to enable it, and use a qwerty-like layout as default. Alternatively, the option that enables key code translation also specifies which layout to use when translating to key codes.

Yes. Maybe. It depends on how we select the "default" layout when detecting raw key codes. Since you don't configure keyboard layout in foot, foot uses the system default layout when detecting key codes. So, if you have a qwerty-like default layout, then foot's current auto-detection of key codes _should_ map the Halmak <kbd>l</kbd> to `u`. I.e. it _should_ work. If we remove support for raw key codes it will not work; you'd have to manually add `Control+Shift+l` to the key binding in `foot.ini`. If we add a per-binding option (`--to-code`), you'd have to manually add it to the hyperlink binding. If we add a global option to enable raw key codes, you'd have to enable it, **and** use a qwerty-like layout as default. Alternatively, the option that enables key code translation **also** specifies which layout to use when translating to key codes.

a per-binding option (--to-code)

Among the options you presented that strikes me as the most versatile, but would it not be better to have a non-latin to latin mapping on the symbol level?

That would work for all programs, i.e. both foot and sway, and allow non-latin users to remap their keyboards, as well.

> a per-binding option (`--to-code`) Among the options you presented that strikes me as the most versatile, but would it not be better to have a non-latin to latin mapping *on the symbol level*? That would work for all programs, i.e. both `foot` and `sway`, and allow non-latin users to remap their keyboards, as well.
Collaborator

I'm kind of glad this issue has come up, since it seems to be quite relevant to one of the requirements of the new kitty keyboard protocol (#319). Hopefully some of the code can be shared between both use cases.

I'm kind of glad this issue has come up, since it seems to be quite relevant to one of the [requirements](https://sw.kovidgoyal.net/kitty/keyboard-protocol.html#key-codes) of the new kitty keyboard protocol (#319). Hopefully some of the code can be shared between both use cases.
Poster
Owner

but would it not be better to have a non-latin to latin mapping on the symbol level?

Who, and how, would be doing that mapping? Either someone has to manually map all layouts to all other layouts, or you use the key code; in layout A key code X maps to symbol S, while in layout B code X maps to symbol Q...

The latter is, more or less, what we're already doing.

> but would it not be better to have a non-latin to latin mapping *on the symbol level*? Who, and how, would be doing that mapping? Either someone has to manually map all layouts to all other layouts, or you use the key code; in layout A key code X maps to symbol S, while in layout B code X maps to symbol Q... The latter is, more or less, what we're already doing.
Poster
Owner

@craigbarnes

Hopefully some of the code can be shared between both use cases.

Doesn't the protocol specify how to handle (consumed) modifiers? If not, I'd say that's a problem with the protocol.

@craigbarnes > Hopefully some of the code can be shared between both use cases. Doesn't the protocol specify how to handle (consumed) modifiers? If not, I'd say that's a problem with the protocol.
Collaborator

Doesn't the protocol specify how to handle (consumed) modifiers?

It doesn't refer to them as "consumed" but it does mention behavior that seems more or less equivalent:

Note that the codepoint used is always the lower-case (or more technically, un-shifted) version of the key. If the user presses, for example, ctrl+shift+a the escape code would be CSI 97;modifiers u. It must not be CSI 65; modifiers u.

It also goes on to mention that additional info can be requested in the emitted sequence:

If alternate key reporting is requested by the program running in the terminal, the terminal can send two additional Unicode codepoints, the shifted key and base layout key, separated by colons. The shifted key is simply the upper-case version of unicode-codepoint, or more technically, the shifted version. So a becomes A and so on, based on the current keyboard layout. This is needed to be able to match against a shortcut such as ctrl+plus which depending on the type of keyboard could be either ctrl+shift+equal or ctrl+plus. Note that the shifted key must be present only if shift is also present in the modifiers.

> Doesn't the protocol specify how to handle (consumed) modifiers? It doesn't refer to them as "consumed" but it does mention behavior that seems more or less equivalent: > Note that the codepoint used is always the lower-case (or more technically, un-shifted) version of the key. If the user presses, for example, ctrl+shift+a the escape code would be `CSI 97;modifiers u`. It must not be `CSI 65; modifiers u`. It also goes on to mention that additional info can be requested in the emitted sequence: > If alternate key reporting is requested by the program running in the terminal, the terminal can send two additional Unicode codepoints, the shifted key and base layout key, separated by colons. The shifted key is simply the upper-case version of `unicode-codepoint`, or more technically, the shifted version. So _a_ becomes _A_ and so on, based on the current keyboard layout. This is needed to be able to match against a shortcut such as ctrl+plus which depending on the type of keyboard could be either ctrl+shift+equal or ctrl+plus. Note that the shifted key must be present only if shift is also present in the modifiers.
Poster
Owner

The last point is very closely related to our problem, I'd say.

The difference is we have all that information already. We just need to decide what to do with it.

The last point is very closely related to our problem, I'd say. The difference is we _have_ all that information already. We just need to decide what to _do_ with it.

we have all that information already.

That totally ties into my idea!

You deduce the base mapping from the key code, but XKB could do better by also suppling the true Latin mapping, which differs from the key code whenever a keyboard was remapped (in any language). The divergence is why we are having this conversation.

> we _have_ all that information already. That totally ties into my idea! You deduce the base mapping from the key code, but XKB could do better by *also* suppling the true Latin mapping, which differs from the key code whenever a keyboard was remapped (in any language). The divergence is why we are having this conversation.
Poster
Owner

You deduce the base mapping from the key code, but XKB could do better by also suppling the true Latin mapping

So either we patch XKB :)

Or we run two XKB state machines in parallel; one with the current layout, and another one for "a" latin layout (though what we really mean in this case is a qwerty layout).

> You deduce the base mapping from the key code, but XKB could do better by also suppling the true Latin mapping So either we patch XKB :) Or we run two XKB state machines in parallel; one with the current layout, and another one for "a" latin layout (though what we really mean in this case is a qwerty layout).

we really mean in this case is a qwerty layout

I don't think so. Here, both state machines would serve the same custom US layout. 🙂

(Or, I could use the same state machine.)

> we really mean in this case is a qwerty layout I don't think so. Here, both state machines would serve the same custom US layout. 🙂 (Or, I could use the same state machine.)

Upon reflection, I think XKB should return five values. For all keyboard layouts, we require:

  1. the excess modifiers, which did not help generate the symbol;
  2. the selected symbol with modifiers applied; and
  3. the selected base symbol without any modifiers applied.

From those, we can reliably infer key strokes in the native character set.

For foreign keyboards we also need:

  1. the Latin-equivalent symbol with modifiers applied; and
  2. the Latin-equivalent base symbol without any modifiers applied.

From those, we can perform the same evaluation for a Latin overlay, if needed.

Upon reflection, I think `XKB` should return five values. For all keyboard layouts, we require: 1. the *excess modifiers*, which did not help generate the symbol; 2. the *selected symbol* with modifiers applied; and 3. the *selected base symbol* without any modifiers applied. From those, we can reliably infer **key strokes in the native character set**. For foreign keyboards we also need: 4. the *Latin-equivalent symbol* with modifiers applied; and 5. the *Latin-equivalent base symbol* without any modifiers applied. From those, we can perform the same evaluation **for a Latin overlay**, if needed.
Poster
Owner

I've been looking at how Sway handles consumed modifiers.

My understanding is that Sway matches bindings twice:

  • Using translated symbols, where e.g. Alt+Shift+2 triggers Alt+@ on US layout. Consumed modifiers are removed from the comparison.
  • Using untranslated symbols, where Alt+Shift+2 triggers Alt+Shift+2 on US layout. Consumed modifiers are not removed from the comparison.

Doing this would mean a couple of things:

  • Ctrl+Shift+u can be written as either Control+Shift+u, or Control+U (but not Control+Shift+U).
  • Using the Control+Shift+u variant in our default bindings would allow us to continue doing automated keycode translation; detection would be done case sensitive, meaning we will not find a keycode for Control+U (since there's no key that produces an upper case U). Comparisons would be done in untranslated mode, forcing us to explicitly match Shift.
I've been looking at how Sway handles consumed modifiers. My understanding is that Sway matches bindings twice: * Using _translated_ symbols, where e.g. <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>2</kbd> triggers `Alt+@` on US layout. Consumed modifiers are **removed** from the comparison. * Using _untranslated_ symbols, where <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>2</kbd> triggers `Alt+Shift+2` on US layout. Consumed modifiers are **not** removed from the comparison. Doing this would mean a couple of things: * <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>u</kbd> can be written as either `Control+Shift+u`, or `Control+U` (but **not** `Control+Shift+U`). * Using the `Control+Shift+u` variant in our default bindings would allow us to continue doing automated keycode translation; detection would be done case **sensitive**, meaning we will **not** find a keycode for `Control+U` (since there's no key that produces an upper case `U`). Comparisons would be done in _untranslated_ mode, forcing us to explicitly match `Shift`.
Poster
Owner

The downside is our documentation explicitly states you must write Control+Shift+U (upper-case U):

Note that if Shift is one of the modifiers, the key must be in upper case. For example, Control+Shift+v will never trigger, but Control+Shift+V will.

But perhaps we can detect combos with Shift and an upper case ASCII letter, lower case it and log a deprecation warning?

The downside is our documentation explicitly states you **must** write `Control+Shift+U` (upper-case `U`): > Note that if `Shift` is one of the modifiers, the _key_ **must** be in upper case. For example, `Control+Shift+v` will never trigger, but `Control+Shift+V` will. But perhaps we can detect combos with `Shift` and an upper case ASCII letter, lower case it and log a deprecation warning?
dnkl closed this issue 8 months ago
Sign in to join this conversation.
No Milestone
No Assignees
3 Participants
Notifications
Due Date

No due date set.

Dependencies

This issue currently doesn't have any dependencies.

Loading…
There is no content yet.