Fix clipboard being bugged out and not pasting or copying
Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
617
src/App.cpp
617
src/App.cpp
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
29
src/App.hpp
29
src/App.hpp
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user