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
@@ -299,6 +299,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;
|
||||
@@ -637,14 +650,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");
|
||||
@@ -668,6 +719,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 DevInfo {
|
||||
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=Storage, 8=PCI, 9=Audio, 10=ACPI
|
||||
uint8_t _pad[3];
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* app_catalog.hpp
|
||||
* The app catalog shared by the desktop and Files: installed app manifests
|
||||
* (0:/apps/<dir>/manifest.toml) plus the desktop's built-in settings pages
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/toml.h>
|
||||
#include <montauk/config.h>
|
||||
#include <gui/desktop.hpp>
|
||||
#include <gui/svg.hpp>
|
||||
|
||||
namespace gui {
|
||||
|
||||
// Built-in settings pages the desktop implements itself. Other processes
|
||||
// list them by id and open them with a WIN_REQ_LAUNCH_ITEM request.
|
||||
struct CatalogBuiltinPage {
|
||||
const char* id;
|
||||
const char* label;
|
||||
const char* icon_path;
|
||||
};
|
||||
|
||||
inline constexpr CatalogBuiltinPage kCatalogDesktopSettings = {
|
||||
"desktop-settings", "Desktop Settings", "0:/os/icons/preferences-desktop-apps.svg"
|
||||
};
|
||||
inline constexpr CatalogBuiltinPage kCatalogUserManagement = {
|
||||
"user-management", "User Management", "0:/os/icons/user-home.svg"
|
||||
};
|
||||
inline constexpr const CatalogBuiltinPage* kCatalogBuiltinPages[] = {
|
||||
&kCatalogDesktopSettings, &kCatalogUserManagement,
|
||||
};
|
||||
|
||||
// out = a + b + c (+ d + e), truncated to out_max.
|
||||
inline void app_catalog_join(char* out, int out_max, const char* a, const char* b,
|
||||
const char* c, const char* d = "", const char* e = "") {
|
||||
const char* parts[5] = { a, b, c, d, e };
|
||||
int n = 0;
|
||||
for (const char* part : parts) {
|
||||
for (int i = 0; part && part[i] && n < out_max - 1; i++) out[n++] = part[i];
|
||||
}
|
||||
if (out_max > 0) out[n] = '\0';
|
||||
}
|
||||
|
||||
// Extract the basename from a readdir entry, tolerating both current
|
||||
// direct-child names and paths returned by older ramdisk kernels.
|
||||
inline void app_catalog_basename(char* out, int outSz, const char* entry) {
|
||||
// Strip trailing slash
|
||||
int len = montauk::slen(entry);
|
||||
while (len > 0 && entry[len - 1] == '/') len--;
|
||||
|
||||
// Find last slash before that
|
||||
int last_slash = -1;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (entry[i] == '/') last_slash = i;
|
||||
}
|
||||
|
||||
const char* base = (last_slash >= 0) ? entry + last_slash + 1 : entry;
|
||||
int base_len = len - (last_slash >= 0 ? last_slash + 1 : 0);
|
||||
if (base_len >= outSz) base_len = outSz - 1;
|
||||
montauk::memcpy(out, base, base_len);
|
||||
out[base_len] = '\0';
|
||||
}
|
||||
|
||||
// Return a slot for the next desktop item, doubling the array as needed.
|
||||
// Returns nullptr if allocation failed.
|
||||
inline DesktopItem* app_catalog_next_slot(DesktopState* ds) {
|
||||
if (ds->desktop_item_count >= ds->desktop_item_capacity) {
|
||||
int new_cap = ds->desktop_item_capacity ? ds->desktop_item_capacity * 2 : 32;
|
||||
auto* grown = (DesktopItem*)montauk::realloc(
|
||||
ds->desktop_items, (uint64_t)new_cap * sizeof(DesktopItem));
|
||||
if (!grown) return nullptr;
|
||||
ds->desktop_items = grown;
|
||||
ds->desktop_item_capacity = new_cap;
|
||||
}
|
||||
return &ds->desktop_items[ds->desktop_item_count];
|
||||
}
|
||||
|
||||
// Append every valid app manifest under 0:/apps to ds->desktop_items.
|
||||
inline void app_catalog_add_manifest_apps(DesktopState* ds) {
|
||||
// Ensure the apps directory exists
|
||||
montauk::fmkdir("0:/apps");
|
||||
|
||||
// Page through the directory with readdir_at; a single readdir call
|
||||
// returns at most one batch and would silently drop apps past it.
|
||||
constexpr int BATCH = 128;
|
||||
const char* entries[BATCH];
|
||||
int scanned = 0;
|
||||
int got;
|
||||
while ((got = montauk::readdir_at("0:/apps", entries, BATCH, scanned)) > 0) {
|
||||
scanned += got;
|
||||
|
||||
for (int i = 0; i < got; i++) {
|
||||
// Strip the directory marker and tolerate old root-relative entries.
|
||||
char dirname[64];
|
||||
app_catalog_basename(dirname, sizeof(dirname), entries[i]);
|
||||
if (dirname[0] == '\0') continue;
|
||||
|
||||
// Try to open the manifest in this app directory
|
||||
char manifest_path[128];
|
||||
app_catalog_join(manifest_path, sizeof(manifest_path), "0:/apps/", dirname, "/manifest.toml");
|
||||
|
||||
int fh = montauk::open(manifest_path);
|
||||
if (fh < 0) continue;
|
||||
|
||||
uint64_t sz = montauk::getsize(fh);
|
||||
if (sz == 0 || sz > 4096) { montauk::close(fh); continue; }
|
||||
|
||||
char* text = (char*)montauk::malloc(sz + 1);
|
||||
if (!text) { montauk::close(fh); continue; }
|
||||
int bytes_read = montauk::read(fh, (uint8_t*)text, 0, sz);
|
||||
montauk::close(fh);
|
||||
if (bytes_read != (int)sz) {
|
||||
montauk::mfree(text);
|
||||
continue;
|
||||
}
|
||||
text[sz] = '\0';
|
||||
|
||||
auto doc = montauk::toml::parse(text);
|
||||
montauk::mfree(text);
|
||||
|
||||
// Read manifest fields
|
||||
const char* id = doc.get_string("app.id", dirname);
|
||||
const char* name = doc.get_string("app.name", "Unknown");
|
||||
const char* binary = doc.get_string("app.binary", "");
|
||||
const char* icon_file = doc.get_string("app.icon", "");
|
||||
const char* category = doc.get_string("menu.category", "Productivity");
|
||||
// "Applications" was the old catch-all; keep older manifests out of
|
||||
// a stray custom category.
|
||||
if (!category[0] || montauk::streq(category, "Applications"))
|
||||
category = "Productivity";
|
||||
bool visible = doc.get_bool("menu.visible", true);
|
||||
bool launch_with_home = doc.get_bool("launch.pass_home_dir", true);
|
||||
const char* section_name = doc.get_string("desktop.section", "apps");
|
||||
bool admin_only = doc.get_bool("desktop.admin_only", false);
|
||||
if (id[0] == '\0' || binary[0] == '\0') {
|
||||
montauk::print("app catalog: skipping app manifest with missing id or binary\n");
|
||||
doc.destroy();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool duplicate = false;
|
||||
for (int existing = 0; existing < ds->desktop_item_count; existing++) {
|
||||
if (montauk::streq(ds->desktop_items[existing].id, id)) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (duplicate) {
|
||||
montauk::print("app catalog: skipping app manifest with duplicate id\n");
|
||||
doc.destroy();
|
||||
continue;
|
||||
}
|
||||
|
||||
DesktopItemSection section = DESKTOP_ITEM_SECTION_APPS;
|
||||
if (montauk::streq(section_name, "settings")) {
|
||||
section = DESKTOP_ITEM_SECTION_SETTINGS;
|
||||
} else if (montauk::streq(section_name, "hidden")) {
|
||||
section = DESKTOP_ITEM_SECTION_HIDDEN;
|
||||
} else if (!montauk::streq(section_name, "apps")) {
|
||||
montauk::print("app catalog: skipping app manifest with invalid desktop.section\n");
|
||||
doc.destroy();
|
||||
continue;
|
||||
}
|
||||
|
||||
DesktopItem* item = app_catalog_next_slot(ds);
|
||||
if (!item) {
|
||||
doc.destroy();
|
||||
return;
|
||||
}
|
||||
montauk::memset(item, 0, sizeof(*item));
|
||||
|
||||
montauk::strncpy(item->id, id, sizeof(item->id));
|
||||
montauk::strncpy(item->name, name, sizeof(item->name));
|
||||
montauk::strncpy(item->category, category, sizeof(item->category));
|
||||
item->menu_visible = visible;
|
||||
item->launch_with_home = launch_with_home;
|
||||
item->admin_only = admin_only;
|
||||
item->launch_kind = DESKTOP_ITEM_LAUNCH_EXECUTABLE;
|
||||
item->builtin_id = -1;
|
||||
item->section = section;
|
||||
|
||||
// Build full binary path: 0:/apps/<dir>/<binary>
|
||||
app_catalog_join(item->binary_path, sizeof(item->binary_path),
|
||||
"0:/apps/", dirname, "/", binary);
|
||||
|
||||
// File types the app opens, used by Files for default apps and
|
||||
// "Open With". Stored lowercased as " ext1 ext2 " for whole-word
|
||||
// matching; entries that would overflow the buffer are dropped.
|
||||
if (auto* types = doc.get_array("files.types")) {
|
||||
int len = 0;
|
||||
item->file_types[len++] = ' ';
|
||||
for (int t = 0; t < types->array.count; t++) {
|
||||
auto* v = types->array.items[t];
|
||||
if (v->type != montauk::toml::Type::String || !v->str[0]) continue;
|
||||
const char* ext = v->str[0] == '.' ? v->str + 1 : v->str;
|
||||
int elen = montauk::slen(ext);
|
||||
if (elen == 0 || len + elen + 2 > (int)sizeof(item->file_types)) continue;
|
||||
for (int k = 0; k < elen; k++) {
|
||||
char ch = ext[k];
|
||||
item->file_types[len++] = (ch >= 'A' && ch <= 'Z') ? ch + 32 : ch;
|
||||
}
|
||||
item->file_types[len++] = ' ';
|
||||
}
|
||||
item->file_types[len < 2 ? 0 : len] = '\0';
|
||||
}
|
||||
|
||||
// Load icon from app directory
|
||||
item->icon = {};
|
||||
item->icon_path[0] = '\0';
|
||||
if (icon_file[0]) {
|
||||
app_catalog_join(item->icon_path, sizeof(item->icon_path),
|
||||
"0:/apps/", dirname, "/", icon_file);
|
||||
Color defColor = colors::ICON_COLOR;
|
||||
item->icon = svg_load(item->icon_path, 20, 20, defColor);
|
||||
}
|
||||
|
||||
doc.destroy();
|
||||
ds->desktop_item_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline int app_catalog_compare_ci(const char* a, const char* b) {
|
||||
while (*a && *b) {
|
||||
char ca = (*a >= 'A' && *a <= 'Z') ? (*a + 32) : *a;
|
||||
char cb = (*b >= 'A' && *b <= 'Z') ? (*b + 32) : *b;
|
||||
if (ca != cb) return ca - cb;
|
||||
a++; b++;
|
||||
}
|
||||
return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
// Indices of the catalog items in `section` the session may see, sorted by
|
||||
// name.
|
||||
inline int app_catalog_list_indices(DesktopState* ds, DesktopItemSection section,
|
||||
int* out, int max_entries) {
|
||||
if (!ds || !out || max_entries <= 0) return 0;
|
||||
|
||||
int written = 0;
|
||||
for (int i = 0; i < ds->desktop_item_count && written < max_entries; i++) {
|
||||
const DesktopItem& item = ds->desktop_items[i];
|
||||
if (item.section != section) continue;
|
||||
if (item.admin_only && !ds->is_admin) continue;
|
||||
out[written++] = i;
|
||||
}
|
||||
|
||||
for (int i = 1; i < written; i++) {
|
||||
int tmp = out[i];
|
||||
int j = i - 1;
|
||||
while (j >= 0 && app_catalog_compare_ci(ds->desktop_items[tmp].name,
|
||||
ds->desktop_items[out[j]].name) < 0) {
|
||||
out[j + 1] = out[j];
|
||||
j--;
|
||||
}
|
||||
out[j + 1] = tmp;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
// ---- File associations ------------------------------------------------------
|
||||
//
|
||||
// Which app opens a file: the user's "Always use" choice from
|
||||
// <home>/config/associations.toml ([open_with] ext = "app id"), else the
|
||||
// first app (by name) whose manifest lists the extension in files.types.
|
||||
|
||||
inline constexpr const char* kAssociationsConfig = "associations";
|
||||
|
||||
inline bool app_catalog_launchable(const DesktopState* ds, const DesktopItem& item) {
|
||||
if (item.launch_kind != DESKTOP_ITEM_LAUNCH_EXECUTABLE || !item.binary_path[0]) return false;
|
||||
return !(item.admin_only && !ds->is_admin);
|
||||
}
|
||||
|
||||
// file_types is stored as " ext1 ext2 ", so a whole-word match is " ext ".
|
||||
inline bool app_catalog_claims(const DesktopItem& item, const char* ext) {
|
||||
if (!ext || !ext[0] || !item.file_types[0]) return false;
|
||||
int elen = montauk::slen(ext);
|
||||
for (const char* p = item.file_types; *p; p++) {
|
||||
if (*p != ' ') continue;
|
||||
int k = 0;
|
||||
while (k < elen && p[1 + k] == ext[k]) k++;
|
||||
if (k == elen && p[1 + elen] == ' ') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The config writer emits bare keys, so only extensions made of bare-key
|
||||
// characters can be remembered.
|
||||
inline bool app_catalog_ext_storable(const char* ext) {
|
||||
if (!ext || !ext[0] || montauk::slen(ext) > 15) return false;
|
||||
for (const char* p = ext; *p; p++) {
|
||||
char c = *p;
|
||||
bool ok = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-';
|
||||
if (!ok) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void app_catalog_assoc_key(char* out, int out_max, const char* ext) {
|
||||
app_catalog_join(out, out_max, "open_with.", ext, "");
|
||||
}
|
||||
|
||||
// Lowercased extension after the last '.', "" if none (".profile" has none).
|
||||
inline void app_catalog_extension(const char* name, char* out, int out_max) {
|
||||
if (!out || out_max <= 0) return;
|
||||
out[0] = '\0';
|
||||
if (!name) return;
|
||||
const char* dot = nullptr;
|
||||
for (const char* p = name; *p; p++) {
|
||||
if (*p == '.') dot = p;
|
||||
if (*p == '/') dot = nullptr;
|
||||
}
|
||||
if (!dot || dot == name || dot[-1] == '/' || !dot[1]) return;
|
||||
int n = 0;
|
||||
for (const char* p = dot + 1; *p && n < out_max - 1; p++) {
|
||||
char ch = *p;
|
||||
out[n++] = (ch >= 'A' && ch <= 'Z') ? ch + 32 : ch;
|
||||
}
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
// Default app for ext as a desktop_items index, or -1.
|
||||
inline int app_catalog_default_for(DesktopState* ds, const char* ext) {
|
||||
if (!ds || !ext || !ext[0]) return -1;
|
||||
|
||||
if (ds->current_user[0] && app_catalog_ext_storable(ext)) {
|
||||
auto doc = montauk::config::load_user(ds->current_user, kAssociationsConfig);
|
||||
char key[48];
|
||||
app_catalog_assoc_key(key, sizeof(key), ext);
|
||||
const char* id = doc.get_string(key, "");
|
||||
int found = -1;
|
||||
for (int i = 0; id[0] && i < ds->desktop_item_count; i++) {
|
||||
const DesktopItem& item = ds->desktop_items[i];
|
||||
if (montauk::streq(item.id, id) && app_catalog_launchable(ds, item)) { found = i; break; }
|
||||
}
|
||||
doc.destroy();
|
||||
if (found >= 0) return found;
|
||||
}
|
||||
|
||||
// Several apps may claim an extension; pick by name so the choice does
|
||||
// not depend on directory enumeration order.
|
||||
int best = -1;
|
||||
for (int i = 0; i < ds->desktop_item_count; i++) {
|
||||
const DesktopItem& item = ds->desktop_items[i];
|
||||
if (!app_catalog_launchable(ds, item) || !app_catalog_claims(item, ext)) continue;
|
||||
if (best < 0 || app_catalog_compare_ci(item.name, ds->desktop_items[best].name) < 0) best = i;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// True if the file looks like text (no NULs / stray control bytes in its
|
||||
// first KiB). Empty files count as text.
|
||||
inline bool app_catalog_file_looks_textual(const char* path) {
|
||||
int fd = montauk::open(path);
|
||||
if (fd < 0) return false;
|
||||
uint64_t size = montauk::getsize(fd);
|
||||
if (size == 0) {
|
||||
montauk::close(fd);
|
||||
return true;
|
||||
}
|
||||
uint8_t buf[1024];
|
||||
uint64_t want = size < sizeof(buf) ? size : sizeof(buf);
|
||||
int got = montauk::read(fd, buf, 0, want);
|
||||
montauk::close(fd);
|
||||
if (got <= 0) return false;
|
||||
// NUL or C0 control bytes other than tab/newline/CR/form feed/escape mean
|
||||
// binary data. Bytes >= 0x80 pass: UTF-8 and the 1252 fallback are text.
|
||||
for (int i = 0; i < got; i++) {
|
||||
uint8_t b = buf[i];
|
||||
if (b >= 0x20 || b == '\t' || b == '\n' || b == '\r' || b == 0x0C || b == 0x1B) continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
@@ -96,6 +96,18 @@ struct DesktopSettings {
|
||||
int tz_offset_minutes; // timezone offset from UTC in minutes
|
||||
};
|
||||
|
||||
// The standard folders in a home directory, in display order, with their
|
||||
// icons (0:/os/icons). Shared by the desktop launcher and Files.
|
||||
inline constexpr int SPECIAL_FOLDER_COUNT = 6;
|
||||
inline constexpr const char* kSpecialFolderNames[SPECIAL_FOLDER_COUNT] = {
|
||||
"Documents", "Desktop", "Music", "Videos", "Pictures", "Downloads"
|
||||
};
|
||||
inline constexpr const char* kSpecialFolderIcons[SPECIAL_FOLDER_COUNT] = {
|
||||
"folder-blue-documents.svg", "folder-blue-desktop.svg",
|
||||
"folder-blue-music.svg", "folder-blue-videos.svg",
|
||||
"folder-blue-pictures.svg", "folder-blue-downloads.svg"
|
||||
};
|
||||
|
||||
struct DesktopState {
|
||||
Framebuffer fb;
|
||||
Window windows[MAX_WINDOWS];
|
||||
@@ -128,6 +140,55 @@ struct DesktopState {
|
||||
// releases are delivered too) so mouse handlers can see Ctrl/Shift.
|
||||
bool kbd_shift_down;
|
||||
bool kbd_ctrl_down;
|
||||
bool kbd_alt_down;
|
||||
|
||||
// Drag and drop between windows (desktop/messaging.cpp). The source app
|
||||
// starts it with SYS_WIN_DRAG_BEGIN; the desktop routes the pointer.
|
||||
struct {
|
||||
bool active;
|
||||
uint32_t serial;
|
||||
int source_ext_id;
|
||||
uint8_t actions; // WIN_DRAG_* offered by the source
|
||||
uint32_t item_count;
|
||||
char label[64];
|
||||
int target_ext_id; // window under the pointer, -1 = none
|
||||
uint8_t target_action; // last WIN_REQ_DRAG_STATUS answer
|
||||
bool target_answered;
|
||||
int last_x, last_y;
|
||||
uint8_t last_modifiers;
|
||||
bool over_desktop; // pointer over the background, not a window
|
||||
} drag;
|
||||
|
||||
// The user's Desktop folder, shown as icons on the background
|
||||
// (desktop/desktop_icons.cpp).
|
||||
struct DesktopIcon {
|
||||
char name[128];
|
||||
bool is_dir;
|
||||
};
|
||||
DesktopIcon* desktop_icons;
|
||||
int desktop_icon_count;
|
||||
int desktop_icon_cap;
|
||||
uint64_t desktop_icons_hash; // detects changes between rescans
|
||||
uint64_t desktop_icons_next_scan; // ms
|
||||
int desktop_icon_selected; // -1 = none
|
||||
int desktop_icon_press; // icon the left button went down on, -1 = none
|
||||
int desktop_icon_press_x, desktop_icon_press_y;
|
||||
int desktop_icon_last_click;
|
||||
uint64_t desktop_icon_last_click_ms;
|
||||
// An incoming drag over the background: its file list (read once per
|
||||
// serial) and the icon highlighted as the drop target.
|
||||
uint32_t desktop_drop_serial;
|
||||
char* desktop_drop_list;
|
||||
int desktop_drop_icon;
|
||||
|
||||
// WIN_REQ_SET_PARENT requests whose dialog window has not been mapped
|
||||
// yet; applied as soon as it appears.
|
||||
struct {
|
||||
int child_ext_id;
|
||||
int parent_ext_id;
|
||||
uint64_t expires_ms;
|
||||
} pending_parents[8];
|
||||
int pending_parent_count;
|
||||
|
||||
SvgIcon icon_terminal;
|
||||
SvgIcon icon_filemanager;
|
||||
@@ -168,7 +229,7 @@ struct DesktopState {
|
||||
SvgIcon icon_system_configuration_lg;
|
||||
|
||||
// Special user folder icons (16x16 and 48x48)
|
||||
static constexpr int SPECIAL_FOLDER_COUNT = 6;
|
||||
static constexpr int SPECIAL_FOLDER_COUNT = gui::SPECIAL_FOLDER_COUNT;
|
||||
SvgIcon icon_special_folder[SPECIAL_FOLDER_COUNT];
|
||||
SvgIcon icon_special_folder_lg[SPECIAL_FOLDER_COUNT];
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -223,6 +223,14 @@ extern "C" {
|
||||
#define MTK_SYS_FUTEX_WAKE 197
|
||||
#define MTK_SYS_ABORT 198
|
||||
#define MTK_SYS_EXIT_GROUP 199
|
||||
#define MTK_SYS_CLIPBOARD_SET_DATA 200
|
||||
#define MTK_SYS_CLIPBOARD_FORMATS 201
|
||||
#define MTK_SYS_CLIPBOARD_GET_DATA 202
|
||||
#define MTK_SYS_WIN_REQUEST 203
|
||||
#define MTK_SYS_WIN_REQUEST_POLL 204
|
||||
#define MTK_SYS_WIN_DRAG_BEGIN 205
|
||||
#define MTK_SYS_WIN_DRAG_FORMATS 206
|
||||
#define MTK_SYS_WIN_DRAG_GET_DATA 207
|
||||
/* @SYSCALLS-END */
|
||||
|
||||
/* Syscall status codes (montauk/statuscodes.h for C++). */
|
||||
@@ -246,6 +254,8 @@ extern "C" {
|
||||
#define MTK_STATUS_OUT_OF_MEMORY (-14)
|
||||
#define MTK_STATUS_TIMED_OUT (-15)
|
||||
#define MTK_STATUS_WOULD_BLOCK (-16)
|
||||
#define MTK_STATUS_NOT_FOUND (-17)
|
||||
#define MTK_STATUS_ALREADY_EXISTS (-18)
|
||||
/* @STATUS-END */
|
||||
|
||||
#define MTK_SOCK_TCP 1
|
||||
|
||||
@@ -29,11 +29,13 @@ typedef enum {
|
||||
BufferTooSmall = 13,
|
||||
OutOfMemory = 14,
|
||||
TimedOut = 15,
|
||||
WouldBlock = 16
|
||||
WouldBlock = 16,
|
||||
NotFound = 17,
|
||||
AlreadyExists = 18
|
||||
} StatusCode;
|
||||
|
||||
// Highest defined StatusCode; update when adding a code above.
|
||||
enum { MaxStatusCode = WouldBlock };
|
||||
enum { MaxStatusCode = AlreadyExists };
|
||||
|
||||
inline const char* GetStatusText(StatusCode statusCode) {
|
||||
const char* statusText[] = {
|
||||
@@ -54,6 +56,8 @@ inline const char* GetStatusText(StatusCode statusCode) {
|
||||
"Out of memory", // 14
|
||||
"Timed out", // 15
|
||||
"Operation would block", // 16
|
||||
"Not found", // 17
|
||||
"Already exists", // 18
|
||||
};
|
||||
|
||||
if (statusCode > 0 && statusCode <= MaxStatusCode) {
|
||||
@@ -90,6 +94,8 @@ inline const char* GetStatusExplanationText(StatusCode statusCode) {
|
||||
0, // 14
|
||||
0, // 15
|
||||
0, // 16
|
||||
0, // 17
|
||||
"Something with that name is already there.", // 18
|
||||
};
|
||||
|
||||
if (statusCode > 0 && statusCode <= MaxStatusCode) {
|
||||
|
||||
@@ -795,6 +795,20 @@ namespace montauk {
|
||||
inline int clipboard_clear() {
|
||||
return (int)syscall0(montauk::abi::SYS_CLIPBOARD_CLEAR);
|
||||
}
|
||||
// Multi-format clipboard. The text calls above operate on its
|
||||
// TRANSFER_MIME_TEXT format; gui/transfer.hpp has friendlier helpers.
|
||||
inline int clipboard_set_data(const montauk::abi::TransferItem* items, uint32_t count) {
|
||||
return (int)syscall2(montauk::abi::SYS_CLIPBOARD_SET_DATA, (uint64_t)items, (uint64_t)count);
|
||||
}
|
||||
inline int clipboard_formats(montauk::abi::TransferFormatInfo* out, uint32_t max,
|
||||
uint64_t* outSerial = nullptr) {
|
||||
return (int)syscall3(montauk::abi::SYS_CLIPBOARD_FORMATS, (uint64_t)out, (uint64_t)max,
|
||||
(uint64_t)outSerial);
|
||||
}
|
||||
inline int clipboard_get_data(const char* mime, void* buf, uint32_t bufLen, uint32_t* outLen) {
|
||||
return (int)syscall4(montauk::abi::SYS_CLIPBOARD_GET_DATA, (uint64_t)mime, (uint64_t)buf,
|
||||
(uint64_t)bufLen, (uint64_t)outLen);
|
||||
}
|
||||
|
||||
// Window server
|
||||
inline int win_create(const char* title, int w, int h, montauk::abi::WinCreateResult* result) {
|
||||
@@ -837,4 +851,26 @@ namespace montauk {
|
||||
return (int)syscall2(montauk::abi::SYS_WINSETFLAGS, (uint64_t)id, (uint64_t)flags);
|
||||
}
|
||||
|
||||
// Window server messaging (see Api/Syscall.hpp, gui/transfer.hpp).
|
||||
inline int win_request(int id, const montauk::abi::WinRequest* request) {
|
||||
return (int)syscall2(montauk::abi::SYS_WIN_REQUEST, (uint64_t)id, (uint64_t)request);
|
||||
}
|
||||
inline int win_request_poll(montauk::abi::WinRequest* out) {
|
||||
return (int)syscall1(montauk::abi::SYS_WIN_REQUEST_POLL, (uint64_t)out);
|
||||
}
|
||||
inline int win_drag_begin(int id, const montauk::abi::WinDragBegin* info,
|
||||
const montauk::abi::TransferItem* items, uint32_t count) {
|
||||
return (int)syscall4(montauk::abi::SYS_WIN_DRAG_BEGIN, (uint64_t)id, (uint64_t)info,
|
||||
(uint64_t)items, (uint64_t)count);
|
||||
}
|
||||
inline int win_drag_formats(uint32_t serial, montauk::abi::TransferFormatInfo* out, uint32_t max) {
|
||||
return (int)syscall3(montauk::abi::SYS_WIN_DRAG_FORMATS, (uint64_t)serial, (uint64_t)out,
|
||||
(uint64_t)max);
|
||||
}
|
||||
inline int win_drag_get_data(uint32_t serial, const char* mime, void* buf, uint32_t bufLen,
|
||||
uint32_t* outLen) {
|
||||
return (int)syscall5(montauk::abi::SYS_WIN_DRAG_GET_DATA, (uint64_t)serial, (uint64_t)mime,
|
||||
(uint64_t)buf, (uint64_t)bufLen, (uint64_t)outLen);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user