diff options
Diffstat (limited to 'packages/kernel/src/server.cpp')
| -rw-r--r-- | packages/kernel/src/server.cpp | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/packages/kernel/src/server.cpp b/packages/kernel/src/server.cpp new file mode 100644 index 0000000..d4d6b56 --- /dev/null +++ b/packages/kernel/src/server.cpp @@ -0,0 +1,225 @@ +#include "server_impl.hpp" + +#include <ctime> +#include <stdexcept> +#include <unistd.h> + +namespace unbox::kernel { + +namespace { + +template <typename T> +auto require(T* ptr, const char* what) -> T* { + if (ptr == nullptr) { + throw std::runtime_error(std::string("failed to create ") + what); + } + return ptr; +} + +} // namespace + +// ---- Server (public surface) ---------------------------------------------- + +auto Server::create(Options options) -> std::unique_ptr<Server> { + auto impl = std::make_unique<Impl>(); + impl->options = std::move(options); + try { + impl->init(); + } catch (...) { + impl->shutdown(); + throw; + } + return std::unique_ptr<Server>(new Server(std::move(impl))); +} + +Server::Server(std::unique_ptr<Impl> impl) : impl_(std::move(impl)) {} + +Server::~Server() { + impl_->shutdown(); +} + +auto Server::socket_name() const -> std::string { + return impl_->socket; +} + +void Server::run() { + wlr_log(WLR_INFO, "unbox running on WAYLAND_DISPLAY=%s", impl_->socket.c_str()); + wl_display_run(impl_->display); +} + +auto Server::dispatch(int timeout_ms) -> bool { + wl_event_loop* loop = wl_display_get_event_loop(impl_->display); + const int rc = wl_event_loop_dispatch(loop, timeout_ms); + wl_display_flush_clients(impl_->display); + return rc >= 0; +} + +void Server::terminate() { + wl_display_terminate(impl_->display); +} + +// ---- Impl lifecycle -------------------------------------------------------- + +void Server::Impl::init() { + wlr_log_init(WLR_INFO, nullptr); + + display = require(wl_display_create(), "wl_display"); + backend = require(wlr_backend_autocreate(wl_display_get_event_loop(display), nullptr), + "wlr_backend"); + renderer = require(wlr_renderer_autocreate(backend), "wlr_renderer"); + wlr_renderer_init_wl_display(renderer, display); + allocator = require(wlr_allocator_autocreate(backend, renderer), "wlr_allocator"); + + wlr_compositor_create(display, 5, renderer); + wlr_subcompositor_create(display); + wlr_data_device_manager_create(display); + + output_layout = require(wlr_output_layout_create(display), "wlr_output_layout"); + new_output.connect(backend->events.new_output, [this](void* data) { + handle_new_output(static_cast<wlr_output*>(data)); + }); + + scene = require(wlr_scene_create(), "wlr_scene"); + scene_layout = require(wlr_scene_attach_output_layout(scene, output_layout), + "wlr_scene_output_layout"); + + xdg_shell = require(wlr_xdg_shell_create(display, 3), "wlr_xdg_shell"); + new_xdg_toplevel.connect(xdg_shell->events.new_toplevel, [this](void* data) { + handle_new_toplevel(static_cast<wlr_xdg_toplevel*>(data)); + }); + new_xdg_popup.connect(xdg_shell->events.new_popup, [this](void* data) { + handle_new_popup(static_cast<wlr_xdg_popup*>(data)); + }); + + cursor = require(wlr_cursor_create(), "wlr_cursor"); + wlr_cursor_attach_output_layout(cursor, output_layout); + cursor_mgr = require(wlr_xcursor_manager_create(nullptr, 24), "wlr_xcursor_manager"); + attach_cursor_handlers(); + + new_input.connect(backend->events.new_input, [this](void* data) { + handle_new_input(static_cast<wlr_input_device*>(data)); + }); + seat = require(wlr_seat_create(display, "seat0"), "wlr_seat"); + attach_seat_handlers(); + + const char* socket_cstr = wl_display_add_socket_auto(display); + if (socket_cstr == nullptr) { + throw std::runtime_error("failed to add a Wayland socket"); + } + socket = socket_cstr; + + if (!wlr_backend_start(backend)) { + throw std::runtime_error("failed to start the wlr_backend"); + } + + if (!options.startup_cmd.empty()) { + if (fork() == 0) { + // Child only: don't pollute our own environment. + setenv("WAYLAND_DISPLAY", socket.c_str(), 1); + execl("/bin/sh", "/bin/sh", "-c", options.startup_cmd.c_str(), + static_cast<char*>(nullptr)); + _exit(127); + } + } +} + +void Server::Impl::shutdown() { + if (display != nullptr) { + wl_display_destroy_clients(display); // fires toplevel/popup destroy events + } + + // Server-level listeners must detach BEFORE the wlr objects owning their + // signals die; a wl_listener outliving its signal is a use-after-free. + new_output.disconnect(); + new_input.disconnect(); + new_xdg_toplevel.disconnect(); + new_xdg_popup.disconnect(); + cursor_motion.disconnect(); + cursor_motion_absolute.disconnect(); + cursor_button.disconnect(); + cursor_axis.disconnect(); + cursor_frame.disconnect(); + cursor_touch_down.disconnect(); + cursor_touch_up.disconnect(); + cursor_touch_motion.disconnect(); + cursor_touch_cancel.disconnect(); + cursor_touch_frame.disconnect(); + seat_request_cursor.disconnect(); + seat_pointer_focus_change.disconnect(); + seat_request_set_selection.disconnect(); + + if (scene != nullptr) { + wlr_scene_node_destroy(&scene->tree.node); + scene = nullptr; + } + if (cursor_mgr != nullptr) { + wlr_xcursor_manager_destroy(cursor_mgr); + cursor_mgr = nullptr; + } + if (cursor != nullptr) { + wlr_cursor_destroy(cursor); + cursor = nullptr; + } + if (allocator != nullptr) { + wlr_allocator_destroy(allocator); + allocator = nullptr; + } + if (renderer != nullptr) { + wlr_renderer_destroy(renderer); + renderer = nullptr; + } + if (backend != nullptr) { + wlr_backend_destroy(backend); // fires output + input-device destroy events + backend = nullptr; + } + if (display != nullptr) { + wl_display_destroy(display); + display = nullptr; + } +} + +// ---- Outputs ---------------------------------------------------------------- + +void Server::Impl::handle_new_output(wlr_output* wlr_output) { + wlr_output_init_render(wlr_output, allocator, renderer); + + wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + if (wlr_output_mode* mode = wlr_output_preferred_mode(wlr_output)) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + auto owned = std::make_unique<Output>(); + Output* output = owned.get(); + output->server = this; + output->output = wlr_output; + outputs.push_back(std::move(owned)); + + output->frame.connect(wlr_output->events.frame, [this, output](void*) { + wlr_scene_output* scene_output = wlr_scene_get_scene_output(scene, output->output); + wlr_scene_output_commit(scene_output, nullptr); + + timespec now{}; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(scene_output, &now); + }); + output->request_state.connect(wlr_output->events.request_state, [output](void* data) { + const auto* event = static_cast<wlr_output_event_request_state*>(data); + wlr_output_commit_state(output->output, event->state); + }); + output->destroy.connect(wlr_output->events.destroy, [this, output](void*) { + // Last action: destroys `output` (and these listeners with it). + outputs.remove_if([output](const auto& owned) { return owned.get() == output; }); + }); + + wlr_output_layout_output* layout_output = wlr_output_layout_add_auto(output_layout, wlr_output); + wlr_scene_output* scene_output = wlr_scene_output_create(scene, wlr_output); + wlr_scene_output_layout_add_output(scene_layout, layout_output, scene_output); + + wlr_log(WLR_INFO, "new output %s", wlr_output->name); +} + +} // namespace unbox::kernel |
