diff -Nru miral-1.3.2+17.04.20170330.5/CMakeLists.txt miral-1.4.0+17.04.20170413/CMakeLists.txt --- miral-1.3.2+17.04.20170330.5/CMakeLists.txt 2017-03-30 14:18:18.000000000 +0000 +++ miral-1.4.0+17.04.20170413/CMakeLists.txt 2017-04-13 15:22:14.000000000 +0000 @@ -41,8 +41,8 @@ include_directories(include SYSTEM ${MIRCLIENT_INCLUDE_DIRS}) set(MIRAL_VERSION_MAJOR 1) -set(MIRAL_VERSION_MINOR 3) -set(MIRAL_VERSION_PATCH 2) +set(MIRAL_VERSION_MINOR 4) +set(MIRAL_VERSION_PATCH 0) set(MIRAL_VERSION ${MIRAL_VERSION_MAJOR}.${MIRAL_VERSION_MINOR}.${MIRAL_VERSION_PATCH}) diff -Nru miral-1.3.2+17.04.20170330.5/debian/changelog miral-1.4.0+17.04.20170413/debian/changelog --- miral-1.3.2+17.04.20170330.5/debian/changelog 2017-04-13 15:32:25.000000000 +0000 +++ miral-1.4.0+17.04.20170413/debian/changelog 2017-04-13 15:32:25.000000000 +0000 @@ -1,3 +1,15 @@ +miral (1.4.0+17.04.20170413-0ubuntu1) zesty; urgency=medium + + [ Alan Griffiths ] + * New upstream release 1.4.0 (https://launchpad.net/miral/+milestone/1.4.0) + - ABI summary: + . miral ABI unchanged at 2 + - Enhancements: + . Support for passing messages to enable Drag & Drop + - Bugs fixed: + + -- Brandon Schaefer Thu, 13 Apr 2017 15:22:34 +0000 + miral (1.3.2+17.04.20170330.5-0ubuntu1) zesty; urgency=medium * New upstream release 1.3.2 (https://launchpad.net/miral/+milestone/1.3.2) @@ -74,11 +86,11 @@ . Chrome-less shell hint does not work any more (LP: #1658117) . WindowSpec::set_state() wrapper for mir_window_spec_set_state() (LP: #1661256) - . "$ miral-app -kiosk" fails with "Unknown command line options: + . "$ miral-app -kiosk" fails with "Unknown command line options: --desktop_file_hint=miral-shell.desktop" (LP: #1660933) . libmiral] Fix focus and movement rules for Input Method and Satellite windows. (LP: #1660691) - + -- Alan Griffiths Wed, 15 Feb 2017 14:05:46 +0000 diff -Nru miral-1.3.2+17.04.20170330.5/debian/libmiral2.symbols miral-1.4.0+17.04.20170413/debian/libmiral2.symbols --- miral-1.3.2+17.04.20170330.5/debian/libmiral2.symbols 2017-04-13 15:32:25.000000000 +0000 +++ miral-1.4.0+17.04.20170413/debian/libmiral2.symbols 2017-04-13 15:32:25.000000000 +0000 @@ -389,3 +389,7 @@ (c++)"miral::SetWindowManagementPolicy::~SetWindowManagementPolicy()@MIRAL_1.3.1" 1.3.1 (c++)"miral::SetWindowManagementPolicy::~SetWindowManagementPolicy()@MIRAL_1.3.1" 1.3.1 (c++)"miral::SetWindowManagementPolicy::operator()(mir::Server&) const@MIRAL_1.3.1" 1.3.1 + MIRAL_1.4.0@MIRAL_1.4.0 1.4.0 + (c++)"miral::WindowManagerTools::end_drag_and_drop()@MIRAL_1.4.0" 1.4.0 + (c++)"miral::WindowManagerTools::start_drag_and_drop(miral::WindowInfo&, std::vector > const&)@MIRAL_1.4.0" 1.4.0 + (c++)"typeinfo for miral::WindowManagementPolicyAddendum2@MIRAL_1.4.0" 1.4.0 diff -Nru miral-1.3.2+17.04.20170330.5/include/miral/window_management_policy_addendum2.h miral-1.4.0+17.04.20170413/include/miral/window_management_policy_addendum2.h --- miral-1.3.2+17.04.20170330.5/include/miral/window_management_policy_addendum2.h 1970-01-01 00:00:00.000000000 +0000 +++ miral-1.4.0+17.04.20170413/include/miral/window_management_policy_addendum2.h 2017-04-13 15:22:14.000000000 +0000 @@ -0,0 +1,70 @@ +/* + * Copyright © 2017 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authored by: Alan Griffiths + */ + +#ifndef MIRAL_WINDOW_MANAGEMENT_ADDENDUM2_H +#define MIRAL_WINDOW_MANAGEMENT_ADDENDUM2_H + +#include +#include + +namespace miral +{ +struct WindowInfo; + +/** + * Handle additional client requests. + * + * \note This interface is intended to be implemented by a WindowManagementPolicy + * implementation, we can't add these functions directly to that interface without + * breaking ABI (the vtab could be incompatible). + * When initializing the window manager this interface will be detected by + * dynamic_cast and registered accordingly. + */ +class WindowManagementPolicyAddendum2 +{ +public: +/** @name handle requests originating from the client + * The policy is expected to update the model as appropriate + * @{ */ + /** request from client to initiate drag and drop + * \note the request has already been validated against the requesting event + * + * @param window_info the window + */ + virtual void handle_request_drag_and_drop(WindowInfo& window_info) = 0; + + /** request from client to initiate move + * \note the request has already been validated against the requesting event + * + * @param window_info the window + * @param input_event the requesting event + */ + virtual void handle_request_move(WindowInfo& window_info, MirInputEvent const* input_event) = 0; +/** @} */ + + virtual ~WindowManagementPolicyAddendum2() = default; + WindowManagementPolicyAddendum2() = default; + WindowManagementPolicyAddendum2(WindowManagementPolicyAddendum2 const&) = delete; + WindowManagementPolicyAddendum2& operator=(WindowManagementPolicyAddendum2 const&) = delete; +}; +#if MIRAL_VERSION >= MIR_VERSION_NUMBER(2, 0, 0) +#error "We've presumably broken ABI - please roll this interface into WindowManagementPolicy" +#endif +} + +#endif //MIRAL_WINDOW_MANAGEMENT_ADDENDUM2_H diff -Nru miral-1.3.2+17.04.20170330.5/include/miral/window_manager_tools.h miral-1.4.0+17.04.20170413/include/miral/window_manager_tools.h --- miral-1.3.2+17.04.20170330.5/include/miral/window_manager_tools.h 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/include/miral/window_manager_tools.h 2017-04-13 15:22:14.000000000 +0000 @@ -166,6 +166,18 @@ /// Raise window and all its children void raise_tree(Window const& root); + /** Start drag and drop. The handle will be passed to the client which can + * then use it to talk to the whatever service is being used to support drag + * and drop (e.g. on Ubuntu the content hub). + * + * @param window_info source window + * @param handle drag handle + */ + void start_drag_and_drop(WindowInfo& window_info, std::vector const& handle); + + /// End drag and drop + void end_drag_and_drop(); + /// Apply modifications to a window void modify_window(WindowInfo& window_info, WindowSpecification const& modifications); diff -Nru miral-1.3.2+17.04.20170330.5/miral/basic_window_manager.cpp miral-1.4.0+17.04.20170413/miral/basic_window_manager.cpp --- miral-1.3.2+17.04.20170330.5/miral/basic_window_manager.cpp 2017-03-30 14:18:18.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral/basic_window_manager.cpp 2017-04-13 15:22:14.000000000 +0000 @@ -19,6 +19,7 @@ #include "basic_window_manager.h" #include "miral/window_manager_tools.h" #include "miral/workspace_policy.h" +#include "miral/window_management_policy_addendum2.h" #include #include @@ -70,7 +71,6 @@ namespace { - auto find_workspace_policy(std::unique_ptr const& policy) -> miral::WorkspacePolicy* { miral::WorkspacePolicy* result = dynamic_cast(policy.get()); @@ -82,6 +82,23 @@ return &null_workspace_policy; } + +auto find_policy_addendum2(std::unique_ptr const& policy) -> miral::WindowManagementPolicyAddendum2* +{ + miral::WindowManagementPolicyAddendum2* result = dynamic_cast(policy.get()); + + if (result) + return result; + + struct NullWindowManagementPolicyAddendum2 : miral::WindowManagementPolicyAddendum2 + { + void handle_request_drag_and_drop(miral::WindowInfo&) override {} + void handle_request_move(miral::WindowInfo&, MirInputEvent const*) override {} + }; + static NullWindowManagementPolicyAddendum2 null_workspace_policy; + + return &null_workspace_policy; +} } @@ -94,10 +111,18 @@ display_layout(display_layout), persistent_surface_store{persistent_surface_store}, policy(build(WindowManagerTools{this})), - workspace_policy{find_workspace_policy(policy)} + workspace_policy{find_workspace_policy(policy)}, + policy2{find_policy_addendum2(policy)} { } +miral::BasicWindowManager::~BasicWindowManager() +{ +#if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 27, 0) + if (last_input_event) + mir_event_unref(last_input_event); +#endif +} void miral::BasicWindowManager::add_session(std::shared_ptr const& session) { Locker lock{this}; @@ -362,10 +387,24 @@ #if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 27, 0) void miral::BasicWindowManager::handle_request_drag_and_drop( std::shared_ptr const& /*session*/, - std::shared_ptr const& /*surface*/, - uint64_t /*timestamp*/) + std::shared_ptr const& surface, + uint64_t timestamp) { - // TODO + Locker lock{this}; + if (timestamp >= last_input_event_timestamp) + policy2->handle_request_drag_and_drop(info_for(surface)); +} + +void miral::BasicWindowManager::handle_request_move( + std::shared_ptr const& /*session*/, + std::shared_ptr const& surface, + uint64_t timestamp) +{ + std::lock_guard lock(mutex); + if (timestamp >= last_input_event_timestamp && last_input_event) + { + policy2->handle_request_move(info_for(surface), mir_event_get_input_event(last_input_event)); + } } #endif @@ -741,6 +780,24 @@ focus_controller->raise({begin(windows), end(windows)}); } +void miral::BasicWindowManager::start_drag_and_drop(WindowInfo& window_info, std::vector const& handle) +{ +#if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 27, 0) + std::shared_ptr(window_info.window())->start_drag_and_drop(handle); + focus_controller->set_drag_and_drop_handle(handle); +#else + (void)window_info; + (void)handle; +#endif +} + +void miral::BasicWindowManager::end_drag_and_drop() +{ +#if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 27, 0) + focus_controller->clear_drag_and_drop_handle(); +#endif +} + void miral::BasicWindowManager::move_tree(miral::WindowInfo& root, mir::geometry::Displacement movement) { if (movement == mir::geometry::Displacement{}) @@ -1163,25 +1220,22 @@ void miral::BasicWindowManager::update_event_timestamp(MirKeyboardEvent const* kev) { - auto iev = mir_keyboard_event_input_event(kev); - last_input_event_timestamp = mir_input_event_get_event_time(iev); + update_event_timestamp(mir_keyboard_event_input_event(kev)); } void miral::BasicWindowManager::update_event_timestamp(MirPointerEvent const* pev) { - auto iev = mir_pointer_event_input_event(pev); auto pointer_action = mir_pointer_event_action(pev); if (pointer_action == mir_pointer_action_button_up || pointer_action == mir_pointer_action_button_down) { - last_input_event_timestamp = mir_input_event_get_event_time(iev); + update_event_timestamp(mir_pointer_event_input_event(pev)); } } void miral::BasicWindowManager::update_event_timestamp(MirTouchEvent const* tev) { - auto iev = mir_touch_event_input_event(tev); auto touch_count = mir_touch_event_point_count(tev); for (unsigned i = 0; i < touch_count; i++) { @@ -1189,12 +1243,22 @@ if (touch_action == mir_touch_action_up || touch_action == mir_touch_action_down) { - last_input_event_timestamp = mir_input_event_get_event_time(iev); + update_event_timestamp(mir_touch_event_input_event(tev)); break; } } } +void miral::BasicWindowManager::update_event_timestamp(MirInputEvent const* iev) +{ + last_input_event_timestamp = mir_input_event_get_event_time(iev); +#if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 27, 0) + if (last_input_event) + mir_event_unref(last_input_event); + last_input_event = mir_event_ref(mir_input_event_get_event(iev)); +#endif +} + void miral::BasicWindowManager::invoke_under_lock(std::function const& callback) { Locker lock{this}; diff -Nru miral-1.3.2+17.04.20170330.5/miral/basic_window_manager.h miral-1.4.0+17.04.20170413/miral/basic_window_manager.h --- miral-1.3.2+17.04.20170330.5/miral/basic_window_manager.h 2017-03-30 14:18:18.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral/basic_window_manager.h 2017-04-13 15:22:14.000000000 +0000 @@ -46,6 +46,7 @@ namespace miral { class WorkspacePolicy; +class WindowManagementPolicyAddendum2; using mir::shell::SurfaceSet; using WindowManagementPolicyBuilder = std::function(miral::WindowManagerTools const& tools)>; @@ -61,6 +62,7 @@ std::shared_ptr const& display_layout, std::shared_ptr const& persistent_surface_store, WindowManagementPolicyBuilder const& build); + ~BasicWindowManager(); void add_session(std::shared_ptr const& session) override; @@ -101,6 +103,11 @@ std::shared_ptr const& session, std::shared_ptr const& surface, uint64_t timestamp) override; + + void handle_request_move( + std::shared_ptr const& session, + std::shared_ptr const& surface, + uint64_t timestamp) override; #endif int set_surface_attribute( @@ -161,6 +168,8 @@ auto active_display() -> mir::geometry::Rectangle const override; void raise_tree(Window const& root) override; + void start_drag_and_drop(WindowInfo& window_info, std::vector const& handle) override; + void end_drag_and_drop() override; void modify_window(WindowInfo& window_info, WindowSpecification const& modifications) override; @@ -190,6 +199,7 @@ std::unique_ptr const policy; WorkspacePolicy* const workspace_policy; + WindowManagementPolicyAddendum2* const policy2; std::mutex mutex; SessionInfoMap app_info; @@ -197,6 +207,9 @@ mir::geometry::Rectangles displays; mir::geometry::Point cursor; uint64_t last_input_event_timestamp{0}; +#if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 27, 0) + MirEvent const* last_input_event{nullptr}; +#endif miral::MRUWindowList mru_active_windows; using FullscreenSurfaces = std::set; FullscreenSurfaces fullscreen_surfaces; @@ -213,6 +226,7 @@ void update_event_timestamp(MirKeyboardEvent const* kev); void update_event_timestamp(MirPointerEvent const* pev); void update_event_timestamp(MirTouchEvent const* tev); + void update_event_timestamp(MirInputEvent const* iev); auto can_activate_window_for_session(miral::Application const& session) -> bool; auto can_activate_window_for_session_in_workspace( diff -Nru miral-1.3.2+17.04.20170330.5/miral/CMakeLists.txt miral-1.4.0+17.04.20170413/miral/CMakeLists.txt --- miral-1.3.2+17.04.20170330.5/miral/CMakeLists.txt 2017-03-30 14:18:18.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral/CMakeLists.txt 2017-04-13 15:22:14.000000000 +0000 @@ -61,6 +61,7 @@ ${CMAKE_SOURCE_DIR}/include/mir/client/display_config.h ${CMAKE_SOURCE_DIR}/include/mir/client/window.h ${CMAKE_SOURCE_DIR}/include/mir/client/detail/mir_forward_compatibility.h + ${CMAKE_SOURCE_DIR}/include/miral/window_management_policy_addendum2.h ) target_link_libraries(miral diff -Nru miral-1.3.2+17.04.20170330.5/miral/symbols.map miral-1.4.0+17.04.20170413/miral/symbols.map --- miral-1.3.2+17.04.20170330.5/miral/symbols.map 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral/symbols.map 2017-04-13 15:22:14.000000000 +0000 @@ -401,3 +401,24 @@ vtable?for?miral::SetWindowManagementPolicy; }; } MIRAL_1.3; + +MIRAL_1.4.0 { +global: + extern "C++" { + miral::WindowManagementPolicyAddendum2::?WindowManagementPolicyAddendum2*; + miral::WindowManagementPolicyAddendum2::WindowManagementPolicyAddendum2*; + miral::WindowManagementPolicyAddendum2::operator*; + miral::WindowManagerTools::end_drag_and_drop*; + miral::WindowManagerTools::start_drag_and_drop*; + miral::toolkit::Window::Window*; + non-virtual?thunk?to?miral::WindowManagementPolicyAddendum2::?WindowManagementPolicyAddendum2*; + typeinfo?for?miral::WindowManagementPolicyAddendum2; + typeinfo?for?miral::toolkit::Window; + typeinfo?for?miral::toolkit::WindowId; + typeinfo?for?miral::toolkit::WindowSpec; + vtable?for?miral::WindowManagementPolicyAddendum2; + vtable?for?miral::toolkit::Window; + vtable?for?miral::toolkit::WindowId; + vtable?for?miral::toolkit::WindowSpec; + }; +} MIRAL_1.3.1; diff -Nru miral-1.3.2+17.04.20170330.5/miral/window_management_trace.cpp miral-1.4.0+17.04.20170413/miral/window_management_trace.cpp --- miral-1.3.2+17.04.20170330.5/miral/window_management_trace.cpp 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral/window_management_trace.cpp 2017-04-13 15:22:14.000000000 +0000 @@ -547,6 +547,24 @@ } MIRAL_TRACE_EXCEPTION +void miral::WindowManagementTrace::start_drag_and_drop(miral::WindowInfo& window_info, std::vector const& handle) +try { + log_input(); + mir::log_info("%s window_info=%s", __func__, dump_of(window_info).c_str()); + trace_count++; + wrapped.start_drag_and_drop(window_info, handle); +} +MIRAL_TRACE_EXCEPTION + +void miral::WindowManagementTrace::end_drag_and_drop() +try { + log_input(); + mir::log_info("%s window_info=%s", __func__); + trace_count++; + wrapped.end_drag_and_drop(); +} +MIRAL_TRACE_EXCEPTION + void miral::WindowManagementTrace::modify_window( miral::WindowInfo& window_info, miral::WindowSpecification const& modifications) try { diff -Nru miral-1.3.2+17.04.20170330.5/miral/window_management_trace.h miral-1.4.0+17.04.20170413/miral/window_management_trace.h --- miral-1.3.2+17.04.20170330.5/miral/window_management_trace.h 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral/window_management_trace.h 2017-04-13 15:22:14.000000000 +0000 @@ -69,6 +69,8 @@ virtual void focus_prev_within_application() override; virtual void raise_tree(Window const& root) override; + virtual void start_drag_and_drop(WindowInfo& window_info, std::vector const& handle) override; + virtual void end_drag_and_drop() override; virtual void modify_window(WindowInfo& window_info, WindowSpecification const& modifications) override; diff -Nru miral-1.3.2+17.04.20170330.5/miral/window_manager_tools.cpp miral-1.4.0+17.04.20170413/miral/window_manager_tools.cpp --- miral-1.3.2+17.04.20170330.5/miral/window_manager_tools.cpp 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral/window_manager_tools.cpp 2017-04-13 15:22:14.000000000 +0000 @@ -83,6 +83,12 @@ void miral::WindowManagerTools::raise_tree(Window const& root) { tools->raise_tree(root); } +void miral::WindowManagerTools::start_drag_and_drop(WindowInfo& window_info, std::vector const& handle) +{ tools->start_drag_and_drop(window_info, handle); } + +void miral::WindowManagerTools::end_drag_and_drop() +{ tools->end_drag_and_drop(); } + void miral::WindowManagerTools::modify_window(WindowInfo& window_info, WindowSpecification const& modifications) { tools->modify_window(window_info,modifications); } diff -Nru miral-1.3.2+17.04.20170330.5/miral/window_manager_tools_implementation.h miral-1.4.0+17.04.20170413/miral/window_manager_tools_implementation.h --- miral-1.3.2+17.04.20170330.5/miral/window_manager_tools_implementation.h 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral/window_manager_tools_implementation.h 2017-04-13 15:22:14.000000000 +0000 @@ -26,6 +26,7 @@ #include #include +#include namespace mir { namespace scene { class Surface; } } @@ -66,6 +67,8 @@ virtual auto window_at(mir::geometry::Point cursor) const -> Window = 0; virtual auto active_display() -> mir::geometry::Rectangle const = 0; virtual void raise_tree(Window const& root) = 0; + virtual void start_drag_and_drop(WindowInfo& window_info, std::vector const& handle) = 0; + virtual void end_drag_and_drop() = 0; virtual void modify_window(WindowInfo& window_info, WindowSpecification const& modifications) = 0; virtual auto info_for_window_id(std::string const& id) const -> WindowInfo& = 0; virtual auto id_for_window(Window const& window) const -> std::string = 0; diff -Nru miral-1.3.2+17.04.20170330.5/miral-shell/miral-screencast.sh miral-1.4.0+17.04.20170413/miral-shell/miral-screencast.sh --- miral-1.3.2+17.04.20170330.5/miral-shell/miral-screencast.sh 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/miral-shell/miral-screencast.sh 2017-04-13 15:22:14.000000000 +0000 @@ -2,7 +2,7 @@ width=1920 height=1080 output=screencast.mp4 -socket=${XDG_RUNTIME_DIR}/mir_socket +socket=${XDG_RUNTIME_DIR}/miral_socket if [ -v MIR_SERVER ]; then socket=${MIR_SERVER}; fi while [ $# -gt 0 ] diff -Nru miral-1.3.2+17.04.20170330.5/scripts/process_doxygen_xml.py miral-1.4.0+17.04.20170413/scripts/process_doxygen_xml.py --- miral-1.3.2+17.04.20170330.5/scripts/process_doxygen_xml.py 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/scripts/process_doxygen_xml.py 2017-04-13 15:22:14.000000000 +0000 @@ -476,10 +476,21 @@ MIRAL_1.3.1 { global: + extern "C++" { + miral::SetWindowManagementPolicy::?SetWindowManagementPolicy*; + miral::SetWindowManagementPolicy::SetWindowManagementPolicy*; + miral::SetWindowManagementPolicy::operator*; + typeinfo?for?miral::SetWindowManagementPolicy; + vtable?for?miral::SetWindowManagementPolicy; + }; +} MIRAL_1.3; + +MIRAL_1.4.0 { +global: extern "C++" {''' END_NEW_STANZA = ''' }; -} MIRAL_1.3;''' +} MIRAL_1.3.1;''' def _print_report(): print OLD_STANZAS diff -Nru miral-1.3.2+17.04.20170330.5/test/client_mediated_gestures.cpp miral-1.4.0+17.04.20170413/test/client_mediated_gestures.cpp --- miral-1.3.2+17.04.20170330.5/test/client_mediated_gestures.cpp 1970-01-01 00:00:00.000000000 +0000 +++ miral-1.4.0+17.04.20170413/test/client_mediated_gestures.cpp 2017-04-13 15:22:14.000000000 +0000 @@ -0,0 +1,285 @@ +/* + * Copyright © 2017 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authored by: Alan Griffiths + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +using namespace std::chrono_literals; +using namespace mir::geometry; +using namespace testing; +using mir::test::fake_shared; +using mir::test::Signal; + +namespace +{ +class Cookie +{ +public: + Cookie() = default; + + explicit Cookie(MirCookie const* cookie) : self{cookie, deleter} {} + + operator MirCookie const*() const { return self.get(); } + + auto get() const -> MirCookie const* { return self.get(); } + + void reset() { self.reset(); } + + void reset(MirCookie const* cookie) { self.reset(cookie, deleter); } + +private: + static void deleter(MirCookie const* cookie) { mir_cookie_release(cookie); } + + std::shared_ptr self; +}; + +void mir_cookie_release(Cookie const&) = delete; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +struct MockWindowManager : mir::shell::CanonicalWindowManager +{ + using mir::shell::CanonicalWindowManager::CanonicalWindowManager; + + MOCK_METHOD3(handle_request_move, + void(std::shared_ptr const&, std::shared_ptr const&, uint64_t)); +}; +#pragma GCC diagnostic pop + +struct MouseMoverAndFaker +{ + void start_dragging_mouse() + { + using namespace mir::input::synthesis; + fake_mouse->emit_event(a_button_down_event().of_button(BTN_LEFT)); + } + + void move_mouse(Displacement const& displacement) + { + using mir::input::synthesis::a_pointer_event; + fake_mouse->emit_event(a_pointer_event().with_movement(displacement.dx.as_int(), displacement.dy.as_int())); + } + + void release_mouse() + { + using namespace mir::input::synthesis; + fake_mouse->emit_event(a_button_up_event().of_button(BTN_LEFT)); + } + +private: + std::unique_ptr fake_mouse{ + mir_test_framework::add_fake_input_device( + mir::input::InputDeviceInfo{"mouse", "mouse-uid", mir::input::DeviceCapability::pointer}) + }; +}; + +Rectangle const screen_geometry{{0, 0}, {800, 600}}; +auto const receive_event_timeout = 90s; + +struct ClientMediatedUserGestures : mir_test_framework::ConnectedClientWithAWindow, + MouseMoverAndFaker +{ + void SetUp() override + { + initial_display_layout({screen_geometry}); + server.override_the_window_manager_builder([this](mir::shell::FocusController* focus_controller) + { + return window_manager = + std::make_shared(focus_controller, server.the_shell_display_layout()); + }); + + mir_test_framework::ConnectedClientWithAWindow::SetUp(); + mir_window_set_event_handler(window, &window_event_handler, this); + + paint_window(); + + center_mouse(); + } + + void TearDown() override + { + reset_window_event_handler(); + window_manager.reset(); + mir_test_framework::ConnectedClientWithAWindow::TearDown(); + } + + auto user_initiates_gesture() -> Cookie; + + std::shared_ptr window_manager; + +private: + void center_mouse(); + void paint_window(); + void set_window_event_handler(std::function const& handler); + void reset_window_event_handler(); + void invoke_window_event_handler(MirEvent const* event) + { + std::lock_guard lock{window_event_handler_mutex}; + window_event_handler_(event); + } + + std::mutex window_event_handler_mutex; + std::function window_event_handler_ = [](MirEvent const*) {}; + + static void window_event_handler(MirWindow* window, MirEvent const* event, void* context); +}; + +void ClientMediatedUserGestures::set_window_event_handler(std::function const& handler) +{ + std::lock_guard lock{window_event_handler_mutex}; + window_event_handler_ = handler; +} + +void ClientMediatedUserGestures::reset_window_event_handler() +{ + std::lock_guard lock{window_event_handler_mutex}; + window_event_handler_ = [](MirEvent const*) {}; +} + +void ClientMediatedUserGestures::window_event_handler(MirWindow* /*window*/, MirEvent const* event, void* context) +{ + static_cast(context)->invoke_window_event_handler(event); +} + +void ClientMediatedUserGestures::paint_window() +{ + Signal have_focus; + + set_window_event_handler([&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_window) + return; + + auto const window_event = mir_event_get_window_event(event); + if (mir_window_event_get_attribute(window_event) != mir_window_attrib_focus) + return; + + if (mir_window_event_get_attribute_value(window_event)) + have_focus.raise(); + }); + + mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window)); + + EXPECT_THAT(have_focus.wait_for(receive_event_timeout), Eq(true)); + + reset_window_event_handler(); +} + +void ClientMediatedUserGestures::center_mouse() +{ + Signal have_mouseover; + + set_window_event_handler([&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter) + return; + + have_mouseover.raise(); + }); + + move_mouse(0.5 * as_displacement(screen_geometry.size)); + +// We miss the "mouseover" occasionally (with valgrind and heavy stress about 1/20). +// But it isn't essential for the test and we've probably waited long enough +// for the mouse-down needed by the test to reach the window. +// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout), Eq(true)); + have_mouseover.wait_for(receive_event_timeout); + + reset_window_event_handler(); +} + +auto ClientMediatedUserGestures::user_initiates_gesture() -> Cookie +{ + Cookie cookie; + Signal have_cookie; + + set_window_event_handler([&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_down) + return; + + cookie = Cookie{mir_input_event_get_cookie(input_event)}; + have_cookie.raise(); + }); + + start_dragging_mouse(); + + EXPECT_THAT(have_cookie.wait_for(receive_event_timeout), Eq(true)); + + reset_window_event_handler(); + return cookie; +} +} + +TEST_F(ClientMediatedUserGestures, when_user_initiates_gesture_client_receives_cookie) +{ + auto const cookie = user_initiates_gesture(); + + EXPECT_THAT(cookie.get(), NotNull()); +} + +TEST_F(ClientMediatedUserGestures, when_client_initiates_move_window_manager_handles_request) +{ + auto const cookie = user_initiates_gesture(); + Signal have_request; + EXPECT_CALL(*window_manager, handle_request_move(_, _, _)).WillOnce(InvokeWithoutArgs([&]{ have_request.raise(); })); + + mir_window_request_user_move(window, cookie); + + EXPECT_THAT(have_request.wait_for(receive_event_timeout), Eq(true)); +} diff -Nru miral-1.3.2+17.04.20170330.5/test/CMakeLists.txt miral-1.4.0+17.04.20170413/test/CMakeLists.txt --- miral-1.3.2+17.04.20170330.5/test/CMakeLists.txt 2017-03-30 14:18:12.000000000 +0000 +++ miral-1.4.0+17.04.20170413/test/CMakeLists.txt 2017-04-13 15:22:14.000000000 +0000 @@ -39,6 +39,16 @@ ${GTEST_INCLUDE_DIR} ) +# MIRAL_TEST_MODERN_FEATURES lists test sourcefiles that require a recent version of Mir +if (MIRTEST_VERSION VERSION_LESS 0.27) + set(MIRAL_TEST_MODERN_FEATURES) +else() + set(MIRAL_TEST_MODERN_FEATURES + drag_and_drop.cpp + client_mediated_gestures.cpp + ) +endif() + add_executable(miral-test mru_window_list.cpp active_outputs.cpp @@ -56,7 +66,9 @@ display_reconfiguration.cpp active_window.cpp raise_tree.cpp - workspaces.cpp) + workspaces.cpp + ${MIRAL_TEST_MODERN_FEATURES} +) target_link_libraries(miral-test ${MIRTEST_LDFLAGS} diff -Nru miral-1.3.2+17.04.20170330.5/test/drag_and_drop.cpp miral-1.4.0+17.04.20170413/test/drag_and_drop.cpp --- miral-1.3.2+17.04.20170330.5/test/drag_and_drop.cpp 1970-01-01 00:00:00.000000000 +0000 +++ miral-1.4.0+17.04.20170413/test/drag_and_drop.cpp 2017-04-13 15:22:14.000000000 +0000 @@ -0,0 +1,641 @@ +/* + * Copyright © 2017 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authored by: Alan Griffiths + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "test_server.h" +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +using namespace std::chrono_literals; +using namespace mir::client; +using namespace mir::geometry; +using namespace testing; +using mir::test::Signal; + +namespace +{ +struct MouseMoverAndFaker +{ + void start_dragging_mouse() + { + using namespace mir::input::synthesis; + fake_mouse->emit_event(a_button_down_event().of_button(BTN_LEFT)); + } + + void move_mouse(Displacement const& displacement) + { + using mir::input::synthesis::a_pointer_event; + fake_mouse->emit_event(a_pointer_event().with_movement(displacement.dx.as_int(), displacement.dy.as_int())); + } + + void release_mouse() + { + using namespace mir::input::synthesis; + fake_mouse->emit_event(a_button_up_event().of_button(BTN_LEFT)); + } + +private: + std::unique_ptr fake_mouse{ + mir_test_framework::add_fake_input_device( + mir::input::InputDeviceInfo{"mouse", "mouse-uid", mir::input::DeviceCapability::pointer})}; +}; + +Rectangle const screen_geometry{{0,0}, {800,600}}; +auto const receive_event_timeout = 1s; //90s; + +struct ConnectedClientWithAWindow : miral::TestServer +{ + Connection connection; + Window window; + + void SetUp() override + { + miral::TestServer::SetUp(); + connection = connect_client(__func__); + window = WindowSpec::for_normal_window(connection, surface_size.width.as_int(), surface_size.height.as_int()) + .set_pixel_format(mir_pixel_format_abgr_8888) + .set_name("ConnectedClientWithAWindow") + .set_buffer_usage(mir_buffer_usage_hardware) + .create_window(); + } + + void TearDown() override + { + window.reset(); + connection.reset(); + miral::TestServer::TearDown(); + } + + mir::geometry::Size const surface_size {640, 480}; +}; + +struct DragAndDrop : ConnectedClientWithAWindow, + MouseMoverAndFaker +{ + MirDragAndDropV1 const* dnd = nullptr; + + void SetUp() override + { + mir_test_framework::set_next_display_rects(std::unique_ptr>(new std::vector({screen_geometry}))); + + ConnectedClientWithAWindow::SetUp(); + dnd = mir_drag_and_drop_v1(connection); + mir_window_set_event_handler(window, &window_event_handler, this); + if (dnd) dnd->set_start_drag_and_drop_callback(window, &window_dnd_start_handler, this); + + create_target_window(); + + paint_window(window); + + center_mouse(); + } + + void TearDown() override + { + reset_window_event_handler(target_window); + reset_window_event_handler(window); + target_window.reset(); + another_connection.reset(); + ConnectedClientWithAWindow::TearDown(); + } + + auto user_initiates_drag() -> Cookie; + auto client_requests_drag(Cookie const& cookie) -> Blob; + auto handle_from_mouse_move() -> Blob; + auto handle_from_mouse_leave() -> Blob; + auto handle_from_mouse_enter() -> Blob; + auto handle_from_mouse_release() -> Blob; + auto count_of_handles_when_moving_mouse() -> int; + +private: + auto build_window_manager_policy(miral::WindowManagerTools const& tools) -> std::unique_ptr override; + void center_mouse(); + void paint_window(MirWindow* w); + void set_window_event_handler(MirWindow* window, std::function const& handler); + void set_window_dnd_start_handler(MirWindow* window, std::function const& handler); + void reset_window_event_handler(MirWindow* window); + + void create_target_window() + { + another_connection = connect_client("another_connection"); + target_window = WindowSpec:: + for_normal_window(connection, screen_geometry.size.width.as_int(), screen_geometry.size.height.as_int()) + .set_pixel_format(mir_pixel_format_abgr_8888) + .set_name("target_window") + .set_buffer_usage(mir_buffer_usage_hardware) + .set_event_handler(&window_event_handler, this) + .create_window(); + + paint_window(target_window); + } + + void invoke_window_event_handler(MirWindow* window, MirEvent const* event) + { + std::lock_guard lock{window_event_handler_mutex}; + if (window == this->window) window_event_handler_(event); + if (window == target_window) target_window_event_handler_(event); + } + + void invoke_window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event) + { + std::lock_guard lock{window_event_handler_mutex}; + if (window == this->window) window_dnd_start_(event); + } + + std::mutex window_event_handler_mutex; + std::function window_dnd_start_ = [](MirDragAndDropEvent const*) {}; + std::function window_event_handler_ = [](MirEvent const*) {}; + std::function target_window_event_handler_ = [](MirEvent const*) {}; + + static void window_event_handler(MirWindow* window, MirEvent const* event, void* context); + static void window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event, void* context); + + Connection another_connection; + Window target_window; +}; + +void DragAndDrop::set_window_event_handler(MirWindow* window, std::function const& handler) +{ + std::lock_guard lock{window_event_handler_mutex}; + if (window == this->window) window_event_handler_ = handler; + if (window == target_window) target_window_event_handler_ = handler; +} + +void DragAndDrop::set_window_dnd_start_handler(MirWindow* window, std::function const& handler) +{ +std::lock_guard lock{window_event_handler_mutex}; +if (window == this->window) window_dnd_start_ = handler; +} + + +void DragAndDrop::reset_window_event_handler(MirWindow* window) +{ + if (window == this->window) window_event_handler_ = [](MirEvent const*) {}; + if (window == target_window) target_window_event_handler_ = [](MirEvent const*) {}; +} + +void DragAndDrop::paint_window(MirWindow* w) +{ + Signal have_focus; + + set_window_event_handler(w, [&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_window) + return; + + auto const window_event = mir_event_get_window_event(event); + if (mir_window_event_get_attribute(window_event) != mir_window_attrib_focus) + return; + + if (mir_window_event_get_attribute_value(window_event)) + have_focus.raise(); + }); + + mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(w)); + + EXPECT_THAT(have_focus.wait_for(receive_event_timeout), Eq(true)); + + reset_window_event_handler(w); +} + +void DragAndDrop::center_mouse() +{ + Signal have_mouseover; + + set_window_event_handler(window, [&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter) + return; + + have_mouseover.raise(); + }); + + move_mouse(0.5 * as_displacement(screen_geometry.size)); + +// We miss the "mouseover" occasionally (with valgrind and heavy stress about 1/20). +// But it isn't essential for the test and we've probably waited long enough +// for the mouse-down needed by the test to reach the window. +// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout), Eq(true)); + have_mouseover.wait_for(receive_event_timeout); + + reset_window_event_handler(window); +} + +void DragAndDrop::window_event_handler(MirWindow* window, MirEvent const* event, void* context) +{ + static_cast(context)->invoke_window_event_handler(window, event); +} + +void DragAndDrop::window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event, void* context) +{ + static_cast(context)->invoke_window_dnd_start_handler(window, event); +} + + +auto DragAndDrop::user_initiates_drag() -> Cookie +{ + Cookie cookie; + Signal have_cookie; + + set_window_event_handler(window, [&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_down) + return; + + cookie = Cookie{mir_input_event_get_cookie(input_event)}; + have_cookie.raise(); + }); + + start_dragging_mouse(); + + EXPECT_THAT(have_cookie.wait_for(receive_event_timeout), Eq(true)); + + reset_window_event_handler(window); + return cookie; +} + +auto DragAndDrop::client_requests_drag(Cookie const& cookie) -> Blob +{ + Blob blob; + Signal initiated; + + set_window_dnd_start_handler(window, [&](MirDragAndDropEvent const* event) + { + if (dnd) + blob.reset(dnd->start_drag_and_drop(event)); + + if (blob) + initiated.raise(); + }); + + EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension"; + + if (dnd) + dnd->request_drag_and_drop(window, cookie); + + EXPECT_TRUE(initiated.wait_for(receive_event_timeout)); + + reset_window_event_handler(window); + return blob; +} + +auto DragAndDrop::handle_from_mouse_move() -> Blob +{ + Blob blob; + Signal have_blob; + + set_window_event_handler(window, [&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension"; + + if (dnd) + blob.reset(dnd->pointer_drag_and_drop(pointer_event)); + + if (blob) + have_blob.raise(); + }); + + move_mouse({1,1}); + + EXPECT_TRUE(have_blob.wait_for(receive_event_timeout)); + + reset_window_event_handler(window); + return blob; +} + +auto DragAndDrop::handle_from_mouse_leave() -> Blob +{ + Blob blob; + Signal have_blob; + + set_window_event_handler(window, [&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + if (mir_pointer_event_action(pointer_event) != mir_pointer_action_leave) + return; + + EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension"; + + if (dnd) + blob.reset(dnd->pointer_drag_and_drop(pointer_event)); + + if (blob) + have_blob.raise(); + }); + + move_mouse({1,1}); + move_mouse(0.5 * as_displacement(surface_size)); + + EXPECT_TRUE(have_blob.wait_for(receive_event_timeout)); + + reset_window_event_handler(window); + return blob; +} + +auto DragAndDrop::handle_from_mouse_enter() -> Blob +{ + Blob blob; + Signal have_blob; + + set_window_event_handler(target_window, [&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter) + return; + + EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension"; + + if (dnd) + blob.reset(dnd->pointer_drag_and_drop(pointer_event)); + + if (blob) + have_blob.raise(); + }); + + move_mouse({1,1}); + move_mouse(0.5 * as_displacement(surface_size)); + + EXPECT_TRUE(have_blob.wait_for(receive_event_timeout)); + + reset_window_event_handler(target_window); + return blob; +} + +auto DragAndDrop::handle_from_mouse_release() -> Blob +{ + Blob blob; + Signal have_blob; + + set_window_event_handler(target_window, [&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_up) + return; + + EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension"; + + if (dnd) + blob.reset(dnd->pointer_drag_and_drop(pointer_event)); + + if (blob) + have_blob.raise(); + }); + + move_mouse({1,1}); + move_mouse(0.5 * as_displacement(surface_size)); + release_mouse(); + + EXPECT_TRUE(have_blob.wait_for(receive_event_timeout)); + + reset_window_event_handler(target_window); + return blob; +} + +auto DragAndDrop::count_of_handles_when_moving_mouse() -> int +{ + Signal have_3_events; + std::atomic events{0}; + std::atomic handles{0}; + + auto counter = [&](MirEvent const* event) + { + if (mir_event_get_type(event) != mir_event_type_input) + return; + + auto const input_event = mir_event_get_input_event(event); + + if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer) + return; + + auto const pointer_event = mir_input_event_get_pointer_event(input_event); + + EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension"; + + Blob blob; + if (dnd) + blob.reset(dnd->pointer_drag_and_drop(pointer_event)); + + if (blob) + handles.fetch_add(1); + + if (events.fetch_add(1) == 2) + have_3_events.raise(); + }; + + set_window_event_handler(window, counter); + set_window_event_handler(target_window, counter); + + start_dragging_mouse(); + move_mouse({1,1}); + release_mouse(); + + EXPECT_TRUE(have_3_events.wait_for(receive_event_timeout)); + + reset_window_event_handler(window); + reset_window_event_handler(target_window); + return handles; +} + +auto DragAndDrop::build_window_manager_policy(miral::WindowManagerTools const& tools) -> std::unique_ptr +{ + struct DnDWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy, miral::WindowManagementPolicyAddendum2 + { + using miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy; + + void handle_request_drag_and_drop(miral::WindowInfo& window_info) override + { + uuid_t uuid; + uuid_generate(uuid); + std::vector const handle{std::begin(uuid), std::end(uuid)}; + + tools.start_drag_and_drop(window_info, handle); + } + + void handle_request_move(miral::WindowInfo&, MirInputEvent const*) override {} + }; + + return std::make_unique(tools, *this); +} + +MATCHER_P(BlobContentEq, p, "") +{ + if (!arg || !p) + return false; + if (mir_blob_size(arg) != mir_blob_size(p)) + return false; + return !memcmp(mir_blob_data(arg), mir_blob_data(p), mir_blob_size(p)); +} +} + +TEST_F(DragAndDrop, when_user_initiates_drag_client_receives_cookie) +{ + auto const cookie = user_initiates_drag(); + + EXPECT_THAT(cookie, NotNull()); +} + +TEST_F(DragAndDrop, when_client_requests_drags_it_receives_handle) +{ + auto const cookie = user_initiates_drag(); + ASSERT_THAT(cookie, NotNull()); + + auto const handle = client_requests_drag(cookie); + + EXPECT_THAT(handle, NotNull()); +} + +TEST_F(DragAndDrop, during_drag_when_user_moves_mouse_client_receives_handle) +{ + auto const cookie = user_initiates_drag(); + ASSERT_THAT(cookie, NotNull()); + auto const handle_from_request = client_requests_drag(cookie); + + auto const handle = handle_from_mouse_move(); + + EXPECT_THAT(handle, NotNull()); + EXPECT_THAT(handle, BlobContentEq(handle_from_request)); +} + +TEST_F(DragAndDrop, when_drag_moves_from_window_leave_event_contains_handle) +{ + auto const cookie = user_initiates_drag(); + ASSERT_THAT(cookie, NotNull()); + auto const handle_from_request = client_requests_drag(cookie); + + auto const handle = handle_from_mouse_leave(); + + EXPECT_THAT(handle, NotNull()); + EXPECT_THAT(handle, BlobContentEq(handle_from_request)); +} + +TEST_F(DragAndDrop, when_drag_enters_target_window_enter_event_contains_handle) +{ + auto const cookie = user_initiates_drag(); + ASSERT_THAT(cookie, NotNull()); + auto const handle_from_request = client_requests_drag(cookie); + + auto const handle = handle_from_mouse_enter(); + + EXPECT_THAT(handle, NotNull()); + EXPECT_THAT(handle, BlobContentEq(handle_from_request)); +} + +TEST_F(DragAndDrop, when_drag_releases_target_window_release_event_contains_handle) +{ + auto const cookie = user_initiates_drag(); + ASSERT_THAT(cookie, NotNull()); + auto const handle_from_request = client_requests_drag(cookie); + + auto const handle = handle_from_mouse_release(); + + EXPECT_THAT(handle, NotNull()); + EXPECT_THAT(handle, BlobContentEq(handle_from_request)); +} + +TEST_F(DragAndDrop, after_drag_finishes_pointer_events_no_longer_contain_handle) +{ + auto const cookie = user_initiates_drag(); + ASSERT_THAT(cookie, NotNull()); + client_requests_drag(cookie); + handle_from_mouse_release(); + + invoke_tools([](miral::WindowManagerTools& tools) { tools.end_drag_and_drop(); }); + + EXPECT_THAT(count_of_handles_when_moving_mouse(), Eq(0)); +}