Mir

Merge lp:~mir-team/mir/cursor-spike-phase-2 into lp:mir/0.1

Proposed by Robert Carr
Status: Work in progress
Proposed branch: lp:~mir-team/mir/cursor-spike-phase-2
Merge into: lp:mir/0.1
Prerequisite: lp:~mir-team/mir/cursor-spike-phase-1-resubmit
Diff against target: 674 lines (+536/-0)
11 files modified
include/client/mir_toolkit/mir_client_library.h (+11/-0)
include/shared/mir_toolkit/client_types.h (+16/-0)
src/client/mir_client_library.cpp (+5/-0)
src/client/mir_surface.cpp (+27/-0)
src/client/mir_surface.h (+5/-0)
src/server/frontend/protobuf_message_processor.cpp (+4/-0)
src/server/frontend/session_mediator.cpp (+9/-0)
src/server/frontend/session_mediator.h (+5/-0)
src/shared/protobuf/mir_protobuf.proto (+9/-0)
tests/acceptance-tests/CMakeLists.txt (+1/-0)
tests/acceptance-tests/test_client_cursor_api.cpp (+444/-0)
To merge this branch: bzr merge lp:~mir-team/mir/cursor-spike-phase-2
Reviewer Review Type Date Requested Status
Kevin DuBois (community) Needs Information
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+213733@code.launchpad.net

Commit message

Define client cursor API and implement up until stub in session mediator. Define expected behavior through disabled acceptance tests.

Description of the change

Cursor spike phase 2! phases 3 and 4* are coming later today. This is just split up for ease of code review.

Contains the client side API definitions protobuf changes and implementation up until SessionMediator.

Contains the behavior definition in terms of disabled acceptance tests.

Phase 3 will contain some refactoring of existing code to enable implementing the tests on server side.

Phase 4 contains the new cursor controller object (new impl of cursor listener) which provides most of the implementation

To post a comment you must log in.
Revision history for this message
Kevin DuBois (kdub) wrote :

nits:
l32, most of the client headers seem to stick to the 80-char-columns rule (although the style guide says 120-char-columns)

l35, 'nad'

needs fixings:
the protobuf message should have:
 optional string error = 127;

l10/l16 comment doesn't seem to match the function

l10, It wasn't immediately obvious to me from the context and functions that the "cursor_theme" and "cursor_name" were c-strings that correspond to something on the server side. (I thought we were sending a raw pixel buffer at first glance).

needs info:
How does the client get the strings (theme name/cursor name) that it needs to use this api?

although it makes sense to ignore the strings if enabled is false, it almost lends itself to having 2 functions added to the API, one concerning turning the cursor on and off, and the other setting the cursor to something different.

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

client_types.h: Restrict to 80 characters, fix typo s/nad/and

1492. By Robert Carr

mir_client_library.h: Update configure_cursor comments

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

Updated comments.

Not sure about the protobuf message containing error? As it stands the message is just the parameters and the function returns void (similar to release_surface for example). Should rpc configure_cursor need to return CursorResponse { optional string error } ?

Strangely not anything like this in the RPC api...either things return real values (and the error is interspersed) or return void (release surface, screencast, etc).

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

with the protobuf error field, we can leave the message as I suppose, and add it later. (adding an error msg is easy, when we need it)

I guess I'll switch to needs info for the other points...
The comments are clearer, but I still don't know where to find the name of available themes from.
Is the cursor being enabled or disabled on a per-surface basis the requirement that we have? (or is it just to turn the cursor on or off on the whole screen?)
Would 2 api functions (enable/disable and select image), be more natural?

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

>> The comments are clearer, but I still don't know where to find the name of available themes from.

I think in general you use the default theme, either toolkits handle configuration of different theme, or the system default changes (I lend towards the second). I think the main use case for theme, is i.e. a game installs a custom crazy game cursor theme and wishes to refer to it by name as a more convenient form of uploading custom cursors via API.

>> Is the cursor being enabled or disabled on a per-surface basis the requirement that we have? (or is it
>> just to turn the cursor on or off on the whole screen?)

