#include "LunarWM_wayland.h" #include "LunarWM_core.h" #include "LunarWM_render.h" #include "common.h" #include "raylib.h" #include "vec.h" #include #include #include #include #include #include #include #include #include #include #include #include static void handle_new_output(struct wl_listener *listener, void *data); static void handle_output_frame(struct wl_listener *listener, void *data); static void handle_output_destroy(struct wl_listener *listener, void *data); static inline SphericalCoord get_forward_spherical_with_nearest( Vector3 fwd, float r) { if (fabs(fwd.y) < 0.2f) { fwd.y = 0; } auto vec = Vector3Scale(Vector3Normalize(fwd), r); return Vector3ToSpherical(vec); } struct ExternalTexturePipeline { bool attempted_init; bool ready; GLuint program; GLint sampler_loc; GLuint vao; GLuint vbo; }; static struct ExternalTexturePipeline g_external_pipeline = { 0 }; static GLuint compile_shader(GLenum type, char const *source) { GLuint shader = glCreateShader(type); if (shader == 0) { TraceLog( LOG_ERROR, "Failed to create shader object for external texture"); return 0; } glShaderSource(shader, 1, &source, NULL); glCompileShader(shader); GLint status = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status != GL_TRUE) { GLint log_len = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); if (log_len > 1) { char *log = malloc((size_t)log_len); if (log) { glGetShaderInfoLog(shader, log_len, NULL, log); TraceLog(LOG_ERROR, "External texture shader compile failed: %s", log); free(log); } } glDeleteShader(shader); return 0; } return shader; } static bool ensure_external_pipeline(void) { if (g_external_pipeline.attempted_init) return g_external_pipeline.ready; g_external_pipeline.attempted_init = true; static char const *vertex_src_300 = "#version 300 es\n" "precision highp float;\n" "layout(location = 0) in vec2 a_pos;\n" "layout(location = 1) in vec2 a_uv;\n" "out vec2 v_uv;\n" "void main() {\n" " v_uv = a_uv;\n" " gl_Position = vec4(a_pos, 0.0, 1.0);\n" "}\n"; static char const *fragment_src_300 = "#version 300 es\n" "#extension GL_OES_EGL_image_external_essl3 : require\n" "precision mediump float;\n" "in vec2 v_uv;\n" "layout(location = 0) out vec4 fragColor;\n" "uniform samplerExternalOES u_texture;\n" "void main() {\n" " fragColor = texture(u_texture, v_uv);\n" "}\n"; static char const *vertex_src_100 = "#version 100\n" "attribute vec2 a_pos;\n" "attribute vec2 a_uv;\n" "varying vec2 v_uv;\n" "void main() {\n" " v_uv = a_uv;\n" " gl_Position = vec4(a_pos, 0.0, 1.0);\n" "}\n"; static char const *fragment_src_100 = "#version 100\n" "#extension GL_OES_EGL_image_external : require\n" "precision mediump float;\n" "varying vec2 v_uv;\n" "uniform samplerExternalOES u_texture;\n" "void main() {\n" " gl_FragColor = texture2D(u_texture, v_uv);\n" "}\n"; struct { char const *vs; char const *fs; } const variants[] = { { vertex_src_300, fragment_src_300 }, { vertex_src_100, fragment_src_100 }, }; GLuint program = 0; GLint sampler_loc = -1; size_t chosen_idx = (size_t)-1; for (size_t i = 0; i < ARRAY_SZ(variants); ++i) { GLuint vert = compile_shader(GL_VERTEX_SHADER, variants[i].vs); if (vert == 0) continue; GLuint frag = compile_shader(GL_FRAGMENT_SHADER, variants[i].fs); if (frag == 0) { glDeleteShader(vert); continue; } GLuint prog = glCreateProgram(); if (prog == 0) { TraceLog(LOG_ERROR, "Failed to create shader program for external texture"); glDeleteShader(vert); glDeleteShader(frag); continue; } glAttachShader(prog, vert); glAttachShader(prog, frag); glBindAttribLocation(prog, 0, "a_pos"); glBindAttribLocation(prog, 1, "a_uv"); glLinkProgram(prog); GLint link_status = GL_FALSE; glGetProgramiv(prog, GL_LINK_STATUS, &link_status); if (link_status != GL_TRUE) { GLint log_len = 0; glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_len); if (log_len > 1) { char *log = malloc((size_t)log_len); if (log) { glGetProgramInfoLog(prog, log_len, NULL, log); TraceLog(LOG_ERROR, "External texture program link failed: %s", log); free(log); } } glDeleteShader(vert); glDeleteShader(frag); glDeleteProgram(prog); continue; } glDetachShader(prog, vert); glDetachShader(prog, frag); glDeleteShader(vert); glDeleteShader(frag); sampler_loc = glGetUniformLocation(prog, "u_texture"); if (sampler_loc < 0) { TraceLog(LOG_ERROR, "External texture program missing u_texture uniform"); glDeleteProgram(prog); continue; } program = prog; chosen_idx = i; break; } if (program == 0) return false; if (chosen_idx == 1) { TraceLog( LOG_DEBUG, "External texture shader fallback to ESSL 100 variant"); } GLuint vao = 0; GLuint vbo = 0; glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); if (vao == 0 || vbo == 0) { TraceLog(LOG_ERROR, "Failed to allocate buffers for external texture"); if (vao) glDeleteVertexArrays(1, &vao); if (vbo) glDeleteBuffers(1, &vbo); glDeleteProgram(program); return false; } GLint prev_vao = 0; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &prev_vao); GLint prev_array_buffer = 0; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prev_array_buffer); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 16, NULL, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void *)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void *)(sizeof(float) * 2)); glBindBuffer(GL_ARRAY_BUFFER, (GLuint)prev_array_buffer); glBindVertexArray((GLuint)prev_vao); g_external_pipeline.program = program; g_external_pipeline.sampler_loc = sampler_loc; g_external_pipeline.vao = vao; g_external_pipeline.vbo = vbo; g_external_pipeline.ready = true; return true; } static bool draw_external_texture( struct wlr_gles2_texture_attribs const *attribs, int tex_width, int tex_height, Rectangle src, Rectangle dst, int target_width, int target_height) { if (!attribs || attribs->target != GL_TEXTURE_EXTERNAL_OES) return false; if (tex_width <= 0 || tex_height <= 0 || target_width <= 0 || target_height <= 0) return false; if (!ensure_external_pipeline()) return false; rlDrawRenderBatchActive(); float x0 = dst.x; float y0 = dst.y; float x1 = dst.x + dst.width; float y1 = dst.y + dst.height; float ndc_x0 = (x0 / (float)target_width) * 2.0f - 1.0f; float ndc_x1 = (x1 / (float)target_width) * 2.0f - 1.0f; float ndc_y0 = 1.0f - (y0 / (float)target_height) * 2.0f; float ndc_y1 = 1.0f - (y1 / (float)target_height) * 2.0f; float inv_tex_w = 1.0f / (float)tex_width; float inv_tex_h = 1.0f / (float)tex_height; float u0 = src.x * inv_tex_w; float v0 = src.y * inv_tex_h; float u1 = (src.x + src.width) * inv_tex_w; float v1 = (src.y + src.height) * inv_tex_h; GLfloat vertices[] = { ndc_x0, ndc_y0, u0, v0, ndc_x1, ndc_y0, u1, v0, ndc_x0, ndc_y1, u0, v1, ndc_x1, ndc_y1, u1, v1, }; GLint prev_program = 0; glGetIntegerv(GL_CURRENT_PROGRAM, &prev_program); GLint prev_active_texture = 0; glGetIntegerv(GL_ACTIVE_TEXTURE, &prev_active_texture); glActiveTexture(GL_TEXTURE0); GLint prev_tex0_external = 0; glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &prev_tex0_external); GLint prev_tex0_2d = 0; glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_tex0_2d); GLint prev_array_buffer = 0; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prev_array_buffer); GLint prev_element_array_buffer = 0; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prev_element_array_buffer); GLint prev_vertex_array = 0; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &prev_vertex_array); glUseProgram(g_external_pipeline.program); glUniform1i(g_external_pipeline.sampler_loc, 0); glBindTexture(GL_TEXTURE_EXTERNAL_OES, attribs->tex); glBindVertexArray(g_external_pipeline.vao); glBindBuffer(GL_ARRAY_BUFFER, g_external_pipeline.vbo); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindBuffer(GL_ARRAY_BUFFER, (GLuint)prev_array_buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)prev_element_array_buffer); glBindVertexArray((GLuint)prev_vertex_array); glBindTexture(GL_TEXTURE_EXTERNAL_OES, (GLuint)prev_tex0_external); glBindTexture(GL_TEXTURE_2D, (GLuint)prev_tex0_2d); glUseProgram((GLuint)prev_program); glActiveTexture((GLenum)prev_active_texture); return true; } static void LunarWM_Toplevel_release_surface_rt(LunarWM_Toplevel *tl) { if (!tl) return; if (IsRenderTextureValid(tl->surface_rt)) { UnloadRenderTexture(tl->surface_rt); } tl->surface_rt = (RenderTexture2D) { 0 }; tl->surface_extents = (struct wlr_box) { 0 }; tl->composed_has_alpha = false; } static void remove_windows_for_tl(LunarWM *wm, LunarWM_Toplevel *tl) { if (!wm || !tl) return; for (size_t ws = 0; ws < ARRAY_SZ(wm->wm.workspaces); ++ws) { auto *vec = &wm->wm.workspaces[ws].v_windows; for (size_t i = 0; i < vector_size(*vec);) { if ((*vec)[i].tl == tl) { vector_remove(*vec, i); } else { i++; } } } } static void focus_fallback(LunarWM *wm); static 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); } static 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; } } remove_windows_for_tl(tl->server, tl); focus_fallback(tl->server); LunarWM_Toplevel_destroy(tl); free(tl); } static void toplevel_map_notify(struct wl_listener *l, void *data) { (void)data; LunarWM_Toplevel *tl = wl_container_of(l, (LunarWM_Toplevel *)0, map); if (!tl || !tl->surface) return; if (tl->is_xwayland && tl->u.xwl) { if (tl->u.xwl->override_redirect && !wlr_xwayland_surface_override_redirect_wants_focus(tl->u.xwl)) { return; } } LunarWM_Toplevel_focus(tl); LunarWM *wm = tl->server; for (size_t i = 0; i < vector_size(wm->wayland.v_toplevels); ++i) { if (wm->wayland.v_toplevels[i] == tl) { wm->wayland.current_focus = (int)i; break; } } } static void toplevel_unmap_notify(struct wl_listener *l, void *data) { (void)data; LunarWM_Toplevel *tl = wl_container_of(l, tl, unmap); if (!tl || !tl->surface) return; if (tl->map.link.prev || tl->map.link.next) wl_list_remove(&tl->map.link); if (tl->unmap.link.prev || tl->unmap.link.next) wl_list_remove(&tl->unmap.link); if (tl->commit.link.prev || tl->commit.link.next) wl_list_remove(&tl->commit.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 }; LunarWM_Toplevel_release_surface_rt(tl); tl->surface = NULL; tl->composed_has_alpha = false; focus_fallback(tl->server); } bool LunarWM_Toplevel_init_xdg( LunarWM_Toplevel *tl, LunarWM *wm, struct wlr_xdg_toplevel *xdg) { tl->id = LunarWM_get_new_id(wm); 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); tl->map.notify = toplevel_map_notify; wl_signal_add(&tl->u.xdg->base->surface->events.map, &tl->map); tl->unmap.notify = toplevel_unmap_notify; wl_signal_add(&tl->u.xdg->base->surface->events.unmap, &tl->unmap); return true; } bool LunarWM_Toplevel_init_xwayland( LunarWM_Toplevel *tl, LunarWM *wm, struct wlr_xwayland_surface *xwl) { tl->id = LunarWM_get_new_id(wm); 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); tl->map.notify = toplevel_map_notify; wl_signal_add(&xwl->surface->events.map, &tl->map); tl->unmap.notify = toplevel_unmap_notify; wl_signal_add(&xwl->surface->events.unmap, &tl->unmap); return true; } bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this) { if (!this) return false; if (this->map.link.prev || this->map.link.next) wl_list_remove(&this->map.link); if (this->unmap.link.prev || this->unmap.link.next) wl_list_remove(&this->unmap.link); if (this->commit.link.prev || this->commit.link.next) wl_list_remove(&this->commit.link); if (this->destroy.link.prev || this->destroy.link.next) wl_list_remove(&this->destroy.link); if (this->locked_buffer) wlr_buffer_unlock(this->locked_buffer); LunarWM_Toplevel_release_surface_rt(this); this->composed_has_alpha = false; return true; } struct SurfaceComposeCtx { struct wlr_box extents; bool *has_alpha; }; char const *GLInternalFormatName(GLenum format) { switch (format) { case GL_RGBA8: return "GL_RGBA8"; case GL_RGB8: return "GL_RGB8"; case GL_RGB565: return "GL_RGB565"; case GL_RGBA4: return "GL_RGBA4"; case GL_RGB5_A1: return "GL_RGB5_A1"; case GL_DEPTH_COMPONENT16: return "GL_DEPTH_COMPONENT16"; case GL_DEPTH_COMPONENT24: return "GL_DEPTH_COMPONENT24"; case GL_DEPTH24_STENCIL8: return "GL_DEPTH24_STENCIL8"; case GL_DEPTH32F_STENCIL8: return "GL_DEPTH32F_STENCIL8"; case GL_R8: return "GL_R8"; case GL_RG8: return "GL_RG8"; default: return "Unknown format"; } } static void surface_compose_draw( struct wlr_surface *surface, int sx, int sy, void *user_data) { struct SurfaceComposeCtx *ctx = user_data; if (!ctx) return; struct wlr_texture *wlr_tex = wlr_surface_get_texture(surface); if (!wlr_tex || wlr_tex->width == 0 || wlr_tex->height == 0) return; struct wlr_gles2_texture *gles_tex = gles2_get_texture(wlr_tex); if (!gles_tex) return; if (ctx->has_alpha && gles_tex->has_alpha) *ctx->has_alpha = true; struct wlr_gles2_texture_attribs attribs; wlr_gles2_texture_get_attribs(wlr_tex, &attribs); Texture2D tex = { .id = (unsigned int)attribs.tex, .width = (int)wlr_tex->width, .height = (int)wlr_tex->height, .mipmaps = 1, .format = gles_tex->has_alpha ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : PIXELFORMAT_UNCOMPRESSED_R8G8B8, }; float dest_w = (float)surface->current.width; float dest_h = (float)surface->current.height; if (dest_w <= 0.0f || dest_h <= 0.0f) { dest_w = (float)tex.width; dest_h = (float)tex.height; } Rectangle src = { 0.0f, 0.0f, (float)tex.width, (float)tex.height }; Rectangle dst = { (float)(sx - ctx->extents.x), (float)(sy - ctx->extents.y), dest_w, dest_h, }; if (attribs.target == GL_TEXTURE_EXTERNAL_OES) { static bool external_draw_warned = false; if (!draw_external_texture(&attribs, tex.width, tex.height, src, dst, ctx->extents.width, ctx->extents.height)) { if (!external_draw_warned) { TraceLog(LOG_WARNING, "Failed to draw external texture, skipping frame"); external_draw_warned = true; } } } else { assert(attribs.target == GL_TEXTURE_2D); DrawTexturePro(tex, src, dst, (Vector2) { 0.0f, 0.0f }, 0.0f, WHITE); } } 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); struct wlr_box extents = { 0 }; wlr_surface_get_extents(this->surface, &extents); if (extents.width <= 0 || extents.height <= 0) { LunarWM_Toplevel_release_surface_rt(this); this->composed_has_alpha = this->gles_texture->has_alpha; return true; } bool needs_alpha = this->gles_texture->has_alpha; if (extents.x != 0 || extents.y != 0 || extents.width != (int)this->texture->width || extents.height != (int)this->texture->height) { needs_alpha = true; } bool const size_changed = !IsRenderTextureValid(this->surface_rt) || this->surface_rt.texture.width != extents.width || this->surface_rt.texture.height != extents.height; if (size_changed) { LunarWM_Toplevel_release_surface_rt(this); this->surface_rt = LoadRenderTexture(extents.width, extents.height); if (!IsRenderTextureValid(this->surface_rt)) { this->composed_has_alpha = needs_alpha; return true; } SetTextureFilter(this->surface_rt.texture, TEXTURE_FILTER_BILINEAR); SetTextureWrap(this->surface_rt.texture, TEXTURE_WRAP_CLAMP); } BeginTextureMode(this->surface_rt); ClearBackground(BLANK); BeginBlendMode(BLEND_ALPHA); struct SurfaceComposeCtx ctx = { .extents = extents, .has_alpha = &needs_alpha, }; wlr_surface_for_each_surface(this->surface, surface_compose_draw, &ctx); EndBlendMode(); EndTextureMode(); this->surface_extents = extents; Texture2D composed = this->surface_rt.texture; composed.width = extents.width; composed.height = extents.height; this->rl_texture = composed; this->composed_has_alpha = needs_alpha; 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 req_activate; struct wl_listener set_geometry; }; static void xwayland_ready_notify(struct wl_listener *l, void *data) { (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); LunarWM_Window window = { .tl = tl, .coord = get_forward_spherical_with_nearest( wm2->renderer.camera.target, wm2->cman->cfg.space.radius), }; vector_add( &wm2->wm.workspaces[wm2->wm.active_workspace].v_windows, window); } 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); remove_windows_for_tl(wm2, tl); 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->map.link.prev || tl->map.link.next) wl_list_remove(&tl->map.link); if (tl->unmap.link.prev || tl->unmap.link.next) wl_list_remove(&tl->unmap.link); 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 }; LunarWM_Toplevel_release_surface_rt(tl); 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; } } focus_fallback(wm); } 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) { (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) { (void)data; struct XwlHooks *xh = wl_container_of(ll, xh, req_fullscreen); wlr_xwayland_surface_set_fullscreen(xh->xwl, true); } static void xwl_on_request_activate(struct wl_listener *ll, void *data) { (void)data; struct XwlHooks *h = wl_container_of(ll, h, req_activate); for (size_t i = 0; i < vector_size(h->wm->wayland.v_toplevels); ++i) { LunarWM_Toplevel *tl = h->wm->wayland.v_toplevels[i]; if (tl->is_xwayland && tl->u.xwl == h->xwl) { LunarWM_Toplevel_focus(tl); break; } } } 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) { (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->req_activate.link.prev) wl_list_remove(&xh->req_activate.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); remove_windows_for_tl(wm, tl); free(tl); break; } } focus_fallback(wm); free(xh); } static bool xwl_wants_focus(struct wlr_xwayland_surface *x) { if (!x) return false; if (!x->surface) return false; if (x->override_redirect && !wlr_xwayland_surface_override_redirect_wants_focus(x)) { return false; } if (x->withdrawn || x->minimized) return false; return true; } static void focus_fallback(LunarWM *wm) { if (!wm || !wm->wayland.seat) return; for (ssize_t i = (ssize_t)vector_size(wm->wayland.v_toplevels) - 1; i >= 0; --i) { LunarWM_Toplevel *cand = wm->wayland.v_toplevels[i]; if (!cand || !cand->surface) continue; if (cand->is_xwayland) { if (!xwl_wants_focus(cand->u.xwl)) continue; } LunarWM_Toplevel_focus(cand); wm->wayland.current_focus = (int)i; return; } struct wlr_seat *seat = wm->wayland.seat; struct wlr_surface *prev = seat->keyboard_state.focused_surface; if (prev) { struct wlr_xdg_toplevel *pt; struct wlr_xwayland_surface *px; if ((pt = wlr_xdg_toplevel_try_from_wlr_surface(prev))) wlr_xdg_toplevel_set_activated(pt, false); if ((px = wlr_xwayland_surface_try_from_wlr_surface(prev))) wlr_xwayland_surface_activate(px, false); } wlr_seat_keyboard_clear_focus(seat); wm->wayland.current_focus = -1; } 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->req_activate.notify = xwl_on_request_activate; wl_signal_add(&xwl->events.request_activate, &h->req_activate); 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 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 *p = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), destroy); wl_list_remove(&p->modifiers.link); wl_list_remove(&p->key.link); wl_list_remove(&p->destroy.link); wl_list_remove(&p->link); free(p); } static inline float wrap_pi(float a) { a = fmodf(a + PI, 2.0f * PI); if (a < 0.0f) a += 2.0f * PI; return a - PI; } static void Pointer_motion_notify(struct wl_listener *listener, void *data) { LunarWM_Pointer *p = wl_container_of(listener, (LunarWM_Pointer *)NULL, motion); if (!p->server || !p->server->wayland.seat) return; struct wlr_pointer_motion_event *ev = data; float dx = (float)ev->delta_x; float dy = (float)ev->delta_y; if (p->server->cman->cfg.input.mouse.invert_x) { dx *= -1; } if (p->server->cman->cfg.input.mouse.invert_y) { dy *= -1; } float const R = p->server->cman->cfg.space.radius; float const g = 0.0005f; float const POLE_GUARD = 6.0f * (float)M_PI / 180.0f; float const MIN_SIN = sinf(POLE_GUARD); SphericalCoord *coord = &p->server->wm.pointer; float theta = coord->theta + dx * g; float phi = coord->phi + dy * g; theta = wrap_pi(theta); float sin_phi = sinf(phi); if (fabsf(sin_phi) < MIN_SIN) { float sign = sin_phi < 0.0f ? -1.0f : 1.0f; phi = asinf(sign * MIN_SIN); } coord->theta = theta; coord->phi = phi; coord->r = R; } static void Pointer_destroy_notify(struct wl_listener *listener, void *) { auto *kbd = wl_container_of(listener, (LunarWM_Pointer *)(NULL), destroy); wl_list_remove(&kbd->motion.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) { struct wlr_pointer *wlr_pointer = wlr_pointer_from_input_device(dev); LunarWM_Pointer *pointer = calloc(1, sizeof(*pointer)); pointer->server = wm; pointer->wlr_pointer = wlr_pointer; pointer->destroy.notify = Pointer_destroy_notify; wl_signal_add(&dev->events.destroy, &pointer->destroy); pointer->motion.notify = Pointer_motion_notify; wl_signal_add(&wlr_pointer->events.motion, &pointer->motion); wl_list_insert(&wm->wayland.pointers, &pointer->link); } 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 *) { } 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); LunarWM_Window window = { .tl = tl, .coord = get_forward_spherical_with_nearest( wm->renderer.camera.target, wm->cman->cfg.space.radius), }; vector_add( &wm->wm.workspaces[wm->wm.active_workspace].v_windows, window); } else { wlr_log(WLR_ERROR, "Failed to initialize Toplevel."); free(tl); } } struct vo_client_res { struct wl_resource *res; struct wl_list link; }; static void vo_send_initial(struct virtual_output *vo, struct wl_resource *res) { 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); } 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; struct wl_resource *res = wl_resource_create(client, &wl_output_interface, version, id); if (!res) return; struct vo_client_res *cr = calloc(1, sizeof(*cr)); if (!cr) { 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) { struct vo_client_res *cr, *tmp; wl_list_for_each_safe(cr, tmp, &vo->clients, link) { wl_resource_destroy(cr->res); } 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; 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 void setup_xwayland(LunarWM *this) { 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); } static void destroy_output(LunarWM_Output *output) { if (!output) { return; } if (output->frame.link.prev || output->frame.link.next) { wl_list_remove(&output->frame.link); } if (output->destroy.link.prev || output->destroy.link.next) { wl_list_remove(&output->destroy.link); } free(output); } static void handle_output_destroy(struct wl_listener *listener, void *data) { (void)data; LunarWM_Output *output = wl_container_of(listener, output, destroy); LunarWM *wm = output->wm; if (wm && wm->wayland.v_outputs) { for (size_t i = 0; i < vector_size(wm->wayland.v_outputs); ++i) { if (wm->wayland.v_outputs[i] == output) { vector_remove(wm->wayland.v_outputs, i); break; } } } destroy_output(output); } static void handle_output_frame(struct wl_listener *listener, void *data) { (void)data; LunarWM_Output *output = wl_container_of(listener, output, frame); LunarWM *wm = output->wm; struct wlr_output *wlr_output = output->wlr_output; if (wm->xr.available) { wlr_output_schedule_frame(wlr_output); return; } struct wlr_output_state state; wlr_output_state_init(&state); int width, height; wlr_output_effective_resolution(wlr_output, &width, &height); struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &state, NULL); if (pass == NULL) { wlr_output_state_finish(&state); wlr_output_schedule_frame(wlr_output); wlr_log(WLR_ERROR, "Failed to begin render pass for output %s", wlr_output->name); return; } GLint drawFboId = 0, readFboId = 0; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFboId); glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFboId); rlMatrixMode(RL_MODELVIEW); rlLoadIdentity(); // if (!IsTextureValid(wm->renderer.main_rt.texture)) { // wm->renderer.main_rt = LoadRenderTexture(width, height); // } if (!wlr_render_pass_submit(pass)) { wlr_output_state_finish(&state); wlr_output_schedule_frame(wlr_output); wlr_log(WLR_ERROR, "Failed to submit render pass for output %s", wlr_output->name); return; } if (!wlr_output_commit_state(wlr_output, &state)) { wlr_output_state_finish(&state); wlr_output_schedule_frame(wlr_output); return; } wm->renderer.tmp_rt.id = drawFboId; wm->renderer.tmp_rt.texture = (Texture) { .id = drawFboId, .width = width, .height = height, .mipmaps = 1, .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, }; if (!IsRenderTextureValid(wm->renderer.tmp_rt)) { wm->renderer.tmp_rt.depth.id = rlLoadTextureDepth(width, height, true); wm->renderer.tmp_rt.depth.width = width; wm->renderer.tmp_rt.depth.height = height; wm->renderer.tmp_rt.depth.format = 19; // DEPTH_COMPONENT_24BIT? wm->renderer.tmp_rt.depth.mipmaps = 1; } rlFramebufferAttach(wm->renderer.tmp_rt.id, wm->renderer.tmp_rt.depth.id, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); if (!wm->xr.available && !wm->renderer.first_frame) { wm->renderer.camera.target = SphericalToVector3(wm->wm.pointer); } int const hud_size = wm->cman->cfg.displays.hud.size; if (!IsTextureValid(wm->renderer.hud_rt.texture)) { wm->renderer.hud_rt = LoadRenderTexture(hud_size, hud_size); } BeginTextureMode(wm->renderer.hud_rt); { LunarWM_render_hud(wm, GetFrameTime(), 1); } EndTextureMode(); if (!IsTextureValid(wm->renderer.main_rt.texture)) { wm->renderer.main_rt = LoadRenderTexture(width, height); } BeginTextureMode(wm->renderer.main_rt); { BeginMode3D(wm->renderer.camera); { ClearBackground(BLACK); LunarWM_render_3d(wm, GetFrameTime()); } EndMode3D(); rlDrawRenderBatchActive(); } EndTextureMode(); BeginTextureMode(wm->renderer.tmp_rt); { DrawTexturePro(wm->renderer.main_rt.texture, (Rectangle) { 0, 0, wm->renderer.main_rt.texture.width, wm->renderer.main_rt.texture.height, }, (Rectangle) { 0, 0, wm->renderer.main_rt.texture.width, -wm->renderer.main_rt.texture.height, }, (Vector2) { 0, 0 }, 0, WHITE); } EndTextureMode(); if (wm->renderer.first_frame) { LunarWM_set_recenter_from_camera(wm); wm->renderer.first_frame = false; wm->wm.pointer = get_forward_spherical_with_nearest( wm->renderer.camera.target, wm->cman->cfg.space.radius); } wlr_output_state_finish(&state); } static void handle_new_output(struct wl_listener *listener, void *data) { struct wlr_output *wlr_output = data; LunarWM *wm = wl_container_of(listener, wm, wayland.new_output_listener); wlr_log(WLR_INFO, "Got new output."); if (!wlr_output_init_render( wlr_output, wm->wayland.allocator, wm->wayland.renderer)) { wlr_log( WLR_ERROR, "Failed to init render for output %s", wlr_output->name); return; } struct wlr_output_state state; wlr_output_state_init(&state); wlr_output_state_set_enabled(&state, true); struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); if (mode != NULL) { wlr_output_state_set_mode(&state, mode); } bool committed = wlr_output_commit_state(wlr_output, &state); wlr_output_state_finish(&state); if (!committed) { wlr_log(WLR_ERROR, "Failed to commit init state for output %s", wlr_output->name); return; } LunarWM_Output *output = calloc(1, sizeof(*output)); if (!output) { wlr_log(WLR_ERROR, "Out of memory creating output state"); return; } output->wm = wm; output->wlr_output = wlr_output; output->frame.notify = handle_output_frame; wl_signal_add(&wlr_output->events.frame, &output->frame); output->destroy.notify = handle_output_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); vector_add(&wm->wayland.v_outputs, output); wlr_output_schedule_frame(wlr_output); } bool LunarWM_wayland_init(LunarWM *this) { wlr_log_init(WLR_DEBUG, nullptr); this->wayland.v_toplevels = vector_create(); this->wayland.v_outputs = vector_create(); this->wayland.display = wl_display_create(); if (this->wayland.display == nullptr) { return false; } 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) { wlr_log(WLR_ERROR, "Failed to create allocator"); return false; } this->wayland.compositor = wlr_compositor_create( this->wayland.display, 5, this->wayland.renderer); if (!this->wayland.compositor) { wlr_log(WLR_ERROR, "Failed to create compositor"); return false; } this->wayland.subcompositor = wlr_subcompositor_create(this->wayland.display); if (!this->wayland.subcompositor) { wlr_log(WLR_ERROR, "Failed to create subcompositor"); return false; } this->wayland.data_device_manager = wlr_data_device_manager_create(this->wayland.display); if (!this->wayland.data_device_manager) { wlr_log(WLR_ERROR, "Failed to create data device manager"); return false; } this->wayland.xdg_shell = wlr_xdg_shell_create(this->wayland.display, 3); if (!this->wayland.xdg_shell) { wlr_log(WLR_ERROR, "Failed to create xdg shell"); return false; } 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); this->wayland.seat = wlr_seat_create(this->wayland.display, "seat0"); if (!this->wayland.seat) { wlr_log(WLR_ERROR, "Failed to create seat"); return false; } wl_list_init(&this->wayland.keyboards); wl_list_init(&this->wayland.pointers); this->wayland.new_output_listener.notify = handle_new_output; wl_signal_add(&this->wayland.backend->events.new_output, &this->wayland.new_output_listener); 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.xwayland = wlr_xwayland_create( this->wayland.display, this->wayland.compositor, false); if (!this->wayland.xwayland) { wlr_log(WLR_ERROR, "Failed to start XWayland"); return false; } setup_xwayland(this); this->wayland.event_loop = wl_display_get_event_loop(this->wayland.display); return true; } void LunarWM_wayland_cleanup(LunarWM *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.new_output_listener.link.prev || this->wayland.new_output_listener.link.next) { wl_list_remove(&this->wayland.new_output_listener.link); this->wayland.new_output_listener.notify = NULL; } if (this->wayland.v_outputs) { for (size_t i = 0; i < vector_size(this->wayland.v_outputs); ++i) { destroy_output(this->wayland.v_outputs[i]); } vector_free(this->wayland.v_outputs); this->wayland.v_outputs = 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.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; } } void LunarWM_wayland_update_virtual_outputs( LunarWM *wm, int virtual_width, int virtual_height, int hud_size) { if (wm->wayland.custom_out_virtual) { vo_destroy(wm->wayland.custom_out_virtual); wm->wayland.custom_out_virtual = NULL; } if (wm->wayland.custom_out_hud) { vo_destroy(wm->wayland.custom_out_hud); wm->wayland.custom_out_hud = NULL; } wm->wayland.custom_out_virtual = vo_create(wm->wayland.display, "Virtual", "Virtual output", virtual_width, virtual_height, 60000, 1, "LunarWM", "Virtual"); wm->wayland.custom_out_hud = vo_create(wm->wayland.display, "HUD", "HUD output", hud_size, hud_size, 60000, 1, "LunarWM", "HUD"); }