Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make key repeat and delay configurable #3730

Merged
merged 13 commits into from
Feb 4, 2025

Conversation

tarek-y-ismail
Copy link
Contributor

@tarek-y-ismail tarek-y-ismail commented Jan 28, 2025

How to test:

  • Create a file at ~/.config/mir_demo_server.input with the following content:
repeat_rate=25
repeat_delay=600
  • Run mir demo server
  • Run a wayland compatible text editor or terminal (kgx), or and X application (xterm)
  • press and hold any key
  • Change repeat_rate to a higher or lower value and watch as the repeat rate speeds or slows down
  • Change repeat_delay to a higher or lower value and watch as the duration you need to hold the key down increases or decreases.

@tarek-y-ismail tarek-y-ismail self-assigned this Jan 28, 2025
@tarek-y-ismail tarek-y-ismail marked this pull request as ready for review January 28, 2025 17:03
@tarek-y-ismail tarek-y-ismail requested a review from a team as a code owner January 28, 2025 17:03
@tarek-y-ismail tarek-y-ismail marked this pull request as draft January 28, 2025 17:09
@tarek-y-ismail tarek-y-ismail force-pushed the MIRENG-892/configurable-key-repeat branch from 4712763 to 9299fb6 Compare January 29, 2025 08:34
@tarek-y-ismail tarek-y-ismail marked this pull request as ready for review January 29, 2025 09:08
Copy link
Collaborator

@AlanGriffiths AlanGriffiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No tests for the new code?

A few things need shuffling around.

Also, it would be nice to split the ConfigFile changes into a separate, precursor, PR. (They should be uncontentious and quick to land)

include/miral/miral/config_file.h Outdated Show resolved Hide resolved
src/include/server/mir/frontend/keyboard_helper.h Outdated Show resolved Hide resolved
src/include/server/mir/shell/accessibility_manager.h Outdated Show resolved Hide resolved
src/server/frontend_wayland/CMakeLists.txt Outdated Show resolved Hide resolved
src/server/report/default_server_configuration.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@mattkae mattkae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty reasonable! I've got a few points for improvement

src/include/server/mir/frontend/keyboard_helper.h Outdated Show resolved Hide resolved
src/include/server/mir/shell/accessibility_manager.h Outdated Show resolved Hide resolved
src/server/report/default_server_configuration.cpp Outdated Show resolved Hide resolved
src/include/server/mir/shell/accessibility_manager.h Outdated Show resolved Hide resolved
src/server/shell/accessibility_manager.cpp Outdated Show resolved Hide resolved
@tarek-y-ismail tarek-y-ismail force-pushed the MIRENG-892/configurable-key-repeat branch from 9299fb6 to a6b1793 Compare January 30, 2025 12:43
@tarek-y-ismail tarek-y-ismail changed the base branch from main to config-file-load-after-server-init January 30, 2025 12:43
@tarek-y-ismail tarek-y-ismail force-pushed the MIRENG-892/configurable-key-repeat branch from a6b1793 to 55c6cb7 Compare January 30, 2025 13:01
@tarek-y-ismail tarek-y-ismail force-pushed the MIRENG-892/configurable-key-repeat branch from 55c6cb7 to 384afb6 Compare January 30, 2025 15:00
@tarek-y-ismail tarek-y-ismail force-pushed the config-file-load-after-server-init branch 2 times, most recently from f6f04ce to 6a0f1b9 Compare January 30, 2025 15:10
Copy link
Contributor

@mattkae mattkae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more questions, but looking good!

src/miral/input_configuration.cpp Show resolved Hide resolved
src/include/server/mir/shell/accessibility_manager.h Outdated Show resolved Hide resolved
src/server/report/default_server_configuration.cpp Outdated Show resolved Hide resolved
src/server/shell/accessibility_manager.cpp Outdated Show resolved Hide resolved
@tarek-y-ismail
Copy link
Contributor Author

Note to self: Just came across this while working on something else, might be a good idea to check out

return input_dispatcher(
[this]()
{
std::chrono::milliseconds const key_repeat_timeout{500};
std::chrono::milliseconds const key_repeat_delay{50};
auto const options = the_options();
// lp:1675357: Disable generation of key repeat events on nested servers
auto enable_repeat = options->get<bool>(options::enable_key_repeat_opt);
auto const idle_poking_dispatcher = std::make_shared<mi::IdlePokingDispatcher>(
the_event_filter_chain_dispatcher(),
the_idle_hub());
auto const keyboard_resync_dispatcher =
std::make_shared<mi::KeyboardResyncDispatcher>(idle_poking_dispatcher);
return std::make_shared<mi::KeyRepeatDispatcher>(
keyboard_resync_dispatcher, the_main_loop(),
enable_repeat, key_repeat_timeout, key_repeat_delay, false);
});

