1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
/*
* Copyright © Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "src/server/input/default_event_builder.h"
#include "src/server/shell/basic_hover_click_transformer.h"
#include <mir/test/doubles/advanceable_clock.h>
#include <mir/test/doubles/queued_alarm_stub_main_loop.h>
#include <mir/test/fake_shared.h>
#include <mir/input/cursor_observer_multiplexer.h>
#include <mir/geometry/displacement.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace ::testing;
using namespace std::chrono_literals;
namespace mt = mir::test;
namespace mtd = mt::doubles;
namespace
{
constexpr static auto test_hover_delay = 500ms;
constexpr static auto test_cancel_displacement_threshold = 10;
constexpr static auto grace_period_percentage = 0.1f;
}
// ObserverMultiplexers require `spawn` to actually be implemented...
struct ImmediatelySpawningQueuedAlarmStubMainLoop : public virtual mtd::QueuedAlarmStubMainLoop
{
// Deleage to the other ctor, we should use this more...
using mtd::QueuedAlarmStubMainLoop::QueuedAlarmStubMainLoop;
void spawn(std::function<void()>&& work) override { work(); }
};
struct TestHoverClickTransformer : Test
{
TestHoverClickTransformer()
{
transformer->hover_duration(test_hover_delay);
transformer->cancel_displacement_threshold(test_cancel_displacement_threshold);
}
mtd::AdvanceableClock clock;
mtd::QueuedAlarmStubMainLoop main_loop{mt::fake_shared(clock)};
mir::input::CursorObserverMultiplexer cursor_observer_multiplexer{mir::immediate_executor};
std::shared_ptr<mir::shell::HoverClickTransformer> const transformer{
std::make_shared<mir::shell::BasicHoverClickTransformer>(
mt::fake_shared(main_loop), mt::fake_shared(cursor_observer_multiplexer))};
mir::input::DefaultEventBuilder event_builder{0, mt::fake_shared(clock)};
std::function<void(std::shared_ptr<MirEvent> const&)> dispatcher{[](auto) { }};
mir::geometry::PointF pointer_location{0, 0};
mir::EventUPtr motion_event(mir::geometry::DisplacementF displacement)
{
pointer_location += displacement;
return event_builder.pointer_event(
std::nullopt,
mir_pointer_action_motion,
0,
pointer_location,
displacement,
mir_pointer_axis_source_none,
mir::events::ScrollAxisV1H{},
mir::events::ScrollAxisV1V{});
}
// If the event is not handled by the transformer, we need to pass it on.
// In this case, it's enough to pass it to the cursor listener multiplexer
// so it notifies that cursor listener inside the hover click transformer,
// which helps track the cursor position for cancellation.
void seat_event_dispatch_mock(auto event)
{
if (!transformer->transform_input_event(dispatcher, &event_builder, *event))
{
cursor_observer_multiplexer.cursor_moved_to(pointer_location.x.as_value(), pointer_location.y.as_value());
}
}
};
struct TestStartCallback: public TestHoverClickTransformer, public WithParamInterface<float>
{
};
TEST_P(TestStartCallback, start_event_called_only_after_grace_period)
{
// How long to wait relative to the hover click delay
auto const delay_percentage = GetParam();
// 0.1 is the hardcoded grace period in the basic transformer
auto const expect_start_called = delay_percentage >= grace_period_percentage;
std::atomic<bool> hover_started;
transformer->on_hover_start([&hover_started] { hover_started = true; });
auto initial_pointer_move_event = motion_event(mir::geometry::DisplacementF{10, 0});
seat_event_dispatch_mock(std::move(initial_pointer_move_event));
// Hover click is active now
// If we move before the grace period, no start callback will be called
// Otherwise, a start event will be called.
// If we move after that, a cancel event will be calld.
clock.advance_by(std::chrono::duration_cast<std::chrono::milliseconds>(delay_percentage * test_hover_delay));
main_loop.call_queued();
EXPECT_THAT(hover_started, Eq(expect_start_called));
}
INSTANTIATE_TEST_SUITE_P(
TestHoverClickTransformer, TestStartCallback, ::Values(0.99 * grace_period_percentage, grace_period_percentage));
struct TestCancelCallback: public TestHoverClickTransformer, public WithParamInterface<float>
{
};
TEST_P(TestCancelCallback, cancel_called_if_pointer_moves_before_hover_delay)
{
// How long to wait relative to the hover click delay
auto const delay_percentage = GetParam();
std::atomic<bool> hover_cancelled{false};
transformer->on_hover_cancel([&hover_cancelled] { hover_cancelled = true; });
std::atomic<bool> click_dipatched{false};
transformer->on_click_dispatched([&click_dipatched] { click_dipatched = true; });
// Invoke hover click
auto initial_pointer_move_event = motion_event(mir::geometry::DisplacementF{10, 0});
seat_event_dispatch_mock(std::move(initial_pointer_move_event));
clock.advance_by(std::chrono::duration_cast<std::chrono::milliseconds>(grace_period_percentage * test_hover_delay));
main_loop.call_queued();
// Have to split into two because one alarm kicks off the other
// By this point, the "real" hover click alarm should be enqueued
auto const remaining_percentage = std::clamp(delay_percentage * (1.0 - grace_period_percentage), 0.0, 1.0);
clock.advance_by(std::chrono::duration_cast<std::chrono::milliseconds>(remaining_percentage * test_hover_delay));
main_loop.call_queued();
auto potentially_cancelling_motion_event =
motion_event(mir::geometry::DisplacementF{test_cancel_displacement_threshold + 1, 0});
seat_event_dispatch_mock(std::move(potentially_cancelling_motion_event));
main_loop.call_queued();
EXPECT_THAT(hover_cancelled, Eq(delay_percentage < 1.0));
EXPECT_THAT(click_dipatched, Eq(delay_percentage >= 1.0));
}
INSTANTIATE_TEST_SUITE_P(TestHoverClickTransformer, TestCancelCallback, ::Values(0.95, 1.01, 1.1));
|