windowmaker-wl/src/backend/wayland/wl_xwayland.c

430 lines
15 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <wlr/xwayland.h>
#include <xcb/xcb.h>
#include <stddef.h>
#include <X11/Xlib.h>
#include <wlr/types/wlr_output.h>
#include "../../screen.h"
#include "window.h"
#include "wl_types.h"
#include <WINGs/WINGsP.h>
#include <wraster.h>
/* Forward declarations */
void view_attach_scene_destroy_listener(struct wl_toplevel_view *view);
void wm_dock_app_add(struct wl_toplevel_view *v);
void wm_dock_app_remove(WNativeWindow id);
void wl_pointer_warp_to(int x, int y);
#define VIEW_FROM_LISTENER(ptr, field) \
((struct wl_toplevel_view *)((char *)(ptr) - offsetof(struct wl_toplevel_view, field)))
/* --- Override-redirect (unmanaged) XWayland surface handlers --- */
static void
handle_or_map(struct wl_listener *listener, void *data)
{
struct wl_or_view *ov = wl_container_of(listener, ov, map_listener);
struct wlr_xwayland_surface *xw = ov->xw_surface;
(void)data;
if (wl_state.startup_pump_running)
return;
if (wl_state.scene && !ov->scene_node && xw->surface) {
struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU]
? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree;
ov->scene_node = wlr_scene_subsurface_tree_create(_op, xw->surface);
if (ov->scene_node)
wlr_scene_node_set_position(&ov->scene_node->node, xw->x, xw->y);
}
}
static void
handle_or_unmap(struct wl_listener *listener, void *data)
{
struct wl_or_view *ov = wl_container_of(listener, ov, unmap_listener);
(void)data;
if (ov->scene_node) {
wlr_scene_node_destroy(&ov->scene_node->node);
ov->scene_node = NULL;
}
}
static void
handle_or_destroy(struct wl_listener *listener, void *data)
{
struct wl_or_view *ov = wl_container_of(listener, ov, destroy_listener);
(void)data;
wl_list_remove(&ov->map_listener.link);
wl_list_remove(&ov->unmap_listener.link);
wl_list_remove(&ov->destroy_listener.link);
wl_list_remove(&ov->set_geometry_listener.link);
wl_list_remove(&ov->link);
if (ov->scene_node)
wlr_scene_node_destroy(&ov->scene_node->node);
wfree(ov);
}
static void
handle_or_set_geometry(struct wl_listener *listener, void *data)
{
struct wl_or_view *ov = wl_container_of(listener, ov, set_geometry_listener);
struct wlr_xwayland_surface *xw = ov->xw_surface;
(void)data;
if (ov->scene_node)
wlr_scene_node_set_position(&ov->scene_node->node, xw->x, xw->y);
}
/* --- Managed XWayland surface handlers --- */
static void
handle_xwayland_set_geometry(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view =
wl_container_of(listener, view, xw_geometry_listener);
struct wlr_xwayland_surface *xw = view->xw_surface;
(void)data;
if (view->scene_node) {
if (view->frame_id) {
wlr_scene_node_set_position(&view->scene_node->node,
view->frame_rel_x, view->frame_rel_y);
} else {
wlr_scene_node_set_position(&view->scene_node->node, xw->x, xw->y);
}
}
}
static void
handle_xwayland_surface_map(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view =
wl_container_of(listener, view, xw_surface_map_listener);
(void)data;
struct wlr_xwayland_surface *xw = view->xw_surface;
wwarning("wl_backend: xwayland surface mapped id=0x%lx class=%s title=%s",
view->id,
xw->class ? xw->class : "(none)",
xw->title ? xw->title : "(none)");
if (wl_state.scene && !view->scene_node && xw->surface) {
struct wlr_scene_tree *_vp = wl_state.level_trees[WL_LAYER_NORMAL]
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
view->scene_node = wlr_scene_subsurface_tree_create(_vp, xw->surface);
if (view->scene_node) {
view_attach_scene_destroy_listener(view);
wlr_scene_node_set_position(&view->scene_node->node, view->x, view->y);
}
}
if (w_global.screen_count == 0)
return;
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WWindow *wwin = wManageWindow(scr, view->id);
if (wwin) {
wwarning("wl_backend: wManageWindow(xwayland) succeeded id=0x%lx "
"class=%s name=%s geo=%dx%d pid=%d",
view->id,
wwin->wm_class ? wwin->wm_class : "(none)",
wwin->wm_instance ? wwin->wm_instance : "(none)",
wwin->client.width, wwin->client.height, (int)xw->pid);
int cx = view->x + wwin->client.width / 2;
int cy = view->y + wwin->client.height / 2;
wl_pointer_warp_to(cx, cy);
wm_dock_app_add(view);
}
}
}
static void
handle_xwayland_associate(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, map_listener);
(void)data;
struct wlr_xwayland_surface *xw = view->xw_surface;
wwarning("wl_backend: xwayland associate id=0x%lx surface=%p",
view->id, (void*)xw->surface);
if (xw->surface) {
view->xw_surface_map_listener.notify = handle_xwayland_surface_map;
wl_signal_add(&xw->surface->events.map, &view->xw_surface_map_listener);
}
}
static void
handle_xwayland_unmap(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, unmap_listener);
(void)data;
wwarning("wl_backend: xwayland unmapped id=0x%lx", view->id);
if (view->xw_surface_map_listener.link.next &&
view->xw_surface_map_listener.link.next != &view->xw_surface_map_listener.link)
wl_list_remove(&view->xw_surface_map_listener.link);
wl_list_init(&view->xw_surface_map_listener.link);
wm_dock_app_remove(view->id);
if (view->scene_node) {
wlr_scene_node_destroy(&view->scene_node->node);
view->scene_node = NULL;
}
if (w_global.screen_count == 0)
return;
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN);
if (desc && desc->parent_type == WCLASS_WINDOW) {
WWindow *wwin = (WWindow *)desc->parent;
wUnmanageWindow(wwin, False, True);
}
}
}
static void
handle_xwayland_destroy(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, destroy_listener);
(void)data;
wl_list_remove(&view->map_listener.link);
wl_list_remove(&view->unmap_listener.link);
wl_list_remove(&view->destroy_listener.link);
wl_list_remove(&view->title_listener.link);
if (view->xw_surface_map_listener.link.next &&
view->xw_surface_map_listener.link.next != &view->xw_surface_map_listener.link)
wl_list_remove(&view->xw_surface_map_listener.link);
if (view->xw_geometry_listener.link.next)
wl_list_remove(&view->xw_geometry_listener.link);
wl_list_remove(&view->link);
if (view->scene_node) {
wlr_scene_node_destroy(&view->scene_node->node);
view->scene_node = NULL;
}
wl_state.managed_surface_count--;
if (view->wm_state) wfree(view->wm_state);
if (view->wm_command) {
int ci;
for (ci = 0; ci < view->wm_command_n; ci++)
wfree(view->wm_command[ci]);
wfree(view->wm_command);
}
wfree(view);
}
static void
handle_xwayland_set_class(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, title_listener);
(void)data;
struct wlr_xwayland_surface *xw = view->xw_surface;
WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN);
if (!desc || desc->parent_type != WCLASS_WINDOW)
return;
WWindow *wwin = (WWindow *)desc->parent;
if (wwin->wm_class) wfree(wwin->wm_class);
if (wwin->wm_instance) wfree(wwin->wm_instance);
wwin->wm_class = xw->class ? strdup(xw->class) : NULL;
wwin->wm_instance = xw->instance ? strdup(xw->instance) : NULL;
}
void
handle_xwayland_server_destroy(struct wl_listener *listener, void *data)
{
(void)listener;
(void)data;
wwarning("wl_backend: XWayland server exited");
wl_state.xwayland_ready = true;
}
void
handle_xwayland_ready(struct wl_listener *listener, void *data)
{
(void)listener;
(void)data;
if (!wl_state.xwayland || !wl_state.xwayland->display_name) {
wwarning("wl_backend: XWayland ready but display_name unavailable");
wl_state.xwayland_ready = true;
return;
}
setenv("DISPLAY", wl_state.xwayland->display_name, 1);
wwarning("wl_backend: XWayland ready on %s — opening WM Xlib connection",
wl_state.xwayland->display_name);
wl_state.x_display = XOpenDisplay(wl_state.xwayland->display_name);
if (!wl_state.x_display)
wwarning("wl_backend: XOpenDisplay(%s) failed — WM UI limited to native Wayland",
wl_state.xwayland->display_name);
wl_state.xwayland_ready = true;
}
void
handle_xwayland_new_surface(struct wl_listener *listener, void *data)
{
struct wlr_xwayland_surface *xw = data;
(void)listener;
if (xw->override_redirect) {
if (wl_state.rcontext_xwin &&
xw->window_id == (xcb_window_t)wl_state.rcontext_xwin)
return;
struct wl_or_view *ov = wmalloc(sizeof(*ov));
memset(ov, 0, sizeof(*ov));
ov->xw_surface = xw;
ov->map_listener.notify = handle_or_map;
ov->unmap_listener.notify = handle_or_unmap;
ov->destroy_listener.notify = handle_or_destroy;
ov->set_geometry_listener.notify = handle_or_set_geometry;
wl_signal_add(&xw->events.destroy, &ov->destroy_listener);
wl_signal_add(&xw->events.associate, &ov->map_listener);
wl_signal_add(&xw->events.dissociate, &ov->unmap_listener);
wl_signal_add(&xw->events.set_geometry, &ov->set_geometry_listener);
wl_list_insert(&wl_state.or_list, &ov->link);
if (xw->surface) {
if (wl_state.scene) {
struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU]
? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree;
ov->scene_node = wlr_scene_subsurface_tree_create(_op, xw->surface);
if (ov->scene_node)
wlr_scene_node_set_position(&ov->scene_node->node,
xw->x, xw->y);
}
}
return;
}
if (wl_state.rcontext_xwin && xw->window_id == (xcb_window_t)wl_state.rcontext_xwin) {
wwarning("wl_backend: xwayland rcontext drawable 0x%x skipped",
(unsigned)xw->window_id);
return;
}
if (w_global.screen_count == 0) {
wwarning("wl_backend: xwayland new surface 0x%x during startup — skipped",
(unsigned)xw->window_id);
return;
}
if (!xw->class && !xw->title) {
wwarning("wl_backend: xwayland surface 0x%x has no class/title yet — deferring",
(unsigned)xw->window_id);
}
struct wl_toplevel_view *view = wmalloc(sizeof(*view));
memset(view, 0, sizeof(*view));
view->xw_surface = xw;
view->id = (WNativeWindow)xw->window_id;
view->map_listener.notify = handle_xwayland_associate;
view->unmap_listener.notify = handle_xwayland_unmap;
view->destroy_listener.notify = handle_xwayland_destroy;
view->title_listener.notify = handle_xwayland_set_class;
view->xw_geometry_listener.notify = handle_xwayland_set_geometry;
wl_signal_add(&xw->events.associate, &view->map_listener);
wl_signal_add(&xw->events.dissociate, &view->unmap_listener);
wl_signal_add(&xw->events.destroy, &view->destroy_listener);
wl_signal_add(&xw->events.set_class, &view->title_listener);
wl_signal_add(&xw->events.set_geometry, &view->xw_geometry_listener);
wl_list_insert(&wl_state.toplevel_list, &view->link);
wl_state.managed_surface_count++;
view->x = (wl_state.output ? (int)wl_state.output->width : 1920) / 4;
view->y = (wl_state.output ? (int)wl_state.output->height : 1080) / 4;
if (xw->surface) {
view->xw_surface_map_listener.notify = handle_xwayland_surface_map;
wl_signal_add(&xw->surface->events.map, &view->xw_surface_map_listener);
}
wwarning("wl_backend: new XWayland surface id=0x%lx class=%s title=%s pos=%d,%d",
view->id,
xw->class ? xw->class : "(none)",
xw->title ? xw->title : "(none)",
xw->x, xw->y);
{
int cw = (xw->width >= 64) ? xw->width : 640;
int ch = (xw->height >= 64) ? xw->height : 480;
wlr_xwayland_surface_configure(xw,
(int16_t)view->x, (int16_t)view->y, cw, ch);
}
}
/* ------------------------------------------------------------------ */
/* XWayland snapshot capture — called from wl_client_snapshot() */
#if USE_X11_BACKEND
RImage *
wl_xwayland_capture_snapshot(WScreen *scr, struct wlr_xwayland_surface *xw)
{
if (!wl_state.x_display || !xw)
return NULL;
Window xid = (Window)xw->window_id;
XWindowAttributes attr;
if (!XGetWindowAttributes(wl_state.x_display, xid, &attr))
return NULL;
XImage *img = XGetImage(wl_state.x_display, xid,
0, 0, attr.width, attr.height,
AllPlanes, ZPixmap);
if (!img) return NULL;
RImage *res = RCreateImageFromXImage(scr->rcontext, img, NULL);
XDestroyImage(img);
return res;
}
float
wl_xwayland_get_window_opacity(WNativeWindow client_win)
{
if (!wl_state.x_display)
return 1.0f;
WMNativeAtom type;
int format;
unsigned long items, rest;
unsigned char *prop = NULL;
unsigned long val = 0xFFFFFFFF;
WMNativeAtom opacity_atom = XInternAtom(wl_state.x_display,
"_NET_WM_WINDOW_OPACITY", False);
if (XGetWindowProperty(wl_state.x_display, (WNativeWindow)client_win,
opacity_atom, 0L, 1L, False, AnyPropertyType,
&type, &format, &items, &rest, &prop) == Success
&& format == 32 && items == 1 && prop) {
val = *(unsigned long *)prop;
}
if (prop)
XFree(prop);
return (float)val / (float)0xFFFFFFFF;
}
#else
RImage *wl_xwayland_capture_snapshot(WScreen *scr, struct wlr_xwayland_surface *xw)
{ (void)scr; (void)xw; return NULL; }
float wl_xwayland_get_window_opacity(WNativeWindow client_win)
{ (void)client_win; return 1.0f; }
#endif