Cursor is being enabled or disabled per surface for XMir v. Unity8 under USC. Of course it could be done just turning the cursor on/off for the whole screen. If we did this though, we would have to introduce some sort of meditation functionality similar to the display configuration (i.e. screen cursor state request from active session). Given that there is still a need to configure cursor per surface (e.g. text input cursor v. pointer), it seems best to not introduce this second full screen cursor state behavior.

>> Would 2 api functions (enable/disable and select image), be more natural?

I decided no because it opens more questions (What happens if you select an image while cursor is disabled?). I think just setting the total cursor state per surface is a hard model to misinterpret.

Unmerged revisions

1492. By Robert Carr

mir_client_library.h: Update configure_cursor comments

1491. By Robert Carr

client_types.h: Restrict to 80 characters, fix typo s/nad/and

1490. By Robert Carr

Define API with client/server RPC infrastructure + acceptance tests (Disabled)

1489. By Robert Carr

render_surfaces.cpp: Remove unused variables

1488. By Robert Carr

Remove old code for input=off from render_surfaces.cpp

1487. By Robert Carr

Rename CursorLoader->CursorImagesV

1486. By Robert Carr

Merge trunk

1485. By Robert Carr

Rename CursorRepository -> CursorLoader

1484. By Robert Carr

Const correctness

1483. By Robert Carr

