Re: [PATCH] HID: apple: Fix stuck function keys when using FN
From: JoÃo Moreno
Date: Mon Sep 02 2019 - 10:44:37 EST
Hi Benjamin,
First of all, sorry for the late reply. Turns out a newborn baby can
keep one quite busy, who would have known? :)
On Tue, 27 Aug 2019 at 18:57, Benjamin Tissoires
<benjamin.tissoires@xxxxxxxxxx> wrote:
>
> Hi JoÃo,
>
> On 8/12/19 6:57 PM, Benjamin Tissoires wrote:
> > Hi JoÃo,
> >
> > On Thu, Aug 8, 2019 at 10:35 PM JoÃo Moreno <mail@xxxxxxxxxxxxxx> wrote:
> >>
> >> Hi Benjamin,
> >>
> >> On Mon, 8 Jul 2019 at 22:35, JoÃo Moreno <mail@xxxxxxxxxxxxxx> wrote:
> >>>
> >>> Hi Benjamin,
> >>>
> >>> No worries, also pretty busy over here. Didn't mean to press.
> >>>
> >>> On Mon, 1 Jul 2019 at 10:32, Benjamin Tissoires
> >>> <benjamin.tissoires@xxxxxxxxxx> wrote:
> >>>>
> >>>> Hi JoÃo,
> >>>>
> >>>> On Sun, Jun 30, 2019 at 10:15 PM JoÃo Moreno <mail@xxxxxxxxxxxxxx> wrote:
> >>>>>
> >>>>> Hi Jiri & Benjamin,
> >>>>>
> >>>>> Let me know if you need something else to get this patch moving forward. This
> >>>>> fixes an issue I hit daily, it would be great to get it fixed.
> >>>>
> >>>> Sorry for the delay, I am very busy with internal corporate stuff, and
> >>>> I tried setting up a new CI system at home, and instead of spending a
> >>>> couple of ours, I am down to 2 weeks of hard work, without possibility
> >>>> to switch to the new right now :(
> >>>> Anyway.
> >>>>
> >>>>>
> >>>>> Thanks.
> >>>>>
> >>>>> On Mon, 10 Jun 2019 at 23:31, Joao Moreno <mail@xxxxxxxxxxxxxx> wrote:
> >>>>>>
> >>>>>> This fixes an issue in which key down events for function keys would be
> >>>>>> repeatedly emitted even after the user has raised the physical key. For
> >>>>>> example, the driver fails to emit the F5 key up event when going through
> >>>>>> the following steps:
> >>>>>> - fnmode=1: hold FN, hold F5, release FN, release F5
> >>>>>> - fnmode=2: hold F5, hold FN, release F5, release FN
> >>>>
> >>>> Ouch :/
> >>>>
> >>>
> >>> Right?!
> >>>
> >>>>>>
> >>>>>> The repeated F5 key down events can be easily verified using xev.
> >>>>>>
> >>>>>> Signed-off-by: Joao Moreno <mail@xxxxxxxxxxxxxx>
> >>>>>> ---
> >>>>>> drivers/hid/hid-apple.c | 21 +++++++++++----------
> >>>>>> 1 file changed, 11 insertions(+), 10 deletions(-)
> >>>>>>
> >>>>>> diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
> >>>>>> index 1cb41992aaa1..81867a6fa047 100644
> >>>>>> --- a/drivers/hid/hid-apple.c
> >>>>>> +++ b/drivers/hid/hid-apple.c
> >>>>>> @@ -205,20 +205,21 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
> >>>>>> trans = apple_find_translation (table, usage->code);
> >>>>>>
> >>>>>> if (trans) {
> >>>>>> - if (test_bit(usage->code, asc->pressed_fn))
> >>>>>> - do_translate = 1;
> >>>>>> - else if (trans->flags & APPLE_FLAG_FKEY)
> >>>>>> - do_translate = (fnmode == 2 && asc->fn_on) ||
> >>>>>> - (fnmode == 1 && !asc->fn_on);
> >>>>>> + int fn_on = value ? asc->fn_on :
> >>>>>> + test_bit(usage->code, asc->pressed_fn);
> >>>>>> +
> >>>>>> + if (!value)
> >>>>>> + clear_bit(usage->code, asc->pressed_fn);
> >>>>>> + else if (asc->fn_on)
> >>>>>> + set_bit(usage->code, asc->pressed_fn);
> >>>>
> >>>> I have the feeling that this is not the correct fix here.
> >>>>
> >>>> I might be wrong, but the following sequence might also mess up the
> >>>> driver state, depending on how the reports are emitted:
> >>>> - hold FN, hold F4, hold F5, release F4, release FN, release F5
> >>>>
> >>>
> >>> I believe this should be fine. Following the code:
> >>>
> >>> - hold FN, sets asc->fn_on to true
> >>> - hold F4, in the trans block fn_on will be true and we'll set the F4
> >>> bit in the bitmap
> >>> - hold F5, in the trans block fn_on will be true and we'll set the F5 bit
> >>> - release F4, in the trans block fn_on will be true (because of the bitmap) and
> >>> we'll clear the F4 bit
> >>> - release FN, asc->fn_on will be false, but it doesn't matter since...
> >>> - release F5, in the trans block we'll look into the bitmap (instead
> >>> of asc->fn_on),
> >>> so fn_on will be true and we'll clear the F5 bit
> >>>
> >>> I tested it in practice using my changes:
> >>>
> >>> Interestingly the Apple keyboard doesn't seem to emit an even for F5 when F4 is
> >>> pressed, seems like a hardware limitation. But F6 does work. So, when I execute
> >>> these events in that order, everything works as it should: xev reports
> >>> the following:
> >>>
> >>> KeyPress F4
> >>> KeyPress F6
> >>> KeyRelease F4
> >>> KeyRelease F6
> >>>
> >>>> The reason is that the driver only considers you have one key pressed
> >>>> with the modifier, and as the code changed its state based on the last
> >>>> value.
> >>>>
> >>>
> >>> I believe the bitmap takes care of storing the FN state per key press. The
> >>> trick I did was to check on the global `asc->fn_on` state only when a key
> >>> is pressed, but check on the bitmap instead when it's released.
> >>>
> >>> Let me know what you think. Am I missing something here?
> >>>
> >>> Cheers,
> >>> JoÃo.
> >>>
> >>>> IMO a better fix would:
> >>>>
> >>>> - keep the existing `trans` mapping lookout
> >>>> - whenever a `trans` mapping gets found:
> >>>> * get both translated and non-translated currently reported values
> >>>> (`test_bit(keycode, input_dev->key)`)
> >>>> * if one of them is set to true, then consider the keycode to be the
> >>>> one of the key (no matter fn_on)
> >>>> -> deal with `value` with the corrected keycode
> >>>> * if the key was not pressed:
> >>>> -> chose the keycode based on `fn_on` and `fnmode` states
> >>>> and report the key press event
> >>>>
> >>>> This should remove the nasty pressed_fn state which depends on the
> >>>> other pressed keys.
> >>>>
> >>>> Cheers,
> >>>> Benjamin
> >>>>
> >>>>>> +
> >>>>>> + if (trans->flags & APPLE_FLAG_FKEY)
> >>>>>> + do_translate = (fnmode == 2 && fn_on) ||
> >>>>>> + (fnmode == 1 && !fn_on);
> >>>>>> else
> >>>>>> do_translate = asc->fn_on;
> >>>>>>
> >>>>>> if (do_translate) {
> >>>>>> - if (value)
> >>>>>> - set_bit(usage->code, asc->pressed_fn);
> >>>>>> - else
> >>>>>> - clear_bit(usage->code, asc->pressed_fn);
> >>>>>> -
> >>>>>> input_event(input, usage->type, trans->to,
> >>>>>> value);
> >>>>>>
> >>>>>> --
> >>>>>> 2.19.1
> >>>>>>
> >>
> >> Gave this another look and I still haven't found any issues, let me
> >> know if you still
> >> think I'm missing something. Thanks!
> >>
> >
> > OK, I am tempted to take the patch, but I'll need to add this as a
> > regression test in my test-suite. This is too complex that it can
> > easily break next time someone looks at it.
> >
> > Can you send me the report descriptors of the device using
> > hid-recorder from hid-tools[0].
> > Ideally, if you can put the faulty sequence in the logs, that would
> > help writing down the use case.
> >
>
> I finally managed to get the regression tests up in
> https://gitlab.freedesktop.org/libevdev/hid-tools/merge_requests/52
>
This is great, really cool that you can write proper tests for this. I
assume you don't need me to send you the report descriptors any more?
> This allowed me to better understand the code, and yes, for the F-keys,
> I could not find a faulty situation with your patch.
>
> However, the same problem is happening with the arrow keys, as arrow up
> is translated to page up.
>
Great catch, I can easily repro that issue as well on my keyboard.
> We *could* adapt your patch to solve this, but I find it really hard to understand.
>
> How about the following diff:
> ---
> diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
> index 81df62f48c4c..ef916c0f3c0b 100644
> --- a/drivers/hid/hid-apple.c
> +++ b/drivers/hid/hid-apple.c
> @@ -54,7 +54,6 @@ MODULE_PARM_DESC(swap_opt_cmd, "Swap the Option (\"Alt\") and Command (\"Flag\")
> struct apple_sc {
> unsigned long quirks;
> unsigned int fn_on;
> - DECLARE_BITMAP(pressed_fn, KEY_CNT);
> DECLARE_BITMAP(pressed_numlock, KEY_CNT);
> };
>
> @@ -181,6 +180,8 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
> {
> struct apple_sc *asc = hid_get_drvdata(hid);
> const struct apple_key_translation *trans, *table;
> + bool do_translate;
> + u16 code = 0;
>
> if (usage->code == KEY_FN) {
> asc->fn_on = !!value;
> @@ -189,8 +190,6 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
> }
>
> if (fnmode) {
> - int do_translate;
> -
> if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
> hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
> table = macbookair_fn_keys;
> @@ -202,25 +201,33 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
> trans = apple_find_translation (table, usage->code);
>
> if (trans) {
> - if (test_bit(usage->code, asc->pressed_fn))
> - do_translate = 1;
> - else if (trans->flags & APPLE_FLAG_FKEY)
> - do_translate = (fnmode == 2 && asc->fn_on) ||
> - (fnmode == 1 && !asc->fn_on);
> - else
> - do_translate = asc->fn_on;
> -
> - if (do_translate) {
> - if (value)
> - set_bit(usage->code, asc->pressed_fn);
> - else
> - clear_bit(usage->code, asc->pressed_fn);
> -
> - input_event(input, usage->type, trans->to,
> - value);
> -
> - return 1;
> + if (test_bit(trans->from, input->key))
> + code = trans->from;
> + if (test_bit(trans->to, input->key))
> + code = trans->to;
I see, this is what you meant before. I still don't quite understand
what information `input->key` actually holds. Can you elaborate on
what it contains? How does it differ from `usage->code`?
Also, should the second `if` be an `else if` instead? We shouldn't
have to call `test_bit` more than once here, right? If right, then the
next `if` would could simply be an `else`, since the translation
tables should not have zeros in them.
> +
> + if (!code) {
> + if (trans->flags & APPLE_FLAG_FKEY) {
> + switch (fnmode) {
> + case 1:
> + do_translate = !asc->fn_on;
> + break;
> + case 2:
> + do_translate = asc->fn_on;
> + break;
> + default:
> + /* should never happen */
> + do_translate = false;
> + }
> + } else {
> + do_translate = asc->fn_on;
> + }
> +
> + code = do_translate ? trans->to : trans->from;
> }
> +
> + input_event(input, usage->type, code, value);
> + return 1;
> }
>
> if (asc->quirks & APPLE_NUMLOCK_EMULATION &&
> ---
>
> This is more or less what I suggested, minus the bug fixes.
>
> I find this easier to understand as the .pressed_fn field is not there
> anymore and we just rely on the actual state of the input subsystem.
>
> Comments?
Love it. Seems to catch all the cases on my hardware. Thanks for
following up with the patch!
>
> Cheers,
> Benjamin
>
>
> > Cheers,
> > Benjamin
> >
> > [0] https://gitlab.freedesktop.org/libevdev/hid-tools
> >
Cheers,
JoÃo