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>
|
2020-08-22 08:29:35 +02:00
|
|
|
|
#include <sys/timerfd.h>
|
2019-11-04 13:11:15 +01:00
|
|
|
|
|
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"
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
#include "commands.h"
|
2020-10-09 19:44:23 +02:00
|
|
|
|
#include "config.h"
|
2020-07-15 11:19:18 +02:00
|
|
|
|
#include "extract.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"
|
2020-08-08 20:34:30 +01:00
|
|
|
|
#include "xmalloc.h"
|
2019-07-11 09:51:51 +02:00
|
|
|
|
|
2019-08-09 21:26:34 +02:00
|
|
|
|
bool
|
2020-07-09 09:52:11 +02:00
|
|
|
|
selection_enabled(const struct terminal *term, struct seat *seat)
|
2019-07-11 09:57:04 +02:00
|
|
|
|
{
|
2019-08-27 20:57:58 +02:00
|
|
|
|
return
|
2020-07-26 12:31:13 +02:00
|
|
|
|
seat->mouse.col >= 0 && seat->mouse.row >= 0 &&
|
|
|
|
|
|
(term->mouse_tracking == MOUSE_NONE ||
|
|
|
|
|
|
term_mouse_grabbed(term, seat) ||
|
|
|
|
|
|
term->is_searching);
|
2019-07-11 09:57:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-05 20:16:17 +02:00
|
|
|
|
bool
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
|
selection_on_rows(const struct terminal *term, int row_start, int row_end)
|
2019-08-05 20:16:17 +02:00
|
|
|
|
{
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
|
LOG_DBG("on rows: %d-%d, range: %d-%d (offset=%d)",
|
2020-05-16 16:28:32 +02:00
|
|
|
|
term->selection.start.row, term->selection.end.row,
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
|
row_start, row_end, term->grid->offset);
|
2020-05-16 16:28:32 +02:00
|
|
|
|
|
2020-05-19 18:51:30 +02:00
|
|
|
|
if (term->selection.end.row < 0)
|
2019-08-05 20:16:17 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
|
assert(term->selection.start.row != -1);
|
|
|
|
|
|
|
|
|
|
|
|
row_start += term->grid->offset;
|
|
|
|
|
|
row_end += term->grid->offset;
|
|
|
|
|
|
|
2019-08-05 20:16:17 +02:00
|
|
|
|
const struct coord *start = &term->selection.start;
|
|
|
|
|
|
const struct coord *end = &term->selection.end;
|
|
|
|
|
|
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
|
if ((row_start <= start->row && row_end >= start->row) ||
|
|
|
|
|
|
(row_start <= end->row && row_end >= end->row))
|
|
|
|
|
|
{
|
|
|
|
|
|
/* The range crosses one of the selection boundaries */
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* For the last check we must ensure start <= end */
|
2020-05-16 16:28:32 +02:00
|
|
|
|
if (start->row > end->row) {
|
|
|
|
|
|
const struct coord *tmp = start;
|
|
|
|
|
|
start = end;
|
|
|
|
|
|
end = tmp;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
|
if (row_start >= start->row && row_end <= end->row) {
|
|
|
|
|
|
LOG_INFO("ON ROWS");
|
2020-05-16 16:28:32 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-05-17 12:01:24 +02:00
|
|
|
|
return false;
|
2019-08-05 20:16:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-05-19 18:49:42 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_view_up(struct terminal *term, int new_view)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (likely(term->selection.start.row < 0))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (likely(new_view < term->grid->view))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
term->selection.start.row += term->grid->num_rows;
|
|
|
|
|
|
if (term->selection.end.row >= 0)
|
|
|
|
|
|
term->selection.end.row += term->grid->num_rows;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
selection_view_down(struct terminal *term, int new_view)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (likely(term->selection.start.row < 0))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (likely(new_view > term->grid->view))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
term->selection.start.row &= term->grid->num_rows - 1;
|
|
|
|
|
|
if (term->selection.end.row >= 0)
|
|
|
|
|
|
term->selection.end.row &= term->grid->num_rows - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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-07-15 11:19:18 +02:00
|
|
|
|
bool (*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-07-15 11:19:18 +02:00
|
|
|
|
if (!cb(term, row, &row->cells[c], c, data))
|
|
|
|
|
|
return;
|
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-07-15 11:19:18 +02:00
|
|
|
|
bool (*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
|
|
|
|
|
2020-07-14 12:59:36 +02:00
|
|
|
|
for (int c = top_left.col; c <= bottom_right.col; c++) {
|
2020-07-15 11:19:18 +02:00
|
|
|
|
if (!cb(term, row, &row->cells[c], c, data))
|
|
|
|
|
|
return;
|
2020-07-14 12:59:36 +02:00
|
|
|
|
}
|
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-07-15 11:19:18 +02:00
|
|
|
|
bool (*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-08-23 09:39:49 +02:00
|
|
|
|
foreach_selected_normal(term, start, end, cb, data);
|
|
|
|
|
|
return;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
|
|
|
|
|
case SELECTION_BLOCK:
|
2020-08-23 09:39:49 +02:00
|
|
|
|
foreach_selected_block(term, start, end, cb, data);
|
|
|
|
|
|
return;
|
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-07-15 11:31:57 +02:00
|
|
|
|
static bool
|
|
|
|
|
|
extract_one_const_wrapper(struct terminal *term,
|
|
|
|
|
|
struct row *row, struct cell *cell,
|
|
|
|
|
|
int col, void *data)
|
|
|
|
|
|
{
|
|
|
|
|
|
return extract_one(term, row, cell, col, data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-31 17:02:53 +02:00
|
|
|
|
char *
|
|
|
|
|
|
selection_to_text(const struct terminal *term)
|
2020-01-04 12:59:29 +01:00
|
|
|
|
{
|
2020-07-31 17:02:53 +02:00
|
|
|
|
if (term->selection.end.row == -1)
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
2020-07-15 11:19:18 +02:00
|
|
|
|
struct extraction_context *ctx = extract_begin(term->selection.kind);
|
|
|
|
|
|
if (ctx == NULL)
|
|
|
|
|
|
return NULL;
|
2020-01-04 12:59:29 +01:00
|
|
|
|
|
2020-01-06 11:56:18 +01:00
|
|
|
|
foreach_selected(
|
|
|
|
|
|
(struct terminal *)term, term->selection.start, term->selection.end,
|
2020-07-15 11:31:57 +02:00
|
|
|
|
&extract_one_const_wrapper, ctx);
|
2019-08-04 13:07:54 +02:00
|
|
|
|
|
2020-07-15 11:19:18 +02:00
|
|
|
|
char *text;
|
|
|
|
|
|
return extract_finish(ctx, &text, NULL) ? text : NULL;
|
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
|
|
|
|
{
|
|
|
|
|
|
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);
|
2020-06-02 18:21:39 +02:00
|
|
|
|
|
2020-01-03 23:29:45 +01:00
|
|
|
|
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-08-11 09:55:33 +02:00
|
|
|
|
term->selection.ongoing = true;
|
2019-07-11 09:51:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-12 18:54:51 +02:00
|
|
|
|
/* Context used while (un)marking selected cells, to be able to
|
|
|
|
|
|
* exclude empty cells */
|
|
|
|
|
|
struct mark_context {
|
|
|
|
|
|
const struct row *last_row;
|
|
|
|
|
|
int empty_count;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2020-07-15 11:19:18 +02:00
|
|
|
|
static bool
|
2020-01-04 12:03:04 +01:00
|
|
|
|
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-07-15 11:19:18 +02:00
|
|
|
|
return true;
|
2020-01-06 11:56:18 +01:00
|
|
|
|
}
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
2020-07-15 09:21:39 +02:00
|
|
|
|
row->dirty = true;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
cell->attrs.selected = 0;
|
|
|
|
|
|
cell->attrs.clean = 0;
|
2020-07-15 11:19:18 +02:00
|
|
|
|
return true;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-15 09:21:39 +02:00
|
|
|
|
|
2020-07-15 11:19:18 +02:00
|
|
|
|
static bool
|
2020-01-06 11:56:18 +01:00
|
|
|
|
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
|
|
|
|
{
|
2020-08-12 18:54:51 +02:00
|
|
|
|
struct mark_context *ctx = data;
|
|
|
|
|
|
assert(ctx != NULL);
|
|
|
|
|
|
|
|
|
|
|
|
if (ctx->last_row != row) {
|
|
|
|
|
|
ctx->last_row = row;
|
|
|
|
|
|
ctx->empty_count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (cell->wc == 0 && term->selection.kind == SELECTION_NORMAL) {
|
|
|
|
|
|
ctx->empty_count++;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-06 11:56:18 +01:00
|
|
|
|
/* Tell unmark to leave this be */
|
2020-08-12 18:54:51 +02:00
|
|
|
|
for (int i = 0; i < ctx->empty_count + 1; i++)
|
|
|
|
|
|
row->cells[col - i].attrs.selected |= 2;
|
|
|
|
|
|
|
2020-07-15 11:19:18 +02:00
|
|
|
|
return true;
|
2020-01-06 11:56:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-15 11:19:18 +02:00
|
|
|
|
static bool
|
2020-01-04 12:03:04 +01:00
|
|
|
|
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-08-12 18:54:51 +02:00
|
|
|
|
struct mark_context *ctx = data;
|
|
|
|
|
|
assert(ctx != NULL);
|
|
|
|
|
|
|
|
|
|
|
|
if (ctx->last_row != row) {
|
|
|
|
|
|
ctx->last_row = row;
|
|
|
|
|
|
ctx->empty_count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (cell->wc == 0 && term->selection.kind == SELECTION_NORMAL) {
|
|
|
|
|
|
ctx->empty_count++;
|
2020-07-15 11:19:18 +02:00
|
|
|
|
return true;
|
2020-01-06 11:56:18 +01:00
|
|
|
|
}
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
2020-08-12 18:54:51 +02:00
|
|
|
|
for (int i = 0; i < ctx->empty_count + 1; i++) {
|
|
|
|
|
|
struct cell *c = &row->cells[col - i];
|
|
|
|
|
|
if (c->attrs.selected & 1)
|
|
|
|
|
|
c->attrs.selected = 1; /* Clear the pre-mark bit */
|
|
|
|
|
|
else {
|
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
|
c->attrs.selected = 1;
|
|
|
|
|
|
c->attrs.clean = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-15 11:19:18 +02:00
|
|
|
|
return true;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
|
static void
|
|
|
|
|
|
selection_modify(struct terminal *term, struct coord start, struct coord end)
|
|
|
|
|
|
{
|
|
|
|
|
|
assert(term->selection.start.row != -1);
|
|
|
|
|
|
assert(start.row != -1 && start.col != -1);
|
|
|
|
|
|
assert(end.row != -1 && end.col != -1);
|
|
|
|
|
|
|
2020-08-23 07:42:20 +02:00
|
|
|
|
struct mark_context ctx = {0};
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
|
/* Premark all cells that *will* be selected */
|
2020-08-12 18:54:51 +02:00
|
|
|
|
foreach_selected(term, start, end, &premark_selected, &ctx);
|
|
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
2020-04-04 11:59:15 +02:00
|
|
|
|
|
2020-05-19 18:51:30 +02:00
|
|
|
|
if (term->selection.end.row >= 0) {
|
2020-04-04 11:59:15 +02:00
|
|
|
|
/* Unmark previous selection, ignoring cells that are part of
|
|
|
|
|
|
* the new selection */
|
|
|
|
|
|
foreach_selected(term, term->selection.start, term->selection.end,
|
2020-08-12 18:54:51 +02:00
|
|
|
|
&unmark_selected, &ctx);
|
|
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
term->selection.start = start;
|
|
|
|
|
|
term->selection.end = end;
|
|
|
|
|
|
|
|
|
|
|
|
/* Mark new selection */
|
2020-08-12 18:54:51 +02:00
|
|
|
|
foreach_selected(term, start, end, &mark_selected, &ctx);
|
2020-04-04 11:59:15 +02:00
|
|
|
|
render_refresh(term);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_update(struct terminal *term, int col, int row)
|
|
|
|
|
|
{
|
2020-05-19 18:51:30 +02:00
|
|
|
|
if (term->selection.start.row < 0)
|
2019-07-11 09:57:04 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
2020-08-11 10:14:38 +02:00
|
|
|
|
if (!term->selection.ongoing)
|
|
|
|
|
|
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->grid->view + row != -1);
|
2019-07-11 11:10:12 +02:00
|
|
|
|
|
2020-06-02 18:21:39 +02:00
|
|
|
|
struct coord new_start = term->selection.start;
|
2020-01-06 11:56:18 +01:00
|
|
|
|
struct coord new_end = {col, term->grid->view + row};
|
2020-06-02 18:21:39 +02:00
|
|
|
|
|
2020-08-13 18:32:56 +02:00
|
|
|
|
size_t start_row_idx = new_start.row & (term->grid->num_rows - 1);
|
|
|
|
|
|
size_t end_row_idx = new_end.row & (term->grid->num_rows - 1);
|
|
|
|
|
|
const struct row *row_start = term->grid->rows[start_row_idx];
|
|
|
|
|
|
const struct row *row_end = term->grid->rows[end_row_idx];
|
|
|
|
|
|
|
2020-08-12 18:50:49 +02:00
|
|
|
|
/* Adjust start point if the selection has changed 'direction' */
|
|
|
|
|
|
if (!(new_end.row == new_start.row && new_end.col == new_start.col)) {
|
|
|
|
|
|
enum selection_direction new_direction;
|
|
|
|
|
|
|
|
|
|
|
|
if (new_end.row > new_start.row ||
|
|
|
|
|
|
(new_end.row == new_start.row && new_end.col > new_start.col))
|
|
|
|
|
|
{
|
2020-08-13 18:32:56 +02:00
|
|
|
|
/* New end point is after the start point */
|
2020-08-12 18:50:49 +02:00
|
|
|
|
new_direction = SELECTION_RIGHT;
|
|
|
|
|
|
} else {
|
2020-08-13 18:32:56 +02:00
|
|
|
|
/* The new end point is before the start point */
|
2020-08-12 18:50:49 +02:00
|
|
|
|
new_direction = SELECTION_LEFT;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (term->selection.direction != new_direction) {
|
|
|
|
|
|
if (term->selection.direction != SELECTION_UNDIR) {
|
|
|
|
|
|
if (new_direction == SELECTION_LEFT) {
|
2020-08-13 18:32:56 +02:00
|
|
|
|
bool keep_going = true;
|
|
|
|
|
|
while (keep_going) {
|
|
|
|
|
|
const wchar_t wc = row_start->cells[new_start.col].wc;
|
|
|
|
|
|
keep_going = wc == CELL_MULT_COL_SPACER;
|
|
|
|
|
|
|
|
|
|
|
|
new_start.col--;
|
|
|
|
|
|
if (new_start.col < 0) {
|
|
|
|
|
|
new_start.col = term->cols - 1;
|
|
|
|
|
|
new_start.row--;
|
|
|
|
|
|
}
|
2020-08-12 18:50:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2020-08-13 18:32:56 +02:00
|
|
|
|
bool keep_going = true;
|
|
|
|
|
|
while (keep_going) {
|
|
|
|
|
|
const wchar_t wc = new_start.col < term->cols - 1
|
|
|
|
|
|
? row_start->cells[new_start.col + 1].wc
|
|
|
|
|
|
: 0;
|
|
|
|
|
|
|
|
|
|
|
|
keep_going = wc == CELL_MULT_COL_SPACER;
|
|
|
|
|
|
|
|
|
|
|
|
new_start.col++;
|
|
|
|
|
|
if (new_start.col >= term->cols) {
|
|
|
|
|
|
new_start.col = 0;
|
|
|
|
|
|
new_start.row++;
|
|
|
|
|
|
}
|
2020-08-12 18:50:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
term->selection.direction = new_direction;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-13 18:33:22 +02:00
|
|
|
|
/* If an end point is in the middle of a multi-column character,
|
|
|
|
|
|
* expand the selection to cover the entire character */
|
2020-06-02 18:21:39 +02:00
|
|
|
|
if (new_start.row < new_end.row ||
|
|
|
|
|
|
(new_start.row == new_end.row && new_start.col <= new_end.col))
|
|
|
|
|
|
{
|
2020-08-13 18:33:22 +02:00
|
|
|
|
while (new_start.col >= 1 &&
|
|
|
|
|
|
row_start->cells[new_start.col].wc == CELL_MULT_COL_SPACER)
|
|
|
|
|
|
new_start.col--;
|
|
|
|
|
|
while (new_end.col < term->cols - 1 &&
|
|
|
|
|
|
row_end->cells[new_end.col + 1].wc == CELL_MULT_COL_SPACER)
|
|
|
|
|
|
new_end.col++;
|
2020-06-02 18:21:39 +02:00
|
|
|
|
} else {
|
2020-08-13 18:33:22 +02:00
|
|
|
|
while (new_end.col >= 1 &&
|
|
|
|
|
|
row_end->cells[new_end.col].wc == CELL_MULT_COL_SPACER)
|
|
|
|
|
|
new_end.col--;
|
|
|
|
|
|
while (new_start.col < term->cols - 1 &&
|
|
|
|
|
|
row_start->cells[new_start.col + 1].wc == CELL_MULT_COL_SPACER)
|
|
|
|
|
|
new_start.col++;
|
2020-06-02 18:21:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
selection_modify(term, new_start, new_end);
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
2019-07-11 11:10:12 +02:00
|
|
|
|
|
2020-05-16 21:36:08 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_dirty_cells(struct terminal *term)
|
|
|
|
|
|
{
|
2020-05-19 18:51:30 +02:00
|
|
|
|
if (term->selection.start.row < 0 || term->selection.end.row < 0)
|
2020-05-16 21:36:08 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
foreach_selected(
|
2020-08-12 18:54:51 +02:00
|
|
|
|
term, term->selection.start, term->selection.end, &mark_selected,
|
2020-08-23 07:42:20 +02:00
|
|
|
|
&(struct mark_context){0});
|
2020-05-16 21:36:08 +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 */
|
2020-07-27 16:44:41 +02:00
|
|
|
|
new_start = *end;
|
|
|
|
|
|
new_end = (struct coord){col, row};
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 */
|
2020-07-27 16:44:41 +02:00
|
|
|
|
new_start = *end;
|
|
|
|
|
|
new_end = (struct coord){col, row};
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)) {
|
2020-07-27 16:44:41 +02:00
|
|
|
|
new_start = bottom_right;
|
|
|
|
|
|
new_end = (struct coord){col, row};
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else {
|
2020-07-27 16:44:41 +02:00
|
|
|
|
new_start = bottom_left;
|
|
|
|
|
|
new_end = (struct coord){col, row};
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_extend(struct seat *seat, struct terminal *term,
|
|
|
|
|
|
int col, int row, uint32_t serial)
|
2020-04-04 11:59:15 +02:00
|
|
|
|
{
|
2020-05-19 18:51:30 +02:00
|
|
|
|
if (term->selection.start.row < 0 || term->selection.end.row < 0) {
|
2020-04-04 11:59:15 +02:00
|
|
|
|
/* No existing selection */
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-11 10:16:52 +02:00
|
|
|
|
term->selection.ongoing = true;
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_to_primary(seat, term, serial);
|
2019-07-11 09:51:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
|
//static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener;
|
2019-07-11 17:34:52 +02:00
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
|
void
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial)
|
2019-07-11 09:51:51 +02:00
|
|
|
|
{
|
2020-08-11 09:55:33 +02:00
|
|
|
|
if (!term->selection.ongoing)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
selection_stop_scroll_timer(term);
|
2020-08-11 09:55:33 +02:00
|
|
|
|
term->selection.ongoing = false;
|
|
|
|
|
|
|
2020-08-11 10:15:01 +02:00
|
|
|
|
if (term->selection.start.row < 0 || term->selection.end.row < 0)
|
|
|
|
|
|
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);
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_to_primary(seat, term, serial);
|
2019-07-11 09:51:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
selection_cancel(struct terminal *term)
|
|
|
|
|
|
{
|
|
|
|
|
|
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-10-11 18:24:47 +02:00
|
|
|
|
selection_stop_scroll_timer(term);
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
2020-05-19 18:51:30 +02:00
|
|
|
|
if (term->selection.start.row >= 0 && term->selection.end.row >= 0) {
|
2020-01-06 11:56:18 +01:00
|
|
|
|
foreach_selected(
|
|
|
|
|
|
term, term->selection.start, term->selection.end,
|
2020-08-23 07:42:20 +02:00
|
|
|
|
&unmark_selected, &(struct mark_context){0});
|
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};
|
2020-08-12 18:50:49 +02:00
|
|
|
|
term->selection.direction = SELECTION_UNDIR;
|
2020-08-11 09:55:33 +02:00
|
|
|
|
term->selection.ongoing = false;
|
2019-07-11 09:51:51 +02:00
|
|
|
|
}
|
2019-07-11 12:16:50 +02:00
|
|
|
|
|
2020-09-09 18:45:10 +02:00
|
|
|
|
bool
|
|
|
|
|
|
selection_clipboard_has_data(const struct seat *seat)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seat->clipboard.data_offer != NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
|
selection_primary_has_data(const struct seat *seat)
|
|
|
|
|
|
{
|
|
|
|
|
|
return seat->primary.data_offer != NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-09 18:44:49 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_clipboard_unset(struct seat *seat)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct wl_clipboard *clipboard = &seat->clipboard;
|
|
|
|
|
|
|
|
|
|
|
|
if (clipboard->data_source == NULL)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
/* Kill previous data source */
|
|
|
|
|
|
assert(clipboard->serial != 0);
|
|
|
|
|
|
wl_data_device_set_selection(seat->data_device, NULL, clipboard->serial);
|
|
|
|
|
|
wl_data_source_destroy(clipboard->data_source);
|
|
|
|
|
|
|
|
|
|
|
|
clipboard->data_source = NULL;
|
|
|
|
|
|
clipboard->serial = 0;
|
|
|
|
|
|
|
|
|
|
|
|
free(clipboard->text);
|
|
|
|
|
|
clipboard->text = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
selection_primary_unset(struct seat *seat)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct wl_primary *primary = &seat->primary;
|
|
|
|
|
|
|
|
|
|
|
|
if (primary->data_source == NULL)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
assert(primary->serial != 0);
|
|
|
|
|
|
zwp_primary_selection_device_v1_set_selection(
|
|
|
|
|
|
seat->primary_selection_device, NULL, primary->serial);
|
|
|
|
|
|
zwp_primary_selection_source_v1_destroy(primary->data_source);
|
|
|
|
|
|
|
|
|
|
|
|
primary->data_source = NULL;
|
|
|
|
|
|
primary->serial = 0;
|
|
|
|
|
|
|
|
|
|
|
|
free(primary->text);
|
|
|
|
|
|
primary->text = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-17 21:30:57 +02:00
|
|
|
|
void
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_mark_word(struct seat *seat, struct terminal *term, int col, int row,
|
|
|
|
|
|
bool spaces_only, uint32_t serial)
|
2019-07-17 21:30:57 +02:00
|
|
|
|
{
|
|
|
|
|
|
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
|
|
|
|
|
2020-10-09 19:44:23 +02:00
|
|
|
|
if (!(c == 0 || !isword(c, spaces_only, term->conf->word_delimiters))) {
|
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;
|
2020-10-09 19:44:23 +02:00
|
|
|
|
if (c == 0 || !isword(c, spaces_only, term->conf->word_delimiters))
|
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
|
|
|
|
|
2020-10-09 19:44:23 +02:00
|
|
|
|
if (!(c == 0 || !isword(c, spaces_only, term->conf->word_delimiters))) {
|
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;
|
2020-10-09 19:44:23 +02:00
|
|
|
|
if (c == '\0' || !isword(c, spaces_only, term->conf->word_delimiters))
|
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);
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_finalize(seat, term, serial);
|
2019-07-17 21:30:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-06 19:32:06 +02:00
|
|
|
|
void
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_mark_row(
|
|
|
|
|
|
struct seat *seat, struct terminal *term, int row, uint32_t serial)
|
2019-08-06 19:32:06 +02:00
|
|
|
|
{
|
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);
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_finalize(seat, term, serial);
|
2019-08-06 19:32:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
static bool
|
|
|
|
|
|
fdm_scroll_timer(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t expiration_count;
|
|
|
|
|
|
ssize_t ret = read(
|
|
|
|
|
|
term->selection.auto_scroll.fd,
|
|
|
|
|
|
&expiration_count, sizeof(expiration_count));
|
|
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
LOG_ERRNO("failed to read selection scroll timer");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (term->selection.auto_scroll.direction) {
|
2020-10-11 18:18:18 +02:00
|
|
|
|
case SELECTION_SCROLL_NOT:
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
case SELECTION_SCROLL_UP:
|
2020-10-11 18:26:24 +02:00
|
|
|
|
cmd_scrollback_up(term, expiration_count);
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
selection_update(term, term->selection.auto_scroll.col, 0);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case SELECTION_SCROLL_DOWN:
|
2020-10-11 18:26:24 +02:00
|
|
|
|
cmd_scrollback_down(term, expiration_count);
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
selection_update(term, term->selection.auto_scroll.col, term->rows - 1);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
selection_start_scroll_timer(struct terminal *term, int interval_ns,
|
|
|
|
|
|
enum selection_scroll_direction direction, int col)
|
|
|
|
|
|
{
|
2020-10-11 18:18:18 +02:00
|
|
|
|
assert(direction != SELECTION_SCROLL_NOT);
|
|
|
|
|
|
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
if (!term->selection.ongoing)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (term->selection.auto_scroll.fd < 0) {
|
|
|
|
|
|
int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
|
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
|
LOG_ERRNO("failed to create selection scroll timer");
|
|
|
|
|
|
goto err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_scroll_timer, term)) {
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
term->selection.auto_scroll.fd = fd;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct itimerspec timer;
|
|
|
|
|
|
if (timerfd_gettime(term->selection.auto_scroll.fd, &timer) < 0) {
|
|
|
|
|
|
LOG_ERRNO("failed to get current selection scroll timer value");
|
|
|
|
|
|
goto err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timer.it_value.tv_sec == 0 && timer.it_value.tv_nsec == 0)
|
|
|
|
|
|
timer.it_value.tv_nsec = 1;
|
|
|
|
|
|
|
|
|
|
|
|
timer.it_interval.tv_sec = interval_ns / 1000000000;
|
|
|
|
|
|
timer.it_interval.tv_nsec = interval_ns % 1000000000;
|
|
|
|
|
|
|
|
|
|
|
|
if (timerfd_settime(term->selection.auto_scroll.fd, 0, &timer, NULL) < 0) {
|
|
|
|
|
|
LOG_ERRNO("failed to set new selection scroll timer value");
|
|
|
|
|
|
goto err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
term->selection.auto_scroll.direction = direction;
|
|
|
|
|
|
term->selection.auto_scroll.col = col;
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
|
selection_stop_scroll_timer(term);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
selection_stop_scroll_timer(struct terminal *term)
|
|
|
|
|
|
{
|
2020-10-11 18:18:18 +02:00
|
|
|
|
if (term->selection.auto_scroll.fd < 0) {
|
|
|
|
|
|
assert(term->selection.auto_scroll.direction == SELECTION_SCROLL_NOT);
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
return;
|
2020-10-11 18:18:18 +02:00
|
|
|
|
}
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
|
|
|
|
|
|
fdm_del(term->fdm, term->selection.auto_scroll.fd);
|
|
|
|
|
|
term->selection.auto_scroll.fd = -1;
|
2020-10-11 18:18:18 +02:00
|
|
|
|
term->selection.auto_scroll.direction = SELECTION_SCROLL_NOT;
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
|
}
|
2020-07-08 16:45:26 +02:00
|
|
|
|
|
2019-07-11 12:16:50 +02:00
|
|
|
|
static void
|
|
|
|
|
|
target(void *data, struct wl_data_source *wl_data_source, const char *mime_type)
|
|
|
|
|
|
{
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
LOG_DBG("TARGET: mime-type=%s", mime_type);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
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
|
2020-10-13 18:38:49 +02:00
|
|
|
|
send_clipboard_or_primary(struct seat *seat, int fd, const char *selection,
|
|
|
|
|
|
const char *source_name)
|
2019-07-11 12:16:50 +02:00
|
|
|
|
{
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-13 18:38:49 +02:00
|
|
|
|
size_t len = strlen(selection);
|
2019-11-28 19:20:25 +01:00
|
|
|
|
size_t async_idx = 0;
|
2020-10-13 18:38:49 +02:00
|
|
|
|
|
2019-11-28 19:20:25 +01:00
|
|
|
|
switch (async_write(fd, selection, len, &async_idx)) {
|
2019-11-04 14:00:51 +01:00
|
|
|
|
case ASYNC_WRITE_REMAIN: {
|
2020-08-08 20:34:30 +01:00
|
|
|
|
struct clipboard_send *ctx = xmalloc(sizeof(*ctx));
|
2019-11-04 14:00:51 +01:00
|
|
|
|
*ctx = (struct clipboard_send) {
|
2020-10-13 18:38:49 +02:00
|
|
|
|
.data = xstrdup(&selection[async_idx]),
|
|
|
|
|
|
.len = len - async_idx,
|
|
|
|
|
|
.idx = 0,
|
2019-11-04 14:00:51 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
if (fdm_add(seat->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:
|
2020-10-13 18:38:49 +02:00
|
|
|
|
LOG_ERRNO("failed write %zu bytes of %s selection data to FD=%d",
|
|
|
|
|
|
len, source_name, fd);
|
2019-11-04 14:00:51 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
close(fd);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-13 18:38:49 +02:00
|
|
|
|
static void
|
|
|
|
|
|
send(void *data, struct wl_data_source *wl_data_source, const char *mime_type,
|
|
|
|
|
|
int32_t fd)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
const struct wl_clipboard *clipboard = &seat->clipboard;
|
|
|
|
|
|
|
|
|
|
|
|
assert(clipboard->text != NULL);
|
|
|
|
|
|
send_clipboard_or_primary(seat, fd, clipboard->text, "clipboard");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-11 12:16:50 +02:00
|
|
|
|
static void
|
|
|
|
|
|
cancelled(void *data, struct wl_data_source *wl_data_source)
|
|
|
|
|
|
{
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
struct wl_clipboard *clipboard = &seat->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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
/* We don’t support dragging *from* */
|
2019-07-11 12:16:50 +02:00
|
|
|
|
static void
|
|
|
|
|
|
dnd_drop_performed(void *data, struct wl_data_source *wl_data_source)
|
|
|
|
|
|
{
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
//LOG_DBG("DnD drop performed");
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
dnd_finished(void *data, struct wl_data_source *wl_data_source)
|
|
|
|
|
|
{
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
//LOG_DBG("DnD finished");
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action)
|
|
|
|
|
|
{
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
//LOG_DBG("DnD action: %u", dnd_action);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
};
|
2020-07-08 18:41:09 +02:00
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
const struct wl_primary *primary = &seat->primary;
|
2019-07-11 17:34:52 +02:00
|
|
|
|
|
|
|
|
|
|
assert(primary->text != NULL);
|
2020-10-13 18:38:49 +02:00
|
|
|
|
send_clipboard_or_primary(seat, fd, primary->text, "primary");
|
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)
|
|
|
|
|
|
{
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
struct wl_primary *primary = &seat->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,
|
|
|
|
|
|
};
|
2020-07-08 18:41:09 +02:00
|
|
|
|
|
2019-07-19 11:12:14 +02:00
|
|
|
|
bool
|
2020-07-08 18:41:09 +02:00
|
|
|
|
text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t serial)
|
2019-07-11 12:16:50 +02:00
|
|
|
|
{
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct wl_clipboard *clipboard = &seat->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);
|
2020-07-08 18:41:09 +02:00
|
|
|
|
wl_data_device_set_selection(seat->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;
|
2020-09-17 20:05:22 +02:00
|
|
|
|
clipboard->text = NULL;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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");
|
2020-07-08 18:41:09 +02:00
|
|
|
|
wl_data_source_add_listener(clipboard->data_source, &data_source_listener, seat);
|
|
|
|
|
|
wl_data_device_set_selection(seat->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
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_to_clipboard(struct seat *seat, struct terminal *term, uint32_t serial)
|
2019-07-19 11:12:14 +02:00
|
|
|
|
{
|
2020-05-19 18:51:30 +02:00
|
|
|
|
if (term->selection.start.row < 0 || term->selection.end.row < 0)
|
2019-11-22 21:52:12 +01:00
|
|
|
|
return;
|
|
|
|
|
|
|
2019-07-19 11:12:14 +02:00
|
|
|
|
/* Get selection as a string */
|
2020-07-31 17:02:53 +02:00
|
|
|
|
char *text = selection_to_text(term);
|
2020-07-08 18:41:09 +02:00
|
|
|
|
if (!text_to_clipboard(seat, term, text, serial))
|
2019-07-19 11:12:14 +02:00
|
|
|
|
free(text);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
|
struct clipboard_receive {
|
2020-08-22 08:29:35 +02:00
|
|
|
|
int read_fd;
|
|
|
|
|
|
int timeout_fd;
|
|
|
|
|
|
struct itimerspec timeout;
|
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
|
/* Callback data */
|
|
|
|
|
|
void (*cb)(const char *data, size_t size, void *user);
|
|
|
|
|
|
void (*done)(void *user);
|
|
|
|
|
|
void *user;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2020-08-22 08:29:35 +02:00
|
|
|
|
static void
|
|
|
|
|
|
clipboard_receive_done(struct fdm *fdm, struct clipboard_receive *ctx)
|
|
|
|
|
|
{
|
|
|
|
|
|
fdm_del(fdm, ctx->timeout_fd);
|
|
|
|
|
|
fdm_del(fdm, ctx->read_fd);
|
|
|
|
|
|
ctx->done(ctx->user);
|
|
|
|
|
|
free(ctx);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
|
fdm_receive_timeout(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct clipboard_receive *ctx = data;
|
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
assert(events & EPOLLIN);
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t expire_count;
|
|
|
|
|
|
ssize_t ret = read(fd, &expire_count, sizeof(expire_count));
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
LOG_ERRNO("failed to read clipboard timeout timer");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_WARN("no data received from clipboard in %llu seconds, aborting",
|
|
|
|
|
|
(unsigned long long)ctx->timeout.it_value.tv_sec);
|
|
|
|
|
|
|
|
|
|
|
|
clipboard_receive_done(fdm, ctx);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2020-07-08 18:41:09 +02:00
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
|
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;
|
|
|
|
|
|
|
2020-08-22 08:29:35 +02:00
|
|
|
|
/* Reset timeout timer */
|
|
|
|
|
|
if (timerfd_settime(ctx->timeout_fd, 0, &ctx->timeout, NULL) < 0) {
|
|
|
|
|
|
LOG_ERRNO("failed to re-arm clipboard timeout timer");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
|
/* 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:
|
2020-08-22 08:29:35 +02:00
|
|
|
|
clipboard_receive_done(fdm, ctx);
|
2019-11-05 09:09:51 +01:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2020-07-08 18:41:09 +02:00
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2020-08-22 08:29:35 +02:00
|
|
|
|
int timeout_fd = -1;
|
|
|
|
|
|
struct clipboard_receive *ctx = NULL;
|
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
|
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");
|
2020-08-22 08:29:35 +02:00
|
|
|
|
goto err;
|
2019-11-05 09:09:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-22 08:29:35 +02:00
|
|
|
|
timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
|
|
|
|
|
|
if (timeout_fd < 0) {
|
|
|
|
|
|
LOG_ERRNO("failed to create clipboard timeout timer FD");
|
|
|
|
|
|
goto err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const struct itimerspec timeout = {.it_value = {.tv_sec = 2}};
|
|
|
|
|
|
if (timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0) {
|
2020-08-27 19:55:27 +02:00
|
|
|
|
LOG_ERRNO("failed to arm clipboard timeout timer");
|
2020-08-22 08:29:35 +02:00
|
|
|
|
goto err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ctx = xmalloc(sizeof(*ctx));
|
2019-11-05 09:09:51 +01:00
|
|
|
|
*ctx = (struct clipboard_receive) {
|
2020-08-22 08:29:35 +02:00
|
|
|
|
.read_fd = read_fd,
|
|
|
|
|
|
.timeout_fd = timeout_fd,
|
|
|
|
|
|
.timeout = timeout,
|
2019-11-05 09:09:51 +01:00
|
|
|
|
.cb = cb,
|
|
|
|
|
|
.done = done,
|
|
|
|
|
|
.user = user,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2020-08-22 08:29:35 +02:00
|
|
|
|
if (!fdm_add(term->fdm, read_fd, EPOLLIN, &fdm_receive, ctx) ||
|
|
|
|
|
|
!fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_receive_timeout, ctx))
|
|
|
|
|
|
{
|
|
|
|
|
|
goto err;
|
2019-11-05 09:09:51 +01:00
|
|
|
|
}
|
2020-08-22 08:29:35 +02:00
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
|
free(ctx);
|
|
|
|
|
|
fdm_del(term->fdm, timeout_fd);
|
|
|
|
|
|
fdm_del(term->fdm, read_fd);
|
|
|
|
|
|
done(user);
|
2019-11-05 09:09:51 +01:00
|
|
|
|
}
|
2020-07-08 18:41:09 +02:00
|
|
|
|
|
2019-07-11 12:33:31 +02:00
|
|
|
|
void
|
2020-07-09 11:20:46 +02:00
|
|
|
|
text_from_clipboard(struct seat *seat, struct terminal *term,
|
2019-07-19 14:20:00 +02:00
|
|
|
|
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
|
|
|
|
{
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct wl_clipboard *clipboard = &seat->clipboard;
|
2020-08-23 09:39:49 +02:00
|
|
|
|
if (clipboard->data_offer == NULL) {
|
|
|
|
|
|
done(user);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
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");
|
2020-08-23 09:39:49 +02:00
|
|
|
|
done(user);
|
|
|
|
|
|
return;
|
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;
|
2020-08-22 09:23:58 +02:00
|
|
|
|
assert(term->is_sending_paste_data);
|
2020-08-22 09:14:18 +02:00
|
|
|
|
term_paste_data_to_slave(term, data, size);
|
2019-07-19 14:20:00 +02:00
|
|
|
|
}
|
2020-07-08 18:41:09 +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)
|
2020-08-22 09:14:18 +02:00
|
|
|
|
term_paste_data_to_slave(term, "\033[201~", 6);
|
|
|
|
|
|
|
|
|
|
|
|
term->is_sending_paste_data = false;
|
|
|
|
|
|
|
|
|
|
|
|
/* Make sure we send any queued up non-paste data */
|
|
|
|
|
|
if (tll_length(term->ptmx_buffers) > 0)
|
|
|
|
|
|
fdm_event_add(term->fdm, term->ptmx, EPOLLOUT);
|
2019-11-05 08:49:32 +01:00
|
|
|
|
}
|
2020-07-08 18:41:09 +02:00
|
|
|
|
|
2019-07-19 14:20:00 +02:00
|
|
|
|
void
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_from_clipboard(struct seat *seat, struct terminal *term, uint32_t serial)
|
2019-07-19 14:20:00 +02:00
|
|
|
|
{
|
2020-08-22 16:30:52 +02:00
|
|
|
|
if (term->is_sending_paste_data) {
|
|
|
|
|
|
/* We're already pasting... */
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct wl_clipboard *clipboard = &seat->clipboard;
|
2019-07-19 14:20:00 +02:00
|
|
|
|
if (clipboard->data_offer == NULL)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
|
term->is_sending_paste_data = true;
|
|
|
|
|
|
|
2019-07-11 12:33:31 +02:00
|
|
|
|
if (term->bracketed_paste)
|
2020-08-22 16:20:49 +02:00
|
|
|
|
term_paste_data_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(
|
2020-07-09 11:20:46 +02:00
|
|
|
|
seat, term, &from_clipboard_cb, &from_clipboard_done, term);
|
2019-07-11 12:33:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-09 21:27:51 +02:00
|
|
|
|
bool
|
2020-07-08 18:41:09 +02:00
|
|
|
|
text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t serial)
|
2019-08-09 21:27:51 +02:00
|
|
|
|
{
|
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;
|
|
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct wl_primary *primary = &seat->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 */
|
2020-07-08 18:41:09 +02:00
|
|
|
|
if (seat->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(
|
2020-07-08 18:41:09 +02:00
|
|
|
|
seat->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;
|
2020-09-17 20:05:22 +02:00
|
|
|
|
primary->text = NULL;
|
2019-08-09 21:27:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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");
|
2020-07-08 18:41:09 +02:00
|
|
|
|
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);
|
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
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_to_primary(struct seat *seat, struct terminal *term, uint32_t serial)
|
2019-08-09 21:27:51 +02:00
|
|
|
|
{
|
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 */
|
2020-07-31 17:02:53 +02:00
|
|
|
|
char *text = selection_to_text(term);
|
2020-07-08 18:41:09 +02:00
|
|
|
|
if (!text_to_primary(seat, term, text, serial))
|
2019-08-09 21:27:51 +02:00
|
|
|
|
free(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
text_from_primary(
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct seat *seat, struct terminal *term,
|
2019-11-05 08:49:32 +01:00
|
|
|
|
void (*cb)(const char *data, size_t size, void *user),
|
|
|
|
|
|
void (*done)(void *user), void *user)
|
2019-07-11 16:42:59 +02:00
|
|
|
|
{
|
2020-08-23 09:39:49 +02:00
|
|
|
|
if (term->wl->primary_selection_device_manager == NULL) {
|
|
|
|
|
|
done(user);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2019-09-25 19:26:55 +02:00
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct wl_primary *primary = &seat->primary;
|
2020-08-23 09:39:49 +02:00
|
|
|
|
if (primary->data_offer == NULL){
|
|
|
|
|
|
done(user);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
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");
|
2020-08-23 09:39:49 +02:00
|
|
|
|
done(user);
|
|
|
|
|
|
return;
|
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
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_from_primary(struct seat *seat, struct terminal *term)
|
2019-08-09 21:27:51 +02:00
|
|
|
|
{
|
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;
|
|
|
|
|
|
|
2020-08-22 16:30:52 +02:00
|
|
|
|
if (term->is_sending_paste_data) {
|
|
|
|
|
|
/* We're already pasting... */
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-10 21:13:08 +03:00
|
|
|
|
struct wl_primary *primary = &seat->primary;
|
|
|
|
|
|
if (primary->data_offer == NULL)
|
2019-08-09 21:27:51 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
|
term->is_sending_paste_data = true;
|
2019-07-11 17:02:21 +02:00
|
|
|
|
if (term->bracketed_paste)
|
2020-08-22 09:14:18 +02:00
|
|
|
|
term_paste_data_to_slave(term, "\033[200~", 6);
|
2019-07-11 17:02:21 +02:00
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
text_from_primary(seat, term, &from_clipboard_cb, &from_clipboard_done, term);
|
2019-07-11 16:42:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-26 21:19:07 +01:00
|
|
|
|
#if 1
|
2019-07-11 12:16:50 +02:00
|
|
|
|
static void
|
|
|
|
|
|
offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type)
|
|
|
|
|
|
{
|
2020-10-26 21:19:07 +01:00
|
|
|
|
LOG_DBG("OFFER: %s", mime_type);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
source_actions(void *data, struct wl_data_offer *wl_data_offer,
|
|
|
|
|
|
uint32_t source_actions)
|
|
|
|
|
|
{
|
2020-10-26 21:19:07 +01:00
|
|
|
|
LOG_DBG("ACTIONS: 0x%08x", source_actions);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action)
|
|
|
|
|
|
{
|
2020-10-26 21:19:07 +01:00
|
|
|
|
LOG_DBG("OFFER ACTION: 0x%08x", dnd_action);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2020-10-26 21:19:07 +01:00
|
|
|
|
wl_data_offer_add_listener(id, &data_offer_listener, data);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
struct wayland *wayl = seat->wayl;
|
|
|
|
|
|
|
|
|
|
|
|
assert(seat->dnd_term == NULL);
|
|
|
|
|
|
|
|
|
|
|
|
/* Remember current DnD offer */
|
|
|
|
|
|
|
|
|
|
|
|
/* Remmeber _which_ terminal the current DnD offer is targetting */
|
|
|
|
|
|
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(id, serial, "text/plain;charset=utf-8");
|
|
|
|
|
|
wl_data_offer_set_actions(
|
|
|
|
|
|
id,
|
|
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
|
|
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
|
|
|
|
|
|
|
|
|
|
|
|
seat->dnd_term = it->item;
|
|
|
|
|
|
seat->dnd_term->window->dnd_offer = id;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Either terminal is alraedy busy sending paste data, or mouse
|
|
|
|
|
|
* pointer isn’t over the grid */
|
|
|
|
|
|
seat->dnd_term = NULL;
|
|
|
|
|
|
wl_data_offer_set_actions(id, 0, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
leave(void *data, struct wl_data_device *wl_data_device)
|
|
|
|
|
|
{
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
if (seat->dnd_term != NULL)
|
|
|
|
|
|
seat->dnd_term->window->dnd_offer = NULL;
|
|
|
|
|
|
seat->dnd_term = NULL;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
motion(void *data, struct wl_data_device *wl_data_device, uint32_t time,
|
|
|
|
|
|
wl_fixed_t x, wl_fixed_t y)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
static void
|
|
|
|
|
|
dnd_done(void *user)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct terminal *term = user;
|
|
|
|
|
|
struct wl_data_offer *offer = term->window->dnd_offer;
|
|
|
|
|
|
|
|
|
|
|
|
assert(offer != NULL);
|
|
|
|
|
|
term->window->dnd_offer = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
wl_data_offer_finish(offer);
|
|
|
|
|
|
wl_data_offer_destroy(offer);
|
|
|
|
|
|
|
|
|
|
|
|
from_clipboard_done(user);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-11 12:16:50 +02:00
|
|
|
|
static void
|
|
|
|
|
|
drop(void *data, struct wl_data_device *wl_data_device)
|
|
|
|
|
|
{
|
selection: implement support for drag-and-drop
We accept COPY and MOVE actions, for text/plain;charset=utf-8
mime-types.
To implement DnD, we need to track the current DnD data offer *and*
the terminal instance it is (currently) targeting.
To do this, a seat has a new member, ‘dnd_term’. On a DnD enter event,
we lookup the corresponding terminal instance and point ‘dnd_term’ to
it.
On a DnD leave event, ‘dnd_term’ is reset.
The DnD data offer is tracked in the terminal’s wayland window
instance. It is reset, along with the seat’s ‘dnd_term’ on a DnD leave
event.
On a drop event, we immediately clear the seat’s ‘dnd_term’, to ensure
we don’t reset it, or destroy the offer before the drop has been
completed.
The drop’s ‘done()’ callback takes care of destroying and resetting
the DnD offer in the terminal’s wayland window instance.
Closes #175
2020-10-26 21:02:53 +01:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
|
|
|
|
|
|
struct terminal *term = seat->dnd_term;
|
|
|
|
|
|
assert(term != NULL);
|
|
|
|
|
|
|
|
|
|
|
|
struct wl_data_offer *offer = term->window->dnd_offer;
|
|
|
|
|
|
assert(offer != NULL);
|
|
|
|
|
|
seat->dnd_term = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
/* 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");
|
|
|
|
|
|
goto err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int read_fd = fds[0];
|
|
|
|
|
|
int write_fd = fds[1];
|
|
|
|
|
|
|
|
|
|
|
|
/* Give write-end of pipe to other client */
|
|
|
|
|
|
wl_data_offer_receive(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);
|
|
|
|
|
|
|
|
|
|
|
|
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, &from_clipboard_cb, &dnd_done, term);
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
|
wl_data_offer_destroy(offer);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
struct wl_clipboard *clipboard = &seat->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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
struct wl_primary *primary = &seat->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,
|
|
|
|
|
|
};
|