@tarek-y-ismail tarek-y-ismail force-pushed the MIRENG-892/configurable-key-repeat branch from f66af7f to d484803 Compare January 31, 2025 13:59
@tarek-y-ismail
Copy link
Contributor Author

tarek-y-ismail commented Jan 31, 2025

Note to self: Just came across this while working on something else, might be a good idea to check out

Very confused what this does since repeat on both wayland and X seem to work without any modification to it.

Edit: From a quick skim, this seems to implement key repeat on the server. For wayland and X(wayland), the code changes here suffice.

Initially, the config file is loaded before the server starts. The
changes need some server components to be applied, so they aren't
applied at that stage.

The few lines added store the changes and apply them once the server
starts.
@tarek-y-ismail tarek-y-ismail changed the base branch from config-file-load-after-server-init to main January 31, 2025 16:04
Comment on lines 123 to +126
miral::InputConfiguration input_configuration;
auto mouse = input_configuration.mouse();
auto touchpad = input_configuration.touchpad();
auto keyboard = input_configuration.keyboard();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could grow a utility class that combines these and the repeated "apply" logic?

Also, we need to be careful: we're now accessing mouse, touchpad and keyboard on multiple threads.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having said that, I like the revised approach

Copy link
Contributor Author

@tarek-y-ismail tarek-y-ismail Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, do you have anything particular in mind? The simplest solution I can think of is keeping the initialization logic as is and wrapping the application logic in a lambda with a mutex.

Something like:

