diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 28df1ccb..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,112 +0,0 @@ -stages: - - build - -variables: - GIT_SUBMODULE_STRATEGY: normal - -before_script: - - apk update - - apk add musl-dev linux-headers meson ninja gcc scdoc ncurses - - apk add libxkbcommon-dev pixman-dev freetype-dev fontconfig-dev harfbuzz-dev utf8proc-dev - - apk add wayland-dev wayland-protocols - - apk add git - - apk add check-dev - - apk add ttf-hack font-noto-emoji - -debug-x64: - image: alpine:edge - stage: build - script: - - cd subprojects - - git clone https://codeberg.org/dnkl/fcft.git - - cd .. - - mkdir -p bld/debug - - cd bld/debug - - meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - - ninja -v -k0 - - ninja -v test - artifacts: - reports: - junit: bld/debug/meson-logs/testlog.junit.xml - -debug-x64-no-grapheme-clustering: - image: alpine:edge - stage: build - script: - - apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev - - cd subprojects - - git clone https://codeberg.org/dnkl/fcft.git - - cd .. - - mkdir -p bld/debug - - cd bld/debug - - meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../../ - - ninja -v -k0 - - ninja -v test - - ./foot --version - - ./footclient --version - artifacts: - reports: - junit: bld/debug/meson-logs/testlog.junit.xml - -release-x64: - image: alpine:edge - stage: build - script: - - cd subprojects - - git clone https://codeberg.org/dnkl/fcft.git - - cd .. - - mkdir -p bld/release - - cd bld/release - - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - - ninja -v -k0 - - ninja -v test - - ./foot --version - - ./footclient --version - artifacts: - reports: - junit: bld/release/meson-logs/testlog.junit.xml - -debug-x86: - image: i386/alpine:edge - stage: build - script: - - cd subprojects - - git clone https://codeberg.org/dnkl/fcft.git - - cd .. - - mkdir -p bld/debug - - cd bld/debug - - meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - - ninja -v -k0 - - ninja -v test - - ./foot --version - - ./footclient --version - artifacts: - reports: - junit: bld/debug/meson-logs/testlog.junit.xml - -release-x86: - image: i386/alpine:edge - stage: build - script: - - cd subprojects - - git clone https://codeberg.org/dnkl/fcft.git - - cd .. - - mkdir -p bld/release - - cd bld/release - - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - - ninja -v -k0 - - ninja -v test - - ./foot --version - - ./footclient --version - artifacts: - reports: - junit: bld/release/meson-logs/testlog.junit.xml - -codespell: - image: alpine:edge - stage: build - script: - - apk add python3 - - apk add py3-pip - - pip install codespell - - codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd diff --git a/.woodpecker.yml b/.woodpecker.yml index 284da761..06631f89 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -33,7 +33,7 @@ pipeline: image: alpine:latest commands: - apk update - - apk add musl-dev linux-headers meson ninja gcc scdoc ncurses + - apk add musl-dev linux-headers meson ninja gcc clang scdoc ncurses - apk add libxkbcommon-dev pixman-dev freetype-dev fontconfig-dev harfbuzz-dev utf8proc-dev - apk add wayland-dev wayland-protocols - apk add git @@ -50,7 +50,7 @@ pipeline: - ./footclient --version - cd ../.. - # Release + # Release (gcc) - mkdir -p bld/release-x64 - cd bld/release-x64 - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. @@ -60,6 +60,16 @@ pipeline: - ./footclient --version - cd ../.. + # Release (clang) + - mkdir -p bld/release-x64-clang + - cd bld/release-x64-clang + - CC=clang meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - ninja -v -k0 + - ninja -v test + - ./foot --version + - ./footclient --version + - cd ../.. + # no grapheme clustering - apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev - mkdir -p bld/debug @@ -80,7 +90,7 @@ pipeline: image: i386/alpine:latest commands: - apk update - - apk add musl-dev linux-headers meson ninja gcc scdoc ncurses + - apk add musl-dev linux-headers meson ninja gcc clang scdoc ncurses - apk add libxkbcommon-dev pixman-dev freetype-dev fontconfig-dev harfbuzz-dev utf8proc-dev - apk add wayland-dev wayland-protocols - apk add git @@ -97,7 +107,7 @@ pipeline: - ./footclient --version - cd ../.. - # Release + # Release (gcc) - mkdir -p bld/release-x86 - cd bld/release-x86 - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. @@ -106,3 +116,13 @@ pipeline: - ./foot --version - ./footclient --version - cd ../.. + + # Release (clang) + - mkdir -p bld/release-x86-clang + - cd bld/release-x86-clang + - CC=clang meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - ninja -v -k0 + - ninja -v test + - ./foot --version + - ./footclient --version + - cd ../.. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a6efd08..ca98ed5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog * [Unreleased](#unreleased) +* [1.14.0](#1-14-0) * [1.13.1](#1-13-1) * [1.13.0](#1-13-0) * [1.12.1](#1-12-1) @@ -42,6 +43,24 @@ ## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed + +* Incorrect icon in dock and window switcher on Gnome ([#1317][1317]) +* Crash when scrolling after resizing the window with non-zero + scrolling regions. + +[1317]: https://codeberg.org/dnkl/foot/issues/1317 + + +### Security +### Contributors + + +## 1.14.0 ### Added @@ -49,8 +68,24 @@ * Support (optional) for utmp logging with libutempter. * `kxIN` and `kxOUT` (focus in/out events) to terminfo. * `name` capability to `XTGETTCAP`. +* String values in `foot.ini` may now be quoted. This can be used to + set a value to the empty string, for example. +* Environment variables can now be **unset**, by setting + `[environment].=""` (quotes are required) ([#1225][1225]). +* `font-size-adjustment=N[px]` option, letting you configure how much + to increment/decrement the font size when zooming in or out + ([#1188][1188]). +* Bracketed paste terminfo entries (`BD`, `BE`, `PE` and `PS`, added + to ncurses in 2022-12-24). Vim makes use of these. +* “Report version” terminfo entries (`XR`/`xr`). +* “Report DA2” terminfo entries (`RV`/`rv`). +* `XF` terminfo capability (focus in/out events available). +* `$TERM_PROGRAM` and `$TERM_PROGRAM_VERSION` environment variables + set in the slave process. [1136]: https://codeberg.org/dnkl/foot/issues/1136 +[1225]: https://codeberg.org/dnkl/foot/issues/1225 +[1188]: https://codeberg.org/dnkl/foot/issues/1188 ### Changed @@ -64,13 +99,20 @@ ("permanently reset") instead of `2` ("reset") for DEC private modes that are known but unsupported. * Set `PWD` environment variable in the slave process ([#1179][1179]). +* DPI is now forced to 96 when found to be unreasonably high. +* Set default log level to warning ([#1215][1215]). +* Default `grapheme-width-method` from `wcswidth` to `double-width`. +* When determining initial font size, do FontConfig config + substitution if the user-provided font pattern has no {pixel}size + option ([#1287][1287]). +* DECRST of DECCOLM and DECSCLM removed from terminfo. [1166]: https://codeberg.org/dnkl/foot/issues/1166 [1179]: https://codeberg.org/dnkl/foot/issues/1179 +[1215]: https://codeberg.org/dnkl/foot/pulls/1215 +[1287]: https://codeberg.org/dnkl/foot/issues/1287 -### Deprecated -### Removed ### Fixed * Crash in `foot --server` on key press, after another `footclient` @@ -80,16 +122,76 @@ that does not allow Wayland buffer re-use (e.g. KDE/plasma) ([#1173][1173]) * Scrollback search matches not being highlighted correctly, on - compositors that does now allow Wayland buffer re-use + compositors that does not allow Wayland buffer re-use (e.g. KDE/plasma). +* Nanosecs "overflow" when calculating timeout value for + `resize-delay-ms` option. +* Missing backslash in ST terminator in escape sequences in the + built-in terminfo (accessed via XTGETTCAP). +* Crash when interactively resizing the window with a very large + scrollback. +* Crash when a sixel image exceeds the current sixel max height. +* Crash after reverse-scrolling (`CSI Ps T`) in the ‘normal’ + (non-alternate) screen ([#1190][1190]). +* Background transparency being applied to the text "behind" the + cursor. Only applies to block cursor using inversed fg/bg + colors. ([#1205][1205]). +* Crash when monitor’s physical size is "too small" ([#1209][1209]). +* Line-height adjustment when incrementing/decrementing the font size + with a user-set line-height ([#1218][1218]). +* Scaling factor not being correctly applied when converting pt-or-px + config values (e.g. letter offsets, line height etc). +* Selection being stuck visually when `IL` and `DL`. +* URL underlines sometimes still being visible after exiting URL mode. +* Text-bindings, and pipe-* bindings, with multiple key mappings + causing a crash (double-free) on exit ([#1259][1259]). +* Double-width glyphs glitching when surrounded by glyphs overflowing + into the double-width glyph ([#1256][1256]). +* Wayland protocol violation when ack:ing a configure event for an + unmapped surface ([#1249][1249]). +* `xdg_toplevel::set_min_size()` not being called. +* Key bindings with consumed modifiers masking other key bindings + ([#1280][1280]). +* Multi-character compose sequences with the kitty keyboard protocol + ([#1288][1288]). +* Crash when application output scrolls very fast, e.g. `yes` + ([#1305][1305]). +* Crash when application scrolls **many** lines (> ~2³¹). +* DECCOLM erasing the screen ([#1265][1265]). [1173]: https://codeberg.org/dnkl/foot/issues/1173 +[1190]: https://codeberg.org/dnkl/foot/issues/1190 +[1205]: https://codeberg.org/dnkl/foot/issues/1205 +[1209]: https://codeberg.org/dnkl/foot/issues/1209 +[1218]: https://codeberg.org/dnkl/foot/issues/1218 +[1259]: https://codeberg.org/dnkl/foot/issues/1259 +[1256]: https://codeberg.org/dnkl/foot/issues/1256 +[1249]: https://codeberg.org/dnkl/foot/issues/1249 +[1280]: https://codeberg.org/dnkl/foot/issues/1280 +[1288]: https://codeberg.org/dnkl/foot/issues/1288 +[1305]: https://codeberg.org/dnkl/foot/issues/1305 +[1265]: https://codeberg.org/dnkl/foot/issues/1265 -### Security ### Contributors +* Alexey Sakovets +* Andrea Pappacoda +* Antoine Beaupré +* argosatcore * Craig Barnes +* EuCaue +* Grigory Kirillov +* Harri Nieminen +* Hugo Osvaldo Barrera +* jaroeichler +* Joakim Nohlgård +* Nick Hastings +* Soren A D +* Torsten Trautwein +* Vladimír Magyar +* woojiq +* Yorick Peterse ## 1.13.1 diff --git a/INSTALL.md b/INSTALL.md index da3a667e..6cc51750 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -327,6 +327,7 @@ We will use the `pgo` binary along with input corpus generated by `scripts/generate-alt-random-writes.py`: ```sh +./utils/xtgettcap ./footclient --version ./foot --version tmp_file=$(mktemp) @@ -349,9 +350,10 @@ rm ${tmp_file} ``` The first step, running `./foot --version` and `./footclient ---version` might seem unnecessary, but is needed to ensure we have -_some_ profiling data for functions not covered by the PGO helper -binary. Without this, the final link phase will fail. +--version` etc, might seem unnecessary, but is needed to ensure we +have _some_ profiling data for functions not covered by the PGO helper +binary, for **all** binaries. Without this, the final link phase will +fail. The snippet above then creates an (empty) temporary file. Then, it runs a script that generates random escape sequences (if you cat @@ -371,6 +373,7 @@ This method requires a running Wayland session. We will use the script `scripts/generate-alt-random-writes.py`: ```sh +./utils/xtgettcap ./footclient --version foot_tmp_file=$(mktemp) ./foot \ @@ -384,9 +387,10 @@ rm ${foot_tmp_file} You should see a foot window open up, with random colored text. The window should close after ~1-2s. -The first step, `./footclient --version` might seem unnecessary, but -is needed to ensure we have _some_ profiling data for -`footclient`. Without this, the final link phase will fail. +The first step, `./utils/xtgettcap && ./footclient --version` +might seem unnecessary, but is needed to ensure we have _some_ +profiling data for **all** binaries we build. Without this, the final +link phase will fail. ##### Use the generated PGO data @@ -436,7 +440,7 @@ sed 's/@default_terminfo@/foot/g' foot.info | \ Where _”output-directory”_ **must** match the value passed to `-Dcustom-terminfo-install-location` in the foot build. If `-Dcustom-terminfo-install-location` has not been set, `-o -` can simply be omitted. +` can simply be omitted. Or, if packaging: diff --git a/README.md b/README.md index 7ce31e50..496bb9f5 100644 --- a/README.md +++ b/README.md @@ -569,7 +569,7 @@ reported the same issue. The report should contain the following: - Foot version (`foot --version`). -- Log output from foot (start foot from another terminal). +- Log output from foot (run `foot -d info` from another terminal). - Which Wayland compositor (and version) you are running. - If reporting a crash, please try to provide a `bt full` backtrace with symbols. diff --git a/client.c b/client.c index 2a802d16..6954d17e 100644 --- a/client.c +++ b/client.c @@ -94,7 +94,7 @@ print_usage(const char *prog_name) " -N,--no-wait detach the client process from the running terminal, exiting immediately\n" " -o,--override=[section.]key=value override configuration option\n" " -E, --client-environment exec shell using footclient's environment, instead of the server's\n" - " -d,--log-level={info|warning|error|none} log level (info)\n" + " -d,--log-level={info|warning|error|none} log level (warning)\n" " -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n" " -v,--version show the version number and quit\n" " -e ignored (for compatibility with xterm -e)\n"; @@ -178,7 +178,7 @@ main(int argc, char *const *argv) const char *custom_cwd = NULL; const char *server_socket_path = NULL; - enum log_class log_level = LOG_CLASS_INFO; + enum log_class log_level = LOG_CLASS_WARNING; enum log_colorize log_colorize = LOG_COLORIZE_AUTO; bool hold = false; bool client_environment = false; diff --git a/completions/bash/foot b/completions/bash/foot index 1fdab062..eb17dad1 100644 --- a/completions/bash/foot +++ b/completions/bash/foot @@ -31,7 +31,7 @@ _foot() cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} - # check if positional argument is completed + # Check if positional argument is completed previous_words=( "${COMP_WORDS[@]}" ) unset previous_words[-1] commands=$(compgen -c | grep -vFx "$(compgen -k)" | grep -vE '^([.:[]|foot)$' | sort -u) @@ -43,41 +43,45 @@ _foot() (( i++ )) continue fi - # positional argument found + # Positional argument found offset=$i fi (( i++ )) done if [[ ! -z "$offset" ]] ; then - # depends on bash_completion being available + # Depends on bash_completion being available declare -F _command_offset >/dev/null || return 1 _command_offset $offset + return 0 elif [[ ${cur} == --* ]] ; then COMPREPLY=( $(compgen -W "${flags}" -- ${cur}) ) - elif [[ ${prev} =~ ^(--config|--print-pid|--server)$ ]] ; then - compopt -o default - elif [[ ${prev} == '--working-directory' ]] ; then - compopt -o dirnames - elif [[ ${prev} == '--term' ]] ; then - # check if toe is available - which toe > /dev/null || return 1 - COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 ~ /[+]/ {next}; {print $1}')" -- ${cur}) ) - elif [[ ${prev} == '--font' ]] ; then - # check if fc-list is available - which fc-list > /dev/null || return 1 - COMPREPLY=( $(compgen -W "$(fc-list : family | sed 's/,/\n/g' | uniq | tr -d ' ')" -- ${cur}) ) - elif [[ ${prev} == '--log-level' ]] ; then - COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) - elif [[ ${prev} == '--log-colorize' ]] ; then - COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) - elif [[ ${prev} =~ ^(--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|--check-config)$ ]] ; then - : # don't autocomplete for these flags - else - # complete commands from $PATH - COMPREPLY=( $(compgen -c -- ${cur}) ) + return 0 fi + case "$prev" in + --config|--print-pid|--server|-[cps]) + compopt -o default ;; + --working-directory|-D) + compopt -o dirnames ;; + --term|-t) + command -v toe > /dev/null || return 1 + COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 !~ /[+]/ {print $1}')" -- ${cur}) ) ;; + --font|-f) + command -v fc-list > /dev/null || return 1 + COMPREPLY=( $(compgen -W "$(fc-list : family | sed 's/,/\n/g' | uniq | tr -d ' ')" -- ${cur}) ) ;; + --log-level|-d) + COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;; + --log-colorize|-l) + COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;; + --app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC]) + # Don't autocomplete for these flags + : ;; + *) + # Complete commands from $PATH + COMPREPLY=( $(compgen -c -- ${cur}) ) ;; + esac + return 0 } diff --git a/completions/bash/footclient b/completions/bash/footclient index b672c247..62abdd65 100644 --- a/completions/bash/footclient +++ b/completions/bash/footclient @@ -27,7 +27,7 @@ _footclient() cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} - # check if positional argument is completed + # Check if positional argument is completed previous_words=( "${COMP_WORDS[@]}" ) unset previous_words[-1] commands=$(compgen -c | grep -vFx "$(compgen -k)" | grep -vE '^([.:[]|footclient)$' | sort -u) @@ -39,37 +39,42 @@ _footclient() (( i++ )) continue fi - # positional argument found + # Positional argument found offset=$i fi (( i++ )) done if [[ ! -z "$offset" ]] ; then - # depends on bash_completion being available + # Depends on bash_completion being available declare -F _command_offset >/dev/null || return 1 _command_offset $offset + return 0 elif [[ ${cur} == --* ]] ; then COMPREPLY=( $(compgen -W "${flags}" -- ${cur}) ) - elif [[ ${prev} == '--server-socket' ]] ; then - compopt -o default - elif [[ ${prev} == '--working-directory' ]] ; then - compopt -o dirnames - elif [[ ${prev} == '--term' ]] ; then - # check if toe is available - which toe > /dev/null || return 1 - COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 ~ /[+]/ {next}; {print $1}')" -- ${cur}) ) - elif [[ ${prev} == '--log-level' ]] ; then - COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) - elif [[ ${prev} == '--log-colorize' ]] ; then - COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) - elif [[ ${prev} =~ ^(--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|)$ ]] ; then - : # don't autocomplete for these flags - else - # complete commands from $PATH - COMPREPLY=( $(compgen -c -- ${cur}) ) + return 0 fi + case "$prev" in + --server-socket|-s) + compopt -o default ;; + --working-directory|-D) + compopt -o dirnames ;; + --term|-t) + command -v toe > /dev/null || return 1 + COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 ~ /[+]/ {next}; {print $1}')" -- ${cur}) ) ;; + --log-level|-d) + COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;; + --log-colorize|-l) + COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;; + --app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|-[ahoTvWw]) + # Don't autocomplete for these flags + : ;; + *) + # Complete commands from $PATH + COMPREPLY=( $(compgen -c -- ${cur}) ) ;; + esac + return 0 } diff --git a/completions/fish/foot.fish b/completions/fish/foot.fish index 81c7da61..86f6616d 100644 --- a/completions/fish/foot.fish +++ b/completions/fish/foot.fish @@ -15,7 +15,7 @@ complete -c foot -x -s W -l window-size-chars complete -c foot -F -s s -l server -d "run as server; open terminals by running footclient" complete -c foot -s H -l hold -d "remain open after child process exits" complete -c foot -r -s p -l print-pid -d "print PID to this file or FD when up and running (server mode only)" -complete -c foot -x -s d -l log-level -a "info warning error none" -d "log-level (info)" +complete -c foot -x -s d -l log-level -a "info warning error none" -d "log-level (warning)" complete -c foot -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr" complete -c foot -s S -l log-no-syslog -d "disable syslog logging (server mode only)" complete -c foot -s v -l version -d "show the version number and quit" diff --git a/completions/zsh/_foot b/completions/zsh/_foot index 0f184cc0..b9f46cdc 100644 --- a/completions/zsh/_foot +++ b/completions/zsh/_foot @@ -18,7 +18,7 @@ _arguments \ '(-s --server)'{-s,--server}'[run as server; open terminals by running footclient]:server:_files' \ '(-H --hold)'{-H,--hold}'[remain open after child process exits]' \ '(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running (server mode only)]:pidfile:_files' \ - '(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error none)' \ + '(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \ '(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \ '(-S --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging (server mode only)]' \ '(-v --version)'{-v,--version}'[show the version number and quit]' \ diff --git a/completions/zsh/_footclient b/completions/zsh/_footclient index b36644c6..c14d65d5 100644 --- a/completions/zsh/_footclient +++ b/completions/zsh/_footclient @@ -16,7 +16,7 @@ _arguments \ '(-N --no-wait)'{-N,--no-wait}'[detach the client process from the running terminal, exiting immediately]' \ '(-o --override)'{-o,--override}'[configuration option to override, in form SECTION.KEY=VALUE]:()' \ '(-E --client-environment)'{-E,--client-environment}"[child process inherits footclient's environment, instead of the server's]" \ - '(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error none)' \ + '(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \ '(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \ '(-v --version)'{-v,--version}'[show the version number and quit]' \ '(-h --help)'{-h,--help}'[show help message and quit]' \ diff --git a/config.c b/config.c index 95a06936..ebef223c 100644 --- a/config.c +++ b/config.c @@ -503,8 +503,48 @@ value_to_double(struct context *ctx, float *res) static bool NOINLINE value_to_str(struct context *ctx, char **res) { + char *copy = xstrdup(ctx->value); + char *end = copy + strlen(copy) - 1; + + /* Un-quote + * + * Note: this is very simple; we only support the *entire* value + * being quoted. That is, no mid-value quotes. Both double and + * single quotes are supported. + * + * - key="value" OK + * - key=abc "quote" def NOT OK + * - key=’value’ OK + * + * Finally, we support escaping the quote character, and the + * escape character itself: + * + * - key="value \"quotes\"" + * - key="backslash: \\" + * + * ONLY the "current" quote character can be escaped: + * + * key="value \'" NOt OK (both backslash and single quote is kept) + */ + + if ((copy[0] == '"' && *end == '"') || + (copy[0] == '\'' && *end == '\'')) + { + const char quote = copy[0]; + *end = '\0'; + + memmove(copy, copy + 1, end - copy); + + /* Un-escape */ + for (char *p = copy; *p != '\0'; p++) { + if (p[0] == '\\' && (p[1] == '\\' || p[1] == quote)) { + memmove(p, p + 1, end - p); + } + } + } + free(*res); - *res = xstrdup(ctx->value); + *res = copy; return true; } @@ -612,7 +652,7 @@ value_to_pt_or_px(struct context *ctx, struct pt_or_px *res) char *end = NULL; long value = strtol(s, &end, 10); - if (!(errno == 0 && end == s + len - 2)) { + if (!(len > 2 && errno == 0 && end == s + len - 2)) { LOG_CONTEXTUAL_ERR("invalid px value (must be in the form 12px)"); return false; } @@ -886,6 +926,31 @@ parse_section_main(struct context *ctx) return true; } + else if (strcmp(key, "font-size-adjustment") == 0) { + const size_t len = strlen(ctx->value); + if (len >= 1 && ctx->value[len - 1] == '%') { + errno = 0; + char *end = NULL; + + float percent = strtof(ctx->value, &end); + if (!(len > 1 && errno == 0 && end == ctx->value + len - 1)) { + LOG_CONTEXTUAL_ERR( + "invalid percent value (must be in the form 10.5%%)"); + return false; + } + + conf->font_size_adjustment.percent = percent / 100.; + conf->font_size_adjustment.pt_or_px.pt = 0; + conf->font_size_adjustment.pt_or_px.px = 0; + return true; + } else { + bool ret = value_to_pt_or_px(ctx, &conf->font_size_adjustment.pt_or_px); + if (ret) + conf->font_size_adjustment.percent = 0.; + return ret; + } + } + else if (strcmp(key, "line-height") == 0) return value_to_pt_or_px(ctx, &conf->line_height); @@ -1413,6 +1478,9 @@ parse_section_csd(struct context *ctx) static void free_binding_aux(struct binding_aux *aux) { + if (!aux->master_copy) + return; + switch (aux->type) { case BINDING_AUX_NONE: break; case BINDING_AUX_PIPE: free_argv(&aux->pipe); break; @@ -2256,21 +2324,22 @@ parse_section_environment(struct context *ctx) { struct config *conf = ctx->conf; const char *key = ctx->key; - const char *value = ctx->value; + /* Check for pre-existing env variable */ tll_foreach(conf->env_vars, it) { - if (strcmp(it->item.name, key) == 0) { - free(it->item.value); - it->item.value = xstrdup(value); - return true; - } + if (strcmp(it->item.name, key) == 0) + return value_to_str(ctx, &it->item.value); } - struct env_var var = { - .name = xstrdup(key), - .value = xstrdup(value), - }; - tll_push_back(conf->env_vars, var); + /* + * No pre-existing variable - allocate a new one + */ + + char *value = NULL; + if (!value_to_str(ctx, &value)) + return false; + + tll_push_back(conf->env_vars, ((struct env_var){xstrdup(key), value})); return true; } @@ -2847,6 +2916,7 @@ config_load(struct config *conf, const char *conf_path, }, .startup_mode = STARTUP_WINDOWED, .fonts = {{0}}, + .font_size_adjustment = {.percent = 0., .pt_or_px = {.pt = 0.5, .px = 0}}, .line_height = {.pt = 0, .px = -1}, .letter_spacing = {.pt = 0, .px = 0}, .horizontal_letter_offset = {.pt = 0, .px = 0}, @@ -2938,7 +3008,7 @@ config_load(struct config *conf, const char *conf_path, #if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING .grapheme_shaping = fcft_caps & FCFT_CAPABILITY_GRAPHEME_SHAPING, #endif - .grapheme_width_method = GRAPHEME_WIDTH_WCSWIDTH, + .grapheme_width_method = GRAPHEME_WIDTH_DOUBLE, .delayed_render_lower_ns = 500000, /* 0.5ms */ .delayed_render_upper_ns = 16666666 / 2, /* half a frame period (60Hz) */ .max_shm_pool_size = 512 * 1024 * 1024, @@ -3322,20 +3392,48 @@ config_font_parse(const char *pattern, struct config_font *font) if (pat == NULL) return false; + /* + * First look for user specified {pixel}size option + * e.g. “font-name:size=12” + */ + double pt_size = -1.0; - FcPatternGetDouble(pat, FC_SIZE, 0, &pt_size); - FcPatternRemove(pat, FC_SIZE, 0); + FcResult have_pt_size = FcPatternGetDouble(pat, FC_SIZE, 0, &pt_size); int px_size = -1; - FcPatternGetInteger(pat, FC_PIXEL_SIZE, 0, &px_size); - FcPatternRemove(pat, FC_PIXEL_SIZE, 0); + FcResult have_px_size = FcPatternGetInteger(pat, FC_PIXEL_SIZE, 0, &px_size); - if (pt_size == -1. && px_size == -1) - pt_size = 8.0; + if (have_pt_size != FcResultMatch && have_px_size != FcResultMatch) { + /* + * Apply fontconfig config. Can’t do that until we’ve first + * checked for a user provided size, since we may end up with + * both “size” and “pixelsize” being set, and we don’t know + * which one takes priority. + */ + FcPattern *pat_copy = FcPatternDuplicate(pat); + if (pat_copy == NULL || + !FcConfigSubstitute(NULL, pat_copy, FcMatchPattern)) + { + LOG_WARN("%s: failed to do config substitution", pattern); + } else { + have_pt_size = FcPatternGetDouble(pat_copy, FC_SIZE, 0, &pt_size); + have_px_size = FcPatternGetInteger(pat_copy, FC_PIXEL_SIZE, 0, &px_size); + } + + FcPatternDestroy(pat_copy); + + if (have_pt_size != FcResultMatch && have_px_size != FcResultMatch) + pt_size = 8.0; + } + + FcPatternRemove(pat, FC_SIZE, 0); + FcPatternRemove(pat, FC_PIXEL_SIZE, 0); char *stripped_pattern = (char *)FcNameUnparse(pat); FcPatternDestroy(pat); + LOG_DBG("%s: pt-size=%.2f, px-size=%d", stripped_pattern, pt_size, px_size); + *font = (struct config_font){ .pattern = stripped_pattern, .pt_size = pt_size, diff --git a/config.h b/config.h index d35abbb2..31dddc64 100644 --- a/config.h +++ b/config.h @@ -22,6 +22,11 @@ struct pt_or_px { float pt; }; +struct font_size_adjustment { + struct pt_or_px pt_or_px; + float percent; +}; + enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM }; enum conf_size_type {CONF_SIZE_PX, CONF_SIZE_CELLS}; @@ -69,11 +74,6 @@ enum key_binding_type { MOUSE_BINDING, }; -struct config_key_binding_text { - char *text; - bool master_copy; -}; - struct config_key_binding { int action; /* One of the varios bind_action_* enums from wayland.h */ struct config_key_modifiers modifiers; @@ -139,6 +139,7 @@ struct config { enum {DPI_AWARE_AUTO, DPI_AWARE_YES, DPI_AWARE_NO} dpi_aware; struct config_font_list fonts[4]; + struct font_size_adjustment font_size_adjustment; /* Custom font metrics (-1 = use real font metrics) */ struct pt_or_px line_height; diff --git a/csi.c b/csi.c index 2eacaaa9..d22c1da5 100644 --- a/csi.c +++ b/csi.c @@ -276,21 +276,6 @@ decset_decrst(struct terminal *term, unsigned param, bool enable) enable ? CURSOR_KEYS_APPLICATION : CURSOR_KEYS_NORMAL; break; - case 3: - /* DECCOLM */ - if (enable) - LOG_WARN("unimplemented: 132 column mode (DECCOLM)"); - - term_erase(term, 0, 0, term->rows - 1, term->cols - 1); - term_cursor_home(term); - break; - - case 4: - /* DECSCLM - Smooth scroll */ - if (enable) - LOG_WARN("unimplemented: Smooth (Slow) Scroll (DECSCLM)"); - break; - case 5: /* DECSCNM */ term->reverse = enable; @@ -558,8 +543,6 @@ decrqm(const struct terminal *term, unsigned param) { switch (param) { case 1: return decrpm(term->cursor_keys_mode == CURSOR_KEYS_APPLICATION); - case 3: return DECRPM_PERMANENTLY_RESET; - case 4: return DECRPM_PERMANENTLY_RESET; case 5: return decrpm(term->reverse); case 6: return decrpm(term->origin); case 7: return decrpm(term->auto_margin); @@ -601,8 +584,6 @@ xtsave(struct terminal *term, unsigned param) { switch (param) { case 1: term->xtsave.application_cursor_keys = term->cursor_keys_mode == CURSOR_KEYS_APPLICATION; break; - case 3: break; - case 4: break; case 5: term->xtsave.reverse = term->reverse; break; case 6: term->xtsave.origin = term->origin; break; case 7: term->xtsave.auto_margin = term->auto_margin; break; @@ -644,8 +625,6 @@ xtrestore(struct terminal *term, unsigned param) bool enable; switch (param) { case 1: enable = term->xtsave.application_cursor_keys; break; - case 3: return; - case 4: return; case 5: enable = term->xtsave.reverse; break; case 6: enable = term->xtsave.origin; break; case 7: enable = term->xtsave.auto_margin; break; @@ -933,7 +912,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } - case 'L': { + case 'L': { /* IL */ if (term->grid->cursor.point.row < term->scroll_region.start || term->grid->cursor.point.row >= term->scroll_region.end) break; @@ -953,7 +932,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } - case 'M': { + case 'M': { /* DL */ if (term->grid->cursor.point.row < term->scroll_region.start || term->grid->cursor.point.row >= term->scroll_region.end) break; @@ -1518,7 +1497,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; /* final == 'm' */ case 'n': { - int resource = vt_param_get(term, 0, 2); /* Default is modifyFuncionKeys */ + int resource = vt_param_get(term, 0, 2); /* Default is modifyFunctionKeys */ switch (resource) { case 0: /* modifyKeyboard */ case 1: /* modifyCursorKeys */ diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index 1d94219f..ec970127 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -22,7 +22,7 @@ This document describes all the control sequences supported by foot. [[ *Sequence* :[ *Name* -:[ *Description* +:< *Description* | \\a : BEL : Depends on what *bell* in *foot.ini*(5) is set to. @@ -60,7 +60,7 @@ equivalent to 8-bit C1 controls. [[ *Sequence* :[ *Name* :[ *Origin* -:[ *Description* +:< *Description* | \\E 7 : DECSC : VT100 @@ -140,7 +140,7 @@ single CSI sequence by separating them with semicolons: *\\E[ 1;2;3 m*. [[ *Parameter* -:[ *Description* +:< *Description* | 0 : Reset all attributes | 1 @@ -223,7 +223,7 @@ following 4 escape sequences: [[ *Sequence* :[ *Name* -:[ *Description* +:< *Description* | \\E[ ? _Pm_ h : DECSET : Enable private mode @@ -243,7 +243,7 @@ that corresponds to one of the following modes: [[ *Parameter* :[ *Origin* -:[ *Description* +:< *Description* | 1 : VT100 : Cursor keys mode (DECCKM) @@ -344,7 +344,7 @@ manipulation sequences. The generic format is: [[ *Parameter 1* :[ *Parameter 2* -:[ *Description* +:< *Description* | 11 : - : Report if window is iconified. Foot always reports *1* - not iconified. @@ -394,7 +394,7 @@ manipulation sequences. The generic format is: [[ *Parameter* :[ *Name* :[ *Origin* -:[ *Description* +:< *Description* | \\E[ _Ps_ c : DA : VT100 @@ -504,7 +504,7 @@ manipulation sequences. The generic format is: | \\E[ = _Ps_ c : DA3 : VT510 -: send tertiary device attributes. Foot responds with "FOOT", in +: Send tertiary device attributes. Foot responds with "FOOT", in hexadecimal. | \\E[ _Pm_ d : VPA @@ -595,7 +595,7 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_. [[ *Sequence* :[ *Origin* -:[ *Description* +:< *Description* | \\E] 0 ; _Pt_ \\E\\ : xterm : Set window icon and title to _Pt_ (foot does not support setting the @@ -693,7 +693,7 @@ All _DCS_ sequences begin with *\\EP* (sometimes abbreviated _DCS_), and are terminated by *\\E\\* (ST). [[ *Sequence* -:[ *Description* +:< *Description* | \\EP q \\E\\ : Emit a sixel image at the current cursor position | \\P $ q \\E\\ @@ -703,7 +703,7 @@ and are terminated by *\\E\\* (ST). : Begin (_C_=*1*) or end (_C_=*2*) application synchronized updates. This sequence is supported for compatibility reasons, but it's recommended to use private mode 2026 (see above) instead. -| \\EP + q \\E\\ +| \\EP + q \\E\\ : Query builtin terminfo database (XTGETTCAP) diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 58dd4790..1950bbf5 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -142,7 +142,7 @@ the foot command line *-d*,*--log-level*={*info*,*warning*,*error*,*none*} Log level, used both for log output on stderr as well as - syslog. Default: _info_. + syslog. Default: _warning_. *-l*,*--log-colorize*=[{*never*,*always*,*auto*}] Enables or disables colorization of log output on stderr. Default: @@ -543,10 +543,24 @@ In all other cases, the exit code is that of the client application set according to either the *--term* command-line option or the *term* config option in *foot.ini*(5). +*PWD* + Current working directory (at the time of launching foot) + *COLORTERM* This variable is set to *truecolor*, to indicate to client applications that 24-bit RGB colors are supported. +*TERM_PROGRAM* + Always set to *foot*. This can be used by client applications to + check which terminal is in use, but with the caveat that it may + have been inherited from a parent process in other terminals that + aren't known to set the variable. + +*TERM_PROGRAM_VERSION* + Set to the foot version string, in the format _major_*.*_minor_*.*_patch_ + or _major_*.*_minor_*.*_patch_*-*_revision_*-\g*_commit_ for inter-release + builds. The same caveat as for *TERM_PROGRAM* applies. + In addition to the variables listed above, custom environment variables may be defined in *foot.ini*(5). @@ -561,7 +575,7 @@ the same issue. The report should contain the following: - Foot version (*foot --version*). -- Log output from foot (start foot from another terminal). +- Log output from foot (run *foot -d info* from another terminal). - Which Wayland compositor (and version) you are running. - If reporting a crash, please try to provide a *bt full* backtrace with symbols. diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index c9e1051d..59526028 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -22,6 +22,15 @@ in this order: An example configuration file containing all options with their default value commented out will usually be installed to */etc/xdg/foot/foot.ini*. +Options are set using KEY=VALUE pairs: + + *\[colors\]*++ +*background=000000*++ +*foreground=ffffff* + +Empty values (*KEY=*) are not supported. String options do allow the +empty string to be set, but it must be quoted: *KEY=""*) + # SECTION: main *shell* @@ -71,6 +80,19 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*. Default: _monospace:size=8_ (*font*), _not set_ (*font-bold*, *font-italic*, *font-bold-italic*). +*font-size-adjustment* + Amount, in _points_, _pixels_ or _percent_, to increment/decrement + the font size when zooming in our out. + + Examples: + ``` + font-size-adjustment=0.5 # Adjust by 0.5 points + font-size-adjustment=10xp # Adjust by 10 pixels + font-size-adjustment=7.5% # Adjust by 7.5 percent + ``` + + Default: _0.5_ + *include* Absolute path to configuration file to import. @@ -213,15 +235,19 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*. Default: _0x0_. *resize-delay-ms* - Time, in milliseconds, of "idle time" before foot sends the new - window dimensions to the client application while doing an - interactive resize of a foot window. Idle time in this context is - a period of time where the window size is not changing. + + Time, in milliseconds, of "idle time" before foot performs text + reflow, and sends the new window dimensions to the client + application while doing an interactive resize of a foot + window. Idle time in this context is a period of time where the + window size is not changing. In other words, while you are fiddling with the window size, foot - does not send the updated dimensions to the client. Only when you - pause the fiddling for *resize-delay-ms* milliseconds is the - client updated. + does not send the updated dimensions to the client. It also does a + fast "truncating" resize of the grid, instead of actually + reflowing the contents. Only when you pause the fiddling for + *resize-delay-ms* milliseconds is the client updated, and the + contents properly reflowed. Emphasis is on _while_ here; as soon as the interactive resize ends (i.e. when you let go of the window border), the final @@ -339,7 +365,7 @@ Note: do not set *TERM* here; use the *term* option in the main *urgent* When set to _yes_, foot will signal urgency to the compositor through the XDG activation protocol whenever *BEL* is received, - and the window does NOT have keyboard foccus. + and the window does NOT have keyboard focus. If the compositor does not implement this protocol, the margins will be painted in red instead. @@ -448,14 +474,13 @@ applications can change these at runtime. by applications. Default: _no_. *color* - Two RRGGBB values (i.e. plain old 6-digit hex values, without - prefix) specifying the foreground (text) and background (cursor) - colors for the cursor. + Two space separated RRGGBB values (i.e. plain old 6-digit hex + values, without prefix) specifying the foreground (text) and + background (cursor) colors for the cursor. - Default: _inverse foreground/background colors_. - - Note that this value only applies to the block cursor. The other - cursor styles are always rendered with the foreground color. + Example: *ff0000 00ff00* (green cursor, red text) + + Default: the regular foreground and background colors, reversed. *beam-thickness* Thickness (width) of the beam styled cursor. The value is in @@ -551,7 +576,7 @@ can configure the background transparency with the _alpha_ option. options are unconfigured). 24-bit RGB colors will typically fall into this category. - Note that applications can change the *regularN* and *brighN* + Note that applications can change the *regularN* and *brightN* colors at runtime. However, they have no way of changing the *dimN* colors. If an application has changed the *regularN* colors, foot will still use the corresponding *dimN* color, as @@ -939,7 +964,7 @@ Be careful; do not use single-letter keys that are also used in original text. But with e.g. OSC-8 URLs (the terminal version of HTML anchors, - i.e. "links"), the text on the screen can be something completey + i.e. "links"), the text on the screen can be something completely different than the URL. This action toggles between showing and hiding the URL on the jump @@ -1235,7 +1260,7 @@ any of these options. *max* uses the width of the largest codepoint in the cluster. - Default: _wcswidth_ + Default: _double-width_ *font-monospace-warn* Boolean. When enabled, foot will use heuristics to try to verify diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index 967a4f1b..189d9e3c 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -75,7 +75,7 @@ terminal has terminated. *-d*,*--log-level*={*info*,*warning*,*error*,*none*} Log level, used both for log output on stderr as well as - syslog. Default: _info_. + syslog. Default: _warning_. *-l*,*--log-colorize*=[{*never*,*always*,*auto*}] Enables or disables colorization of log output on stderr. @@ -89,7 +89,7 @@ terminal has terminated. # EXIT STATUS -Footlient will exit with code 220 if there is a failure in footclient +Footclient will exit with code 220 if there is a failure in footclient itself (for example, the server socket does not exist). If *-N*,*--no-wait* is used, footclient exits with code 0 as soon as @@ -158,6 +158,17 @@ terminfo entries manually, by copying *foot* and *foot-direct* to This variable is set to *truecolor*, to indicate to client applications that 24-bit RGB colors are supported. +*TERM_PROGRAM* + Always set to *foot*. This can be used by client applications to + check which terminal is in use, but with the caveat that it may + have been inherited from a parent process in other terminals that + aren't known to set the variable. + +*TERM_PROGRAM_VERSION* + Set to the foot version string, in the format _major_*.*_minor_*.*_patch_ + or _major_*.*_minor_*.*_patch_*-*_revision_*-\g*_commit_ for inter-release + builds. The same caveat as for *TERM_PROGRAM* applies. + In addition to the variables listed above, custom environment variables may be defined in *foot.ini*(5). diff --git a/foot.info b/foot.info index f4030b22..cf81d721 100644 --- a/foot.info +++ b/foot.info @@ -12,6 +12,10 @@ setaf=\E[%?%p1%{8}%<%t3%p1%d%e38\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m, @default_terminfo@+base|foot base fragment, + AX, + Tc, + XF, + XT, am, bce, bw, @@ -21,21 +25,24 @@ msgr, npc, xenl, - AX, - XT, - Tc, cols#80, it#8, lines#24, pairs#0x10000, + BD=\E[?2004l, + BE=\E[?2004h, Cr=\E]112\E\\, Cs=\E]12;%p1%s\E\\, E3=\E[3J, Ms=\E]52;%p1%s;%p2%s\E\\, + PE=\E[201~, + PS=\E[200~, + RV=\E[>c, Se=\E[ q, Ss=\E[%p1%d q, Sync=\E[?2026%?%p1%{1}%-%tl%eh, XM=\E[?1006;1000%?%p1%{1}%=%th%el%;, + XR=\E[>0q, acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, bel=^G, blink=\E[5m, @@ -79,7 +86,7 @@ indn=\E[%p1%dS, initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, invis=\E[8m, - is2=\E[!p\E[?3;4l\E[4l\E>, + is2=\E[!p\E[4l\E>, kDC3=\E[3;3~, kDC4=\E[3;4~, kDC5=\E[3;5~, @@ -237,7 +244,8 @@ rmul=\E[24m, rmxx=\E[29m, rs1=\Ec, - rs2=\E[!p\E[?3;4l\E[4l\E>, + rs2=\E[!p\E[4l\E>, + rv=\E\\[[0-9]+;[0-9]+;[0-9]+c, sc=\E7, setrgbb=\E[48\:2\:\:%p1%d\:%p2%d\:%p3%dm, setrgbf=\E[38\:2\:\:%p1%d\:%p2%d\:%p3%dm, @@ -260,6 +268,7 @@ u9=\E[c, vpa=\E[%i%p1%dd, xm=\E[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;, + xr=\EP>\\|[ -~]+\E\\\\, # XT, # AX, diff --git a/foot.ini b/foot.ini index b2f71fc7..25c49363 100644 --- a/foot.ini +++ b/foot.ini @@ -12,6 +12,7 @@ # font-bold= # font-italic= # font-bold-italic= +# font-size-adjustment=0.5 # line-height= # letter-spacing=0 # horizontal-letter-offset=0 @@ -48,7 +49,7 @@ # lines=1000 # multiplier=3.0 # indicator-position=relative -# indicator-format= +# indicator-format="" [url] # launch=xdg-open ${url} diff --git a/generate-version.sh b/generate-version.sh index a030d512..3772008b 100755 --- a/generate-version.sh +++ b/generate-version.sh @@ -41,6 +41,7 @@ patch=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\3/') extra=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+)(-([0-9]+-g[a-z0-9]+) .*)?.*/\5/') new_version="#define FOOT_VERSION \"${new_version}\" +#define FOOT_VERSION_SHORT \"${git_version:-${default_version}}\" #define FOOT_MAJOR ${major} #define FOOT_MINOR ${minor} #define FOOT_PATCH ${patch} diff --git a/grid.c b/grid.c index 7bfef5cb..e1c4d28b 100644 --- a/grid.c +++ b/grid.c @@ -210,6 +210,8 @@ grid_snapshot(const struct grid *grid) clone->offset = grid->offset; clone->view = grid->view; clone->cursor = grid->cursor; + clone->saved_cursor = grid->saved_cursor; + clone->kitty_kbd = grid->kitty_kbd; clone->rows = xcalloc(grid->num_rows, sizeof(clone->rows[0])); memset(&clone->scroll_damage, 0, sizeof(clone->scroll_damage)); memset(&clone->sixel_images, 0, sizeof(clone->sixel_images)); @@ -285,6 +287,9 @@ grid_snapshot(const struct grid *grid) void grid_free(struct grid *grid) { + if (grid == NULL) + return; + for (int r = 0; r < grid->num_rows; r++) grid_row_free(grid->rows[r]); @@ -483,6 +488,8 @@ grid_resize_without_reflow( grid->saved_cursor.point = saved_cursor; grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)]; + xassert(grid->cur_row != NULL); + grid->cursor.lcf = false; grid->saved_cursor.lcf = false; @@ -786,7 +793,7 @@ grid_resize_and_reflow( /* * Set end-coordinate for this chunk, by finding the next - * point-of-interrest on this row. + * point-of-interest on this row. * * If there are no more tracking points, or URI ranges, * the end-coordinate will be at the end of the row, @@ -1045,6 +1052,8 @@ grid_resize_and_reflow( saved_cursor.col = min(saved_cursor.col, new_cols - 1); grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)]; + xassert(grid->cur_row != NULL); + grid->cursor.point = cursor; grid->saved_cursor.point = saved_cursor; diff --git a/input.c b/input.c index 0a3773bc..7e5d204d 100644 --- a/input.c +++ b/input.c @@ -898,7 +898,7 @@ struct kbd_ctx { const uint8_t *buf; size_t count; } utf8; - uint32_t utf32; + uint32_t *utf32; enum xkb_compose_status compose_status; enum wl_keyboard_key_state key_state; @@ -1121,12 +1121,18 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, (seat->kbd.mod_num != XKB_MOD_INVALID ? 1 << seat->kbd.mod_num : 0); const xkb_keysym_t sym = ctx->sym; - const uint32_t utf32 = ctx->utf32; + const uint32_t *utf32 = ctx->utf32; const uint8_t *const utf8 = ctx->utf8.buf; - - const bool is_text = iswprint(utf32) && (effective & ~caps_num) == 0; const size_t count = ctx->utf8.count; + bool is_text = count > 0 && utf32 != NULL && (effective & ~caps_num) == 0; + for (size_t i = 0; utf32[i] != U'\0'; i++) { + if (!iswprint(utf32[i])) { + is_text = false; + break; + } + } + const bool report_associated_text = (flags & KITTY_KBD_REPORT_ASSOCIATED) && is_text && !released; @@ -1245,7 +1251,7 @@ emit_escapes: : sym; if (composed) - key = utf32; + key = utf32[0]; /* TODO: what if there are multiple codepoints? */ else { key = xkb_keysym_to_utf32(sym_to_use); if (key == 0) @@ -1284,7 +1290,7 @@ emit_escapes: } else event[0] = '\0'; - char buf[64], *p = buf; + char buf[128], *p = buf; size_t left = sizeof(buf); size_t bytes; @@ -1316,8 +1322,16 @@ emit_escapes: } if (report_associated_text) { - bytes = snprintf(p, left, "%s;%u", !emit_mods ? ";" : "", utf32); + bytes = snprintf(p, left, "%s;%u", !emit_mods ? ";" : "", utf32[0]); p += bytes; left -= bytes; + + /* Additional text codepoints */ + if (utf32[0] != U'\0') { + for (size_t i = 1; utf32[i] != U'\0'; i++) { + bytes = snprintf(p, left, ":%u", utf32[i]); + p += bytes; left -= bytes; + } + } } bytes = snprintf(p, left, "%c", final); @@ -1514,19 +1528,20 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, * and use a malloc:ed buffer when necessary */ uint8_t buf[32]; uint8_t *utf8 = count < sizeof(buf) ? buf : xmalloc(count + 1); - uint32_t utf32 = (uint32_t)-1; + uint32_t *utf32 = NULL; if (composed) { xkb_compose_state_get_utf8( seat->kbd.xkb_compose_state, (char *)utf8, count + 1); - char32_t wc; - if (mbrtoc32(&wc, (const char *)utf8, count, &(mbstate_t){0}) == count) - utf32 = wc; + if (count > 0) + utf32 = ambstoc32((const char *)utf8); } else { xkb_state_key_get_utf8( seat->kbd.xkb_state, key, (char *)utf8, count + 1); - utf32 = xkb_state_key_get_utf32(seat->kbd.xkb_state, key); + + utf32 = xcalloc(2, sizeof(utf32[0])); + utf32[0] = xkb_state_key_get_utf32(seat->kbd.xkb_state, key); } struct kbd_ctx ctx = { @@ -1563,6 +1578,8 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, selection_cancel(term); } + free(utf32); + maybe_repeat: clock_gettime( term->wl->presentation_clock_id, &term->render.input_time); diff --git a/key-binding.c b/key-binding.c index 1876a885..1dffd3ee 100644 --- a/key-binding.c +++ b/key-binding.c @@ -350,6 +350,60 @@ maybe_repair_key_combo(const struct seat *seat, return sym; } +static int +key_cmp(struct key_binding a, struct key_binding b) +{ + xassert(a.type == b.type); + + /* + * Sort bindings such that bindings with the same symbol are + * sorted with the binding having the most modifiers comes first. + * + * This fixes an issue where the “wrong” key binding are triggered + * when used with “consumed” modifiers. + * + * For example: if Control+BackSpace is bound before + * Control+Shift+BackSpace, then the latter binding is never + * triggered. + * + * Why? Because Shift is a consumed modifier. This means + * Control+BackSpace is “the same” as Control+Shift+BackSpace. + * + * By sorting bindings with more modifiers first, we work around + * the problem. But note that it is *just* a workaround, and I’m + * not confident there aren’t cases where it doesn’t work. + * + * See https://codeberg.org/dnkl/foot/issues/1280 + */ + + const int a_mod_count = __builtin_popcount(a.mods); + const int b_mod_count = __builtin_popcount(b.mods); + + switch (a.type) { + case KEY_BINDING: + if (a.k.sym != b.k.sym) + return b.k.sym - a.k.sym; + return b_mod_count - a_mod_count; + + case MOUSE_BINDING: { + if (a.m.button != b.m.button) + return b.m.button - a.m.button; + if (a_mod_count != b_mod_count) + return b_mod_count - a_mod_count; + return b.m.count - a.m.count; + } + } + + BUG("invalid key binding type"); + return 0; +} + +static void NOINLINE +sort_binding_list(key_binding_list_t *list) +{ + tll_sort(*list, key_cmp); +} + static void NOINLINE convert_key_binding(struct key_set *set, const struct config_key_binding *conf_binding, @@ -371,6 +425,7 @@ convert_key_binding(struct key_set *set, }, }; tll_push_back(*bindings, binding); + sort_binding_list(bindings); } static void @@ -421,6 +476,7 @@ convert_mouse_binding(struct key_set *set, }, }; tll_push_back(set->public.mouse, binding); + sort_binding_list(&set->public.mouse); } static void diff --git a/log.c b/log.c index b93b2cde..360ca1c0 100644 --- a/log.c +++ b/log.c @@ -15,7 +15,7 @@ #include "xsnprintf.h" static bool colorize = false; -static bool do_syslog = true; +static bool do_syslog = false; static enum log_class log_level = LOG_CLASS_NONE; static const struct { @@ -45,8 +45,13 @@ log_init(enum log_colorize _colorize, bool _do_syslog, log_level = _log_level; int slvl = log_level_map[_log_level].syslog_equivalent; - if (do_syslog && slvl != -1) { + if (slvl < 0) + do_syslog = false; + + if (do_syslog) { openlog(NULL, /*LOG_PID*/0, facility_map[syslog_facility]); + + xassert(slvl >= 0); setlogmask(LOG_UPTO(slvl)); } } diff --git a/main.c b/main.c index a3ae579d..4af200fd 100644 --- a/main.c +++ b/main.c @@ -83,7 +83,7 @@ print_usage(const char *prog_name) " Without PATH, $XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock will be used.\n" " -H,--hold remain open after child process exits\n" " -p,--print-pid=FILE|FD print PID to file or FD (only applicable in server mode)\n" - " -d,--log-level={info|warning|error|none} log level (info)\n" + " -d,--log-level={info|warning|error|none} log level (warning)\n" " -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n" " -s,--log-no-syslog disable syslog logging (only applicable in server mode)\n" " -v,--version show the version number and quit\n" @@ -236,7 +236,7 @@ main(int argc, char *const *argv) bool fullscreen = false; bool unlink_pid_file = false; const char *pid_file = NULL; - enum log_class log_level = LOG_CLASS_INFO; + enum log_class log_level = LOG_CLASS_WARNING; enum log_colorize log_colorize = LOG_COLORIZE_AUTO; bool log_syslog = true; user_notifications_t user_notifications = tll_init(); @@ -433,8 +433,13 @@ main(int argc, char *const *argv) const char *locale = setlocale(LC_CTYPE, ""); if (locale == NULL) { - LOG_ERR("setlocale() failed"); - return ret; + /* + * If the user has configured an invalid locale, or a name of a locale + * that does not exist on this system, then the above call may return + * NULL. We should just continue with the fallback method below. + */ + LOG_WARN("setlocale() failed"); + locale = "C"; } LOG_INFO("locale: %s", locale); diff --git a/meson.build b/meson.build index 4d3b6213..a6892cc4 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.13.1', + version: '1.14.0', license: 'MIT', meson_version: '>=0.58.0', default_options: [ @@ -110,7 +110,7 @@ if utf8proc.found() add_project_arguments('-DFOOT_GRAPHEME_CLUSTERING=1', language: 'c') endif -tllist = dependency('tllist', version: '>=1.0.4', fallback: 'tllist') +tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist') fcft = dependency('fcft', version: ['>=3.0.1', '<4.0.0'], fallback: 'fcft') wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir') @@ -331,6 +331,7 @@ endif subdir('completions') subdir('icons') +subdir('utils') if (get_option('tests')) subdir('tests') diff --git a/org.codeberg.dnkl.foot-server.desktop b/org.codeberg.dnkl.foot-server.desktop index 6e8891c0..a40117c7 100644 --- a/org.codeberg.dnkl.foot-server.desktop +++ b/org.codeberg.dnkl.foot-server.desktop @@ -9,3 +9,4 @@ Keywords=shell;prompt;command;commandline; Name=Foot Server GenericName=Terminal Comment=A wayland native terminal emulator (server) +StartupWMClass=foot diff --git a/org.codeberg.dnkl.foot.desktop b/org.codeberg.dnkl.foot.desktop index f072568d..720d35a9 100644 --- a/org.codeberg.dnkl.foot.desktop +++ b/org.codeberg.dnkl.foot.desktop @@ -9,3 +9,4 @@ Keywords=shell;prompt;command;commandline; Name=Foot GenericName=Terminal Comment=A wayland native terminal emulator +StartupWMClass=foot diff --git a/org.codeberg.dnkl.foot.metainfo.xml b/org.codeberg.dnkl.foot.metainfo.xml index 22512ce8..1c0b7985 100644 --- a/org.codeberg.dnkl.foot.metainfo.xml +++ b/org.codeberg.dnkl.foot.metainfo.xml @@ -1,7 +1,7 @@ org.codeberg.dnkl.foot - CC0-1.0 + MIT MIT dnkl foot diff --git a/org.codeberg.dnkl.footclient.desktop b/org.codeberg.dnkl.footclient.desktop index f82f282b..dc8bc5dc 100644 --- a/org.codeberg.dnkl.footclient.desktop +++ b/org.codeberg.dnkl.footclient.desktop @@ -9,3 +9,4 @@ Keywords=shell;prompt;command;commandline; Name=Foot Client GenericName=Terminal Comment=A wayland native terminal emulator (client) +StartupWMClass=foot diff --git a/pgo/full-inner.sh b/pgo/full-inner.sh index 599d6aad..c2205e5e 100755 --- a/pgo/full-inner.sh +++ b/pgo/full-inner.sh @@ -15,6 +15,7 @@ rm -f "${blddir}"/pgo-ok # To ensure profiling data is generated in the build directory cd "${blddir}" +"${blddir}"/utils/xtgettcap "${blddir}"/footclient --version "${blddir}"/foot \ --config=/dev/null \ diff --git a/pgo/partial.sh b/pgo/partial.sh index c16de324..6d6fdffe 100755 --- a/pgo/partial.sh +++ b/pgo/partial.sh @@ -21,6 +21,7 @@ rm -f "${blddir}"/pgo-ok # To ensure profiling data is generated in the build directory cd "${blddir}" +"${blddir}"/utils/xtgettcap "${blddir}"/footclient --version "${blddir}"/foot --version "${blddir}"/pgo "${pgo_data}" diff --git a/pgo/pgo.c b/pgo/pgo.c index 7f6f758b..b41b5850 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -228,10 +228,14 @@ main(int argc, const char *const *argv) return EXIT_FAILURE; } - struct row **rows = calloc(grid_row_count, sizeof(rows[0])); + struct row **normal_rows = calloc(grid_row_count, sizeof(normal_rows[0])); + struct row **alt_rows = calloc(grid_row_count, sizeof(alt_rows[0])); + for (int i = 0; i < grid_row_count; i++) { - rows[i] = calloc(1, sizeof(*rows[i])); - rows[i]->cells = calloc(col_count, sizeof(rows[i]->cells[0])); + normal_rows[i] = calloc(1, sizeof(*normal_rows[i])); + normal_rows[i]->cells = calloc(col_count, sizeof(normal_rows[i]->cells[0])); + alt_rows[i] = calloc(1, sizeof(*alt_rows[i])); + alt_rows[i]->cells = calloc(col_count, sizeof(alt_rows[i]->cells[0])); } struct config conf = { @@ -254,14 +258,14 @@ main(int argc, const char *const *argv) .normal = { .num_rows = grid_row_count, .num_cols = col_count, - .rows = rows, - .cur_row = rows[0], + .rows = normal_rows, + .cur_row = normal_rows[0], }, .alt = { .num_rows = grid_row_count, .num_cols = col_count, - .rows = rows, - .cur_row = rows[0], + .rows = alt_rows, + .cur_row = alt_rows[0], }, .scale = 1, .width = col_count * 8, @@ -371,11 +375,17 @@ out: tll_free(wayl.terms); for (int i = 0; i < grid_row_count; i++) { - free(rows[i]->cells); - free(rows[i]); + if (normal_rows[i] != NULL) + free(normal_rows[i]->cells); + free(normal_rows[i]); + + if (alt_rows[i] != NULL) + free(alt_rows[i]->cells); + free(alt_rows[i]); } - free(rows); + free(normal_rows); + free(alt_rows); close(lower_fd); close(upper_fd); return ret; diff --git a/pgo/pgo.sh b/pgo/pgo.sh index b2ce7fe5..2f409268 100755 --- a/pgo/pgo.sh +++ b/pgo/pgo.sh @@ -30,7 +30,7 @@ do_pgo=no CFLAGS="${CFLAGS-} -O3" case $(${CC-cc} --version) in - *GCC*) + *Free\ Software\ Foundation*) compiler=gcc do_pgo=yes ;; diff --git a/render.c b/render.c index b285b120..521c8b7f 100644 --- a/render.c +++ b/render.c @@ -420,6 +420,12 @@ cursor_colors_for_cell(const struct terminal *term, const struct cell *cell, } else { *cursor_color = *fg; *text_color = *bg; + + if (unlikely(text_color->alpha != 0xffff)) { + /* The *only* color that can have transparency is the + * default background color */ + *text_color = color_hex_to_pixman(term->colors.bg); + } } } @@ -639,17 +645,21 @@ render_cell(struct terminal *term, pixman_image_t *pix, } } - if (single == NULL && grapheme == NULL) { - xassert(base != 0); - single = fcft_rasterize_char_utf32(font, base, term->font_subpixel); - if (single == NULL) { + if (unlikely(base >= CELL_SPACER)) { glyph_count = 0; cell_cols = 1; } else { - glyph_count = 1; - glyphs = &single; - cell_cols = single->cols; + xassert(base != 0); + single = fcft_rasterize_char_utf32(font, base, term->font_subpixel); + if (single == NULL) { + glyph_count = 0; + cell_cols = 1; + } else { + glyph_count = 1; + glyphs = &single; + cell_cols = single->cols; + } } } } @@ -919,14 +929,19 @@ static void grid_render_scroll(struct terminal *term, struct buffer *buf, const struct damage *dmg) { - int height = (dmg->region.end - dmg->region.start - dmg->lines) * term->cell_height; - LOG_DBG( "damage: SCROLL: %d-%d by %d lines", dmg->region.start, dmg->region.end, dmg->lines); - if (height <= 0) + const int region_size = dmg->region.end - dmg->region.start; + + if (dmg->lines >= region_size) { + /* The entire scroll region will be scrolled out (i.e. replaced) */ return; + } + + const int height = (region_size - dmg->lines) * term->cell_height; + xassert(height > 0); #if TIME_SCROLL_DAMAGE struct timespec start_time; @@ -1027,14 +1042,19 @@ static void grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, const struct damage *dmg) { - int height = (dmg->region.end - dmg->region.start - dmg->lines) * term->cell_height; - LOG_DBG( "damage: SCROLL REVERSE: %d-%d by %d lines", dmg->region.start, dmg->region.end, dmg->lines); - if (height <= 0) + const int region_size = dmg->region.end - dmg->region.start; + + if (dmg->lines >= region_size) { + /* The entire scroll region will be scrolled out (i.e. replaced) */ return; + } + + const int height = (region_size - dmg->lines) * term->cell_height; + xassert(height > 0); #if TIME_SCROLL_DAMAGE struct timespec start_time; @@ -3663,13 +3683,67 @@ tiocswinsz(struct terminal *term) } } +static void +delayed_reflow_of_normal_grid(struct terminal *term) +{ + if (term->interactive_resizing.grid == NULL) + return; + + xassert(term->interactive_resizing.new_rows > 0); + + struct coord *const tracking_points[] = { + &term->selection.coords.start, + &term->selection.coords.end, + }; + + /* Reflow the original (since before the resize was started) grid, + * to the *current* dimensions */ + grid_resize_and_reflow( + term->interactive_resizing.grid, + term->interactive_resizing.new_rows, term->normal.num_cols, + term->interactive_resizing.old_screen_rows, term->rows, + term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, + tracking_points); + + /* Replace the current, truncated, “normal” grid with the + * correctly reflowed one */ + grid_free(&term->normal); + term->normal = *term->interactive_resizing.grid; + free(term->interactive_resizing.grid); + + term->hide_cursor = term->interactive_resizing.old_hide_cursor; + + /* Reset */ + term->interactive_resizing.grid = NULL; + term->interactive_resizing.old_screen_rows = 0; + term->interactive_resizing.new_rows = 0; + term->interactive_resizing.old_hide_cursor = false; + + /* Invalidate render pointers */ + shm_unref(term->render.last_buf); + term->render.last_buf = NULL; + term->render.last_cursor.row = NULL; + + tll_free(term->normal.scroll_damage); + sixel_reflow_grid(term, &term->normal); + + if (term->grid == &term->normal) { + term_damage_view(term); + render_refresh(term); + } + + term_ptmx_resume(term); +} + static bool fdm_tiocswinsz(struct fdm *fdm, int fd, int events, void *data) { struct terminal *term = data; - if (events & EPOLLIN) + if (events & EPOLLIN) { tiocswinsz(term); + delayed_reflow_of_normal_grid(term); + } if (term->window->resize_timeout_fd >= 0) { fdm_del(fdm, term->window->resize_timeout_fd); @@ -3686,6 +3760,7 @@ send_dimensions_to_client(struct terminal *term) if (!win->is_resizing || term->conf->resize_delay_ms == 0) { /* Send new dimensions to client immediately */ tiocswinsz(term); + delayed_reflow_of_normal_grid(term); /* And make sure to reset and deallocate a lingering timer */ if (win->resize_timeout_fd >= 0) { @@ -3716,7 +3791,10 @@ send_dimensions_to_client(struct terminal *term) if (fd >= 0) { /* Reset timeout */ const struct itimerspec timeout = { - .it_value = {.tv_sec = 0, .tv_nsec = delay_ms * 1000000}, + .it_value = { + .tv_sec = delay_ms / 1000, + .tv_nsec = (delay_ms % 1000) * 1000000, + }, }; if (timerfd_settime(fd, 0, &timeout, NULL) < 0) { @@ -3727,8 +3805,10 @@ send_dimensions_to_client(struct terminal *term) successfully_scheduled = true; } - if (!successfully_scheduled) + if (!successfully_scheduled) { tiocswinsz(term); + delayed_reflow_of_normal_grid(term); + } } } @@ -3815,9 +3895,9 @@ maybe_resize(struct terminal *term, int width, int height, bool force) const int min_cols = 2; const int min_rows = 1; - /* Minimum window size */ - const int min_width = min_cols * term->cell_width; - const int min_height = min_rows * term->cell_height; + /* Minimum window size (must be divisible by the scaling factor)*/ + const int min_width = (min_cols * term->cell_width + scale - 1) / scale * scale; + const int min_height = (min_rows * term->cell_height + scale - 1) / scale * scale; width = max(width, min_width); height = max(height, min_height); @@ -3844,8 +3924,8 @@ maybe_resize(struct terminal *term, int width, int height, bool force) const uint32_t scrollback_lines = term->render.scrollback_lines; /* Screen rows/cols before resize */ - const int old_cols = term->cols; - const int old_rows = term->rows; + int old_cols = term->cols; + int old_rows = term->rows; /* Screen rows/cols after resize */ const int new_cols = (term->width - 2 * pad_x) / term->cell_width; @@ -3881,9 +3961,86 @@ maybe_resize(struct terminal *term, int width, int height, bool force) if (new_cols == old_cols && new_rows == old_rows) { LOG_DBG("grid layout unaffected; skipping reflow"); + term->interactive_resizing.new_rows = new_normal_grid_rows; goto damage_view; } + + /* + * Since text reflow is slow, don’t do it *while* resizing. Only + * do it when done, or after “pausing” the resize for sufficiently + * long. We re-use the TIOCSWINSZ timer to handle this. See + * send_dimensions_to_client() and fdm_tiocswinsz(). + * + * To be able to do the final reflow correctly, we need a copy of + * the original grid, before the resize started. + */ + if (term->window->is_resizing && term->conf->resize_delay_ms > 0) { + if (term->interactive_resizing.grid == NULL) { + term_ptmx_pause(term); + + /* Stash the current ‘normal’ grid, as-is, to be used when + * doing the final reflow */ + term->interactive_resizing.old_screen_rows = term->rows; + term->interactive_resizing.old_cols = term->cols; + term->interactive_resizing.old_hide_cursor = term->hide_cursor; + term->interactive_resizing.grid = xmalloc(sizeof(*term->interactive_resizing.grid)); + *term->interactive_resizing.grid = term->normal; + term->interactive_resizing.selection_coords = term->selection.coords; + } else { + /* We’ll replace the current temporary grid, with a new + * one (again based on the original grid) */ + grid_free(&term->normal); + } + + struct grid *orig = term->interactive_resizing.grid; + + /* + * Copy the current viewport (of the original grid) to a new + * grid that will be used during the resize. For now, throw + * away sixels and OSC-8 URLs. They’ll be "restored" when we + * do the final reflow. + * + * Note that OSC-8 URLs are perfectly ok to throw away; they + * cannot be interacted with during the resize. And, even if + * url.osc8-underline=always, the “underline” attribute is + * part of the cell, not the URI struct (and thus our faked + * grid will still render OSC-8 links underlined). + * + * TODO: + * - sixels? + */ + struct grid g = { + .num_rows = 1 << (32 - __builtin_clz(term->interactive_resizing.old_screen_rows)), + .num_cols = term->interactive_resizing.old_cols, + .offset = 0, + .view = 0, + .cursor = orig->cursor, + .saved_cursor = orig->saved_cursor, + .rows = xcalloc(g.num_rows, sizeof(g.rows[0])), + .cur_row = NULL, + .scroll_damage = tll_init(), + .sixel_images = tll_init(), + .kitty_kbd = orig->kitty_kbd, + }; + + term->selection.coords.start.row -= orig->view; + term->selection.coords.end.row -= orig->view; + + for (size_t i = 0, j = orig->view; + i < term->interactive_resizing.old_screen_rows; + i++, j = (j + 1) & (orig->num_rows - 1)) + { + g.rows[i] = grid_row_alloc(g.num_cols, false); + memcpy(g.rows[i]->cells, + orig->rows[j]->cells, + g.num_cols * sizeof(g.rows[i]->cells[0])); + } + + term->normal = g; + term->hide_cursor = true; + } + if (term->grid == &term->alt) selection_cancel(term); else { @@ -3903,16 +4060,51 @@ maybe_resize(struct terminal *term, int width, int height, bool force) * selection’s pivot point coordinates *must* be added to the * tracking points list. */ - struct coord *const tracking_points[] = { - &term->selection.coords.start, - &term->selection.coords.end, - }; - /* Resize grids */ - grid_resize_and_reflow( - &term->normal, new_normal_grid_rows, new_cols, old_rows, new_rows, - term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, - tracking_points); + if (term->window->is_resizing && term->conf->resize_delay_ms > 0) { + /* Simple truncating resize, *while* an interactive resize is + * ongoing. */ + xassert(term->interactive_resizing.grid != NULL); + xassert(new_normal_grid_rows > 0); + term->interactive_resizing.new_rows = new_normal_grid_rows; + + grid_resize_without_reflow( + &term->normal, new_alt_grid_rows, new_cols, + term->interactive_resizing.old_screen_rows, new_rows); + } else { + /* Full text reflow */ + + if (term->interactive_resizing.grid != NULL) { + /* Throw away the current, truncated, “normal” grid, and + * use the original grid instead (from before the resize + * started) */ + grid_free(&term->normal); + term->normal = *term->interactive_resizing.grid; + free(term->interactive_resizing.grid); + + term->hide_cursor = term->interactive_resizing.old_hide_cursor; + term->selection.coords = term->interactive_resizing.selection_coords; + + old_rows = term->interactive_resizing.old_screen_rows; + + term->interactive_resizing.grid = NULL; + term->interactive_resizing.old_screen_rows = 0; + term->interactive_resizing.new_rows = 0; + term->interactive_resizing.old_hide_cursor = false; + term->interactive_resizing.selection_coords = (struct range){{-1, -1}, {-1, -1}}; + term_ptmx_resume(term); + } + + struct coord *const tracking_points[] = { + &term->selection.coords.start, + &term->selection.coords.end, + }; + + grid_resize_and_reflow( + &term->normal, new_normal_grid_rows, new_cols, old_rows, new_rows, + term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, + tracking_points); + } grid_resize_without_reflow( &term->alt, new_alt_grid_rows, new_cols, old_rows, new_rows); @@ -3936,9 +4128,11 @@ maybe_resize(struct terminal *term, int width, int height, bool force) if (term->scroll_region.start >= term->rows) term->scroll_region.start = 0; - - if (term->scroll_region.end >= old_rows) + if (term->scroll_region.end > term->rows || + term->scroll_region.end >= old_rows) + { term->scroll_region.end = term->rows; + } term->render.last_cursor.row = NULL; @@ -3956,12 +4150,6 @@ damage_view: term->stashed_height = term->height; } -#if 0 - /* TODO: doesn't include CSD title bar */ - xdg_toplevel_set_min_size( - term->window->xdg_toplevel, min_width / scale, min_height / scale); -#endif - { const bool title_shown = wayl_win_csd_titlebar_visible(term->window); const bool border_shown = wayl_win_csd_borders_visible(term->window); @@ -3971,6 +4159,11 @@ damage_view: const int border_width = border_shown ? term->conf->csd.border_width_visible : 0; + xdg_toplevel_set_min_size( + term->window->xdg_toplevel, + min_width / scale + 2 * border_width, + min_height / scale + title_height + 2 * border_width); + xdg_surface_set_window_geometry( term->window->xdg_surface, -border_width, diff --git a/scripts/generate-builtin-terminfo.py b/scripts/generate-builtin-terminfo.py index 906e2be0..acbf5279 100755 --- a/scripts/generate-builtin-terminfo.py +++ b/scripts/generate-builtin-terminfo.py @@ -52,14 +52,25 @@ class StringCapability(Capability): def __init__(self, name: str, value: str): # Expand \E to literal ESC in non-parameterized capabilities if '%' not in value: + # Ensure e.g. \E7 doesn’t get translated to “\0337”, which + # would be interpreted as octal 337 by the C compiler value = re.sub(r'\\E([0-7])', r'\\033" "\1', value) - value = re.sub(r'\\E', r'\\033', value) - else: - # Need to double-escape \E in C string literals - value = value.replace('\\E', '\\\\E') - # Don’t escape ‘:’ - value = value.replace('\\:', ':') + # Replace \E with an actual escape + value = re.sub(r'\\E', r'\\033', value) + + # Don’t escape ‘:’ + value = value.replace('\\:', ':') + + else: + value = value.replace("\\", "\\\\") + # # Need to double-escape backslashes. These only occur in + # # ‘\E\’ combos. Note that \E itself is updated below + # value = value.replace('\\E\\\\', '\\E\\\\\\\\') + + # # Need to double-escape \E in C string literals + # value = value.replace('\\E', '\\\\E') + super().__init__(name, value) diff --git a/selection.c b/selection.c index 329b2ae3..9fbe4a26 100644 --- a/selection.c +++ b/selection.c @@ -86,7 +86,7 @@ selection_on_rows(const struct terminal *term, int row_start, int row_end) 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); + grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, row_end); int rel_sel_start = grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, start->row); int rel_sel_end = @@ -133,13 +133,18 @@ selection_scroll_down(struct terminal *term, int rows) { xassert(term->selection.coords.end.row >= 0); + const struct grid *grid = term->grid; + const struct range *sel = &term->selection.coords; + + const int screen_end = + grid_row_abs_to_sb(grid, term->rows, grid->offset + term->rows - 1); const int rel_row_start = - grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.start.row); + grid_row_abs_to_sb(term->grid, term->rows, sel->start.row); const int rel_row_end = - grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.end.row); + grid_row_abs_to_sb(term->grid, term->rows, sel->end.row); const int actual_end = max(rel_row_start, rel_row_end); - if (actual_end + rows <= term->grid->num_rows) { + if (actual_end > screen_end - rows) { /* Part of the selection will be scrolled out, cancel it */ selection_cancel(term); } diff --git a/sixel.c b/sixel.c index 0c94117f..592f48f8 100644 --- a/sixel.c +++ b/sixel.c @@ -154,7 +154,7 @@ verify_list_order(const struct terminal *term) int prev_col_count = 0; /* To aid debugging */ - size_t idx = 0; + size_t UNUSED idx = 0; tll_foreach(term->grid->sixel_images, it) { int row = grid_row_abs_to_sb( @@ -838,85 +838,89 @@ sixel_cell_size_changed(struct terminal *term) } void -sixel_reflow(struct terminal *term) +sixel_reflow_grid(struct terminal *term, struct grid *grid) { - struct grid *g = term->grid; + /* Meh - the sixel functions we call use term->grid... */ + struct grid *active_grid = term->grid; + term->grid = grid; - for (size_t i = 0; i < 2; i++) { - struct grid *grid = i == 0 ? &term->normal : &term->alt; + /* Need the “real” list to be empty from the beginning */ + tll(struct sixel) copy = tll_init(); + tll_foreach(grid->sixel_images, it) + tll_push_back(copy, it->item); + tll_free(grid->sixel_images); - term->grid = grid; + tll_rforeach(copy, it) { + struct sixel *six = &it->item; + int start = six->pos.row; + int end = (start + six->rows - 1) & (grid->num_rows - 1); - /* Need the “real” list to be empty from the beginning */ - tll(struct sixel) copy = tll_init(); - tll_foreach(grid->sixel_images, it) - tll_push_back(copy, it->item); - tll_free(grid->sixel_images); - - tll_rforeach(copy, it) { - struct sixel *six = &it->item; - int start = six->pos.row; - int end = (start + six->rows - 1) & (grid->num_rows - 1); - - if (end < start) { - /* Crosses scrollback wrap-around */ - /* TODO: split image */ - sixel_destroy(six); - continue; - } - - if (six->rows > grid->num_rows) { - /* Image too large */ - /* TODO: keep bottom part? */ - sixel_destroy(six); - continue; - } - - /* Drop sixels that now cross the current scrollback end - * border. This is similar to a sixel that have been - * scrolled out */ - /* TODO: should be possible to optimize this */ - bool sixel_destroyed = false; - int last_row = -1; - - for (int j = 0; j < six->rows; j++) { - int row_no = grid_row_abs_to_sb( - term->grid, term->rows, six->pos.row + j); - if (last_row != -1 && last_row >= row_no) { - sixel_destroy(six); - sixel_destroyed = true; - break; - } - - last_row = row_no; - } - - if (sixel_destroyed) { - LOG_WARN("destroyed sixel that now crossed history"); - continue; - } - - /* Sixels that didn’t overlap may now do so, which isn’t - * allowed of course */ - _sixel_overwrite_by_rectangle( - term, six->pos.row, six->pos.col, six->rows, six->cols, - &it->item.pix, &it->item.opaque); - - if (it->item.data != pixman_image_get_data(it->item.pix)) { - it->item.data = pixman_image_get_data(it->item.pix); - it->item.width = pixman_image_get_width(it->item.pix); - it->item.height = pixman_image_get_height(it->item.pix); - it->item.cols = (it->item.width + term->cell_width - 1) / term->cell_width; - it->item.rows = (it->item.height + term->cell_height - 1) / term->cell_height; - } - - sixel_insert(term, it->item); + if (end < start) { + /* Crosses scrollback wrap-around */ + /* TODO: split image */ + sixel_destroy(six); + continue; } - tll_free(copy); + if (six->rows > grid->num_rows) { + /* Image too large */ + /* TODO: keep bottom part? */ + sixel_destroy(six); + continue; + } + + /* Drop sixels that now cross the current scrollback end + * border. This is similar to a sixel that have been + * scrolled out */ + /* TODO: should be possible to optimize this */ + bool sixel_destroyed = false; + int last_row = -1; + + for (int j = 0; j < six->rows; j++) { + int row_no = grid_row_abs_to_sb( + term->grid, term->rows, six->pos.row + j); + if (last_row != -1 && last_row >= row_no) { + sixel_destroy(six); + sixel_destroyed = true; + break; + } + + last_row = row_no; + } + + if (sixel_destroyed) { + LOG_WARN("destroyed sixel that now crossed history"); + continue; + } + + /* Sixels that didn’t overlap may now do so, which isn’t + * allowed of course */ + _sixel_overwrite_by_rectangle( + term, six->pos.row, six->pos.col, six->rows, six->cols, + &it->item.pix, &it->item.opaque); + + if (it->item.data != pixman_image_get_data(it->item.pix)) { + it->item.data = pixman_image_get_data(it->item.pix); + it->item.width = pixman_image_get_width(it->item.pix); + it->item.height = pixman_image_get_height(it->item.pix); + it->item.cols = (it->item.width + term->cell_width - 1) / term->cell_width; + it->item.rows = (it->item.height + term->cell_height - 1) / term->cell_height; + } + + sixel_insert(term, it->item); } - term->grid = g; + tll_free(copy); + term->grid = active_grid; +} + +void +sixel_reflow(struct terminal *term) +{ + for (size_t i = 0; i < 2; i++) { + struct grid *grid = i == 0 ? &term->normal : &term->alt; + sixel_reflow_grid(term, grid); + } } void @@ -1291,7 +1295,7 @@ sixel_add_many(struct terminal *term, uint8_t c, unsigned count) if (unlikely(col + count - 1 >= width)) { resize_horizontally(term, col + count); width = term->sixel.image.width; - count = min(count, width - col); + count = min(count, max(width - col, 0)); } uint32_t color = term->sixel.color; diff --git a/sixel.h b/sixel.h index a57957c3..f72b4dc4 100644 --- a/sixel.h +++ b/sixel.h @@ -19,6 +19,10 @@ void sixel_scroll_up(struct terminal *term, int rows); void sixel_scroll_down(struct terminal *term, int rows); void sixel_cell_size_changed(struct terminal *term); + +void sixel_reflow_grid(struct terminal *term, struct grid *grid); + +/* Shortcut for sixel_reflow_grid(normal) + sixel_reflow_grid(alt) */ void sixel_reflow(struct terminal *term); /* diff --git a/slave.c b/slave.c index 4dd80e6f..2f23e996 100644 --- a/slave.c +++ b/slave.c @@ -21,6 +21,7 @@ #include "macros.h" #include "terminal.h" #include "tokenize.h" +#include "version.h" #include "xmalloc.h" extern char **environ; @@ -351,6 +352,8 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, } setenv("TERM", term_env, 1); + setenv("TERM_PROGRAM", "foot", 1); + setenv("TERM_PROGRAM_VERSION", FOOT_VERSION_SHORT, 1); setenv("COLORTERM", "truecolor", 1); setenv("PWD", cwd, 1); @@ -359,8 +362,15 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, #endif if (extra_env_vars != NULL) { - tll_foreach(*extra_env_vars, it) - setenv(it->item.name, it->item.value, 1); + tll_foreach(*extra_env_vars, it) { + const char *name = it->item.name; + const char *value = it->item.value; + + if (strlen(value) == 0) + unsetenv(name); + else + setenv(name, value, 1); + } } char **_shell_argv = NULL; diff --git a/terminal.c b/terminal.c index df17201b..2e62fbb7 100644 --- a/terminal.c +++ b/terminal.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -255,8 +256,18 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) cursor_blink_rearm_timer(term); } + if (unlikely(term->interactive_resizing.grid != NULL)) { + /* + * Don’t consume PTMX while we’re doing an interactive resize, + * since the ‘normal’ grid we’re currently using is a + * temporary one - all changes done to it will be lost when + * the interactive resize ends. + */ + return true; + } + uint8_t buf[24 * 1024]; - const size_t max_iterations = !hup ? 10 : (size_t)-1ll; + const size_t max_iterations = !hup ? 10 : SIZE_MAX; for (size_t i = 0; i < max_iterations && pollin; i++) { xassert(pollin); @@ -278,6 +289,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) break; } + xassert(term->interactive_resizing.grid == NULL); vt_from_slave(term, buf, count); } @@ -358,6 +370,18 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) return true; } +bool +term_ptmx_pause(struct terminal *term) +{ + return fdm_event_del(term->fdm, term->ptmx, EPOLLIN); +} + +bool +term_ptmx_resume(struct terminal *term) +{ + return fdm_event_add(term->fdm, term->ptmx, EPOLLIN); +} + static bool fdm_flash(struct fdm *fdm, int fd, int events, void *data) { @@ -680,6 +704,37 @@ free_custom_glyphs(struct fcft_glyph ***glyphs, size_t count) *glyphs = NULL; } +static void +term_line_height_update(struct terminal *term) +{ + const struct config *conf = term->conf; + + if (term->conf->line_height.px < 0) { + term->font_line_height.pt = 0; + term->font_line_height.px = -1; + return; + } + + const float dpi = term->font_is_sized_by_dpi ? term->font_dpi : 96.; + + const float font_original_pt_size = + conf->fonts[0].arr[0].px_size > 0 + ? conf->fonts[0].arr[0].px_size * 72. / dpi + : conf->fonts[0].arr[0].pt_size; + const float font_current_pt_size = + term->font_sizes[0][0].px_size > 0 + ? term->font_sizes[0][0].px_size * 72. / dpi + : term->font_sizes[0][0].pt_size; + + const float change = font_current_pt_size / font_original_pt_size; + const float line_original_pt_size = conf->line_height.px > 0 + ? conf->line_height.px * 72. / dpi + : conf->line_height.pt; + + term->font_line_height.px = 0; + term->font_line_height.pt = fmaxf(line_original_pt_size * change, 0.); +} + static bool term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) { @@ -706,6 +761,8 @@ term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) fonts[0], U'M', term->font_subpixel); int advance = M != NULL ? M->advance.x : term->fonts[0]->max_advance.x; + term_line_height_update(term); + term->cell_width = advance + term_pt_or_px_as_pixels(term, &conf->letter_spacing); @@ -889,7 +946,7 @@ term_pt_or_px_as_pixels(const struct terminal *term, return pt_or_px->px == 0 ? round(pt_or_px->pt * scale * dpi / 72) - : pt_or_px->px; + : pt_or_px->px * scale; } struct font_load_data { @@ -1054,7 +1111,6 @@ load_fonts_from_conf(struct terminal *term) } } - term->font_line_height = term->conf->line_height; return reload_fonts(term); } @@ -1274,7 +1330,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .pt_size = font->pt_size, .px_size = font->px_size}; } } - term->font_line_height = conf->line_height; add_utmp_record(conf, reaper, ptmx); @@ -1728,6 +1783,8 @@ term_destroy(struct terminal *term) grid_free(&term->normal); grid_free(&term->alt); + grid_free(term->interactive_resizing.grid); + free(term->interactive_resizing.grid); free(term->foot_exe); free(term->cwd); @@ -1978,39 +2035,71 @@ term_reset(struct terminal *term, bool hard) } static bool -term_font_size_adjust(struct terminal *term, double amount) +term_font_size_adjust_by_points(struct terminal *term, float amount) { const struct config *conf = term->conf; - const float dpi = term->font_is_sized_by_dpi ? term->font_dpi : 96.; for (size_t i = 0; i < 4; i++) { const struct config_font_list *font_list = &conf->fonts[i]; for (size_t j = 0; j < font_list->count; j++) { - float old_pt_size = term->font_sizes[i][j].pt_size; + struct config_font *font = &term->font_sizes[i][j]; + float old_pt_size = font->pt_size; - /* - * To ensure primary and user-configured fallback fonts are - * resizes by the same amount, convert pixel sizes to point - * sizes, and to the adjustment on point sizes only. - */ + if (font->px_size > 0) + old_pt_size = font->px_size * 72. / dpi; - if (term->font_sizes[i][j].px_size > 0) - old_pt_size = term->font_sizes[i][j].px_size * 72. / dpi; - - term->font_sizes[i][j].pt_size = fmaxf(old_pt_size + amount, 0.); - term->font_sizes[i][j].px_size = -1; + font->pt_size = fmaxf(old_pt_size + amount, 0.); + font->px_size = -1; } } - if (term->font_line_height.px >= 0) { - float old_pt_size = term->font_line_height.px > 0 - ? term->font_line_height.px * 72. / dpi - : term->font_line_height.pt; + return reload_fonts(term); +} - term->font_line_height.px = 0; - term->font_line_height.pt = fmaxf(old_pt_size + amount, 0.); +static bool +term_font_size_adjust_by_pixels(struct terminal *term, int amount) +{ + const struct config *conf = term->conf; + const float dpi = term->font_is_sized_by_dpi ? term->font_dpi : 96.; + + for (size_t i = 0; i < 4; i++) { + const struct config_font_list *font_list = &conf->fonts[i]; + + for (size_t j = 0; j < font_list->count; j++) { + struct config_font *font = &term->font_sizes[i][j]; + int old_px_size = font->px_size; + + if (font->px_size <= 0) + old_px_size = font->pt_size * dpi / 72.; + + font->px_size = max(old_px_size + amount, 1); + } + } + + return reload_fonts(term); +} + +static bool +term_font_size_adjust_by_percent(struct terminal *term, bool increment, float percent) +{ + const struct config *conf = term->conf; + const float multiplier = increment + ? 1. + percent + : 1. / (1. + percent); + + for (size_t i = 0; i < 4; i++) { + const struct config_font_list *font_list = &conf->fonts[i]; + + for (size_t j = 0; j < font_list->count; j++) { + struct config_font *font = &term->font_sizes[i][j]; + + if (font->px_size > 0) + font->px_size = max(font->px_size * multiplier, 1); + else + font->pt_size = fmax(font->pt_size * multiplier, 0); + } } return reload_fonts(term); @@ -2019,19 +2108,29 @@ term_font_size_adjust(struct terminal *term, double amount) bool term_font_size_increase(struct terminal *term) { - if (!term_font_size_adjust(term, 0.5)) - return false; + const struct config *conf = term->conf; + const struct font_size_adjustment *inc_dec = &conf->font_size_adjustment; - return true; + if (inc_dec->percent > 0.) + return term_font_size_adjust_by_percent(term, true, inc_dec->percent); + else if (inc_dec->pt_or_px.px > 0) + return term_font_size_adjust_by_pixels(term, inc_dec->pt_or_px.px); + else + return term_font_size_adjust_by_points(term, inc_dec->pt_or_px.pt); } bool term_font_size_decrease(struct terminal *term) { - if (!term_font_size_adjust(term, -0.5)) - return false; + const struct config *conf = term->conf; + const struct font_size_adjustment *inc_dec = &conf->font_size_adjustment; - return true; + if (inc_dec->percent > 0.) + return term_font_size_adjust_by_percent(term, false, inc_dec->percent); + else if (inc_dec->pt_or_px.px > 0) + return term_font_size_adjust_by_pixels(term, -inc_dec->pt_or_px.px); + else + return term_font_size_adjust_by_points(term, -inc_dec->pt_or_px.pt); } bool @@ -2153,15 +2252,20 @@ void term_damage_scroll(struct terminal *term, enum damage_type damage_type, struct scroll_region region, int lines) { - if (tll_length(term->grid->scroll_damage) > 0) { + if (likely(tll_length(term->grid->scroll_damage) > 0)) { struct damage *dmg = &tll_back(term->grid->scroll_damage); - if (dmg->type == damage_type && - dmg->region.start == region.start && - dmg->region.end == region.end) + if (likely( + dmg->type == damage_type && + dmg->region.start == region.start && + dmg->region.end == region.end)) { - dmg->lines += lines; - return; + /* Make sure we don’t overflow... */ + int new_line_count = (int)dmg->lines + lines; + if (likely(new_line_count <= UINT16_MAX)) { + dmg->lines = new_line_count; + return; + } } } struct damage dmg = { @@ -2612,13 +2716,13 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows erase_line(term, row); } - term_damage_scroll(term, DAMAGE_SCROLL, region, rows); - term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); - #if defined(_DEBUG) for (int r = 0; r < term->rows; r++) xassert(grid_row(term->grid, r) != NULL); #endif + + term_damage_scroll(term, DAMAGE_SCROLL, region, rows); + term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); } void @@ -2652,6 +2756,18 @@ term_scroll_reverse_partial(struct terminal *term, selection_scroll_down(term, rows); } + /* Unallocate scrolled out lines */ + for (int r = region.end - rows; r < region.end; r++) { + const int abs_r = grid_row_absolute(term->grid, r); + struct row *row = term->grid->rows[abs_r]; + + grid_row_free(row); + term->grid->rows[abs_r] = NULL; + + if (term->render.last_cursor.row == row) + term->render.last_cursor.row = NULL; + } + sixel_scroll_down(term, rows); bool view_follows = term->grid->view == term->grid->offset; diff --git a/terminal.h b/terminal.h index 0dde6330..d2762a5a 100644 --- a/terminal.h +++ b/terminal.h @@ -96,7 +96,7 @@ enum damage_type {DAMAGE_SCROLL, DAMAGE_SCROLL_REVERSE, struct damage { enum damage_type type; struct scroll_region region; - int lines; + uint16_t lines; }; struct row_uri_range { @@ -598,6 +598,15 @@ struct terminal { struct timespec input_time; } render; + struct { + struct grid *grid; /* Original ‘normal’ grid, before resize started */ + int old_screen_rows; /* term->rows before resize started */ + int old_cols; /* term->cols before resize started */ + int old_hide_cursor; /* term->hide_cursor before resize started */ + int new_rows; /* New number of scrollback rows */ + struct range selection_coords; + } interactive_resizing; + struct { enum { SIXEL_DECSIXEL, /* DECSIXEL body part ", $, -, ? ... ~ */ @@ -805,6 +814,9 @@ void term_collect_urls(struct terminal *term); void term_osc8_open(struct terminal *term, uint64_t id, const char *uri); void term_osc8_close(struct terminal *term); +bool term_ptmx_pause(struct terminal *term); +bool term_ptmx_resume(struct terminal *term); + static inline void term_reset_grapheme_state(struct terminal *term) { #if defined(FOOT_GRAPHEME_CLUSTERING) diff --git a/tests/test-config.c b/tests/test-config.c index ae7969dc..4736a46b 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -467,6 +467,7 @@ test_section_main(void) test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title); test_boolean(&ctx, &parse_section_main, "notify-focus-inhibit", &conf.notify_focus_inhibit); + test_pt_or_px(&ctx, &parse_section_main, "font-size-adjustment", &conf.font_size_adjustment.pt_or_px); /* TODO: test ‘N%’ values too */ test_pt_or_px(&ctx, &parse_section_main, "line-height", &conf.line_height); test_pt_or_px(&ctx, &parse_section_main, "letter-spacing", &conf.letter_spacing); test_pt_or_px(&ctx, &parse_section_main, "horizontal-letter-offset", &conf.horizontal_letter_offset); @@ -894,7 +895,7 @@ enum collision_test_mode { FAIL_DIFFERENT_ACTION, FAIL_DIFFERENT_ARGV, FAIL_MOUSE_OVERRIDE, - SUCCED_SAME_ACTION_AND_ARGV, + SUCCEED_SAME_ACTION_AND_ARGV, }; static void @@ -948,7 +949,7 @@ _test_binding_collisions(struct context *ctx, break; case FAIL_DIFFERENT_ARGV: - case SUCCED_SAME_ACTION_AND_ARGV: + case SUCCEED_SAME_ACTION_AND_ARGV: bindings.arr[0].aux.type = BINDING_AUX_PIPE; bindings.arr[0].aux.master_copy = true; bindings.arr[0].aux.pipe.args = xcalloc( @@ -964,13 +965,13 @@ _test_binding_collisions(struct context *ctx, bindings.arr[1].aux.pipe.args[0] = xstrdup("/usr/bin/foobar"); bindings.arr[1].aux.pipe.args[1] = xstrdup("hello"); - if (test_mode == SUCCED_SAME_ACTION_AND_ARGV) + if (test_mode == SUCCEED_SAME_ACTION_AND_ARGV) bindings.arr[1].aux.pipe.args[2] = xstrdup("world"); break; } bool expected_result = - test_mode == SUCCED_SAME_ACTION_AND_ARGV ? true : false; + test_mode == SUCCEED_SAME_ACTION_AND_ARGV ? true : false; if (resolve_key_binding_collisions( ctx->conf, ctx->section, map, &bindings, type) != expected_result) @@ -1003,7 +1004,7 @@ test_binding_collisions(struct context *ctx, { _test_binding_collisions(ctx, max_action, map, type, FAIL_DIFFERENT_ACTION); _test_binding_collisions(ctx, max_action, map, type, FAIL_DIFFERENT_ARGV); - _test_binding_collisions(ctx, max_action, map, type, SUCCED_SAME_ACTION_AND_ARGV); + _test_binding_collisions(ctx, max_action, map, type, SUCCEED_SAME_ACTION_AND_ARGV); if (type == MOUSE_BINDING) { _test_binding_collisions( @@ -1303,6 +1304,7 @@ test_section_tweak(void) int main(int argc, const char *const *argv) { + FcInit(); log_init(LOG_COLORIZE_AUTO, false, 0, LOG_CLASS_ERROR); test_section_main(); test_section_bell(); @@ -1324,5 +1326,6 @@ main(int argc, const char *const *argv) test_section_environment(); test_section_tweak(); log_deinit(); + FcFini(); return 0; } diff --git a/themes/apprentice b/themes/apprentice index 06d26315..941a27b4 100644 --- a/themes/apprentice +++ b/themes/apprentice @@ -1,3 +1,4 @@ +# -*- conf -*- # https://github.com/romainl/Apprentice [cursor] diff --git a/themes/catppuccin b/themes/catppuccin index f873aa3f..4ccfabec 100644 --- a/themes/catppuccin +++ b/themes/catppuccin @@ -1,10 +1,10 @@ +# -*- conf -*- # Catppuccin [cursor] color=1A1826 D9E0EE [colors] -alpha=1.0 foreground=D9E0EE background=1E1D2F regular0=6E6C7E # black diff --git a/themes/derp b/themes/derp index 1d1afcd5..0925d2c2 100644 --- a/themes/derp +++ b/themes/derp @@ -1,3 +1,4 @@ +# -*- conf -*- # Derp [cursor] diff --git a/themes/deus b/themes/deus new file mode 100644 index 00000000..8fb37f75 --- /dev/null +++ b/themes/deus @@ -0,0 +1,31 @@ +# -*- conf -*- +# Deus +# Color palette based on: https://github.com/ajmwagar/vim-deus + +[cursor] +color=2c323b eaeaea + +[colors] +background=2c323b +foreground=eaeaea +regular0=242a32 +regular1=d54e53 +regular2=98c379 +regular3=e5c07b +regular4=83a598 +regular5=c678dd +regular6=70c0ba +regular7=eaeaea +bright0=666666 +bright1=ec3e45 +bright2=90c966 +bright3=edbf69 +bright4=73ba9f +bright5=c858e9 +bright6=2bcec2 +bright7=ffffff + +# Enable if prefer Deus colors instead of inverterd fg/bg for +# highlighting (mouse selection) +# selection-foreground=2c323b +# selection-background=eaeaea diff --git a/themes/dracula b/themes/dracula index cd60e2e6..8b6ab542 100644 --- a/themes/dracula +++ b/themes/dracula @@ -1,10 +1,10 @@ +# -*- conf -*- # Dracula [cursor] color=282a36 f8f8f2 [colors] -alpha=1.0 foreground=f8f8f2 background=282a36 regular0=000000 # black diff --git a/themes/gruvbox-dark b/themes/gruvbox-dark index 1716647d..73207199 100644 --- a/themes/gruvbox-dark +++ b/themes/gruvbox-dark @@ -1,3 +1,4 @@ +# -*- conf -*- # Gruvbox [colors] diff --git a/themes/gruvbox-light b/themes/gruvbox-light index a0788d0c..6a7a2416 100644 --- a/themes/gruvbox-light +++ b/themes/gruvbox-light @@ -1,3 +1,4 @@ +# -*- conf -*- # Gruvbox - Light [colors] diff --git a/themes/hacktober b/themes/hacktober index 4c7c6233..acb6c0b1 100644 --- a/themes/hacktober +++ b/themes/hacktober @@ -1,3 +1,4 @@ +# -*- conf -*- [cursor] color=141414 c9c9c9 diff --git a/themes/jetbrains-darcula b/themes/jetbrains-darcula index a4c811c5..82528498 100644 --- a/themes/jetbrains-darcula +++ b/themes/jetbrains-darcula @@ -1,3 +1,4 @@ +# -*- conf -*- # JetBrains Darcula # Palette based on the same theme from https://github.com/dexpota/kitty-themes @@ -5,7 +6,6 @@ color=202020 ffffff [colors] -#alpha=0.80 background=202020 foreground=adadad regular0=000000 # black @@ -24,5 +24,5 @@ bright4=6d9df1 # bright blue bright5=fb82ff # bright magenta bright6=60d3d1 # bright cyan bright7=eeeeee # bright white -selection-foreground=202020 -selection-background=1a3272 +# selection-foreground=202020 +# selection-background=1a3272 diff --git a/themes/kitty b/themes/kitty index 670a4559..b5b813cc 100644 --- a/themes/kitty +++ b/themes/kitty @@ -1,3 +1,5 @@ +# -*- conf -*- + [cursor] color=111111 cccccc diff --git a/themes/material-amber b/themes/material-amber new file mode 100644 index 00000000..ee2c21b5 --- /dev/null +++ b/themes/material-amber @@ -0,0 +1,40 @@ +# -*- conf -*- +# Material Amber +# Based on material.io guidelines with Amber 50 background + +# [cursor] +# color=fff8e1 21201d + +[colors] +foreground = 21201d +background = fff8e1 + +regular0 = 21201d # black +regular1 = cd4340 # red +regular2 = 498d49 # green +regular3 = fab32d # yellow +regular4 = 3378c4 # blue +regular5 = b83269 # magenta +regular6 = 21929a # cyan +regular7 = ffd7d7 # white + +bright0 = 66635a # bright black +bright1 = dd7b72 # bright red +bright2 = 82ae78 # bright green +bright3 = fbc870 # bright yellow +bright4 = 73a0cd # bright blue +bright5 = ce6f8e # bright magenta +bright6 = 548c94 # bright cyan +bright7 = ffe1da # bright white + +dim0 = 9e9a8c # dim black +dim1 = e9a99b # dim red +dim2 = b0c99f # dim green +dim3 = fdda9a # dim yellow +dim4 = a6c0d4 # dim blue +dim5 = e0a1ad # dim magenta +dim6 = 3c6064 # dim cyan +dim7 = ffe9dd # dim white + +# selection-foreground=fff8e1 +# selection-background=21201d diff --git a/themes/material-design b/themes/material-design index 4b017391..4a9e008a 100644 --- a/themes/material-design +++ b/themes/material-design @@ -1,11 +1,10 @@ +# -*- conf -*- # Material # From https://github.com/MartinSeeler/iterm2-material-design [colors] foreground=ECEFF1 background=263238 -selection-foreground=ECEFF1 -selection-background=607D8B regular0=546E7A # black regular1=FF5252 # red regular2=5CF19E # green @@ -22,3 +21,5 @@ bright4=80D8FF # bright blue bright5=FF80AB # bright magenta bright6=A7FDEB # bright cyan bright7=FFFFFF # bright white +# selection-foreground=ECEFF1 +# selection-background=607D8B diff --git a/themes/modus-operandi b/themes/modus-operandi new file mode 100644 index 00000000..5e3a9fd6 --- /dev/null +++ b/themes/modus-operandi @@ -0,0 +1,24 @@ +# -*- conf -*- +# +# modus-operandi +# See: https://protesilaos.com/emacs/modus-themes +# +[colors] +background=ffffff +foreground=000000 +regular0=000000 +regular1=a60000 +regular2=005e00 +regular3=813e00 +regular4=0031a9 +regular5=721045 +regular6=00538b +regular7=bfbfbf +bright0=595959 +bright1=972500 +bright2=315b00 +bright3=70480f +bright4=2544bb +bright5=5317ac +bright6=005a5f +bright7=ffffff diff --git a/themes/modus-vivendi b/themes/modus-vivendi new file mode 100644 index 00000000..82b1075d --- /dev/null +++ b/themes/modus-vivendi @@ -0,0 +1,25 @@ +# -*- conf -*- +# +# modus-vivendi +# See: https://protesilaos.com/emacs/modus-themes +# + +[colors] +background=000000 +foreground=ffffff +regular0=000000 +regular1=ff8059 +regular2=44bc44 +regular3=d0bc00 +regular4=2fafff +regular5=feacd0 +regular6=00d3d0 +regular7=bfbfbf +bright0=595959 +bright1=ef8b50 +bright2=70b900 +bright3=c0c530 +bright4=79a8ff +bright5=b6a0ff +bright6=6ae4b9 +bright7=ffffff diff --git a/themes/monokai-pro b/themes/monokai-pro index eecdd3f7..5d9f31a9 100644 --- a/themes/monokai-pro +++ b/themes/monokai-pro @@ -1,3 +1,4 @@ +# -*- conf -*- # Monokai Pro [colors] diff --git a/themes/moonfly b/themes/moonfly index 54d9203b..870de9d0 100644 --- a/themes/moonfly +++ b/themes/moonfly @@ -1,3 +1,4 @@ +# -*- conf -*- # moonfly # Based on https://github.com/bluz71/vim-moonfly-colors @@ -7,8 +8,9 @@ color = 080808 9e9e9e [colors] foreground = b2b2b2 background = 080808 -selection-foreground = 080808 -selection-background = b2ceee + +# selection-foreground = 080808 +# selection-background = b2ceee regular0 = 323437 regular1 = ff5454 diff --git a/themes/nightfly b/themes/nightfly index 3815b42e..2a27fb2d 100644 --- a/themes/nightfly +++ b/themes/nightfly @@ -1,3 +1,4 @@ +# -*- conf -*- # nightfly # Based on https://github.com/bluz71/vim-nightfly-guicolors @@ -7,8 +8,9 @@ color = 080808 9ca1aa [colors] foreground = acb4c2 background = 011627 -selection-foreground = 080808 -selection-background = b2ceee + +# selection-foreground = 080808 +# selection-background = b2ceee regular0 = 1d3b53 regular1 = fc514e diff --git a/themes/nord b/themes/nord index 4f6d8c2b..4ce3a53e 100644 --- a/themes/nord +++ b/themes/nord @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Nord # author: Arctic Ice Studio , Sven Greb # description: „Nord“ — An arctic, north-bluish color palette @@ -11,8 +12,9 @@ color = 2e3440 d8dee9 [colors] foreground = d8dee9 background = 2e3440 -#selection-foreground = d8dee9 -#selection-background = 4c566a + +# selection-foreground = d8dee9 +# selection-background = 4c566a regular0 = 3b4252 regular1 = bf616a diff --git a/themes/nordiq b/themes/nordiq index df45e80a..f309de23 100644 --- a/themes/nordiq +++ b/themes/nordiq @@ -1,3 +1,4 @@ +# -*- conf -*- # Nordiq [cursor] diff --git a/themes/onedark b/themes/onedark new file mode 100644 index 00000000..ac5cc834 --- /dev/null +++ b/themes/onedark @@ -0,0 +1,27 @@ +# OneDark +# Palette based on the same theme from https://github.com/dexpota/kitty-themes + +[cursor] +color=111111 cccccc + +[colors] +foreground=979eab +background=282c34 +regular0=282c34 # black +regular1=e06c75 # red +regular2=98c379 # green +regular3=e5c07b # yellow +regular4=61afef # blue +regular5=be5046 # magenta +regular6=56b6c2 # cyan +regular7=979eab # white +bright0=393e48 # bright black +bright1=d19a66 # bright red +bright2=56b6c2 # bright green +bright3=e5c07b # bright yellow +bright4=61afef # bright blue +bright5=be5046 # bright magenta +bright6=56b6c2 # bright cyan +bright7=abb2bf # bright white +# selection-foreground=282c34 +# selection-background=979eab diff --git a/themes/paper-color-dark b/themes/paper-color-dark index 17d569ac..18cd7f17 100644 --- a/themes/paper-color-dark +++ b/themes/paper-color-dark @@ -1,28 +1,28 @@ +# -*- conf -*- # PaperColorDark # Palette based on https://github.com/NLKNguyen/papercolor-theme [cursor] - color=1c1c1c eeeeee +color=1c1c1c eeeeee [colors] - alpha=0.80 - background=1c1c1c - foreground=eeeeee - regular0=1c1c1c # black - regular1=af005f # red - regular2=5faf00 # green - regular3=d7af5f # yellow - regular4=5fafd7 # blue - regular5=808080 # magenta - regular6=d7875f # cyan - regular7=d0d0d0 # white - bright0=bcbcbc # bright black - bright1=5faf5f # bright red - bright2=afd700 # bright green - bright3=af87d7 # bright yellow - bright4=ffaf00 # bright blue - bright5=ff5faf # bright magenta - bright6=00afaf # bright cyan - bright7=5f8787 # bright white - #selection-foreground=1c1c1c - #selection-background=af87d7 +background=1c1c1c +foreground=eeeeee +regular0=1c1c1c # black +regular1=af005f # red +regular2=5faf00 # green +regular3=d7af5f # yellow +regular4=5fafd7 # blue +regular5=808080 # magenta +regular6=d7875f # cyan +regular7=d0d0d0 # white +bright0=bcbcbc # bright black +bright1=5faf5f # bright red +bright2=afd700 # bright green +bright3=af87d7 # bright yellow +bright4=ffaf00 # bright blue +bright5=ff5faf # bright magenta +bright6=00afaf # bright cyan +bright7=5f8787 # bright white +# selection-foreground=1c1c1c +# selection-background=af87d7 diff --git a/themes/paper-color-light b/themes/paper-color-light index 6e9f59f6..b08ea707 100644 --- a/themes/paper-color-light +++ b/themes/paper-color-light @@ -1,28 +1,28 @@ +# -*- conf -*- # PaperColor Light # Palette based on https://github.com/NLKNguyen/papercolor-theme [cursor] - color=eeeeee 444444 +color=eeeeee 444444 [colors] - alpha=1.0 - background=eeeeee - foreground=444444 - regular0=eeeeee # black - regular1=af0000 # red - regular2=008700 # green - regular3=5f8700 # yellow - regular4=0087af # blue - regular5=878787 # magenta - regular6=005f87 # cyan - regular7=764e37 # white - bright0=bcbcbc # bright black - bright1=d70000 # bright red - bright2=d70087 # bright green - bright3=8700af # bright yellow - bright4=d75f00 # bright blue - bright5=d75f00 # bright magenta - bright6=4c7a5d # bright cyan - bright7=005faf # bright white - #selection-foreground=eeeeee - #selection-background=0087af +background=eeeeee +foreground=444444 +regular0=eeeeee # black +regular1=af0000 # red +regular2=008700 # green +regular3=5f8700 # yellow +regular4=0087af # blue +regular5=878787 # magenta +regular6=005f87 # cyan +regular7=764e37 # white +bright0=bcbcbc # bright black +bright1=d70000 # bright red +bright2=d70087 # bright green +bright3=8700af # bright yellow +bright4=d75f00 # bright blue +bright5=d75f00 # bright magenta +bright6=4c7a5d # bright cyan +bright7=005faf # bright white +# selection-foreground=eeeeee +# selection-background=0087af diff --git a/themes/rezza b/themes/rezza index b9ef4a1f..56814a77 100644 --- a/themes/rezza +++ b/themes/rezza @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: rezza # author: Doug Whiteley (rezza) # original URL: http://metawire.org/~rezza/index.php diff --git a/themes/rose-pine b/themes/rose-pine new file mode 100644 index 00000000..6b58a66c --- /dev/null +++ b/themes/rose-pine @@ -0,0 +1,26 @@ +# -*- conf -*- +# Rose-Piné + +[cursor] +color=191724 e0def4 + +[colors] +background=191724 +foreground=e0def4 +regular0=26233a # black +regular1=eb6f92 # red +regular2=31748f # green +regular3=f6c177 # yellow +regular4=9ccfd8 # blue +regular5=c4a7e7 # magenta +regular6=ebbcba # cyan +regular7=e0def4 # white + +bright0=6e6a86 # bright black +bright1=eb6f92 # bright red +bright2=31748f # bright green +bright3=f6c177 # bright yellow +bright4=9ccfd8 # bright blue +bright5=c4a7e7 # bright magenta +bright6=ebbcba # bright cyan +bright7=e0def4 # bright white \ No newline at end of file diff --git a/themes/selenized-black b/themes/selenized-black index a75891f9..28392add 100644 --- a/themes/selenized-black +++ b/themes/selenized-black @@ -1,3 +1,4 @@ +# -*- conf -*- # Selenized black [cursor] diff --git a/themes/selenized-dark b/themes/selenized-dark index e3a7b7b4..ed74cdfc 100644 --- a/themes/selenized-dark +++ b/themes/selenized-dark @@ -1,3 +1,4 @@ +# -*- conf -*- # Selenized dark [cursor] diff --git a/themes/selenized-light b/themes/selenized-light index 5f2b7c08..7e599d8e 100644 --- a/themes/selenized-light +++ b/themes/selenized-light @@ -1,3 +1,4 @@ +# -*- conf -*- # Selenized light [cursor] diff --git a/themes/selenized-white b/themes/selenized-white index 492c01f6..b4d25315 100644 --- a/themes/selenized-white +++ b/themes/selenized-white @@ -1,3 +1,4 @@ +# -*- conf -*- # Selenized white [cursor] diff --git a/themes/solarized-dark b/themes/solarized-dark index 0d0a233f..cad2945e 100644 --- a/themes/solarized-dark +++ b/themes/solarized-dark @@ -1,3 +1,4 @@ +# -*- conf -*- # Solarized dark [cursor] @@ -23,7 +24,7 @@ bright5= 6c71c4 bright6= 93a1a1 bright7= fdf6e3 -## Enable if prefer solarized colors instead of inverterd fg/bg for -## highlighting (mouse selection) +# Enable if prefer solarized colors instead of inverterd fg/bg for +# highlighting (mouse selection) # selection-foreground=93a1a1 # selection-background=073642 diff --git a/themes/solarized-dark-normal-brights b/themes/solarized-dark-normal-brights index 484e5dc2..1ab7d375 100644 --- a/themes/solarized-dark-normal-brights +++ b/themes/solarized-dark-normal-brights @@ -1,3 +1,4 @@ +# -*- conf -*- # Solarized dark [cursor] @@ -25,7 +26,7 @@ bright5= dc619d bright6= 32c1b6 bright7= ffffff -## Enable if prefer solarized colors instead of inverterd fg/bg for -## highlighting (mouse selection) +# Enable if prefer solarized colors instead of inverterd fg/bg for +# highlighting (mouse selection) # selection-foreground=93a1a1 # selection-background=073642 diff --git a/themes/solarized-light b/themes/solarized-light index fb4b762a..74474573 100644 --- a/themes/solarized-light +++ b/themes/solarized-light @@ -1,3 +1,4 @@ +# -*- conf -*- # Solarized light [cursor] diff --git a/themes/tango b/themes/tango index 3b5c93c4..a326f8ad 100644 --- a/themes/tango +++ b/themes/tango @@ -1,3 +1,4 @@ +# -*- conf -*- # Tango [cursor] diff --git a/themes/tempus-autumn b/themes/tempus-autumn index 9fafb5a2..9c1f8797 100644 --- a/themes/tempus-autumn +++ b/themes/tempus-autumn @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Autumn # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by earthly colours (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 958fdf bright5 = ce7dc4 bright6 = 2fa6b7 bright7 = a9a2a6 -#selection-foreground = a8948a -#selection-background = 36302a +# selection-foreground = a8948a +# selection-background = 36302a diff --git a/themes/tempus-classic b/themes/tempus-classic index af86b024..0164605b 100644 --- a/themes/tempus-classic +++ b/themes/tempus-classic @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Classic # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with warm hues (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 8e9cc0 bright5 = d58888 bright6 = 7aa880 bright7 = aeadaf -#selection-foreground = 949d9f -#selection-background = 312e30 +# selection-foreground = 949d9f +# selection-background = 312e30 diff --git a/themes/tempus-dawn b/themes/tempus-dawn index ef599373..cf143fba 100644 --- a/themes/tempus-dawn +++ b/themes/tempus-dawn @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Dawn # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme with a soft, slightly desaturated palette (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 5c59b2 bright5 = 8e45a8 bright6 = 3f649c bright7 = eff0f2 -#selection-foreground = 676364 -#selection-background = dee2e0 +# selection-foreground = 676364 +# selection-background = dee2e0 diff --git a/themes/tempus-day b/themes/tempus-day index 2e4cfdc9..b287d45c 100644 --- a/themes/tempus-day +++ b/themes/tempus-day @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Day # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme with warm colours (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 0f64c4 bright5 = 8050a7 bright6 = 336c87 bright7 = f8f2e5 -#selection-foreground = 68607d -#selection-background = e7e3d7 +# selection-foreground = 68607d +# selection-background = e7e3d7 diff --git a/themes/tempus-dusk b/themes/tempus-dusk index 07907efc..2c0308e1 100644 --- a/themes/tempus-dusk +++ b/themes/tempus-dusk @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Dusk # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a deep blue-ish, slightly desaturated palette (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 9ca5de bright5 = c69ac6 bright6 = 8caeb6 bright7 = a2a8ba -#selection-foreground = a29899 -#selection-background = 2c3150 +# selection-foreground = a29899 +# selection-background = 2c3150 diff --git a/themes/tempus-fugit b/themes/tempus-fugit index 37484f4c..9ebbcee7 100644 --- a/themes/tempus-fugit +++ b/themes/tempus-fugit @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Fugit # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light, pleasant theme optimised for long writing/coding sessions (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 485adf bright5 = a234c0 bright6 = 00756a bright7 = fff5f3 -#selection-foreground = 796271 -#selection-background = efe6e4 +# selection-foreground = 796271 +# selection-background = efe6e4 diff --git a/themes/tempus-future b/themes/tempus-future index 462e53dc..3dd8c7a6 100644 --- a/themes/tempus-future +++ b/themes/tempus-future @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Future # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with colours inspired by concept art of outer space (WCAG AAA compliant) @@ -24,5 +25,5 @@ bright4 = 8ba7ea bright5 = e08bd6 bright6 = 2cbab6 bright7 = b4abac -#selection-foreground = a7a2c4 -#selection-background = 2b1329 +# selection-foreground = a7a2c4 +# selection-background = 2b1329 diff --git a/themes/tempus-night b/themes/tempus-night index 72e46bce..de7be5ff 100644 --- a/themes/tempus-night +++ b/themes/tempus-night @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Night # author: Protesilaos Stavrou (https://protesilaos.com) # description: High contrast dark theme with bright colours (WCAG AAA compliant) @@ -24,5 +25,5 @@ bright4 = 8cb4f0 bright5 = de99f0 bright6 = 00ca9a bright7 = e0e0e0 -#selection-foreground = c4bdaf -#selection-background = 242536 +# selection-foreground = c4bdaf +# selection-background = 242536 diff --git a/themes/tempus-past b/themes/tempus-past index 5dcee1c2..8c66f54d 100644 --- a/themes/tempus-past +++ b/themes/tempus-past @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Past # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme inspired by old vaporwave concept art (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 5559bb bright5 = b022a7 bright6 = 07707a bright7 = f3f2f4 -#selection-foreground = 80565d -#selection-background = eae2de +# selection-foreground = 80565d +# selection-background = eae2de diff --git a/themes/tempus-rift b/themes/tempus-rift index b60409e7..3657a7fe 100644 --- a/themes/tempus-rift +++ b/themes/tempus-rift @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Rift # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a subdued palette on the green side of the spectrum (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 56bdad bright5 = cca0ba bright6 = 10c480 bright7 = bbbcbc -#selection-foreground = ab9aa9 -#selection-background = 283431 +# selection-foreground = ab9aa9 +# selection-background = 283431 diff --git a/themes/tempus-spring b/themes/tempus-spring index dcee15f7..d50e6d06 100644 --- a/themes/tempus-spring +++ b/themes/tempus-spring @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Spring # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by early spring colours (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 70afef bright5 = d095e2 bright6 = 3cbfaf bright7 = b5b8b7 -#selection-foreground = 99afae -#selection-background = 2a453d +# selection-foreground = 99afae +# selection-background = 2a453d diff --git a/themes/tempus-summer b/themes/tempus-summer index 3662c04a..7da1d8c4 100644 --- a/themes/tempus-summer +++ b/themes/tempus-summer @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Summer # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with colours inspired by summer evenings by the sea (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 8599ef bright5 = cc82d7 bright6 = 2aacbf bright7 = a0abae -#selection-foreground = 949cbf -#selection-background = 39304f +# selection-foreground = 949cbf +# selection-background = 39304f diff --git a/themes/tempus-tempest b/themes/tempus-tempest index aab3c44e..57c300aa 100644 --- a/themes/tempus-tempest +++ b/themes/tempus-tempest @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Tempest # author: Protesilaos Stavrou (https://protesilaos.com) # description: A green-scale, subtle theme for late night hackers (WCAG AAA compliant) @@ -24,5 +25,5 @@ bright4 = 74e4cd bright5 = d2d4aa bright6 = 9bdfc4 bright7 = b6e0ca -#selection-foreground = b0c8ca -#selection-background = 323535 +# selection-foreground = b0c8ca +# selection-background = 323535 diff --git a/themes/tempus-totus b/themes/tempus-totus index 2673a70c..01e84692 100644 --- a/themes/tempus-totus +++ b/themes/tempus-totus @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Totus # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme for prose or for coding in an open space (WCAG AAA compliant) @@ -24,5 +25,5 @@ bright4 = 2d45b0 bright5 = 700dc9 bright6 = 005289 bright7 = ffffff -#selection-foreground = 5e4b4f -#selection-background = efefef +# selection-foreground = 5e4b4f +# selection-background = efefef diff --git a/themes/tempus-warp b/themes/tempus-warp index 48fd47bc..fa8c21c2 100644 --- a/themes/tempus-warp +++ b/themes/tempus-warp @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Warp # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a vibrant palette (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 8887f0 bright5 = d85cf2 bright6 = 1da1af bright7 = a29fa0 -#selection-foreground = 968282 -#selection-background = 261c2c +# selection-foreground = 968282 +# selection-background = 261c2c diff --git a/themes/tempus-winter b/themes/tempus-winter index 69c8b867..8db97057 100644 --- a/themes/tempus-winter +++ b/themes/tempus-winter @@ -1,3 +1,4 @@ +# -*- conf -*- # theme: Tempus Winter # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by winter nights at the city (WCAG AA compliant) @@ -24,5 +25,5 @@ bright4 = 329fcb bright5 = ca77c5 bright6 = 1ba6a4 bright7 = 8da3b8 -#selection-foreground = 91959b -#selection-background = 2a2e38 +# selection-foreground = 91959b +# selection-background = 2a2e38 diff --git a/themes/tokyonight-day b/themes/tokyonight-day index 744ef351..5143aa07 100644 --- a/themes/tokyonight-day +++ b/themes/tokyonight-day @@ -1,3 +1,5 @@ +# -*- conf -*- + [colors] background=e1e2e7 foreground=3760bf diff --git a/themes/tokyonight-night b/themes/tokyonight-night index e48d7cd6..f789e1bd 100644 --- a/themes/tokyonight-night +++ b/themes/tokyonight-night @@ -1,3 +1,5 @@ +# -*- conf -*- + [colors] background=1a1b26 foreground=c0caf5 diff --git a/themes/tokyonight-storm b/themes/tokyonight-storm index 96b90eb8..074b4697 100644 --- a/themes/tokyonight-storm +++ b/themes/tokyonight-storm @@ -1,3 +1,5 @@ +# -*- conf -*- + [colors] background=24283b foreground=c0caf5 diff --git a/themes/visibone b/themes/visibone index a27c815f..3ee665d0 100644 --- a/themes/visibone +++ b/themes/visibone @@ -1,3 +1,4 @@ +# -*- conf -*- # VisiBone [cursor] diff --git a/themes/zenburn b/themes/zenburn new file mode 100644 index 00000000..bace080c --- /dev/null +++ b/themes/zenburn @@ -0,0 +1,25 @@ +# -*- conf -*- + +[colors] +foreground=dcdccc +background=111111 + +## Normal/regular colors (color palette 0-7) +regular0=222222 # black +regular1=cc9393 # red +regular2=7f9f7f # green +regular3=d0bf8f # yellow +regular4=6ca0a3 # blue +regular5=dc8cc3 # magenta +regular6=93e0e3 # cyan +regular7=dcdccc # white + +## Bright colors (color palette 8-15) +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 diff --git a/uri.c b/uri.c index 39073bde..7214a479 100644 --- a/uri.c +++ b/uri.c @@ -159,7 +159,7 @@ uri_parse(const char *uri, size_t len, char *p = decoded; size_t encoded_len = path_len; - size_t decoded_len = 0; + size_t UNUSED decoded_len = 0; while (true) { /* Find next '%' */ diff --git a/url-mode.c b/url-mode.c index 6fa16623..7d7ffd81 100644 --- a/url-mode.c +++ b/url-mode.c @@ -746,15 +746,18 @@ tag_cells_for_url(struct terminal *term, const struct url *url, bool value) if (url->url_mode_dont_change_url_attr) return; + struct grid *grid = term->url_grid_snapshot; + xassert(grid != NULL); + const struct coord *start = &url->range.start; const struct coord *end = &url->range.end; - size_t end_r = end->row & (term->grid->num_rows - 1); + size_t end_r = end->row & (grid->num_rows - 1); - size_t r = start->row & (term->grid->num_rows - 1); + size_t r = start->row & (grid->num_rows - 1); size_t c = start->col; - struct row *row = term->grid->rows[r]; + struct row *row = grid->rows[r]; row->dirty = true; while (true) { @@ -766,10 +769,10 @@ tag_cells_for_url(struct terminal *term, const struct url *url, bool value) break; if (++c >= term->cols) { - r = (r + 1) & (term->grid->num_rows - 1); + r = (r + 1) & (grid->num_rows - 1); c = 0; - row = term->grid->rows[r]; + row = grid->rows[r]; if (row == NULL) { /* Un-allocated scrollback. This most likely means a * runaway OSC-8 URL. */ @@ -788,15 +791,6 @@ urls_render(struct terminal *term) if (tll_length(win->term->urls) == 0) return; - xassert(tll_length(win->urls) == 0); - tll_foreach(win->term->urls, it) { - struct wl_url url = {.url = &it->item}; - wayl_win_subsurface_new(win, &url.surf, false); - - tll_push_back(win->urls, url); - tag_cells_for_url(term, &it->item, true); - } - /* Dirty the last cursor, to ensure it is erased */ { struct row *cursor_row = term->render.last_cursor.row; @@ -819,6 +813,15 @@ urls_render(struct terminal *term) /* Snapshot the current grid */ term->url_grid_snapshot = grid_snapshot(term->grid); + xassert(tll_length(win->urls) == 0); + tll_foreach(win->term->urls, it) { + struct wl_url url = {.url = &it->item}; + wayl_win_subsurface_new(win, &url.surf, false); + + tll_push_back(win->urls, url); + tag_cells_for_url(term, &it->item, true); + } + render_refresh_urls(term); render_refresh(term); } @@ -860,7 +863,6 @@ urls_reset(struct terminal *term) } tll_foreach(term->urls, it) { - tag_cells_for_url(term, &it->item, false); url_destroy(&it->item); tll_remove(term->urls, it); } diff --git a/utils/meson.build b/utils/meson.build new file mode 100644 index 00000000..2836788c --- /dev/null +++ b/utils/meson.build @@ -0,0 +1 @@ +executable('xtgettcap', 'xtgettcap.c') diff --git a/utils/xtgettcap.c b/utils/xtgettcap.c new file mode 100644 index 00000000..b3ab712a --- /dev/null +++ b/utils/xtgettcap.c @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct termios orig_termios; + +static void +disable_raw_mode(void) +{ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) < 0) + exit(__LINE__); +} + +static void +enable_raw_mode(void) +{ + if (tcgetattr(STDIN_FILENO, &orig_termios) < 0) + exit(__LINE__); + + atexit(disable_raw_mode); + + struct termios raw = orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) < 0) + exit(__LINE__); +} + +static const char * +hexlify(const char *s) +{ + static char buf[1024]; + + const size_t len = strlen(s); + for (size_t i = 0; i < len; i++) + sprintf(&buf[i * 2], "%02x", s[i]); + buf[len * 2 + 1] = '\0'; + + return buf; +} + +static size_t +unhexlify(char *dst, const char *src) +{ + size_t count = 0; + for (const char *p = src; *p != '\0'; p += 2, dst++, count++) + sscanf(p, "%02hhx", (unsigned char *)dst); + + *dst = '\0'; + return count; +} + +int +main(int argc, const char *const *argv) +{ + const size_t query_count = argc - 1; + + if (query_count == 0) + return 0; + + enable_raw_mode(); + + printf("\x1bP+q"); + for (int i = 1; i < argc; i++) + printf("%s%s", i > 1 ? ";" : "", hexlify(argv[i])); + printf("\033\\"); + + fflush(NULL); + + size_t replies = 0; + while (replies < query_count) { + struct pollfd fds[] = {{.fd = STDIN_FILENO, .events = POLLIN}}; + int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); + if (r < 0) + exit(__LINE__); + + char buf[1024] = {0}; + ssize_t count = read(STDIN_FILENO, buf, sizeof(buf)); + + if (count < 0) + exit(__LINE__); + + if (count == 1 && buf[0] == 'q') + break; + + printf("reply: (%zd chars): ", count); + + for (size_t i = 0; i < (size_t)count; i++) { + if (isprint(buf[i])) + printf("%c", buf[i]); + else if (buf[i] == '\033') + printf("\033[1;31m\\E\033[m"); + else + printf("%02x", (uint8_t)buf[i]); + } + printf("\r\n"); + + const char *p = buf; + const char *end = buf + count; + + while (p < end) { + + const char *ST = strstr(p, "\033\\"); + if (ST == NULL) + break; + + if (count < 5 || + (strncmp(p, "\033P1+r", 5) != 00 && + strncmp(p, "\033P0+r", 5) != 0)) + { + break; + } + + const bool success = p[2] == '1'; + + char decoded[1024]; + char copy[ST - &p[5] + 1]; + strncpy(copy, &p[5], ST - &p[5]); + copy[ST - &p[5]] = '\0'; + + char *saveptr = NULL; + for (char *key_value = strtok_r(copy, "; ", &saveptr); + key_value != NULL; + key_value = strtok_r(NULL, "; ", &saveptr)) + { + // printf("key-value=%s\n", key_value); + const char *key = strtok(key_value, "="); + const char *value = strtok(NULL, "="); + +#if 0 + assert((success && value != NULL) || + (!success && value == NULL)); +#endif + + //printf("key=%s, value=%s\n", key, value); + size_t len = unhexlify(decoded, key); + + if (value != NULL) { + decoded[len++] = '='; + len += unhexlify(&decoded[len], value); + } + + const int color = success ? 39 : 31; + + printf(" \033[%dm", color); + for (size_t i = 0 ; i < len; i++) { + if (isprint(decoded[i])) + printf("%c", decoded[i]); + else if (decoded[i] == '\033') + printf("\033[1;31m\\E\033[22;%dm", color); + else + printf("\033[1m%02x\033[22m", (uint8_t)decoded[i]); + } + printf("\033[m\r\n"); + replies++; + } + + p = ST + 2; + } + + } + + return 0; +} diff --git a/wayland.c b/wayland.c index cd052532..68a7a4f1 100644 --- a/wayland.c +++ b/wayland.c @@ -370,8 +370,8 @@ output_update_ppi(struct monitor *mon) if (mon->dim.mm.width <= 0 || mon->dim.mm.height <= 0) return; - int x_inches = mon->dim.mm.width * 0.03937008; - int y_inches = mon->dim.mm.height * 0.03937008; + double x_inches = mon->dim.mm.width * 0.03937008; + double y_inches = mon->dim.mm.height * 0.03937008; mon->ppi.real.x = mon->dim.px_real.width / x_inches; mon->ppi.real.y = mon->dim.px_real.height / y_inches; @@ -407,8 +407,16 @@ output_update_ppi(struct monitor *mon) mon->ppi.scaled.x = scaled_width / x_inches; mon->ppi.scaled.y = scaled_height / y_inches; - float px_diag = sqrt(pow(scaled_width, 2) + pow(scaled_height, 2)); + double px_diag = sqrt(pow(scaled_width, 2) + pow(scaled_height, 2)); mon->dpi = px_diag / mon->inch * mon->scale; + + if (mon->dpi > 1000) { + if (mon->name != NULL) { + LOG_WARN("%s: DPI=%f is unreasonable, using 96 instead", + mon->name, mon->dpi); + } + mon->dpi = 96; + } } static void @@ -761,6 +769,16 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, struct wl_window *win = data; struct terminal *term = win->term; + if (win->unmapped) { + /* + * https://codeberg.org/dnkl/foot/issues/1249 + * https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3487 + * https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/3719 + * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/108 + */ + return; + } + bool wasnt_configured = !win->is_configured; bool was_resizing = win->is_resizing; bool csd_was_enabled = win->csd_mode == CSD_YES && !win->is_fullscreen; @@ -1611,6 +1629,7 @@ wayl_win_destroy(struct wl_window *win) wayl_roundtrip(win->term->wl); /* Main window */ + win->unmapped = true; wl_surface_attach(win->surface, NULL, 0, 0); wl_surface_commit(win->surface); wayl_roundtrip(win->term->wl); @@ -1696,7 +1715,8 @@ wayl_reload_xcursor_theme(struct seat *seat, int new_scale) const char *xcursor_theme = getenv("XCURSOR_THEME"); LOG_INFO("cursor theme: %s, size: %d, scale: %d", - xcursor_theme, xcursor_size, new_scale); + xcursor_theme ? xcursor_theme : "(null)", + xcursor_size, new_scale); seat->pointer.theme = wl_cursor_theme_load( xcursor_theme, xcursor_size * new_scale, seat->wayl->shm); @@ -1915,7 +1935,7 @@ activation_token_done(void *data, struct xdg_activation_token_v1 *xdg_token, return; } - xassert(false); + BUG("activation token not found in list"); } static const struct diff --git a/wayland.h b/wayland.h index e86c6a3d..4b6939ab 100644 --- a/wayland.h +++ b/wayland.h @@ -327,6 +327,7 @@ struct wl_window { tll(struct xdg_activation_token_context *) xdg_tokens; bool urgency_token_is_pending; #endif + bool unmapped; struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration;