aboutsummaryrefslogtreecommitdiff
path: root/cross
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2026-01-07 00:26:32 -0700
committerJosh Rahm <joshuarahm@gmail.com>2026-01-07 00:26:32 -0700
commit4847aa0b4a6f945c24e1523e729652271bbc9680 (patch)
tree9ffac8b1934153cbde6f6814277f80c337d937b1 /cross
parent37e5037b68fb35276f0c426779f61aa278d5b21b (diff)
downloadmontis-4847aa0b4a6f945c24e1523e729652271bbc9680.tar.gz
montis-4847aa0b4a6f945c24e1523e729652271bbc9680.tar.bz2
montis-4847aa0b4a6f945c24e1523e729652271bbc9680.zip
[rebrand] Arken -> Ark, Plugin -> Soul, Erebor -> Cross
Diffstat (limited to 'cross')
-rw-r--r--cross/CMakeLists.txt27
-rw-r--r--cross/README.md32
-rw-r--r--cross/include/util.h24
-rw-r--r--cross/src/runtime_requests.c69
-rw-r--r--cross/src/util.c133
5 files changed, 285 insertions, 0 deletions
diff --git a/cross/CMakeLists.txt b/cross/CMakeLists.txt
new file mode 100644
index 0000000..7180c6e
--- /dev/null
+++ b/cross/CMakeLists.txt
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.10)
+project(cross LANGUAGES C)
+
+set(WLROOTS_SOURCE_DIR "${CMAKE_BINARY_DIR}/wlroots-src")
+set(WLROOTS_BUILD_DIR "${CMAKE_BINARY_DIR}/wlroots")
+
+file(GLOB_RECURSE CROSS_SOURCES src/*.c)
+
+add_library(cross STATIC ${CROSS_SOURCES})
+set_target_properties(cross PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/cross"
+)
+
+add_dependencies(cross wlroots_build)
+
+target_include_directories(cross PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/ark/include
+ ${CMAKE_BINARY_DIR}
+ ${WLROOTS_SOURCE_DIR}/include
+ ${WLROOTS_BUILD_DIR}/include
+ ${WLROOTS_BUILD_DIR}/protocol
+ /usr/include/pixman-1
+)
+
+target_compile_definitions(cross PRIVATE WLR_USE_UNSTABLE)
diff --git a/cross/README.md b/cross/README.md
new file mode 100644
index 0000000..cea84bb
--- /dev/null
+++ b/cross/README.md
@@ -0,0 +1,32 @@
+Cross (Native Bridge Library)
+=============================
+
+Cross is a static C library (`libcross.a`) that provides native helper
+functions for a dynamically loaded soul to interact with the runtime and
+wlroots.
+
+Why a static library?
+---------------------
+
+These helpers are linked into the soul (`.so`) so they hot-reload along with
+the soul, instead of being stuck inside the long-running runtime process.
+
+API surface
+-----------
+
+Public headers live in `cross/include`.
+
+- `cross/include/util.h`: soul-facing functions used via FFI, including:
+ - runtime requests (log/exit/hot-reload)
+ - seat access
+ - toplevel queries and basic positioning/geometry helpers
+
+Building
+--------
+
+Cross is built via the top-level CMake project:
+
+```sh
+cmake -S .. -B ../build
+cmake --build ../build --target cross
+```
diff --git a/cross/include/util.h b/cross/include/util.h
new file mode 100644
index 0000000..3707ae1
--- /dev/null
+++ b/cross/include/util.h
@@ -0,0 +1,24 @@
+#ifndef MONTIS_UTIL_H
+#define MONTIS_UTIL_H
+
+/*
+ * Runtime helpers exposed to souls. These operate on compositor state and are
+ * intended for direct FFI use from a soul implementation.
+ */
+
+void montis_do_request_hot_reload(void *plugv);
+void montis_do_request_log(void *plugv, const char *str);
+void montis_do_request_exit(void *plugv, int ec);
+
+void *arksoul_get_seat(void *ctx);
+void *arksoul_toplevel_at(void *ctx, double lx, double ly);
+void arksoul_get_toplevel_position(void *toplevel, double *x, double *y);
+void arksoul_set_toplevel_position(void *toplevel, double x, double y);
+void arksoul_get_toplevel_geometry(void *toplevel, double *x, double *y,
+ double *w, double *h);
+void arksoul_set_toplevel_geometry(void *toplevel, double x, double y,
+ double w, double h);
+void arksoul_focus_toplevel(void *toplevel);
+void arksoul_warp_cursor(void *ctx, double lx, double ly);
+
+#endif /* MONTIS_UTIL_H */
diff --git a/cross/src/runtime_requests.c b/cross/src/runtime_requests.c
new file mode 100644
index 0000000..adb2b2f
--- /dev/null
+++ b/cross/src/runtime_requests.c
@@ -0,0 +1,69 @@
+#include "util.h"
+#include "wl.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int soul_hot_reload_same_state_action_(soul_t *soul, void *ignore)
+{
+ (void)ignore;
+ return soul_hot_reload_same_state(soul);
+}
+
+void montis_do_request_hot_reload(void *plugv)
+{
+ soul_t *soul = plugv;
+
+ size_t n = soul->n_requested_actions++;
+ if (n < MAX_QUEUED_ACTIONS) {
+ soul->requested_actions[n].action = soul_hot_reload_same_state_action_;
+ soul->requested_actions[n].arg_dtor = NULL;
+ }
+}
+
+static int soul_do_log(soul_t *soul, void *chrs)
+{
+ (void)soul;
+ char *str = chrs;
+ puts(str);
+ return 0;
+}
+
+void montis_do_request_log(void *plugv, const char *str)
+{
+ soul_t *soul = plugv;
+
+ size_t n = soul->n_requested_actions++;
+ if (n < MAX_QUEUED_ACTIONS) {
+ soul->requested_actions[n].action = soul_do_log;
+ soul->requested_actions[n].str_arg = strdup(str);
+ soul->requested_actions[n].arg_dtor = free;
+ }
+}
+
+static int arksoul_do_exit(void *plugv, int ec)
+{
+ (void)plugv;
+ exit(ec);
+ return 0;
+}
+
+void montis_do_request_exit(void *plugv, int ec)
+{
+ soul_t *soul = plugv;
+
+ size_t n = soul->n_requested_actions++;
+ if (n < MAX_QUEUED_ACTIONS) {
+ soul->requested_actions[n].action =
+ (int (*)(soul_t *, void *))arksoul_do_exit;
+ soul->requested_actions[n].int_arg = ec;
+ soul->requested_actions[n].arg_dtor = NULL;
+ }
+}
+
+void *arksoul_get_seat(void *ctx)
+{
+ struct montis_server *server = wl_container_of(ctx, server, soul);
+ return server->seat;
+}
diff --git a/cross/src/util.c b/cross/src/util.c
new file mode 100644
index 0000000..0c02dfe
--- /dev/null
+++ b/cross/src/util.c
@@ -0,0 +1,133 @@
+#include "util.h"
+#include "wl.h"
+
+#include <wlr/types/wlr_scene.h>
+#include <wlr/types/wlr_xdg_shell.h>
+
+static struct montis_server *server_from_ctx(void *ctx)
+{
+ struct montis_server *server = wl_container_of(ctx, server, soul);
+ return server;
+}
+
+static struct montis_toplevel *toplevel_at(struct montis_server *server,
+ double lx, double ly)
+{
+ double sx = 0.0;
+ double sy = 0.0;
+
+ struct wlr_scene_node *node =
+ wlr_scene_node_at(&server->scene->tree.node, lx, ly, &sx, &sy);
+ if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) {
+ return NULL;
+ }
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+ struct wlr_scene_surface *scene_surface =
+ wlr_scene_surface_try_from_buffer(scene_buffer);
+ if (!scene_surface) {
+ return NULL;
+ }
+
+ struct wlr_scene_tree *tree = node->parent;
+ while (tree != NULL && tree->node.data == NULL) {
+ tree = tree->node.parent;
+ }
+ return tree ? tree->node.data : NULL;
+}
+
+void *arksoul_toplevel_at(void *ctx, double lx, double ly)
+{
+ if (!ctx) {
+ return NULL;
+ }
+ struct montis_server *server = server_from_ctx(ctx);
+ return toplevel_at(server, lx, ly);
+}
+
+void arksoul_get_toplevel_position(void *toplevel, double *x, double *y)
+{
+ if (!toplevel || !x || !y) {
+ return;
+ }
+ struct montis_toplevel *tl = toplevel;
+ *x = tl->scene_tree->node.x;
+ *y = tl->scene_tree->node.y;
+}
+
+void arksoul_set_toplevel_position(void *toplevel, double x, double y)
+{
+ if (!toplevel) {
+ return;
+ }
+ struct montis_toplevel *tl = toplevel;
+ wlr_scene_node_set_position(&tl->scene_tree->node, (int)x, (int)y);
+}
+
+void arksoul_get_toplevel_geometry(void *toplevel, double *x, double *y,
+ double *w, double *h)
+{
+ if (!toplevel || !x || !y || !w || !h) {
+ return;
+ }
+ struct montis_toplevel *tl = toplevel;
+ struct wlr_box geo_box;
+ wlr_xdg_surface_get_geometry(tl->xdg_toplevel->base, &geo_box);
+ *x = tl->scene_tree->node.x;
+ *y = tl->scene_tree->node.y;
+ *w = geo_box.width;
+ *h = geo_box.height;
+}
+
+void arksoul_set_toplevel_geometry(void *toplevel, double x, double y,
+ double w, double h)
+{
+ if (!toplevel) {
+ return;
+ }
+ struct montis_toplevel *tl = toplevel;
+ wlr_scene_node_set_position(&tl->scene_tree->node, (int)x, (int)y);
+ wlr_xdg_toplevel_set_size(tl->xdg_toplevel, (int)w, (int)h);
+}
+
+void arksoul_warp_cursor(void *ctx, double lx, double ly)
+{
+ if (!ctx) {
+ return;
+ }
+ struct montis_server *server = server_from_ctx(ctx);
+ wlr_cursor_warp(server->cursor, NULL, lx, ly);
+}
+
+void arksoul_focus_toplevel(void *toplevel)
+{
+ if (!toplevel) {
+ return;
+ }
+ struct montis_toplevel *tl = toplevel;
+ struct montis_server *server = tl->server;
+ struct wlr_seat *seat = server->seat;
+ struct wlr_surface *surface = tl->xdg_toplevel->base->surface;
+ struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface;
+
+ if (prev_surface == surface) {
+ return;
+ }
+ if (prev_surface) {
+ struct wlr_xdg_toplevel *prev_toplevel =
+ wlr_xdg_toplevel_try_from_wlr_surface(prev_surface);
+ if (prev_toplevel != NULL) {
+ wlr_xdg_toplevel_set_activated(prev_toplevel, false);
+ }
+ }
+
+ struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat);
+ wlr_scene_node_raise_to_top(&tl->scene_tree->node);
+ wl_list_remove(&tl->link);
+ wl_list_insert(&server->toplevels, &tl->link);
+ wlr_xdg_toplevel_set_activated(tl->xdg_toplevel, true);
+ if (keyboard != NULL) {
+ wlr_seat_keyboard_notify_enter(seat, surface, keyboard->keycodes,
+ keyboard->num_keycodes,
+ &keyboard->modifiers);
+ }
+}