feat: move Files to its own process; add window server messaging (multi-format clipboard, drag and drop), undo/redo, sidebar, breadcrumbs, thumbnails, file type icons, New menu and proper text fields
This commit is contained in:
1 parent
07191b1286
commit
d3e49a925d
66 files changed
+6855
-905
No files matched your search
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* DataTransfer.cpp
|
||||
* Multi-format payloads shared by the clipboard and drag and drop
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "DataTransfer.hpp"
|
||||
#include "UserMemory.hpp"
|
||||
#include <Memory/Heap.hpp>
|
||||
|
||||
namespace DataTransfer {
|
||||
|
||||
using namespace montauk::abi;
|
||||
|
||||
static bool MimeEquals(const char* a, const char* b) {
|
||||
int i = 0;
|
||||
for (; a[i] && b[i]; i++) {
|
||||
char ca = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] + 32 : a[i];
|
||||
char cb = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] + 32 : b[i];
|
||||
if (ca != cb) return false;
|
||||
}
|
||||
return a[i] == b[i];
|
||||
}
|
||||
|
||||
// Length of the "type/subtype" part, before any ";parameters".
|
||||
static int MimeBaseLen(const char* mime) {
|
||||
int n = 0;
|
||||
while (mime[n] && mime[n] != ';') n++;
|
||||
while (n > 0 && mime[n - 1] == ' ') n--;
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool MimeBaseEquals(const char* a, const char* b) {
|
||||
int la = MimeBaseLen(a);
|
||||
int lb = MimeBaseLen(b);
|
||||
if (la != lb) return false;
|
||||
for (int i = 0; i < la; i++) {
|
||||
char ca = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] + 32 : a[i];
|
||||
char cb = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] + 32 : b[i];
|
||||
if (ca != cb) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidMime(const char* mime) {
|
||||
if (mime == nullptr) return false;
|
||||
bool slash = false;
|
||||
uint32_t i = 0;
|
||||
for (; mime[i]; i++) {
|
||||
if (i + 1 >= TRANSFER_MIME_MAX) return false;
|
||||
char c = mime[i];
|
||||
if (c < 0x20 || c > 0x7E) return false;
|
||||
if (c == '/') slash = true;
|
||||
}
|
||||
return i > 0 && slash;
|
||||
}
|
||||
|
||||
void Free(Transfer& transfer) {
|
||||
for (uint32_t i = 0; i < transfer.count; i++) {
|
||||
if (transfer.formats[i].data != nullptr) Memory::g_heap->Free(transfer.formats[i].data);
|
||||
transfer.formats[i].data = nullptr;
|
||||
transfer.formats[i].len = 0;
|
||||
}
|
||||
transfer.count = 0;
|
||||
}
|
||||
|
||||
static int AddFormat(Transfer& transfer, const char* mime, const uint8_t* data, uint32_t len) {
|
||||
if (transfer.count >= TRANSFER_MAX_FORMATS) return InvalidArgument;
|
||||
Format& f = transfer.formats[transfer.count];
|
||||
uint32_t m = 0;
|
||||
for (; mime[m] && m + 1 < TRANSFER_MIME_MAX; m++) f.mime[m] = mime[m];
|
||||
f.mime[m] = '\0';
|
||||
|
||||
// Never hand out a null pointer for an empty format; one byte keeps
|
||||
// "present but empty" distinct from "absent".
|
||||
f.data = (uint8_t*)Memory::g_heap->Request(len > 0 ? len : 1);
|
||||
if (f.data == nullptr) return OutOfMemory;
|
||||
for (uint32_t i = 0; i < len; i++) f.data[i] = data[i];
|
||||
f.len = len;
|
||||
transfer.count++;
|
||||
return Success;
|
||||
}
|
||||
|
||||
int BuildFromUser(const TransferItem* items, uint32_t count, Transfer& out) {
|
||||
out.count = 0;
|
||||
if (items == nullptr || count == 0 || count > TRANSFER_MAX_FORMATS) return InvalidArgument;
|
||||
if (Memory::g_heap == nullptr) return OutOfMemory;
|
||||
|
||||
// Validate everything before allocating anything.
|
||||
uint64_t total = 0;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (!UserMemory::String(items[i].mime, TRANSFER_MIME_MAX)) return BadPointer;
|
||||
if (!ValidMime((const char*)items[i].mime)) return InvalidArgument;
|
||||
if (items[i].len > 0 && !UserMemory::Range(items[i].data, items[i].len, false)) return BadPointer;
|
||||
total += items[i].len;
|
||||
for (uint32_t j = 0; j < i; j++) {
|
||||
if (MimeEquals((const char*)items[i].mime, (const char*)items[j].mime))
|
||||
return InvalidArgument;
|
||||
}
|
||||
}
|
||||
if (total > TRANSFER_MAX_BYTES) return InvalidArgument;
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
int rc = AddFormat(out, (const char*)items[i].mime,
|
||||
(const uint8_t*)items[i].data, items[i].len);
|
||||
if (rc != Success) {
|
||||
Free(out);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
int BuildText(const char* text, uint32_t len, Transfer& out) {
|
||||
out.count = 0;
|
||||
if (Memory::g_heap == nullptr) return OutOfMemory;
|
||||
int rc = AddFormat(out, TRANSFER_MIME_TEXT, (const uint8_t*)text, len);
|
||||
if (rc != Success) Free(out);
|
||||
return rc;
|
||||
}
|
||||
|
||||
const Format* Find(const Transfer& transfer, const char* mime) {
|
||||
if (mime == nullptr) return nullptr;
|
||||
for (uint32_t i = 0; i < transfer.count; i++) {
|
||||
if (MimeEquals(transfer.formats[i].mime, mime)) return &transfer.formats[i];
|
||||
}
|
||||
bool has_params = false;
|
||||
for (int i = 0; mime[i]; i++) {
|
||||
if (mime[i] == ';') { has_params = true; break; }
|
||||
}
|
||||
if (has_params) return nullptr;
|
||||
for (uint32_t i = 0; i < transfer.count; i++) {
|
||||
if (MimeBaseEquals(transfer.formats[i].mime, mime)) return &transfer.formats[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t ListFormats(const Transfer& transfer, TransferFormatInfo* out, uint32_t max) {
|
||||
uint32_t n = 0;
|
||||
for (; n < transfer.count && n < max; n++) {
|
||||
for (uint32_t c = 0; c < TRANSFER_MIME_MAX; c++) out[n].mime[c] = transfer.formats[n].mime[c];
|
||||
out[n].len = transfer.formats[n].len;
|
||||
out[n]._reserved = 0;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int CopyOut(const Format& format, void* buffer, uint32_t bufferLen, uint32_t* outLen) {
|
||||
if (outLen != nullptr) *outLen = format.len;
|
||||
if (format.len == 0) return Success;
|
||||
if (buffer == nullptr || bufferLen < format.len) return BufferTooSmall;
|
||||
uint8_t* dst = (uint8_t*)buffer;
|
||||
for (uint32_t i = 0; i < format.len; i++) dst[i] = format.data[i];
|
||||
return Success;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user