wip: add clipboard support

This commit is contained in:
2026-04-09 20:44:52 +02:00
parent b7b810e2a4
commit 906828f3ec
11 changed files with 582 additions and 0 deletions
+220
View File
@@ -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;
}
}
+17
View File
@@ -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 "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 "LibSyscall.hpp" // SYS_LOAD_LIB, SYS_UNLOAD_LIB, SYS_DLSYM
#include "CrashReportSyscall.hpp" // SYS_CRASH_REPORT #include "CrashReportSyscall.hpp" // SYS_CRASH_REPORT
#include "Clipboard.hpp" // SYS_CLIPBOARD_*
// Assembly entry point // Assembly entry point
extern "C" void SyscallEntry(); extern "C" void SyscallEntry();
@@ -422,6 +423,22 @@ namespace Montauk {
return (int64_t)Sys_GetLibBase(frame->arg1); return (int64_t)Sys_GetLibBase(frame->arg1);
case SYS_CRASH_REPORT: case SYS_CRASH_REPORT:
return Sys_CrashReport((CrashReportInfo*)frame->arg1); 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: default:
return -1; return -1;
} }
+12
View File
@@ -214,6 +214,12 @@ namespace Montauk {
/* CrashReport.hpp */ /* CrashReport.hpp */
static constexpr uint64_t SYS_CRASH_REPORT = 118; 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_READABLE = 1u << 0;
static constexpr uint32_t IPC_SIGNAL_WRITABLE = 1u << 1; static constexpr uint32_t IPC_SIGNAL_WRITABLE = 1u << 1;
@@ -278,6 +284,12 @@ namespace Montauk {
uint32_t signals; uint32_t signals;
}; };
struct ClipboardInfo {
uint32_t textBytes;
uint32_t _pad;
uint64_t serial;
};
// Window server shared types // Window server shared types
struct WinEvent { struct WinEvent {
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
+12
View File
@@ -152,6 +152,12 @@ namespace Montauk {
/* CrashReport */ /* CrashReport */
static constexpr uint64_t SYS_CRASH_REPORT = 118; 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_READABLE = 1u << 0;
static constexpr uint32_t IPC_SIGNAL_WRITABLE = 1u << 1; static constexpr uint32_t IPC_SIGNAL_WRITABLE = 1u << 1;
@@ -225,6 +231,12 @@ namespace Montauk {
uint32_t signals; uint32_t signals;
}; };
struct ClipboardInfo {
uint32_t textBytes;
uint32_t _pad;
uint64_t serial;
};
// Window server shared types // Window server shared types
struct WinEvent { struct WinEvent {
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
+65
View File
@@ -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;
}
}
+16
View File
@@ -494,6 +494,22 @@ namespace montauk {
return (int)syscall2(Montauk::SYS_GETUSER, (uint64_t)buf, maxLen); 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 // Window server
inline int win_create(const char* title, int w, int h, Montauk::WinCreateResult* result) { 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); return (int)syscall4(Montauk::SYS_WINCREATE, (uint64_t)title, (uint64_t)w, (uint64_t)h, (uint64_t)result);
+2
View File
@@ -7,6 +7,7 @@
#include "desktop_internal.hpp" #include "desktop_internal.hpp"
#include <montauk/config.h> #include <montauk/config.h>
#include <montauk/user.h> #include <montauk/user.h>
#include <gui/clipboard.hpp>
// ============================================================================ // ============================================================================
// App Manifest Scanning // App Manifest Scanning
@@ -187,6 +188,7 @@ void gui::desktop_init(DesktopState* ds) {
montauk::memset(&ds->mouse, 0, sizeof(Montauk::MouseState)); montauk::memset(&ds->mouse, 0, sizeof(Montauk::MouseState));
montauk::set_mouse_bounds(ds->screen_w - 1, ds->screen_h - 1); 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 // Load SVG icons — scalable (colorful) for app menu, symbolic for toolbar/panel
Color defColor = colors::ICON_COLOR; Color defColor = colors::ICON_COLOR;
+77
View File
@@ -13,6 +13,7 @@
#include <gui/truetype.hpp> #include <gui/truetype.hpp>
#include <gui/svg.hpp> #include <gui/svg.hpp>
#include <gui/dialogs.hpp> #include <gui/dialogs.hpp>
#include <gui/clipboard.hpp>
#include <gui/stb_math.h> #include <gui/stb_math.h>
extern "C" { extern "C" {
@@ -442,6 +443,64 @@ static void set_status(const char* msg) {
g_status_msg[sizeof(g_status_msg) - 1] = '\0'; 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) { static bool looks_binary(const char* data, int len) {
if (!data || len <= 0) return false; if (!data || len <= 0) return false;
@@ -951,6 +1010,24 @@ static bool handle_key(Montauk::KeyEvent& key, int win_id) {
return true; 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 // Arrow keys
if (key.scancode == 0x48) { // Up if (key.scancode == 0x48) { // Up
if (key.shift) start_selection(); else if (g_has_selection) clear_selection(); if (key.shift) start_selection(); else if (g_has_selection) clear_selection();
+93
View File
@@ -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) { static void wp_on_insert_newline(WordProcessorState* wp, int abs_pos) {
if (wp->paragraph_count >= WP_MAX_PARAGRAPHS) return; if (wp->paragraph_count >= WP_MAX_PARAGRAPHS) return;
int para = wp_find_paragraph_at(wp, abs_pos); int para = wp_find_paragraph_at(wp, abs_pos);
@@ -1115,6 +1136,46 @@ void wp_delete_selection(WordProcessorState* wp) {
wp_clear_selection(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) { void wp_recompute_wrap(WordProcessorState* wp, int content_w, int layout_dpi) {
int wrap_width = content_w - WP_MARGIN * 2 - WP_SCROLLBAR_W; int wrap_width = content_w - WP_MARGIN * 2 - WP_SCROLLBAR_W;
if (wrap_width < 50) wrap_width = 50; if (wrap_width < 50) wrap_width = 50;
@@ -1664,6 +1725,38 @@ static bool wp_restore_snapshot(WordProcessorState* wp, const UndoSnapshot* snap
return true; 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) { void wp_save_file(WordProcessorState* wp) {
if (wp->filepath[0] == '\0') { if (wp->filepath[0] == '\0') {
wp_open_save_pathbar(wp); wp_open_save_pathbar(wp);
+65
View File
@@ -6,6 +6,7 @@
#include "wordprocessor.hpp" #include "wordprocessor.hpp"
#include <gui/dialogs.hpp> #include <gui/dialogs.hpp>
#include <gui/clipboard.hpp>
static void wp_set_status(WordProcessorState* wp, const char* msg) { static void wp_set_status(WordProcessorState* wp, const char* msg) {
if (!msg) 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'; 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) { static void wp_open_pathbar_for_open(WordProcessorState* wp) {
char path[256] = {}; char path[256] = {};
char msg[160] = {}; char msg[160] = {};
@@ -584,6 +636,19 @@ void wp_handle_key(const Montauk::KeyEvent& key) {
return; 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.scancode == 0x48) {
if (key.shift) wp_start_selection(wp); if (key.shift) wp_start_selection(wp);
else wp_clear_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_toggle_list(WordProcessorState* wp, uint8_t list_type);
void wp_insert_divider(WordProcessorState* wp, uint8_t divider_type); void wp_insert_divider(WordProcessorState* wp, uint8_t divider_type);
void wp_delete_selection(WordProcessorState* wp); 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); void wp_recompute_wrap(WordProcessorState* wp, int content_w, int layout_dpi);
int wp_find_wrap_line(WordProcessorState* wp, int abs_pos); int wp_find_wrap_line(WordProcessorState* wp, int abs_pos);