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
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 7
|
||||
#define MONTAUK_BUILD_NUMBER 16
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Clipboard.hpp
|
||||
* SYS_CLIPBOARD_* plain-text clipboard syscalls
|
||||
* SYS_CLIPBOARD_* syscalls: per-user multi-format clipboard
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
@@ -11,24 +11,25 @@
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
|
||||
#include "Syscall.hpp"
|
||||
#include "DataTransfer.hpp"
|
||||
|
||||
namespace montauk::abi {
|
||||
|
||||
static constexpr int ClipboardMaxSlots = 32;
|
||||
|
||||
// One clipboard per user. It holds a multi-format transfer; the text
|
||||
// syscalls read and replace its TRANSFER_MIME_TEXT format, so text-only
|
||||
// apps and multi-format apps share the same clipboard.
|
||||
struct ClipboardSlot {
|
||||
bool inUse;
|
||||
char user[32];
|
||||
char* text;
|
||||
uint32_t len;
|
||||
DataTransfer::Transfer transfer;
|
||||
uint64_t serial;
|
||||
};
|
||||
|
||||
static ClipboardSlot g_clipboardSlots[ClipboardMaxSlots];
|
||||
static kcp::Mutex g_clipboardLock;
|
||||
|
||||
static int Sys_ClipboardClear();
|
||||
|
||||
static const char* ClipboardCurrentUser() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return nullptr;
|
||||
@@ -81,57 +82,73 @@ namespace montauk::abi {
|
||||
|
||||
freeSlot->inUse = true;
|
||||
ClipboardCopyUser(freeSlot->user, user);
|
||||
freeSlot->text = nullptr;
|
||||
freeSlot->len = 0;
|
||||
freeSlot->transfer.count = 0;
|
||||
freeSlot->serial = 0;
|
||||
return freeSlot;
|
||||
}
|
||||
|
||||
// Swap `incoming` into the caller's clipboard (it becomes empty) and free
|
||||
// what was there. An empty transfer clears the clipboard.
|
||||
static int ClipboardReplace(DataTransfer::Transfer& incoming) {
|
||||
const char* currentUser = ClipboardCurrentUser();
|
||||
if (currentUser == nullptr) {
|
||||
DataTransfer::Free(incoming);
|
||||
return GenericFailure;
|
||||
}
|
||||
|
||||
char user[32];
|
||||
ClipboardCopyUser(user, currentUser);
|
||||
|
||||
DataTransfer::Transfer old;
|
||||
old.count = 0;
|
||||
|
||||
g_clipboardLock.Acquire();
|
||||
ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
|
||||
if (slot == nullptr) {
|
||||
g_clipboardLock.Release();
|
||||
DataTransfer::Free(incoming);
|
||||
return Busy;
|
||||
}
|
||||
|
||||
old = slot->transfer;
|
||||
slot->transfer = incoming;
|
||||
slot->serial = ClipboardNextSerial(slot->serial);
|
||||
g_clipboardLock.Release();
|
||||
|
||||
incoming.count = 0;
|
||||
DataTransfer::Free(old);
|
||||
return Success;
|
||||
}
|
||||
|
||||
static int Sys_ClipboardClear() {
|
||||
DataTransfer::Transfer empty;
|
||||
empty.count = 0;
|
||||
return ClipboardReplace(empty);
|
||||
}
|
||||
|
||||
static int Sys_ClipboardSetText(const char* data, uint32_t len) {
|
||||
if (len == 0)
|
||||
return Sys_ClipboardClear();
|
||||
if (data == nullptr || len > CLIPBOARD_MAX_TEXT_BYTES)
|
||||
return InvalidArgument;
|
||||
if (Memory::g_heap == nullptr)
|
||||
return OutOfMemory;
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (data[i] == '\0')
|
||||
return InvalidArgument;
|
||||
}
|
||||
|
||||
const char* currentUser = ClipboardCurrentUser();
|
||||
if (currentUser == nullptr) return GenericFailure;
|
||||
DataTransfer::Transfer transfer;
|
||||
int rc = DataTransfer::BuildText(data, len, transfer);
|
||||
if (rc != Success) return rc;
|
||||
return ClipboardReplace(transfer);
|
||||
}
|
||||
|
||||
char user[32];
|
||||
ClipboardCopyUser(user, currentUser);
|
||||
|
||||
char* newText = (char*)Memory::g_heap->Request(len + 1);
|
||||
if (newText == nullptr) return OutOfMemory;
|
||||
for (uint32_t i = 0; i < len; i++)
|
||||
newText[i] = data[i];
|
||||
newText[len] = '\0';
|
||||
|
||||
char* oldText = nullptr;
|
||||
|
||||
g_clipboardLock.Acquire();
|
||||
ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
|
||||
if (slot == nullptr) {
|
||||
g_clipboardLock.Release();
|
||||
Memory::g_heap->Free(newText);
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldText = slot->text;
|
||||
slot->text = newText;
|
||||
slot->len = len;
|
||||
slot->serial = ClipboardNextSerial(slot->serial);
|
||||
g_clipboardLock.Release();
|
||||
|
||||
if (oldText != nullptr)
|
||||
Memory::g_heap->Free(oldText);
|
||||
|
||||
return Success;
|
||||
static int Sys_ClipboardSetData(const TransferItem* items, uint32_t count) {
|
||||
if (count == 0) return Sys_ClipboardClear();
|
||||
DataTransfer::Transfer transfer;
|
||||
int rc = DataTransfer::BuildFromUser(items, count, transfer);
|
||||
if (rc != Success) return rc;
|
||||
return ClipboardReplace(transfer);
|
||||
}
|
||||
|
||||
static int Sys_ClipboardGetInfo(ClipboardInfo* out) {
|
||||
@@ -150,7 +167,8 @@ namespace montauk::abi {
|
||||
g_clipboardLock.Acquire();
|
||||
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||
if (slot != nullptr) {
|
||||
out->textBytes = slot->len;
|
||||
const DataTransfer::Format* text = DataTransfer::Find(slot->transfer, TRANSFER_MIME_TEXT);
|
||||
out->textBytes = text ? text->len : 0;
|
||||
out->serial = slot->serial;
|
||||
}
|
||||
g_clipboardLock.Release();
|
||||
@@ -171,52 +189,57 @@ namespace montauk::abi {
|
||||
|
||||
g_clipboardLock.Acquire();
|
||||
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||
if (slot == nullptr || slot->len == 0) {
|
||||
if (slot != nullptr && outSerial != nullptr)
|
||||
*outSerial = slot->serial;
|
||||
if (slot != nullptr && outSerial != nullptr)
|
||||
*outSerial = slot->serial;
|
||||
const DataTransfer::Format* text =
|
||||
slot ? DataTransfer::Find(slot->transfer, TRANSFER_MIME_TEXT) : nullptr;
|
||||
if (text == nullptr || text->len == 0) {
|
||||
g_clipboardLock.Release();
|
||||
return Success;
|
||||
}
|
||||
|
||||
*outLen = slot->len;
|
||||
if (outSerial != nullptr) *outSerial = slot->serial;
|
||||
if (buffer == nullptr || bufferLen < slot->len) {
|
||||
g_clipboardLock.Release();
|
||||
return BufferTooSmall;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < slot->len; i++)
|
||||
buffer[i] = slot->text[i];
|
||||
int rc = DataTransfer::CopyOut(*text, buffer, bufferLen, outLen);
|
||||
g_clipboardLock.Release();
|
||||
return Success;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int Sys_ClipboardClear() {
|
||||
static int Sys_ClipboardFormats(TransferFormatInfo* out, uint32_t max, uint64_t* outSerial) {
|
||||
if (outSerial != nullptr) *outSerial = 0;
|
||||
|
||||
const char* currentUser = ClipboardCurrentUser();
|
||||
if (currentUser == nullptr) return GenericFailure;
|
||||
|
||||
char user[32];
|
||||
ClipboardCopyUser(user, currentUser);
|
||||
|
||||
char* oldText = nullptr;
|
||||
g_clipboardLock.Acquire();
|
||||
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||
uint32_t n = 0;
|
||||
if (slot != nullptr) {
|
||||
if (outSerial != nullptr) *outSerial = slot->serial;
|
||||
n = out ? DataTransfer::ListFormats(slot->transfer, out, max) : 0;
|
||||
if (out == nullptr || max == 0) n = slot->transfer.count;
|
||||
}
|
||||
g_clipboardLock.Release();
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
static int Sys_ClipboardGetData(const char* mime, void* buffer, uint32_t bufferLen, uint32_t* outLen) {
|
||||
if (outLen == nullptr) return InvalidArgument;
|
||||
*outLen = 0;
|
||||
|
||||
const char* currentUser = ClipboardCurrentUser();
|
||||
if (currentUser == nullptr) return GenericFailure;
|
||||
|
||||
char user[32];
|
||||
ClipboardCopyUser(user, currentUser);
|
||||
|
||||
g_clipboardLock.Acquire();
|
||||
ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
|
||||
if (slot == nullptr) {
|
||||
g_clipboardLock.Release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldText = slot->text;
|
||||
slot->text = nullptr;
|
||||
slot->len = 0;
|
||||
slot->serial = ClipboardNextSerial(slot->serial);
|
||||
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||
const DataTransfer::Format* format = slot ? DataTransfer::Find(slot->transfer, mime) : nullptr;
|
||||
int rc = format ? DataTransfer::CopyOut(*format, buffer, bufferLen, outLen) : NotFound;
|
||||
g_clipboardLock.Release();
|
||||
|
||||
if (oldText != nullptr)
|
||||
Memory::g_heap->Free(oldText);
|
||||
|
||||
return Success;
|
||||
return rc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* DataTransfer.hpp
|
||||
* Multi-format payloads shared by the clipboard and drag and drop
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Syscall.hpp"
|
||||
|
||||
// A transfer is a small set of formats, each one MIME type plus bytes, all
|
||||
// owned by the kernel heap. Producers offer every representation they can
|
||||
// (a file list and its paths as text, say) and each consumer reads the one it
|
||||
// understands. The clipboard holds one transfer per user; the window server
|
||||
// holds the active drag's.
|
||||
namespace DataTransfer {
|
||||
|
||||
struct Format {
|
||||
char mime[montauk::abi::TRANSFER_MIME_MAX];
|
||||
uint8_t* data;
|
||||
uint32_t len;
|
||||
};
|
||||
|
||||
struct Transfer {
|
||||
Format formats[montauk::abi::TRANSFER_MAX_FORMATS];
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
// Copy `count` formats in from user memory, validating every pointer the
|
||||
// items carry (the item array itself must already be validated). On
|
||||
// failure `out` is left empty. Returns a StatusCodes value.
|
||||
int BuildFromUser(const montauk::abi::TransferItem* items, uint32_t count, Transfer& out);
|
||||
|
||||
// A transfer holding just `text` as TRANSFER_MIME_TEXT.
|
||||
int BuildText(const char* text, uint32_t len, Transfer& out);
|
||||
|
||||
void Free(Transfer& transfer);
|
||||
|
||||
// Exact match first; a request without parameters ("text/plain") also
|
||||
// matches a stored format that has them ("text/plain;charset=utf-8").
|
||||
const Format* Find(const Transfer& transfer, const char* mime);
|
||||
|
||||
uint32_t ListFormats(const Transfer& transfer, montauk::abi::TransferFormatInfo* out, uint32_t max);
|
||||
|
||||
// Copy a format to a (validated) user buffer. *outLen always receives the
|
||||
// full length; BufferTooSmall when it does not fit, nothing copied then.
|
||||
int CopyOut(const Format& format, void* buffer, uint32_t bufferLen, uint32_t* outLen);
|
||||
|
||||
// MIME string checks shared by every entry point: "type/subtype" with
|
||||
// optional parameters, printable ASCII, shorter than TRANSFER_MIME_MAX.
|
||||
bool ValidMime(const char* mime);
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
#include "Device.hpp" // SYS_DEVLIST, SYS_DISKINFO
|
||||
#include "Input.hpp" // SYS_INPUT_WAIT
|
||||
#include "Storage.hpp" // SYS_PARTLIST, SYS_VOLINFO, SYS_DISKREAD, SYS_DISKWRITE
|
||||
#include "Window.hpp" // SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE
|
||||
#include "Window.hpp" // SYS_WIN_REQUEST*, SYS_WIN_DRAG_*, SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE
|
||||
#include "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL
|
||||
#include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO
|
||||
#include "Usb.hpp" // generic process-owned USB interface access
|
||||
@@ -39,7 +39,7 @@
|
||||
#include "IpcSyscall.hpp" // SYS_DUPHANDLE, SYS_WAIT_HANDLE, SYS_STREAM_CREATE, SYS_STREAM_READ, SYS_STREAM_WRITE, SYS_MAILBOX_CREATE, SYS_MAILBOX_SEND, SYS_MAILBOX_RECV, SYS_WAITSET_CREATE, SYS_WAITSET_ADD, SYS_WAITSET_REMOVE, SYS_WAITSET_WAIT, SYS_PROC_OPEN, SYS_SURFACE_CREATE, SYS_SURFACE_MAP, SYS_SURFACE_RESIZE
|
||||
#include "LibSyscall.hpp" // SYS_LOAD_LIB, SYS_UNLOAD_LIB, SYS_DLSYM
|
||||
#include "CrashReportSyscall.hpp" // SYS_CRASH_REPORT
|
||||
#include "Clipboard.hpp" // SYS_CLIPBOARD_*
|
||||
#include "Clipboard.hpp" // SYS_CLIPBOARD_* (text and multi-format)
|
||||
#include "UserMemory.hpp"
|
||||
|
||||
// Assembly entry point
|
||||
@@ -726,6 +726,51 @@ namespace montauk::abi {
|
||||
UserMemory::IsUserPtr(frame->arg4) ? (uint64_t*)frame->arg4 : nullptr);
|
||||
case SYS_CLIPBOARD_CLEAR:
|
||||
return Sys_ClipboardClear();
|
||||
case SYS_CLIPBOARD_SET_DATA:
|
||||
if (frame->arg2 > TRANSFER_MAX_FORMATS) return InvalidArgument;
|
||||
if (!UserMemory::Range(frame->arg1, frame->arg2 * sizeof(TransferItem), false)) return BadPointer;
|
||||
return Sys_ClipboardSetData((const TransferItem*)frame->arg1, (uint32_t)frame->arg2);
|
||||
case SYS_CLIPBOARD_FORMATS: {
|
||||
uint64_t max = frame->arg2 > TRANSFER_MAX_FORMATS ? TRANSFER_MAX_FORMATS : frame->arg2;
|
||||
if (!UserMemory::OptionalRange(frame->arg1, max * sizeof(TransferFormatInfo), true)) return BadPointer;
|
||||
if (!UserMemory::OptionalRange(frame->arg3, sizeof(uint64_t), true)) return BadPointer;
|
||||
return Sys_ClipboardFormats(frame->arg1 ? (TransferFormatInfo*)frame->arg1 : nullptr,
|
||||
(uint32_t)max,
|
||||
frame->arg3 ? (uint64_t*)frame->arg3 : nullptr);
|
||||
}
|
||||
case SYS_CLIPBOARD_GET_DATA:
|
||||
if (!UserMemory::String(frame->arg1, TRANSFER_MIME_MAX)) return BadPointer;
|
||||
if (frame->arg3 > 0 && !UserMemory::Range(frame->arg2, frame->arg3, true)) return BadPointer;
|
||||
if (!UserMemory::Writable<uint32_t>(frame->arg4)) return BadPointer;
|
||||
return Sys_ClipboardGetData((const char*)frame->arg1,
|
||||
frame->arg3 > 0 ? (void*)frame->arg2 : nullptr,
|
||||
(uint32_t)frame->arg3, (uint32_t*)frame->arg4);
|
||||
case SYS_WIN_REQUEST:
|
||||
if (!UserMemory::Readable<WinRequest>(frame->arg2)) return BadPointer;
|
||||
return Sys_WinRequest((int)frame->arg1, (const WinRequest*)frame->arg2);
|
||||
case SYS_WIN_REQUEST_POLL:
|
||||
if (!UserMemory::Writable<WinRequest>(frame->arg1)) return BadPointer;
|
||||
return Sys_WinRequestPoll((WinRequest*)frame->arg1);
|
||||
case SYS_WIN_DRAG_BEGIN:
|
||||
if (!UserMemory::Readable<WinDragBegin>(frame->arg2)) return BadPointer;
|
||||
if (frame->arg4 == 0 || frame->arg4 > TRANSFER_MAX_FORMATS) return InvalidArgument;
|
||||
if (!UserMemory::Range(frame->arg3, frame->arg4 * sizeof(TransferItem), false)) return BadPointer;
|
||||
return Sys_WinDragBegin((int)frame->arg1, (const WinDragBegin*)frame->arg2,
|
||||
(const TransferItem*)frame->arg3, (uint32_t)frame->arg4);
|
||||
case SYS_WIN_DRAG_FORMATS: {
|
||||
uint64_t max = frame->arg3 > TRANSFER_MAX_FORMATS ? TRANSFER_MAX_FORMATS : frame->arg3;
|
||||
if (!UserMemory::OptionalRange(frame->arg2, max * sizeof(TransferFormatInfo), true)) return BadPointer;
|
||||
return Sys_WinDragFormats((uint32_t)frame->arg1,
|
||||
frame->arg2 ? (TransferFormatInfo*)frame->arg2 : nullptr,
|
||||
(uint32_t)max);
|
||||
}
|
||||
case SYS_WIN_DRAG_GET_DATA:
|
||||
if (!UserMemory::String(frame->arg2, TRANSFER_MIME_MAX)) return BadPointer;
|
||||
if (frame->arg4 > 0 && !UserMemory::Range(frame->arg3, frame->arg4, true)) return BadPointer;
|
||||
if (!UserMemory::Writable<uint32_t>(frame->arg5)) return BadPointer;
|
||||
return Sys_WinDragGetData((uint32_t)frame->arg1, (const char*)frame->arg2,
|
||||
frame->arg4 > 0 ? (void*)frame->arg3 : nullptr,
|
||||
(uint32_t)frame->arg4, (uint32_t*)frame->arg5);
|
||||
case SYS_INPUT_WAIT:
|
||||
return (int64_t)Sys_InputWait(frame->arg1, frame->arg2);
|
||||
case SYS_LOG_WRITE: {
|
||||
|
||||
+163
-2
@@ -386,6 +386,19 @@ namespace montauk::abi {
|
||||
thread ends the process rather than only that thread. */
|
||||
static constexpr uint64_t SYS_EXIT_GROUP = 199; // (exitCode) -> does not return
|
||||
|
||||
/* Multi-format clipboard (see "Window server messaging" below). The text
|
||||
calls above operate on its TRANSFER_MIME_TEXT format. */
|
||||
static constexpr uint64_t SYS_CLIPBOARD_SET_DATA = 200; // (const TransferItem*, count) -> status
|
||||
static constexpr uint64_t SYS_CLIPBOARD_FORMATS = 201; // (TransferFormatInfo*, max, uint64_t* serial) -> count
|
||||
static constexpr uint64_t SYS_CLIPBOARD_GET_DATA = 202; // (mime, buf, max, uint32_t* len) -> status
|
||||
|
||||
/* Window server requests and drag and drop. */
|
||||
static constexpr uint64_t SYS_WIN_REQUEST = 203; // (windowId, const WinRequest*) -> status
|
||||
static constexpr uint64_t SYS_WIN_REQUEST_POLL = 204; // (WinRequest*) -> 1 / 0; compositor only
|
||||
static constexpr uint64_t SYS_WIN_DRAG_BEGIN = 205; // (windowId or -1 for the compositor's own surface, const WinDragBegin*, const TransferItem*, count) -> serial
|
||||
static constexpr uint64_t SYS_WIN_DRAG_FORMATS = 206; // (serial, TransferFormatInfo*, max) -> count
|
||||
static constexpr uint64_t SYS_WIN_DRAG_GET_DATA = 207; // (serial, mime, buf, max, uint32_t* len) -> status
|
||||
|
||||
/* Kernel-owned process capabilities. User identities may namespace
|
||||
per-user resources, but never participate in authorization decisions. */
|
||||
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
|
||||
@@ -706,14 +719,52 @@ namespace montauk::abi {
|
||||
};
|
||||
|
||||
// Window server shared types
|
||||
enum WinEventType : uint8_t {
|
||||
WIN_EVENT_KEY = 0,
|
||||
WIN_EVENT_MOUSE = 1,
|
||||
WIN_EVENT_RESIZE = 2,
|
||||
WIN_EVENT_CLOSE = 3,
|
||||
WIN_EVENT_SCALE = 4,
|
||||
// Drag and drop (WinEvent::drag). A window may read the drag's data
|
||||
// (SYS_WIN_DRAG_*) once it has received one of these for that serial.
|
||||
WIN_EVENT_DRAG_ENTER = 5, // pointer entered the window during a drag
|
||||
WIN_EVENT_DRAG_OVER = 6, // pointer moved; coalesced like mouse moves
|
||||
WIN_EVENT_DRAG_LEAVE = 7, // pointer left, or the drag was cancelled
|
||||
WIN_EVENT_DROP = 8, // released here; drag.actions = chosen action
|
||||
WIN_EVENT_DRAG_END = 9, // to the source only; drag.actions = action
|
||||
// taken (0 = cancelled / not accepted)
|
||||
};
|
||||
|
||||
// Modifier keys held at the time of a drag event (WinEvent::drag.modifiers).
|
||||
static constexpr uint8_t WIN_MOD_SHIFT = 1u << 0;
|
||||
static constexpr uint8_t WIN_MOD_CTRL = 1u << 1;
|
||||
static constexpr uint8_t WIN_MOD_ALT = 1u << 2;
|
||||
|
||||
// Drag actions; a source offers a set, a drop carries exactly one.
|
||||
static constexpr uint8_t WIN_DRAG_COPY = 1u << 0;
|
||||
static constexpr uint8_t WIN_DRAG_MOVE = 1u << 1;
|
||||
static constexpr uint8_t WIN_DRAG_LINK = 1u << 2;
|
||||
|
||||
struct WinDragEvent {
|
||||
int32_t x, y; // window-local; -1,-1 when not over the window
|
||||
uint32_t serial; // identifies the drag
|
||||
uint8_t actions; // WIN_DRAG_* (see WinEventType)
|
||||
uint8_t modifiers; // WIN_MOD_*
|
||||
uint8_t _pad[2];
|
||||
};
|
||||
static_assert(sizeof(WinDragEvent) == 16, "WinDragEvent must fit the WinEvent union");
|
||||
|
||||
struct WinEvent {
|
||||
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
|
||||
uint8_t type; // WinEventType
|
||||
uint8_t _pad[3];
|
||||
union {
|
||||
KeyEvent key; // 16 bytes: the union was already that size (mouse)
|
||||
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
|
||||
// modifiers (WIN_MOD_*) was padding before; compositors that
|
||||
// predate it send 0.
|
||||
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons, modifiers; } mouse;
|
||||
struct { int32_t w, h; } resize;
|
||||
struct { int32_t scale; } scale;
|
||||
WinDragEvent drag; // WIN_EVENT_DRAG_* / WIN_EVENT_DROP
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(WinEvent) == 20, "WinEvent layout is shared with existing binaries");
|
||||
@@ -737,6 +788,116 @@ namespace montauk::abi {
|
||||
uint64_t pixelVa; // VA of pixel buffer in caller's address space
|
||||
};
|
||||
|
||||
// ---- Window server messaging ----------------------------------------
|
||||
//
|
||||
// Three pieces, shared by the compositor (the desktop) and every app:
|
||||
// * WinEvent types 5-9 below: drag-and-drop events, compositor -> app.
|
||||
// * WinRequest: typed records an app posts about one of its own windows
|
||||
// (SYS_WIN_REQUEST); only the compositor can collect them
|
||||
// (SYS_WIN_REQUEST_POLL). Unknown types are ignored, so new requests
|
||||
// can be added without breaking older compositors or apps.
|
||||
// * Transfers: multi-format payloads (one MIME type per format) carried
|
||||
// by the clipboard (SYS_CLIPBOARD_*_DATA) and by drags
|
||||
// (SYS_WIN_DRAG_*). The older text clipboard calls read and write the
|
||||
// TRANSFER_MIME_TEXT format of the same clipboard.
|
||||
|
||||
// Transfers -----------------------------------------------------------
|
||||
|
||||
static constexpr uint32_t TRANSFER_MAX_FORMATS = 8;
|
||||
static constexpr uint32_t TRANSFER_MIME_MAX = 64; // incl. NUL
|
||||
static constexpr uint32_t TRANSFER_MAX_BYTES = 256 * 1024; // all formats together
|
||||
|
||||
// Well-known formats. Any other "type/subtype" string is allowed.
|
||||
static constexpr const char* TRANSFER_MIME_TEXT = "text/plain;charset=utf-8";
|
||||
// UTF-8 absolute paths ("0:/..."), one per line.
|
||||
static constexpr const char* TRANSFER_MIME_FILE_LIST = "application/x-montauk-file-list";
|
||||
// Clipboard companion to a file list: "copy" or "cut".
|
||||
static constexpr const char* TRANSFER_MIME_FILE_OP = "application/x-montauk-file-op";
|
||||
|
||||
// One format to store. Pointers are carried as 64-bit values so the
|
||||
// layout is the same for every caller.
|
||||
struct TransferItem {
|
||||
uint64_t mime; // const char*, NUL-terminated, < TRANSFER_MIME_MAX
|
||||
uint64_t data; // const void*
|
||||
uint32_t len;
|
||||
uint32_t _reserved;
|
||||
};
|
||||
static_assert(sizeof(TransferItem) == 24, "TransferItem is ABI");
|
||||
|
||||
struct TransferFormatInfo {
|
||||
char mime[TRANSFER_MIME_MAX];
|
||||
uint32_t len;
|
||||
uint32_t _reserved;
|
||||
};
|
||||
static_assert(sizeof(TransferFormatInfo) == 72, "TransferFormatInfo is ABI");
|
||||
|
||||
// Requests --------------------------------------------------------------
|
||||
|
||||
static constexpr uint32_t WIN_REQUEST_PAYLOAD_MAX = 240;
|
||||
|
||||
struct WinRequest {
|
||||
uint16_t type; // WinRequestType
|
||||
uint16_t size; // payload bytes in use
|
||||
int32_t windowId; // window the request is about (the sender must own it
|
||||
// when posting); -1 if it was destroyed since
|
||||
int32_t senderPid; // filled in by the kernel
|
||||
uint32_t serial; // filled in by the kernel; increases per request
|
||||
uint8_t payload[WIN_REQUEST_PAYLOAD_MAX];
|
||||
};
|
||||
static_assert(sizeof(WinRequest) == 256, "WinRequest is ABI");
|
||||
|
||||
enum WinRequestType : uint16_t {
|
||||
WIN_REQ_LAUNCH_ITEM = 1, // WinReqLaunchItem
|
||||
WIN_REQ_SET_PARENT = 2, // WinReqSetParent
|
||||
WIN_REQ_DRAG_STATUS = 3, // WinReqDragStatus
|
||||
WIN_REQ_ACTIVATE = 4, // no payload: raise and focus the window
|
||||
// Types from here on are generated by the kernel; SYS_WIN_REQUEST
|
||||
// refuses them so an app cannot forge one.
|
||||
WIN_REQ_KERNEL_BASE = 0x8000,
|
||||
WIN_REQ_DRAG_BEGIN = 0x8001, // WinReqDragBegin (from SYS_WIN_DRAG_BEGIN)
|
||||
};
|
||||
|
||||
// Open a desktop catalog item (an app manifest id, or a built-in such as
|
||||
// "desktop-settings"), as if the user had picked it from the launcher, so
|
||||
// it runs with the authority the desktop would give it. `args` reach only
|
||||
// apps whose manifest declares files.types (they take a document path);
|
||||
// anything else starts without them, so a request cannot steer a
|
||||
// privileged app through its command line.
|
||||
struct WinReqLaunchItem {
|
||||
char itemId[48];
|
||||
char args[192];
|
||||
};
|
||||
|
||||
// Mark the window as a dialog of another window owned by the same
|
||||
// process: the compositor centres it over the parent.
|
||||
struct WinReqSetParent {
|
||||
int32_t parentWindowId;
|
||||
};
|
||||
|
||||
// Answer to DRAG_ENTER/DRAG_OVER: the action this window would perform
|
||||
// on a drop at the current position (0 = would refuse). Advisory; it
|
||||
// drives the compositor's drag feedback.
|
||||
struct WinReqDragStatus {
|
||||
uint32_t serial;
|
||||
uint8_t action; // one WIN_DRAG_* or 0
|
||||
uint8_t _pad[3];
|
||||
};
|
||||
|
||||
// Describes a drag for the compositor's feedback. Supplied by the app to
|
||||
// SYS_WIN_DRAG_BEGIN and relayed (with the serial) as WIN_REQ_DRAG_BEGIN.
|
||||
struct WinDragBegin {
|
||||
uint8_t actions; // WIN_DRAG_* offered
|
||||
uint8_t _pad[3];
|
||||
uint32_t itemCount; // for a "3 items" badge; 0 = do not show
|
||||
char label[64]; // short description, e.g. the file name
|
||||
};
|
||||
static_assert(sizeof(WinDragBegin) == 72, "WinDragBegin is ABI");
|
||||
|
||||
struct WinReqDragBegin {
|
||||
uint32_t serial;
|
||||
WinDragBegin info;
|
||||
};
|
||||
|
||||
struct MemStats {
|
||||
uint64_t totalBytes;
|
||||
uint64_t freeBytes;
|
||||
|
||||
@@ -73,7 +73,14 @@ namespace WinServer {
|
||||
return result;
|
||||
}
|
||||
|
||||
static void OrphanRequestsLocked(int windowId);
|
||||
|
||||
static void ReleaseSlotResourcesLocked(WindowSlot& slot, bool unmapOwner) {
|
||||
// Queued requests stay deliverable (a dialog may post "open this
|
||||
// app" and close at once), but no longer name a window that could be
|
||||
// handed to someone else.
|
||||
OrphanRequestsLocked((int)(&slot - g_slots));
|
||||
|
||||
if (unmapOwner && slot.liveSurface != nullptr) {
|
||||
auto* owner = Sched::GetProcessByPid(slot.ownerPid);
|
||||
if (owner != nullptr) {
|
||||
@@ -94,13 +101,57 @@ namespace WinServer {
|
||||
ResetSlotLocked(slot);
|
||||
}
|
||||
|
||||
// ---- Messaging state ----
|
||||
|
||||
static constexpr int MaxRequests = 64;
|
||||
// One app may not hold more than this many queued requests, so a single
|
||||
// misbehaving process cannot starve the others out of the queue.
|
||||
static constexpr int MaxRequestsPerSender = 16;
|
||||
static constexpr int MaxDragReaders = 8;
|
||||
|
||||
static montauk::abi::WinRequest g_requests[MaxRequests];
|
||||
static int g_requestHead = 0; // oldest
|
||||
static int g_requestCount = 0;
|
||||
static uint32_t g_requestSerial = 0;
|
||||
|
||||
struct DragState {
|
||||
bool active;
|
||||
uint32_t serial;
|
||||
int sourceWindow;
|
||||
int sourcePid;
|
||||
DataTransfer::Transfer transfer;
|
||||
int readers[MaxDragReaders];
|
||||
int readerCount;
|
||||
};
|
||||
static DragState g_drag;
|
||||
static uint32_t g_dragSerial = 0;
|
||||
|
||||
static bool IsDragEvent(uint8_t type) {
|
||||
return type >= montauk::abi::WIN_EVENT_DRAG_ENTER && type <= montauk::abi::WIN_EVENT_DRAG_END;
|
||||
}
|
||||
|
||||
static void GrantDragReaderLocked(uint32_t serial, int pid) {
|
||||
if (!g_drag.active || g_drag.serial != serial || pid <= 0) return;
|
||||
if (pid == g_drag.sourcePid) return;
|
||||
for (int i = 0; i < g_drag.readerCount; i++) {
|
||||
if (g_drag.readers[i] == pid) return;
|
||||
}
|
||||
if (g_drag.readerCount < MaxDragReaders) g_drag.readers[g_drag.readerCount++] = pid;
|
||||
}
|
||||
|
||||
static int SendEventLocked(int windowId, const montauk::abi::WinEvent* event) {
|
||||
if (windowId < 0 || windowId >= MaxWindows || event == nullptr) return InvalidArgument;
|
||||
WindowSlot& slot = g_slots[windowId];
|
||||
if (!slot.used || slot.eventMailbox == nullptr) return InvalidArgument;
|
||||
|
||||
int rc = (event->type == 1)
|
||||
? Ipc::MailboxSendCoalescedMouse(slot.eventMailbox, 0, event, sizeof(*event))
|
||||
// Delivering a drag event is what lets the receiving app read the
|
||||
// drag's data: it needs the formats to decide whether to accept.
|
||||
if (IsDragEvent(event->type)) GrantDragReaderLocked(event->drag.serial, slot.ownerPid);
|
||||
|
||||
bool motion = event->type == montauk::abi::WIN_EVENT_MOUSE ||
|
||||
event->type == montauk::abi::WIN_EVENT_DRAG_OVER;
|
||||
int rc = motion
|
||||
? Ipc::MailboxSendCoalescedMotion(slot.eventMailbox, 0, event, sizeof(*event))
|
||||
: Ipc::MailboxSend(slot.eventMailbox, 0, event, sizeof(*event));
|
||||
if (rc < 0) return GenericFailure;
|
||||
return rc > 0 ? Success : Busy;
|
||||
@@ -376,12 +427,153 @@ namespace WinServer {
|
||||
return g_uiScale;
|
||||
}
|
||||
|
||||
static void OrphanRequestsLocked(int windowId) {
|
||||
for (int i = 0; i < g_requestCount; i++) {
|
||||
montauk::abi::WinRequest& r = g_requests[(g_requestHead + i) % MaxRequests];
|
||||
if (r.windowId == windowId) r.windowId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void EndDragLocked() {
|
||||
DataTransfer::Free(g_drag.transfer);
|
||||
g_drag.active = false;
|
||||
g_drag.readerCount = 0;
|
||||
}
|
||||
|
||||
void CleanupProcess(int pid) {
|
||||
WsGuard guard;
|
||||
for (int i = 0; i < MaxWindows; i++) {
|
||||
if (!g_slots[i].used || g_slots[i].ownerPid != pid) continue;
|
||||
ReleaseSlotResourcesLocked(g_slots[i], false);
|
||||
}
|
||||
// Requests the process already posted are still delivered: they are
|
||||
// messages in flight, and a process may well exit right after its
|
||||
// last one ("open this file" from a dialog-only Files).
|
||||
if (g_drag.active && g_drag.sourcePid == pid) EndDragLocked();
|
||||
}
|
||||
|
||||
static int EnqueueRequestLocked(montauk::abi::WinRequest& request) {
|
||||
if (g_requestCount >= MaxRequests) return WouldBlock;
|
||||
request.serial = ++g_requestSerial;
|
||||
if (request.serial == 0) request.serial = ++g_requestSerial;
|
||||
g_requests[(g_requestHead + g_requestCount) % MaxRequests] = request;
|
||||
g_requestCount++;
|
||||
return Success;
|
||||
}
|
||||
|
||||
static bool OwnsWindowLocked(int windowId, int pid) {
|
||||
return windowId >= 0 && windowId < MaxWindows && g_slots[windowId].used &&
|
||||
g_slots[windowId].ownerPid == pid;
|
||||
}
|
||||
|
||||
int PostRequest(int windowId, int callerPid, const montauk::abi::WinRequest& request) {
|
||||
using namespace montauk::abi;
|
||||
if (request.type == 0 || request.type >= WIN_REQ_KERNEL_BASE) return InvalidArgument;
|
||||
if (request.size > WIN_REQUEST_PAYLOAD_MAX) return InvalidArgument;
|
||||
|
||||
WsGuard guard;
|
||||
if (!OwnsWindowLocked(windowId, callerPid)) return PermissionDenied;
|
||||
|
||||
int mine = 0;
|
||||
for (int i = 0; i < g_requestCount; i++) {
|
||||
if (g_requests[(g_requestHead + i) % MaxRequests].senderPid == callerPid) mine++;
|
||||
}
|
||||
if (mine >= MaxRequestsPerSender) return WouldBlock;
|
||||
|
||||
WinRequest copy = request;
|
||||
copy.windowId = windowId;
|
||||
copy.senderPid = callerPid;
|
||||
// Bytes past `size` are not the sender's business to pass along.
|
||||
for (uint32_t i = copy.size; i < WIN_REQUEST_PAYLOAD_MAX; i++) copy.payload[i] = 0;
|
||||
return EnqueueRequestLocked(copy);
|
||||
}
|
||||
|
||||
int PollRequest(montauk::abi::WinRequest* out) {
|
||||
WsGuard guard;
|
||||
while (g_requestCount > 0) {
|
||||
montauk::abi::WinRequest& r = g_requests[g_requestHead];
|
||||
g_requestHead = (g_requestHead + 1) % MaxRequests;
|
||||
g_requestCount--;
|
||||
// A destroyed window's requests arrive with windowId -1 (see
|
||||
// OrphanRequestsLocked); anything else must still match.
|
||||
if (r.windowId >= 0 && !OwnsWindowLocked(r.windowId, r.senderPid)) continue;
|
||||
*out = r;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DragBegin(int windowId, int callerPid, bool compositor,
|
||||
const montauk::abi::WinDragBegin& info, DataTransfer::Transfer& transfer) {
|
||||
using namespace montauk::abi;
|
||||
WsGuard guard;
|
||||
// The compositor drags from its own surface (desktop icons) with
|
||||
// windowId -1; it already knows about such a drag, so no request.
|
||||
bool fromSurface = compositor && windowId == -1;
|
||||
if (!fromSurface && !OwnsWindowLocked(windowId, callerPid)) {
|
||||
DataTransfer::Free(transfer);
|
||||
return PermissionDenied;
|
||||
}
|
||||
|
||||
uint32_t serial = ++g_dragSerial;
|
||||
if (serial == 0) serial = ++g_dragSerial;
|
||||
|
||||
if (!fromSurface) {
|
||||
WinRequest request = {};
|
||||
request.type = WIN_REQ_DRAG_BEGIN;
|
||||
request.size = sizeof(WinReqDragBegin);
|
||||
request.windowId = windowId;
|
||||
request.senderPid = callerPid;
|
||||
WinReqDragBegin* payload = (WinReqDragBegin*)request.payload;
|
||||
payload->serial = serial;
|
||||
payload->info = info;
|
||||
payload->info.label[sizeof(payload->info.label) - 1] = '\0';
|
||||
int rc = EnqueueRequestLocked(request);
|
||||
if (rc != Success) {
|
||||
DataTransfer::Free(transfer);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
// A new drag replaces the previous one (only one pointer exists).
|
||||
if (g_drag.active) EndDragLocked();
|
||||
g_drag.active = true;
|
||||
g_drag.serial = serial;
|
||||
g_drag.sourceWindow = windowId;
|
||||
g_drag.sourcePid = callerPid;
|
||||
g_drag.transfer = transfer;
|
||||
g_drag.readerCount = 0;
|
||||
transfer.count = 0;
|
||||
return (int)serial;
|
||||
}
|
||||
|
||||
static bool DragReadableLocked(uint32_t serial, int callerPid, bool compositor) {
|
||||
if (!g_drag.active || g_drag.serial != serial) return false;
|
||||
if (compositor || callerPid == g_drag.sourcePid) return true;
|
||||
for (int i = 0; i < g_drag.readerCount; i++) {
|
||||
if (g_drag.readers[i] == callerPid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int DragFormats(uint32_t serial, int callerPid, bool compositor,
|
||||
montauk::abi::TransferFormatInfo* out, uint32_t max) {
|
||||
WsGuard guard;
|
||||
if (!g_drag.active || g_drag.serial != serial) return NotFound;
|
||||
if (!DragReadableLocked(serial, callerPid, compositor)) return PermissionDenied;
|
||||
if (out == nullptr || max == 0) return (int)g_drag.transfer.count;
|
||||
return (int)DataTransfer::ListFormats(g_drag.transfer, out, max);
|
||||
}
|
||||
|
||||
int DragGetData(uint32_t serial, int callerPid, bool compositor, const char* mime,
|
||||
void* buffer, uint32_t bufferLen, uint32_t* outLen) {
|
||||
WsGuard guard;
|
||||
if (outLen != nullptr) *outLen = 0;
|
||||
if (!g_drag.active || g_drag.serial != serial) return NotFound;
|
||||
if (!DragReadableLocked(serial, callerPid, compositor)) return PermissionDenied;
|
||||
const DataTransfer::Format* format = DataTransfer::Find(g_drag.transfer, mime);
|
||||
if (format == nullptr) return NotFound;
|
||||
return DataTransfer::CopyOut(*format, buffer, bufferLen, outLen);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Syscall.hpp"
|
||||
#include <cstdint>
|
||||
#include <Ipc/Ipc.hpp>
|
||||
#include "DataTransfer.hpp"
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
@@ -44,4 +45,26 @@ namespace WinServer {
|
||||
int SetScale(int scale);
|
||||
int GetScale();
|
||||
|
||||
// ---- Messaging (see "Window server messaging" in Api/Syscall.hpp) ----
|
||||
|
||||
// Queue an app request about one of the caller's own windows.
|
||||
int PostRequest(int windowId, int callerPid, const montauk::abi::WinRequest& request);
|
||||
// Pop the oldest request (compositor only; the caller checks the cap).
|
||||
// Returns 1 with *out filled, or 0 when the queue is empty.
|
||||
int PollRequest(montauk::abi::WinRequest* out);
|
||||
|
||||
// Start a drag from the caller's window, or (compositor only, windowId
|
||||
// -1) from the desktop surface itself. Takes ownership of `transfer` (it
|
||||
// is emptied either way) and returns the new drag serial (> 0) or a
|
||||
// negative StatusCodes value.
|
||||
int DragBegin(int windowId, int callerPid, bool compositor,
|
||||
const montauk::abi::WinDragBegin& info, DataTransfer::Transfer& transfer);
|
||||
// Read the drag's formats or data. Allowed for the source process, the
|
||||
// compositor, and processes the compositor has sent a drag event for this
|
||||
// serial to.
|
||||
int DragFormats(uint32_t serial, int callerPid, bool compositor,
|
||||
montauk::abi::TransferFormatInfo* out, uint32_t max);
|
||||
int DragGetData(uint32_t serial, int callerPid, bool compositor, const char* mime,
|
||||
void* buffer, uint32_t bufferLen, uint32_t* outLen);
|
||||
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
* Window.hpp
|
||||
* SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL,
|
||||
* SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE,
|
||||
* SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE syscalls
|
||||
* SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE syscalls,
|
||||
* and window server messaging: SYS_WIN_REQUEST, SYS_WIN_REQUEST_POLL,
|
||||
* SYS_WIN_DRAG_BEGIN, SYS_WIN_DRAG_FORMATS, SYS_WIN_DRAG_GET_DATA
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
@@ -92,4 +94,41 @@ namespace montauk::abi {
|
||||
static int Sys_WinGetScale() {
|
||||
return WinServer::GetScale();
|
||||
}
|
||||
|
||||
// Any app may post requests, but only about its own windows (checked in
|
||||
// WinServer); only the compositor may read them back.
|
||||
static int Sys_WinRequest(int windowId, const WinRequest* request) {
|
||||
if (request == nullptr) return InvalidArgument;
|
||||
WinRequest copy = *request;
|
||||
return WinServer::PostRequest(windowId, Sched::GetCurrentPid(), copy);
|
||||
}
|
||||
|
||||
static int Sys_WinRequestPoll(WinRequest* out) {
|
||||
if (!Sched::HasCapability(CAP_COMPOSITOR)) return CapabilityDenied;
|
||||
if (out == nullptr) return InvalidArgument;
|
||||
return WinServer::PollRequest(out);
|
||||
}
|
||||
|
||||
static int Sys_WinDragBegin(int windowId, const WinDragBegin* info,
|
||||
const TransferItem* items, uint32_t count) {
|
||||
if (info == nullptr) return InvalidArgument;
|
||||
WinDragBegin infoCopy = *info;
|
||||
DataTransfer::Transfer transfer;
|
||||
int rc = DataTransfer::BuildFromUser(items, count, transfer);
|
||||
if (rc != Success) return rc;
|
||||
return WinServer::DragBegin(windowId, Sched::GetCurrentPid(),
|
||||
Sched::HasCapability(CAP_COMPOSITOR), infoCopy, transfer);
|
||||
}
|
||||
|
||||
static int Sys_WinDragFormats(uint32_t serial, TransferFormatInfo* out, uint32_t max) {
|
||||
return WinServer::DragFormats(serial, Sched::GetCurrentPid(),
|
||||
Sched::HasCapability(CAP_COMPOSITOR), out, max);
|
||||
}
|
||||
|
||||
static int Sys_WinDragGetData(uint32_t serial, const char* mime, void* buffer,
|
||||
uint32_t bufferLen, uint32_t* outLen) {
|
||||
return WinServer::DragGetData(serial, Sched::GetCurrentPid(),
|
||||
Sched::HasCapability(CAP_COMPOSITOR), mime,
|
||||
buffer, bufferLen, outLen);
|
||||
}
|
||||
};
|
||||
@@ -25,5 +25,7 @@ enum StatusCodes {
|
||||
BufferTooSmall = -13,
|
||||
OutOfMemory = -14,
|
||||
TimedOut = -15,
|
||||
WouldBlock = -16
|
||||
WouldBlock = -16,
|
||||
NotFound = -17,
|
||||
AlreadyExists = -18
|
||||
};
|
||||
+12
-10
@@ -1003,16 +1003,21 @@ namespace Ipc {
|
||||
return MailboxSendInternal(mailbox, -1, msgType, data, len, -1);
|
||||
}
|
||||
|
||||
int MailboxSendCoalescedMouse(Mailbox* mailbox, uint32_t msgType, const void* data, uint16_t len) {
|
||||
int MailboxSendCoalescedMotion(Mailbox* mailbox, uint32_t msgType, const void* data, uint16_t len) {
|
||||
if (mailbox == nullptr || data == nullptr) return -1;
|
||||
if (len != sizeof(montauk::abi::WinEvent)) return MailboxSend(mailbox, msgType, data, len);
|
||||
|
||||
const montauk::abi::WinEvent* incoming = (const montauk::abi::WinEvent*)data;
|
||||
if (incoming->type != 1 || incoming->mouse.scroll != 0)
|
||||
return MailboxSend(mailbox, msgType, data, len);
|
||||
// Plain pointer motion (mouse moves, drag-over updates) only matters
|
||||
// in its latest state, so a queued one is overwritten rather than
|
||||
// stacking up. Clicks, scrolls and every other event queue normally.
|
||||
auto isMotion = [](const montauk::abi::WinEvent* e) {
|
||||
if (e->type == montauk::abi::WIN_EVENT_DRAG_OVER) return true;
|
||||
return e->type == montauk::abi::WIN_EVENT_MOUSE && e->mouse.scroll == 0 &&
|
||||
e->mouse.buttons == e->mouse.prev_buttons;
|
||||
};
|
||||
|
||||
bool incomingTransition = incoming->mouse.buttons != incoming->mouse.prev_buttons;
|
||||
if (incomingTransition)
|
||||
const montauk::abi::WinEvent* incoming = (const montauk::abi::WinEvent*)data;
|
||||
if (!isMotion(incoming))
|
||||
return MailboxSend(mailbox, msgType, data, len);
|
||||
|
||||
mailbox->lock.Acquire();
|
||||
@@ -1026,10 +1031,7 @@ namespace Ipc {
|
||||
MailboxMessage& latest = mailbox->messages[idx];
|
||||
if (latest.type == msgType && latest.size == len && !latest.hasAttachment) {
|
||||
const montauk::abi::WinEvent* queued = (const montauk::abi::WinEvent*)latest.data;
|
||||
bool queuedTransition = queued->type != 1 ||
|
||||
queued->mouse.scroll != 0 ||
|
||||
queued->mouse.buttons != queued->mouse.prev_buttons;
|
||||
if (!queuedTransition) {
|
||||
if (isMotion(queued) && queued->type == incoming->type) {
|
||||
memcpy(latest.data, data, len);
|
||||
mailbox->lock.Release();
|
||||
NotifyObjectChanged((Object*)mailbox);
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace Ipc {
|
||||
void RetainMailbox(Mailbox* mailbox, bool sender, bool receiver);
|
||||
void ReleaseMailbox(Mailbox* mailbox, bool sender, bool receiver);
|
||||
int MailboxSend(Mailbox* mailbox, uint32_t msgType, const void* data, uint16_t len);
|
||||
int MailboxSendCoalescedMouse(Mailbox* mailbox, uint32_t msgType, const void* data, uint16_t len);
|
||||
int MailboxSendCoalescedMotion(Mailbox* mailbox, uint32_t msgType, const void* data, uint16_t len);
|
||||
int MailboxRecv(Mailbox* mailbox, uint32_t* msgType, void* data, uint16_t* inOutLen, bool nonBlocking);
|
||||
int MailboxSendHandle(int handle, uint32_t msgType, const void* data, uint16_t len, int attachHandle);
|
||||
int MailboxRecvHandle(int handle, uint32_t* msgType, void* data, uint16_t* inOutLen, int* outAttachHandle);
|
||||
|
||||
Reference in new issue
Block a user