blob: f3cc617571e1af89119bd217b53e8b95232ab667 [file] [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 <christopher.halse.rogers@canonical.com>
*/
#include "in_process_server.h"
#include "display_server.h"
#include "helpers.h"
#include "pointer.h"
#include <boost/throw_exception.hpp>
#include <stdexcept>
#include <wayland-client.h>
#include <memory>
#include <vector>
#include <algorithm>
#include <experimental/optional>
class ShimNotImplemented : public std::logic_error
{
public:
ShimNotImplemented() : std::logic_error("Function not implemented in display server shim")
{
}
};
class wlcs::Pointer::Impl
{
public:
Impl(WlcsPointer* raw_device)
: pointer{raw_device, &wlcs_destroy_pointer}
{
}
void move_to(int x, int y)
{
wlcs_pointer_move_absolute(
pointer.get(),
wl_fixed_from_int(x),
wl_fixed_from_int(y));
}
void move_by(int dx, int dy)
{
wlcs_pointer_move_relative(
pointer.get(),
wl_fixed_from_int(dx),
wl_fixed_from_int(dy));
}
private:
std::unique_ptr<WlcsPointer, decltype(&wlcs_destroy_pointer)> const pointer;
};
wlcs::Pointer::~Pointer() = default;
wlcs::Pointer::Pointer(Pointer&&) = default;
wlcs::Pointer::Pointer(WlcsPointer* raw_device)
: impl{std::make_unique<Impl>(raw_device)}
{
}
void wlcs::Pointer::move_to(int x, int y)
{
impl->move_to(x, y);
}
void wlcs::Pointer::move_by(int dx, int dy)
{
impl->move_by(dx, dy);
}
class wlcs::Server::Impl
{
public:
Impl(int argc, char const** argv)
: server{wlcs_create_server(argc, argv), &wlcs_destroy_server}
{
if (!wlcs_server_start)
{
BOOST_THROW_EXCEPTION((std::logic_error{"Missing required wlcs_server_start definition"}));
}
if (!wlcs_server_stop)
{
BOOST_THROW_EXCEPTION((std::logic_error{"Missing required wlcs_server_stop definition"}));
}
}
void start()
{
wlcs_server_start(server.get());
}
void stop()
{
wlcs_server_stop(server.get());
}
int create_client_socket()
{
if (wlcs_server_create_client_socket)
{
auto fd = wlcs_server_create_client_socket(server.get());
if (fd < 0)
{
BOOST_THROW_EXCEPTION((std::system_error{
errno,
std::system_category(),
"Failed to get client socket from server"}));
}
return fd;
}
else
{
BOOST_THROW_EXCEPTION(ShimNotImplemented{});
}
}
void move_surface_to(Surface& surface, int x, int y)
{
// Ensure the server knows about the IDs we're about to send...
surface.owner().roundtrip();
wlcs_server_position_window_absolute(server.get(), surface.owner(), surface, x, y);
}
WlcsDisplayServer* wlcs_server() const
{
return server.get();
}
private:
std::unique_ptr<WlcsDisplayServer, void(*)(WlcsDisplayServer*)> const server;
};
wlcs::Server::Server(int argc, char const** argv)
: impl{std::make_unique<wlcs::Server::Impl>(argc, argv)}
{
}
wlcs::Server::~Server() = default;
void wlcs::Server::start()
{
impl->start();
}
void wlcs::Server::stop()
{
impl->stop();
}
int wlcs::Server::create_client_socket()
{
return impl->create_client_socket();
}
void wlcs::Server::move_surface_to(Surface& surface, int x, int y)
{
impl->move_surface_to(surface, x, y);
}
wlcs::Pointer wlcs::Server::create_pointer()
{
if (!wlcs_server_create_pointer || !wlcs_destroy_pointer)
{
BOOST_THROW_EXCEPTION((ShimNotImplemented{}));
}
return Pointer{wlcs_server_create_pointer(impl->wlcs_server())};
}
wlcs::InProcessServer::InProcessServer()
: server{helpers::get_argc(), helpers::get_argv()}
{
}
void wlcs::InProcessServer::SetUp()
{
server.start();
}
void wlcs::InProcessServer::TearDown()
{
server.stop();
}
wlcs::Server& wlcs::InProcessServer::the_server()
{
return server;
}
void throw_wayland_error(wl_display* display)
{
auto err = wl_display_get_error(display);
if (err != EPROTO)
{
BOOST_THROW_EXCEPTION((std::system_error{
err,
std::system_category(),
"Error while dispatching Wayland events"
}));
}
else
{
uint32_t object_id;
uint32_t protocol_error;
wl_interface const* interface;
protocol_error = wl_display_get_protocol_error(display, &interface, &object_id);
BOOST_THROW_EXCEPTION((wlcs::ProtocolError{interface, protocol_error}));
}
}
class wlcs::Client::Impl
{
public:
Impl(Server& server)
{
try
{
display = wl_display_connect_to_fd(server.create_client_socket());
}
catch (ShimNotImplemented const&)
{
// TODO: Warn about connecting to who-knows-what
display = wl_display_connect(NULL);
}
if (!display)
{
BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to connect to Wayland socket"}));
}
registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, this);
server_roundtrip();
}
~Impl()
{
// Free any buffers before we destroy the Wayland state…
client_buffers.clear();
if (shm) wl_shm_destroy(shm);
if (shell) wl_shell_destroy(shell);
if (compositor) wl_compositor_destroy(compositor);
if (registry) wl_registry_destroy(registry);
if (shell_surface) wl_shell_surface_destroy(shell_surface);
if (seat) wl_seat_destroy(seat);
if (pointer) wl_pointer_destroy(pointer);
wl_display_disconnect(display);
}
struct wl_display* wl_display() const
{
return display;
}
struct wl_compositor* wl_compositor() const
{
return compositor;
}
struct wl_shm* wl_shm() const
{
return shm;
}
Surface create_visible_surface(
Client& client,
int width,
int height)
{
Surface surface{client};
shell_surface = wl_shell_get_shell_surface(shell, surface);
wl_shell_surface_set_toplevel(shell_surface);
auto buffer = std::make_shared<ShmBuffer>(client, width, height);
wl_surface_attach(surface, *buffer, 0, 0);
wl_surface_commit(surface);
/*
* We can't drive buffer cleanup by the buffer.release() event, as that's not
* guaranteed to be sent in the case of error or server shutdown.
*
* Just keep the buffers alive for the lifetime of the client.
*/
client_buffers.push_back(buffer);
return surface;
}
wl_surface* focused_window() const
{
if (current_pointer_location)
{
return current_pointer_location->surface;
}
return nullptr;
}
std::pair<wl_fixed_t, wl_fixed_t> pointer_position() const
{
return current_pointer_location.value().coordinates;
};
void add_pointer_enter_notification(PointerEnterNotifier const& on_enter)
{
enter_notifiers.push_back(on_enter);
}
void add_pointer_leave_notification(PointerLeaveNotifier const& on_leave)
{
leave_notifiers.push_back(on_leave);
}
void add_pointer_motion_notification(PointerMotionNotifier const& on_motion)
{
motion_notifiers.push_back(on_motion);
}
void* acquire_interface(std::string const& interface, wl_interface const* to_bind, uint32_t version)
{
wl_registry* temp_registry = wl_display_get_registry(display);
struct InterfaceResult
{
void* bound_interface;
std::string const& interface;
wl_interface const* to_bind;
uint32_t version;
} result{nullptr, interface, to_bind, version};
wl_registry_listener const listener{
[](
void* ctx,
wl_registry* registry,
uint32_t id,
char const* interface,
uint32_t version)
{
auto request = static_cast<InterfaceResult*>(ctx);
if ((request->interface == interface) &&
version >= request->version)
{
request->bound_interface =
wl_registry_bind(registry, id, request->to_bind, version);
}
},
[](auto, auto, auto) {}
};
wl_registry_add_listener(
temp_registry,
&listener,
&result);
wl_display_roundtrip(display);
wl_registry_destroy(temp_registry);
if (result.bound_interface == nullptr)
{
BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to acquire interface"}));
}
return result.bound_interface;
}
void dispatch_until(std::function<bool()> const& predicate)
{
// TODO: Drive this with epoll on the fd and have a timerfd for timeout
while (!predicate())
{
if (wl_display_dispatch(display) < 0)
{
throw_wayland_error(display);
}
}
}
void server_roundtrip()
{
if (wl_display_roundtrip(display) < 0)
{
throw_wayland_error(display);
}
}
private:
static void pointer_enter(
void* ctx,
wl_pointer* /*pointer*/,
uint32_t /*serial*/,
wl_surface* surface,
wl_fixed_t x,
wl_fixed_t y)
{
auto me = static_cast<Impl*>(ctx);
me->current_pointer_location = PointerLocation {
surface,
std::make_pair(x,y)
};
std::vector<decltype(enter_notifiers)::const_iterator> to_remove;
for (auto notifier = me->enter_notifiers.begin(); notifier != me->enter_notifiers.end(); ++notifier)
{
if (!(*notifier)(surface, x, y))
{
to_remove.push_back(notifier);
}
}
for (auto removed : to_remove)
{
me->enter_notifiers.erase(removed);
}
}
static void pointer_leave(
void* ctx,
wl_pointer* /*pointer*/,
uint32_t /*serial*/,
wl_surface* surface)
{
auto me = static_cast<Impl*>(ctx);
me->current_pointer_location = {};
std::vector<decltype(leave_notifiers)::const_iterator> to_remove;
for (auto notifier = me->leave_notifiers.begin(); notifier != me->leave_notifiers.end(); ++notifier)
{
if (!(*notifier)(surface))
{
to_remove.push_back(notifier);
}
}
for (auto removed : to_remove)
{
me->leave_notifiers.erase(removed);
}
}
static void pointer_motion(
void* ctx,
wl_pointer* /*pointer*/,
uint32_t /*time*/,
wl_fixed_t x,
wl_fixed_t y)
{
auto me = static_cast<Impl*>(ctx);
me->current_pointer_location.value().coordinates = std::make_pair(x, y);
std::vector<decltype(motion_notifiers)::const_iterator> to_remove;
for (auto notifier = me->motion_notifiers.begin(); notifier != me->motion_notifiers.end(); ++notifier)
{
if (!(*notifier)(x, y))
{
to_remove.push_back(notifier);
}
}
for (auto removed : to_remove)
{
me->motion_notifiers.erase(removed);
}
}
static void pointer_frame(void* /*ctx*/, wl_pointer* /*pointer*/)
{
}
static constexpr wl_pointer_listener pointer_listener = {
&Impl::pointer_enter,
&Impl::pointer_leave,
&Impl::pointer_motion,
nullptr, // button
nullptr, // axis
&Impl::pointer_frame, // frame
nullptr, // axis_source
nullptr, // axis_stop
nullptr // axis_discrete
};
static void seat_capabilities(
void* ctx,
wl_seat* seat,
uint32_t capabilities)
{
auto me = static_cast<Impl*>(ctx);
if (capabilities & WL_SEAT_CAPABILITY_POINTER)
{
me->pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(me->pointer, &pointer_listener, me);
}
}
static void seat_name(
void*,
wl_seat*,
char const*)
{
}
static constexpr wl_seat_listener seat_listener = {
&Impl::seat_capabilities,
&Impl::seat_name
};
static void global_handler(
void* ctx,
wl_registry* registry,
uint32_t id,
char const* interface,
uint32_t version)
{
using namespace std::literals::string_literals;
auto me = static_cast<Impl*>(ctx);
if ("wl_shm"s == interface)
{
me->shm = static_cast<struct wl_shm*>(
wl_registry_bind(registry, id, &wl_shm_interface, version));
}
else if ("wl_compositor"s == interface)
{
me->compositor = static_cast<struct wl_compositor*>(
wl_registry_bind(registry, id, &wl_compositor_interface, version));
}
else if ("wl_shell"s == interface)
{
me->shell = static_cast<struct wl_shell*>(
wl_registry_bind(registry, id, &wl_shell_interface, version));
}
else if ("wl_seat"s == interface)
{
me->seat = static_cast<struct wl_seat*>(
wl_registry_bind(registry, id, &wl_seat_interface, version));
wl_seat_add_listener(me->seat, &seat_listener, me);
// Ensure we receive the initial seat events.
me->server_roundtrip();
}
}
static void global_removed(void*, wl_registry*, uint32_t)
{
// TODO: Remove our globals
}
constexpr static wl_registry_listener registry_listener = {
&global_handler,
&global_removed
};
struct wl_display* display;
struct wl_registry* registry = nullptr;
struct wl_compositor* compositor = nullptr;
struct wl_shm* shm = nullptr;
struct wl_shell_surface* shell_surface = nullptr;
struct wl_shell* shell = nullptr;
struct wl_seat* seat = nullptr;
struct wl_pointer* pointer = nullptr;
struct PointerLocation
{
wl_surface* surface;
std::pair<wl_fixed_t, wl_fixed_t> coordinates;
};
std::experimental::optional<PointerLocation> current_pointer_location;
std::vector<std::shared_ptr<ShmBuffer>> client_buffers;
std::vector<PointerEnterNotifier> enter_notifiers;
std::vector<PointerLeaveNotifier> leave_notifiers;
std::vector<PointerMotionNotifier> motion_notifiers;
};
constexpr wl_pointer_listener wlcs::Client::Impl::pointer_listener;
constexpr wl_seat_listener wlcs::Client::Impl::seat_listener;
constexpr wl_registry_listener wlcs::Client::Impl::registry_listener;
wlcs::Client::Client(Server& server)
: impl{std::make_unique<Impl>(server)}
{
}
wlcs::Client::~Client() = default;
wlcs::Client::operator wl_display*() const
{
return impl->wl_display();
}
wl_compositor* wlcs::Client::compositor() const
{
return impl->wl_compositor();
}
wl_shm* wlcs::Client::shm() const
{
return impl->wl_shm();
}
wlcs::Surface wlcs::Client::create_visible_surface(int width, int height)
{
return impl->create_visible_surface(*this, width, height);
}
wl_surface* wlcs::Client::focused_window() const
{
return impl->focused_window();
}
std::pair<wl_fixed_t, wl_fixed_t> wlcs::Client::pointer_position() const
{
return impl->pointer_position();
}
void wlcs::Client::add_pointer_enter_notification(PointerEnterNotifier const& on_enter)
{
impl->add_pointer_enter_notification(on_enter);
}
void wlcs::Client::add_pointer_leave_notification(PointerLeaveNotifier const& on_leave)
{
impl->add_pointer_leave_notification(on_leave);
}
void wlcs::Client::add_pointer_motion_notification(PointerMotionNotifier const& on_motion)
{
impl->add_pointer_motion_notification(on_motion);
}
void wlcs::Client::dispatch_until(std::function<bool()> const& predicate)
{
impl->dispatch_until(predicate);
}
void wlcs::Client::roundtrip()
{
impl->server_roundtrip();
}
void* wlcs::Client::acquire_interface(
std::string const& name,
wl_interface const* interface,
uint32_t version)
{
return impl->acquire_interface(name, interface, version);
}
class wlcs::Surface::Impl
{
public:
Impl(Client& client)
: surface_{wl_compositor_create_surface(client.compositor())},
owner_{client}
{
}
~Impl()
{
for (auto i = 0u; i < pending_callbacks.size(); )
{
if (pending_callbacks[i].first == this)
{
auto pending_callback = pending_callbacks[i].second;
delete static_cast<std::function<void(uint32_t)>*>(wl_callback_get_user_data(pending_callback));
wl_callback_destroy(pending_callback);
pending_callbacks.erase(pending_callbacks.begin() + i);
}
else
{
++i;
}
}
wl_surface_destroy(surface_);
}
wl_surface* surface() const
{
return surface_;
}
void add_frame_callback(std::function<void(uint32_t)> const& on_frame)
{
std::unique_ptr<std::function<void(uint32_t)>> holder{
new std::function<void(uint32_t)>(on_frame)};
auto callback = wl_surface_frame(surface_);
pending_callbacks.push_back(std::make_pair(this, callback));
wl_callback_add_listener(callback, &frame_listener, holder.release());
}
Client& owner() const
{
return owner_;
}
private:
static std::vector<std::pair<Impl const*, wl_callback*>> pending_callbacks;
static void frame_callback(void* ctx, wl_callback* callback, uint32_t frame_time)
{
auto us = std::find_if(
pending_callbacks.begin(),
pending_callbacks.end(),
[callback](auto const& elem)
{
return elem.second == callback;
});
pending_callbacks.erase(us);
auto frame_callback = static_cast<std::function<void(uint32_t)>*>(ctx);
(*frame_callback)(frame_time);
wl_callback_destroy(callback);
delete frame_callback;
}
static constexpr wl_callback_listener frame_listener = {
&frame_callback
};
struct wl_surface* const surface_;
Client& owner_;
};
std::vector<std::pair<wlcs::Surface::Impl const*, wl_callback*>> wlcs::Surface::Impl::pending_callbacks;
constexpr wl_callback_listener wlcs::Surface::Impl::frame_listener;
wlcs::Surface::Surface(Client& client)
: impl{std::make_unique<Impl>(client)}
{
}
wlcs::Surface::~Surface() = default;
wlcs::Surface::Surface(Surface&&) = default;
wlcs::Surface::operator wl_surface*() const
{
return impl->surface();
}
void wlcs::Surface::add_frame_callback(std::function<void(int)> const& on_frame)
{
impl->add_frame_callback(on_frame);
}
wlcs::Client& wlcs::Surface::owner() const
{
return impl->owner();
}
class wlcs::ShmBuffer::Impl
{
public:
Impl(Client& client, int width, int height)
{
auto stride = width * 4;
auto size = stride * height;
auto fd = wlcs::helpers::create_anonymous_file(size);
auto pool = wl_shm_create_pool(client.shm(), fd, size);
buffer_ = wl_shm_pool_create_buffer(
pool,
0,
width,
height,
stride,
WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
close(fd);
wl_buffer_add_listener(buffer_, &listener, this);
}
~Impl()
{
wl_buffer_destroy(buffer_);
}
wl_buffer* buffer() const
{
return buffer_;
}
void add_release_listener(std::function<bool()> const& on_release)
{
release_notifiers.push_back(on_release);
}
private:
static void on_release(void* ctx, wl_buffer* /*buffer*/)
{
auto me = static_cast<Impl*>(ctx);
std::vector<decltype(me->release_notifiers.begin())> expired_notifiers;
for (auto notifier = me->release_notifiers.begin(); notifier != me->release_notifiers.end(); ++notifier)
{
if (!(*notifier)())
{
expired_notifiers.push_back(notifier);
}
}
for (auto const& expired : expired_notifiers)
me->release_notifiers.erase(expired);
}
static constexpr wl_buffer_listener listener {
&on_release
};
wl_buffer* buffer_;
std::vector<std::function<bool()>> release_notifiers;
};
constexpr wl_buffer_listener wlcs::ShmBuffer::Impl::listener;
wlcs::ShmBuffer::ShmBuffer(Client &client, int width, int height)
: impl{std::make_unique<Impl>(client, width, height)}
{
}
wlcs::ShmBuffer::ShmBuffer(ShmBuffer&&) = default;
wlcs::ShmBuffer::~ShmBuffer() = default;
wlcs::ShmBuffer::operator wl_buffer*() const
{
return impl->buffer();
}
void wlcs::ShmBuffer::add_release_listener(std::function<bool()> const &on_release)
{
impl->add_release_listener(on_release);
}