405 lines
16 KiB
C
405 lines
16 KiB
C
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
#include <string.h>
|
|
#include <wayland-server-core.h>
|
|
#include <wlr/types/wlr_keyboard.h>
|
|
#include <wlr/types/wlr_pointer.h>
|
|
#include <wlr/types/wlr_idle_inhibit_v1.h>
|
|
#include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h>
|
|
#include <wlr/types/wlr_data_device.h>
|
|
#include <wlr/types/wlr_pointer_constraints_v1.h>
|
|
#include <wlr/types/wlr_xdg_shell.h>
|
|
#include <wlr/types/wlr_primary_selection.h>
|
|
#include <wlr/types/wlr_scene.h>
|
|
#include <wlr/types/wlr_seat.h>
|
|
#include <wlr/util/log.h>
|
|
#include "../../screen.h"
|
|
#include "wl_types.h"
|
|
#include "../../actions.h"
|
|
#include <WINGs/WINGsP.h>
|
|
|
|
void wl_grab_end(void);
|
|
void wl_update_pointer_focus(uint32_t time_msec);
|
|
struct wlr_surface *view_surface_at(int px, int py, double *sx_out, double *sy_out, struct wl_toplevel_view **view_out);
|
|
void wm_dock_app_remove(WNativeWindow id);
|
|
|
|
void
|
|
handle_seat_request_set_selection(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_seat_request_set_selection_event *ev = data;
|
|
if (!wl_state.seat)
|
|
return;
|
|
wlr_seat_set_selection(wl_state.seat, ev->source, ev->serial);
|
|
}
|
|
|
|
|
|
/*
|
|
* seat request_set_primary_selection — client writes to the
|
|
* primary selection buffer (e.g. by selecting text). Forward to
|
|
* wlr_seat so middle-click paste works in other clients.
|
|
*/
|
|
void
|
|
handle_seat_request_primary_selection(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_seat_request_set_primary_selection_event *ev = data;
|
|
if (!wl_state.seat)
|
|
return;
|
|
wlr_seat_set_primary_selection(wl_state.seat, ev->source, ev->serial);
|
|
}
|
|
|
|
/*
|
|
* idle_inhibit new_inhibitor — a client (e.g. mpv, a game) created
|
|
* an idle-inhibit object. wlroots tracks the inhibitor list internally;
|
|
* the compositor can query wl_state.idle_inhibit_mgr->inhibitors to decide
|
|
* whether to suppress screen blanking. For now we just log the event so
|
|
* the capability is advertised and the inhibitor object doesn't error out.
|
|
*/
|
|
void
|
|
handle_idle_new_inhibitor(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_idle_inhibitor_v1 *inhibitor = data;
|
|
(void)inhibitor;
|
|
wwarning("wl_backend: idle inhibitor created (client suppressing screen blank)");
|
|
}
|
|
|
|
/*
|
|
* scroll-wheel axis event handler.
|
|
* wlr_event_pointer_axis carries orientation (vertical/horizontal), delta,
|
|
* delta_discrete (notches), and source (wheel vs finger). Forward directly
|
|
* to the seat so the focused Wayland client receives wl_pointer.axis events.
|
|
* Without this, scrolling is silently dropped in every Wayland client.
|
|
*/
|
|
void
|
|
handle_pointer_axis(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_pointer_axis_event *ev = data;
|
|
if (!wl_state.seat)
|
|
return;
|
|
/* Drop axis events during a compositor grab so the grabbed window
|
|
* does not receive unexpected scroll events mid-move/resize. */
|
|
if (wl_state.grab_type)
|
|
return;
|
|
wlr_seat_pointer_notify_axis(wl_state.seat,
|
|
ev->time_msec,
|
|
ev->orientation,
|
|
ev->delta,
|
|
ev->delta_discrete,
|
|
ev->source,
|
|
ev->relative_direction);
|
|
}
|
|
|
|
/*
|
|
* new pointer constraint — a client requested pointer lock or
|
|
* pointer confinement (e.g. a game locking the cursor to its window).
|
|
* We activate the constraint immediately so the client gets confirmed.
|
|
* In headless mode the pointer is virtual so there is nothing physically
|
|
* to lock, but activating keeps the client happy.
|
|
*/
|
|
struct wl_constraint_ctx {
|
|
struct wlr_pointer_constraint_v1 *constraint;
|
|
struct wl_listener destroy;
|
|
};
|
|
|
|
static void
|
|
handle_pointer_constraint_destroy(struct wl_listener *listener, void *data)
|
|
{
|
|
struct wl_constraint_ctx *ctx =
|
|
wl_container_of(listener, ctx, destroy);
|
|
struct wlr_pointer_constraint_v1 *constraint = ctx->constraint;
|
|
(void)data;
|
|
|
|
if (constraint->current.cursor_hint.enabled && constraint->surface) {
|
|
/* Convert surface-local hint to global coordinates */
|
|
double sx = constraint->current.cursor_hint.x;
|
|
double sy = constraint->current.cursor_hint.y;
|
|
int gx = 0, gy = 0;
|
|
/* Find the toplevel view owning this surface to get global pos */
|
|
struct wl_toplevel_view *v;
|
|
wl_list_for_each(v, &wl_state.toplevel_list, link) {
|
|
if (v->xdg_surface && v->xdg_surface->surface == constraint->surface) {
|
|
gx = v->x;
|
|
gy = v->y;
|
|
break;
|
|
}
|
|
}
|
|
wl_pointer_warp_to(gx + (int)sx, gy + (int)sy);
|
|
}
|
|
|
|
wl_list_remove(&ctx->destroy.link);
|
|
free(ctx);
|
|
}
|
|
|
|
void
|
|
handle_new_pointer_constraint(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_pointer_constraint_v1 *constraint = data;
|
|
|
|
wlr_pointer_constraint_v1_send_activated(constraint);
|
|
|
|
struct wl_constraint_ctx *ctx = calloc(1, sizeof(*ctx));
|
|
if (ctx) {
|
|
ctx->constraint = constraint;
|
|
ctx->destroy.notify = handle_pointer_constraint_destroy;
|
|
wl_signal_add(&constraint->events.destroy, &ctx->destroy);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* new keyboard shortcuts inhibitor — a fullscreen app or remote
|
|
* desktop client wants to suppress WM keyboard shortcuts.
|
|
* Activate the inhibitor immediately so the client knows it is granted.
|
|
* The existing wWaylandKeyPress() dispatcher in handle_keyboard_key already
|
|
* forwards all non-shortcut keys; with the inhibitor active we should skip
|
|
* the shortcut check for that focused client. For now we simply activate
|
|
* and log; per-client inhibitor tracking can be refined later.
|
|
*/
|
|
void
|
|
handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data;
|
|
wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor);
|
|
wwarning("wl_backend: keyboard shortcuts inhibitor activated");
|
|
}
|
|
|
|
|
|
void
|
|
handle_pointer_button(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener;
|
|
struct wlr_pointer_button_event *ev = data;
|
|
if (!wl_state.seat)
|
|
return;
|
|
/* any button release ends an active compositor grab (move/resize).
|
|
* Type 3 (WM policy grab) is NOT ended here — the policy code calls
|
|
* pointer_ungrab itself after processing the button release event. */
|
|
if (wl_state.grab_type && wl_state.grab_type != 3 &&
|
|
ev->state == WLR_BUTTON_RELEASED) {
|
|
wwarning("wl_backend: grab ended (button release) grab_type=%d", wl_state.grab_type);
|
|
wl_grab_end();
|
|
wl_update_pointer_focus(ev->time_msec);
|
|
return;
|
|
}
|
|
/* click-to-focus — on button press, find the view under the
|
|
* pointer and focus + raise it via the WM policy layer.
|
|
* This mirrors what the X11 backend does via ButtonPress on frame windows. */
|
|
if (ev->state == WLR_BUTTON_PRESSED) {
|
|
double sx, sy;
|
|
struct wl_toplevel_view *view = NULL;
|
|
view_surface_at(wl_state.pointer_x, wl_state.pointer_y,
|
|
&sx, &sy, &view);
|
|
if (view) {
|
|
WObjDescriptor *desc =
|
|
wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN);
|
|
if (desc && desc->parent_type == WCLASS_WINDOW) {
|
|
WWindow *wwin = (WWindow *)desc->parent;
|
|
WScreen *scr = wScreenWithNumber(0);
|
|
wSetFocusTo(scr, wwin);
|
|
/* Raise entire window assembly via tree */
|
|
if (view->frame_id) {
|
|
struct wl_frame_buf *fb = frame_buf_find(view->frame_id);
|
|
if (fb && fb->tree)
|
|
wlr_scene_node_raise_to_top(&fb->tree->node);
|
|
} else if (view->scene_node) {
|
|
wlr_scene_node_raise_to_top(&view->scene_node->node);
|
|
}
|
|
/* Also raise it in our toplevel list so view_surface_at()
|
|
* returns it first on subsequent hits. */
|
|
wl_list_remove(&view->link);
|
|
wl_list_insert(&wl_state.toplevel_list, &view->link);
|
|
}
|
|
} else {
|
|
double _sx, _sy;
|
|
struct wl_toplevel_view *_tv = NULL;
|
|
view_surface_at(wl_state.pointer_x, wl_state.pointer_y,
|
|
&_sx, &_sy, &_tv);
|
|
if (!_tv) {
|
|
/* No toplevel surface — check if a WINGs dialog frame_buf was clicked */
|
|
struct wl_frame_buf *hit = frame_buf_hit(wl_state.pointer_x,
|
|
wl_state.pointer_y);
|
|
if (hit) {
|
|
WNativeWindow lookup = hit->parent_id ? hit->parent_id : hit->id;
|
|
WObjDescriptor *desc =
|
|
wm_backend->context_find(lookup, WM_CTX_CLIENT_WIN);
|
|
if (desc && desc->parent_type == WCLASS_WINDOW) {
|
|
WWindow *wwin = (WWindow *)desc->parent;
|
|
WScreen *scr = wScreenWithNumber(0);
|
|
wSetFocusTo(scr, wwin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WMScreen *wmscr = wl_get_wmscreen();
|
|
if (wmscr) {
|
|
WMEvent wev;
|
|
memset(&wev, 0, sizeof(wev));
|
|
wev.type = (ev->state == WLR_BUTTON_PRESSED)
|
|
? WME_BUTTON_PRESS : WME_BUTTON_RELEASE;
|
|
|
|
/* Hit-test: prefer scene-local coordinates when a client/view surface
|
|
* is under the pointer. Fall back to frame_buf-only hit testing for
|
|
* titlebars, buttons, resizebars, menus, and other WM-internal nodes. */
|
|
{
|
|
double _sx = 0.0, _sy = 0.0;
|
|
struct wl_toplevel_view *_tv = NULL;
|
|
struct wlr_surface *_surf = view_surface_at(wl_state.pointer_x, wl_state.pointer_y,
|
|
&_sx, &_sy, &_tv);
|
|
if (_surf && _tv) {
|
|
struct wl_frame_buf *_wtb = NULL;
|
|
wl_list_for_each(_wtb, &wl_state.frame_list, link) {
|
|
if (_wtb->id >= 0x20000000 &&
|
|
wl_state.pointer_x >= _wtb->x &&
|
|
wl_state.pointer_x < _wtb->x + _wtb->width &&
|
|
wl_state.pointer_y >= _wtb->y &&
|
|
wl_state.pointer_y < _wtb->y + _wtb->height) {
|
|
break;
|
|
}
|
|
}
|
|
WObjDescriptor *_wd = wm_backend->context_find(_wtb->id, WM_CTX_CLIENT_WIN);
|
|
wev.window = _wd ? _wtb->id : _tv->id;
|
|
wev.u.button.window = wev.window;
|
|
wev.u.button.x = (int)_sx;
|
|
wev.u.button.y = (int)_sy;
|
|
} else {
|
|
struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x,
|
|
wl_state.pointer_y);
|
|
if (fb) {
|
|
wev.window = fb->id;
|
|
wev.u.button.window = fb->id;
|
|
wev.u.button.x = wl_state.pointer_x - fb->x;
|
|
wev.u.button.y = wl_state.pointer_y - fb->y;
|
|
} else {
|
|
WScreen *scr0 = wScreenWithNumber(0);
|
|
WNativeWindow root = scr0 ? scr0->root_win : (WNativeWindow)0x0FFFFFFF;
|
|
wev.window = root;
|
|
wev.u.button.window = root;
|
|
wev.u.button.x = wl_state.pointer_x;
|
|
wev.u.button.y = wl_state.pointer_y;
|
|
}
|
|
}
|
|
}
|
|
wev.u.button.time = ev->time_msec;
|
|
wev.u.button.x_root = wl_state.pointer_x;
|
|
wev.u.button.y_root = wl_state.pointer_y;
|
|
/* Linux evdev BTN_LEFT=0x110, BTN_RIGHT=0x111, BTN_MIDDLE=0x112
|
|
* Button1=1(left), Button2=2(middle), Button3=3(right) */
|
|
switch (ev->button) {
|
|
case 0x110: wev.u.button.button = 1; break;
|
|
case 0x111: wev.u.button.button = 3; break;
|
|
case 0x112: wev.u.button.button = 2; break;
|
|
default: wev.u.button.button = (ev->button - 0x110) + 1; break;
|
|
}
|
|
/* Track button mask for motion events (WM_BUTTON*_MASK = 0x100<<n) */
|
|
unsigned int bmask = (1u << 8) << (wev.u.button.button - 1);
|
|
if (ev->state == WLR_BUTTON_PRESSED)
|
|
wl_state.current_buttons |= bmask;
|
|
else
|
|
wl_state.current_buttons &= ~bmask;
|
|
wev.u.button.state = wl_state.current_mods | wl_state.current_buttons;
|
|
/* For WINGs views, resolve the child widget at click coordinates.
|
|
* Two cases:
|
|
* 1. fb->id >= 0x20000000 — fb IS a WINGs view frame_buf, use directly.
|
|
* 2. fb->id < 0x20000000 but has a WWindow with client_leader —
|
|
* the fb is a WM fake_leader; look up the WINGs panel via leader. */
|
|
struct W_View *target = NULL;
|
|
WNativeWindow wings_id = 0;
|
|
struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x,
|
|
wl_state.pointer_y);
|
|
if (fb && fb->id >= 0x20000000) {
|
|
wings_id = fb->id;
|
|
} else if (fb) {
|
|
/* Try to find a WWindow whose client_leader is a WINGs view */
|
|
WObjDescriptor *desc = wm_backend->context_find(fb->id, WM_CTX_CLIENT_WIN);
|
|
if (desc && desc->parent_type == WCLASS_WINDOW) {
|
|
WWindow *wwin = (WWindow *)desc->parent;
|
|
if (wwin->client_leader >= 0x20000000)
|
|
wings_id = wwin->client_leader;
|
|
}
|
|
}
|
|
if (wings_id) {
|
|
extern W_View *W_GetViewForWindow(unsigned long id);
|
|
extern struct W_View *W_FindViewAtPoint(struct W_View *parent, int x, int y);
|
|
W_View *toplevel = W_GetViewForWindow((unsigned long)wings_id);
|
|
if (toplevel) {
|
|
target = W_FindViewAtPoint(toplevel,
|
|
wev.u.button.x, wev.u.button.y);
|
|
if (!target)
|
|
target = toplevel;
|
|
/* Adjust event coordinates to target view's local space */
|
|
if (target != toplevel) {
|
|
W_View *p = target;
|
|
int ox = 0, oy = 0;
|
|
while (p && p != toplevel) {
|
|
ox += p->pos.x;
|
|
oy += p->pos.y;
|
|
p = p->parent;
|
|
}
|
|
wev.u.button.x -= ox;
|
|
wev.u.button.y -= oy;
|
|
}
|
|
}
|
|
}
|
|
W_WL_EnqueueEvent(wmscr, &wev, target);
|
|
}
|
|
|
|
/* Refresh pointer focus before delivering button event.
|
|
* The last wl_update_pointer_focus call was during the most recent
|
|
* motion event. If no motion happened since then, the XWayland
|
|
* surface might not have pointer focus, causing
|
|
* wlr_seat_pointer_notify_button to be a no-op. */
|
|
wl_update_pointer_focus(ev->time_msec);
|
|
|
|
/* Suppress forwarding to the client when the WM modifier key is held
|
|
* on a button press over a client surface. In this case frameMouseDown
|
|
* will handle the event (move or resize), and the app must not see it. */
|
|
Bool forward_to_client = True;
|
|
if (ev->state == WLR_BUTTON_PRESSED && wl_state.seat) {
|
|
if (wl_state.current_mods & wPreferences.modifier_mask) {
|
|
double _sx, _sy;
|
|
struct wl_toplevel_view *_tv = NULL;
|
|
struct wlr_surface *_surf = view_surface_at(
|
|
wl_state.pointer_x, wl_state.pointer_y, &_sx, &_sy, &_tv);
|
|
if (_surf && _tv)
|
|
forward_to_client = False;
|
|
}
|
|
}
|
|
|
|
if (forward_to_client &&
|
|
(wl_state.seat->pointer_state.focused_surface ||
|
|
ev->state == WLR_BUTTON_RELEASED)) {
|
|
wlr_seat_pointer_notify_button(wl_state.seat, ev->time_msec,
|
|
ev->button, ev->state);
|
|
wlr_seat_pointer_notify_frame(wl_state.seat);
|
|
}
|
|
}
|
|
|
|
void
|
|
handle_pointer_frame(struct wl_listener *listener, void *data)
|
|
{
|
|
(void)listener; (void)data;
|
|
if (wl_state.seat)
|
|
wlr_seat_pointer_notify_frame(wl_state.seat);
|
|
}
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Keyboard grabs */
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Screen internals */
|
|
|
|
int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 1; }
|