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-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
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
#include <pixman.h>
|
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
|
#define LOG_MODULE "selection"
|
2019-07-11 10:01:46 +02:00
|
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-07-11 09:51:51 +02:00
|
|
|
|
#include "log.h"
|
2019-11-04 14:00:51 +01:00
|
|
|
|
|
|
|
|
|
|
#include "async.h"
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
#include "char32.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"
|
2021-07-22 17:53:29 +02:00
|
|
|
|
#include "search.h"
|
2020-10-28 19:16:04 +01:00
|
|
|
|
#include "uri.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
|
|
|
|
|
2020-10-28 19:16:04 +01:00
|
|
|
|
static const char *const mime_type_map[] = {
|
|
|
|
|
|
[DATA_OFFER_MIME_UNSET] = NULL,
|
|
|
|
|
|
[DATA_OFFER_MIME_TEXT_PLAIN] = "text/plain",
|
|
|
|
|
|
[DATA_OFFER_MIME_TEXT_UTF8] = "text/plain;charset=utf-8",
|
|
|
|
|
|
[DATA_OFFER_MIME_URI_LIST] = "text/uri-list",
|
2021-06-09 09:51:07 +02:00
|
|
|
|
|
|
|
|
|
|
[DATA_OFFER_MIME_TEXT_TEXT] = "TEXT",
|
|
|
|
|
|
[DATA_OFFER_MIME_TEXT_STRING] = "STRING",
|
|
|
|
|
|
[DATA_OFFER_MIME_TEXT_UTF8_STRING] = "UTF8_STRING",
|
2020-10-28 19:16:04 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2022-04-25 19:59:23 +02:00
|
|
|
|
static inline struct coord
|
|
|
|
|
|
bounded(const struct grid *grid, struct coord coord)
|
|
|
|
|
|
{
|
|
|
|
|
|
coord.row &= grid->num_rows - 1;
|
|
|
|
|
|
return coord;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct coord
|
|
|
|
|
|
selection_get_start(const struct terminal *term)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (term->selection.coords.start.row < 0)
|
|
|
|
|
|
return term->selection.coords.start;
|
|
|
|
|
|
return bounded(term->grid, term->selection.coords.start);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct coord
|
|
|
|
|
|
selection_get_end(const struct terminal *term)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (term->selection.coords.end.row < 0)
|
|
|
|
|
|
return term->selection.coords.end;
|
|
|
|
|
|
return bounded(term->grid, term->selection.coords.end);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2022-07-28 18:27:13 +02:00
|
|
|
|
xassert(term->selection.coords.end.row >= 0);
|
|
|
|
|
|
|
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)",
|
2022-04-17 19:16:47 +02:00
|
|
|
|
term->selection.coords.start.row, term->selection.coords.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
|
|
|
|
|
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 += term->grid->offset;
|
|
|
|
|
|
row_end += term->grid->offset;
|
2022-07-28 18:27:13 +02:00
|
|
|
|
xassert(row_end >= row_start);
|
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
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
const struct coord *start = &term->selection.coords.start;
|
|
|
|
|
|
const struct coord *end = &term->selection.coords.end;
|
2019-08-05 20:16:17 +02:00
|
|
|
|
|
2022-07-28 18:27:13 +02:00
|
|
|
|
const struct grid *grid = term->grid;
|
|
|
|
|
|
const int sb_start = grid->offset + term->rows;
|
|
|
|
|
|
|
|
|
|
|
|
/* Use scrollback relative coords when checking for overlap */
|
|
|
|
|
|
const int rel_row_start =
|
|
|
|
|
|
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, row_start);
|
|
|
|
|
|
const int rel_row_end =
|
|
|
|
|
|
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, row_start);
|
|
|
|
|
|
int rel_sel_start =
|
|
|
|
|
|
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, start->row);
|
|
|
|
|
|
int rel_sel_end =
|
|
|
|
|
|
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, end->row);
|
|
|
|
|
|
|
|
|
|
|
|
if (rel_sel_start > rel_sel_end) {
|
|
|
|
|
|
int tmp = rel_sel_start;
|
|
|
|
|
|
rel_sel_start = rel_sel_end;
|
|
|
|
|
|
rel_sel_end = tmp;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ((rel_row_start <= rel_sel_start && rel_row_end >= rel_sel_start) ||
|
|
|
|
|
|
(rel_row_start <= rel_sel_end && rel_row_end >= rel_sel_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
|
|
|
|
{
|
|
|
|
|
|
/* The range crosses one of the selection boundaries */
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-28 18:27:13 +02:00
|
|
|
|
if (rel_row_start >= rel_sel_start && rel_row_end <= rel_sel_end)
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-28 18:27:13 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_scroll_up(struct terminal *term, int rows)
|
|
|
|
|
|
{
|
|
|
|
|
|
xassert(term->selection.coords.end.row >= 0);
|
|
|
|
|
|
|
|
|
|
|
|
const int rel_row_start =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.start.row);
|
|
|
|
|
|
const int rel_row_end =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.end.row);
|
|
|
|
|
|
const int actual_start = min(rel_row_start, rel_row_end);
|
|
|
|
|
|
|
|
|
|
|
|
if (actual_start - rows < 0) {
|
|
|
|
|
|
/* Part of the selection will be scrolled out, cancel it */
|
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
selection_scroll_down(struct terminal *term, int rows)
|
|
|
|
|
|
{
|
|
|
|
|
|
xassert(term->selection.coords.end.row >= 0);
|
|
|
|
|
|
|
|
|
|
|
|
const int rel_row_start =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.start.row);
|
|
|
|
|
|
const int rel_row_end =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.end.row);
|
|
|
|
|
|
const int actual_end = max(rel_row_start, rel_row_end);
|
|
|
|
|
|
|
|
|
|
|
|
if (actual_end + rows <= term->grid->num_rows) {
|
|
|
|
|
|
/* Part of the selection will be scrolled out, cancel it */
|
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-05-19 18:49:42 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_view_up(struct terminal *term, int new_view)
|
|
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (likely(term->selection.coords.start.row < 0))
|
2020-05-19 18:49:42 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (likely(new_view < term->grid->view))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start.row += term->grid->num_rows;
|
|
|
|
|
|
if (term->selection.coords.end.row >= 0)
|
|
|
|
|
|
term->selection.coords.end.row += term->grid->num_rows;
|
2020-05-19 18:49:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
selection_view_down(struct terminal *term, int new_view)
|
|
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (likely(term->selection.coords.start.row < 0))
|
2020-05-19 18:49:42 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (likely(new_view > term->grid->view))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start.row &= term->grid->num_rows - 1;
|
|
|
|
|
|
if (term->selection.coords.end.row >= 0)
|
|
|
|
|
|
term->selection.coords.end.row &= term->grid->num_rows - 1;
|
2020-05-19 18:49:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
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,
|
2022-02-07 14:13:38 +01:00
|
|
|
|
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell,
|
|
|
|
|
|
int row_no, 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
|
|
|
|
|
2022-02-07 14:13:38 +01:00
|
|
|
|
const int grid_rows = term->grid->num_rows;
|
|
|
|
|
|
|
2022-07-28 18:31:11 +02:00
|
|
|
|
/* Start/end rows, relative to the scrollback start */
|
2022-02-07 14:13:38 +01:00
|
|
|
|
/* Start/end rows, relative to the scrollback start */
|
|
|
|
|
|
const int rel_start_row =
|
2022-07-28 18:31:11 +02:00
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, start->row);
|
2022-02-07 14:13:38 +01:00
|
|
|
|
const int rel_end_row =
|
2022-07-28 18:31:11 +02:00
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, end->row);
|
2022-02-07 14:13:38 +01:00
|
|
|
|
|
2020-01-04 12:03:04 +01:00
|
|
|
|
int start_row, end_row;
|
|
|
|
|
|
int start_col, end_col;
|
|
|
|
|
|
|
2022-02-07 14:13:38 +01:00
|
|
|
|
if (rel_start_row < rel_end_row) {
|
2020-01-04 12:03:04 +01:00
|
|
|
|
start_row = start->row;
|
|
|
|
|
|
start_col = start->col;
|
2022-02-07 14:13:38 +01:00
|
|
|
|
end_row = end->row;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
end_col = end->col;
|
2022-02-07 14:13:38 +01:00
|
|
|
|
} else if (rel_start_row > rel_end_row) {
|
2020-01-04 12:03:04 +01:00
|
|
|
|
start_row = end->row;
|
|
|
|
|
|
start_col = end->col;
|
2022-02-07 14:13:38 +01:00
|
|
|
|
end_row = start->row;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-07 14:13:38 +01:00
|
|
|
|
start_row &= (grid_rows - 1);
|
|
|
|
|
|
end_row &= (grid_rows - 1);
|
|
|
|
|
|
|
|
|
|
|
|
for (int r = start_row; r != end_row; r = (r + 1) & (grid_rows - 1)) {
|
|
|
|
|
|
struct row *row = term->grid->rows[r];
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(row != NULL);
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
2022-02-07 14:13:38 +01:00
|
|
|
|
for (int c = start_col; c <= term->cols - 1; c++) {
|
|
|
|
|
|
if (!cb(term, row, &row->cells[c], r, c, data))
|
2020-07-15 11:19:18 +02:00
|
|
|
|
return;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
start_col = 0;
|
|
|
|
|
|
}
|
2022-02-07 14:13:38 +01:00
|
|
|
|
|
|
|
|
|
|
/* Last, partial row */
|
|
|
|
|
|
struct row *row = term->grid->rows[end_row];
|
|
|
|
|
|
xassert(row != NULL);
|
|
|
|
|
|
|
|
|
|
|
|
for (int c = start_col; c <= end_col; c++) {
|
|
|
|
|
|
if (!cb(term, row, &row->cells[c], end_row, c, data))
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2020-01-04 12:03:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
foreach_selected_block(
|
2020-01-06 11:56:18 +01:00
|
|
|
|
struct terminal *term, struct coord _start, struct coord _end,
|
2022-02-07 14:13:38 +01:00
|
|
|
|
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell,
|
|
|
|
|
|
int row_no, 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
|
|
|
|
|
2022-02-07 14:13:38 +01:00
|
|
|
|
const int grid_rows = term->grid->num_rows;
|
|
|
|
|
|
|
|
|
|
|
|
/* Start/end rows, relative to the scrollback start */
|
|
|
|
|
|
const int rel_start_row =
|
2022-07-28 18:31:11 +02:00
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, start->row);
|
2022-02-07 14:13:38 +01:00
|
|
|
|
const int rel_end_row =
|
2022-07-28 18:31:11 +02:00
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, end->row);
|
2022-02-07 14:13:38 +01:00
|
|
|
|
|
2020-01-04 12:03:04 +01:00
|
|
|
|
struct coord top_left = {
|
2022-02-07 14:13:38 +01:00
|
|
|
|
.row = (rel_start_row < rel_end_row
|
|
|
|
|
|
? start->row : end->row) & (grid_rows - 1),
|
2020-01-04 12:03:04 +01:00
|
|
|
|
.col = min(start->col, end->col),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct coord bottom_right = {
|
2022-02-07 14:13:38 +01:00
|
|
|
|
.row = (rel_start_row > rel_end_row
|
|
|
|
|
|
? start->row : end->row) & (grid_rows - 1),
|
2020-01-04 12:03:04 +01:00
|
|
|
|
.col = max(start->col, end->col),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2022-02-07 14:13:38 +01:00
|
|
|
|
int r = top_left.row;
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
struct row *row = term->grid->rows[r];
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(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++) {
|
2022-02-07 14:13:38 +01:00
|
|
|
|
if (!cb(term, row, &row->cells[c], r, c, data))
|
2020-07-15 11:19:18 +02:00
|
|
|
|
return;
|
2020-07-14 12:59:36 +02:00
|
|
|
|
}
|
2022-02-07 14:13:38 +01:00
|
|
|
|
|
|
|
|
|
|
if (r == bottom_right.row)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
r++;
|
|
|
|
|
|
r &= grid_rows - 1;
|
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,
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int row_no, int col, void *data),
|
2020-01-04 12:03:04 +01:00
|
|
|
|
void *data)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (term->selection.kind) {
|
2021-01-06 10:53:27 +01:00
|
|
|
|
case SELECTION_CHAR_WISE:
|
|
|
|
|
|
case SELECTION_WORD_WISE:
|
|
|
|
|
|
case SELECTION_LINE_WISE:
|
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:
|
2021-02-10 09:01:51 +00:00
|
|
|
|
break;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-10 09:01:51 +00:00
|
|
|
|
BUG("Invalid selection kind");
|
2020-01-04 12:03:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-15 11:31:57 +02:00
|
|
|
|
static bool
|
|
|
|
|
|
extract_one_const_wrapper(struct terminal *term,
|
|
|
|
|
|
struct row *row, struct cell *cell,
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
int row_no, int col, void *data)
|
2020-07-15 11:31:57 +02:00
|
|
|
|
{
|
|
|
|
|
|
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
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (term->selection.coords.end.row == -1)
|
2020-07-31 17:02:53 +02:00
|
|
|
|
return NULL;
|
|
|
|
|
|
|
2021-03-30 14:40:21 +02:00
|
|
|
|
struct extraction_context *ctx = extract_begin(term->selection.kind, true);
|
2020-07-15 11:19:18 +02:00
|
|
|
|
if (ctx == NULL)
|
|
|
|
|
|
return NULL;
|
2020-01-04 12:59:29 +01:00
|
|
|
|
|
2020-01-06 11:56:18 +01:00
|
|
|
|
foreach_selected(
|
2022-04-09 15:09:02 +02:00
|
|
|
|
(struct terminal *)term, term->selection.coords.start, term->selection.coords.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;
|
2021-03-30 14:40:21 +02:00
|
|
|
|
return extract_finish(ctx, &text, NULL) ? text : NULL;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-28 21:03:53 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_find_word_boundary_left(struct terminal *term, struct coord *pos,
|
|
|
|
|
|
bool spaces_only)
|
2021-01-02 23:00:54 +01:00
|
|
|
|
{
|
2022-04-23 12:23:27 +02:00
|
|
|
|
xassert(pos->row >= 0);
|
|
|
|
|
|
xassert(pos->row < term->rows);
|
|
|
|
|
|
xassert(pos->col >= 0);
|
|
|
|
|
|
xassert(pos->col < term->cols);
|
|
|
|
|
|
|
2021-01-02 23:00:54 +01:00
|
|
|
|
const struct row *r = grid_row_in_view(term->grid, pos->row);
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
char32_t c = r->cells[pos->col].wc;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
while (c >= CELL_SPACER) {
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(pos->col > 0);
|
2021-01-03 14:07:40 +01:00
|
|
|
|
if (pos->col == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
pos->col--;
|
|
|
|
|
|
c = r->cells[pos->col].wc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.
However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.
Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.
Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.
So what can we do?
We can’t sort the array, because
a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).
b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.
This patch uses a binary search tree to store the chains instead of a
simple array.
The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.
The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.
Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.
Lookup is of course slower than before, O(log n) instead of
O(1).
Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.
But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.
With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.
Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.
As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.
Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 13:17:07 +02:00
|
|
|
|
if (c >= CELL_COMB_CHARS_LO && c <= CELL_COMB_CHARS_HI)
|
|
|
|
|
|
c = composed_lookup(term->composed, c - CELL_COMB_CHARS_LO)->chars[0];
|
2021-01-03 13:09:24 +01:00
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
bool initial_is_space = c == 0 || isc32space(c);
|
2021-01-03 14:07:40 +01:00
|
|
|
|
bool initial_is_delim =
|
2021-01-03 14:33:35 +01:00
|
|
|
|
!initial_is_space && !isword(c, spaces_only, term->conf->word_delimiters);
|
|
|
|
|
|
bool initial_is_word =
|
|
|
|
|
|
c != 0 && isword(c, spaces_only, term->conf->word_delimiters);
|
2021-01-03 13:54:41 +01:00
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
int next_col = pos->col - 1;
|
|
|
|
|
|
int next_row = pos->row;
|
|
|
|
|
|
|
2021-05-31 17:12:38 +02:00
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, next_row);
|
|
|
|
|
|
|
2021-01-03 13:54:41 +01:00
|
|
|
|
/* Linewrap */
|
|
|
|
|
|
if (next_col < 0) {
|
|
|
|
|
|
next_col = term->cols - 1;
|
|
|
|
|
|
if (--next_row < 0)
|
|
|
|
|
|
break;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
|
2021-05-31 17:12:38 +02:00
|
|
|
|
row = grid_row_in_view(term->grid, next_row);
|
|
|
|
|
|
|
|
|
|
|
|
if (row->linebreak) {
|
|
|
|
|
|
/* Hard linebreak, treat as space. I.e. break selection */
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-01-02 23:00:54 +01:00
|
|
|
|
|
2021-01-03 13:54:41 +01:00
|
|
|
|
c = row->cells[next_col].wc;
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
while (c >= CELL_SPACER) {
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(next_col > 0);
|
2021-01-03 14:07:40 +01:00
|
|
|
|
if (--next_col < 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
c = row->cells[next_col].wc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.
However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.
Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.
Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.
So what can we do?
We can’t sort the array, because
a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).
b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.
This patch uses a binary search tree to store the chains instead of a
simple array.
The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.
The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.
Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.
Lookup is of course slower than before, O(log n) instead of
O(1).
Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.
But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.
With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.
Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.
As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.
Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 13:17:07 +02:00
|
|
|
|
if (c >= CELL_COMB_CHARS_LO && c <= CELL_COMB_CHARS_HI)
|
|
|
|
|
|
c = composed_lookup(term->composed, c - CELL_COMB_CHARS_LO)->chars[0];
|
2021-01-03 13:54:41 +01:00
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
bool is_space = c == 0 || isc32space(c);
|
2021-01-03 14:33:35 +01:00
|
|
|
|
bool is_delim =
|
|
|
|
|
|
!is_space && !isword(c, spaces_only, term->conf->word_delimiters);
|
|
|
|
|
|
bool is_word =
|
|
|
|
|
|
c != 0 && isword(c, spaces_only, term->conf->word_delimiters);
|
|
|
|
|
|
|
2021-01-03 13:54:41 +01:00
|
|
|
|
if (initial_is_space && !is_space)
|
|
|
|
|
|
break;
|
2021-01-03 14:33:35 +01:00
|
|
|
|
if (initial_is_delim && !is_delim)
|
|
|
|
|
|
break;
|
|
|
|
|
|
if (initial_is_word && !is_word)
|
|
|
|
|
|
break;
|
2021-01-03 13:54:41 +01:00
|
|
|
|
|
|
|
|
|
|
pos->col = next_col;
|
|
|
|
|
|
pos->row = next_row;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-28 21:03:53 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_find_word_boundary_right(struct terminal *term, struct coord *pos,
|
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).
Take this, for example:
The Quick Brown Fox
The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.
This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want
The Quick Brown Fox
to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.
Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.
This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
2022-04-27 18:44:57 +02:00
|
|
|
|
bool spaces_only,
|
|
|
|
|
|
bool stop_on_space_to_word_boundary)
|
2021-01-02 23:00:54 +01:00
|
|
|
|
{
|
2022-04-23 12:23:27 +02:00
|
|
|
|
xassert(pos->row >= 0);
|
|
|
|
|
|
xassert(pos->row < term->rows);
|
|
|
|
|
|
xassert(pos->col >= 0);
|
|
|
|
|
|
xassert(pos->col < term->cols);
|
|
|
|
|
|
|
2021-01-02 23:00:54 +01:00
|
|
|
|
const struct row *r = grid_row_in_view(term->grid, pos->row);
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
char32_t c = r->cells[pos->col].wc;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
while (c >= CELL_SPACER) {
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(pos->col > 0);
|
2021-01-03 14:07:40 +01:00
|
|
|
|
if (pos->col == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
pos->col--;
|
|
|
|
|
|
c = r->cells[pos->col].wc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.
However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.
Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.
Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.
So what can we do?
We can’t sort the array, because
a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).
b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.
This patch uses a binary search tree to store the chains instead of a
simple array.
The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.
The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.
Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.
Lookup is of course slower than before, O(log n) instead of
O(1).
Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.
But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.
With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.
Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.
As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.
Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 13:17:07 +02:00
|
|
|
|
if (c >= CELL_COMB_CHARS_LO && c <= CELL_COMB_CHARS_HI)
|
|
|
|
|
|
c = composed_lookup(term->composed, c - CELL_COMB_CHARS_LO)->chars[0];
|
2021-01-03 13:09:24 +01:00
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
bool initial_is_space = c == 0 || isc32space(c);
|
2021-01-03 14:07:40 +01:00
|
|
|
|
bool initial_is_delim =
|
2021-01-03 14:33:35 +01:00
|
|
|
|
!initial_is_space && !isword(c, spaces_only, term->conf->word_delimiters);
|
|
|
|
|
|
bool initial_is_word =
|
|
|
|
|
|
c != 0 && isword(c, spaces_only, term->conf->word_delimiters);
|
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).
Take this, for example:
The Quick Brown Fox
The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.
This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want
The Quick Brown Fox
to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.
Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.
This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
2022-04-27 18:44:57 +02:00
|
|
|
|
bool have_seen_word = initial_is_word;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
|
2021-01-03 13:54:41 +01:00
|
|
|
|
while (true) {
|
|
|
|
|
|
int next_col = pos->col + 1;
|
|
|
|
|
|
int next_row = pos->row;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
|
2021-05-31 17:12:38 +02:00
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, next_row);
|
|
|
|
|
|
|
2021-01-03 13:54:41 +01:00
|
|
|
|
/* Linewrap */
|
|
|
|
|
|
if (next_col >= term->cols) {
|
2021-05-31 17:12:38 +02:00
|
|
|
|
if (row->linebreak) {
|
|
|
|
|
|
/* Hard linebreak, treat as space. I.e. break selection */
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-03 13:54:41 +01:00
|
|
|
|
next_col = 0;
|
|
|
|
|
|
if (++next_row >= term->rows)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2021-05-31 17:12:38 +02:00
|
|
|
|
row = grid_row_in_view(term->grid, next_row);
|
|
|
|
|
|
}
|
2021-01-03 13:09:24 +01:00
|
|
|
|
|
2021-01-03 13:54:41 +01:00
|
|
|
|
c = row->cells[next_col].wc;
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
while (c >= CELL_SPACER) {
|
2021-01-03 14:07:40 +01:00
|
|
|
|
if (++next_col >= term->cols) {
|
|
|
|
|
|
next_col = 0;
|
|
|
|
|
|
if (++next_row >= term->rows)
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
c = row->cells[next_col].wc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.
However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.
Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.
Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.
So what can we do?
We can’t sort the array, because
a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).
b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.
This patch uses a binary search tree to store the chains instead of a
simple array.
The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.
The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.
Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.
Lookup is of course slower than before, O(log n) instead of
O(1).
Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.
But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.
With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.
Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.
As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.
Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 13:17:07 +02:00
|
|
|
|
if (c >= CELL_COMB_CHARS_LO && c <= CELL_COMB_CHARS_HI)
|
|
|
|
|
|
c = composed_lookup(term->composed, c - CELL_COMB_CHARS_LO)->chars[0];
|
2021-01-03 13:54:41 +01:00
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
bool is_space = c == 0 || isc32space(c);
|
2021-01-03 14:33:35 +01:00
|
|
|
|
bool is_delim =
|
|
|
|
|
|
!is_space && !isword(c, spaces_only, term->conf->word_delimiters);
|
|
|
|
|
|
bool is_word =
|
|
|
|
|
|
c != 0 && isword(c, spaces_only, term->conf->word_delimiters);
|
|
|
|
|
|
|
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).
Take this, for example:
The Quick Brown Fox
The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.
This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want
The Quick Brown Fox
to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.
Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.
This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
2022-04-27 18:44:57 +02:00
|
|
|
|
if (stop_on_space_to_word_boundary) {
|
|
|
|
|
|
if (initial_is_space && !is_space)
|
|
|
|
|
|
break;
|
|
|
|
|
|
if (initial_is_delim && !is_delim)
|
|
|
|
|
|
break;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (initial_is_space && ((have_seen_word && is_space) || is_delim))
|
|
|
|
|
|
break;
|
|
|
|
|
|
if (initial_is_delim && ((have_seen_word && is_delim) || is_space))
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-01-03 14:33:35 +01:00
|
|
|
|
if (initial_is_word && !is_word)
|
|
|
|
|
|
break;
|
2021-01-03 13:54:41 +01:00
|
|
|
|
|
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).
Take this, for example:
The Quick Brown Fox
The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.
This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want
The Quick Brown Fox
to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.
Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.
This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
2022-04-27 18:44:57 +02:00
|
|
|
|
have_seen_word = is_word;
|
|
|
|
|
|
|
2021-01-03 13:54:41 +01:00
|
|
|
|
pos->col = next_col;
|
|
|
|
|
|
pos->row = next_row;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-30 13:39:23 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_find_line_boundary_left(struct terminal *term, struct coord *pos,
|
|
|
|
|
|
bool spaces_only)
|
|
|
|
|
|
{
|
|
|
|
|
|
int next_row = pos->row;
|
|
|
|
|
|
pos->col = 0;
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
if (--next_row < 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, next_row);
|
|
|
|
|
|
assert(row != NULL);
|
|
|
|
|
|
|
|
|
|
|
|
if (row->linebreak)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
pos->col = 0;
|
|
|
|
|
|
pos->row = next_row;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
selection_find_line_boundary_right(struct terminal *term, struct coord *pos,
|
|
|
|
|
|
bool spaces_only)
|
|
|
|
|
|
{
|
|
|
|
|
|
int next_row = pos->row;
|
|
|
|
|
|
pos->col = term->cols - 1;
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, next_row);
|
|
|
|
|
|
assert(row != NULL);
|
|
|
|
|
|
|
|
|
|
|
|
if (row->linebreak)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (++next_row >= term->rows)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
pos->col = term->cols - 1;
|
|
|
|
|
|
pos->row = next_row;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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,
|
2021-01-02 23:00:54 +01:00
|
|
|
|
enum selection_kind kind,
|
|
|
|
|
|
bool spaces_only)
|
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",
|
2021-01-06 10:53:27 +01:00
|
|
|
|
kind == SELECTION_CHAR_WISE ? "character-wise" :
|
|
|
|
|
|
kind == SELECTION_WORD_WISE ? "word-wise" :
|
|
|
|
|
|
kind == SELECTION_LINE_WISE ? "line-wise" :
|
2020-01-03 23:29:45 +01:00
|
|
|
|
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;
|
2020-08-11 09:55:33 +02:00
|
|
|
|
term->selection.ongoing = true;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
term->selection.spaces_only = spaces_only;
|
|
|
|
|
|
|
2021-01-06 10:53:27 +01:00
|
|
|
|
switch (kind) {
|
|
|
|
|
|
case SELECTION_CHAR_WISE:
|
|
|
|
|
|
case SELECTION_BLOCK:
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start = (struct coord){col, term->grid->view + row};
|
|
|
|
|
|
term->selection.coords.end = (struct coord){-1, -1};
|
2021-01-03 13:11:46 +01:00
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.pivot.start = term->selection.coords.start;
|
|
|
|
|
|
term->selection.pivot.end = term->selection.coords.end;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
2021-01-06 10:53:27 +01:00
|
|
|
|
case SELECTION_WORD_WISE: {
|
2021-01-02 23:00:54 +01:00
|
|
|
|
struct coord start = {col, row}, end = {col, row};
|
2021-03-28 21:03:53 +02:00
|
|
|
|
selection_find_word_boundary_left(term, &start, spaces_only);
|
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).
Take this, for example:
The Quick Brown Fox
The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.
This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want
The Quick Brown Fox
to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.
Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.
This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
2022-04-27 18:44:57 +02:00
|
|
|
|
selection_find_word_boundary_right(term, &end, spaces_only, true);
|
2021-01-02 23:00:54 +01:00
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start = (struct coord){
|
2021-01-02 23:00:54 +01:00
|
|
|
|
start.col, term->grid->view + start.row};
|
|
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.pivot.start = term->selection.coords.start;
|
2021-01-03 13:11:46 +01:00
|
|
|
|
term->selection.pivot.end = (struct coord){end.col, term->grid->view + end.row};
|
|
|
|
|
|
|
2021-01-02 23:00:54 +01:00
|
|
|
|
selection_update(term, end.col, end.row);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-30 13:39:23 +02:00
|
|
|
|
case SELECTION_LINE_WISE: {
|
|
|
|
|
|
struct coord start = {0, row}, end = {term->cols - 1, row};
|
|
|
|
|
|
selection_find_line_boundary_left(term, &start, spaces_only);
|
|
|
|
|
|
selection_find_line_boundary_right(term, &end, spaces_only);
|
|
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start = (struct coord){
|
2021-09-30 13:39:23 +02:00
|
|
|
|
start.col, term->grid->view + start.row};
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.pivot.start = term->selection.coords.start;
|
2021-09-30 13:39:23 +02:00
|
|
|
|
term->selection.pivot.end = (struct coord){end.col, term->grid->view + end.row};
|
2021-01-03 13:11:46 +01:00
|
|
|
|
|
2021-09-30 13:39:23 +02:00
|
|
|
|
selection_update(term, end.col, end.row);
|
2021-01-02 23:00:54 +01:00
|
|
|
|
break;
|
2021-09-30 13:39:23 +02:00
|
|
|
|
}
|
2021-01-06 10:53:27 +01:00
|
|
|
|
|
|
|
|
|
|
case SELECTION_NONE:
|
2021-02-10 09:01:51 +00:00
|
|
|
|
BUG("Invalid selection kind");
|
2021-01-06 10:53:27 +01:00
|
|
|
|
break;
|
2021-01-02 23:00:54 +01:00
|
|
|
|
}
|
2021-01-03 13:11:46 +01:00
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
static pixman_region32_t
|
|
|
|
|
|
pixman_region_for_coords_normal(const struct terminal *term,
|
|
|
|
|
|
const struct coord *start,
|
|
|
|
|
|
const struct coord *end)
|
2020-01-04 12:03:04 +01:00
|
|
|
|
{
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
pixman_region32_t region;
|
|
|
|
|
|
pixman_region32_init(®ion);
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
const int rel_start_row =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, start->row);
|
|
|
|
|
|
const int rel_end_row =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, end->row);
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
if (rel_start_row < rel_end_row) {
|
|
|
|
|
|
/* First partial row (start ->)*/
|
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
|
®ion, ®ion,
|
|
|
|
|
|
start->col, rel_start_row,
|
|
|
|
|
|
term->cols - start->col, 1);
|
|
|
|
|
|
|
|
|
|
|
|
/* Full rows between start and end */
|
|
|
|
|
|
if (rel_start_row + 1 < rel_end_row) {
|
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
|
®ion, ®ion,
|
|
|
|
|
|
0, rel_start_row + 1,
|
|
|
|
|
|
term->cols, rel_end_row - rel_start_row - 1);
|
|
|
|
|
|
}
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
/* Last partial row (-> end) */
|
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
|
®ion, ®ion,
|
|
|
|
|
|
0, rel_end_row,
|
|
|
|
|
|
end->col + 1, 1);
|
|
|
|
|
|
|
|
|
|
|
|
} else if (rel_start_row > rel_end_row) {
|
|
|
|
|
|
/* First partial row (end ->) */
|
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
|
®ion, ®ion,
|
|
|
|
|
|
end->col, rel_end_row,
|
|
|
|
|
|
term->cols - end->col, 1);
|
|
|
|
|
|
|
|
|
|
|
|
/* Full rows between end and start */
|
|
|
|
|
|
if (rel_end_row + 1 < rel_start_row) {
|
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
|
®ion, ®ion,
|
|
|
|
|
|
0, rel_end_row + 1,
|
|
|
|
|
|
term->cols, rel_start_row - rel_end_row - 1);
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
}
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
|
|
|
|
|
|
/* Last partial row (-> start) */
|
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
|
®ion, ®ion,
|
|
|
|
|
|
0, rel_start_row,
|
|
|
|
|
|
start->col + 1, 1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const int start_col = min(start->col, end->col);
|
|
|
|
|
|
const int end_col = max(start->col, end->col);
|
|
|
|
|
|
|
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
|
®ion, ®ion,
|
|
|
|
|
|
start_col, rel_start_row,
|
|
|
|
|
|
end_col + 1 - start_col, 1);
|
2020-01-06 11:56:18 +01:00
|
|
|
|
}
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
return region;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
static pixman_region32_t
|
|
|
|
|
|
pixman_region_for_coords_block(const struct terminal *term,
|
|
|
|
|
|
const struct coord *start, const struct coord *end)
|
2020-01-06 11:56:18 +01:00
|
|
|
|
{
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
pixman_region32_t region;
|
|
|
|
|
|
pixman_region32_init(®ion);
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
const int rel_start_row =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, start->row);
|
|
|
|
|
|
const int rel_end_row =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, end->row);
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
|
®ion, ®ion,
|
|
|
|
|
|
min(start->col, end->col), min(rel_start_row, rel_end_row),
|
|
|
|
|
|
abs(start->col - end->col) + 1, abs(rel_start_row - rel_end_row) + 1);
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
return region;
|
|
|
|
|
|
}
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
/* Returns a pixman region representing the selection between ‘start’
|
|
|
|
|
|
* and ‘end’ (given the current selection kind), in *scrollback
|
|
|
|
|
|
* relative coordinates* */
|
|
|
|
|
|
static pixman_region32_t
|
|
|
|
|
|
pixman_region_for_coords(const struct terminal *term,
|
|
|
|
|
|
const struct coord *start, const struct coord *end)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (term->selection.kind) {
|
|
|
|
|
|
default: return pixman_region_for_coords_normal(term, start, end);
|
|
|
|
|
|
case SELECTION_BLOCK: return pixman_region_for_coords_block(term, start, end);
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
}
|
2020-01-06 11:56:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
static void
|
|
|
|
|
|
mark_selected_region(struct terminal *term, pixman_box32_t *boxes,
|
selection: once again highlight non-trailing empty cells
Foot 1.13.0 introduced a regression where non-trailing empty cells
were highlighted inconsistently (cells that shouldn’t be highlighted,
were, seemingly at random).
86663522d5915c0fdb2b8b132bacc247fcfd1fb8 “fixed” this by never
highlighting *any* empty cells.
This meant the behavior, compared to foot 1.12 and earlier,
changed. In foot 1.12 and older versions, non-trailing empty cells
were highlighted, as long as the selection covered at least one of the
trailing non-empty cells.
This patch restores that behavior.
To understand how this works, lets first take a look at how selection
works:
When a selection is made, and updated (i.e. the mouse is dragged, or
the selection is extended through RMB etc), we need to (un)tag and dirty
the cells that are a) newly selected, or b) newly deselected. That is,
we look at the diff between the “old” and the “new” selection, and
only update those cells.
This is for performance reasons: iterating the entire selection is not
feasible with large selections. However, it also means we cannot
reason about empty cells; we simply don’t know if an empty cells is a
trailing empty cell, or a non-trailing one.
Then, when we render a frame, we iterate all the *visible*
and *selected* cells, once again tagging them as selected (this is
needed since a selected cell might have lost its selected tag if the
cell was written to, by the client application, after the selection
was made).
At this point, we *can* reason about empty cells.
So, to restore the highlighting behavior to that of foot 1.12, we do
this:
When working with the selection diffs when a selection is updated, we
don’t special case empty cells at all. Thus, all empty cells covered
by the selection is highlighted, and dirtied.
But, when rendering the frame, we _do_ special case them. The only
difference (compared to foot 1.12) is that we *must*
explicitly *clear* the selection tag, and dirty the empty cells. This
is to ensure the empty cells that were incorrectly highlighted by the
selection update algorithm, isn’t rendered as that.
This does have a slight performance impact, as empty cells are now
always re-rendered. The impact should however be small.
2022-08-22 20:14:06 +02:00
|
|
|
|
size_t count, bool selected, bool dirty_cells,
|
|
|
|
|
|
bool highlight_empty)
|
2020-01-04 12:03:04 +01:00
|
|
|
|
{
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
|
|
const pixman_box32_t *box = &boxes[i];
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
LOG_DBG("%s selection in region: %dx%d - %dx%d",
|
|
|
|
|
|
selected ? "marking" : "unmarking",
|
|
|
|
|
|
box->x1, box->y1,
|
|
|
|
|
|
box->x2, box->y2);
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
int abs_row_start = grid_row_sb_to_abs(
|
|
|
|
|
|
term->grid, term->rows, box->y1);
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
for (int r = abs_row_start, rel_r = box->y1;
|
|
|
|
|
|
rel_r < box->y2;
|
|
|
|
|
|
r = (r + 1) & (term->grid->num_rows - 1), rel_r++)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct row *row = term->grid->rows[r];
|
|
|
|
|
|
xassert(row != NULL);
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
if (dirty_cells)
|
|
|
|
|
|
row->dirty = true;
|
2020-01-04 12:03:04 +01:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
for (int c = box->x1, empty_count = 0; c < box->x2; c++) {
|
selection: once again highlight non-trailing empty cells
Foot 1.13.0 introduced a regression where non-trailing empty cells
were highlighted inconsistently (cells that shouldn’t be highlighted,
were, seemingly at random).
86663522d5915c0fdb2b8b132bacc247fcfd1fb8 “fixed” this by never
highlighting *any* empty cells.
This meant the behavior, compared to foot 1.12 and earlier,
changed. In foot 1.12 and older versions, non-trailing empty cells
were highlighted, as long as the selection covered at least one of the
trailing non-empty cells.
This patch restores that behavior.
To understand how this works, lets first take a look at how selection
works:
When a selection is made, and updated (i.e. the mouse is dragged, or
the selection is extended through RMB etc), we need to (un)tag and dirty
the cells that are a) newly selected, or b) newly deselected. That is,
we look at the diff between the “old” and the “new” selection, and
only update those cells.
This is for performance reasons: iterating the entire selection is not
feasible with large selections. However, it also means we cannot
reason about empty cells; we simply don’t know if an empty cells is a
trailing empty cell, or a non-trailing one.
Then, when we render a frame, we iterate all the *visible*
and *selected* cells, once again tagging them as selected (this is
needed since a selected cell might have lost its selected tag if the
cell was written to, by the client application, after the selection
was made).
At this point, we *can* reason about empty cells.
So, to restore the highlighting behavior to that of foot 1.12, we do
this:
When working with the selection diffs when a selection is updated, we
don’t special case empty cells at all. Thus, all empty cells covered
by the selection is highlighted, and dirtied.
But, when rendering the frame, we _do_ special case them. The only
difference (compared to foot 1.12) is that we *must*
explicitly *clear* the selection tag, and dirty the empty cells. This
is to ensure the empty cells that were incorrectly highlighted by the
selection update algorithm, isn’t rendered as that.
This does have a slight performance impact, as empty cells are now
always re-rendered. The impact should however be small.
2022-08-22 20:14:06 +02:00
|
|
|
|
struct cell *cell = &row->cells[c];
|
|
|
|
|
|
|
|
|
|
|
|
if (cell->wc == 0 && !highlight_empty) {
|
selection: never highlight selected, empty cells
This fixes a regression, where empty cells "between" non-empty
cells (i.e. non-trailing empty cells) sometimes were incorrectly
highlighted.
The idea has always been to highlight exactly those cells that will
get extracted when they’re copied. This means we’ve not highlighted
trailing empty cells, but we _have_ highlighted other empty cells,
since they are converted to spaces when copied (whereas trailing empty
cells are skipped).
fa2d9f86996467ba33cc381f810ea966a4323381 changed how a selection is
updated. That is, which cells gets marked as selected, and which ones
gets unmarked.
Since we no longer walk all the cells, but instead work with pixman
regions representing selection diffs, we can no longer determine (with
certainty) which empty cells should be selected and which shouldn’t.
Before this patch (but after
fa2d9f86996467ba33cc381f810ea966a4323381), we sometimes incorrectly
highlighted empty cells that should not have been highlighted. This
happened when we’ve first (correctly) highlighted a region of empty
cells, but then shrink the selection such that all those empty cells
should be de-selected.
This patch changes the selection behavior to *never* highlight empty
cells. This fixes the regression, but also means slightly different
behavior, compared to pre-fa2d9f86996467ba33cc381f810ea966a4323381.
The other alternative is to always highlight all empty cells. But,
since I personally like the fact that we’re skipping trailing empty
cells, I prefer the approach taken by this patch.
2022-08-17 17:36:10 +02:00
|
|
|
|
/*
|
|
|
|
|
|
* We used to highlight empty cells *if* they were
|
|
|
|
|
|
* followed by non-empty cell(s), since this
|
|
|
|
|
|
* corresponds to what gets extracted when the
|
|
|
|
|
|
* selection is copied (that is, empty cells
|
|
|
|
|
|
* “between” non-empty cells are converted to
|
|
|
|
|
|
* spaces).
|
|
|
|
|
|
*
|
|
|
|
|
|
* However, they way we handle selection updates
|
|
|
|
|
|
* (diffing the “old” selection area against the
|
|
|
|
|
|
* “new” one, using pixman regions), means we
|
|
|
|
|
|
* can’t correctly update the state of empty
|
|
|
|
|
|
* cells. The result is “random” empty cells being
|
|
|
|
|
|
* rendered as selected when they shouldn’t.
|
|
|
|
|
|
*
|
|
|
|
|
|
* “Fix” by *never* highlighting selected empty
|
|
|
|
|
|
* cells (they still get converted to spaces when
|
|
|
|
|
|
* copied, if followed by non-empty cells).
|
|
|
|
|
|
*/
|
selection: once again highlight non-trailing empty cells
Foot 1.13.0 introduced a regression where non-trailing empty cells
were highlighted inconsistently (cells that shouldn’t be highlighted,
were, seemingly at random).
86663522d5915c0fdb2b8b132bacc247fcfd1fb8 “fixed” this by never
highlighting *any* empty cells.
This meant the behavior, compared to foot 1.12 and earlier,
changed. In foot 1.12 and older versions, non-trailing empty cells
were highlighted, as long as the selection covered at least one of the
trailing non-empty cells.
This patch restores that behavior.
To understand how this works, lets first take a look at how selection
works:
When a selection is made, and updated (i.e. the mouse is dragged, or
the selection is extended through RMB etc), we need to (un)tag and dirty
the cells that are a) newly selected, or b) newly deselected. That is,
we look at the diff between the “old” and the “new” selection, and
only update those cells.
This is for performance reasons: iterating the entire selection is not
feasible with large selections. However, it also means we cannot
reason about empty cells; we simply don’t know if an empty cells is a
trailing empty cell, or a non-trailing one.
Then, when we render a frame, we iterate all the *visible*
and *selected* cells, once again tagging them as selected (this is
needed since a selected cell might have lost its selected tag if the
cell was written to, by the client application, after the selection
was made).
At this point, we *can* reason about empty cells.
So, to restore the highlighting behavior to that of foot 1.12, we do
this:
When working with the selection diffs when a selection is updated, we
don’t special case empty cells at all. Thus, all empty cells covered
by the selection is highlighted, and dirtied.
But, when rendering the frame, we _do_ special case them. The only
difference (compared to foot 1.12) is that we *must*
explicitly *clear* the selection tag, and dirty the empty cells. This
is to ensure the empty cells that were incorrectly highlighted by the
selection update algorithm, isn’t rendered as that.
This does have a slight performance impact, as empty cells are now
always re-rendered. The impact should however be small.
2022-08-22 20:14:06 +02:00
|
|
|
|
empty_count++;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* When the selection is *modified*, empty cells
|
|
|
|
|
|
* are treated just like non-empty cells; they are
|
|
|
|
|
|
* marked as selected, and dirtied.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is due to how the algorithm for updating
|
|
|
|
|
|
* the selection works; it uses regions to
|
|
|
|
|
|
* calculate the difference between the “old” and
|
|
|
|
|
|
* the “new” selection. This makes it impossible
|
|
|
|
|
|
* to tell if an empty cell is a *trailing* empty
|
|
|
|
|
|
* cell (that should not be highlighted), or an
|
|
|
|
|
|
* empty cells between non-empty cells (that
|
|
|
|
|
|
* *should* be highlighted).
|
|
|
|
|
|
*
|
|
|
|
|
|
* Then, when a frame is rendered, we loop the
|
|
|
|
|
|
* *visibible* cells that belong to the
|
|
|
|
|
|
* selection. At this point, we *can* tell if an
|
|
|
|
|
|
* empty cell is trailing or not.
|
|
|
|
|
|
*
|
|
|
|
|
|
* So, what we need to do is check if a
|
|
|
|
|
|
* ‘selected’, and empty cell has been marked as
|
|
|
|
|
|
* selected, temporarily unmark (forcing it dirty,
|
|
|
|
|
|
* to ensure it gets re-rendered). If it is *not*
|
|
|
|
|
|
* a trailing empty cell, it will get re-tagged as
|
|
|
|
|
|
* selected in the for-loop below.
|
|
|
|
|
|
*/
|
|
|
|
|
|
cell->attrs.clean = false;
|
|
|
|
|
|
cell->attrs.selected = false;
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < empty_count + 1; j++) {
|
|
|
|
|
|
xassert(c - j >= 0);
|
|
|
|
|
|
struct cell *cell = &row->cells[c - j];
|
|
|
|
|
|
|
|
|
|
|
|
if (dirty_cells)
|
|
|
|
|
|
cell->attrs.clean = false;
|
|
|
|
|
|
cell->attrs.selected = selected;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
empty_count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-08-14 09:56:14 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
|
static void
|
|
|
|
|
|
selection_modify(struct terminal *term, struct coord start, struct coord end)
|
|
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
xassert(term->selection.coords.start.row != -1);
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(start.row != -1 && start.col != -1);
|
|
|
|
|
|
xassert(end.row != -1 && end.col != -1);
|
2020-04-04 11:59:15 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
pixman_region32_t previous_selection;
|
|
|
|
|
|
if (term->selection.coords.end.row >= 0) {
|
|
|
|
|
|
previous_selection = pixman_region_for_coords(
|
|
|
|
|
|
term,
|
|
|
|
|
|
&term->selection.coords.start,
|
|
|
|
|
|
&term->selection.coords.end);
|
|
|
|
|
|
} else
|
|
|
|
|
|
pixman_region32_init(&previous_selection);
|
|
|
|
|
|
|
|
|
|
|
|
pixman_region32_t current_selection = pixman_region_for_coords(
|
|
|
|
|
|
term, &start, &end);
|
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:
* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.
Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.
This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).
To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.
In short, we only dirty cells whose selection state have *changed*.
To do this, we used a second ‘selected’ bit in the cell attribute
struct.
Those bits are *scarce*.
This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.
This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.
The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.
The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.
The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.
The pre-pass allocates the inner arrays on demand.
The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-13 17:45:09 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
pixman_region32_t no_longer_selected;
|
|
|
|
|
|
pixman_region32_init(&no_longer_selected);
|
|
|
|
|
|
pixman_region32_subtract(
|
|
|
|
|
|
&no_longer_selected, &previous_selection, ¤t_selection);
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
pixman_region32_t newly_selected;
|
|
|
|
|
|
pixman_region32_init(&newly_selected);
|
|
|
|
|
|
pixman_region32_subtract(
|
|
|
|
|
|
&newly_selected, ¤t_selection, &previous_selection);
|
2020-04-04 11:59:15 +02:00
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
/* Clear selection in cells no longer selected */
|
|
|
|
|
|
int n_rects = -1;
|
|
|
|
|
|
pixman_box32_t *boxes = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
boxes = pixman_region32_rectangles(&no_longer_selected, &n_rects);
|
selection: once again highlight non-trailing empty cells
Foot 1.13.0 introduced a regression where non-trailing empty cells
were highlighted inconsistently (cells that shouldn’t be highlighted,
were, seemingly at random).
86663522d5915c0fdb2b8b132bacc247fcfd1fb8 “fixed” this by never
highlighting *any* empty cells.
This meant the behavior, compared to foot 1.12 and earlier,
changed. In foot 1.12 and older versions, non-trailing empty cells
were highlighted, as long as the selection covered at least one of the
trailing non-empty cells.
This patch restores that behavior.
To understand how this works, lets first take a look at how selection
works:
When a selection is made, and updated (i.e. the mouse is dragged, or
the selection is extended through RMB etc), we need to (un)tag and dirty
the cells that are a) newly selected, or b) newly deselected. That is,
we look at the diff between the “old” and the “new” selection, and
only update those cells.
This is for performance reasons: iterating the entire selection is not
feasible with large selections. However, it also means we cannot
reason about empty cells; we simply don’t know if an empty cells is a
trailing empty cell, or a non-trailing one.
Then, when we render a frame, we iterate all the *visible*
and *selected* cells, once again tagging them as selected (this is
needed since a selected cell might have lost its selected tag if the
cell was written to, by the client application, after the selection
was made).
At this point, we *can* reason about empty cells.
So, to restore the highlighting behavior to that of foot 1.12, we do
this:
When working with the selection diffs when a selection is updated, we
don’t special case empty cells at all. Thus, all empty cells covered
by the selection is highlighted, and dirtied.
But, when rendering the frame, we _do_ special case them. The only
difference (compared to foot 1.12) is that we *must*
explicitly *clear* the selection tag, and dirty the empty cells. This
is to ensure the empty cells that were incorrectly highlighted by the
selection update algorithm, isn’t rendered as that.
This does have a slight performance impact, as empty cells are now
always re-rendered. The impact should however be small.
2022-08-22 20:14:06 +02:00
|
|
|
|
mark_selected_region(term, boxes, n_rects, false, true, true);
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
|
|
|
|
|
|
boxes = pixman_region32_rectangles(&newly_selected, &n_rects);
|
selection: once again highlight non-trailing empty cells
Foot 1.13.0 introduced a regression where non-trailing empty cells
were highlighted inconsistently (cells that shouldn’t be highlighted,
were, seemingly at random).
86663522d5915c0fdb2b8b132bacc247fcfd1fb8 “fixed” this by never
highlighting *any* empty cells.
This meant the behavior, compared to foot 1.12 and earlier,
changed. In foot 1.12 and older versions, non-trailing empty cells
were highlighted, as long as the selection covered at least one of the
trailing non-empty cells.
This patch restores that behavior.
To understand how this works, lets first take a look at how selection
works:
When a selection is made, and updated (i.e. the mouse is dragged, or
the selection is extended through RMB etc), we need to (un)tag and dirty
the cells that are a) newly selected, or b) newly deselected. That is,
we look at the diff between the “old” and the “new” selection, and
only update those cells.
This is for performance reasons: iterating the entire selection is not
feasible with large selections. However, it also means we cannot
reason about empty cells; we simply don’t know if an empty cells is a
trailing empty cell, or a non-trailing one.
Then, when we render a frame, we iterate all the *visible*
and *selected* cells, once again tagging them as selected (this is
needed since a selected cell might have lost its selected tag if the
cell was written to, by the client application, after the selection
was made).
At this point, we *can* reason about empty cells.
So, to restore the highlighting behavior to that of foot 1.12, we do
this:
When working with the selection diffs when a selection is updated, we
don’t special case empty cells at all. Thus, all empty cells covered
by the selection is highlighted, and dirtied.
But, when rendering the frame, we _do_ special case them. The only
difference (compared to foot 1.12) is that we *must*
explicitly *clear* the selection tag, and dirty the empty cells. This
is to ensure the empty cells that were incorrectly highlighted by the
selection update algorithm, isn’t rendered as that.
This does have a slight performance impact, as empty cells are now
always re-rendered. The impact should however be small.
2022-08-22 20:14:06 +02:00
|
|
|
|
mark_selected_region(term, boxes, n_rects, true, true, true);
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
|
|
|
|
|
|
pixman_region32_fini(&newly_selected);
|
|
|
|
|
|
pixman_region32_fini(&no_longer_selected);
|
|
|
|
|
|
pixman_region32_fini(¤t_selection);
|
|
|
|
|
|
pixman_region32_fini(&previous_selection);
|
2020-04-04 11:59:15 +02:00
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start = start;
|
|
|
|
|
|
term->selection.coords.end = end;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
render_refresh(term);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-06 10:54:53 +01:00
|
|
|
|
static void
|
|
|
|
|
|
set_pivot_point_for_block_and_char_wise(struct terminal *term,
|
|
|
|
|
|
struct coord start,
|
|
|
|
|
|
enum selection_direction new_direction)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct coord *pivot_start = &term->selection.pivot.start;
|
|
|
|
|
|
struct coord *pivot_end = &term->selection.pivot.end;
|
|
|
|
|
|
|
|
|
|
|
|
*pivot_start = start;
|
|
|
|
|
|
|
|
|
|
|
|
/* First, make sure ‘start’ isn’t in the middle of a
|
|
|
|
|
|
* multi-column character */
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
const struct row *row = term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)];
|
|
|
|
|
|
const struct cell *cell = &row->cells[pivot_start->col];
|
|
|
|
|
|
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
if (cell->wc < CELL_SPACER)
|
2021-01-06 10:54:53 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* Multi-column chars don’t cross rows */
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(pivot_start->col > 0);
|
2021-01-06 10:54:53 +01:00
|
|
|
|
if (pivot_start->col == 0)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
pivot_start->col--;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Setup pivot end to be one character *before* start
|
|
|
|
|
|
* Which one we move, the end or start point, depends
|
|
|
|
|
|
* on the initial selection direction.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
*pivot_end = *pivot_start;
|
|
|
|
|
|
|
|
|
|
|
|
if (new_direction == SELECTION_RIGHT) {
|
|
|
|
|
|
bool keep_going = true;
|
|
|
|
|
|
while (keep_going) {
|
|
|
|
|
|
const struct row *row = term->grid->rows[pivot_end->row & (term->grid->num_rows - 1)];
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
const char32_t wc = row->cells[pivot_end->col].wc;
|
2021-01-06 10:54:53 +01:00
|
|
|
|
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
keep_going = wc >= CELL_SPACER;
|
2021-01-06 10:54:53 +01:00
|
|
|
|
|
|
|
|
|
|
if (pivot_end->col == 0) {
|
|
|
|
|
|
if (pivot_end->row - term->grid->view <= 0)
|
|
|
|
|
|
break;
|
|
|
|
|
|
pivot_end->col = term->cols - 1;
|
|
|
|
|
|
pivot_end->row--;
|
|
|
|
|
|
} else
|
|
|
|
|
|
pivot_end->col--;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bool keep_going = true;
|
|
|
|
|
|
while (keep_going) {
|
|
|
|
|
|
const struct row *row = term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)];
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
|
const char32_t wc = pivot_start->col < term->cols - 1
|
2021-01-06 10:54:53 +01:00
|
|
|
|
? row->cells[pivot_start->col + 1].wc : 0;
|
|
|
|
|
|
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
keep_going = wc >= CELL_SPACER;
|
2021-01-06 10:54:53 +01:00
|
|
|
|
|
|
|
|
|
|
if (pivot_start->col >= term->cols - 1) {
|
|
|
|
|
|
if (pivot_start->row - term->grid->view >= term->rows - 1)
|
|
|
|
|
|
break;
|
|
|
|
|
|
pivot_start->col = 0;
|
|
|
|
|
|
pivot_start->row++;
|
|
|
|
|
|
} else
|
|
|
|
|
|
pivot_start->col++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)]->
|
2021-06-02 19:29:06 +02:00
|
|
|
|
cells[pivot_start->col].wc <= CELL_SPACER);
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(term->grid->rows[pivot_end->row & (term->grid->num_rows - 1)]->
|
2021-06-02 19:29:06 +02:00
|
|
|
|
cells[pivot_end->col].wc <= CELL_SPACER + 1);
|
2021-01-06 10:54:53 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_update(struct terminal *term, int col, int row)
|
|
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (term->selection.coords.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",
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start.row, term->selection.coords.start.col,
|
|
|
|
|
|
term->selection.coords.end.row, term->selection.coords.end.col,
|
2019-07-11 09:51:51 +02:00
|
|
|
|
row, col);
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(term->grid->view + row != -1);
|
2019-07-11 11:10:12 +02:00
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
struct coord new_start = term->selection.coords.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-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)) {
|
2021-01-03 13:11:46 +01:00
|
|
|
|
enum selection_direction new_direction = term->selection.direction;
|
|
|
|
|
|
|
|
|
|
|
|
struct coord *pivot_start = &term->selection.pivot.start;
|
|
|
|
|
|
struct coord *pivot_end = &term->selection.pivot.end;
|
2020-08-12 18:50:49 +02:00
|
|
|
|
|
2021-01-06 10:54:53 +01:00
|
|
|
|
if (term->selection.kind == SELECTION_BLOCK) {
|
|
|
|
|
|
if (new_end.col > pivot_start->col)
|
|
|
|
|
|
new_direction = SELECTION_RIGHT;
|
|
|
|
|
|
else
|
|
|
|
|
|
new_direction = SELECTION_LEFT;
|
2020-08-12 18:50:49 +02:00
|
|
|
|
|
2021-01-06 10:54:53 +01:00
|
|
|
|
if (term->selection.direction == SELECTION_UNDIR)
|
|
|
|
|
|
set_pivot_point_for_block_and_char_wise(term, *pivot_start, new_direction);
|
2021-01-03 13:11:46 +01:00
|
|
|
|
|
2021-01-06 10:54:53 +01:00
|
|
|
|
if (new_direction == SELECTION_LEFT)
|
|
|
|
|
|
new_start = *pivot_end;
|
|
|
|
|
|
else
|
|
|
|
|
|
new_start = *pivot_start;
|
|
|
|
|
|
term->selection.direction = new_direction;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (new_end.row < pivot_start->row ||
|
|
|
|
|
|
(new_end.row == pivot_start->row &&
|
|
|
|
|
|
new_end.col < pivot_start->col))
|
|
|
|
|
|
{
|
|
|
|
|
|
/* New end point is before the start point */
|
|
|
|
|
|
new_direction = SELECTION_LEFT;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
/* The new end point is after the start point */
|
|
|
|
|
|
new_direction = SELECTION_RIGHT;
|
|
|
|
|
|
}
|
2021-01-03 13:11:46 +01:00
|
|
|
|
|
2021-01-06 10:54:53 +01:00
|
|
|
|
if (term->selection.direction != new_direction) {
|
|
|
|
|
|
if (term->selection.direction == SELECTION_UNDIR &&
|
|
|
|
|
|
pivot_end->row < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
set_pivot_point_for_block_and_char_wise(
|
|
|
|
|
|
term, *pivot_start, new_direction);
|
2021-01-03 13:11:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-06 10:54:53 +01:00
|
|
|
|
if (new_direction == SELECTION_LEFT) {
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(pivot_end->row >= 0);
|
2021-01-06 10:54:53 +01:00
|
|
|
|
new_start = *pivot_end;
|
|
|
|
|
|
} else
|
|
|
|
|
|
new_start = *pivot_start;
|
2021-01-03 13:11:46 +01:00
|
|
|
|
|
2021-01-06 10:54:53 +01:00
|
|
|
|
term->selection.direction = new_direction;
|
2020-08-12 18:50:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-06 10:53:27 +01:00
|
|
|
|
switch (term->selection.kind) {
|
|
|
|
|
|
case SELECTION_CHAR_WISE:
|
|
|
|
|
|
case SELECTION_BLOCK:
|
2021-01-02 21:26:28 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
2021-01-06 10:53:27 +01:00
|
|
|
|
case SELECTION_WORD_WISE:
|
2021-01-02 21:20:41 +01:00
|
|
|
|
switch (term->selection.direction) {
|
2021-01-03 15:15:34 +01:00
|
|
|
|
case SELECTION_LEFT: {
|
|
|
|
|
|
struct coord end = {col, row};
|
2021-03-28 21:03:53 +02:00
|
|
|
|
selection_find_word_boundary_left(
|
|
|
|
|
|
term, &end, term->selection.spaces_only);
|
2021-01-03 15:15:34 +01:00
|
|
|
|
new_end = (struct coord){end.col, term->grid->view + end.row};
|
2021-01-02 21:20:41 +01:00
|
|
|
|
break;
|
2021-01-03 15:15:34 +01:00
|
|
|
|
}
|
2021-01-02 21:20:41 +01:00
|
|
|
|
|
2021-01-03 15:15:34 +01:00
|
|
|
|
case SELECTION_RIGHT: {
|
|
|
|
|
|
struct coord end = {col, row};
|
2021-03-28 21:03:53 +02:00
|
|
|
|
selection_find_word_boundary_right(
|
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).
Take this, for example:
The Quick Brown Fox
The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.
This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want
The Quick Brown Fox
to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.
Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.
This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
2022-04-27 18:44:57 +02:00
|
|
|
|
term, &end, term->selection.spaces_only, true);
|
2021-01-03 15:15:34 +01:00
|
|
|
|
new_end = (struct coord){end.col, term->grid->view + end.row};
|
2021-01-02 21:20:41 +01:00
|
|
|
|
break;
|
2021-01-03 15:15:34 +01:00
|
|
|
|
}
|
2021-01-02 21:20:41 +01:00
|
|
|
|
|
|
|
|
|
|
case SELECTION_UNDIR:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-01-02 21:26:28 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
2021-01-06 10:53:27 +01:00
|
|
|
|
case SELECTION_LINE_WISE:
|
2021-01-02 21:26:28 +01:00
|
|
|
|
switch (term->selection.direction) {
|
2021-09-30 13:39:23 +02:00
|
|
|
|
case SELECTION_LEFT: {
|
|
|
|
|
|
struct coord end = {0, row};
|
|
|
|
|
|
selection_find_line_boundary_left(
|
|
|
|
|
|
term, &end, term->selection.spaces_only);
|
|
|
|
|
|
new_end = (struct coord){end.col, term->grid->view + end.row};
|
2021-01-02 21:26:28 +01:00
|
|
|
|
break;
|
2021-09-30 13:39:23 +02:00
|
|
|
|
}
|
2021-01-02 21:26:28 +01:00
|
|
|
|
|
2021-09-30 13:39:23 +02:00
|
|
|
|
case SELECTION_RIGHT: {
|
|
|
|
|
|
struct coord end = {col, row};
|
|
|
|
|
|
selection_find_line_boundary_right(
|
|
|
|
|
|
term, &end, term->selection.spaces_only);
|
|
|
|
|
|
new_end = (struct coord){end.col, term->grid->view + end.row};
|
2021-01-02 21:26:28 +01:00
|
|
|
|
break;
|
2021-09-30 13:39:23 +02:00
|
|
|
|
}
|
2021-01-02 21:26:28 +01:00
|
|
|
|
|
|
|
|
|
|
case SELECTION_UNDIR:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2021-01-06 10:53:27 +01:00
|
|
|
|
|
|
|
|
|
|
case SELECTION_NONE:
|
2021-02-10 09:01:51 +00:00
|
|
|
|
BUG("Invalid selection kind");
|
2021-01-06 10:53:27 +01:00
|
|
|
|
break;
|
2021-01-02 21:20:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-03 16:01:05 +01: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-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 &&
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
row_start->cells[new_start.col].wc >= CELL_SPACER)
|
2020-08-13 18:33:22 +02:00
|
|
|
|
new_start.col--;
|
|
|
|
|
|
while (new_end.col < term->cols - 1 &&
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
row_end->cells[new_end.col + 1].wc >= CELL_SPACER)
|
2020-08-13 18:33:22 +02:00
|
|
|
|
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 &&
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
row_end->cells[new_end.col].wc >= CELL_SPACER)
|
2020-08-13 18:33:22 +02:00
|
|
|
|
new_end.col--;
|
|
|
|
|
|
while (new_start.col < term->cols - 1 &&
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
|
row_start->cells[new_start.col + 1].wc >= CELL_SPACER)
|
2020-08-13 18:33:22 +02:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (term->selection.coords.start.row < 0 || term->selection.coords.end.row < 0)
|
2020-05-16 21:36:08 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
pixman_region32_t selection = pixman_region_for_coords(
|
|
|
|
|
|
term, &term->selection.coords.start, &term->selection.coords.end);
|
|
|
|
|
|
|
|
|
|
|
|
pixman_region32_t view = pixman_region_for_coords(
|
|
|
|
|
|
term,
|
|
|
|
|
|
&(struct coord){0, term->grid->view},
|
|
|
|
|
|
&(struct coord){term->cols - 1, term->grid->view + term->rows - 1});
|
|
|
|
|
|
|
|
|
|
|
|
pixman_region32_t visible_and_selected;
|
|
|
|
|
|
pixman_region32_init(&visible_and_selected);
|
|
|
|
|
|
pixman_region32_intersect(&visible_and_selected, &selection, &view);
|
|
|
|
|
|
|
|
|
|
|
|
int n_rects = -1;
|
|
|
|
|
|
pixman_box32_t *boxes =
|
|
|
|
|
|
pixman_region32_rectangles(&visible_and_selected, &n_rects);
|
selection: once again highlight non-trailing empty cells
Foot 1.13.0 introduced a regression where non-trailing empty cells
were highlighted inconsistently (cells that shouldn’t be highlighted,
were, seemingly at random).
86663522d5915c0fdb2b8b132bacc247fcfd1fb8 “fixed” this by never
highlighting *any* empty cells.
This meant the behavior, compared to foot 1.12 and earlier,
changed. In foot 1.12 and older versions, non-trailing empty cells
were highlighted, as long as the selection covered at least one of the
trailing non-empty cells.
This patch restores that behavior.
To understand how this works, lets first take a look at how selection
works:
When a selection is made, and updated (i.e. the mouse is dragged, or
the selection is extended through RMB etc), we need to (un)tag and dirty
the cells that are a) newly selected, or b) newly deselected. That is,
we look at the diff between the “old” and the “new” selection, and
only update those cells.
This is for performance reasons: iterating the entire selection is not
feasible with large selections. However, it also means we cannot
reason about empty cells; we simply don’t know if an empty cells is a
trailing empty cell, or a non-trailing one.
Then, when we render a frame, we iterate all the *visible*
and *selected* cells, once again tagging them as selected (this is
needed since a selected cell might have lost its selected tag if the
cell was written to, by the client application, after the selection
was made).
At this point, we *can* reason about empty cells.
So, to restore the highlighting behavior to that of foot 1.12, we do
this:
When working with the selection diffs when a selection is updated, we
don’t special case empty cells at all. Thus, all empty cells covered
by the selection is highlighted, and dirtied.
But, when rendering the frame, we _do_ special case them. The only
difference (compared to foot 1.12) is that we *must*
explicitly *clear* the selection tag, and dirty the empty cells. This
is to ensure the empty cells that were incorrectly highlighted by the
selection update algorithm, isn’t rendered as that.
This does have a slight performance impact, as empty cells are now
always re-rendered. The impact should however be small.
2022-08-22 20:14:06 +02:00
|
|
|
|
mark_selected_region(term, boxes, n_rects, true, false, false);
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
|
|
|
|
|
|
pixman_region32_fini(&visible_and_selected);
|
|
|
|
|
|
pixman_region32_fini(&view);
|
|
|
|
|
|
pixman_region32_fini(&selection);
|
2020-05-16 21:36:08 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
|
static void
|
2021-01-06 11:11:46 +01:00
|
|
|
|
selection_extend_normal(struct terminal *term, int col, int row,
|
|
|
|
|
|
enum selection_kind new_kind)
|
2020-04-04 11:59:15 +02:00
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
const struct coord *start = &term->selection.coords.start;
|
|
|
|
|
|
const struct coord *end = &term->selection.coords.end;
|
2019-07-11 09:51:51 +02:00
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
const int rel_row = grid_row_abs_to_sb(term->grid, term->rows, row);
|
|
|
|
|
|
int rel_start_row = grid_row_abs_to_sb(term->grid, term->rows, start->row);
|
|
|
|
|
|
int rel_end_row = grid_row_abs_to_sb(term->grid, term->rows, end->row);
|
|
|
|
|
|
|
|
|
|
|
|
if (rel_start_row > rel_end_row ||
|
|
|
|
|
|
(rel_start_row == rel_end_row && start->col > end->col))
|
2020-04-04 11:59:15 +02:00
|
|
|
|
{
|
|
|
|
|
|
const struct coord *tmp = start;
|
|
|
|
|
|
start = end;
|
|
|
|
|
|
end = tmp;
|
2020-01-06 11:56:18 +01:00
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
int tmp_row = rel_start_row;
|
|
|
|
|
|
rel_start_row = rel_end_row;
|
|
|
|
|
|
rel_end_row = tmp_row;
|
|
|
|
|
|
}
|
2020-01-06 11:56:18 +01:00
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
|
struct coord new_start, new_end;
|
2020-11-06 20:12:15 +01:00
|
|
|
|
enum selection_direction direction;
|
2020-01-06 11:56:18 +01:00
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
if (rel_row < rel_start_row ||
|
|
|
|
|
|
(rel_row == rel_start_row && col < start->col))
|
|
|
|
|
|
{
|
2020-04-04 11:59:15 +02:00
|
|
|
|
/* 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-11-04 19:01:59 +01:00
|
|
|
|
direction = SELECTION_LEFT;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
else if (rel_row > rel_end_row ||
|
|
|
|
|
|
(rel_row == rel_end_row && col > end->col))
|
|
|
|
|
|
{
|
2020-04-04 11:59:15 +02:00
|
|
|
|
/* Extend selection to end *after* current end */
|
|
|
|
|
|
new_start = *start;
|
|
|
|
|
|
new_end = (struct coord){col, row};
|
2020-11-04 19:01:59 +01:00
|
|
|
|
direction = SELECTION_RIGHT;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
/* Shrink selection from start or end, depending on which one is closest */
|
|
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
const int linear = rel_row * term->cols + col;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
if (abs(linear - (rel_start_row * term->cols + start->col)) <
|
|
|
|
|
|
abs(linear - (rel_end_row * term->cols + end->col)))
|
2020-04-04 11:59:15 +02:00
|
|
|
|
{
|
|
|
|
|
|
/* Move start point */
|
2020-07-27 16:44:41 +02:00
|
|
|
|
new_start = *end;
|
|
|
|
|
|
new_end = (struct coord){col, row};
|
2020-11-04 19:01:59 +01:00
|
|
|
|
direction = SELECTION_LEFT;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
/* Move end point */
|
|
|
|
|
|
new_start = *start;
|
|
|
|
|
|
new_end = (struct coord){col, row};
|
2020-11-04 19:01:59 +01:00
|
|
|
|
direction = SELECTION_RIGHT;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-30 13:39:23 +02:00
|
|
|
|
const bool spaces_only = term->selection.spaces_only;
|
|
|
|
|
|
|
2021-01-06 11:07:19 +01:00
|
|
|
|
switch (term->selection.kind) {
|
|
|
|
|
|
case SELECTION_CHAR_WISE:
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(new_kind == SELECTION_CHAR_WISE);
|
2021-01-06 11:07:19 +01:00
|
|
|
|
set_pivot_point_for_block_and_char_wise(term, new_start, direction);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case SELECTION_WORD_WISE: {
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(new_kind == SELECTION_CHAR_WISE ||
|
2021-01-06 11:11:46 +01:00
|
|
|
|
new_kind == SELECTION_WORD_WISE);
|
|
|
|
|
|
|
2021-01-06 11:07:19 +01:00
|
|
|
|
struct coord pivot_start = {new_start.col, new_start.row - term->grid->view};
|
|
|
|
|
|
struct coord pivot_end = pivot_start;
|
|
|
|
|
|
|
2021-09-30 13:39:23 +02:00
|
|
|
|
selection_find_word_boundary_left(term, &pivot_start, spaces_only);
|
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).
Take this, for example:
The Quick Brown Fox
The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.
This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want
The Quick Brown Fox
to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.
Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.
This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
2022-04-27 18:44:57 +02:00
|
|
|
|
selection_find_word_boundary_right(term, &pivot_end, spaces_only, true);
|
2021-01-06 11:07:19 +01:00
|
|
|
|
|
|
|
|
|
|
term->selection.pivot.start =
|
|
|
|
|
|
(struct coord){pivot_start.col, term->grid->view + pivot_start.row};
|
|
|
|
|
|
term->selection.pivot.end =
|
|
|
|
|
|
(struct coord){pivot_end.col, term->grid->view + pivot_end.row};
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-30 13:39:23 +02:00
|
|
|
|
case SELECTION_LINE_WISE: {
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(new_kind == SELECTION_CHAR_WISE ||
|
2021-09-30 13:39:23 +02:00
|
|
|
|
new_kind == SELECTION_LINE_WISE);
|
|
|
|
|
|
|
|
|
|
|
|
struct coord pivot_start = {new_start.col, new_start.row - term->grid->view};
|
|
|
|
|
|
struct coord pivot_end = pivot_start;
|
|
|
|
|
|
|
|
|
|
|
|
selection_find_line_boundary_left(term, &pivot_start, spaces_only);
|
|
|
|
|
|
selection_find_line_boundary_right(term, &pivot_end, spaces_only);
|
2021-01-06 11:11:46 +01:00
|
|
|
|
|
2021-09-30 13:39:23 +02:00
|
|
|
|
term->selection.pivot.start =
|
|
|
|
|
|
(struct coord){pivot_start.col, term->grid->view + pivot_start.row};
|
|
|
|
|
|
term->selection.pivot.end =
|
|
|
|
|
|
(struct coord){pivot_end.col, term->grid->view + pivot_end.row};
|
2021-01-06 11:07:19 +01:00
|
|
|
|
break;
|
2021-09-30 13:39:23 +02:00
|
|
|
|
}
|
2021-01-06 11:07:19 +01:00
|
|
|
|
|
|
|
|
|
|
case SELECTION_BLOCK:
|
|
|
|
|
|
case SELECTION_NONE:
|
2021-02-10 09:01:51 +00:00
|
|
|
|
BUG("Invalid selection kind in this context");
|
2021-01-06 11:07:19 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-06 11:11:46 +01:00
|
|
|
|
term->selection.kind = new_kind;
|
2020-11-04 19:01:59 +01:00
|
|
|
|
term->selection.direction = direction;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
selection_modify(term, new_start, new_end);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2021-01-06 11:11:46 +01:00
|
|
|
|
selection_extend_block(struct terminal *term, int col, int row)
|
2020-04-04 11:59:15 +02:00
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
const struct coord *start = &term->selection.coords.start;
|
|
|
|
|
|
const struct coord *end = &term->selection.coords.end;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
const int rel_start_row =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, start->row);
|
|
|
|
|
|
const int rel_end_row =
|
|
|
|
|
|
grid_row_abs_to_sb(term->grid, term->rows, end->row);
|
|
|
|
|
|
|
2020-04-04 11:59:15 +02:00
|
|
|
|
struct coord top_left = {
|
2022-07-28 18:32:17 +02:00
|
|
|
|
.row = rel_start_row < rel_end_row ? start->row : end->row,
|
2020-04-04 11:59:15 +02:00
|
|
|
|
.col = min(start->col, end->col),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct coord top_right = {
|
2022-07-28 18:32:17 +02:00
|
|
|
|
.row = top_left.row,
|
2020-04-04 11:59:15 +02:00
|
|
|
|
.col = max(start->col, end->col),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct coord bottom_left = {
|
2022-07-28 18:32:17 +02:00
|
|
|
|
.row = rel_start_row > rel_end_row ? start->row : end->row,
|
2020-04-04 11:59:15 +02:00
|
|
|
|
.col = min(start->col, end->col),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct coord bottom_right = {
|
2022-07-28 18:32:17 +02:00
|
|
|
|
.row = bottom_left.row,
|
2020-04-04 11:59:15 +02:00
|
|
|
|
.col = max(start->col, end->col),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
const int rel_row = grid_row_abs_to_sb(term->grid, term->rows, row);
|
|
|
|
|
|
const int rel_top_row = grid_row_abs_to_sb(term->grid, term->rows, top_left.row);
|
|
|
|
|
|
const int rel_bottom_row = grid_row_abs_to_sb(term->grid, term->rows, bottom_left.row);
|
2020-04-04 11:59:15 +02:00
|
|
|
|
struct coord new_start;
|
|
|
|
|
|
struct coord new_end;
|
|
|
|
|
|
|
2021-01-06 11:14:11 +01:00
|
|
|
|
enum selection_direction direction = SELECTION_UNDIR;
|
|
|
|
|
|
|
2022-07-28 18:32:17 +02:00
|
|
|
|
if (rel_row <= rel_top_row ||
|
|
|
|
|
|
abs(rel_row - rel_top_row) < abs(rel_row - rel_bottom_row))
|
2020-04-04 11:59:15 +02:00
|
|
|
|
{
|
|
|
|
|
|
/* 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};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-06 11:14:11 +01:00
|
|
|
|
direction = col > new_start.col ? SELECTION_RIGHT : SELECTION_LEFT;
|
|
|
|
|
|
set_pivot_point_for_block_and_char_wise(term, new_start, direction);
|
|
|
|
|
|
|
|
|
|
|
|
term->selection.direction = direction;
|
2020-04-04 11:59:15 +02:00
|
|
|
|
selection_modify(term, new_start, new_end);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2020-07-08 18:41:09 +02:00
|
|
|
|
selection_extend(struct seat *seat, struct terminal *term,
|
2021-01-06 11:11:46 +01:00
|
|
|
|
int col, int row, enum selection_kind new_kind)
|
2020-04-04 11:59:15 +02:00
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (term->selection.coords.start.row < 0 || term->selection.coords.end.row < 0) {
|
2020-04-04 11:59:15 +02:00
|
|
|
|
/* No existing selection */
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-06 11:11:46 +01:00
|
|
|
|
if (term->selection.kind == SELECTION_BLOCK && new_kind != SELECTION_BLOCK)
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if ((row == term->selection.coords.start.row && col == term->selection.coords.start.col) ||
|
|
|
|
|
|
(row == term->selection.coords.end.row && col == term->selection.coords.end.col))
|
2020-04-04 11:59:15 +02:00
|
|
|
|
{
|
|
|
|
|
|
/* Extension point *is* one of the current end points */
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (term->selection.kind) {
|
|
|
|
|
|
case SELECTION_NONE:
|
2021-02-10 09:01:51 +00:00
|
|
|
|
BUG("Invalid selection kind");
|
2020-04-04 11:59:15 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
2021-01-06 10:53:27 +01:00
|
|
|
|
case SELECTION_CHAR_WISE:
|
|
|
|
|
|
case SELECTION_WORD_WISE:
|
|
|
|
|
|
case SELECTION_LINE_WISE:
|
2021-01-06 11:11:46 +01:00
|
|
|
|
selection_extend_normal(term, col, row, new_kind);
|
2020-04-04 11:59:15 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case SELECTION_BLOCK:
|
2021-01-06 11:11:46 +01:00
|
|
|
|
selection_extend_block(term, col, row);
|
2020-04-04 11:59:15 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
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;
|
|
|
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
|
LOG_DBG("selection finalize");
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (term->selection.coords.start.row < 0 || term->selection.coords.end.row < 0)
|
2020-08-11 10:15:01 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
xassert(term->selection.coords.start.row != -1);
|
|
|
|
|
|
xassert(term->selection.coords.end.row != -1);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start.row &= (term->grid->num_rows - 1);
|
|
|
|
|
|
term->selection.coords.end.row &= (term->grid->num_rows - 1);
|
2021-01-16 11:26:45 +01:00
|
|
|
|
|
|
|
|
|
|
switch (term->conf->selection_target) {
|
2021-01-16 15:39:44 +01:00
|
|
|
|
case SELECTION_TARGET_NONE:
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2021-01-16 11:26:45 +01:00
|
|
|
|
case SELECTION_TARGET_PRIMARY:
|
|
|
|
|
|
selection_to_primary(seat, term, serial);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case SELECTION_TARGET_CLIPBOARD:
|
|
|
|
|
|
selection_to_clipboard(seat, term, serial);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case SELECTION_TARGET_BOTH:
|
|
|
|
|
|
selection_to_primary(seat, term, serial);
|
|
|
|
|
|
selection_to_clipboard(seat, term, serial);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2019-07-11 09:51:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
static bool
|
|
|
|
|
|
unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
|
|
|
|
|
int row_no, int col, void *data)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!cell->attrs.selected)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
|
cell->attrs.selected = false;
|
|
|
|
|
|
cell->attrs.clean = false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
|
void
|
|
|
|
|
|
selection_cancel(struct terminal *term)
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_DBG("selection cancelled: start = %d,%d end = %d,%d",
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start.row, term->selection.coords.start.col,
|
|
|
|
|
|
term->selection.coords.end.row, term->selection.coords.end.col);
|
2019-07-11 09:51:51 +02:00
|
|
|
|
|
2020-10-11 18:24:47 +02:00
|
|
|
|
selection_stop_scroll_timer(term);
|
2020-08-12 18:54:51 +02:00
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (term->selection.coords.start.row >= 0 && term->selection.coords.end.row >= 0) {
|
2020-01-06 11:56:18 +01:00
|
|
|
|
foreach_selected(
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term, term->selection.coords.start, term->selection.coords.end,
|
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.
Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.
This quickly gets *very* slow.
This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.
By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.
We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.
By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.
selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.
Closes #1114
2022-07-28 18:45:25 +02:00
|
|
|
|
&unmark_selected, NULL);
|
2020-01-04 12:03:04 +01:00
|
|
|
|
render_refresh(term);
|
|
|
|
|
|
}
|
2019-07-11 09:51:51 +02:00
|
|
|
|
|
2020-01-04 12:09:09 +01:00
|
|
|
|
term->selection.kind = SELECTION_NONE;
|
2022-04-09 15:09:02 +02:00
|
|
|
|
term->selection.coords.start = (struct coord){-1, -1};
|
|
|
|
|
|
term->selection.coords.end = (struct coord){-1, -1};
|
2021-01-03 15:25:43 +01:00
|
|
|
|
term->selection.pivot.start = (struct coord){-1, -1};
|
|
|
|
|
|
term->selection.pivot.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;
|
2021-07-22 17:53:29 +02:00
|
|
|
|
|
|
|
|
|
|
search_selection_cancelled(term);
|
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 */
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(clipboard->serial != 0);
|
2020-09-09 18:44:49 +02:00
|
|
|
|
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;
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(primary->serial != 0);
|
2020-09-09 18:44:49 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(direction != SELECTION_SCROLL_NOT);
|
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
|
|
|
|
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) {
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(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;
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(clipboard->text != NULL);
|
2020-10-13 18:38:49 +02:00
|
|
|
|
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;
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(clipboard->data_source == wl_data_source);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(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
|
|
|
|
{
|
2022-04-23 15:49:25 +02:00
|
|
|
|
xassert(serial != 0);
|
|
|
|
|
|
|
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 */
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(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 */
|
2020-10-28 19:16:04 +01:00
|
|
|
|
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8]);
|
2020-11-30 20:02:47 +01:00
|
|
|
|
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_PLAIN]);
|
2021-06-09 09:51:07 +02:00
|
|
|
|
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_TEXT]);;
|
|
|
|
|
|
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_STRING]);
|
|
|
|
|
|
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8_STRING]);
|
2020-11-30 20:02:47 +01:00
|
|
|
|
|
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-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
|
|
|
|
{
|
2022-04-09 15:09:02 +02:00
|
|
|
|
if (term->selection.coords.start.row < 0 || term->selection.coords.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;
|
2021-01-27 10:44:28 +01:00
|
|
|
|
bool bracketed;
|
2022-02-02 21:17:01 +01:00
|
|
|
|
bool quote_paths;
|
2020-08-22 08:29:35 +02:00
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
void (*decoder)(struct clipboard_receive *ctx, char *data, size_t size);
|
2021-01-12 14:45:41 +01:00
|
|
|
|
void (*finish)(struct clipboard_receive *ctx);
|
2020-11-01 11:52:11 +01:00
|
|
|
|
|
|
|
|
|
|
/* URI state */
|
|
|
|
|
|
bool add_space;
|
|
|
|
|
|
struct {
|
|
|
|
|
|
char *data;
|
|
|
|
|
|
size_t sz;
|
|
|
|
|
|
size_t idx;
|
|
|
|
|
|
} buf;
|
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
|
/* Callback data */
|
2020-10-28 19:16:04 +01:00
|
|
|
|
void (*cb)(char *data, size_t size, void *user);
|
2019-11-05 09:09:51 +01:00
|
|
|
|
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);
|
2020-11-01 11:52:11 +01:00
|
|
|
|
free(ctx->buf.data);
|
2020-08-22 08:29:35 +02:00
|
|
|
|
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;
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(events & EPOLLIN);
|
2020-08-22 08:29:35 +02:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
static void
|
|
|
|
|
|
fdm_receive_decoder_plain(struct clipboard_receive *ctx, char *data, size_t size)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->cb(data, size, ctx->user);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-12 14:45:41 +01:00
|
|
|
|
static void
|
|
|
|
|
|
fdm_receive_finish_plain(struct clipboard_receive *ctx)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-12 14:45:04 +01:00
|
|
|
|
static bool
|
|
|
|
|
|
decode_one_uri(struct clipboard_receive *ctx, char *uri, size_t len)
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_DBG("URI: \"%.*s\"", (int)len, uri);
|
|
|
|
|
|
|
2021-01-12 14:56:47 +01:00
|
|
|
|
if (len == 0)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
2021-01-12 14:45:04 +01:00
|
|
|
|
char *scheme, *host, *path;
|
|
|
|
|
|
if (!uri_parse(uri, len, &scheme, NULL, NULL, &host, NULL, &path, NULL, NULL)) {
|
|
|
|
|
|
LOG_ERR("drag-and-drop: invalid URI: %.*s", (int)len, uri);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (ctx->add_space)
|
|
|
|
|
|
ctx->cb(" ", 1, ctx->user);
|
|
|
|
|
|
ctx->add_space = true;
|
|
|
|
|
|
|
|
|
|
|
|
if (strcmp(scheme, "file") == 0 && hostname_is_localhost(host)) {
|
2022-02-02 21:17:01 +01:00
|
|
|
|
if (ctx->quote_paths)
|
|
|
|
|
|
ctx->cb("'", 1, ctx->user);
|
|
|
|
|
|
|
2021-01-12 14:45:04 +01:00
|
|
|
|
ctx->cb(path, strlen(path), ctx->user);
|
2022-02-02 21:17:01 +01:00
|
|
|
|
|
|
|
|
|
|
if (ctx->quote_paths)
|
|
|
|
|
|
ctx->cb("'", 1, ctx->user);
|
2021-01-12 14:45:04 +01:00
|
|
|
|
} else
|
|
|
|
|
|
ctx->cb(uri, len, ctx->user);
|
|
|
|
|
|
|
|
|
|
|
|
free(scheme);
|
|
|
|
|
|
free(host);
|
|
|
|
|
|
free(path);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
static void
|
|
|
|
|
|
fdm_receive_decoder_uri(struct clipboard_receive *ctx, char *data, size_t size)
|
|
|
|
|
|
{
|
|
|
|
|
|
while (ctx->buf.idx + size > ctx->buf.sz) {
|
|
|
|
|
|
size_t new_sz = ctx->buf.sz == 0 ? size : 2 * ctx->buf.sz;
|
|
|
|
|
|
ctx->buf.data = xrealloc(ctx->buf.data, new_sz);
|
|
|
|
|
|
ctx->buf.sz = new_sz;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
memcpy(&ctx->buf.data[ctx->buf.idx], data, size);
|
|
|
|
|
|
ctx->buf.idx += size;
|
|
|
|
|
|
|
|
|
|
|
|
char *start = ctx->buf.data;
|
|
|
|
|
|
char *end = NULL;
|
|
|
|
|
|
|
2021-01-27 10:44:28 +01:00
|
|
|
|
while (true) {
|
|
|
|
|
|
for (end = start; end < &ctx->buf.data[ctx->buf.idx]; end++) {
|
|
|
|
|
|
if (*end == '\r' || *end == '\n')
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (end >= &ctx->buf.data[ctx->buf.idx])
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2021-01-12 14:45:04 +01:00
|
|
|
|
decode_one_uri(ctx, start, end - start);
|
2020-11-01 11:52:11 +01:00
|
|
|
|
start = end + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const size_t ofs = start - ctx->buf.data;
|
|
|
|
|
|
const size_t left = ctx->buf.idx - ofs;
|
|
|
|
|
|
|
|
|
|
|
|
memmove(&ctx->buf.data[0], &ctx->buf.data[ofs], left);
|
|
|
|
|
|
ctx->buf.idx = left;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-12 14:45:41 +01:00
|
|
|
|
static void
|
|
|
|
|
|
fdm_receive_finish_uri(struct clipboard_receive *ctx)
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_DBG("finish: %.*s", (int)ctx->buf.idx, ctx->buf.data);
|
2021-01-12 14:56:47 +01:00
|
|
|
|
decode_one_uri(ctx, ctx->buf.data, ctx->buf.idx);
|
2021-01-12 14:45:41 +01: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;
|
|
|
|
|
|
|
2021-01-20 19:20:03 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* Call cb while at same time replace:
|
2022-03-16 20:17:02 +01:00
|
|
|
|
* - \r\n -> \r (non-bracketed paste)
|
|
|
|
|
|
* - \n -> \r (non-bracketed paste)
|
2021-01-23 11:28:41 +01:00
|
|
|
|
* - C0 -> <nothing> (strip non-formatting C0 characters)
|
2021-01-20 19:20:03 +01:00
|
|
|
|
* - \e -> <nothing> (i.e. strip ESC)
|
|
|
|
|
|
*/
|
2020-10-28 19:16:04 +01:00
|
|
|
|
char *p = text;
|
2019-12-15 12:11:12 +01:00
|
|
|
|
size_t left = count;
|
2021-02-12 10:03:01 +01:00
|
|
|
|
|
|
|
|
|
|
#define skip_one() \
|
|
|
|
|
|
do { \
|
|
|
|
|
|
ctx->decoder(ctx, p, i); \
|
|
|
|
|
|
xassert(i + 1 <= left); \
|
|
|
|
|
|
p += i + 1; \
|
|
|
|
|
|
left -= i + 1; \
|
|
|
|
|
|
} while (0)
|
|
|
|
|
|
|
2019-12-15 12:11:12 +01:00
|
|
|
|
again:
|
2021-01-20 19:20:03 +01:00
|
|
|
|
for (size_t i = 0; i < left; i++) {
|
|
|
|
|
|
switch (p[i]) {
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '\n':
|
2021-01-27 10:44:28 +01:00
|
|
|
|
if (!ctx->bracketed)
|
|
|
|
|
|
p[i] = '\r';
|
2021-01-20 19:20:03 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '\r':
|
2021-01-23 11:22:22 +01:00
|
|
|
|
/* Convert \r\n -> \r */
|
2021-01-27 10:44:28 +01:00
|
|
|
|
if (!ctx->bracketed && i + 1 < left && p[i + 1] == '\n') {
|
2021-02-12 10:03:01 +01:00
|
|
|
|
i++;
|
|
|
|
|
|
skip_one();
|
2021-01-20 19:20:03 +01:00
|
|
|
|
goto again;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2021-01-23 11:22:22 +01:00
|
|
|
|
/* C0 non-formatting control characters (\b \t \n \r excluded) */
|
|
|
|
|
|
case '\x01': case '\x02': case '\x03': case '\x04': case '\x05':
|
2021-02-12 10:03:01 +01:00
|
|
|
|
case '\x06': case '\x07': case '\x0e': case '\x0f': case '\x10':
|
|
|
|
|
|
case '\x11': case '\x12': case '\x13': case '\x14': case '\x15':
|
|
|
|
|
|
case '\x16': case '\x17': case '\x18': case '\x19': case '\x1a':
|
|
|
|
|
|
case '\x1b': case '\x1c': case '\x1d': case '\x1e': case '\x1f':
|
|
|
|
|
|
skip_one();
|
|
|
|
|
|
goto again;
|
2021-01-23 11:22:22 +01:00
|
|
|
|
|
selection: allow HT, VT and FF, disallow NUL in non-bracketed paste mode
This syncs foot with more recent versions of XTerm, where it’s
“disallowedPasteControls” resource has changed its default value to
BS,DEL,ENQ,EOT,ESC,NULL
Note that we’re already stripping out ENQ,EOT,ESC in all modes.
What does it mean for foot:
* HT, VT and FF are now allowed, regardless of paste mode
* NUL is now stripped in non-bracketed paste mode
Closes #1084
2022-06-13 11:41:54 +02:00
|
|
|
|
/*
|
|
|
|
|
|
* In addition to stripping non-formatting C0 controls,
|
|
|
|
|
|
* XTerm has an option, “disallowedPasteControls”, that
|
|
|
|
|
|
* defines C0 controls that will be replaced with spaces
|
|
|
|
|
|
* when pasted.
|
|
|
|
|
|
*
|
|
|
|
|
|
* It’s default value is BS,DEL,ENQ,EOT,NUL
|
|
|
|
|
|
*
|
|
|
|
|
|
* Instead of replacing them with spaces, we allow them in
|
|
|
|
|
|
* bracketed paste mode, and strip them completely in
|
|
|
|
|
|
* non-bracketed mode.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Note some of the (default) XTerm controls are already
|
|
|
|
|
|
* handled above.
|
|
|
|
|
|
*/
|
|
|
|
|
|
case '\b': case '\x7f': case '\x00':
|
2021-02-12 10:03:01 +01:00
|
|
|
|
if (!ctx->bracketed) {
|
|
|
|
|
|
skip_one();
|
|
|
|
|
|
goto again;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2019-11-05 09:09:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
ctx->decoder(ctx, p, left);
|
2019-12-15 12:11:12 +01:00
|
|
|
|
left = 0;
|
2019-11-05 09:09:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-12 10:03:01 +01:00
|
|
|
|
#undef skip_one
|
|
|
|
|
|
|
2019-11-05 09:09:51 +01:00
|
|
|
|
done:
|
2021-01-12 14:45:41 +01:00
|
|
|
|
ctx->finish(ctx);
|
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,
|
2020-11-01 11:52:11 +01:00
|
|
|
|
enum data_offer_mime_type mime_type,
|
2020-10-28 19:16:04 +01:00
|
|
|
|
void (*cb)(char *data, size_t size, void *user),
|
2019-11-05 09:09:51 +01:00
|
|
|
|
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,
|
2021-01-27 10:44:28 +01:00
|
|
|
|
.bracketed = term->bracketed_paste,
|
2022-02-02 21:17:01 +01:00
|
|
|
|
.quote_paths = term->grid == &term->normal,
|
2020-11-01 11:52:11 +01:00
|
|
|
|
.decoder = (mime_type == DATA_OFFER_MIME_URI_LIST
|
|
|
|
|
|
? &fdm_receive_decoder_uri
|
|
|
|
|
|
: &fdm_receive_decoder_plain),
|
2021-01-12 14:45:41 +01:00
|
|
|
|
.finish = (mime_type == DATA_OFFER_MIME_URI_LIST
|
|
|
|
|
|
? &fdm_receive_finish_uri
|
|
|
|
|
|
: &fdm_receive_finish_plain),
|
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,
|
2020-10-28 19:16:04 +01:00
|
|
|
|
void (*cb)(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-11-30 20:04:17 +01:00
|
|
|
|
if (clipboard->data_offer == NULL ||
|
|
|
|
|
|
clipboard->mime_type == DATA_OFFER_MIME_UNSET)
|
|
|
|
|
|
{
|
2020-08-23 09:39:49 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-12 14:45:41 +01:00
|
|
|
|
LOG_DBG("receive from clipboard: mime-type=%s",
|
|
|
|
|
|
mime_type_map[clipboard->mime_type]);
|
|
|
|
|
|
|
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(
|
2020-10-28 19:16:04 +01:00
|
|
|
|
clipboard->data_offer, mime_type_map[clipboard->mime_type], 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);
|
|
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
begin_receive_clipboard(term, read_fd, clipboard->mime_type, cb, done, user);
|
2019-07-19 14:20:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2020-11-01 11:52:11 +01:00
|
|
|
|
receive_offer(char *data, size_t size, void *user)
|
2019-07-19 14:20:00 +02:00
|
|
|
|
{
|
2020-11-01 11:52:11 +01:00
|
|
|
|
struct terminal *term = user;
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(term->is_sending_paste_data);
|
2020-11-01 11:52:11 +01:00
|
|
|
|
term_paste_data_to_slave(term, data, size);
|
2020-10-28 19:16:04 +01:00
|
|
|
|
}
|
2022-02-02 21:17:01 +01:00
|
|
|
|
|
2020-10-28 19:16:04 +01:00
|
|
|
|
static void
|
|
|
|
|
|
receive_offer_done(void *user)
|
|
|
|
|
|
{
|
2020-11-01 11:52:11 +01:00
|
|
|
|
struct terminal *term = user;
|
2019-11-05 08:49:32 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
text_from_clipboard(seat, term, &receive_offer, &receive_offer_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;
|
|
|
|
|
|
|
2022-04-23 15:49:25 +02:00
|
|
|
|
xassert(serial != 0);
|
|
|
|
|
|
|
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 */
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(primary->serial != 0);
|
2019-08-09 21:27:51 +02:00
|
|
|
|
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 */
|
2020-10-28 19:16:04 +01:00
|
|
|
|
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8]);
|
2020-11-30 20:02:47 +01:00
|
|
|
|
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_PLAIN]);
|
2021-06-09 09:51:07 +02:00
|
|
|
|
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_TEXT]);
|
|
|
|
|
|
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_STRING]);
|
|
|
|
|
|
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8_STRING]);
|
2020-11-30 20:02:47 +01:00
|
|
|
|
|
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,
|
2020-10-28 19:16:04 +01:00
|
|
|
|
void (*cb)(char *data, size_t size, void *user),
|
2019-11-05 08:49:32 +01:00
|
|
|
|
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-11-30 20:04:17 +01:00
|
|
|
|
if (primary->data_offer == NULL ||
|
|
|
|
|
|
primary->mime_type == DATA_OFFER_MIME_UNSET)
|
|
|
|
|
|
{
|
2020-08-23 09:39:49 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-12 14:45:41 +01:00
|
|
|
|
LOG_DBG("receive from primary: mime-type=%s",
|
|
|
|
|
|
mime_type_map[primary->mime_type]);
|
|
|
|
|
|
|
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(
|
2020-10-28 19:16:04 +01:00
|
|
|
|
primary->data_offer, mime_type_map[primary->mime_type], write_fd);
|
2019-07-11 17:02:21 +02:00
|
|
|
|
|
|
|
|
|
|
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
|
|
|
|
|
close(write_fd);
|
|
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
begin_receive_clipboard(term, read_fd, primary->mime_type, 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-11-01 11:52:11 +01:00
|
|
|
|
text_from_primary(seat, term, &receive_offer, &receive_offer_done, term);
|
2020-10-28 19:16:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
select_mime_type_for_offer(const char *_mime_type,
|
|
|
|
|
|
enum data_offer_mime_type *type)
|
|
|
|
|
|
{
|
|
|
|
|
|
enum data_offer_mime_type mime_type = DATA_OFFER_MIME_UNSET;
|
|
|
|
|
|
|
|
|
|
|
|
/* Translate offered mime type to our mime type enum */
|
|
|
|
|
|
for (size_t i = 0; i < ALEN(mime_type_map); i++) {
|
|
|
|
|
|
if (mime_type_map[i] == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (strcmp(_mime_type, mime_type_map[i]) == 0) {
|
|
|
|
|
|
mime_type = i;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DBG("mime-type: %s -> %s (offered type was %s)",
|
|
|
|
|
|
mime_type_map[*type], mime_type_map[mime_type], _mime_type);
|
|
|
|
|
|
|
|
|
|
|
|
/* Mime-type transition; if the new mime-type is "better" than
|
|
|
|
|
|
* previously offered types, use the new type */
|
|
|
|
|
|
|
|
|
|
|
|
switch (mime_type) {
|
|
|
|
|
|
case DATA_OFFER_MIME_TEXT_PLAIN:
|
2021-06-09 09:51:07 +02:00
|
|
|
|
case DATA_OFFER_MIME_TEXT_TEXT:
|
|
|
|
|
|
case DATA_OFFER_MIME_TEXT_STRING:
|
2020-10-28 19:16:04 +01:00
|
|
|
|
/* text/plain is our least preferred type. Only use if current
|
|
|
|
|
|
* type is unset */
|
|
|
|
|
|
switch (*type) {
|
|
|
|
|
|
case DATA_OFFER_MIME_UNSET:
|
|
|
|
|
|
*type = mime_type;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case DATA_OFFER_MIME_TEXT_UTF8:
|
2021-06-09 09:51:07 +02:00
|
|
|
|
case DATA_OFFER_MIME_TEXT_UTF8_STRING:
|
2020-10-28 19:16:04 +01:00
|
|
|
|
/* text/plain;charset=utf-8 is preferred over text/plain */
|
|
|
|
|
|
switch (*type) {
|
|
|
|
|
|
case DATA_OFFER_MIME_UNSET:
|
|
|
|
|
|
case DATA_OFFER_MIME_TEXT_PLAIN:
|
2021-06-09 09:51:07 +02:00
|
|
|
|
case DATA_OFFER_MIME_TEXT_TEXT:
|
|
|
|
|
|
case DATA_OFFER_MIME_TEXT_STRING:
|
2020-10-28 19:16:04 +01:00
|
|
|
|
*type = mime_type;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case DATA_OFFER_MIME_URI_LIST:
|
|
|
|
|
|
/* text/uri-list is always used when offered */
|
|
|
|
|
|
*type = mime_type;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case DATA_OFFER_MIME_UNSET:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
data_offer_reset(struct wl_clipboard *clipboard)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (clipboard->data_offer != NULL) {
|
|
|
|
|
|
wl_data_offer_destroy(clipboard->data_offer);
|
|
|
|
|
|
clipboard->data_offer = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
clipboard->window = NULL;
|
|
|
|
|
|
clipboard->mime_type = DATA_OFFER_MIME_UNSET;
|
2019-07-11 16:42:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
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-28 19:16:04 +01:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
select_mime_type_for_offer(mime_type, &seat->clipboard.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-28 19:16:04 +01:00
|
|
|
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
|
|
|
|
|
char actions_as_string[1024];
|
|
|
|
|
|
size_t idx = 0;
|
|
|
|
|
|
|
|
|
|
|
|
actions_as_string[0] = '\0';
|
|
|
|
|
|
actions_as_string[sizeof(actions_as_string) - 1] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < 31; i++) {
|
|
|
|
|
|
if (((source_actions >> i) & 1) == 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
enum wl_data_device_manager_dnd_action action = 1 << i;
|
|
|
|
|
|
|
|
|
|
|
|
const char *s = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: s = NULL; break;
|
|
|
|
|
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: s = "copy"; break;
|
|
|
|
|
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: s = "move"; break;
|
|
|
|
|
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: s = "ask"; break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (s == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
strncat(actions_as_string, s, sizeof(actions_as_string) - idx - 1);
|
|
|
|
|
|
idx += strlen(s);
|
|
|
|
|
|
strncat(actions_as_string, ", ", sizeof(actions_as_string) - idx - 1);
|
|
|
|
|
|
idx += 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Strip trailing ", " */
|
|
|
|
|
|
if (strlen(actions_as_string) > 2)
|
|
|
|
|
|
actions_as_string[strlen(actions_as_string) - 2] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DBG("DnD actions: %s (0x%08x)", actions_as_string, source_actions);
|
|
|
|
|
|
#endif
|
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-28 19:16:04 +01:00
|
|
|
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
|
|
|
|
|
const char *s = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
switch (dnd_action) {
|
|
|
|
|
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: s = "<none>"; break;
|
|
|
|
|
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: s = "copy"; break;
|
|
|
|
|
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: s = "move"; break;
|
|
|
|
|
|
case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: s = "ask"; break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DBG("DnD offer action: %s (0x%08x)", s, dnd_action);
|
|
|
|
|
|
#endif
|
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,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
data_offer(void *data, struct wl_data_device *wl_data_device,
|
2020-10-28 19:16:04 +01:00
|
|
|
|
struct wl_data_offer *offer)
|
2019-07-11 12:16:50 +02:00
|
|
|
|
{
|
2020-10-28 19:16:04 +01:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
data_offer_reset(&seat->clipboard);
|
|
|
|
|
|
seat->clipboard.data_offer = offer;
|
|
|
|
|
|
wl_data_offer_add_listener(offer, &data_offer_listener, seat);
|
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,
|
2020-10-28 19:16:04 +01:00
|
|
|
|
struct wl_data_offer *offer)
|
2019-07-11 12:16:50 +02:00
|
|
|
|
{
|
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;
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(offer == seat->clipboard.data_offer);
|
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
|
|
|
|
|
2022-06-20 20:57:26 +02:00
|
|
|
|
if (seat->clipboard.mime_type == DATA_OFFER_MIME_UNSET)
|
|
|
|
|
|
goto reject_offer;
|
|
|
|
|
|
|
2020-10-28 19:34:49 +01:00
|
|
|
|
/* Remember _which_ terminal the current DnD offer is targeting */
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(seat->clipboard.window == 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
|
|
|
|
tll_foreach(wayl->terms, it) {
|
|
|
|
|
|
if (term_surface_kind(it->item, surface) == TERM_SURF_GRID &&
|
|
|
|
|
|
!it->item->is_sending_paste_data)
|
|
|
|
|
|
{
|
2020-10-28 19:16:04 +01:00
|
|
|
|
wl_data_offer_accept(
|
|
|
|
|
|
offer, serial, mime_type_map[seat->clipboard.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
|
|
|
|
wl_data_offer_set_actions(
|
2020-10-28 19:16:04 +01:00
|
|
|
|
offer,
|
|
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY,
|
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
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
|
|
|
|
|
|
|
2020-10-28 19:16:04 +01:00
|
|
|
|
seat->clipboard.window = it->item->window;
|
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
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-20 20:57:26 +02:00
|
|
|
|
reject_offer:
|
2020-10-28 19:34:49 +01:00
|
|
|
|
/* Either terminal is already busy sending paste data, or mouse
|
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
|
|
|
|
* pointer isn’t over the grid */
|
2020-10-28 19:16:04 +01:00
|
|
|
|
seat->clipboard.window = NULL;
|
2022-06-20 20:57:23 +02:00
|
|
|
|
wl_data_offer_accept(offer, serial, NULL);
|
2022-06-21 19:46:31 +02:00
|
|
|
|
wl_data_offer_set_actions(
|
|
|
|
|
|
offer,
|
|
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE,
|
|
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE);
|
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;
|
2020-10-28 19:16:04 +01:00
|
|
|
|
seat->clipboard.window = 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)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
struct dnd_context {
|
|
|
|
|
|
struct terminal *term;
|
|
|
|
|
|
struct wl_data_offer *data_offer;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
receive_dnd(char *data, size_t size, void *user)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct dnd_context *ctx = user;
|
|
|
|
|
|
receive_offer(data, size, ctx->term);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
2020-10-28 19:16:04 +01:00
|
|
|
|
receive_dnd_done(void *user)
|
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
|
|
|
|
{
|
2020-11-01 11:52:11 +01:00
|
|
|
|
struct dnd_context *ctx = user;
|
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
|
|
|
|
|
2020-10-28 19:16:04 +01:00
|
|
|
|
wl_data_offer_finish(ctx->data_offer);
|
2020-10-31 10:36:11 +01:00
|
|
|
|
wl_data_offer_destroy(ctx->data_offer);
|
2020-11-01 11:52:11 +01:00
|
|
|
|
receive_offer_done(ctx->term);
|
|
|
|
|
|
free(ctx);
|
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
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(seat->clipboard.window != NULL);
|
2020-10-28 19:16:04 +01:00
|
|
|
|
struct terminal *term = seat->clipboard.window->term;
|
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
|
|
|
|
|
2020-10-28 19:16:04 +01:00
|
|
|
|
struct wl_clipboard *clipboard = &seat->clipboard;
|
|
|
|
|
|
|
2022-06-20 19:29:57 +02:00
|
|
|
|
if (clipboard->mime_type == DATA_OFFER_MIME_UNSET) {
|
|
|
|
|
|
LOG_WARN("compositor called data_device::drop() "
|
|
|
|
|
|
"even though we rejected the drag-and-drop");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-11-01 11:52:11 +01:00
|
|
|
|
struct dnd_context *ctx = xmalloc(sizeof(*ctx));
|
|
|
|
|
|
*ctx = (struct dnd_context){
|
2020-10-28 19:16:04 +01:00
|
|
|
|
.term = term,
|
|
|
|
|
|
.data_offer = clipboard->data_offer,
|
|
|
|
|
|
};
|
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
|
|
|
|
|
|
|
|
|
|
/* 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-10-28 19:16:04 +01:00
|
|
|
|
free(ctx);
|
|
|
|
|
|
return;
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int read_fd = fds[0];
|
|
|
|
|
|
int write_fd = fds[1];
|
|
|
|
|
|
|
2020-10-28 19:16:04 +01:00
|
|
|
|
LOG_DBG("DnD drop: mime-type=%s", mime_type_map[clipboard->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
|
|
|
|
/* Give write-end of pipe to other client */
|
2020-10-28 19:16:04 +01:00
|
|
|
|
wl_data_offer_receive(
|
|
|
|
|
|
clipboard->data_offer, mime_type_map[clipboard->mime_type], write_fd);
|
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
|
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
|
2020-10-28 19:16:04 +01:00
|
|
|
|
begin_receive_clipboard(
|
2020-11-01 11:52:11 +01:00
|
|
|
|
term, read_fd, clipboard->mime_type,
|
|
|
|
|
|
&receive_dnd, &receive_dnd_done, ctx);
|
2020-10-31 10:36:11 +01:00
|
|
|
|
|
|
|
|
|
|
/* data offer is now “owned” by the receive context */
|
|
|
|
|
|
clipboard->data_offer = NULL;
|
|
|
|
|
|
clipboard->mime_type = DATA_OFFER_MIME_UNSET;
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
selection(void *data, struct wl_data_device *wl_data_device,
|
2020-10-28 19:16:04 +01:00
|
|
|
|
struct wl_data_offer *offer)
|
2019-07-11 12:16:50 +02:00
|
|
|
|
{
|
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;
|
2020-10-28 19:16:04 +01:00
|
|
|
|
if (offer == NULL)
|
|
|
|
|
|
data_offer_reset(&seat->clipboard);
|
|
|
|
|
|
else
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(offer == seat->clipboard.data_offer);
|
2019-07-11 12:16:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const struct wl_data_device_listener data_device_listener = {
|
|
|
|
|
|
.data_offer = &data_offer,
|
|
|
|
|
|
.enter = &enter,
|
|
|
|
|
|
.leave = &leave,
|
|
|
|
|
|
.motion = &motion,
|
|
|
|
|
|
.drop = &drop,
|
|
|
|
|
|
.selection = &selection,
|
|
|
|
|
|
};
|
2019-07-11 17:02:21 +02:00
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
primary_offer(void *data,
|
|
|
|
|
|
struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer,
|
|
|
|
|
|
const char *mime_type)
|
|
|
|
|
|
{
|
2020-10-28 19:16:04 +01:00
|
|
|
|
LOG_DBG("primary offer: %s", mime_type);
|
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
select_mime_type_for_offer(mime_type, &seat->primary.mime_type);
|
2019-07-11 17:02:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
|
|
|
|
|
|
.offer = &primary_offer,
|
|
|
|
|
|
};
|
2020-10-28 19:16:04 +01:00
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
primary_offer_reset(struct wl_primary *primary)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (primary->data_offer != NULL) {
|
|
|
|
|
|
zwp_primary_selection_offer_v1_destroy(primary->data_offer);
|
|
|
|
|
|
primary->data_offer = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
primary->mime_type = DATA_OFFER_MIME_UNSET;
|
|
|
|
|
|
}
|
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)
|
|
|
|
|
|
{
|
2020-10-28 19:16:04 +01:00
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
|
primary_offer_reset(&seat->primary);
|
|
|
|
|
|
seat->primary.data_offer = offer;
|
|
|
|
|
|
zwp_primary_selection_offer_v1_add_listener(
|
|
|
|
|
|
offer, &primary_selection_offer_listener, seat);
|
2019-07-11 17:02:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
primary_selection(void *data,
|
|
|
|
|
|
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device,
|
2020-10-28 19:16:04 +01:00
|
|
|
|
struct zwp_primary_selection_offer_v1 *offer)
|
2019-07-11 17:02:21 +02:00
|
|
|
|
{
|
|
|
|
|
|
/* Selection offer from other client, for primary */
|
|
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
|
struct seat *seat = data;
|
2020-10-28 19:16:04 +01:00
|
|
|
|
if (offer == NULL)
|
|
|
|
|
|
primary_offer_reset(&seat->primary);
|
|
|
|
|
|
else
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(seat->primary.data_offer == offer);
|
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,
|
|
|
|
|
|
};
|
2022-04-25 19:59:23 +02:00
|
|
|
|
|