Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-10-10 03:45:30 +03:00
parent 18614ccee9
commit 7834724e53
6 changed files with 676 additions and 28 deletions

View File

@@ -1,10 +1,12 @@
#include "App.hpp"
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
@@ -30,6 +32,84 @@
#include "blur-client-protocol.h"
#include "ext-background-effect-v1-client-protocol.h"
namespace {
constexpr std::size_t MAX_SURROUNDING_BYTES = 4000;
inline auto is_utf8_continuation(char c) -> bool
{
return (static_cast<unsigned char>(c) & 0xC0) == 0x80;
}
inline auto adjust_utf8_backward(std::string const &text, int index) -> int
{
index = std::clamp(index, 0, static_cast<int>(text.size()));
while (index > 0
&& is_utf8_continuation(text[static_cast<std::size_t>(index - 1)]))
--index;
return index;
}
inline auto adjust_utf8_forward(std::string const &text, int index) -> int
{
int const size = static_cast<int>(text.size());
index = std::clamp(index, 0, size);
while (index < size
&& is_utf8_continuation(text[static_cast<std::size_t>(index)]))
++index;
return index;
}
struct SurroundingSlice {
std::string text;
int cursor { 0 };
int anchor { 0 };
};
auto clamp_surrounding_text(
std::string const &text, int cursor, int anchor) -> SurroundingSlice
{
int const size = static_cast<int>(text.size());
cursor = std::clamp(cursor, 0, size);
anchor = std::clamp(anchor, 0, size);
if (text.size() <= MAX_SURROUNDING_BYTES) {
return SurroundingSlice { text, cursor, anchor };
}
int window_start = std::max(0,
std::min(cursor, anchor)
- static_cast<int>(MAX_SURROUNDING_BYTES / 2));
int window_end = window_start + static_cast<int>(MAX_SURROUNDING_BYTES);
int const max_pos = std::max(cursor, anchor);
if (window_end < max_pos) {
window_end = max_pos;
window_start = std::max(0,
window_end - static_cast<int>(MAX_SURROUNDING_BYTES));
}
if (window_end > size)
window_end = size;
if (window_end - window_start
> static_cast<int>(MAX_SURROUNDING_BYTES)) {
window_start = window_end - static_cast<int>(MAX_SURROUNDING_BYTES);
}
window_start = adjust_utf8_backward(text, window_start);
window_end = adjust_utf8_forward(text, window_end);
if (window_end < window_start)
window_end = window_start;
std::string slice(text.begin() + window_start, text.begin() + window_end);
int const new_cursor
= std::clamp(cursor - window_start, 0, static_cast<int>(slice.size()));
int const new_anchor
= std::clamp(anchor - window_start, 0, static_cast<int>(slice.size()));
return SurroundingSlice { std::move(slice), new_cursor, new_anchor };
}
} // namespace
auto TypingBuffer::push_utf8(char const *s) -> void
{
for (unsigned char const *p = reinterpret_cast<unsigned char const *>(s);
@@ -82,6 +162,14 @@ App::~App()
xkb_keymap_unref(m_kbd.xkb_keymap_v);
if (m_kbd.xkb_ctx_v)
xkb_context_unref(m_kbd.xkb_ctx_v);
if (m_wayland.text_input) {
zwp_text_input_v3_destroy(m_wayland.text_input);
m_wayland.text_input = nullptr;
}
if (m_wayland.text_input_mgr) {
zwp_text_input_manager_v3_destroy(m_wayland.text_input_mgr);
m_wayland.text_input_mgr = nullptr;
}
if (m_wayland.kbd)
wl_keyboard_destroy(m_wayland.kbd);
if (m_wayland.seat)
@@ -267,6 +355,91 @@ auto App::init_wayland() -> void
kb_repeat_info };
}
static zwp_text_input_v3_listener text_input_listener {};
{
auto ti_enter = [](void *data, zwp_text_input_v3 *, wl_surface *surface)
-> void {
auto *app = static_cast<App *>(data);
bool const focused_surface
= surface && surface == app->m_wayland.surface;
app->m_ime.seat_focus = focused_surface;
app->m_ime.pending = {};
app->m_ime.pending_done = false;
if (!focused_surface) {
app->m_ime.enabled = false;
app->m_ime.last_surrounding.clear();
if (app->m_gui)
app->m_gui->ime_clear_preedit();
} else {
app->m_ime.surrounding_dirty = true;
}
};
auto ti_leave = [](void *data, zwp_text_input_v3 *, wl_surface *) -> void {
auto *app = static_cast<App *>(data);
app->m_ime.seat_focus = false;
app->m_ime.enabled = false;
app->m_ime.pending = {};
app->m_ime.pending_done = false;
app->m_ime.last_surrounding.clear();
if (app->m_gui)
app->m_gui->ime_clear_preedit();
};
auto ti_preedit = [](void *data, zwp_text_input_v3 *, char const *text,
int32_t cursor_begin, int32_t cursor_end) -> void {
auto *app = static_cast<App *>(data);
auto &pending = app->m_ime.pending;
pending.has_preedit = true;
pending.preedit_text = text ? text : "";
pending.cursor_begin = cursor_begin;
pending.cursor_end = cursor_end;
};
auto ti_commit = [](void *data, zwp_text_input_v3 *, char const *text)
-> void {
auto *app = static_cast<App *>(data);
auto &pending = app->m_ime.pending;
pending.has_commit = true;
pending.commit_text = text ? text : "";
};
auto ti_delete = [](void *data, zwp_text_input_v3 *, uint32_t before,
uint32_t after) -> void {
auto *app = static_cast<App *>(data);
auto &pending = app->m_ime.pending;
pending.has_delete = true;
pending.before = before;
pending.after = after;
};
auto ti_done = [](void *data, zwp_text_input_v3 *, uint32_t serial) -> void {
auto *app = static_cast<App *>(data);
app->m_ime.pending_done = true;
app->m_ime.pending_serial = serial;
app->m_ime.surrounding_dirty = true;
};
text_input_listener = { ti_enter, ti_leave, ti_preedit, ti_commit,
ti_delete, ti_done };
}
static auto ensure_text_input = +[](App *app) -> void {
if (!app->m_wayland.text_input_mgr || !app->m_wayland.seat
|| app->m_wayland.text_input)
return;
app->m_wayland.text_input = zwp_text_input_manager_v3_get_text_input(
app->m_wayland.text_input_mgr, app->m_wayland.seat);
if (!app->m_wayland.text_input)
return;
zwp_text_input_v3_add_listener(
app->m_wayland.text_input, &text_input_listener, app);
app->m_ime.supported = true;
app->m_ime.enabled = false;
app->m_ime.last_surrounding.clear();
app->m_ime.sent_serial = 0;
};
auto handle_registry_global
= [](void *data, wl_registry *registry, u32 name, char const *interface,
u32 version) -> void {
@@ -285,7 +458,9 @@ auto App::init_wayland() -> void
app->m_wayland.kbd = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(
app->m_wayland.kbd, &keyboard_listener, data);
app->m_ime.seat_focus = false;
}
ensure_text_input(app);
},
.name = [](void *, struct wl_seat *, char const *) {},
};
@@ -306,6 +481,14 @@ auto App::init_wayland() -> void
app->m_wayland.kde_blur_mgr
= static_cast<org_kde_kwin_blur_manager *>(wl_registry_bind(
registry, name, &org_kde_kwin_blur_manager_interface, 1));
} else if (std::strcmp(interface,
zwp_text_input_manager_v3_interface.name)
== 0) {
app->m_wayland.text_input_mgr
= static_cast<zwp_text_input_manager_v3 *>(wl_registry_bind(
registry, name, &zwp_text_input_manager_v3_interface, 1));
app->m_ime.supported = true;
ensure_text_input(app);
}
};
@@ -619,6 +802,128 @@ auto App::update_blur_region() -> void
wl_region_destroy(region);
}
auto App::process_pending_text_input() -> void
{
if (!m_ime.pending_done)
return;
if (!m_gui)
return;
if (!m_ime.bound_text)
return;
if (!m_wayland.text_input)
return;
auto focused = m_gui->focused_text_input();
if (!focused || *focused != m_ime.bound_id) {
m_ime.pending = {};
m_ime.pending_done = false;
return;
}
m_gui->ime_clear_preedit();
if (m_ime.pending.has_delete) {
m_gui->ime_delete_surrounding(
*m_ime.bound_text, m_ime.pending.before, m_ime.pending.after);
}
if (m_ime.pending.has_commit) {
m_gui->ime_commit_text(
*m_ime.bound_text, m_ime.pending.commit_text);
}
if (m_ime.pending.has_preedit) {
m_gui->ime_set_preedit(m_ime.pending.preedit_text,
m_ime.pending.cursor_begin, m_ime.pending.cursor_end);
} else {
m_gui->ime_clear_preedit();
}
m_ime.pending = {};
m_ime.pending_done = false;
m_ime.surrounding_dirty = true;
}
auto App::update_text_input_state(
std::pmr::string const &text, std::size_t id, Rectangle field_rect) -> void
{
if (!m_wayland.text_input || !m_ime.supported || !m_gui)
return;
m_ime.bound_rect = field_rect;
auto focused = m_gui->focused_text_input();
bool const has_focus = focused && (*focused == id);
bool const should_enable = has_focus && m_ime.seat_focus;
if (!should_enable) {
if (m_ime.enabled) {
zwp_text_input_v3_disable(m_wayland.text_input);
zwp_text_input_v3_commit(m_wayland.text_input);
m_ime.sent_serial++;
m_ime.enabled = false;
m_ime.last_surrounding.clear();
}
return;
}
bool state_dirty = false;
if (!m_ime.enabled) {
zwp_text_input_v3_enable(m_wayland.text_input);
zwp_text_input_v3_set_content_type(m_wayland.text_input,
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL);
m_ime.enabled = true;
m_ime.surrounding_dirty = true;
state_dirty = true;
}
if (auto info = m_gui->text_input_surrounding(id, text)) {
auto slice = clamp_surrounding_text(
info->text, info->cursor, info->anchor);
if (m_ime.surrounding_dirty || slice.text != m_ime.last_surrounding
|| slice.cursor != m_ime.last_cursor
|| slice.anchor != m_ime.last_anchor) {
zwp_text_input_v3_set_surrounding_text(m_wayland.text_input,
slice.text.c_str(), slice.cursor, slice.anchor);
m_ime.last_surrounding = std::move(slice.text);
m_ime.last_cursor = slice.cursor;
m_ime.last_anchor = slice.anchor;
state_dirty = true;
}
}
if (auto cursor_info = m_gui->text_input_cursor(id)) {
Rectangle rect = cursor_info->rect;
int32_t const x = static_cast<int32_t>(std::round(rect.x));
int32_t const y = static_cast<int32_t>(std::round(rect.y));
int32_t const width
= std::max(1, static_cast<int32_t>(std::round(rect.width)));
int32_t const height
= std::max(1, static_cast<int32_t>(std::round(rect.height)));
bool const visible = cursor_info->visible;
if (rect.x != m_ime.last_cursor_rect.x
|| rect.y != m_ime.last_cursor_rect.y
|| rect.width != m_ime.last_cursor_rect.width
|| rect.height != m_ime.last_cursor_rect.height
|| visible != m_ime.last_cursor_visible) {
zwp_text_input_v3_set_cursor_rectangle(
m_wayland.text_input, x, y, width, height);
m_ime.last_cursor_rect = rect;
m_ime.last_cursor_visible = visible;
state_dirty = true;
}
}
if (state_dirty) {
zwp_text_input_v3_commit(m_wayland.text_input);
m_ime.sent_serial++;
m_ime.surrounding_dirty = false;
}
}
auto App::pump_events() -> void
{
while (g_main_context_iteration(nullptr, false))