+    std::mutex config_mutex;
+
+    auto config_applier = [&] mutable
+    {
+        input_configuration.mouse(mouse);
+        input_configuration.touchpad(touchpad);
+        input_configuration.keyboard(keyboard);
+    };
 
     miral::ConfigFile test{runner, "mir_demo_server.input", miral::ConfigFile::Mode::reload_on_change,
         [&](auto& in, auto path)
     {
         std::cout << "** Reloading: " << path << std::endl;
 
+        std::lock_guard lock{config_mutex};
 
         for (std::string line; std::getline(in, line);)
         {
@@ -185,17 +196,14 @@ try
             }
         }
 
-        input_configuration.mouse(mouse);
-        input_configuration.touchpad(touchpad);
-        input_configuration.keyboard(keyboard);
+        config_applier();
     }};
 
     runner.add_start_callback(
         [&]
         {
-            input_configuration.mouse(mouse);
-            input_configuration.touchpad(touchpad);
-            input_configuration.keyboard(keyboard);
+            std::lock_guard lock{config_mutex};
+            config_applier();

But I don't really think this needs its own utility class, just having something like this as an example should be enough.

Edit: Moved the lock to be external to config_applier since we also need to protect mouse, keyboard, and touchpad from being modified as we apply the config or vice-versa

Copy link
Collaborator

@AlanGriffiths AlanGriffiths Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is always possible to have data and related functions separately, but it is clearer to the reader when they are grouped as a class. In particular, it is easier to check invariants when it is clear which code can access the data.

In this case, I think the logic around config_mutex, mouse, touchpad and keyboard is complex enough to make isolating this data desirable:

class InputConfigFile : miral::ConfigFile
{
public:
    InputConfigFile(miral::MirRunner& runner, std::filesystem::path file) :
        ConfigFile{runner, file, ConfigFile::Mode::reload_on_change, [this](auto args...) {loader(args...); }}
    {
        runner.add_start_callback(
            [&]
            {
                std::lock_guard lock{config_mutex};
                config_applier();
            });
    }

    void operator()(mir::Server& server)
    {
        input_configuration(server);
    }

private:
    miral::InputConfiguration input_configuration;
    auto mouse = input_configuration.mouse();
    auto touchpad = input_configuration.touchpad();
    auto keyboard = input_configuration.keyboard();
    std::mutex config_mutex;

    void config_applier() const
    {
        input_configuration.mouse(mouse);
        input_configuration.touchpad(touchpad);
        input_configuration.keyboard(keyboard);
    };

    
    void loader(std::istream& istream, std::filesystem::path const& path)
    {
        std::lock_guard lock{config_mutex};
        // ...
    }
};

It is quick to see that the data is only touched by this code, and that it is appropriately locked.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a version without the undefined behaviour and compilation errors:

class InputConfigFile
{
public:
    InputConfigFile(miral::MirRunner& runner, std::filesystem::path file) :
        config_file{
            runner,
            file,
            miral::ConfigFile::Mode::reload_on_change,
            [this](std::istream& istream, std::filesystem::path const& path) {loader(istream, path); }}
    {
        runner.add_start_callback([this]
            {
                std::lock_guard lock{config_mutex};
                apply_config();
            });
    }

    void operator()(mir::Server& server)
    {
        input_configuration(server);
    }

private:
    miral::InputConfiguration input_configuration;
    miral::InputConfiguration::Mouse mouse = input_configuration.mouse();
    miral::InputConfiguration::Touchpad touchpad = input_configuration.touchpad();
    miral::InputConfiguration::Keyboard keyboard = input_configuration.keyboard();
    miral::ConfigFile config_file;
    std::mutex config_mutex;

    void apply_config()
    {
        input_configuration.mouse(mouse);
        input_configuration.touchpad(touchpad);
        input_configuration.keyboard(keyboard);
    };


    void loader(std::istream& in, std::filesystem::path const& path)
    {
        std::cout << "** Reloading: " << path << std::endl;

        std::lock_guard lock{config_mutex};

        for (std::string line; std::getline(in, line);)
        {
            std::cout << line << std::endl;

            if (line == "mir_pointer_handedness_right")
                mouse.handedness(mir_pointer_handedness_right);
            if (line == "mir_pointer_handedness_left")
                mouse.handedness(mir_pointer_handedness_left);

            if (line == "mir_touchpad_scroll_mode_none")
                touchpad.scroll_mode(mir_touchpad_scroll_mode_none);
            if (line == "mir_touchpad_scroll_mode_two_finger_scroll")
                touchpad.scroll_mode(mir_touchpad_scroll_mode_two_finger_scroll);
            if (line == "mir_touchpad_scroll_mode_edge_scroll")
                touchpad.scroll_mode(mir_touchpad_scroll_mode_edge_scroll);
            if (line == "mir_touchpad_scroll_mode_button_down_scroll")
                touchpad.scroll_mode(mir_touchpad_scroll_mode_button_down_scroll);

            if (line.contains("="))
            {
                auto const eq = line.find_first_of("=");
                auto const key = line.substr(0, eq);
                auto const value = line.substr(eq+1);

                auto const parse_and_validate = [](std::string const& key, std::string_view val) -> std::optional<int>
                {
                    auto const int_val = std::atoi(val.data());
                    if (int_val < 0)
                    {
                        mir::log_warning(
                            "Config value %s does not support negative values. Ignoring the supplied value (%d)...",
                            key.c_str(), int_val);
                        return std::nullopt;
                    }

                    return int_val;
                };

                if (key == "repeat_rate")
                {
                    auto const parsed = parse_and_validate(key, value);
                    if (parsed)
                        keyboard.set_repeat_rate(*parsed);
                }

                if (key == "repeat_delay")
                {
                    auto const parsed = parse_and_validate(key, value);
                    if (parsed)
                        keyboard.set_repeat_delay(*parsed);
                }
            }
        }

        apply_config();
    }
};

One small annoyance is that this isn't Copyable, and that means the run_with() parameter needs passing with std::ref(). Hoisting the state into a shared Self would work, but seems overkill.

Reduces unnecessary duplication and protects the code with a mutex.
mattkae
mattkae previously requested changes Feb 3, 2025
Copy link
Contributor

@mattkae mattkae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I found a bug, but I don't know where it could be in the code! Here's a reproducer for you:

  1. Start mir_demo_server
  2. Open up gedit
  3. Have the following input config:
    repeat_rate=1
    repeat_delay=1
    
  4. Hold down the 'a' key and start inserting 'a's. It should insert two 'a's almost immediately, and then 1 'a' every second or so
  5. Clear the gedit file. Set repeat_rate to 2 and save the config. Then set it to 1 again immediately and save the config.
  6. Go back to gedit and hold down the 'a' key. Two 'a's will be inserted almost immediately and then 1 'a' will appear every second or so. HOWEVER, note that at some point later, you will get the "double 'a' insertion" again, even though you didn't start inserting again!

Other than this bug, it works well for me!

@AlanGriffiths
Copy link
Collaborator

I believe I found a bug, but I don't know where it could be in the code

From the description the bug could lie in GTK (implementing the repeat is the client's responsibility).

The thing to check is what repeat_info events are being sent to the client when the edit occurs.

@mattkae
Copy link
Contributor

mattkae commented Feb 3, 2025

I believe I found a bug, but I don't know where it could be in the code

From the description the bug could lie in GTK (implementing the repeat is the client's responsibility).

The thing to check is what repeat_info events are being sent to the client when the edit occurs.

Same issue with foot. This time I just had to have both repeat_rate and repeate_delay set to 1 and it happened quickly. I held the 'a' key and the following happend:

  • Immediately got 'aa' (expected)
  • Got 'a' 1 second later (expected)
  • Got 'aa' 1 second later (unexpected)

Here was my WAYLAND_DEBUG trace when it happened. This might actually be unrelated to this work. I am definitely holding down the key on my keyboard, but its registering a release + press again. Perhaps it is just my keyboard though since it only happens some times. Can either @AlanGriffiths or @tarek-y-ismail confirm that this doesn't happen for you?

[ 353960.582] [email protected](99, 26840498, 30, 1)
[ 353961.588]  -> [email protected]_buffer(96, 104, 12, 13)
[ 353961.629]  -> [email protected](new id wl_callback@42)
[ 353961.649]  -> [email protected]_buffer_scale(1)
[ 353961.672]  -> [email protected]_destination(688, 470)
[ 353961.691]  -> [email protected](wl_buffer@35, 0, 0)
[ 353961.709]  -> [email protected]()
[ 353961.723]  -> [email protected]_cursor_rectangle(102, 104, 6, 13)
[ 353961.742]  -> [email protected]()
[ 353962.477] [email protected]()
[ 353966.395] [email protected]_id(42)
[ 353966.434] [email protected](26840504)
[ 353966.713]  -> [email protected]_buffer(102, 104, 12, 13)
[ 353966.762]  -> [email protected](new id wl_callback@42)
[ 353966.785]  -> [email protected]_buffer_scale(1)
[ 353966.802]  -> [email protected]_destination(688, 470)
[ 353966.818]  -> [email protected](wl_buffer@38, 0, 0)
[ 353966.834]  -> [email protected]()
[ 353966.848]  -> [email protected]_cursor_rectangle(108, 104, 6, 13)
[ 353966.865]  -> [email protected]()
[ 353967.679] [email protected]()
[ 353969.689] [email protected]_id(42)
[ 353969.721] [email protected](26840508)
[ 354962.764]  -> [email protected]_buffer(108, 104, 12, 13)
[ 354962.807]  -> [email protected](new id wl_callback@42)
[ 354962.828]  -> [email protected]_buffer_scale(1)
[ 354962.845]  -> [email protected]_destination(688, 470)
[ 354962.863]  -> [email protected](wl_buffer@35, 0, 0)
[ 354962.881]  -> [email protected]()
[ 354962.906]  -> [email protected]_cursor_rectangle(114, 104, 6, 13)
[ 354962.925]  -> [email protected]()
[ 354963.647] [email protected]()
[ 354966.805] [email protected]_id(42)
[ 354966.846] [email protected](26841505)
[ 355544.160] [email protected](100, 26842082, 30, 0)
[ 355544.383] [email protected](101, 26842082, 30, 1)
[ 355545.522]  -> [email protected]_buffer(114, 104, 12, 13)
[ 355545.566]  -> [email protected](new id wl_callback@42)
[ 355545.587]  -> [email protected]_buffer_scale(1)
[ 355545.602]  -> [email protected]_destination(688, 470)
[ 355545.621]  -> [email protected](wl_buffer@38, 0, 0)
[ 355545.641]  -> [email protected]()
[ 355545.657]  -> [email protected]_cursor_rectangle(120, 104, 6, 13)
[ 355545.675]  -> [email protected]()
[ 355546.348] [email protected]()
[ 355549.742] [email protected]_id(42)
[ 355549.780] [email protected](26842088)
[ 355550.159]  -> [email protected]_buffer(120, 104, 12, 13)
[ 355550.195]  -> [email protected](new id wl_callback@42)
[ 355550.214]  -> [email protected]_buffer_scale(1)
[ 355550.229]  -> [email protected]_destination(688, 470)
[ 355550.257]  -> [email protected](wl_buffer@35, 0, 0)
[ 355550.273]  -> [email protected]()
[ 355550.287]  -> [email protected]_cursor_rectangle(126, 104, 6, 13)
[ 355550.303]  -> [email protected]()
[ 355550.956] [email protected]()
[ 355553.386] [email protected]_id(42)
[ 355553.426] [email protected](26842091)
[ 355997.363] [email protected](102, 26842535, 30, 0)

@AlanGriffiths
Copy link
Collaborator

Can either @AlanGriffiths or @tarek-y-ismail confirm that this doesn't happen for you?

It took a while (first few tries I didn't see it) but I finally managed to occasionally. But it doesn't look like this PR is responsible.

Here's my test scenario:

  1. cmake-build-debug/bin/miral-app -demo-server
  2. in the nested terminal window: WAYLAND_DEBUG=client gedit 2>&1 | grep wl_keyboard@
  3. Press "a"

Expect: "a" followed quickly (1ms later) by "a" and another "a" each second until key released
Actual: "a" followed 1ms later by "a" and another "a" each second until key released but, occasionally, restarting this sequence

Here's what I see when the "aa" happens:

[2260865.954] [email protected](162, 7535907, 30, 1)
[2303513.449] [email protected](163, 7578554, 30, 0)
[2303513.534] [email protected](164, 7578554, 30, 1)
[2304433.438] [email protected](165, 7579474, 30, 0)

That is, we're telling the client the key is released and pressed at 7578554, so it is normal for the key repeat sequence to restart. (I wonder if this is an artefact of our X11 platform - I know there's some messy logic handling the input there)

Regardless, so far as this PR is concerned, we are sending the expected repeat_info when the file is updated:

$  WAYLAND_DISPLAY=wayland-1 WAYLAND_DEBUG=client gedit 2>&1 | grep repeat_info
[ 899340.551] [email protected]_info(25, 600)
[ 908092.333] [email protected]_info(1, 1)

@AlanGriffiths
Copy link
Collaborator

our X11 platform - I know there's some messy logic handling the input there

Some "printf() debugging" supports this:

void mir::input::X::XInputDevice::key_press(EventTime, xkb_keysym_t, int)
void mir::input::X::XInputDevice::key_release(EventTime, xkb_keysym_t, int)
void mir::input::X::XInputDevice::key_press(EventTime, xkb_keysym_t, int)
void mir::input::X::XInputDevice::key_release(EventTime, xkb_keysym_t, int)

That is, X11 is telling Mir the key has been released and pressed and we pass that on. (I thought we had logic to suppress these release/press pairs, but I don't see it know)

@AlanGriffiths
Copy link
Collaborator

logic to suppress these release/press pairs

That disappeared with the move to xcb (5207ca9)

@AlanGriffiths
Copy link
Collaborator

@tarek-y-ismail I think the only thing to address is my InputConfigFile suggestion. (If nobody else thinks it worthwhile, then 🤷)

@AlanGriffiths AlanGriffiths dismissed mattkae’s stale review February 4, 2025 11:24

The queried behaviour isn't related to the PR

@AlanGriffiths
Copy link
Collaborator

X11 is telling Mir the key has been released and pressed and we pass that on

Likely the same cause as #1471

@mattkae
Copy link
Contributor

mattkae commented Feb 4, 2025

X11 is telling Mir the key has been released and pressed and we pass that on

Likely the same cause as #1471

Great. I marked it as triaged. That's a good thing to fix, as it will negatively affect certain experience (e.g. gaming)

Copy link
Contributor

@mattkae mattkae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My issue was not your issue, so I am happy 😄

@AlanGriffiths
Copy link
Collaborator

Great. I marked it as triaged. That's a good thing to fix, as it will negatively affect certain experience (e.g. gaming)

Is anyone really going to game on the X11 platform?!

Copy link
Collaborator

@AlanGriffiths AlanGriffiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still like to improve the example code, but won't block on it

@AlanGriffiths AlanGriffiths added this pull request to the merge queue Feb 4, 2025
@Saviq
Copy link
Collaborator

Saviq commented Feb 4, 2025

Is anyone really going to game on the X11 platform?!

That's the only way for WINE/Proton-based games (i.e. the majority) still...

@AlanGriffiths
Copy link
Collaborator

That's the only way for WINE/Proton-based games (i.e. the majority) still...

The mir:X11 graphics platform

Merged via the queue into main with commit c66d9ae Feb 4, 2025
31 of 39 checks passed
@AlanGriffiths AlanGriffiths deleted the MIRENG-892/configurable-key-repeat branch February 4, 2025 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants