Mir

Merge lp:~alan-griffiths/mir/drag-and-drop-II into lp:mir

Proposed by Alan Griffiths on 2017-03-14
Status: Superseded
Proposed branch: lp:~alan-griffiths/mir/drag-and-drop-II
Merge into: lp:mir
Diff against target: 2548 lines (+1425/-27)
75 files modified
examples/server_example_basic_window_manager.cpp (+8/-0)
examples/server_example_basic_window_manager.h (+5/-0)
include/client/mir/events/event_builders.h (+2/-0)
include/client/mir_toolkit/extensions/drag_and_drop.h (+81/-0)
include/server/mir/scene/null_surface_observer.h (+1/-0)
include/server/mir/scene/surface.h (+1/-0)
include/server/mir/scene/surface_observer.h (+2/-0)
include/server/mir/shell/abstract_shell.h (+8/-0)
include/server/mir/shell/focus_controller.h (+5/-0)
include/server/mir/shell/input_targeter.h (+5/-0)
include/server/mir/shell/shell.h (+4/-0)
include/server/mir/shell/shell_wrapper.h (+8/-0)
include/server/mir/shell/system_compositor_window_manager.h (+5/-0)
include/server/mir/shell/window_manager.h (+5/-0)
include/test/mir/test/doubles/mock_window_manager.h (+1/-0)
include/test/mir/test/doubles/stub_surface.h (+1/-0)
include/test/mir_test_framework/observant_shell.h (+8/-0)
src/capnproto/mir_event.capnp (+5/-1)
src/client/CMakeLists.txt (+2/-0)
src/client/drag_and_drop.cpp (+85/-0)
src/client/drag_and_drop.h (+32/-0)
src/client/events/event_builders.cpp (+25/-0)
src/client/mir_blob.cpp (+1/-8)
src/client/mir_connection.cpp (+4/-0)
src/client/mir_surface.cpp (+32/-0)
src/client/mir_surface.h (+4/-0)
src/client/rpc/mir_display_server.cpp (+9/-0)
src/client/rpc/mir_display_server.h (+4/-0)
src/client/symbols.map (+2/-0)
src/common/events/pointer_event.cpp (+41/-3)
src/common/events/surface_event.cpp (+39/-1)
src/common/symbols.map (+4/-0)
src/include/common/mir/events/pointer_event.h (+6/-1)
src/include/common/mir/events/surface_event.h (+6/-1)
src/include/common/mir/protobuf/display_server.h (+4/-0)
src/include/common/mir_blob.h (+32/-0)
src/include/server/mir/frontend/shell.h (+5/-0)
src/include/server/mir/scene/surface_event_source.h (+1/-0)
src/include/server/mir/scene/surface_observers.h (+1/-0)
src/include/server/mir/shell/basic_window_manager.h (+15/-0)
src/include/server/mir/shell/canonical_window_manager.h (+4/-0)
src/protobuf/mir_protobuf.proto (+5/-0)
src/protobuf/symbols.map (+26/-0)
src/server/frontend/protobuf_message_processor.cpp (+4/-0)
src/server/frontend/session_mediator.cpp (+20/-1)
src/server/frontend/session_mediator.h (+4/-0)
src/server/frontend/shell_wrapper.cpp (+8/-0)
src/server/frontend/shell_wrapper.h (+5/-0)
src/server/input/null_input_targeter.h (+3/-0)
src/server/input/surface_input_dispatcher.cpp (+42/-8)
src/server/input/surface_input_dispatcher.h (+5/-1)
src/server/scene/basic_surface.cpp (+11/-0)
src/server/scene/basic_surface.h (+1/-0)
src/server/scene/legacy_surface_change_notification.cpp (+4/-0)
src/server/scene/legacy_surface_change_notification.h (+1/-0)
src/server/scene/null_surface_observer.cpp (+1/-0)
src/server/scene/surface_event_source.cpp (+5/-0)
src/server/shell/abstract_shell.cpp (+17/-0)
src/server/shell/basic_window_manager.cpp (+21/-0)
src/server/shell/canonical_window_manager.cpp (+13/-0)
src/server/shell/frontend_shell.cpp (+10/-0)
src/server/shell/frontend_shell.h (+5/-0)
src/server/shell/shell_wrapper.cpp (+18/-0)
src/server/shell/system_compositor_window_manager.cpp (+7/-0)
src/server/symbols.map (+10/-0)
tests/acceptance-tests/CMakeLists.txt (+2/-0)
tests/acceptance-tests/drag_and_drop.cpp (+624/-0)
tests/acceptance-tests/test_client_cursor_api.cpp (+1/-0)
tests/include/mir/test/doubles/mock_input_targeter.h (+3/-0)
tests/include/mir/test/doubles/mock_shell.h (+3/-0)
tests/include/mir/test/doubles/stub_display_server.h (+4/-0)
tests/include/mir/test/doubles/stub_input_targeter.h (+3/-0)
tests/include/mir/test/doubles/stub_scene_surface.h (+1/-0)
tests/mir_test_framework/observant_shell.cpp (+21/-2)
tests/mir_test_framework/stub_surface.cpp (+4/-0)
To merge this branch: bzr merge lp:~alan-griffiths/mir/drag-and-drop-II
Reviewer Review Type Date Requested Status
Alan Griffiths Needs Information on 2017-03-16
Andreas Pokorny (community) Needs Information on 2017-03-16
Chris Halse Rogers Needs Information on 2017-03-15
Brandon Schaefer (community) Approve on 2017-03-15
Cemil Azizoglu (community) Needs Information on 2017-03-15
Mir CI Bot continuous-integration 2017-03-14 Approve on 2017-03-15
Kevin DuBois (community) Approve on 2017-03-14
Review via email: mp+319820@code.launchpad.net

This proposal supersedes a proposal from 2017-03-13.

This proposal has been superseded by a proposal from 2017-03-17.

Commit Message

Initial support for Drag and Drop: tests (and implementation for) starting drag and releasing on target surface

Description of the Change

Initial support for Drag and Drop: tests (and implementation for) starting drag and releasing on target surface

One more episode to follow for handling the cancellation from content hub.

To post a comment you must log in.
Mir CI Bot (mir-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:4086
https://mir-jenkins.ubuntu.com/job/mir-ci/3142/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4216/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/4303
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/4293
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/4293
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/4293
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4243
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4243/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4243
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4243/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4243/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4243
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4243/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4243
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4243/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4243
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4243/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3142/rebuild

review: Needs Fixing (continuous-integration)
Alan Griffiths (alan-griffiths) wrote : Posted in a previous version of this proposal

18:39:25 [ RUN ] DragAndDrop.when_drag_moves_from_window_leave_event_contains_cookie
...
18:39:25 /<<BUILDDIR>>/mir-0.26.0+zesty4293bzr4086/tests/acceptance-tests/drag_and_drop.cpp:256: Failure
18:39:25 Value of: have_cookie.wait_for(receive_event_timeout)
18:39:25 Expected: is equal to true
18:39:25 Actual: false (of type bool)
18:39:25 ==11713== Invalid read of size 8

The memory errors and FD leaks that follow result from following the resulting nullptr.

review: Needs Fixing
Alan Griffiths (alan-griffiths) wrote : Posted in a previous version of this proposal

> 18:39:25 [ RUN ]
> DragAndDrop.when_drag_moves_from_window_leave_event_contains_cookie
> ...
> 18:39:25 /<<BUILDDIR>>/mir-0.26.0+zesty4293bzr4086/tests/acceptance-
> tests/drag_and_drop.cpp:256: Failure
> 18:39:25 Value of: have_cookie.wait_for(receive_event_timeout)
> 18:39:25 Expected: is equal to true
> 18:39:25 Actual: false (of type bool)
> 18:39:25 ==11713== Invalid read of size 8
>
> The memory errors and FD leaks that follow result from following the resulting
> nullptr.

Hmm wrong paste buffer, but still I don't see how this can happen:

18:35:34 11: [ RUN ] DragAndDrop.when_drag_moves_from_window_leave_event_contains_cookie
...
18:37:06 11: /<<BUILDDIR>>/mir-0.26.0+zesty4293bzr4086/tests/acceptance-tests/drag_and_drop.cpp:256: Failure
18:37:06 11: Value of: have_cookie.wait_for(receive_event_timeout)
18:37:06 11: Expected: is equal to true
18:37:06 11: Actual: false (of type bool)
18:37:06 11: ==11713== Invalid read of size 8

Basically, the click isn't seen by the window. And on this branch the window has definitely received {mir_window_attrib_focus, true}

Mir CI Bot (mir-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:4088
https://mir-jenkins.ubuntu.com/job/mir-ci/3151/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4230/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/4317
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/4307
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/4307
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/4307
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4257
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4257/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4257
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4257/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4257/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4257
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4257/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4257
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4257/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4257
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4257/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3151/rebuild

review: Needs Fixing (continuous-integration)
Mir CI Bot (mir-ci-bot) wrote : Posted in a previous version of this proposal

PASSED: Continuous integration, rev:4089
https://mir-jenkins.ubuntu.com/job/mir-ci/3153/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/4232
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/4319
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/4309
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/4309
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/4309
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4259
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4259/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4259
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4259/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4259
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4259/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4259
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4259/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4259
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4259/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4259
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4259/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3153/rebuild

review: Approve (continuous-integration)
Mir CI Bot (mir-ci-bot) wrote : Posted in a previous version of this proposal

PASSED: Continuous integration, rev:4090
https://mir-jenkins.ubuntu.com/job/mir-ci/3154/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/4233
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/4320
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/4310
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/4310
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/4310
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4260
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4260/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4260
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4260/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4260
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4260/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4260
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4260/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4260
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4260/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4260
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4260/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3154/rebuild

review: Approve (continuous-integration)
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:4091
https://mir-jenkins.ubuntu.com/job/mir-ci/3155/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4234/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/4321
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/4311
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/4311
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/4311
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4261
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4261/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4261
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4261/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4261
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4261/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4261/console
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4261/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4261
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4261/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4261
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4261/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3155/rebuild

review: Needs Fixing (continuous-integration)
Alan Griffiths (alan-griffiths) wrote :

Failure is unrelated (lp:1660889)

So that's 3 CI runs with the new tests passing. (And soak testing them locally.)

4092. By Alan Griffiths on 2017-03-14

Correct test names

Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:4092
https://mir-jenkins.ubuntu.com/job/mir-ci/3158/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/4237
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/4324
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/4314
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/4314
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/4314
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4264
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4264/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4264
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4264/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4264
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4264/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4264
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4264/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4264
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4264/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4264
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4264/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3158/rebuild

review: Approve (continuous-integration)
Kevin DuBois (kdub) wrote :

l773 could use comment here as well (like the other comment noting how CapPnP has problems)
+ //std::copy(dnd_handle.begin(), dnd_handle.end(), back_inserter(blob->data_));

+// We miss the "mouseover" occasionally (with valgrind and heavy stress about 1/20).
1995 +// But it isn't essential for the test and we've probably waited long enough
1996 +// for the mouse-down needed by the test to reach the window.
1997 +// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout), Eq(true));
does that need a bug?

both those minor, lgtm overall

review: Approve
4093. By Alan Griffiths on 2017-03-15

Better comment

4094. By Alan Griffiths on 2017-03-15

merge :parent

Alan Griffiths (alan-griffiths) wrote :

> l773 could use comment here as well (like the other comment noting how CapPnP
> has problems)
> + //std::copy(dnd_handle.begin(), dnd_handle.end(),
> back_inserter(blob->data_));

Fixed.

> +// We miss the "mouseover" occasionally (with valgrind and heavy stress about
> 1/20).
> 1995 +// But it isn't essential for the test and we've probably waited long
> enough
> 1996 +// for the mouse-down needed by the test to reach the window.
> 1997 +// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout),
> Eq(true));
> does that need a bug?

Possibly. It would appear there is a lack of consistency between the window management "model" that says the surface is ready and has focus (hence the mir_window_attrib_focus notification in paint_window) and the input "model" that does not (yet) route input to the surface. That's what you get by updating observers asynchronously - which, given the code we landed to support it, appears to have been a deliberate decision.

I doubt we'll see an issue in "real life".

Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:4094
https://mir-jenkins.ubuntu.com/job/mir-ci/3168/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/4251
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/4338
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/4328
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/4328
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/4328
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4278
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4278/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4278
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4278/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4278
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4278/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4278
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/4278/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4278
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/4278/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4278
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4278/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3168/rebuild

review: Approve (continuous-integration)
Cemil Azizoglu (cemil-azizoglu) wrote :

Why is this an extension? Because it requires an outside entity like a content hub?
---------------------------------

Is this needed?

130 + mir_surface_attrib_drag_and_drop_handle,
---------------------------------

review: Needs Information
Alan Griffiths (alan-griffiths) wrote :

> Why is this an extension? Because it requires an outside entity like a content
> hub?

So that we can evolve the API without breaking ABI

> Is this needed?
>
> 130 + mir_surface_attrib_drag_and_drop_handle,
> ---------------------------------

It doesn't have to be done this way, but the mir_surface_attribs and mir_window_attribs enum members have to be kept in step somehow. This seemed the clearest approach.

Kevin DuBois (kdub) wrote :

> > Why is this an extension? Because it requires an outside entity like a
> content
> > hub?
>
> So that we can evolve the API without breaking ABI
+1 to evolving client api this way, keeps 1.0 stable, and can be incorporated to main api once the work is done and there are no more warts.

Brandon Schaefer (brandontschaefer) wrote :

lgtm

review: Approve
Chris Halse Rogers (raof) wrote :

Needs info:
I'm not sure that overriding the events is appropriate - as I understand it, this will result in Mir sending pointer_enter/pointer_leave/pointer_motion events in cases where we currently don't?

It looks like this will cause clients to behave incorrectly unless their event handler checks each pointer event for a drag handle, and treats pointer events with drag handles as not pointer events?

For example: given a client B that doesn't check pointer events for drag handles, if you initiate a drag and then move the pointer over B's window, this will act like you're just moving the pointer normally over B's window, and B will highlight buttons, perform on-hover actions, and so on? And then if you release the drag over a button in B's window, B will respond as if you mouse-button-released on that button, possibly activating it?

Nit: The locking in
+void MirSurface::request_drag_and_drop(MirCookie const* cookie)
appears unnecessary? All of the things it accesses are stack variables (cookie) or written-once-at-initialisation (surface_id).

Is the locking for the appeasement of ThreadSanitizer?

review: Needs Information
Andreas Pokorny (andreas-pokorny) wrote :

> Needs info:
> I'm not sure that overriding the events is appropriate - as I understand it,
> this will result in Mir sending pointer_enter/pointer_leave/pointer_motion
> events in cases where we currently don't?

Yes leaving the boundaries of a window even when buttons are pressed will cause the source window to receive a pointer leave and potentially crossed windows to receive pointer_enter and motion events even though pointer buttons are still pressed.

>
> It looks like this will cause clients to behave incorrectly unless their event
> handler checks each pointer event for a drag handle, and treats pointer events
> with drag handles as not pointer events?

I doubt ui toolkits unaware of the drag handle will treat it as a click sequence, so there is wrong behavior but not really severe.

We cannot safely expand enums with extensions - so we either expand the MirPointerAction enum in an unsafe and problematic way (i.e. we have values like mir_pointer_actions) or we do not treat this as an extension or we accept the possible damage we do to current clients.

Andreas Pokorny (andreas-pokorny) wrote :

Now looking again - why do we need a surface attribute for drag and drop?

Are downstreams using values like: mir_window_attribs?

I think we should remove those.

Besides the enum changes and window attribute - lgtm

review: Needs Information
Alan Griffiths (alan-griffiths) wrote :

> > Needs info:
> > I'm not sure that overriding the events is appropriate - as I understand it,
> > this will result in Mir sending pointer_enter/pointer_leave/pointer_motion
> > events in cases where we currently don't?
>
> Yes leaving the boundaries of a window even when buttons are pressed will
> cause the source window to receive a pointer leave and potentially crossed
> windows to receive pointer_enter and motion events even though pointer buttons
> are still pressed.
>
> >
> > It looks like this will cause clients to behave incorrectly unless their
> event
> > handler checks each pointer event for a drag handle, and treats pointer
> events
> > with drag handles as not pointer events?
>
> I doubt ui toolkits unaware of the drag handle will treat it as a click
> sequence, so there is wrong behavior but not really severe.

The Nuremberg defence: https://goo.gl/AfG5v2 - shows the D&D handle carried by the mouse movement events. I don't know the domain well enough to be certain if the behaviour of drag-and-drop oblivious clients could be significantly affected, but it seems unlikely.

Alan Griffiths (alan-griffiths) wrote :

> Now looking again - why do we need a surface attribute for drag and drop?
>
> Are downstreams using values like: mir_window_attribs?
>
> I think we should remove those.
>
> Besides the enum changes and window attribute - lgtm

We needed some event to carry the drag handle. Extending the window event seemed the path of least resistance. But we've hard-coded the existence of an attribute value pair into the API.

I can think of two alternatives:

/1/ Use window events as the transport, but filter out those with a D&D handle (and pass the handle to an entirely separate callback registered through the extension API).

/2/ Introduce a new drag-and-drop event to the transport.

If we were to introduce a new drag-and-drop event to carry the initial handle then we could also use it for leave/enter/move/release - which would address some of the concerns about using pointer made above.

OTOH /1/ seems simpler.

*Seeking guidance* on /1/ or /2/

review: Needs Information
Andreas Pokorny (andreas-pokorny) wrote :

Which route will allow us to easily expand drag and drop to touch guestures?

Alan Griffiths (alan-griffiths) wrote :

> Which route will allow us to easily expand drag and drop to touch guestures?

I imagine the client-side processing needs to be different (i.e. different visual effects) so it might be easier to "piggy back" on the existing input events. But without a design for D&D touch I can't be sure.

/me wonders if Unity7 has prior art.

Chris Halse Rogers (raof) wrote :

I'd be reasonably happy with (1).

If people don't like throwing in an ad-hoc callback for each extension, an alternative would be to assign event types *in* the extension, like so:

struct DnDv1
{
    int (*dnd_event_type)();
    MirDndEvent const* (*dnd_event_from_event)(MirEvent const*);
    MirBlob* (*dnd_event_get_handle)(MirDndEvent const*);
    MirDndAction (*dnd_event_get_action)(MirDndEvent const*);
}

…
DnDv1* dnd = mir_drag_and_drop_v1(connection);
…
<in event handler>
if (mir_event_get_type(event) == dnd->dnd_event_type())
{
    MirDndEvent const* dnd_event = dnd->dnd_event_from_event(event);
    if (dnd->dnd_event_get_action(dnd_event) == mir_dnd_event_enter)
    {
        handle_drop_enter_with_handle(dnd->dnd_event_get_handle(dnd_event));
        â€¦
    }
}

I prefer this to one-callback-per-extension, but not strongly enough to block. I don't *think* it'd be significantly more work to implement than (1), though.

Alan Griffiths (alan-griffiths) wrote :

> I'd be reasonably happy with (1).
>
> If people don't like throwing in an ad-hoc callback for each extension, an
> alternative would be to assign event types *in* the extension, like so:
...
> if (mir_event_get_type(event) == dnd->dnd_event_type())

Perhaps:

- if (mir_event_get_type(event) == dnd->dnd_event_type())
+ if (dnd && dnd->is_dnd_event(event))

Chris Halse Rogers (raof) wrote :

Also happy with that :)

4095. By Alan Griffiths on 2017-03-17

merge lp:~alan-griffiths/mir/drag-and-drop-III

4096. By Alan Griffiths on 2017-03-17

merge :parent

4097. By Alan Griffiths on 2017-03-17

Rework avoid a fake WindowEvent attribute

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'examples/server_example_basic_window_manager.cpp'
2--- examples/server_example_basic_window_manager.cpp 2017-01-18 02:29:37 +0000
3+++ examples/server_example_basic_window_manager.cpp 2017-03-17 15:49:43 +0000
4@@ -136,6 +136,14 @@
5 policy->handle_raise_surface(session, surface);
6 }
7
8+void me::BasicWindowManager::handle_request_drag_and_drop(
9+ std::shared_ptr<scene::Session> const& /*session*/,
10+ std::shared_ptr<scene::Surface> const& /*surface*/,
11+ uint64_t /*timestamp*/)
12+{
13+ // Not supported in example servers
14+}
15+
16 int me::BasicWindowManager::set_surface_attribute(
17 std::shared_ptr<scene::Session> const& /*session*/,
18 std::shared_ptr<scene::Surface> const& surface,
19
20=== modified file 'examples/server_example_basic_window_manager.h'
21--- examples/server_example_basic_window_manager.h 2017-01-18 02:29:37 +0000
22+++ examples/server_example_basic_window_manager.h 2017-03-17 15:49:43 +0000
23@@ -174,6 +174,11 @@
24 std::shared_ptr<scene::Surface> const& surface,
25 uint64_t timestamp) override;
26
27+ void handle_request_drag_and_drop(
28+ std::shared_ptr<scene::Session> const& session,
29+ std::shared_ptr<scene::Surface> const& surface,
30+ uint64_t timestamp) override;
31+
32 int set_surface_attribute(
33 std::shared_ptr<scene::Session> const& /*session*/,
34 std::shared_ptr<scene::Surface> const& surface,
35
36=== modified file 'include/client/mir/events/event_builders.h'
37--- include/client/mir/events/event_builders.h 2017-02-15 13:36:35 +0000
38+++ include/client/mir/events/event_builders.h 2017-03-17 15:49:43 +0000
39@@ -161,6 +161,8 @@
40 void transform_positions(MirEvent& event, mir::geometry::Displacement const& movement);
41 void set_window_id(MirEvent& event, int window_id);
42
43+EventUPtr make_start_drag_and_drop_event(frontend::SurfaceId const& surface_id, std::vector<uint8_t> const& handle);
44+void set_drag_and_drop_handle(MirEvent& event, std::vector<uint8_t> const& handle);
45 }
46 }
47
48
49=== added file 'include/client/mir_toolkit/extensions/drag_and_drop.h'
50--- include/client/mir_toolkit/extensions/drag_and_drop.h 1970-01-01 00:00:00 +0000
51+++ include/client/mir_toolkit/extensions/drag_and_drop.h 2017-03-17 15:49:43 +0000
52@@ -0,0 +1,81 @@
53+/*
54+ * Copyright © 2017 Canonical Ltd.
55+ *
56+ * This program is free software: you can redistribute it and/or modify it
57+ * under the terms of the GNU Lesser General Public License version 3,
58+ * as published by the Free Software Foundation.
59+ *
60+ * This program is distributed in the hope that it will be useful,
61+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
62+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63+ * GNU Lesser General Public License for more details.
64+ *
65+ * You should have received a copy of the GNU Lesser General Public License
66+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
67+ *
68+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
69+ */
70+
71+#ifndef MIR_DRAG_AND_DROP_H
72+#define MIR_DRAG_AND_DROP_H
73+
74+#include "mir_toolkit/mir_extension_core.h"
75+#include "mir_toolkit/client_types.h"
76+
77+#ifdef __cplusplus
78+extern "C" {
79+#endif
80+
81+typedef struct MirDragAndDropEvent MirDragAndDropEvent;
82+
83+typedef struct MirDragAndDropV1
84+{
85+ /**
86+ * Request drag and drop. If the request succeeds a window event with a
87+ * "start drag" handle will be received.
88+ *
89+ * \warning An invalid cookie will terminate the client connection.
90+ *
91+ * \param [in] window The source window
92+ * \param [in] cookie A cookie instance obtained from an input event.
93+ */
94+ void (*request_drag_and_drop)(MirWindow* window, MirCookie const* cookie);
95+
96+ /**
97+ * Set the drag and drop callback. This receives
98+ *
99+ * \param [in] window The window
100+ * \param [in] callback The callback function.
101+ * \param [in] context To be passed to callback
102+ */
103+ void (*set_start_drag_and_drop_callback)(MirWindow* window,
104+ void (*callback)(MirWindow* window, MirDragAndDropEvent const* event, void* context),
105+ void* context);
106+
107+ /**
108+ * Retrieve any "drag & drop" handle associated with the event.
109+ *
110+ * \param [in] event The event
111+ * \return The associated drag handle or NULL
112+ */
113+ MirBlob* (*start_drag_and_drop)(MirDragAndDropEvent const* event);
114+
115+ /**
116+ * Retrieve any "drag & drop" handle associated with the event.
117+ *
118+ * \param [in] event The event
119+ * \return The associated drag handle or NULL
120+ */
121+ MirBlob* (*pointer_drag_and_drop)(MirPointerEvent const* event);
122+
123+} MirDragAndDropV1;
124+
125+static inline MirDragAndDropV1 const* mir_drag_and_drop_v1(MirConnection* connection)
126+{
127+ return (MirDragAndDropV1 const*) mir_connection_request_extension(connection, "mir_drag_and_drop", 1);
128+}
129+
130+#ifdef __cplusplus
131+}
132+#endif
133+#endif //MIR_DRAG_AND_DROP_H
134\ No newline at end of file
135
136=== modified file 'include/server/mir/scene/null_surface_observer.h'
137--- include/server/mir/scene/null_surface_observer.h 2017-02-15 13:36:35 +0000
138+++ include/server/mir/scene/null_surface_observer.h 2017-03-17 15:49:43 +0000
139@@ -47,6 +47,7 @@
140 void cursor_image_removed() override;
141 void placed_relative(geometry::Rectangle const& placement) override;
142 void input_consumed(MirEvent const* event) override;
143+ void start_drag_and_drop(std::vector<uint8_t> const& handle) override;
144
145 protected:
146 NullSurfaceObserver(NullSurfaceObserver const&) = delete;
147
148=== modified file 'include/server/mir/scene/surface.h'
149--- include/server/mir/scene/surface.h 2017-02-15 14:45:41 +0000
150+++ include/server/mir/scene/surface.h 2017-03-17 15:49:43 +0000
151@@ -116,6 +116,7 @@
152 virtual MirPointerConfinementState confine_pointer_state() const = 0;
153
154 virtual void placed_relative(geometry::Rectangle const& placement) = 0;
155+ virtual void start_drag_and_drop(std::vector<uint8_t> const& handle) = 0;
156 };
157 }
158 }
159
160=== modified file 'include/server/mir/scene/surface_observer.h'
161--- include/server/mir/scene/surface_observer.h 2017-02-15 13:36:35 +0000
162+++ include/server/mir/scene/surface_observer.h 2017-03-17 15:49:43 +0000
163@@ -27,6 +27,7 @@
164
165 #include <glm/glm.hpp>
166 #include <string>
167+#include <vector>
168
169 namespace mir
170 {
171@@ -62,6 +63,7 @@
172 virtual void cursor_image_removed() = 0;
173 virtual void placed_relative(geometry::Rectangle const& placement) = 0;
174 virtual void input_consumed(MirEvent const* event) = 0;
175+ virtual void start_drag_and_drop(std::vector<uint8_t> const& handle) = 0;
176
177 protected:
178 SurfaceObserver() = default;
179
180=== modified file 'include/server/mir/shell/abstract_shell.h'
181--- include/server/mir/shell/abstract_shell.h 2017-01-18 02:29:37 +0000
182+++ include/server/mir/shell/abstract_shell.h 2017-03-17 15:49:43 +0000
183@@ -82,6 +82,11 @@
184 std::shared_ptr<scene::Surface> const& surface,
185 uint64_t timestamp) override;
186
187+ void request_drag_and_drop(
188+ std::shared_ptr<scene::Session> const& session,
189+ std::shared_ptr<scene::Surface> const& surface,
190+ uint64_t timestamp) override;
191+
192 std::shared_ptr<scene::PromptSession> start_prompt_session_for(
193 std::shared_ptr<scene::Session> const& session,
194 scene::PromptSessionCreationParameters const& params) override;
195@@ -124,6 +129,9 @@
196
197 void update_focused_surface_confined_region();
198
199+ void set_drag_and_drop_handle(std::vector<uint8_t> const& handle) override;
200+ void clear_drag_and_drop_handle() override;
201+
202 protected:
203 std::shared_ptr<InputTargeter> const input_targeter;
204 std::shared_ptr<SurfaceStack> const surface_stack;
205
206=== modified file 'include/server/mir/shell/focus_controller.h'
207--- include/server/mir/shell/focus_controller.h 2016-01-29 08:18:22 +0000
208+++ include/server/mir/shell/focus_controller.h 2017-03-17 15:49:43 +0000
209@@ -19,8 +19,10 @@
210 #ifndef MIR_SHELL_FOCUS_CONTROLLER_H_
211 #define MIR_SHELL_FOCUS_CONTROLLER_H_
212
213+#include <stddef.h>
214 #include <memory>
215 #include <set>
216+#include <vector>
217
218 namespace mir
219 {
220@@ -54,6 +56,9 @@
221
222 virtual void raise(SurfaceSet const& surfaces) = 0;
223
224+ virtual void set_drag_and_drop_handle(std::vector<uint8_t> const& handle) = 0;
225+ virtual void clear_drag_and_drop_handle() = 0;
226+
227 protected:
228 FocusController() = default;
229 FocusController(FocusController const&) = delete;
230
231=== modified file 'include/server/mir/shell/input_targeter.h'
232--- include/server/mir/shell/input_targeter.h 2015-06-18 02:46:16 +0000
233+++ include/server/mir/shell/input_targeter.h 2017-03-17 15:49:43 +0000
234@@ -19,7 +19,9 @@
235 #ifndef MIR_SHELL_INPUT_TARGETER_H_
236 #define MIR_SHELL_INPUT_TARGETER_H_
237
238+#include <stddef.h>
239 #include <memory>
240+#include <vector>
241
242 namespace mir
243 {
244@@ -40,6 +42,9 @@
245 virtual void set_focus(std::shared_ptr<input::Surface> const& focus_surface) = 0;
246 virtual void clear_focus() = 0;
247
248+ virtual void set_drag_and_drop_handle(std::vector<uint8_t> const& handle) = 0;
249+ virtual void clear_drag_and_drop_handle() = 0;
250+
251 protected:
252 InputTargeter() = default;
253 InputTargeter(InputTargeter const&) = delete;
254
255=== modified file 'include/server/mir/shell/shell.h'
256--- include/server/mir/shell/shell.h 2017-01-19 00:27:55 +0000
257+++ include/server/mir/shell/shell.h 2017-03-17 15:49:43 +0000
258@@ -100,6 +100,10 @@
259 std::shared_ptr<scene::Surface> const& surface,
260 uint64_t timestamp) = 0;
261
262+ virtual void request_drag_and_drop(
263+ std::shared_ptr<scene::Session> const& session,
264+ std::shared_ptr<scene::Surface> const& surface,
265+ uint64_t timestamp) = 0;
266 /** @} */
267 };
268 }
269
270=== modified file 'include/server/mir/shell/shell_wrapper.h'
271--- include/server/mir/shell/shell_wrapper.h 2017-01-18 02:29:37 +0000
272+++ include/server/mir/shell/shell_wrapper.h 2017-03-17 15:49:43 +0000
273@@ -85,11 +85,19 @@
274 std::shared_ptr<scene::Surface> const& surface,
275 uint64_t timestamp) override;
276
277+ void request_drag_and_drop(
278+ std::shared_ptr<scene::Session> const& session,
279+ std::shared_ptr<scene::Surface> const& surface,
280+ uint64_t timestamp) override;
281+
282 void add_display(geometry::Rectangle const& area) override;
283 void remove_display(geometry::Rectangle const& area) override;
284
285 bool handle(MirEvent const& event) override;
286
287+ void set_drag_and_drop_handle(std::vector<uint8_t> const& handle) override;
288+ void clear_drag_and_drop_handle() override;
289+
290 protected:
291 std::shared_ptr<Shell> const wrapped;
292 };
293
294=== modified file 'include/server/mir/shell/system_compositor_window_manager.h'
295--- include/server/mir/shell/system_compositor_window_manager.h 2017-01-18 02:29:37 +0000
296+++ include/server/mir/shell/system_compositor_window_manager.h 2017-03-17 15:49:43 +0000
297@@ -95,6 +95,11 @@
298 std::shared_ptr<scene::Surface> const& surface,
299 uint64_t timestamp) override;
300
301+ void handle_request_drag_and_drop(
302+ std::shared_ptr<scene::Session> const& session,
303+ std::shared_ptr<scene::Surface> const& surface,
304+ uint64_t timestamp) override;
305+
306 int set_surface_attribute(
307 std::shared_ptr<scene::Session> const& session,
308 std::shared_ptr<scene::Surface> const& surface,
309
310=== modified file 'include/server/mir/shell/window_manager.h'
311--- include/server/mir/shell/window_manager.h 2017-01-18 02:29:37 +0000
312+++ include/server/mir/shell/window_manager.h 2017-03-17 15:49:43 +0000
313@@ -76,6 +76,11 @@
314 std::shared_ptr<scene::Surface> const& surface,
315 uint64_t timestamp) = 0;
316
317+ virtual void handle_request_drag_and_drop(
318+ std::shared_ptr<scene::Session> const& session,
319+ std::shared_ptr<scene::Surface> const& surface,
320+ uint64_t timestamp) = 0;
321+
322 virtual ~WindowManager() = default;
323 WindowManager() = default;
324 WindowManager(WindowManager const&) = delete;
325
326=== modified file 'include/test/mir/test/doubles/mock_window_manager.h'
327--- include/test/mir/test/doubles/mock_window_manager.h 2017-01-18 02:29:37 +0000
328+++ include/test/mir/test/doubles/mock_window_manager.h 2017-03-17 15:49:43 +0000
329@@ -58,6 +58,7 @@
330 MOCK_METHOD1(handle_pointer_event, bool(MirPointerEvent const*));
331
332 MOCK_METHOD3(handle_raise_surface, void(std::shared_ptr<scene::Session> const&, std::shared_ptr<scene::Surface> const&, uint64_t));
333+ MOCK_METHOD3(handle_request_drag_and_drop, void(std::shared_ptr<scene::Session> const&, std::shared_ptr<scene::Surface> const&, uint64_t));
334
335 MOCK_METHOD4(set_surface_attribute,
336 int(std::shared_ptr<scene::Session> const& session,
337
338=== modified file 'include/test/mir/test/doubles/stub_surface.h'
339--- include/test/mir/test/doubles/stub_surface.h 2017-02-15 14:45:41 +0000
340+++ include/test/mir/test/doubles/stub_surface.h 2017-03-17 15:49:43 +0000
341@@ -70,6 +70,7 @@
342 void set_confine_pointer_state(MirPointerConfinementState state) override;
343 MirPointerConfinementState confine_pointer_state() const override;
344 void placed_relative(geometry::Rectangle const& placement) override;
345+ void start_drag_and_drop(std::vector<uint8_t> const& handle) override;
346 };
347 }
348 }
349
350=== modified file 'include/test/mir_test_framework/observant_shell.h'
351--- include/test/mir_test_framework/observant_shell.h 2017-01-20 00:01:50 +0000
352+++ include/test/mir_test_framework/observant_shell.h 2017-03-17 15:49:43 +0000
353@@ -96,6 +96,14 @@
354 std::shared_ptr<mir::scene::Surface> const& window,
355 uint64_t timestamp) override;
356
357+ void request_drag_and_drop(
358+ std::shared_ptr<mir::scene::Session> const& session,
359+ std::shared_ptr<mir::scene::Surface> const& window,
360+ uint64_t timestamp) override;
361+
362+ void set_drag_and_drop_handle(std::vector<uint8_t> const& handle) override;
363+ void clear_drag_and_drop_handle() override;
364+
365 private:
366 std::shared_ptr<mir::shell::Shell> const wrapped;
367 std::shared_ptr<mir::scene::SurfaceObserver> const surface_observer;
368
369=== modified file 'src/capnproto/mir_event.capnp'
370--- src/capnproto/mir_event.capnp 2017-02-17 08:46:05 +0000
371+++ src/capnproto/mir_event.capnp 2017-03-17 15:49:43 +0000
372@@ -104,6 +104,8 @@
373
374 buttons @7 :UInt32;
375
376+ dndHandle @8 :List(UInt8);
377+
378 enum PointerAction
379 {
380 up @0;
381@@ -149,6 +151,7 @@
382 id @0 :Int32;
383 attrib @1 :Attrib;
384 value @2 :Int32;
385+ dndHandle @3 :List(UInt8);
386
387 enum Attrib
388 {
389@@ -160,8 +163,9 @@
390 dpi @4;
391 visibility @5;
392 preferredOrientation @6;
393+ startDragAndDrop @7;
394 # Must be last
395- surfaceAttrib @7;
396+ surfaceAttrib @8;
397 }
398 }
399
400
401=== modified file 'src/client/CMakeLists.txt'
402--- src/client/CMakeLists.txt 2017-01-24 13:43:12 +0000
403+++ src/client/CMakeLists.txt 2017-03-17 15:49:43 +0000
404@@ -49,6 +49,7 @@
405 add_library(mirclientobjects OBJECT
406
407 display_configuration.cpp
408+ drag_and_drop.cpp drag_and_drop.h
409 error_connections.cpp
410 event.cpp
411 event_printer.cpp
412@@ -105,6 +106,7 @@
413 ${CMAKE_SOURCE_DIR}/include/client/mir_toolkit/mir_error.h
414 mir_extension_core.cpp
415 ${CMAKE_SOURCE_DIR}/include/client/mir_toolkit/mir_extension_core.h
416+ ${CMAKE_SOURCE_DIR}/include/client/mir_toolkit/extensions/drag_and_drop.h
417 )
418
419 # Ensure protobuf C++ headers have been produced before
420
421=== added file 'src/client/drag_and_drop.cpp'
422--- src/client/drag_and_drop.cpp 1970-01-01 00:00:00 +0000
423+++ src/client/drag_and_drop.cpp 2017-03-17 15:49:43 +0000
424@@ -0,0 +1,85 @@
425+/*
426+ * Copyright © 2017 Canonical Ltd.
427+ *
428+ * This program is free software: you can redistribute it and/or modify it
429+ * under the terms of the GNU Lesser General Public License version 3,
430+ * as published by the Free Software Foundation.
431+ *
432+ * This program is distributed in the hope that it will be useful,
433+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
434+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
435+ * GNU Lesser General Public License for more details.
436+ *
437+ * You should have received a copy of the GNU Lesser General Public License
438+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
439+ *
440+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
441+ */
442+
443+#include "drag_and_drop.h"
444+#include "mir_toolkit/extensions/drag_and_drop.h"
445+
446+#include "mir/uncaught.h"
447+#include "mir/events/surface_event.h"
448+#include "mir/events/pointer_event.h"
449+
450+#include "mir_surface.h"
451+
452+namespace
453+{
454+
455+void request_drag_and_drop(MirWindow* window, MirCookie const* cookie)
456+try
457+{
458+ window->request_drag_and_drop(cookie);
459+}
460+catch (std::exception const& e)
461+{
462+ MIR_LOG_UNCAUGHT_EXCEPTION(e);
463+ abort();
464+}
465+
466+void set_start_drag_and_drop_callback(MirWindow* window,
467+ void (*callback)(MirWindow* window, MirDragAndDropEvent const* event, void* context),
468+ void* context)
469+try
470+{
471+ window->set_drag_and_drop_start_handler([callback,window,context](MirWindowEvent const* event)
472+ { callback(window, reinterpret_cast<MirDragAndDropEvent const*>(event), context); });
473+}
474+catch (std::exception const& e)
475+{
476+ MIR_LOG_UNCAUGHT_EXCEPTION(e);
477+ abort();
478+}
479+
480+MirBlob* start_drag_and_drop(MirDragAndDropEvent const* event)
481+try
482+{
483+ return reinterpret_cast<MirWindowEvent const*>(event)->dnd_handle();
484+}
485+catch (std::exception const& e)
486+{
487+ MIR_LOG_UNCAUGHT_EXCEPTION(e);
488+ abort();
489+}
490+
491+MirBlob* pointer_drag_and_drop(MirPointerEvent const* event)
492+try
493+{
494+ return event->dnd_handle();
495+}
496+catch (std::exception const& e)
497+{
498+ MIR_LOG_UNCAUGHT_EXCEPTION(e);
499+ abort();
500+}
501+
502+MirDragAndDropV1 const impl{
503+ &request_drag_and_drop,
504+ &set_start_drag_and_drop_callback,
505+ &start_drag_and_drop,
506+ &pointer_drag_and_drop};
507+}
508+
509+MirDragAndDropV1 const* const mir::drag_and_drop::v1 = &impl;
510
511=== added file 'src/client/drag_and_drop.h'
512--- src/client/drag_and_drop.h 1970-01-01 00:00:00 +0000
513+++ src/client/drag_and_drop.h 2017-03-17 15:49:43 +0000
514@@ -0,0 +1,32 @@
515+/*
516+ * Copyright © 2017 Canonical Ltd.
517+ *
518+ * This program is free software: you can redistribute it and/or modify it
519+ * under the terms of the GNU Lesser General Public License version 3,
520+ * as published by the Free Software Foundation.
521+ *
522+ * This program is distributed in the hope that it will be useful,
523+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
524+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
525+ * GNU Lesser General Public License for more details.
526+ *
527+ * You should have received a copy of the GNU Lesser General Public License
528+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
529+ *
530+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
531+ */
532+
533+#ifndef MIR_DRAG_AND_DROP_V1_H
534+#define MIR_DRAG_AND_DROP_V1_H
535+
536+typedef struct MirDragAndDropV1 MirDragAndDropV1;
537+
538+namespace mir
539+{
540+namespace drag_and_drop
541+{
542+extern MirDragAndDropV1 const* const v1;
543+}
544+}
545+
546+#endif //MIR_DRAG_AND_DROP_V1_H
547
548=== modified file 'src/client/events/event_builders.cpp'
549--- src/client/events/event_builders.cpp 2017-02-15 13:36:35 +0000
550+++ src/client/events/event_builders.cpp 2017-03-17 15:49:43 +0000
551@@ -110,6 +110,20 @@
552 return make_uptr_event(e);
553 }
554
555+auto mev::make_start_drag_and_drop_event(frontend::SurfaceId const& surface_id, std::vector<uint8_t> const& handle)
556+ -> EventUPtr
557+{
558+ auto e = new_event<MirWindowEvent>();
559+
560+ e->set_id(surface_id.as_value());
561+ e->set_attrib(mir_window_attribs);
562+ e->set_value(0);
563+ e->set_dnd_handle(handle);
564+
565+ return make_uptr_event(e);
566+
567+}
568+
569 mir::EventUPtr mev::make_event(mf::SurfaceId const& surface_id)
570 {
571 auto e = new_event<MirCloseWindowEvent>();
572@@ -450,3 +464,14 @@
573 BOOST_THROW_EXCEPTION(std::invalid_argument("Event has no window id."));
574 }
575 }
576+
577+void mev::set_drag_and_drop_handle(MirEvent& event, std::vector<uint8_t> const& handle)
578+{
579+ if (event.type() == mir_event_type_input)
580+ {
581+ auto const input_event = event.to_input();
582+ if (mir_input_event_get_type(input_event) == mir_input_event_type_pointer)
583+ const_cast<MirPointerEvent*>(mir_input_event_get_pointer_event(input_event))->set_dnd_handle(handle);
584+ }
585+}
586+
587
588=== modified file 'src/client/mir_blob.cpp'
589--- src/client/mir_blob.cpp 2017-01-18 02:29:37 +0000
590+++ src/client/mir_blob.cpp 2017-03-17 15:49:43 +0000
591@@ -16,6 +16,7 @@
592 * Authored by: Alan Griffiths <alan@octopull.co.uk>
593 */
594
595+#include "mir_blob.h"
596 #include "display_configuration.h"
597
598 #include "mir_toolkit/mir_blob.h"
599@@ -25,14 +26,6 @@
600
601 namespace mp = mir::protobuf;
602
603-struct MirBlob
604-{
605- virtual size_t size() const = 0;
606- virtual void const* data() const = 0;
607-
608- virtual ~MirBlob() = default;
609-};
610-
611 namespace
612 {
613 struct MirManagedBlob : MirBlob
614
615=== modified file 'src/client/mir_connection.cpp'
616--- src/client/mir_connection.cpp 2017-03-14 04:41:33 +0000
617+++ src/client/mir_connection.cpp 2017-03-17 15:49:43 +0000
618@@ -17,6 +17,7 @@
619 */
620
621 #include "mir_connection.h"
622+#include "drag_and_drop.h"
623 #include "mir_surface.h"
624 #include "mir_prompt_session.h"
625 #include "mir_toolkit/extensions/graphics_module.h"
626@@ -1451,6 +1452,9 @@
627 if (!strcmp(name, "mir_extension_graphics_module") && (version == 1) && graphics_module_extension.is_set())
628 return &graphics_module_extension.value();
629
630+ if (!strcmp(name, "mir_drag_and_drop") && (version == 1))
631+ return const_cast<MirDragAndDropV1*>(mir::drag_and_drop::v1);
632+
633 return platform->request_interface(name, version);
634 }
635
636
637=== modified file 'src/client/mir_surface.cpp'
638--- src/client/mir_surface.cpp 2017-03-14 04:41:33 +0000
639+++ src/client/mir_surface.cpp 2017-03-17 15:49:43 +0000
640@@ -24,6 +24,7 @@
641 #include "connection_surface_map.h"
642
643 #include "mir_toolkit/mir_client_library.h"
644+#include "mir_toolkit/mir_blob.h"
645 #include "mir/frontend/client_constants.h"
646 #include "mir/client_buffer.h"
647 #include "mir/mir_buffer_stream.h"
648@@ -32,6 +33,7 @@
649 #include "mir/cookie/cookie.h"
650 #include "mir_cookie.h"
651 #include "mir/time/posix_timestamp.h"
652+#include "mir/events/surface_event.h"
653
654 #include <cassert>
655 #include <unistd.h>
656@@ -493,7 +495,14 @@
657 auto sev = mir_event_get_window_event(&e);
658 auto a = mir_window_event_get_attribute(sev);
659 if (a < mir_window_attribs)
660+ {
661 attrib_cache[a] = mir_window_event_get_attribute_value(sev);
662+ }
663+ else
664+ {
665+ handle_drag_and_drop_start_callback(sev);
666+ return;
667+ }
668 break;
669 }
670 case mir_event_type_input:
671@@ -598,6 +607,29 @@
672 google::protobuf::NewCallback(google::protobuf::DoNothing));
673 }
674
675+void MirSurface::request_drag_and_drop(MirCookie const* cookie)
676+{
677+ mp::RequestAuthority authority;
678+
679+ std::unique_lock<decltype(mutex)> lock(mutex);
680+ authority.mutable_surface_id()->set_value(surface->id().value());
681+
682+ auto const event_cookie = authority.mutable_cookie();
683+
684+ event_cookie->set_cookie(cookie->cookie().data(), cookie->size());
685+
686+ server->request_drag_and_drop(
687+ &authority,
688+ void_response.get(),
689+ google::protobuf::NewCallback(google::protobuf::DoNothing));
690+}
691+
692+void MirSurface::set_drag_and_drop_start_handler(std::function<void(MirWindowEvent const*)> const& callback)
693+{
694+ std::lock_guard<decltype(mutex)> lock(mutex);
695+ handle_drag_and_drop_start_callback = callback;
696+}
697+
698 MirBufferStream* MirSurface::get_buffer_stream()
699 {
700 std::lock_guard<decltype(mutex)> lock(mutex);
701
702=== modified file 'src/client/mir_surface.h'
703--- src/client/mir_surface.h 2017-02-15 14:45:41 +0000
704+++ src/client/mir_surface.h 2017-03-17 15:49:43 +0000
705@@ -200,6 +200,8 @@
706 MirWaitHandle* set_preferred_orientation(MirOrientationMode mode);
707
708 void raise_surface(MirCookie const* cookie);
709+ void request_drag_and_drop(MirCookie const* cookie);
710+ void set_drag_and_drop_start_handler(std::function<void(MirWindowEvent const*)> const& callback);
711
712 MirWaitHandle* configure_cursor(MirCursorConfiguration const* cursor);
713
714@@ -262,6 +264,8 @@
715 std::shared_ptr<mir::client::FrameClock> const frame_clock;
716
717 std::function<void(MirEvent const*)> handle_event_callback;
718+ std::function<void(MirWindowEvent const*)> handle_drag_and_drop_start_callback = [](auto){};
719+
720 std::shared_ptr<mir::dispatch::ThreadedDispatcher> input_thread;
721
722 //a bit batty, but the creation handle has to exist for as long as the MirSurface does,
723
724=== modified file 'src/client/rpc/mir_display_server.cpp'
725--- src/client/rpc/mir_display_server.cpp 2017-02-15 07:38:33 +0000
726+++ src/client/rpc/mir_display_server.cpp 2017-03-17 15:49:43 +0000
727@@ -238,6 +238,15 @@
728 {
729 channel->call_method(std::string(__func__), request, response, done);
730 }
731+
732+void mclr::DisplayServer::request_drag_and_drop(
733+ mir::protobuf::RequestAuthority const* request,
734+ mir::protobuf::Void* response,
735+ google::protobuf::Closure* done)
736+{
737+ channel->call_method(std::string(__func__), request, response, done);
738+}
739+
740 void mclr::DisplayServer::apply_input_configuration(
741 mir::protobuf::InputConfigurationRequest const* request,
742 mir::protobuf::Void* response,
743
744=== modified file 'src/client/rpc/mir_display_server.h'
745--- src/client/rpc/mir_display_server.h 2017-02-15 07:38:33 +0000
746+++ src/client/rpc/mir_display_server.h 2017-03-17 15:49:43 +0000
747@@ -155,6 +155,10 @@
748 mir::protobuf::RaiseRequest const* request,
749 mir::protobuf::Void* response,
750 google::protobuf::Closure* done) override;
751+ void request_drag_and_drop(
752+ mir::protobuf::RequestAuthority const* request,
753+ mir::protobuf::Void* response,
754+ google::protobuf::Closure* done) override;
755 void apply_input_configuration(
756 mir::protobuf::InputConfigurationRequest const* request,
757 mir::protobuf::Void* response,
758
759=== modified file 'src/client/symbols.map'
760--- src/client/symbols.map 2017-03-14 04:41:33 +0000
761+++ src/client/symbols.map 2017-03-17 15:49:43 +0000
762@@ -604,6 +604,8 @@
763 global:
764 extern "C++" {
765 mir::events::set_window_id*;
766+ mir::events::make_start_drag_and_drop_event*;
767+ mir::events::set_drag_and_drop_handle*;
768 };
769 } MIR_CLIENT_DETAIL_0.26.1;
770
771
772=== modified file 'src/common/events/pointer_event.cpp'
773--- src/common/events/pointer_event.cpp 2016-11-07 23:02:26 +0000
774+++ src/common/events/pointer_event.cpp 2017-03-17 15:49:43 +0000
775@@ -1,5 +1,5 @@
776 /*
777- * Copyright © 2016 Canonical Ltd.
778+ * Copyright © 2016-2017 Canonical Ltd.
779 *
780 * This program is free software: you can redistribute it and/or modify it
781 * under the terms of the GNU Lesser General Public License version 3,
782@@ -16,10 +16,11 @@
783 * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
784 */
785
786+#include "mir/events/pointer_event.h"
787+#include "mir_blob.h"
788+
789 #include <boost/throw_exception.hpp>
790
791-#include "mir/events/pointer_event.h"
792-
793 MirPointerEvent::MirPointerEvent()
794 {
795 event.initInput();
796@@ -132,3 +133,40 @@
797 {
798 event.getInput().getPointer().setAction(static_cast<mir::capnp::PointerEvent::PointerAction>(action));
799 }
800+
801+void MirPointerEvent::set_dnd_handle(std::vector<uint8_t> const& handle)
802+{
803+ event.getInput().getPointer().initDndHandle(handle.size());
804+ event.getInput().getPointer().setDndHandle(::kj::ArrayPtr<uint8_t const>{&*begin(handle), &*end(handle)});
805+}
806+
807+namespace
808+{
809+struct MyMirBlob : MirBlob
810+{
811+
812+ size_t size() const override { return data_.size(); }
813+ virtual void const* data() const override { return data_.data(); }
814+
815+ std::vector<uint8_t> data_;
816+};
817+}
818+
819+MirBlob* MirPointerEvent::dnd_handle() const
820+{
821+ auto const reader = event.asReader().getInput().getPointer();
822+
823+ if (!reader.hasDndHandle())
824+ return nullptr;
825+
826+ auto const dnd_handle = reader.getDndHandle();
827+
828+ auto blob = std::make_unique<MyMirBlob>();
829+ blob->data_.reserve(dnd_handle.size());
830+
831+ // Can't use std::copy() as the CapnP iterators don't provide an iterator category
832+ for (auto p = dnd_handle.begin(); p != dnd_handle.end(); ++p)
833+ blob->data_.push_back(*p);
834+
835+ return blob.release();
836+}
837
838=== modified file 'src/common/events/surface_event.cpp'
839--- src/common/events/surface_event.cpp 2017-01-23 03:38:33 +0000
840+++ src/common/events/surface_event.cpp 2017-03-17 15:49:43 +0000
841@@ -1,5 +1,5 @@
842 /*
843- * Copyright © 2016 Canonical Ltd.
844+ * Copyright © 2016-2017 Canonical Ltd.
845 *
846 * This program is free software: you can redistribute it and/or modify it
847 * under the terms of the GNU Lesser General Public License version 3,
848@@ -17,6 +17,7 @@
849 */
850
851 #include "mir/events/surface_event.h"
852+#include "mir_blob.h"
853
854 // MirSurfaceEvent is a deprecated type, but we need to implement it
855 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
856@@ -55,3 +56,40 @@
857 {
858 event.getSurface().setValue(value);
859 }
860+
861+void MirSurfaceEvent::set_dnd_handle(std::vector<uint8_t> const& handle)
862+{
863+ event.getSurface().initDndHandle(handle.size());
864+ event.getSurface().setDndHandle(::kj::ArrayPtr<uint8_t const>{&*begin(handle), &*end(handle)});
865+}
866+
867+namespace
868+{
869+struct MyMirBlob : MirBlob
870+{
871+
872+ size_t size() const override { return data_.size(); }
873+ virtual void const* data() const override { return data_.data(); }
874+
875+ std::vector<uint8_t> data_;
876+};
877+}
878+
879+MirBlob* MirSurfaceEvent::dnd_handle() const
880+{
881+ if (!event.asReader().getSurface().hasDndHandle())
882+ return nullptr;
883+
884+ auto blob = std::make_unique<MyMirBlob>();
885+
886+ auto reader = event.asReader().getSurface().getDndHandle();
887+
888+ blob->data_.reserve(reader.size());
889+
890+ // Can't use std::copy() as the CapnP iterators don't provide an iterator category
891+ for (auto p = reader.begin(); p != reader.end(); ++p)
892+ blob->data_.push_back(*p);
893+
894+ return blob.release();
895+}
896+
897
898=== modified file 'src/common/symbols.map'
899--- src/common/symbols.map 2017-02-28 08:53:57 +0000
900+++ src/common/symbols.map 2017-03-17 15:49:43 +0000
901@@ -432,5 +432,9 @@
902 MirInputEvent::window_id*;
903 MirKeyboardEvent::set_text*;
904 MirKeyboardEvent::text*;
905+ MirPointerEvent::dnd_handle*;
906+ MirPointerEvent::set_dnd_handle*;
907+ MirSurfaceEvent::dnd_handle*;
908+ MirSurfaceEvent::set_dnd_handle*;
909 };
910 } MIR_COMMON_0.26;
911
912=== modified file 'src/include/common/mir/events/pointer_event.h'
913--- src/include/common/mir/events/pointer_event.h 2016-09-22 19:21:34 +0000
914+++ src/include/common/mir/events/pointer_event.h 2017-03-17 15:49:43 +0000
915@@ -1,5 +1,5 @@
916 /*
917- * Copyright © 2016 Canonical Ltd.
918+ * Copyright © 2016-2017 Canonical Ltd.
919 *
920 * This program is free software: you can redistribute it and/or modify it
921 * under the terms of the GNU Lesser General Public License version 3,
922@@ -21,6 +21,8 @@
923
924 #include "mir/events/input_event.h"
925
926+typedef struct MirBlob MirBlob;
927+
928 struct MirPointerEvent : MirInputEvent
929 {
930 MirPointerEvent();
931@@ -60,6 +62,9 @@
932
933 MirPointerButtons buttons() const;
934 void set_buttons(MirPointerButtons buttons);
935+
936+ void set_dnd_handle(std::vector<uint8_t> const& handle);
937+ MirBlob* dnd_handle() const;
938 private:
939 };
940
941
942=== modified file 'src/include/common/mir/events/surface_event.h'
943--- src/include/common/mir/events/surface_event.h 2017-01-18 02:29:37 +0000
944+++ src/include/common/mir/events/surface_event.h 2017-03-17 15:49:43 +0000
945@@ -1,5 +1,5 @@
946 /*
947- * Copyright © 2016 Canonical Ltd.
948+ * Copyright © 2016-2017 Canonical Ltd.
949 *
950 * This program is free software: you can redistribute it and/or modify it
951 * under the terms of the GNU Lesser General Public License version 3,
952@@ -21,6 +21,8 @@
953
954 #include "mir/events/event.h"
955
956+typedef struct MirBlob MirBlob;
957+
958 struct MirSurfaceEvent : MirEvent
959 {
960 MirSurfaceEvent();
961@@ -33,6 +35,9 @@
962
963 int value() const;
964 void set_value(int value);
965+
966+ void set_dnd_handle(std::vector<uint8_t> const& handle);
967+ MirBlob* dnd_handle() const;
968 };
969
970 #endif /* MIR_COMMON_SURFACE_EVENT_H_ */
971
972=== modified file 'src/include/common/mir/protobuf/display_server.h'
973--- src/include/common/mir/protobuf/display_server.h 2017-02-15 07:38:33 +0000
974+++ src/include/common/mir/protobuf/display_server.h 2017-03-17 15:49:43 +0000
975@@ -151,6 +151,10 @@
976 mir::protobuf::RaiseRequest const* request,
977 mir::protobuf::Void* response,
978 google::protobuf::Closure* done) = 0;
979+ virtual void request_drag_and_drop(
980+ mir::protobuf::RequestAuthority const* request,
981+ mir::protobuf::Void* response,
982+ google::protobuf::Closure* done) = 0;
983 virtual void apply_input_configuration(
984 mir::protobuf::InputConfigurationRequest const* request,
985 mir::protobuf::Void* response,
986
987=== added file 'src/include/common/mir_blob.h'
988--- src/include/common/mir_blob.h 1970-01-01 00:00:00 +0000
989+++ src/include/common/mir_blob.h 2017-03-17 15:49:43 +0000
990@@ -0,0 +1,32 @@
991+/*
992+ * Copyright © 2017 Canonical Ltd.
993+ *
994+ * This program is free software: you can redistribute it and/or modify
995+ * it under the terms of the GNU Lesser General Public License version 3 as
996+ * published by the Free Software Foundation.
997+ *
998+ * This program is distributed in the hope that it will be useful,
999+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1000+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1001+ * GNU Lesser General Public License for more details.
1002+ *
1003+ * You should have received a copy of the GNU Lesser General Public License
1004+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1005+ *
1006+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
1007+ */
1008+
1009+#ifndef MIR_MIR_BLOB_H_H
1010+#define MIR_MIR_BLOB_H_H
1011+
1012+#include <stddef.h>
1013+
1014+struct MirBlob
1015+{
1016+ virtual size_t size() const = 0;
1017+ virtual void const* data() const = 0;
1018+
1019+ virtual ~MirBlob() = default;
1020+};
1021+
1022+#endif //MIR_MIR_BLOB_H_H
1023
1024=== modified file 'src/include/server/mir/frontend/shell.h'
1025--- src/include/server/mir/frontend/shell.h 2017-01-18 02:29:37 +0000
1026+++ src/include/server/mir/frontend/shell.h 2017-03-17 15:49:43 +0000
1027@@ -87,6 +87,11 @@
1028 SurfaceId surface_id,
1029 uint64_t timestamp) = 0;
1030
1031+ virtual void request_drag_and_drop(
1032+ std::shared_ptr<Session> const& session,
1033+ SurfaceId surface_id,
1034+ uint64_t timestamp) = 0;
1035+
1036 protected:
1037 Shell() = default;
1038 Shell(const Shell&) = delete;
1039
1040=== modified file 'src/include/server/mir/scene/surface_event_source.h'
1041--- src/include/server/mir/scene/surface_event_source.h 2017-02-15 13:36:35 +0000
1042+++ src/include/server/mir/scene/surface_event_source.h 2017-03-17 15:49:43 +0000
1043@@ -51,6 +51,7 @@
1044 std::string const& variant, std::string const& options) override;
1045 void placed_relative(geometry::Rectangle const& placement) override;
1046 void input_consumed(MirEvent const* event) override;
1047+ void start_drag_and_drop(std::vector<uint8_t> const& handle) override;
1048
1049 private:
1050 frontend::SurfaceId const id;
1051
1052=== modified file 'src/include/server/mir/scene/surface_observers.h'
1053--- src/include/server/mir/scene/surface_observers.h 2017-02-15 13:36:35 +0000
1054+++ src/include/server/mir/scene/surface_observers.h 2017-03-17 15:49:43 +0000
1055@@ -51,6 +51,7 @@
1056 void cursor_image_removed() override;
1057 void placed_relative(geometry::Rectangle const& placement) override;
1058 void input_consumed(MirEvent const* event) override;
1059+ void start_drag_and_drop(std::vector<uint8_t> const& handle) override;
1060 };
1061
1062 }
1063
1064=== modified file 'src/include/server/mir/shell/basic_window_manager.h'
1065--- src/include/server/mir/shell/basic_window_manager.h 2017-01-18 02:29:37 +0000
1066+++ src/include/server/mir/shell/basic_window_manager.h 2017-03-17 15:49:43 +0000
1067@@ -65,6 +65,9 @@
1068
1069 virtual void raise_tree(std::shared_ptr<scene::Surface> const& root) = 0;
1070
1071+ virtual void set_drag_and_drop_handle(std::vector<uint8_t> const& handle) = 0;
1072+ virtual void clear_drag_and_drop_handle() = 0;
1073+
1074 virtual ~WindowManagerTools() = default;
1075 WindowManagerTools() = default;
1076 WindowManagerTools(WindowManagerTools const&) = delete;
1077@@ -107,6 +110,10 @@
1078 std::shared_ptr<scene::Session> const& session,
1079 std::shared_ptr<scene::Surface> const& surface) = 0;
1080
1081+ virtual void handle_request_drag_and_drop(
1082+ std::shared_ptr<scene::Session> const& session,
1083+ std::shared_ptr<scene::Surface> const& surface) = 0;
1084+
1085 virtual ~WindowManagementPolicy() = default;
1086 WindowManagementPolicy() = default;
1087 WindowManagementPolicy(WindowManagementPolicy const&) = delete;
1088@@ -163,6 +170,11 @@
1089 std::shared_ptr<scene::Surface> const& surface,
1090 uint64_t timestamp) override;
1091
1092+ void handle_request_drag_and_drop(
1093+ std::shared_ptr<scene::Session> const& session,
1094+ std::shared_ptr<scene::Surface> const& surface,
1095+ uint64_t timestamp) override;
1096+
1097 int set_surface_attribute(
1098 std::shared_ptr<scene::Session> const& /*session*/,
1099 std::shared_ptr<scene::Surface> const& surface,
1100@@ -192,6 +204,9 @@
1101
1102 void raise_tree(std::shared_ptr<scene::Surface> const& root) override;
1103
1104+ void set_drag_and_drop_handle(std::vector<uint8_t> const& handle) override;
1105+ void clear_drag_and_drop_handle() override;
1106+
1107 private:
1108 shell::FocusController* const focus_controller;
1109 std::unique_ptr<WindowManagementPolicy> const policy;
1110
1111=== modified file 'src/include/server/mir/shell/canonical_window_manager.h'
1112--- src/include/server/mir/shell/canonical_window_manager.h 2017-01-18 02:29:37 +0000
1113+++ src/include/server/mir/shell/canonical_window_manager.h 2017-03-17 15:49:43 +0000
1114@@ -75,6 +75,10 @@
1115 std::shared_ptr<scene::Session> const& session,
1116 std::shared_ptr<scene::Surface> const& surface) override;
1117
1118+ void handle_request_drag_and_drop(
1119+ std::shared_ptr<scene::Session> const& session,
1120+ std::shared_ptr<scene::Surface> const& surface) override;
1121+
1122 private:
1123 static const int modifier_mask =
1124 mir_input_event_modifier_alt |
1125
1126=== modified file 'src/protobuf/mir_protobuf.proto'
1127--- src/protobuf/mir_protobuf.proto 2017-03-14 04:41:33 +0000
1128+++ src/protobuf/mir_protobuf.proto 2017-03-17 15:49:43 +0000
1129@@ -444,6 +444,11 @@
1130 required SurfaceId surface_id = 2;
1131 }
1132
1133+message RequestAuthority {
1134+ required Cookie cookie = 1;
1135+ required SurfaceId surface_id = 2;
1136+}
1137+
1138 message InputDevices {
1139 repeated InputDeviceInfo device_info = 1;
1140 }
1141
1142=== modified file 'src/protobuf/symbols.map'
1143--- src/protobuf/symbols.map 2017-02-15 07:38:33 +0000
1144+++ src/protobuf/symbols.map 2017-03-17 15:49:43 +0000
1145@@ -1112,3 +1112,29 @@
1146 vtable?for?mir::protobuf::PromptSession;
1147 };
1148 } MIR_PROTOBUF_0.22;
1149+
1150+MIR_PROTOBUF_0.27 {
1151+ global:
1152+ extern "C++" {
1153+ mir::protobuf::RequestAuthority::ByteSize*;
1154+ mir::protobuf::RequestAuthority::CheckTypeAndMergeFrom*;
1155+ mir::protobuf::RequestAuthority::Clear*;
1156+ mir::protobuf::RequestAuthority::CopyFrom*;
1157+ mir::protobuf::RequestAuthority::default_instance*;
1158+ mir::protobuf::RequestAuthority::DiscardUnknownFields*;
1159+ mir::protobuf::RequestAuthority::GetTypeName*;
1160+ mir::protobuf::RequestAuthority::IsInitialized*;
1161+ mir::protobuf::RequestAuthority::kCookieFieldNumber*;
1162+ mir::protobuf::RequestAuthority::kSurfaceIdFieldNumber*;
1163+ mir::protobuf::RequestAuthority::MergeFrom*;
1164+ mir::protobuf::RequestAuthority::MergePartialFromCodedStream*;
1165+ mir::protobuf::RequestAuthority::New*;
1166+ mir::protobuf::RequestAuthority::?RequestAuthority*;
1167+ mir::protobuf::RequestAuthority::RequestAuthority*;
1168+ mir::protobuf::RequestAuthority::SerializeWithCachedSizes*;
1169+ mir::protobuf::RequestAuthority::Swap*;
1170+ non-virtual?thunk?to?mir::protobuf::RequestAuthority::?RequestAuthority*;
1171+ typeinfo?for?mir::protobuf::RequestAuthority;
1172+ vtable?for?mir::protobuf::RequestAuthority;
1173+ };
1174+} MIR_PROTOBUF_0.26;
1175
1176=== modified file 'src/server/frontend/protobuf_message_processor.cpp'
1177--- src/server/frontend/protobuf_message_processor.cpp 2017-02-15 07:38:33 +0000
1178+++ src/server/frontend/protobuf_message_processor.cpp 2017-03-17 15:49:43 +0000
1179@@ -305,6 +305,10 @@
1180 {
1181 invoke(this, display_server.get(), &protobuf::DisplayServer::stop_prompt_session, invocation);
1182 }
1183+ else if ("request_drag_and_drop" == invocation.method_name())
1184+ {
1185+ invoke(this, display_server.get(), &protobuf::DisplayServer::request_drag_and_drop, invocation);
1186+ }
1187 else if ("disconnect" == invocation.method_name())
1188 {
1189 invoke(this, display_server.get(), &DisplayServer::disconnect, invocation);
1190
1191=== modified file 'src/server/frontend/session_mediator.cpp'
1192--- src/server/frontend/session_mediator.cpp 2017-03-14 04:41:33 +0000
1193+++ src/server/frontend/session_mediator.cpp 2017-03-17 15:49:43 +0000
1194@@ -1175,6 +1175,26 @@
1195 done->Run();
1196 }
1197
1198+void mir::frontend::SessionMediator::request_drag_and_drop(mir::protobuf::RequestAuthority const* request,
1199+ mir::protobuf::Void*, google::protobuf::Closure* done)
1200+{
1201+ auto const session = weak_session.lock();
1202+ if (!session)
1203+ BOOST_THROW_EXCEPTION(std::logic_error("Invalid application session"));
1204+
1205+ auto const cookie = request->cookie();
1206+ auto const surface_id = request->surface_id();
1207+
1208+ auto cookie_string = cookie.cookie();
1209+
1210+ std::vector<uint8_t> cookie_bytes(cookie_string.begin(), cookie_string.end());
1211+ auto const cookie_ptr = cookie_authority->make_cookie(cookie_bytes);
1212+
1213+ shell->request_drag_and_drop(session, mf::SurfaceId{surface_id.value()}, cookie_ptr->timestamp());
1214+
1215+ done->Run();
1216+}
1217+
1218 void mf::SessionMediator::apply_input_configuration(
1219 mir::protobuf::InputConfigurationRequest const* request,
1220 mir::protobuf::Void*,
1221@@ -1254,7 +1274,6 @@
1222 screencast_buffer_tracker.remove_session(id);
1223 }
1224
1225-
1226 auto mf::detail::PromptSessionStore::insert(std::shared_ptr<PromptSession> const& session) -> PromptSessionId
1227 {
1228 std::lock_guard<decltype(mutex)> lock{mutex};
1229
1230=== modified file 'src/server/frontend/session_mediator.h'
1231--- src/server/frontend/session_mediator.h 2017-03-14 02:26:28 +0000
1232+++ src/server/frontend/session_mediator.h 2017-03-17 15:49:43 +0000
1233@@ -249,6 +249,10 @@
1234 mir::protobuf::RaiseRequest const* request,
1235 mir::protobuf::Void*,
1236 google::protobuf::Closure* done) override;
1237+ void request_drag_and_drop(
1238+ mir::protobuf::RequestAuthority const* request,
1239+ mir::protobuf::Void*,
1240+ google::protobuf::Closure* done) override;
1241 void apply_input_configuration(
1242 mir::protobuf::InputConfigurationRequest const* request,
1243 mir::protobuf::Void* response,
1244
1245=== modified file 'src/server/frontend/shell_wrapper.cpp'
1246--- src/server/frontend/shell_wrapper.cpp 2017-01-18 02:29:37 +0000
1247+++ src/server/frontend/shell_wrapper.cpp 2017-03-17 15:49:43 +0000
1248@@ -106,3 +106,11 @@
1249 {
1250 wrapped->raise_surface(session, surface_id, timestamp);
1251 }
1252+
1253+void mf::ShellWrapper::request_drag_and_drop(
1254+ std::shared_ptr<Session> const& session,
1255+ SurfaceId surface_id,
1256+ uint64_t timestamp)
1257+{
1258+ wrapped->request_drag_and_drop(session, surface_id, timestamp);
1259+}
1260
1261=== modified file 'src/server/frontend/shell_wrapper.h'
1262--- src/server/frontend/shell_wrapper.h 2017-01-18 02:29:37 +0000
1263+++ src/server/frontend/shell_wrapper.h 2017-03-17 15:49:43 +0000
1264@@ -80,6 +80,11 @@
1265 SurfaceId surface_id,
1266 uint64_t timestamp) override;
1267
1268+ void request_drag_and_drop(
1269+ std::shared_ptr<Session> const& session,
1270+ SurfaceId surface_id,
1271+ uint64_t timestamp) override;
1272+
1273 protected:
1274 std::shared_ptr<Shell> const wrapped;
1275 };
1276
1277=== modified file 'src/server/input/null_input_targeter.h'
1278--- src/server/input/null_input_targeter.h 2017-02-15 14:45:41 +0000
1279+++ src/server/input/null_input_targeter.h 2017-03-17 15:49:43 +0000
1280@@ -38,6 +38,9 @@
1281 void clear_focus() override
1282 {
1283 }
1284+
1285+ void set_drag_and_drop_handle(std::vector<uint8_t> const&) override {}
1286+ void clear_drag_and_drop_handle() override {}
1287 };
1288
1289 }
1290
1291=== modified file 'src/server/input/surface_input_dispatcher.cpp'
1292--- src/server/input/surface_input_dispatcher.cpp 2017-01-18 02:29:37 +0000
1293+++ src/server/input/surface_input_dispatcher.cpp 2017-03-17 15:49:43 +0000
1294@@ -70,7 +70,10 @@
1295 std::function<void(ms::Surface*)> const on_removed;
1296 };
1297
1298-void deliver_without_relative_motion(std::shared_ptr<mi::Surface> const& surface, MirEvent const* ev)
1299+void deliver_without_relative_motion(
1300+ std::shared_ptr<mi::Surface> const& surface,
1301+ MirEvent const* ev,
1302+ std::vector<uint8_t> const& drag_and_drop_handle)
1303 {
1304 auto const* input_ev = mir_event_get_input_event(ev);
1305 auto const* pev = mir_input_event_get_pointer_event(input_ev);
1306@@ -98,12 +101,21 @@
1307 0.0f);
1308
1309 mev::transform_positions(*to_deliver, geom::Displacement{bounds.top_left.x.as_int(), bounds.top_left.y.as_int()});
1310+ if (!drag_and_drop_handle.empty())
1311+ mev::set_drag_and_drop_handle(*to_deliver, drag_and_drop_handle);
1312 surface->consume(to_deliver.get());
1313 }
1314
1315-void deliver(std::shared_ptr<mi::Surface> const& surface, MirEvent const* ev)
1316+void deliver(
1317+ std::shared_ptr<mi::Surface> const& surface,
1318+ MirEvent const* ev,
1319+ std::vector<uint8_t> const& drag_and_drop_handle)
1320 {
1321 auto to_deliver = mev::clone_event(*ev);
1322+
1323+ if (!drag_and_drop_handle.empty())
1324+ mev::set_drag_and_drop_handle(*to_deliver, drag_and_drop_handle);
1325+
1326 auto const& bounds = surface->input_bounds();
1327 mev::transform_positions(*to_deliver, geom::Displacement{bounds.top_left.x.as_int(), bounds.top_left.y.as_int()});
1328 surface->consume(to_deliver.get());
1329@@ -243,6 +255,8 @@
1330 mir_pointer_event_axis_value(pev, mir_pointer_axis_relative_x),
1331 mir_pointer_event_axis_value(pev, mir_pointer_axis_relative_y));
1332
1333+ if (!drag_and_drop_handle.empty())
1334+ mev::set_drag_and_drop_handle(*event, drag_and_drop_handle);
1335 surface->consume(event.get());
1336 }
1337
1338@@ -270,12 +284,17 @@
1339
1340 if (pointer_state.gesture_owner)
1341 {
1342- deliver(pointer_state.gesture_owner, ev);
1343-
1344- if (is_gesture_terminator(pev))
1345+ deliver(pointer_state.gesture_owner, ev, drag_and_drop_handle);
1346+
1347+ auto const gesture_terminated = is_gesture_terminator(pev);
1348+
1349+ if (gesture_terminated)
1350 {
1351 pointer_state.gesture_owner.reset();
1352+ }
1353
1354+ if (gesture_terminated || !drag_and_drop_handle.empty())
1355+ {
1356 auto target = find_target_surface(event_x_y);
1357
1358 if (pointer_state.current_target != target)
1359@@ -286,6 +305,9 @@
1360 pointer_state.current_target = target;
1361 if (target)
1362 send_enter_exit_event(target, pev, mir_pointer_action_enter);
1363+
1364+ if (!gesture_terminated)
1365+ pointer_state.gesture_owner = target;
1366 }
1367 }
1368
1369@@ -323,11 +345,11 @@
1370 if (sent_ev)
1371 {
1372 if (action != mir_pointer_action_motion)
1373- deliver_without_relative_motion(target, ev);
1374+ deliver_without_relative_motion(target, ev, drag_and_drop_handle);
1375 }
1376 else
1377 {
1378- deliver(target, ev);
1379+ deliver(target, ev, drag_and_drop_handle);
1380 }
1381 return true;
1382 }
1383@@ -383,7 +405,7 @@
1384
1385 if (gesture_owner)
1386 {
1387- deliver(gesture_owner, ev);
1388+ deliver(gesture_owner, ev, drag_and_drop_handle);
1389
1390 if (is_gesture_end(tev))
1391 gesture_owner.reset();
1392@@ -450,3 +472,15 @@
1393 set_focus_locked(lg, nullptr);
1394 }
1395
1396+void mir::input::SurfaceInputDispatcher::set_drag_and_drop_handle(std::vector<uint8_t> const& handle)
1397+{
1398+ std::lock_guard<std::mutex> lg(dispatcher_mutex);
1399+ drag_and_drop_handle = handle;
1400+}
1401+
1402+void mir::input::SurfaceInputDispatcher::clear_drag_and_drop_handle()
1403+{
1404+ std::lock_guard<std::mutex> lg(dispatcher_mutex);
1405+ drag_and_drop_handle.clear();
1406+}
1407+
1408
1409=== modified file 'src/server/input/surface_input_dispatcher.h'
1410--- src/server/input/surface_input_dispatcher.h 2016-10-05 13:18:38 +0000
1411+++ src/server/input/surface_input_dispatcher.h 2017-03-17 15:49:43 +0000
1412@@ -54,7 +54,10 @@
1413 // InputTargeter
1414 void set_focus(std::shared_ptr<input::Surface> const& target) override;
1415 void clear_focus() override;
1416-
1417+
1418+ void set_drag_and_drop_handle(std::vector<uint8_t> const& handle) override;
1419+ void clear_drag_and_drop_handle() override;
1420+
1421 private:
1422 void device_reset(MirInputDeviceId reset_device_id, std::chrono::nanoseconds when);
1423 bool dispatch_key(MirEvent const* kev);
1424@@ -92,6 +95,7 @@
1425
1426 std::mutex dispatcher_mutex;
1427 std::weak_ptr<input::Surface> focus_surface;
1428+ std::vector<uint8_t> drag_and_drop_handle;
1429 bool started;
1430 };
1431
1432
1433=== modified file 'src/server/scene/basic_surface.cpp'
1434--- src/server/scene/basic_surface.cpp 2017-02-15 14:45:41 +0000
1435+++ src/server/scene/basic_surface.cpp 2017-03-17 15:49:43 +0000
1436@@ -144,6 +144,12 @@
1437 { observer->input_consumed(event); });
1438 }
1439
1440+void ms::SurfaceObservers::start_drag_and_drop(std::vector<uint8_t> const& handle)
1441+{
1442+ for_each([&](std::shared_ptr<SurfaceObserver> const& observer)
1443+ { observer->start_drag_and_drop(handle); });
1444+}
1445+
1446
1447 struct ms::CursorStreamImageAdapter
1448 {
1449@@ -923,3 +929,8 @@
1450 {
1451 observers.placed_relative(placement);
1452 }
1453+
1454+void mir::scene::BasicSurface::start_drag_and_drop(std::vector<uint8_t> const& handle)
1455+{
1456+ observers.start_drag_and_drop(handle);
1457+}
1458
1459=== modified file 'src/server/scene/basic_surface.h'
1460--- src/server/scene/basic_surface.h 2017-02-15 14:45:41 +0000
1461+++ src/server/scene/basic_surface.h 2017-03-17 15:49:43 +0000
1462@@ -139,6 +139,7 @@
1463 void set_confine_pointer_state(MirPointerConfinementState state) override;
1464 MirPointerConfinementState confine_pointer_state() const override;
1465 void placed_relative(geometry::Rectangle const& placement) override;
1466+ void start_drag_and_drop(std::vector<uint8_t> const& handle) override;
1467
1468 private:
1469 bool visible(std::unique_lock<std::mutex>&) const;
1470
1471=== modified file 'src/server/scene/legacy_surface_change_notification.cpp'
1472--- src/server/scene/legacy_surface_change_notification.cpp 2017-02-15 13:36:35 +0000
1473+++ src/server/scene/legacy_surface_change_notification.cpp 2017-03-17 15:49:43 +0000
1474@@ -108,3 +108,7 @@
1475 void ms::LegacySurfaceChangeNotification::input_consumed(MirEvent const*)
1476 {
1477 }
1478+
1479+void ms::LegacySurfaceChangeNotification::start_drag_and_drop(std::vector<uint8_t> const& /*handle*/)
1480+{
1481+}
1482\ No newline at end of file
1483
1484=== modified file 'src/server/scene/legacy_surface_change_notification.h'
1485--- src/server/scene/legacy_surface_change_notification.h 2017-02-15 13:36:35 +0000
1486+++ src/server/scene/legacy_surface_change_notification.h 2017-03-17 15:49:43 +0000
1487@@ -51,6 +51,7 @@
1488 void cursor_image_removed() override;
1489 void placed_relative(geometry::Rectangle const& placement) override;
1490 void input_consumed(MirEvent const* event) override;
1491+ void start_drag_and_drop(std::vector<uint8_t> const& handle) override;
1492
1493 private:
1494 std::function<void()> const notify_scene_change;
1495
1496=== modified file 'src/server/scene/null_surface_observer.cpp'
1497--- src/server/scene/null_surface_observer.cpp 2017-02-15 13:36:35 +0000
1498+++ src/server/scene/null_surface_observer.cpp 2017-03-17 15:49:43 +0000
1499@@ -41,3 +41,4 @@
1500 void ms::NullSurfaceObserver::cursor_image_removed() {}
1501 void ms::NullSurfaceObserver::placed_relative(geometry::Rectangle const& /*placement*/) {}
1502 void ms::NullSurfaceObserver::input_consumed(MirEvent const* /*event*/) {}
1503+void ms::NullSurfaceObserver::start_drag_and_drop(std::vector<uint8_t> const& /*handle*/) {}
1504
1505=== modified file 'src/server/scene/surface_event_source.cpp'
1506--- src/server/scene/surface_event_source.cpp 2017-02-15 13:36:35 +0000
1507+++ src/server/scene/surface_event_source.cpp 2017-03-17 15:49:43 +0000
1508@@ -101,3 +101,8 @@
1509 mev::set_window_id(*ev, id.as_value());
1510 event_sink->handle_event(*ev);
1511 }
1512+
1513+void ms::SurfaceEventSource::start_drag_and_drop(std::vector<uint8_t> const& handle)
1514+{
1515+ event_sink->handle_event(*mev::make_start_drag_and_drop_event(id, handle));
1516+}
1517
1518=== modified file 'src/server/shell/abstract_shell.cpp'
1519--- src/server/shell/abstract_shell.cpp 2017-03-10 19:47:57 +0000
1520+++ src/server/shell/abstract_shell.cpp 2017-03-17 15:49:43 +0000
1521@@ -251,6 +251,14 @@
1522 window_manager->handle_raise_surface(session, surface, timestamp);
1523 }
1524
1525+void msh::AbstractShell::request_drag_and_drop(
1526+ std::shared_ptr<scene::Session> const& session,
1527+ std::shared_ptr<scene::Surface> const& surface,
1528+ uint64_t timestamp)
1529+{
1530+ window_manager->handle_request_drag_and_drop(session, surface, timestamp);
1531+}
1532+
1533 void msh::AbstractShell::focus_next_session()
1534 {
1535 std::unique_lock<std::mutex> lock(focus_mutex);
1536@@ -401,3 +409,12 @@
1537 report->surfaces_raised(surfaces);
1538 }
1539
1540+void msh::AbstractShell::set_drag_and_drop_handle(std::vector<uint8_t> const& handle)
1541+{
1542+ input_targeter->set_drag_and_drop_handle(handle);
1543+}
1544+
1545+void msh::AbstractShell::clear_drag_and_drop_handle()
1546+{
1547+ input_targeter->clear_drag_and_drop_handle();
1548+}
1549
1550=== modified file 'src/server/shell/basic_window_manager.cpp'
1551--- src/server/shell/basic_window_manager.cpp 2017-01-18 02:29:37 +0000
1552+++ src/server/shell/basic_window_manager.cpp 2017-03-17 15:49:43 +0000
1553@@ -135,6 +135,16 @@
1554 policy->handle_raise_surface(session, surface);
1555 }
1556
1557+void msh::BasicWindowManager::handle_request_drag_and_drop(
1558+ std::shared_ptr<scene::Session> const& session,
1559+ std::shared_ptr<scene::Surface> const& surface,
1560+ uint64_t timestamp)
1561+{
1562+ std::lock_guard<decltype(mutex)> lock(mutex);
1563+ if (timestamp >= last_input_event_timestamp)
1564+ policy->handle_request_drag_and_drop(session, surface);
1565+}
1566+
1567 int msh::BasicWindowManager::set_surface_attribute(
1568 std::shared_ptr<scene::Session> const& /*session*/,
1569 std::shared_ptr<scene::Surface> const& surface,
1570@@ -309,3 +319,14 @@
1571 }
1572 }
1573 }
1574+
1575+void mir::shell::BasicWindowManager::set_drag_and_drop_handle(std::vector<uint8_t> const& handle)
1576+{
1577+ focus_controller->set_drag_and_drop_handle(handle);
1578+}
1579+
1580+void mir::shell::BasicWindowManager::clear_drag_and_drop_handle()
1581+{
1582+ focus_controller->clear_drag_and_drop_handle();
1583+}
1584+
1585
1586=== modified file 'src/server/shell/canonical_window_manager.cpp'
1587--- src/server/shell/canonical_window_manager.cpp 2017-03-10 19:47:57 +0000
1588+++ src/server/shell/canonical_window_manager.cpp 2017-03-17 15:49:43 +0000
1589@@ -24,6 +24,7 @@
1590 #include "mir/shell/surface_ready_observer.h"
1591 #include "mir/shell/display_layout.h"
1592
1593+#include <uuid/uuid.h>
1594 #include <linux/input.h>
1595 #include <csignal>
1596
1597@@ -583,6 +584,18 @@
1598 select_active_surface(surface);
1599 }
1600
1601+void msh::CanonicalWindowManagerPolicy::handle_request_drag_and_drop(
1602+ std::shared_ptr<ms::Session> const& /*session*/,
1603+ std::shared_ptr<ms::Surface> const& surface)
1604+{
1605+ uuid_t uuid;
1606+ uuid_generate(uuid);
1607+ std::vector<uint8_t> const handle{std::begin(uuid), std::end(uuid)};
1608+
1609+ surface->start_drag_and_drop(handle);
1610+ tools->set_drag_and_drop_handle(handle);
1611+}
1612+
1613 bool msh::CanonicalWindowManagerPolicy::handle_keyboard_event(MirKeyboardEvent const* event)
1614 {
1615 auto const action = mir_keyboard_event_action(event);
1616
1617=== modified file 'src/server/shell/frontend_shell.cpp'
1618--- src/server/shell/frontend_shell.cpp 2017-01-18 02:29:37 +0000
1619+++ src/server/shell/frontend_shell.cpp 2017-03-17 15:49:43 +0000
1620@@ -157,3 +157,13 @@
1621 auto const surface = scene_session->surface(surface_id);
1622 wrapped->raise_surface(scene_session, surface, timestamp);
1623 }
1624+
1625+void msh::FrontendShell::request_drag_and_drop(
1626+ std::shared_ptr<mf::Session> const& session,
1627+ mf::SurfaceId surface_id,
1628+ uint64_t timestamp)
1629+{
1630+ auto const scene_session = std::dynamic_pointer_cast<ms::Session>(session);
1631+ auto const surface = scene_session->surface(surface_id);
1632+ wrapped->request_drag_and_drop(scene_session, surface, timestamp);
1633+}
1634
1635=== modified file 'src/server/shell/frontend_shell.h'
1636--- src/server/shell/frontend_shell.h 2017-01-18 02:29:37 +0000
1637+++ src/server/shell/frontend_shell.h 2017-03-17 15:49:43 +0000
1638@@ -91,6 +91,11 @@
1639 std::shared_ptr<mf::Session> const& session,
1640 mf::SurfaceId surface_id,
1641 uint64_t timestamp) override;
1642+
1643+ void request_drag_and_drop(
1644+ std::shared_ptr<mf::Session> const& session,
1645+ mf::SurfaceId surface_id,
1646+ uint64_t timestamp) override;
1647 };
1648 }
1649 }
1650
1651=== modified file 'src/server/shell/shell_wrapper.cpp'
1652--- src/server/shell/shell_wrapper.cpp 2017-01-18 02:29:37 +0000
1653+++ src/server/shell/shell_wrapper.cpp 2017-03-17 15:49:43 +0000
1654@@ -120,6 +120,14 @@
1655 wrapped->raise_surface(session, surface, timestamp);
1656 }
1657
1658+void msh::ShellWrapper::request_drag_and_drop(
1659+ std::shared_ptr<ms::Session> const& session,
1660+ std::shared_ptr<ms::Surface> const& surface,
1661+ uint64_t timestamp)
1662+{
1663+ wrapped->request_drag_and_drop(session, surface, timestamp);
1664+}
1665+
1666 void msh::ShellWrapper::add_display(geometry::Rectangle const& area)
1667 {
1668 wrapped->add_display(area);
1669@@ -149,3 +157,13 @@
1670 {
1671 return wrapped->raise(surfaces);
1672 }
1673+
1674+void msh::ShellWrapper::set_drag_and_drop_handle(std::vector<uint8_t> const& handle)
1675+{
1676+ wrapped->set_drag_and_drop_handle(handle);
1677+}
1678+
1679+void msh::ShellWrapper::clear_drag_and_drop_handle()
1680+{
1681+ wrapped->clear_drag_and_drop_handle();
1682+}
1683
1684=== modified file 'src/server/shell/system_compositor_window_manager.cpp'
1685--- src/server/shell/system_compositor_window_manager.cpp 2017-01-18 02:29:37 +0000
1686+++ src/server/shell/system_compositor_window_manager.cpp 2017-03-17 15:49:43 +0000
1687@@ -201,3 +201,10 @@
1688 uint64_t /*timestamp*/)
1689 {
1690 }
1691+
1692+void msh::SystemCompositorWindowManager::handle_request_drag_and_drop(
1693+ std::shared_ptr<ms::Session> const& /*session*/,
1694+ std::shared_ptr<ms::Surface> const& /*surface*/,
1695+ uint64_t /*timestamp*/)
1696+{
1697+}
1698
1699=== modified file 'src/server/symbols.map'
1700--- src/server/symbols.map 2017-03-15 10:38:02 +0000
1701+++ src/server/symbols.map 2017-03-17 15:49:43 +0000
1702@@ -129,6 +129,7 @@
1703 mir::scene::NullSurfaceObserver::reception_mode_set_to*;
1704 mir::scene::NullSurfaceObserver::renamed*;
1705 mir::scene::NullSurfaceObserver::resized_to*;
1706+ mir::scene::NullSurfaceObserver::start_drag_and_drop*;
1707 mir::scene::NullSurfaceObserver::transformation_set_to*;
1708 mir::scene::Observer::?Observer*;
1709 mir::scene::Observer::Observer*;
1710@@ -293,6 +294,7 @@
1711 mir::shell::ShellReport::ShellReport*;
1712 mir::shell::ShellWrapper::add_display*;
1713 mir::shell::ShellWrapper::add_prompt_provider_for*;
1714+ mir::shell::ShellWrapper::clear_drag_and_drop_handle*;
1715 mir::shell::ShellWrapper::close_session*;
1716 mir::shell::ShellWrapper::create_surface*;
1717 mir::shell::ShellWrapper::destroy_surface*;
1718@@ -306,9 +308,11 @@
1719 mir::shell::ShellWrapper::raise*;
1720 mir::shell::ShellWrapper::raise_surface*;
1721 mir::shell::ShellWrapper::remove_display*;
1722+ mir::shell::ShellWrapper::request_drag_and_drop*;
1723 mir::shell::ShellWrapper::set_focus_to*;
1724 mir::shell::ShellWrapper::set_surface_attribute*;
1725 mir::shell::ShellWrapper::ShellWrapper*;
1726+ mir::shell::ShellWrapper::set_drag_and_drop_handle*;
1727 mir::shell::ShellWrapper::start_prompt_session_for*;
1728 mir::shell::ShellWrapper::stop_prompt_session*;
1729 mir::shell::ShellWrapper::surface_at*;
1730@@ -338,6 +342,7 @@
1731 mir::shell::SystemCompositorWindowManager::remove_display*;
1732 mir::shell::SystemCompositorWindowManager::remove_session*;
1733 mir::shell::SystemCompositorWindowManager::remove_surface*;
1734+ mir::shell::SystemCompositorWindowManager::handle_request_drag_and_drop*;
1735 mir::shell::SystemCompositorWindowManager::set_surface_attribute*;
1736 mir::shell::SystemCompositorWindowManager::SystemCompositorWindowManager*;
1737 mir::shell::WindowManager::operator*;
1738@@ -695,6 +700,7 @@
1739 mir::shell::CanonicalWindowManagerPolicy::handle_touch_event*;
1740 mir::shell::CanonicalWindowManagerPolicy::handle_pointer_event*;
1741 mir::shell::CanonicalWindowManagerPolicy::handle_raise_surface*;
1742+ mir::shell::CanonicalWindowManagerPolicy::handle_request_drag_and_drop*;
1743 typeinfo?for?mir::shell::CanonicalWindowManagerPolicy;
1744 vtable?for?mir::shell::CanonicalWindowManagerPolicy;
1745 VTT?for?mir::shell::CanonicalWindowManagerPolicy;
1746@@ -831,6 +837,7 @@
1747 mir::shell::BasicWindowManager::add_session*;
1748 mir::shell::BasicWindowManager::add_surface*;
1749 mir::shell::BasicWindowManager::BasicWindowManager*;
1750+ mir::shell::BasicWindowManager::clear_drag_and_drop_handle*;
1751 mir::shell::BasicWindowManager::find_session*;
1752 mir::shell::BasicWindowManager::focused_session*;
1753 mir::shell::BasicWindowManager::focused_surface*;
1754@@ -839,6 +846,7 @@
1755 mir::shell::BasicWindowManager::handle_keyboard_event*;
1756 mir::shell::BasicWindowManager::handle_pointer_event*;
1757 mir::shell::BasicWindowManager::handle_raise_surface*;
1758+ mir::shell::BasicWindowManager::handle_request_drag_and_drop*;
1759 mir::shell::BasicWindowManager::handle_touch_event*;
1760 mir::shell::BasicWindowManager::info_for*;
1761 mir::shell::BasicWindowManager::modify_surface*;
1762@@ -846,6 +854,7 @@
1763 mir::shell::BasicWindowManager::remove_display*;
1764 mir::shell::BasicWindowManager::remove_session*;
1765 mir::shell::BasicWindowManager::remove_surface*;
1766+ mir::shell::BasicWindowManager::set_drag_and_drop_handle*;
1767 mir::shell::BasicWindowManager::set_focus_to*;
1768 mir::shell::BasicWindowManager::set_surface_attribute*;
1769 mir::shell::BasicWindowManager::surface_at*;
1770@@ -856,6 +865,7 @@
1771 virtual?thunk?to?mir::shell::BasicWindowManager::handle_keyboard_event*;
1772 virtual?thunk?to?mir::shell::BasicWindowManager::handle_pointer_event*;
1773 virtual?thunk?to?mir::shell::BasicWindowManager::handle_raise_surface*;
1774+ virtual?thunk?to?mir::shell::BasicWindowManager::handle_request_drag_and_drop*;
1775 virtual?thunk?to?mir::shell::BasicWindowManager::handle_touch_event*;
1776 virtual?thunk?to?mir::shell::BasicWindowManager::modify_surface*;
1777 virtual?thunk?to?mir::shell::BasicWindowManager::remove_display*;
1778
1779=== modified file 'tests/acceptance-tests/CMakeLists.txt'
1780--- tests/acceptance-tests/CMakeLists.txt 2017-03-10 19:47:57 +0000
1781+++ tests/acceptance-tests/CMakeLists.txt 2017-03-17 15:49:43 +0000
1782@@ -3,6 +3,8 @@
1783 set(
1784 SOURCES
1785
1786+ drag_and_drop.cpp
1787+
1788 # Catch double-free bugs by wrapping close() and abort()ing on EBADF
1789 strict_close.cpp
1790
1791
1792=== added file 'tests/acceptance-tests/drag_and_drop.cpp'
1793--- tests/acceptance-tests/drag_and_drop.cpp 1970-01-01 00:00:00 +0000
1794+++ tests/acceptance-tests/drag_and_drop.cpp 2017-03-17 15:49:43 +0000
1795@@ -0,0 +1,624 @@
1796+/*
1797+ * Copyright © 2017 Canonical Ltd.
1798+ *
1799+ * This program is free software: you can redistribute it and/or modify it
1800+ * under the terms of the GNU General Public License version 3,
1801+ * as published by the Free Software Foundation.
1802+ *
1803+ * This program is distributed in the hope that it will be useful,
1804+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1805+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1806+ * GNU General Public License for more details.
1807+ *
1808+ * You should have received a copy of the GNU General Public License
1809+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1810+ *
1811+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
1812+ */
1813+
1814+#include <mir_toolkit/extensions/drag_and_drop.h>
1815+#include <mir_toolkit/mir_blob.h>
1816+
1817+#include <mir/geometry/displacement.h>
1818+#include <mir/input/input_device_info.h>
1819+#include <mir/input/device_capability.h>
1820+#include <mir/shell/shell.h>
1821+
1822+#include <mir_test_framework/connected_client_with_a_window.h>
1823+#include <mir_test_framework/fake_input_device.h>
1824+#include <mir_test_framework/stub_server_platform_factory.h>
1825+#include <mir/test/event_factory.h>
1826+#include <mir/test/signal.h>
1827+
1828+#include <gmock/gmock.h>
1829+#include <gtest/gtest.h>
1830+
1831+#include <linux/input.h>
1832+
1833+#include <boost/throw_exception.hpp>
1834+#include <atomic>
1835+
1836+using namespace std::chrono_literals;
1837+using namespace mir::geometry;
1838+using namespace testing;
1839+using mir::test::Signal;
1840+
1841+namespace
1842+{
1843+class Cookie
1844+{
1845+public:
1846+ Cookie() = default;
1847+ explicit Cookie(MirCookie const* cookie) : self{cookie, deleter} {}
1848+
1849+ operator MirCookie const*() const { return self.get(); }
1850+ auto get() const -> MirCookie const* { return self.get(); }
1851+
1852+ void reset() { self.reset(); }
1853+ void reset(MirCookie const* cookie) { self.reset(cookie, deleter); }
1854+
1855+private:
1856+ static void deleter(MirCookie const* cookie) { mir_cookie_release(cookie); }
1857+ std::shared_ptr<MirCookie const> self;
1858+};
1859+
1860+void mir_cookie_release(Cookie const&) = delete;
1861+
1862+class Blob
1863+{
1864+public:
1865+ Blob() = default;
1866+ explicit Blob(MirBlob* blob) : self{blob, deleter} {}
1867+
1868+ operator MirBlob*() const { return self.get(); }
1869+ auto get() const -> MirBlob* { return self.get(); }
1870+
1871+ void reset() { self.reset(); }
1872+ void reset(MirBlob* blob) { self.reset(blob, deleter); }
1873+
1874+private:
1875+ static void deleter(MirBlob* blob) { mir_blob_release(blob); }
1876+ std::shared_ptr<MirBlob> self;
1877+};
1878+
1879+void mir_blob_release(Blob const&) = delete;
1880+
1881+struct MouseMoverAndFaker
1882+{
1883+ void start_dragging_mouse()
1884+ {
1885+ using namespace mir::input::synthesis;
1886+ fake_mouse->emit_event(a_button_down_event().of_button(BTN_LEFT));
1887+ }
1888+
1889+ void move_mouse(Displacement const& displacement)
1890+ {
1891+ using mir::input::synthesis::a_pointer_event;
1892+ fake_mouse->emit_event(a_pointer_event().with_movement(displacement.dx.as_int(), displacement.dy.as_int()));
1893+ }
1894+
1895+ void release_mouse()
1896+ {
1897+ using namespace mir::input::synthesis;
1898+ fake_mouse->emit_event(a_button_up_event().of_button(BTN_LEFT));
1899+ }
1900+
1901+private:
1902+ std::unique_ptr<mir_test_framework::FakeInputDevice> fake_mouse{
1903+ mir_test_framework::add_fake_input_device(
1904+ mir::input::InputDeviceInfo{"mouse", "mouse-uid", mir::input::DeviceCapability::pointer})};
1905+};
1906+
1907+Rectangle const screen_geometry{{0,0}, {800,600}};
1908+auto const receive_event_timeout = 90s;
1909+
1910+struct DragAndDrop : mir_test_framework::ConnectedClientWithAWindow,
1911+ MouseMoverAndFaker
1912+{
1913+ MirDragAndDropV1 const* dnd = nullptr;
1914+
1915+ void SetUp() override
1916+ {
1917+ initial_display_layout({screen_geometry});
1918+ mir_test_framework::ConnectedClientWithAWindow::SetUp();
1919+ dnd = mir_drag_and_drop_v1(connection);
1920+ mir_window_set_event_handler(window, &window_event_handler, this);
1921+ if (dnd) dnd->set_start_drag_and_drop_callback(window, &window_dnd_start_handler, this);
1922+
1923+ create_target_window();
1924+
1925+ paint_window(window);
1926+
1927+ center_mouse();
1928+ }
1929+
1930+ void TearDown() override
1931+ {
1932+ reset_window_event_handler(target_window);
1933+ reset_window_event_handler(window);
1934+ mir_window_release_sync(target_window);
1935+ mir_connection_release(another_connection);
1936+ mir_test_framework::ConnectedClientWithAWindow::TearDown();
1937+ }
1938+
1939+ auto user_initiates_drag() -> Cookie;
1940+ auto client_requests_drag(Cookie const& cookie) -> Blob;
1941+ auto handle_from_mouse_move() -> Blob;
1942+ auto handle_from_mouse_leave() -> Blob;
1943+ auto handle_from_mouse_enter() -> Blob;
1944+ auto handle_from_mouse_release() -> Blob;
1945+ auto count_of_handles_when_moving_mouse() -> int;
1946+
1947+private:
1948+ void center_mouse();
1949+ void paint_window(MirWindow* w);
1950+ void set_window_event_handler(MirWindow* window, std::function<void(MirEvent const* event)> const& handler);
1951+ void set_window_dnd_start_handler(MirWindow* window, std::function<void(MirDragAndDropEvent const*)> const& handler);
1952+ void reset_window_event_handler(MirWindow* window);
1953+
1954+ void create_target_window()
1955+ {
1956+ another_connection = mir_connect_sync(new_connection().c_str(), "another_connection");
1957+ auto const spec = mir_create_normal_window_spec(
1958+ connection, screen_geometry.size.width.as_int(), screen_geometry.size.height.as_int());
1959+ mir_window_spec_set_pixel_format(spec, mir_pixel_format_abgr_8888);
1960+ mir_window_spec_set_name(spec, "target_window");
1961+ mir_window_spec_set_buffer_usage(spec, mir_buffer_usage_hardware);
1962+ mir_window_spec_set_event_handler(spec, &window_event_handler, this);
1963+
1964+ target_window = mir_create_window_sync(spec);
1965+ mir_window_spec_release(spec);
1966+
1967+ paint_window(target_window);
1968+ }
1969+
1970+ void invoke_window_event_handler(MirWindow* window, MirEvent const* event)
1971+ {
1972+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
1973+ if (window == this->window) window_event_handler_(event);
1974+ if (window == target_window) target_window_event_handler_(event);
1975+ }
1976+
1977+ void invoke_window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event)
1978+ {
1979+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
1980+ if (window == this->window) window_dnd_start_(event);
1981+ }
1982+
1983+ std::mutex window_event_handler_mutex;
1984+ std::function<void(MirDragAndDropEvent const* event)> window_dnd_start_ = [](MirDragAndDropEvent const*) {};
1985+ std::function<void(MirEvent const* event)> window_event_handler_ = [](MirEvent const*) {};
1986+ std::function<void(MirEvent const* event)> target_window_event_handler_ = [](MirEvent const*) {};
1987+
1988+ static void window_event_handler(MirWindow* window, MirEvent const* event, void* context);
1989+ static void window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event, void* context);
1990+
1991+ MirConnection* another_connection{nullptr};
1992+ MirWindow* target_window{nullptr};
1993+};
1994+
1995+void DragAndDrop::set_window_event_handler(MirWindow* window, std::function<void(MirEvent const* event)> const& handler)
1996+{
1997+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
1998+ if (window == this->window) window_event_handler_ = handler;
1999+ if (window == target_window) target_window_event_handler_ = handler;
2000+}
2001+
2002+void DragAndDrop::set_window_dnd_start_handler(MirWindow* window, std::function<void(MirDragAndDropEvent const*)> const& handler)
2003+{
2004+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
2005+ if (window == this->window) window_dnd_start_ = handler;
2006+}
2007+
2008+
2009+void DragAndDrop::reset_window_event_handler(MirWindow* window)
2010+{
2011+ if (window == this->window) window_event_handler_ = [](MirEvent const*) {};
2012+ if (window == target_window) target_window_event_handler_ = [](MirEvent const*) {};
2013+}
2014+
2015+void DragAndDrop::paint_window(MirWindow* w)
2016+{
2017+ Signal have_focus;
2018+
2019+ set_window_event_handler(w, [&](MirEvent const* event)
2020+ {
2021+ if (mir_event_get_type(event) != mir_event_type_window)
2022+ return;
2023+
2024+ auto const window_event = mir_event_get_window_event(event);
2025+ if (mir_window_event_get_attribute(window_event) != mir_window_attrib_focus)
2026+ return;
2027+
2028+ if (mir_window_event_get_attribute_value(window_event))
2029+ have_focus.raise();
2030+ });
2031+
2032+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(w));
2033+
2034+ EXPECT_THAT(have_focus.wait_for(receive_event_timeout), Eq(true));
2035+
2036+ reset_window_event_handler(w);
2037+}
2038+
2039+void DragAndDrop::center_mouse()
2040+{
2041+ Signal have_mouseover;
2042+
2043+ set_window_event_handler(window, [&](MirEvent const* event)
2044+ {
2045+ if (mir_event_get_type(event) != mir_event_type_input)
2046+ return;
2047+
2048+ auto const input_event = mir_event_get_input_event(event);
2049+
2050+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
2051+ return;
2052+
2053+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
2054+
2055+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter)
2056+ return;
2057+
2058+ have_mouseover.raise();
2059+ });
2060+
2061+ move_mouse(0.5 * as_displacement(screen_geometry.size));
2062+
2063+// We miss the "mouseover" occasionally (with valgrind and heavy stress about 1/20).
2064+// But it isn't essential for the test and we've probably waited long enough
2065+// for the mouse-down needed by the test to reach the window.
2066+// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout), Eq(true));
2067+ have_mouseover.wait_for(receive_event_timeout);
2068+
2069+ reset_window_event_handler(window);
2070+}
2071+
2072+void DragAndDrop::window_event_handler(MirWindow* window, MirEvent const* event, void* context)
2073+{
2074+ static_cast<DragAndDrop*>(context)->invoke_window_event_handler(window, event);
2075+}
2076+
2077+void DragAndDrop::window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event, void* context)
2078+{
2079+ static_cast<DragAndDrop*>(context)->invoke_window_dnd_start_handler(window, event);
2080+}
2081+
2082+
2083+auto DragAndDrop::user_initiates_drag() -> Cookie
2084+{
2085+ Cookie cookie;
2086+ Signal have_cookie;
2087+
2088+ set_window_event_handler(window, [&](MirEvent const* event)
2089+ {
2090+ if (mir_event_get_type(event) != mir_event_type_input)
2091+ return;
2092+
2093+ auto const input_event = mir_event_get_input_event(event);
2094+
2095+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
2096+ return;
2097+
2098+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
2099+
2100+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_down)
2101+ return;
2102+
2103+ cookie = Cookie{mir_input_event_get_cookie(input_event)};
2104+ have_cookie.raise();
2105+ });
2106+
2107+ start_dragging_mouse();
2108+
2109+ EXPECT_THAT(have_cookie.wait_for(receive_event_timeout), Eq(true));
2110+
2111+ reset_window_event_handler(window);
2112+ return cookie;
2113+}
2114+
2115+auto DragAndDrop::client_requests_drag(Cookie const& cookie) -> Blob
2116+{
2117+ Blob blob;
2118+ Signal initiated;
2119+
2120+ set_window_dnd_start_handler(window, [&](MirDragAndDropEvent const* event)
2121+ {
2122+ if (dnd)
2123+ blob.reset(dnd->start_drag_and_drop(event));
2124+
2125+ if (blob)
2126+ initiated.raise();
2127+ });
2128+
2129+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
2130+
2131+ if (dnd)
2132+ dnd->request_drag_and_drop(window, cookie);
2133+
2134+ EXPECT_TRUE(initiated.wait_for(receive_event_timeout));
2135+
2136+ reset_window_event_handler(window);
2137+ return blob;
2138+}
2139+
2140+auto DragAndDrop::handle_from_mouse_move() -> Blob
2141+{
2142+ Blob blob;
2143+ Signal have_blob;
2144+
2145+ set_window_event_handler(window, [&](MirEvent const* event)
2146+ {
2147+ if (mir_event_get_type(event) != mir_event_type_input)
2148+ return;
2149+
2150+ auto const input_event = mir_event_get_input_event(event);
2151+
2152+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
2153+ return;
2154+
2155+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
2156+
2157+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
2158+
2159+ if (dnd)
2160+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
2161+
2162+ if (blob)
2163+ have_blob.raise();
2164+ });
2165+
2166+ move_mouse({1,1});
2167+
2168+ EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
2169+
2170+ reset_window_event_handler(window);
2171+ return blob;
2172+}
2173+
2174+auto DragAndDrop::handle_from_mouse_leave() -> Blob
2175+{
2176+ Blob blob;
2177+ Signal have_blob;
2178+
2179+ set_window_event_handler(window, [&](MirEvent const* event)
2180+ {
2181+ if (mir_event_get_type(event) != mir_event_type_input)
2182+ return;
2183+
2184+ auto const input_event = mir_event_get_input_event(event);
2185+
2186+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
2187+ return;
2188+
2189+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
2190+
2191+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_leave)
2192+ return;
2193+
2194+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
2195+
2196+ if (dnd)
2197+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
2198+
2199+ if (blob)
2200+ have_blob.raise();
2201+ });
2202+
2203+ move_mouse({1,1});
2204+ move_mouse(0.5 * as_displacement(surface_size));
2205+
2206+ EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
2207+
2208+ reset_window_event_handler(window);
2209+ return blob;
2210+}
2211+
2212+auto DragAndDrop::handle_from_mouse_enter() -> Blob
2213+{
2214+ Blob blob;
2215+ Signal have_blob;
2216+
2217+ set_window_event_handler(target_window, [&](MirEvent const* event)
2218+ {
2219+ if (mir_event_get_type(event) != mir_event_type_input)
2220+ return;
2221+
2222+ auto const input_event = mir_event_get_input_event(event);
2223+
2224+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
2225+ return;
2226+
2227+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
2228+
2229+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter)
2230+ return;
2231+
2232+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
2233+
2234+ if (dnd)
2235+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
2236+
2237+ if (blob)
2238+ have_blob.raise();
2239+ });
2240+
2241+ move_mouse({1,1});
2242+ move_mouse(0.5 * as_displacement(surface_size));
2243+
2244+ EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
2245+
2246+ reset_window_event_handler(target_window);
2247+ return blob;
2248+}
2249+
2250+auto DragAndDrop::handle_from_mouse_release() -> Blob
2251+{
2252+ Blob blob;
2253+ Signal have_blob;
2254+
2255+ set_window_event_handler(target_window, [&](MirEvent const* event)
2256+ {
2257+ if (mir_event_get_type(event) != mir_event_type_input)
2258+ return;
2259+
2260+ auto const input_event = mir_event_get_input_event(event);
2261+
2262+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
2263+ return;
2264+
2265+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
2266+
2267+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_up)
2268+ return;
2269+
2270+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
2271+
2272+ if (dnd)
2273+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
2274+
2275+ if (blob)
2276+ have_blob.raise();
2277+ });
2278+
2279+ move_mouse({1,1});
2280+ move_mouse(0.5 * as_displacement(surface_size));
2281+ release_mouse();
2282+
2283+ EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
2284+
2285+ reset_window_event_handler(target_window);
2286+ return blob;
2287+}
2288+
2289+auto DragAndDrop::count_of_handles_when_moving_mouse() -> int
2290+{
2291+ Signal have_3_events;
2292+ std::atomic<int> events{0};
2293+ std::atomic<int> handles{0};
2294+
2295+ auto counter = [&](MirEvent const* event)
2296+ {
2297+ if (mir_event_get_type(event) != mir_event_type_input)
2298+ return;
2299+
2300+ auto const input_event = mir_event_get_input_event(event);
2301+
2302+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
2303+ return;
2304+
2305+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
2306+
2307+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
2308+
2309+ Blob blob;
2310+ if (dnd)
2311+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
2312+
2313+ if (blob)
2314+ handles.fetch_add(1);
2315+
2316+ if (events.fetch_add(1) == 2)
2317+ have_3_events.raise();
2318+ };
2319+
2320+ set_window_event_handler(window, counter);
2321+ set_window_event_handler(target_window, counter);
2322+
2323+ start_dragging_mouse();
2324+ move_mouse({1,1});
2325+ release_mouse();
2326+
2327+ EXPECT_TRUE(have_3_events.wait_for(receive_event_timeout));
2328+
2329+ reset_window_event_handler(window);
2330+ reset_window_event_handler(target_window);
2331+ return handles;
2332+}
2333+
2334+MATCHER_P(BlobContentEq, p, "")
2335+{
2336+ if (!arg || !p)
2337+ return false;
2338+ if (mir_blob_size(arg) != mir_blob_size(p))
2339+ return false;
2340+ return !memcmp(mir_blob_data(arg), mir_blob_data(p), mir_blob_size(p));
2341+}
2342+}
2343+
2344+TEST_F(DragAndDrop, when_user_initiates_drag_client_receives_cookie)
2345+{
2346+ auto const cookie = user_initiates_drag();
2347+
2348+ EXPECT_THAT(cookie.get(), NotNull());
2349+}
2350+
2351+TEST_F(DragAndDrop, when_client_requests_drags_it_receives_handle)
2352+{
2353+ auto const cookie = user_initiates_drag();
2354+ ASSERT_THAT(cookie.get(), NotNull());
2355+
2356+ auto const handle = client_requests_drag(cookie);
2357+
2358+ EXPECT_THAT(handle.get(), NotNull());
2359+}
2360+
2361+TEST_F(DragAndDrop, during_drag_when_user_moves_mouse_client_receives_handle)
2362+{
2363+ auto const cookie = user_initiates_drag();
2364+ ASSERT_THAT(cookie.get(), NotNull());
2365+ auto const handle_from_request = client_requests_drag(cookie);
2366+
2367+ auto const handle = handle_from_mouse_move();
2368+
2369+ EXPECT_THAT(handle.get(), NotNull());
2370+ EXPECT_THAT(handle.get(), BlobContentEq(handle_from_request.get()));
2371+}
2372+
2373+TEST_F(DragAndDrop, when_drag_moves_from_window_leave_event_contains_handle)
2374+{
2375+ auto const cookie = user_initiates_drag();
2376+ ASSERT_THAT(cookie.get(), NotNull());
2377+ auto const handle_from_request = client_requests_drag(cookie);
2378+
2379+ auto const handle = handle_from_mouse_leave();
2380+
2381+ EXPECT_THAT(handle.get(), NotNull());
2382+ EXPECT_THAT(handle.get(), BlobContentEq(handle_from_request.get()));
2383+}
2384+
2385+TEST_F(DragAndDrop, when_drag_enters_target_window_enter_event_contains_handle)
2386+{
2387+ auto const cookie = user_initiates_drag();
2388+ ASSERT_THAT(cookie.get(), NotNull());
2389+ auto const handle_from_request = client_requests_drag(cookie);
2390+
2391+ auto const handle = handle_from_mouse_enter();
2392+
2393+ EXPECT_THAT(handle.get(), NotNull());
2394+ EXPECT_THAT(handle.get(), BlobContentEq(handle_from_request.get()));
2395+}
2396+
2397+TEST_F(DragAndDrop, when_drag_releases_target_window_release_event_contains_handle)
2398+{
2399+ auto const cookie = user_initiates_drag();
2400+ ASSERT_THAT(cookie.get(), NotNull());
2401+ auto const handle_from_request = client_requests_drag(cookie);
2402+
2403+ auto const handle = handle_from_mouse_release();
2404+
2405+ EXPECT_THAT(handle.get(), NotNull());
2406+ EXPECT_THAT(handle.get(), BlobContentEq(handle_from_request.get()));
2407+}
2408+
2409+TEST_F(DragAndDrop, after_drag_finishes_pointer_events_no_longer_contain_handle)
2410+{
2411+ auto const cookie = user_initiates_drag();
2412+ ASSERT_THAT(cookie.get(), NotNull());
2413+ client_requests_drag(cookie);
2414+ handle_from_mouse_release();
2415+
2416+ server.the_shell()->clear_drag_and_drop_handle();
2417+
2418+ EXPECT_THAT(count_of_handles_when_moving_mouse(), Eq(0));
2419+}
2420
2421=== modified file 'tests/acceptance-tests/test_client_cursor_api.cpp'
2422--- tests/acceptance-tests/test_client_cursor_api.cpp 2017-02-28 08:53:57 +0000
2423+++ tests/acceptance-tests/test_client_cursor_api.cpp 2017-03-17 15:49:43 +0000
2424@@ -82,6 +82,7 @@
2425 MOCK_METHOD0(cursor_image_removed, void());
2426 MOCK_METHOD1(placed_relative, void(geom::Rectangle const& placement));
2427 MOCK_METHOD1(input_consumed, void(MirEvent const*));
2428+ MOCK_METHOD1(start_drag_and_drop, void(std::vector<uint8_t> const& handle));
2429 };
2430
2431
2432
2433=== modified file 'tests/include/mir/test/doubles/mock_input_targeter.h'
2434--- tests/include/mir/test/doubles/mock_input_targeter.h 2015-06-18 02:46:16 +0000
2435+++ tests/include/mir/test/doubles/mock_input_targeter.h 2017-03-17 15:49:43 +0000
2436@@ -35,6 +35,9 @@
2437 virtual ~MockInputTargeter() noexcept(true) {}
2438 MOCK_METHOD1(set_focus, void(std::shared_ptr<input::Surface> const&));
2439 MOCK_METHOD0(clear_focus, void());
2440+
2441+ void set_drag_and_drop_handle(std::vector<uint8_t> const&) override {}
2442+ void clear_drag_and_drop_handle() override {}
2443 };
2444
2445 }
2446
2447=== modified file 'tests/include/mir/test/doubles/mock_shell.h'
2448--- tests/include/mir/test/doubles/mock_shell.h 2017-01-18 02:29:37 +0000
2449+++ tests/include/mir/test/doubles/mock_shell.h 2017-03-17 15:49:43 +0000
2450@@ -72,6 +72,9 @@
2451
2452 MOCK_METHOD3(raise_surface, void(std::shared_ptr<frontend::Session> const& session,
2453 frontend::SurfaceId surface_id, uint64_t timestamp));
2454+
2455+ MOCK_METHOD3(request_drag_and_drop, void(std::shared_ptr<frontend::Session> const& session,
2456+ frontend::SurfaceId surface_id, uint64_t timestamp));
2457 };
2458
2459 }
2460
2461=== modified file 'tests/include/mir/test/doubles/stub_display_server.h'
2462--- tests/include/mir/test/doubles/stub_display_server.h 2017-02-15 07:38:33 +0000
2463+++ tests/include/mir/test/doubles/stub_display_server.h 2017-03-17 15:49:43 +0000
2464@@ -158,6 +158,10 @@
2465 mir::protobuf::RaiseRequest const* /*request*/,
2466 mir::protobuf::Void* /*response*/,
2467 google::protobuf::Closure* /*done*/) {}
2468+ void request_drag_and_drop(
2469+ mir::protobuf::RequestAuthority const* /*request*/,
2470+ mir::protobuf::Void* /*response*/,
2471+ google::protobuf::Closure* /*done*/) {}
2472 void apply_input_configuration(
2473 mir::protobuf::InputConfigurationRequest const* /*request*/,
2474 mir::protobuf::Void* /*response*/,
2475
2476=== modified file 'tests/include/mir/test/doubles/stub_input_targeter.h'
2477--- tests/include/mir/test/doubles/stub_input_targeter.h 2015-06-18 02:46:16 +0000
2478+++ tests/include/mir/test/doubles/stub_input_targeter.h 2017-03-17 15:49:43 +0000
2479@@ -36,6 +36,9 @@
2480 void clear_focus()
2481 {
2482 }
2483+
2484+ void set_drag_and_drop_handle(std::vector<uint8_t> const&) override {}
2485+ void clear_drag_and_drop_handle() override {}
2486 };
2487
2488 }
2489
2490=== modified file 'tests/include/mir/test/doubles/stub_scene_surface.h'
2491--- tests/include/mir/test/doubles/stub_scene_surface.h 2017-02-15 14:45:41 +0000
2492+++ tests/include/mir/test/doubles/stub_scene_surface.h 2017-03-17 15:49:43 +0000
2493@@ -94,6 +94,7 @@
2494 void set_confine_pointer_state(MirPointerConfinementState /*state*/) override {}
2495 MirPointerConfinementState confine_pointer_state() const override { return {}; }
2496 void placed_relative(geometry::Rectangle const& /*placement*/) override {}
2497+ void start_drag_and_drop(std::vector<uint8_t> const& /*handle*/) override {}
2498 };
2499
2500 }
2501
2502=== modified file 'tests/mir_test_framework/observant_shell.cpp'
2503--- tests/mir_test_framework/observant_shell.cpp 2017-01-20 00:01:50 +0000
2504+++ tests/mir_test_framework/observant_shell.cpp 2017-03-17 15:49:43 +0000
2505@@ -155,7 +155,26 @@
2506 void mtf::ObservantShell::raise_surface(
2507 std::shared_ptr<msc::Session> const& session,
2508 std::shared_ptr<msc::Surface> const& window,
2509+ uint64_t timestamp)
2510+{
2511+ return wrapped->raise_surface(session, window, timestamp);
2512+}
2513+
2514+void mtf::ObservantShell::request_drag_and_drop(
2515+ std::shared_ptr<msc::Session> const& session,
2516+ std::shared_ptr<msc::Surface> const& window,
2517 uint64_t timestamp)
2518 {
2519- return wrapped->raise_surface(session, window, timestamp);
2520-}
2521+ return wrapped->request_drag_and_drop(session, window, timestamp);
2522+}
2523+
2524+void mtf::ObservantShell::set_drag_and_drop_handle(std::vector<uint8_t> const& handle)
2525+{
2526+ wrapped->set_drag_and_drop_handle(handle);
2527+}
2528+
2529+void mtf::ObservantShell::clear_drag_and_drop_handle()
2530+{
2531+ wrapped->clear_drag_and_drop_handle();
2532+}
2533+
2534
2535=== modified file 'tests/mir_test_framework/stub_surface.cpp'
2536--- tests/mir_test_framework/stub_surface.cpp 2017-02-15 14:45:41 +0000
2537+++ tests/mir_test_framework/stub_surface.cpp 2017-03-17 15:49:43 +0000
2538@@ -198,6 +198,10 @@
2539 {
2540 }
2541
2542+void mtd::StubSurface::start_drag_and_drop(std::vector<uint8_t> const& /*handle*/)
2543+{
2544+}
2545+
2546 namespace
2547 {
2548 // Ensure we don't accidentally have an abstract class

Subscribers

People subscribed via source and target branches