From 8a3401c94c267a9bca29563a0960e0d6473c4c34 Mon Sep 17 00:00:00 2001 From: "Enrico Weigelt, metux IT consult" Date: Mon, 17 Mar 2025 18:26:21 +0100 Subject: [PATCH] dix: add selection filtering hooks Add hooks for filtering and fully rewrite rewrite selection requests and events (what existing XACE hooks cannot do), e.g. for supporting separate selection name spaces. The hook can change individual fields in the parameter struct, so operation continues with these changed values (eg. replace the original selection name atom by a different one). It's also possible to stop operations completely (with given result code) - in that case the hook needs to take care of the remaining work to do (eg. sending events) Signed-off-by: Enrico Weigelt, metux IT consult --- dix/selection.c | 176 ++++++++++++++++++++++++++++++++----------- dix/selection_priv.h | 37 +++++++++ xfixes/select.c | 43 +++++++++-- 3 files changed, 205 insertions(+), 51 deletions(-) create mode 100644 dix/selection_priv.h diff --git a/dix/selection.c b/dix/selection.c index ec9b4e85b..c368487a8 100644 --- a/dix/selection.c +++ b/dix/selection.c @@ -47,11 +47,11 @@ SOFTWARE. #include #include "dix/dix_priv.h" +#include "dix/selection_priv.h" #include "windowstr.h" #include "dixstruct.h" #include "dispatch.h" -#include "selection.h" #include "xace.h" /***************************************************************** @@ -66,6 +66,7 @@ SOFTWARE. Selection *CurrentSelections; CallbackListPtr SelectionCallback; +CallbackListPtr SelectionFilterCallback = NULL; int dixLookupSelection(Selection ** result, Atom selectionName, @@ -171,12 +172,27 @@ ProcSetSelectionOwner(ClientPtr client) if (CompareTimeStamps(time, currentTime) == LATER) return Success; - if (stuff->window != None) { - rc = dixLookupWindow(&pWin, stuff->window, client, DixSetAttrAccess); + /* allow extensions to intercept */ + SelectionFilterParamRec param = { + .client = client, + .selection = stuff->selection, + .owner = stuff->window, + .op = SELECTION_FILTER_SETOWNER, + }; + CallCallbacks(&SelectionFilterCallback, ¶m); + if (param.skip) { + if (param.status != Success) + client->errorValue = stuff->selection; + return param.status; + } + + if (param.owner != None) { + rc = dixLookupWindow(&pWin, param.owner, client, DixSetAttrAccess); if (rc != Success) return rc; } - if (!ValidAtom(stuff->selection)) { + + if (!ValidAtom(param.selection)) { client->errorValue = stuff->selection; return BadAtom; } @@ -184,10 +200,11 @@ ProcSetSelectionOwner(ClientPtr client) /* * First, see if the selection is already set... */ - rc = dixLookupSelection(&pSel, stuff->selection, client, DixSetAttrAccess); - - if (rc != Success) + rc = dixLookupSelection(&pSel, param.selection, client, DixSetAttrAccess); + if (rc != Success) { + client->errorValue = stuff->selection; return rc; + } /* If the timestamp in client's request is in the past relative to the time stamp indicating the last time the owner of the @@ -196,17 +213,27 @@ ProcSetSelectionOwner(ClientPtr client) if (CompareTimeStamps(time, pSel->lastTimeChanged) == EARLIER) return Success; if (pSel->client && (!pWin || (pSel->client != client))) { - xEvent event = { - .u.selectionClear.time = time.milliseconds, - .u.selectionClear.window = pSel->window, - .u.selectionClear.atom = pSel->selection + SelectionFilterParamRec eventParam = { + .client = client, + .recvClient = pSel->client, + .owner = pSel->window, + .selection = stuff->selection, + .op = SELECTION_FILTER_EV_CLEAR, }; - event.u.u.type = SelectionClear; - WriteEventsToClient(pSel->client, 1, &event); + CallCallbacks(&SelectionFilterCallback, &eventParam); + if (!param.skip) { + xEvent event = { + .u.selectionClear.time = time.milliseconds, + .u.selectionClear.window = eventParam.owner, + .u.selectionClear.atom = eventParam.selection, + }; + event.u.u.type = SelectionClear; + WriteEventsToClient(eventParam.recvClient, 1, &event); + } } pSel->lastTimeChanged = time; - pSel->window = stuff->window; + pSel->window = param.owner; pSel->pWin = pWin; pSel->client = (pWin ? client : NullClient); @@ -217,16 +244,26 @@ ProcSetSelectionOwner(ClientPtr client) int ProcGetSelectionOwner(ClientPtr client) { - int rc; Selection *pSel; xGetSelectionOwnerReply reply; REQUEST(xResourceReq); REQUEST_SIZE_MATCH(xResourceReq); - if (!ValidAtom(stuff->id)) { - client->errorValue = stuff->id; - return BadAtom; + /* allow extensions to intercept */ + SelectionFilterParamRec param = { + .client = client, + .selection = stuff->id, + .op = SELECTION_FILTER_GETOWNER, + }; + CallCallbacks(&SelectionFilterCallback, ¶m); + if (param.skip) { + goto out; + } + + if (!ValidAtom(param.selection)) { + param.status = BadAtom; + goto out; } reply = (xGetSelectionOwnerReply) { @@ -235,16 +272,21 @@ ProcGetSelectionOwner(ClientPtr client) .length = 0, }; - rc = dixLookupSelection(&pSel, stuff->id, client, DixGetAttrAccess); - if (rc == Success) + param.status = dixLookupSelection(&pSel, param.selection, param.client, DixGetAttrAccess); + if (param.status == Success) reply.owner = pSel->window; - else if (rc == BadMatch) + else if (param.status == BadMatch) reply.owner = None; else - return rc; + goto out; WriteReplyToClient(client, sizeof(xGetSelectionOwnerReply), &reply); return Success; + +out: + if (param.status != Success) + client->errorValue = stuff->id; + return param.status; } int @@ -259,12 +301,29 @@ ProcConvertSelection(ClientPtr client) REQUEST(xConvertSelectionReq); REQUEST_SIZE_MATCH(xConvertSelectionReq); - rc = dixLookupWindow(&pWin, stuff->requestor, client, DixSetAttrAccess); + /* allow extensions to intercept */ + SelectionFilterParamRec param = { + .client = client, + .selection = stuff->selection, + .op = SELECTION_FILTER_CONVERT, + .requestor = stuff->requestor, + .property = stuff->property, + .target = stuff->target, + .time = stuff->time, + }; + CallCallbacks(&SelectionFilterCallback, ¶m); + if (param.skip) { + if (param.status != Success) + client->errorValue = stuff->selection; + return param.status; + } + + rc = dixLookupWindow(&pWin, param.requestor, client, DixSetAttrAccess); if (rc != Success) return rc; - paramsOkay = ValidAtom(stuff->selection) && ValidAtom(stuff->target); - paramsOkay &= (stuff->property == None) || ValidAtom(stuff->property); + paramsOkay = ValidAtom(param.selection) && ValidAtom(param.target); + paramsOkay &= (param.property == None) || ValidAtom(param.property); if (!paramsOkay) { client->errorValue = stuff->property; return BadAtom; @@ -273,32 +332,63 @@ ProcConvertSelection(ClientPtr client) if (stuff->time == CurrentTime) UpdateCurrentTime(); - rc = dixLookupSelection(&pSel, stuff->selection, client, DixReadAccess); + rc = dixLookupSelection(&pSel, param.selection, client, DixReadAccess); memset(&event, 0, sizeof(xEvent)); if (rc != Success && rc != BadMatch) return rc; - else if (rc == Success && pSel->window != None) { - event.u.u.type = SelectionRequest; - event.u.selectionRequest.owner = pSel->window; - event.u.selectionRequest.time = stuff->time; - event.u.selectionRequest.requestor = stuff->requestor; - event.u.selectionRequest.selection = stuff->selection; - event.u.selectionRequest.target = stuff->target; - event.u.selectionRequest.property = stuff->property; - if (pSel->client && pSel->client != serverClient && - !pSel->client->clientGone) { - WriteEventsToClient(pSel->client, 1, &event); - return Success; + + /* If the specified selection has an owner, the X server sends + SelectionRequest event to that owner */ + if (rc == Success && pSel->window != None && pSel->client && + pSel->client != serverClient && !pSel->client->clientGone) + { + SelectionFilterParamRec evParam = { + .client = client, + .selection = stuff->selection, + .op = SELECTION_FILTER_EV_REQUEST, + .owner = pSel->window, + .requestor = stuff->requestor, + .property = stuff->property, + .target = stuff->target, + .time = stuff->time, + .recvClient = pSel->client, + }; + + CallCallbacks(&SelectionFilterCallback, &evParam); + if (evParam.skip) { + if (evParam.status != Success) + client->errorValue = stuff->selection; + return evParam.status; } + + event.u.u.type = SelectionRequest; + event.u.selectionRequest.owner = evParam.owner; + event.u.selectionRequest.time = evParam.time; + event.u.selectionRequest.requestor = evParam.requestor; + event.u.selectionRequest.selection = evParam.selection; + event.u.selectionRequest.target = evParam.target; + event.u.selectionRequest.property = evParam.property; + WriteEventsToClient(evParam.recvClient, 1, &event); + return Success; + } + + /* If no owner for the specified selection exists, the X server generates + a SelectionNotify event to the requestor with property None. */ + param.property = None; + CallCallbacks(&SelectionFilterCallback, ¶m); + if (param.skip) { + if (param.status != Success) + client->errorValue = stuff->selection; + return param.status; } event.u.u.type = SelectionNotify; - event.u.selectionNotify.time = stuff->time; - event.u.selectionNotify.requestor = stuff->requestor; - event.u.selectionNotify.selection = stuff->selection; - event.u.selectionNotify.target = stuff->target; - event.u.selectionNotify.property = None; + event.u.selectionNotify.time = param.time; + event.u.selectionNotify.requestor = param.requestor; + event.u.selectionNotify.selection = param.selection; + event.u.selectionNotify.target = param.target; + event.u.selectionNotify.property = param.property; WriteEventsToClient(client, 1, &event); return Success; } diff --git a/dix/selection_priv.h b/dix/selection_priv.h new file mode 100644 index 000000000..4cbcf374d --- /dev/null +++ b/dix/selection_priv.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: MIT OR X11 + * + * Copyright © 2024 Enrico Weigelt, metux IT consult + */ +#ifndef _XSERVER_DIX_SELECTION_PRIV_H +#define _XSERVER_DIX_SELECTION_PRIV_H + +#include +#include + +#include "include/selection.h" + +#define SELECTION_FILTER_GETOWNER 1 +#define SELECTION_FILTER_SETOWNER 2 +#define SELECTION_FILTER_CONVERT 3 +#define SELECTION_FILTER_LISTEN 4 +#define SELECTION_FILTER_EV_REQUEST 5 +#define SELECTION_FILTER_EV_CLEAR 6 +#define SELECTION_FILTER_NOTIFY 7 + +typedef struct { + int op; + Bool skip; + int status; + Atom selection; + ClientPtr client; // initiating client + ClientPtr recvClient; // client receiving event + Time time; // request time stamp + Window requestor; + Window owner; + Atom property; + Atom target; +} SelectionFilterParamRec, *SelectionFilterParamPtr; + +extern CallbackListPtr SelectionFilterCallback; + +#endif /* _XSERVER_DIX_SELECTION_PRIV_H */ diff --git a/xfixes/select.c b/xfixes/select.c index 553a9ebea..975113640 100644 --- a/xfixes/select.c +++ b/xfixes/select.c @@ -23,6 +23,7 @@ #include #include "dix/dix_priv.h" +#include "dix/selection_priv.h" #include "xfixesint.h" #include "xace.h" @@ -78,13 +79,25 @@ XFixesSelectionCallback(CallbackListPtr *callbacks, void *data, void *args) UpdateCurrentTimeIf(); for (e = selectionEvents; e; e = e->next) { if (e->selection == selection && (e->eventMask & eventMask)) { + + /* allow extensions to intercept */ + SelectionFilterParamRec param = { + .client = e->pClient, + .selection = selection->selection, + .owner = (subtype == XFixesSetSelectionOwnerNotify) ? + selection->window : 0, + .op = SELECTION_FILTER_NOTIFY, + }; + CallCallbacks(&SelectionFilterCallback, ¶m); + if (param.skip) + continue; + xXFixesSelectionNotifyEvent ev = { .type = XFixesEventBase + XFixesSelectionNotify, .subtype = subtype, .window = e->pWindow->drawable.id, - .owner = (subtype == XFixesSetSelectionOwnerNotify) ? - selection->window : 0, - .selection = e->selection->selection, + .owner = param.owner, + .selection = param.selection, .timestamp = currentTime.milliseconds, .selectionTimestamp = selection->lastTimeChanged.milliseconds }; @@ -122,8 +135,22 @@ ProcXFixesSelectSelectionInput(ClientPtr client) REQUEST(xXFixesSelectSelectionInputReq); REQUEST_SIZE_MATCH(xXFixesSelectSelectionInputReq); + /* allow extensions to intercept */ + SelectionFilterParamRec param = { + .client = client, + .selection = stuff->selection, + .owner = stuff->window, + .op = SELECTION_FILTER_LISTEN, + }; + CallCallbacks(&SelectionFilterCallback, ¶m); + if (param.skip) { + if (param.status != Success) + client->errorValue = param.selection; + return param.status; + } + WindowPtr pWindow; - int rc = dixLookupWindow(&pWindow, stuff->window, client, DixGetAttrAccess); + int rc = dixLookupWindow(&pWindow, param.owner, param.client, DixGetAttrAccess); if (rc != Success) return rc; if (stuff->eventMask & ~SelectionAllEvents) { @@ -135,13 +162,13 @@ ProcXFixesSelectSelectionInput(ClientPtr client) SelectionEventPtr *prev, e; Selection *selection; - rc = dixLookupSelection(&selection, stuff->selection, client, DixGetAttrAccess); + rc = dixLookupSelection(&selection, param.selection, param.client, DixGetAttrAccess); if (rc != Success) return rc; for (prev = &selectionEvents; (e = *prev); prev = &e->next) { if (e->selection == selection && - e->pClient == client && e->pWindow == pWindow) { + e->pClient == param.client && e->pWindow == pWindow) { break; } } @@ -158,9 +185,9 @@ ProcXFixesSelectSelectionInput(ClientPtr client) e->next = 0; e->selection = selection; - e->pClient = client; + e->pClient = param.client; e->pWindow = pWindow; - e->clientResource = FakeClientID(client->index); + e->clientResource = FakeClientID(param.client->index); /* * Add a resource hanging from the window to