Fix clipboard being bugged out and not pasting or copying

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-11-26 19:14:24 +02:00
parent cb2c866a35
commit 13589e1db9
6 changed files with 817 additions and 126 deletions

View File

@@ -233,6 +233,40 @@ 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.dd_source) {
wl_data_source_destroy(m_wayland.dd_source);
m_wayland.dd_source = nullptr;
}
if (m_wayland.dd_offer) {
wl_data_offer_destroy(m_wayland.dd_offer);
m_wayland.dd_offer = nullptr;
}
m_wl_data_offers.clear();
if (m_wayland.ddev) {
wl_data_device_destroy(m_wayland.ddev);
m_wayland.ddev = nullptr;
}
if (m_wayland.ddm) {
wl_data_device_manager_destroy(m_wayland.ddm);
m_wayland.ddm = nullptr;
}
if (m_wayland.curr_source) {
zwlr_data_control_source_v1_destroy(m_wayland.curr_source);
m_wayland.curr_source = nullptr;
}
for (auto &[offer, _] : m_data_control_offers) {
if (offer)
zwlr_data_control_offer_v1_destroy(offer);
}
m_data_control_offers.clear();
if (m_wayland.data_control_device) {
zwlr_data_control_device_v1_destroy(m_wayland.data_control_device);
m_wayland.data_control_device = nullptr;
}
if (m_wayland.data_control_mgr) {
zwlr_data_control_manager_v1_destroy(m_wayland.data_control_mgr);
m_wayland.data_control_mgr = nullptr;
}
if (m_wayland.text_input) {
zwp_text_input_v3_destroy(m_wayland.text_input);
m_wayland.text_input = nullptr;
@@ -289,6 +323,11 @@ auto App::set_visible(bool visible) -> void
auto App::init_wayland() -> void
{
m_debug_log = []() {
auto const env = getenv("WAYLIGHT_DEBUG");
return env && *env;
}();
m_wayland.display = wl_display_connect(nullptr);
if (!m_wayland.display) {
std::fprintf(stderr, "failed to connect to Wayland display\n");
@@ -540,10 +579,358 @@ auto App::init_wayland() -> void
app->m_ime.sent_serial = 0;
} };
static auto destroy_data_control_offer
= +[](App *app, zwlr_data_control_offer_v1 *offer) {
if (!offer)
return;
zwlr_data_control_offer_v1_destroy(offer);
app->m_data_control_offers.erase(offer);
};
static wl_data_offer_listener const dd_offer_listener {
.offer =
[](void *data, wl_data_offer *offer, char const *mime) {
auto *app = static_cast<App *>(data);
auto &info = app->m_wl_data_offers[offer];
auto const is_text_plain = [](char const *m) {
return std::strncmp(m, "text/plain", 10) == 0;
};
auto const is_text = [](char const *m) {
return std::strncmp(m, "text/", 5) == 0;
};
if (std::strcmp(mime, "text/plain;charset=utf-8") == 0)
info.has_utf8 = true;
else if (std::strcmp(mime, "text/plain") == 0)
info.has_text = true;
if (info.first_text_mime.empty() && mime) {
if (is_text(mime) || is_text_plain(mime)
|| std::strcmp(mime, "UTF8_STRING") == 0
|| std::strcmp(mime, "STRING") == 0) {
info.first_text_mime = mime;
}
}
if (app->m_debug_log)
std::println("wl-data: offer mime {}", mime);
},
#if WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION
.source_actions = [](void *, wl_data_offer *, uint32_t) {},
.action = [](void *, wl_data_offer *, uint32_t) {},
#endif
};
static wl_data_device_listener const dd_listener {
.data_offer =
[](void *data, wl_data_device *, wl_data_offer *offer) {
auto *app = static_cast<App *>(data);
wl_data_offer_add_listener(offer, &dd_offer_listener, app);
if (app->m_wayland.dd_offer && app->m_wayland.dd_offer != offer)
wl_data_offer_destroy(app->m_wayland.dd_offer);
app->m_wayland.dd_offer = offer;
app->m_wl_data_offers.emplace(offer, WlDataOfferInfo {});
},
.enter = [](void *, wl_data_device *, uint32_t, wl_surface *, wl_fixed_t,
wl_fixed_t, wl_data_offer *) {},
.leave = [](void *, wl_data_device *) {},
.motion = [](void *, wl_data_device *, uint32_t, wl_fixed_t,
wl_fixed_t) {},
.drop = [](void *, wl_data_device *) {},
.selection =
[](void *data, wl_data_device *, wl_data_offer *offer) {
auto *app = static_cast<App *>(data);
if (!offer) {
app->m_clipboard_cache.clear();
return;
}
auto it = app->m_wl_data_offers.find(offer);
bool const has_utf8
= it != app->m_wl_data_offers.end() && it->second.has_utf8;
bool const has_text
= it != app->m_wl_data_offers.end() && it->second.has_text;
std::string const &first_text_mime
= it != app->m_wl_data_offers.end()
? it->second.first_text_mime
: std::string {};
char const *mime = nullptr;
if (has_utf8) {
mime = "text/plain;charset=utf-8";
} else if (has_text) {
mime = "text/plain";
} else if (!first_text_mime.empty()) {
mime = first_text_mime.c_str();
} else {
if (app->m_debug_log)
std::println("wl-data: no usable mime for selection");
return;
}
int fds[2];
if (pipe(fds) != 0)
return;
wl_data_offer_receive(offer, mime, fds[1]);
wl_display_flush(app->m_wayland.display);
close(fds[1]);
int rfd = fds[0];
std::thread([app, rfd, offer]() {
std::string data;
char buf[4096];
for (;;) {
ssize_t n = read(rfd, buf, sizeof buf);
if (n > 0) {
data.append(buf, buf + n);
continue;
}
if (n < 0 && errno == EINTR)
continue;
break;
}
close(rfd);
struct Ctx {
App *app;
wl_data_offer *offer;
std::string data;
};
auto *ctx = new Ctx { app, offer, std::move(data) };
g_main_context_invoke(
nullptr,
+[](gpointer p) -> gboolean {
auto *ctx = static_cast<Ctx *>(p);
if (!ctx->data.empty()) {
ctx->app->m_clipboard_cache
= std::move(ctx->data);
if (ctx->app->m_debug_log)
std::println("wl-data: selection received size {}",
ctx->app->m_clipboard_cache.size());
}
if (ctx->offer == ctx->app->m_wayland.dd_offer) {
wl_data_offer_destroy(ctx->offer);
ctx->app->m_wayland.dd_offer = nullptr;
}
ctx->app->m_wl_data_offers.erase(ctx->offer);
delete ctx;
return G_SOURCE_REMOVE;
},
ctx);
}).detach();
},
};
static auto ensure_data_device { +[](App *app) -> bool {
if (!app->m_wayland.ddm || !app->m_wayland.seat || app->m_wayland.ddev)
return false;
app->m_wayland.ddev
= wl_data_device_manager_get_data_device(app->m_wayland.ddm,
app->m_wayland.seat);
if (!app->m_wayland.ddev)
return false;
if (app->m_debug_log) {
std::println("wl-data: created data_device");
}
wl_data_device_add_listener(app->m_wayland.ddev, &dd_listener, app);
return true;
} };
static auto handle_selection_offer
= +[](App *app, zwlr_data_control_offer_v1 *offer) {
if (app->m_wayland.curr_offer
&& app->m_wayland.curr_offer != offer) {
destroy_data_control_offer(app, app->m_wayland.curr_offer);
}
app->m_wayland.curr_offer = offer;
if (!offer) {
if (app->m_debug_log)
std::println("data-control: selection cleared");
app->m_clipboard_cache.clear();
return;
}
auto it = app->m_data_control_offers.find(offer);
bool const has_utf8
= it != app->m_data_control_offers.end() && it->second.has_utf8;
bool const has_text
= it != app->m_data_control_offers.end() && it->second.has_text;
std::string const &first_text_mime
= it != app->m_data_control_offers.end()
? it->second.first_text_mime
: std::string {};
char const *mime = nullptr;
if (has_utf8) {
mime = "text/plain;charset=utf-8";
} else if (has_text) {
mime = "text/plain";
} else if (!first_text_mime.empty()) {
mime = first_text_mime.c_str();
} else {
if (app->m_debug_log)
std::println("data-control: no usable mime, skipping");
return;
}
if (app->m_debug_log)
std::println("data-control: receive selection mime {}", mime);
int fds[2];
if (pipe(fds) != 0)
return;
zwlr_data_control_offer_v1_receive(offer, mime, fds[1]);
wl_display_flush(app->m_wayland.display);
close(fds[1]);
int rfd = fds[0];
std::thread([app, rfd, offer]() {
std::string data;
char buf[4096];
for (;;) {
ssize_t n = read(rfd, buf, sizeof buf);
if (n > 0) {
data.append(buf, buf + n);
continue;
}
if (n < 0 && errno == EINTR)
continue;
break;
}
close(rfd);
struct Ctx {
App *app;
zwlr_data_control_offer_v1 *offer;
std::string data;
};
auto *ctx = new Ctx { app, offer, std::move(data) };
g_main_context_invoke(
nullptr,
+[](gpointer p) -> gboolean {
auto *ctx = static_cast<Ctx *>(p);
if (!ctx->data.empty())
ctx->app->m_clipboard_cache
= std::move(ctx->data);
if (ctx->offer
== ctx->app->m_wayland.curr_offer) {
destroy_data_control_offer(
ctx->app, ctx->offer);
ctx->app->m_wayland.curr_offer = nullptr;
}
delete ctx;
return G_SOURCE_REMOVE;
},
ctx);
}).detach();
};
static zwlr_data_control_offer_v1_listener const data_offer_listener {
.offer =
[](void *data, zwlr_data_control_offer_v1 *offer, char const *mime) {
auto *app = static_cast<App *>(data);
auto &info = app->m_data_control_offers[offer];
auto const is_text_plain = [](char const *m) {
return std::strncmp(m, "text/plain", 10) == 0;
};
auto const is_text = [](char const *m) {
return std::strncmp(m, "text/", 5) == 0;
};
if (std::strcmp(mime, "text/plain;charset=utf-8") == 0)
info.has_utf8 = true;
else if (std::strcmp(mime, "text/plain") == 0)
info.has_text = true;
if (info.first_text_mime.empty() && mime) {
if (is_text(mime) || is_text_plain(mime)
|| std::strcmp(mime, "UTF8_STRING") == 0
|| std::strcmp(mime, "STRING") == 0) {
info.first_text_mime = mime;
}
}
if (app->m_debug_log)
std::println("data-control: offer mime {}", mime);
},
};
static zwlr_data_control_device_v1_listener const data_device_listener {
.data_offer =
[](void *data, zwlr_data_control_device_v1 *,
zwlr_data_control_offer_v1 *offer) {
auto *app = static_cast<App *>(data);
zwlr_data_control_offer_v1_add_listener(
offer, &data_offer_listener, app);
if (app->m_debug_log)
std::println("data-control: new offer {}", static_cast<void *>(offer));
},
.selection =
[](void *data, zwlr_data_control_device_v1 *,
zwlr_data_control_offer_v1 *offer) {
auto *app = static_cast<App *>(data);
handle_selection_offer(app, offer);
},
.finished =
[](void *data, zwlr_data_control_device_v1 *device) {
auto *app = static_cast<App *>(data);
for (auto &[offer, _] : app->m_data_control_offers) {
if (offer)
zwlr_data_control_offer_v1_destroy(offer);
}
app->m_data_control_offers.clear();
app->m_wayland.curr_offer = nullptr;
if (app->m_wayland.curr_source) {
zwlr_data_control_source_v1_destroy(app->m_wayland.curr_source);
app->m_wayland.curr_source = nullptr;
}
if (app->m_wayland.data_control_device == device)
app->m_wayland.data_control_device = nullptr;
if (app->m_debug_log)
std::println("data-control: device finished");
zwlr_data_control_device_v1_destroy(device);
},
.primary_selection =
[](void *data, zwlr_data_control_device_v1 *,
zwlr_data_control_offer_v1 *offer) {
auto *app = static_cast<App *>(data);
handle_selection_offer(app, offer);
},
};
static auto ensure_data_control_device { +[](App *app) -> bool {
if (!app->m_wayland.data_control_mgr || !app->m_wayland.seat
|| app->m_wayland.data_control_device)
return false;
app->m_wayland.data_control_device
= zwlr_data_control_manager_v1_get_data_device(
app->m_wayland.data_control_mgr, app->m_wayland.seat);
if (!app->m_wayland.data_control_device)
return false;
if (app->m_debug_log) {
std::println("data-control: created device (manager {}, seat present {})",
static_cast<void *>(app->m_wayland.data_control_mgr), true);
}
zwlr_data_control_device_v1_add_listener(
app->m_wayland.data_control_device, &data_device_listener, app);
return true;
} };
auto handle_registry_global
= [](void *data, wl_registry *registry, u32 name, char const *interface,
u32 version) -> void {
auto *app { static_cast<App *>(data) };
if (app->m_debug_log) {
std::println(
"registry: interface={} name={} version={}", interface, name, version);
}
if (std::strcmp(interface, wl_compositor_interface.name) == 0) {
app->m_wayland.compositor = static_cast<wl_compositor *>(
wl_registry_bind(registry, name, &wl_compositor_interface, 4));
@@ -561,6 +948,11 @@ auto App::init_wayland() -> void
app->m_ime.seat_focus = false;
}
ensure_text_input(app);
bool const created_ctrl
= ensure_data_control_device(app);
bool const created_dd = ensure_data_device(app);
if (created_ctrl || created_dd)
wl_display_roundtrip(app->m_wayland.display);
},
.name = [](void *, struct wl_seat *, char const *) {},
};
@@ -585,112 +977,29 @@ auto App::init_wayland() -> void
registry, name, &zwp_text_input_manager_v3_interface, 1));
app->m_ime.supported = true;
ensure_text_input(app);
} else if (std::strcmp(interface,
zwlr_data_control_manager_v1_interface.name)
== 0) {
if (app->m_debug_log) {
std::println(
"registry: data-control name={} version={}", name, version);
}
uint32_t const bind_version = std::min<uint32_t>(version, 2);
app->m_wayland.data_control_mgr
= static_cast<zwlr_data_control_manager_v1 *>(wl_registry_bind(
registry, name, &zwlr_data_control_manager_v1_interface,
bind_version));
bool const created = ensure_data_control_device(app);
if (created)
wl_display_roundtrip(app->m_wayland.display);
} else if (std::strcmp(interface, wl_data_device_manager_interface.name)
== 0) {
app->m_wayland.ddm
= static_cast<wl_data_device_manager *>(wl_registry_bind(
registry, name, &wl_data_device_manager_interface,
app->m_wayland.ddm = static_cast<wl_data_device_manager *>(
wl_registry_bind(registry, name, &wl_data_device_manager_interface,
std::min<uint32_t>(version, 3)));
if (app->m_wayland.ddm && !app->m_wayland.ddev) {
app->m_wayland.ddev = wl_data_device_manager_get_data_device(
app->m_wayland.ddm, app->m_wayland.seat);
static wl_data_device_listener const ddev_l = {
.data_offer =
[](void *data, wl_data_device *, wl_data_offer *offer) {
auto *app = static_cast<App *>(data);
static wl_data_offer_listener const offer_l = {
.offer =
[](void *data, wl_data_offer *,
char const *mime) {
auto *app = static_cast<App *>(data);
(void)app;
(void)mime;
},
#if WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION
.source_actions
= [](void *, wl_data_offer *, uint32_t) {},
.action
= [](void *, wl_data_offer *, uint32_t) {}
#endif
};
wl_data_offer_add_listener(offer, &offer_l, app);
if (app->m_wayland.curr_offer
&& app->m_wayland.curr_offer != offer)
wl_data_offer_destroy(
app->m_wayland.curr_offer);
app->m_wayland.curr_offer = offer;
},
.enter
= [](void *, wl_data_device *, uint32_t, wl_surface *,
wl_fixed_t, wl_fixed_t, wl_data_offer *) {},
.leave = [](void *, wl_data_device *) {},
.motion = [](void *, wl_data_device *, uint32_t, wl_fixed_t,
wl_fixed_t) {},
.drop = [](void *, wl_data_device *) {},
.selection =
[](void *data, wl_data_device *, wl_data_offer *offer) {
auto *app = static_cast<App *>(data);
if (!offer) {
app->m_clipboard_cache.clear();
return;
}
char const *mime = "text/plain;charset=utf-8";
int fds[2];
if (pipe(fds) != 0)
return;
wl_data_offer_receive(offer, mime, fds[1]);
wl_display_flush(app->m_wayland.display);
close(fds[1]);
int rfd = fds[0];
std::thread([app, rfd, offer]() {
std::string data;
char buf[4096];
for (;;) {
ssize_t n = read(rfd, buf, sizeof buf);
if (n > 0) {
data.append(buf, buf + n);
continue;
}
if (n < 0 && errno == EINTR)
continue;
break;
}
close(rfd);
struct Ctx {
App *app;
wl_data_offer *offer;
std::string data;
};
auto *ctx
= new Ctx { app, offer, std::move(data) };
g_main_context_invoke(
nullptr,
+[](gpointer p) -> gboolean {
auto *ctx = static_cast<Ctx *>(p);
if (!ctx->data.empty())
ctx->app->m_clipboard_cache
= std::move(ctx->data);
if (ctx->offer
== ctx->app->m_wayland.curr_offer) {
wl_data_offer_destroy(ctx->offer);
ctx->app->m_wayland.curr_offer
= nullptr;
}
delete ctx;
return G_SOURCE_REMOVE;
},
ctx);
}).detach();
},
};
wl_data_device_add_listener(app->m_wayland.ddev, &ddev_l, app);
}
bool const created = ensure_data_device(app);
if (created)
wl_display_roundtrip(app->m_wayland.display);
}
};
@@ -705,6 +1014,14 @@ auto App::init_wayland() -> void
m_kbd.xkb_ctx_v = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
wl_display_roundtrip(m_wayland.display);
if (m_debug_log) {
std::println("data-control: mgr={} device={}",
static_cast<void *>(m_wayland.data_control_mgr),
static_cast<void *>(m_wayland.data_control_device));
std::println("wl-data: ddm={} ddev={}",
static_cast<void *>(m_wayland.ddm),
static_cast<void *>(m_wayland.ddev));
}
create_layer_surface();
}
@@ -1177,19 +1494,87 @@ auto App::pump_events() -> void
}
}
auto App::clipboard(std::string_view const &str) -> void
auto App::set_clipboard(std::string_view const &str) -> void
{
m_clipboard_cache.assign(str.begin(), str.end());
if (m_wayland.data_control_mgr && m_wayland.data_control_device) {
if (m_debug_log)
std::println("data-control: set_clipboard size {}", str.size());
if (m_wayland.curr_source) {
zwlr_data_control_source_v1_destroy(m_wayland.curr_source);
m_wayland.curr_source = nullptr;
}
m_wayland.curr_source = zwlr_data_control_manager_v1_create_data_source(
m_wayland.data_control_mgr);
static zwlr_data_control_source_v1_listener const src_l = {
.send =
[](void *data, zwlr_data_control_source_v1 *, char const *,
int32_t fd) {
auto *app = static_cast<App *>(data);
int wfd = dup(fd);
close(fd);
std::pmr::string payload = app->m_clipboard_cache;
std::thread([wfd, payload = std::move(payload)]() {
size_t off = 0;
while (off < payload.size()) {
ssize_t n = write(wfd, payload.data() + off,
std::min<size_t>(
64 * 1024, payload.size() - off));
if (n > 0) {
off += (size_t)n;
continue;
}
if (n < 0 && (errno == EINTR))
continue;
if (n < 0 && (errno == EAGAIN)) {
std::this_thread::sleep_for(
std::chrono::milliseconds(1));
continue;
}
break;
}
close(wfd);
}).detach();
},
.cancelled =
[](void *data, zwlr_data_control_source_v1 *src) {
auto *app = static_cast<App *>(data);
if (app->m_wayland.curr_source == src)
app->m_wayland.curr_source = nullptr;
zwlr_data_control_source_v1_destroy(src);
},
};
zwlr_data_control_source_v1_add_listener(
m_wayland.curr_source, &src_l, this);
zwlr_data_control_source_v1_offer(
m_wayland.curr_source, "text/plain;charset=utf-8");
zwlr_data_control_source_v1_offer(m_wayland.curr_source, "text/plain");
zwlr_data_control_device_v1_set_selection(
m_wayland.data_control_device, m_wayland.curr_source);
wl_display_flush(m_wayland.display);
return;
}
if (!m_wayland.ddm || !m_wayland.ddev || !m_wayland.seat)
return;
if (m_last_serial == 0)
return;
if (m_wayland.curr_source) {
wl_data_source_destroy(m_wayland.curr_source);
m_wayland.curr_source = nullptr;
if (m_debug_log)
std::println("wl-data: set_clipboard size {}", str.size());
if (m_wayland.dd_source) {
wl_data_source_destroy(m_wayland.dd_source);
m_wayland.dd_source = nullptr;
}
m_wayland.curr_source
= wl_data_device_manager_create_data_source(m_wayland.ddm);
m_wayland.dd_source = wl_data_device_manager_create_data_source(m_wayland.ddm);
static wl_data_source_listener const src_l = {
.target = [](void *, wl_data_source *, char const *) {},
@@ -1225,8 +1610,8 @@ auto App::clipboard(std::string_view const &str) -> void
.cancelled =
[](void *data, wl_data_source *src) {
auto *app = static_cast<App *>(data);
if (app->m_wayland.curr_source == src)
app->m_wayland.curr_source = nullptr;
if (app->m_wayland.dd_source == src)
app->m_wayland.dd_source = nullptr;
wl_data_source_destroy(src);
},
#if WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION
@@ -1235,14 +1620,12 @@ auto App::clipboard(std::string_view const &str) -> void
.action = [](void *, wl_data_source *, uint32_t) {}
#endif
};
wl_data_source_add_listener(m_wayland.curr_source, &src_l, this);
wl_data_source_offer(m_wayland.curr_source, "text/plain;charset=utf-8");
wl_data_source_offer(m_wayland.curr_source, "text/plain");
m_clipboard_cache.assign(str.begin(), str.end());
wl_data_source_add_listener(m_wayland.dd_source, &src_l, this);
wl_data_source_offer(m_wayland.dd_source, "text/plain;charset=utf-8");
wl_data_source_offer(m_wayland.dd_source, "text/plain");
wl_data_device_set_selection(
m_wayland.ddev, m_wayland.curr_source, m_last_serial);
m_wayland.ddev, m_wayland.dd_source, m_last_serial);
wl_display_flush(m_wayland.display);
}

