105 lines
4.2 KiB
C++
105 lines
4.2 KiB
C++
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
// Key repeat as wl_keyboard.repeat_info promises it: one repeat after `delay`
|
||
|
|
// milliseconds, then `rate` a second. The first implementation set the last
|
||
|
|
// fire time to the press and let the catch-up loop count the delay as missed
|
||
|
|
// periods, so the first repeat was a burst of floor(delay / period) events —
|
||
|
|
// a held Backspace deleted a dozen characters at once — and a rate above 1000
|
||
|
|
// made the period zero and the loop endless. Drives Device::TickKeyRepeats
|
||
|
|
// with an injected clock on a default-constructed Window (inert without a
|
||
|
|
// compositor): no compositor, no keyboard needed.
|
||
|
|
|
||
|
|
#include <cstdlib>
|
||
|
|
|
||
|
|
import Crafter.Graphics;
|
||
|
|
import Crafter.Event;
|
||
|
|
import std;
|
||
|
|
using namespace Crafter;
|
||
|
|
using namespace std::chrono_literals;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
int failures = 0;
|
||
|
|
|
||
|
|
void Check(bool ok, std::string_view what) {
|
||
|
|
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||
|
|
if (!ok) ++failures;
|
||
|
|
}
|
||
|
|
|
||
|
|
using Clock = std::chrono::steady_clock;
|
||
|
|
|
||
|
|
// A press of `key` at t0 with the compositor's rate and delay, as keyboard_key records it.
|
||
|
|
void Press(KeyCode key, std::string utf8, int rate, int delay, Clock::time_point t0) {
|
||
|
|
Device::keyRepeat.rate = rate;
|
||
|
|
Device::keyRepeat.delay = delay;
|
||
|
|
Device::keyRepeat.active = rate > 0;
|
||
|
|
Device::keyRepeat.key = key;
|
||
|
|
Device::keyRepeat.utf8 = std::move(utf8);
|
||
|
|
Device::keyRepeat.pressTime = t0;
|
||
|
|
Device::keyRepeat.lastFireTime = t0;
|
||
|
|
Device::keyRepeat.fired = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
Window window; // default ctor creates no surface — safe without a compositor
|
||
|
|
int downs = 0;
|
||
|
|
int texts = 0;
|
||
|
|
EventListener<KeyCode> down(&window.onRawKeyDown, [&](KeyCode) { ++downs; });
|
||
|
|
EventListener<const std::string_view> text(&window.onTextInput, [&](const std::string_view) { ++texts; });
|
||
|
|
Device::keyboardWindow = &window;
|
||
|
|
|
||
|
|
const Clock::time_point t0 = Clock::now();
|
||
|
|
|
||
|
|
// rate 25 (a 40 ms period), delay 600: wlroots' and KWin's defaults.
|
||
|
|
Press(14, "a", 25, 600, t0);
|
||
|
|
Device::TickKeyRepeats(t0 + 30ms);
|
||
|
|
Device::TickKeyRepeats(t0 + 599ms);
|
||
|
|
Check(downs == 0 && texts == 0, "nothing repeats before the delay");
|
||
|
|
Device::TickKeyRepeats(t0 + 600ms);
|
||
|
|
Check(downs == 1 && texts == 1, "one repeat at the delay, not a burst of fifteen");
|
||
|
|
Device::TickKeyRepeats(t0 + 620ms);
|
||
|
|
Check(downs == 1, "none before the next period");
|
||
|
|
Device::TickKeyRepeats(t0 + 640ms);
|
||
|
|
Check(downs == 2, "one per period from there");
|
||
|
|
Device::TickKeyRepeats(t0 + 680ms);
|
||
|
|
Device::TickKeyRepeats(t0 + 720ms);
|
||
|
|
Check(downs == 4, "a tick per period fires once each");
|
||
|
|
|
||
|
|
// Frames stalled for a second: a few catch up, the rest of the stall is dropped.
|
||
|
|
Device::TickKeyRepeats(t0 + 1720ms);
|
||
|
|
Check(downs == 4 + kKeyRepeatCatchUp, "a stall catches up by a few repeats, not one per missed period");
|
||
|
|
Device::TickKeyRepeats(t0 + 1730ms);
|
||
|
|
Check(downs == 4 + kKeyRepeatCatchUp, "and the stall's remainder is dropped");
|
||
|
|
Device::TickKeyRepeats(t0 + 1760ms);
|
||
|
|
Check(downs == 5 + kKeyRepeatCatchUp, "the next period after the stall fires once");
|
||
|
|
|
||
|
|
// A key with no text (Backspace): the key event alone.
|
||
|
|
downs = texts = 0;
|
||
|
|
Press(14, "", 25, 500, t0);
|
||
|
|
Device::TickKeyRepeats(t0 + 500ms);
|
||
|
|
Check(downs == 1 && texts == 0, "a key without text repeats the key alone");
|
||
|
|
|
||
|
|
// A rate above 1000 a second: a period of a millisecond, no endless loop.
|
||
|
|
downs = 0;
|
||
|
|
Press(14, "", 2000, 100, t0);
|
||
|
|
Device::TickKeyRepeats(t0 + 100ms);
|
||
|
|
Check(downs == 1, "a rate above 1000 repeats once at the delay");
|
||
|
|
Device::TickKeyRepeats(t0 + 200ms);
|
||
|
|
Check(downs == 1 + kKeyRepeatCatchUp, "and a tick late by many periods fires the cap, not forever");
|
||
|
|
|
||
|
|
// Released: nothing more.
|
||
|
|
Device::keyRepeat.active = false;
|
||
|
|
Device::TickKeyRepeats(t0 + 300ms);
|
||
|
|
Check(downs == 1 + kKeyRepeatCatchUp, "a released key repeats no more");
|
||
|
|
|
||
|
|
// A compositor that disables repeat (rate 0).
|
||
|
|
Press(14, "", 0, 100, t0);
|
||
|
|
Device::TickKeyRepeats(t0 + 1000ms);
|
||
|
|
Check(downs == 1 + kKeyRepeatCatchUp, "rate 0 is no repeat");
|
||
|
|
|
||
|
|
Device::keyboardWindow = nullptr;
|
||
|
|
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||
|
|
}
|