diff options
| -rw-r--r-- | lib/CWayland/CWayland.cc | 611 | ||||
| -rw-r--r-- | lib/CWayland/CWayland.h | 11 | ||||
| -rw-r--r-- | lib/Wayland/Native.lean | 33 | ||||
| -rw-r--r-- | src/Waylean.lean | 19 |
4 files changed, 636 insertions, 38 deletions
diff --git a/lib/CWayland/CWayland.cc b/lib/CWayland/CWayland.cc index 5877ad4..8fae40c 100644 --- a/lib/CWayland/CWayland.cc +++ b/lib/CWayland/CWayland.cc @@ -1,43 +1,139 @@ #include "CWayland.h" #include <wayland-client.h> +#include <array> +#include <vector> +#include <queue> +#include <unordered_map> +#include <chrono> +#include <poll.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <signal.h> -static void noop_foreach(void *ptr, b_lean_obj_arg fn) +namespace { -} + class ref + { + lean_object* ptr; -static void waylean_display_finalize(void* ptr) -{ - wl_display* disp = static_cast<wl_display*>(ptr); - wl_display_disconnect(disp); -} + struct steal_token {}; + + explicit ref(lean_obj_arg ptr, steal_token) : ptr{ ptr } + { + } -static lean_external_class* waylean_display_class() -{ - static lean_external_class* klass = lean_register_external_class(&waylean_display_finalize, &noop_foreach); - return klass; -} + void clear() + { + if (ptr) + { + lean_dec(ptr); + ptr = nullptr; + } + } + + public: + static ref steal(lean_obj_arg ptr) + { + return ref{ ptr, steal_token{} }; + } -static char const* option_cstr_or_null(b_lean_obj_arg str) -{ - if (lean_is_scalar(str)) + explicit ref() : ptr{ nullptr } + { + } + + explicit ref(b_lean_obj_arg ptr) : ptr{ ptr } + { + lean_inc(ptr); + } + + ~ref() + { + clear(); + } + + ref(ref const& other) : ref{ other.get() } + { + } + + ref(ref&& other) : ref{ std::move(other).release(), steal_token{} } + { + } + + ref& operator=(ref const& other) + { + if (ptr != other.ptr) + { + clear(); + ptr = other.get(); + lean_inc(ptr); + } + return *this; + } + + ref& operator=(ref&& other) + { + if (ptr != other.ptr) + { + clear(); + ptr = std::move(other).release(); + } + return *this; + } + + void swap(ref& other) + { + std::swap(ptr, other.ptr); + } + + [[nodiscard]] b_lean_obj_res get() const + { + return ptr; + } + + [[nodiscard]] lean_obj_res release() && + { + lean_object* result = get(); + ptr = nullptr; + return result; + } + }; + + char const* option_cstr_or_null(b_lean_obj_arg str) { - return nullptr; + if (lean_is_scalar(str)) + { + return nullptr; + } + else + { + lean_object* val = lean_ctor_get(str, 0); + return lean_string_cstr(val); + } } - else + + lean_obj_res io_result_mk_from_errno(char const* file_name) { - lean_object* val = lean_ctor_get(str, 0); - return lean_string_cstr(val); + int err = errno; + ref file_name_str = ref::steal(lean_mk_string(file_name)); + lean_object* error = lean_decode_io_error(err, file_name_str.get()); + + return lean_io_result_mk_error(error); } } -static lean_obj_res io_result_mk_from_errno(char const* file_name) +// -- Display + +namespace { - int err = errno; - lean_object* file_name_str = lean_mk_string(file_name); - lean_object* error = lean_decode_io_error(err, file_name_str); - lean_dec(file_name_str); - - return lean_io_result_mk_error(error); + lean_external_class* waylean_display_class() + { + static lean_external_class* klass = lean_register_external_class( + [](void* ptr) { wl_display_disconnect(static_cast<wl_display*>(ptr)); }, + [](void*, b_lean_obj_arg fn) { } + ); + return klass; + } } // Display.new : (name : @& Option String) → IO Display @@ -51,10 +147,465 @@ lean_obj_res waylean_display_new(b_lean_obj_arg name) return lean_io_result_mk_ok(waylean_disp); } -// Display.getVersion : (display : @& Display) → IO UInt32 -lean_obj_res waylean_display_get_version(b_lean_obj_arg display) +// Display.roundtrip : (display : @& Display) → IO Unit +lean_obj_res waylean_display_roundtrip(b_lean_obj_arg display) { wl_display* disp = static_cast<wl_display*>(lean_get_external_data(display)); - lean_object* version = lean_box_uint32(wl_display_get_version(disp)); - return lean_io_result_mk_ok(version); + if (wl_display_roundtrip(disp) == -1) { return io_result_mk_from_errno("<unknown>"); } + return lean_io_result_mk_ok(lean_box(0)); +} + +// -- Event Loop + +namespace +{ + class signal_manager + { + std::array<int, 2> fds{ -1, -1 }; + static inline int tx_fd = -1; + + explicit signal_manager() + { + if (pipe(fds.data()) == -1) + { + perror("signal_manager::signal_manager: pipe"); + return; + } + + if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1) + { + perror("signal_manager::signal_manager: fcntl"); + } + if (fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) + { + perror("signal_manager::signal_manager: fcntl"); + } + int flags = fcntl(fds[0], F_GETFL, 0); + if (flags == -1) + { + perror("signal_manager::signal_manager: fcntl"); + } + else + { + if (fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) == -1) + { + perror("signal_manager::signal_manager: fcntl"); + } + } + + struct sigaction sigpipe_action{}; + sigpipe_action.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sigpipe_action, nullptr) == -1) + { + perror("signal_manager::signal_manager: sigaction"); + } + } + + static signal_manager& instance() + { + static signal_manager res; + return res; + } + + static void signal_handler(int signum) + { + write(tx_fd, &signum, sizeof(signum)); + } + + public: + static int get_rx_fd() + { + return instance().fds[0]; + } + + static void handle(int signum) + { + tx_fd = instance().fds[1]; + struct sigaction action{}; + action.sa_handler = signal_handler; + action.sa_flags = SA_RESTART; + sigemptyset(&action.sa_mask); + if (sigaction(signum, &action, nullptr) == -1) + { + perror("signal_manager::handle: sigaction"); + } + } + }; + + struct event_source + { + enum class type + { + display, + timer, + signal, + } type{}; + size_t id{}; + int signum{}; + }; + + struct display_source + { + ref display_ref{}; + wl_display* disp{}; + size_t id{}; + }; + + struct timer_source + { + std::chrono::high_resolution_clock::time_point scheduled_time{}; + bool invalid{}; + ref message{}; + size_t id{}; + }; + + bool operator>(timer_source const& lhs, timer_source const& rhs) + { + return lhs.scheduled_time > rhs.scheduled_time; + } + + struct timer_source_queue : std::priority_queue<timer_source, std::vector<timer_source>, std::greater<timer_source>> + { + void invalidate(size_t id) + { + for (auto& source : c) + { + if (source.id == id) { source.invalid = true; } + } + } + + timer_source release_top() + { + return timer_source{ std::move(c.front()) }; + } + }; + + struct signal_source + { + ref message{}; + size_t id{}; + }; + + struct scheduled_event + { + ref message{}; + size_t id{}; + }; + + class event_loop + { + size_t next_id{ 0 }; + bool should_quit{}; + std::vector<display_source> displays; + std::unordered_map<int, std::vector<signal_source>> signals; + timer_source_queue timers; + std::vector<pollfd> pollfds; + std::deque<scheduled_event> scheduled; + + void rebuild_pollfds() + { + pollfds.clear(); + + pollfd signal_pollfd{}; + signal_pollfd.fd = signal_manager::get_rx_fd(); + signal_pollfd.events = POLLIN; + pollfds.emplace_back(signal_pollfd); + + for (auto& src : displays) + { + pollfd disp_pollfd{}; + disp_pollfd.fd = wl_display_get_fd(src.disp); + disp_pollfd.events = POLLIN; + pollfds.emplace_back(disp_pollfd); + } + } + + int get_next_timer_ms() + { + if (timers.empty()) { return -1; } + + auto duration = timers.top().scheduled_time - std::chrono::high_resolution_clock::now(); + auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); + return std::clamp<decltype(duration_ms)>(duration_ms, 0, std::numeric_limits<int>::max()); + } + + public: + explicit event_loop() + { + rebuild_pollfds(); + } + + event_source add_display(ref display) + { + auto res = event_source{}; + res.type = event_source::type::display; + res.id = next_id++; + + auto source = display_source{}; + source.disp = static_cast<wl_display*>(lean_get_external_data(display.get())); + source.display_ref = std::move(display); + source.id = res.id; + displays.emplace_back(std::move(source)); + + rebuild_pollfds(); + + return res; + } + + event_source add_timer(unsigned delay_ms, ref message) + { + auto res = event_source{}; + res.type = event_source::type::timer; + res.id = next_id++; + + auto source = timer_source{}; + source.scheduled_time = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds{ delay_ms }; + source.message = std::move(message); + source.id = res.id; + timers.emplace(std::move(source)); + + return res; + } + + event_source add_signal(int signum, ref message) + { + signal_manager::handle(signum); + + auto res = event_source{}; + res.type = event_source::type::signal; + res.id = next_id++; + res.signum = signum; + + auto source = signal_source{}; + source.message = std::move(message); + source.id = res.id; + signals[signum].emplace_back(std::move(source)); + + return res; + } + + void remove(event_source const& source) + { + // Discharge event source long-term. + switch (source.type) + { + case event_source::type::display: + { + auto it = std::find_if( + displays.begin(), displays.end(), + [&](display_source const& other) { return other.id == source.id; } + ); + if (it != displays.end()) { displays.erase(it); } + break; + } + case event_source::type::timer: + { + timers.invalidate(source.id); + break; + } + case event_source::type::signal: + { + auto& sources = signals[source.signum]; + auto it = std::find_if( + sources.begin(), sources.end(), + [&](signal_source const& other) { return other.id == source.id; } + ); + if (it != sources.end()) { sources.erase(it); } + break; + } + } + + // Discharge immediately scheduled events. + scheduled.erase( + std::remove_if( + scheduled.begin(), scheduled.end(), + [&](scheduled_event const& ev) { return ev.id == source.id; } + ), + scheduled.end() + ); + } + + void quit() + { + should_quit = true; + } + + ref poll() + { + while (scheduled.empty() && !should_quit) + { + // Otherwise, fetch any new events. + for (auto& src : displays) + { + while (wl_display_prepare_read(src.disp)) + { + if (wl_display_dispatch_pending(src.disp) == -1) + { + perror("event_loop::poll: wl_display_dispatch_pending"); + } + } + + if (wl_display_flush(src.disp) == -1) + { + perror("event_loop::poll: wl_display_flush"); + } + } + + int res; + do + { + res = ::poll(pollfds.data(), pollfds.size(), get_next_timer_ms()); + } + while (res == -1 && errno == EINTR); + if (res == -1) + { + perror("event_loop::poll: poll"); + } + + // 1. Process signals. + int signum; + ssize_t nread; + while ((nread = read(signal_manager::get_rx_fd(), &signum, sizeof(signum))) > 0) + { + if (nread == sizeof(signum)) + { + auto it = signals.find(signum); + if (it != signals.end()) + { + for (auto& source : it->second) + { + auto ev = scheduled_event{}; + ev.id = source.id; + ev.message = source.message; + scheduled.emplace_back(std::move(ev)); + } + } + } + } + if (nread == -1 && errno != EWOULDBLOCK && errno != EAGAIN) + { + perror("event_loop::poll: read"); + } + + // 2. Process display events. + for (auto& src : displays) + { + if (wl_display_read_events(src.disp) == -1) + { + perror("event_loop::poll: wl_display_read_events"); + continue; + } + if (wl_display_dispatch_pending(src.disp) == -1) + { + perror("event_loop::poll: wl_display_dispatch_pending"); + } + } + + // 3. Process expired timers. + auto new_now = std::chrono::high_resolution_clock::now(); + while (!timers.empty()) + { + if (timers.top().scheduled_time > new_now) { break; } + + auto timer = timers.release_top(); + if (!timer.invalid) + { + auto ev = scheduled_event{}; + ev.id = timer.id; + ev.message = std::move(timer.message); + scheduled.emplace_back(std::move(ev)); + } + timers.pop(); + } + } + + if (!scheduled.empty() && !should_quit) + { + auto event = std::move(scheduled.front()); + scheduled.pop_front(); + auto option = ref::steal(lean_alloc_ctor(1, 1, 0)); + lean_ctor_set(option.get(), 0, std::move(event.message).release()); + return option; + } + + return ref::steal(lean_box(0)); + } + }; + + lean_external_class* waylean_event_loop_class() + { + static lean_external_class* klass = lean_register_external_class( + [](void* ptr) { delete static_cast<event_loop*>(ptr); }, + [](void*, b_lean_obj_arg fn) { } + ); + return klass; + } + + lean_external_class* waylean_event_source_class() + { + static lean_external_class* klass = lean_register_external_class( + [](void* ptr) { delete static_cast<event_source*>(ptr); }, + [](void*, b_lean_obj_arg fn) { } + ); + return klass; + } +} + +// EventLoop.new : IO EventLoop M +lean_obj_res waylean_event_loop_new(void) +{ + event_loop* loop = new event_loop; + lean_object* waylean_loop = lean_alloc_external(waylean_event_loop_class(), loop); + return lean_io_result_mk_ok(waylean_loop); +} + +// EventLoop.addDisplay : (eventLoop : @& EventLoop M) → (display : @& Display M) → IO EventSource +lean_obj_res waylean_event_loop_add_display(b_lean_obj_arg loop_obj, b_lean_obj_arg display) +{ + auto loop = static_cast<event_loop*>(lean_get_external_data(loop_obj)); + auto source = new event_source{ loop->add_display(ref{ display }) }; + lean_object* waylean_source = lean_alloc_external(waylean_event_source_class(), source); + return lean_io_result_mk_ok(waylean_source); +} + +// EventLoop.addTimer : (eventLoop : @& EventLoop M) → (delayMs : UInt32) → (msg : @& M) → IO EventSource +lean_obj_res waylean_event_loop_add_timer(b_lean_obj_arg loop_obj, uint32_t delay_ms, b_lean_obj_arg message) +{ + auto loop = static_cast<event_loop*>(lean_get_external_data(loop_obj)); + auto source = new event_source{ loop->add_timer(delay_ms, ref{ message }) }; + lean_object* waylean_source = lean_alloc_external(waylean_event_source_class(), source); + return lean_io_result_mk_ok(waylean_source); +} + +// EventLoop.addSignal : (eventLoop : @& EventLoop M) → (signum : UInt32) → (msg : @& M) → IO EventSource +lean_obj_res waylean_event_loop_add_signal(b_lean_obj_arg loop_obj, uint32_t signum, b_lean_obj_arg message) +{ + auto loop = static_cast<event_loop*>(lean_get_external_data(loop_obj)); + auto source = new event_source{ loop->add_signal(static_cast<int>(signum), ref{ message }) }; + lean_object* waylean_source = lean_alloc_external(waylean_event_source_class(), source); + return lean_io_result_mk_ok(waylean_source); +} + +// EventLoop.remove : (eventLoop : @& EventLoop M) → (source : @& EventSource) → IO Unit +lean_obj_res waylean_event_loop_remove(b_lean_obj_arg loop_obj, b_lean_obj_arg source_obj) +{ + auto loop = static_cast<event_loop*>(lean_get_external_data(loop_obj)); + auto source = static_cast<event_source*>(lean_get_external_data(source_obj)); + loop->remove(*source); + return lean_io_result_mk_ok(lean_box(0)); +} + +// EventLoop.quit : (eventLoop : @& EventLoop M) → IO Unit +lean_obj_res waylean_event_loop_quit(b_lean_obj_arg loop_obj) +{ + auto loop = static_cast<event_loop*>(lean_get_external_data(loop_obj)); + loop->quit(); + return lean_io_result_mk_ok(lean_box(0)); +} + +// EventLoop.poll : (eventLoop : @& EventLoop M) → IO (Option M) +lean_obj_res waylean_event_loop_poll(b_lean_obj_arg loop_obj) +{ + auto loop = static_cast<event_loop*>(lean_get_external_data(loop_obj)); + return lean_io_result_mk_ok(loop->poll().release()); }
\ No newline at end of file diff --git a/lib/CWayland/CWayland.h b/lib/CWayland/CWayland.h index adcadc0..52106f2 100644 --- a/lib/CWayland/CWayland.h +++ b/lib/CWayland/CWayland.h @@ -9,8 +9,8 @@ extern "C" // Display -lean_obj_res waylean_display_new (b_lean_obj_arg name); -lean_obj_res waylean_display_get_version(b_lean_obj_arg display); +lean_obj_res waylean_display_new (b_lean_obj_arg name); +lean_obj_res waylean_display_roundtrip(b_lean_obj_arg display); // Event Loop @@ -18,11 +18,14 @@ lean_obj_res waylean_event_loop_new (void); lean_obj_res waylean_event_loop_add_display(b_lean_obj_arg event_loop, b_lean_obj_arg display); lean_obj_res waylean_event_loop_add_timer (b_lean_obj_arg event_loop, - unsigned delay_ms, + uint32_t delay_ms, b_lean_obj_arg message); lean_obj_res waylean_event_loop_add_signal (b_lean_obj_arg event_loop, - int signum, + uint32_t signum, b_lean_obj_arg message); +lean_obj_res waylean_event_loop_remove (b_lean_obj_arg event_loop, + b_lean_obj_arg source); +lean_obj_res waylean_event_loop_quit (b_lean_obj_arg event_loop); lean_obj_res waylean_event_loop_poll (b_lean_obj_arg event_loop); #ifdef __cplusplus diff --git a/lib/Wayland/Native.lean b/lib/Wayland/Native.lean index c529b63..d2c3e33 100644 --- a/lib/Wayland/Native.lean +++ b/lib/Wayland/Native.lean @@ -5,5 +5,34 @@ instance : Nonempty Display := DisplayP.property @[extern "waylean_display_new"] opaque Display.new : (name : @& Option String) → IO Display -@[extern "waylean_display_get_version"] -opaque Display.getVersion : (display : @& Display) → IO UInt32 +@[extern "waylean_display_roundtrip"] +opaque Display.roundtrip : (display : @& Display) → IO Unit + +private opaque EventLoopP : NonemptyType +def EventLoop : Type := EventLoopP.type +instance : Nonempty EventLoop := EventLoopP.property + +private opaque EventSourceP : NonemptyType +def EventSource : Type := EventSourceP.type +instance : Nonempty EventSource := EventSourceP.property + +@[extern "waylean_event_loop_new"] +opaque EventLoop.new : IO EventLoop + +@[extern "waylean_event_loop_add_display"] +opaque EventLoop.addDisplay : (eventLoop : @& EventLoop) → (display : @& Display) → IO EventSource + +@[extern "waylean_event_loop_add_timer"] +opaque EventLoop.addTimer : (eventLoop : @& EventLoop) → (delayMs : UInt32) → (msg : @& IO Unit) → IO EventSource + +@[extern "waylean_event_loop_add_signal"] +opaque EventLoop.addSignal : (eventLoop : @& EventLoop) → (signum : UInt32) → (msg : @& IO Unit) → IO EventSource + +@[extern "waylean_event_loop_remove"] +opaque EventLoop.remove : (eventLoop : @& EventLoop) → (source : @& EventSource) → IO Unit + +@[extern "waylean_event_loop_quit"] +opaque EventLoop.quit : (eventLoop : @& EventLoop) → IO Unit + +@[extern "waylean_event_loop_poll"] +opaque EventLoop.poll : (eventLoop : @& EventLoop) → IO (Option (IO Unit)) diff --git a/src/Waylean.lean b/src/Waylean.lean index d05df11..28c5f58 100644 --- a/src/Waylean.lean +++ b/src/Waylean.lean @@ -1,5 +1,20 @@ import Wayland -def main : IO Unit := do +partial def mainLoop (loop : EventLoop) : IO Unit := do + match (← loop.poll) with + | .none => return + | .some msg => msg; mainLoop loop + +partial def main : IO Unit := do let disp ← Display.new .none - IO.println s!"Hello, world! Version: {← disp.getVersion}" + let loop ← EventLoop.new + _ ← loop.addDisplay disp + _ ← loop.addSignal 2 (do + IO.println "Received SIGINT" + loop.quit) + let rec setUpTimer := loop.addTimer 500 (do + IO.println "Timer tick" + _ ← setUpTimer) + _ ← setUpTimer + disp.roundtrip + mainLoop loop |
