#include "LunarWM.h" #include "RayExt.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vec.h" void toplevel_commit_notify(struct wl_listener *l, void *) { auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), commit); if (!tl || !tl->surface) return; if (!tl->is_xwayland) { if (tl->u.xdg && tl->u.xdg->base->initial_commit) { wlr_xdg_toplevel_set_size(tl->u.xdg, 0, 0); return; } } LunarWM_Toplevel_update(tl); } void toplevel_destroy_notify(struct wl_listener *l, void *) { auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), destroy); for (size_t i = 0; i < vector_size(tl->server->wayland.v_toplevels); i++) { if (tl == tl->server->wayland.v_toplevels[i]) { vector_remove(tl->server->wayland.v_toplevels, i); break; } } LunarWM_Toplevel_destroy(tl); free(tl); } bool LunarWM_Toplevel_init_xdg( LunarWM_Toplevel *tl, LunarWM *wm, struct wlr_xdg_toplevel *xdg) { tl->id = vector_size(wm->wayland.v_toplevels) + 1; tl->server = wm; tl->is_xwayland = false; tl->u.xdg = xdg; tl->surface = xdg->base->surface; assert(tl->surface); tl->commit.notify = toplevel_commit_notify; wl_signal_add(&tl->surface->events.commit, &tl->commit); tl->destroy.notify = toplevel_destroy_notify; wl_signal_add(&tl->surface->events.destroy, &tl->destroy); return true; } bool LunarWM_Toplevel_init_xwayland( LunarWM_Toplevel *tl, LunarWM *wm, struct wlr_xwayland_surface *xwl) { tl->id = vector_size(wm->wayland.v_toplevels) + 1; tl->server = wm; tl->is_xwayland = true; tl->u.xwl = xwl; tl->surface = xwl->surface; assert(tl->surface); tl->commit.notify = toplevel_commit_notify; wl_signal_add(&tl->surface->events.commit, &tl->commit); tl->destroy.notify = toplevel_destroy_notify; wl_signal_add(&tl->surface->events.destroy, &tl->destroy); return true; } bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this) { wl_list_remove(&this->commit.link); wl_list_remove(&this->destroy.link); if (this->locked_buffer != nullptr) { wlr_buffer_unlock(this->locked_buffer); } return true; } bool LunarWM_Toplevel_update(LunarWM_Toplevel *this) { if (!this || !this->surface) return false; this->texture = wlr_surface_get_texture(this->surface); struct wlr_client_buffer *cl_buf = this->surface->buffer; if ((this->texture == nullptr) || (cl_buf == nullptr)) { return false; } if ((this->locked_buffer != nullptr) && this->locked_buffer != &cl_buf->base) { wlr_buffer_unlock(this->locked_buffer); this->locked_buffer = nullptr; } if (this->locked_buffer == nullptr) { this->locked_buffer = wlr_buffer_lock(&cl_buf->base); } wlr_gles2_texture_get_attribs(this->texture, &this->attribs); this->gles_texture = gles2_get_texture(this->texture); if (this->gles_texture == nullptr) { return false; } this->rl_texture.id = (unsigned int)this->attribs.tex; this->rl_texture.width = (int)this->texture->width; this->rl_texture.height = (int)this->texture->height; this->rl_texture.mipmaps = 1; this->rl_texture.format = this->gles_texture->has_alpha ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : PIXELFORMAT_UNCOMPRESSED_R8G8B8; SetTextureFilter(this->rl_texture, TEXTURE_FILTER_BILINEAR); SetTextureWrap(this->rl_texture, TEXTURE_WRAP_CLAMP); return true; } static void clamp_to_hints( const struct wlr_xwayland_surface *x, uint16_t *w, uint16_t *h) { xcb_size_hints_t *hints = x->size_hints; if (!hints) return; if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) { if (*w < hints->min_width) *w = hints->min_width; if (*h < hints->min_height) *h = hints->min_height; } if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) { if (hints->max_width > 0 && *w > hints->max_width) *w = hints->max_width; if (hints->max_height > 0 && *h > hints->max_height) *h = hints->max_height; } if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) { if (hints->width_inc > 0) *w = (*w / hints->width_inc) * hints->width_inc; if (hints->height_inc > 0) *h = (*h / hints->height_inc) * hints->height_inc; } if ((hints->flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE)) { if (*w < hints->base_width) *w = hints->base_width; if (*h < hints->base_height) *h = hints->base_height; } } struct XwlHooks { LunarWM *wm; struct wlr_xwayland_surface *xwl; struct wl_listener associate; struct wl_listener dissociate; struct wl_listener destroy; struct wl_listener req_configure; struct wl_listener req_maximize; struct wl_listener req_fullscreen; struct wl_listener set_geometry; }; static void xwayland_ready_notify(struct wl_listener *l, void *data) { LunarWM *wm = wl_container_of(l, wm, wayland.xwayland_ready); wlr_xwayland_set_seat(wm->wayland.xwayland, wm->wayland.seat); setenv("DISPLAY", wm->wayland.xwayland->display_name, 1); } static void handle_associate(struct wl_listener *ll, void *d) { struct wlr_xwayland_surface *x = d; LunarWM *wm2 = wl_container_of(ll, wm2, wayland.xwayland_associate_tmp); if (!x->surface) return; LunarWM_Toplevel *tl = calloc(1, sizeof *tl); if (!tl) return; if (LunarWM_Toplevel_init_xwayland(tl, wm2, x)) { vector_add(&wm2->wayland.v_toplevels, tl); } else { free(tl); } wl_list_remove(&wm2->wayland.xwayland_associate_tmp.link); } static void handle_dissociate(struct wl_listener *ll, void *d) { struct wlr_xwayland_surface *x = d; LunarWM *wm2 = wl_container_of(ll, wm2, wayland.xwayland_dissociate_tmp); for (size_t i = 0; i < vector_size(wm2->wayland.v_toplevels); ++i) { LunarWM_Toplevel *tl = wm2->wayland.v_toplevels[i]; if (tl->is_xwayland && tl->u.xwl == x) { vector_remove(wm2->wayland.v_toplevels, i); LunarWM_Toplevel_destroy(tl); free(tl); break; } } wl_list_remove(&wm2->wayland.xwayland_dissociate_tmp.link); } static void xwl_hooks_destroy(struct XwlHooks *h) { if (!h) return; if (h->associate.link.prev || h->associate.link.next) wl_list_remove(&h->associate.link); if (h->dissociate.link.prev || h->dissociate.link.next) wl_list_remove(&h->dissociate.link); if (h->destroy.link.prev || h->destroy.link.next) wl_list_remove(&h->destroy.link); free(h); } static void xwl_on_associate(struct wl_listener *ll, void *data) { struct XwlHooks *h = wl_container_of(ll, h, associate); struct wlr_xwayland_surface *x = h->xwl; if (!x || !x->surface) return; LunarWM_Toplevel *tl = calloc(1, sizeof *tl); if (!tl) return; if (LunarWM_Toplevel_init_xwayland(tl, h->wm, x)) { vector_add(&h->wm->wayland.v_toplevels, tl); } else { free(tl); } } static void xwl_unmap_toplevel(LunarWM_Toplevel *tl) { if (!tl) return; if (tl->commit.link.prev || tl->commit.link.next) wl_list_remove(&tl->commit.link); if (tl->destroy.link.prev || tl->destroy.link.next) wl_list_remove(&tl->destroy.link); if (tl->locked_buffer) { wlr_buffer_unlock(tl->locked_buffer); tl->locked_buffer = NULL; } tl->texture = NULL; tl->gles_texture = NULL; tl->rl_texture = (Texture) { 0 }; tl->surface = NULL; } static void xwl_on_dissociate(struct wl_listener *ll, void *data) { struct XwlHooks *h = wl_container_of(ll, h, dissociate); LunarWM *wm = h->wm; struct wlr_xwayland_surface *x = h->xwl; for (size_t i = 0; i < vector_size(wm->wayland.v_toplevels); ++i) { LunarWM_Toplevel *tl = wm->wayland.v_toplevels[i]; if (tl->is_xwayland && tl->u.xwl == x) { xwl_unmap_toplevel(tl); break; } } } static void xwl_on_request_configure(struct wl_listener *ll, void *data) { struct XwlHooks *xh = wl_container_of(ll, xh, req_configure); struct wlr_xwayland_surface_configure_event *ev = data; if (!xh->xwl) return; int16_t x = xh->xwl->x, y = xh->xwl->y; uint16_t w = xh->xwl->width, h = xh->xwl->height; if (ev->mask & XCB_CONFIG_WINDOW_X) x = ev->x; if (ev->mask & XCB_CONFIG_WINDOW_Y) y = ev->y; if (ev->mask & XCB_CONFIG_WINDOW_WIDTH) w = ev->width; if (ev->mask & XCB_CONFIG_WINDOW_HEIGHT) h = ev->height; clamp_to_hints(xh->xwl, &w, &h); wlr_xwayland_surface_configure(xh->xwl, x, y, w, h); } static void xwl_on_request_maximize(struct wl_listener *ll, void *data) { struct XwlHooks *xh = wl_container_of(ll, xh, req_maximize); wlr_xwayland_surface_set_maximized(xh->xwl, true, true); } static void xwl_on_request_fullscreen(struct wl_listener *ll, void *data) { struct XwlHooks *xh = wl_container_of(ll, xh, req_fullscreen); wlr_xwayland_surface_set_fullscreen(xh->xwl, true); } static void xwl_on_set_geometry(struct wl_listener *ll, void *data) { (void)ll; (void)data; } static void xwl_on_destroy(struct wl_listener *ll, void *data) { struct XwlHooks *xh = wl_container_of(ll, xh, destroy); LunarWM *wm = xh->wm; struct wlr_xwayland_surface *x = xh->xwl; if (xh->req_configure.link.prev) wl_list_remove(&xh->req_configure.link); if (xh->req_maximize.link.prev) wl_list_remove(&xh->req_maximize.link); if (xh->req_fullscreen.link.prev) wl_list_remove(&xh->req_fullscreen.link); if (xh->set_geometry.link.prev) wl_list_remove(&xh->set_geometry.link); if (xh->associate.link.prev) wl_list_remove(&xh->associate.link); if (xh->dissociate.link.prev) wl_list_remove(&xh->dissociate.link); if (xh->destroy.link.prev) wl_list_remove(&xh->destroy.link); for (size_t i = 0; i < vector_size(wm->wayland.v_toplevels); ++i) { LunarWM_Toplevel *tl = wm->wayland.v_toplevels[i]; if (tl->is_xwayland && tl->u.xwl == x) { vector_remove(wm->wayland.v_toplevels, i); free(tl); break; } } free(xh); } static void xwayland_new_surface_notify(struct wl_listener *l, void *data) { LunarWM *wm = wl_container_of(l, wm, wayland.xwayland_new_surface); struct wlr_xwayland_surface *xwl = data; if (xwl->surface) { LunarWM_Toplevel *tl = calloc(1, sizeof *tl); if (tl && LunarWM_Toplevel_init_xwayland(tl, wm, xwl)) vector_add(&wm->wayland.v_toplevels, tl); else free(tl); } struct XwlHooks *h = calloc(1, sizeof *h); if (!h) return; h->wm = wm; h->xwl = xwl; h->associate.notify = xwl_on_associate; wl_signal_add(&xwl->events.associate, &h->associate); h->dissociate.notify = xwl_on_dissociate; wl_signal_add(&xwl->events.dissociate, &h->dissociate); h->req_configure.notify = xwl_on_request_configure; wl_signal_add(&xwl->events.request_configure, &h->req_configure); h->req_maximize.notify = xwl_on_request_maximize; wl_signal_add(&xwl->events.request_maximize, &h->req_maximize); h->req_fullscreen.notify = xwl_on_request_fullscreen; wl_signal_add(&xwl->events.request_fullscreen, &h->req_fullscreen); h->set_geometry.notify = xwl_on_set_geometry; wl_signal_add(&xwl->events.set_geometry, &h->set_geometry); h->destroy.notify = xwl_on_destroy; wl_signal_add(&xwl->events.destroy, &h->destroy); xwl->data = h; } void LunarWM_Toplevel_focus(LunarWM_Toplevel *this) { if (!this) return; LunarWM *wm = this->server; struct wlr_seat *seat = wm->wayland.seat; struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; struct wlr_surface *surface = this->surface; if (prev_surface == surface) { return; } if (prev_surface) { struct wlr_xdg_toplevel *prev_tl = wlr_xdg_toplevel_try_from_wlr_surface(prev_surface); if (prev_tl) wlr_xdg_toplevel_set_activated(prev_tl, false); struct wlr_xwayland_surface *prev_x = wlr_xwayland_surface_try_from_wlr_surface(prev_surface); if (prev_x) wlr_xwayland_surface_activate(prev_x, false); } struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); if (this->is_xwayland) { wlr_xwayland_surface_offer_focus(this->u.xwl); wlr_xwayland_surface_activate(this->u.xwl, true); } else { wlr_xdg_toplevel_set_activated(this->u.xdg, true); } if (keyboard != NULL) { wlr_seat_keyboard_notify_enter(seat, surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } } static void keysym_name(xkb_keysym_t sym, char *buf, size_t bufsz) { if (bufsz == 0) return; buf[0] = 0; char tmp[128] = { 0 }; int n = xkb_keysym_get_name(sym, tmp, sizeof tmp); if (n <= 0) { snprintf(buf, bufsz, "Unknown"); return; } if (strlen(tmp) == 1) { buf[0] = (char)toupper((unsigned char)tmp[0]); buf[1] = 0; } else { snprintf(buf, bufsz, "%s", tmp); } } static void Keyboard_modifiers_notify(struct wl_listener *listener, void *) { auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), modifiers); if (!kbd->server || !kbd->server->wayland.seat) { return; } wlr_seat_set_keyboard(kbd->server->wayland.seat, kbd->wlr_keyboard); wlr_seat_keyboard_notify_modifiers( kbd->server->wayland.seat, &kbd->wlr_keyboard->modifiers); } static void Keyboard_key_notify(struct wl_listener *listener, void *data) { auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), key); if (!kbd->server || !kbd->server->wayland.seat) { return; } auto *server = kbd->server; auto *event = (struct wlr_keyboard_key_event *)data; struct wlr_seat *seat = server->wayland.seat; uint32_t const keycode = event->keycode + 8; xkb_keysym_t const *syms = nullptr; int const nsyms = xkb_state_key_get_syms(kbd->wlr_keyboard->xkb_state, keycode, &syms); xkb_keysym_t const keysym = xkb_state_key_get_one_sym(kbd->wlr_keyboard->xkb_state, keycode); bool handled = false; uint32_t const modifiers = wlr_keyboard_get_modifiers(kbd->wlr_keyboard); if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { if (server->wayland.session && keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { unsigned const vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; wlr_session_change_vt(server->wayland.session, vt); return; } for (int i = 0; i < server->cman->cfg.keybindings.count; i++) { BindingRef ref = server->cman->cfg.keybindings.items[i]; if (ref.mods_mask == 0 || ref.sym == XKB_KEY_NoSymbol) continue; bool sym_match = false; if (syms && nsyms > 0) { xkb_keysym_t want = xkb_keysym_to_lower(ref.sym); for (int s = 0; s < nsyms; ++s) { if (syms[s] == want) { sym_match = true; break; } if (xkb_keysym_to_lower(syms[s]) == want) { sym_match = true; break; } } } if (((modifiers & ref.mods_mask) == ref.mods_mask) && sym_match) { config_trigger_ref( server->cman->L, &server->cman->cfg, ref.action_ref); handled = true; break; } } } if (!handled) { if (!seat) return; if (!kbd->wlr_keyboard) return; wlr_seat_set_keyboard(seat, kbd->wlr_keyboard); wlr_seat_keyboard_notify_key( seat, event->time_msec, event->keycode, event->state); } } static void Keyboard_destroy_notify(struct wl_listener *listener, void *) { auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), destroy); wl_list_remove(&kbd->modifiers.link); wl_list_remove(&kbd->key.link); wl_list_remove(&kbd->destroy.link); wl_list_remove(&kbd->link); free(kbd); } static void new_input_listener_notify(struct wl_listener *listener, void *data) { LunarWM *wm = wl_container_of(listener, wm, wayland.new_input_listener); auto *dev = (struct wlr_input_device *)data; if (dev->type == WLR_INPUT_DEVICE_KEYBOARD) { struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(dev); LunarWM_Keyboard *keyboard = calloc(1, sizeof(*keyboard)); keyboard->server = wm; keyboard->wlr_keyboard = wlr_keyboard; struct xkb_rule_names const rule_names = { .options = wm->cman->cfg.input.keyboard.xkb_options, }; wlr_log(LOG_INFO, "xkb_options=%s", wm->cman->cfg.input.keyboard.xkb_options); struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *keymap = xkb_keymap_new_from_names( context, &rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS); wlr_keyboard_set_keymap(wlr_keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600); keyboard->modifiers.notify = Keyboard_modifiers_notify; wl_signal_add(&wlr_keyboard->events.modifiers, &keyboard->modifiers); keyboard->key.notify = Keyboard_key_notify; wl_signal_add(&wlr_keyboard->events.key, &keyboard->key); keyboard->destroy.notify = Keyboard_destroy_notify; wl_signal_add(&dev->events.destroy, &keyboard->destroy); wlr_seat_set_keyboard(wm->wayland.seat, keyboard->wlr_keyboard); wl_list_insert(&wm->wayland.keyboards, &keyboard->link); } else if (dev->type == WLR_INPUT_DEVICE_POINTER) { wlr_cursor_attach_input_device(wm->wayland.cursor, dev); } uint32_t caps = WL_SEAT_CAPABILITY_POINTER; if (!wl_list_empty(&wm->wayland.keyboards)) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } assert(wm->wayland.seat); wlr_seat_set_capabilities(wm->wayland.seat, caps); } static void new_xdg_popup_listener_notify(struct wl_listener *, void *) { // FIXME: Add support for popups } static void new_xdg_toplevel_listener_notify( struct wl_listener *listener, void *data) { LunarWM *wm = wl_container_of(listener, wm, wayland.new_xdg_toplevel_listener); auto *xdg_tl = (struct wlr_xdg_toplevel *)data; LunarWM_Toplevel *tl = (LunarWM_Toplevel *)calloc(1, sizeof(*tl)); if (!tl) { wlr_log(WLR_ERROR, "oom"); return; } if (LunarWM_Toplevel_init_xdg(tl, wm, xdg_tl)) { vector_add(&wm->wayland.v_toplevels, tl); } else { wlr_log(WLR_ERROR, "Failed to initialize Toplevel."); free(tl); } } struct vo_client_res { struct wl_resource *res; struct wl_list link; // vo_client_res.link }; static void vo_send_initial(struct virtual_output *vo, struct wl_resource *res) { // wl_output v1..v4 ordering: geometry, mode, scale (v2+), name/desc (v4), // done (v2+) wl_output_send_geometry(res, vo->x, vo->y, vo->phys_w_mm, vo->phys_h_mm, vo->subpixel, vo->make, vo->model, vo->transform); uint32_t flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; wl_output_send_mode(res, flags, vo->width, vo->height, vo->refresh_mhz); if (wl_resource_get_version(res) >= 2) wl_output_send_scale(res, vo->scale); if (wl_resource_get_version(res) >= 4) { wl_output_send_name(res, vo->name); wl_output_send_description(res, vo->desc); } if (wl_resource_get_version(res) >= 2) wl_output_send_done(res); } static void vo_resource_destroy(struct wl_resource *res) { struct vo_client_res *cr = wl_resource_get_user_data(res); if (!cr) return; wl_list_remove(&cr->link); free(cr); } // wl_output requests (only release exists in v3+) static void output_release(struct wl_client *client, struct wl_resource *res) { (void)client; wl_resource_destroy(res); } static const struct wl_output_interface output_impl = { .release = output_release, }; static void vo_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct virtual_output *vo = data; uint32_t ver = version; if (ver > 4) ver = 4; // we implement up to v4 struct wl_resource *res = wl_resource_create(client, &wl_output_interface, ver, id); if (!res) { wl_client_post_no_memory(client); return; } struct vo_client_res *cr = calloc(1, sizeof *cr); if (!cr) { wl_client_post_no_memory(client); wl_resource_destroy(res); return; } cr->res = res; wl_resource_set_implementation(res, &output_impl, cr, vo_resource_destroy); wl_list_insert(&vo->clients, &cr->link); vo_send_initial(vo, res); } static void vo_broadcast_mode(struct virtual_output *vo) { struct vo_client_res *cr; wl_list_for_each(cr, &vo->clients, link) { struct wl_resource *res = cr->res; uint32_t flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; wl_output_send_mode(res, flags, vo->width, vo->height, vo->refresh_mhz); if (wl_resource_get_version(res) >= 2) wl_output_send_done(res); } } static void vo_broadcast_scale(struct virtual_output *vo) { struct vo_client_res *cr; wl_list_for_each(cr, &vo->clients, link) { struct wl_resource *res = cr->res; if (wl_resource_get_version(res) >= 2) { wl_output_send_scale(res, vo->scale); wl_output_send_done(res); } } } static void vo_destroy(struct virtual_output *vo) { // destroy client resources struct vo_client_res *cr, *tmp; wl_list_for_each_safe(cr, tmp, &vo->clients, link) { wl_resource_destroy(cr->res); // calls vo_resource_destroy } if (vo->global) { wl_global_destroy(vo->global); vo->global = NULL; } free(vo); } static struct virtual_output *vo_create(struct wl_display *display, char const *name, char const *desc, int32_t w, int32_t h, int32_t refresh_mhz, int32_t scale, char const *make, char const *model) { struct virtual_output *vo = calloc(1, sizeof *vo); if (!vo) return NULL; vo->display = display; wl_list_init(&vo->clients); vo->x = 0; vo->y = 0; vo->phys_w_mm = 0; vo->phys_h_mm = 0; // unknown; set if you care vo->width = w; vo->height = h; vo->refresh_mhz = refresh_mhz; vo->scale = scale > 0 ? scale : 1; vo->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; vo->transform = WL_OUTPUT_TRANSFORM_NORMAL; vo->make = make ? make : "Lunar"; vo->model = model ? model : "Virtual"; vo->name = name; vo->desc = desc ? desc : name; vo->global = wl_global_create(display, &wl_output_interface, 4, vo, vo_bind); if (!vo->global) { free(vo); return NULL; } return vo; } static bool init_wayland(LunarWM *this) { wlr_log_init(WLR_DEBUG, nullptr); this->wayland.v_toplevels = vector_create(); this->wayland.display = wl_display_create(); if (this->wayland.display == nullptr) { return false; wlr_log(WLR_ERROR, "Failed to create wayland display"); } this->wayland.event_loop = wl_display_get_event_loop(this->wayland.display); if (this->wayland.event_loop == nullptr) { wlr_log(WLR_ERROR, "Failed to get wayland event loop"); return false; } this->wayland.backend = wlr_backend_autocreate(this->wayland.event_loop, nullptr); if (this->wayland.backend == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots backend"); return false; } setenv("WLR_RENDERER", "gles2", 1); this->wayland.renderer = wlr_renderer_autocreate(this->wayland.backend); if (this->wayland.renderer == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots renderer"); return false; } this->wayland.session = wlr_session_create(this->wayland.event_loop); if (this->wayland.session == nullptr) { wlr_log(WLR_ERROR, "Failed to create session"); return false; } this->wayland.egl = wlr_gles2_renderer_get_egl(this->wayland.renderer); if (this->wayland.egl == nullptr) { wlr_log(WLR_ERROR, "Failed to get egl information from renderer"); return false; } this->wayland.egl_display = wlr_egl_get_display(this->wayland.egl); this->wayland.egl_context = wlr_egl_get_context(this->wayland.egl); this->wayland.egl_config = EGL_NO_CONFIG_KHR; if (!wlr_renderer_init_wl_display( this->wayland.renderer, this->wayland.display)) { wlr_log( WLR_ERROR, "Failed to initialize renderer with wayland display"); return false; } this->wayland.allocator = wlr_allocator_autocreate( this->wayland.backend, this->wayland.renderer); if (this->wayland.allocator == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots allocator"); } this->wayland.compositor = wlr_compositor_create( this->wayland.display, 5, this->wayland.renderer); if (this->wayland.compositor == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots compositor"); } this->wayland.subcompositor = wlr_subcompositor_create(this->wayland.display); if (this->wayland.subcompositor == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots subcompositor"); } this->wayland.data_device_manager = wlr_data_device_manager_create(this->wayland.display); if (this->wayland.data_device_manager == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots data device manager"); } this->wayland.cursor = wlr_cursor_create(); wl_list_init(&this->wayland.keyboards); this->wayland.new_input_listener.notify = new_input_listener_notify; wl_signal_add(&this->wayland.backend->events.new_input, &this->wayland.new_input_listener); this->wayland.seat = wlr_seat_create(this->wayland.display, "seat0"); if (this->wayland.seat == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots seat"); return false; } this->wayland.xdg_shell = wlr_xdg_shell_create(this->wayland.display, 3); this->wayland.new_xdg_toplevel_listener.notify = new_xdg_toplevel_listener_notify; wl_signal_add(&this->wayland.xdg_shell->events.new_toplevel, &this->wayland.new_xdg_toplevel_listener); this->wayland.new_xdg_popup_listener.notify = new_xdg_popup_listener_notify; wl_signal_add(&this->wayland.xdg_shell->events.new_popup, &this->wayland.new_xdg_popup_listener); if (this->cman->cfg.xwayland.enabled) { this->wayland.xwayland = wlr_xwayland_create(this->wayland.display, this->wayland.compositor, this->cman->cfg.xwayland.lazy); this->wayland.xwayland_ready.notify = xwayland_ready_notify; wl_signal_add(&this->wayland.xwayland->events.ready, &this->wayland.xwayland_ready); this->wayland.xwayland_new_surface.notify = xwayland_new_surface_notify; wl_signal_add(&this->wayland.xwayland->events.new_surface, &this->wayland.xwayland_new_surface); } return true; } static uint32_t get_swapchain_image( LunarWM *this, int swapchain_images_i, uint32_t index) { return this->xr.swapchain_images[swapchain_images_i].a_imgs[index].image; } static bool init_xr(LunarWM *this) { XrResult res = XR_SUCCESS; XrApplicationInfo app_info = { .applicationVersion = 1, .engineVersion = 1, .apiVersion = XR_CURRENT_API_VERSION, }; strncpy((char *)app_info.applicationName, "LunarWM", XR_MAX_APPLICATION_NAME_SIZE); strncpy( (char *)app_info.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE); char const *instance_extensions[] = { XR_EXT_DEBUG_UTILS_EXTENSION_NAME, XR_MNDX_EGL_ENABLE_EXTENSION_NAME, XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, XR_EXT_HAND_TRACKING_EXTENSION_NAME, }; char const **v_active_instance_extensions = vector_create(); uint32_t extension_properties_count = 0; XrExtensionProperties *extension_properties; if (xrEnumerateInstanceExtensionProperties( nullptr, 0, &extension_properties_count, nullptr) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to enumerate OpenXR instance extension properties (count)"); return false; } extension_properties = malloc(sizeof(*extension_properties) * extension_properties_count); for (uint32_t i = 0; i < extension_properties_count; ++i) { extension_properties[i].type = XR_TYPE_EXTENSION_PROPERTIES; extension_properties[i].next = NULL; } if (xrEnumerateInstanceExtensionProperties(nullptr, extension_properties_count, &extension_properties_count, extension_properties) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to enumerate OpenXR instance extension properties"); return false; } for (size_t i = 0; i < ARRAY_SZ(instance_extensions); i++) { char const *requested_instance_extension = instance_extensions[i]; bool found = false; for (int j = 0; j < extension_properties_count; j++) { if (strcmp(requested_instance_extension, extension_properties[j].extensionName) != 0) { continue; } vector_add( &v_active_instance_extensions, requested_instance_extension); found = true; break; } if (!found) { wlr_log(WLR_ERROR, "Failed to find OpenXR instance extension: %s", requested_instance_extension); return false; } } { XrInstanceCreateInfo const ci = { .type = XR_TYPE_INSTANCE_CREATE_INFO, .next = nullptr, .createFlags = 0, .applicationInfo = app_info, .enabledApiLayerCount = 0, .enabledApiLayerNames = nullptr, .enabledExtensionCount = (uint32_t)vector_size(v_active_instance_extensions), .enabledExtensionNames = v_active_instance_extensions, }; XrInstance instance = nullptr; if (xrCreateInstance(&ci, &instance) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR instance"); return false; } this->xr.instance = instance; res = xrGetInstanceProcAddr(this->xr.instance, "xrCreateHandTrackerEXT", (PFN_xrVoidFunction *)&this->xr.CreateHandTrackerEXT); if (res != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get proc addr xrCreateHandTrackerEXT"); return false; } res = xrGetInstanceProcAddr(this->xr.instance, "xrDestroyHandTrackerEXT", (PFN_xrVoidFunction *)&this->xr.DestroyHandTrackerEXT); if (res != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get proc addr xrDestroyHandTrackerEXT"); return false; } res = xrGetInstanceProcAddr(this->xr.instance, "xrLocateHandJointsEXT", (PFN_xrVoidFunction *)&this->xr.LocateHandJointsEXT); if (res != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get proc addr xrLocateHandJointsEXT"); return false; } } { XrSystemGetInfo gi = { .type = XR_TYPE_SYSTEM_GET_INFO, .next = nullptr, }; XrFormFactor const factors[2] = { XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY, XR_FORM_FACTOR_HANDHELD_DISPLAY, }; for (size_t i = 0; i < ARRAY_SZ(factors); i++) { auto factor = factors[i]; gi.formFactor = factor; XrSystemId system_id = 0; if (xrGetSystem(this->xr.instance, &gi, &system_id) == XR_SUCCESS) { this->xr.system_id = system_id; break; } } if (!this->xr.system_id) { wlr_log(WLR_ERROR, "Failed to find valid form factor"); return false; } } { XrSystemProperties system_props = { .type = XR_TYPE_SYSTEM_PROPERTIES, .next = &this->xr.hand_tracking_system_properties, }; res = xrGetSystemProperties( this->xr.instance, this->xr.system_id, &system_props); if (res != XR_SUCCESS) { wlr_log(WLR_ERROR, "xrGetSystemProperties failed: %d", res); return false; } } XrGraphicsRequirementsOpenGLESKHR reqs = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR, .next = nullptr, }; PFN_xrGetOpenGLESGraphicsRequirementsKHR xrGetOpenGLESGraphicsRequirementsKHR = nullptr; xrGetInstanceProcAddr(this->xr.instance, "xrGetOpenGLESGraphicsRequirementsKHR", (PFN_xrVoidFunction *)&xrGetOpenGLESGraphicsRequirementsKHR); if (xrGetOpenGLESGraphicsRequirementsKHR( this->xr.instance, this->xr.system_id, &reqs) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get GLES graphics requirements"); return false; } wlr_log(WLR_INFO, "OpenGL ES range: %d.%d.%d - %d.%d.%d\n", XR_VERSION_MAJOR(reqs.minApiVersionSupported), XR_VERSION_MINOR(reqs.minApiVersionSupported), XR_VERSION_PATCH(reqs.minApiVersionSupported), XR_VERSION_MAJOR(reqs.maxApiVersionSupported), XR_VERSION_MINOR(reqs.maxApiVersionSupported), XR_VERSION_PATCH(reqs.maxApiVersionSupported)); glEnable(GL_DEBUG_OUTPUT_KHR); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); { XrGraphicsBindingEGLMNDX gbind = { .type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX, .next = nullptr, .getProcAddress = eglGetProcAddress, .display = this->wayland.egl_display, .config = this->wayland.egl_config, .context = this->wayland.egl_context, }; XrSessionCreateInfo const ci = { .type = XR_TYPE_SESSION_CREATE_INFO, .next = &gbind, .createFlags = 0, .systemId = this->xr.system_id, }; if (xrCreateSession(this->xr.instance, &ci, &this->xr.session) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR session"); return false; } } // Swapchain time! XrViewConfigurationType *a_view_config_types; uint32_t view_config_types_count = 0; { if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, 0, &view_config_types_count, nullptr) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get amount of OpenXR view configurations"); return false; } a_view_config_types = malloc(sizeof(*a_view_config_types) * view_config_types_count); for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { this->xr.a_view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; this->xr.a_view_configuration_views[i].next = NULL; } if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, view_config_types_count, &view_config_types_count, a_view_config_types) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to enumerate OpenXR view configurations"); return false; } } { bool found = false; for (size_t i = 0; i < view_config_types_count; i++) { if (a_view_config_types[i] == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) { found = true; break; } } if (!found) { wlr_log(WLR_ERROR, "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO not present"); return false; } } { if (xrEnumerateViewConfigurationViews(this->xr.instance, this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &this->xr.view_configuration_views_count, nullptr) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get amount of OpenXR view configuration views"); return false; } this->xr.a_view_configuration_views = malloc(sizeof(*this->xr.a_view_configuration_views) * this->xr.view_configuration_views_count); for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { this->xr.a_view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; this->xr.a_view_configuration_views[i].next = NULL; } if (xrEnumerateViewConfigurationViews(this->xr.instance, this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, this->xr.view_configuration_views_count, &this->xr.view_configuration_views_count, this->xr.a_view_configuration_views) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to enumerate OpenXR view configuration views"); return false; } } int64_t *a_swapchain_formats; uint32_t a_swapchain_formats_count = 0; { if (xrEnumerateSwapchainFormats( this->xr.session, 0, &a_swapchain_formats_count, nullptr) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get amount of OpenXR swapchain formats"); return false; } a_swapchain_formats = malloc(sizeof(*a_swapchain_formats) * a_swapchain_formats_count); if (xrEnumerateSwapchainFormats(this->xr.session, a_swapchain_formats_count, &a_swapchain_formats_count, a_swapchain_formats) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to enumerate OpenXR swapchain formats"); return false; } } { bool found = false; for (size_t i = 0; i < a_swapchain_formats_count; i++) { if (a_swapchain_formats[i] == GL_DEPTH_COMPONENT16) { found = true; break; } } if (!found) { wlr_log( WLR_ERROR, "Failed to find satisfying depth swapchain format"); return false; } } auto const swapchain_format_depth = GL_DEPTH_COMPONENT16; auto const swapchain_format_color = GL_SRGB8_ALPHA8; { bool found = false; for (size_t i = 0; i < a_swapchain_formats_count; i++) { if (a_swapchain_formats[i] == GL_SRGB8_ALPHA8) { found = true; break; } } if (!found) { wlr_log( WLR_ERROR, "Failed to find satisfying color swapchain format"); return false; } } uint32_t const view_count = this->xr.view_configuration_views_count; uint32_t const eye_w = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; uint32_t const eye_h = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; uint32_t const buf_w = eye_w * view_count; if (!this->xr.swapchains.v_color) { this->xr.swapchains.v_color = vector_create(); } if (!this->xr.swapchains.v_depth) { this->xr.swapchains.v_depth = vector_create(); } vector_add(&this->xr.swapchains.v_color, (LunarWM_SwapchainInfo) {}); vector_add(&this->xr.swapchains.v_depth, (LunarWM_SwapchainInfo) {}); { auto *color_sc = &this->xr.swapchains.v_color[0]; auto *depth_sc = &this->xr.swapchains.v_depth[0]; auto *vcv = &this->xr.a_view_configuration_views[0]; { // Create color swapchain XrSwapchainCreateInfo const ci = { .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, .next = nullptr, .createFlags = 0, .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, .format = swapchain_format_color, .sampleCount = vcv->recommendedSwapchainSampleCount, .width = buf_w, .height = eye_h, .faceCount = 1, .arraySize = 1, .mipCount = 1, }; color_sc->swapchain_format = ci.format; if (xrCreateSwapchain(this->xr.session, &ci, &color_sc->swapchain) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR color swapchain"); return false; } } { // Create depth swapchain XrSwapchainCreateInfo const ci = { .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, .next = nullptr, .createFlags = 0, .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, .format = swapchain_format_depth, .sampleCount = vcv->recommendedSwapchainSampleCount, .width = buf_w, .height = eye_h, .faceCount = 1, .arraySize = 1, .mipCount = 1, }; depth_sc->swapchain_format = ci.format; if (xrCreateSwapchain(this->xr.session, &ci, &depth_sc->swapchain) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR depth swapchain"); return false; } } // Enumerate swapchain images { // Color this->xr.swapchain_images[0].handle = color_sc->swapchain; if (xrEnumerateSwapchainImages(color_sc->swapchain, 0, &this->xr.swapchain_images[0].a_imgs_count, nullptr) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get Color Swapchain Images count."); return false; } this->xr.swapchain_images[0].a_imgs = malloc(sizeof(*this->xr.swapchain_images[0].a_imgs) * this->xr.swapchain_images[0].a_imgs_count); for (uint32_t i = 0; i < this->xr.swapchain_images[0].a_imgs_count; ++i) { this->xr.swapchain_images[0].a_imgs[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; this->xr.swapchain_images[0].a_imgs[i].next = NULL; } if (xrEnumerateSwapchainImages(color_sc->swapchain, this->xr.swapchain_images[0].a_imgs_count, &this->xr.swapchain_images[0].a_imgs_count, (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[0] .a_imgs) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to enumerate color swapchain images."); return false; } } { // Depth this->xr.swapchain_images[1].handle = depth_sc->swapchain; if (xrEnumerateSwapchainImages(depth_sc->swapchain, 0, &this->xr.swapchain_images[1].a_imgs_count, nullptr) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get depth Swapchain Images count."); return false; } this->xr.swapchain_images[1].a_imgs = malloc(sizeof(*this->xr.swapchain_images[1].a_imgs) * this->xr.swapchain_images[1].a_imgs_count); for (uint32_t i = 0; i < this->xr.swapchain_images[1].a_imgs_count; ++i) { this->xr.swapchain_images[1].a_imgs[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; this->xr.swapchain_images[1].a_imgs[i].next = NULL; } if (xrEnumerateSwapchainImages(depth_sc->swapchain, this->xr.swapchain_images[1].a_imgs_count, &this->xr.swapchain_images[1].a_imgs_count, (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[1] .a_imgs) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to enumerate depth swapchain images."); return false; } } // Get image views for (uint32_t j = 0; j < this->xr.swapchain_images[0].a_imgs_count; j++) { GLuint framebuffer = 0; glGenFramebuffers(1, &framebuffer); GLenum const attachment = GL_COLOR_ATTACHMENT0; glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, GL_TEXTURE_2D, get_swapchain_image(this, 0, j), 0); GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); if (result != GL_FRAMEBUFFER_COMPLETE) { wlr_log(WLR_ERROR, "Failed to create color framebuffer"); return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); if (!color_sc->v_image_views) { color_sc->v_image_views = vector_create(); } vector_add(&color_sc->v_image_views, framebuffer); } for (uint32_t j = 0; j < this->xr.swapchain_images[1].a_imgs_count; j++) { GLuint framebuffer = 0; glGenFramebuffers(1, &framebuffer); GLenum const attachment = GL_DEPTH_ATTACHMENT; glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, GL_TEXTURE_2D, get_swapchain_image(this, 1, j), 0); GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); if (result != GL_FRAMEBUFFER_COMPLETE) { wlr_log(WLR_ERROR, "Failed to create depth framebuffer"); return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); if (!depth_sc->v_image_views) { depth_sc->v_image_views = vector_create(); } vector_add(&depth_sc->v_image_views, framebuffer); } } assert(this->xr.instance); assert(this->xr.system_id); XrEnvironmentBlendMode *a_environment_blend_modes; uint32_t a_environment_blend_modes_count = 0; { // Get available blend modes if (xrEnumerateEnvironmentBlendModes(this->xr.instance, this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &a_environment_blend_modes_count, nullptr) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get OpenXR environment blend mode count"); return false; } a_environment_blend_modes = malloc(sizeof(*a_environment_blend_modes) * a_environment_blend_modes_count); if (xrEnumerateEnvironmentBlendModes(this->xr.instance, this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, a_environment_blend_modes_count, &a_environment_blend_modes_count, a_environment_blend_modes) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get XR environment blend modes"); return false; } } XrEnvironmentBlendMode const requested_environment_blend_modes[] = { XR_ENVIRONMENT_BLEND_MODE_OPAQUE, XR_ENVIRONMENT_BLEND_MODE_ADDITIVE, XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM, }; for (size_t i = 0; i < ARRAY_SZ(requested_environment_blend_modes); i++) { this->xr.environment_blend_mode = requested_environment_blend_modes[i]; bool found = false; for (size_t j = 0; j < a_environment_blend_modes_count; j++) { if (requested_environment_blend_modes[i] == a_environment_blend_modes[j]) { found = true; break; } } if (found) { break; } } if (this->xr.environment_blend_mode == XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) { wlr_log(WLR_INFO, "Failed to find a compatible blend mode. Defaulting to " "XR_ENVIRONMENT_BLEND_MODE_OPAQUE."); this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; } { // Reference space XrReferenceSpaceCreateInfo const ci = { .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, .next = nullptr, .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL, .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, .position = { 0.0f, 0.0f, 0.0f } }, }; if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.local_space) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); return false; } } { // View reference space XrReferenceSpaceCreateInfo const ci = { .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, .next = nullptr, .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW, .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, .position = { 0.0f, 0.0f, 0.0f } }, }; if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.view_space) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); return false; } } { // Create hand trackers for (size_t i = 0; i < 2; i++) { auto *hand = &this->xr.hands[i]; XrHandTrackerCreateInfoEXT const ci = { .type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, .hand = i == 0 ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT, .handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT, }; res = this->xr.CreateHandTrackerEXT( this->xr.session, &ci, &hand->hand_tracker); if (res != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create hand tracker: %d", res); return false; } } } free(extension_properties); free(a_view_config_types); free(a_swapchain_formats); free(a_environment_blend_modes); vector_free(v_active_instance_extensions); return true; } static void sync_config(LunarWM *this) { if (this->cman->cfg.cubemap) { Skybox_init(&this->renderer.skybox, this->cman->cfg.cubemap); } else { Skybox_destroy(&this->renderer.skybox); } if (IsTextureValid(this->renderer.hud_rt.texture)) { UnloadTexture(this->renderer.hud_rt.texture); this->renderer.hud_rt.texture.id = 0; this->renderer.hud_rt.texture.width = 0; this->renderer.hud_rt.texture.height = 0; } if (this->wayland.custom_out_virtual) { vo_destroy(this->wayland.custom_out_virtual); this->wayland.custom_out_virtual = NULL; } if (this->wayland.custom_out_hud) { vo_destroy(this->wayland.custom_out_hud); this->wayland.custom_out_hud = NULL; } int vw = (int)this->cman->cfg.displays.virtual.resolution.x; int vh = (int)this->cman->cfg.displays.virtual.resolution.y; int hud = this->cman->cfg.displays.hud.size; this->wayland.custom_out_virtual = vo_create(this->wayland.display, "Virtual", "Virtual output", vw, vh, 60000, 1, "LunarWM", "Virtual"); this->wayland.custom_out_hud = vo_create(this->wayland.display, "HUD", "HUD output", hud, hud, 60000, 1, "LunarWM", "HUD"); } extern char **environ; static int l_exec(lua_State *L) { char const *cmd = luaL_checkstring(L, 1); pid_t pid; char *argv[] = { (char *)"sh", (char *)"-c", (char *)cmd, NULL }; int rc = posix_spawnp(&pid, "sh", NULL, NULL, argv, environ); if (rc != 0) { lua_pushnil(L); lua_pushfstring(L, "posix_spawnp failed: %s", strerror(rc)); return 2; } lua_pushinteger(L, (lua_Integer)pid); return 1; } static int l_cycle_next(lua_State *L) { if (vector_size(g_wm.wayland.v_toplevels) == 0) { lua_pushnil(L); return 1; } g_wm.wayland.current_focus++; if (g_wm.wayland.current_focus >= vector_size(g_wm.wayland.v_toplevels)) { g_wm.wayland.current_focus = 0; } LunarWM_Toplevel *tl = g_wm.wayland.v_toplevels[g_wm.wayland.current_focus]; LunarWM_Toplevel_focus(tl); lua_pushnil(L); return 1; } static int l_recenter(lua_State *L) { g_wm.renderer.center = g_wm.renderer.camera.position; lua_pushnil(L); return 1; } static int l_reload_config(lua_State *L) { ConfigManager *cm = g_wm.cman; if (!cm) { lua_pushnil(L); return 1; } config_manager_reload(cm); sync_config(&g_wm); lua_pushnil(L); return 1; } static int l_quit_compositor(lua_State *L) { LunarWM_terminate(&g_wm); lua_pushnil(L); return 1; } bool LunarWM_init(LunarWM *this) { { // Init defaults memset(this, 0, sizeof(*this)); this->xr.session = XR_NULL_HANDLE; this->xr.session_state = XR_SESSION_STATE_UNKNOWN; this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM; this->xr.local_space = this->xr.view_space = XR_NULL_HANDLE; this->xr.hand_tracking_system_properties.type = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT; this->renderer.camera.position = (Vector3) { 0, 0, 0 }; this->renderer.camera.target = (Vector3) { 0, 0, 1 }; this->renderer.camera.up = (Vector3) { 0, 1, 0 }; this->renderer.camera.fovy = 45; this->renderer.camera.projection = CAMERA_PERSPECTIVE; this->renderer.center = (Vector3) { 0, 0, 0 }; } this->cman = config_manager_create(get_config_path()); assert(this->cman); { lua_State *L = this->cman->L; lua_newtable(L); lua_pushcfunction(L, l_quit_compositor); lua_setfield(L, -2, "quit_compositor"); lua_pushcfunction(L, l_reload_config); lua_setfield(L, -2, "reload_config"); lua_pushcfunction(L, l_recenter); lua_setfield(L, -2, "recenter"); lua_pushcfunction(L, l_cycle_next); lua_setfield(L, -2, "cycle_next"); lua_pushcfunction(L, l_exec); lua_setfield(L, -2, "exec"); lua_setglobal(L, "lunar"); config_manager_reload(this->cman); this->renderer.center = this->cman->cfg.space.initial_center; } if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) { wlr_log(WLR_ERROR, "This compositor can only be ran in DRM mode."); return false; } if (!init_wayland(this)) { wlr_log(WLR_ERROR, "Failed to initialize wlroots"); return false; } auto *draw = eglGetCurrentSurface(EGL_DRAW); auto *read = eglGetCurrentSurface(EGL_READ); if (eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->wayland.egl_context) == EGL_FALSE) { wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); return false; } if (!init_xr(this)) { wlr_log(WLR_ERROR, "Failed to initialize OpenXR"); return false; } wlr_log(WLR_INFO, "OpenGL ES version: %s", glGetString(GL_VERSION)); InitWindow(0, 0, ""); if (eglMakeCurrent( this->wayland.egl_display, draw, read, this->wayland.egl_context) == EGL_FALSE) { wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); return false; } sync_config(this); this->initialized = true; return true; } static void free_swapchain_info(LunarWM_SwapchainInfo *sc) { if (!sc) return; if (sc->v_image_views) { for (size_t i = 0; i < vector_size(sc->v_image_views); ++i) { GLuint fb = sc->v_image_views[i]; if (fb) glDeleteFramebuffers(1, &fb); } vector_free(sc->v_image_views); sc->v_image_views = NULL; } if (sc->swapchain) { xrDestroySwapchain(sc->swapchain); sc->swapchain = XR_NULL_HANDLE; } } static void cleanup_xr(LunarWM *this) { for (int i = 0; i < 2; ++i) { if (this->xr.swapchain_images[i].a_imgs) { free(this->xr.swapchain_images[i].a_imgs); this->xr.swapchain_images[i].a_imgs = NULL; this->xr.swapchain_images[i].a_imgs_count = 0; } } if (this->xr.swapchains.v_color) { for (size_t i = 0; i < vector_size(this->xr.swapchains.v_color); ++i) free_swapchain_info(&this->xr.swapchains.v_color[i]); vector_free(this->xr.swapchains.v_color); this->xr.swapchains.v_color = NULL; } if (this->xr.swapchains.v_depth) { for (size_t i = 0; i < vector_size(this->xr.swapchains.v_depth); ++i) free_swapchain_info(&this->xr.swapchains.v_depth[i]); vector_free(this->xr.swapchains.v_depth); this->xr.swapchains.v_depth = NULL; } if (this->renderer.fbo) { glDeleteFramebuffers(1, &this->renderer.fbo); this->renderer.fbo = 0; } this->renderer.tmp_rt = (RenderTexture2D) { 0 }; if (this->xr.view_space) xrDestroySpace(this->xr.view_space), this->xr.view_space = XR_NULL_HANDLE; if (this->xr.local_space) xrDestroySpace(this->xr.local_space), this->xr.local_space = XR_NULL_HANDLE; for (size_t i = 0; i < 2; ++i) { if (this->xr.hands[i].hand_tracker && this->xr.DestroyHandTrackerEXT) this->xr.DestroyHandTrackerEXT(this->xr.hands[i].hand_tracker); this->xr.hands[i].hand_tracker = XR_NULL_HANDLE; } if (this->xr.a_view_configuration_views) { free(this->xr.a_view_configuration_views); this->xr.a_view_configuration_views = NULL; this->xr.view_configuration_views_count = 0; } if (this->xr.session != XR_NULL_HANDLE) { xrDestroySession(this->xr.session); this->xr.session = XR_NULL_HANDLE; this->xr.session_running = false; } if (this->xr.instance) xrDestroyInstance(this->xr.instance), this->xr.instance = XR_NULL_HANDLE; } static void cleanup_wayland(LunarWM *this) { assert(this); if (this->wayland.custom_out_virtual) { vo_destroy(this->wayland.custom_out_virtual); this->wayland.custom_out_virtual = NULL; } if (this->wayland.custom_out_hud) { vo_destroy(this->wayland.custom_out_hud); this->wayland.custom_out_hud = NULL; } if (this->wayland.xwayland) { if (this->wayland.xwayland_new_surface.link.prev || this->wayland.xwayland_new_surface.link.next) { wl_list_remove(&this->wayland.xwayland_new_surface.link); this->wayland.xwayland_new_surface.notify = NULL; } if (this->wayland.xwayland_ready.link.prev || this->wayland.xwayland_ready.link.next) { wl_list_remove(&this->wayland.xwayland_ready.link); this->wayland.xwayland_ready.notify = NULL; } wlr_xwayland_destroy(this->wayland.xwayland); this->wayland.xwayland = NULL; } if (this->wayland.new_xdg_toplevel_listener.link.prev || this->wayland.new_xdg_toplevel_listener.link.next) { wl_list_remove(&this->wayland.new_xdg_toplevel_listener.link); this->wayland.new_xdg_toplevel_listener.notify = NULL; } if (this->wayland.new_xdg_popup_listener.link.prev || this->wayland.new_xdg_popup_listener.link.next) { wl_list_remove(&this->wayland.new_xdg_popup_listener.link); this->wayland.new_xdg_popup_listener.notify = NULL; } if (this->wayland.new_input_listener.link.prev || this->wayland.new_input_listener.link.next) { wl_list_remove(&this->wayland.new_input_listener.link); this->wayland.new_input_listener.notify = NULL; } if (this->wayland.v_toplevels) { for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); ++i) { if (this->wayland.v_toplevels[i]) { LunarWM_Toplevel_destroy(this->wayland.v_toplevels[i]); free(this->wayland.v_toplevels[i]); } } vector_free(this->wayland.v_toplevels); this->wayland.v_toplevels = NULL; } if (this->wayland.cursor) { wlr_cursor_destroy(this->wayland.cursor); this->wayland.cursor = NULL; } if (this->wayland.backend) { wlr_backend_destroy(this->wayland.backend); this->wayland.backend = NULL; } if (this->wayland.seat) { wlr_seat_destroy(this->wayland.seat); this->wayland.seat = NULL; } if (this->wayland.allocator) { wlr_allocator_destroy(this->wayland.allocator); this->wayland.allocator = NULL; } if (this->wayland.renderer) { wlr_renderer_destroy(this->wayland.renderer); this->wayland.renderer = NULL; } if (this->wayland.display) { wl_display_destroy(this->wayland.display); this->wayland.display = NULL; } } static void cleanup_lua_cfg(LunarWM *this) { if (this->cman) { config_manager_destroy(this->cman); this->cman = NULL; } } static void cleanup_raylib_egl(LunarWM *this) { if (IsWindowReady()) { CloseWindow(); } } void LunarWM_destroy(LunarWM *this) { if (!this) return; eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->wayland.egl_context); cleanup_xr(this); cleanup_raylib_egl(this); cleanup_wayland(this); cleanup_lua_cfg(this); memset(this, 0, sizeof(*this)); } void LunarWM_terminate(LunarWM *this) { wlr_log(WLR_INFO, "Stopping compositor"); this->running = false; } static void poll_events_xr(LunarWM *this) { XrEventDataBuffer event_data = { .type = XR_TYPE_EVENT_DATA_BUFFER, }; while (xrPollEvent(this->xr.instance, &event_data) == XR_SUCCESS) { switch (event_data.type) { case XR_TYPE_EVENT_DATA_EVENTS_LOST: { auto *el = (XrEventDataEventsLost *)&event_data; wlr_log(WLR_INFO, "OPENXR: Events Lost: %d", el->lostEventCount); break; } case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { auto *ilp = (XrEventDataInstanceLossPending *)&event_data; wlr_log(WLR_INFO, "OPENXR: Instance Loss Pending at: %ld", ilp->lossTime); this->xr.session_running = false; this->running = false; break; } case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { auto *ipc = (XrEventDataInteractionProfileChanged *)&event_data; wlr_log(WLR_INFO, "OPENXR: Interaction Profile changed for Session: " "%p", ipc->session); if (ipc->session != this->xr.session) { wlr_log(WLR_ERROR, "XrEventDataInteractionProfileChanged for " "unknown Session"); break; } break; } case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { auto *scp = (XrEventDataReferenceSpaceChangePending *)&event_data; wlr_log(WLR_INFO, "OPENXR: Reference Space Change pending for " "Session: %p", scp->session); if (scp->session != this->xr.session) { wlr_log(WLR_ERROR, "XrEventDataReferenceSpaceChangePending for " "unknown " "Session"); break; } break; } case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { auto *sc = (XrEventDataSessionStateChanged *)&event_data; if (sc->session != this->xr.session) { wlr_log(WLR_ERROR, "XrEventDataSessionStateChanged for unknown " "Session"); break; } if (sc->state == XR_SESSION_STATE_READY) { XrSessionBeginInfo bi = { .type = XR_TYPE_SESSION_BEGIN_INFO }; bi.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; bi.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; if (xrBeginSession(this->xr.session, &bi) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to begin session"); } else { this->xr.session_running = true; } } if (sc->state == XR_SESSION_STATE_STOPPING) { if (xrEndSession(this->xr.session) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to end session"); } this->xr.session_running = false; } if (sc->state == XR_SESSION_STATE_EXITING) { this->xr.session_running = false; this->running = false; } if (sc->state == XR_SESSION_STATE_LOSS_PENDING) { this->xr.session_running = false; this->running = false; } this->xr.session_state = sc->state; break; } default: { break; } } } } bool acquire_wait(XrSwapchain sc, uint32_t *idx) { XrSwapchainImageAcquireInfo ai = { .type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, .next = NULL }; if (xrAcquireSwapchainImage(sc, &ai, idx) != XR_SUCCESS) { return false; } XrSwapchainImageWaitInfo wi = { .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, .next = NULL, .timeout = XR_INFINITE_DURATION }; return xrWaitSwapchainImage(sc, &wi) == XR_SUCCESS; } static inline Matrix xr_matrix(XrPosef const pose) { Matrix const translation = MatrixTranslate(pose.position.x, pose.position.y, pose.position.z); Matrix const rotation = QuaternionToMatrix((Quaternion) { pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w, }); return MatrixMultiply(rotation, translation); } static inline Matrix xr_projection_matrix(XrFovf const fov) { static_assert(RL_CULL_DISTANCE_FAR > RL_CULL_DISTANCE_NEAR); Matrix matrix = {}; auto const near = (float)RL_CULL_DISTANCE_NEAR; auto const far = (float)RL_CULL_DISTANCE_FAR; float const tan_angle_left = tanf(fov.angleLeft); float const tan_angle_right = tanf(fov.angleRight); float const tan_angle_down = tanf(fov.angleDown); float const tan_angle_up = tanf(fov.angleUp); float const tan_angle_width = tan_angle_right - tan_angle_left; float const tan_angle_height = tan_angle_up - tan_angle_down; matrix.m0 = 2 / tan_angle_width; matrix.m4 = 0; matrix.m8 = (tan_angle_right + tan_angle_left) / tan_angle_width; matrix.m12 = 0; matrix.m1 = 0; matrix.m5 = 2 / tan_angle_height; matrix.m9 = (tan_angle_up + tan_angle_down) / tan_angle_height; matrix.m13 = 0; matrix.m2 = 0; matrix.m6 = 0; matrix.m10 = -(far + near) / (far - near); matrix.m14 = -(far * (near + near)) / (far - near); matrix.m3 = 0; matrix.m7 = 0; matrix.m11 = -1; matrix.m15 = 0; return matrix; } static void DrawBillboardNoShear( Camera3D const cam, Texture2D tex, Vector3 pos, float scale, Color tint) { Rectangle const src = { 0, 0, tex.width, tex.height }; Vector2 const size = { scale * fabsf(src.width / src.height), -scale }; Vector2 const origin = { size.x * 0.5f, size.y * 0.5f }; DrawBillboardPro(cam, tex, src, pos, cam.up, size, origin, 0.0f, tint); } // pass yFlip from tl->attribs.invert_y static void DrawTexture3D( Texture2D tex, Vector3 position, Vector3 target, float scale, bool y_flip) { if (!tex.id) return; Vector3 fwd = Vector3Normalize(Vector3Subtract(target, position)); Vector3 up_ref = (fabsf(fwd.y) > 0.99f) ? (Vector3) { 0, 0, 1 } : (Vector3) { 0, 1, 0 }; Vector3 right = Vector3Normalize(Vector3CrossProduct(fwd, up_ref)); Vector3 up = Vector3CrossProduct(right, fwd); float half_w = 0.5f * (float)tex.width * scale; float half_h = 0.5f * (float)tex.height * scale; Vector3 tl = Vector3Add(Vector3Subtract(position, Vector3Scale(right, half_w)), Vector3Scale(up, half_h)); Vector3 tr = Vector3Add(Vector3Add(position, Vector3Scale(right, half_w)), Vector3Scale(up, half_h)); Vector3 br = Vector3Subtract(Vector3Add(position, Vector3Scale(right, half_w)), Vector3Scale(up, half_h)); Vector3 bl = Vector3Subtract( Vector3Subtract(position, Vector3Scale(right, half_w)), Vector3Scale(up, half_h)); float vt = y_flip ? 1.0f : 0.0f; float vb = y_flip ? 0.0f : 1.0f; rlDisableBackfaceCulling(); // ensure visible regardless of winding rlSetTexture(tex.id); rlBegin(RL_QUADS); rlNormal3f(fwd.x, fwd.y, fwd.z); // TL, TR, BR, BL with canonical UVs rlTexCoord2f(1.0f, vt); rlVertex3f(tl.x, tl.y, tl.z); rlTexCoord2f(0.0f, vt); rlVertex3f(tr.x, tr.y, tr.z); rlTexCoord2f(0.0f, vb); rlVertex3f(br.x, br.y, br.z); rlTexCoord2f(1.0f, vb); rlVertex3f(bl.x, bl.y, bl.z); rlEnd(); rlSetTexture(0); rlEnableBackfaceCulling(); } void render_hud(LunarWM *this, float /*dt*/, int hud_size) { ClearBackground((Color) { 0, 0, 0, 0 }); float const text_size = this->cman->cfg.displays.hud.font_size; char const *txt = TextFormat("WAYLAND_DISPLAY=%s", getenv("WAYLAND_DISPLAY")); auto txt_w = MeasureText(txt, 24); DrawText( txt, hud_size / 2 - txt_w / 2, hud_size - text_size, text_size, WHITE); txt = TextFormat("DISPLAY=%s", getenv("DISPLAY")); txt_w = MeasureText(txt, text_size); DrawText(txt, hud_size / 2 - txt_w / 2, hud_size - text_size * 2, text_size, WHITE); { time_t t = time(NULL); struct tm *tm_info = localtime(&t); int hours = tm_info->tm_hour; int minutes = tm_info->tm_min; txt = TextFormat("%02d:%02d", hours, minutes); txt_w = MeasureText(txt, 32); DrawText(txt, hud_size / 2 - txt_w / 2, 0, text_size, WHITE); } } void render_3d(LunarWM *this, float /*dt*/) { Skybox_draw(this->renderer.skybox, this->renderer.center); // rlDisableBackfaceCulling(); for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { auto *tl = this->wayland.v_toplevels[i]; if (!tl || !tl->surface) continue; // if (!LunarWM_Toplevel_update(tl)) // continue; DrawTexture3D(tl->rl_texture, Vector3Add(this->renderer.center, (Vector3) { 0, 0, this->cman->cfg.space.radius - 0.01 * (float)i }), this->renderer.center, this->cman->cfg.space.window_scale, false); } // rlEnableBackfaceCulling(); for (int h = 0; h < 2; ++h) { auto *hand_info = &this->xr.hands[h]; for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) { auto const *jl = &hand_info->joint_locations[k]; Vector3 const pos = { jl->pose.position.x, jl->pose.position.y, jl->pose.position.z, }; DrawSphere(pos, jl->radius, (Color) { 255, 0, 0, 255 }); } } if (IsTextureValid(this->renderer.hud_rt.texture)) { rlDrawRenderBatchActive(); rlDisableDepthTest(); Vector3 camPos = this->renderer.camera.position; Vector3 camDir = Vector3Normalize( Vector3Subtract(this->renderer.camera.target, camPos)); Vector3 up = this->renderer.camera.up; Vector3 right = Vector3Normalize(Vector3CrossProduct(camDir, up)); up = Vector3CrossProduct(right, camDir); Vector3 center = Vector3Add(camPos, Vector3Scale(camDir, 0.6f)); float heightMeters = 0.10f; DrawBillboardNoShear(this->renderer.camera, this->renderer.hud_rt.texture, center, heightMeters * 5, WHITE); rlDrawRenderBatchActive(); rlEnableDepthTest(); } } static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) { auto const view_count = (uint32_t)this->xr.view_configuration_views_count; assert(view_count == 2); XrView views[2] = {}; for (int i = 0; i < ARRAY_SZ(views); i++) { views[i] = (XrView) { .type = XR_TYPE_VIEW, }; } XrViewLocateInfo locInfo = { .type = XR_TYPE_VIEW_LOCATE_INFO, .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, .displayTime = info->predicted_display_time, .space = this->xr.local_space, }; XrViewState viewState = { .type = XR_TYPE_VIEW_STATE }; uint32_t located = 0; if (xrLocateViews( this->xr.session, &locInfo, &viewState, view_count, &located, views) != XR_SUCCESS || located != view_count) { wlr_log(WLR_ERROR, "Failed to locate views"); return false; } // acquire swapchain images auto *color_sc = &this->xr.swapchains.v_color[0]; auto *depth_sc = &this->xr.swapchains.v_depth[0]; uint32_t col_idx = 0; uint32_t dep_idx = 0; if (!acquire_wait(color_sc->swapchain, &col_idx) || !acquire_wait(depth_sc->swapchain, &dep_idx)) { wlr_log(WLR_ERROR, "Swap-chain acquire failed"); return false; } GLuint color_tex = get_swapchain_image(this, 0, col_idx); GLuint depth_tex = get_swapchain_image(this, 1, dep_idx); // build FBO if (this->renderer.fbo == 0u) { glGenFramebuffers(1, &this->renderer.fbo); } glBindFramebuffer(GL_FRAMEBUFFER, this->renderer.fbo); rlFramebufferAttach(this->renderer.fbo, color_tex, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); rlFramebufferAttach(this->renderer.fbo, depth_tex, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_TEXTURE2D, 0); assert(rlFramebufferComplete(this->renderer.fbo)); uint32_t const eye_w = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; uint32_t const eye_h = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; this->renderer.tmp_rt = (RenderTexture2D) { .id = this->renderer.fbo, .texture = { color_tex, (int)eye_w * view_count, (int)eye_h, 1, -1 }, .depth = { depth_tex, (int)eye_w * view_count, (int)eye_h, 1, -1 }, }; // head-space view matrix (matches rlOpenXR) XrSpaceLocation headLoc = { .type = XR_TYPE_SPACE_LOCATION }; xrLocateSpace(this->xr.view_space, this->xr.local_space, info->predicted_display_time, &headLoc); auto const head_view = MatrixInvert(xr_matrix(headLoc.pose)); // per-eye projection + view-offset Matrix const view_off_l = MatrixMultiply(xr_matrix(views[0].pose), head_view); Matrix const view_off_r = MatrixMultiply(xr_matrix(views[1].pose), head_view); Matrix const proj_r = xr_projection_matrix(views[0].fov); Matrix const proj_l = xr_projection_matrix(views[1].fov); int const hud_size = this->cman->cfg.displays.hud.size; if (!IsTextureValid(this->renderer.hud_rt.texture)) { this->renderer.hud_rt = LoadRenderTexture(hud_size, hud_size); } if (IsTextureValid(this->renderer.hud_rt.texture)) { BeginTextureMode(this->renderer.hud_rt); { render_hud(this, dt, hud_size); } EndTextureMode(); } // draw if (!IsTextureValid(this->renderer.main_rt.texture)) { this->renderer.main_rt = LoadRenderTexture(eye_w * view_count, eye_h); } BeginTextureMode(this->renderer.main_rt); rlEnableStereoRender(); rlSetMatrixProjectionStereo(proj_r, proj_l); rlSetMatrixViewOffsetStereo(view_off_r, view_off_l); glViewport(0, 0, (GLsizei)eye_w * view_count, (GLsizei)eye_h); rlClearColor(0, 0, 10, 255); rlClearScreenBuffers(); for (int i = 0; i < 1; i++) { XrTime const time = info->predicted_display_time; XrSpaceLocation view_location = { .type = XR_TYPE_SPACE_LOCATION }; XrResult const result = xrLocateSpace( this->xr.view_space, this->xr.local_space, time, &view_location); if (result != XR_SUCCESS) { break; } if ((view_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0u) { auto const pos = view_location.pose.position; this->renderer.camera.position = (Vector3) { pos.x, pos.y, pos.z }; } if ((view_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0u) { auto const rot = view_location.pose.orientation; auto const forward = Vector3RotateByQuaternion((Vector3) { 0, 0, -1 }, (Quaternion) { rot.x, rot.y, rot.z, rot.w }); auto const up = Vector3RotateByQuaternion((Vector3) { 0, 1, 0 }, (Quaternion) { rot.x, rot.y, rot.z, rot.w }); this->renderer.camera.target = Vector3Add(this->renderer.camera.position, forward); this->renderer.camera.up = up; } } BeginMode3D(this->renderer.camera); { render_3d(this, dt); } EndMode3D(); rlDisableStereoRender(); EndTextureMode(); if (!IsShaderValid(this->renderer.linear_srgb)) { static char const linear_srgb[] = { #embed "../assets/linear_srgb.fs" , 0 }; this->renderer.linear_srgb = LoadShaderFromMemory(NULL, linear_srgb); } BeginTextureMode(this->renderer.tmp_rt); rlDisableColorBlend(); ClearBackground(BLACK); BeginShaderMode(this->renderer.linear_srgb); DrawTexturePro(this->renderer.main_rt.texture, (Rectangle) { 0, 0, this->renderer.main_rt.texture.width, -this->renderer.main_rt.texture.height, }, (Rectangle) { 0, 0, this->renderer.tmp_rt.texture.width, this->renderer.tmp_rt.texture.height, }, (Vector2) { 0, 0 }, 0, WHITE); EndShaderMode(); EndTextureMode(); rlEnableColorBlend(); // release swapchain images XrSwapchainImageReleaseInfo const ri = { .type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; xrReleaseSwapchainImage(color_sc->swapchain, &ri); xrReleaseSwapchainImage(depth_sc->swapchain, &ri); // fill projection layer info->layer_projection_views_count = view_count; for (uint32_t i = 0; i < view_count; ++i) { int32_t const xOff = i * eye_w; auto *pv = &info->layer_projection_views[i]; pv->pose = views[i].pose; pv->fov = views[i].fov; pv->subImage.swapchain = color_sc->swapchain; pv->subImage.imageRect.offset = (XrOffset2Di) { .x = xOff, .y = 0 }; pv->subImage.imageRect.extent = (XrExtent2Di) { .width = eye_w, .height = eye_h }; pv->subImage.imageArrayIndex = 0; } info->layer_projection = (XrCompositionLayerProjection) { .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, .layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, .space = this->xr.local_space, .viewCount = view_count, .views = info->layer_projection_views, }; info->layers_count = 0; info->layers[info->layers_count++] = (XrCompositionLayerBaseHeader *)&info->layer_projection; return true; } void LunarWM_run(LunarWM *this) { assert(this); assert(this->initialized); if (!wlr_backend_start(this->wayland.backend)) { wlr_log(WLR_ERROR, "Failed to start backend"); return; } auto const *socket = wl_display_add_socket_auto(this->wayland.display); if (socket == nullptr) { wlr_log(WLR_ERROR, "Failed to add wayland socket to display"); return; } setenv("WAYLAND_DISPLAY", socket, 1); wlr_log(LOG_INFO, "Running on WAYLAND_DISPLAY=%s", socket); this->running = true; struct timespec last, now; clock_gettime(CLOCK_MONOTONIC, &last); while (this->running) { clock_gettime(CLOCK_MONOTONIC, &now); float dt = (now.tv_sec - last.tv_sec) + (now.tv_nsec - last.tv_nsec) / 1000000000.0f; last = now; for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { LunarWM_Toplevel *tl = this->wayland.v_toplevels[i]; if (tl->surface) { wlr_surface_send_frame_done(tl->surface, &now); } } wl_display_flush_clients(this->wayland.display); wl_event_loop_dispatch(this->wayland.event_loop, 0); auto *draw = eglGetCurrentSurface(EGL_DRAW); auto *read = eglGetCurrentSurface(EGL_READ); if (eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->wayland.egl_context) == EGL_FALSE) { wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); return; } WindowShouldClose(); BeginDrawing(); poll_events_xr(this); if (!this->xr.session_running) { EndDrawing(); continue; } XrFrameState frame_state = { .type = XR_TYPE_FRAME_STATE, }; XrFrameWaitInfo const frame_wait_info = { .type = XR_TYPE_FRAME_WAIT_INFO, .next = nullptr, }; if (xrWaitFrame(this->xr.session, &frame_wait_info, &frame_state) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to wait for OpenXR frame"); return; } XrFrameBeginInfo const frame_begin_info = { .type = XR_TYPE_FRAME_BEGIN_INFO, .next = nullptr, }; XrResult res = xrBeginFrame(this->xr.session, &frame_begin_info); if (res != XR_FRAME_DISCARDED && res != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to begin the OpenXR Frame: %d", res); return; } if (this->xr.hand_tracking_system_properties.supportsHandTracking) { XrActionStateGetInfo const si = { .type = XR_TYPE_ACTION_STATE_GET_INFO, }; for (size_t i = 0; i < 2; i++) { LunarWM_Hand *hand = &this->xr.hands[i]; bool const unobstructed = true; XrHandJointsMotionRangeInfoEXT mri = { .type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT, }; if (unobstructed) { mri.handJointsMotionRange = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; } else { mri.handJointsMotionRange = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; } XrHandJointsLocateInfoEXT const li = { .type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, .next = &mri, .baseSpace = this->xr.local_space, .time = frame_state.predictedDisplayTime, }; XrHandJointLocationsEXT hji = { .type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT, .jointCount = XR_HAND_JOINT_COUNT_EXT, .jointLocations = hand->joint_locations, }; if (this->xr.LocateHandJointsEXT(hand->hand_tracker, &li, &hji) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to locate hand joints"); return; } } } LunarWM_RenderLayerInfo render_layer_info = { .predicted_display_time = frame_state.predictedDisplayTime, .layer_projection.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, .layer_projection.next = nullptr, }; bool const session_active = (this->xr.session_state == XR_SESSION_STATE_SYNCHRONIZED || this->xr.session_state == XR_SESSION_STATE_VISIBLE || this->xr.session_state == XR_SESSION_STATE_FOCUSED); if (session_active && (frame_state.shouldRender != 0u)) { auto rendered = render_layer(this, &render_layer_info, dt); if (rendered) { render_layer_info.layers[render_layer_info.layers_count] = (XrCompositionLayerBaseHeader *)&render_layer_info .layer_projection; } } XrFrameEndInfo const frame_end_info = { .type = XR_TYPE_FRAME_END_INFO, .displayTime = frame_state.predictedDisplayTime, .environmentBlendMode = this->xr.environment_blend_mode, .layerCount = render_layer_info.layers_count, .layers = (XrCompositionLayerBaseHeader const **)render_layer_info.layers, }; if (xrEndFrame(this->xr.session, &frame_end_info) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to end OpenXR frame"); return; } EndDrawing(); } }