blob: 62d38b5ed6223ffb523862a524f6560ac113505d [file] [log] [blame] [edit]
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authored by: Christopher James Halse Rogers <[email protected]>
*/
#include <fcntl.h>
#include <unordered_map>
#include <deque>
#include <wayland-client.h>
#include <wayland-server.h>
#include <mir/scene/surface_creation_parameters.h>
#include <mir/input/composite_event_filter.h>
#include <mir/input/device.h>
#include <mir/input/input_device_hub.h>
#include <mir/input/input_device_observer.h>
#include <mir/input/cursor_listener.h>
#include <mir/input/seat_observer.h>
#include <mir/observer_registrar.h>
#include <mir/executor.h>
#include <atomic>
#include <sys/eventfd.h>
#include <mir/log.h>
#include "display_server.h"
#include "pointer.h"
#include "mutex.h"
#include "mir/fd.h"
#include "mir/server.h"
#include "mir/options/option.h"
#include "mir_test_framework/async_server_runner.h"
#include "mir_test_framework/headless_display_buffer_compositor_factory.h"
#include "mir_test_framework/executable_path.h"
#include "mir_test_framework/stub_server_platform_factory.h"
#include "mir_test_framework/fake_input_device.h"
#include "mir/input/device_capability.h"
#include "mir/input/input_device_info.h"
#include "mir/test/doubles/mock_gl.h"
#include "mir/frontend/session.h"
#include "mir/scene/session_listener.h"
#include "mir/scene/surface.h"
#include "mir/scene/session.h"
#include "mir/test/signal.h"
namespace mtf = mir_test_framework;
namespace mi = mir::input;
namespace mf = mir::frontend;
namespace std
{
// std::chrono::nanoseconds doesn't have a standard hash<> implementation?!
template<>
struct hash<::std::chrono::nanoseconds>
{
typedef std::chrono::nanoseconds argument_type;
typedef size_t result_type;
result_type operator()(argument_type const& arg) const noexcept
{
/*
* The underlying type of std::chrono::nanoseconds is guaranteed to be an
* integer of at least 50-something bits, which will already have a perfectly
* functional std::hash<> implementation…
*/
return std::hash<decltype(arg.count())>{}(arg.count());
}
};
}
namespace
{
using ClientFd = int;
class ResourceMapper : public mir::scene::SessionListener
{
public:
ResourceMapper()
: listeners{&this->state}
{
}
void starting(std::shared_ptr<mir::scene::Session> const&) override
{
}
void stopping(std::shared_ptr<mir::scene::Session> const&) override
{
}
void focused(std::shared_ptr<mir::scene::Session> const&) override
{
}
void unfocused() override
{
}
void surface_created(
mir::scene::Session&,
std::shared_ptr<mir::scene::Surface> const& surface) override
{
auto state_accessor = state.lock();
if (std::this_thread::get_id() == state_accessor->wayland_thread)
{
if (listeners.last_wl_window == nullptr)
{
BOOST_THROW_EXCEPTION((
std::runtime_error{
"Called Shell::create_surface() without first creating a wl_shell_surface?"}));
}
auto stream = surface->primary_buffer_stream();
auto wl_surface = state_accessor->stream_map.at(stream);
state_accessor->surface_map[wl_surface] = surface;
}
}
void destroying_surface(
mir::scene::Session&,
std::shared_ptr<mir::scene::Surface> const&) override
{
// TODO: Maybe delete from map?
}
void buffer_stream_created(
mir::scene::Session&,
std::shared_ptr<mir::frontend::BufferStream> const& stream) override
{
auto state_accessor = state.lock();
if (std::this_thread::get_id() == state_accessor->wayland_thread)
{
if (listeners.last_wl_surface == nullptr)
{
BOOST_THROW_EXCEPTION((
std::runtime_error{"BufferStream created without first constructing a wl_surface?"}));
}
state_accessor->stream_map[stream] = listeners.last_wl_surface;
listeners.last_wl_surface = nullptr;
}
}
void buffer_stream_destroyed(
mir::scene::Session&,
std::shared_ptr<mir::frontend::BufferStream> const& stream) override
{
state.lock()->stream_map.erase(stream);
}
void init(wl_display* display)
{
state.lock()->wayland_thread = std::this_thread::get_id();
listeners.client_listener.notify = &client_created;
wl_display_add_client_created_listener(display, &listeners.client_listener);
}
std::weak_ptr<mir::scene::Surface> surface_for_resource(wl_resource* surface)
{
if (strcmp(wl_resource_get_class(surface), "wl_surface") != 0)
{
BOOST_THROW_EXCEPTION((
std::logic_error{
std::string{"Expected a wl_surface, got: "} +
wl_resource_get_class(surface)
}));
}
auto state_accessor = state.lock();
return state_accessor->surface_map.at(surface);
}
wl_client* client_for_fd(int client_socket)
{
return listeners.state->lock()->client_session_map.at(client_socket);
}
void associate_client_socket(int client_socket)
{
auto state_accessor = state.wait_for(
[](State& state) { return static_cast<bool>(state.latest_client) ; },
std::chrono::seconds{30});
state_accessor->client_session_map[client_socket] = state_accessor->latest_client.value();
state_accessor->latest_client = {};
}
private:
struct State
{
std::thread::id wayland_thread;
std::unordered_map<wl_resource*, std::weak_ptr<mir::scene::Surface>> surface_map;
std::unordered_map<std::shared_ptr<mir::frontend::BufferStream>, wl_resource*> stream_map;
std::experimental::optional<wl_client*> latest_client;
std::unordered_map<ClientFd, wl_client*> client_session_map;
};
wlcs::WaitableMutex<State> state;
struct Listeners
{
Listeners(wlcs::WaitableMutex<State>* const state)
: state{state}
{
}
wl_listener client_listener;
wl_listener resource_listener;
wl_resource* last_wl_surface{nullptr};
wl_resource* last_wl_window{nullptr};
wlcs::WaitableMutex<State>* const state;
} listeners;
static void resource_created(wl_listener* listener, void* ctx)
{
auto resource = static_cast<wl_resource*>(ctx);
Listeners* listeners;
listeners =
wl_container_of(listener, listeners, resource_listener);
bool const is_surface = strcmp(
wl_resource_get_class(resource),
"wl_surface") == 0;
bool const is_window = strcmp(
wl_resource_get_class(resource),
"wl_shell_surface") == 0; // Extend to also accept eg: xdg_shell_surface later.
if (is_surface)
{
listeners->last_wl_surface = resource;
}
else if (is_window)
{
listeners->last_wl_window = resource;
}
}
static void client_created(wl_listener* listener, void* ctx)
{
auto client = static_cast<wl_client*>(ctx);
Listeners* listeners;
listeners =
wl_container_of(listener, listeners, client_listener);
{
auto state_accessor = listeners->state->lock();
state_accessor->latest_client = client;
}
listeners->state->notify_all();
listeners->resource_listener.notify = &resource_created;
wl_client_add_resource_created_listener(client, &listeners->resource_listener);
}
};
namespace
{
class WaylandExecutor : public mir::Executor
{
public:
void spawn (std::function<void()>&& work) override
{
{
std::lock_guard<std::recursive_mutex> lock{mutex};
workqueue.emplace_back(std::move(work));
}
if (auto err = eventfd_write(notify_fd, 1))
{
BOOST_THROW_EXCEPTION((std::system_error{err, std::system_category(), "eventfd_write failed to notify event loop"}));
}
}
/**
* Get an Executor which dispatches onto a wl_event_loop
*
* \note The executor may outlive the wl_event_loop, but no tasks will be dispatched
* after the wl_event_loop is destroyed.
*
* \param [in] loop The event loop to dispatch on
* \return An Executor that queues onto the wl_event_loop
*/
static std::shared_ptr<mir::Executor> executor_for_event_loop(wl_event_loop* loop)
{
if (auto notifier = wl_event_loop_get_destroy_listener(loop, &on_display_destruction))
{
DestructionShim* shim;
shim = wl_container_of(notifier, shim, destruction_listener);
return shim->executor;
}
else
{
auto const executor = std::shared_ptr<WaylandExecutor>{new WaylandExecutor{loop}};
auto shim = std::make_unique<DestructionShim>(executor);
shim->destruction_listener.notify = &on_display_destruction;
wl_event_loop_add_destroy_listener(loop, &(shim.release())->destruction_listener);
return executor;
}
}
private:
WaylandExecutor(wl_event_loop* loop)
: notify_fd{eventfd(0, EFD_CLOEXEC | EFD_SEMAPHORE | EFD_NONBLOCK)},
notify_source{wl_event_loop_add_fd(loop, notify_fd, WL_EVENT_READABLE, &on_notify, this)}
{
if (notify_fd == mir::Fd::invalid)
{
BOOST_THROW_EXCEPTION((std::system_error{
errno,
std::system_category(),
"Failed to create IPC pause notification eventfd"}));
}
}
std::function<void()> get_work()
{
std::lock_guard<std::recursive_mutex> lock{mutex};
if (!workqueue.empty())
{
auto const work = std::move(workqueue.front());
workqueue.pop_front();
return work;
}
return {};
}
static int on_notify(int fd, uint32_t, void* data)
{
auto executor = static_cast<WaylandExecutor*>(data);
eventfd_t unused;
if (auto err = eventfd_read(fd, &unused))
{
mir::log(
mir::logging::Severity::error,
"wlcs-integration",
"eventfd_read failed to consume wakeup notification: %s (%i)",
strerror(err),
err);
}
std::unique_lock<std::recursive_mutex> lock{executor->mutex};
while (auto work = executor->get_work())
{
try
{
work();
}
catch(...)
{
mir::log(
mir::logging::Severity::critical,
"wlcs-integration",
std::current_exception(),
"Exception processing Wayland event loop work item");
}
}
return 0;
}
static void on_display_destruction(wl_listener* listener, void*)
{
DestructionShim* shim;
shim = wl_container_of(listener, shim, destruction_listener);
{
std::lock_guard<std::recursive_mutex> lock{shim->executor->mutex};
wl_event_source_remove(shim->executor->notify_source);
}
delete shim;
}
std::recursive_mutex mutex;
mir::Fd const notify_fd;
std::deque<std::function<void()>> workqueue;
wl_event_source* const notify_source;
struct DestructionShim
{
explicit DestructionShim(std::shared_ptr<WaylandExecutor> const& executor)
: executor{executor}
{
}
std::shared_ptr<WaylandExecutor> const executor;
wl_listener destruction_listener;
};
static_assert(
std::is_standard_layout<DestructionShim>::value,
"DestructionShim must be Standard Layout for wl_container_of to be defined behaviour");
};
}
class InputEventListener;
struct MirWlcsDisplayServer : mtf::AsyncServerRunner
{
testing::NiceMock<mir::test::doubles::MockGL> mockgl;
std::shared_ptr<ResourceMapper> const resource_mapper{std::make_shared<ResourceMapper>()};
std::shared_ptr<InputEventListener> event_listener;
std::shared_ptr<mir::Executor> executor;
std::atomic<double> cursor_x{0}, cursor_y{0};
};
class InputEventListener : public mir::input::SeatObserver
{
public:
InputEventListener(MirWlcsDisplayServer& runner)
: runner{runner}
{
}
std::shared_ptr<mir::test::Signal> expect_event_with_time(
std::chrono::nanoseconds event_time)
{
auto done_signal = std::make_shared<mir::test::Signal>();
expected_events.lock()->insert(std::make_pair(event_time, done_signal));
return done_signal;
}
void seat_add_device(uint64_t /*id*/) override
{
}
void seat_remove_device(uint64_t /*id*/) override
{
}
void seat_dispatch_event(std::shared_ptr<MirEvent const> const& event) override
{
auto iev = mir_event_get_input_event(event.get());
auto event_time = std::chrono::nanoseconds{mir_input_event_get_event_time(iev)};
auto expected_events_accessor = expected_events.lock();
if (expected_events_accessor->count(event_time))
{
expected_events_accessor->at(event_time)->raise();
expected_events_accessor->erase(event_time);
}
}
void seat_set_key_state(
uint64_t /*id*/,
std::vector<uint32_t> const& /*scan_codes*/) override
{
}
void seat_set_pointer_state(
uint64_t /*id*/,
unsigned /*buttons*/) override
{
}
void seat_set_cursor_position(
float cursor_x,
float cursor_y) override
{
runner.cursor_x = cursor_x;
runner.cursor_y = cursor_y;
}
void seat_set_confinement_region_called(
mir::geometry::Rectangles const& /*regions*/) override
{
}
void seat_reset_confinement_regions() override
{
}
private:
wlcs::Mutex<std::unordered_map<std::chrono::nanoseconds, std::shared_ptr<mir::test::Signal>>> expected_events;
MirWlcsDisplayServer& runner;
};
}
void wlcs_server_start(WlcsDisplayServer* server)
{
auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
runner->start_server();
}
void wlcs_server_stop(WlcsDisplayServer* server)
{
auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
runner->stop_server();
}
WlcsDisplayServer* wlcs_create_server(int argc, char const** argv)
{
auto runner = new MirWlcsDisplayServer;
runner->add_to_environment("MIR_SERVER_PLATFORM_GRAPHICS_LIB", mtf::server_platform("graphics-dummy.so").c_str());
runner->add_to_environment("MIR_SERVER_PLATFORM_INPUT_LIB", mtf::server_platform("input-stub.so").c_str());
runner->add_to_environment("MIR_SERVER_ENABLE_KEY_REPEAT", "false");
runner->add_to_environment("MIR_SERVER_NO_FILE", "");
runner->server.override_the_display_buffer_compositor_factory([]
{
return std::make_shared<mtf::HeadlessDisplayBufferCompositorFactory>();
});
runner->server.override_the_session_listener(
[runner]()
{
return runner->resource_mapper;
});
runner->event_listener = std::make_shared<InputEventListener>(*runner);
runner->server.set_command_line(argc, argv);
runner->server.add_init_callback(
[runner]()
{
runner->server.run_on_wayland_display(
[runner](auto wayland_display)
{
runner->resource_mapper->init(wayland_display);
runner->executor = WaylandExecutor::executor_for_event_loop(
wl_display_get_event_loop(wayland_display));
// Execute all observations on the Wayland event loop…
runner->server.the_seat_observer_registrar()->register_interest(
runner->event_listener,
*runner->executor);
});
});
runner->server.wrap_cursor_listener(
[runner](auto const& wrappee)
{
class ListenerWrapper : public mir::input::CursorListener
{
public:
ListenerWrapper(
MirWlcsDisplayServer* runner,
std::shared_ptr<mir::input::CursorListener> const& wrapped)
: runner{runner},
wrapped{wrapped}
{
}
void cursor_moved_to(float abs_x, float abs_y) override
{
runner->cursor_x = abs_x;
runner->cursor_y = abs_y;
wrapped->cursor_moved_to(abs_x, abs_y);
}
private:
MirWlcsDisplayServer* const runner;
std::shared_ptr<mir::input::CursorListener> const wrapped;
};
return std::make_shared<ListenerWrapper>(runner, wrappee);
});
return reinterpret_cast<WlcsDisplayServer*>(runner);
}
void wlcs_destroy_server(WlcsDisplayServer* server)
{
auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
delete runner;
}
int wlcs_server_create_client_socket(WlcsDisplayServer* server)
{
auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
try
{
auto client_fd = fcntl(
runner->server.open_wayland_client_socket(),
F_DUPFD_CLOEXEC,
3);
runner->resource_mapper->associate_client_socket(client_fd);
return client_fd;
}
catch (std::exception const&)
{
mir::log(
mir::logging::Severity::critical,
"wlcs-bindings",
std::current_exception(),
"Failed to create Wayland client socket");
}
return -1;
}
struct FakePointer
{
decltype(mtf::add_fake_input_device(mi::InputDeviceInfo())) pointer;
MirWlcsDisplayServer* runner;
};
WlcsPointer* wlcs_server_create_pointer(WlcsDisplayServer* server)
{
auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
auto constexpr uid = "mouse-uid";
class DeviceObserver : public mir::input::InputDeviceObserver
{
public:
DeviceObserver(std::shared_ptr<mir::test::Signal> const& done)
: done{done}
{
}
void device_added(std::shared_ptr<mir::input::Device> const& device) override
{
if (device->unique_id() == uid)
seen_device = true;
}
void device_changed(std::shared_ptr<mir::input::Device> const&) override
{
}
void device_removed(std::shared_ptr<mir::input::Device> const&) override
{
}
void changes_complete() override
{
if (seen_device)
done->raise();
}
private:
std::shared_ptr<mir::test::Signal> const done;
bool seen_device{false};
};
auto mouse_added = std::make_shared<mir::test::Signal>();
auto observer = std::make_shared<DeviceObserver>(mouse_added);
runner->server.the_input_device_hub()->add_observer(observer);
auto fake_mouse = mtf::add_fake_input_device(
mi::InputDeviceInfo{"mouse", uid, mi::DeviceCapability::pointer});
mouse_added->wait_for(std::chrono::seconds{5});
runner->server.the_input_device_hub()->remove_observer(observer);
auto fake_pointer = new FakePointer;
fake_pointer->runner = runner;
fake_pointer->pointer = std::move(fake_mouse);
return reinterpret_cast<WlcsPointer*>(fake_pointer);
}
void wlcs_destroy_pointer(WlcsPointer* pointer)
{
delete reinterpret_cast<FakePointer*>(pointer);
}
void wlcs_pointer_move_relative(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y)
{
auto device = reinterpret_cast<FakePointer*>(pointer);
auto event_time = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch());
auto event_sent = device->runner->event_listener->expect_event_with_time(event_time);
device->pointer->emit_event(
mir::input::synthesis::a_pointer_event()
.with_movement(wl_fixed_to_int(x), wl_fixed_to_int(y))
.with_event_time(event_time));
event_sent->wait_for(std::chrono::seconds{5});
}
void wlcs_pointer_move_absolute(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y)
{
auto device = reinterpret_cast<FakePointer*>(pointer);
auto rel_x = wl_fixed_to_double(x) - device->runner->cursor_x;
auto rel_y = wl_fixed_to_double(y) - device->runner->cursor_y;
wlcs_pointer_move_relative(pointer, wl_fixed_from_double(rel_x), wl_fixed_from_double(rel_y));
}
void wlcs_server_position_window_absolute(WlcsDisplayServer* server, wl_display* client, wl_surface* surface, int x, int y)
{
auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
try
{
auto const fd = wl_display_get_fd(client);
auto const client = runner->resource_mapper->client_for_fd(fd);
auto const id = wl_proxy_get_id(reinterpret_cast<wl_proxy*>(surface));
auto resource = wl_client_get_object(client, id);
auto mir_surface = runner->resource_mapper->surface_for_resource(resource);
if (auto live_surface = mir_surface.lock())
{
live_surface->move_to(mir::geometry::Point{x, y});
}
else
{
abort();
// TODO: log? Error handling?
}
}
catch(std::out_of_range const&)
{
abort();
// TODO: Error handling.
}
}
WlcsSubsurface* wlcs_server_get_subsurfaces(WlcsDisplayServer* server, wl_surface* surface)
{
// TODO
return nullptr;
}