Mir

Merge lp:~mir-team/mir/add-client-cursor-conf-from-argb into lp:mir

Proposed by Robert Carr
Status: Work in progress
Proposed branch: lp:~mir-team/mir/add-client-cursor-conf-from-argb
Merge into: lp:mir
Diff against target: 433 lines (+274/-29)
8 files modified
client-ABI-sha1sums (+1/-1)
include/client/mir_toolkit/mir_cursor_configuration.h (+16/-0)
src/client/cursor_configuration.h (+23/-3)
src/client/mir_cursor_api.cpp (+70/-22)
src/client/mir_surface.cpp (+20/-2)
src/protobuf/mir_protobuf.proto (+9/-1)
src/server/frontend/session_mediator.cpp (+56/-0)
tests/acceptance-tests/test_client_cursor_api.cpp (+79/-0)
To merge this branch: bzr merge lp:~mir-team/mir/add-client-cursor-conf-from-argb
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Robert Carr (community) Abstain
Review via email: mp+242832@code.launchpad.net

This proposal supersedes a proposal from 2014-11-24.

Commit message

Add a method for clients to upload ARGB cursor images from pixel data.

Description of the change

Add a method for clients to upload ARGB cursor images from pixel data. Previously noted as required, just not added. Doing now to enable nested cursor API passthrough (user session would be responsible for loading cursor themes and pass images up to USC...), and complete Qt cursor support.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Chris Halse Rogers (raof) wrote : Posted in a previous version of this proposal

Hm. Could this instead be

MirSurface* create_cursor_surface();

MirCursorConfiguration* mir_cursor_configuration_from_surface(MirSurface, hotspot_x, hotspot_y)?

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

Hey Chris, I started taking a stab at it this morning...the more I thought about it the less im convinced it's what we want though.

From the client perspective, the surface API is a little more difficult:
1. Create cursor-image-surface
2. Map cursor-image-surface and copy contents
3. Swap buffers
4. Attach cursor-imagesurface to cursor configuration and cursor configuration to surface

vs.

1. Create cursor conf with contents
2. Attach cursor conf to surface.

What does the client gain from the more difficult API though? my perspective is not much...besides perhaps pixel format negotiation.

Once I noticed this I started thinking about if it was really a surface? I think it bears some resemblance to a surface as it is a format of buffer upload to the server...but I think it omits the key aspects of a surface:

1. Presence in scene
2. Frame synchronization/buffer exchange ala frame-dropping is always on for cursor-image-surfaces.
3. Attribute/state-fullness, e.g. cursor upload is fire and forget

Maybe I am missing a benefit to having it represented as a surface. Maybe the alarming bit is that it isn't using MirPixelFormat, etc? Perhaps with a few tweaks it could be mir_cursor_configuration_from_graphics_region

Let me know what you think. Thanks :)

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

Unmerged revisions

2103. By Robert Carr

Fix pointer arithmetic

2102. By Robert Carr

Fix uninitialized memory

2101. By Robert Carr

Do not allow setting hotspot past bounds of cursor

2100. By Robert Carr

Only support default sized cursors

2099. By Robert Carr

Implement hotspot in raw image API

2098. By Robert Carr

First impl

2097. By Robert Carr

Test skeleton for image cursors

2096. By Robert Carr

Test skeleton for image cursors

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'client-ABI-sha1sums'
2--- client-ABI-sha1sums 2014-11-25 02:34:24 +0000
3+++ client-ABI-sha1sums 2014-11-25 19:34:43 +0000
4@@ -2,7 +2,7 @@
5 b53736bcb22ddc09aab8275cfdd05c109fdd0f12 include/client/mir_toolkit/mir_client_library_drm.h
6 18b2b173b3baeaace89cd777e91e0ece5a611cee include/client/mir_toolkit/mir_client_library.h
7 626fbcc008539c5f1713d8f6eef8c2acc4929af9 include/client/mir_toolkit/mir_connection.h
8-1ef8f51a3e3f8d1559266c5af58fbfde7cfabf0a include/client/mir_toolkit/mir_cursor_configuration.h
9+196593955a52b87f4e28bd5f452d82eaa50da692 include/client/mir_toolkit/mir_cursor_configuration.h
10 9d50df5a141ca03ee8a79f7e844ed4b8b3b7d5d3 include/client/mir_toolkit/mir_prompt_session.h
11 21d07e655e85eeec8a3523e1c6f9c2252176ec01 include/client/mir_toolkit/mir_screencast.h
12 b9a11709737d6178c4bf30d067922ca823957045 include/client/mir_toolkit/mir_surface.h
13
14=== modified file 'include/client/mir_toolkit/mir_cursor_configuration.h'
15--- include/client/mir_toolkit/mir_cursor_configuration.h 2014-10-29 06:21:10 +0000
16+++ include/client/mir_toolkit/mir_cursor_configuration.h 2014-11-25 19:34:43 +0000
17@@ -20,6 +20,8 @@
18
19 #include <mir_toolkit/common.h>
20
21+#include <stdint.h>
22+
23 /**
24 * Opaque structure containing cursor parameterization. Create with mir_cursor* family.
25 * Used with mir_surface_configure_cursor.
26@@ -52,6 +54,20 @@
27 */
28 MirCursorConfiguration *mir_cursor_configuration_from_name(char const* name);
29
30+/**
31+ * Returns a new MirCursorConfiguration representing a cursor visualizing given pixel data.
32+ *
33+ * \param[in] pixels The unstrided pixel data, of size width*height.
34+ * \param[in] width Width of a line in pixel data.
35+ * \param[in] height Height of pixel data
36+ * \param[in] hotspot_x X displacement in to image on which to place pointer
37+ * \param[in] hotspot_y X displacement in to image on which to place pointer
38+ * \return A cursor parameters object which must be passed
39+ * to_mir_cursor_configuration_destroy
40+ */
41+MirCursorConfiguration *mir_cursor_configuration_from_argb_8888(uint32_t const* pixels, unsigned width,
42+ unsigned height, unsigned hotspot_x, unsigned hotspot_y);
43+
44 #ifdef __cplusplus
45 }
46 /**@}*/
47
48=== modified file 'src/client/cursor_configuration.h'
49--- src/client/cursor_configuration.h 2014-10-29 06:21:10 +0000
50+++ src/client/cursor_configuration.h 2014-11-25 19:34:43 +0000
51@@ -19,15 +19,35 @@
52 #ifndef MIR_CLIENT_CURSOR_CONFIGURATION_H_
53 #define MIR_CLIENT_CURSOR_CONFIGURATION_H_
54
55+#include "mir/geometry/size.h"
56+#include "mir/geometry/displacement.h"
57+
58 #include <string>
59+#include <tuple>
60+#include <memory>
61+
62+#include <stdint.h>
63
64 // Parameters for configuring the apperance and behavior of the system cursor.
65 // Will grow to include cursors specified by raw RGBA data, hotspots, etc...
66-struct MirCursorConfiguration
67+class MirCursorConfiguration
68 {
69+public:
70 MirCursorConfiguration(char const* name);
71-
72- std::string name;
73+ MirCursorConfiguration(uint32_t const* pixels, mir::geometry::Size const& size, mir::geometry::Displacement const& hotspot);
74+
75+ std::string cursor_name() const;
76+
77+ bool has_pixels() const;
78+ std::tuple<uint32_t const*, mir::geometry::Size> pixels() const;
79+ mir::geometry::Displacement hotspot() const;
80+private:
81+ std::string const name;
82+
83+ std::unique_ptr<uint32_t[]> pixel_data;
84+ mir::geometry::Size const pixels_size;
85+
86+ mir::geometry::Displacement hotspot_;
87 };
88
89 #endif // MIR_CLIENT_CURSOR_CONFIGURATION_H_
90
91=== modified file 'src/client/mir_cursor_api.cpp'
92--- src/client/mir_cursor_api.cpp 2014-10-29 06:21:10 +0000
93+++ src/client/mir_cursor_api.cpp 2014-11-25 19:34:43 +0000
94@@ -21,6 +21,76 @@
95
96 #include <memory>
97
98+#include <string.h>
99+
100+namespace geom = mir::geometry;
101+
102+MirCursorConfiguration::MirCursorConfiguration(char const* name) :
103+ name{name ? name : std::string()},
104+ pixel_data(nullptr)
105+{
106+}
107+
108+MirCursorConfiguration::MirCursorConfiguration(uint32_t const* pixels, geom::Size const& size,
109+ geom::Displacement const& hotspot) :
110+ name(std::string()),
111+ pixel_data(new uint32_t[size.width.as_int()*size.height.as_int()]),
112+ pixels_size(size),
113+ hotspot_(hotspot)
114+{
115+ memcpy(pixel_data.get(), pixels, size.width.as_int()*size.height.as_int()*sizeof(uint32_t));
116+}
117+
118+std::string MirCursorConfiguration::cursor_name() const
119+{
120+ return name;
121+}
122+
123+bool MirCursorConfiguration::has_pixels() const
124+{
125+ return pixel_data != nullptr;
126+}
127+
128+std::tuple<uint32_t const*, geom::Size> MirCursorConfiguration::pixels() const
129+{
130+ return std::tuple<uint32_t const*, geom::Size>(pixel_data.get(), pixels_size);
131+}
132+
133+geom::Displacement MirCursorConfiguration::hotspot() const
134+{
135+ return hotspot_;
136+}
137+
138+void mir_cursor_configuration_destroy(MirCursorConfiguration *cursor)
139+{
140+ delete cursor;
141+}
142+
143+MirCursorConfiguration* mir_cursor_configuration_from_name(char const* name)
144+{
145+ try
146+ {
147+ return new MirCursorConfiguration(name);
148+ }
149+ catch (...)
150+ {
151+ return nullptr;
152+ }
153+}
154+
155+MirCursorConfiguration* mir_cursor_configuration_from_argb_8888(uint32_t const* pixels, unsigned width,
156+ unsigned height, unsigned hotspot_x, unsigned hotspot_y)
157+{
158+ try
159+ {
160+ return new MirCursorConfiguration(pixels, {width, height}, {hotspot_x, hotspot_y});
161+ }
162+ catch (...)
163+ {
164+ return nullptr;
165+ }
166+}
167+
168 char const *const mir_default_cursor_name = "default";
169 char const *const mir_disabled_cursor_name = "disabled";
170 char const* const mir_arrow_cursor_name = "arrow";
171@@ -36,25 +106,3 @@
172 char const* const mir_omnidirectional_resize_cursor_name = "omnidirectional-resize";
173 char const* const mir_vsplit_resize_cursor_name = "vsplit-resize";
174 char const* const mir_hsplit_resize_cursor_name = "hsplit-resize";
175-
176-MirCursorConfiguration::MirCursorConfiguration(char const* name) :
177- name{name ? name : std::string()}
178-{
179-}
180-
181-void mir_cursor_configuration_destroy(MirCursorConfiguration *cursor)
182-{
183- delete cursor;
184-}
185-
186-MirCursorConfiguration* mir_cursor_configuration_from_name(char const* name)
187-{
188- try
189- {
190- return new MirCursorConfiguration(name);
191- }
192- catch (...)
193- {
194- return nullptr;
195- }
196-}
197
198=== modified file 'src/client/mir_surface.cpp'
199--- src/client/mir_surface.cpp 2014-11-24 03:10:55 +0000
200+++ src/client/mir_surface.cpp 2014-11-25 19:34:43 +0000
201@@ -414,8 +414,26 @@
202 {
203 std::unique_lock<decltype(mutex)> lock(mutex);
204 setting.mutable_surfaceid()->CopyFrom(surface.id());
205- if (cursor && cursor->name != mir_disabled_cursor_name)
206- setting.set_name(cursor->name.c_str());
207+ if (cursor && cursor->has_pixels())
208+ {
209+ auto image = cursor->pixels();
210+ auto pixels = std::get<0>(image);
211+ auto size = std::get<1>(image);
212+
213+ google::protobuf::RepeatedField<uint32_t> r(pixels,
214+ pixels + (size.width.as_int() * size.height.as_int()));
215+ setting.mutable_pixels()->Swap(&r);
216+ setting.set_width(size.width.as_uint32_t());
217+ setting.set_height(size.height.as_uint32_t());
218+
219+ auto hotspot = cursor->hotspot();
220+ setting.set_hotspot_x(hotspot.dx.as_uint32_t());
221+ setting.set_hotspot_y(hotspot.dy.as_uint32_t());
222+ }
223+ else if (cursor && cursor->cursor_name() != mir_disabled_cursor_name)
224+ {
225+ setting.set_name(cursor->cursor_name().c_str());
226+ }
227 }
228
229 configure_cursor_wait_handle.expect_result();
230
231=== modified file 'src/protobuf/mir_protobuf.proto'
232--- src/protobuf/mir_protobuf.proto 2014-10-29 06:21:10 +0000
233+++ src/protobuf/mir_protobuf.proto 2014-11-25 19:34:43 +0000
234@@ -184,8 +184,16 @@
235
236 message CursorSetting {
237 required SurfaceId surfaceid = 1;
238- // No name is interpreted as disabled cursor.
239+
240 optional string name = 2;
241+ // If both pixels and name erroneously included, name will be used over pixels.
242+ // If pixels is included width and height must be included.
243+ // If both fields are omitted the cursor will be disabled.
244+ repeated uint32 pixels = 3;
245+ optional uint32 width = 4;
246+ optional uint32 height = 5;
247+ optional uint32 hotspot_x = 6;
248+ optional uint32 hotspot_y = 7;
249 }
250
251 message SocketFDRequest {
252
253=== modified file 'src/server/frontend/session_mediator.cpp'
254--- src/server/frontend/session_mediator.cpp 2014-11-21 12:16:36 +0000
255+++ src/server/frontend/session_mediator.cpp 2014-11-25 19:34:43 +0000
256@@ -37,6 +37,7 @@
257 #include "mir/graphics/pixel_format_utils.h"
258 #include "mir/graphics/platform_ipc_operations.h"
259 #include "mir/graphics/platform_ipc_package.h"
260+#include "mir/graphics/cursor_image.h"
261 #include "mir/frontend/client_constants.h"
262 #include "mir/frontend/event_sink.h"
263 #include "mir/frontend/screencast.h"
264@@ -483,6 +484,39 @@
265 };
266 }
267
268+namespace
269+{
270+struct Argb8888CursorImage : public mg::CursorImage
271+{
272+ Argb8888CursorImage(uint32_t const* pixels, geom::Size const& size, geom::Displacement hotspot) :
273+ pixel_data(new uint32_t[size.width.as_int()*size.height.as_int()]),
274+ pixels_size(size),
275+ hotspot_(hotspot)
276+ {
277+ memcpy(pixel_data.get(), pixels, size.width.as_int()*size.height.as_int()*sizeof(uint32_t));
278+ }
279+
280+ void const* as_argb_8888() const
281+ {
282+ return pixel_data.get();
283+ }
284+
285+ geom::Size size() const
286+ {
287+ return pixels_size;
288+ }
289+
290+ geom::Displacement hotspot() const
291+ {
292+ return hotspot_;
293+ }
294+
295+ std::unique_ptr<uint32_t[]> pixel_data;
296+ geom::Size pixels_size;
297+ geom::Displacement hotspot_;
298+};
299+}
300+
301 void mf::SessionMediator::configure_cursor(
302 google::protobuf::RpcController*,
303 mir::protobuf::CursorSetting const* cursor_request,
304@@ -507,6 +541,28 @@
305 auto const& image = cursor_images->image(cursor_request->name(), mi::default_cursor_size);
306 surface->set_cursor_image(image);
307 }
308+ else if (cursor_request->has_width() && cursor_request->has_height())
309+ {
310+ int width = cursor_request->width();
311+ int height = cursor_request->height();
312+ int hotspot_x = cursor_request->hotspot_x();
313+ int hotspot_y = cursor_request->hotspot_y();
314+
315+ auto const& pixels = cursor_request->pixels();
316+
317+ if (pixels.size() != width*height)
318+ BOOST_THROW_EXCEPTION(std::logic_error("cursor image pixel size does not match dimensions"));
319+
320+ auto size = geom::Size{width, height};
321+ if (size != mi::default_cursor_size)
322+ BOOST_THROW_EXCEPTION(std::logic_error("invalid cursor size only 24x24 cursors are currently supported"));
323+ if (hotspot_x > width || hotspot_y > height)
324+ BOOST_THROW_EXCEPTION(std::logic_error("hotspot past bounds of cursor"));
325+
326+ auto image = std::make_shared<Argb8888CursorImage>(pixels.data(), geom::Size{width, height},
327+ geom::Displacement{hotspot_x, hotspot_y});
328+ surface->set_cursor_image(image);
329+ }
330 else
331 {
332 surface->set_cursor_image({});
333
334=== modified file 'tests/acceptance-tests/test_client_cursor_api.cpp'
335--- tests/acceptance-tests/test_client_cursor_api.cpp 2014-10-29 06:21:10 +0000
336+++ tests/acceptance-tests/test_client_cursor_api.cpp 2014-11-25 19:34:43 +0000
337@@ -97,6 +97,21 @@
338 return cursor_is_named(arg, name);
339 }
340
341+MATCHER_P5(CursorWithPixels, pixels, width, height, hotspot_x, hotspot_y, "")
342+{
343+ if (width != arg.size().width.as_uint32_t())
344+ return false;
345+ if (height != arg.size().height.as_uint32_t())
346+ return false;
347+ if (hotspot_x != arg.hotspot().dx.as_uint32_t())
348+ return false;
349+ if (hotspot_y != arg.hotspot().dy.as_uint32_t())
350+ return false;
351+ if (memcmp(arg.as_argb_8888(), pixels, width*height*sizeof(uint32_t)) != 0)
352+ return false;
353+ return true;
354+}
355+
356 struct CursorClient
357 {
358 CursorClient(std::string const& connect_string, std::string const& client_name)
359@@ -207,6 +222,35 @@
360 std::string const cursor_name;
361 };
362
363+struct ImageCursorClient : CursorClient
364+{
365+ ImageCursorClient(
366+ std::string const& connect_string,
367+ std::string const& client_name,
368+ uint32_t const* argb_8888,
369+ unsigned width, unsigned height,
370+ unsigned hotspot_x, unsigned hotspot_y)
371+ : CursorClient{connect_string, client_name},
372+ argb_8888(argb_8888),
373+ width(width),
374+ height(height),
375+ hotspot_x(hotspot_x),
376+ hotspot_y(hotspot_y)
377+ {
378+ }
379+
380+ void setup_cursor(MirSurface* surface) override
381+ {
382+ auto conf = mir_cursor_configuration_from_argb_8888(argb_8888, width, height, hotspot_x, hotspot_y);
383+ mir_wait_for(mir_surface_configure_cursor(surface, conf));
384+ mir_cursor_configuration_destroy(conf);
385+ }
386+
387+ std::string const cursor_name;
388+ uint32_t const* argb_8888;
389+ unsigned width, height, hotspot_x, hotspot_y;
390+};
391+
392 struct TestServerConfiguration : mtf::FakeEventHubServerConfiguration
393 {
394 std::shared_ptr<mir::scene::PlacementStrategy> the_placement_strategy() override
395@@ -403,3 +447,38 @@
396
397 client_shutdown_expectations();
398 }
399+
400+TEST_F(TestClientCursorAPI, pixels_from_image_cursor_given_to_server_cursor)
401+{
402+ using namespace ::testing;
403+
404+ unsigned width = 24, height = 24, hotspot_x = 7, hotspot_y = 9;
405+ uint32_t pixels[24*24];
406+ uint32_t black = 0xffffffff, transparent = 0x00000000;
407+ for (unsigned i = 0; i < height; i++)
408+ {
409+ for (unsigned j = 0; j < width; j++)
410+ {
411+ auto &pixel = pixels[i*width+j];
412+ if (i % 2 == 0)
413+ pixel = black;
414+ else
415+ pixel = transparent;
416+ }
417+ }
418+
419+
420+ test_server_config().client_geometries[client_name_1] =
421+ geom::Rectangle{{1, 0}, {1, 1}};
422+
423+ ImageCursorClient client{new_connection(), client_name_1, pixels, width, height, hotspot_x, hotspot_y};
424+ client.run();
425+
426+ EXPECT_CALL(test_server_config().cursor, show(CursorWithPixels(pixels, width, height, hotspot_x, hotspot_y)))
427+ .WillOnce(mt::WakeUp(&expectations_satisfied));
428+ fake_event_hub()->synthesize_event(mis::a_motion_event().with_movement(1, 0));
429+
430+ expectations_satisfied.wait_for_at_most_seconds(5);
431+
432+ client_shutdown_expectations();
433+}

Subscribers

People subscribed via source and target branches