2019-07-11 09:51:51 +02:00
|
|
|
#include "selection.h"
|
|
|
|
|
|
2019-07-17 21:30:57 +02:00
|
|
|
#include <ctype.h>
|
2019-07-11 12:16:50 +02:00
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
2019-07-11 12:33:31 +02:00
|
|
|
#include <fcntl.h>
|
2019-07-11 16:26:25 +02:00
|
|
|
#include <errno.h>
|
2019-08-02 18:19:07 +02:00
|
|
|
#include <wctype.h>
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2019-11-04 13:11:15 +01:00
|
|
|
#include <sys/epoll.h>
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
#define LOG_MODULE "selection"
|
2019-07-11 10:01:46 +02:00
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-07-11 09:51:51 +02:00
|
|
|
#include "log.h"
|
2019-11-04 14:00:51 +01:00
|
|
|
|
|
|
|
|
#include "async.h"
|
2019-07-11 12:16:50 +02:00
|
|
|
#include "grid.h"
|
2019-12-03 19:16:05 +01:00
|
|
|
#include "misc.h"
|
2019-11-04 14:00:51 +01:00
|
|
|
#include "render.h"
|
2020-05-01 11:46:24 +02:00
|
|
|
#include "util.h"
|
2019-07-15 15:42:21 +02:00
|
|
|
#include "vt.h"
|
2019-07-11 09:51:51 +02:00
|
|
|
|
2019-08-09 21:26:34 +02:00
|
|
|
bool
|
2019-07-11 09:57:04 +02:00
|
|
|
selection_enabled(const struct terminal *term)
|
|
|
|
|
{
|
2019-08-27 20:57:58 +02:00
|
|
|
return
|
|
|
|
|
term->mouse_tracking == MOUSE_NONE ||
|
2019-11-30 17:06:15 +01:00
|
|
|
term_mouse_grabbed(term) ||
|
2019-08-27 20:57:58 +02:00
|
|
|
term->is_searching;
|
2019-07-11 09:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-05 20:16:17 +02:00
|
|
|
bool
|
|
|
|
|
selection_on_row_in_view(const struct terminal *term, int row_no)
|
|
|
|
|
{
|
|
|
|
|
if (term->selection.start.row == -1 || term->selection.end.row == -1)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const struct coord *start = &term->selection.start;
|
|
|
|
|
const struct coord *end = &term->selection.end;
|
|
|
|
|
assert(start->row <= end->row);
|
|
|
|
|
|
|
|
|
|
row_no += term->grid->view;
|
|
|
|
|
return row_no >= start->row && row_no <= end->row;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 12:03:04 +01:00
|
|
|
static void
|
|
|
|
|
foreach_selected_normal(
|
2020-01-06 11:56:18 +01:00
|
|
|
struct terminal *term, struct coord _start, struct coord _end,
|
2020-05-01 11:59:09 +02:00
|
|
|
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
2020-01-04 12:03:04 +01:00
|
|
|
void *data)
|
|
|
|
|
{
|
2020-01-06 11:56:18 +01:00
|
|
|
const struct coord *start = &_start;
|
|
|
|
|
const struct coord *end = &_end;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
|
|
|
int start_row, end_row;
|
|
|
|
|
int start_col, end_col;
|
|
|
|
|
|
|
|
|
|
if (start->row < end->row) {
|
|
|
|
|
start_row = start->row;
|
|
|
|
|
end_row = end->row;
|
|
|
|
|
start_col = start->col;
|
|
|
|
|
end_col = end->col;
|
|
|
|
|
} else if (start->row > end->row) {
|
|
|
|
|
start_row = end->row;
|
|
|
|
|
end_row = start->row;
|
|
|
|
|
start_col = end->col;
|
|
|
|
|
end_col = start->col;
|
|
|
|
|
} else {
|
|
|
|
|
start_row = end_row = start->row;
|
|
|
|
|
start_col = min(start->col, end->col);
|
|
|
|
|
end_col = max(start->col, end->col);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int r = start_row; r <= end_row; r++) {
|
2020-01-05 15:38:45 +01:00
|
|
|
size_t real_r = r & (term->grid->num_rows - 1);
|
|
|
|
|
struct row *row = term->grid->rows[real_r];
|
|
|
|
|
assert(row != NULL);
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
|
|
|
for (int c = start_col;
|
|
|
|
|
c <= (r == end_row ? end_col : term->cols - 1);
|
|
|
|
|
c++)
|
|
|
|
|
{
|
2020-05-01 11:59:09 +02:00
|
|
|
cb(term, row, &row->cells[c], c, data);
|
2020-01-04 12:03:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start_col = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
foreach_selected_block(
|
2020-01-06 11:56:18 +01:00
|
|
|
struct terminal *term, struct coord _start, struct coord _end,
|
2020-05-01 11:59:09 +02:00
|
|
|
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
2020-01-04 12:03:04 +01:00
|
|
|
void *data)
|
|
|
|
|
{
|
2020-01-06 11:56:18 +01:00
|
|
|
const struct coord *start = &_start;
|
|
|
|
|
const struct coord *end = &_end;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
|
|
|
struct coord top_left = {
|
|
|
|
|
.row = min(start->row, end->row),
|
|
|
|
|
.col = min(start->col, end->col),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct coord bottom_right = {
|
|
|
|
|
.row = max(start->row, end->row),
|
|
|
|
|
.col = max(start->col, end->col),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (int r = top_left.row; r <= bottom_right.row; r++) {
|
2020-01-05 15:38:45 +01:00
|
|
|
size_t real_r = r & (term->grid->num_rows - 1);
|
|
|
|
|
struct row *row = term->grid->rows[real_r];
|
|
|
|
|
assert(row != NULL);
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
|
|
|
for (int c = top_left.col; c <= bottom_right.col; c++)
|
2020-05-01 11:59:09 +02:00
|
|
|
cb(term, row, &row->cells[c], c, data);
|
2020-01-04 12:03:04 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
foreach_selected(
|
2020-01-06 11:56:18 +01:00
|
|
|
struct terminal *term, struct coord start, struct coord end,
|
2020-05-01 11:59:09 +02:00
|
|
|
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
2020-01-04 12:03:04 +01:00
|
|
|
void *data)
|
|
|
|
|
{
|
|
|
|
|
switch (term->selection.kind) {
|
|
|
|
|
case SELECTION_NORMAL:
|
2020-01-06 11:56:18 +01:00
|
|
|
return foreach_selected_normal(term, start, end, cb, data);
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
|
|
|
case SELECTION_BLOCK:
|
2020-01-06 11:56:18 +01:00
|
|
|
return foreach_selected_block(term, start, end, cb, data);
|
2020-01-04 12:09:09 +01:00
|
|
|
|
|
|
|
|
case SELECTION_NONE:
|
|
|
|
|
assert(false);
|
|
|
|
|
return;
|
2020-01-04 12:03:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 12:59:29 +01:00
|
|
|
static size_t
|
2020-01-04 13:56:52 +01:00
|
|
|
min_bufsize_for_extraction(const struct terminal *term)
|
2019-07-11 12:16:50 +02:00
|
|
|
{
|
|
|
|
|
const struct coord *start = &term->selection.start;
|
|
|
|
|
const struct coord *end = &term->selection.end;
|
2020-05-01 12:05:38 +02:00
|
|
|
const size_t chars_per_cell =
|
2020-05-02 17:29:00 +02:00
|
|
|
#if FOOT_UNICODE_MAX_COMBINING_CHARS > 0
|
2020-05-01 12:05:38 +02:00
|
|
|
1 + ALEN(term->grid->cur_row->comb_chars[0].chars);
|
|
|
|
|
#else
|
|
|
|
|
1;
|
|
|
|
|
#endif
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2020-01-04 12:59:29 +01:00
|
|
|
switch (term->selection.kind) {
|
|
|
|
|
case SELECTION_NONE:
|
|
|
|
|
return 0;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2020-01-04 12:59:29 +01:00
|
|
|
case SELECTION_NORMAL:
|
|
|
|
|
if (term->selection.end.row == -1)
|
|
|
|
|
return 0;
|
2019-07-11 16:32:33 +02:00
|
|
|
|
2020-01-04 12:59:29 +01:00
|
|
|
assert(term->selection.start.row != -1);
|
|
|
|
|
|
|
|
|
|
if (start->row > end->row) {
|
|
|
|
|
const struct coord *tmp = start;
|
|
|
|
|
start = end;
|
|
|
|
|
end = tmp;
|
2019-07-11 12:16:50 +02:00
|
|
|
}
|
2019-08-09 21:27:51 +02:00
|
|
|
|
2020-01-04 12:59:29 +01:00
|
|
|
if (start->row == end->row)
|
2020-05-01 11:59:09 +02:00
|
|
|
return (end->col - start->col + 1) * chars_per_cell;
|
2020-01-04 12:59:29 +01:00
|
|
|
else {
|
2020-01-04 13:53:30 +01:00
|
|
|
size_t cells = 0;
|
|
|
|
|
|
2020-01-04 14:06:39 +01:00
|
|
|
/* Add one extra column on each row, for \n */
|
2020-01-04 13:53:30 +01:00
|
|
|
|
2020-01-04 14:06:39 +01:00
|
|
|
cells += term->cols - start->col + 1;
|
2020-01-04 13:53:30 +01:00
|
|
|
cells += (term->cols + 1) * (end->row - start->row - 1);
|
2020-01-04 14:06:39 +01:00
|
|
|
cells += end->col + 1 + 1;
|
2020-05-01 11:59:09 +02:00
|
|
|
return cells * chars_per_cell;
|
2020-01-04 12:59:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case SELECTION_BLOCK: {
|
|
|
|
|
struct coord top_left = {
|
|
|
|
|
.row = min(start->row, end->row),
|
|
|
|
|
.col = min(start->col, end->col),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct coord bottom_right = {
|
|
|
|
|
.row = max(start->row, end->row),
|
|
|
|
|
.col = max(start->col, end->col),
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-04 14:06:39 +01:00
|
|
|
/* Add one extra column on each row, for \n */
|
2020-01-04 13:53:30 +01:00
|
|
|
int cols = bottom_right.col - top_left.col + 1 + 1;
|
2020-01-04 12:59:29 +01:00
|
|
|
int rows = bottom_right.row - top_left.row + 1;
|
2020-05-01 11:59:09 +02:00
|
|
|
return rows * cols * chars_per_cell;
|
2020-01-04 12:59:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct extract {
|
2020-01-04 13:09:06 +01:00
|
|
|
wchar_t *buf;
|
2020-01-04 12:59:29 +01:00
|
|
|
size_t size;
|
|
|
|
|
size_t idx;
|
|
|
|
|
size_t empty_count;
|
2020-05-01 11:59:09 +02:00
|
|
|
const struct row *last_row;
|
|
|
|
|
const struct cell *last_cell;
|
2020-01-04 12:59:29 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
extract_one(struct terminal *term, struct row *row, struct cell *cell,
|
2020-05-01 11:59:09 +02:00
|
|
|
int col, void *data)
|
2020-01-04 12:59:29 +01:00
|
|
|
{
|
|
|
|
|
struct extract *ctx = data;
|
|
|
|
|
|
|
|
|
|
if (ctx->last_row != NULL && row != ctx->last_row &&
|
2020-02-11 19:36:31 +01:00
|
|
|
((term->selection.kind == SELECTION_NORMAL &&
|
2020-02-14 22:39:26 +01:00
|
|
|
ctx->last_row->linebreak) ||
|
2020-01-04 12:59:29 +01:00
|
|
|
term->selection.kind == SELECTION_BLOCK))
|
|
|
|
|
{
|
|
|
|
|
/* Last cell was the last column in the selection */
|
2020-01-04 13:09:06 +01:00
|
|
|
ctx->buf[ctx->idx++] = L'\n';
|
2020-01-04 12:59:29 +01:00
|
|
|
ctx->empty_count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 13:19:30 +01:00
|
|
|
if (cell->wc == 0) {
|
2020-01-04 12:59:29 +01:00
|
|
|
ctx->empty_count++;
|
2020-01-04 13:19:30 +01:00
|
|
|
ctx->last_row = row;
|
|
|
|
|
ctx->last_cell = cell;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-01-04 12:59:29 +01:00
|
|
|
|
2020-01-04 13:19:30 +01:00
|
|
|
/* Replace empty cells with spaces when followed by non-empty cell */
|
|
|
|
|
assert(ctx->idx + ctx->empty_count <= ctx->size);
|
|
|
|
|
for (size_t i = 0; i < ctx->empty_count; i++)
|
|
|
|
|
ctx->buf[ctx->idx++] = L' ';
|
|
|
|
|
ctx->empty_count = 0;
|
2020-01-04 12:59:29 +01:00
|
|
|
|
2020-01-04 13:19:30 +01:00
|
|
|
assert(ctx->idx + 1 <= ctx->size);
|
|
|
|
|
ctx->buf[ctx->idx++] = cell->wc;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2020-05-02 17:29:00 +02:00
|
|
|
#if FOOT_UNICODE_MAX_COMBINING_CHARS > 0
|
2020-05-01 11:59:09 +02:00
|
|
|
const struct combining_chars *comb_chars = &row->comb_chars[col];
|
|
|
|
|
|
|
|
|
|
assert(cell->wc != 0);
|
|
|
|
|
assert(ctx->idx + comb_chars->count <= ctx->size);
|
|
|
|
|
for (size_t i = 0; i < comb_chars->count; i++)
|
|
|
|
|
ctx->buf[ctx->idx++] = comb_chars->chars[i];
|
2020-05-01 12:05:38 +02:00
|
|
|
#endif
|
2020-05-01 11:59:09 +02:00
|
|
|
|
2020-01-04 12:59:29 +01:00
|
|
|
ctx->last_row = row;
|
|
|
|
|
ctx->last_cell = cell;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *
|
|
|
|
|
extract_selection(const struct terminal *term)
|
|
|
|
|
{
|
2020-01-04 13:56:52 +01:00
|
|
|
const size_t max_cells = min_bufsize_for_extraction(term);
|
2020-01-04 13:09:06 +01:00
|
|
|
const size_t buf_size = max_cells + 1;
|
2020-01-04 12:59:29 +01:00
|
|
|
|
|
|
|
|
struct extract ctx = {
|
2020-01-04 13:09:06 +01:00
|
|
|
.buf = malloc(buf_size * sizeof(wchar_t)),
|
2020-01-04 12:59:29 +01:00
|
|
|
.size = buf_size,
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-06 11:56:18 +01:00
|
|
|
foreach_selected(
|
|
|
|
|
(struct terminal *)term, term->selection.start, term->selection.end,
|
|
|
|
|
&extract_one, &ctx);
|
2020-01-04 12:59:29 +01:00
|
|
|
|
|
|
|
|
if (ctx.idx == 0) {
|
2019-11-16 10:56:10 +01:00
|
|
|
/* Selection of empty cells only */
|
2020-01-04 13:09:06 +01:00
|
|
|
ctx.buf[ctx.idx] = L'\0';
|
|
|
|
|
} else {
|
|
|
|
|
assert(ctx.idx > 0);
|
|
|
|
|
assert(ctx.idx < ctx.size);
|
|
|
|
|
if (ctx.buf[ctx.idx - 1] == L'\n')
|
|
|
|
|
ctx.buf[ctx.idx - 1] = L'\0';
|
|
|
|
|
else
|
|
|
|
|
ctx.buf[ctx.idx] = L'\0';
|
2019-11-16 10:56:10 +01:00
|
|
|
}
|
|
|
|
|
|
2020-01-04 13:09:06 +01:00
|
|
|
size_t len = wcstombs(NULL, ctx.buf, 0);
|
|
|
|
|
if (len == (size_t)-1) {
|
|
|
|
|
LOG_ERRNO("failed to convert selection to UTF-8");
|
|
|
|
|
free(ctx.buf);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2019-08-04 13:07:54 +02:00
|
|
|
|
2020-01-04 13:09:06 +01:00
|
|
|
char *ret = malloc(len + 1);
|
|
|
|
|
wcstombs(ret, ctx.buf, len + 1);
|
|
|
|
|
free(ctx.buf);
|
|
|
|
|
return ret;
|
2019-07-11 12:16:50 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
void
|
2020-01-03 23:29:45 +01:00
|
|
|
selection_start(struct terminal *term, int col, int row,
|
|
|
|
|
enum selection_kind kind)
|
2019-07-11 09:51:51 +02:00
|
|
|
{
|
2019-07-11 09:57:04 +02:00
|
|
|
if (!selection_enabled(term))
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
selection_cancel(term);
|
|
|
|
|
|
2020-01-03 23:29:45 +01:00
|
|
|
LOG_DBG("%s selection started at %d,%d",
|
|
|
|
|
kind == SELECTION_NORMAL ? "normal" :
|
|
|
|
|
kind == SELECTION_BLOCK ? "block" : "<unknown>",
|
|
|
|
|
row, col);
|
|
|
|
|
term->selection.kind = kind;
|
2019-07-17 12:59:12 +02:00
|
|
|
term->selection.start = (struct coord){col, term->grid->view + row};
|
2019-07-11 09:51:51 +02:00
|
|
|
term->selection.end = (struct coord){-1, -1};
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 12:03:04 +01:00
|
|
|
static void
|
|
|
|
|
unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
2020-05-01 11:59:09 +02:00
|
|
|
int col, void *data)
|
2020-01-04 12:03:04 +01:00
|
|
|
{
|
2020-01-06 11:56:18 +01:00
|
|
|
if (cell->attrs.selected == 0 || (cell->attrs.selected & 2)) {
|
|
|
|
|
/* Ignore if already deselected, or if premarked for updated selection */
|
2020-01-04 12:03:04 +01:00
|
|
|
return;
|
2020-01-06 11:56:18 +01:00
|
|
|
}
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
|
|
|
row->dirty = 1;
|
|
|
|
|
cell->attrs.selected = 0;
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-06 11:56:18 +01:00
|
|
|
static void
|
|
|
|
|
premark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
2020-05-01 11:59:09 +02:00
|
|
|
int col, void *data)
|
2020-01-06 11:56:18 +01:00
|
|
|
{
|
|
|
|
|
/* Tell unmark to leave this be */
|
|
|
|
|
cell->attrs.selected |= 2;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 12:03:04 +01:00
|
|
|
static void
|
|
|
|
|
mark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
2020-05-01 11:59:09 +02:00
|
|
|
int col, void *data)
|
2020-01-04 12:03:04 +01:00
|
|
|
{
|
2020-01-06 11:56:18 +01:00
|
|
|
if (cell->attrs.selected & 1) {
|
|
|
|
|
cell->attrs.selected = 1; /* Clear the pre-mark bit */
|
2020-01-04 12:03:04 +01:00
|
|
|
return;
|
2020-01-06 11:56:18 +01:00
|
|
|
}
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
|
|
|
row->dirty = 1;
|
|
|
|
|
cell->attrs.selected = 1;
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
static void
|
|
|
|
|
selection_modify(struct terminal *term, struct coord start, struct coord end)
|
|
|
|
|
{
|
|
|
|
|
assert(selection_enabled(term));
|
|
|
|
|
assert(term->selection.start.row != -1);
|
|
|
|
|
assert(start.row != -1 && start.col != -1);
|
|
|
|
|
assert(end.row != -1 && end.col != -1);
|
|
|
|
|
|
|
|
|
|
/* Premark all cells that *will* be selected */
|
|
|
|
|
foreach_selected(term, start, end, &premark_selected, NULL);
|
|
|
|
|
|
|
|
|
|
if (term->selection.end.row != -1) {
|
|
|
|
|
/* Unmark previous selection, ignoring cells that are part of
|
|
|
|
|
* the new selection */
|
|
|
|
|
foreach_selected(term, term->selection.start, term->selection.end,
|
|
|
|
|
&unmark_selected, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
term->selection.start = start;
|
|
|
|
|
term->selection.end = end;
|
|
|
|
|
|
|
|
|
|
/* Mark new selection */
|
|
|
|
|
foreach_selected(term, start, end, &mark_selected, NULL);
|
|
|
|
|
render_refresh(term);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
void
|
|
|
|
|
selection_update(struct terminal *term, int col, int row)
|
|
|
|
|
{
|
2019-07-11 09:57:04 +02:00
|
|
|
if (!selection_enabled(term))
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
LOG_DBG("selection updated: start = %d,%d, end = %d,%d -> %d, %d",
|
|
|
|
|
term->selection.start.row, term->selection.start.col,
|
|
|
|
|
term->selection.end.row, term->selection.end.col,
|
|
|
|
|
row, col);
|
|
|
|
|
|
2020-01-04 12:03:04 +01:00
|
|
|
assert(term->selection.start.row != -1);
|
|
|
|
|
assert(term->grid->view + row != -1);
|
2019-07-11 11:10:12 +02:00
|
|
|
|
2020-01-06 11:56:18 +01:00
|
|
|
struct coord new_end = {col, term->grid->view + row};
|
2020-04-04 11:59:15 +02:00
|
|
|
selection_modify(term, term->selection.start, new_end);
|
|
|
|
|
}
|
2019-07-11 11:10:12 +02:00
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
static void
|
|
|
|
|
selection_extend_normal(struct terminal *term, int col, int row, uint32_t serial)
|
|
|
|
|
{
|
|
|
|
|
const struct coord *start = &term->selection.start;
|
|
|
|
|
const struct coord *end = &term->selection.end;
|
2019-07-11 09:51:51 +02:00
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
if (start->row > end->row ||
|
|
|
|
|
(start->row == end->row && start->col > end->col))
|
|
|
|
|
{
|
|
|
|
|
const struct coord *tmp = start;
|
|
|
|
|
start = end;
|
|
|
|
|
end = tmp;
|
2020-01-06 11:56:18 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
assert(start->row < end->row || start->col < end->col);
|
2020-01-06 11:56:18 +01:00
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
struct coord new_start, new_end;
|
2020-01-06 11:56:18 +01:00
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
if (row < start->row || (row == start->row && col < start->col)) {
|
|
|
|
|
/* Extend selection to start *before* current start */
|
|
|
|
|
new_start = (struct coord){col, row};
|
|
|
|
|
new_end = *end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (row > end->row || (row == end->row && col > end->col)) {
|
|
|
|
|
/* Extend selection to end *after* current end */
|
|
|
|
|
new_start = *start;
|
|
|
|
|
new_end = (struct coord){col, row};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
/* Shrink selection from start or end, depending on which one is closest */
|
|
|
|
|
|
|
|
|
|
const int linear = row * term->cols + col;
|
|
|
|
|
|
|
|
|
|
if (abs(linear - (start->row * term->cols + start->col)) <
|
|
|
|
|
abs(linear - (end->row * term->cols + end->col)))
|
|
|
|
|
{
|
|
|
|
|
/* Move start point */
|
|
|
|
|
new_start = (struct coord){col, row};
|
|
|
|
|
new_end = *end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
/* Move end point */
|
|
|
|
|
new_start = *start;
|
|
|
|
|
new_end = (struct coord){col, row};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selection_modify(term, new_start, new_end);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
selection_extend_block(struct terminal *term, int col, int row, uint32_t serial)
|
|
|
|
|
{
|
|
|
|
|
const struct coord *start = &term->selection.start;
|
|
|
|
|
const struct coord *end = &term->selection.end;
|
|
|
|
|
|
|
|
|
|
struct coord top_left = {
|
|
|
|
|
.row = min(start->row, end->row),
|
|
|
|
|
.col = min(start->col, end->col),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct coord top_right = {
|
|
|
|
|
.row = min(start->row, end->row),
|
|
|
|
|
.col = max(start->col, end->col),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct coord bottom_left = {
|
|
|
|
|
.row = max(start->row, end->row),
|
|
|
|
|
.col = min(start->col, end->col),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct coord bottom_right = {
|
|
|
|
|
.row = max(start->row, end->row),
|
|
|
|
|
.col = max(start->col, end->col),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct coord new_start;
|
|
|
|
|
struct coord new_end;
|
|
|
|
|
|
|
|
|
|
if (row <= top_left.row ||
|
|
|
|
|
abs(row - top_left.row) < abs(row - bottom_left.row))
|
|
|
|
|
{
|
|
|
|
|
/* Move one of the top corners */
|
|
|
|
|
|
|
|
|
|
if (abs(col - top_left.col) < abs(col - top_right.col)) {
|
|
|
|
|
new_start = (struct coord){col, row};
|
|
|
|
|
new_end = bottom_right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
new_start = (struct coord){col, row};
|
|
|
|
|
new_end = bottom_left;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
/* Move one of the bottom corners */
|
|
|
|
|
|
|
|
|
|
if (abs(col - bottom_left.col) < abs(col - bottom_right.col)) {
|
|
|
|
|
new_start = top_right;
|
|
|
|
|
new_end = (struct coord){col, row};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
new_start = top_left;
|
|
|
|
|
new_end = (struct coord){col, row};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selection_modify(term, new_start, new_end);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
selection_extend(struct terminal *term, int col, int row, uint32_t serial)
|
|
|
|
|
{
|
|
|
|
|
if (!selection_enabled(term))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (term->selection.start.row == -1 || term->selection.end.row == -1) {
|
|
|
|
|
/* No existing selection */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-04 12:05:40 +02:00
|
|
|
row += term->grid->view;
|
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
if ((row == term->selection.start.row && col == term->selection.start.col) ||
|
|
|
|
|
(row == term->selection.end.row && col == term->selection.end.col))
|
|
|
|
|
{
|
|
|
|
|
/* Extension point *is* one of the current end points */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (term->selection.kind) {
|
|
|
|
|
case SELECTION_NONE:
|
|
|
|
|
assert(false);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case SELECTION_NORMAL:
|
|
|
|
|
selection_extend_normal(term, col, row, serial);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SELECTION_BLOCK:
|
|
|
|
|
selection_extend_block(term, col, row, serial);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selection_to_primary(term, serial);
|
2019-07-11 09:51:51 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-11 17:34:52 +02:00
|
|
|
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener;
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
void
|
2019-07-11 17:34:52 +02:00
|
|
|
selection_finalize(struct terminal *term, uint32_t serial)
|
2019-07-11 09:51:51 +02:00
|
|
|
{
|
2019-07-11 11:11:12 +02:00
|
|
|
if (term->selection.start.row == -1 || term->selection.end.row == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
assert(term->selection.start.row != -1);
|
|
|
|
|
assert(term->selection.end.row != -1);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2019-08-05 20:15:18 +02:00
|
|
|
if (term->selection.start.row > term->selection.end.row ||
|
|
|
|
|
(term->selection.start.row == term->selection.end.row &&
|
|
|
|
|
term->selection.start.col > term->selection.end.col))
|
|
|
|
|
{
|
|
|
|
|
struct coord tmp = term->selection.start;
|
|
|
|
|
term->selection.start = term->selection.end;
|
|
|
|
|
term->selection.end = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(term->selection.start.row <= term->selection.end.row);
|
2019-08-09 21:27:51 +02:00
|
|
|
selection_to_primary(term, serial);
|
2019-07-11 09:51:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
selection_cancel(struct terminal *term)
|
|
|
|
|
{
|
2019-07-11 09:57:04 +02:00
|
|
|
if (!selection_enabled(term))
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
LOG_DBG("selection cancelled: start = %d,%d end = %d,%d",
|
|
|
|
|
term->selection.start.row, term->selection.start.col,
|
|
|
|
|
term->selection.end.row, term->selection.end.col);
|
|
|
|
|
|
2020-01-04 12:03:04 +01:00
|
|
|
if (term->selection.start.row != -1 && term->selection.end.row != -1) {
|
2020-01-06 11:56:18 +01:00
|
|
|
foreach_selected(
|
|
|
|
|
term, term->selection.start, term->selection.end,
|
|
|
|
|
&unmark_selected, NULL);
|
2020-01-04 12:03:04 +01:00
|
|
|
render_refresh(term);
|
|
|
|
|
}
|
2019-07-11 09:51:51 +02:00
|
|
|
|
2020-01-04 12:09:09 +01:00
|
|
|
term->selection.kind = SELECTION_NONE;
|
2019-07-11 09:51:51 +02:00
|
|
|
term->selection.start = (struct coord){-1, -1};
|
|
|
|
|
term->selection.end = (struct coord){-1, -1};
|
|
|
|
|
}
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2019-07-17 21:30:57 +02:00
|
|
|
void
|
2019-08-05 19:02:27 +02:00
|
|
|
selection_mark_word(struct terminal *term, int col, int row, bool spaces_only,
|
|
|
|
|
uint32_t serial)
|
2019-07-17 21:30:57 +02:00
|
|
|
{
|
|
|
|
|
if (!selection_enabled(term))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
|
|
|
|
|
struct coord start = {col, row};
|
|
|
|
|
struct coord end = {col, row};
|
|
|
|
|
|
|
|
|
|
const struct row *r = grid_row_in_view(term->grid, start.row);
|
2019-08-02 18:19:07 +02:00
|
|
|
wchar_t c = r->cells[start.col].wc;
|
2019-07-17 21:30:57 +02:00
|
|
|
|
2019-08-05 19:02:27 +02:00
|
|
|
if (!(c == 0 || !isword(c, spaces_only))) {
|
2019-07-17 21:30:57 +02:00
|
|
|
while (true) {
|
|
|
|
|
int next_col = start.col - 1;
|
|
|
|
|
int next_row = start.row;
|
|
|
|
|
|
|
|
|
|
/* Linewrap */
|
|
|
|
|
if (next_col < 0) {
|
|
|
|
|
next_col = term->cols - 1;
|
|
|
|
|
if (--next_row < 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, next_row);
|
|
|
|
|
|
2019-08-02 18:19:07 +02:00
|
|
|
c = row->cells[next_col].wc;
|
2019-08-05 19:02:27 +02:00
|
|
|
if (c == 0 || !isword(c, spaces_only))
|
2019-07-17 21:30:57 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
start.col = next_col;
|
|
|
|
|
start.row = next_row;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r = grid_row_in_view(term->grid, end.row);
|
2019-08-02 18:19:07 +02:00
|
|
|
c = r->cells[end.col].wc;
|
2019-07-17 21:30:57 +02:00
|
|
|
|
2019-08-05 19:02:27 +02:00
|
|
|
if (!(c == 0 || !isword(c, spaces_only))) {
|
2019-07-17 21:30:57 +02:00
|
|
|
while (true) {
|
|
|
|
|
int next_col = end.col + 1;
|
|
|
|
|
int next_row = end.row;
|
|
|
|
|
|
|
|
|
|
/* Linewrap */
|
|
|
|
|
if (next_col >= term->cols) {
|
|
|
|
|
next_col = 0;
|
|
|
|
|
if (++next_row >= term->rows)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, next_row);
|
|
|
|
|
|
2019-08-02 18:19:07 +02:00
|
|
|
c = row->cells[next_col].wc;
|
2019-08-05 19:02:27 +02:00
|
|
|
if (c == '\0' || !isword(c, spaces_only))
|
2019-07-17 21:30:57 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
end.col = next_col;
|
|
|
|
|
end.row = next_row;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-03 23:29:45 +01:00
|
|
|
selection_start(term, start.col, start.row, SELECTION_NORMAL);
|
2019-07-17 21:30:57 +02:00
|
|
|
selection_update(term, end.col, end.row);
|
|
|
|
|
selection_finalize(term, serial);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-06 19:32:06 +02:00
|
|
|
void
|
|
|
|
|
selection_mark_row(struct terminal *term, int row, uint32_t serial)
|
|
|
|
|
{
|
2020-01-03 23:29:45 +01:00
|
|
|
selection_start(term, 0, row, SELECTION_NORMAL);
|
2019-08-06 19:32:06 +02:00
|
|
|
selection_update(term, term->cols - 1, row);
|
|
|
|
|
selection_finalize(term, serial);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 12:16:50 +02:00
|
|
|
static void
|
|
|
|
|
target(void *data, struct wl_data_source *wl_data_source, const char *mime_type)
|
|
|
|
|
{
|
|
|
|
|
LOG_WARN("TARGET: mime-type=%s", mime_type);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-04 13:11:15 +01:00
|
|
|
struct clipboard_send {
|
|
|
|
|
char *data;
|
|
|
|
|
size_t len;
|
|
|
|
|
size_t idx;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
fdm_send(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
2019-11-05 08:51:10 +01:00
|
|
|
struct clipboard_send *ctx = data;
|
|
|
|
|
|
2019-11-05 08:40:39 +01:00
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
goto done;
|
2019-11-04 13:11:15 +01:00
|
|
|
|
2019-11-04 14:00:51 +01:00
|
|
|
switch (async_write(fd, ctx->data, ctx->len, &ctx->idx)) {
|
|
|
|
|
case ASYNC_WRITE_REMAIN:
|
|
|
|
|
return true;
|
2019-11-04 13:11:15 +01:00
|
|
|
|
2019-11-04 14:00:51 +01:00
|
|
|
case ASYNC_WRITE_DONE:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ASYNC_WRITE_ERR:
|
|
|
|
|
LOG_ERRNO(
|
|
|
|
|
"failed to asynchronously write %zu of selection data to FD=%d",
|
|
|
|
|
ctx->len - ctx->idx, fd);
|
|
|
|
|
break;
|
2019-11-04 13:11:15 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-05 08:40:39 +01:00
|
|
|
done:
|
2019-11-04 13:11:15 +01:00
|
|
|
fdm_del(fdm, fd);
|
|
|
|
|
free(ctx->data);
|
|
|
|
|
free(ctx);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 12:16:50 +02:00
|
|
|
static void
|
|
|
|
|
send(void *data, struct wl_data_source *wl_data_source, const char *mime_type,
|
|
|
|
|
int32_t fd)
|
|
|
|
|
{
|
2019-11-04 13:11:15 +01:00
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
const struct wl_clipboard *clipboard = &wayl->clipboard;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
|
|
|
|
assert(clipboard != NULL);
|
|
|
|
|
assert(clipboard->text != NULL);
|
|
|
|
|
|
2019-11-04 14:00:51 +01:00
|
|
|
const char *selection = clipboard->text;
|
|
|
|
|
const size_t len = strlen(selection);
|
|
|
|
|
|
2019-11-05 08:40:24 +01:00
|
|
|
/* Make it NONBLOCK:ing right away - we don't want to block if the
|
|
|
|
|
* initial attempt to send the data synchronously fails */
|
2019-11-04 13:11:15 +01:00
|
|
|
int flags;
|
|
|
|
|
if ((flags = fcntl(fd, F_GETFL)) < 0 ||
|
|
|
|
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to set O_NONBLOCK");
|
|
|
|
|
return;
|
2019-07-11 16:26:25 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-28 19:20:25 +01:00
|
|
|
size_t async_idx = 0;
|
|
|
|
|
switch (async_write(fd, selection, len, &async_idx)) {
|
2019-11-04 14:00:51 +01:00
|
|
|
case ASYNC_WRITE_REMAIN: {
|
|
|
|
|
struct clipboard_send *ctx = malloc(sizeof(*ctx));
|
|
|
|
|
*ctx = (struct clipboard_send) {
|
|
|
|
|
.data = strdup(selection),
|
|
|
|
|
.len = len,
|
2019-11-28 19:20:25 +01:00
|
|
|
.idx = async_idx,
|
2019-11-04 14:00:51 +01:00
|
|
|
};
|
|
|
|
|
|
2020-01-10 19:49:48 +01:00
|
|
|
if (fdm_add(wayl->fdm, fd, EPOLLOUT, &fdm_send, ctx))
|
2019-11-04 14:00:51 +01:00
|
|
|
return;
|
2019-11-04 13:11:15 +01:00
|
|
|
|
2019-11-05 08:40:24 +01:00
|
|
|
free(ctx->data);
|
2019-11-04 13:11:15 +01:00
|
|
|
free(ctx);
|
2019-11-04 14:00:51 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case ASYNC_WRITE_DONE:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ASYNC_WRITE_ERR:
|
|
|
|
|
LOG_ERRNO(
|
|
|
|
|
"failed to write %zu bytes of clipboard selection data to FD=%d",
|
|
|
|
|
len, fd);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close(fd);
|
2019-07-11 12:16:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
cancelled(void *data, struct wl_data_source *wl_data_source)
|
|
|
|
|
{
|
2019-11-04 14:11:18 +01:00
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
struct wl_clipboard *clipboard = &wayl->clipboard;
|
2019-07-11 12:16:50 +02:00
|
|
|
assert(clipboard->data_source == wl_data_source);
|
|
|
|
|
|
|
|
|
|
wl_data_source_destroy(clipboard->data_source);
|
|
|
|
|
clipboard->data_source = NULL;
|
|
|
|
|
clipboard->serial = 0;
|
|
|
|
|
|
|
|
|
|
free(clipboard->text);
|
|
|
|
|
clipboard->text = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dnd_drop_performed(void *data, struct wl_data_source *wl_data_source)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dnd_finished(void *data, struct wl_data_source *wl_data_source)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wl_data_source_listener data_source_listener = {
|
|
|
|
|
.target = &target,
|
|
|
|
|
.send = &send,
|
|
|
|
|
.cancelled = &cancelled,
|
|
|
|
|
.dnd_drop_performed = &dnd_drop_performed,
|
|
|
|
|
.dnd_finished = &dnd_finished,
|
|
|
|
|
.action = &action,
|
|
|
|
|
};
|
|
|
|
|
|
2019-07-11 17:34:52 +02:00
|
|
|
static void
|
|
|
|
|
primary_send(void *data,
|
|
|
|
|
struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1,
|
|
|
|
|
const char *mime_type, int32_t fd)
|
|
|
|
|
{
|
2019-11-04 13:11:15 +01:00
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
const struct wl_primary *primary = &wayl->primary;
|
2019-07-11 17:34:52 +02:00
|
|
|
|
|
|
|
|
assert(primary != NULL);
|
|
|
|
|
assert(primary->text != NULL);
|
|
|
|
|
|
2019-11-04 14:00:51 +01:00
|
|
|
const char *selection = primary->text;
|
|
|
|
|
const size_t len = strlen(selection);
|
|
|
|
|
|
2019-11-04 13:11:15 +01:00
|
|
|
int flags;
|
|
|
|
|
if ((flags = fcntl(fd, F_GETFL)) < 0 ||
|
|
|
|
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to set O_NONBLOCK");
|
|
|
|
|
return;
|
2019-07-11 17:34:52 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-28 19:20:25 +01:00
|
|
|
size_t async_idx = 0;
|
|
|
|
|
switch (async_write(fd, selection, len, &async_idx)) {
|
2019-11-04 14:00:51 +01:00
|
|
|
case ASYNC_WRITE_REMAIN: {
|
|
|
|
|
struct clipboard_send *ctx = malloc(sizeof(*ctx));
|
|
|
|
|
*ctx = (struct clipboard_send) {
|
|
|
|
|
.data = strdup(selection),
|
|
|
|
|
.len = len,
|
2019-11-28 19:20:25 +01:00
|
|
|
.idx = async_idx,
|
2019-11-04 14:00:51 +01:00
|
|
|
};
|
|
|
|
|
|
2020-01-10 19:49:48 +01:00
|
|
|
if (fdm_add(wayl->fdm, fd, EPOLLOUT, &fdm_send, ctx))
|
2019-11-04 14:00:51 +01:00
|
|
|
return;
|
2019-11-04 13:11:15 +01:00
|
|
|
|
2019-11-05 08:40:24 +01:00
|
|
|
free(ctx->data);
|
2019-11-04 13:11:15 +01:00
|
|
|
free(ctx);
|
2019-11-04 14:00:51 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case ASYNC_WRITE_DONE:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ASYNC_WRITE_ERR:
|
|
|
|
|
LOG_ERRNO(
|
|
|
|
|
"failed to write %zu bytes of primary selection data to FD=%d",
|
|
|
|
|
len, fd);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close(fd);
|
2019-07-11 17:34:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
primary_cancelled(void *data,
|
|
|
|
|
struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
|
|
|
|
|
{
|
2019-11-04 14:11:18 +01:00
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
struct wl_primary *primary = &wayl->primary;
|
2019-07-11 17:34:52 +02:00
|
|
|
|
|
|
|
|
zwp_primary_selection_source_v1_destroy(primary->data_source);
|
|
|
|
|
primary->data_source = NULL;
|
|
|
|
|
primary->serial = 0;
|
|
|
|
|
|
|
|
|
|
free(primary->text);
|
|
|
|
|
primary->text = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
|
|
|
|
|
.send = &primary_send,
|
|
|
|
|
.cancelled = &primary_cancelled,
|
|
|
|
|
};
|
|
|
|
|
|
2019-07-19 11:12:14 +02:00
|
|
|
bool
|
|
|
|
|
text_to_clipboard(struct terminal *term, char *text, uint32_t serial)
|
2019-07-11 12:16:50 +02:00
|
|
|
{
|
2019-10-27 18:51:14 +01:00
|
|
|
struct wl_clipboard *clipboard = &term->wl->clipboard;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2019-11-22 21:51:53 +01:00
|
|
|
if (clipboard->data_source != NULL) {
|
2019-10-27 16:15:32 +01:00
|
|
|
/* Kill previous data source */
|
2019-07-11 12:16:50 +02:00
|
|
|
assert(clipboard->serial != 0);
|
2019-10-27 18:51:14 +01:00
|
|
|
wl_data_device_set_selection(term->wl->data_device, NULL, clipboard->serial);
|
2019-07-11 12:16:50 +02:00
|
|
|
wl_data_source_destroy(clipboard->data_source);
|
|
|
|
|
free(clipboard->text);
|
|
|
|
|
|
|
|
|
|
clipboard->data_source = NULL;
|
|
|
|
|
clipboard->serial = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clipboard->data_source
|
2019-10-27 18:51:14 +01:00
|
|
|
= wl_data_device_manager_create_data_source(term->wl->data_device_manager);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2019-07-11 16:37:45 +02:00
|
|
|
if (clipboard->data_source == NULL) {
|
|
|
|
|
LOG_ERR("failed to create clipboard data source");
|
2019-07-19 11:12:14 +02:00
|
|
|
return false;
|
2019-07-11 16:37:45 +02:00
|
|
|
}
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2019-07-19 11:12:14 +02:00
|
|
|
clipboard->text = text;
|
|
|
|
|
|
2019-07-11 16:37:45 +02:00
|
|
|
/* Configure source */
|
2019-07-11 12:16:50 +02:00
|
|
|
wl_data_source_offer(clipboard->data_source, "text/plain;charset=utf-8");
|
2019-11-04 13:11:15 +01:00
|
|
|
wl_data_source_add_listener(clipboard->data_source, &data_source_listener, term->wl);
|
2019-10-27 18:51:14 +01:00
|
|
|
wl_data_device_set_selection(term->wl->data_device, clipboard->data_source, serial);
|
2019-07-11 16:37:45 +02:00
|
|
|
|
|
|
|
|
/* Needed when sending the selection to other client */
|
2019-11-22 21:52:59 +01:00
|
|
|
assert(serial != 0);
|
2019-07-11 12:16:50 +02:00
|
|
|
clipboard->serial = serial;
|
2019-07-19 11:12:14 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
selection_to_clipboard(struct terminal *term, uint32_t serial)
|
|
|
|
|
{
|
2019-11-22 21:52:12 +01:00
|
|
|
if (term->selection.start.row == -1 || term->selection.end.row == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-19 11:12:14 +02:00
|
|
|
/* Get selection as a string */
|
|
|
|
|
char *text = extract_selection(term);
|
|
|
|
|
if (!text_to_clipboard(term, text, serial))
|
|
|
|
|
free(text);
|
2019-07-11 12:16:50 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
struct clipboard_receive {
|
|
|
|
|
/* Callback data */
|
|
|
|
|
void (*cb)(const char *data, size_t size, void *user);
|
|
|
|
|
void (*done)(void *user);
|
|
|
|
|
void *user;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
fdm_receive(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct clipboard_receive *ctx = data;
|
|
|
|
|
|
|
|
|
|
if ((events & EPOLLHUP) && !(events & EPOLLIN))
|
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
|
|
/* Read until EOF */
|
|
|
|
|
while (true) {
|
|
|
|
|
char text[256];
|
|
|
|
|
ssize_t count = read(fd, text, sizeof(text));
|
|
|
|
|
|
|
|
|
|
if (count == -1) {
|
|
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
LOG_ERRNO("failed to read clipboard data");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count == 0)
|
|
|
|
|
break;
|
|
|
|
|
|
2019-12-15 12:11:12 +01:00
|
|
|
/* Call cb while at same time replacing \r\n with \n */
|
|
|
|
|
const char *p = text;
|
|
|
|
|
size_t left = count;
|
|
|
|
|
again:
|
|
|
|
|
for (size_t i = 0; i < left - 1; i++) {
|
|
|
|
|
if (p[i] == '\r' && p[i + 1] == '\n') {
|
|
|
|
|
ctx->cb(p, i, ctx->user);
|
|
|
|
|
|
|
|
|
|
assert(i + 1 <= left);
|
|
|
|
|
p += i + 1;
|
|
|
|
|
left -= i + 1;
|
|
|
|
|
goto again;
|
2019-11-05 09:09:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-15 12:11:12 +01:00
|
|
|
ctx->cb(p, left, ctx->user);
|
|
|
|
|
left = 0;
|
2019-11-05 09:09:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
fdm_del(fdm, fd);
|
|
|
|
|
ctx->done(ctx->user);
|
|
|
|
|
free(ctx);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
begin_receive_clipboard(struct terminal *term, int read_fd,
|
|
|
|
|
void (*cb)(const char *data, size_t size, void *user),
|
|
|
|
|
void (*done)(void *user), void *user)
|
|
|
|
|
{
|
|
|
|
|
int flags;
|
|
|
|
|
if ((flags = fcntl(read_fd, F_GETFL)) < 0 ||
|
|
|
|
|
fcntl(read_fd, F_SETFL, flags | O_NONBLOCK) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to set O_NONBLOCK");
|
|
|
|
|
close(read_fd);
|
|
|
|
|
return done(user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct clipboard_receive *ctx = malloc(sizeof(*ctx));
|
|
|
|
|
*ctx = (struct clipboard_receive) {
|
|
|
|
|
.cb = cb,
|
|
|
|
|
.done = done,
|
|
|
|
|
.user = user,
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-10 19:49:48 +01:00
|
|
|
if (!fdm_add(term->fdm, read_fd, EPOLLIN, &fdm_receive, ctx)) {
|
2019-11-05 09:09:51 +01:00
|
|
|
close(read_fd);
|
|
|
|
|
free(ctx);
|
|
|
|
|
done(user);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 12:33:31 +02:00
|
|
|
void
|
2019-07-19 14:20:00 +02:00
|
|
|
text_from_clipboard(struct terminal *term, uint32_t serial,
|
|
|
|
|
void (*cb)(const char *data, size_t size, void *user),
|
2019-11-05 08:49:32 +01:00
|
|
|
void (*done)(void *user), void *user)
|
2019-07-11 12:33:31 +02:00
|
|
|
{
|
2019-10-27 18:51:14 +01:00
|
|
|
struct wl_clipboard *clipboard = &term->wl->clipboard;
|
2019-07-11 12:33:31 +02:00
|
|
|
if (clipboard->data_offer == NULL)
|
2019-11-05 09:09:51 +01:00
|
|
|
return done(user);
|
2019-07-11 12:33:31 +02:00
|
|
|
|
2019-07-11 16:37:45 +02:00
|
|
|
/* Prepare a pipe the other client can write its selection to us */
|
2019-07-11 12:33:31 +02:00
|
|
|
int fds[2];
|
|
|
|
|
if (pipe2(fds, O_CLOEXEC) == -1) {
|
|
|
|
|
LOG_ERRNO("failed to create pipe");
|
2019-11-05 09:09:51 +01:00
|
|
|
return done(user);
|
2019-07-11 12:33:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int read_fd = fds[0];
|
|
|
|
|
int write_fd = fds[1];
|
|
|
|
|
|
2019-07-11 16:37:45 +02:00
|
|
|
/* Give write-end of pipe to other client */
|
2019-07-11 16:27:12 +02:00
|
|
|
wl_data_offer_receive(
|
|
|
|
|
clipboard->data_offer, "text/plain;charset=utf-8", write_fd);
|
2019-07-11 12:33:31 +02:00
|
|
|
|
2019-07-11 16:37:45 +02:00
|
|
|
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
2019-07-11 12:33:31 +02:00
|
|
|
close(write_fd);
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
begin_receive_clipboard(term, read_fd, cb, done, user);
|
2019-07-19 14:20:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
from_clipboard_cb(const char *data, size_t size, void *user)
|
|
|
|
|
{
|
|
|
|
|
struct terminal *term = user;
|
2019-11-03 00:27:39 +01:00
|
|
|
term_to_slave(term, data, size);
|
2019-07-19 14:20:00 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-05 08:49:32 +01:00
|
|
|
static void
|
|
|
|
|
from_clipboard_done(void *user)
|
|
|
|
|
{
|
|
|
|
|
struct terminal *term = user;
|
|
|
|
|
|
|
|
|
|
if (term->bracketed_paste)
|
|
|
|
|
term_to_slave(term, "\033[201~", 6);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 14:20:00 +02:00
|
|
|
void
|
|
|
|
|
selection_from_clipboard(struct terminal *term, uint32_t serial)
|
|
|
|
|
{
|
2019-10-27 18:51:14 +01:00
|
|
|
struct wl_clipboard *clipboard = &term->wl->clipboard;
|
2019-07-19 14:20:00 +02:00
|
|
|
if (clipboard->data_offer == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-11 12:33:31 +02:00
|
|
|
if (term->bracketed_paste)
|
2019-11-03 00:27:39 +01:00
|
|
|
term_to_slave(term, "\033[200~", 6);
|
2019-07-11 16:27:12 +02:00
|
|
|
|
2019-11-05 08:49:32 +01:00
|
|
|
text_from_clipboard(
|
|
|
|
|
term, serial, &from_clipboard_cb, &from_clipboard_done, term);
|
2019-07-11 12:33:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-09 21:27:51 +02:00
|
|
|
bool
|
|
|
|
|
text_to_primary(struct terminal *term, char *text, uint32_t serial)
|
|
|
|
|
{
|
2019-10-27 18:51:14 +01:00
|
|
|
if (term->wl->primary_selection_device_manager == NULL)
|
2019-09-25 19:26:55 +02:00
|
|
|
return false;
|
|
|
|
|
|
2019-10-27 18:51:14 +01:00
|
|
|
struct wl_primary *primary = &term->wl->primary;
|
2019-10-27 16:15:32 +01:00
|
|
|
|
2019-08-09 21:27:51 +02:00
|
|
|
/* TODO: somehow share code with the clipboard equivalent */
|
2019-10-27 18:51:14 +01:00
|
|
|
if (term->wl->primary.data_source != NULL) {
|
2019-08-09 21:27:51 +02:00
|
|
|
/* Kill previous data source */
|
|
|
|
|
|
|
|
|
|
assert(primary->serial != 0);
|
|
|
|
|
zwp_primary_selection_device_v1_set_selection(
|
2019-10-27 18:51:14 +01:00
|
|
|
term->wl->primary_selection_device, NULL, primary->serial);
|
2019-08-09 21:27:51 +02:00
|
|
|
zwp_primary_selection_source_v1_destroy(primary->data_source);
|
|
|
|
|
free(primary->text);
|
|
|
|
|
|
|
|
|
|
primary->data_source = NULL;
|
|
|
|
|
primary->serial = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
primary->data_source
|
|
|
|
|
= zwp_primary_selection_device_manager_v1_create_source(
|
2019-10-27 18:51:14 +01:00
|
|
|
term->wl->primary_selection_device_manager);
|
2019-08-09 21:27:51 +02:00
|
|
|
|
|
|
|
|
if (primary->data_source == NULL) {
|
|
|
|
|
LOG_ERR("failed to create clipboard data source");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get selection as a string */
|
|
|
|
|
primary->text = text;
|
|
|
|
|
|
|
|
|
|
/* Configure source */
|
|
|
|
|
zwp_primary_selection_source_v1_offer(primary->data_source, "text/plain;charset=utf-8");
|
2019-11-04 13:11:15 +01:00
|
|
|
zwp_primary_selection_source_v1_add_listener(primary->data_source, &primary_selection_source_listener, term->wl);
|
2019-10-27 18:51:14 +01:00
|
|
|
zwp_primary_selection_device_v1_set_selection(term->wl->primary_selection_device, primary->data_source, serial);
|
2019-08-09 21:27:51 +02:00
|
|
|
|
|
|
|
|
/* Needed when sending the selection to other client */
|
|
|
|
|
primary->serial = serial;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 16:42:59 +02:00
|
|
|
void
|
2019-08-09 21:27:51 +02:00
|
|
|
selection_to_primary(struct terminal *term, uint32_t serial)
|
|
|
|
|
{
|
2019-10-27 18:51:14 +01:00
|
|
|
if (term->wl->primary_selection_device_manager == NULL)
|
2019-09-25 19:26:55 +02:00
|
|
|
return;
|
|
|
|
|
|
2019-08-09 21:27:51 +02:00
|
|
|
/* Get selection as a string */
|
|
|
|
|
char *text = extract_selection(term);
|
|
|
|
|
if (!text_to_primary(term, text, serial))
|
|
|
|
|
free(text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
text_from_primary(
|
2019-11-05 08:49:32 +01:00
|
|
|
struct terminal *term,
|
|
|
|
|
void (*cb)(const char *data, size_t size, void *user),
|
|
|
|
|
void (*done)(void *user), void *user)
|
2019-07-11 16:42:59 +02:00
|
|
|
{
|
2019-10-27 18:51:14 +01:00
|
|
|
if (term->wl->primary_selection_device_manager == NULL)
|
2019-11-05 09:09:51 +01:00
|
|
|
return done(user);
|
2019-09-25 19:26:55 +02:00
|
|
|
|
2019-10-27 18:51:14 +01:00
|
|
|
struct wl_primary *primary = &term->wl->primary;
|
2019-07-11 17:02:21 +02:00
|
|
|
if (primary->data_offer == NULL)
|
2019-11-05 09:09:51 +01:00
|
|
|
return done(user);
|
2019-07-11 17:02:21 +02:00
|
|
|
|
|
|
|
|
/* 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");
|
2019-11-05 09:09:51 +01:00
|
|
|
return done(user);
|
2019-07-11 17:02:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int read_fd = fds[0];
|
|
|
|
|
int write_fd = fds[1];
|
|
|
|
|
|
|
|
|
|
/* Give write-end of pipe to other client */
|
|
|
|
|
zwp_primary_selection_offer_v1_receive(
|
|
|
|
|
primary->data_offer, "text/plain;charset=utf-8", write_fd);
|
|
|
|
|
|
|
|
|
|
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
|
|
|
|
close(write_fd);
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
begin_receive_clipboard(term, read_fd, cb, done, user);
|
2019-08-09 21:27:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
selection_from_primary(struct terminal *term)
|
|
|
|
|
{
|
2019-10-27 18:51:14 +01:00
|
|
|
if (term->wl->primary_selection_device_manager == NULL)
|
2019-09-25 19:26:55 +02:00
|
|
|
return;
|
|
|
|
|
|
2019-10-27 18:51:14 +01:00
|
|
|
struct wl_clipboard *clipboard = &term->wl->clipboard;
|
2019-08-09 21:27:51 +02:00
|
|
|
if (clipboard->data_offer == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-11 17:02:21 +02:00
|
|
|
if (term->bracketed_paste)
|
2019-11-03 00:27:39 +01:00
|
|
|
term_to_slave(term, "\033[200~", 6);
|
2019-07-11 17:02:21 +02:00
|
|
|
|
2019-11-05 08:49:32 +01:00
|
|
|
text_from_primary(term, &from_clipboard_cb, &from_clipboard_done, term);
|
2019-07-11 16:42:59 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-11 17:37:32 +02:00
|
|
|
#if 0
|
2019-07-11 12:16:50 +02:00
|
|
|
static void
|
|
|
|
|
offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
source_actions(void *data, struct wl_data_offer *wl_data_offer,
|
|
|
|
|
uint32_t source_actions)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wl_data_offer_listener data_offer_listener = {
|
|
|
|
|
.offer = &offer,
|
|
|
|
|
.source_actions = &source_actions,
|
|
|
|
|
.action = &offer_action,
|
|
|
|
|
};
|
2019-07-11 17:37:32 +02:00
|
|
|
#endif
|
2019-07-11 12:16:50 +02:00
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
data_offer(void *data, struct wl_data_device *wl_data_device,
|
|
|
|
|
struct wl_data_offer *id)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
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_data_offer *id)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
leave(void *data, struct wl_data_device *wl_data_device)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
motion(void *data, struct wl_data_device *wl_data_device, uint32_t time,
|
|
|
|
|
wl_fixed_t x, wl_fixed_t y)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
drop(void *data, struct wl_data_device *wl_data_device)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
selection(void *data, struct wl_data_device *wl_data_device,
|
|
|
|
|
struct wl_data_offer *id)
|
|
|
|
|
{
|
2019-07-11 16:37:45 +02:00
|
|
|
/* Selection offer from other client */
|
|
|
|
|
|
2019-10-27 18:43:07 +01:00
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
struct wl_clipboard *clipboard = &wayl->clipboard;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
|
|
|
|
if (clipboard->data_offer != NULL)
|
|
|
|
|
wl_data_offer_destroy(clipboard->data_offer);
|
|
|
|
|
|
|
|
|
|
clipboard->data_offer = id;
|
2019-07-11 17:37:32 +02:00
|
|
|
#if 0
|
2019-07-11 12:33:31 +02:00
|
|
|
if (id != NULL)
|
|
|
|
|
wl_data_offer_add_listener(id, &data_offer_listener, term);
|
2019-07-11 17:37:32 +02:00
|
|
|
#endif
|
2019-07-11 12:16:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct wl_data_device_listener data_device_listener = {
|
|
|
|
|
.data_offer = &data_offer,
|
|
|
|
|
.enter = &enter,
|
|
|
|
|
.leave = &leave,
|
|
|
|
|
.motion = &motion,
|
|
|
|
|
.drop = &drop,
|
|
|
|
|
.selection = &selection,
|
|
|
|
|
};
|
2019-07-11 17:02:21 +02:00
|
|
|
|
2019-07-11 17:37:32 +02:00
|
|
|
#if 0
|
2019-07-11 17:02:21 +02:00
|
|
|
static void
|
|
|
|
|
primary_offer(void *data,
|
|
|
|
|
struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer,
|
|
|
|
|
const char *mime_type)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
|
|
|
|
|
.offer = &primary_offer,
|
|
|
|
|
};
|
2019-07-11 17:37:32 +02:00
|
|
|
#endif
|
2019-07-11 17:02:21 +02:00
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
primary_data_offer(void *data,
|
|
|
|
|
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device,
|
|
|
|
|
struct zwp_primary_selection_offer_v1 *offer)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
primary_selection(void *data,
|
|
|
|
|
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device,
|
|
|
|
|
struct zwp_primary_selection_offer_v1 *id)
|
|
|
|
|
{
|
|
|
|
|
/* Selection offer from other client, for primary */
|
|
|
|
|
|
2019-10-27 18:43:07 +01:00
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
struct wl_primary *primary = &wayl->primary;
|
2019-07-11 17:02:21 +02:00
|
|
|
|
|
|
|
|
if (primary->data_offer != NULL)
|
|
|
|
|
zwp_primary_selection_offer_v1_destroy(primary->data_offer);
|
|
|
|
|
|
|
|
|
|
primary->data_offer = id;
|
2019-07-11 17:37:32 +02:00
|
|
|
#if 0
|
2019-07-11 17:02:21 +02:00
|
|
|
if (id != NULL) {
|
|
|
|
|
zwp_primary_selection_offer_v1_add_listener(
|
|
|
|
|
id, &primary_selection_offer_listener, term);
|
|
|
|
|
}
|
2019-07-11 17:37:32 +02:00
|
|
|
#endif
|
2019-07-11 17:02:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
|
|
|
|
|
.data_offer = &primary_data_offer,
|
|
|
|
|
.selection = &primary_selection,
|
|
|
|
|
};
|