diff --git a/CHANGELOG.md b/CHANGELOG.md index a9bcb1ab..53c2462a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.4.2](#1.4.2) * [1.4.1](#1-4-1) * [1.4.0](#1-4-0) @@ -9,6 +10,53 @@ * [1.2.1](#1-2-1) * [1.2.0](#1-2-0) +## Unreleased +### Added + +* Section to [README.md](README.md) describing how to programmatically + identify foot. +* [LICENSE](LICENSE), [README.md](README.md) and + [CHANGELOG.md](CHANGELOG.md) are now installed to + `${datadir}/doc/foot`. +* Support for escaping quotes in **pipe-visible** and + **pipe-scrollback** commands. + + +### Changed + +* Primary DA to no longer indicate support for _Selective Erase_, + _Technical Characters_ and _Terminal State Interrogation_. +* Secondary DA to report foot as a VT220 instead of a VT420. +* Secondary DA to report foot's version number in parameter 2, the + _Firmware Version_. The string is made up of foot's major, minor and + patch version numbers, always using two digits for each version + number and without any other separating characters. Thus, _1.4.2_ + would be reported as `010402` (i.e. the full response would be + `\E[>1;010402;0c`). +* Scrollback search to only move the viewport if the match lies + outside it. +* Scrollback search to focus match, that requires a viewport change, + roughly in the center of the screen. +* Extending a selection with the right mouse button now works while + dragging the mouse. + + +### Deprecated +### Removed +### Fixed + +* Crash in scrollback search. +* Crash when a **pipe-visible** or **pipe-scrollback** command + contained an unclosed quote + (https://codeberg.org/dnkl/foot/issues/49). + + +### Security +### Contributors + +* [birger](https://codeberg.org/birger) +* [cherti](https://codeberg.org/cherti) + ## 1.4.2 diff --git a/LICENSE b/LICENSE index 6d93abca..a915c5b5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Daniel Eklöf +Copyright (c) 2019 Daniel Eklöf Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PKGBUILD b/PKGBUILD index 33cc9786..ae70f263 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -42,6 +42,7 @@ check() { package_foot-git() { pkgdesc="A wayland native terminal emulator" + changelog=CHANGELOG.md optdepends=('foot-terminfo: terminfo for foot') conflicts=('foot') provides=('foot') diff --git a/README.md b/README.md index 7aa29df4..233703d1 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator. ## Index 1. [Features](#features) +1. [Configuration](#configuration) 1. [Troubleshooting](#troubleshooting) 1. [Why the name 'foot'?](#why-the-name-foot) 1. [Fonts](#fonts) @@ -18,6 +19,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator. 1. [Backspace](#backspace) 1. [DPI and font size](#dpi-and-font-size) 1. [Supported OSCs](#supported-oscs) +1. [Programmatically checking if running in foot](#programmatically-checking-if-running-in-foot) 1. [Requirements](#requirements) 1. [Running](#running) 1. [Building](#building) @@ -33,7 +35,6 @@ The fast, lightweight and minimalistic Wayland terminal emulator. 1. [Bugs](#bugs) 1. [Mastodon](#mastodon) - ## Features * Fast (see [benchmarks](doc/benchmark.md)) @@ -52,6 +53,14 @@ The fast, lightweight and minimalistic Wayland terminal emulator. ![wow](doc/sixel-wow.png "Sixel screenshot") +## Configuration + +**foot** can be configured by creating a file `$XDG_CONFIG_HOME/footrc` (defaulting to `~/.config/footrc`). +A template for that can usually be found in `/usr/share/foot/footrc` or +[here](https://codeberg.org/dnkl/foot/src/branch/master/footrc). + +Further information can be found in foot's manpage `foot(5)`. + ## Troubleshooting @@ -222,14 +231,14 @@ want to launch a new terminal. By default, foot prefixes _Meta characters_ with ESC. This corresponds to XTerm's `metaSendsEscape` option set to `true`. -This can be disabled programatically with `\E[?1036l` (and enabled +This can be disabled programmatically with `\E[?1036l` (and enabled again with `\E[?1036h`). When disabled, foot will instead set the 8:th bit of meta character and then UTF-8 encode it. This corresponds to XTerm's `eightBitMeta` option set to `true`. -This can also be disabled programatically with `rmm` (_reset meta +This can also be disabled programmatically with `rmm` (_reset meta mode_, `\E[?1034l`), and enabled again with `smm` (_set meta mode_, `\E[?1034h`). @@ -301,6 +310,39 @@ with the terminal emulator itself. Foot implements the following OSCs: * `OSC 555` - flash screen (**foot specific**) +## Programmatically checking if running in foot + +Foot does **not** set any environment variables that can be used to +identify foot (reading `TERM` is not reliable since the user may have +chosen to use a different terminfo). + +You can instead use the escape sequences to read the _Secondary_ and +_Tertiary Device Attributes_ (secondary/tertiary DA, for short). + +The tertiary DA response is always `\EP!|464f4f54\E\\`. The `\EP!|` is +the standard tertiary DA response prefix, `DCS ! |`. The trailing +`\E\\` is of course the standard string terminator, `ST`. + +In the response above, the interresting part is `464f4f54`; this is +the string _FOOT_ in hex. + +The secondary DA response is `\E[>1;XXYYZZ;0c`, where XXYYZZ is foot's +major, minor and patch version numbers, in decimal, using two digits +for each number. For example, foot-1.4.2 would respond with +`\E[>1;010402;0c`. + +**Note**: not all terminal emulators implement tertiary DA. Most +implement secondary DA, but not all. All _should_ however implement +_Primary DA_. + +Thus, a safe way to query the terminal is to request the tertiary, +secondary and primary DA all at once, in that order. All terminals +should ignore escape sequences they do not recognize. You will have to +parse the response (which in foot will consist of all three DA +responses, all at once) to determine which requests the terminal +emulator actually responded to. + + ## Requirements ### Running diff --git a/csi.c b/csi.c index fe102ada..2122d272 100644 --- a/csi.c +++ b/csi.c @@ -13,10 +13,11 @@ #define LOG_ENABLE_DBG 0 #include "log.h" #include "grid.h" -#include "vt.h" #include "selection.h" #include "sixel.h" #include "util.h" +#include "version.h" +#include "vt.h" #define UNHANDLED() LOG_DBG("unhandled: %s", csi_as_string(term, final, -1)) #define UNHANDLED_SGR(idx) LOG_DBG("unhandled: %s", csi_as_string(term, 'm', idx)) @@ -373,8 +374,17 @@ csi_dispatch(struct terminal *term, uint8_t final) * - 22 ANSI color, e.g., VT525. * - 28 Rectangular editing. * - 29 ANSI text locator (i.e., DEC Locator mode). + * + * Note: we report ourselves as a VT220, mainly to be able + * to pass parameters, to indiciate we support sixel, and + * ANSI colors. + * + * The VT level must be synchronized with the secondary DA + * response. + * + * Note: tertiary DA responds with "FOOT". */ - const char *reply = "\033[?62;4;6;15;17;22c"; + const char *reply = "\033[?62;4;22c"; term_to_slave(term, reply, strlen(reply)); break; } @@ -1405,9 +1415,22 @@ csi_dispatch(struct terminal *term, uint8_t final) * xterm uses its version number. We use an xterm * version number too, since e.g. Emacs uses this to * determine level of support. + * + * We report ourselves as a VT220. This must be + * synchronized with the primary DA respons. + * + * Note: tertiary DA replies with "FOOT". */ - term_to_slave(term, "\033[>41;347;0c", 12); + static_assert(FOOT_MAJOR < 100, "Major version must not exceed 99"); + static_assert(FOOT_MINOR < 100, "Minor version must not exceed 99"); + static_assert(FOOT_PATCH < 100, "Patch version must not exceed 99"); + + char reply[64]; + snprintf(reply, sizeof(reply), "\033[>1;%02u%02u%02u;0c", + FOOT_MAJOR, FOOT_MINOR, FOOT_PATCH); + + term_to_slave(term, reply, strlen(reply)); break; case 'm': diff --git a/doc/benchmark.md b/doc/benchmark.md index daac1e10..8d4bb1fe 100644 --- a/doc/benchmark.md +++ b/doc/benchmark.md @@ -18,7 +18,7 @@ terminal. This is done **20** times for each test. Then it calculates the _mean_ and _standard deviation_ for each test. -## 2020-06-05 +## 2020-07-25 ### System @@ -40,17 +40,17 @@ Scrollback: 10000 lines ### Results -| Benchmark | Foot (GCC+PGO) 1.3.0.r59 | Alacritty 0.4.3 | URxvt 9.22 | XTerm 356 | +| Benchmark | Foot (GCC+PGO) 1.4.2.r14 | Alacritty 0.4.3 | URxvt 9.22 | XTerm 358 | |------------------------|-------------------------:|---------------------:|---------------:|---------------:| -| alt-random | 0.450s ±0.009 | 0.905s ±0.003 | 1.151s ±0.004 | 12.906s ±0.078 | -| alt-random-colors | 0.451s ±0.013 | 0.931s ±0.005 | 1.192s ±0.005 | 11.858s ±0.155 | -| scrolling | 1.181s ±0.036 | 1.096s ±0.022 | 1.080s ±0.007 | 38.244s ±0.118 | -| scrolling-filled-lines | 0.878s ±0.015 | 1.320s ±0.040 | 1.251s ±0.006 | 6.812s ±0.031 | -| unicode-random | 0.926s ±1.179 | 0.089s ±0.001 [^1] | 24.039s ±3.559 | 26.558s ±3.841 | +| alt-random | 0.423s ±0.014 | 0.904s ±0.006 | 1.111s ±0.003 | 12.851s ±0.087 | +| alt-random-colors | 0.382s ±0.005 | 0.935s ±0.005 | 1.146s ±0.007 | 11.816s ±0.088 | +| scrolling | 1.380s ±0.048 | 1.011s ±0.012 | 1.021s ±0.016 | 38.483s ±0.122 | +| scrolling-filled-lines | 0.826s ±0.020 | 1.307s ±0.008 | 1.213s ±0.015 | 6.725s ±0.016 | +| unicode-random | 0.243s ±0.006 | 0.091s ±0.003 [^1] | 24.507s ±3.264 | 26.127s ±3.891 | -## 2020-05-31 +## 2020-07-25 ### System @@ -73,13 +73,13 @@ Scrollback=10000 lines ### Results -| Benchmark | Foot (GCC+PGO) 1.3.0.r59 | Alacritty 0.4.2 | URxvt 9.22 | St 0.8.3 | XTerm 356 | -|------------------------|-------------------------:|---------------------:|---------------:|--------------:|---------------:| -| alt-random | 0.791s ±0.080 | 1.558s ±0.038 | 1.746s ±0.065 | 2.628s ±0.085 | 1.706s ±0.064 | -| alt-random-colors | 0.830s ±0.076 | 1.587s ±0.041 | 2.049s ±0.118 | 3.033s ±0.129 | 2.109s ±0.131 | -| scrolling | 1.603s ±0.070 | 1.464s ±0.098 | 1.439s ±0.035 | 3.760s ±0.113 | 1.459s ±0.036 | -| scrolling-filled-lines | 1.888s ±0.021 | 2.334s ±0.078 | 2.145s ±0.074 | 3.372s ±0.078 | 2.144s ±0.091 | -| unicode-random | 1.545s ±0.229 | 0.164s ±0.012 [^1] | 11.180s ±0.342 | crashed | 11.389s ±0.269 | +| Benchmark | Foot (GCC+PGO) 1.4.2.r9 | Alacritty 0.4.3 | URxvt 9.22 | St 0.8.4 | XTerm 358 | +|------------------------|------------------------:|---------------------:|---------------:|--------------:|----------------:| +| alt-random | 0.784s ±0.074 | 1.568s ±0.094 | 1.600s ±0.052 | 1.917s ±0.054 | 34.487s ±0.118 | +| alt-random-colors | 0.823s ±0.067 | 1.627s ±0.107 | 1.932s ±0.073 | 2.111s ±0.163 | 30.676s ±0.127 | +| scrolling | 1.612s ±0.092 | 1.492s ±0.051 | 1.504s ±0.033 | 3.767s ±0.140 | 125.202s ±0.383 | +| scrolling-filled-lines | 1.874s ±0.039 | 2.423s ±0.083 | 1.994s ±0.037 | 2.751s ±0.076 | 19.608s ±0.056 | +| unicode-random | 0.458s ±0.026 | 0.159s ±0.007 [^1] | 12.416s ±0.223 | crashed | 16.336s ±0.410 | [^1]: [Alacritty and "unicode-random"](#alacritty-and-unicode-random) diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 1874c0cb..883ae8b1 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -13,7 +13,7 @@ arguments, to execute (instead of the default shell). # OPTIONS *-c*,*--config*=_PATH_ - Path to configuration file. Default: *XDG_RUNTIME_DIR/footrc*. + Path to configuration file. Default: *XDG_CONFIG_HOME/footrc*. *-f*,*--font*=_FONT_ Comma separated list of fonts to use, in fontconfig format (see @@ -214,14 +214,14 @@ _Examples_: By default, foot prefixes meta characters with *ESC*. This corresponds to XTerm's *metaSendsEscape* option set to *true*. -This can be disabled programatically with *\E[?1036l* (and enabled +This can be disabled programmatically with *\E[?1036l* (and enabled again with *\E[?1036h*). When disabled, foot will instead set the 8:th bit of meta character and then UTF-8 encode it. This corresponds to XTerm's *eightBitMeta* option set to *true*. -This can also be disabled programatically with *rmm* (Reset Meta Mode, +This can also be disabled programmatically with *rmm* (Reset Meta Mode, *\E[?1034l*), and enabled again with *smm* (Set Meta Mode, *\E[?1034h*). diff --git a/doc/foot.5.scd b/doc/foot.5.scd index 92ac7c01..d837cd1a 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -176,7 +176,7 @@ This section lets you override the default key bindings. The general format is _action=combo1...comboN_. That is, each action may have one or more key combinations, space separated. Each combination is on the form _mod1+mod2+key_. The names of the modifiers -and the key *must* be a valid XKB key name. +and the key *must* be valid XKB key names. Note that if *Shift* is one of the modifiers, the _key_ *must* be in upper case. For example, *Control+Shift+v* will never trigger - @@ -335,7 +335,7 @@ any of these options. Now, that was a lof of text. But what is it foot actually does? When receiving client data, it schedules a timer, the - *delayed-render-lower*. If we do not recieve any more client data + *delayed-render-lower*. If we do not receive any more client data before the timer has run out, we render the frame. If however, we do receive more data, the timer is re-scheduled. That is, each time we receive client data, frame rendering is delayed another diff --git a/footrc b/footrc index 624c688b..e7139a83 100644 --- a/footrc +++ b/footrc @@ -19,22 +19,22 @@ # alpha=1.0 # foreground=dcdccc # background=111111 -# regular0=222222 -# regular1=cc9393 -# regular2=7f9f7f -# regular3=d0bf8f -# regular4=6ca0a3 -# regular5=dc8cc3 -# regular6=93e0e3 -# regular7=dcdccc -# bright0=666666 -# bright1=dca3a3 -# bright2=bfebbf -# bright3=f0dfaf -# bright4=8cd0d3 -# bright5=fcace3 -# bright6=b3ffff -# bright7=ffffff +# regular0=222222 # black +# regular1=cc9393 # red +# regular2=7f9f7f # green +# regular3=d0bf8f # yellow +# regular4=6ca0a3 # blue +# regular5=dc8cc3 # magenta +# regular6=93e0e3 # cyan +# regular7=dcdccc # white +# bright0=666666 # bright black +# bright1=dca3a3 # bright red +# bright2=bfebbf # bright green +# bright3=f0dfaf # bright yellow +# bright4=8cd0d3 # bright blue +# bright5=fcace3 # bright magenta +# bright6=b3ffff # bright cyan +# bright7=ffffff # bright white [csd] # preferred=server diff --git a/generate-version.sh b/generate-version.sh index ecd0306f..cfb85059 100755 --- a/generate-version.sh +++ b/generate-version.sh @@ -22,7 +22,14 @@ else new_version="${default_version}" fi -new_version="#define FOOT_VERSION \"${new_version}\"" +major=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1/') +minor=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\2/') +patch=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\3/') + +new_version="#define FOOT_VERSION \"${new_version}\" +#define FOOT_MAJOR ${major} +#define FOOT_MINOR ${minor} +#define FOOT_PATCH ${patch}" if [ -f "${out_file}" ]; then old_version=$(cat "${out_file}") diff --git a/input.c b/input.c index 442cd322..c7f4b073 100644 --- a/input.c +++ b/input.c @@ -1171,7 +1171,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, if (col < 0 || row < 0 || col >= term->cols || row >= term->rows) return; - bool update_selection = seat->mouse.button == BTN_LEFT; + bool update_selection = seat->mouse.button == BTN_LEFT || seat->mouse.button == BTN_RIGHT; bool update_selection_early = term->selection.end.row == -1; if (update_selection && update_selection_early) diff --git a/meson.build b/meson.build index 0977e3c5..493b4392 100644 --- a/meson.build +++ b/meson.build @@ -148,7 +148,12 @@ custom_target( install: true, install_dir: join_paths(get_option('datadir'), 'terminfo')) -install_data('foot.desktop', 'foot-server.desktop', install_dir: join_paths(get_option('datadir'), 'applications')) +install_data( + 'LICENSE', 'README.md', 'CHANGELOG.md', + install_dir: join_paths(get_option('datadir'), 'doc', 'foot')) +install_data( + 'foot.desktop', 'foot-server.desktop', + install_dir: join_paths(get_option('datadir'), 'applications')) install_data('footrc', install_dir: join_paths(get_option('datadir'), 'foot')) subdir('completions') diff --git a/search.c b/search.c index 7786937a..a0b87a13 100644 --- a/search.c +++ b/search.c @@ -104,39 +104,57 @@ search_update_selection(struct terminal *term, int start_row, int start_col, int end_row, int end_col) { - int old_view = term->grid->view; - int new_view = start_row; + bool move_viewport = true; - /* 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; + int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1); + if (view_end >= term->grid->view) { + /* Viewport does *not* wrap around */ + if (start_row >= term->grid->view && end_row <= view_end) + move_viewport = false; } else { - if (new_view >= term->grid->offset || new_view <= end) - new_view = term->grid->offset; + /* Viewport wraps */ + if (start_row >= term->grid->view || end_row <= view_end) + move_viewport = false; } - /* Update view */ - term->grid->view = new_view; - if (new_view != old_view) - term_damage_view(term); + if (move_viewport) { + int old_view = term->grid->view; + int new_view = start_row - term->rows / 2; + + while (new_view < 0) + new_view += term->grid->num_rows; + + /* 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) { @@ -184,7 +202,7 @@ search_find_next(struct terminal *term) int start_row = term->search.match.row; int start_col = term->search.match.col; - size_t len __attribute__((unused)) = term->search.match_len; + size_t len = term->search.match_len; assert((len == 0 && start_row == -1 && start_col == -1) || (len > 0 && start_row >= 0 && start_col >= 0)); diff --git a/selection.c b/selection.c index d5add464..354f6f53 100644 --- a/selection.c +++ b/selection.c @@ -372,8 +372,8 @@ selection_extend_normal(struct terminal *term, int col, int row, uint32_t serial if (row < start->row || (row == start->row && col < start->col)) { /* Extend selection to start *before* current start */ - new_start = (struct coord){col, row}; - new_end = *end; + new_start = *end; + new_end = (struct coord){col, row}; } else if (row > end->row || (row == end->row && col > end->col)) { @@ -391,8 +391,8 @@ selection_extend_normal(struct terminal *term, int col, int row, uint32_t serial abs(linear - (end->row * term->cols + end->col))) { /* Move start point */ - new_start = (struct coord){col, row}; - new_end = *end; + new_start = *end; + new_end = (struct coord){col, row}; } else { @@ -440,13 +440,13 @@ selection_extend_block(struct terminal *term, int col, int row, uint32_t serial) /* Move one of the top corners */ if (abs(col - top_left.col) < abs(col - top_right.col)) { - new_start = (struct coord){col, row}; - new_end = bottom_right; + new_start = bottom_right; + new_end = (struct coord){col, row}; } else { - new_start = (struct coord){col, row}; - new_end = bottom_left; + new_start = bottom_left; + new_end = (struct coord){col, row}; } } diff --git a/tokenize.c b/tokenize.c index b4aab6b5..35d52345 100644 --- a/tokenize.c +++ b/tokenize.c @@ -38,14 +38,16 @@ tokenize_cmdline(char *cmdline, char ***argv) char delim = first_token_is_quoted ? cmdline[0] : ' '; char *p = first_token_is_quoted ? &cmdline[1] : &cmdline[0]; + char *search_start = p; size_t idx = 0; while (*p != '\0') { - char *end = strchr(p, delim); + char *end = strchr(search_start, delim); if (end == NULL) { if (delim != ' ') { - LOG_ERR("unterminated %s quote\n", delim == '"' ? "double" : "single"); + LOG_ERR("unterminated %s quote", delim == '"' ? "double" : "single"); free(*argv); + *argv = NULL; return false; } @@ -57,6 +59,15 @@ tokenize_cmdline(char *cmdline, char ***argv) return true; } + if (end > p && *(end - 1) == '\\') { + /* Escaped quote, remove one level of escaping and + * continue searching for "our" closing quote */ + memmove(end - 1, end, strlen(end)); + end[strlen(end) - 1] = '\0'; + search_start = end; + continue; + } + *end = '\0'; if (!push_argv(argv, &argv_size, p, &idx)) @@ -74,6 +85,7 @@ tokenize_cmdline(char *cmdline, char ***argv) p++; } else delim = ' '; + search_start = p; } if (!push_argv(argv, &argv_size, NULL, &idx))