mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-04-03 07:15:29 -04:00
Merge branch 'drag-and-drop'
This commit is contained in:
commit
dddeb14e1a
9 changed files with 683 additions and 127 deletions
|
|
@ -45,6 +45,8 @@
|
||||||
`foot.ini`. These options allow custom bold/italic fonts. They are
|
`foot.ini`. These options allow custom bold/italic fonts. They are
|
||||||
unset by default, meaning the bold/italic version of the regular
|
unset by default, meaning the bold/italic version of the regular
|
||||||
font is used (https://codeberg.org/dnkl/foot/issues/169).
|
font is used (https://codeberg.org/dnkl/foot/issues/169).
|
||||||
|
* Drag & drop support; text, files and URLs can now be dropped in a
|
||||||
|
foot terminal window (https://codeberg.org/dnkl/foot/issues/175).
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ executable(
|
||||||
'spawn.c', 'spawn.h',
|
'spawn.c', 'spawn.h',
|
||||||
'terminal.c', 'terminal.h',
|
'terminal.c', 'terminal.h',
|
||||||
'tokenize.c', 'tokenize.h',
|
'tokenize.c', 'tokenize.h',
|
||||||
|
'uri.c', 'uri.h',
|
||||||
'user-notification.h',
|
'user-notification.h',
|
||||||
'vt.c', 'vt.h',
|
'vt.c', 'vt.h',
|
||||||
'wayland.c', 'wayland.h',
|
'wayland.c', 'wayland.h',
|
||||||
|
|
|
||||||
88
osc.c
88
osc.c
|
|
@ -3,8 +3,6 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#define LOG_MODULE "osc"
|
#define LOG_MODULE "osc"
|
||||||
#define LOG_ENABLE_DBG 0
|
#define LOG_ENABLE_DBG 0
|
||||||
|
|
@ -14,6 +12,7 @@
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
#include "selection.h"
|
#include "selection.h"
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
|
#include "uri.h"
|
||||||
#include "vt.h"
|
#include "vt.h"
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
|
@ -99,7 +98,7 @@ struct clip_context {
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
from_clipboard_cb(const char *text, size_t size, void *user)
|
from_clipboard_cb(char *text, size_t size, void *user)
|
||||||
{
|
{
|
||||||
struct clip_context *ctx = user;
|
struct clip_context *ctx = user;
|
||||||
struct terminal *term = ctx->term;
|
struct terminal *term = ctx->term;
|
||||||
|
|
@ -352,87 +351,26 @@ parse_rgb(const char *string, uint32_t *color)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t
|
|
||||||
nibble2hex(char c)
|
|
||||||
{
|
|
||||||
switch (c) {
|
|
||||||
case '0': case '1': case '2': case '3': case '4':
|
|
||||||
case '5': case '6': case '7': case '8': case '9':
|
|
||||||
return c - '0';
|
|
||||||
|
|
||||||
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
|
||||||
return c - 'a' + 10;
|
|
||||||
|
|
||||||
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
|
||||||
return c - 'A' + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(false);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
osc_set_pwd(struct terminal *term, char *string)
|
osc_set_pwd(struct terminal *term, char *string)
|
||||||
{
|
{
|
||||||
LOG_DBG("PWD: URI: %s", string);
|
LOG_DBG("PWD: URI: %s", string);
|
||||||
|
|
||||||
if (memcmp(string, "file://", 7) != 0)
|
char *scheme, *host, *path;
|
||||||
return;
|
if (!uri_parse(string, strlen(string), &scheme, NULL, NULL, &host, NULL, &path, NULL, NULL)) {
|
||||||
string += 7;
|
LOG_ERR("OSC7: invalid URI: %s", string);
|
||||||
|
|
||||||
const char *hostname = string;
|
|
||||||
char *hostname_end = strchr(string, '/');
|
|
||||||
if (hostname_end == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
char this_host[HOST_NAME_MAX];
|
|
||||||
if (gethostname(this_host, sizeof(this_host)) < 0)
|
|
||||||
this_host[0] = '\0';
|
|
||||||
|
|
||||||
/* Ignore this CWD if the hostname isn't 'localhost' or our gethostname() */
|
|
||||||
size_t hostname_len = hostname_end - hostname;
|
|
||||||
if (strncmp(hostname, "", hostname_len) != 0 &&
|
|
||||||
strncmp(hostname, "localhost", hostname_len) != 0 &&
|
|
||||||
strncmp(hostname, this_host, hostname_len) != 0)
|
|
||||||
{
|
|
||||||
LOG_DBG("ignoring OSC 7: hostname mismatch: %.*s != %s",
|
|
||||||
(int)hostname_len, hostname, this_host);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Decode %xx encoded characters */
|
if (strcmp(scheme, "file") == 0 && hostname_is_localhost(host)) {
|
||||||
const char *path = hostname_end;
|
LOG_DBG("OSC7: pwd: %s", path);
|
||||||
char *pwd = xmalloc(strlen(path) + 1);
|
free(term->cwd);
|
||||||
char *p = pwd;
|
term->cwd = path;
|
||||||
|
} else
|
||||||
|
free(path);
|
||||||
|
|
||||||
while (true) {
|
free(scheme);
|
||||||
/* Find next '%' */
|
free(host);
|
||||||
const char *next = strchr(path, '%');
|
|
||||||
|
|
||||||
if (next == NULL) {
|
|
||||||
strcpy(p, path);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy everything leading up to the '%' */
|
|
||||||
size_t prefix_len = next - path;
|
|
||||||
memcpy(p, path, prefix_len);
|
|
||||||
p += prefix_len;
|
|
||||||
|
|
||||||
if (isxdigit(next[1]) && isxdigit(next[2])) {
|
|
||||||
*p++ = nibble2hex(next[1]) << 4 | nibble2hex(next[2]);
|
|
||||||
*p = '\0';
|
|
||||||
path = next + 3;
|
|
||||||
} else {
|
|
||||||
*p++ = *next;
|
|
||||||
*p = '\0';
|
|
||||||
path = next + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("PWD: decoded: %s", pwd);
|
|
||||||
free(term->cwd);
|
|
||||||
term->cwd = pwd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
|
|
|
||||||
416
selection.c
416
selection.c
|
|
@ -21,10 +21,18 @@
|
||||||
#include "grid.h"
|
#include "grid.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
#include "uri.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "vt.h"
|
#include "vt.h"
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
static const char *const mime_type_map[] = {
|
||||||
|
[DATA_OFFER_MIME_UNSET] = NULL,
|
||||||
|
[DATA_OFFER_MIME_TEXT_PLAIN] = "text/plain",
|
||||||
|
[DATA_OFFER_MIME_TEXT_UTF8] = "text/plain;charset=utf-8",
|
||||||
|
[DATA_OFFER_MIME_URI_LIST] = "text/uri-list",
|
||||||
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
selection_enabled(const struct terminal *term, struct seat *seat)
|
selection_enabled(const struct terminal *term, struct seat *seat)
|
||||||
{
|
{
|
||||||
|
|
@ -898,7 +906,7 @@ selection_stop_scroll_timer(struct terminal *term)
|
||||||
static void
|
static void
|
||||||
target(void *data, struct wl_data_source *wl_data_source, const char *mime_type)
|
target(void *data, struct wl_data_source *wl_data_source, const char *mime_type)
|
||||||
{
|
{
|
||||||
LOG_WARN("TARGET: mime-type=%s", mime_type);
|
LOG_DBG("TARGET: mime-type=%s", mime_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct clipboard_send {
|
struct clipboard_send {
|
||||||
|
|
@ -1008,19 +1016,23 @@ cancelled(void *data, struct wl_data_source *wl_data_source)
|
||||||
clipboard->text = NULL;
|
clipboard->text = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We don’t support dragging *from* */
|
||||||
static void
|
static void
|
||||||
dnd_drop_performed(void *data, struct wl_data_source *wl_data_source)
|
dnd_drop_performed(void *data, struct wl_data_source *wl_data_source)
|
||||||
{
|
{
|
||||||
|
//LOG_DBG("DnD drop performed");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dnd_finished(void *data, struct wl_data_source *wl_data_source)
|
dnd_finished(void *data, struct wl_data_source *wl_data_source)
|
||||||
{
|
{
|
||||||
|
//LOG_DBG("DnD finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action)
|
action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action)
|
||||||
{
|
{
|
||||||
|
//LOG_DBG("DnD action: %u", dnd_action);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct wl_data_source_listener data_source_listener = {
|
static const struct wl_data_source_listener data_source_listener = {
|
||||||
|
|
@ -1092,7 +1104,7 @@ text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t
|
||||||
clipboard->text = text;
|
clipboard->text = text;
|
||||||
|
|
||||||
/* Configure source */
|
/* Configure source */
|
||||||
wl_data_source_offer(clipboard->data_source, "text/plain;charset=utf-8");
|
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8]);
|
||||||
wl_data_source_add_listener(clipboard->data_source, &data_source_listener, seat);
|
wl_data_source_add_listener(clipboard->data_source, &data_source_listener, seat);
|
||||||
wl_data_device_set_selection(seat->data_device, clipboard->data_source, serial);
|
wl_data_device_set_selection(seat->data_device, clipboard->data_source, serial);
|
||||||
|
|
||||||
|
|
@ -1120,7 +1132,7 @@ struct clipboard_receive {
|
||||||
struct itimerspec timeout;
|
struct itimerspec timeout;
|
||||||
|
|
||||||
/* Callback data */
|
/* Callback data */
|
||||||
void (*cb)(const char *data, size_t size, void *user);
|
void (*cb)(char *data, size_t size, void *user);
|
||||||
void (*done)(void *user);
|
void (*done)(void *user);
|
||||||
void *user;
|
void *user;
|
||||||
};
|
};
|
||||||
|
|
@ -1191,7 +1203,7 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Call cb while at same time replacing \r\n with \n */
|
/* Call cb while at same time replacing \r\n with \n */
|
||||||
const char *p = text;
|
char *p = text;
|
||||||
size_t left = count;
|
size_t left = count;
|
||||||
again:
|
again:
|
||||||
for (size_t i = 0; i < left - 1; i++) {
|
for (size_t i = 0; i < left - 1; i++) {
|
||||||
|
|
@ -1216,7 +1228,7 @@ done:
|
||||||
|
|
||||||
static void
|
static void
|
||||||
begin_receive_clipboard(struct terminal *term, int read_fd,
|
begin_receive_clipboard(struct terminal *term, int read_fd,
|
||||||
void (*cb)(const char *data, size_t size, void *user),
|
void (*cb)(char *data, size_t size, void *user),
|
||||||
void (*done)(void *user), void *user)
|
void (*done)(void *user), void *user)
|
||||||
{
|
{
|
||||||
int timeout_fd = -1;
|
int timeout_fd = -1;
|
||||||
|
|
@ -1269,7 +1281,7 @@ err:
|
||||||
|
|
||||||
void
|
void
|
||||||
text_from_clipboard(struct seat *seat, struct terminal *term,
|
text_from_clipboard(struct seat *seat, struct terminal *term,
|
||||||
void (*cb)(const char *data, size_t size, void *user),
|
void (*cb)(char *data, size_t size, void *user),
|
||||||
void (*done)(void *user), void *user)
|
void (*done)(void *user), void *user)
|
||||||
{
|
{
|
||||||
struct wl_clipboard *clipboard = &seat->clipboard;
|
struct wl_clipboard *clipboard = &seat->clipboard;
|
||||||
|
|
@ -1291,7 +1303,7 @@ text_from_clipboard(struct seat *seat, struct terminal *term,
|
||||||
|
|
||||||
/* Give write-end of pipe to other client */
|
/* Give write-end of pipe to other client */
|
||||||
wl_data_offer_receive(
|
wl_data_offer_receive(
|
||||||
clipboard->data_offer, "text/plain;charset=utf-8", write_fd);
|
clipboard->data_offer, mime_type_map[clipboard->mime_type], write_fd);
|
||||||
|
|
||||||
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
||||||
close(write_fd);
|
close(write_fd);
|
||||||
|
|
@ -1299,18 +1311,94 @@ text_from_clipboard(struct seat *seat, struct terminal *term,
|
||||||
begin_receive_clipboard(term, read_fd, cb, done, user);
|
begin_receive_clipboard(term, read_fd, cb, done, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct receive_offer_context {
|
||||||
|
struct terminal *term;
|
||||||
|
|
||||||
|
/* URI state */
|
||||||
|
bool add_space;
|
||||||
|
struct {
|
||||||
|
char *data;
|
||||||
|
size_t sz;
|
||||||
|
size_t idx;
|
||||||
|
} buf;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct wl_data_offer *data_offer;
|
||||||
|
struct zwp_primary_selection_offer_v1 *primary_offer;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
from_clipboard_cb(const char *data, size_t size, void *user)
|
receive_offer_text(char *data, size_t size, void *user)
|
||||||
{
|
{
|
||||||
struct terminal *term = user;
|
struct receive_offer_context *ctx = user;
|
||||||
assert(term->is_sending_paste_data);
|
assert(ctx->term->is_sending_paste_data);
|
||||||
term_paste_data_to_slave(term, data, size);
|
term_paste_data_to_slave(ctx->term, data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
from_clipboard_done(void *user)
|
receive_offer_uri(char *data, size_t size, void *user)
|
||||||
{
|
{
|
||||||
struct terminal *term = user;
|
struct receive_offer_context *ctx = user;
|
||||||
|
|
||||||
|
while (ctx->buf.idx + size > ctx->buf.sz) {
|
||||||
|
size_t new_sz = ctx->buf.sz == 0 ? size : 2 * ctx->buf.sz;
|
||||||
|
ctx->buf.data = xrealloc(ctx->buf.data, new_sz);
|
||||||
|
ctx->buf.sz = new_sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&ctx->buf.data[ctx->buf.idx], data, size);
|
||||||
|
ctx->buf.idx += size;
|
||||||
|
|
||||||
|
char *start = ctx->buf.data;
|
||||||
|
char *end = NULL;
|
||||||
|
|
||||||
|
while ((end = memchr(start, '\n', ctx->buf.idx - (start - ctx->buf.data))) != NULL) {
|
||||||
|
const size_t len = end - start;
|
||||||
|
|
||||||
|
LOG_DBG("URI: \"%.*s\"", (int)len, start);
|
||||||
|
|
||||||
|
char *scheme, *host, *path;
|
||||||
|
if (!uri_parse(start, len, &scheme, NULL, NULL, &host, NULL, &path, NULL, NULL)) {
|
||||||
|
LOG_ERR("drag-and-drop: invalid URI: %.*s", (int)len, start);
|
||||||
|
start = end + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->add_space)
|
||||||
|
receive_offer_text(" ", 1, ctx);
|
||||||
|
ctx->add_space = true;
|
||||||
|
|
||||||
|
receive_offer_text("'", 1, ctx);
|
||||||
|
|
||||||
|
if (strcmp(scheme, "file") == 0 && hostname_is_localhost(host))
|
||||||
|
receive_offer_text(path, strlen(path), ctx);
|
||||||
|
else
|
||||||
|
receive_offer_text(start, len, ctx);
|
||||||
|
|
||||||
|
receive_offer_text("'", 1, ctx);
|
||||||
|
start = end + 1;
|
||||||
|
|
||||||
|
free(scheme);
|
||||||
|
free(host);
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t ofs = start - ctx->buf.data;
|
||||||
|
const size_t left = ctx->buf.idx - ofs;
|
||||||
|
|
||||||
|
memmove(&ctx->buf.data[0], &ctx->buf.data[ofs], left);
|
||||||
|
ctx->buf.idx = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
receive_offer_done(void *user)
|
||||||
|
{
|
||||||
|
struct receive_offer_context *ctx = user;
|
||||||
|
struct terminal *term = ctx->term;
|
||||||
|
|
||||||
|
free(ctx->buf.data);
|
||||||
|
free(ctx);
|
||||||
|
|
||||||
if (term->bracketed_paste)
|
if (term->bracketed_paste)
|
||||||
term_paste_data_to_slave(term, "\033[201~", 6);
|
term_paste_data_to_slave(term, "\033[201~", 6);
|
||||||
|
|
@ -1339,8 +1427,18 @@ selection_from_clipboard(struct seat *seat, struct terminal *term, uint32_t seri
|
||||||
if (term->bracketed_paste)
|
if (term->bracketed_paste)
|
||||||
term_paste_data_to_slave(term, "\033[200~", 6);
|
term_paste_data_to_slave(term, "\033[200~", 6);
|
||||||
|
|
||||||
|
struct receive_offer_context *ctx = xmalloc(sizeof(*ctx));
|
||||||
|
*ctx = (struct receive_offer_context) {
|
||||||
|
.term = term,
|
||||||
|
.data_offer = clipboard->data_offer,
|
||||||
|
};
|
||||||
|
|
||||||
text_from_clipboard(
|
text_from_clipboard(
|
||||||
seat, term, &from_clipboard_cb, &from_clipboard_done, term);
|
seat, term,
|
||||||
|
(clipboard->mime_type == DATA_OFFER_MIME_URI_LIST
|
||||||
|
? &receive_offer_uri
|
||||||
|
: &receive_offer_text),
|
||||||
|
&receive_offer_done, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|
@ -1379,7 +1477,7 @@ text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t s
|
||||||
primary->text = text;
|
primary->text = text;
|
||||||
|
|
||||||
/* Configure source */
|
/* Configure source */
|
||||||
zwp_primary_selection_source_v1_offer(primary->data_source, "text/plain;charset=utf-8");
|
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8]);
|
||||||
zwp_primary_selection_source_v1_add_listener(primary->data_source, &primary_selection_source_listener, seat);
|
zwp_primary_selection_source_v1_add_listener(primary->data_source, &primary_selection_source_listener, seat);
|
||||||
zwp_primary_selection_device_v1_set_selection(seat->primary_selection_device, primary->data_source, serial);
|
zwp_primary_selection_device_v1_set_selection(seat->primary_selection_device, primary->data_source, serial);
|
||||||
|
|
||||||
|
|
@ -1403,7 +1501,7 @@ selection_to_primary(struct seat *seat, struct terminal *term, uint32_t serial)
|
||||||
void
|
void
|
||||||
text_from_primary(
|
text_from_primary(
|
||||||
struct seat *seat, struct terminal *term,
|
struct seat *seat, struct terminal *term,
|
||||||
void (*cb)(const char *data, size_t size, void *user),
|
void (*cb)(char *data, size_t size, void *user),
|
||||||
void (*done)(void *user), void *user)
|
void (*done)(void *user), void *user)
|
||||||
{
|
{
|
||||||
if (term->wl->primary_selection_device_manager == NULL) {
|
if (term->wl->primary_selection_device_manager == NULL) {
|
||||||
|
|
@ -1430,7 +1528,7 @@ text_from_primary(
|
||||||
|
|
||||||
/* Give write-end of pipe to other client */
|
/* Give write-end of pipe to other client */
|
||||||
zwp_primary_selection_offer_v1_receive(
|
zwp_primary_selection_offer_v1_receive(
|
||||||
primary->data_offer, "text/plain;charset=utf-8", write_fd);
|
primary->data_offer, mime_type_map[primary->mime_type], write_fd);
|
||||||
|
|
||||||
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
||||||
close(write_fd);
|
close(write_fd);
|
||||||
|
|
@ -1457,24 +1555,157 @@ selection_from_primary(struct seat *seat, struct terminal *term)
|
||||||
if (term->bracketed_paste)
|
if (term->bracketed_paste)
|
||||||
term_paste_data_to_slave(term, "\033[200~", 6);
|
term_paste_data_to_slave(term, "\033[200~", 6);
|
||||||
|
|
||||||
text_from_primary(seat, term, &from_clipboard_cb, &from_clipboard_done, term);
|
struct receive_offer_context *ctx = xmalloc(sizeof(*ctx));
|
||||||
|
*ctx = (struct receive_offer_context){
|
||||||
|
.term = term,
|
||||||
|
.primary_offer = primary->data_offer,
|
||||||
|
};
|
||||||
|
|
||||||
|
text_from_primary(
|
||||||
|
seat, term,
|
||||||
|
(primary->mime_type == DATA_OFFER_MIME_URI_LIST
|
||||||
|
? &receive_offer_uri
|
||||||
|
: &receive_offer_text),
|
||||||
|
&receive_offer_done, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
select_mime_type_for_offer(const char *_mime_type,
|
||||||
|
enum data_offer_mime_type *type)
|
||||||
|
{
|
||||||
|
enum data_offer_mime_type mime_type = DATA_OFFER_MIME_UNSET;
|
||||||
|
|
||||||
|
/* Translate offered mime type to our mime type enum */
|
||||||
|
for (size_t i = 0; i < ALEN(mime_type_map); i++) {
|
||||||
|
if (mime_type_map[i] == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strcmp(_mime_type, mime_type_map[i]) == 0) {
|
||||||
|
mime_type = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("mime-type: %s -> %s (offered type was %s)",
|
||||||
|
mime_type_map[*type], mime_type_map[mime_type], _mime_type);
|
||||||
|
|
||||||
|
/* Mime-type transition; if the new mime-type is "better" than
|
||||||
|
* previously offered types, use the new type */
|
||||||
|
|
||||||
|
switch (mime_type) {
|
||||||
|
case DATA_OFFER_MIME_TEXT_PLAIN:
|
||||||
|
/* text/plain is our least preferred type. Only use if current
|
||||||
|
* type is unset */
|
||||||
|
switch (*type) {
|
||||||
|
case DATA_OFFER_MIME_UNSET:
|
||||||
|
*type = mime_type;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DATA_OFFER_MIME_TEXT_UTF8:
|
||||||
|
/* text/plain;charset=utf-8 is preferred over text/plain */
|
||||||
|
switch (*type) {
|
||||||
|
case DATA_OFFER_MIME_UNSET:
|
||||||
|
case DATA_OFFER_MIME_TEXT_PLAIN:
|
||||||
|
*type = mime_type;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DATA_OFFER_MIME_URI_LIST:
|
||||||
|
/* text/uri-list is always used when offered */
|
||||||
|
*type = mime_type;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DATA_OFFER_MIME_UNSET:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
data_offer_reset(struct wl_clipboard *clipboard)
|
||||||
|
{
|
||||||
|
if (clipboard->data_offer != NULL) {
|
||||||
|
wl_data_offer_destroy(clipboard->data_offer);
|
||||||
|
clipboard->data_offer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
clipboard->window = NULL;
|
||||||
|
clipboard->mime_type = DATA_OFFER_MIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
static void
|
static void
|
||||||
offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type)
|
offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type)
|
||||||
{
|
{
|
||||||
|
struct seat *seat = data;
|
||||||
|
select_mime_type_for_offer(mime_type, &seat->clipboard.mime_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
source_actions(void *data, struct wl_data_offer *wl_data_offer,
|
source_actions(void *data, struct wl_data_offer *wl_data_offer,
|
||||||
uint32_t source_actions)
|
uint32_t source_actions)
|
||||||
{
|
{
|
||||||
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
||||||
|
char actions_as_string[1024];
|
||||||
|
size_t idx = 0;
|
||||||
|
|
||||||
|
actions_as_string[0] = '\0';
|
||||||
|
actions_as_string[sizeof(actions_as_string) - 1] = '\0';
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 31; i++) {
|
||||||
|
if (((source_actions >> i) & 1) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
enum wl_data_device_manager_dnd_action action = 1 << i;
|
||||||
|
|
||||||
|
const char *s = NULL;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: s = NULL; break;
|
||||||
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: s = "copy"; break;
|
||||||
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: s = "move"; break;
|
||||||
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: s = "ask"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
strncat(actions_as_string, s, sizeof(actions_as_string) - idx - 1);
|
||||||
|
idx += strlen(s);
|
||||||
|
strncat(actions_as_string, ", ", sizeof(actions_as_string) - idx - 1);
|
||||||
|
idx += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strip trailing ", " */
|
||||||
|
if (strlen(actions_as_string) > 2)
|
||||||
|
actions_as_string[strlen(actions_as_string) - 2] = '\0';
|
||||||
|
|
||||||
|
LOG_DBG("DnD actions: %s (0x%08x)", actions_as_string, source_actions);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action)
|
offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action)
|
||||||
{
|
{
|
||||||
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
||||||
|
const char *s = NULL;
|
||||||
|
|
||||||
|
switch (dnd_action) {
|
||||||
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: s = "<none>"; break;
|
||||||
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: s = "copy"; break;
|
||||||
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: s = "move"; break;
|
||||||
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: s = "ask"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("DnD offer action: %s (0x%08x)", s, dnd_action);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct wl_data_offer_listener data_offer_listener = {
|
static const struct wl_data_offer_listener data_offer_listener = {
|
||||||
|
|
@ -1482,24 +1713,56 @@ static const struct wl_data_offer_listener data_offer_listener = {
|
||||||
.source_actions = &source_actions,
|
.source_actions = &source_actions,
|
||||||
.action = &offer_action,
|
.action = &offer_action,
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
data_offer(void *data, struct wl_data_device *wl_data_device,
|
data_offer(void *data, struct wl_data_device *wl_data_device,
|
||||||
struct wl_data_offer *id)
|
struct wl_data_offer *offer)
|
||||||
{
|
{
|
||||||
|
struct seat *seat = data;
|
||||||
|
data_offer_reset(&seat->clipboard);
|
||||||
|
seat->clipboard.data_offer = offer;
|
||||||
|
wl_data_offer_add_listener(offer, &data_offer_listener, seat);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial,
|
enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial,
|
||||||
struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y,
|
struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y,
|
||||||
struct wl_data_offer *id)
|
struct wl_data_offer *offer)
|
||||||
{
|
{
|
||||||
|
struct seat *seat = data;
|
||||||
|
struct wayland *wayl = seat->wayl;
|
||||||
|
|
||||||
|
assert(offer == seat->clipboard.data_offer);
|
||||||
|
|
||||||
|
/* Remember _which_ terminal the current DnD offer is targeting */
|
||||||
|
assert(seat->clipboard.window == NULL);
|
||||||
|
tll_foreach(wayl->terms, it) {
|
||||||
|
if (term_surface_kind(it->item, surface) == TERM_SURF_GRID &&
|
||||||
|
!it->item->is_sending_paste_data)
|
||||||
|
{
|
||||||
|
wl_data_offer_accept(
|
||||||
|
offer, serial, mime_type_map[seat->clipboard.mime_type]);
|
||||||
|
wl_data_offer_set_actions(
|
||||||
|
offer,
|
||||||
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY,
|
||||||
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
|
||||||
|
|
||||||
|
seat->clipboard.window = it->item->window;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Either terminal is already busy sending paste data, or mouse
|
||||||
|
* pointer isn’t over the grid */
|
||||||
|
seat->clipboard.window = NULL;
|
||||||
|
wl_data_offer_set_actions(offer, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
leave(void *data, struct wl_data_device *wl_data_device)
|
leave(void *data, struct wl_data_device *wl_data_device)
|
||||||
{
|
{
|
||||||
|
struct seat *seat = data;
|
||||||
|
seat->clipboard.window = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -1508,28 +1771,74 @@ motion(void *data, struct wl_data_device *wl_data_device, uint32_t time,
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
receive_dnd_done(void *user)
|
||||||
|
{
|
||||||
|
struct receive_offer_context *ctx = user;
|
||||||
|
|
||||||
|
wl_data_offer_finish(ctx->data_offer);
|
||||||
|
receive_offer_done(user);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
drop(void *data, struct wl_data_device *wl_data_device)
|
drop(void *data, struct wl_data_device *wl_data_device)
|
||||||
{
|
{
|
||||||
|
struct seat *seat = data;
|
||||||
|
|
||||||
|
assert(seat->clipboard.window != NULL);
|
||||||
|
struct terminal *term = seat->clipboard.window->term;
|
||||||
|
|
||||||
|
struct wl_clipboard *clipboard = &seat->clipboard;
|
||||||
|
|
||||||
|
struct receive_offer_context *ctx = xmalloc(sizeof(*ctx));
|
||||||
|
*ctx = (struct receive_offer_context){
|
||||||
|
.term = term,
|
||||||
|
.data_offer = clipboard->data_offer,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Prepare a pipe the other client can write its selection to us */
|
||||||
|
int fds[2];
|
||||||
|
if (pipe2(fds, O_CLOEXEC) == -1) {
|
||||||
|
LOG_ERRNO("failed to create pipe");
|
||||||
|
free(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int read_fd = fds[0];
|
||||||
|
int write_fd = fds[1];
|
||||||
|
|
||||||
|
LOG_DBG("DnD drop: mime-type=%s", mime_type_map[clipboard->mime_type]);
|
||||||
|
|
||||||
|
/* Give write-end of pipe to other client */
|
||||||
|
wl_data_offer_receive(
|
||||||
|
clipboard->data_offer, mime_type_map[clipboard->mime_type], write_fd);
|
||||||
|
|
||||||
|
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
||||||
|
close(write_fd);
|
||||||
|
|
||||||
|
term->is_sending_paste_data = true;
|
||||||
|
|
||||||
|
if (term->bracketed_paste)
|
||||||
|
term_paste_data_to_slave(term, "\033[200~", 6);
|
||||||
|
|
||||||
|
begin_receive_clipboard(
|
||||||
|
term, read_fd,
|
||||||
|
(clipboard->mime_type == DATA_OFFER_MIME_URI_LIST
|
||||||
|
? &receive_offer_uri
|
||||||
|
: &receive_offer_text),
|
||||||
|
&receive_dnd_done, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
selection(void *data, struct wl_data_device *wl_data_device,
|
selection(void *data, struct wl_data_device *wl_data_device,
|
||||||
struct wl_data_offer *id)
|
struct wl_data_offer *offer)
|
||||||
{
|
{
|
||||||
/* Selection offer from other client */
|
/* Selection offer from other client */
|
||||||
|
|
||||||
struct seat *seat = data;
|
struct seat *seat = data;
|
||||||
struct wl_clipboard *clipboard = &seat->clipboard;
|
if (offer == NULL)
|
||||||
|
data_offer_reset(&seat->clipboard);
|
||||||
if (clipboard->data_offer != NULL)
|
else
|
||||||
wl_data_offer_destroy(clipboard->data_offer);
|
assert(offer == seat->clipboard.data_offer);
|
||||||
|
|
||||||
clipboard->data_offer = id;
|
|
||||||
#if 0
|
|
||||||
if (id != NULL)
|
|
||||||
wl_data_offer_add_listener(id, &data_offer_listener, term);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct wl_data_device_listener data_device_listener = {
|
const struct wl_data_device_listener data_device_listener = {
|
||||||
|
|
@ -1541,46 +1850,55 @@ const struct wl_data_device_listener data_device_listener = {
|
||||||
.selection = &selection,
|
.selection = &selection,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if 0
|
|
||||||
static void
|
static void
|
||||||
primary_offer(void *data,
|
primary_offer(void *data,
|
||||||
struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer,
|
struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer,
|
||||||
const char *mime_type)
|
const char *mime_type)
|
||||||
{
|
{
|
||||||
|
LOG_DBG("primary offer: %s", mime_type);
|
||||||
|
struct seat *seat = data;
|
||||||
|
select_mime_type_for_offer(mime_type, &seat->primary.mime_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
|
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
|
||||||
.offer = &primary_offer,
|
.offer = &primary_offer,
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
static void
|
||||||
|
primary_offer_reset(struct wl_primary *primary)
|
||||||
|
{
|
||||||
|
if (primary->data_offer != NULL) {
|
||||||
|
zwp_primary_selection_offer_v1_destroy(primary->data_offer);
|
||||||
|
primary->data_offer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
primary->mime_type = DATA_OFFER_MIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
primary_data_offer(void *data,
|
primary_data_offer(void *data,
|
||||||
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device,
|
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device,
|
||||||
struct zwp_primary_selection_offer_v1 *offer)
|
struct zwp_primary_selection_offer_v1 *offer)
|
||||||
{
|
{
|
||||||
|
struct seat *seat = data;
|
||||||
|
primary_offer_reset(&seat->primary);
|
||||||
|
seat->primary.data_offer = offer;
|
||||||
|
zwp_primary_selection_offer_v1_add_listener(
|
||||||
|
offer, &primary_selection_offer_listener, seat);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
primary_selection(void *data,
|
primary_selection(void *data,
|
||||||
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device,
|
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device,
|
||||||
struct zwp_primary_selection_offer_v1 *id)
|
struct zwp_primary_selection_offer_v1 *offer)
|
||||||
{
|
{
|
||||||
/* Selection offer from other client, for primary */
|
/* Selection offer from other client, for primary */
|
||||||
|
|
||||||
struct seat *seat = data;
|
struct seat *seat = data;
|
||||||
struct wl_primary *primary = &seat->primary;
|
if (offer == NULL)
|
||||||
|
primary_offer_reset(&seat->primary);
|
||||||
if (primary->data_offer != NULL)
|
else
|
||||||
zwp_primary_selection_offer_v1_destroy(primary->data_offer);
|
assert(seat->primary.data_offer == offer);
|
||||||
|
|
||||||
primary->data_offer = id;
|
|
||||||
#if 0
|
|
||||||
if (id != NULL) {
|
|
||||||
zwp_primary_selection_offer_v1_add_listener(
|
|
||||||
id, &primary_selection_offer_listener, term);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
|
const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
|
||||||
|
|
|
||||||
|
|
@ -67,12 +67,12 @@ bool text_to_primary(
|
||||||
*/
|
*/
|
||||||
void text_from_clipboard(
|
void text_from_clipboard(
|
||||||
struct seat *seat, struct terminal *term,
|
struct seat *seat, struct terminal *term,
|
||||||
void (*cb)(const char *data, size_t size, void *user),
|
void (*cb)(char *data, size_t size, void *user),
|
||||||
void (*done)(void *user), void *user);
|
void (*done)(void *user), void *user);
|
||||||
|
|
||||||
void text_from_primary(
|
void text_from_primary(
|
||||||
struct seat *seat, struct terminal *term,
|
struct seat *seat, struct terminal *term,
|
||||||
void (*cb)(const char *data, size_t size, void *user),
|
void (*cb)(char *data, size_t size, void *user),
|
||||||
void (*dont)(void *user), void *user);
|
void (*dont)(void *user), void *user);
|
||||||
|
|
||||||
void selection_start_scroll_timer(
|
void selection_start_scroll_timer(
|
||||||
|
|
|
||||||
274
uri.c
Normal file
274
uri.c
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
#include "uri.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "uri"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
static uint8_t
|
||||||
|
nibble2hex(char c)
|
||||||
|
{
|
||||||
|
switch (c) {
|
||||||
|
case '0': case '1': case '2': case '3': case '4':
|
||||||
|
case '5': case '6': case '7': case '8': case '9':
|
||||||
|
return c - '0';
|
||||||
|
|
||||||
|
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
||||||
|
return c - 'a' + 10;
|
||||||
|
|
||||||
|
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
||||||
|
return c - 'A' + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
uri_parse(const char *uri, size_t len,
|
||||||
|
char **scheme, char **user, char **password, char **host,
|
||||||
|
uint16_t *port, char **path, char **query, char **fragment)
|
||||||
|
{
|
||||||
|
LOG_DBG("parse URI: \"%.*s\"", (int)len, uri);
|
||||||
|
|
||||||
|
if (scheme != NULL) *scheme = NULL;
|
||||||
|
if (user != NULL) *user = NULL;
|
||||||
|
if (password != NULL) *password = NULL;
|
||||||
|
if (host != NULL) *host = NULL;
|
||||||
|
if (port != NULL) *port = 0;
|
||||||
|
if (path != NULL) *path = NULL;
|
||||||
|
if (query != NULL) *query = NULL;
|
||||||
|
if (fragment != NULL) *fragment = NULL;
|
||||||
|
|
||||||
|
size_t left = len;
|
||||||
|
const char *start = uri;
|
||||||
|
const char *end = NULL;
|
||||||
|
|
||||||
|
if ((end = memchr(start, ':', left)) == NULL)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
size_t scheme_len = end - start;
|
||||||
|
if (scheme_len == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (scheme != NULL)
|
||||||
|
*scheme = xstrndup(start, scheme_len);
|
||||||
|
|
||||||
|
LOG_DBG("scheme: \"%.*s\"", (int)scheme_len, start);
|
||||||
|
|
||||||
|
start = end + 1;
|
||||||
|
left = len - (start - uri);
|
||||||
|
|
||||||
|
/* Authinfo */
|
||||||
|
if (left >= 2 && start[0] == '/' && start[1] == '/') {
|
||||||
|
start += 2;
|
||||||
|
left -= 2;
|
||||||
|
|
||||||
|
/* [user[:password]@]@host[:port] */
|
||||||
|
|
||||||
|
/* Find beginning of path segment (required component
|
||||||
|
* following the authinfo) */
|
||||||
|
const char *path_segment = memchr(start, '/', left);
|
||||||
|
if (path_segment == NULL)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
size_t auth_left = path_segment - start;
|
||||||
|
|
||||||
|
/* Do we have a user (and optionally a password)? */
|
||||||
|
const char *user_pw_end = memchr(start, '@', auth_left);
|
||||||
|
if (user_pw_end != NULL) {
|
||||||
|
size_t user_pw_len = user_pw_end - start;
|
||||||
|
|
||||||
|
/* Do we have a password? */
|
||||||
|
const char *user_end = memchr(start, ':', user_pw_end - start);
|
||||||
|
if (user_end != NULL) {
|
||||||
|
size_t user_len = user_end - start;
|
||||||
|
if (user_len == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (user != NULL)
|
||||||
|
*user = xstrndup(start, user_len);
|
||||||
|
|
||||||
|
const char *pw = user_end + 1;
|
||||||
|
size_t pw_len = user_pw_end - pw;
|
||||||
|
if (pw_len == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (password != NULL)
|
||||||
|
*password = xstrndup(pw, pw_len);
|
||||||
|
|
||||||
|
LOG_DBG("user: \"%.*s\"", (int)user_len, start);
|
||||||
|
LOG_DBG("password: \"%.*s\"", (int)pw_len, pw);
|
||||||
|
} else {
|
||||||
|
size_t user_len = user_pw_end - start;
|
||||||
|
if (user_len == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (user != NULL)
|
||||||
|
*user = xstrndup(start, user_len);
|
||||||
|
|
||||||
|
LOG_DBG("user: \"%.*s\"", (int)user_len, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = user_pw_end + 1;
|
||||||
|
left = len - (start - uri);
|
||||||
|
auth_left -= user_pw_len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *host_end = memchr(start, ':', auth_left);
|
||||||
|
if (host_end != NULL) {
|
||||||
|
size_t host_len = host_end - start;
|
||||||
|
if (host != NULL)
|
||||||
|
*host = xstrndup(start, host_len);
|
||||||
|
|
||||||
|
const char *port_str = host_end + 1;
|
||||||
|
size_t port_len = path_segment - port_str;
|
||||||
|
if (port_len == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
uint16_t _port = 0;
|
||||||
|
for (size_t i = 0; i < port_len; i++) {
|
||||||
|
if (!(port_str[i] >= '0' && port_str[i] <= '9'))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
_port *= 10;
|
||||||
|
_port += port_str[i] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port != NULL)
|
||||||
|
*port = _port;
|
||||||
|
|
||||||
|
LOG_DBG("host: \"%.*s\"", (int)host_len, start);
|
||||||
|
LOG_DBG("port: \"%.*s\" (%hu)", (int)port_len, port_str, _port);
|
||||||
|
} else {
|
||||||
|
size_t host_len = path_segment - start;
|
||||||
|
if (host != NULL)
|
||||||
|
*host = xstrndup(start, host_len);
|
||||||
|
|
||||||
|
LOG_DBG("host: \"%.*s\"", (int)host_len, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = path_segment;
|
||||||
|
left = len - (start - uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do we have a query? */
|
||||||
|
const char *query_start = memchr(start, '?', left);
|
||||||
|
const char *fragment_start = memchr(start, '#', left);
|
||||||
|
|
||||||
|
size_t path_len =
|
||||||
|
query_start != NULL ? query_start - start :
|
||||||
|
fragment_start != NULL ? fragment_start - start :
|
||||||
|
left;
|
||||||
|
|
||||||
|
if (path_len == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
/* Path - decode %xx encoded characters */
|
||||||
|
if (path != NULL) {
|
||||||
|
const char *encoded = start;
|
||||||
|
char *decoded = xmalloc(path_len + 1);
|
||||||
|
char *p = decoded;
|
||||||
|
|
||||||
|
size_t encoded_len = path_len;
|
||||||
|
size_t decoded_len = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
/* Find next '%' */
|
||||||
|
const char *next = memchr(encoded, '%', encoded_len);
|
||||||
|
|
||||||
|
if (next == NULL) {
|
||||||
|
strncpy(p, encoded, encoded_len);
|
||||||
|
decoded_len += encoded_len;
|
||||||
|
p += encoded_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy everything leading up to the '%' */
|
||||||
|
size_t prefix_len = next - encoded;
|
||||||
|
memcpy(p, encoded, prefix_len);
|
||||||
|
|
||||||
|
p += prefix_len;
|
||||||
|
encoded_len -= prefix_len;
|
||||||
|
decoded_len += prefix_len;
|
||||||
|
|
||||||
|
if (isxdigit(next[1]) && isxdigit(next[2])) {
|
||||||
|
*p++ = nibble2hex(next[1]) << 4 | nibble2hex(next[2]);
|
||||||
|
decoded_len++;
|
||||||
|
encoded_len -= 3;
|
||||||
|
encoded = next + 3;
|
||||||
|
} else {
|
||||||
|
*p++ = *next;
|
||||||
|
decoded_len++;
|
||||||
|
encoded_len -= 1;
|
||||||
|
encoded = next + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = '\0';
|
||||||
|
*path = decoded;
|
||||||
|
|
||||||
|
LOG_DBG("path: encoded=\"%.*s\", decoded=\"%s\"", (int)path_len, start, decoded);
|
||||||
|
} else
|
||||||
|
LOG_DBG("path: encoded=\"%.*s\", decoded=<skipped>", (int)path_len, start);
|
||||||
|
|
||||||
|
start = query_start != NULL ? query_start + 1 : fragment_start != NULL ? fragment_start + 1 : uri + len;
|
||||||
|
left = len - (start - uri);
|
||||||
|
|
||||||
|
if (query_start != NULL) {
|
||||||
|
size_t query_len = fragment_start != NULL
|
||||||
|
? fragment_start - start : left;
|
||||||
|
|
||||||
|
if (query_len == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (query != NULL)
|
||||||
|
*query = xstrndup(start, query_len);
|
||||||
|
|
||||||
|
LOG_DBG("query: \"%.*s\"", (int)query_len, start);
|
||||||
|
|
||||||
|
start = fragment_start != NULL ? fragment_start + 1 : uri + len;
|
||||||
|
left = len - (start - uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fragment_start != NULL) {
|
||||||
|
if (left == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (fragment != NULL)
|
||||||
|
*fragment = xstrndup(start, left);
|
||||||
|
|
||||||
|
LOG_DBG("fragment: \"%.*s\"", (int)left, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (scheme != NULL) free(*scheme);
|
||||||
|
if (user != NULL) free(*user);
|
||||||
|
if (password != NULL) free(*password);
|
||||||
|
if (host != NULL) free(*host);
|
||||||
|
if (path != NULL) free(*path);
|
||||||
|
if (query != NULL) free(*query);
|
||||||
|
if (fragment != NULL) free(*fragment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
hostname_is_localhost(const char *hostname)
|
||||||
|
{
|
||||||
|
char this_host[HOST_NAME_MAX];
|
||||||
|
if (gethostname(this_host, sizeof(this_host)) < 0)
|
||||||
|
this_host[0] = '\0';
|
||||||
|
|
||||||
|
return (strcmp(hostname, "") == 0 ||
|
||||||
|
strcmp(hostname, "localhost") == 0 ||
|
||||||
|
strcmp(hostname, this_host) == 0);
|
||||||
|
}
|
||||||
11
uri.h
Normal file
11
uri.h
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
bool uri_parse(const char *uri, size_t len,
|
||||||
|
char **scheme, char **user, char **password, char **host,
|
||||||
|
uint16_t *port, char **path, char **query, char **fragment);
|
||||||
|
|
||||||
|
bool hostname_is_localhost(const char *hostname);
|
||||||
|
|
@ -849,7 +849,7 @@ handle_global(void *data, struct wl_registry *registry,
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
|
else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
|
||||||
const uint32_t required = 1;
|
const uint32_t required = 3;
|
||||||
if (!verify_iface_version(interface, version, required))
|
if (!verify_iface_version(interface, version, required))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
12
wayland.h
12
wayland.h
|
|
@ -101,9 +101,20 @@ struct key_binding_search {
|
||||||
enum bind_action_search action;
|
enum bind_action_search action;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Mime-types we support when dealing with data offers (e.g. copy-paste, or DnD) */
|
||||||
|
enum data_offer_mime_type {
|
||||||
|
DATA_OFFER_MIME_UNSET,
|
||||||
|
DATA_OFFER_MIME_TEXT_PLAIN,
|
||||||
|
DATA_OFFER_MIME_TEXT_UTF8,
|
||||||
|
DATA_OFFER_MIME_URI_LIST,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wl_window;
|
||||||
struct wl_clipboard {
|
struct wl_clipboard {
|
||||||
|
struct wl_window *window; /* For DnD */
|
||||||
struct wl_data_source *data_source;
|
struct wl_data_source *data_source;
|
||||||
struct wl_data_offer *data_offer;
|
struct wl_data_offer *data_offer;
|
||||||
|
enum data_offer_mime_type mime_type;
|
||||||
char *text;
|
char *text;
|
||||||
uint32_t serial;
|
uint32_t serial;
|
||||||
};
|
};
|
||||||
|
|
@ -111,6 +122,7 @@ struct wl_clipboard {
|
||||||
struct wl_primary {
|
struct wl_primary {
|
||||||
struct zwp_primary_selection_source_v1 *data_source;
|
struct zwp_primary_selection_source_v1 *data_source;
|
||||||
struct zwp_primary_selection_offer_v1 *data_offer;
|
struct zwp_primary_selection_offer_v1 *data_offer;
|
||||||
|
enum data_offer_mime_type mime_type;
|
||||||
char *text;
|
char *text;
|
||||||
uint32_t serial;
|
uint32_t serial;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue