2042 lines
56 KiB
C
2042 lines
56 KiB
C
#include "LunarWM_wayland.h"
|
|
|
|
#include "LunarWM_core.h"
|
|
#include "LunarWM_render.h"
|
|
#include "common.h"
|
|
#include "raylib.h"
|
|
#include "vec.h"
|
|
|
|
#include <pixman.h>
|
|
#include <wlr/render/color.h>
|
|
#include <wlr/render/pass.h>
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <xcb/xcb.h>
|
|
#include <xcb/xcb_icccm.h>
|
|
|
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
#include <rlgl.h>
|
|
|
|
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 SurfaceDamageListener {
|
|
struct wl_listener client_commit;
|
|
struct wl_listener destroy;
|
|
};
|
|
|
|
static void surface_damage_client_commit(
|
|
struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_surface *surface = data;
|
|
if (!surface) {
|
|
return;
|
|
}
|
|
if (!wlr_surface_state_has_buffer(&surface->pending)) {
|
|
return;
|
|
}
|
|
if (pixman_region32_not_empty(&surface->pending.surface_damage)
|
|
|| pixman_region32_not_empty(&surface->pending.buffer_damage)) {
|
|
return;
|
|
}
|
|
|
|
int surface_width = surface->pending.width > 0 ? surface->pending.width
|
|
: surface->current.width;
|
|
int surface_height = surface->pending.height > 0 ? surface->pending.height
|
|
: surface->current.height;
|
|
int buffer_width = surface->pending.buffer_width > 0
|
|
? surface->pending.buffer_width
|
|
: surface->current.buffer_width;
|
|
int buffer_height = surface->pending.buffer_height > 0
|
|
? surface->pending.buffer_height
|
|
: surface->current.buffer_height;
|
|
|
|
if (surface_width <= 0 || surface_height <= 0 || buffer_width <= 0
|
|
|| buffer_height <= 0) {
|
|
return;
|
|
}
|
|
|
|
pixman_region32_union_rect(&surface->pending.surface_damage,
|
|
&surface->pending.surface_damage, 0, 0, surface_width, surface_height);
|
|
pixman_region32_union_rect(&surface->pending.buffer_damage,
|
|
&surface->pending.buffer_damage, 0, 0, buffer_width, buffer_height);
|
|
}
|
|
|
|
static void surface_damage_destroy(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)data;
|
|
struct SurfaceDamageListener *hook
|
|
= wl_container_of(listener, hook, destroy);
|
|
wl_list_remove(&hook->client_commit.link);
|
|
wl_list_remove(&hook->destroy.link);
|
|
free(hook);
|
|
}
|
|
|
|
static void surface_damage_track(struct wlr_surface *surface)
|
|
{
|
|
if (!surface) {
|
|
return;
|
|
}
|
|
|
|
struct SurfaceDamageListener *hook = calloc(1, sizeof(*hook));
|
|
if (!hook) {
|
|
wlr_log(WLR_ERROR, "Failed to allocate surface damage listener");
|
|
return;
|
|
}
|
|
|
|
hook->client_commit.notify = surface_damage_client_commit;
|
|
wl_signal_add(&surface->events.client_commit, &hook->client_commit);
|
|
|
|
hook->destroy.notify = surface_damage_destroy;
|
|
wl_signal_add(&surface->events.destroy, &hook->destroy);
|
|
}
|
|
|
|
static void compositor_new_surface_notify(
|
|
struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_surface *surface = data;
|
|
if (!surface) {
|
|
return;
|
|
}
|
|
|
|
surface_damage_track(surface);
|
|
}
|
|
|
|
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.new_surface_listener.notify = compositor_new_surface_notify;
|
|
wl_signal_add(&this->wayland.compositor->events.new_surface,
|
|
&this->wayland.new_surface_listener);
|
|
|
|
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.new_surface_listener.link.prev
|
|
|| this->wayland.new_surface_listener.link.next) {
|
|
wl_list_remove(&this->wayland.new_surface_listener.link);
|
|
this->wayland.new_surface_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");
|
|
}
|