325 lines
11 KiB
C++
325 lines
11 KiB
C++
/*
|
|
* dialogs.hpp
|
|
* Shared modal dialog protocol and client helpers
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <stdio.h>
|
|
#include <montauk/syscall.h>
|
|
#include <montauk/string.h>
|
|
#include <Api/Syscall.hpp>
|
|
|
|
namespace gui::dialogs {
|
|
|
|
inline constexpr const char* APP_BINARY = "0:/apps/dialogs/dialogs.elf";
|
|
inline constexpr const char* STORAGE_DIR = "0:/tmp/dialogs";
|
|
|
|
enum RequestKind : uint8_t {
|
|
REQUEST_KIND_FILE = 1,
|
|
REQUEST_KIND_PRINT = 2,
|
|
};
|
|
|
|
enum FileDialogMode : uint8_t {
|
|
FILE_DIALOG_OPEN = 1,
|
|
FILE_DIALOG_SAVE = 2,
|
|
};
|
|
|
|
struct Request {
|
|
uint8_t kind;
|
|
uint8_t mode;
|
|
char title[96];
|
|
char initial_path[256];
|
|
char suggested_name[128];
|
|
char source_path[256];
|
|
char job_name[128];
|
|
char result_path[256];
|
|
};
|
|
|
|
struct Result {
|
|
char status[16];
|
|
char path[256];
|
|
char job_id[64];
|
|
char message[160];
|
|
};
|
|
|
|
inline void safe_copy(char* dst, int dst_len, const char* src) {
|
|
if (!dst || dst_len <= 0) return;
|
|
if (!src) src = "";
|
|
montauk::strncpy(dst, src, dst_len - 1);
|
|
dst[dst_len - 1] = '\0';
|
|
}
|
|
|
|
inline void reset_request(Request* req) {
|
|
if (!req) return;
|
|
montauk::memset(req, 0, sizeof(Request));
|
|
}
|
|
|
|
inline void reset_result(Result* res) {
|
|
if (!res) return;
|
|
montauk::memset(res, 0, sizeof(Result));
|
|
}
|
|
|
|
inline void ensure_storage_dirs() {
|
|
montauk::fmkdir("0:/tmp");
|
|
montauk::fmkdir(STORAGE_DIR);
|
|
}
|
|
|
|
inline bool write_text_file(const char* path, const char* text) {
|
|
if (!path || !path[0] || !text) return false;
|
|
int fd = montauk::fcreate(path);
|
|
if (fd < 0) return false;
|
|
int len = montauk::slen(text);
|
|
bool ok = montauk::fwrite(fd, (const uint8_t*)text, 0, (uint64_t)len) >= 0;
|
|
montauk::close(fd);
|
|
return ok;
|
|
}
|
|
|
|
inline bool read_text_file(const char* path, char* out, int out_len) {
|
|
if (!out || out_len <= 0) return false;
|
|
out[0] = '\0';
|
|
if (!path || !path[0]) return false;
|
|
int fd = montauk::open(path);
|
|
if (fd < 0) return false;
|
|
uint64_t size = montauk::getsize(fd);
|
|
if ((int)size >= out_len) size = (uint64_t)(out_len - 1);
|
|
int got = montauk::read(fd, (uint8_t*)out, 0, size);
|
|
montauk::close(fd);
|
|
if (got < 0) return false;
|
|
out[got] = '\0';
|
|
return true;
|
|
}
|
|
|
|
inline bool line_value(const char* text, const char* key, char* out, int out_len) {
|
|
if (!text || !key || !out || out_len <= 0) return false;
|
|
|
|
int key_len = montauk::slen(key);
|
|
const char* p = text;
|
|
while (*p) {
|
|
const char* line = p;
|
|
while (*p && *p != '\n') p++;
|
|
int line_len = (int)(p - line);
|
|
if (*p == '\n') p++;
|
|
|
|
if (line_len <= key_len || line[key_len] != '=') continue;
|
|
|
|
bool match = true;
|
|
for (int i = 0; i < key_len; i++) {
|
|
if (line[i] != key[i]) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!match) continue;
|
|
|
|
const char* value = line + key_len + 1;
|
|
int value_len = line_len - key_len - 1;
|
|
while (value_len > 0 && (value[value_len - 1] == '\r' || value[value_len - 1] == '\n'))
|
|
value_len--;
|
|
if (value_len >= out_len) value_len = out_len - 1;
|
|
montauk::memcpy(out, value, (uint64_t)value_len);
|
|
out[value_len] = '\0';
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline uint32_t parse_u32(const char* text) {
|
|
if (!text) return 0;
|
|
uint32_t value = 0;
|
|
for (int i = 0; text[i] >= '0' && text[i] <= '9'; i++) {
|
|
value = value * 10u + (uint32_t)(text[i] - '0');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
inline bool write_request_file(const char* path, const Request* req) {
|
|
if (!path || !req) return false;
|
|
char text[1536];
|
|
int n = snprintf(text, sizeof(text),
|
|
"kind=%u\n"
|
|
"mode=%u\n"
|
|
"title=%s\n"
|
|
"initial_path=%s\n"
|
|
"suggested_name=%s\n"
|
|
"source_path=%s\n"
|
|
"job_name=%s\n"
|
|
"result_path=%s\n",
|
|
(unsigned)req->kind,
|
|
(unsigned)req->mode,
|
|
req->title,
|
|
req->initial_path,
|
|
req->suggested_name,
|
|
req->source_path,
|
|
req->job_name,
|
|
req->result_path);
|
|
if (n <= 0 || n >= (int)sizeof(text)) return false;
|
|
return write_text_file(path, text);
|
|
}
|
|
|
|
inline bool read_request_file(const char* path, Request* req) {
|
|
if (!path || !req) return false;
|
|
char text[1536];
|
|
if (!read_text_file(path, text, sizeof(text))) return false;
|
|
|
|
reset_request(req);
|
|
char value[256];
|
|
if (line_value(text, "kind", value, sizeof(value))) req->kind = (uint8_t)parse_u32(value);
|
|
if (line_value(text, "mode", value, sizeof(value))) req->mode = (uint8_t)parse_u32(value);
|
|
line_value(text, "title", req->title, sizeof(req->title));
|
|
line_value(text, "initial_path", req->initial_path, sizeof(req->initial_path));
|
|
line_value(text, "suggested_name", req->suggested_name, sizeof(req->suggested_name));
|
|
line_value(text, "source_path", req->source_path, sizeof(req->source_path));
|
|
line_value(text, "job_name", req->job_name, sizeof(req->job_name));
|
|
line_value(text, "result_path", req->result_path, sizeof(req->result_path));
|
|
return req->kind != 0 && req->result_path[0] != '\0';
|
|
}
|
|
|
|
inline bool write_result_file(const char* path, const Result* res) {
|
|
if (!path || !res) return false;
|
|
char text[1024];
|
|
int n = snprintf(text, sizeof(text),
|
|
"status=%s\n"
|
|
"path=%s\n"
|
|
"job_id=%s\n"
|
|
"message=%s\n",
|
|
res->status,
|
|
res->path,
|
|
res->job_id,
|
|
res->message);
|
|
if (n <= 0 || n >= (int)sizeof(text)) return false;
|
|
return write_text_file(path, text);
|
|
}
|
|
|
|
inline bool read_result_file(const char* path, Result* res) {
|
|
if (!path || !res) return false;
|
|
char text[1024];
|
|
if (!read_text_file(path, text, sizeof(text))) return false;
|
|
|
|
reset_result(res);
|
|
line_value(text, "status", res->status, sizeof(res->status));
|
|
line_value(text, "path", res->path, sizeof(res->path));
|
|
line_value(text, "job_id", res->job_id, sizeof(res->job_id));
|
|
line_value(text, "message", res->message, sizeof(res->message));
|
|
return res->status[0] != '\0';
|
|
}
|
|
|
|
inline void make_unique_paths(char* request_path, int request_path_len,
|
|
char* result_path, int result_path_len) {
|
|
static uint32_t seq = 0;
|
|
ensure_storage_dirs();
|
|
uint64_t now = montauk::get_milliseconds();
|
|
int pid = montauk::getpid();
|
|
uint32_t cur = ++seq;
|
|
snprintf(request_path, request_path_len, "%s/%d-%llu-%u.req",
|
|
STORAGE_DIR, pid, (unsigned long long)now, (unsigned)cur);
|
|
snprintf(result_path, result_path_len, "%s/%d-%llu-%u.res",
|
|
STORAGE_DIR, pid, (unsigned long long)now, (unsigned)cur);
|
|
}
|
|
|
|
inline bool run_request(const Request* req, Result* out_res, char* out_message, int out_message_len) {
|
|
if (out_message && out_message_len > 0) out_message[0] = '\0';
|
|
if (!req) {
|
|
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, "invalid dialog request");
|
|
return false;
|
|
}
|
|
|
|
char request_path[256];
|
|
char result_path[256];
|
|
make_unique_paths(request_path, sizeof(request_path), result_path, sizeof(result_path));
|
|
|
|
Request req_copy = *req;
|
|
safe_copy(req_copy.result_path, sizeof(req_copy.result_path), result_path);
|
|
|
|
if (!write_request_file(request_path, &req_copy)) {
|
|
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, "failed to write dialog request");
|
|
return false;
|
|
}
|
|
|
|
int pid = montauk::spawn(APP_BINARY, request_path);
|
|
if (pid < 0) {
|
|
montauk::fdelete(request_path);
|
|
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, "failed to start dialog");
|
|
return false;
|
|
}
|
|
|
|
int proc = montauk::proc_open(pid);
|
|
if (proc >= 0) {
|
|
montauk::wait_handle(proc, Montauk::IPC_SIGNAL_EXITED);
|
|
montauk::close(proc);
|
|
} else {
|
|
montauk::waitpid(pid);
|
|
}
|
|
|
|
Result local_res = {};
|
|
bool ok = read_result_file(result_path, &local_res);
|
|
|
|
montauk::fdelete(request_path);
|
|
montauk::fdelete(result_path);
|
|
|
|
if (!ok) {
|
|
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, "dialog did not return a result");
|
|
return false;
|
|
}
|
|
|
|
if (out_res) *out_res = local_res;
|
|
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, local_res.message);
|
|
return montauk::streq(local_res.status, "ok");
|
|
}
|
|
|
|
inline bool open_file(const char* title,
|
|
const char* initial_path,
|
|
char* out_path, int out_path_len,
|
|
char* out_message = nullptr, int out_message_len = 0) {
|
|
Request req = {};
|
|
req.kind = REQUEST_KIND_FILE;
|
|
req.mode = FILE_DIALOG_OPEN;
|
|
safe_copy(req.title, sizeof(req.title), title ? title : "Open");
|
|
safe_copy(req.initial_path, sizeof(req.initial_path), initial_path);
|
|
|
|
Result res = {};
|
|
bool ok = run_request(&req, &res, out_message, out_message_len);
|
|
if (ok && out_path && out_path_len > 0) safe_copy(out_path, out_path_len, res.path);
|
|
return ok;
|
|
}
|
|
|
|
inline bool save_file(const char* title,
|
|
const char* initial_path,
|
|
const char* suggested_name,
|
|
char* out_path, int out_path_len,
|
|
char* out_message = nullptr, int out_message_len = 0) {
|
|
Request req = {};
|
|
req.kind = REQUEST_KIND_FILE;
|
|
req.mode = FILE_DIALOG_SAVE;
|
|
safe_copy(req.title, sizeof(req.title), title ? title : "Save");
|
|
safe_copy(req.initial_path, sizeof(req.initial_path), initial_path);
|
|
safe_copy(req.suggested_name, sizeof(req.suggested_name), suggested_name);
|
|
|
|
Result res = {};
|
|
bool ok = run_request(&req, &res, out_message, out_message_len);
|
|
if (ok && out_path && out_path_len > 0) safe_copy(out_path, out_path_len, res.path);
|
|
return ok;
|
|
}
|
|
|
|
inline bool print_file(const char* title,
|
|
const char* source_path,
|
|
const char* job_name,
|
|
char* out_job_id, int out_job_id_len,
|
|
char* out_message = nullptr, int out_message_len = 0) {
|
|
Request req = {};
|
|
req.kind = REQUEST_KIND_PRINT;
|
|
safe_copy(req.title, sizeof(req.title), title ? title : "Print");
|
|
safe_copy(req.source_path, sizeof(req.source_path), source_path);
|
|
safe_copy(req.job_name, sizeof(req.job_name), job_name);
|
|
|
|
Result res = {};
|
|
bool ok = run_request(&req, &res, out_message, out_message_len);
|
|
if (ok && out_job_id && out_job_id_len > 0) safe_copy(out_job_id, out_job_id_len, res.job_id);
|
|
return ok;
|
|
}
|
|
|
|
} // namespace gui::dialogs
|