GHOST/Wayland: drag & drop cleanup, minor changes

- Don't generate a drop event when the drop data failed to read.
- Remove redundant drop-buffer duplication.
This commit is contained in:
Campbell Barton 2024-02-16 14:26:58 +11:00
parent eafecb2bc3
commit 82c753a940
4 changed files with 95 additions and 62 deletions

View File

@ -102,7 +102,8 @@ GHOST_DropTargetX11::~GHOST_DropTargetX11()
char *GHOST_DropTargetX11::FileUrlDecode(const char *fileUrl) char *GHOST_DropTargetX11::FileUrlDecode(const char *fileUrl)
{ {
if (strncmp(fileUrl, "file://", 7) == 0) { if (strncmp(fileUrl, "file://", 7) == 0) {
return GHOST_URL_decode_alloc(fileUrl + 7); const char *file = fileUrl + 7;
return GHOST_URL_decode_alloc(file, strlen(file));
} }
return nullptr; return nullptr;

View File

@ -12,6 +12,7 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include "GHOST_Debug.hh"
#include "GHOST_PathUtils.hh" #include "GHOST_PathUtils.hh"
#include "GHOST_Types.h" #include "GHOST_Types.h"
@ -24,9 +25,10 @@ using DecodeState_e = enum DecodeState_e {
STATE_CONVERTING STATE_CONVERTING
}; };
void GHOST_URL_decode(char *buf_dst, int buf_dst_size, const char *buf_src) void GHOST_URL_decode(char *buf_dst, int buf_dst_size, const char *buf_src, const int buf_src_len)
{ {
const uint buf_src_len = strlen(buf_src); GHOST_ASSERT(strnlen(buf_src, buf_src_len) == buf_src_len, "Incorrect length");
DecodeState_e state = STATE_SEARCH; DecodeState_e state = STATE_SEARCH;
uint ascii_character; uint ascii_character;
@ -85,12 +87,12 @@ void GHOST_URL_decode(char *buf_dst, int buf_dst_size, const char *buf_src)
} }
} }
char *GHOST_URL_decode_alloc(const char *buf_src) char *GHOST_URL_decode_alloc(const char *buf_src, const int buf_src_len)
{ {
/* Assume one character of encoded URL can be expanded to 4 chars max. */ /* Assume one character of encoded URL can be expanded to 4 chars max. */
const size_t decoded_size_max = 4 * strlen(buf_src) + 1; const size_t decoded_size_max = 4 * buf_src_len + 1;
char *buf_dst = (char *)malloc(decoded_size_max); char *buf_dst = (char *)malloc(decoded_size_max);
GHOST_URL_decode(buf_dst, decoded_size_max, buf_src); GHOST_URL_decode(buf_dst, decoded_size_max, buf_src, buf_src_len);
const size_t decoded_size = strlen(buf_dst) + 1; const size_t decoded_size = strlen(buf_dst) + 1;
if (decoded_size != decoded_size_max) { if (decoded_size != decoded_size_max) {
char *buf_dst_trim = (char *)malloc(decoded_size); char *buf_dst_trim = (char *)malloc(decoded_size);

View File

@ -14,12 +14,13 @@
* \param buf_dst: Buffer for decoded URL. * \param buf_dst: Buffer for decoded URL.
* \param buf_dst_maxlen: Size of output buffer. * \param buf_dst_maxlen: Size of output buffer.
* \param buf_src: Input encoded buffer to be decoded. * \param buf_src: Input encoded buffer to be decoded.
* \param buf_src_len: The length of `buf_src` to use.
*/ */
void GHOST_URL_decode(char *buf_dst, int buf_dst_size, const char *buf_src); void GHOST_URL_decode(char *buf_dst, int buf_dst_size, const char *buf_src, int buf_src_len);
/** /**
* A version of #GHOST_URL_decode that allocates the string & returns it. * A version of #GHOST_URL_decode that allocates the string & returns it.
* *
* \param buf_src: Input encoded buffer to be decoded. * \param buf_src: Input encoded buffer to be decoded.
* \return The decoded output buffer. * \return The decoded output buffer.
*/ */
char *GHOST_URL_decode_alloc(const char *buf_src); char *GHOST_URL_decode_alloc(const char *buf_src, int buf_src_len);

View File

@ -2926,6 +2926,9 @@ static char *read_buffer_from_data_offer(GWL_DataOffer *data_offer,
} }
close(pipefd[0]); close(pipefd[0]);
} }
else {
*r_len = 0;
}
return buf; return buf;
} }
@ -3259,20 +3262,18 @@ static void data_device_handle_drop(void *data, wl_data_device * /*wl_data_devic
CLOG_INFO(LOG, 2, "drop mime_recieve=%s", mime_receive); CLOG_INFO(LOG, 2, "drop mime_recieve=%s", mime_receive);
auto read_uris_fn = [](GWL_Seat *const seat, auto read_drop_data_fn = [](GWL_Seat *const seat,
GWL_DataOffer *data_offer, GWL_DataOffer *data_offer,
wl_surface *wl_surface_window, wl_surface *wl_surface_window,
const char *mime_receive) { const char *mime_receive) {
const uint64_t event_ms = seat->system->getMilliSeconds(); const uint64_t event_ms = seat->system->getMilliSeconds();
const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)}; const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)};
size_t data_buf_len = 0; size_t data_buf_len = 0;
const char *data_buf = read_buffer_from_data_offer( const char *data_buf = read_buffer_from_data_offer(
data_offer, mime_receive, nullptr, false, &data_buf_len); data_offer, mime_receive, nullptr, false, &data_buf_len);
std::string data = data_buf ? std::string(data_buf, data_buf_len) : "";
free(const_cast<char *>(data_buf));
CLOG_INFO(LOG, 2, "drop_read_uris mime_receive=%s, data=%s", mime_receive, data.c_str()); CLOG_INFO(LOG, 2, "read_drop_data mime_receive=%s, data_len=%zu", mime_receive, data_buf_len);
wl_data_offer_finish(data_offer->wl.id); wl_data_offer_finish(data_offer->wl.id);
wl_data_offer_destroy(data_offer->wl.id); wl_data_offer_destroy(data_offer->wl.id);
@ -3283,69 +3284,97 @@ static void data_device_handle_drop(void *data, wl_data_device * /*wl_data_devic
delete data_offer; delete data_offer;
data_offer = nullptr; data_offer = nullptr;
GHOST_SystemWayland *const system = seat->system; /* Don't generate a drop event if the data could not be read,
* an error will have been logged. */
if (data_buf != nullptr) {
GHOST_TDragnDropTypes ghost_dnd_type = GHOST_kDragnDropTypeUnknown;
void *ghost_dnd_data = nullptr;
if (mime_receive == ghost_wl_mime_text_uri) { /* Failure to receive drop data . */
const char file_proto[] = "file://"; if (mime_receive == ghost_wl_mime_text_uri) {
/* NOTE: some applications CRLF (`\r\n`) GTK3 for e.g. & others don't `pcmanfm-qt`. const char file_proto[] = "file://";
* So support both, once `\n` is found, strip the preceding `\r` if found. */ /* NOTE: some applications CRLF (`\r\n`) GTK3 for e.g. & others don't `pcmanfm-qt`.
const char lf = '\n'; * So support both, once `\n` is found, strip the preceding `\r` if found. */
const char lf = '\n';
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_window); const std::string_view data = std::string_view(data_buf, data_buf_len);
std::vector<std::string> uris; std::vector<std::string_view> uris;
size_t pos = 0; size_t pos = 0;
while (pos != std::string::npos) { while (pos != std::string::npos) {
pos = data.find(file_proto, pos); pos = data.find(file_proto, pos);
if (pos == std::string::npos) { if (pos == std::string::npos) {
break; break;
}
const size_t start = pos + sizeof(file_proto) - 1;
pos = data.find(lf, pos);
size_t end = pos;
if (UNLIKELY(end == std::string::npos)) {
/* Note that most well behaved file managers will add a trailing newline,
* Gnome's web browser (44.3) doesn't, so support reading up until the last byte. */
end = data.size();
}
/* Account for 'CRLF' case. */
if (data[end - 1] == '\r') {
end -= 1;
}
std::string_view data_substr = data.substr(start, end - start);
uris.push_back(data_substr);
CLOG_INFO(LOG,
2,
"read_drop_data pos=%zu, text_uri=\"%.*s\"",
start,
int(data_substr.size()),
data_substr.data());
} }
const size_t start = pos + sizeof(file_proto) - 1;
pos = data.find(lf, pos);
size_t end = pos; GHOST_TStringArray *flist = static_cast<GHOST_TStringArray *>(
if (UNLIKELY(end == std::string::npos)) { malloc(sizeof(GHOST_TStringArray)));
/* Note that most well behaved file managers will add a trailing newline, flist->count = int(uris.size());
* Gnome's web browser (44.3) doesn't, so support reading up until the last byte. */ flist->strings = static_cast<uint8_t **>(malloc(uris.size() * sizeof(uint8_t *)));
end = data.size(); for (size_t i = 0; i < uris.size(); i++) {
flist->strings[i] = reinterpret_cast<uint8_t *>(
GHOST_URL_decode_alloc(uris[i].data(), uris[i].size()));
} }
/* Account for 'CRLF' case. */
if (data[end - 1] == '\r') { CLOG_INFO(LOG, 2, "read_drop_data file_count=%d", flist->count);
end -= 1; ghost_dnd_type = GHOST_kDragnDropTypeFilenames;
} ghost_dnd_data = flist;
uris.push_back(data.substr(start, end - start)); }
CLOG_INFO(LOG, 2, "drop_read_uris pos=%zu, text_uri=\"%s\"", start, uris.back().c_str()); else if (ELEM(mime_receive, ghost_wl_mime_text_plain, ghost_wl_mime_text_utf8)) {
/* TODO: enable use of internal functions 'txt_insert_buf' and
* 'text_update_edited' to behave like dropped text was pasted. */
CLOG_INFO(LOG, 2, "read_drop_data, unhandled!");
} }
GHOST_TStringArray *flist = static_cast<GHOST_TStringArray *>( if (ghost_dnd_type != GHOST_kDragnDropTypeUnknown) {
malloc(sizeof(GHOST_TStringArray))); GHOST_SystemWayland *const system = seat->system;
flist->count = int(uris.size()); GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_window);
flist->strings = static_cast<uint8_t **>(malloc(uris.size() * sizeof(uint8_t *))); const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy)};
for (size_t i = 0; i < uris.size(); i++) {
flist->strings[i] = reinterpret_cast<uint8_t *>(GHOST_URL_decode_alloc(uris[i].c_str())); system->pushEvent_maybe_pending(new GHOST_EventDragnDrop(event_ms,
GHOST_kEventDraggingDropDone,
ghost_dnd_type,
win,
UNPACK2(event_xy),
ghost_dnd_data));
wl_display_roundtrip(system->wl_display_get());
}
else {
CLOG_INFO(LOG, 2, "read_drop_data, unhandled!");
} }
CLOG_INFO(LOG, 2, "drop_read_uris_fn file_count=%d", flist->count); free(const_cast<char *>(data_buf));
const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy)};
system->pushEvent_maybe_pending(new GHOST_EventDragnDrop(event_ms,
GHOST_kEventDraggingDropDone,
GHOST_kDragnDropTypeFilenames,
win,
UNPACK2(event_xy),
flist));
} }
else if (ELEM(mime_receive, ghost_wl_mime_text_plain, ghost_wl_mime_text_utf8)) {
/* TODO: enable use of internal functions 'txt_insert_buf' and
* 'text_update_edited' to behave like dropped text was pasted. */
CLOG_INFO(LOG, 2, "drop_read_uris_fn (text_plain, text_utf8), unhandled!");
}
wl_display_roundtrip(system->wl_display_get());
}; };
/* Pass in `seat->wl_surface_window_focus_dnd` instead of accessing it from `seat` since the /* Pass in `seat->wl_surface_window_focus_dnd` instead of accessing it from `seat` since the
* leave callback (#data_device_handle_leave) will clear the value once this function starts. */ * leave callback (#data_device_handle_leave) will clear the value once this function starts. */
std::thread read_thread( std::thread read_thread(
read_uris_fn, seat, data_offer, seat->wl.surface_window_focus_dnd, mime_receive); read_drop_data_fn, seat, data_offer, seat->wl.surface_window_focus_dnd, mime_receive);
read_thread.detach(); read_thread.detach();
} }