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 <info@metux.net>
This commit is contained in:
Enrico Weigelt, metux IT consult 2025-03-17 18:26:21 +01:00
parent e6467895f9
commit 16215b98d2
3 changed files with 189 additions and 50 deletions

View File

@ -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, &param);
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,15 +244,25 @@ ProcSetSelectionOwner(ClientPtr client)
int
ProcGetSelectionOwner(ClientPtr client)
{
int rc;
Selection *pSel;
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, &param);
if (param.skip) {
goto out;
}
if (!ValidAtom(param.selection)) {
param.status = BadAtom;
goto out;
}
xGetSelectionOwnerReply rep = {
@ -234,13 +271,13 @@ 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)
rep.owner = pSel->window;
else if (rc == BadMatch)
else if (param.status == BadMatch)
rep.owner = None;
else
return rc;
goto out;
if (client->swapped) {
swaps(&rep.sequenceNumber);
@ -249,6 +286,11 @@ ProcGetSelectionOwner(ClientPtr client)
WriteToClient(client, sizeof(rep), &rep);
return Success;
out:
if (param.status != Success)
client->errorValue = stuff->id;
return param.status;
}
int
@ -263,12 +305,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, &param);
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;
@ -277,32 +336,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, &param);
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;
}

View File

@ -34,10 +34,32 @@ typedef struct {
SelectionCallbackKind kind;
} SelectionInfoRec;
#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 Selection *CurrentSelections;
extern CallbackListPtr SelectionCallback;
extern CallbackListPtr SelectionFilterCallback;
int dixLookupSelection(Selection **result,
Atom name,

View File

@ -23,6 +23,7 @@
#include <dix-config.h>
#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, &param);
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, &param);
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