Fix clipboard being bugged out and not pasting or copying
Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
@@ -153,18 +153,23 @@ set(WLR_LAYER_SHELL_XML
|
||||
set(TEXT_INPUT_XML
|
||||
"${WAYLAND_PROTOCOLS_DIR}/unstable/text-input/text-input-unstable-v3.xml"
|
||||
)
|
||||
set(WLR_DATA_CONTROL_XML
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/protocols/wlr-data-control-unstable-v1.xml"
|
||||
)
|
||||
|
||||
set(GEN_C_HEADERS
|
||||
"${GEN_DIR}/xdg-shell-client-protocol.h"
|
||||
"${GEN_DIR}/wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||
"${GEN_DIR}/ext-background-effect-v1-client-protocol.h"
|
||||
"${GEN_DIR}/text-input-unstable-v3-client-protocol.h"
|
||||
"${GEN_DIR}/wlr-data-control-unstable-v1-client-protocol.h"
|
||||
)
|
||||
set(GEN_C_PRIVATES
|
||||
"${GEN_DIR}/xdg-shell-protocol.c"
|
||||
"${GEN_DIR}/wlr-layer-shell-unstable-v1-protocol.c"
|
||||
"${GEN_DIR}/ext-background-effect-v1-protocol.c"
|
||||
"${GEN_DIR}/text-input-unstable-v3-protocol.c"
|
||||
"${GEN_DIR}/wlr-data-control-unstable-v1-protocol.c"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
@@ -180,7 +185,10 @@ add_custom_command(
|
||||
# text-input-unstable-v3
|
||||
COMMAND "${WAYLAND_SCANNER}" client-header "${TEXT_INPUT_XML}" "${GEN_DIR}/text-input-unstable-v3-client-protocol.h"
|
||||
COMMAND "${WAYLAND_SCANNER}" private-code "${TEXT_INPUT_XML}" "${GEN_DIR}/text-input-unstable-v3-protocol.c"
|
||||
DEPENDS "${XDG_SHELL_XML}" "${WLR_LAYER_SHELL_XML}" "${TEXT_INPUT_XML}"
|
||||
# wlr-data-control
|
||||
COMMAND "${WAYLAND_SCANNER}" client-header "${WLR_DATA_CONTROL_XML}" "${GEN_DIR}/wlr-data-control-unstable-v1-client-protocol.h"
|
||||
COMMAND "${WAYLAND_SCANNER}" private-code "${WLR_DATA_CONTROL_XML}" "${GEN_DIR}/wlr-data-control-unstable-v1-protocol.c"
|
||||
DEPENDS "${XDG_SHELL_XML}" "${WLR_LAYER_SHELL_XML}" "${TEXT_INPUT_XML}" "${WLR_DATA_CONTROL_XML}"
|
||||
COMMENT "Generating Wayland + wlr-layer-shell client headers and private code"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
278
protocols/wlr-data-control-unstable-v1.xml
Normal file
278
protocols/wlr-data-control-unstable-v1.xml
Normal file
@@ -0,0 +1,278 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_data_control_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2018 Simon Ser
|
||||
Copyright © 2019 Ivan Molodetskikh
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="control data devices">
|
||||
This protocol allows a privileged client to control data devices. In
|
||||
particular, the client will be able to manage the current selection and take
|
||||
the role of a clipboard manager.
|
||||
|
||||
Warning! The protocol described in this file is experimental and
|
||||
backward incompatible changes may be made. Backward compatible changes
|
||||
may be added together with the corresponding interface version bump.
|
||||
Backward incompatible changes are done by bumping the version number in
|
||||
the protocol and interface names and resetting the interface version.
|
||||
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
version number in the protocol and interface names are removed and the
|
||||
interface version number is reset.
|
||||
</description>
|
||||
|
||||
<interface name="zwlr_data_control_manager_v1" version="2">
|
||||
<description summary="manager to control data devices">
|
||||
This interface is a manager that allows creating per-seat data device
|
||||
controls.
|
||||
</description>
|
||||
|
||||
<request name="create_data_source">
|
||||
<description summary="create a new data source">
|
||||
Create a new data source.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_data_control_source_v1"
|
||||
summary="data source to create"/>
|
||||
</request>
|
||||
|
||||
<request name="get_data_device">
|
||||
<description summary="get a data device for a seat">
|
||||
Create a data device that can be used to manage a seat's selection.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_data_control_device_v1"/>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the manager">
|
||||
All objects created by the manager will still remain valid, until their
|
||||
appropriate destroy request has been called.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_data_control_device_v1" version="2">
|
||||
<description summary="manage a data device for a seat">
|
||||
This interface allows a client to manage a seat's selection.
|
||||
|
||||
When the seat is destroyed, this object becomes inert.
|
||||
</description>
|
||||
|
||||
<request name="set_selection">
|
||||
<description summary="copy data to the selection">
|
||||
This request asks the compositor to set the selection to the data from
|
||||
the source on behalf of the client.
|
||||
|
||||
The given source may not be used in any further set_selection or
|
||||
set_primary_selection requests. Attempting to use a previously used
|
||||
source is a protocol error.
|
||||
|
||||
To unset the selection, set the source to NULL.
|
||||
</description>
|
||||
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
|
||||
allow-null="true"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy this data device">
|
||||
Destroys the data device object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="data_offer">
|
||||
<description summary="introduce a new wlr_data_control_offer">
|
||||
The data_offer event introduces a new wlr_data_control_offer object,
|
||||
which will subsequently be used in either the
|
||||
wlr_data_control_device.selection event (for the regular clipboard
|
||||
selections) or the wlr_data_control_device.primary_selection event (for
|
||||
the primary clipboard selections). Immediately following the
|
||||
wlr_data_control_device.data_offer event, the new data_offer object
|
||||
will send out wlr_data_control_offer.offer events to describe the MIME
|
||||
types it offers.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_data_control_offer_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="selection">
|
||||
<description summary="advertise new selection">
|
||||
The selection event is sent out to notify the client of a new
|
||||
wlr_data_control_offer for the selection for this device. The
|
||||
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
|
||||
events are sent out immediately before this event to introduce the data
|
||||
offer object. The selection event is sent to a client when a new
|
||||
selection is set. The wlr_data_control_offer is valid until a new
|
||||
wlr_data_control_offer or NULL is received. The client must destroy the
|
||||
previous selection wlr_data_control_offer, if any, upon receiving this
|
||||
event.
|
||||
|
||||
The first selection event is sent upon binding the
|
||||
wlr_data_control_device object.
|
||||
</description>
|
||||
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
|
||||
allow-null="true"/>
|
||||
</event>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="this data control is no longer valid">
|
||||
This data control object is no longer valid and should be destroyed by
|
||||
the client.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<event name="primary_selection" since="2">
|
||||
<description summary="advertise new primary selection">
|
||||
The primary_selection event is sent out to notify the client of a new
|
||||
wlr_data_control_offer for the primary selection for this device. The
|
||||
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
|
||||
events are sent out immediately before this event to introduce the data
|
||||
offer object. The primary_selection event is sent to a client when a
|
||||
new primary selection is set. The wlr_data_control_offer is valid until
|
||||
a new wlr_data_control_offer or NULL is received. The client must
|
||||
destroy the previous primary selection wlr_data_control_offer, if any,
|
||||
upon receiving this event.
|
||||
|
||||
If the compositor supports primary selection, the first
|
||||
primary_selection event is sent upon binding the
|
||||
wlr_data_control_device object.
|
||||
</description>
|
||||
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
|
||||
allow-null="true"/>
|
||||
</event>
|
||||
|
||||
<request name="set_primary_selection" since="2">
|
||||
<description summary="copy data to the primary selection">
|
||||
This request asks the compositor to set the primary selection to the
|
||||
data from the source on behalf of the client.
|
||||
|
||||
The given source may not be used in any further set_selection or
|
||||
set_primary_selection requests. Attempting to use a previously used
|
||||
source is a protocol error.
|
||||
|
||||
To unset the primary selection, set the source to NULL.
|
||||
|
||||
The compositor will ignore this request if it does not support primary
|
||||
selection.
|
||||
</description>
|
||||
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
|
||||
allow-null="true"/>
|
||||
</request>
|
||||
|
||||
<enum name="error" since="2">
|
||||
<entry name="used_source" value="1"
|
||||
summary="source given to set_selection or set_primary_selection was already used before"/>
|
||||
</enum>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_data_control_source_v1" version="1">
|
||||
<description summary="offer to transfer data">
|
||||
The wlr_data_control_source object is the source side of a
|
||||
wlr_data_control_offer. It is created by the source client in a data
|
||||
transfer and provides a way to describe the offered data and a way to
|
||||
respond to requests to transfer the data.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_offer" value="1"
|
||||
summary="offer sent after wlr_data_control_device.set_selection"/>
|
||||
</enum>
|
||||
|
||||
<request name="offer">
|
||||
<description summary="add an offered MIME type">
|
||||
This request adds a MIME type to the set of MIME types advertised to
|
||||
targets. Can be called several times to offer multiple types.
|
||||
|
||||
Calling this after wlr_data_control_device.set_selection is a protocol
|
||||
error.
|
||||
</description>
|
||||
<arg name="mime_type" type="string"
|
||||
summary="MIME type offered by the data source"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy this source">
|
||||
Destroys the data source object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="send">
|
||||
<description summary="send the data">
|
||||
Request for data from the client. Send the data as the specified MIME
|
||||
type over the passed file descriptor, then close it.
|
||||
</description>
|
||||
<arg name="mime_type" type="string" summary="MIME type for the data"/>
|
||||
<arg name="fd" type="fd" summary="file descriptor for the data"/>
|
||||
</event>
|
||||
|
||||
<event name="cancelled">
|
||||
<description summary="selection was cancelled">
|
||||
This data source is no longer valid. The data source has been replaced
|
||||
by another data source.
|
||||
|
||||
The client should clean up and destroy this data source.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_data_control_offer_v1" version="1">
|
||||
<description summary="offer to transfer data">
|
||||
A wlr_data_control_offer represents a piece of data offered for transfer
|
||||
by another client (the source client). The offer describes the different
|
||||
MIME types that the data can be converted to and provides the mechanism
|
||||
for transferring the data directly from the source client.
|
||||
</description>
|
||||
|
||||
<request name="receive">
|
||||
<description summary="request that the data is transferred">
|
||||
To transfer the offered data, the client issues this request and
|
||||
indicates the MIME type it wants to receive. The transfer happens
|
||||
through the passed file descriptor (typically created with the pipe
|
||||
system call). The source client writes the data in the MIME type
|
||||
representation requested and then closes the file descriptor.
|
||||
|
||||
The receiving client reads from the read end of the pipe until EOF and
|
||||
then closes its end, at which point the transfer is complete.
|
||||
|
||||
This request may happen multiple times for different MIME types.
|
||||
</description>
|
||||
<arg name="mime_type" type="string"
|
||||
summary="MIME type desired by receiver"/>
|
||||
<arg name="fd" type="fd" summary="file descriptor for data transfer"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy this offer">
|
||||
Destroys the data offer object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="offer">
|
||||
<description summary="advertise offered MIME type">
|
||||
Sent immediately after creating the wlr_data_control_offer object.
|
||||
One event per offered MIME type.
|
||||
</description>
|
||||
<arg name="mime_type" type="string" summary="offered MIME type"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
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