View File

@@ -3,6 +3,7 @@
#include <cassert>
#include <filesystem>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -14,6 +15,7 @@ extern "C" {
#include "ext-background-effect-v1-client-protocol.h"
#include "text-input-unstable-v3-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "wlr-data-control-unstable-v1-client-protocol.h"
#include <libportal/settings.h>
#undef namespace
}
@@ -65,15 +67,26 @@ private:
return m_themes[m_active_theme];
}
auto clipboard() const -> std::pmr::string const &
auto get_clipboard() const -> std::pmr::string const &
{
return m_clipboard_cache;
}
auto clipboard(std::string_view const &str) -> void;
auto set_clipboard(std::string_view const &str) -> void;
static void on_settings_changed(XdpSettings * /*self*/, char const *ns,
char const *key, GVariant * /*value*/, gpointer data);
struct DataControlOfferInfo {
bool has_utf8 { false };
bool has_text { false };
std::string first_text_mime;
};
struct WlDataOfferInfo {
bool has_utf8 { false };
bool has_text { false };
std::string first_text_mime;
};
struct {
wl_display *display {};
wl_registry *registry {};
@@ -89,11 +102,19 @@ private:
zwp_text_input_v3 *text_input {};
wl_data_device_manager *ddm {};
wl_data_device *ddev {};
wl_data_offer *curr_offer {};
wl_data_source *curr_source {};
wl_data_offer *dd_offer {};
wl_data_source *dd_source {};
zwlr_data_control_manager_v1 *data_control_mgr {};
zwlr_data_control_device_v1 *data_control_device {};
zwlr_data_control_offer_v1 *curr_offer {};
zwlr_data_control_source_v1 *curr_source {};
} m_wayland;
std::pmr::string m_clipboard_cache;
std::unordered_map<zwlr_data_control_offer_v1 *, DataControlOfferInfo>
m_data_control_offers;
std::unordered_map<wl_data_offer *, WlDataOfferInfo> m_wl_data_offers;
u32 m_last_serial { 0 };
bool m_debug_log { false };
struct {
EGLDisplay edpy { EGL_NO_DISPLAY };

View File

@@ -543,7 +543,7 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
std::string clip2;
clip2.reserve(m_clipboard.size());
std::copy_if(m_clipboard.begin(), m_clipboard.end(),
clip2.begin(),
std::back_inserter(clip2),
[](char ch) { return ch != '\n' && ch != '\r'; });
str.insert(caret_byte, clip2);
state.current_rune_idx += (int)utf8_length(clip2);

View File

@@ -1,6 +1,7 @@
#include "App.hpp"
#include <cassert>
#include <string>
#include <EGL/egl.h>
#include <GLES3/gl3.h>
@@ -51,8 +52,8 @@ auto App::tick() -> void
m_kbd.typing.clear();
}
ImGuiGuard gui_scope(m_gui, rune, m_kbd.ctrl(), m_kbd.shift(),
clipboard(),
[this](std::string_view const &str) { clipboard(str); });
get_clipboard(),
[this](std::string_view const &str) { set_clipboard(str); });
Rectangle const input_rect {
0.0f,
@@ -60,7 +61,7 @@ auto App::tick() -> void
static_cast<float>(GetScreenWidth()),
static_cast<float>(GetScreenHeight()),
};
;
if (auto const result
= m_gui->text_input(1, text_input_data, input_rect);
result.test(1)) {