wip: add clipboard support
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Clipboard.hpp
|
||||
* SYS_CLIPBOARD_* plain-text clipboard syscalls
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
|
||||
#include "Syscall.hpp"
|
||||
|
||||
namespace Montauk {
|
||||
|
||||
static constexpr int ClipboardMaxSlots = 32;
|
||||
|
||||
struct ClipboardSlot {
|
||||
bool inUse;
|
||||
char user[32];
|
||||
char* text;
|
||||
uint32_t len;
|
||||
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;
|
||||
return proc->user[0] ? proc->user : "default";
|
||||
}
|
||||
|
||||
static void ClipboardCopyUser(char dst[32], const char* src) {
|
||||
if (src == nullptr || src[0] == '\0') src = "default";
|
||||
int i = 0;
|
||||
for (; i < 31 && src[i]; i++)
|
||||
dst[i] = src[i];
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
static bool ClipboardUserEquals(const char* a, const char* b) {
|
||||
int i = 0;
|
||||
while (a[i] && b[i]) {
|
||||
if (a[i] != b[i]) return false;
|
||||
i++;
|
||||
}
|
||||
return a[i] == b[i];
|
||||
}
|
||||
|
||||
static uint64_t ClipboardNextSerial(uint64_t serial) {
|
||||
serial++;
|
||||
return serial == 0 ? 1 : serial;
|
||||
}
|
||||
|
||||
static ClipboardSlot* ClipboardFindSlotLocked(const char* user) {
|
||||
for (int i = 0; i < ClipboardMaxSlots; i++) {
|
||||
if (g_clipboardSlots[i].inUse && ClipboardUserEquals(g_clipboardSlots[i].user, user))
|
||||
return &g_clipboardSlots[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ClipboardSlot* ClipboardEnsureSlotLocked(const char* user) {
|
||||
ClipboardSlot* freeSlot = nullptr;
|
||||
for (int i = 0; i < ClipboardMaxSlots; i++) {
|
||||
ClipboardSlot* slot = &g_clipboardSlots[i];
|
||||
if (slot->inUse) {
|
||||
if (ClipboardUserEquals(slot->user, user))
|
||||
return slot;
|
||||
} else if (freeSlot == nullptr) {
|
||||
freeSlot = slot;
|
||||
}
|
||||
}
|
||||
|
||||
if (freeSlot == nullptr) return nullptr;
|
||||
|
||||
freeSlot->inUse = true;
|
||||
ClipboardCopyUser(freeSlot->user, user);
|
||||
freeSlot->text = nullptr;
|
||||
freeSlot->len = 0;
|
||||
freeSlot->serial = 0;
|
||||
return freeSlot;
|
||||
}
|
||||
|
||||
static int Sys_ClipboardSetText(const char* data, uint32_t len) {
|
||||
if (len == 0)
|
||||
return Sys_ClipboardClear();
|
||||
if (data == nullptr || len > CLIPBOARD_MAX_TEXT_BYTES || Memory::g_heap == nullptr)
|
||||
return -1;
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (data[i] == '\0')
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* currentUser = ClipboardCurrentUser();
|
||||
if (currentUser == nullptr) return -1;
|
||||
|
||||
char user[32];
|
||||
ClipboardCopyUser(user, currentUser);
|
||||
|
||||
char* newText = (char*)Memory::g_heap->Request(len + 1);
|
||||
if (newText == nullptr) return -1;
|
||||
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 0;
|
||||
}
|
||||
|
||||
static int Sys_ClipboardGetInfo(ClipboardInfo* out) {
|
||||
if (out == nullptr) return -1;
|
||||
|
||||
out->textBytes = 0;
|
||||
out->_pad = 0;
|
||||
out->serial = 0;
|
||||
|
||||
const char* currentUser = ClipboardCurrentUser();
|
||||
if (currentUser == nullptr) return -1;
|
||||
|
||||
char user[32];
|
||||
ClipboardCopyUser(user, currentUser);
|
||||
|
||||
g_clipboardLock.Acquire();
|
||||
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||
if (slot != nullptr) {
|
||||
out->textBytes = slot->len;
|
||||
out->serial = slot->serial;
|
||||
}
|
||||
g_clipboardLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Sys_ClipboardGetText(char* buffer, uint32_t bufferLen, uint32_t* outLen, uint64_t* outSerial) {
|
||||
if (outLen == nullptr) return -1;
|
||||
|
||||
*outLen = 0;
|
||||
if (outSerial != nullptr) *outSerial = 0;
|
||||
|
||||
const char* currentUser = ClipboardCurrentUser();
|
||||
if (currentUser == nullptr) return -1;
|
||||
|
||||
char user[32];
|
||||
ClipboardCopyUser(user, currentUser);
|
||||
|
||||
g_clipboardLock.Acquire();
|
||||
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||
if (slot == nullptr || slot->len == 0) {
|
||||
if (slot != nullptr && outSerial != nullptr)
|
||||
*outSerial = slot->serial;
|
||||
g_clipboardLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
*outLen = slot->len;
|
||||
if (outSerial != nullptr) *outSerial = slot->serial;
|
||||
if (buffer == nullptr || bufferLen < slot->len) {
|
||||
g_clipboardLock.Release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < slot->len; i++)
|
||||
buffer[i] = slot->text[i];
|
||||
g_clipboardLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Sys_ClipboardClear() {
|
||||
const char* currentUser = ClipboardCurrentUser();
|
||||
if (currentUser == nullptr) return -1;
|
||||
|
||||
char user[32];
|
||||
ClipboardCopyUser(user, currentUser);
|
||||
|
||||
char* oldText = nullptr;
|
||||
|
||||
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);
|
||||
g_clipboardLock.Release();
|
||||
|
||||
if (oldText != nullptr)
|
||||
Memory::g_heap->Free(oldText);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,6 +35,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_*
|
||||
|
||||
// Assembly entry point
|
||||
extern "C" void SyscallEntry();
|
||||
@@ -422,6 +423,22 @@ namespace Montauk {
|
||||
return (int64_t)Sys_GetLibBase(frame->arg1);
|
||||
case SYS_CRASH_REPORT:
|
||||
return Sys_CrashReport((CrashReportInfo*)frame->arg1);
|
||||
case SYS_CLIPBOARD_SET_TEXT:
|
||||
if (frame->arg2 > 0 && !ValidUserPtr(frame->arg1)) return -1;
|
||||
return Sys_ClipboardSetText((const char*)frame->arg1, (uint32_t)frame->arg2);
|
||||
case SYS_CLIPBOARD_GET_INFO:
|
||||
if (!ValidUserPtr(frame->arg1)) return -1;
|
||||
return Sys_ClipboardGetInfo((ClipboardInfo*)frame->arg1);
|
||||
case SYS_CLIPBOARD_GET_TEXT:
|
||||
if (frame->arg2 > 0 && !ValidUserPtr(frame->arg1)) return -1;
|
||||
if (!ValidUserPtr(frame->arg3)) return -1;
|
||||
if (frame->arg4 != 0 && !IsUserPtr(frame->arg4)) return -1;
|
||||
return Sys_ClipboardGetText(IsUserPtr(frame->arg1) ? (char*)frame->arg1 : nullptr,
|
||||
(uint32_t)frame->arg2,
|
||||
(uint32_t*)frame->arg3,
|
||||
IsUserPtr(frame->arg4) ? (uint64_t*)frame->arg4 : nullptr);
|
||||
case SYS_CLIPBOARD_CLEAR:
|
||||
return Sys_ClipboardClear();
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -214,6 +214,12 @@ namespace Montauk {
|
||||
|
||||
/* CrashReport.hpp */
|
||||
static constexpr uint64_t SYS_CRASH_REPORT = 118;
|
||||
static constexpr uint64_t SYS_CLIPBOARD_SET_TEXT = 119;
|
||||
static constexpr uint64_t SYS_CLIPBOARD_GET_INFO = 120;
|
||||
static constexpr uint64_t SYS_CLIPBOARD_GET_TEXT = 121;
|
||||
static constexpr uint64_t SYS_CLIPBOARD_CLEAR = 122;
|
||||
|
||||
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
|
||||
|
||||
static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0;
|
||||
static constexpr uint32_t IPC_SIGNAL_WRITABLE = 1u << 1;
|
||||
@@ -278,6 +284,12 @@ namespace Montauk {
|
||||
uint32_t signals;
|
||||
};
|
||||
|
||||
struct ClipboardInfo {
|
||||
uint32_t textBytes;
|
||||
uint32_t _pad;
|
||||
uint64_t serial;
|
||||
};
|
||||
|
||||
// Window server shared types
|
||||
struct WinEvent {
|
||||
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
|
||||
|
||||
@@ -152,6 +152,12 @@ namespace Montauk {
|
||||
|
||||
/* CrashReport */
|
||||
static constexpr uint64_t SYS_CRASH_REPORT = 118;
|
||||
static constexpr uint64_t SYS_CLIPBOARD_SET_TEXT = 119;
|
||||
static constexpr uint64_t SYS_CLIPBOARD_GET_INFO = 120;
|
||||
static constexpr uint64_t SYS_CLIPBOARD_GET_TEXT = 121;
|
||||
static constexpr uint64_t SYS_CLIPBOARD_CLEAR = 122;
|
||||
|
||||
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
|
||||
|
||||
static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0;
|
||||
static constexpr uint32_t IPC_SIGNAL_WRITABLE = 1u << 1;
|
||||
@@ -225,6 +231,12 @@ namespace Montauk {
|
||||
uint32_t signals;
|
||||
};
|
||||
|
||||
struct ClipboardInfo {
|
||||
uint32_t textBytes;
|
||||
uint32_t _pad;
|
||||
uint64_t serial;
|
||||
};
|
||||
|
||||
// Window server shared types
|
||||
struct WinEvent {
|
||||
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* clipboard.hpp
|
||||
* Simple plain-text clipboard helpers for MontaukOS GUI apps
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
|
||||
namespace gui {
|
||||
|
||||
inline bool clipboard_set_text(const char* text, int len) {
|
||||
if (len < 0) return false;
|
||||
if (len > 0 && text == nullptr) return false;
|
||||
return montauk::clipboard_set_text(text, (uint32_t)len) == 0;
|
||||
}
|
||||
|
||||
inline bool clipboard_set_text(const char* text) {
|
||||
return clipboard_set_text(text, text ? montauk::slen(text) : 0);
|
||||
}
|
||||
|
||||
inline bool clipboard_get_info(Montauk::ClipboardInfo* out) {
|
||||
if (out == nullptr) return false;
|
||||
return montauk::clipboard_get_info(out) == 0;
|
||||
}
|
||||
|
||||
inline bool clipboard_has_text() {
|
||||
Montauk::ClipboardInfo info = {};
|
||||
return clipboard_get_info(&info) && info.textBytes > 0;
|
||||
}
|
||||
|
||||
inline char* clipboard_get_text_alloc(int* out_len = nullptr, uint64_t* out_serial = nullptr) {
|
||||
if (out_len) *out_len = 0;
|
||||
if (out_serial) *out_serial = 0;
|
||||
|
||||
Montauk::ClipboardInfo info = {};
|
||||
if (!clipboard_get_info(&info) || info.textBytes == 0)
|
||||
return nullptr;
|
||||
|
||||
uint32_t cap = info.textBytes + 1;
|
||||
char* text = (char*)montauk::malloc(cap);
|
||||
if (text == nullptr) return nullptr;
|
||||
|
||||
uint32_t actual_len = 0;
|
||||
uint64_t serial = 0;
|
||||
if (montauk::clipboard_get_text(text, info.textBytes, &actual_len, &serial) != 0 ||
|
||||
actual_len > info.textBytes) {
|
||||
montauk::mfree(text);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
text[actual_len] = '\0';
|
||||
if (out_len) *out_len = (int)actual_len;
|
||||
if (out_serial) *out_serial = serial;
|
||||
return text;
|
||||
}
|
||||
|
||||
inline bool clipboard_clear() {
|
||||
return montauk::clipboard_clear() == 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -494,6 +494,22 @@ namespace montauk {
|
||||
return (int)syscall2(Montauk::SYS_GETUSER, (uint64_t)buf, maxLen);
|
||||
}
|
||||
|
||||
// Clipboard
|
||||
inline int clipboard_set_text(const char* data, uint32_t len) {
|
||||
return (int)syscall2(Montauk::SYS_CLIPBOARD_SET_TEXT, (uint64_t)data, (uint64_t)len);
|
||||
}
|
||||
inline int clipboard_get_info(Montauk::ClipboardInfo* out) {
|
||||
return (int)syscall1(Montauk::SYS_CLIPBOARD_GET_INFO, (uint64_t)out);
|
||||
}
|
||||
inline int clipboard_get_text(char* buf, uint32_t bufLen, uint32_t* outLen,
|
||||
uint64_t* outSerial = nullptr) {
|
||||
return (int)syscall4(Montauk::SYS_CLIPBOARD_GET_TEXT, (uint64_t)buf, (uint64_t)bufLen,
|
||||
(uint64_t)outLen, (uint64_t)outSerial);
|
||||
}
|
||||
inline int clipboard_clear() {
|
||||
return (int)syscall0(Montauk::SYS_CLIPBOARD_CLEAR);
|
||||
}
|
||||
|
||||
// Window server
|
||||
inline int win_create(const char* title, int w, int h, Montauk::WinCreateResult* result) {
|
||||
return (int)syscall4(Montauk::SYS_WINCREATE, (uint64_t)title, (uint64_t)w, (uint64_t)h, (uint64_t)result);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "desktop_internal.hpp"
|
||||
#include <montauk/config.h>
|
||||
#include <montauk/user.h>
|
||||
#include <gui/clipboard.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// App Manifest Scanning
|
||||
@@ -187,6 +188,7 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
|
||||
montauk::memset(&ds->mouse, 0, sizeof(Montauk::MouseState));
|
||||
montauk::set_mouse_bounds(ds->screen_w - 1, ds->screen_h - 1);
|
||||
gui::clipboard_clear();
|
||||
|
||||
// Load SVG icons — scalable (colorful) for app menu, symbolic for toolbar/panel
|
||||
Color defColor = colors::ICON_COLOR;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <gui/truetype.hpp>
|
||||
#include <gui/svg.hpp>
|
||||
#include <gui/dialogs.hpp>
|
||||
#include <gui/clipboard.hpp>
|
||||
#include <gui/stb_math.h>
|
||||
|
||||
extern "C" {
|
||||
@@ -442,6 +443,64 @@ static void set_status(const char* msg) {
|
||||
g_status_msg[sizeof(g_status_msg) - 1] = '\0';
|
||||
}
|
||||
|
||||
static int selection_length() {
|
||||
if (!g_has_selection) return 0;
|
||||
int ss, se;
|
||||
sel_range(&ss, &se);
|
||||
return se - ss;
|
||||
}
|
||||
|
||||
static bool copy_selection_to_clipboard() {
|
||||
int len = selection_length();
|
||||
if (len <= 0) {
|
||||
set_status("Nothing selected");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ss, se;
|
||||
sel_range(&ss, &se);
|
||||
if (!gui::clipboard_set_text(g_buffer + ss, len)) {
|
||||
set_status("Copy failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
set_status("Copied selection");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cut_selection_to_clipboard() {
|
||||
if (!copy_selection_to_clipboard())
|
||||
return false;
|
||||
|
||||
delete_selection();
|
||||
set_status("Cut selection");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool paste_from_clipboard() {
|
||||
int clip_len = 0;
|
||||
char* clip_text = gui::clipboard_get_text_alloc(&clip_len);
|
||||
if (clip_text == nullptr || clip_len <= 0) {
|
||||
if (clip_text) montauk::mfree(clip_text);
|
||||
set_status("Clipboard is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
int replaced_len = selection_length();
|
||||
if (g_buf_len - replaced_len + clip_len >= MAX_CAP) {
|
||||
montauk::mfree(clip_text);
|
||||
set_status("Clipboard text too large");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_has_selection)
|
||||
delete_selection();
|
||||
insert_string(clip_text, clip_len);
|
||||
montauk::mfree(clip_text);
|
||||
set_status("Pasted clipboard");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool looks_binary(const char* data, int len) {
|
||||
if (!data || len <= 0) return false;
|
||||
|
||||
@@ -951,6 +1010,24 @@ static bool handle_key(Montauk::KeyEvent& key, int win_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ctrl+C: copy
|
||||
if (key.ctrl && !key.alt && (key.ascii == 'c' || key.ascii == 'C')) {
|
||||
copy_selection_to_clipboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ctrl+X: cut
|
||||
if (key.ctrl && !key.alt && (key.ascii == 'x' || key.ascii == 'X')) {
|
||||
cut_selection_to_clipboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ctrl+V: paste
|
||||
if (key.ctrl && !key.alt && (key.ascii == 'v' || key.ascii == 'V')) {
|
||||
paste_from_clipboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Arrow keys
|
||||
if (key.scancode == 0x48) { // Up
|
||||
if (key.shift) start_selection(); else if (g_has_selection) clear_selection();
|
||||
|
||||
@@ -194,6 +194,27 @@ static void wp_sync_paragraph_count(WordProcessorState* wp) {
|
||||
}
|
||||
}
|
||||
|
||||
static int wp_count_newline_chars(const char* text, int len) {
|
||||
if (len < 0) return -1;
|
||||
if (len > 0 && text == nullptr) return -1;
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (text[i] == '\0') return -1;
|
||||
if (text[i] == '\n') count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static int wp_count_newlines_in_range(WordProcessorState* wp, int start, int end) {
|
||||
int count = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
if (wp_char_at(wp, i) == '\n')
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void wp_on_insert_newline(WordProcessorState* wp, int abs_pos) {
|
||||
if (wp->paragraph_count >= WP_MAX_PARAGRAPHS) return;
|
||||
int para = wp_find_paragraph_at(wp, abs_pos);
|
||||
@@ -1115,6 +1136,46 @@ void wp_delete_selection(WordProcessorState* wp) {
|
||||
wp_clear_selection(wp);
|
||||
}
|
||||
|
||||
char* wp_selection_plain_text_alloc(WordProcessorState* wp, int* out_len) {
|
||||
if (out_len) *out_len = 0;
|
||||
if (wp == nullptr || !wp->has_selection) return nullptr;
|
||||
|
||||
int sel_s, sel_e;
|
||||
wp_sel_range(wp, &sel_s, &sel_e);
|
||||
int len = sel_e - sel_s;
|
||||
if (len <= 0) return nullptr;
|
||||
|
||||
char* text = (char*)montauk::malloc(len + 1);
|
||||
if (text == nullptr) return nullptr;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
text[i] = wp_char_at(wp, sel_s + i);
|
||||
text[len] = '\0';
|
||||
|
||||
if (out_len) *out_len = len;
|
||||
return text;
|
||||
}
|
||||
|
||||
bool wp_insert_text(WordProcessorState* wp, const char* text, int len) {
|
||||
int newline_count = wp_count_newline_chars(text, len);
|
||||
if (wp == nullptr || newline_count < 0) return false;
|
||||
if (len == 0) return true;
|
||||
if (wp->cursor_run < 0 || wp->cursor_run >= wp->run_count) return false;
|
||||
if (wp->total_text_len + len > WP_MAX_TEXT - 1) return false;
|
||||
if (wp->paragraph_count + newline_count > WP_MAX_PARAGRAPHS) return false;
|
||||
|
||||
StyledRun* cur = &wp->runs[wp->cursor_run];
|
||||
int extra_runs = 0;
|
||||
if (!wp_same_style(cur, wp->cur_font_id, wp->cur_size, wp->cur_flags)) {
|
||||
extra_runs = (wp->cursor_offset == 0 || wp->cursor_offset == cur->len) ? 1 : 2;
|
||||
}
|
||||
if (wp->run_count + extra_runs > WP_MAX_RUNS) return false;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
wp_insert_char(wp, text[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void wp_recompute_wrap(WordProcessorState* wp, int content_w, int layout_dpi) {
|
||||
int wrap_width = content_w - WP_MARGIN * 2 - WP_SCROLLBAR_W;
|
||||
if (wrap_width < 50) wrap_width = 50;
|
||||
@@ -1664,6 +1725,38 @@ static bool wp_restore_snapshot(WordProcessorState* wp, const UndoSnapshot* snap
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wp_replace_selection_with_text(WordProcessorState* wp, const char* text, int len) {
|
||||
int newline_count = wp_count_newline_chars(text, len);
|
||||
if (wp == nullptr || newline_count < 0) return false;
|
||||
|
||||
int sel_len = 0;
|
||||
int sel_newlines = 0;
|
||||
if (wp->has_selection) {
|
||||
int sel_s, sel_e;
|
||||
wp_sel_range(wp, &sel_s, &sel_e);
|
||||
sel_len = sel_e - sel_s;
|
||||
sel_newlines = wp_count_newlines_in_range(wp, sel_s, sel_e);
|
||||
}
|
||||
|
||||
if (wp->total_text_len - sel_len + len > WP_MAX_TEXT - 1)
|
||||
return false;
|
||||
if (wp->paragraph_count - sel_newlines + newline_count > WP_MAX_PARAGRAPHS)
|
||||
return false;
|
||||
|
||||
UndoSnapshot snap = {};
|
||||
if (!wp_capture_snapshot(wp, &snap))
|
||||
return false;
|
||||
|
||||
if (wp->has_selection)
|
||||
wp_delete_selection(wp);
|
||||
|
||||
bool ok = wp_insert_text(wp, text, len);
|
||||
if (!ok)
|
||||
wp_restore_snapshot(wp, &snap);
|
||||
wp_free_snapshot(&snap);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void wp_save_file(WordProcessorState* wp) {
|
||||
if (wp->filepath[0] == '\0') {
|
||||
wp_open_save_pathbar(wp);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "wordprocessor.hpp"
|
||||
#include <gui/dialogs.hpp>
|
||||
#include <gui/clipboard.hpp>
|
||||
|
||||
static void wp_set_status(WordProcessorState* wp, const char* msg) {
|
||||
if (!msg) msg = "";
|
||||
@@ -13,6 +14,57 @@ static void wp_set_status(WordProcessorState* wp, const char* msg) {
|
||||
wp->status_msg[sizeof(wp->status_msg) - 1] = '\0';
|
||||
}
|
||||
|
||||
static bool wp_copy_selection_to_clipboard(WordProcessorState* wp) {
|
||||
int len = 0;
|
||||
char* text = wp_selection_plain_text_alloc(wp, &len);
|
||||
if (text == nullptr || len <= 0) {
|
||||
if (text) montauk::mfree(text);
|
||||
wp_set_status(wp, "Nothing selected");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = gui::clipboard_set_text(text, len);
|
||||
montauk::mfree(text);
|
||||
if (!ok) {
|
||||
wp_set_status(wp, "Copy failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
wp_set_status(wp, "Copied selection");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool wp_cut_selection_to_clipboard(WordProcessorState* wp) {
|
||||
if (!wp_copy_selection_to_clipboard(wp))
|
||||
return false;
|
||||
|
||||
wp_delete_selection(wp);
|
||||
wp_history_checkpoint(wp);
|
||||
wp_set_status(wp, "Cut selection");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool wp_paste_from_clipboard(WordProcessorState* wp) {
|
||||
int len = 0;
|
||||
char* text = gui::clipboard_get_text_alloc(&len);
|
||||
if (text == nullptr || len <= 0) {
|
||||
if (text) montauk::mfree(text);
|
||||
wp_set_status(wp, "Clipboard is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = wp_replace_selection_with_text(wp, text, len);
|
||||
montauk::mfree(text);
|
||||
if (!ok) {
|
||||
wp_set_status(wp, "Clipboard text could not be inserted");
|
||||
return false;
|
||||
}
|
||||
|
||||
wp_history_checkpoint(wp);
|
||||
wp_set_status(wp, "Pasted clipboard");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void wp_open_pathbar_for_open(WordProcessorState* wp) {
|
||||
char path[256] = {};
|
||||
char msg[160] = {};
|
||||
@@ -584,6 +636,19 @@ void wp_handle_key(const Montauk::KeyEvent& key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.ctrl && !key.alt && (key.ascii == 'c' || key.ascii == 'C')) {
|
||||
wp_copy_selection_to_clipboard(wp);
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && !key.alt && (key.ascii == 'x' || key.ascii == 'X')) {
|
||||
wp_cut_selection_to_clipboard(wp);
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && !key.alt && (key.ascii == 'v' || key.ascii == 'V')) {
|
||||
wp_paste_from_clipboard(wp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.scancode == 0x48) {
|
||||
if (key.shift) wp_start_selection(wp);
|
||||
else wp_clear_selection(wp);
|
||||
|
||||
@@ -424,6 +424,9 @@ void wp_set_line_spacing(WordProcessorState* wp, int value);
|
||||
void wp_toggle_list(WordProcessorState* wp, uint8_t list_type);
|
||||
void wp_insert_divider(WordProcessorState* wp, uint8_t divider_type);
|
||||
void wp_delete_selection(WordProcessorState* wp);
|
||||
char* wp_selection_plain_text_alloc(WordProcessorState* wp, int* out_len);
|
||||
bool wp_insert_text(WordProcessorState* wp, const char* text, int len);
|
||||
bool wp_replace_selection_with_text(WordProcessorState* wp, const char* text, int len);
|
||||
|
||||
void wp_recompute_wrap(WordProcessorState* wp, int content_w, int layout_dpi);
|
||||
int wp_find_wrap_line(WordProcessorState* wp, int abs_pos);
|
||||
|
||||
Reference in New Issue
Block a user