render_surfaces: Use a null cursor instance instead of a nullptr\!

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_client_library.h'
2--- include/client/mir_toolkit/mir_client_library.h 2014-03-06 06:05:17 +0000
3+++ include/client/mir_toolkit/mir_client_library.h 2014-04-09 13:03:02 +0000
4@@ -403,6 +403,17 @@
5 * Returns -1 if surface is invalid.
6 */
7 int mir_surface_get_swapinterval(MirSurface* surface);
8+
9+/**
10+ * Configure the cursor parameters for a given surface.
11+ * It is possible to enable or disable the cursor over a surface.
12+ * When the cursor is enabled it is possible to request the image
13+ * which will be shown (by cursor theme and name).
14+ * \param [in] surface The surface to operate on
15+ * \param [in] cursor_parameters The cursor configuration parameters
16+ * \return A wait handle that can be passed to mir_wait_for
17+ */
18+MirWaitHandle* mir_surface_configure_cursor(MirSurface* surface, MirCursorParameters const* parameters);
19
20 #ifdef __cplusplus
21 }
22
23=== modified file 'include/shared/mir_toolkit/client_types.h'
24--- include/shared/mir_toolkit/client_types.h 2014-03-11 16:19:27 +0000
25+++ include/shared/mir_toolkit/client_types.h 2014-04-09 13:03:02 +0000
26@@ -306,6 +306,22 @@
27 */
28 typedef void (*mir_screencast_callback)(MirScreencast *screencast, void *client_context);
29
30+/**
31+ * MirCursorParameters is the structure of required information to
32+ * configure the cursor request for a surface.
33+ *
34+ * The cursor may be enabled, or disabled. When enabled the cursor may
35+ * be chosen by name, from a given cursor theme.
36+ *
37+ * Cursor theme and name are ignored if enabled is false.
38+ */
39+typedef struct MirCursorParameters
40+{
41+ MirBool enabled;
42+ char const* cursor_theme;
43+ char const* cursor_name;
44+} MirCursorParameters;
45+
46 #ifdef __cplusplus
47 }
48 /**@}*/
49
50=== modified file 'src/client/mir_client_library.cpp'
51--- src/client/mir_client_library.cpp 2014-04-09 13:03:01 +0000
52+++ src/client/mir_client_library.cpp 2014-04-09 13:03:02 +0000
53@@ -496,6 +496,11 @@
54 return surf ? surf->attrib(mir_surface_attrib_swapinterval) : -1;
55 }
56
57+MirWaitHandle* mir_surface_configure_cursor(MirSurface* surf, MirCursorParameters const* cursor_parameters)
58+{
59+ return surf->configure_cursor(cursor_parameters);
60+}
61+
62 void mir_connection_set_lifecycle_event_callback(MirConnection* connection,
63 mir_lifecycle_event_callback callback, void* context)
64 {
65
66=== modified file 'src/client/mir_surface.cpp'
67--- src/client/mir_surface.cpp 2014-03-06 06:05:17 +0000
68+++ src/client/mir_surface.cpp 2014-04-09 13:03:02 +0000
69@@ -297,6 +297,33 @@
70 return *accelerated_window;
71 }
72
73+MirWaitHandle* MirSurface::configure_cursor(MirCursorParameters const* params)
74+{
75+ std::unique_lock<decltype(mutex)> lock(mutex);
76+ mp::CursorParameters msg;
77+
78+ msg.mutable_surfaceid()->CopyFrom(surface.id());
79+ msg.set_enabled(params->enabled);
80+ if (params->enabled == mir_true)
81+ {
82+ msg.set_cursor_theme(params->cursor_theme);
83+ msg.set_cursor_name(params->cursor_name);
84+ }
85+ lock.unlock();
86+
87+ configure_cursor_wait_handle.expect_result();
88+ server.configure_cursor(0, &msg, &void_response,
89+ google::protobuf::NewCallback(this, &MirSurface::on_cursor_configured));
90+
91+ return &configure_cursor_wait_handle;
92+}
93+
94+void MirSurface::on_cursor_configured()
95+{
96+ std::lock_guard<decltype(mutex)> lock(mutex);
97+ configure_cursor_wait_handle.result_received();
98+}
99+
100 MirWaitHandle* MirSurface::configure(MirSurfaceAttrib at, int value)
101 {
102 std::unique_lock<decltype(mutex)> lock(mutex);
103
104=== modified file 'src/client/mir_surface.h'
105--- src/client/mir_surface.h 2014-03-11 15:46:03 +0000
106+++ src/client/mir_surface.h 2014-04-09 13:03:02 +0000
107@@ -94,11 +94,14 @@
108 /* mir::client::ClientSurface */
109 void request_and_wait_for_next_buffer();
110 void request_and_wait_for_configure(MirSurfaceAttrib a, int value);
111+
112+ MirWaitHandle* configure_cursor(MirCursorParameters const* params);
113
114 private:
115 mutable std::mutex mutex; // Protects all members of *this
116
117 void on_configured();
118+ void on_cursor_configured();
119 void process_incoming_buffer();
120 void populate(MirBufferPackage& buffer_package);
121 void created(mir_surface_callback callback, void * context);
122@@ -108,12 +111,14 @@
123
124 mir::protobuf::DisplayServer::Stub & server;
125 mir::protobuf::Surface surface;
126+ mir::protobuf::Void void_response;
127 std::string error_message;
128
129 MirConnection* const connection;
130 MirWaitHandle create_wait_handle;
131 MirWaitHandle next_buffer_wait_handle;
132 MirWaitHandle configure_wait_handle;
133+ MirWaitHandle configure_cursor_wait_handle;
134
135 std::shared_ptr<mir::client::MemoryRegion> secured_region;
136 std::shared_ptr<mir::client::ClientBufferDepository> buffer_depository;
137
138=== modified file 'src/server/frontend/protobuf_message_processor.cpp'
139--- src/server/frontend/protobuf_message_processor.cpp 2014-03-06 06:05:17 +0000
140+++ src/server/frontend/protobuf_message_processor.cpp 2014-04-09 13:03:02 +0000
141@@ -173,6 +173,10 @@
142 {
143 invoke(this, display_server.get(), &protobuf::DisplayServer::release_screencast, invocation);
144 }
145+ else if ("configure_cursor" == invocation.method_name())
146+ {
147+ invoke(this, display_server.get(), &protobuf::DisplayServer::configure_cursor, invocation);
148+ }
149 else if ("disconnect" == invocation.method_name())
150 {
151 invoke(this, display_server.get(), &protobuf::DisplayServer::disconnect, invocation);
152
153=== modified file 'src/server/frontend/session_mediator.cpp'
154--- src/server/frontend/session_mediator.cpp 2014-04-09 13:03:01 +0000
155+++ src/server/frontend/session_mediator.cpp 2014-04-09 13:03:02 +0000
156@@ -410,6 +410,15 @@
157 done->Run();
158 }
159
160+void mf::SessionMediator::configure_cursor(
161+ google::protobuf::RpcController*,
162+ mir::protobuf::CursorParameters const* /* cursor_parameters */,
163+ mir::protobuf::Void*,
164+ google::protobuf::Closure* /* done */)
165+{
166+ BOOST_THROW_EXCEPTION(std::logic_error("Cursor API not yet implemented."));
167+}
168+
169 void mf::SessionMediator::drm_auth_magic(
170 google::protobuf::RpcController* /*controller*/,
171 const mir::protobuf::DRMMagic* request,
172
173=== modified file 'src/server/frontend/session_mediator.h'
174--- src/server/frontend/session_mediator.h 2014-03-06 06:05:17 +0000
175+++ src/server/frontend/session_mediator.h 2014-04-09 13:03:02 +0000
176@@ -123,6 +123,11 @@
177 const mir::protobuf::ScreencastId*,
178 mir::protobuf::Buffer*,
179 google::protobuf::Closure* done);
180+
181+ void configure_cursor(google::protobuf::RpcController*,
182+ mir::protobuf::CursorParameters const*,
183+ mir::protobuf::Void*,
184+ google::protobuf::Closure* done);
185
186 /* Platform specific requests */
187 void drm_auth_magic(google::protobuf::RpcController* controller,
188
189=== modified file 'src/shared/protobuf/mir_protobuf.proto'
190--- src/shared/protobuf/mir_protobuf.proto 2014-03-11 16:19:27 +0000
191+++ src/shared/protobuf/mir_protobuf.proto 2014-04-09 13:03:02 +0000
192@@ -175,6 +175,13 @@
193 optional string error = 127;
194 }
195
196+message CursorParameters {
197+ required SurfaceId surfaceid = 1;
198+ required bool enabled = 2;
199+ optional string cursor_theme = 3;
200+ optional string cursor_name = 4;
201+}
202+
203 service DisplayServer {
204 // Platform independent requests
205 rpc connect(ConnectParameters) returns (Connection);
206@@ -194,5 +201,7 @@
207 rpc create_screencast(ScreencastParameters) returns (Screencast);
208 rpc screencast_buffer(ScreencastId) returns (Buffer);
209 rpc release_screencast(ScreencastId) returns (Void);
210+
211+ rpc configure_cursor(CursorParameters) returns (Void);
212 }
213
214
215=== modified file 'tests/acceptance-tests/CMakeLists.txt'
216--- tests/acceptance-tests/CMakeLists.txt 2014-03-06 06:05:17 +0000
217+++ tests/acceptance-tests/CMakeLists.txt 2014-04-09 13:03:02 +0000
218@@ -28,6 +28,7 @@
219 test_client_library_drm.cpp
220 test_protobuf.cpp
221 test_client_screencast.cpp
222+ test_client_cursor_api.cpp
223 ${GENERATED_PROTOBUF_SRCS}
224 ${GENERATED_PROTOBUF_HDRS}
225 )
226
227=== added file 'tests/acceptance-tests/test_client_cursor_api.cpp'
228--- tests/acceptance-tests/test_client_cursor_api.cpp 1970-01-01 00:00:00 +0000
229+++ tests/acceptance-tests/test_client_cursor_api.cpp 2014-04-09 13:03:02 +0000
230@@ -0,0 +1,444 @@
231+/*
232+ * Copyright © 2013 Canonical Ltd.
233+ *
234+ * This program is free software: you can redistribute it and/or modify
235+ * it under the terms of the GNU General Public License version 3 as
236+ * published by the Free Software Foundation.
237+ *
238+ * This program is distributed in the hope that it will be useful,
239+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
240+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
241+ * GNU General Public License for more details.
242+ *
243+ * You should have received a copy of the GNU General Public License
244+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
245+ *
246+ * Authored by: Robert Carr <robert.carr@canonical.com>
247+ */
248+
249+#include "mir/graphics/cursor.h"
250+#include "mir/graphics/cursor_image.h"
251+#include "mir/graphics/cursor_images.h"
252+
253+#include "mir_toolkit/mir_client_library.h"
254+
255+#include "mir_test/fake_event_hub.h"
256+#include "mir_test/fake_shared.h"
257+#include "mir_test/event_factory.h"
258+#include "mir_test/wait_condition.h"
259+#include "mir_test_framework/display_server_test_fixture.h"
260+#include "mir_test_framework/input_testing_server_configuration.h"
261+#include "mir_test_framework/input_testing_client_configuration.h"
262+#include "mir_test_framework/declarative_placement_strategy.h"
263+#include "mir_test_framework/cross_process_sync.h"
264+
265+#include <gtest/gtest.h>
266+#include <gmock/gmock.h>
267+
268+namespace mg = mir::graphics;
269+namespace ms = mir::scene;
270+namespace msh = mir::shell;
271+namespace mis = mir::input::synthesis;
272+namespace geom = mir::geometry;
273+
274+namespace mt = mir::test;
275+namespace mtf = mir_test_framework;
276+
277+namespace
278+{
279+ char const* const mir_test_socket = mtf::test_socket_file().c_str();
280+}
281+
282+namespace
283+{
284+
285+struct MockCursor : public mg::Cursor
286+{
287+ MOCK_METHOD1(set_image, void(std::shared_ptr<mg::CursorImage const> const&));
288+ MOCK_METHOD1(move_to, void(geom::Point));
289+};
290+
291+struct NamedCursorImage : public mg::CursorImage
292+{
293+ NamedCursorImage(std::string const& theme, std::string const& name)
294+ : cursor_theme(theme),
295+ cursor_name(name)
296+ {
297+ }
298+ void const* as_argb_8888() const { return nullptr; }
299+ geom::Size size() const { return geom::Size{}; }
300+ std::string const cursor_theme;
301+ std::string const cursor_name;
302+};
303+
304+struct StubCursorImages : public mg::CursorImages
305+{
306+ std::shared_ptr<mg::CursorImage> lookup_cursor(std::string const& theme,
307+ std::string const& name, geom::Size const& /* size */)
308+ {
309+ return std::make_shared<NamedCursorImage>(theme, name);
310+ }
311+};
312+
313+MATCHER(DefaultCursorImage, "")
314+{
315+ auto image = std::dynamic_pointer_cast<NamedCursorImage const>(arg);
316+ if (image->cursor_theme != "default")
317+ return false;
318+ if (image->cursor_name != "default")
319+ return false;
320+ return true;
321+}
322+
323+MATCHER_P(CursorNamed, name, "")
324+{
325+ auto image = std::dynamic_pointer_cast<NamedCursorImage const>(arg);
326+ if (image->cursor_name != name)
327+ return false;
328+ return true;
329+}
330+
331+struct CursorSettingClient : mtf::TestingClientConfiguration
332+{
333+ std::string const client_name;
334+
335+ mtf::CrossProcessSync set_cursor_complete;
336+ mtf::CrossProcessSync client_may_exit;
337+
338+ std::function<void(MirSurface*)> const set_cursor;
339+
340+ CursorSettingClient(std::string const& client_name,
341+ mtf::CrossProcessSync const& cursor_ready_fence,
342+ mtf::CrossProcessSync const& client_may_exit_fence,
343+ std::function<void(MirSurface*)> const& set_cursor)
344+ : client_name(client_name),
345+ set_cursor_complete(cursor_ready_fence),
346+ client_may_exit(client_may_exit_fence),
347+ set_cursor(set_cursor)
348+ {
349+ }
350+
351+ void exec() override
352+ {
353+ auto connection = mir_connect_sync(mir_test_socket,
354+ client_name.c_str());
355+
356+ ASSERT_TRUE(connection != NULL);
357+ MirSurfaceParameters const request_params =
358+ {
359+ client_name.c_str(),
360+ // For this fixture, we force geometry on server side
361+ 0, 0,
362+ mir_pixel_format_abgr_8888,
363+ mir_buffer_usage_hardware,
364+ mir_display_output_id_invalid
365+ };
366+ auto surface = mir_connection_create_surface_sync(connection, &request_params);
367+
368+ set_cursor(surface);
369+ set_cursor_complete.signal_ready();
370+
371+ client_may_exit.wait_for_signal_ready_for();
372+
373+ mir_surface_release_sync(surface);
374+ mir_connection_release(connection);
375+ }
376+};
377+
378+typedef unsigned ClientCount;
379+struct CursorTestServerConfiguration : mtf::InputTestingServerConfiguration
380+{
381+ std::shared_ptr<msh::PlacementStrategy> placement_strategy;
382+ mtf::CrossProcessSync client_ready_fence;
383+ mtf::CrossProcessSync client_may_exit_fence;
384+ int const number_of_clients;
385+
386+ std::function<void(MockCursor&, mt::WaitCondition&)> const expect_cursor_states;
387+ std::function<void(CursorTestServerConfiguration*)> const synthesize_cursor_motion;
388+
389+ MockCursor cursor;
390+
391+ CursorTestServerConfiguration(mtf::SurfaceGeometries surface_geometries_by_name,
392+ mtf::SurfaceDepths surface_depths_by_name,
393+ mtf::CrossProcessSync client_ready_fence,
394+ mtf::CrossProcessSync client_may_exit_fence,
395+ ClientCount const number_of_clients,
396+ std::function<void(MockCursor&, mt::WaitCondition&)> const& expect_cursor_states,
397+ std::function<void(CursorTestServerConfiguration*)> const& synthesize_cursor_motion)
398+ : placement_strategy(
399+ std::make_shared<mtf::DeclarativePlacementStrategy>(InputTestingServerConfiguration::the_shell_placement_strategy(),
400+ surface_geometries_by_name, surface_depths_by_name)),
401+ client_ready_fence(client_ready_fence),
402+ client_may_exit_fence(client_may_exit_fence),
403+ number_of_clients(number_of_clients),
404+ expect_cursor_states(expect_cursor_states),
405+ synthesize_cursor_motion(synthesize_cursor_motion)
406+ {
407+ }
408+
409+ std::shared_ptr<msh::PlacementStrategy> the_shell_placement_strategy() override
410+ {
411+ return placement_strategy;
412+ }
413+
414+ std::shared_ptr<mg::Cursor> the_cursor() override
415+ {
416+ return mt::fake_shared(cursor);
417+ }
418+
419+ std::shared_ptr<mg::CursorImages> the_cursor_images() override
420+ {
421+ return std::make_shared<StubCursorImages>();
422+ }
423+
424+ void inject_input()
425+ {
426+ using namespace ::testing;
427+
428+ for (int i = 1; i < number_of_clients + 1; i++)
429+ EXPECT_EQ(i, client_ready_fence.wait_for_signal_ready_for());
430+
431+ mt::WaitCondition expectations_satisfied;
432+
433+ // Clear any states applied during server initialization.
434+ Mock::VerifyAndClearExpectations(&cursor);
435+ expect_cursor_states(cursor, expectations_satisfied);
436+
437+ synthesize_cursor_motion(this);
438+ expectations_satisfied.wait_for_at_most_seconds(60);
439+
440+ EXPECT_CALL(cursor, set_image(_)).Times(AnyNumber()); // Client shutdown
441+ for (int i = 0; i < number_of_clients; i++)
442+ client_may_exit_fence.signal_ready();
443+ }
444+};
445+
446+}
447+
448+using TestClientCursorAPI = BespokeDisplayServerTestFixture;
449+
450+// In this set we create a 1x1 client surface at the point (1,0). The client requests to disable the cursor
451+// over this surface. Since the cursor starts at (0,0) we when we move the cursor by (1,0) thus causing it
452+// to enter the bounds of the first surface, we should observe it being disabled.
453+TEST_F(TestClientCursorAPI, DISABLED_client_may_disable_cursor_over_surface)
454+{
455+ using namespace ::testing;
456+
457+ std::string const test_client_name = "1";
458+ mtf::SurfaceGeometries client_geometries;
459+ client_geometries[test_client_name] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
460+ geom::Size{geom::Width{1}, geom::Height{1}}};
461+
462+ mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
463+
464+ CursorTestServerConfiguration server_conf(
465+ client_geometries, mtf::SurfaceDepths(),
466+ client_ready_fence, client_may_exit_fence,
467+ ClientCount{1},
468+ [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
469+ {
470+ EXPECT_CALL(cursor, set_image(Eq(nullptr))).Times(1)
471+ .WillOnce(mt::WakeUp(&expectations_satisfied));
472+ },
473+ [](CursorTestServerConfiguration *server)
474+ {
475+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
476+ });
477+ launch_server_process(server_conf);
478+
479+ CursorSettingClient client_conf(test_client_name, client_ready_fence, client_may_exit_fence,
480+ [](MirSurface *surface)
481+ {
482+ // Disable cursor
483+ MirCursorParameters cparams = { mir_false, NULL, NULL };
484+ mir_wait_for(mir_surface_configure_cursor(surface, &cparams));
485+ });
486+ launch_client_process(client_conf);
487+}
488+
489+TEST_F(TestClientCursorAPI, DISABLED_cursor_restored_when_leaving_surface)
490+{
491+ using namespace ::testing;
492+
493+ std::string const test_client_name = "1";
494+ mtf::SurfaceGeometries client_geometries;
495+ client_geometries[test_client_name] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
496+ geom::Size{geom::Width{1}, geom::Height{1}}};
497+
498+ mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
499+
500+ CursorTestServerConfiguration server_conf(
501+ client_geometries, mtf::SurfaceDepths(),
502+ client_ready_fence, client_may_exit_fence,
503+ ClientCount{1},
504+ [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
505+ {
506+ InSequence seq;
507+ EXPECT_CALL(cursor, set_image(Eq(nullptr))).Times(1);
508+ EXPECT_CALL(cursor, set_image(DefaultCursorImage())).Times(1)
509+ .WillOnce(mt::WakeUp(&expectations_satisfied));
510+ },
511+ [](CursorTestServerConfiguration *server)
512+ {
513+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
514+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(2,0));
515+ });
516+ launch_server_process(server_conf);
517+
518+ CursorSettingClient client_conf(test_client_name, client_ready_fence, client_may_exit_fence,
519+ [](MirSurface *surface)
520+ {
521+ // Disable cursor
522+ MirCursorParameters cparams = { mir_false, NULL, NULL };
523+ mir_wait_for(mir_surface_configure_cursor(surface, &cparams));
524+ });
525+ launch_client_process(client_conf);
526+}
527+
528+TEST_F(TestClientCursorAPI, DISABLED_cursor_changed_when_crossing_surface_boundaries)
529+{
530+ using namespace ::testing;
531+
532+ static std::string const test_client_name_1 = "1";
533+ static std::string const test_client_name_2 = "2";
534+ static std::string const client_1_cursor = test_client_name_1;
535+ static std::string const client_2_cursor = test_client_name_2;
536+
537+ mtf::SurfaceGeometries client_geometries;
538+ client_geometries[test_client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
539+ geom::Size{geom::Width{1}, geom::Height{1}}};
540+ client_geometries[test_client_name_2] = geom::Rectangle{geom::Point{geom::X{2}, geom::Y{0}},
541+ geom::Size{geom::Width{1}, geom::Height{1}}};
542+
543+ mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
544+
545+ CursorTestServerConfiguration server_conf(
546+ client_geometries, mtf::SurfaceDepths(),
547+ client_ready_fence, client_may_exit_fence,
548+ ClientCount{2},
549+ [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
550+ {
551+ InSequence seq;
552+ EXPECT_CALL(cursor, set_image(CursorNamed(client_1_cursor))).Times(1);
553+ EXPECT_CALL(cursor, set_image(CursorNamed(client_2_cursor))).Times(1)
554+ .WillOnce(mt::WakeUp(&expectations_satisfied));
555+ },
556+ [](CursorTestServerConfiguration *server)
557+ {
558+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
559+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
560+ });
561+ launch_server_process(server_conf);
562+
563+ CursorSettingClient client1_conf(test_client_name_1, client_ready_fence, client_may_exit_fence,
564+ [](MirSurface *surface)
565+ {
566+ MirCursorParameters cparams = { mir_true, "default", client_1_cursor.c_str() };
567+ mir_wait_for(mir_surface_configure_cursor(surface, &cparams));
568+ });
569+ launch_client_process(client1_conf);
570+ CursorSettingClient client2_conf(test_client_name_2, client_ready_fence, client_may_exit_fence,
571+ [](MirSurface *surface)
572+ {
573+ // Disable cursor
574+ MirCursorParameters cparams = { mir_true, "default", client_2_cursor.c_str() };
575+ mir_wait_for(mir_surface_configure_cursor(surface, &cparams));
576+ });
577+ launch_client_process(client2_conf);
578+}
579+
580+TEST_F(TestClientCursorAPI, DISABLED_cursor_request_taken_from_top_surface)
581+{
582+ using namespace ::testing;
583+
584+ static std::string const test_client_name_1 = "1";
585+ static std::string const test_client_name_2 = "2";
586+ static std::string const client_1_cursor = test_client_name_1;
587+ static std::string const client_2_cursor = test_client_name_2;
588+
589+ mtf::SurfaceGeometries client_geometries;
590+ client_geometries[test_client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
591+ geom::Size{geom::Width{1}, geom::Height{1}}};
592+ client_geometries[test_client_name_1] = geom::Rectangle{geom::Point{geom::X{1}, geom::Y{0}},
593+ geom::Size{geom::Width{1}, geom::Height{1}}};
594+ mtf::SurfaceDepths client_depths;
595+ client_depths[test_client_name_1] = ms::DepthId{0};
596+ client_depths[test_client_name_2] = ms::DepthId{1};
597+
598+ mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
599+
600+ CursorTestServerConfiguration server_conf(
601+ client_geometries, client_depths,
602+ client_ready_fence, client_may_exit_fence,
603+ ClientCount{2},
604+ [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
605+ {
606+ InSequence seq;
607+ EXPECT_CALL(cursor, set_image(CursorNamed(client_2_cursor))).Times(1)
608+ .WillOnce(mt::WakeUp(&expectations_satisfied));
609+ },
610+ [](CursorTestServerConfiguration *server)
611+ {
612+ server->fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 0));
613+ });
614+ launch_server_process(server_conf);
615+
616+ CursorSettingClient client1_conf(test_client_name_1, client_ready_fence, client_may_exit_fence,
617+ [](MirSurface *surface)
618+ {
619+ MirCursorParameters cparams = { mir_true, "default", client_1_cursor.c_str() };
620+ mir_wait_for(mir_surface_configure_cursor(surface, &cparams));
621+ });
622+ launch_client_process(client1_conf);
623+ CursorSettingClient client2_conf(test_client_name_2, client_ready_fence, client_may_exit_fence,
624+ [](MirSurface *surface)
625+ {
626+ // Disable cursor
627+ MirCursorParameters cparams = { mir_true, "default", client_2_cursor.c_str() };
628+ mir_wait_for(mir_surface_configure_cursor(surface, &cparams));
629+ });
630+ launch_client_process(client2_conf);
631+}
632+
633+TEST_F(TestClientCursorAPI, DISABLED_cursor_request_applied_without_cursor_motion)
634+{
635+ using namespace ::testing;
636+ static std::string const test_client_name_1 = "1";
637+ static std::string const client_1_cursor = test_client_name_1;
638+
639+ mtf::SurfaceGeometries client_geometries;
640+ client_geometries[test_client_name_1] = geom::Rectangle{geom::Point{geom::X{0}, geom::Y{0}},
641+ geom::Size{geom::Width{1}, geom::Height{1}}};
642+
643+ mtf::CrossProcessSync client_ready_fence, client_may_exit_fence;
644+ static mtf::CrossProcessSync client_may_change_cursor;
645+
646+ CursorTestServerConfiguration server_conf(
647+ client_geometries, mtf::SurfaceDepths(),
648+ client_ready_fence, client_may_exit_fence,
649+ ClientCount{1},
650+ [](MockCursor& cursor, mt::WaitCondition& expectations_satisfied)
651+ {
652+ InSequence seq;
653+ EXPECT_CALL(cursor, set_image(CursorNamed(client_1_cursor))).Times(1);
654+ EXPECT_CALL(cursor, set_image(Eq(nullptr))).Times(1)
655+ .WillOnce(mt::WakeUp(&expectations_satisfied));
656+ },
657+ [](CursorTestServerConfiguration * /* server */)
658+ {
659+ client_may_change_cursor.signal_ready();
660+ });
661+ launch_server_process(server_conf);
662+
663+ CursorSettingClient client1_conf(test_client_name_1, client_ready_fence, client_may_exit_fence,
664+ [&client_ready_fence](MirSurface *surface)
665+ {
666+ client_ready_fence.signal_ready();
667+ client_may_change_cursor.wait_for_signal_ready_for();
668+ MirCursorParameters cparams = { mir_true, "default", client_1_cursor.c_str() };
669+ mir_wait_for(mir_surface_configure_cursor(surface, &cparams));
670+ cparams = { mir_false, NULL, NULL };
671+ mir_wait_for(mir_surface_configure_cursor(surface, &cparams));
672+ });
673+ launch_client_process(client1_conf);
674+}

Subscribers

People subscribed via source and target branches