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,237 @@
|
||||
/*
|
||||
* transfer.hpp
|
||||
* Multi-format clipboard, drag and drop, and window server requests for
|
||||
* MontaukOS GUI apps (the raw ABI is "Window server messaging" in
|
||||
* Api/Syscall.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
|
||||
namespace gui {
|
||||
|
||||
using montauk::abi::TransferItem;
|
||||
using montauk::abi::TransferFormatInfo;
|
||||
|
||||
// ---- Building a transfer ----------------------------------------------
|
||||
|
||||
// Collects the formats of one clipboard write or drag. Holds pointers
|
||||
// only: the strings and buffers passed to add() must outlive the call
|
||||
// that hands the transfer to the kernel (which copies them).
|
||||
struct TransferBuilder {
|
||||
TransferItem items[montauk::abi::TRANSFER_MAX_FORMATS];
|
||||
uint32_t count = 0;
|
||||
|
||||
bool add(const char* mime, const void* data, uint32_t len) {
|
||||
if (count >= montauk::abi::TRANSFER_MAX_FORMATS || !mime) return false;
|
||||
items[count].mime = (uint64_t)(uintptr_t)mime;
|
||||
items[count].data = (uint64_t)(uintptr_t)data;
|
||||
items[count].len = len;
|
||||
items[count]._reserved = 0;
|
||||
count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool add_text(const char* mime, const char* text) {
|
||||
return add(mime, text, text ? (uint32_t)montauk::slen(text) : 0);
|
||||
}
|
||||
};
|
||||
|
||||
// ---- Clipboard ---------------------------------------------------------
|
||||
|
||||
inline bool clipboard_set(const TransferBuilder& transfer) {
|
||||
return montauk::clipboard_set_data(transfer.items, transfer.count) == 0;
|
||||
}
|
||||
|
||||
inline bool clipboard_has_format(const char* mime) {
|
||||
uint32_t len = 0;
|
||||
int rc = montauk::clipboard_get_data(mime, nullptr, 0, &len);
|
||||
return rc == 0 || montauk::StatusFromResult(rc) == montauk::BufferTooSmall;
|
||||
}
|
||||
|
||||
inline uint64_t clipboard_serial() {
|
||||
uint64_t serial = 0;
|
||||
montauk::clipboard_formats(nullptr, 0, &serial);
|
||||
return serial;
|
||||
}
|
||||
|
||||
// Read a whole format into a new NUL-terminated buffer (montauk::mfree
|
||||
// it). Returns nullptr if the format is absent.
|
||||
inline char* clipboard_get_alloc(const char* mime, uint32_t* out_len = nullptr) {
|
||||
if (out_len) *out_len = 0;
|
||||
// The clipboard may change between asking for the size and the
|
||||
// copy; retry a few times rather than return a torn read.
|
||||
for (int attempt = 0; attempt < 4; attempt++) {
|
||||
uint32_t len = 0;
|
||||
int rc = montauk::clipboard_get_data(mime, nullptr, 0, &len);
|
||||
if (rc < 0 && montauk::StatusFromResult(rc) != montauk::BufferTooSmall) return nullptr;
|
||||
char* buf = (char*)montauk::malloc(len + 1);
|
||||
if (!buf) return nullptr;
|
||||
uint32_t got = 0;
|
||||
rc = montauk::clipboard_get_data(mime, buf, len, &got);
|
||||
if (rc == 0 && got <= len) {
|
||||
buf[got] = '\0';
|
||||
if (out_len) *out_len = got;
|
||||
return buf;
|
||||
}
|
||||
montauk::mfree(buf);
|
||||
if (montauk::StatusFromResult(rc) != montauk::BufferTooSmall) return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---- Drag and drop -----------------------------------------------------
|
||||
|
||||
// Start a drag from window `win_id` (call it on the mouse move that
|
||||
// crosses the drag threshold). Returns the drag serial, or 0 on failure.
|
||||
// The compositor then shows the drag, sends DRAG_ENTER/OVER/LEAVE and
|
||||
// DROP to the windows under the pointer, and DRAG_END back to this one.
|
||||
inline uint32_t drag_begin(int win_id, const TransferBuilder& transfer, uint8_t actions,
|
||||
uint32_t item_count, const char* label) {
|
||||
montauk::abi::WinDragBegin info = {};
|
||||
info.actions = actions;
|
||||
info.itemCount = item_count;
|
||||
if (label) montauk::strncpy(info.label, label, sizeof(info.label));
|
||||
int rc = montauk::win_drag_begin(win_id, &info, transfer.items, transfer.count);
|
||||
return rc > 0 ? (uint32_t)rc : 0;
|
||||
}
|
||||
|
||||
inline bool drag_has_format(uint32_t serial, const char* mime) {
|
||||
uint32_t len = 0;
|
||||
int rc = montauk::win_drag_get_data(serial, mime, nullptr, 0, &len);
|
||||
return rc == 0 || montauk::StatusFromResult(rc) == montauk::BufferTooSmall;
|
||||
}
|
||||
|
||||
// Read a whole drag format into a new NUL-terminated buffer (montauk::mfree
|
||||
// it). Allowed once this window has received a drag event for `serial`.
|
||||
inline char* drag_get_alloc(uint32_t serial, const char* mime, uint32_t* out_len = nullptr) {
|
||||
if (out_len) *out_len = 0;
|
||||
uint32_t len = 0;
|
||||
int rc = montauk::win_drag_get_data(serial, mime, nullptr, 0, &len);
|
||||
if (rc < 0 && montauk::StatusFromResult(rc) != montauk::BufferTooSmall) return nullptr;
|
||||
char* buf = (char*)montauk::malloc(len + 1);
|
||||
if (!buf) return nullptr;
|
||||
uint32_t got = 0;
|
||||
rc = montauk::win_drag_get_data(serial, mime, buf, len, &got);
|
||||
if (rc != 0 || got > len) {
|
||||
montauk::mfree(buf);
|
||||
return nullptr;
|
||||
}
|
||||
buf[got] = '\0';
|
||||
if (out_len) *out_len = got;
|
||||
return buf;
|
||||
}
|
||||
|
||||
// The conventional modifier mapping for a drop: Ctrl copies, Shift moves,
|
||||
// otherwise `preferred` (e.g. move within a volume, copy across them).
|
||||
// Falls back to whatever the source offers. 0 if nothing is possible.
|
||||
inline uint8_t drag_pick_action(uint8_t offered, uint8_t modifiers, uint8_t preferred) {
|
||||
using namespace montauk::abi;
|
||||
uint8_t want = preferred;
|
||||
if (modifiers & WIN_MOD_CTRL) want = WIN_DRAG_COPY;
|
||||
else if (modifiers & WIN_MOD_SHIFT) want = WIN_DRAG_MOVE;
|
||||
if (offered & want) return want;
|
||||
if (offered & WIN_DRAG_COPY) return WIN_DRAG_COPY;
|
||||
if (offered & WIN_DRAG_MOVE) return WIN_DRAG_MOVE;
|
||||
if (offered & WIN_DRAG_LINK) return WIN_DRAG_LINK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- File lists (TRANSFER_MIME_FILE_LIST) ------------------------------
|
||||
|
||||
// Calls fn(path) for each non-empty line of a file list, in place
|
||||
// (temporarily NUL-terminating each line). Returns the number of paths.
|
||||
template <typename Fn>
|
||||
inline int file_list_each(char* list, Fn fn) {
|
||||
int n = 0;
|
||||
char* p = list;
|
||||
while (p && *p) {
|
||||
char* end = p;
|
||||
while (*end && *end != '\n') end++;
|
||||
char saved = *end;
|
||||
*end = '\0';
|
||||
char* line = p;
|
||||
int len = (int)(end - p);
|
||||
if (len > 0 && line[len - 1] == '\r') line[len - 1] = '\0';
|
||||
if (line[0]) { fn((const char*)line); n++; }
|
||||
*end = saved;
|
||||
p = saved ? end + 1 : end;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// ---- Window server requests ---------------------------------------------
|
||||
|
||||
inline bool win_request_send(int win_id, uint16_t type, const void* payload, uint16_t size) {
|
||||
if (size > montauk::abi::WIN_REQUEST_PAYLOAD_MAX) return false;
|
||||
montauk::abi::WinRequest req = {};
|
||||
req.type = type;
|
||||
req.size = size;
|
||||
req.windowId = win_id;
|
||||
if (payload && size) montauk::memcpy(req.payload, payload, size);
|
||||
return montauk::win_request(win_id, &req) == 0;
|
||||
}
|
||||
|
||||
// Ask the desktop to open a catalog item (app manifest id or a built-in
|
||||
// such as "desktop-settings") as if the user had launched it.
|
||||
inline bool win_request_launch_item(int win_id, const char* item_id, const char* args = nullptr) {
|
||||
montauk::abi::WinReqLaunchItem p = {};
|
||||
montauk::strncpy(p.itemId, item_id ? item_id : "", sizeof(p.itemId));
|
||||
if (args) montauk::strncpy(p.args, args, sizeof(p.args));
|
||||
return win_request_send(win_id, montauk::abi::WIN_REQ_LAUNCH_ITEM, &p, sizeof(p));
|
||||
}
|
||||
|
||||
// Declare `win_id` a dialog of `parent_id` (same process): the desktop
|
||||
// centres it over the parent.
|
||||
inline bool win_request_set_parent(int win_id, int parent_id) {
|
||||
montauk::abi::WinReqSetParent p = { parent_id };
|
||||
return win_request_send(win_id, montauk::abi::WIN_REQ_SET_PARENT, &p, sizeof(p));
|
||||
}
|
||||
|
||||
// Bring one of this app's windows to the front (e.g. re-show an open
|
||||
// dialog instead of opening a second one).
|
||||
inline bool win_request_activate(int win_id) {
|
||||
return win_request_send(win_id, montauk::abi::WIN_REQ_ACTIVATE, nullptr, 0);
|
||||
}
|
||||
|
||||
// Answer DRAG_ENTER/DRAG_OVER with what a drop here would do (0 = refuse).
|
||||
inline bool win_request_drag_status(int win_id, uint32_t serial, uint8_t action) {
|
||||
montauk::abi::WinReqDragStatus p = {};
|
||||
p.serial = serial;
|
||||
p.action = action;
|
||||
return win_request_send(win_id, montauk::abi::WIN_REQ_DRAG_STATUS, &p, sizeof(p));
|
||||
}
|
||||
|
||||
// ---- File drops for document apps ----------------------------------------
|
||||
|
||||
// For document apps that open a dropped file. Call it for every window
|
||||
// event; it answers drag events (copy, if the drag carries files and
|
||||
// `accept` is true) and returns true on a drop, with the first dropped
|
||||
// path in out_path. Anything else returns false and can be handled as
|
||||
// usual.
|
||||
inline bool handle_file_drop(int win_id, const montauk::abi::WinEvent& ev,
|
||||
char* out_path, int out_max, bool accept = true) {
|
||||
using namespace montauk::abi;
|
||||
if (ev.type == WIN_EVENT_DRAG_ENTER || ev.type == WIN_EVENT_DRAG_OVER) {
|
||||
bool files = accept && drag_has_format(ev.drag.serial, TRANSFER_MIME_FILE_LIST);
|
||||
win_request_drag_status(win_id, ev.drag.serial, files ? WIN_DRAG_COPY : 0);
|
||||
return false;
|
||||
}
|
||||
if (ev.type != WIN_EVENT_DROP || !accept || !out_path || out_max <= 0) return false;
|
||||
char* list = drag_get_alloc(ev.drag.serial, TRANSFER_MIME_FILE_LIST);
|
||||
if (!list) return false;
|
||||
bool got = false;
|
||||
file_list_each(list, [&](const char* path) {
|
||||
if (got) return;
|
||||
montauk::strncpy(out_path, path, out_max);
|
||||
got = true;
|
||||
});
|
||||
montauk::mfree(list);
|
||||
return got;
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
Reference in new issue
Block a user