[client] clipboard: preserve formats after file import failure

Retain non-file clipboard representations while asynchronous file MIME
imports are validated. If an offered URI list is invalid or cannot be
read, publish the retained image and text formats instead of clearing
the clipboard.
This commit is contained in:
Geoffrey McRae
2026-08-21 13:32:25 +10:00
parent 423269802f
commit ccb80d6464
2 changed files with 64 additions and 42 deletions

View File

@@ -210,6 +210,17 @@ static bool hasImageMimetype(char ** mimetypes)
return false;
}
static size_t getRepresentationTypes(char * const mimetypes[],
LG_ClipboardData types[])
{
size_t count = 0;
for (enum LG_ClipboardData type = 0;
type < LG_CLIPBOARD_DATA_NONE; ++type)
if (type != LG_CLIPBOARD_DATA_FILES && mimetypes[type])
types[count++] = type;
return count;
}
enum ClipboardInvalidateMode
{
CLIPBOARD_INVALIDATE_CONDITIONAL,
@@ -307,6 +318,8 @@ static void fileImportCleanup(void * opaque)
static void fileImportCallback(uint32_t events, void * opaque)
{
struct ClipboardFileImport * import = opaque;
LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
size_t typeCount = 0;
bool complete = false;
bool failed = (events & EPOLLERR) != 0;
while (!failed)
@@ -337,6 +350,8 @@ static void fileImportCallback(uint32_t events, void * opaque)
LG_LOCK(wlCb.lock);
const bool current = wlCb.fileImport == import &&
wlCb.offer == import->offer;
if (current)
typeCount = getRepresentationTypes(wlCb.mimetypes, types);
if (wlCb.fileImport == import)
wlCb.fileImport = NULL;
LG_UNLOCK(wlCb.lock);
@@ -345,7 +360,7 @@ static void fileImportCallback(uint32_t events, void * opaque)
import->data, import->size)))
{
clipboardFiles_clearLocal();
clipboard_release();
clipboard_notifyTypes(types, typeCount);
}
if (current)
waylandPollUnregister(import->fd);
@@ -516,11 +531,9 @@ static void dataDeviceHandleSelection(void * opaque,
return;
}
size_t idx = 0;
enum LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
if (extra->mimetypes[i])
types[idx++] = i;
const size_t typeCount =
getRepresentationTypes(extra->mimetypes, types);
char * oldMimetypes[LG_CLIPBOARD_DATA_NONE];
LG_LOCK(wlCb.lock);
@@ -555,11 +568,11 @@ static void dataDeviceHandleSelection(void * opaque,
if (!fileImportStart(offer, wlCb.mimetypes[LG_CLIPBOARD_DATA_FILES]))
{
clipboardFiles_clearLocal();
clipboard_release();
clipboard_notifyTypes(types, typeCount);
}
}
else
clipboard_notifyTypes(types, idx);
clipboard_notifyTypes(types, typeCount);
}
static void dataDeviceHandleEnter(void * data, struct wl_data_device * device,

View File

@@ -84,6 +84,8 @@ struct X11ClipboardFileImport
size_t size;
size_t capacity;
bool incremental;
LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
size_t typeCount;
};
struct X11ClipboardState
@@ -858,7 +860,8 @@ static bool fileImportGrowNL(size_t wanted)
return true;
}
static bool startFileImport(Atom target)
static bool startFileImport(Atom target,
const LG_ClipboardData types[], size_t typeCount)
{
const char * mime = fileMimeForAtom(target);
if (!mime)
@@ -886,6 +889,8 @@ static bool startFileImport(Atom target)
.target = target,
.mime = mime,
};
memcpy(x11cb.fileImport.types, types, typeCount * sizeof(*types));
x11cb.fileImport.typeCount = typeCount;
XSelectInput(x11.display, window, PropertyChangeMask);
XConvertSelection(x11.display, x11cb.aCurSelection, target,
x11atoms.SEL_DATA, window, CurrentTime);
@@ -900,11 +905,18 @@ static void finishFileImport(struct X11ClipboardFileImport import,
if (!clipboardFiles_setLocal(import.mime, data, size))
{
clipboardFiles_clearLocal();
clipboard_release();
clipboard_notifyTypes(import.types, import.typeCount);
}
free(import.data);
}
static void failFileImport(struct X11ClipboardFileImport import)
{
free(import.data);
clipboardFiles_clearLocal();
clipboard_notifyTypes(import.types, import.typeCount);
}
static bool x11CBFileImportSelectionNotify(const XSelectionEvent e)
{
Atom type;
@@ -930,8 +942,7 @@ static bool x11CBFileImportSelectionNotify(const XSelectionEvent e)
LG_UNLOCK(x11cb.lock);
if (data)
XFree(data);
free(failed.data);
clipboard_release();
failFileImport(failed);
return true;
}
@@ -943,8 +954,7 @@ static bool x11CBFileImportSelectionNotify(const XSelectionEvent e)
LG_UNLOCK(x11cb.lock);
if (data)
XFree(data);
free(failed.data);
clipboard_release();
failFileImport(failed);
return true;
}
x11cb.fileImport.incremental = true;
@@ -960,10 +970,7 @@ static bool x11CBFileImportSelectionNotify(const XSelectionEvent e)
if (valid)
finishFileImport(import, data, itemCount);
else
{
free(import.data);
clipboard_release();
}
failFileImport(import);
if (data)
XFree(data);
return true;
@@ -1000,8 +1007,7 @@ readProperty:
LG_UNLOCK(x11cb.lock);
if (data)
XFree(data);
free(failed.data);
clipboard_release();
failFileImport(failed);
return;
}
@@ -1026,8 +1032,7 @@ readProperty:
{
const struct X11ClipboardFileImport failed = takeFileImportNL();
LG_UNLOCK(x11cb.lock);
free(failed.data);
clipboard_release();
failFileImport(failed);
return;
}
x11cb.fileImport.propertyOffset += (long)units;
@@ -1412,6 +1417,18 @@ static void x11CBSelectionNotify(const XSelectionEvent e)
// see if we support any of the targets listed
const Atom * targets = (const Atom *)data;
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
{
if (n == LG_CLIPBOARD_DATA_FILES)
continue;
for(unsigned long i = 0; i < itemCount; ++i)
if (x11cb.aTypes[n] == targets[i])
{
types[typeCount++] = n;
break;
}
}
Atom fileTarget = None;
for (unsigned long i = 0; i < itemCount; ++i)
{
@@ -1438,19 +1455,11 @@ static void x11CBSelectionNotify(const XSelectionEvent e)
if (fileTarget != None)
{
clipboardFiles_clearLocal();
if (!startFileImport(fileTarget))
clipboard_release();
if (!startFileImport(fileTarget, types, typeCount))
clipboard_notifyTypes(types, typeCount);
goto out;
}
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
for(unsigned long i = 0; i < itemCount; ++i)
if (x11cb.aTypes[n] == targets[i])
{
types[typeCount++] = n;
break;
}
clipboard_notifyTypes(types, typeCount);
goto out;
}