mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-03-15 05:33:58 -04:00
Merge branch 'scrollback-search'
This commit is contained in:
commit
e673bd4ab0
12 changed files with 940 additions and 88 deletions
171
README.md
171
README.md
|
|
@ -2,6 +2,127 @@
|
||||||
|
|
||||||
**foot** is a fast Wayland terminal emulator.
|
**foot** is a fast Wayland terminal emulator.
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
1. [Features](#features)
|
||||||
|
1. [Non-features](#non-features)
|
||||||
|
1. [Fonts](#fonts)
|
||||||
|
1. [Shortcuts](#shortcuts)
|
||||||
|
1. [Keyboard](#keyboard)
|
||||||
|
1. [Mouse](#mouse)
|
||||||
|
1. [Requirements](#requirements)
|
||||||
|
1. [Running](#running)
|
||||||
|
1. [Building](#building)
|
||||||
|
1. [Installing](#installing)
|
||||||
|
1. [Arch Linux](#arch-linux)
|
||||||
|
1. [Other](#other)
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Fast (**TODO** insert benchmark results here)
|
||||||
|
* Wayland native
|
||||||
|
* DE agnostic
|
||||||
|
* User configurable font fallback
|
||||||
|
* Scrollback search
|
||||||
|
* Color emoji support
|
||||||
|
|
||||||
|
|
||||||
|
## Non-features
|
||||||
|
|
||||||
|
This is a non-exhaustive list of things some people might consider
|
||||||
|
being important features (i.e. _"must-haves"_), that are unlikely to
|
||||||
|
ever be supported by foot.
|
||||||
|
|
||||||
|
* Tabs
|
||||||
|
* Multiple windows
|
||||||
|
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
**foot** supports all fonts that can be loaded by _freetype_,
|
||||||
|
including **bitmap** fonts and **color emoji** fonts.
|
||||||
|
|
||||||
|
Foot uses _fontconfig_ to locate and configure the font(s) to
|
||||||
|
use. Since fontconfig's fallback mechanism is imperfect, especially
|
||||||
|
for monospace fonts (it doesn't prefer monospace fonts even though the
|
||||||
|
requested font is one), foot allows you, the user, to configure the
|
||||||
|
fallback fonts to use.
|
||||||
|
|
||||||
|
This also means you can configure _each_ fallback font individually;
|
||||||
|
you want _that_ fallback font to use _this_ size, and you want that
|
||||||
|
_other_ fallback font to be _italic_? No problem!
|
||||||
|
|
||||||
|
If a glyph cannot be found in _any_ of the user configured fallback
|
||||||
|
fonts, _then_ fontconfig's list is used.
|
||||||
|
|
||||||
|
|
||||||
|
## Shortcuts
|
||||||
|
|
||||||
|
At the moment, all shortcuts are hard coded and cannot be changed. It
|
||||||
|
is **not** possible to define new key bindings.
|
||||||
|
|
||||||
|
|
||||||
|
### Keyboard
|
||||||
|
|
||||||
|
* <kbd>shift</kbd>+<kbd>page up</kbd>/<kbd>page down</kbd>
|
||||||
|
|
||||||
|
Scroll up/down in history
|
||||||
|
|
||||||
|
* <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>c</kbd>
|
||||||
|
|
||||||
|
Copy selected text to the _clipboard_
|
||||||
|
|
||||||
|
* <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>v</kbd>
|
||||||
|
|
||||||
|
Paste from _clipboard_
|
||||||
|
|
||||||
|
* <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>r</kbd>
|
||||||
|
|
||||||
|
Start a scrollback search
|
||||||
|
|
||||||
|
While doing a scrollback search, the following shortcuts are
|
||||||
|
available:
|
||||||
|
|
||||||
|
* <kbd>ctrl</kbd>+<kbd>r</kbd>
|
||||||
|
|
||||||
|
Search for next match
|
||||||
|
|
||||||
|
* <kbd>escape</kbd>
|
||||||
|
|
||||||
|
Cancel the search
|
||||||
|
|
||||||
|
* <kbd>ctrl</kbd>+<kbd>g</kbd>
|
||||||
|
|
||||||
|
Cancel the search (same as <kbd>escape</kbd>)
|
||||||
|
|
||||||
|
* <kbd>return</kbd>
|
||||||
|
|
||||||
|
Finish the search and put the current match to the primary selection
|
||||||
|
|
||||||
|
### Mouse
|
||||||
|
|
||||||
|
* <kbd>left</kbd> - **single-click**
|
||||||
|
|
||||||
|
Drag to select; when released, the selected text is copied to the
|
||||||
|
_primary_ selection. Note that this feature is normally **disabled**
|
||||||
|
whenever the client has enabled _mouse tracking_, but can be forced
|
||||||
|
by holding <kbd>shift</kbd>.
|
||||||
|
|
||||||
|
* <kbd>left</kbd> - **double-click**
|
||||||
|
|
||||||
|
Selects the _word_ (separated by spaces, period, comma, parenthesis
|
||||||
|
etc) under the pointer. Hold <kbd>ctrl</kbd> to select everything
|
||||||
|
under the pointer up to, and until, the next space characters.
|
||||||
|
|
||||||
|
* <kbd>left</kbd> - **triple-click**
|
||||||
|
|
||||||
|
Selects the entire row
|
||||||
|
|
||||||
|
* <kbd>middle</kbd>
|
||||||
|
|
||||||
|
Paste from _primary_ selection
|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|
@ -25,46 +146,24 @@ In addition to the dev variant of the packages above, you need:
|
||||||
* scdoc
|
* scdoc
|
||||||
|
|
||||||
|
|
||||||
## Fonts
|
## Installing
|
||||||
|
|
||||||
**foot** supports all fonts that can be loaded by freetype, including
|
### Arch Linux
|
||||||
**bitmap** fonts and **color emoji** fonts.
|
|
||||||
|
|
||||||
Foot uses its own font fallback mechanism, rather than relying on
|
Use [makepkg](https://wiki.archlinux.org/index.php/Makepkg) to build
|
||||||
fontconfig's fallback. This is because fontconfig is quite bad at
|
the bundled `PKGBUILD` (just run `makepkg` in the source root
|
||||||
selecting fallback fonts suitable for a terminal (i.e. monospaced
|
directory)..
|
||||||
fonts).
|
|
||||||
|
|
||||||
Instead, foot allows you to specify a font fallback list, where _each_
|
Note that it will do a profiling-guided build, and that this requires
|
||||||
font can be configured independently (for example, you can configure
|
a running wayland session since it needs to run an intermediate build
|
||||||
the size for each font individually).
|
of foot.
|
||||||
|
|
||||||
If a glyph cannot be found in _any_ of the user configured fallback
|
|
||||||
fonts, _then_ fontconfig's list is used.
|
|
||||||
|
|
||||||
|
|
||||||
## Shortcuts
|
### Other
|
||||||
|
|
||||||
At the moment, all shortcuts are hard coded and cannot be changed. It
|
Foot uses _meson_. If you are unfamiliar with it, the official
|
||||||
is **not** possible to define new key bindings.
|
[tutorial](https://mesonbuild.com/Tutorial.html) might be a good
|
||||||
|
starting point.
|
||||||
|
|
||||||
|
I also recommend taking a look at that bundled Arch `PKGBUILD` file,
|
||||||
### Keyboard
|
to see how it builds foot.
|
||||||
|
|
||||||
* `shift+page up/down` - scroll up/down in history
|
|
||||||
* `ctrl+shift+c` - copy selected text to the _clipboard_
|
|
||||||
* `ctrl+shift+v` - paste from _clipboard_
|
|
||||||
|
|
||||||
|
|
||||||
### Mouse
|
|
||||||
|
|
||||||
* `left` - single-click: drag to select; when released, the selected
|
|
||||||
text is copied to the _primary_ selection. Note that this feature is
|
|
||||||
normally disabled whenever the client has enabled mouse tracking,
|
|
||||||
but can be forced by holding `shift`.
|
|
||||||
* `left` - double-click: selects the _word_ (separated by spaces,
|
|
||||||
period, comma, parenthesis etc) under the pointer. Hold `ctrl` to
|
|
||||||
select everything under the pointer up to, and until, the next space
|
|
||||||
characters.
|
|
||||||
* `left` - triple-click: selects the entire row
|
|
||||||
* `middle` - paste from _primary_ selection
|
|
||||||
|
|
|
||||||
16
grid.h
16
grid.h
|
|
@ -7,12 +7,24 @@ void grid_swap_row(struct grid *grid, int row_a, int row_b, bool initialize);
|
||||||
struct row *grid_row_alloc(int cols, bool initialize);
|
struct row *grid_row_alloc(int cols, bool initialize);
|
||||||
void grid_row_free(struct row *row);
|
void grid_row_free(struct row *row);
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
grid_row_absolute(const struct grid *grid, int row_no)
|
||||||
|
{
|
||||||
|
return (grid->offset + row_no) & (grid->num_rows - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
grid_row_absolute_in_view(const struct grid *grid, int row_no)
|
||||||
|
{
|
||||||
|
return (grid->view + row_no) & (grid->num_rows - 1);
|
||||||
|
}
|
||||||
|
|
||||||
static inline struct row *
|
static inline struct row *
|
||||||
_grid_row_maybe_alloc(struct grid *grid, int row_no, bool alloc_if_null)
|
_grid_row_maybe_alloc(struct grid *grid, int row_no, bool alloc_if_null)
|
||||||
{
|
{
|
||||||
assert(grid->offset >= 0);
|
assert(grid->offset >= 0);
|
||||||
|
|
||||||
int real_row = (grid->offset + row_no) & (grid->num_rows - 1);
|
int real_row = grid_row_absolute(grid, row_no);
|
||||||
struct row *row = grid->rows[real_row];
|
struct row *row = grid->rows[real_row];
|
||||||
|
|
||||||
if (row == NULL && alloc_if_null) {
|
if (row == NULL && alloc_if_null) {
|
||||||
|
|
@ -41,7 +53,7 @@ grid_row_in_view(struct grid *grid, int row_no)
|
||||||
{
|
{
|
||||||
assert(grid->view >= 0);
|
assert(grid->view >= 0);
|
||||||
|
|
||||||
int real_row = (grid->view + row_no) & (grid->num_rows - 1);
|
int real_row = grid_row_absolute_in_view(grid, row_no);
|
||||||
struct row *row = grid->rows[real_row];
|
struct row *row = grid->rows[real_row];
|
||||||
|
|
||||||
assert(row != NULL);
|
assert(row != NULL);
|
||||||
|
|
|
||||||
33
input.c
33
input.c
|
|
@ -18,11 +18,12 @@
|
||||||
#define LOG_MODULE "input"
|
#define LOG_MODULE "input"
|
||||||
#define LOG_ENABLE_DBG 0
|
#define LOG_ENABLE_DBG 0
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "terminal.h"
|
|
||||||
#include "render.h"
|
|
||||||
#include "keymap.h"
|
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
|
#include "keymap.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "search.h"
|
||||||
#include "selection.h"
|
#include "selection.h"
|
||||||
|
#include "terminal.h"
|
||||||
#include "vt.h"
|
#include "vt.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -178,6 +179,12 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
||||||
xkb_mod_mask_t significant = ctrl | alt | shift | meta;
|
xkb_mod_mask_t significant = ctrl | alt | shift | meta;
|
||||||
xkb_mod_mask_t effective_mods = mods & ~consumed & significant;
|
xkb_mod_mask_t effective_mods = mods & ~consumed & significant;
|
||||||
|
|
||||||
|
if (term->is_searching) {
|
||||||
|
start_repeater(term, key - 8);
|
||||||
|
search_input(term, key, sym, effective_mods);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
for (size_t i = 0; i < 32; i++) {
|
for (size_t i = 0; i < 32; i++) {
|
||||||
if (mods & (1 << i)) {
|
if (mods & (1 << i)) {
|
||||||
|
|
@ -218,6 +225,12 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
||||||
|
|
||||||
else if (sym == XKB_KEY_V) {
|
else if (sym == XKB_KEY_V) {
|
||||||
selection_from_clipboard(term, serial);
|
selection_from_clipboard(term, serial);
|
||||||
|
term_reset_view(term);
|
||||||
|
found_map = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (sym == XKB_KEY_R) {
|
||||||
|
search_begin(term);
|
||||||
found_map = true;
|
found_map = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -243,11 +256,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
||||||
vt_to_slave(term, info->seq, strlen(info->seq));
|
vt_to_slave(term, info->seq, strlen(info->seq));
|
||||||
found_map = true;
|
found_map = true;
|
||||||
|
|
||||||
if (term->grid->view != term->grid->offset) {
|
term_reset_view(term);
|
||||||
term->grid->view = term->grid->offset;
|
|
||||||
term_damage_all(term);
|
|
||||||
}
|
|
||||||
|
|
||||||
selection_cancel(term);
|
selection_cancel(term);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -308,13 +317,9 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
||||||
vt_to_slave(term, "\x1b", 1);
|
vt_to_slave(term, "\x1b", 1);
|
||||||
|
|
||||||
vt_to_slave(term, buf, count);
|
vt_to_slave(term, buf, count);
|
||||||
|
|
||||||
if (term->grid->view != term->grid->offset) {
|
|
||||||
term->grid->view = term->grid->offset;
|
|
||||||
term_damage_all(term);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
term_reset_view(term);
|
||||||
selection_cancel(term);
|
selection_cancel(term);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,6 +441,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
||||||
|
|
||||||
struct terminal *term = data;
|
struct terminal *term = data;
|
||||||
|
|
||||||
|
search_cancel(term);
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case WL_POINTER_BUTTON_STATE_PRESSED: {
|
case WL_POINTER_BUTTON_STATE_PRESSED: {
|
||||||
/* Time since last click */
|
/* Time since last click */
|
||||||
|
|
|
||||||
122
main.c
122
main.c
|
|
@ -14,6 +14,7 @@
|
||||||
#include <sys/sysinfo.h>
|
#include <sys/sysinfo.h>
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
#include <freetype/tttables.h>
|
#include <freetype/tttables.h>
|
||||||
#include <wayland-client.h>
|
#include <wayland-client.h>
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
#include <xkbcommon/xkbcommon-compose.h>
|
#include <xkbcommon/xkbcommon-compose.h>
|
||||||
|
|
||||||
#include <xdg-output-unstable-v1.h>
|
#include <xdg-output-unstable-v1.h>
|
||||||
|
#include <xdg-decoration-unstable-v1.h>
|
||||||
|
|
||||||
#define LOG_MODULE "main"
|
#define LOG_MODULE "main"
|
||||||
#define LOG_ENABLE_DBG 0
|
#define LOG_ENABLE_DBG 0
|
||||||
|
|
@ -129,8 +131,10 @@ output_scale(void *data, struct wl_output *wl_output, int32_t factor)
|
||||||
{
|
{
|
||||||
struct monitor *mon = data;
|
struct monitor *mon = data;
|
||||||
mon->scale = factor;
|
mon->scale = factor;
|
||||||
|
|
||||||
|
int old_scale = mon->term->scale >= 1 ? mon->term->scale : 1;
|
||||||
render_reload_cursor_theme(mon->term);
|
render_reload_cursor_theme(mon->term);
|
||||||
render_resize(mon->term, mon->term->width, mon->term->height);
|
render_resize(mon->term, mon->term->width / old_scale, mon->term->height / old_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct wl_output_listener output_listener = {
|
static const struct wl_output_listener output_listener = {
|
||||||
|
|
@ -197,6 +201,11 @@ handle_global(void *data, struct wl_registry *registry,
|
||||||
term->wl.registry, name, &wl_compositor_interface, 4);
|
term->wl.registry, name, &wl_compositor_interface, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
|
||||||
|
term->wl.sub_compositor = wl_registry_bind(
|
||||||
|
term->wl.registry, name, &wl_subcompositor_interface, 1);
|
||||||
|
}
|
||||||
|
|
||||||
else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||||
term->wl.shm = wl_registry_bind(
|
term->wl.shm = wl_registry_bind(
|
||||||
term->wl.registry, name, &wl_shm_interface, 1);
|
term->wl.registry, name, &wl_shm_interface, 1);
|
||||||
|
|
@ -210,6 +219,10 @@ handle_global(void *data, struct wl_registry *registry,
|
||||||
xdg_wm_base_add_listener(term->wl.shell, &xdg_wm_base_listener, term);
|
xdg_wm_base_add_listener(term->wl.shell, &xdg_wm_base_listener, term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0)
|
||||||
|
term->wl.xdg_decoration_manager = wl_registry_bind(
|
||||||
|
term->wl.registry, name, &zxdg_decoration_manager_v1_interface, 1);
|
||||||
|
|
||||||
else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||||
term->wl.seat = wl_registry_bind(
|
term->wl.seat = wl_registry_bind(
|
||||||
term->wl.registry, name, &wl_seat_interface, 5);
|
term->wl.registry, name, &wl_seat_interface, 5);
|
||||||
|
|
@ -261,8 +274,9 @@ surface_enter(void *data, struct wl_surface *wl_surface,
|
||||||
tll_push_back(term->wl.on_outputs, &it->item);
|
tll_push_back(term->wl.on_outputs, &it->item);
|
||||||
|
|
||||||
/* Resize, since scale-to-use may have changed */
|
/* Resize, since scale-to-use may have changed */
|
||||||
|
int scale = term->scale >= 1 ? term->scale : 1;
|
||||||
render_reload_cursor_theme(term);
|
render_reload_cursor_theme(term);
|
||||||
render_resize(term, term->width, term->height);
|
render_resize(term, term->width / scale, term->height / scale);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -283,8 +297,9 @@ surface_leave(void *data, struct wl_surface *wl_surface,
|
||||||
tll_remove(term->wl.on_outputs, it);
|
tll_remove(term->wl.on_outputs, it);
|
||||||
|
|
||||||
/* Resize, since scale-to-use may have changed */
|
/* Resize, since scale-to-use may have changed */
|
||||||
|
int scale = term->scale >= 1 ? term->scale : 1;
|
||||||
render_reload_cursor_theme(term);
|
render_reload_cursor_theme(term);
|
||||||
render_resize(term, term->width, term->height);
|
render_resize(term, term->width / scale, term->height / scale);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,6 +350,30 @@ static const struct xdg_surface_listener xdg_surface_listener = {
|
||||||
.configure = &xdg_surface_configure,
|
.configure = &xdg_surface_configure,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
xdg_toplevel_decoration_configure(void *data,
|
||||||
|
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
|
||||||
|
uint32_t mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
|
||||||
|
LOG_ERR("unimplemented: client-side decorations");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
|
||||||
|
LOG_DBG("using server-side decorations");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG_ERR("unimplemented: unknown XDG toplevel decoration mode: %u", mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = {
|
||||||
|
.configure = &xdg_toplevel_decoration_configure,
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
||||||
{
|
{
|
||||||
|
|
@ -630,7 +669,7 @@ main(int argc, char *const *argv)
|
||||||
|
|
||||||
term.cell_width = (int)ceil(term.fextents.max_x_advance);
|
term.cell_width = (int)ceil(term.fextents.max_x_advance);
|
||||||
term.cell_height = (int)ceil(term.fextents.height);
|
term.cell_height = (int)ceil(term.fextents.height);
|
||||||
LOG_DBG("cell width=%d, height=%d", term.cell_width, term.cell_height);
|
LOG_INFO("cell width=%d, height=%d", term.cell_width, term.cell_height);
|
||||||
|
|
||||||
term.wl.display = wl_display_connect(NULL);
|
term.wl.display = wl_display_connect(NULL);
|
||||||
if (term.wl.display == NULL) {
|
if (term.wl.display == NULL) {
|
||||||
|
|
@ -718,6 +757,7 @@ main(int argc, char *const *argv)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Main window */
|
||||||
term.wl.surface = wl_compositor_create_surface(term.wl.compositor);
|
term.wl.surface = wl_compositor_create_surface(term.wl.compositor);
|
||||||
if (term.wl.surface == NULL) {
|
if (term.wl.surface == NULL) {
|
||||||
LOG_ERR("failed to create wayland surface");
|
LOG_ERR("failed to create wayland surface");
|
||||||
|
|
@ -735,6 +775,20 @@ main(int argc, char *const *argv)
|
||||||
xdg_toplevel_set_app_id(term.wl.xdg_toplevel, "foot");
|
xdg_toplevel_set_app_id(term.wl.xdg_toplevel, "foot");
|
||||||
term_set_window_title(&term, "foot");
|
term_set_window_title(&term, "foot");
|
||||||
|
|
||||||
|
/* Request server-side decorations */
|
||||||
|
term.wl.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||||
|
term.wl.xdg_decoration_manager, term.wl.xdg_toplevel);
|
||||||
|
zxdg_toplevel_decoration_v1_set_mode(
|
||||||
|
term.wl.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||||
|
zxdg_toplevel_decoration_v1_add_listener(
|
||||||
|
term.wl.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &term);
|
||||||
|
|
||||||
|
/* Scrollback search box */
|
||||||
|
term.wl.search_surface = wl_compositor_create_surface(term.wl.compositor);
|
||||||
|
term.wl.search_sub_surface = wl_subcompositor_get_subsurface(
|
||||||
|
term.wl.sub_compositor, term.wl.search_surface, term.wl.surface);
|
||||||
|
wl_subsurface_set_desync(term.wl.search_sub_surface);
|
||||||
|
|
||||||
wl_surface_commit(term.wl.surface);
|
wl_surface_commit(term.wl.surface);
|
||||||
wl_display_roundtrip(term.wl.display);
|
wl_display_roundtrip(term.wl.display);
|
||||||
|
|
||||||
|
|
@ -835,8 +889,9 @@ main(int argc, char *const *argv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool timeout_is_armed = false;
|
||||||
|
int timeout_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
|
||||||
|
|
||||||
int timeout_ms = -1;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
struct pollfd fds[] = {
|
struct pollfd fds[] = {
|
||||||
{.fd = wl_display_get_fd(term.wl.display), .events = POLLIN},
|
{.fd = wl_display_get_fd(term.wl.display), .events = POLLIN},
|
||||||
|
|
@ -844,10 +899,11 @@ main(int argc, char *const *argv)
|
||||||
{.fd = term.kbd.repeat.fd, .events = POLLIN},
|
{.fd = term.kbd.repeat.fd, .events = POLLIN},
|
||||||
{.fd = term.flash.fd, .events = POLLIN},
|
{.fd = term.flash.fd, .events = POLLIN},
|
||||||
{.fd = term.blink.fd, .events = POLLIN},
|
{.fd = term.blink.fd, .events = POLLIN},
|
||||||
|
{.fd = timeout_fd, .events = POLLIN},
|
||||||
};
|
};
|
||||||
|
|
||||||
wl_display_flush(term.wl.display);
|
wl_display_flush(term.wl.display);
|
||||||
int pret = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout_ms);
|
int pret = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||||
|
|
||||||
if (pret == -1) {
|
if (pret == -1) {
|
||||||
if (errno == EINTR)
|
if (errno == EINTR)
|
||||||
|
|
@ -857,13 +913,19 @@ main(int argc, char *const *argv)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pret == 0 || (timeout_ms != -1 && !(fds[1].revents & POLLIN))) {
|
if (fds[5].revents & POLLIN) {
|
||||||
/* Delayed rendering */
|
assert(timeout_is_armed);
|
||||||
render_refresh(&term);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset poll timeout to infinity */
|
uint64_t unused;
|
||||||
timeout_ms = -1;
|
ssize_t ret = read(timeout_fd, &unused, sizeof(unused));
|
||||||
|
|
||||||
|
if (ret < 0 && errno != EAGAIN)
|
||||||
|
LOG_ERRNO("failed to read timeout timer");
|
||||||
|
else if (ret > 0) {
|
||||||
|
timeout_is_armed = false;
|
||||||
|
render_refresh(&term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fds[0].revents & POLLIN) {
|
if (fds[0].revents & POLLIN) {
|
||||||
wl_display_dispatch(term.wl.display);
|
wl_display_dispatch(term.wl.display);
|
||||||
|
|
@ -915,7 +977,10 @@ main(int argc, char *const *argv)
|
||||||
* ourselves we just received keyboard input, and in
|
* ourselves we just received keyboard input, and in
|
||||||
* this case *not* delay rendering?
|
* this case *not* delay rendering?
|
||||||
*/
|
*/
|
||||||
timeout_ms = 1;
|
if (!timeout_is_armed) {
|
||||||
|
timerfd_settime(timeout_fd, 0, &(struct itimerspec){.it_value = {.tv_nsec = 1000000}}, NULL);
|
||||||
|
timeout_is_armed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -986,9 +1051,10 @@ main(int argc, char *const *argv)
|
||||||
render_refresh(&term);
|
render_refresh(&term);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(timeout_fd);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
mtx_lock(&term.render.workers.lock);
|
mtx_lock(&term.render.workers.lock);
|
||||||
assert(tll_length(term.render.workers.queue) == 0);
|
assert(tll_length(term.render.workers.queue) == 0);
|
||||||
|
|
@ -1014,12 +1080,6 @@ out:
|
||||||
if (term.wl.xdg_output_manager != NULL)
|
if (term.wl.xdg_output_manager != NULL)
|
||||||
zxdg_output_manager_v1_destroy(term.wl.xdg_output_manager);
|
zxdg_output_manager_v1_destroy(term.wl.xdg_output_manager);
|
||||||
|
|
||||||
if (term.render.frame_callback != NULL)
|
|
||||||
wl_callback_destroy(term.render.frame_callback);
|
|
||||||
if (term.wl.xdg_toplevel != NULL)
|
|
||||||
xdg_toplevel_destroy(term.wl.xdg_toplevel);
|
|
||||||
if (term.wl.xdg_surface != NULL)
|
|
||||||
xdg_surface_destroy(term.wl.xdg_surface);
|
|
||||||
free(term.wl.pointer.theme_name);
|
free(term.wl.pointer.theme_name);
|
||||||
if (term.wl.pointer.theme != NULL)
|
if (term.wl.pointer.theme != NULL)
|
||||||
wl_cursor_theme_destroy(term.wl.pointer.theme);
|
wl_cursor_theme_destroy(term.wl.pointer.theme);
|
||||||
|
|
@ -1049,12 +1109,28 @@ out:
|
||||||
zwp_primary_selection_device_manager_v1_destroy(term.wl.primary_selection_device_manager);
|
zwp_primary_selection_device_manager_v1_destroy(term.wl.primary_selection_device_manager);
|
||||||
if (term.wl.seat != NULL)
|
if (term.wl.seat != NULL)
|
||||||
wl_seat_destroy(term.wl.seat);
|
wl_seat_destroy(term.wl.seat);
|
||||||
if (term.wl.surface != NULL)
|
if (term.wl.search_sub_surface != NULL)
|
||||||
wl_surface_destroy(term.wl.surface);
|
wl_subsurface_destroy(term.wl.search_sub_surface);
|
||||||
|
if (term.wl.search_surface != NULL)
|
||||||
|
wl_surface_destroy(term.wl.search_surface);
|
||||||
|
if (term.render.frame_callback != NULL)
|
||||||
|
wl_callback_destroy(term.render.frame_callback);
|
||||||
|
if (term.wl.xdg_toplevel_decoration != NULL)
|
||||||
|
zxdg_toplevel_decoration_v1_destroy(term.wl.xdg_toplevel_decoration);
|
||||||
|
if (term.wl.xdg_decoration_manager != NULL)
|
||||||
|
zxdg_decoration_manager_v1_destroy(term.wl.xdg_decoration_manager);
|
||||||
|
if (term.wl.xdg_toplevel != NULL)
|
||||||
|
xdg_toplevel_destroy(term.wl.xdg_toplevel);
|
||||||
|
if (term.wl.xdg_surface != NULL)
|
||||||
|
xdg_surface_destroy(term.wl.xdg_surface);
|
||||||
if (term.wl.shell != NULL)
|
if (term.wl.shell != NULL)
|
||||||
xdg_wm_base_destroy(term.wl.shell);
|
xdg_wm_base_destroy(term.wl.shell);
|
||||||
|
if (term.wl.surface != NULL)
|
||||||
|
wl_surface_destroy(term.wl.surface);
|
||||||
if (term.wl.shm != NULL)
|
if (term.wl.shm != NULL)
|
||||||
wl_shm_destroy(term.wl.shm);
|
wl_shm_destroy(term.wl.shm);
|
||||||
|
if (term.wl.sub_compositor != NULL)
|
||||||
|
wl_subcompositor_destroy(term.wl.sub_compositor);
|
||||||
if (term.wl.compositor != NULL)
|
if (term.wl.compositor != NULL)
|
||||||
wl_compositor_destroy(term.wl.compositor);
|
wl_compositor_destroy(term.wl.compositor);
|
||||||
if (term.wl.registry != NULL)
|
if (term.wl.registry != NULL)
|
||||||
|
|
@ -1086,6 +1162,8 @@ out:
|
||||||
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++)
|
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++)
|
||||||
font_destroy(&term.fonts[i]);
|
font_destroy(&term.fonts[i]);
|
||||||
|
|
||||||
|
free(term.search.buf);
|
||||||
|
|
||||||
if (term.flash.fd != -1)
|
if (term.flash.fd != -1)
|
||||||
close(term.flash.fd);
|
close(term.flash.fd);
|
||||||
if (term.blink.fd != -1)
|
if (term.blink.fd != -1)
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ wl_proto_headers = []
|
||||||
wl_proto_src = []
|
wl_proto_src = []
|
||||||
foreach prot : [
|
foreach prot : [
|
||||||
wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml',
|
wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml',
|
||||||
|
wayland_protocols_datadir + '/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
|
||||||
wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml',
|
wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml',
|
||||||
wayland_protocols_datadir + '/unstable/primary-selection/primary-selection-unstable-v1.xml',
|
wayland_protocols_datadir + '/unstable/primary-selection/primary-selection-unstable-v1.xml',
|
||||||
]
|
]
|
||||||
|
|
@ -90,6 +91,7 @@ executable(
|
||||||
'main.c',
|
'main.c',
|
||||||
'osc.c', 'osc.h',
|
'osc.c', 'osc.h',
|
||||||
'render.c', 'render.h',
|
'render.c', 'render.h',
|
||||||
|
'search.c', 'search.h',
|
||||||
'selection.c', 'selection.h',
|
'selection.c', 'selection.h',
|
||||||
'shm.c', 'shm.h',
|
'shm.c', 'shm.h',
|
||||||
'slave.c', 'slave.h',
|
'slave.c', 'slave.h',
|
||||||
|
|
|
||||||
127
render.c
127
render.c
|
|
@ -72,14 +72,44 @@ pixman_color_dim(pixman_color_t *color)
|
||||||
color->blue /= 2;
|
color->blue /= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
pixman_color_dim_for_search(pixman_color_t *color)
|
||||||
|
{
|
||||||
|
color->red /= 3;
|
||||||
|
color->green /= 3;
|
||||||
|
color->blue /= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
font_baseline(const struct terminal *term)
|
||||||
|
{
|
||||||
|
assert(term->fextents.ascent >= 0);
|
||||||
|
assert(term->fextents.descent >= 0);
|
||||||
|
|
||||||
|
int diff = term->fextents.height - (term->fextents.ascent + term->fextents.descent);
|
||||||
|
assert(diff >= 0);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
LOG_INFO("height=%d, ascent=%d, descent=%d, diff=%d",
|
||||||
|
term->fextents.height,
|
||||||
|
term->fextents.ascent, term->fextents.descent,
|
||||||
|
diff);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return term->fextents.height - diff / 2 - term->fextents.descent;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
draw_bar(const struct terminal *term, pixman_image_t *pix,
|
draw_bar(const struct terminal *term, pixman_image_t *pix,
|
||||||
|
const struct font *font,
|
||||||
const pixman_color_t *color, int x, int y)
|
const pixman_color_t *color, int x, int y)
|
||||||
{
|
{
|
||||||
/* TODO: investigate if this is the best way */
|
int baseline = y + font_baseline(term) - term->fextents.ascent;
|
||||||
pixman_image_fill_rectangles(
|
pixman_image_fill_rectangles(
|
||||||
PIXMAN_OP_SRC, pix, color,
|
PIXMAN_OP_SRC, pix, color,
|
||||||
1, &(pixman_rectangle16_t){x, y, 1, term->cell_height});
|
1, &(pixman_rectangle16_t){
|
||||||
|
x, baseline,
|
||||||
|
font->underline.thickness, term->fextents.ascent + term->fextents.descent});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -87,7 +117,7 @@ draw_underline(const struct terminal *term, pixman_image_t *pix,
|
||||||
const struct font *font,
|
const struct font *font,
|
||||||
const pixman_color_t *color, int x, int y, int cols)
|
const pixman_color_t *color, int x, int y, int cols)
|
||||||
{
|
{
|
||||||
int baseline = y + term->fextents.height - term->fextents.descent;
|
int baseline = y + font_baseline(term);
|
||||||
int width = font->underline.thickness;
|
int width = font->underline.thickness;
|
||||||
int y_under = baseline - font->underline.position - width / 2;
|
int y_under = baseline - font->underline.position - width / 2;
|
||||||
|
|
||||||
|
|
@ -101,7 +131,7 @@ draw_strikeout(const struct terminal *term, pixman_image_t *pix,
|
||||||
const struct font *font,
|
const struct font *font,
|
||||||
const pixman_color_t *color, int x, int y, int cols)
|
const pixman_color_t *color, int x, int y, int cols)
|
||||||
{
|
{
|
||||||
int baseline = y + term->fextents.height - term->fextents.descent;
|
int baseline = y + font_baseline(term);
|
||||||
int width = font->strikeout.thickness;
|
int width = font->strikeout.thickness;
|
||||||
int y_strike = baseline - font->strikeout.position - width / 2;
|
int y_strike = baseline - font->strikeout.position - width / 2;
|
||||||
|
|
||||||
|
|
@ -204,6 +234,11 @@ render_cell(struct terminal *term, pixman_image_t *pix,
|
||||||
bg = color_hex_to_pixman(term->cursor_color.cursor);
|
bg = color_hex_to_pixman(term->cursor_color.cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (term->is_searching && !is_selected) {
|
||||||
|
pixman_color_dim_for_search(&fg);
|
||||||
|
pixman_color_dim_for_search(&bg);
|
||||||
|
}
|
||||||
|
|
||||||
struct font *font = attrs_to_font(term, &cell->attrs);
|
struct font *font = attrs_to_font(term, &cell->attrs);
|
||||||
const struct glyph *glyph = font_glyph_for_wc(font, cell->wc);
|
const struct glyph *glyph = font_glyph_for_wc(font, cell->wc);
|
||||||
|
|
||||||
|
|
@ -216,12 +251,16 @@ render_cell(struct terminal *term, pixman_image_t *pix,
|
||||||
|
|
||||||
/* Non-block cursors */
|
/* Non-block cursors */
|
||||||
if (has_cursor && !block_cursor) {
|
if (has_cursor && !block_cursor) {
|
||||||
pixman_color_t cursor_color = term->cursor_color.text >> 31
|
pixman_color_t cursor_color;
|
||||||
? color_hex_to_pixman(term->cursor_color.cursor)
|
if (term->cursor_color.text >> 31) {
|
||||||
: fg;
|
cursor_color = color_hex_to_pixman(term->cursor_color.cursor);
|
||||||
|
if (term->is_searching)
|
||||||
|
pixman_color_dim(&cursor_color);
|
||||||
|
} else
|
||||||
|
cursor_color = fg;
|
||||||
|
|
||||||
if (term->cursor_style == CURSOR_BAR)
|
if (term->cursor_style == CURSOR_BAR)
|
||||||
draw_bar(term, pix, &cursor_color, x, y);
|
draw_bar(term, pix, font, &cursor_color, x, y);
|
||||||
else if (term->cursor_style == CURSOR_UNDERLINE)
|
else if (term->cursor_style == CURSOR_UNDERLINE)
|
||||||
draw_underline(
|
draw_underline(
|
||||||
term, pix, attrs_to_font(term, &cell->attrs), &cursor_color,
|
term, pix, attrs_to_font(term, &cell->attrs), &cursor_color,
|
||||||
|
|
@ -435,9 +474,10 @@ grid_render(struct terminal *term)
|
||||||
term_damage_view(term);
|
term_damage_view(term);
|
||||||
|
|
||||||
/* If we resized the window, or is flashing, or just stopped flashing */
|
/* If we resized the window, or is flashing, or just stopped flashing */
|
||||||
if (term->render.last_buf != buf || term->flash.active || term->render.was_flashing) {
|
if (term->render.last_buf != buf ||
|
||||||
LOG_DBG("new buffer");
|
term->flash.active || term->render.was_flashing ||
|
||||||
|
term->is_searching != term->render.was_searching)
|
||||||
|
{
|
||||||
/* Fill area outside the cell grid with the default background color */
|
/* Fill area outside the cell grid with the default background color */
|
||||||
int rmargin = term->x_margin + term->cols * term->cell_width;
|
int rmargin = term->x_margin + term->cols * term->cell_width;
|
||||||
int bmargin = term->y_margin + term->rows * term->cell_height;
|
int bmargin = term->y_margin + term->rows * term->cell_height;
|
||||||
|
|
@ -445,8 +485,10 @@ grid_render(struct terminal *term)
|
||||||
int bmargin_height = term->height - bmargin;
|
int bmargin_height = term->height - bmargin;
|
||||||
|
|
||||||
uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
|
uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
|
||||||
|
|
||||||
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha);
|
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha);
|
||||||
|
if (term->is_searching)
|
||||||
|
pixman_color_dim(&bg);
|
||||||
|
|
||||||
pixman_image_fill_rectangles(
|
pixman_image_fill_rectangles(
|
||||||
PIXMAN_OP_SRC, pix, &bg, 4,
|
PIXMAN_OP_SRC, pix, &bg, 4,
|
||||||
(pixman_rectangle16_t[]){
|
(pixman_rectangle16_t[]){
|
||||||
|
|
@ -469,6 +511,7 @@ grid_render(struct terminal *term)
|
||||||
|
|
||||||
term->render.last_buf = buf;
|
term->render.last_buf = buf;
|
||||||
term->render.was_flashing = term->flash.active;
|
term->render.was_flashing = term->flash.active;
|
||||||
|
term->render.was_searching = term->is_searching;
|
||||||
}
|
}
|
||||||
|
|
||||||
tll_foreach(term->grid->scroll_damage, it) {
|
tll_foreach(term->grid->scroll_damage, it) {
|
||||||
|
|
@ -626,6 +669,7 @@ grid_render(struct terminal *term)
|
||||||
|
|
||||||
if (term->flash.active) {
|
if (term->flash.active) {
|
||||||
/* Note: alpha is pre-computed in each color component */
|
/* Note: alpha is pre-computed in each color component */
|
||||||
|
/* TODO: dim while searching */
|
||||||
pixman_image_fill_rectangles(
|
pixman_image_fill_rectangles(
|
||||||
PIXMAN_OP_OVER, pix,
|
PIXMAN_OP_OVER, pix,
|
||||||
&(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff},
|
&(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff},
|
||||||
|
|
@ -669,6 +713,65 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
|
||||||
grid_render(term);
|
grid_render(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
render_search_box(struct terminal *term)
|
||||||
|
{
|
||||||
|
assert(term->wl.search_sub_surface != NULL);
|
||||||
|
|
||||||
|
/* TODO: at least sway allows the subsurface to extend outside the
|
||||||
|
* main window. Do we want that? */
|
||||||
|
const int scale = term->scale >= 1 ? term->scale : 1;
|
||||||
|
const int margin = scale * 3;
|
||||||
|
const int width = 2 * margin + max(20, term->search.len) * term->cell_width;
|
||||||
|
const int height = 2 * margin + 1 * term->cell_height;
|
||||||
|
|
||||||
|
struct buffer *buf = shm_get_buffer(term->wl.shm, width, height, 1);
|
||||||
|
|
||||||
|
/* Background - yellow on empty/match, red on mismatch */
|
||||||
|
pixman_color_t color = color_hex_to_pixman(
|
||||||
|
term->search.match_len == term->search.len ? 0xffff00 : 0xff0000);
|
||||||
|
|
||||||
|
pixman_image_fill_rectangles(
|
||||||
|
PIXMAN_OP_SRC, buf->pix, &color,
|
||||||
|
1, &(pixman_rectangle16_t){0, 0, width, height});
|
||||||
|
|
||||||
|
struct font *font = &term->fonts[0];
|
||||||
|
int x = margin;
|
||||||
|
int y = margin;
|
||||||
|
pixman_color_t fg = color_hex_to_pixman(0x000000);
|
||||||
|
|
||||||
|
/* Text (what the user entered - *not* match(es)) */
|
||||||
|
for (size_t i = 0; i < term->search.len; i++) {
|
||||||
|
if (i == term->search.cursor)
|
||||||
|
draw_bar(term, buf->pix, font, &fg, x, y);
|
||||||
|
|
||||||
|
const struct glyph *glyph = font_glyph_for_wc(font, term->search.buf[i]);
|
||||||
|
if (glyph == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
pixman_image_t *src = pixman_image_create_solid_fill(&fg);
|
||||||
|
pixman_image_composite32(
|
||||||
|
PIXMAN_OP_OVER, src, glyph->pix, buf->pix, 0, 0, 0, 0,
|
||||||
|
x + glyph->x, y + term->fextents.ascent - glyph->y,
|
||||||
|
glyph->width, glyph->height);
|
||||||
|
pixman_image_unref(src);
|
||||||
|
|
||||||
|
x += term->cell_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (term->search.cursor >= term->search.len)
|
||||||
|
draw_bar(term, buf->pix, font, &fg, x, y);
|
||||||
|
|
||||||
|
wl_subsurface_set_position(
|
||||||
|
term->wl.search_sub_surface,
|
||||||
|
term->width - width - margin, term->height - height - margin);
|
||||||
|
|
||||||
|
wl_surface_damage_buffer(term->wl.search_surface, 0, 0, width, height);
|
||||||
|
wl_surface_attach(term->wl.search_surface, buf->wl_buf, 0, 0);
|
||||||
|
wl_surface_set_buffer_scale(term->wl.search_surface, scale);
|
||||||
|
wl_surface_commit(term->wl.search_surface);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
reflow(struct row **new_grid, int new_cols, int new_rows,
|
reflow(struct row **new_grid, int new_cols, int new_rows,
|
||||||
struct row *const *old_grid, int old_cols, int old_rows)
|
struct row *const *old_grid, int old_cols, int old_rows)
|
||||||
|
|
|
||||||
1
render.h
1
render.h
|
|
@ -10,6 +10,7 @@ void render_resize(struct terminal *term, int width, int height);
|
||||||
void render_set_title(struct terminal *term, const char *title);
|
void render_set_title(struct terminal *term, const char *title);
|
||||||
void render_refresh(struct terminal *term);
|
void render_refresh(struct terminal *term);
|
||||||
|
|
||||||
|
void render_search_box(struct terminal *term);
|
||||||
bool render_reload_cursor_theme(struct terminal *term);
|
bool render_reload_cursor_theme(struct terminal *term);
|
||||||
void render_update_cursor_surface(struct terminal *term);
|
void render_update_cursor_surface(struct terminal *term);
|
||||||
|
|
||||||
|
|
|
||||||
496
search.c
Normal file
496
search.c
Normal file
|
|
@ -0,0 +1,496 @@
|
||||||
|
#include "search.h"
|
||||||
|
|
||||||
|
#include <wchar.h>
|
||||||
|
#include <wctype.h>
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include <xkbcommon/xkbcommon-compose.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "search"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "grid.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "selection.h"
|
||||||
|
#include "shm.h"
|
||||||
|
|
||||||
|
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||||
|
|
||||||
|
static void
|
||||||
|
search_cancel_keep_selection(struct terminal *term)
|
||||||
|
{
|
||||||
|
wl_surface_attach(term->wl.search_surface, NULL, 0, 0);
|
||||||
|
wl_surface_commit(term->wl.search_surface);
|
||||||
|
|
||||||
|
free(term->search.buf);
|
||||||
|
term->search.buf = NULL;
|
||||||
|
term->search.len = 0;
|
||||||
|
term->search.sz = 0;
|
||||||
|
term->search.cursor = 0;
|
||||||
|
term->search.match = (struct coord){-1, -1};
|
||||||
|
term->search.match_len = 0;
|
||||||
|
term->is_searching = false;
|
||||||
|
|
||||||
|
render_refresh(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
search_begin(struct terminal *term)
|
||||||
|
{
|
||||||
|
LOG_DBG("search: begin");
|
||||||
|
|
||||||
|
search_cancel_keep_selection(term);
|
||||||
|
selection_cancel(term);
|
||||||
|
|
||||||
|
term->search.original_view = term->grid->view;
|
||||||
|
term->search.view_followed_offset = term->grid->view == term->grid->offset;
|
||||||
|
term->is_searching = true;
|
||||||
|
|
||||||
|
render_search_box(term);
|
||||||
|
render_refresh(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
search_cancel(struct terminal *term)
|
||||||
|
{
|
||||||
|
if (!term->is_searching)
|
||||||
|
return;
|
||||||
|
|
||||||
|
search_cancel_keep_selection(term);
|
||||||
|
selection_cancel(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
search_update(struct terminal *term)
|
||||||
|
{
|
||||||
|
bool backward = term->search.direction == SEARCH_BACKWARD;
|
||||||
|
term->search.direction = SEARCH_BACKWARD;
|
||||||
|
|
||||||
|
if (term->search.len == 0) {
|
||||||
|
term->search.match = (struct coord){-1, -1};
|
||||||
|
term->search.match_len = 0;
|
||||||
|
selection_cancel(term);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start_row = term->search.match.row;
|
||||||
|
int start_col = term->search.match.col;
|
||||||
|
size_t len __attribute__((unused)) = term->search.match_len;
|
||||||
|
|
||||||
|
assert((len == 0 && start_row == -1 && start_col == -1) ||
|
||||||
|
(len > 0 && start_row >= 0 && start_col >= 0));
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
if (backward) {
|
||||||
|
start_row = grid_row_absolute_in_view(term->grid, term->rows - 1);
|
||||||
|
start_col = term->cols - 1;
|
||||||
|
} else {
|
||||||
|
start_row = grid_row_absolute_in_view(term->grid, 0);
|
||||||
|
start_col = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("search: update: %s: starting at row=%d col=%d (offset = %d, view = %d)",
|
||||||
|
backward ? "backward" : "forward", start_row, start_col,
|
||||||
|
term->grid->offset, term->grid->view);
|
||||||
|
|
||||||
|
#define ROW_DEC(_r) ((_r) = ((_r) - 1 + term->grid->num_rows) % term->grid->num_rows)
|
||||||
|
#define ROW_INC(_r) ((_r) = ((_r) + 1) % term->grid->num_rows)
|
||||||
|
|
||||||
|
/* Scan backward from current end-of-output */
|
||||||
|
/* TODO: don't search "scrollback" in alt screen? */
|
||||||
|
for (size_t r = 0;
|
||||||
|
r < term->grid->num_rows;
|
||||||
|
backward ? ROW_DEC(start_row) : ROW_INC(start_row), r++)
|
||||||
|
{
|
||||||
|
const struct row *row = term->grid->rows[start_row];
|
||||||
|
if (row == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (;
|
||||||
|
backward ? start_col >= 0 : start_col < term->cols;
|
||||||
|
backward ? start_col-- : start_col++)
|
||||||
|
{
|
||||||
|
if (wcsncasecmp(&row->cells[start_col].wc, term->search.buf, 1) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Got a match on the first letter. Now we'll see if the
|
||||||
|
* rest of the search buffer matches.
|
||||||
|
*/
|
||||||
|
|
||||||
|
LOG_DBG("search: initial match at row=%d, col=%d", start_row, start_col);
|
||||||
|
|
||||||
|
int end_row = start_row;
|
||||||
|
int end_col = start_col;
|
||||||
|
size_t match_len = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < term->search.len; i++, match_len++) {
|
||||||
|
if (wcsncasecmp(&row->cells[end_col].wc, &term->search.buf[i], 1) != 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (++end_col >= term->cols) {
|
||||||
|
if (end_row + 1 > grid_row_absolute(term->grid, term->grid->offset + term->rows - 1)) {
|
||||||
|
/* Don't continue past end of the world */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_row++;
|
||||||
|
end_col = 0;
|
||||||
|
row = term->grid->rows[end_row];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match_len != term->search.len) {
|
||||||
|
/* Didn't match (completely) */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We matched the entire buffer. Move view to ensure the
|
||||||
|
* match is visible, create a selection and return.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int old_view = term->grid->view;
|
||||||
|
int new_view = start_row;
|
||||||
|
|
||||||
|
/* Prevent scrolling in uninitialized rows */
|
||||||
|
bool all_initialized = false;
|
||||||
|
do {
|
||||||
|
all_initialized = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < term->rows; i++) {
|
||||||
|
int row_no = (new_view + i) % term->grid->num_rows;
|
||||||
|
if (term->grid->rows[row_no] == NULL) {
|
||||||
|
all_initialized = false;
|
||||||
|
new_view--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (!all_initialized);
|
||||||
|
|
||||||
|
/* Don't scroll past scrollback history */
|
||||||
|
int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows;
|
||||||
|
if (end >= term->grid->offset) {
|
||||||
|
/* Not wrapped */
|
||||||
|
if (new_view >= term->grid->offset && new_view <= end)
|
||||||
|
new_view = term->grid->offset;
|
||||||
|
} else {
|
||||||
|
if (new_view >= term->grid->offset || new_view <= end)
|
||||||
|
new_view = term->grid->offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update view */
|
||||||
|
term->grid->view = new_view;
|
||||||
|
if (new_view != old_view)
|
||||||
|
term_damage_view(term);
|
||||||
|
|
||||||
|
/* Selection endpoint is inclusive */
|
||||||
|
if (--end_col < 0) {
|
||||||
|
end_col = term->cols - 1;
|
||||||
|
start_row--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Begin a new selection if the start coords changed */
|
||||||
|
if (start_row != term->search.match.row ||
|
||||||
|
start_col != term->search.match.col)
|
||||||
|
{
|
||||||
|
int selection_row = start_row - term->grid->view;
|
||||||
|
while (selection_row < 0)
|
||||||
|
selection_row += term->grid->num_rows;
|
||||||
|
|
||||||
|
assert(selection_row >= 0 &&
|
||||||
|
selection_row < term->grid->num_rows);
|
||||||
|
selection_start(term, start_col, selection_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update selection endpoint */
|
||||||
|
{
|
||||||
|
int selection_row = end_row - term->grid->view;
|
||||||
|
while (selection_row < 0)
|
||||||
|
selection_row += term->grid->num_rows;
|
||||||
|
|
||||||
|
assert(selection_row >= 0 &&
|
||||||
|
selection_row < term->grid->num_rows);
|
||||||
|
selection_update(term, end_col, selection_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update match state */
|
||||||
|
term->search.match.row = start_row;
|
||||||
|
term->search.match.col = start_col;
|
||||||
|
term->search.match_len = match_len;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_col = backward ? term->cols - 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No match */
|
||||||
|
LOG_DBG("no match");
|
||||||
|
term->search.match = (struct coord){-1, -1};
|
||||||
|
term->search.match_len = 0;
|
||||||
|
selection_cancel(term);
|
||||||
|
#undef ROW_DEC
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
distance_next_word(const struct terminal *term)
|
||||||
|
{
|
||||||
|
size_t cursor = term->search.cursor;
|
||||||
|
|
||||||
|
/* First eat non-whitespace. This is the word we're skipping past */
|
||||||
|
while (cursor < term->search.len) {
|
||||||
|
if (iswspace(term->search.buf[cursor++]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(cursor == term->search.len || iswspace(term->search.buf[cursor - 1]));
|
||||||
|
|
||||||
|
/* Now skip past whitespace, so that we end up at the beginning of
|
||||||
|
* the next word */
|
||||||
|
while (cursor < term->search.len) {
|
||||||
|
if (!iswspace(term->search.buf[cursor++]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("cursor = %zu, iswspace() = %d", cursor, iswspace(term->search.buf[cursor - 1]));
|
||||||
|
assert(cursor == term->search.len || !iswspace(term->search.buf[cursor - 1]));
|
||||||
|
|
||||||
|
if (cursor < term->search.len && !iswspace(term->search.buf[cursor]))
|
||||||
|
cursor--;
|
||||||
|
|
||||||
|
return cursor - term->search.cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
distance_prev_word(const struct terminal *term)
|
||||||
|
{
|
||||||
|
int cursor = term->search.cursor;
|
||||||
|
|
||||||
|
/* First, eat whitespace prefix */
|
||||||
|
while (cursor > 0) {
|
||||||
|
if (!iswspace(term->search.buf[--cursor]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(cursor == 0 || !iswspace(term->search.buf[cursor]));
|
||||||
|
|
||||||
|
/* Now eat non-whitespace. This is the word we're skipping past */
|
||||||
|
while (cursor > 0) {
|
||||||
|
if (iswspace(term->search.buf[--cursor]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(cursor == 0 || iswspace(term->search.buf[cursor]));
|
||||||
|
if (cursor > 0 && iswspace(term->search.buf[cursor]))
|
||||||
|
cursor++;
|
||||||
|
|
||||||
|
return term->search.cursor - cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods)
|
||||||
|
{
|
||||||
|
LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x", sym, sym, mods);
|
||||||
|
|
||||||
|
const xkb_mod_mask_t ctrl = 1 << term->kbd.mod_ctrl;
|
||||||
|
const xkb_mod_mask_t alt = 1 << term->kbd.mod_alt;
|
||||||
|
//const xkb_mod_mask_t shift = 1 << term->kbd.mod_shift;
|
||||||
|
//const xkb_mod_mask_t meta = 1 << term->kbd.mod_meta;
|
||||||
|
|
||||||
|
enum xkb_compose_status compose_status = xkb_compose_state_get_status(
|
||||||
|
term->kbd.xkb_compose_state);
|
||||||
|
|
||||||
|
/* Cancel search */
|
||||||
|
if ((mods == 0 && sym == XKB_KEY_Escape) ||
|
||||||
|
(mods == ctrl && sym == XKB_KEY_g))
|
||||||
|
{
|
||||||
|
if (term->search.view_followed_offset)
|
||||||
|
term->grid->view = term->grid->offset;
|
||||||
|
else
|
||||||
|
term->grid->view = term->search.original_view;
|
||||||
|
term_damage_view(term);
|
||||||
|
search_cancel(term);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "Commit" search - copy selection to primary and cancel search */
|
||||||
|
else if (mods == 0 && sym == XKB_KEY_Return) {
|
||||||
|
selection_finalize(term, term->input_serial);
|
||||||
|
search_cancel_keep_selection(term);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (mods == ctrl && sym == XKB_KEY_r) {
|
||||||
|
if (term->search.match_len > 0) {
|
||||||
|
int new_col = term->search.match.col - 1;
|
||||||
|
int new_row = term->search.match.row;
|
||||||
|
|
||||||
|
if (new_col < 0) {
|
||||||
|
new_col = term->cols - 1;
|
||||||
|
new_row--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_row >= 0) {
|
||||||
|
term->search.match.col = new_col;
|
||||||
|
term->search.match.row = new_row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (mods == ctrl && sym == XKB_KEY_s) {
|
||||||
|
if (term->search.match_len > 0) {
|
||||||
|
int new_col = term->search.match.col + 1;
|
||||||
|
int new_row = term->search.match.row;
|
||||||
|
|
||||||
|
if (new_col >= term->cols) {
|
||||||
|
new_col = 0;
|
||||||
|
new_row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_row < term->grid->num_rows) {
|
||||||
|
term->search.match.col = new_col;
|
||||||
|
term->search.match.row = new_row;
|
||||||
|
term->search.direction = SEARCH_FORWARD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (mods == 0 && sym == XKB_KEY_Left) {
|
||||||
|
if (term->search.cursor > 0)
|
||||||
|
term->search.cursor--;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((mods == ctrl && sym == XKB_KEY_Left) ||
|
||||||
|
(mods == alt && sym == XKB_KEY_b))
|
||||||
|
{
|
||||||
|
size_t diff = distance_prev_word(term);
|
||||||
|
term->search.cursor -= diff;
|
||||||
|
assert(term->search.cursor >= 0);
|
||||||
|
assert(term->search.cursor <= term->search.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (mods == 0 && sym == XKB_KEY_Right) {
|
||||||
|
if (term->search.cursor < term->search.len)
|
||||||
|
term->search.cursor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((mods == ctrl && sym == XKB_KEY_Right) ||
|
||||||
|
(mods == alt && sym == XKB_KEY_f))
|
||||||
|
{
|
||||||
|
size_t diff = distance_next_word(term);
|
||||||
|
term->search.cursor += diff;
|
||||||
|
assert(term->search.cursor >= 0);
|
||||||
|
assert(term->search.cursor <= term->search.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((mods == 0 && sym == XKB_KEY_Home) ||
|
||||||
|
(mods == ctrl && sym == XKB_KEY_a))
|
||||||
|
term->search.cursor = 0;
|
||||||
|
|
||||||
|
else if ((mods == 0 && sym == XKB_KEY_End) ||
|
||||||
|
(mods == ctrl && sym == XKB_KEY_e))
|
||||||
|
term->search.cursor = term->search.len;
|
||||||
|
|
||||||
|
else if (mods == 0 && sym == XKB_KEY_BackSpace) {
|
||||||
|
if (term->search.cursor > 0) {
|
||||||
|
memmove(
|
||||||
|
&term->search.buf[term->search.cursor - 1],
|
||||||
|
&term->search.buf[term->search.cursor],
|
||||||
|
(term->search.len - term->search.cursor) * sizeof(wchar_t));
|
||||||
|
term->search.cursor--;
|
||||||
|
term->search.buf[--term->search.len] = L'\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((mods == alt || mods == ctrl) && sym == XKB_KEY_BackSpace) {
|
||||||
|
size_t diff = distance_prev_word(term);
|
||||||
|
size_t old_cursor = term->search.cursor;
|
||||||
|
size_t new_cursor = old_cursor - diff;
|
||||||
|
|
||||||
|
memmove(&term->search.buf[new_cursor],
|
||||||
|
&term->search.buf[old_cursor],
|
||||||
|
(term->search.len - old_cursor) * sizeof(wchar_t));
|
||||||
|
|
||||||
|
term->search.len -= diff;
|
||||||
|
term->search.cursor = new_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((mods == alt && sym == XKB_KEY_d) ||
|
||||||
|
(mods == ctrl && sym == XKB_KEY_Delete)) {
|
||||||
|
size_t diff = distance_next_word(term);
|
||||||
|
size_t cursor = term->search.cursor;
|
||||||
|
|
||||||
|
memmove(&term->search.buf[cursor],
|
||||||
|
&term->search.buf[cursor + diff],
|
||||||
|
(term->search.len - (cursor + diff)) * sizeof(wchar_t));
|
||||||
|
|
||||||
|
term->search.len -= diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (mods == 0 && sym == XKB_KEY_Delete) {
|
||||||
|
if (term->search.cursor < term->search.len) {
|
||||||
|
memmove(
|
||||||
|
&term->search.buf[term->search.cursor],
|
||||||
|
&term->search.buf[term->search.cursor + 1],
|
||||||
|
(term->search.len - term->search.cursor - 1) * sizeof(wchar_t));
|
||||||
|
term->search.buf[--term->search.len] = L'\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
uint8_t buf[64] = {0};
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
if (compose_status == XKB_COMPOSE_COMPOSED) {
|
||||||
|
count = xkb_compose_state_get_utf8(
|
||||||
|
term->kbd.xkb_compose_state, (char *)buf, sizeof(buf));
|
||||||
|
xkb_compose_state_reset(term->kbd.xkb_compose_state);
|
||||||
|
} else {
|
||||||
|
count = xkb_state_key_get_utf8(
|
||||||
|
term->kbd.xkb_state, key, (char *)buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *src = (const char *)buf;
|
||||||
|
mbstate_t ps = {0};
|
||||||
|
size_t wchars = mbsnrtowcs(NULL, &src, count, 0, &ps);
|
||||||
|
|
||||||
|
if (wchars == -1) {
|
||||||
|
LOG_ERRNO("failed to convert %.*s to wchars", count, buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (term->search.len + wchars >= term->search.sz) {
|
||||||
|
size_t new_sz = term->search.sz == 0 ? 64 : term->search.sz * 2;
|
||||||
|
wchar_t *new_buf = realloc(term->search.buf, new_sz * sizeof(term->search.buf[0]));
|
||||||
|
|
||||||
|
if (new_buf == NULL) {
|
||||||
|
LOG_ERRNO("failed to resize search buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
term->search.buf = new_buf;
|
||||||
|
term->search.sz = new_sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(term->search.len + wchars < term->search.sz);
|
||||||
|
|
||||||
|
memmove(&term->search.buf[term->search.cursor + wchars],
|
||||||
|
&term->search.buf[term->search.cursor],
|
||||||
|
(term->search.len - term->search.cursor) * sizeof(wchar_t));
|
||||||
|
|
||||||
|
memset(&ps, 0, sizeof(ps));
|
||||||
|
mbsnrtowcs(&term->search.buf[term->search.cursor], &src, count,
|
||||||
|
wchars, &ps);
|
||||||
|
|
||||||
|
term->search.len += wchars;
|
||||||
|
term->search.cursor += wchars;
|
||||||
|
term->search.buf[term->search.len] = L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("search: buffer: %S", term->search.buf);
|
||||||
|
search_update(term);
|
||||||
|
render_refresh(term);
|
||||||
|
render_search_box(term);
|
||||||
|
}
|
||||||
8
search.h
Normal file
8
search.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
void search_begin(struct terminal *term);
|
||||||
|
void search_cancel(struct terminal *term);
|
||||||
|
void search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods);
|
||||||
|
|
@ -20,7 +20,10 @@
|
||||||
bool
|
bool
|
||||||
selection_enabled(const struct terminal *term)
|
selection_enabled(const struct terminal *term)
|
||||||
{
|
{
|
||||||
return term->mouse_tracking == MOUSE_NONE || term->kbd.shift;
|
return
|
||||||
|
term->mouse_tracking == MOUSE_NONE ||
|
||||||
|
term->kbd.shift ||
|
||||||
|
term->is_searching;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|
|
||||||
10
terminal.c
10
terminal.c
|
|
@ -362,6 +362,16 @@ term_reverse_index(struct terminal *term)
|
||||||
term_cursor_up(term, 1);
|
term_cursor_up(term, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
term_reset_view(struct terminal *term)
|
||||||
|
{
|
||||||
|
if (term->grid->view == term->grid->offset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
term->grid->view = term->grid->offset;
|
||||||
|
term_damage_view(term);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
term_restore_cursor(struct terminal *term)
|
term_restore_cursor(struct terminal *term)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
37
terminal.h
37
terminal.h
|
|
@ -41,15 +41,20 @@ struct wayland {
|
||||||
struct wl_display *display;
|
struct wl_display *display;
|
||||||
struct wl_registry *registry;
|
struct wl_registry *registry;
|
||||||
struct wl_compositor *compositor;
|
struct wl_compositor *compositor;
|
||||||
struct wl_surface *surface;
|
struct wl_subcompositor *sub_compositor;
|
||||||
struct wl_shm *shm;
|
struct wl_shm *shm;
|
||||||
|
|
||||||
struct wl_seat *seat;
|
struct wl_seat *seat;
|
||||||
|
struct wl_keyboard *keyboard;
|
||||||
struct zxdg_output_manager_v1 *xdg_output_manager;
|
struct zxdg_output_manager_v1 *xdg_output_manager;
|
||||||
|
|
||||||
|
/* Clipboard */
|
||||||
struct wl_data_device_manager *data_device_manager;
|
struct wl_data_device_manager *data_device_manager;
|
||||||
struct wl_data_device *data_device;
|
struct wl_data_device *data_device;
|
||||||
struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager;
|
struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager;
|
||||||
struct zwp_primary_selection_device_v1 *primary_selection_device;
|
struct zwp_primary_selection_device_v1 *primary_selection_device;
|
||||||
struct wl_keyboard *keyboard;
|
|
||||||
|
/* Cursor */
|
||||||
struct {
|
struct {
|
||||||
struct wl_pointer *pointer;
|
struct wl_pointer *pointer;
|
||||||
uint32_t serial;
|
uint32_t serial;
|
||||||
|
|
@ -60,9 +65,20 @@ struct wayland {
|
||||||
int size;
|
int size;
|
||||||
char *theme_name;
|
char *theme_name;
|
||||||
} pointer;
|
} pointer;
|
||||||
|
|
||||||
|
/* Main window */
|
||||||
|
struct wl_surface *surface;
|
||||||
struct xdg_wm_base *shell;
|
struct xdg_wm_base *shell;
|
||||||
struct xdg_surface *xdg_surface;
|
struct xdg_surface *xdg_surface;
|
||||||
struct xdg_toplevel *xdg_toplevel;
|
struct xdg_toplevel *xdg_toplevel;
|
||||||
|
|
||||||
|
struct zxdg_decoration_manager_v1 *xdg_decoration_manager;
|
||||||
|
struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration;
|
||||||
|
|
||||||
|
/* Scrollback search */
|
||||||
|
struct wl_surface *search_surface;
|
||||||
|
struct wl_subsurface *search_sub_surface;
|
||||||
|
|
||||||
bool have_argb8888;
|
bool have_argb8888;
|
||||||
tll(struct monitor) monitors; /* All available outputs */
|
tll(struct monitor) monitors; /* All available outputs */
|
||||||
tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */
|
tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */
|
||||||
|
|
@ -331,6 +347,20 @@ struct terminal {
|
||||||
struct primary primary;
|
struct primary primary;
|
||||||
} selection;
|
} selection;
|
||||||
|
|
||||||
|
bool is_searching;
|
||||||
|
struct {
|
||||||
|
wchar_t *buf;
|
||||||
|
size_t len;
|
||||||
|
size_t sz;
|
||||||
|
size_t cursor;
|
||||||
|
enum { SEARCH_BACKWARD, SEARCH_FORWARD} direction;
|
||||||
|
|
||||||
|
int original_view;
|
||||||
|
bool view_followed_offset;
|
||||||
|
struct coord match;
|
||||||
|
size_t match_len;
|
||||||
|
} search;
|
||||||
|
|
||||||
struct grid normal;
|
struct grid normal;
|
||||||
struct grid alt;
|
struct grid alt;
|
||||||
struct grid *grid;
|
struct grid *grid;
|
||||||
|
|
@ -368,6 +398,7 @@ struct terminal {
|
||||||
|
|
||||||
struct buffer *last_buf; /* Buffer we rendered to last time */
|
struct buffer *last_buf; /* Buffer we rendered to last time */
|
||||||
bool was_flashing; /* Flash was active last time we rendered */
|
bool was_flashing; /* Flash was active last time we rendered */
|
||||||
|
bool was_searching;
|
||||||
} render;
|
} render;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -379,6 +410,8 @@ void term_damage_rows_in_view(struct terminal *term, int start, int end);
|
||||||
void term_damage_all(struct terminal *term);
|
void term_damage_all(struct terminal *term);
|
||||||
void term_damage_view(struct terminal *term);
|
void term_damage_view(struct terminal *term);
|
||||||
|
|
||||||
|
void term_reset_view(struct terminal *term);
|
||||||
|
|
||||||
void term_damage_scroll(
|
void term_damage_scroll(
|
||||||
struct terminal *term, enum damage_type damage_type,
|
struct terminal *term, enum damage_type damage_type,
|
||||||
struct scroll_region region, int lines);
|
struct scroll_region region, int lines);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue