Mir

Merge lp:~mir-team/mir/cursor-spike-phase-4-implement-api into lp:mir

Proposed by Robert Carr
Status: Merged
Approved by: Robert Carr
Approved revision: no longer in the source branch.
Merged at revision: 1717
Proposed branch: lp:~mir-team/mir/cursor-spike-phase-4-implement-api
Merge into: lp:mir
Prerequisite: lp:~mir-team/mir/cursor-spike-phase-3-wire-surface-data
Diff against target: 2029 lines (+1172/-415)
27 files modified
include/client/mir_toolkit/mir_cursor_configuration.h (+2/-11)
include/server/mir/input/input_targets.h (+8/-0)
include/server/mir/input/surface.h (+11/-0)
include/server/mir/scene/surface.h (+1/-1)
include/shared/mir_toolkit/common.h (+11/-0)
include/test/mir_test_doubles/mock_input_surface.h (+1/-14)
include/test/mir_test_doubles/stub_input_surface.h (+2/-0)
include/test/mir_test_doubles/stub_input_targets.h (+6/-0)
include/test/mir_test_doubles/stub_scene_surface.h (+1/-1)
include/test/mir_test_framework/stubbed_server_configuration.h (+2/-0)
src/client/cursor_configuration.h (+2/-0)
src/client/mir_cursor_api.cpp (+8/-5)
src/client/mir_surface.cpp (+1/-1)
src/server/default_server_configuration.cpp (+0/-24)
src/server/graphics/default_configuration.cpp (+3/-1)
src/server/input/CMakeLists.txt (+1/-0)
src/server/input/cursor_controller.cpp (+244/-0)
src/server/input/cursor_controller.h (+77/-0)
src/server/input/default_configuration.cpp (+11/-0)
src/server/scene/basic_surface.cpp (+5/-2)
src/server/scene/basic_surface.h (+1/-1)
tests/acceptance-tests/test_client_cursor_api.cpp (+357/-350)
tests/mir_test_framework/input_testing_server_options.cpp (+1/-3)
tests/mir_test_framework/stubbed_server_configuration.cpp (+15/-1)
tests/unit-tests/input/CMakeLists.txt (+1/-0)
tests/unit-tests/input/android/test_android_input_target_enumerator.cpp (+9/-0)
tests/unit-tests/input/test_cursor_controller.cpp (+391/-0)
To merge this branch: bzr merge lp:~mir-team/mir/cursor-spike-phase-4-implement-api
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Andreas Pokorny (community) Approve
Alan Griffiths Approve
Kevin DuBois (community) Approve
Alberto Aguirre Pending
Review via email: mp+220108@code.launchpad.net

Commit message

Enable client cursor API.

Description of the change

Enable the cursor API.

Remaining phases:
5 - XCursor loader
6 - Demo client

Diff will shrink once
https://code.launchpad.net/~mir-team/mir/cursor-spike-lightning-round-fix-input-area-contains/+merge/220102

lands

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

looks good overall, just nits really:

361, 423-425
could be private

416/417 indentation

CursorController seems more like a CursorState. It listens for events and changes the state accordingly.

we could get rid of returning nullptr in topmost_surface_containing_point() (and the variable top_surface_at_point would still make the algorithm quickly comprehendable at first glance)

review: Needs Fixing
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

A few nits, and I think some left-overs that you do not need -> Needs Fixing.
There are some Information Needed questions inside the inline comments.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

   Start 21: mir_acceptance_tests.TestClientCursorAPI.*
Build timed out (after 120 minutes). Marking the build as failed.

I concur - this test hangs.

review: Needs Fixing
Revision history for this message
Robert Carr (robertcarr) wrote :

Things now not present:

anpoks nits
deadlock (removed lock scope {} in a merge error)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Robert Carr (robertcarr) wrote :

Fixed conflicts

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

299 +struct SurfaceObserver : ms::SurfaceObserver

"SurfaceObserver" is a bad name - it doesn't indicate why the class exists. "UpdateCursorOnSurfaceChanges"?

~~~~

301 + SurfaceObserver(std::function<void()> const& update_cursor)

I don't see the advantage of "std::function<void()> const& update_cursor" over CursorController* - this class is buried in an implementation file so there is only abstraction cost here.

~~~~

436 + auto strong_observer = std::make_shared<Observer>([&]()
437 + {
438 + std::lock_guard<std::mutex> lg(cursor_state_guard);
439 + update_cursor_image_locked(lg);
440 + });

I'd prefer to see a lambda that didn't manipulate internal state; Vis

    auto strong_observer = std::make_shared<Observer>([&]{ update_cursor_image(); });

But, given the above comments, we probably don't need a lambda at all. Just:

    auto strong_observer = std::make_shared<UpdateCursorOnSceneChanges>(this);

~~~~

20 + virtual void add_observer(std::shared_ptr<scene::Observer> const& observer) = 0;
21 + virtual void remove_observer(std::weak_ptr<scene::Observer> const& observer) = 0;

It is unintuitive (to say the least) that scene::Observer observes input::InputTargets. (Yes, I know that these functions are implemented in scene::SurfaceStack - so this is probably best left until we get to cleaning up that part of scene.)

review: Needs Fixing
Revision history for this message
Robert Carr (robertcarr) wrote :

Alan: Addressed your suggestions.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Failure looks spurious. Rebuilding

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Broken chroot tarball. Rebuilding.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

could be private:
405 + mi::CursorController* const cursor_controller;
406 +
407 + std::mutex surface_observers_guard;
408 + std::map<ms::Surface*, std::weak_ptr<ms::SurfaceObserver>> surface_observers;

should guard against exceptions:
441 +mi::CursorController::~CursorController()
442 +{
443 + input_targets->remove_observer(observer);
444 +}

needed?
484 + assert(input_targets);

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

152 char const *const mir_default_cursor_name = "default";
237 + return the_cursor_images()->image("default", default_cursor_size);
1575 +std::string const default_cursor_name = "default";

I assume these string constants all need to be the same. Can't we use a single constant?

~~~~

164 + std::unique_ptr<MirCursorConfiguration> c(new MirCursorConfiguration);
165 + c->name = name ? std::string(name) : std::string();
166
167 - return c;
168 + return c.release();

Wouldn't a constructor that took a char* be a cleaner way to initialize the c?

review: Needs Fixing
Revision history for this message
Robert Carr (robertcarr) wrote :

kdub:

>> could be private:
Now private!

>> should guard against exceptions:
Good catch!

>> needed?
>> 484 + assert(input_targets);
Nah, left over from an earlier version which had two phase initialization.

Alan:
>> I assume these string constants all need to be the same. Can't we use a single constant?

Consolidated!
~~~~
>> Wouldn't a constructor that took a char* be a cleaner way to initialize the c?

Handling it this way now.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

A lot of nits:

222 +MirCursorConfiguration::MirCursorConfiguration(char const* s_name)
223 +{
224 + s_name ? name = std::string(s_name) : name = std::string();
225 +}

MirCursorConfiguration::MirCursorConfiguration(char const* name) :
    name{name ? std::string(s_name) : std::string()}
{
}

~~~~

235 + std::unique_ptr<MirCursorConfiguration> c(new MirCursorConfiguration(name));
236
237 - return c;
238 + return c.release();

return new MirCursorConfiguration(name);

~~~~

263 +

Why?

~~~~

434 + auto observer = std::make_shared<UpdateCursorOnSurfaceChanges>(cursor_controller);

auto const?

~~~~

494 + std::shared_ptr<mi::Surface> top_surface_at_point = nullptr;

std::shared_ptr<mi::Surface> top_surface_at_point;

~~~~

523 + try
524 + {
525 + input_targets->remove_observer(observer);
526 + }
527 + catch (...)
528 + {
529 + }

I don't expect input_targets->remove_observer(observer) to throw.

If removing observer does throw then surely things are so broken that we should stop execution. Not eat the exception!

~~~~

730 #include "mir_test_framework/display_server_test_fixture.h"

Unused. (I suspect a whole bunch more includes are unused - e.g. #include "mir/compositor/scene.h")

~~~~

1017 +struct DeferredInProcessServer : mtf::InProcessServer
1018 +{
1019 + void SetUp() override { /* TODO this is a nasty frig around the unfortunate coupling in InProcessServer */ }
1020 + void start_server() { mtf::InProcessServer::SetUp(); }
1021 +};

You might like to update to match test_client_input.cpp (or even put somewhere common).

struct DeferredInProcessServer : testing::Test, private mtf::ServerRunner
{
    void TearDown() override { ServerRunner::stop_server(); }

    using ServerRunner::start_server;
    using ServerRunner::new_connection;
};

~~~~

2001 + StubInputSurface surface1{geom::Rectangle{geom::Point{geom::X{0}, geom::Y{0}},
2002 + geom::Size{geom::Width{1}, geom::Height{1}}},
2003 + image};
2004 + StubInputSurface surface2{geom::Rectangle{geom::Point{geom::X{0}, geom::Y{0}},
2005 + geom::Size{geom::Width{1}, geom::Height{1}}},
2006 + image};

Alignment is wrong. (That's why I don't like aligning to the open brace.)

Also, I'm pretty sure most of those explicit constructions are unnecessary.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Robert Carr (robertcarr) wrote :

Nits addressed. updated to look like test client input.

Revision history for this message
Kevin DuBois (kdub) wrote :

okay, once the nitfixes suggested by Alan are pushed

review: Approve
Revision history for this message
Robert Carr (robertcarr) wrote :

Pushed! somehow my SSH password saver is just totally broken.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> FAILED: Continuous integration, rev:1552
> http://jenkins.qa.ubuntu.com/job/mir-team-mir-development-branch-ci/1919/
> Executed test runs:
> FAILURE: http://jenkins.qa.ubuntu.com/job/mir-android-
> utopic-i386-build/591/console
> FAILURE: http://jenkins.qa.ubuntu.com/job/mir-clang-utopic-
> amd64-build/597/console
> FAILURE: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-utopic-
> touch/590/console
> FAILURE: http://jenkins.qa.ubuntu.com/job/mir-team-mir-development-branch-
> utopic-amd64-ci/440/console
> FAILURE: http://jenkins.qa.ubuntu.com/job/mir-team-mir-development-branch-
> utopic-armhf-ci/437/console
> FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-
> utopic-armhf/1707/console
>
> Click here to trigger a rebuild:
> http://s-jenkins.ubuntu-ci:8080/job/mir-team-mir-development-branch-
> ci/1919/rebuild

bzr: ERROR: Conflicts from merge

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

203 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
204 +o * along with this program. If not, see <http://www.gnu.org/licenses/>.

:)

review: Needs Fixing
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

1912 + StubInputSurface surface{geom::Rectangle{geom::Point{geom::X{1}, geom::Y{1}},
1913 + geom::Size{geom::Width{1}, geom::Height{1}}},
1914 + nullptr};

There's a lot of verbose stuff repeated in the test bodies that could be shifted to the fixture. (Making the tests easier to read.)

E.g.

static geom::Rectangle const rect_1_1_1_1{{1, 1}, {1, 1}};
StubInputSurface surface_1_1_1_1{rect_1_1_1_1, nullptr};

Revision history for this message
Robert Carr (robertcarr) wrote :

Thanks alan. Fixed stray o. Made the tests a bit easier to read...factored out some strings in addition to the rects.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

OK

review: Approve
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

Ok - I still do not like that extra book keeping of surfaces and observers to surfaces are necessary necessary. But that is something to address later.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/client/mir_toolkit/mir_cursor_configuration.h'
2--- include/client/mir_toolkit/mir_cursor_configuration.h 2014-05-14 16:50:03 +0000
3+++ include/client/mir_toolkit/mir_cursor_configuration.h 2014-06-23 02:58:32 +0000
4@@ -18,6 +18,8 @@
5 #ifndef MIR_TOOLKIT_MIR_CURSOR_H_
6 #define MIR_TOOLKIT_MIR_CURSOR_H_
7
8+#include <mir_toolkit/common.h>
9+
10 /**
11 * Opaque structure containing cursor parameterization. Create with mir_cursor* family.
12 * Used with mir_surface_configure_cursor.
13@@ -33,17 +35,6 @@
14 #endif
15
16 /**
17- * A special cursor name for use with mir_cursor_configuration_from_name
18- * representing the system default cursor.
19- */
20-extern char const *const mir_default_cursor_name;
21-/**
22- * A special cursor name for use with mir_cursor_configuration_from_name
23- * representing a disabled cursor image.
24- */
25-extern char const *const mir_disabled_cursor_name;
26-
27-/**
28 * Release resources assosciated with cursor parameters
29 * \param [in] parameters The operand
30 */
31
32=== modified file 'include/server/mir/input/input_targets.h'
33--- include/server/mir/input/input_targets.h 2014-04-24 08:42:12 +0000
34+++ include/server/mir/input/input_targets.h 2014-06-23 02:58:32 +0000
35@@ -26,6 +26,11 @@
36
37 namespace mir
38 {
39+namespace scene
40+{
41+class Observer;
42+}
43+
44 namespace input
45 {
46 class Surface;
47@@ -37,6 +42,9 @@
48
49 virtual void for_each(std::function<void(std::shared_ptr<input::Surface> const&)> const& callback) = 0;
50
51+ virtual void add_observer(std::shared_ptr<scene::Observer> const& observer) = 0;
52+ virtual void remove_observer(std::weak_ptr<scene::Observer> const& observer) = 0;
53+
54 protected:
55 InputTargets() = default;
56 InputTargets(InputTargets const&) = delete;
57
58=== modified file 'include/server/mir/input/surface.h'
59--- include/server/mir/input/surface.h 2014-06-03 11:04:15 +0000
60+++ include/server/mir/input/surface.h 2014-06-23 02:58:32 +0000
61@@ -28,6 +28,16 @@
62
63 namespace mir
64 {
65+namespace graphics
66+{
67+class CursorImage;
68+}
69+
70+namespace scene
71+{
72+class SurfaceObserver;
73+}
74+
75 namespace input
76 {
77 class InputChannel;
78@@ -39,6 +49,7 @@
79 virtual geometry::Rectangle input_bounds() const = 0;
80 virtual bool input_area_contains(geometry::Point const& point) const = 0;
81 virtual std::shared_ptr<input::InputChannel> input_channel() const = 0;
82+ virtual std::shared_ptr<graphics::CursorImage> cursor_image() const = 0;
83 virtual InputReceptionMode reception_mode() const = 0;
84
85 protected:
86
87=== modified file 'include/server/mir/scene/surface.h'
88--- include/server/mir/scene/surface.h 2014-06-19 11:35:40 +0000
89+++ include/server/mir/scene/surface.h 2014-06-23 02:58:32 +0000
90@@ -74,7 +74,7 @@
91 virtual void force_requests_to_complete() = 0;
92
93 virtual void set_cursor_image(std::shared_ptr<graphics::CursorImage> const& image) = 0;
94- virtual std::shared_ptr<graphics::CursorImage> cursor_image() = 0;
95+ virtual std::shared_ptr<graphics::CursorImage> cursor_image() const = 0;
96
97 virtual void add_observer(std::shared_ptr<SurfaceObserver> const& observer) = 0;
98 virtual void remove_observer(std::weak_ptr<SurfaceObserver> const& observer) = 0;
99
100=== modified file 'include/shared/mir_toolkit/common.h'
101--- include/shared/mir_toolkit/common.h 2014-06-20 12:05:55 +0000
102+++ include/shared/mir_toolkit/common.h 2014-06-23 02:58:32 +0000
103@@ -133,6 +133,17 @@
104 mir_orientation_right = 270
105 } MirOrientation;
106
107+/**
108+ * A special cursor name for use with mir_cursor_configuration_from_name
109+ * representing the system default cursor.
110+ */
111+extern char const *const mir_default_cursor_name;
112+/**
113+ * A special cursor name for use with mir_cursor_configuration_from_name
114+ * representing a disabled cursor image.
115+ */
116+extern char const *const mir_disabled_cursor_name;
117+
118 /**@}*/
119
120 #endif
121
122=== modified file 'include/test/mir_test_doubles/mock_input_surface.h'
123--- include/test/mir_test_doubles/mock_input_surface.h 2014-06-03 11:04:15 +0000
124+++ include/test/mir_test_doubles/mock_input_surface.h 2014-06-23 02:58:32 +0000
125@@ -32,25 +32,12 @@
126 class MockInputSurface : public input::Surface
127 {
128 public:
129- MockInputSurface()
130- {
131- using namespace ::testing;
132- ON_CALL(*this, input_bounds())
133- .WillByDefault(Return(geometry::Rectangle()));
134- static std::string n;
135- ON_CALL(*this, name())
136- .WillByDefault(Return(n));
137- static std::shared_ptr<input::InputChannel> c{nullptr};
138- ON_CALL(*this, input_channel())
139- .WillByDefault(Return(c));
140- ON_CALL(*this, reception_mode())
141- .WillByDefault(Return(input::InputReceptionMode::normal));
142- }
143 ~MockInputSurface() noexcept {}
144 MOCK_CONST_METHOD0(name, std::string());
145 MOCK_CONST_METHOD0(input_bounds, geometry::Rectangle());
146 MOCK_CONST_METHOD1(input_area_contains, bool(geometry::Point const&));
147 MOCK_CONST_METHOD0(input_channel, std::shared_ptr<input::InputChannel>());
148+ MOCK_CONST_METHOD0(cursor_image, std::shared_ptr<graphics::CursorImage>());
149 MOCK_CONST_METHOD0(reception_mode, input::InputReceptionMode());
150 };
151
152
153=== modified file 'include/test/mir_test_doubles/stub_input_surface.h'
154--- include/test/mir_test_doubles/stub_input_surface.h 2014-05-14 16:50:03 +0000
155+++ include/test/mir_test_doubles/stub_input_surface.h 2014-06-23 02:58:32 +0000
156@@ -50,6 +50,8 @@
157 std::string name() const { return {}; }
158 mir::geometry::Rectangle input_bounds() const override { return {{},{}}; }
159 bool input_area_contains(mir::geometry::Point const&) const { return false; }
160+
161+ std::shared_ptr<graphics::CursorImage> cursor_image() const { return nullptr; }
162
163 std::shared_ptr<mir::input::InputChannel> const channel;
164 };
165
166=== modified file 'include/test/mir_test_doubles/stub_input_targets.h'
167--- include/test/mir_test_doubles/stub_input_targets.h 2014-04-01 22:48:55 +0000
168+++ include/test/mir_test_doubles/stub_input_targets.h 2014-06-23 02:58:32 +0000
169@@ -33,6 +33,12 @@
170 void for_each(std::function<void(std::shared_ptr<input::Surface> const&)> const& ) override
171 {
172 }
173+ void add_observer(std::shared_ptr<scene::Observer> const& /* observer */)
174+ {
175+ }
176+ void remove_observer(std::weak_ptr<scene::Observer> const& /* observer */)
177+ {
178+ }
179 };
180
181 }
182
183=== modified file 'include/test/mir_test_doubles/stub_scene_surface.h'
184--- include/test/mir_test_doubles/stub_scene_surface.h 2014-06-19 11:35:40 +0000
185+++ include/test/mir_test_doubles/stub_scene_surface.h 2014-06-23 02:58:32 +0000
186@@ -86,7 +86,7 @@
187 void set_reception_mode(input::InputReceptionMode mode) override { input_mode = mode; }
188
189 void set_cursor_image(std::shared_ptr<graphics::CursorImage> const& /* image */) {}
190- std::shared_ptr<graphics::CursorImage> cursor_image() { return {}; }
191+ std::shared_ptr<graphics::CursorImage> cursor_image() const { return {}; }
192
193 MirPixelFormat pixel_format() const override { return mir_pixel_format_xrgb_8888; }
194
195
196=== modified file 'include/test/mir_test_framework/stubbed_server_configuration.h'
197--- include/test/mir_test_framework/stubbed_server_configuration.h 2014-06-17 11:21:31 +0000
198+++ include/test/mir_test_framework/stubbed_server_configuration.h 2014-06-23 02:58:32 +0000
199@@ -47,6 +47,8 @@
200 std::shared_ptr<input::InputDispatcher> the_input_dispatcher() override;
201 std::shared_ptr<shell::InputTargeter> the_input_targeter() override;
202
203+ std::shared_ptr<graphics::Cursor> the_cursor() override;
204+
205 private:
206 std::shared_ptr<graphics::Platform> graphics_platform;
207 std::vector<geometry::Rectangle> const display_rects;
208
209=== modified file 'src/client/cursor_configuration.h'
210--- src/client/cursor_configuration.h 2014-05-14 16:50:03 +0000
211+++ src/client/cursor_configuration.h 2014-06-23 02:58:32 +0000
212@@ -25,6 +25,8 @@
213 // Will grow to include cursors specified by raw RGBA data, hotspots, etc...
214 struct MirCursorConfiguration
215 {
216+ MirCursorConfiguration(char const* name);
217+
218 std::string name;
219 };
220
221
222=== modified file 'src/client/mir_cursor_api.cpp'
223--- src/client/mir_cursor_api.cpp 2014-05-14 16:50:03 +0000
224+++ src/client/mir_cursor_api.cpp 2014-06-23 02:58:32 +0000
225@@ -19,9 +19,15 @@
226 #include "mir_toolkit/mir_cursor_configuration.h"
227 #include "cursor_configuration.h"
228
229+#include <memory>
230+
231 char const *const mir_default_cursor_name = "default";
232-char const *const mir_disabled_cursor_name = nullptr;
233+char const *const mir_disabled_cursor_name = "disabled";
234
235+MirCursorConfiguration::MirCursorConfiguration(char const* name) :
236+ name{name ? name : std::string()}
237+{
238+}
239
240 void mir_cursor_configuration_destroy(MirCursorConfiguration *cursor)
241 {
242@@ -32,10 +38,7 @@
243 {
244 try
245 {
246- auto c = new MirCursorConfiguration;
247- c->name = std::string(name);
248-
249- return c;
250+ return new MirCursorConfiguration(name);
251 }
252 catch (...)
253 {
254
255=== modified file 'src/client/mir_surface.cpp'
256--- src/client/mir_surface.cpp 2014-06-19 16:15:42 +0000
257+++ src/client/mir_surface.cpp 2014-06-23 02:58:32 +0000
258@@ -326,7 +326,7 @@
259 {
260 std::unique_lock<decltype(mutex)> lock(mutex);
261 setting.mutable_surfaceid()->CopyFrom(surface.id());
262- if (cursor->name != "")
263+ if (cursor && cursor->name != mir_disabled_cursor_name)
264 setting.set_name(cursor->name.c_str());
265 }
266
267
268=== modified file 'src/server/default_server_configuration.cpp'
269--- src/server/default_server_configuration.cpp 2014-06-11 16:11:32 +0000
270+++ src/server/default_server_configuration.cpp 2014-06-23 02:58:32 +0000
271@@ -101,30 +101,6 @@
272 });
273 }
274
275-std::shared_ptr<mi::CursorListener>
276-mir::DefaultServerConfiguration::the_cursor_listener()
277-{
278- struct DefaultCursorListener : mi::CursorListener
279- {
280- DefaultCursorListener(std::shared_ptr<mg::Cursor> const& cursor) :
281- cursor(cursor)
282- {
283- }
284-
285- void cursor_moved_to(float abs_x, float abs_y)
286- {
287- cursor->move_to(geom::Point{abs_x, abs_y});
288- }
289-
290- std::shared_ptr<mg::Cursor> const cursor;
291- };
292- return cursor_listener(
293- [this]() -> std::shared_ptr<mi::CursorListener>
294- {
295- return std::make_shared<DefaultCursorListener>(the_cursor());
296- });
297-}
298-
299 std::shared_ptr<ms::SurfaceConfigurator> mir::DefaultServerConfiguration::the_surface_configurator()
300 {
301 struct DefaultSurfaceConfigurator : public ms::SurfaceConfigurator
302
303=== modified file 'src/server/graphics/default_configuration.cpp'
304--- src/server/graphics/default_configuration.cpp 2014-06-10 14:40:23 +0000
305+++ src/server/graphics/default_configuration.cpp 2014-06-23 02:58:32 +0000
306@@ -34,6 +34,8 @@
307 #include "mir/abnormal_exit.h"
308 #include "mir/emergency_cleanup.h"
309
310+#include "mir_toolkit/common.h"
311+
312 #include <boost/throw_exception.hpp>
313
314 #include "builtin_cursor_images.h"
315@@ -138,7 +140,7 @@
316 return default_cursor_image(
317 [this]()
318 {
319- return the_cursor_images()->image("arrow", default_cursor_size);
320+ return the_cursor_images()->image(mir_default_cursor_name, default_cursor_size);
321 });
322 }
323
324
325=== modified file 'src/server/input/CMakeLists.txt'
326--- src/server/input/CMakeLists.txt 2014-06-02 17:07:02 +0000
327+++ src/server/input/CMakeLists.txt 2014-06-23 02:58:32 +0000
328@@ -10,6 +10,7 @@
329 nested_input_configuration.cpp
330 display_input_region.cpp
331 vt_filter.cpp
332+ cursor_controller.cpp
333 default_configuration.cpp
334 )
335
336
337=== added file 'src/server/input/cursor_controller.cpp'
338--- src/server/input/cursor_controller.cpp 1970-01-01 00:00:00 +0000
339+++ src/server/input/cursor_controller.cpp 2014-06-23 02:58:32 +0000
340@@ -0,0 +1,244 @@
341+/*
342+ * Copyright © 2014 Canonical Ltd.
343+ *
344+ * This program is free software: you can redistribute it and/or modify it
345+ * under the terms of the GNU General Public License version 3,
346+ * as published by the Free Software Foundation.
347+ *
348+ * This program is distributed in the hope that it will be useful,
349+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
350+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
351+ * GNU General Public License for more details.
352+ *
353+ * You should have received a copy of the GNU General Public License
354+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
355+ *
356+ * Authored by: Robert Carr <robert.carr@canonical.com>
357+ */
358+
359+#include "cursor_controller.h"
360+
361+#include "mir/input/input_targets.h"
362+#include "mir/input/surface.h"
363+#include "mir/graphics/cursor.h"
364+#include "mir/scene/observer.h"
365+#include "mir/scene/surface_observer.h"
366+#include "mir/scene/surface.h"
367+
368+#include <functional>
369+#include <mutex>
370+#include <map>
371+
372+#include <assert.h>
373+
374+namespace mi = mir::input;
375+namespace mg = mir::graphics;
376+namespace ms = mir::scene;
377+namespace geom = mir::geometry;
378+
379+namespace
380+{
381+
382+struct UpdateCursorOnSurfaceChanges : ms::SurfaceObserver
383+{
384+ UpdateCursorOnSurfaceChanges(mi::CursorController* cursor_controller)
385+ : cursor_controller(cursor_controller)
386+ {
387+ }
388+
389+ void attrib_changed(MirSurfaceAttrib, int) override
390+ {
391+ // Attribute changing alone wont trigger a cursor update
392+ }
393+ void resized_to(geom::Size const&) override
394+ {
395+ cursor_controller->update_cursor_image();
396+ }
397+ void moved_to(geom::Point const&) override
398+ {
399+ cursor_controller->update_cursor_image();
400+ }
401+ void hidden_set_to(bool) override
402+ {
403+ cursor_controller->update_cursor_image();
404+ }
405+ void frame_posted(int) override
406+ {
407+ // Frame posting wont trigger a cursor update
408+ }
409+ void alpha_set_to(float) override
410+ {
411+ cursor_controller->update_cursor_image();
412+ }
413+ void transformation_set_to(glm::mat4 const&) override
414+ {
415+ cursor_controller->update_cursor_image();
416+ }
417+ void reception_mode_set_to(mi::InputReceptionMode) override
418+ {
419+ cursor_controller->update_cursor_image();
420+ }
421+ void cursor_image_set_to(mg::CursorImage const&) override
422+ {
423+ cursor_controller->update_cursor_image();
424+ }
425+ void orientation_set_to(MirOrientation /* orientation */) override
426+ {
427+ // No need to update cursor for orientation property change alone.
428+ }
429+
430+ mi::CursorController* const cursor_controller;
431+};
432+
433+struct UpdateCursorOnSceneChanges : ms::Observer
434+{
435+ UpdateCursorOnSceneChanges(mi::CursorController* cursor_controller)
436+ : cursor_controller(cursor_controller)
437+ {
438+ }
439+
440+ void add_surface_observer(ms::Surface* surface)
441+ {
442+ auto const observer = std::make_shared<UpdateCursorOnSurfaceChanges>(cursor_controller);
443+ surface->add_observer(observer);
444+
445+ {
446+ std::unique_lock<decltype(surface_observers_guard)> lg(surface_observers_guard);
447+ surface_observers[surface] = observer;
448+ }
449+ }
450+
451+ void surface_added(ms::Surface *surface)
452+ {
453+ add_surface_observer(surface);
454+ cursor_controller->update_cursor_image();
455+ }
456+ void surface_removed(ms::Surface *surface)
457+ {
458+ {
459+ std::unique_lock<decltype(surface_observers_guard)> lg(surface_observers_guard);
460+ auto it = surface_observers.find(surface);
461+ if (it != surface_observers.end())
462+ {
463+ surface->remove_observer(it->second);
464+ surface_observers.erase(it);
465+ }
466+ }
467+ cursor_controller->update_cursor_image();
468+ }
469+ void surfaces_reordered()
470+ {
471+ cursor_controller->update_cursor_image();
472+ }
473+
474+ void surface_exists(ms::Surface *surface)
475+ {
476+ add_surface_observer(surface);
477+ cursor_controller->update_cursor_image();
478+ }
479+
480+ void end_observation()
481+ {
482+ std::unique_lock<decltype(surface_observers_guard)> lg(surface_observers_guard);
483+ for (auto &kv : surface_observers)
484+ {
485+ auto surface = kv.first;
486+ if (surface)
487+ surface->remove_observer(kv.second);
488+ }
489+ surface_observers.clear();
490+ }
491+
492+private:
493+ mi::CursorController* const cursor_controller;
494+
495+ std::mutex surface_observers_guard;
496+ std::map<ms::Surface*, std::weak_ptr<ms::SurfaceObserver>> surface_observers;
497+};
498+
499+std::shared_ptr<mi::Surface> topmost_surface_containing_point(
500+ std::shared_ptr<mi::InputTargets> const& targets, geom::Point const& point)
501+{
502+ std::shared_ptr<mi::Surface> top_surface_at_point;
503+ targets->for_each([&top_surface_at_point, &point]
504+ (std::shared_ptr<mi::Surface> const& surface)
505+ {
506+ if (surface->input_area_contains(point))
507+ top_surface_at_point = surface;
508+ });
509+ return top_surface_at_point;
510+}
511+
512+}
513+
514+mi::CursorController::CursorController(std::shared_ptr<mi::InputTargets> const& input_targets,
515+ std::shared_ptr<mg::Cursor> const& cursor,
516+ std::shared_ptr<mg::CursorImage> const& default_cursor_image) :
517+ input_targets(input_targets),
518+ cursor(cursor),
519+ default_cursor_image(default_cursor_image),
520+ current_cursor(default_cursor_image)
521+{
522+ // TODO: Add observer could return weak_ptr to eliminate this
523+ // pattern
524+ auto strong_observer = std::make_shared<UpdateCursorOnSceneChanges>(this);
525+ input_targets->add_observer(strong_observer);
526+ observer = strong_observer;
527+}
528+
529+mi::CursorController::~CursorController()
530+{
531+ try
532+ {
533+ input_targets->remove_observer(observer);
534+ }
535+ catch (...)
536+ {
537+ std::terminate();
538+ }
539+}
540+
541+void mi::CursorController::set_cursor_image_locked(std::lock_guard<std::mutex> const&,
542+ std::shared_ptr<mg::CursorImage> const& image)
543+{
544+ if (current_cursor == image)
545+ {
546+ return;
547+ }
548+
549+ current_cursor = image;
550+ if (image)
551+ cursor->show(*image);
552+ else
553+ cursor->hide();
554+}
555+
556+void mi::CursorController::update_cursor_image_locked(std::lock_guard<std::mutex> const& lg)
557+{
558+ auto surface = topmost_surface_containing_point(input_targets, cursor_location);
559+ if (surface)
560+ {
561+ set_cursor_image_locked(lg, surface->cursor_image());
562+ }
563+ else
564+ {
565+ set_cursor_image_locked(lg, default_cursor_image);
566+ }
567+}
568+
569+void mi::CursorController::update_cursor_image()
570+{
571+ std::lock_guard<std::mutex> lg(cursor_state_guard);
572+ update_cursor_image_locked(lg);
573+}
574+
575+void mi::CursorController::cursor_moved_to(float abs_x, float abs_y)
576+{
577+ std::lock_guard<std::mutex> lg(cursor_state_guard);
578+
579+ cursor_location = geom::Point{geom::X{abs_x}, geom::Y{abs_y}};
580+
581+ update_cursor_image_locked(lg);
582+
583+ cursor->move_to(cursor_location);
584+}
585
586=== added file 'src/server/input/cursor_controller.h'
587--- src/server/input/cursor_controller.h 1970-01-01 00:00:00 +0000
588+++ src/server/input/cursor_controller.h 2014-06-23 02:58:32 +0000
589@@ -0,0 +1,77 @@
590+/*
591+ * Copyright © 2014 Canonical Ltd.
592+ *
593+ * This program is free software: you can redistribute it and/or modify it
594+ * under the terms of the GNU General Public License version 3,
595+ * as published by the Free Software Foundation.
596+ *
597+ * This program is distributed in the hope that it will be useful,
598+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
599+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
600+ * GNU General Public License for more details.
601+ *
602+ * You should have received a copy of the GNU General Public License
603+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
604+ *
605+ * Authored by: Robert Carr <robert.carr@canonical.com>
606+ */
607+
608+#ifndef MIR_INPUT_CURSOR_CONTROLLER_H_
609+#define MIR_INPUT_CURSOR_CONTROLLER_H_
610+
611+#include "mir/input/cursor_listener.h"
612+#include "mir/geometry/point.h"
613+
614+#include <memory>
615+#include <mutex>
616+
617+namespace mir
618+{
619+namespace graphics
620+{
621+class Cursor;
622+class CursorImage;
623+}
624+namespace scene
625+{
626+class Observer;
627+}
628+
629+namespace input
630+{
631+class InputTargets;
632+
633+class CursorController : public CursorListener
634+{
635+public:
636+ CursorController(std::shared_ptr<InputTargets> const& input_targets,
637+ std::shared_ptr<graphics::Cursor> const& cursor,
638+ std::shared_ptr<graphics::CursorImage> const& default_cursor_image);
639+ virtual ~CursorController();
640+
641+ void cursor_moved_to(float abs_x, float abs_y);
642+
643+ // Trigger an update of the cursor image without cursor motion, e.g.
644+ // in response to scene changes.
645+ void update_cursor_image();
646+
647+private:
648+ std::shared_ptr<InputTargets> const input_targets;
649+ std::shared_ptr<graphics::Cursor> const cursor;
650+ std::shared_ptr<graphics::CursorImage> const default_cursor_image;
651+
652+ std::mutex cursor_state_guard;
653+ geometry::Point cursor_location;
654+ std::shared_ptr<graphics::CursorImage> current_cursor;
655+
656+ std::weak_ptr<scene::Observer> observer;
657+
658+
659+ void update_cursor_image_locked(std::lock_guard<std::mutex> const&);
660+ void set_cursor_image_locked(std::lock_guard<std::mutex> const&, std::shared_ptr<graphics::CursorImage> const& image);
661+};
662+
663+}
664+}
665+
666+#endif // MIR_INPUT_CURSOR_CONTROLLER_H_
667
668=== modified file 'src/server/input/default_configuration.cpp'
669--- src/server/input/default_configuration.cpp 2014-06-06 10:03:54 +0000
670+++ src/server/input/default_configuration.cpp 2014-06-23 02:58:32 +0000
671@@ -28,6 +28,7 @@
672 #include "event_filter_chain.h"
673 #include "nested_input_configuration.h"
674 #include "null_input_configuration.h"
675+#include "cursor_controller.h"
676 #include "null_input_dispatcher.h"
677 #include "null_input_targeter.h"
678
679@@ -193,4 +194,14 @@
680 return the_input_configuration()->the_input_channel_factory();
681 }
682
683+std::shared_ptr<mi::CursorListener>
684+mir::DefaultServerConfiguration::the_cursor_listener()
685+{
686+ return cursor_listener(
687+ [this]() -> std::shared_ptr<mi::CursorListener>
688+ {
689+ return std::make_shared<mi::CursorController>(the_input_targets(),
690+ the_cursor(), the_default_cursor_image());
691+ });
692
693+}
694
695=== modified file 'src/server/scene/basic_surface.cpp'
696--- src/server/scene/basic_surface.cpp 2014-06-20 12:05:55 +0000
697+++ src/server/scene/basic_surface.cpp 2014-06-23 02:58:32 +0000
698@@ -504,13 +504,16 @@
699
700 void ms::BasicSurface::set_cursor_image(std::shared_ptr<mg::CursorImage> const& image)
701 {
702+ {
703 std::unique_lock<std::mutex> lock(guard);
704 cursor_image_ = image;
705+ }
706
707- observers.cursor_image_set_to(*image);
708+ observers.cursor_image_set_to(*image);
709 }
710
711-std::shared_ptr<mg::CursorImage> ms::BasicSurface::cursor_image()
712+
713+std::shared_ptr<mg::CursorImage> ms::BasicSurface::cursor_image() const
714 {
715 std::unique_lock<std::mutex> lock(guard);
716 return cursor_image_;
717
718=== modified file 'src/server/scene/basic_surface.h'
719--- src/server/scene/basic_surface.h 2014-06-20 12:05:55 +0000
720+++ src/server/scene/basic_surface.h 2014-06-23 02:58:32 +0000
721@@ -140,7 +140,7 @@
722 void show() override;
723
724 void set_cursor_image(std::shared_ptr<graphics::CursorImage> const& image);
725- std::shared_ptr<graphics::CursorImage> cursor_image();
726+ std::shared_ptr<graphics::CursorImage> cursor_image() const;
727
728 void add_observer(std::shared_ptr<SurfaceObserver> const& observer) override;
729 void remove_observer(std::weak_ptr<SurfaceObserver> const& observer) override;
730
731=== modified file 'tests/acceptance-tests/test_client_cursor_api.cpp'
732--- tests/acceptance-tests/test_client_cursor_api.cpp 2014-06-19 00:02:28 +0000
733+++ tests/acceptance-tests/test_client_cursor_api.cpp 2014-06-23 02:58:32 +0000
734@@ -23,7 +23,6 @@
735 #include "mir/scene/surface_factory.h"
736 #include "mir/scene/null_observer.h"
737 #include "mir/scene/null_surface_observer.h"
738-#include "mir/compositor/scene.h"
739
740 #include "mir_toolkit/mir_client_library.h"
741
742@@ -31,6 +30,7 @@
743 #include "mir_test/fake_shared.h"
744 #include "mir_test/event_factory.h"
745 #include "mir_test/wait_condition.h"
746+#include "mir_test_framework/server_runner.h"
747 #include "mir_test_framework/display_server_test_fixture.h"
748 #include "mir_test_framework/input_testing_server_configuration.h"
749 #include "mir_test_framework/input_testing_client_configuration.h"
750@@ -59,7 +59,11 @@
751 MOCK_METHOD1(show, void(mg::CursorImage const&));
752 MOCK_METHOD0(hide, void());
753
754- MOCK_METHOD1(move_to, void(geom::Point));
755+ // We are not interested in mocking the motion in these tests as we
756+ // generate it ourself.
757+ void move_to(geom::Point)
758+ {
759+ }
760 };
761
762 struct NamedCursorImage : public mg::CursorImage
763@@ -101,133 +105,87 @@
764 return cursor_is_named(arg, name);
765 }
766
767-struct CursorSettingClient : mtf::TestingClientConfiguration
768+struct ClientConfig : mtf::TestingClientConfiguration
769 {
770- static std::string const mir_test_socket;
771+ std::string connect_string;
772
773 std::string const client_name;
774
775- mtf::CrossProcessSync set_cursor_complete;
776- mtf::CrossProcessSync client_may_exit;
777+ mt::Barrier& set_cursor_complete;
778+ mt::Barrier& client_may_exit;
779
780- std::function<void(MirSurface*)> const set_cursor;
781+ std::function<void(MirSurface*)> set_cursor;
782
783- CursorSettingClient(std::string const& client_name,
784- mtf::CrossProcessSync const& cursor_ready_fence,
785- mtf::CrossProcessSync const& client_may_exit_fence,
786- std::function<void(MirSurface*)> const& set_cursor)
787+ ClientConfig(std::string const& client_name,
788+ mt::Barrier& cursor_ready_fence,
789+ mt::Barrier& client_may_exit_fence)
790 : client_name(client_name),
791 set_cursor_complete(cursor_ready_fence),
792- client_may_exit(client_may_exit_fence),
793- set_cursor(set_cursor)
794- {
795- }
796+ client_may_exit(client_may_exit_fence)
797+ {
798+ }
799+
800+ virtual void thread_exec()
801+ {
802+ auto connection = mir_connect_sync(connect_string.c_str(),
803+ client_name.c_str());
804+
805+ ASSERT_TRUE(connection != NULL);
806+ MirSurfaceParameters const request_params =
807+ {
808+ client_name.c_str(),
809+ // For this fixture, we force geometry on server side
810+ 0, 0,
811+ mir_pixel_format_abgr_8888,
812+ mir_buffer_usage_hardware,
813+ mir_display_output_id_invalid
814+ };
815+ auto surface = mir_connection_create_surface_sync(connection, &request_params);
816+
817+ set_cursor(surface);
818+ set_cursor_complete.ready();
819+
820+ client_may_exit.ready();
821+
822+ mir_surface_release_sync(surface);
823+ mir_connection_release(connection);
824+ }
825+ void tear_down() { if (thread.joinable()) thread.join(); }
826
827 void exec() override
828 {
829- auto connection = mir_connect_sync(mir_test_socket.c_str(),
830- client_name.c_str());
831-
832- ASSERT_TRUE(connection != NULL);
833- MirSurfaceParameters const request_params =
834- {
835- client_name.c_str(),
836- // For this fixture, we force geometry on server side
837- 0, 0,
838- mir_pixel_format_abgr_8888,
839- mir_buffer_usage_hardware,
840- mir_display_output_id_invalid
841- };
842- auto surface = mir_connection_create_surface_sync(connection, &request_params);
843-
844- set_cursor(surface);
845- set_cursor_complete.signal_ready();
846-
847- client_may_exit.wait_for_signal_ready_for();
848-
849- mir_surface_release_sync(surface);
850- mir_connection_release(connection);
851- }
852-};
853-
854-std::string const CursorSettingClient::mir_test_socket = mtf::test_socket_file();
855-
856-struct MockSurfaceObserver : public ms::NullSurfaceObserver
857-{
858- MOCK_METHOD1(cursor_image_set_to, void(mg::CursorImage const&));
859-};
860-
861-struct SurfaceObserverInstaller : public ms::NullObserver
862-{
863- SurfaceObserverInstaller(std::shared_ptr<ms::SurfaceObserver> const& observer)
864- : observer(observer)
865- {
866- }
867-
868- void surface_added(ms::Surface *surf) override
869- {
870- surf->add_observer(observer);
871- }
872-
873- std::shared_ptr<ms::SurfaceObserver> const observer;
874-};
875-
876-struct SurfaceObservingServerConfiguration : mtf::TestingServerConfiguration
877-{
878- SurfaceObservingServerConfiguration(std::function<void(MockSurfaceObserver&)> const& set_expectations)
879- : set_expectations(set_expectations),
880- observer(std::make_shared<MockSurfaceObserver>())
881- {
882- }
883-
884- void on_start() override
885- {
886- auto scene = the_scene();
887- scene->add_observer(std::make_shared<SurfaceObserverInstaller>(observer));
888-
889- set_expectations(*observer);
890- }
891-
892- std::function<void(MockSurfaceObserver&)> const set_expectations;
893- std::shared_ptr<MockSurfaceObserver> const observer;
894-};
895-
896-typedef unsigned ClientCount;
897-struct CursorTestServerConfiguration : mtf::InputTestingServerConfiguration
898-{
899- std::shared_ptr<ms::PlacementStrategy> placement_strategy;
900- mtf::CrossProcessSync client_ready_fence;
901- mtf::CrossProcessSync client_may_exit_fence;
902- int const number_of_clients;
903-
904- std::function<void(MockCursor&, mt::WaitCondition&)> const expect_cursor_states;
905- std::function<void(CursorTestServerConfiguration*)> const synthesize_cursor_motion;
906-
907+ thread = std::thread([this]{ thread_exec(); });
908+ }
909+private:
910+ std::thread thread;
911+};
912+
913+struct ServerConfiguration : mtf::InputTestingServerConfiguration
914+{
915+ mt::Barrier& cursor_configured_fence;
916+ mt::Barrier& client_may_exit_fence;
917+
918+ std::function<void(MockCursor&, mt::WaitCondition&)> expect_cursor_states;
919+ std::function<void(ServerConfiguration*)> synthesize_cursor_motion;
920+
921+ mtf::SurfaceGeometries client_geometries;
922+ mtf::SurfaceDepths client_depths;
923+
924 MockCursor cursor;
925-
926- CursorTestServerConfiguration(mtf::SurfaceGeometries surface_geometries_by_name,
927- mtf::SurfaceDepths surface_depths_by_name,
928- mtf::CrossProcessSync client_ready_fence,
929- mtf::CrossProcessSync client_may_exit_fence,
930- ClientCount const number_of_clients,
931- std::function<void(MockCursor&, mt::WaitCondition&)> const& expect_cursor_states,
932- std::function<void(CursorTestServerConfiguration*)> const& synthesize_cursor_motion)
933- : placement_strategy(
934- std::make_shared<mtf::DeclarativePlacementStrategy>(InputTestingServerConfiguration::the_placement_strategy(),
935- surface_geometries_by_name, surface_depths_by_name)),
936- client_ready_fence(client_ready_fence),
937- client_may_exit_fence(client_may_exit_fence),
938- number_of_clients(number_of_clients),
939- expect_cursor_states(expect_cursor_states),
940- synthesize_cursor_motion(synthesize_cursor_motion)
941+
942+ ServerConfiguration(mt::Barrier& cursor_configured_fence, mt::Barrier& client_may_exit_fence)
943+ : cursor_configured_fence(cursor_configured_fence),
944+ client_may_exit_fence(client_may_exit_fence)
945 {
946 }
947-
948+
949 std::shared_ptr<ms::PlacementStrategy> the_placement_strategy() override
950 {
951- return placement_strategy;
952+ return std::make_shared<mtf::DeclarativePlacementStrategy>(
953+ InputTestingServerConfiguration::the_placement_strategy(),
954+ client_geometries, client_depths);
955 }
956-
957+
958 std::shared_ptr<mg::Cursor> the_cursor() override
959 {
960 return mt::fake_shared(cursor);
961@@ -241,283 +199,332 @@
962 void inject_input()
963 {
964 using namespace ::testing;
965-
966- for (int i = 1; i < number_of_clients + 1; i++)
967- EXPECT_EQ(i, client_ready_fence.wait_for_signal_ready_for());
968-
969+ cursor_configured_fence.ready();
970+
971 mt::WaitCondition expectations_satisfied;
972-
973+
974 // Clear any states applied during server initialization.
975 Mock::VerifyAndClearExpectations(&cursor);
976 expect_cursor_states(cursor, expectations_satisfied);
977+
978+ // We are only interested in the cursor image changes, not
979+ // the synthetic motion.
980
981 synthesize_cursor_motion(this);
982 expectations_satisfied.wait_for_at_most_seconds(60);
983-
984- EXPECT_CALL(cursor, show(_)).Times(AnyNumber()); // Client shutdown
985- for (int i = 0; i < number_of_clients; i++)
986- client_may_exit_fence.signal_ready();
987- }
988-};
989-
990-}
991-
992-// TODO: A lot of common code setup in these tests could be moved to
993-// a fixture.
994-using TestClientCursorAPI = BespokeDisplayServerTestFixture;
995-
996-TEST_F(TestClientCursorAPI, client_cursor_request_is_made_surface_data)
997-{
998- using namespace ::testing;
999-
1000- static std::string const test_client_name = "1";
1001- static std::string const client_1_cursor = "1";
1002-
1003- static mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
1004-
1005- SurfaceObservingServerConfiguration config([&](MockSurfaceObserver &observer)
1006- {
1007- EXPECT_CALL(observer, cursor_image_set_to(_)).WillOnce(Invoke(
1008- [&](mg::CursorImage const&)
1009- {
1010- client_may_exit_fence.signal_ready();
1011- }));
1012- client_ready_fence.signal_ready();
1013- });
1014- launch_server_process(config);
1015-
1016- CursorSettingClient client1_conf(test_client_name, client_ready_fence, client_may_exit_fence,
1017- [](MirSurface *surface)
1018- {
1019- auto conf = mir_cursor_configuration_from_name(client_1_cursor.c_str());
1020- mir_wait_for(mir_surface_configure_cursor(surface, conf));
1021- mir_cursor_configuration_destroy(conf);
1022- });
1023- launch_client_process(client1_conf);
1024+
1025+ Mock::VerifyAndClearExpectations(&cursor);
1026+
1027+ // Client shutdown
1028+ EXPECT_CALL(cursor, show(_)).Times(AnyNumber());
1029+ EXPECT_CALL(cursor, hide()).Times(AnyNumber());
1030+ client_may_exit_fence.ready();
1031+ }
1032+
1033+};
1034+
1035+struct DeferredInProcessServer : testing::Test, private mtf::ServerRunner
1036+{
1037+ void TearDown() override { ServerRunner::stop_server(); }
1038+
1039+ using ServerRunner::start_server;
1040+ using ServerRunner::new_connection;
1041+};
1042+
1043+struct TestClientCursorAPI : DeferredInProcessServer
1044+{
1045+ std::string const client_name_1 = "1";
1046+ std::string const client_name_2 = "2";
1047+ std::string const client_cursor_1 = "cursor-1";
1048+ std::string const client_cursor_2 = "cursor-2";
1049+
1050+ // Reset to higher values for more clients.
1051+ mt::Barrier cursor_configured_fence{2};
1052+ mt::Barrier client_may_exit_fence{2};
1053+
1054+ ServerConfiguration server_configuration{cursor_configured_fence, client_may_exit_fence};
1055+ mir::DefaultServerConfiguration& server_config() override { return server_configuration; }
1056+
1057+ ClientConfig client_config_1{client_name_1, cursor_configured_fence, client_may_exit_fence};
1058+ ClientConfig client_config_2{client_name_2, cursor_configured_fence, client_may_exit_fence};
1059+
1060+ // Default number allows one client.
1061+ void set_client_count(unsigned count)
1062+ {
1063+ cursor_configured_fence.reset(count + 1);
1064+ client_may_exit_fence.reset(count + 1);
1065+ }
1066+
1067+ void start_server()
1068+ {
1069+ DeferredInProcessServer::start_server();
1070+ server_configuration.exec();
1071+ }
1072+
1073+ void start_client(ClientConfig& config)
1074+ {
1075+ config.connect_string = new_connection();
1076+ config.exec();
1077+ }
1078+
1079+ void TearDown()
1080+ {
1081+ client_config_1.tear_down();
1082+ client_config_2.tear_down();
1083+ server_configuration.on_exit();
1084+ DeferredInProcessServer::TearDown();
1085+ }
1086+};
1087+
1088 }
1089
1090 // In this set we create a 1x1 client surface at the point (1,0). The client requests to disable the cursor
1091 // over this surface. Since the cursor starts at (0,0) we when we move the cursor by (1,0) thus causing it
1092 // to enter the bounds of the first surface, we should observe it being disabled.
1093-// TODO: Enable
1094-TEST_F(TestClientCursorAPI, DISABLED_client_may_disable_cursor_over_surface)
1095+TEST_F(TestClientCursorAPI, client_may_disable_cursor_over_surface)
1096 {
1097 using namespace ::testing;
1098
1099- std::string const test_client_name = "1";
1100- mtf::SurfaceGeometries client_geometries;
1101- client_geometries[test_client_name] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1102- geom::Size{geom::Width{1}, geom::Height{1}}};
1103+ server_configuration.client_geometries[client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1104+ geom::Size{geom::Width{1}, geom::Height{1}}};
1105
1106- mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
1107
1108- CursorTestServerConfiguration server_conf(
1109- client_geometries, mtf::SurfaceDepths(),
1110- client_ready_fence, client_may_exit_fence,
1111- ClientCount{1},
1112- [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1113+ server_configuration.expect_cursor_states = [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1114 {
1115 EXPECT_CALL(cursor, hide()).Times(1)
1116 .WillOnce(mt::WakeUp(&expectations_satisfied));
1117- },
1118- [](CursorTestServerConfiguration *server)
1119+ };
1120+ server_configuration.synthesize_cursor_motion = [](ServerConfiguration *server)
1121 {
1122 server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
1123- });
1124- launch_server_process(server_conf);
1125+ };
1126+ start_server();
1127
1128- CursorSettingClient client_conf(test_client_name, client_ready_fence, client_may_exit_fence,
1129- [](MirSurface *surface)
1130+ client_config_1.set_cursor = [](MirSurface *surface)
1131 {
1132- // Disable cursor
1133- mir_wait_for(mir_surface_configure_cursor(surface,
1134- mir_cursor_configuration_from_name(mir_disabled_cursor_name)));
1135- });
1136- launch_client_process(client_conf);
1137+ auto conf = mir_cursor_configuration_from_name(mir_disabled_cursor_name);
1138+ mir_wait_for(mir_surface_configure_cursor(surface, conf));
1139+ mir_cursor_configuration_destroy(conf);
1140+ };
1141+ start_client(client_config_1);
1142 }
1143
1144-// TODO: Enable
1145-TEST_F(TestClientCursorAPI, DISABLED_cursor_restored_when_leaving_surface)
1146+TEST_F(TestClientCursorAPI, cursor_restored_when_leaving_surface)
1147 {
1148 using namespace ::testing;
1149
1150- std::string const test_client_name = "1";
1151- mtf::SurfaceGeometries client_geometries;
1152- client_geometries[test_client_name] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1153- geom::Size{geom::Width{1}, geom::Height{1}}};
1154+ server_configuration.client_geometries[client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1155+ geom::Size{geom::Width{1}, geom::Height{1}}};
1156
1157 mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
1158
1159- CursorTestServerConfiguration server_conf(
1160- client_geometries, mtf::SurfaceDepths(),
1161- client_ready_fence, client_may_exit_fence,
1162- ClientCount{1},
1163- [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1164+ server_configuration.expect_cursor_states = [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1165 {
1166 InSequence seq;
1167 EXPECT_CALL(cursor, hide()).Times(1);
1168 EXPECT_CALL(cursor, show(DefaultCursorImage())).Times(1)
1169 .WillOnce(mt::WakeUp(&expectations_satisfied));
1170- },
1171- [](CursorTestServerConfiguration *server)
1172+ };
1173+ server_configuration.synthesize_cursor_motion = [](ServerConfiguration *server)
1174 {
1175 server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
1176 server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(2,0));
1177- });
1178- launch_server_process(server_conf);
1179-
1180- CursorSettingClient client_conf(test_client_name, client_ready_fence, client_may_exit_fence,
1181- [](MirSurface *surface)
1182- {
1183- // Disable cursor
1184- mir_wait_for(mir_surface_configure_cursor(surface,
1185- mir_cursor_configuration_from_name(mir_disabled_cursor_name)));
1186- });
1187- launch_client_process(client_conf);
1188-}
1189-
1190-// TODO: Enable
1191-TEST_F(TestClientCursorAPI, DISABLED_cursor_changed_when_crossing_surface_boundaries)
1192-{
1193- using namespace ::testing;
1194-
1195- static std::string const test_client_name_1 = "1";
1196- static std::string const test_client_name_2 = "2";
1197- static std::string const client_1_cursor = test_client_name_1;
1198- static std::string const client_2_cursor = test_client_name_2;
1199-
1200- mtf::SurfaceGeometries client_geometries;
1201- client_geometries[test_client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1202- geom::Size{geom::Width{1}, geom::Height{1}}};
1203- client_geometries[test_client_name_2] = geom::Rectangle{geom::Point{geom::X{2}, geom::Y{0}},
1204- geom::Size{geom::Width{1}, geom::Height{1}}};
1205-
1206- mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
1207-
1208- CursorTestServerConfiguration server_conf(
1209- client_geometries, mtf::SurfaceDepths(),
1210- client_ready_fence, client_may_exit_fence,
1211- ClientCount{2},
1212- [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1213- {
1214- InSequence seq;
1215- EXPECT_CALL(cursor, show(CursorNamed(client_1_cursor))).Times(1);
1216- EXPECT_CALL(cursor, show(CursorNamed(client_2_cursor))).Times(1)
1217- .WillOnce(mt::WakeUp(&expectations_satisfied));
1218- },
1219- [](CursorTestServerConfiguration *server)
1220- {
1221- server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
1222- server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
1223- });
1224- launch_server_process(server_conf);
1225-
1226- CursorSettingClient client1_conf(test_client_name_1, client_ready_fence, client_may_exit_fence,
1227- [](MirSurface *surface)
1228- {
1229- mir_wait_for(mir_surface_configure_cursor(surface, mir_cursor_configuration_from_name(client_1_cursor.c_str())));
1230- });
1231- launch_client_process(client1_conf);
1232- CursorSettingClient client2_conf(test_client_name_2, client_ready_fence, client_may_exit_fence,
1233- [](MirSurface *surface)
1234- {
1235- // Disable cursor
1236- mir_wait_for(mir_surface_configure_cursor(surface, mir_cursor_configuration_from_name(client_2_cursor.c_str())));
1237- });
1238- launch_client_process(client2_conf);
1239-}
1240-
1241-// TODO: Enable
1242-TEST_F(TestClientCursorAPI, DISABLED_cursor_request_taken_from_top_surface)
1243-{
1244- using namespace ::testing;
1245-
1246- static std::string const test_client_name_1 = "1";
1247- static std::string const test_client_name_2 = "2";
1248- static std::string const client_1_cursor = test_client_name_1;
1249- static std::string const client_2_cursor = test_client_name_2;
1250-
1251- mtf::SurfaceGeometries client_geometries;
1252- client_geometries[test_client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1253- geom::Size{geom::Width{1}, geom::Height{1}}};
1254- client_geometries[test_client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1255- geom::Size{geom::Width{1}, geom::Height{1}}};
1256- mtf::SurfaceDepths client_depths;
1257- client_depths[test_client_name_1] = ms::DepthId{0};
1258- client_depths[test_client_name_2] = ms::DepthId{1};
1259-
1260- mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
1261-
1262- CursorTestServerConfiguration server_conf(
1263- client_geometries, client_depths,
1264- client_ready_fence, client_may_exit_fence,
1265- ClientCount{2},
1266- [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1267- {
1268- InSequence seq;
1269- EXPECT_CALL(cursor, show(CursorNamed(client_2_cursor))).Times(1)
1270- .WillOnce(mt::WakeUp(&expectations_satisfied));
1271- },
1272- [](CursorTestServerConfiguration *server)
1273- {
1274- server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
1275- });
1276- launch_server_process(server_conf);
1277-
1278- CursorSettingClient client1_conf(test_client_name_1, client_ready_fence, client_may_exit_fence,
1279- [](MirSurface *surface)
1280- {
1281- mir_wait_for(mir_surface_configure_cursor(surface, mir_cursor_configuration_from_name(client_1_cursor.c_str())));
1282- });
1283- launch_client_process(client1_conf);
1284- CursorSettingClient client2_conf(test_client_name_2, client_ready_fence, client_may_exit_fence,
1285- [](MirSurface *surface)
1286- {
1287- mir_wait_for(mir_surface_configure_cursor(surface, mir_cursor_configuration_from_name(client_1_cursor.c_str())));
1288- });
1289-
1290- launch_client_process(client2_conf);
1291-}
1292-
1293-// TODO: Enable
1294-TEST_F(TestClientCursorAPI, DISABLED_cursor_request_applied_without_cursor_motion)
1295-{
1296- using namespace ::testing;
1297- static std::string const test_client_name_1 = "1";
1298- static std::string const client_1_cursor = test_client_name_1;
1299-
1300- mtf::SurfaceGeometries client_geometries;
1301- client_geometries[test_client_name_1] = geom::Rectangle{geom::Point{geom::X{0}, geom::Y{0}},
1302- geom::Size{geom::Width{1}, geom::Height{1}}};
1303-
1304- mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
1305- static mtf::CrossProcessSync client_may_change_cursor;
1306-
1307- CursorTestServerConfiguration server_conf(
1308- client_geometries, mtf::SurfaceDepths(),
1309- client_ready_fence, client_may_exit_fence,
1310- ClientCount{1},
1311- [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1312- {
1313- InSequence seq;
1314- EXPECT_CALL(cursor, show(CursorNamed(client_1_cursor))).Times(1);
1315+ };
1316+ start_server();
1317+
1318+
1319+ client_config_1.set_cursor = [](MirSurface *surface)
1320+ {
1321+ // Disable cursor
1322+ auto conf = mir_cursor_configuration_from_name(mir_disabled_cursor_name);
1323+ mir_wait_for(mir_surface_configure_cursor(surface, conf));
1324+ mir_cursor_configuration_destroy(conf);
1325+ };
1326+ start_client(client_config_1);
1327+}
1328+
1329+TEST_F(TestClientCursorAPI, cursor_changed_when_crossing_surface_boundaries)
1330+{
1331+ using namespace ::testing;
1332+
1333+ server_configuration.client_geometries[client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1334+ geom::Size{geom::Width{1}, geom::Height{1}}};
1335+ server_configuration.client_geometries[client_name_2] = geom::Rectangle{geom::Point{geom::X{2}, geom::Y{0}},
1336+ geom::Size{geom::Width{1}, geom::Height{1}}};
1337+ set_client_count(2);
1338+
1339+ server_configuration.expect_cursor_states =
1340+ [this](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1341+ {
1342+ InSequence seq;
1343+ EXPECT_CALL(cursor, show(CursorNamed(client_cursor_1))).Times(1);
1344+ EXPECT_CALL(cursor, show(CursorNamed(client_cursor_2))).Times(1)
1345+ .WillOnce(mt::WakeUp(&expectations_satisfied));
1346+ };
1347+ server_configuration.synthesize_cursor_motion =
1348+ [](ServerConfiguration *server)
1349+ {
1350+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
1351+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
1352+ };
1353+ start_server();
1354+
1355+ client_config_1.set_cursor =
1356+ [this](MirSurface *surface)
1357+ {
1358+ auto conf = mir_cursor_configuration_from_name(client_cursor_1.c_str());
1359+ mir_wait_for(mir_surface_configure_cursor(surface, conf));
1360+ mir_cursor_configuration_destroy(conf);
1361+ };
1362+ start_client(client_config_1);
1363+
1364+ client_config_2.set_cursor =
1365+ [this](MirSurface *surface)
1366+ {
1367+ auto conf = mir_cursor_configuration_from_name(client_cursor_2.c_str());
1368+ mir_wait_for(mir_surface_configure_cursor(surface, conf));
1369+ mir_cursor_configuration_destroy(conf);
1370+ };
1371+ start_client(client_config_2);
1372+}
1373+
1374+TEST_F(TestClientCursorAPI, cursor_request_taken_from_top_surface)
1375+{
1376+ using namespace ::testing;
1377+
1378+ server_configuration.client_geometries[client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1379+ geom::Size{geom::Width{1}, geom::Height{1}}};
1380+ server_configuration.client_geometries[client_name_2] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
1381+ geom::Size{geom::Width{1}, geom::Height{1}}};
1382+ server_configuration.client_depths[client_name_1] = ms::DepthId{0};
1383+ server_configuration.client_depths[client_name_2] = ms::DepthId{1};
1384+
1385+ set_client_count(2);
1386+
1387+ server_configuration.expect_cursor_states =
1388+ [this](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1389+ {
1390+ InSequence seq;
1391+ EXPECT_CALL(cursor, show(CursorNamed(client_cursor_2))).Times(1)
1392+ .WillOnce(mt::WakeUp(&expectations_satisfied));
1393+ };
1394+ server_configuration.synthesize_cursor_motion =
1395+ [](ServerConfiguration *server)
1396+ {
1397+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
1398+ };
1399+ start_server();
1400+
1401+
1402+ client_config_1.set_cursor =
1403+ [this](MirSurface *surface)
1404+ {
1405+ auto conf = mir_cursor_configuration_from_name(client_cursor_1.c_str());
1406+ mir_wait_for(mir_surface_configure_cursor(surface, conf));
1407+ mir_cursor_configuration_destroy(conf);
1408+ };
1409+ client_config_2.set_cursor =
1410+ [this](MirSurface *surface)
1411+ {
1412+ auto conf = mir_cursor_configuration_from_name(client_cursor_2.c_str());
1413+ mir_wait_for(mir_surface_configure_cursor(surface, conf));
1414+ mir_cursor_configuration_destroy(conf);
1415+ };
1416+ start_client(client_config_1);
1417+ start_client(client_config_2);
1418+}
1419+
1420+namespace
1421+{
1422+
1423+// In the following test the cursor changes are not responsive
1424+// to cursor motion so we need a different synchronization model.
1425+struct WaitsToChangeCursorClient : ClientConfig
1426+{
1427+ WaitsToChangeCursorClient(std::string const& client_name,
1428+ mt::Barrier& cursor_ready_fence,
1429+ mt::Barrier& client_may_exit_fence)
1430+ : ClientConfig(client_name, cursor_ready_fence, client_may_exit_fence)
1431+ {
1432+ }
1433+
1434+ void thread_exec() override
1435+ {
1436+ auto connection = mir_connect_sync(connect_string.c_str(),
1437+ client_name.c_str());
1438+
1439+ ASSERT_TRUE(connection != NULL);
1440+ MirSurfaceParameters const request_params =
1441+ {
1442+ client_name.c_str(),
1443+ // For this fixture, we force geometry on server side
1444+ 0, 0,
1445+ mir_pixel_format_abgr_8888,
1446+ mir_buffer_usage_hardware,
1447+ mir_display_output_id_invalid
1448+ };
1449+ auto surface = mir_connection_create_surface_sync(connection, &request_params);
1450+
1451+ set_cursor_complete.ready();
1452+ set_cursor(surface);
1453+
1454+ client_may_exit.ready();
1455+
1456+ mir_surface_release_sync(surface);
1457+ mir_connection_release(connection);
1458+ }
1459+};
1460+
1461+struct TestClientCursorAPINoMotion : TestClientCursorAPI
1462+{
1463+ mt::Barrier client_may_change_cursor{2};
1464+ WaitsToChangeCursorClient waiting_client{client_name_1, cursor_configured_fence, client_may_exit_fence};
1465+
1466+ void TearDown() override
1467+ {
1468+ waiting_client.tear_down();
1469+ TestClientCursorAPI::TearDown();
1470+ }
1471+};
1472+
1473+}
1474+
1475+TEST_F(TestClientCursorAPINoMotion, cursor_request_applied_without_cursor_motion)
1476+{
1477+ using namespace ::testing;
1478+
1479+ server_configuration.client_geometries[client_name_1] =
1480+ geom::Rectangle{geom::Point{geom::X{0}, geom::Y{0}},
1481+ geom::Size{geom::Width{1}, geom::Height{1}}};
1482+
1483+ server_configuration.expect_cursor_states =
1484+ [this](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
1485+ {
1486+ InSequence seq;
1487+ EXPECT_CALL(cursor, show(CursorNamed(client_cursor_1))).Times(1);
1488 EXPECT_CALL(cursor, hide()).Times(1)
1489- .WillOnce(mt::WakeUp(&expectations_satisfied));
1490- },
1491- [](CursorTestServerConfiguration * /* server */)
1492- {
1493- client_may_change_cursor.signal_ready();
1494- });
1495- launch_server_process(server_conf);
1496-
1497- CursorSettingClient client1_conf(test_client_name_1, client_ready_fence, client_may_exit_fence,
1498- [&client_ready_fence](MirSurface *surface)
1499- {
1500- client_ready_fence.signal_ready();
1501- client_may_change_cursor.wait_for_signal_ready_for();
1502- mir_wait_for(mir_surface_configure_cursor(surface, mir_cursor_configuration_from_name(client_1_cursor.c_str())));
1503- mir_wait_for(mir_surface_configure_cursor(surface,
1504- mir_cursor_configuration_from_name(mir_disabled_cursor_name)));
1505- });
1506- launch_client_process(client1_conf);
1507+ .WillOnce(mt::WakeUp(&expectations_satisfied));
1508+ };
1509+ server_configuration.synthesize_cursor_motion =
1510+ [this](ServerConfiguration * /* server */)
1511+ {
1512+ client_may_change_cursor.ready();
1513+ };
1514+ start_server();
1515+
1516+ waiting_client.set_cursor =
1517+ [this](MirSurface *surface)
1518+ {
1519+ client_may_change_cursor.ready();
1520+ auto conf1 = mir_cursor_configuration_from_name(client_cursor_1.c_str());
1521+ auto conf2 = mir_cursor_configuration_from_name(mir_disabled_cursor_name);
1522+
1523+ mir_wait_for(mir_surface_configure_cursor(surface, conf1));
1524+ mir_wait_for(mir_surface_configure_cursor(surface, conf2));
1525+
1526+ mir_cursor_configuration_destroy(conf1);
1527+ mir_cursor_configuration_destroy(conf2);
1528+ };
1529+ start_client(waiting_client);
1530 }
1531+
1532
1533=== modified file 'tests/mir_test_framework/input_testing_server_options.cpp'
1534--- tests/mir_test_framework/input_testing_server_options.cpp 2014-06-03 11:04:15 +0000
1535+++ tests/mir_test_framework/input_testing_server_options.cpp 2014-06-23 02:58:32 +0000
1536@@ -71,12 +71,10 @@
1537 {
1538 if (!input_configuration)
1539 {
1540- std::shared_ptr<mi::CursorListener> null_cursor_listener{nullptr};
1541-
1542 input_configuration = std::make_shared<mtd::FakeEventHubInputConfiguration>(
1543 the_input_dispatcher(),
1544 the_input_region(),
1545- null_cursor_listener,
1546+ the_cursor_listener(),
1547 the_input_report());
1548 fake_event_hub = input_configuration->the_fake_event_hub();
1549
1550
1551=== modified file 'tests/mir_test_framework/stubbed_server_configuration.cpp'
1552--- tests/mir_test_framework/stubbed_server_configuration.cpp 2014-06-19 09:06:54 +0000
1553+++ tests/mir_test_framework/stubbed_server_configuration.cpp 2014-06-23 02:58:32 +0000
1554@@ -21,6 +21,7 @@
1555
1556 #include "mir/options/default_configuration.h"
1557 #include "mir/graphics/buffer_ipc_packer.h"
1558+#include "mir/graphics/cursor.h"
1559 #include "mir/input/input_channel.h"
1560 #include "mir/input/input_manager.h"
1561
1562@@ -118,6 +119,13 @@
1563 }
1564 };
1565
1566+class StubCursor : public mg::Cursor
1567+{
1568+ void show(mg::CursorImage const&) override {}
1569+ void hide() override {}
1570+ void move_to(geom::Point) override {}
1571+};
1572+
1573 class StubGraphicPlatform : public mtd::NullPlatform
1574 {
1575 public:
1576@@ -162,7 +170,7 @@
1577 {
1578 return std::make_shared<mtd::StubDisplay>(display_rects);
1579 }
1580-
1581+
1582 std::vector<geom::Rectangle> const display_rects;
1583 };
1584
1585@@ -253,3 +261,9 @@
1586 else
1587 return std::make_shared<mi::NullInputDispatcher>();
1588 }
1589+
1590+std::shared_ptr<mg::Cursor> mtf::StubbedServerConfiguration::the_cursor()
1591+{
1592+ return std::make_shared<StubCursor>();
1593+}
1594+
1595
1596=== modified file 'tests/unit-tests/input/CMakeLists.txt'
1597--- tests/unit-tests/input/CMakeLists.txt 2013-08-28 03:41:48 +0000
1598+++ tests/unit-tests/input/CMakeLists.txt 2014-06-23 02:58:32 +0000
1599@@ -3,6 +3,7 @@
1600 list(APPEND UNIT_TEST_SOURCES
1601 ${CMAKE_CURRENT_SOURCE_DIR}/test_event_filter_chain.cpp
1602 ${CMAKE_CURRENT_SOURCE_DIR}/test_display_input_region.cpp
1603+ ${CMAKE_CURRENT_SOURCE_DIR}/test_cursor_controller.cpp
1604 )
1605
1606 set(
1607
1608=== modified file 'tests/unit-tests/input/android/test_android_input_target_enumerator.cpp'
1609--- tests/unit-tests/input/android/test_android_input_target_enumerator.cpp 2014-06-03 11:04:15 +0000
1610+++ tests/unit-tests/input/android/test_android_input_target_enumerator.cpp 2014-06-23 02:58:32 +0000
1611@@ -39,6 +39,7 @@
1612
1613 namespace mi = mir::input;
1614 namespace mia = mir::input::android;
1615+namespace ms = mir::scene;
1616 namespace geom = mir::geometry;
1617
1618 namespace mt = mir::test;
1619@@ -60,6 +61,14 @@
1620 callback(target);
1621 }
1622
1623+ void add_observer(std::shared_ptr<ms::Observer> const& /* observer */)
1624+ {
1625+ }
1626+
1627+ void remove_observer(std::weak_ptr<ms::Observer> const& /* observer */)
1628+ {
1629+ }
1630+
1631 std::vector<std::shared_ptr<mi::Surface>> targets;
1632 };
1633
1634
1635=== added file 'tests/unit-tests/input/test_cursor_controller.cpp'
1636--- tests/unit-tests/input/test_cursor_controller.cpp 1970-01-01 00:00:00 +0000
1637+++ tests/unit-tests/input/test_cursor_controller.cpp 2014-06-23 02:58:32 +0000
1638@@ -0,0 +1,391 @@
1639+/*
1640+ * Copyright © 2014 Canonical Ltd.
1641+ *
1642+ * This program is free software: you can redistribute it and/or modify
1643+ * it under the terms of the GNU General Public License version 3 as
1644+ * published by the Free Software Foundation.
1645+ *
1646+ * This program is distributed in the hope that it will be useful,
1647+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1648+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1649+ * GNU General Public License for more details.
1650+ *
1651+ * You should have received a copy of the GNU General Public License
1652+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1653+ *
1654+ * Authored by: Robert Carr <robert.carr@canonical.com>
1655+ */
1656+
1657+#include "src/server/input/cursor_controller.h"
1658+
1659+#include "mir/input/surface.h"
1660+#include "mir/input/input_targets.h"
1661+#include "mir/scene/observer.h"
1662+#include "mir/scene/surface_observer.h"
1663+#include "mir/graphics/cursor_image.h"
1664+#include "mir/graphics/cursor.h"
1665+
1666+#include "mir_toolkit/common.h"
1667+
1668+#include "mir_test/fake_shared.h"
1669+#include "mir_test_doubles/stub_scene_surface.h"
1670+
1671+#include <gtest/gtest.h>
1672+#include <gmock/gmock.h>
1673+
1674+#include <initializer_list>
1675+#include <mutex>
1676+#include <algorithm>
1677+
1678+#include <assert.h>
1679+
1680+namespace mi = mir::input;
1681+namespace mg = mir::graphics;
1682+namespace ms = mir::scene;
1683+namespace geom = mir::geometry;
1684+namespace mt = mir::test;
1685+namespace mtd = mt::doubles;
1686+
1687+namespace
1688+{
1689+
1690+struct NamedCursorImage : public mg::CursorImage
1691+{
1692+ NamedCursorImage(std::string const& name)
1693+ : cursor_name(name)
1694+ {
1695+ }
1696+
1697+ void const* as_argb_8888() const { return nullptr; }
1698+ geom::Size size() const { return geom::Size{}; }
1699+
1700+ std::string const cursor_name;
1701+};
1702+
1703+bool cursor_is_named(mg::CursorImage const& i, std::string const& name)
1704+{
1705+ auto image = dynamic_cast<NamedCursorImage const*>(&i);
1706+ assert(image);
1707+
1708+ return image->cursor_name == name;
1709+}
1710+
1711+MATCHER(DefaultCursorImage, "")
1712+{
1713+ return cursor_is_named(arg, mir_default_cursor_name);
1714+}
1715+
1716+MATCHER_P(CursorNamed, name, "")
1717+{
1718+ return cursor_is_named(arg, name);
1719+}
1720+
1721+struct MockCursor : public mg::Cursor
1722+{
1723+ MOCK_METHOD1(show, void(mg::CursorImage const&));
1724+ MOCK_METHOD0(hide, void());
1725+
1726+ MOCK_METHOD1(move_to, void(geom::Point));
1727+};
1728+
1729+// TODO: This should only inherit from mi::Surface but to use the Scene observer we need an
1730+// ms::Surface base class.
1731+struct StubInputSurface : public mtd::StubSceneSurface
1732+{
1733+ StubInputSurface(geom::Rectangle const& input_bounds, std::shared_ptr<mg::CursorImage> const& cursor_image)
1734+ : mtd::StubSceneSurface(0),
1735+ bounds(input_bounds),
1736+ cursor_image_(cursor_image)
1737+ {
1738+ }
1739+
1740+ std::string name() const override
1741+ {
1742+ return std::string();
1743+ }
1744+
1745+ geom::Rectangle input_bounds() const override
1746+ {
1747+ // We could return bounds here but lets make sure the cursor controller
1748+ // is only using input_area_contains.
1749+ return geom::Rectangle();
1750+ }
1751+
1752+ bool input_area_contains(geom::Point const& point) const override
1753+ {
1754+ return bounds.contains(point);
1755+ }
1756+
1757+ std::shared_ptr<mi::InputChannel> input_channel() const override
1758+ {
1759+ return nullptr;
1760+ }
1761+
1762+ mi::InputReceptionMode reception_mode() const override
1763+ {
1764+ return mi::InputReceptionMode::normal;
1765+ }
1766+
1767+ std::shared_ptr<mg::CursorImage> cursor_image() const override
1768+ {
1769+ return cursor_image_;
1770+ }
1771+
1772+ void set_cursor_image(std::shared_ptr<mg::CursorImage> const& image) override
1773+ {
1774+ cursor_image_ = image;
1775+
1776+ {
1777+ std::unique_lock<decltype(observer_guard)> lk(observer_guard);
1778+ for (auto o : observers)
1779+ o->cursor_image_set_to(*image);
1780+ }
1781+ }
1782+
1783+ void add_observer(std::shared_ptr<ms::SurfaceObserver> const& observer) override
1784+ {
1785+ std::unique_lock<decltype(observer_guard)> lk(observer_guard);
1786+
1787+ observers.push_back(observer);
1788+ }
1789+
1790+ void remove_observer(std::weak_ptr<ms::SurfaceObserver> const& observer) override
1791+ {
1792+ auto o = observer.lock();
1793+ assert(o);
1794+
1795+ auto it = std::find(observers.begin(), observers.end(), o);
1796+ observers.erase(it);
1797+ }
1798+
1799+ geom::Rectangle const bounds;
1800+ std::shared_ptr<mg::CursorImage> cursor_image_;
1801+
1802+ std::mutex observer_guard;
1803+ std::vector<std::shared_ptr<ms::SurfaceObserver>> observers;
1804+};
1805+
1806+struct StubInputTargets : public mi::InputTargets
1807+{
1808+ StubInputTargets(std::initializer_list<std::shared_ptr<ms::Surface>> const& targets)
1809+ : targets(targets.begin(), targets.end())
1810+ {
1811+ }
1812+
1813+ void for_each(std::function<void(std::shared_ptr<mi::Surface> const&)> const& callback) override
1814+ {
1815+ for (auto const& target : targets)
1816+ callback(target);
1817+ }
1818+
1819+ void add_observer(std::shared_ptr<ms::Observer> const& observer) override
1820+ {
1821+ std::unique_lock<decltype(observer_guard)> lk(observer_guard);
1822+
1823+ observers.push_back(observer);
1824+
1825+ for (auto target : targets)
1826+ {
1827+ for (auto observer : observers)
1828+ {
1829+ observer->surface_exists(target.get());
1830+ }
1831+ }
1832+ }
1833+
1834+ void remove_observer(std::weak_ptr<ms::Observer> const& observer) override
1835+ {
1836+ std::unique_lock<decltype(observer_guard)> lk(observer_guard);
1837+
1838+ auto o = observer.lock();
1839+ assert(o);
1840+
1841+ auto it = std::find(observers.begin(), observers.end(), o);
1842+ observers.erase(it);
1843+ }
1844+
1845+ void add_surface(std::shared_ptr<StubInputSurface> const& surface)
1846+ {
1847+ targets.push_back(surface);
1848+ for (auto observer : observers)
1849+ {
1850+ observer->surface_added(surface.get());
1851+ }
1852+ }
1853+
1854+ // TODO: Should be mi::Surface. See comment on StubInputSurface.
1855+ std::vector<std::shared_ptr<ms::Surface>> targets;
1856+
1857+ std::mutex observer_guard;
1858+
1859+ std::vector<std::shared_ptr<ms::Observer>> observers;
1860+};
1861+
1862+struct TestCursorController : public testing::Test
1863+{
1864+ TestCursorController()
1865+ : default_cursor_image(std::make_shared<NamedCursorImage>(mir_default_cursor_name))
1866+ {
1867+ }
1868+ geom::Rectangle const rect_0_0_1_1{{0, 0}, {1, 1}};
1869+ geom::Rectangle const rect_1_1_1_1{{1, 1}, {1, 1}};
1870+ std::string const cursor_name_1 = "test-cursor-1";
1871+ std::string const cursor_name_2 = "test-cursor-2";
1872+
1873+ MockCursor cursor;
1874+ std::shared_ptr<mg::CursorImage> const default_cursor_image;
1875+};
1876+
1877+}
1878+
1879+TEST_F(TestCursorController, moves_cursor)
1880+{
1881+ using namespace ::testing;
1882+
1883+ StubInputTargets targets({});
1884+
1885+ mi::CursorController controller(mt::fake_shared(targets),
1886+ mt::fake_shared(cursor), default_cursor_image);
1887+
1888+ InSequence seq;
1889+ EXPECT_CALL(cursor, move_to(geom::Point{geom::X{1.0f}, geom::Y{1.0f}}));
1890+ EXPECT_CALL(cursor, move_to(geom::Point{geom::X{0.0f}, geom::Y{0.0f}}));
1891+
1892+ controller.cursor_moved_to(1.0f, 1.0f);
1893+ controller.cursor_moved_to(0.0f, 0.0f);
1894+}
1895+
1896+TEST_F(TestCursorController, updates_cursor_image_when_entering_surface)
1897+{
1898+ using namespace ::testing;
1899+
1900+ StubInputSurface surface{rect_1_1_1_1,
1901+ std::make_shared<NamedCursorImage>(cursor_name_1)};
1902+ StubInputTargets targets({mt::fake_shared(surface)});
1903+
1904+ mi::CursorController controller(mt::fake_shared(targets),
1905+ mt::fake_shared(cursor), default_cursor_image);
1906+
1907+ EXPECT_CALL(cursor, move_to(_)).Times(AnyNumber());
1908+ EXPECT_CALL(cursor, show(CursorNamed(cursor_name_1))).Times(1);
1909+
1910+ controller.cursor_moved_to(1.0f, 1.0f);
1911+}
1912+
1913+TEST_F(TestCursorController, surface_with_no_cursor_image_hides_cursor)
1914+{
1915+ using namespace ::testing;
1916+
1917+ StubInputSurface surface{rect_1_1_1_1,
1918+ nullptr};
1919+ StubInputTargets targets({mt::fake_shared(surface)});
1920+
1921+ mi::CursorController controller(mt::fake_shared(targets),
1922+ mt::fake_shared(cursor), default_cursor_image);
1923+
1924+ EXPECT_CALL(cursor, move_to(_)).Times(AnyNumber());
1925+ EXPECT_CALL(cursor, hide()).Times(1);
1926+
1927+ controller.cursor_moved_to(1.0f, 1.0f);
1928+}
1929+
1930+TEST_F(TestCursorController, takes_cursor_image_from_topmost_surface)
1931+{
1932+ using namespace ::testing;
1933+
1934+ StubInputSurface surface_1{rect_1_1_1_1, std::make_shared<NamedCursorImage>(cursor_name_1)};
1935+ StubInputSurface surface_2{rect_1_1_1_1, std::make_shared<NamedCursorImage>(cursor_name_2)};
1936+ StubInputTargets targets({mt::fake_shared(surface_1), mt::fake_shared(surface_2)});
1937+
1938+ mi::CursorController controller(mt::fake_shared(targets),
1939+ mt::fake_shared(cursor), default_cursor_image);
1940+
1941+ EXPECT_CALL(cursor, move_to(_)).Times(AnyNumber());
1942+ EXPECT_CALL(cursor, show(CursorNamed(cursor_name_2))).Times(1);
1943+
1944+ controller.cursor_moved_to(1.0f, 1.0f);
1945+}
1946+
1947+TEST_F(TestCursorController, restores_cursor_when_leaving_surface)
1948+{
1949+ using namespace ::testing;
1950+
1951+ StubInputSurface surface{rect_1_1_1_1,
1952+ std::make_shared<NamedCursorImage>(cursor_name_1)};
1953+ StubInputTargets targets({mt::fake_shared(surface)});
1954+
1955+ mi::CursorController controller(mt::fake_shared(targets),
1956+ mt::fake_shared(cursor), default_cursor_image);
1957+
1958+ EXPECT_CALL(cursor, move_to(_)).Times(AnyNumber());
1959+
1960+ {
1961+ InSequence seq;
1962+ EXPECT_CALL(cursor, show(CursorNamed(cursor_name_1))).Times(1);
1963+ EXPECT_CALL(cursor, show(DefaultCursorImage())).Times(1);
1964+ }
1965+
1966+ controller.cursor_moved_to(1.0f, 1.0f);
1967+ controller.cursor_moved_to(2.0f, 2.0f);
1968+}
1969+
1970+TEST_F(TestCursorController, change_in_cursor_request_triggers_image_update_without_cursor_motion)
1971+{
1972+ using namespace ::testing;
1973+
1974+ StubInputSurface surface{rect_1_1_1_1,
1975+ std::make_shared<NamedCursorImage>(cursor_name_1)};
1976+ StubInputTargets targets({mt::fake_shared(surface)});
1977+
1978+ mi::CursorController controller(mt::fake_shared(targets),
1979+ mt::fake_shared(cursor), default_cursor_image);
1980+
1981+ EXPECT_CALL(cursor, move_to(_)).Times(AnyNumber());
1982+ {
1983+ InSequence seq;
1984+ EXPECT_CALL(cursor, show(CursorNamed(cursor_name_1))).Times(1);
1985+ EXPECT_CALL(cursor, show(CursorNamed(cursor_name_2))).Times(1);
1986+ }
1987+
1988+ controller.cursor_moved_to(1.0f, 1.0f);
1989+ surface.set_cursor_image(std::make_shared<NamedCursorImage>(cursor_name_2));
1990+}
1991+
1992+TEST_F(TestCursorController, change_in_scene_triggers_image_update)
1993+{
1994+ using namespace ::testing;
1995+
1996+ // Here we also demonstrate that the cursor begins at 0,0.
1997+ StubInputSurface surface{rect_0_0_1_1,
1998+ std::make_shared<NamedCursorImage>(cursor_name_1)};
1999+ StubInputTargets targets({});
2000+
2001+ mi::CursorController controller(mt::fake_shared(targets),
2002+ mt::fake_shared(cursor), default_cursor_image);
2003+
2004+ EXPECT_CALL(cursor, move_to(_)).Times(AnyNumber());
2005+ EXPECT_CALL(cursor, show(CursorNamed(cursor_name_1))).Times(1);
2006+
2007+ targets.add_surface(mt::fake_shared(surface));
2008+}
2009+
2010+TEST_F(TestCursorController, cursor_image_not_reset_needlessly)
2011+{
2012+ using namespace ::testing;
2013+
2014+ auto image = std::make_shared<NamedCursorImage>(cursor_name_1);
2015+
2016+ // Here we also demonstrate that the cursor begins at 0,0.
2017+ StubInputSurface surface1{rect_0_0_1_1, image};
2018+ StubInputSurface surface2{rect_0_0_1_1, image};
2019+ StubInputTargets targets({});
2020+
2021+ mi::CursorController controller(mt::fake_shared(targets),
2022+ mt::fake_shared(cursor), default_cursor_image);
2023+
2024+ EXPECT_CALL(cursor, move_to(_)).Times(AnyNumber());
2025+ EXPECT_CALL(cursor, show(CursorNamed(cursor_name_1))).Times(1);
2026+
2027+ targets.add_surface(mt::fake_shared(surface1));
2028+ targets.add_surface(mt::fake_shared(surface2));
2029+}

Subscribers

People subscribed via source and target branches