1251 lines
34 KiB
C
1251 lines
34 KiB
C
|
|
#include "LunarWM_wayland.h"
|
||
|
|
|
||
|
|
#include "common.h"
|
||
|
|
#include "vec.h"
|
||
|
|
|
||
|
|
#include <assert.h>
|
||
|
|
#include <ctype.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>
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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 };
|
||
|
|
tl->surface = NULL;
|
||
|
|
|
||
|
|
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);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool LunarWM_Toplevel_update(LunarWM_Toplevel *this)
|
||
|
|
{
|
||
|
|
if (!this || !this->surface)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
this->texture = wlr_surface_get_texture(this->surface);
|
||
|
|
struct wlr_client_buffer *cl_buf = this->surface->buffer;
|
||
|
|
if ((this->texture == nullptr) || (cl_buf == nullptr)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((this->locked_buffer != nullptr)
|
||
|
|
&& this->locked_buffer != &cl_buf->base) {
|
||
|
|
wlr_buffer_unlock(this->locked_buffer);
|
||
|
|
this->locked_buffer = nullptr;
|
||
|
|
}
|
||
|
|
if (this->locked_buffer == nullptr) {
|
||
|
|
this->locked_buffer = wlr_buffer_lock(&cl_buf->base);
|
||
|
|
}
|
||
|
|
|
||
|
|
wlr_gles2_texture_get_attribs(this->texture, &this->attribs);
|
||
|
|
this->gles_texture = gles2_get_texture(this->texture);
|
||
|
|
if (this->gles_texture == nullptr) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
this->rl_texture.id = (unsigned int)this->attribs.tex;
|
||
|
|
this->rl_texture.width = (int)this->texture->width;
|
||
|
|
this->rl_texture.height = (int)this->texture->height;
|
||
|
|
this->rl_texture.mipmaps = 1;
|
||
|
|
this->rl_texture.format = this->gles_texture->has_alpha
|
||
|
|
? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
|
||
|
|
: PIXELFORMAT_UNCOMPRESSED_R8G8B8;
|
||
|
|
|
||
|
|
SetTextureFilter(this->rl_texture, TEXTURE_FILTER_BILINEAR);
|
||
|
|
SetTextureWrap(this->rl_texture, TEXTURE_WRAP_CLAMP);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clamp_to_hints(
|
||
|
|
const struct wlr_xwayland_surface *x, uint16_t *w, uint16_t *h)
|
||
|
|
{
|
||
|
|
xcb_size_hints_t *hints = x->size_hints;
|
||
|
|
if (!hints)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
|
||
|
|
if (*w < hints->min_width)
|
||
|
|
*w = hints->min_width;
|
||
|
|
if (*h < hints->min_height)
|
||
|
|
*h = hints->min_height;
|
||
|
|
}
|
||
|
|
if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
|
||
|
|
if (hints->max_width > 0 && *w > hints->max_width)
|
||
|
|
*w = hints->max_width;
|
||
|
|
if (hints->max_height > 0 && *h > hints->max_height)
|
||
|
|
*h = hints->max_height;
|
||
|
|
}
|
||
|
|
if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
|
||
|
|
if (hints->width_inc > 0)
|
||
|
|
*w = (*w / hints->width_inc) * hints->width_inc;
|
||
|
|
if (hints->height_inc > 0)
|
||
|
|
*h = (*h / hints->height_inc) * hints->height_inc;
|
||
|
|
}
|
||
|
|
if ((hints->flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE)) {
|
||
|
|
if (*w < hints->base_width)
|
||
|
|
*w = hints->base_width;
|
||
|
|
if (*h < hints->base_height)
|
||
|
|
*h = hints->base_height;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
struct XwlHooks {
|
||
|
|
LunarWM *wm;
|
||
|
|
struct wlr_xwayland_surface *xwl;
|
||
|
|
struct wl_listener associate;
|
||
|
|
struct wl_listener dissociate;
|
||
|
|
struct wl_listener destroy;
|
||
|
|
|
||
|
|
struct wl_listener req_configure;
|
||
|
|
struct wl_listener req_maximize;
|
||
|
|
struct wl_listener req_fullscreen;
|
||
|
|
struct wl_listener 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 };
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool LunarWM_wayland_init(LunarWM *this)
|
||
|
|
{
|
||
|
|
wlr_log_init(WLR_DEBUG, nullptr);
|
||
|
|
|
||
|
|
this->wayland.v_toplevels = vector_create();
|
||
|
|
|
||
|
|
this->wayland.display = wl_display_create();
|
||
|
|
if (this->wayland.display == nullptr) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
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_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.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");
|
||
|
|
}
|