aboutsummaryrefslogtreecommitdiffstats
path: root/unix/xserver/hw/vnc/vncHooks.c
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2017-02-13 13:47:37 +0100
committerPierre Ossman <ossman@cendio.se>2017-02-13 13:47:37 +0100
commit84c72feb3999339fc6529b4b0abae9b50fee4245 (patch)
tree3fe2730da67bfe98921c38b924fcaeea70b5a16c /unix/xserver/hw/vnc/vncHooks.c
parentb6c46a1a99a402d5d17b1afafc4784ce0958d6ec (diff)
downloadtigervnc-84c72feb3999339fc6529b4b0abae9b50fee4245.tar.gz
tigervnc-84c72feb3999339fc6529b4b0abae9b50fee4245.zip
Fix wrapping/unwrapping of X11 hooks
The functions might change so we need to also make sure we grab the updated value after each call. Clean up the code to use the same style as the rest of the Xorg code.
Diffstat (limited to 'unix/xserver/hw/vnc/vncHooks.c')
-rw-r--r--unix/xserver/hw/vnc/vncHooks.c244
1 files changed, 118 insertions, 126 deletions
diff --git a/unix/xserver/hw/vnc/vncHooks.c b/unix/xserver/hw/vnc/vncHooks.c
index 29f3f8b8..ef340b35 100644
--- a/unix/xserver/hw/vnc/vncHooks.c
+++ b/unix/xserver/hw/vnc/vncHooks.c
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2009-2015 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -73,22 +73,31 @@ typedef struct _vncHooksScreenRec {
GlyphsProcPtr Glyphs;
#endif
#ifdef RANDR
- RRSetConfigProcPtr RandRSetConfig;
- RRScreenSetSizeProcPtr RandRScreenSetSize;
- RRCrtcSetProcPtr RandRCrtcSet;
+ RRSetConfigProcPtr rrSetConfig;
+ RRScreenSetSizeProcPtr rrScreenSetSize;
+ RRCrtcSetProcPtr rrCrtcSet;
#endif
} vncHooksScreenRec, *vncHooksScreenPtr;
typedef struct _vncHooksGCRec {
#if XORG >= 116
- const GCFuncs *wrappedFuncs;
- const GCOps *wrappedOps;
+ const GCFuncs *funcs;
+ const GCOps *ops;
#else
- GCFuncs *wrappedFuncs;
- GCOps *wrappedOps;
+ GCFuncs *funcs;
+ GCOps *ops;
#endif
} vncHooksGCRec, *vncHooksGCPtr;
+#define wrap(priv, real, mem, func) {\
+ priv->mem = real->mem; \
+ real->mem = func; \
+}
+
+#define unwrap(priv, real, mem) {\
+ real->mem = priv->mem; \
+}
+
#if XORG < 19
static int vncHooksScreenPrivateKeyIndex;
static int vncHooksGCPrivateKeyIndex;
@@ -284,55 +293,28 @@ int vncHooksInit(int scrIdx)
vncHooksScreen->ignoreHooks = 0;
- vncHooksScreen->CloseScreen = pScreen->CloseScreen;
- vncHooksScreen->CreateGC = pScreen->CreateGC;
- vncHooksScreen->CopyWindow = pScreen->CopyWindow;
- vncHooksScreen->ClearToBackground = pScreen->ClearToBackground;
+ wrap(vncHooksScreen, pScreen, CloseScreen, vncHooksCloseScreen);
+ wrap(vncHooksScreen, pScreen, CreateGC, vncHooksCreateGC);
+ wrap(vncHooksScreen, pScreen, CopyWindow, vncHooksCopyWindow);
+ wrap(vncHooksScreen, pScreen, ClearToBackground, vncHooksClearToBackground);
#if XORG < 110
- vncHooksScreen->RestoreAreas = pScreen->RestoreAreas;
+ wrap(vncHooksScreen, pScreen, RestoreAreas, vncHooksRestoreAreas);
#endif
- vncHooksScreen->DisplayCursor = pScreen->DisplayCursor;
- vncHooksScreen->BlockHandler = pScreen->BlockHandler;
+ wrap(vncHooksScreen, pScreen, DisplayCursor, vncHooksDisplayCursor);
+ wrap(vncHooksScreen, pScreen, BlockHandler, vncHooksBlockHandler);
#ifdef RENDER
ps = GetPictureScreenIfSet(pScreen);
if (ps) {
- vncHooksScreen->Composite = ps->Composite;
- vncHooksScreen->Glyphs = ps->Glyphs;
+ wrap(vncHooksScreen, ps, Composite, vncHooksComposite);
+ wrap(vncHooksScreen, ps, Glyphs, vncHooksGlyphs);
}
#endif
#ifdef RANDR
rp = rrGetScrPriv(pScreen);
if (rp) {
- vncHooksScreen->RandRSetConfig = rp->rrSetConfig;
- vncHooksScreen->RandRScreenSetSize = rp->rrScreenSetSize;
- vncHooksScreen->RandRCrtcSet = rp->rrCrtcSet;
- }
-#endif
-
- pScreen->CloseScreen = vncHooksCloseScreen;
- pScreen->CreateGC = vncHooksCreateGC;
- pScreen->CopyWindow = vncHooksCopyWindow;
- pScreen->ClearToBackground = vncHooksClearToBackground;
-#if XORG < 110
- pScreen->RestoreAreas = vncHooksRestoreAreas;
-#endif
- pScreen->DisplayCursor = vncHooksDisplayCursor;
- pScreen->BlockHandler = vncHooksBlockHandler;
-#ifdef RENDER
- if (ps) {
- ps->Composite = vncHooksComposite;
- ps->Glyphs = vncHooksGlyphs;
- }
-#endif
-#ifdef RANDR
- if (rp) {
- /* Some RandR callbacks are optional */
- if (rp->rrSetConfig)
- rp->rrSetConfig = vncHooksRandRSetConfig;
- if (rp->rrScreenSetSize)
- rp->rrScreenSetSize = vncHooksRandRScreenSetSize;
- if (rp->rrCrtcSet)
- rp->rrCrtcSet = vncHooksRandRCrtcSet;
+ wrap(vncHooksScreen, rp, rrSetConfig, vncHooksRandRSetConfig);
+ wrap(vncHooksScreen, rp, rrScreenSetSize, vncHooksRandRScreenSetSize);
+ wrap(vncHooksScreen, rp, rrCrtcSet, vncHooksRandRCrtcSet);
}
#endif
@@ -433,15 +415,16 @@ static inline Bool is_visible(DrawablePtr drawable)
// screen functions
//
-// SCREEN_UNWRAP and SCREEN_REWRAP unwrap and rewrap the given screen function.
+// Unwrap and rewrap helpers
-#define SCREEN_UNWRAP(scrn,field) \
+#define SCREEN_PROLOGUE(scrn,field) \
ScreenPtr pScreen = scrn; \
vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen); \
- pScreen->field = vncHooksScreen->field; \
+ unwrap(vncHooksScreen, pScreen, field); \
DBGPRINT((stderr,"vncHooks" #field " called\n"));
-#define SCREEN_REWRAP(field) pScreen->field = vncHooks##field;
+#define SCREEN_EPILOGUE(field) \
+ wrap(vncHooksScreen, pScreen, field, vncHooks##field); \
// CloseScreen - unwrap the screen functions and call the original CloseScreen
@@ -460,29 +443,29 @@ static Bool vncHooksCloseScreen(ScreenPtr pScreen_)
rrScrPrivPtr rp;
#endif
- SCREEN_UNWRAP(pScreen_, CloseScreen);
+ SCREEN_PROLOGUE(pScreen_, CloseScreen);
- pScreen->CreateGC = vncHooksScreen->CreateGC;
- pScreen->CopyWindow = vncHooksScreen->CopyWindow;
- pScreen->ClearToBackground = vncHooksScreen->ClearToBackground;
+ unwrap(vncHooksScreen, pScreen, CreateGC);
+ unwrap(vncHooksScreen, pScreen, CopyWindow);
+ unwrap(vncHooksScreen, pScreen, ClearToBackground);
#if XORG < 110
- pScreen->RestoreAreas = vncHooksScreen->RestoreAreas;
+ unwrap(vncHooksScreen, pScreen, RestoreAreas);
#endif
- pScreen->DisplayCursor = vncHooksScreen->DisplayCursor;
- pScreen->BlockHandler = vncHooksScreen->BlockHandler;
+ unwrap(vncHooksScreen, pScreen, DisplayCursor);
+ unwrap(vncHooksScreen, pScreen, BlockHandler);
#ifdef RENDER
ps = GetPictureScreenIfSet(pScreen);
if (ps) {
- ps->Composite = vncHooksScreen->Composite;
- ps->Glyphs = vncHooksScreen->Glyphs;
+ unwrap(vncHooksScreen, ps, Composite);
+ unwrap(vncHooksScreen, ps, Glyphs);
}
#endif
#ifdef RANDR
rp = rrGetScrPriv(pScreen);
if (rp) {
- rp->rrSetConfig = vncHooksScreen->RandRSetConfig;
- rp->rrScreenSetSize = vncHooksScreen->RandRScreenSetSize;
- rp->rrCrtcSet = vncHooksScreen->RandRCrtcSet;
+ unwrap(vncHooksScreen, rp, rrSetConfig);
+ unwrap(vncHooksScreen, rp, rrScreenSetSize);
+ unwrap(vncHooksScreen, rp, rrCrtcSet);
}
#endif
@@ -502,15 +485,15 @@ static Bool vncHooksCreateGC(GCPtr pGC)
vncHooksGCPtr vncHooksGC = vncHooksGCPrivate(pGC);
Bool ret;
- SCREEN_UNWRAP(pGC->pScreen, CreateGC);
+ SCREEN_PROLOGUE(pGC->pScreen, CreateGC);
ret = (*pScreen->CreateGC) (pGC);
- vncHooksGC->wrappedOps = 0;
- vncHooksGC->wrappedFuncs = pGC->funcs;
+ vncHooksGC->ops = NULL;
+ vncHooksGC->funcs = pGC->funcs;
pGC->funcs = &vncHooksGCFuncs;
- SCREEN_REWRAP(CreateGC);
+ SCREEN_EPILOGUE(CreateGC);
return ret;
}
@@ -526,7 +509,7 @@ static void vncHooksCopyWindow(WindowPtr pWin, DDXPointRec ptOldOrg,
BoxRec screen_box;
RegionRec copied, screen_rgn;
- SCREEN_UNWRAP(pWin->drawable.pScreen, CopyWindow);
+ SCREEN_PROLOGUE(pWin->drawable.pScreen, CopyWindow);
REGION_NULL(pScreen, &copied);
REGION_COPY(pScreen, &copied, pOldRegion);
@@ -558,7 +541,7 @@ static void vncHooksCopyWindow(WindowPtr pWin, DDXPointRec ptOldOrg,
REGION_UNINIT(pScreen, &copied);
REGION_UNINIT(pScreen, &screen_rgn);
- SCREEN_REWRAP(CopyWindow);
+ SCREEN_EPILOGUE(CopyWindow);
}
// ClearToBackground - changed region is the given rectangle, clipped by
@@ -570,7 +553,7 @@ static void vncHooksClearToBackground(WindowPtr pWin, int x, int y, int w,
BoxRec box;
RegionRec reg;
- SCREEN_UNWRAP(pWin->drawable.pScreen, ClearToBackground);
+ SCREEN_PROLOGUE(pWin->drawable.pScreen, ClearToBackground);
box.x1 = x + pWin->drawable.x;
box.y1 = y + pWin->drawable.y;
@@ -588,7 +571,7 @@ static void vncHooksClearToBackground(WindowPtr pWin, int x, int y, int w,
REGION_UNINIT(pScreen, &reg);
- SCREEN_REWRAP(ClearToBackground);
+ SCREEN_EPILOGUE(ClearToBackground);
}
#if XORG < 110
@@ -598,7 +581,7 @@ static RegionPtr vncHooksRestoreAreas(WindowPtr pWin, RegionPtr pRegion)
{
RegionRec reg;
- SCREEN_UNWRAP(pWin->drawable.pScreen, RestoreAreas);
+ SCREEN_PROLOGUE(pWin->drawable.pScreen, RestoreAreas);
REGION_NULL(pScreen, &reg);
REGION_COPY(pScreen, &reg, pRegion);
@@ -609,7 +592,7 @@ static RegionPtr vncHooksRestoreAreas(WindowPtr pWin, RegionPtr pRegion)
REGION_UNINIT(pScreen, &reg);
- SCREEN_REWRAP(RestoreAreas);
+ SCREEN_EPILOGUE(RestoreAreas);
return result;
}
@@ -622,7 +605,7 @@ static Bool vncHooksDisplayCursor(DeviceIntPtr pDev,
{
Bool ret;
- SCREEN_UNWRAP(pScreen_, DisplayCursor);
+ SCREEN_PROLOGUE(pScreen_, DisplayCursor);
ret = (*pScreen->DisplayCursor) (pDev, pScreen, cursor);
@@ -707,7 +690,7 @@ static Bool vncHooksDisplayCursor(DeviceIntPtr pDev,
}
out:
- SCREEN_REWRAP(DisplayCursor);
+ SCREEN_EPILOGUE(DisplayCursor);
return ret;
}
@@ -726,9 +709,9 @@ static void vncHooksBlockHandler(ScreenPtr pScreen_, void * pTimeout)
#endif
{
#if XORG <= 112
- SCREEN_UNWRAP(screenInfo.screens[i], BlockHandler);
+ SCREEN_PROLOGUE(screenInfo.screens[i], BlockHandler);
#else
- SCREEN_UNWRAP(pScreen_, BlockHandler);
+ SCREEN_PROLOGUE(pScreen_, BlockHandler);
#endif
vncHooksScreen->ignoreHooks++;
@@ -743,23 +726,33 @@ static void vncHooksBlockHandler(ScreenPtr pScreen_, void * pTimeout)
vncHooksScreen->ignoreHooks--;
- SCREEN_REWRAP(BlockHandler);
+ SCREEN_EPILOGUE(BlockHandler);
}
#ifdef RENDER
+// Unwrap and rewrap helpers
+
+#define RENDER_PROLOGUE(scrn,field) \
+ ScreenPtr pScreen = scrn; \
+ PictureScreenPtr ps = GetPictureScreen(pScreen); \
+ vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen); \
+ unwrap(vncHooksScreen, ps, field); \
+ DBGPRINT((stderr,"vncHooks" #field " called\n"));
+
+#define RENDER_EPILOGUE(field) \
+ wrap(vncHooksScreen, ps, field, vncHooks##field); \
+
// Composite - The core of XRENDER
static void vncHooksComposite(CARD8 op, PicturePtr pSrc, PicturePtr pMask,
PicturePtr pDst, INT16 xSrc, INT16 ySrc, INT16 xMask,
INT16 yMask, INT16 xDst, INT16 yDst, CARD16 width, CARD16 height)
{
- ScreenPtr pScreen = pDst->pDrawable->pScreen;
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- PictureScreenPtr ps = GetPictureScreen(pScreen);
-
RegionRec changed;
+ RENDER_PROLOGUE(pDst->pDrawable->pScreen, Composite);
+
if (is_visible(pDst->pDrawable)) {
BoxRec box;
RegionRec fbreg;
@@ -784,15 +777,15 @@ static void vncHooksComposite(CARD8 op, PicturePtr pSrc, PicturePtr pMask,
}
- ps->Composite = vncHooksScreen->Composite;
(*ps->Composite)(op, pSrc, pMask, pDst, xSrc, ySrc,
xMask, yMask, xDst, yDst, width, height);
- ps->Composite = vncHooksComposite;
if (REGION_NOTEMPTY(pScreen, &changed))
add_changed(pScreen, &changed);
REGION_UNINIT(pScreen, &changed);
+
+ RENDER_EPILOGUE(Composite);
}
static int
@@ -849,12 +842,10 @@ static void vncHooksGlyphs(CARD8 op, PicturePtr pSrc, PicturePtr pDst,
PictFormatPtr maskFormat, INT16 xSrc, INT16 ySrc, int nlists,
GlyphListPtr lists, GlyphPtr * glyphs)
{
- ScreenPtr pScreen = pDst->pDrawable->pScreen;
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- PictureScreenPtr ps = GetPictureScreen(pScreen);
-
RegionPtr changed;
+ RENDER_PROLOGUE(pDst->pDrawable->pScreen, Glyphs);
+
if (is_visible(pDst->pDrawable)) {
BoxRec fbbox;
RegionRec fbreg;
@@ -876,37 +867,46 @@ static void vncHooksGlyphs(CARD8 op, PicturePtr pSrc, PicturePtr pDst,
changed = REGION_CREATE(pScreen, NullBox, 0);
}
- ps->Glyphs = vncHooksScreen->Glyphs;
(*ps->Glyphs)(op, pSrc, pDst, maskFormat, xSrc, ySrc, nlists, lists, glyphs);
- ps->Glyphs = vncHooksGlyphs;
if (REGION_NOTEMPTY(pScreen, changed))
add_changed(pScreen, changed);
REGION_DESTROY(pScreen, changed);
+
+ RENDER_EPILOGUE(Glyphs);
}
#endif /* RENDER */
-// RandRSetConfig - follow any framebuffer changes
-
#ifdef RANDR
+// Unwrap and rewrap helpers
+
+#define RANDR_PROLOGUE(field) \
+ rrScrPrivPtr rp = rrGetScrPriv(pScreen); \
+ vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen); \
+ unwrap(vncHooksScreen, rp, rr##field); \
+ DBGPRINT((stderr,"vncHooksRandR" #field " called\n"));
+
+#define RANDR_EPILOGUE(field) \
+ wrap(vncHooksScreen, rp, rr##field, vncHooksRandR##field); \
+
+// RandRSetConfig - follow any framebuffer changes
+
static Bool vncHooksRandRSetConfig(ScreenPtr pScreen, Rotation rotation,
int rate, RRScreenSizePtr pSize)
{
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- rrScrPrivPtr rp = rrGetScrPriv(pScreen);
Bool ret;
- vncPreScreenResize(pScreen->myNum);
+ RANDR_PROLOGUE(SetConfig);
- rp->rrSetConfig = vncHooksScreen->RandRSetConfig;
+ vncPreScreenResize(pScreen->myNum);
ret = (*rp->rrSetConfig)(pScreen, rotation, rate, pSize);
- rp->rrSetConfig = vncHooksRandRSetConfig;
-
vncPostScreenResize(pScreen->myNum, ret, pScreen->width, pScreen->height);
+ RANDR_EPILOGUE(SetConfig);
+
if (!ret)
return FALSE;
@@ -917,18 +917,16 @@ static Bool vncHooksRandRScreenSetSize(ScreenPtr pScreen,
CARD16 width, CARD16 height,
CARD32 mmWidth, CARD32 mmHeight)
{
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- rrScrPrivPtr rp = rrGetScrPriv(pScreen);
Bool ret;
- vncPreScreenResize(pScreen->myNum);
+ RANDR_PROLOGUE(ScreenSetSize);
- rp->rrScreenSetSize = vncHooksScreen->RandRScreenSetSize;
+ vncPreScreenResize(pScreen->myNum);
ret = (*rp->rrScreenSetSize)(pScreen, width, height, mmWidth, mmHeight);
- rp->rrScreenSetSize = vncHooksRandRScreenSetSize;
-
vncPostScreenResize(pScreen->myNum, ret, pScreen->width, pScreen->height);
+ RANDR_EPILOGUE(ScreenSetSize);
+
if (!ret)
return FALSE;
@@ -940,14 +938,14 @@ static Bool vncHooksRandRCrtcSet(ScreenPtr pScreen, RRCrtcPtr crtc,
Rotation rotation, int num_outputs,
RROutputPtr *outputs)
{
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- rrScrPrivPtr rp = rrGetScrPriv(pScreen);
Bool ret;
- rp->rrCrtcSet = vncHooksScreen->RandRCrtcSet;
+ RANDR_PROLOGUE(CrtcSet);
+
ret = (*rp->rrCrtcSet)(pScreen, crtc, mode, x, y, rotation,
num_outputs, outputs);
- rp->rrCrtcSet = vncHooksRandRCrtcSet;
+
+ RANDR_EPILOGUE(CrtcSet);
if (!ret)
return FALSE;
@@ -968,18 +966,13 @@ static Bool vncHooksRandRCrtcSet(ScreenPtr pScreen, RRCrtcPtr crtc,
#define GC_FUNC_PROLOGUE(pGC, name)\
vncHooksGCPtr pGCPriv = vncHooksGCPrivate(pGC);\
- (pGC)->funcs = pGCPriv->wrappedFuncs;\
- if(pGCPriv->wrappedOps)\
- (pGC)->ops = pGCPriv->wrappedOps; \
+ unwrap(pGCPriv, pGC, funcs);\
+ if (pGCPriv->ops) unwrap(pGCPriv, pGC, ops)\
DBGPRINT((stderr,"vncHooks" #name " called\n"))
#define GC_FUNC_EPILOGUE(pGC)\
- pGCPriv->wrappedFuncs = (pGC)->funcs;\
- (pGC)->funcs = &vncHooksGCFuncs;\
- if(pGCPriv->wrappedOps) {\
- pGCPriv->wrappedOps = (pGC)->ops;\
- (pGC)->ops = &vncHooksGCOps;\
- }
+ wrap(pGCPriv, pGC, funcs, &vncHooksGCFuncs);\
+ if (pGCPriv->ops) wrap(pGCPriv, pGC, ops, &vncHooksGCOps)
// ValidateGC - wrap the "ops" if the drawable is on screen
@@ -989,10 +982,10 @@ static void vncHooksValidateGC(GCPtr pGC, unsigned long changes,
GC_FUNC_PROLOGUE(pGC, ValidateGC);
(*pGC->funcs->ValidateGC) (pGC, changes, pDrawable);
if (is_visible(pDrawable)) {
- pGCPriv->wrappedOps = pGC->ops;
+ pGCPriv->ops = pGC->ops;
DBGPRINT((stderr,"vncHooksValidateGC: wrapped GC ops\n"));
} else {
- pGCPriv->wrappedOps = NULL;
+ pGCPriv->ops = NULL;
}
GC_FUNC_EPILOGUE(pGC);
}
@@ -1043,22 +1036,21 @@ static void vncHooksCopyClip(GCPtr dst, GCPtr src) {
#define GC_OP_PROLOGUE(pGC, name)\
vncHooksGCPtr pGCPriv = vncHooksGCPrivate(pGC);\
const GCFuncs *oldFuncs = pGC->funcs;\
- pGC->funcs = pGCPriv->wrappedFuncs;\
- pGC->ops = pGCPriv->wrappedOps; \
+ unwrap(pGCPriv, pGC, funcs);\
+ unwrap(pGCPriv, pGC, ops);\
DBGPRINT((stderr,"vncHooks" #name " called\n"))
#else
#define GC_OP_PROLOGUE(pGC, name)\
vncHooksGCPtr pGCPriv = vncHooksGCPrivate(pGC);\
GCFuncs *oldFuncs = pGC->funcs;\
- pGC->funcs = pGCPriv->wrappedFuncs;\
- pGC->ops = pGCPriv->wrappedOps; \
+ unwrap(pGCPriv, pGC, funcs);\
+ unwrap(pGCPriv, pGC, ops);\
DBGPRINT((stderr,"vncHooks" #name " called\n"))
#endif
#define GC_OP_EPILOGUE(pGC)\
- pGCPriv->wrappedOps = pGC->ops;\
- pGC->funcs = oldFuncs;\
- pGC->ops = &vncHooksGCOps
+ wrap(pGCPriv, pGC, funcs, oldFuncs); \
+ wrap(pGCPriv, pGC, ops, &vncHooksGCOps)
// FillSpans - assume the entire clip region is damaged. This is pessimistic,
// but I believe this function is rarely used so it doesn't matter.