tabs and tab overview
This commit is contained in:
commit
d07c2a5cc9
244 changed files with 72046 additions and 0 deletions
55
.builds/alpine-x64.yml.disabled
Normal file
55
.builds/alpine-x64.yml.disabled
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
image: alpine/edge
|
||||||
|
packages:
|
||||||
|
- musl-dev
|
||||||
|
- eudev-libs
|
||||||
|
- eudev-dev
|
||||||
|
- linux-headers
|
||||||
|
- meson
|
||||||
|
- ninja
|
||||||
|
- gcc
|
||||||
|
- scdoc
|
||||||
|
- wayland-dev
|
||||||
|
- wayland-protocols
|
||||||
|
- freetype-dev
|
||||||
|
- fontconfig-dev
|
||||||
|
- harfbuzz-dev
|
||||||
|
- utf8proc-dev
|
||||||
|
- pixman-dev
|
||||||
|
- libxkbcommon-dev
|
||||||
|
- ncurses
|
||||||
|
- python3
|
||||||
|
- py3-pip
|
||||||
|
- check-dev
|
||||||
|
- ttf-hack
|
||||||
|
- font-noto-emoji
|
||||||
|
|
||||||
|
sources:
|
||||||
|
- https://git.sr.ht/~dnkl/foot
|
||||||
|
|
||||||
|
# triggers:
|
||||||
|
# - action: email
|
||||||
|
# condition: failure
|
||||||
|
# to: <committer>
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- fcft: |
|
||||||
|
cd foot/subprojects
|
||||||
|
git clone https://codeberg.org/dnkl/fcft.git
|
||||||
|
cd ../..
|
||||||
|
- debug: |
|
||||||
|
mkdir -p bld/debug
|
||||||
|
meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug
|
||||||
|
ninja -C bld/debug -k0
|
||||||
|
meson test -C bld/debug --print-errorlogs
|
||||||
|
- release: |
|
||||||
|
mkdir -p bld/release
|
||||||
|
meson --buildtype=minsize -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
|
||||||
|
ninja -C bld/release -k0
|
||||||
|
meson test -C bld/release --print-errorlogs
|
||||||
|
- codespell: |
|
||||||
|
python3 -m venv codespell-venv
|
||||||
|
source codespell-venv/bin/activate
|
||||||
|
pip install codespell
|
||||||
|
cd foot
|
||||||
|
~/.local/bin/codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd
|
||||||
|
deactivate
|
||||||
43
.builds/alpine-x86.yml.disabled
Normal file
43
.builds/alpine-x86.yml.disabled
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
image: alpine/edge
|
||||||
|
arch: x86
|
||||||
|
packages:
|
||||||
|
- musl-dev
|
||||||
|
- eudev-libs
|
||||||
|
- eudev-dev
|
||||||
|
- linux-headers
|
||||||
|
- meson
|
||||||
|
- ninja
|
||||||
|
- gcc
|
||||||
|
- scdoc
|
||||||
|
- wayland-dev
|
||||||
|
- wayland-protocols
|
||||||
|
- freetype-dev
|
||||||
|
- fontconfig-dev
|
||||||
|
- harfbuzz-dev
|
||||||
|
- utf8proc-dev
|
||||||
|
- pixman-dev
|
||||||
|
- libxkbcommon-dev
|
||||||
|
- ncurses
|
||||||
|
- check-dev
|
||||||
|
- ttf-hack
|
||||||
|
- font-noto-emoji
|
||||||
|
|
||||||
|
sources:
|
||||||
|
- https://git.sr.ht/~dnkl/foot
|
||||||
|
|
||||||
|
# triggers:
|
||||||
|
# - action: email
|
||||||
|
# condition: failure
|
||||||
|
# to: <committer>
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- debug: |
|
||||||
|
mkdir -p bld/debug
|
||||||
|
meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug
|
||||||
|
ninja -C bld/debug -k0
|
||||||
|
meson test -C bld/debug --print-errorlogs
|
||||||
|
- release: |
|
||||||
|
mkdir -p bld/release
|
||||||
|
meson --buildtype=minsize -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
|
||||||
|
ninja -C bld/release -k0
|
||||||
|
meson test -C bld/release --print-errorlogs
|
||||||
49
.builds/freebsd-x64.yml
Normal file
49
.builds/freebsd-x64.yml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
image: freebsd/latest
|
||||||
|
packages:
|
||||||
|
- evdev-proto
|
||||||
|
- libepoll-shim
|
||||||
|
- meson
|
||||||
|
- ninja
|
||||||
|
- pkgconf
|
||||||
|
- scdoc
|
||||||
|
- wayland
|
||||||
|
- wayland-protocols
|
||||||
|
- freetype2
|
||||||
|
- fontconfig
|
||||||
|
- harfbuzz
|
||||||
|
- utf8proc
|
||||||
|
- pixman
|
||||||
|
- libxkbcommon
|
||||||
|
- check
|
||||||
|
- hack-font
|
||||||
|
- noto-emoji
|
||||||
|
|
||||||
|
sources:
|
||||||
|
- https://codeberg.org/dnkl/foot.git
|
||||||
|
|
||||||
|
# triggers:
|
||||||
|
# - action: email
|
||||||
|
# condition: failure
|
||||||
|
# to: <committer>
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- fcft: |
|
||||||
|
cd foot/subprojects
|
||||||
|
git clone https://codeberg.org/dnkl/tllist.git
|
||||||
|
git clone https://codeberg.org/dnkl/fcft.git
|
||||||
|
cd ../..
|
||||||
|
- debug: |
|
||||||
|
mkdir -p bld/debug
|
||||||
|
meson setup --buildtype=debug -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug
|
||||||
|
ninja -C bld/debug -k0
|
||||||
|
meson test -C bld/debug --print-errorlogs
|
||||||
|
bld/debug/foot --version
|
||||||
|
bld/debug/footclient --version
|
||||||
|
|
||||||
|
- release: |
|
||||||
|
mkdir -p bld/release
|
||||||
|
meson setup --buildtype=minsize -Db_pgo=generate -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
|
||||||
|
ninja -C bld/release -k0
|
||||||
|
meson test -C bld/release --print-errorlogs
|
||||||
|
bld/release/foot --version
|
||||||
|
bld/release/footclient --version
|
||||||
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 70
|
||||||
|
|
||||||
|
[{meson.build,PKGBUILD}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.scd]
|
||||||
|
indent_style = tab
|
||||||
|
trim_trailing_whitespace = false
|
||||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
/bld/
|
||||||
|
/build/
|
||||||
|
/build-pgo/
|
||||||
|
/pkg/
|
||||||
|
/src/
|
||||||
|
/.cache/
|
||||||
|
/subprojects/*/
|
||||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
139
.woodpecker.yaml
Normal file
139
.woodpecker.yaml
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
# -*- yaml -*-
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: pychecks
|
||||||
|
when:
|
||||||
|
- event: [manual, pull_request]
|
||||||
|
- event: [push, tag]
|
||||||
|
branch: [master, releases/*]
|
||||||
|
image: alpine:edge
|
||||||
|
commands:
|
||||||
|
- apk add openssl
|
||||||
|
- apk add python3
|
||||||
|
- apk add py3-pip
|
||||||
|
- python3 -m venv venv
|
||||||
|
- source venv/bin/activate
|
||||||
|
- python -m pip install --upgrade pip
|
||||||
|
- pip install codespell
|
||||||
|
- pip install mypy
|
||||||
|
- pip install ruff
|
||||||
|
- codespell
|
||||||
|
- mypy
|
||||||
|
- ruff check
|
||||||
|
- deactivate
|
||||||
|
|
||||||
|
- name: subprojects
|
||||||
|
when:
|
||||||
|
- event: [manual, pull_request]
|
||||||
|
- event: [push, tag]
|
||||||
|
branch: [master, releases/*]
|
||||||
|
image: alpine:edge
|
||||||
|
commands:
|
||||||
|
- apk add git
|
||||||
|
- mkdir -p subprojects && cd subprojects
|
||||||
|
- git clone https://codeberg.org/dnkl/tllist.git
|
||||||
|
- git clone https://codeberg.org/dnkl/fcft.git
|
||||||
|
- cd ..
|
||||||
|
|
||||||
|
- name: x64
|
||||||
|
when:
|
||||||
|
- event: [manual, pull_request]
|
||||||
|
- event: [push, tag]
|
||||||
|
branch: [master, releases/*]
|
||||||
|
depends_on: [subprojects]
|
||||||
|
image: alpine:edge
|
||||||
|
commands:
|
||||||
|
- apk update
|
||||||
|
- 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
|
||||||
|
- apk add check-dev
|
||||||
|
- apk add ttf-hack font-noto-emoji
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
- mkdir -p bld/debug-x64
|
||||||
|
- cd bld/debug-x64
|
||||||
|
- meson setup --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
|
||||||
|
- cd ../..
|
||||||
|
|
||||||
|
# Release (gcc)
|
||||||
|
- mkdir -p bld/release-x64
|
||||||
|
- cd bld/release-x64
|
||||||
|
- meson setup --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
|
||||||
|
- cd ../..
|
||||||
|
|
||||||
|
# Release (clang)
|
||||||
|
- mkdir -p bld/release-x64-clang
|
||||||
|
- cd bld/release-x64-clang
|
||||||
|
- CC=clang meson setup --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
|
||||||
|
- cd bld/debug
|
||||||
|
- meson setup --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
|
||||||
|
- cd ../..
|
||||||
|
|
||||||
|
- name: x86
|
||||||
|
when:
|
||||||
|
- event: [manual, pull_request]
|
||||||
|
- event: [push, tag]
|
||||||
|
branch: [master, releases/*]
|
||||||
|
depends_on: [subprojects]
|
||||||
|
image: i386/alpine:edge
|
||||||
|
commands:
|
||||||
|
- apk update
|
||||||
|
- 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
|
||||||
|
- apk add check-dev
|
||||||
|
- apk add ttf-hack font-noto-emoji
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
- mkdir -p bld/debug-x86
|
||||||
|
- cd bld/debug-x86
|
||||||
|
- meson setup --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
|
||||||
|
- cd ../..
|
||||||
|
|
||||||
|
# Release (gcc)
|
||||||
|
- mkdir -p bld/release-x86
|
||||||
|
- cd bld/release-x86
|
||||||
|
- meson setup --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
|
||||||
|
- cd ../..
|
||||||
|
|
||||||
|
# Release (clang)
|
||||||
|
- mkdir -p bld/release-x86-clang
|
||||||
|
- cd bld/release-x86-clang
|
||||||
|
- CC=clang meson setup --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 ../..
|
||||||
3703
CHANGELOG.md
Normal file
3703
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load diff
83
CODE_OF_CONDUCT.md
Normal file
83
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Foot Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Participants in the foot community are expected to uphold the described
|
||||||
|
standards not only in official community spaces (issue trackers, IRC channels,
|
||||||
|
etc.) but in all public spaces. The Code of Conduct however does acknowledge
|
||||||
|
that people are fallible and that it is possible to truly correct a past
|
||||||
|
pattern of unacceptable behavior. That is to say, the scope of the Code of
|
||||||
|
Conduct does not necessarily extend into the distant past.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||||
|
may be reported to the community leaders responsible for enforcement
|
||||||
|
at [daniel@ekloef.se](mailto:daniel@ekloef.se). All complaints will
|
||||||
|
be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
The consequences for Code of Conduct violations will be decided upon and
|
||||||
|
enforced by community leaders. These may include a formal warning, a temporary
|
||||||
|
ban from community spaces, a permanent ban from community spaces, etc.
|
||||||
|
|
||||||
|
There are no hard and fast rules for exactly what behavior in which space will
|
||||||
|
result in what consequences, it is up to the community leaders to enforce the
|
||||||
|
Code of Conduct in a way that they believe best promotes a healthy community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the
|
||||||
|
[Contributor Covenant](https://www.contributor-covenant.org/),
|
||||||
|
version 2.1, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
||||||
476
INSTALL.md
Normal file
476
INSTALL.md
Normal file
|
|
@ -0,0 +1,476 @@
|
||||||
|
# Installing
|
||||||
|
|
||||||
|
1. [Overview](#overview)
|
||||||
|
1. [Requirements](#requirements)
|
||||||
|
1. [Running](#running)
|
||||||
|
1. [Building](#building)
|
||||||
|
1. [Other](#other)
|
||||||
|
1. [Setup](#setup)
|
||||||
|
1. [Options](#options)
|
||||||
|
1. [Release build](#release-build)
|
||||||
|
1. [Size optimized](#size-optimized)
|
||||||
|
1. [Performance optimized, non-PGO](#performance-optimized-non-pgo)
|
||||||
|
1. [Performance optimized, PGO](#performance-optimized-pgo)
|
||||||
|
1. [Partial PGO](#partial-pgo)
|
||||||
|
1. [Full PGO](#full-pgo)
|
||||||
|
1. [Use the generated PGO data](#use-the-generated-pgo-data)
|
||||||
|
1. [Profile Guided Optimization](#profile-guided-optimization)
|
||||||
|
1. [Debug build](#debug-build)
|
||||||
|
1. [Terminfo](#terminfo)
|
||||||
|
1. [Running the new build](#running-the-new-build)
|
||||||
|
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
foot makes use of a couple of libraries I have developed:
|
||||||
|
[tllist](https://codeberg.org/dnkl/tllist) and
|
||||||
|
[fcft](https://codeberg.org/dnkl/fcft). As such, they will most likely
|
||||||
|
not have been installed already. You can either install them as system
|
||||||
|
libraries or build them as _subprojects_ in foot.
|
||||||
|
|
||||||
|
When building foot, they will first be searched for as system
|
||||||
|
libraries. If **found**, foot will link dynamically against them.
|
||||||
|
If **not** found, meson will attempt to download and build them as
|
||||||
|
subprojects.
|
||||||
|
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Running
|
||||||
|
|
||||||
|
* UTF-8 locale
|
||||||
|
* fontconfig
|
||||||
|
* freetype
|
||||||
|
* pixman
|
||||||
|
* wayland (_client_ and _cursor_ libraries)
|
||||||
|
* xkbcommon
|
||||||
|
* utf8proc (_optional_, needed for grapheme clustering)
|
||||||
|
* libutempter (_optional_, needed for utmp logging on Linux)
|
||||||
|
* ulog (_optional_, needed for utmp logging on FreeBSD)
|
||||||
|
* [fcft](https://codeberg.org/dnkl/fcft) [^1]
|
||||||
|
|
||||||
|
[^1]: can also be built as subprojects, in which case they are
|
||||||
|
statically linked.
|
||||||
|
|
||||||
|
If you are packaging foot, you may also want to consider adding the
|
||||||
|
following **optional** dependencies:
|
||||||
|
|
||||||
|
* libnotify: desktop notifications by default uses `notify-send`.
|
||||||
|
* xdg-utils: URLs are by default launched with `xdg-open`.
|
||||||
|
* bash-completion: If you want completion for positional arguments.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
In addition to the dev variant of the packages above, you need:
|
||||||
|
|
||||||
|
* meson
|
||||||
|
* ninja
|
||||||
|
* wayland protocols
|
||||||
|
* ncurses (needed to generate terminfo)
|
||||||
|
* scdoc (for man page generation, not needed if documentation is disabled)
|
||||||
|
* llvm (for PGO builds with Clang)
|
||||||
|
* [tllist](https://codeberg.org/dnkl/tllist) [^1]
|
||||||
|
* systemd (optional, foot will install systemd unit files if detected)
|
||||||
|
|
||||||
|
A note on compilers; in general, foot runs **much** faster when
|
||||||
|
compiled with gcc instead of clang. A profile-guided gcc build can be
|
||||||
|
more than twice as fast as a clang build.
|
||||||
|
|
||||||
|
**Note** GCC 10.1 has a performance regression that severely affects
|
||||||
|
foot when doing PGO builds and building with `-O2`; it is about 30-40%
|
||||||
|
slower compared to GCC 9.3.
|
||||||
|
|
||||||
|
The work around is simple: make sure you build with `-O3`. This is the
|
||||||
|
default with `meson --buildtype=release`, but e.g. `makepkg` can
|
||||||
|
override it (`makepkg` uses `-O2` by default).
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
Foot uses _meson_. If you are unfamiliar with it, the official
|
||||||
|
[tutorial](https://mesonbuild.com/Tutorial.html) might be a good
|
||||||
|
starting point.
|
||||||
|
|
||||||
|
A note on terminfo; the terminfo database exposes terminal
|
||||||
|
capabilities to the applications running inside the terminal. As such,
|
||||||
|
it is important that the terminfo used reflects the actual
|
||||||
|
terminal. Using the `xterm-256color` terminfo will, in many cases,
|
||||||
|
work, but I still recommend using foot's own terminfo. There are two
|
||||||
|
reasons for this:
|
||||||
|
|
||||||
|
* foot's terminfo contains a couple of non-standard capabilities,
|
||||||
|
used by e.g. tmux.
|
||||||
|
* New capabilities added to the `xterm-256color` terminfo could
|
||||||
|
potentially break foot.
|
||||||
|
* There may be future additions or changes to foot's terminfo.
|
||||||
|
|
||||||
|
As of ncurses 2021-07-31, ncurses includes a version of foot's
|
||||||
|
terminfo. **The recommendation is to use those**, and only install the
|
||||||
|
terminfo definitions from this git repo if the system's ncurses
|
||||||
|
predates 2021-07-31.
|
||||||
|
|
||||||
|
But, note that the foot terminfo definitions in ncurses' lack the
|
||||||
|
non-standard capabilities. This mostly affects tmux; without them,
|
||||||
|
`terminal-overrides` must be configured to enable truecolor
|
||||||
|
support. For this reason, it _is_ possible to install "our" terminfo
|
||||||
|
definitions as well, either in a non-default location, or under a
|
||||||
|
different name.
|
||||||
|
|
||||||
|
Both have their set of issues. When installing to a non-default
|
||||||
|
location, foot will set the environment variable `TERMINFO` in the
|
||||||
|
child process. However, there are many situations where this simply
|
||||||
|
does not work. See https://codeberg.org/dnkl/foot/issues/695 for
|
||||||
|
details.
|
||||||
|
|
||||||
|
Installing them under a different name generally works well, but will
|
||||||
|
break applications that check if `$TERM == foot`.
|
||||||
|
|
||||||
|
Hence the recommendation to simply use ncurses' terminfo definitions
|
||||||
|
if available.
|
||||||
|
|
||||||
|
If packaging "our" terminfo definitions, I recommend doing that as a
|
||||||
|
separate package, to allow them to be installed on remote systems
|
||||||
|
without having to install foot itself.
|
||||||
|
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
To build, first, create a build directory, and switch to it:
|
||||||
|
```sh
|
||||||
|
mkdir -p bld/release && cd bld/release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
Available compile-time options:
|
||||||
|
|
||||||
|
| Option | Type | Default | Description | Extra dependencies |
|
||||||
|
|--------------------------------------|---------|-------------------------|---------------------------------------------------------------------------------|---------------------|
|
||||||
|
| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc |
|
||||||
|
| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | None |
|
||||||
|
| `-Dime` | bool | `true` | Enables IME support | None |
|
||||||
|
| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc |
|
||||||
|
| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) |
|
||||||
|
| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | None |
|
||||||
|
| `-Dterminfo-base-name` | string | `-Ddefault-terminfo` | Base name of the generated terminfo files | None |
|
||||||
|
| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None |
|
||||||
|
| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None |
|
||||||
|
| `-Dutmp-backend` | combo | `auto` | Which utmp backend to use (`none`, `libutempter`, `ulog` or `auto`) | libutempter or ulog |
|
||||||
|
| `-Dutmp-default-helper-path` | string | `auto` | Default path to utmp helper binary. `auto` selects path based on `utmp-backend` | None |
|
||||||
|
|
||||||
|
Documentation includes the man pages, readme, changelog and license
|
||||||
|
files.
|
||||||
|
|
||||||
|
`-Ddefault-terminfo`: I strongly recommend leaving the default
|
||||||
|
value. Use this option if you plan on installing the terminfo files
|
||||||
|
under a different name. Setting this changes the default value of
|
||||||
|
`$TERM`, and the names of the terminfo files (if
|
||||||
|
`-Dterminfo=enabled`).
|
||||||
|
|
||||||
|
If you want foot to use the terminfo files from ncurses, but still
|
||||||
|
package foot's own terminfo files under a different name, you can use
|
||||||
|
the `-Dterminfo-base-name` option. Many distributions use the name
|
||||||
|
`foot-extra`, and thus it might be a good idea to reuse that:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
meson ... -Ddefault-terminfo=foot -Dterminfo-base-name=foot-extra
|
||||||
|
```
|
||||||
|
(or just leave out `-Ddefault-terminfo`, since it defaults to `foot` anyway).
|
||||||
|
|
||||||
|
Finally, `-Dcustom-terminfo-install-location` enables foot's terminfo
|
||||||
|
to co-exist with ncurses' version, without changing the terminfo
|
||||||
|
names. The idea is that you install foot's terminfo to a non-standard
|
||||||
|
location, for example `/usr/share/foot/terminfo`. Use
|
||||||
|
`-Dcustom-terminfo-install-location` to tell foot where the terminfo
|
||||||
|
is. Foot will set the environment variable `TERMINFO` to this value
|
||||||
|
(with `${prefix}` added). The value is **relative to ${prefix}**.
|
||||||
|
|
||||||
|
Note that there are several issues with this approach:
|
||||||
|
https://codeberg.org/dnkl/foot/issues/695.
|
||||||
|
|
||||||
|
If left unset, foot will **not** set or modify `TERMINFO`.
|
||||||
|
|
||||||
|
`-Dterminfo` can be used to disable building the terminfo definitions
|
||||||
|
in the meson build. It does **not** change the default value of
|
||||||
|
`TERM`, and it does **not** disable `TERMINFO`, if
|
||||||
|
`-Dcustom-terminfo-install-location` has been set. Use this if
|
||||||
|
packaging the terminfo definitions in a separate package (and the
|
||||||
|
build script isn't shared with the 'foot' package).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
meson --prefix=/usr -Dcustom-terminfo-install-location=lib/foot/terminfo
|
||||||
|
```
|
||||||
|
|
||||||
|
The above tells foot its terminfo definitions will be installed to
|
||||||
|
`/usr/lib/foot/terminfo`. This is the value foot will set the
|
||||||
|
`TERMINFO` environment variable to.
|
||||||
|
|
||||||
|
If `-Dterminfo` is enabled (the default), then the terminfo files will
|
||||||
|
be built as part of the regular build process, and installed to the
|
||||||
|
specified location.
|
||||||
|
|
||||||
|
Packagers may want to set `-Dterminfo=disabled`, and manually build
|
||||||
|
and [install the terminfo](#terminfo) files instead.
|
||||||
|
|
||||||
|
|
||||||
|
### Release build
|
||||||
|
|
||||||
|
Below are instructions for building foot either [size
|
||||||
|
optimized](#size-optimized), [performance
|
||||||
|
optimized](performance-optimized-non-pgo), or performance
|
||||||
|
optimized using [PGO](#performance-optimized-pgo).
|
||||||
|
|
||||||
|
PGO - _Profile Guided Optimization_ - is a way to optimize a program
|
||||||
|
better than `-O3` can, and is done by compiling foot twice: first to
|
||||||
|
generate an instrumented version which is used to run a payload that
|
||||||
|
exercises the performance critical parts of foot, and then a second
|
||||||
|
time to rebuild foot using the generated profiling data to guide
|
||||||
|
optimization.
|
||||||
|
|
||||||
|
In addition to being faster, PGO builds also tend to be smaller than
|
||||||
|
regular `-O3` builds.
|
||||||
|
|
||||||
|
|
||||||
|
#### Size optimized
|
||||||
|
|
||||||
|
To optimize for size (i.e. produce a small binary):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export CFLAGS="$CFLAGS -Os"
|
||||||
|
meson --buildtype=release --prefix=/usr -Db_lto=true ../..
|
||||||
|
ninja
|
||||||
|
ninja test
|
||||||
|
ninja install
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Performance optimized, non-PGO
|
||||||
|
|
||||||
|
To do a regular, non-PGO build optimized for performance:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export CFLAGS="$CFLAGS -O3"
|
||||||
|
meson --buildtype=release --prefix=/usr -Db_lto=true ../..
|
||||||
|
ninja
|
||||||
|
ninja test
|
||||||
|
ninja install
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `-O2` instead of `-O3` if you prefer a slightly smaller (and
|
||||||
|
slower!) binary.
|
||||||
|
|
||||||
|
|
||||||
|
#### Performance optimized, PGO
|
||||||
|
|
||||||
|
There are a lot more steps involved in a PGO build, and for this
|
||||||
|
reason there are a number of helper scripts available.
|
||||||
|
|
||||||
|
`pgo/pgo.sh` is a standalone script that pieces together the other
|
||||||
|
scripts in the `pgo` directory to do a complete PGO build. This script
|
||||||
|
is intended to be used when doing manual builds.
|
||||||
|
|
||||||
|
Note that all "full" PGO builds (which `auto` will prefer, if
|
||||||
|
possible) **require** `LC_CTYPE` to be set to an UTF-8 locale. This is
|
||||||
|
**not** done automatically.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd foot
|
||||||
|
./pgo/pgo.sh auto . /tmp/foot-pgo-build-output
|
||||||
|
```
|
||||||
|
|
||||||
|
(run `./pgo/pgo.sh` to get help on usage)
|
||||||
|
|
||||||
|
It supports a couple of different PGO builds; partial (covered in
|
||||||
|
detail below), full (also covered in detail below), and (full)
|
||||||
|
headless builds using Sway or cage.
|
||||||
|
|
||||||
|
Packagers may want to use it as inspiration, but may choose to support
|
||||||
|
only a specific build type; e.g. full/headless with Sway.
|
||||||
|
|
||||||
|
To do a manual PGO build, instead of using the script(s) mentioned
|
||||||
|
above, detailed instructions follows:
|
||||||
|
|
||||||
|
First, configure the build directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export CFLAGS="$CFLAGS -O3"
|
||||||
|
meson --buildtype=release --prefix=/usr -Db_lto=true ../..
|
||||||
|
```
|
||||||
|
|
||||||
|
It is **very** important `-O3` is being used here, as GCC-10.1.x and
|
||||||
|
later have a regression where PGO with `-O2` is **much** slower.
|
||||||
|
|
||||||
|
Clang users **must** add `-Wno-ignored-optimization-argument` to
|
||||||
|
`CFLAGS`.
|
||||||
|
|
||||||
|
Then, tell meson we want to _generate_ profiling data, and build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
meson configure -Db_pgo=generate
|
||||||
|
ninja
|
||||||
|
ninja test
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, we need to actually generate the profiling data.
|
||||||
|
|
||||||
|
There are two ways to do this: a [partial PGO build using a PGO
|
||||||
|
helper](#partial-pgo) binary, or a [full PGO build](#full-pgo) by
|
||||||
|
running the real foot binary. The latter has slightly better results
|
||||||
|
(i.e. results in a faster binary), but must be run in a Wayland
|
||||||
|
session.
|
||||||
|
|
||||||
|
A full PGO build also tends to be smaller than a partial build.
|
||||||
|
|
||||||
|
|
||||||
|
##### Partial PGO
|
||||||
|
|
||||||
|
This method uses a PGO helper binary that links against the VT parser
|
||||||
|
only. It is similar to a mock test; it instantiates a dummy terminal
|
||||||
|
instance and then directly calls the VT parser with stimuli.
|
||||||
|
|
||||||
|
It explicitly does **not** include the Wayland backend and as such, it
|
||||||
|
does not require a running Wayland session. The downside is that not
|
||||||
|
all code paths in foot is exercised. In particular, the **rendering**
|
||||||
|
code is not. As a result, the final binary built using this method is
|
||||||
|
slightly slower than when doing a [full PGO](#full-pgo) build.
|
||||||
|
|
||||||
|
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)
|
||||||
|
../../scripts/generate-alt-random-writes \
|
||||||
|
--rows=67 \
|
||||||
|
--cols=135 \
|
||||||
|
--scroll \
|
||||||
|
--scroll-region \
|
||||||
|
--colors-regular \
|
||||||
|
--colors-bright \
|
||||||
|
--colors-256 \
|
||||||
|
--colors-rgb \
|
||||||
|
--attr-bold \
|
||||||
|
--attr-italic \
|
||||||
|
--attr-underline \
|
||||||
|
--sixel \
|
||||||
|
${tmp_file}
|
||||||
|
./pgo ${tmp_file} ${tmp_file} ${tmp_file}
|
||||||
|
rm ${tmp_file}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first step, running `./foot --version` and `./footclient
|
||||||
|
--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
|
||||||
|
`${tmp_file}` in a terminal, you'll see random colored characters all
|
||||||
|
over the screen). Finally, we feed the randomly generated escape
|
||||||
|
sequences to the PGO helper. This is what generates the profiling data
|
||||||
|
used in the next step.
|
||||||
|
|
||||||
|
You are now ready to [use the generated PGO
|
||||||
|
data](#use-the-generated-pgo-data).
|
||||||
|
|
||||||
|
|
||||||
|
##### Full PGO
|
||||||
|
|
||||||
|
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 \
|
||||||
|
--config=/dev/null \
|
||||||
|
--override tweak.grapheme-shaping=no \
|
||||||
|
--term=xterm \
|
||||||
|
sh -c "<path-to-generate-alt-random-writes.py> --scroll --scroll-region --colors-regular --colors-bright --colors-256 --colors-rgb --attr-bold --attr-italic --attr-underline --sixel ${foot_tmp_file} && cat ${foot_tmp_file}"
|
||||||
|
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, `./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
|
||||||
|
|
||||||
|
Now that we have _generated_ PGO data, we need to rebuild foot. This
|
||||||
|
time telling meson (and ultimately gcc/clang) to _use_ the PGO data.
|
||||||
|
|
||||||
|
If using Clang, now do (this requires _llvm_ to have been installed):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
llvm-profdata merge default_*profraw --output=default.profdata
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, tell meson to _use_ the profile data we just generated, and rebuild:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
meson configure -Db_pgo=use
|
||||||
|
ninja
|
||||||
|
ninja test
|
||||||
|
```
|
||||||
|
|
||||||
|
Continue reading in [Running the new build](#running-the-new-build)
|
||||||
|
|
||||||
|
|
||||||
|
### Debug build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
meson --buildtype=debug ../..
|
||||||
|
ninja
|
||||||
|
ninja test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terminfo
|
||||||
|
|
||||||
|
By default, building foot also builds the terminfo files. If packaging
|
||||||
|
the terminfo files in a separate package, it might be easier to simply
|
||||||
|
disable the terminfo files in the regular build, and compile the
|
||||||
|
terminfo files manually instead.
|
||||||
|
|
||||||
|
To build the terminfo files, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sed 's/@default_terminfo@/foot/g' foot.info | \
|
||||||
|
tic -o <output-directory> -x -e foot,foot-direct -
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
<output-directory>` can simply be omitted.
|
||||||
|
|
||||||
|
Or, if packaging:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tic -o ${DESTDIR}/usr/share/terminfo ...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Running the new build
|
||||||
|
|
||||||
|
You can now run it directly from the build directory:
|
||||||
|
```sh
|
||||||
|
./foot
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if you did not install the terminfo definitions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./foot --term xterm-256color
|
||||||
|
```
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Daniel Eklöf
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
1
README.md
Normal file
1
README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
toes
|
||||||
35
async.c
Normal file
35
async.c
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include "async.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "async"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
enum async_write_status
|
||||||
|
async_write(int fd, const void *_data, size_t len, size_t *idx)
|
||||||
|
{
|
||||||
|
const uint8_t *const data = _data;
|
||||||
|
size_t left = len - *idx;
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
ssize_t ret = write(fd, &data[*idx], left);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||||
|
return ASYNC_WRITE_REMAIN;
|
||||||
|
|
||||||
|
return ASYNC_WRITE_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("wrote %zd bytes of %zu (%zu left) to FD=%d",
|
||||||
|
ret, left, left - ret, fd);
|
||||||
|
|
||||||
|
*idx += ret;
|
||||||
|
left -= ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ASYNC_WRITE_DONE;
|
||||||
|
}
|
||||||
24
async.h
Normal file
24
async.h
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
enum async_write_status {ASYNC_WRITE_DONE, ASYNC_WRITE_REMAIN, ASYNC_WRITE_ERR};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Primitive that writes data to a NONBLOCK:ing FD.
|
||||||
|
*
|
||||||
|
* _data: points to the beginning of the buffer
|
||||||
|
* len: total size of the data buffer
|
||||||
|
* idx: pointer to byte offset into data buffer - writing starts here.
|
||||||
|
*
|
||||||
|
* Thus, the total amount of data to write is (len - *idx). *idx is
|
||||||
|
* updated such that it points to the next unwritten byte in the data
|
||||||
|
* buffer.
|
||||||
|
*
|
||||||
|
* I.e. if the return value is:
|
||||||
|
* - ASYNC_WRITE_DONE, then the *idx == len.
|
||||||
|
* - ASYNC_WRITE_REMAIN, then *idx < len
|
||||||
|
* - ASYNC_WRITE_ERR, there was an error, and no data was written
|
||||||
|
*/
|
||||||
|
enum async_write_status async_write(
|
||||||
|
int fd, const void *data, size_t len, size_t *idx);
|
||||||
172
base64.c
Normal file
172
base64.c
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "base64"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
P = 1 << 6, // Padding byte (=)
|
||||||
|
I = 1 << 7, // Invalid byte ([^A-Za-z0-9+/=])
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t reverse_lookup[256] = {
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, 62, I, I, I, 63,
|
||||||
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, I, I, I, P, I, I,
|
||||||
|
I, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, I, I, I, I, I,
|
||||||
|
I, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||||
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,
|
||||||
|
I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char lookup[64] = {
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"0123456789+/"
|
||||||
|
};
|
||||||
|
|
||||||
|
char *
|
||||||
|
base64_decode(const char *s, size_t *size)
|
||||||
|
{
|
||||||
|
const size_t len = strlen(s);
|
||||||
|
if (unlikely(len % 4 != 0)) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ret = malloc(len / 4 * 3 + 1);
|
||||||
|
if (unlikely(ret == NULL))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (unlikely(size != NULL))
|
||||||
|
*size = len / 4 * 3;
|
||||||
|
|
||||||
|
for (size_t i = 0, o = 0; i < len; i += 4, o += 3) {
|
||||||
|
unsigned a = reverse_lookup[(unsigned char)s[i + 0]];
|
||||||
|
unsigned b = reverse_lookup[(unsigned char)s[i + 1]];
|
||||||
|
unsigned c = reverse_lookup[(unsigned char)s[i + 2]];
|
||||||
|
unsigned d = reverse_lookup[(unsigned char)s[i + 3]];
|
||||||
|
|
||||||
|
unsigned u = a | b | c | d;
|
||||||
|
if (unlikely(u & I))
|
||||||
|
goto invalid;
|
||||||
|
|
||||||
|
if (unlikely(u & P)) {
|
||||||
|
if (unlikely(i + 4 != len || (a | b) & P || (c & P && !(d & P))))
|
||||||
|
goto invalid;
|
||||||
|
|
||||||
|
if (unlikely(size != NULL)) {
|
||||||
|
if (c & P)
|
||||||
|
*size = len / 4 * 3 - 2;
|
||||||
|
else
|
||||||
|
*size = len / 4 * 3 - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
c &= 63;
|
||||||
|
d &= 63;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t v = a << 18 | b << 12 | c << 6 | d << 0;
|
||||||
|
char x = (v >> 16) & 0xff;
|
||||||
|
char y = (v >> 8) & 0xff;
|
||||||
|
char z = (v >> 0) & 0xff;
|
||||||
|
|
||||||
|
LOG_DBG("%c%c%c", x, y, z);
|
||||||
|
ret[o + 0] = x;
|
||||||
|
ret[o + 1] = y;
|
||||||
|
ret[o + 2] = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret[len / 4 * 3] = '\0';
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
invalid:
|
||||||
|
free(ret);
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
base64_encode(const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
xassert(size % 3 == 0);
|
||||||
|
if (unlikely(size % 3 != 0))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char *ret = malloc(size / 3 * 4 + 1);
|
||||||
|
if (unlikely(ret == NULL))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0, o = 0; i < size; i += 3, o += 4) {
|
||||||
|
int x = data[i + 0];
|
||||||
|
int y = data[i + 1];
|
||||||
|
int z = data[i + 2];
|
||||||
|
|
||||||
|
uint32_t v = x << 16 | y << 8 | z << 0;
|
||||||
|
|
||||||
|
unsigned a = (v >> 18) & 0x3f;
|
||||||
|
unsigned b = (v >> 12) & 0x3f;
|
||||||
|
unsigned c = (v >> 6) & 0x3f;
|
||||||
|
unsigned d = (v >> 0) & 0x3f;
|
||||||
|
|
||||||
|
char c0 = lookup[a];
|
||||||
|
char c1 = lookup[b];
|
||||||
|
char c2 = lookup[c];
|
||||||
|
char c3 = lookup[d];
|
||||||
|
|
||||||
|
ret[o + 0] = c0;
|
||||||
|
ret[o + 1] = c1;
|
||||||
|
ret[o + 2] = c2;
|
||||||
|
ret[o + 3] = c3;
|
||||||
|
|
||||||
|
LOG_DBG("base64: encode: %c%c%c%c", c0, c1, c2, c3);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret[size / 3 * 4] = '\0';
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
base64_encode_final(const uint8_t *data, size_t size, char result[4])
|
||||||
|
{
|
||||||
|
xassert(size > 0);
|
||||||
|
xassert(size < 3);
|
||||||
|
|
||||||
|
uint32_t v = 0;
|
||||||
|
if (size >= 1)
|
||||||
|
v |= data[0] << 16;
|
||||||
|
if (size >= 2)
|
||||||
|
v |= data[1] << 8;
|
||||||
|
|
||||||
|
unsigned a = (v >> 18) & 0x3f;
|
||||||
|
unsigned b = (v >> 12) & 0x3f;
|
||||||
|
unsigned c = (v >> 6) & 0x3f;
|
||||||
|
|
||||||
|
char c0 = lookup[a];
|
||||||
|
char c1 = lookup[b];
|
||||||
|
char c2 = size == 2 ? lookup[c] : '=';
|
||||||
|
char c3 = '=';
|
||||||
|
|
||||||
|
result[0] = c0;
|
||||||
|
result[1] = c1;
|
||||||
|
result[2] = c2;
|
||||||
|
result[3] = c3;
|
||||||
|
|
||||||
|
LOG_DBG("base64: encode: %c%c%c%c", c0, c1, c2, c3);
|
||||||
|
}
|
||||||
8
base64.h
Normal file
8
base64.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
char *base64_decode(const char *s, size_t *out_len);
|
||||||
|
char *base64_encode(const uint8_t *data, size_t size);
|
||||||
|
void base64_encode_final(const uint8_t *data, size_t size, char result[4]);
|
||||||
3450
box-drawing.c
Normal file
3450
box-drawing.c
Normal file
File diff suppressed because it is too large
Load diff
7
box-drawing.h
Normal file
7
box-drawing.h
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <uchar.h>
|
||||||
|
#include <fcft/fcft.h>
|
||||||
|
|
||||||
|
struct terminal;
|
||||||
|
struct fcft_glyph *box_drawing(const struct terminal *term, char32_t wc);
|
||||||
432
char32.c
Normal file
432
char32.c
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
#include "char32.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <locale.h>
|
||||||
|
|
||||||
|
#include <wctype.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
|
#if defined __has_include
|
||||||
|
#if __has_include (<stdc-predef.h>)
|
||||||
|
#include <stdc-predef.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LOG_MODULE "char32"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "macros.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For now, assume we can map directly to the corresponding wchar_t
|
||||||
|
* functions. This is true if:
|
||||||
|
*
|
||||||
|
* - both data types have the same size
|
||||||
|
* - both use the same encoding (though we require that encoding to be UTF-32)
|
||||||
|
*/
|
||||||
|
|
||||||
|
_Static_assert(
|
||||||
|
sizeof(wchar_t) == sizeof(char32_t), "wchar_t vs. char32_t size mismatch");
|
||||||
|
|
||||||
|
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
|
||||||
|
#error "char32_t does not use UTF-32"
|
||||||
|
#endif
|
||||||
|
#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
|
||||||
|
#error "wchar_t does not use UTF-32"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
xassert(c32len(U"") == 0);
|
||||||
|
xassert(c32len(U"foobar") == 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
xassert(c32cmp(U"foobar", U"foobar") == 0);
|
||||||
|
xassert(c32cmp(U"foo", U"foobar") < 0);
|
||||||
|
xassert(c32cmp(U"foobar", U"foo") > 0);
|
||||||
|
xassert(c32cmp(U"a", U"b") < 0);
|
||||||
|
xassert(c32cmp(U"b", U"a") > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
xassert(c32ncmp(U"foo", U"foot", 3) == 0);
|
||||||
|
xassert(c32ncmp(U"foot", U"FOOT", 4) > 0);
|
||||||
|
xassert(c32ncmp(U"a", U"b", 1) < 0);
|
||||||
|
xassert(c32ncmp(U"bb", U"aa", 2) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
char32_t copy[16];
|
||||||
|
char32_t *ret = c32ncpy(copy, U"foobar", 16);
|
||||||
|
|
||||||
|
xassert(ret == copy);
|
||||||
|
xassert(copy[0] == U'f');
|
||||||
|
xassert(copy[1] == U'o');
|
||||||
|
xassert(copy[2] == U'o');
|
||||||
|
xassert(copy[3] == U'b');
|
||||||
|
xassert(copy[4] == U'a');
|
||||||
|
xassert(copy[5] == U'r');
|
||||||
|
|
||||||
|
unsigned char zeroes[(16 - 6) * sizeof(copy[0])] = {0};
|
||||||
|
xassert(memcmp(©[6], zeroes, sizeof(zeroes)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
char32_t copy[16];
|
||||||
|
memset(copy, 0x55, sizeof(copy));
|
||||||
|
|
||||||
|
char32_t *ret = c32cpy(copy, U"foobar");
|
||||||
|
|
||||||
|
xassert(ret == copy);
|
||||||
|
xassert(copy[0] == U'f');
|
||||||
|
xassert(copy[1] == U'o');
|
||||||
|
xassert(copy[2] == U'o');
|
||||||
|
xassert(copy[3] == U'b');
|
||||||
|
xassert(copy[4] == U'a');
|
||||||
|
xassert(copy[5] == U'r');
|
||||||
|
xassert(copy[6] == U'\0');
|
||||||
|
|
||||||
|
unsigned char fives[(16 - 6 - 1) * sizeof(copy[0])];
|
||||||
|
memset(fives, 0x55, sizeof(fives));
|
||||||
|
xassert(memcmp(©[7], fives, sizeof(fives)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
xassert(c32casecmp(U"foobar", U"FOOBAR") == 0);
|
||||||
|
xassert(c32casecmp(U"foo", U"FOOO") < 0);
|
||||||
|
xassert(c32casecmp(U"FOOO", U"foo") > 0);
|
||||||
|
xassert(c32casecmp(U"a", U"B") < 0);
|
||||||
|
xassert(c32casecmp(U"B", U"a") > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
xassert(c32ncasecmp(U"foo", U"FOObar", 3) == 0);
|
||||||
|
xassert(c32ncasecmp(U"foo", U"FOOO", 4) < 0);
|
||||||
|
xassert(c32ncasecmp(U"FOOO", U"foo", 4) > 0);
|
||||||
|
xassert(c32ncasecmp(U"a", U"BB", 1) < 0);
|
||||||
|
xassert(c32ncasecmp(U"BB", U"a", 1) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
char32_t dst[32] = U"foobar";
|
||||||
|
char32_t *ret = c32ncat(dst, U"12345678XXXXXXXXX", 8);
|
||||||
|
|
||||||
|
xassert(ret == dst);
|
||||||
|
xassert(c32cmp(dst, U"foobar12345678") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
char32_t dst[32] = U"foobar";
|
||||||
|
char32_t *ret = c32cat(dst, U"12345678");
|
||||||
|
|
||||||
|
xassert(ret == dst);
|
||||||
|
xassert(c32cmp(dst, U"foobar12345678") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
xassert(!isc32upper(U'a'));
|
||||||
|
xassert(isc32upper(U'A'));
|
||||||
|
xassert(!isc32upper(U'a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
xassert(hasc32upper(U"abc1A"));
|
||||||
|
xassert(!hasc32upper(U"abc1_aaa"));
|
||||||
|
xassert(!hasc32upper(U""));
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
char32_t *c = xc32dup(U"foobar");
|
||||||
|
xassert(c32cmp(c, U"foobar") == 0);
|
||||||
|
free(c);
|
||||||
|
|
||||||
|
c = xc32dup(U"");
|
||||||
|
xassert(c32cmp(c, U"") == 0);
|
||||||
|
free(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
mbsntoc32(char32_t *dst, const char *src, size_t nms, size_t len)
|
||||||
|
{
|
||||||
|
mbstate_t ps = {0};
|
||||||
|
|
||||||
|
char32_t *out = dst;
|
||||||
|
const char *in = src;
|
||||||
|
|
||||||
|
size_t consumed = 0;
|
||||||
|
size_t chars = 0;
|
||||||
|
size_t rc;
|
||||||
|
|
||||||
|
while ((out == NULL || chars < len) &&
|
||||||
|
consumed < nms &&
|
||||||
|
(rc = mbrtoc32(out, in, nms - consumed, &ps)) != 0)
|
||||||
|
{
|
||||||
|
switch (rc) {
|
||||||
|
case 0:
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
case (size_t)-1:
|
||||||
|
case (size_t)-2:
|
||||||
|
case (size_t)-3:
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
in += rc;
|
||||||
|
consumed += rc;
|
||||||
|
chars++;
|
||||||
|
|
||||||
|
if (out != NULL)
|
||||||
|
out++;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
return chars;
|
||||||
|
|
||||||
|
err:
|
||||||
|
return (size_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
const char input[] = "foobarzoo";
|
||||||
|
char32_t c32[32];
|
||||||
|
|
||||||
|
size_t ret = mbsntoc32(NULL, input, sizeof(input), 0);
|
||||||
|
xassert(ret == 9);
|
||||||
|
|
||||||
|
memset(c32, 0x55, sizeof(c32));
|
||||||
|
ret = mbsntoc32(c32, input, sizeof(input), 32);
|
||||||
|
|
||||||
|
xassert(ret == 9);
|
||||||
|
xassert(c32[0] == U'f');
|
||||||
|
xassert(c32[1] == U'o');
|
||||||
|
xassert(c32[2] == U'o');
|
||||||
|
xassert(c32[3] == U'b');
|
||||||
|
xassert(c32[4] == U'a');
|
||||||
|
xassert(c32[5] == U'r');
|
||||||
|
xassert(c32[6] == U'z');
|
||||||
|
xassert(c32[7] == U'o');
|
||||||
|
xassert(c32[8] == U'o');
|
||||||
|
xassert(c32[9] == U'\0');
|
||||||
|
xassert(c32[10] == 0x55555555);
|
||||||
|
|
||||||
|
memset(c32, 0x55, sizeof(c32));
|
||||||
|
ret = mbsntoc32(c32, input, 1, 32);
|
||||||
|
|
||||||
|
xassert(ret == 1);
|
||||||
|
xassert(c32[0] == U'f');
|
||||||
|
xassert(c32[1] == 0x55555555);
|
||||||
|
|
||||||
|
memset(c32, 0x55, sizeof(c32));
|
||||||
|
ret = mbsntoc32(c32, input, sizeof(input), 1);
|
||||||
|
|
||||||
|
xassert(ret == 1);
|
||||||
|
xassert(c32[0] == U'f');
|
||||||
|
xassert(c32[1] == 0x55555555);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
const char input[] = "foobarzoo";
|
||||||
|
char32_t c32[32];
|
||||||
|
|
||||||
|
size_t ret = mbstoc32(NULL, input, 0);
|
||||||
|
xassert(ret == 9);
|
||||||
|
|
||||||
|
memset(c32, 0x55, sizeof(c32));
|
||||||
|
ret = mbstoc32(c32, input, 32);
|
||||||
|
|
||||||
|
xassert(ret == 9);
|
||||||
|
xassert(c32[0] == U'f');
|
||||||
|
xassert(c32[1] == U'o');
|
||||||
|
xassert(c32[2] == U'o');
|
||||||
|
xassert(c32[3] == U'b');
|
||||||
|
xassert(c32[4] == U'a');
|
||||||
|
xassert(c32[5] == U'r');
|
||||||
|
xassert(c32[6] == U'z');
|
||||||
|
xassert(c32[7] == U'o');
|
||||||
|
xassert(c32[8] == U'o');
|
||||||
|
xassert(c32[9] == U'\0');
|
||||||
|
xassert(c32[10] == 0x55555555);
|
||||||
|
|
||||||
|
memset(c32, 0x55, sizeof(c32));
|
||||||
|
ret = mbstoc32(c32, input, 1);
|
||||||
|
|
||||||
|
xassert(ret == 1);
|
||||||
|
xassert(c32[0] == U'f');
|
||||||
|
xassert(c32[1] == 0x55555555);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char32_t *
|
||||||
|
ambstoc32(const char *src)
|
||||||
|
{
|
||||||
|
if (src == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
const size_t src_len = strlen(src);
|
||||||
|
|
||||||
|
char32_t *ret = xmalloc((src_len + 1) * sizeof(ret[0]));
|
||||||
|
mbstate_t ps = {0};
|
||||||
|
|
||||||
|
char32_t *out = ret;
|
||||||
|
const char *in = src;
|
||||||
|
const char *const end = src + src_len + 1;
|
||||||
|
|
||||||
|
size_t chars = 0;
|
||||||
|
size_t rc;
|
||||||
|
|
||||||
|
while ((rc = mbrtoc32(out, in, end - in, &ps)) != 0) {
|
||||||
|
switch (rc) {
|
||||||
|
case (size_t)-1:
|
||||||
|
case (size_t)-2:
|
||||||
|
case (size_t)-3:
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
in += rc;
|
||||||
|
out++;
|
||||||
|
chars++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = U'\0';
|
||||||
|
|
||||||
|
ret = xrealloc(ret, (chars + 1) * sizeof(ret[0]));
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err:
|
||||||
|
free(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
const char* locale = setlocale(LC_CTYPE, "en_US.UTF-8");
|
||||||
|
if (!locale)
|
||||||
|
locale = setlocale(LC_CTYPE, "C.UTF-8");
|
||||||
|
if (!locale)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char32_t *hello = ambstoc32(u8"hello");
|
||||||
|
xassert(hello != NULL);
|
||||||
|
xassert(hello[0] == U'h');
|
||||||
|
xassert(hello[1] == U'e');
|
||||||
|
xassert(hello[2] == U'l');
|
||||||
|
xassert(hello[3] == U'l');
|
||||||
|
xassert(hello[4] == U'o');
|
||||||
|
xassert(hello[5] == U'\0');
|
||||||
|
free(hello);
|
||||||
|
|
||||||
|
char32_t *swedish = ambstoc32(u8"åäö");
|
||||||
|
xassert(swedish != NULL);
|
||||||
|
xassert(swedish[0] == U'å');
|
||||||
|
xassert(swedish[1] == U'ä');
|
||||||
|
xassert(swedish[2] == U'ö');
|
||||||
|
xassert(swedish[3] == U'\0');
|
||||||
|
free(swedish);
|
||||||
|
|
||||||
|
char32_t *emoji = ambstoc32(u8"👨👩👧👦");
|
||||||
|
xassert(emoji != NULL);
|
||||||
|
xassert(emoji[0] == U'👨');
|
||||||
|
xassert(emoji[1] == U'');
|
||||||
|
xassert(emoji[2] == U'👩');
|
||||||
|
xassert(emoji[3] == U'');
|
||||||
|
xassert(emoji[4] == U'👧');
|
||||||
|
xassert(emoji[5] == U'');
|
||||||
|
xassert(emoji[6] == U'👦');
|
||||||
|
xassert(emoji[7] == U'\0');
|
||||||
|
free(emoji);
|
||||||
|
|
||||||
|
xassert(ambstoc32(NULL) == NULL);
|
||||||
|
xassert(setlocale(LC_CTYPE, "C") != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
ac32tombs(const char32_t *src)
|
||||||
|
{
|
||||||
|
if (src == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
const size_t src_len = c32len(src);
|
||||||
|
|
||||||
|
size_t allocated = src_len + 1;
|
||||||
|
char *ret = xmalloc(allocated);
|
||||||
|
mbstate_t ps = {0};
|
||||||
|
|
||||||
|
char *out = ret;
|
||||||
|
const char32_t *const end = src + src_len + 1;
|
||||||
|
|
||||||
|
size_t bytes = 0;
|
||||||
|
|
||||||
|
char mb[MB_CUR_MAX];
|
||||||
|
|
||||||
|
for (const char32_t *in = src; in < end; in++) {
|
||||||
|
size_t rc = c32rtomb(mb, *in, &ps);
|
||||||
|
|
||||||
|
switch (rc) {
|
||||||
|
case (size_t)-1:
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes + rc > allocated) {
|
||||||
|
allocated *= 2;
|
||||||
|
ret = xrealloc(ret, allocated);
|
||||||
|
out = &ret[bytes];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < rc; i++, out++)
|
||||||
|
*out = mb[i];
|
||||||
|
|
||||||
|
bytes += rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
xassert(ret[bytes - 1] == '\0');
|
||||||
|
ret = xrealloc(ret, bytes);
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err:
|
||||||
|
free(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
const char* locale = setlocale(LC_CTYPE, "en_US.UTF-8");
|
||||||
|
if (!locale)
|
||||||
|
locale = setlocale(LC_CTYPE, "C.UTF-8");
|
||||||
|
if (!locale)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char *s = ac32tombs(U"foobar");
|
||||||
|
xassert(s != NULL);
|
||||||
|
xassert(strcmp(s, "foobar") == 0);
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
s = ac32tombs(U"åäö");
|
||||||
|
xassert(s != NULL);
|
||||||
|
xassert(strcmp(s, u8"åäö") == 0);
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
s = ac32tombs(U"👨👩👧👦");
|
||||||
|
xassert(s != NULL);
|
||||||
|
xassert(strcmp(s, u8"👨👩👧👦") == 0);
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
xassert(ac32tombs(NULL) == NULL);
|
||||||
|
xassert(setlocale(LC_CTYPE, "C") != NULL);
|
||||||
|
}
|
||||||
115
char32.h
Normal file
115
char32.h
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <uchar.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
#include <wctype.h>
|
||||||
|
|
||||||
|
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||||
|
#include <utf8proc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline size_t c32len(const char32_t *s) {
|
||||||
|
return wcslen((const wchar_t *)s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int c32cmp(const char32_t *s1, const char32_t *s2) {
|
||||||
|
return wcscmp((const wchar_t *)s1, (const wchar_t *)s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int c32ncmp(const char32_t *s1, const char32_t *s2, size_t n) {
|
||||||
|
return wcsncmp((const wchar_t *)s1, (const wchar_t *)s2, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char32_t *c32ncpy(char32_t *dst, const char32_t *src, size_t n) {
|
||||||
|
return (char32_t *)wcsncpy((wchar_t *)dst, (const wchar_t *)src, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char32_t *c32cpy(char32_t *dst, const char32_t *src) {
|
||||||
|
return (char32_t *)wcscpy((wchar_t *)dst, (const wchar_t *)src);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char32_t *c32ncat(char32_t *dst, const char32_t *src, size_t n) {
|
||||||
|
return (char32_t *)wcsncat((wchar_t *)dst, (const wchar_t *)src, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char32_t *c32cat(char32_t *dst, const char32_t *src) {
|
||||||
|
return (char32_t *)wcscat((wchar_t *)dst, (const wchar_t *)src);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char32_t *c32dup(const char32_t *s) {
|
||||||
|
return (char32_t *)wcsdup((const wchar_t *)s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char32_t *c32chr(const char32_t *s, char32_t c) {
|
||||||
|
return (char32_t *)wcschr((const wchar_t *)s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int c32casecmp(const char32_t *s1, const char32_t *s2) {
|
||||||
|
return wcscasecmp((const wchar_t *)s1, (const wchar_t *)s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int c32ncasecmp(const char32_t *s1, const char32_t *s2, size_t n) {
|
||||||
|
return wcsncasecmp((const wchar_t *)s1, (const wchar_t *)s2, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char32_t toc32lower(char32_t c) {
|
||||||
|
return (char32_t)towlower((wint_t)c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char32_t toc32upper(char32_t c) {
|
||||||
|
return (char32_t)towupper((wint_t)c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isc32upper(char32_t c32) {
|
||||||
|
return iswupper((wint_t)c32);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isc32space(char32_t c32) {
|
||||||
|
return iswspace((wint_t)c32);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isc32print(char32_t c32) {
|
||||||
|
return iswprint((wint_t)c32);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isc32graph(char32_t c32) {
|
||||||
|
return iswgraph((wint_t)c32);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool hasc32upper(const char32_t *s) {
|
||||||
|
for (int i = 0; s[i] != '\0'; i++) {
|
||||||
|
if (isc32upper(s[i])) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int c32width(char32_t c) {
|
||||||
|
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||||
|
return utf8proc_charwidth((utf8proc_int32_t)c);
|
||||||
|
#else
|
||||||
|
return wcwidth((wchar_t)c);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int c32swidth(const char32_t *s, size_t n) {
|
||||||
|
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||||
|
int width = 0;
|
||||||
|
for (size_t i = 0; i < n; i++)
|
||||||
|
width += utf8proc_charwidth((utf8proc_int32_t)s[i]);
|
||||||
|
return width;
|
||||||
|
#else
|
||||||
|
return wcswidth((const wchar_t *)s, n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t mbsntoc32(char32_t *dst, const char *src, size_t nms, size_t len);
|
||||||
|
char32_t *ambstoc32(const char *src);
|
||||||
|
char *ac32tombs(const char32_t *src);
|
||||||
|
|
||||||
|
static inline size_t mbstoc32(char32_t *dst, const char *src, size_t len) {
|
||||||
|
return mbsntoc32(dst, src, strlen(src) + 1, len);
|
||||||
|
}
|
||||||
45
client-protocol.h
Normal file
45
client-protocol.h
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
struct client_string {
|
||||||
|
uint16_t len;
|
||||||
|
/* char str[static len]; */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct client_data {
|
||||||
|
bool hold:1;
|
||||||
|
bool no_wait:1;
|
||||||
|
bool xdga_token:1;
|
||||||
|
uint8_t reserved:5;
|
||||||
|
|
||||||
|
uint8_t token_len;
|
||||||
|
uint16_t cwd_len;
|
||||||
|
uint16_t override_count;
|
||||||
|
uint16_t argc;
|
||||||
|
uint16_t env_count;
|
||||||
|
|
||||||
|
/* char cwd[static cwd_len]; */
|
||||||
|
/* char token[static token_len]; */
|
||||||
|
/* struct client_string overrides[static override_count]; */
|
||||||
|
/* struct client_string argv[static argc]; */
|
||||||
|
/* struct client_string envp[static env_count]; */
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
_Static_assert(sizeof(struct client_data) == 10, "protocol struct size error");
|
||||||
|
|
||||||
|
enum client_ipc_code {
|
||||||
|
FOOT_IPC_SIGUSR,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct client_ipc_hdr {
|
||||||
|
enum client_ipc_code ipc_code;
|
||||||
|
uint8_t size;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
|
||||||
|
struct client_ipc_sigusr {
|
||||||
|
int signo;
|
||||||
|
} __attribute__((packed));
|
||||||
604
client.c
Normal file
604
client.c
Normal file
|
|
@ -0,0 +1,604 @@
|
||||||
|
#include <errno.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <tllist.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "foot-client"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "client-protocol.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "foot-features.h"
|
||||||
|
#include "macros.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
extern char **environ;
|
||||||
|
|
||||||
|
struct string {
|
||||||
|
size_t len;
|
||||||
|
char *str;
|
||||||
|
};
|
||||||
|
typedef tll(struct string) string_list_t;
|
||||||
|
|
||||||
|
static volatile sig_atomic_t aborted = 0;
|
||||||
|
static volatile sig_atomic_t sigusr = 0;
|
||||||
|
|
||||||
|
static void
|
||||||
|
sigint_handler(int signo)
|
||||||
|
{
|
||||||
|
aborted = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sigusr_handler(int signo)
|
||||||
|
{
|
||||||
|
sigusr = signo;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
sendall(int sock, const void *_buf, size_t len)
|
||||||
|
{
|
||||||
|
const uint8_t *buf = _buf;
|
||||||
|
size_t left = len;
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
ssize_t r = send(sock, buf, left, MSG_NOSIGNAL);
|
||||||
|
if (r < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf += r;
|
||||||
|
left -= r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_usage(const char *prog_name)
|
||||||
|
{
|
||||||
|
static const char options[] =
|
||||||
|
"\nOptions:\n"
|
||||||
|
" -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n"
|
||||||
|
" -T,--title=TITLE initial window title (foot)\n"
|
||||||
|
" -a,--app-id=ID window application ID (foot)\n"
|
||||||
|
" --toplevel-tag=TAG set a custom toplevel tag\n"
|
||||||
|
" -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n"
|
||||||
|
" -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n"
|
||||||
|
" -m,--maximized start in maximized mode\n"
|
||||||
|
" -F,--fullscreen start in fullscreen mode\n"
|
||||||
|
" -L,--login-shell start shell as a login shell\n"
|
||||||
|
" -D,--working-directory=DIR directory to start in (CWD)\n"
|
||||||
|
" -s,--server-socket=PATH path to the server UNIX domain socket (default=$XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)\n"
|
||||||
|
" -H,--hold remain open after child process exits\n"
|
||||||
|
" -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 (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";
|
||||||
|
|
||||||
|
printf("Usage: %s [OPTIONS...]\n", prog_name);
|
||||||
|
printf("Usage: %s [OPTIONS...] command [ARGS...]\n", prog_name);
|
||||||
|
puts(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool NOINLINE
|
||||||
|
push_string(string_list_t *string_list, const char *s, uint64_t *total_len)
|
||||||
|
{
|
||||||
|
size_t len = strlen(s) + 1;
|
||||||
|
if (len >= 1 << (8 * sizeof(uint16_t))) {
|
||||||
|
LOG_ERR("string length overflow");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct string o = {len, xstrdup(s)};
|
||||||
|
tll_push_back(*string_list, o);
|
||||||
|
*total_len += sizeof(struct client_string) + o.len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
free_string_list(string_list_t *string_list)
|
||||||
|
{
|
||||||
|
tll_foreach(*string_list, it) {
|
||||||
|
free(it->item.str);
|
||||||
|
tll_remove(*string_list, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
send_string_list(int fd, const string_list_t *string_list)
|
||||||
|
{
|
||||||
|
tll_foreach(*string_list, it) {
|
||||||
|
const struct client_string s = {it->item.len};
|
||||||
|
if (sendall(fd, &s, sizeof(s)) < 0 ||
|
||||||
|
sendall(fd, it->item.str, s.len) < 0)
|
||||||
|
{
|
||||||
|
LOG_ERRNO("failed to send setup packet to server");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
TOPLEVEL_TAG_OPTION = CHAR_MAX + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char *const *argv)
|
||||||
|
{
|
||||||
|
/* Custom exit code, to enable users to differentiate between foot
|
||||||
|
* itself failing, and the client application failing */
|
||||||
|
static const int foot_exit_failure = -36;
|
||||||
|
int ret = foot_exit_failure;
|
||||||
|
|
||||||
|
const char *const prog_name = argc > 0 ? argv[0] : "<nullptr>";
|
||||||
|
|
||||||
|
static const struct option longopts[] = {
|
||||||
|
{"term", required_argument, NULL, 't'},
|
||||||
|
{"title", required_argument, NULL, 'T'},
|
||||||
|
{"app-id", required_argument, NULL, 'a'},
|
||||||
|
{"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION},
|
||||||
|
{"window-size-pixels", required_argument, NULL, 'w'},
|
||||||
|
{"window-size-chars", required_argument, NULL, 'W'},
|
||||||
|
{"maximized", no_argument, NULL, 'm'},
|
||||||
|
{"fullscreen", no_argument, NULL, 'F'},
|
||||||
|
{"login-shell", no_argument, NULL, 'L'},
|
||||||
|
{"working-directory", required_argument, NULL, 'D'},
|
||||||
|
{"server-socket", required_argument, NULL, 's'},
|
||||||
|
{"hold", no_argument, NULL, 'H'},
|
||||||
|
{"no-wait", no_argument, NULL, 'N'},
|
||||||
|
{"override", required_argument, NULL, 'o'},
|
||||||
|
{"client-environment", no_argument, NULL, 'E'},
|
||||||
|
{"log-level", required_argument, NULL, 'd'},
|
||||||
|
{"log-colorize", optional_argument, NULL, 'l'},
|
||||||
|
{"version", no_argument, NULL, 'v'},
|
||||||
|
{"help", no_argument, NULL, 'h'},
|
||||||
|
{NULL, no_argument, NULL, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *custom_cwd = NULL;
|
||||||
|
const char *server_socket_path = NULL;
|
||||||
|
enum log_class log_level = LOG_CLASS_WARNING;
|
||||||
|
enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
|
||||||
|
bool hold = false;
|
||||||
|
bool client_environment = false;
|
||||||
|
|
||||||
|
/* Used to format overrides */
|
||||||
|
bool no_wait = false;
|
||||||
|
|
||||||
|
/* For XDG activation */
|
||||||
|
const char *token = getenv("XDG_ACTIVATION_TOKEN");
|
||||||
|
bool xdga_token = token != NULL;
|
||||||
|
size_t token_len = xdga_token ? strlen(token) + 1 : 0;
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
|
||||||
|
/* Total packet length, not (yet) including overrides or argv[] */
|
||||||
|
uint64_t total_len = 0;
|
||||||
|
|
||||||
|
/* malloc:ed and needs to be in scope of all goto's */
|
||||||
|
int fd = -1;
|
||||||
|
char *_cwd = NULL;
|
||||||
|
struct client_string *cargv = NULL;
|
||||||
|
string_list_t overrides = tll_init();
|
||||||
|
string_list_t envp = tll_init();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int c = getopt_long(argc, argv, "+t:T:a:w:W:mFLD:s:HNo:Ed:l::veh", longopts, NULL);
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case 't':
|
||||||
|
snprintf(buf, sizeof(buf), "term=%s", optarg);
|
||||||
|
if (!push_string(&overrides, buf, &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'T':
|
||||||
|
snprintf(buf, sizeof(buf), "title=%s", optarg);
|
||||||
|
if (!push_string(&overrides, buf, &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
snprintf(buf, sizeof(buf), "app-id=%s", optarg);
|
||||||
|
if (!push_string(&overrides, buf, &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOPLEVEL_TAG_OPTION:
|
||||||
|
snprintf(buf, sizeof(buf), "toplevel-tag=%s", optarg);
|
||||||
|
if (!push_string(&overrides, buf, &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'L':
|
||||||
|
if (!push_string(&overrides, "login-shell=yes", &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D': {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(optarg, &st) < 0 || !(st.st_mode & S_IFDIR)) {
|
||||||
|
fprintf(stderr, "error: %s: not a directory\n", optarg);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
custom_cwd = optarg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'w': {
|
||||||
|
unsigned width, height;
|
||||||
|
if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
|
||||||
|
fprintf(stderr, "error: invalid window-size-pixels: %s\n", optarg);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(buf, sizeof(buf), "initial-window-size-pixels=%ux%u", width, height);
|
||||||
|
if (!push_string(&overrides, buf, &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'W': {
|
||||||
|
unsigned width, height;
|
||||||
|
if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
|
||||||
|
fprintf(stderr, "error: invalid window-size-chars: %s\n", optarg);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(buf, sizeof(buf), "initial-window-size-chars=%ux%u", width, height);
|
||||||
|
if (!push_string(&overrides, buf, &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
if (!push_string(&overrides, "initial-window-mode=maximized", &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'F':
|
||||||
|
if (!push_string(&overrides, "initial-window-mode=fullscreen", &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
server_socket_path = optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'H':
|
||||||
|
hold = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'N':
|
||||||
|
no_wait = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
if (!push_string(&overrides, optarg, &total_len))
|
||||||
|
goto err;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
client_environment = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd': {
|
||||||
|
int lvl = log_level_from_string(optarg);
|
||||||
|
if (unlikely(lvl < 0)) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"-d,--log-level: %s: argument must be one of %s\n",
|
||||||
|
optarg,
|
||||||
|
log_level_string_hint());
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
log_level = lvl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
if (optarg == NULL || streq(optarg, "auto"))
|
||||||
|
log_colorize = LOG_COLORIZE_AUTO;
|
||||||
|
else if (streq(optarg, "never"))
|
||||||
|
log_colorize = LOG_COLORIZE_NEVER;
|
||||||
|
else if (streq(optarg, "always"))
|
||||||
|
log_colorize = LOG_COLORIZE_ALWAYS;
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
print_version_and_features("footclient ");
|
||||||
|
ret = EXIT_SUCCESS;
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
print_usage(prog_name);
|
||||||
|
ret = EXIT_SUCCESS;
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '?':
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc > 0) {
|
||||||
|
argc -= optind;
|
||||||
|
argv += optind;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_init(log_colorize, false, LOG_FACILITY_USER, log_level);
|
||||||
|
|
||||||
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (fd == -1) {
|
||||||
|
LOG_ERRNO("failed to create socket");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
||||||
|
|
||||||
|
if (server_socket_path != NULL) {
|
||||||
|
strncpy(addr.sun_path, server_socket_path, sizeof(addr.sun_path) - 1);
|
||||||
|
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||||
|
LOG_ERR("%s: failed to connect (is 'foot --server' running?)", server_socket_path);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bool connected = false;
|
||||||
|
|
||||||
|
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
|
||||||
|
if (xdg_runtime != NULL) {
|
||||||
|
const char *wayland_display = getenv("WAYLAND_DISPLAY");
|
||||||
|
if (wayland_display != NULL) {
|
||||||
|
snprintf(addr.sun_path, sizeof(addr.sun_path),
|
||||||
|
"%s/foot-%s.sock", xdg_runtime, wayland_display);
|
||||||
|
connected = (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0);
|
||||||
|
}
|
||||||
|
if (!connected) {
|
||||||
|
LOG_WARN("%s: failed to connect, will now try %s/foot.sock",
|
||||||
|
addr.sun_path, xdg_runtime);
|
||||||
|
snprintf(addr.sun_path, sizeof(addr.sun_path),
|
||||||
|
"%s/foot.sock", xdg_runtime);
|
||||||
|
connected = (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0);
|
||||||
|
}
|
||||||
|
if (!connected)
|
||||||
|
LOG_WARN("%s: failed to connect, will now try /tmp/foot.sock", addr.sun_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connected) {
|
||||||
|
strncpy(addr.sun_path, "/tmp/foot.sock", sizeof(addr.sun_path) - 1);
|
||||||
|
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||||
|
LOG_ERRNO("failed to connect (is 'foot --server' running?)");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *cwd = custom_cwd;
|
||||||
|
if (cwd == NULL) {
|
||||||
|
size_t buf_len = 1024;
|
||||||
|
do {
|
||||||
|
_cwd = xrealloc(_cwd, buf_len);
|
||||||
|
errno = 0;
|
||||||
|
if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) {
|
||||||
|
LOG_ERRNO("failed to get current working directory");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
buf_len *= 2;
|
||||||
|
} while (errno == ERANGE);
|
||||||
|
cwd = _cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *pwd = getenv("PWD");
|
||||||
|
if (pwd != NULL) {
|
||||||
|
char *resolved_path_cwd = realpath(cwd, NULL);
|
||||||
|
char *resolved_path_pwd = realpath(pwd, NULL);
|
||||||
|
|
||||||
|
if (resolved_path_cwd != NULL &&
|
||||||
|
resolved_path_pwd != NULL &&
|
||||||
|
streq(resolved_path_cwd, resolved_path_pwd))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The resolved path of $PWD matches the resolved path of
|
||||||
|
* the *actual* working directory - use $PWD.
|
||||||
|
*
|
||||||
|
* This makes a difference when $PWD refers to a symlink.
|
||||||
|
*/
|
||||||
|
cwd = pwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(resolved_path_cwd);
|
||||||
|
free(resolved_path_pwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_environment) {
|
||||||
|
for (char **e = environ; *e != NULL; e++) {
|
||||||
|
if (!push_string(&envp, *e, &total_len))
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* String lengths, including NULL terminator */
|
||||||
|
const size_t cwd_len = strlen(cwd) + 1;
|
||||||
|
const size_t override_count = tll_length(overrides);
|
||||||
|
const size_t env_count = tll_length(envp);
|
||||||
|
|
||||||
|
const struct client_data data = {
|
||||||
|
.hold = hold,
|
||||||
|
.no_wait = no_wait,
|
||||||
|
.xdga_token = xdga_token,
|
||||||
|
.token_len = token_len,
|
||||||
|
.cwd_len = cwd_len,
|
||||||
|
.override_count = override_count,
|
||||||
|
.argc = argc,
|
||||||
|
.env_count = env_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Total packet length, not (yet) including argv[] */
|
||||||
|
total_len += sizeof(data) + cwd_len + token_len;
|
||||||
|
|
||||||
|
/* Add argv[] size to total packet length */
|
||||||
|
cargv = xmalloc(argc * sizeof(cargv[0]));
|
||||||
|
for (size_t i = 0; i < argc; i++) {
|
||||||
|
const size_t arg_len = strlen(argv[i]) + 1;
|
||||||
|
|
||||||
|
if (arg_len >= 1 << (8 * sizeof(cargv[i].len))) {
|
||||||
|
LOG_ERR("argv length overflow");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
cargv[i].len = arg_len;
|
||||||
|
total_len += sizeof(cargv[i]) + cargv[i].len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for size overflows */
|
||||||
|
if (total_len >= 1llu << (8 * sizeof(uint32_t)) ||
|
||||||
|
cwd_len >= 1 << (8 * sizeof(data.cwd_len)) ||
|
||||||
|
token_len >= 1 << (8 * sizeof(data.token_len)) ||
|
||||||
|
override_count > (size_t)(unsigned int)data.override_count ||
|
||||||
|
argc > (int)(unsigned int)data.argc ||
|
||||||
|
env_count > (size_t)(unsigned int)data.env_count)
|
||||||
|
{
|
||||||
|
LOG_ERR("size overflow");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send everything except argv[] */
|
||||||
|
if (sendall(fd, &(uint32_t){total_len}, sizeof(uint32_t)) < 0 ||
|
||||||
|
sendall(fd, &data, sizeof(data)) < 0 ||
|
||||||
|
sendall(fd, cwd, cwd_len) < 0)
|
||||||
|
{
|
||||||
|
LOG_ERRNO("failed to send setup packet to server");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send XDGA token, if we have one */
|
||||||
|
if (xdga_token) {
|
||||||
|
if (sendall(fd, token, token_len) != token_len)
|
||||||
|
{
|
||||||
|
LOG_ERRNO("failed to send xdg activation token to server");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send overrides */
|
||||||
|
if (!send_string_list(fd, &overrides))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
/* Send argv[] */
|
||||||
|
for (size_t i = 0; i < argc; i++) {
|
||||||
|
if (sendall(fd, &cargv[i], sizeof(cargv[i])) < 0 ||
|
||||||
|
sendall(fd, argv[i], cargv[i].len) < 0)
|
||||||
|
{
|
||||||
|
LOG_ERRNO("failed to send setup packet (argv) to server");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send environment */
|
||||||
|
if (!send_string_list(fd, &envp))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
struct sigaction sa_int = {.sa_handler = &sigint_handler};
|
||||||
|
struct sigaction sa_usr = {.sa_handler = &sigusr_handler};
|
||||||
|
sigemptyset(&sa_int.sa_mask);
|
||||||
|
sigemptyset(&sa_usr.sa_mask);
|
||||||
|
|
||||||
|
if (sigaction(SIGINT, &sa_int, NULL) < 0 ||
|
||||||
|
sigaction(SIGTERM, &sa_int, NULL) < 0 ||
|
||||||
|
sigaction(SIGUSR1, &sa_usr, NULL) < 0 ||
|
||||||
|
sigaction(SIGUSR2, &sa_usr, NULL) < 0)
|
||||||
|
{
|
||||||
|
LOG_ERRNO("failed to register signal handlers");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exit_code;
|
||||||
|
ssize_t rcvd = -1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
|
||||||
|
|
||||||
|
const int got_sigusr = sigusr;
|
||||||
|
sigusr = 0;
|
||||||
|
|
||||||
|
if (rcvd < 0 && errno == EINTR) {
|
||||||
|
if (aborted)
|
||||||
|
break;
|
||||||
|
else if (got_sigusr != 0) {
|
||||||
|
LOG_DBG("sending sigusr %d to server", got_sigusr);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct client_ipc_hdr hdr;
|
||||||
|
struct client_ipc_sigusr sigusr;
|
||||||
|
} ipc = {
|
||||||
|
.hdr = {
|
||||||
|
.ipc_code = FOOT_IPC_SIGUSR,
|
||||||
|
.size = sizeof(struct client_ipc_sigusr),
|
||||||
|
},
|
||||||
|
.sigusr = {
|
||||||
|
.signo = got_sigusr,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ssize_t count = send(fd, &ipc, sizeof(ipc), 0);
|
||||||
|
if (count < 0) {
|
||||||
|
LOG_ERRNO("failed to send SIGUSR IPC to server");
|
||||||
|
goto err;
|
||||||
|
} else if ((size_t)count != sizeof(ipc)) {
|
||||||
|
LOG_ERR("failed to send SIGUSR IPC to server");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rcvd == -1 && errno == EINTR)
|
||||||
|
xassert(aborted);
|
||||||
|
else if (rcvd != sizeof(exit_code))
|
||||||
|
LOG_ERRNO("failed to read server response");
|
||||||
|
else
|
||||||
|
ret = exit_code;
|
||||||
|
|
||||||
|
err:
|
||||||
|
free_string_list(&envp);
|
||||||
|
free_string_list(&overrides);
|
||||||
|
free(cargv);
|
||||||
|
free(_cwd);
|
||||||
|
if (fd != -1)
|
||||||
|
close(fd);
|
||||||
|
log_deinit();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
115
commands.c
Normal file
115
commands.c
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
#include "commands.h"
|
||||||
|
|
||||||
|
#define LOG_MODULE "commands"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "grid.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "selection.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
#include "url-mode.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
cmd_scrollback_up(struct terminal *term, int rows)
|
||||||
|
{
|
||||||
|
if (term->grid == &term->alt)
|
||||||
|
return;
|
||||||
|
if (urls_mode_is_active(term))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const struct grid *grid = term->grid;
|
||||||
|
const int view = grid->view;
|
||||||
|
const int grid_rows = grid->num_rows;
|
||||||
|
|
||||||
|
/* The view row number in scrollback relative coordinates. This is
|
||||||
|
* the maximum number of rows we're allowed to scroll */
|
||||||
|
int sb_start = grid_sb_start_ignore_uninitialized(grid, term->rows);
|
||||||
|
int view_sb_rel =
|
||||||
|
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, view);
|
||||||
|
|
||||||
|
rows = min(rows, view_sb_rel);
|
||||||
|
if (rows == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int new_view = (view + grid_rows) - rows;
|
||||||
|
new_view &= grid_rows - 1;
|
||||||
|
|
||||||
|
xassert(new_view != view);
|
||||||
|
xassert(grid->rows[new_view] != NULL);
|
||||||
|
#if defined(_DEBUG)
|
||||||
|
for (int r = 0; r < term->rows; r++)
|
||||||
|
xassert(grid->rows[(new_view + r) & (grid->num_rows - 1)] != NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOG_DBG("scrollback UP: %d -> %d (offset = %d, rows = %d)",
|
||||||
|
view, new_view, offset, grid_rows);
|
||||||
|
|
||||||
|
selection_view_up(term, new_view);
|
||||||
|
term->grid->view = new_view;
|
||||||
|
|
||||||
|
if (rows < term->rows) {
|
||||||
|
term_damage_scroll(
|
||||||
|
term, DAMAGE_SCROLL_REVERSE_IN_VIEW,
|
||||||
|
(struct scroll_region){0, term->rows}, rows);
|
||||||
|
term_damage_rows_in_view(term, 0, rows - 1);
|
||||||
|
} else
|
||||||
|
term_damage_view(term);
|
||||||
|
|
||||||
|
render_refresh_urls(term);
|
||||||
|
render_refresh(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cmd_scrollback_down(struct terminal *term, int rows)
|
||||||
|
{
|
||||||
|
if (term->grid == &term->alt)
|
||||||
|
return;
|
||||||
|
if (urls_mode_is_active(term))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const struct grid *grid = term->grid;
|
||||||
|
const int offset = grid->offset;
|
||||||
|
const int view = grid->view;
|
||||||
|
const int grid_rows = grid->num_rows;
|
||||||
|
const int screen_rows = term->rows;
|
||||||
|
|
||||||
|
const int scrollback_end = offset;
|
||||||
|
|
||||||
|
/* Number of rows to scroll, without going past the scrollback end */
|
||||||
|
int max_rows = 0;
|
||||||
|
if (view <= scrollback_end)
|
||||||
|
max_rows = scrollback_end - view;
|
||||||
|
else
|
||||||
|
max_rows = offset + (grid_rows - view);
|
||||||
|
|
||||||
|
rows = min(rows, max_rows);
|
||||||
|
if (rows == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int new_view = (view + rows) & (grid_rows - 1);
|
||||||
|
|
||||||
|
xassert(new_view != view);
|
||||||
|
xassert(grid->rows[new_view] != NULL);
|
||||||
|
#if defined(_DEBUG)
|
||||||
|
for (int r = 0; r < term->rows; r++)
|
||||||
|
xassert(grid->rows[(new_view + r) & (grid_rows - 1)] != NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOG_DBG("scrollback DOWN: %d -> %d (offset = %d, rows = %d)",
|
||||||
|
view, new_view, offset, grid_rows);
|
||||||
|
|
||||||
|
selection_view_down(term, new_view);
|
||||||
|
term->grid->view = new_view;
|
||||||
|
|
||||||
|
if (rows < term->rows) {
|
||||||
|
term_damage_scroll(
|
||||||
|
term, DAMAGE_SCROLL_IN_VIEW,
|
||||||
|
(struct scroll_region){0, term->rows}, rows);
|
||||||
|
term_damage_rows_in_view(term, term->rows - rows, screen_rows - 1);
|
||||||
|
} else
|
||||||
|
term_damage_view(term);
|
||||||
|
|
||||||
|
render_refresh_urls(term);
|
||||||
|
render_refresh(term);
|
||||||
|
}
|
||||||
6
commands.h
Normal file
6
commands.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
void cmd_scrollback_up(struct terminal *term, int rows);
|
||||||
|
void cmd_scrollback_down(struct terminal *term, int rows);
|
||||||
90
completions/bash/foot
Normal file
90
completions/bash/foot
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Bash completion script for foot
|
||||||
|
_foot()
|
||||||
|
{
|
||||||
|
COMPREPLY=()
|
||||||
|
|
||||||
|
local cur prev flags word commands match previous_words i offset
|
||||||
|
flags=(
|
||||||
|
"--app-id"
|
||||||
|
"--toplevel-tag"
|
||||||
|
"--check-config"
|
||||||
|
"--config"
|
||||||
|
"--font"
|
||||||
|
"--fullscreen"
|
||||||
|
"--help"
|
||||||
|
"--hold"
|
||||||
|
"--log-colorize"
|
||||||
|
"--log-level"
|
||||||
|
"--log-no-syslog"
|
||||||
|
"--login-shell"
|
||||||
|
"--maximized"
|
||||||
|
"--override"
|
||||||
|
"--print-pid"
|
||||||
|
"--pty"
|
||||||
|
"--server"
|
||||||
|
"--term"
|
||||||
|
"--title"
|
||||||
|
"--version"
|
||||||
|
"--window-size-pixels"
|
||||||
|
"--window-size-chars"
|
||||||
|
"--working-directory"
|
||||||
|
)
|
||||||
|
flags="${flags[@]}"
|
||||||
|
cur=${COMP_WORDS[COMP_CWORD]}
|
||||||
|
prev=${COMP_WORDS[COMP_CWORD-1]}
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
i=0
|
||||||
|
for word in "${previous_words[@]}" ; do
|
||||||
|
match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null)
|
||||||
|
if [[ ! -z "$match" ]] ; then
|
||||||
|
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--toplevel-tag|--config|--font|--log-level|--pty|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
|
||||||
|
(( i++ ))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Positional argument found
|
||||||
|
offset=$i
|
||||||
|
fi
|
||||||
|
(( i++ ))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -z "$offset" ]] ; then
|
||||||
|
# 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}) )
|
||||||
|
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|--toplevel-tag|--help|--override|--pty|--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
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _foot foot
|
||||||
82
completions/bash/footclient
Normal file
82
completions/bash/footclient
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
# Bash completion script for footclient
|
||||||
|
_footclient()
|
||||||
|
{
|
||||||
|
COMPREPLY=()
|
||||||
|
|
||||||
|
local cur prev flags word commands match previous_words i offset
|
||||||
|
flags=(
|
||||||
|
"--app-id"
|
||||||
|
"--toplevel-tag"
|
||||||
|
"--fullscreen"
|
||||||
|
"--help"
|
||||||
|
"--hold"
|
||||||
|
"--login-shell"
|
||||||
|
"--log-level"
|
||||||
|
"--log-colorize"
|
||||||
|
"--maximized"
|
||||||
|
"--override"
|
||||||
|
"--client-environment"
|
||||||
|
"--server-socket"
|
||||||
|
"--term"
|
||||||
|
"--title"
|
||||||
|
"--version"
|
||||||
|
"--window-size-pixels"
|
||||||
|
"--window-size-chars"
|
||||||
|
"--working-directory"
|
||||||
|
)
|
||||||
|
flags="${flags[@]}"
|
||||||
|
cur=${COMP_WORDS[COMP_CWORD]}
|
||||||
|
prev=${COMP_WORDS[COMP_CWORD-1]}
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
i=0
|
||||||
|
for word in "${previous_words[@]}" ; do
|
||||||
|
match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null)
|
||||||
|
if [[ ! -z "$match" ]] ; then
|
||||||
|
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--toplevel-tag|--log-level|--server-socket|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
|
||||||
|
(( i++ ))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Positional argument found
|
||||||
|
offset=$i
|
||||||
|
fi
|
||||||
|
(( i++ ))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -z "$offset" ]] ; then
|
||||||
|
# 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}) )
|
||||||
|
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|--toplevel-tag|--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
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _footclient footclient
|
||||||
24
completions/fish/foot.fish
Normal file
24
completions/fish/foot.fish
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
complete -c foot -x -a "(__fish_complete_subcommand)"
|
||||||
|
complete -c foot -r -s c -l config -d "path to configuration file (XDG_CONFIG_HOME/foot/foot.ini)"
|
||||||
|
complete -c foot -s C -l check-config -d "verify configuration and exit with 0 if ok, otherwise exit with 1"
|
||||||
|
complete -c foot -x -s o -l override -d "configuration option to override, in form SECTION.KEY=VALUE"
|
||||||
|
complete -c foot -x -s f -l font -a "(fc-list : family | sed 's/,/\n/g' | sort | uniq)" -d "font name and style in fontconfig format (monospace)"
|
||||||
|
complete -c foot -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)"
|
||||||
|
complete -c foot -x -s T -l title -d "initial window title"
|
||||||
|
complete -c foot -x -s a -l app-id -d "value to set the app-id property on the Wayland window to (foot)"
|
||||||
|
complete -c foot -x -l toplevel-tag -d "value to set the toplevel-tag property on the Wayland window to"
|
||||||
|
complete -c foot -s m -l maximized -d "start in maximized mode"
|
||||||
|
complete -c foot -s F -l fullscreen -d "start in fullscreen mode"
|
||||||
|
complete -c foot -s L -l login-shell -d "start shell as a login shell"
|
||||||
|
complete -c foot -F -s D -l working-directory -d "initial working directory for the client application (CWD)"
|
||||||
|
complete -c foot -x -s w -l window-size-pixels -d "window WIDTHxHEIGHT, in pixels (700x500)"
|
||||||
|
complete -c foot -x -s W -l window-size-chars -d "window WIDTHxHEIGHT, in characters (not set)"
|
||||||
|
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 (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 -r -l pty -d "display an existing pty instead of creating one"
|
||||||
|
complete -c foot -s v -l version -d "show the version number and quit"
|
||||||
|
complete -c foot -s h -l help -d "show help message and quit"
|
||||||
20
completions/fish/footclient.fish
Normal file
20
completions/fish/footclient.fish
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
complete -c footclient -x -a "(__fish_complete_subcommand)"
|
||||||
|
complete -c footclient -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)"
|
||||||
|
complete -c footclient -x -s T -l title -d "initial window title"
|
||||||
|
complete -c footclient -x -s a -l app-id -d "value to set the app-id property on the Wayland window to (foot)"
|
||||||
|
complete -c footclient -x -l toplevel-tag -d "value to set the toplevel-tag property on the Wayland window to"
|
||||||
|
complete -c footclient -s m -l maximized -d "start in maximized mode"
|
||||||
|
complete -c footclient -s F -l fullscreen -d "start in fullscreen mode"
|
||||||
|
complete -c footclient -s L -l login-shell -d "start shell as a login shell"
|
||||||
|
complete -c footclient -F -s D -l working-directory -d "initial working directory for the client application (CWD)"
|
||||||
|
complete -c footclient -x -s w -l window-size-pixels -d "window WIDTHxHEIGHT, in pixels (700x500)"
|
||||||
|
complete -c footclient -x -s W -l window-size-chars -d "window WIDTHxHEIGHT, in characters (not set)"
|
||||||
|
complete -c footclient -F -s s -l server-socket -d "override the default path to the foot server socket ($XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)"
|
||||||
|
complete -c footclient -s H -l hold -d "remain open after child process exits"
|
||||||
|
complete -c footclient -s N -l no-wait -d "detach the client process from the running terminal, exiting immediately"
|
||||||
|
complete -c footclient -x -s o -l override -d "configuration option to override, in form SECTION.KEY=VALUE"
|
||||||
|
complete -c footclient -s E -l client-environment -d "child process inherits footclient's environment, instead of the server's"
|
||||||
|
complete -c footclient -x -s d -l log-level -a "info warning error none" -d "log-level (info)"
|
||||||
|
complete -c footclient -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr"
|
||||||
|
complete -c footclient -s v -l version -d "show the version number and quit"
|
||||||
|
complete -c footclient -s h -l help -d "show help message and quit"
|
||||||
9
completions/meson.build
Normal file
9
completions/meson.build
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
zsh_install_dir = join_paths(get_option('datadir'), 'zsh', 'site-functions')
|
||||||
|
fish_install_dir = join_paths(get_option('datadir'), 'fish', 'vendor_completions.d')
|
||||||
|
bash_install_dir = join_paths(get_option('datadir'), 'bash-completion', 'completions')
|
||||||
|
install_data('zsh/_foot', install_dir: zsh_install_dir)
|
||||||
|
install_data('zsh/_footclient', install_dir: zsh_install_dir)
|
||||||
|
install_data('fish/foot.fish', install_dir: fish_install_dir)
|
||||||
|
install_data('fish/footclient.fish', install_dir: fish_install_dir)
|
||||||
|
install_data('bash/foot', install_dir: bash_install_dir)
|
||||||
|
install_data('bash/footclient', install_dir: bash_install_dir)
|
||||||
41
completions/zsh/_foot
Normal file
41
completions/zsh/_foot
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
#compdef foot
|
||||||
|
|
||||||
|
_arguments \
|
||||||
|
-s -S -C \
|
||||||
|
'(-c --config)'{-c,--config}'[path to configuration file (XDG_CONFIG_HOME/foot/foot.ini)]:config:_files' \
|
||||||
|
'(-C --check-config)'{-C,--check-config}'[verify configuration and exit with 0 if ok, otherwise exit with 1]' \
|
||||||
|
'(-o --override)'{-o,--override}'[configuration option to override, in form SECTION.KEY=VALUE]:()' \
|
||||||
|
'(-f --font)'{-f,--font}'[font name and style in fontconfig format (monospace)]:font:->fonts' \
|
||||||
|
'(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
|
||||||
|
'(-T --title)'{-T,--title}'[initial window title]:()' \
|
||||||
|
'(-a --app-id)'{-a,--app-id}'[value to set the app-id property on the Wayland window to (foot)]:()' \
|
||||||
|
'--toplevel-tag=[value to set the toplevel-tag property on the Wayland window to]:()' \
|
||||||
|
'(-m --maximized)'{-m,--maximized}'[start in maximized mode]' \
|
||||||
|
'(-F --fullscreen)'{-F,--fullscreen}'[start in fullscreen mode]' \
|
||||||
|
'(-L --login-shell)'{-L,--login-shell}'[start shell as a login shell]' \
|
||||||
|
'(-D --working-directory)'{-D,--working-directory}'[initial working directory for the client application (CWD)]:working_directory:_files' \
|
||||||
|
'(-w --window-size-pixels)'{-w,--window-size-pixels}'[window WIDTHxHEIGHT, in pixels (700x500)]:size_pixels:()' \
|
||||||
|
'(-W --window-size-chars)'{-W,--window-size-chars}'[window WIDTHxHEIGHT, in characters (not set)]:size_chars:()' \
|
||||||
|
'(-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' \
|
||||||
|
'--pty=[display an existing pty instead of creating one]:pty:_files' \
|
||||||
|
'(-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]' \
|
||||||
|
'(-h --help)'{-h,--help}'[show help message and quit]' \
|
||||||
|
':command: _command_names -e' \
|
||||||
|
'*::command arguments: _dispatch ${words[1]} ${words[1]}'
|
||||||
|
|
||||||
|
case ${state} in
|
||||||
|
fonts)
|
||||||
|
IFS=$'\n'
|
||||||
|
_values -s , 'font families' $(fc-list : family | sed 's/,/\n/g' | sort | uniq)
|
||||||
|
unset IFS
|
||||||
|
;;
|
||||||
|
|
||||||
|
terms)
|
||||||
|
_values 'terminal definitions' /usr/share/terminfo/**/*(.:t)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
31
completions/zsh/_footclient
Normal file
31
completions/zsh/_footclient
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#compdef footclient
|
||||||
|
|
||||||
|
_arguments \
|
||||||
|
-s -S -C \
|
||||||
|
'(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
|
||||||
|
'(-T --title)'{-T,--title}'[initial window title]:()' \
|
||||||
|
'(-a --app-id)'{-a,--app-id}'[value to set the app-id property on the Wayland window to (foot)]:()' \
|
||||||
|
'--toplevel-tag=[value to set the toplevel-tag property on the Wayland window to]:()' \
|
||||||
|
'(-m --maximized)'{-m,--maximized}'[start in maximized mode]' \
|
||||||
|
'(-F --fullscreen)'{-F,--fullscreen}'[start in fullscreen mode]' \
|
||||||
|
'(-L --login-shell)'{-L,--login-shell}'[start shell as a login shell]' \
|
||||||
|
'(-D --working-directory)'{-D,--working-directory}'[initial working directory for the client application (CWD)]:working_directory:_files' \
|
||||||
|
'(-w --window-size-pixels)'{-w,--window-size-pixels}'[window WIDTHxHEIGHT, in pixels (700x500)]:size_pixels:()' \
|
||||||
|
'(-W --window-size-chars)'{-W,--window-size-chars}'[window WIDTHxHEIGHT, in characters (not set)]:size_chars:()' \
|
||||||
|
'(-s --server-socket)'{-s,--server-socket}'[override the default path to the foot server socket ($XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)]:server:_files' \
|
||||||
|
'(-H --hold)'{-H,--hold}'[remain open after child process exits]' \
|
||||||
|
'(-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 (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]' \
|
||||||
|
':command: _command_names -e' \
|
||||||
|
'*::command arguments: _dispatch ${words[1]} ${words[1]}'
|
||||||
|
|
||||||
|
case ${state} in
|
||||||
|
terms)
|
||||||
|
_values 'terminal definitions' /usr/share/terminfo/**/*(.:t)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
149
composed.c
Normal file
149
composed.c
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
#include "composed.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
composed_key_from_chars(const uint32_t chars[], size_t count)
|
||||||
|
{
|
||||||
|
if (count == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint32_t key = chars[0];
|
||||||
|
for (size_t i = 1; i < count; i++)
|
||||||
|
key = composed_key_from_key(key, chars[i]);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
composed_key_from_key(uint32_t prev_key, uint32_t next_char)
|
||||||
|
{
|
||||||
|
unsigned bits = 32 - __builtin_clz(CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO);
|
||||||
|
|
||||||
|
/* Rotate old key 8 bits */
|
||||||
|
uint32_t new_key = (prev_key << 8) | (prev_key >> (bits - 8));
|
||||||
|
|
||||||
|
/* xor with new char */
|
||||||
|
new_key ^= next_char;
|
||||||
|
|
||||||
|
/* Multiply with magic hash constant */
|
||||||
|
new_key *= 2654435761ul;
|
||||||
|
|
||||||
|
/* And mask, to ensure the new value is within range */
|
||||||
|
new_key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO;
|
||||||
|
return new_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
const char32_t chars[] = U"abcdef";
|
||||||
|
|
||||||
|
uint32_t k1 = composed_key_from_key(chars[0], chars[1]);
|
||||||
|
uint32_t k2 = composed_key_from_chars(chars, 2);
|
||||||
|
xassert(k1 == k2);
|
||||||
|
|
||||||
|
uint32_t k3 = composed_key_from_key(k2, chars[2]);
|
||||||
|
uint32_t k4 = composed_key_from_chars(chars, 3);
|
||||||
|
xassert(k3 == k4);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct composed *
|
||||||
|
composed_lookup(struct composed *root, uint32_t key)
|
||||||
|
{
|
||||||
|
struct composed *node = root;
|
||||||
|
|
||||||
|
while (node != NULL) {
|
||||||
|
if (key == node->key)
|
||||||
|
return node;
|
||||||
|
|
||||||
|
node = key < node->key ? node->left : node->right;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct composed *
|
||||||
|
composed_lookup_without_collision(struct composed *root, uint32_t *key,
|
||||||
|
const char32_t *prefix_text, size_t prefix_len,
|
||||||
|
char32_t wc, int forced_width)
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
const struct composed *cc = composed_lookup(root, *key);
|
||||||
|
if (cc == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
bool match = cc->count == prefix_len + 1 &&
|
||||||
|
cc->forced_width == forced_width &&
|
||||||
|
cc->chars[prefix_len] == wc;
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
for (size_t i = 0; i < prefix_len; i++) {
|
||||||
|
if (cc->chars[i] != prefix_text[i]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match)
|
||||||
|
return cc;
|
||||||
|
|
||||||
|
(*key)++;
|
||||||
|
*key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO;
|
||||||
|
|
||||||
|
/* TODO: this will loop infinitely if the composed table is full */
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
composed_insert(struct composed **root, struct composed *node)
|
||||||
|
{
|
||||||
|
node->left = node->right = NULL;
|
||||||
|
|
||||||
|
if (*root == NULL) {
|
||||||
|
*root = node;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t key = node->key;
|
||||||
|
|
||||||
|
struct composed *prev = NULL;
|
||||||
|
struct composed *n = *root;
|
||||||
|
|
||||||
|
while (n != NULL) {
|
||||||
|
xassert(n->key != node->key);
|
||||||
|
|
||||||
|
prev = n;
|
||||||
|
n = key < n->key ? n->left : n->right;
|
||||||
|
}
|
||||||
|
|
||||||
|
xassert(prev != NULL);
|
||||||
|
xassert(n == NULL);
|
||||||
|
|
||||||
|
if (key < prev->key) {
|
||||||
|
xassert(prev->left == NULL);
|
||||||
|
prev->left = node;
|
||||||
|
} else {
|
||||||
|
xassert(prev->right == NULL);
|
||||||
|
prev->right = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
composed_free(struct composed *root)
|
||||||
|
{
|
||||||
|
if (root == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
composed_free(root->left);
|
||||||
|
composed_free(root->right);
|
||||||
|
|
||||||
|
free(root->chars);
|
||||||
|
free(root);
|
||||||
|
}
|
||||||
25
composed.h
Normal file
25
composed.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <uchar.h>
|
||||||
|
|
||||||
|
struct composed {
|
||||||
|
char32_t *chars;
|
||||||
|
struct composed *left;
|
||||||
|
struct composed *right;
|
||||||
|
uint32_t key;
|
||||||
|
uint8_t count;
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t forced_width;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t composed_key_from_chars(const uint32_t chars[], size_t count);
|
||||||
|
uint32_t composed_key_from_key(uint32_t prev_key, uint32_t next_char);
|
||||||
|
|
||||||
|
const struct composed *composed_lookup(struct composed *root, uint32_t key);
|
||||||
|
const struct composed *composed_lookup_without_collision(
|
||||||
|
struct composed *root, uint32_t *key,
|
||||||
|
const char32_t *prefix, size_t prefix_len, char32_t wc, int forced_width);
|
||||||
|
void composed_insert(struct composed **root, struct composed *node);
|
||||||
|
|
||||||
|
void composed_free(struct composed *root);
|
||||||
523
config.h
Normal file
523
config.h
Normal file
|
|
@ -0,0 +1,523 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <regex.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <uchar.h>
|
||||||
|
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include <tllist.h>
|
||||||
|
#include <fcft/fcft.h>
|
||||||
|
|
||||||
|
#include "user-notification.h"
|
||||||
|
|
||||||
|
#define DEFINE_LIST(type) \
|
||||||
|
type##_list { \
|
||||||
|
size_t count; \
|
||||||
|
type *arr; \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If px != 0 then px is valid, otherwise pt is valid */
|
||||||
|
struct pt_or_px {
|
||||||
|
int16_t 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, CURSOR_HOLLOW };
|
||||||
|
enum cursor_unfocused_style {
|
||||||
|
CURSOR_UNFOCUSED_UNCHANGED,
|
||||||
|
CURSOR_UNFOCUSED_HOLLOW,
|
||||||
|
CURSOR_UNFOCUSED_NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum conf_size_type {CONF_SIZE_PX, CONF_SIZE_CELLS};
|
||||||
|
|
||||||
|
struct config_font {
|
||||||
|
char *pattern;
|
||||||
|
float pt_size;
|
||||||
|
int px_size;
|
||||||
|
};
|
||||||
|
DEFINE_LIST(struct config_font);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
struct config_key_modifiers {
|
||||||
|
bool shift;
|
||||||
|
bool alt;
|
||||||
|
bool ctrl;
|
||||||
|
bool super;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct argv {
|
||||||
|
char **args;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum binding_aux_type {
|
||||||
|
BINDING_AUX_NONE,
|
||||||
|
BINDING_AUX_PIPE,
|
||||||
|
BINDING_AUX_TEXT,
|
||||||
|
BINDING_AUX_REGEX,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct binding_aux {
|
||||||
|
enum binding_aux_type type;
|
||||||
|
bool master_copy;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct argv pipe;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint8_t *data;
|
||||||
|
size_t len;
|
||||||
|
} text;
|
||||||
|
|
||||||
|
char *regex_name;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum key_binding_type {
|
||||||
|
KEY_BINDING,
|
||||||
|
MOUSE_BINDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef tll(char *) config_modifier_list_t;
|
||||||
|
|
||||||
|
struct config_key_binding {
|
||||||
|
int action; /* One of the various bind_action_* enums from wayland.h */
|
||||||
|
//struct config_key_modifiers modifiers;
|
||||||
|
config_modifier_list_t modifiers;
|
||||||
|
union {
|
||||||
|
/* Key bindings */
|
||||||
|
struct {
|
||||||
|
xkb_keysym_t sym;
|
||||||
|
} k;
|
||||||
|
|
||||||
|
/* Mouse bindings */
|
||||||
|
struct {
|
||||||
|
int button;
|
||||||
|
int count;
|
||||||
|
} m;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct binding_aux aux;
|
||||||
|
|
||||||
|
/* For error messages in collision handling */
|
||||||
|
const char *path;
|
||||||
|
int lineno;
|
||||||
|
};
|
||||||
|
DEFINE_LIST(struct config_key_binding);
|
||||||
|
|
||||||
|
typedef tll(char *) config_override_t;
|
||||||
|
|
||||||
|
struct config_spawn_template {
|
||||||
|
struct argv argv;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct env_var {
|
||||||
|
char *name;
|
||||||
|
char *value;
|
||||||
|
};
|
||||||
|
typedef tll(struct env_var) env_var_list_t;
|
||||||
|
|
||||||
|
struct custom_regex {
|
||||||
|
char *name;
|
||||||
|
char *regex;
|
||||||
|
regex_t preg;
|
||||||
|
struct config_spawn_template launch;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct color_theme {
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
uint32_t flash;
|
||||||
|
uint32_t flash_alpha;
|
||||||
|
uint32_t table[256];
|
||||||
|
uint16_t alpha;
|
||||||
|
uint32_t selection_fg;
|
||||||
|
uint32_t selection_bg;
|
||||||
|
uint32_t url;
|
||||||
|
|
||||||
|
uint32_t dim[8];
|
||||||
|
uint32_t sixel[16];
|
||||||
|
|
||||||
|
enum {
|
||||||
|
DIM_BLEND_TOWARDS_BLACK,
|
||||||
|
DIM_BLEND_TOWARDS_WHITE,
|
||||||
|
} dim_blend_towards;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ALPHA_MODE_DEFAULT,
|
||||||
|
ALPHA_MODE_MATCHING,
|
||||||
|
ALPHA_MODE_ALL
|
||||||
|
} alpha_mode;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t text;
|
||||||
|
uint32_t cursor;
|
||||||
|
} cursor;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
} jump_label;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
} scrollback_indicator;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct {
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
} no_match;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
} match;
|
||||||
|
} search_box;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool cursor:1;
|
||||||
|
bool jump_label:1;
|
||||||
|
bool scrollback_indicator:1;
|
||||||
|
bool url:1;
|
||||||
|
bool search_box_no_match:1;
|
||||||
|
bool search_box_match:1;
|
||||||
|
uint8_t dim;
|
||||||
|
} use_custom;
|
||||||
|
|
||||||
|
bool blur;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum which_color_theme {
|
||||||
|
COLOR_THEME_DARK,
|
||||||
|
COLOR_THEME_LIGHT,
|
||||||
|
COLOR_THEME_1, /* Deprecated */
|
||||||
|
COLOR_THEME_2, /* Deprecated */
|
||||||
|
};
|
||||||
|
|
||||||
|
enum shm_bit_depth {
|
||||||
|
SHM_BITS_AUTO,
|
||||||
|
SHM_BITS_8,
|
||||||
|
SHM_BITS_10,
|
||||||
|
SHM_BITS_16,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum center_when {
|
||||||
|
CENTER_INVALID,
|
||||||
|
CENTER_NEVER,
|
||||||
|
CENTER_FULLSCREEN,
|
||||||
|
CENTER_MAXIMIZED_AND_FULLSCREEN,
|
||||||
|
CENTER_ALWAYS,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum underline_style {
|
||||||
|
UNDERLINE_NONE,
|
||||||
|
UNDERLINE_SINGLE, /* Legacy underline */
|
||||||
|
UNDERLINE_DOUBLE,
|
||||||
|
UNDERLINE_CURLY,
|
||||||
|
UNDERLINE_DOTTED,
|
||||||
|
UNDERLINE_DASHED,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct config {
|
||||||
|
char *conf_path;
|
||||||
|
char *term;
|
||||||
|
char *shell;
|
||||||
|
char *title;
|
||||||
|
char *app_id;
|
||||||
|
char *toplevel_tag;
|
||||||
|
char32_t *word_delimiters;
|
||||||
|
bool login_shell;
|
||||||
|
bool locked_title;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
enum conf_size_type type;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
} size;
|
||||||
|
|
||||||
|
unsigned pad_left;
|
||||||
|
unsigned pad_top;
|
||||||
|
unsigned pad_right;
|
||||||
|
unsigned pad_bottom;
|
||||||
|
enum center_when center_when;
|
||||||
|
|
||||||
|
bool resize_by_cells;
|
||||||
|
bool resize_keep_grid;
|
||||||
|
|
||||||
|
uint16_t resize_delay_ms;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
float amount;
|
||||||
|
} dim;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool enabled;
|
||||||
|
bool palette_based;
|
||||||
|
float amount;
|
||||||
|
} bold_in_bright;
|
||||||
|
|
||||||
|
enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode;
|
||||||
|
|
||||||
|
bool dpi_aware;
|
||||||
|
bool gamma_correct;
|
||||||
|
bool uppercase_regex_insert;
|
||||||
|
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;
|
||||||
|
struct pt_or_px letter_spacing;
|
||||||
|
|
||||||
|
/* Adjusted letter x/y offsets */
|
||||||
|
struct pt_or_px horizontal_letter_offset;
|
||||||
|
struct pt_or_px vertical_letter_offset;
|
||||||
|
|
||||||
|
bool use_custom_underline_offset;
|
||||||
|
struct pt_or_px underline_offset;
|
||||||
|
struct pt_or_px underline_thickness;
|
||||||
|
|
||||||
|
struct pt_or_px strikeout_thickness;
|
||||||
|
|
||||||
|
bool box_drawings_uses_font_glyphs;
|
||||||
|
bool can_shape_grapheme;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
enum {
|
||||||
|
OSC52_DISABLED,
|
||||||
|
OSC52_COPY_ENABLED,
|
||||||
|
OSC52_PASTE_ENABLED,
|
||||||
|
OSC52_ENABLED,
|
||||||
|
} osc52;
|
||||||
|
} security;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool urgent;
|
||||||
|
bool notify;
|
||||||
|
bool flash;
|
||||||
|
bool system_bell;
|
||||||
|
struct config_spawn_template command;
|
||||||
|
bool command_focused;
|
||||||
|
} bell;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t lines;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
enum {
|
||||||
|
SCROLLBACK_INDICATOR_POSITION_NONE,
|
||||||
|
SCROLLBACK_INDICATOR_POSITION_FIXED,
|
||||||
|
SCROLLBACK_INDICATOR_POSITION_RELATIVE
|
||||||
|
} position;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SCROLLBACK_INDICATOR_FORMAT_PERCENTAGE,
|
||||||
|
SCROLLBACK_INDICATOR_FORMAT_LINENO,
|
||||||
|
SCROLLBACK_INDICATOR_FORMAT_TEXT,
|
||||||
|
} format;
|
||||||
|
|
||||||
|
char32_t *text;
|
||||||
|
} indicator;
|
||||||
|
float multiplier;
|
||||||
|
} scrollback;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
char32_t *label_letters;
|
||||||
|
struct config_spawn_template launch;
|
||||||
|
enum {
|
||||||
|
OSC8_UNDERLINE_URL_MODE,
|
||||||
|
OSC8_UNDERLINE_ALWAYS,
|
||||||
|
} osc8_underline;
|
||||||
|
enum underline_style style;
|
||||||
|
|
||||||
|
char *regex;
|
||||||
|
regex_t preg;
|
||||||
|
} url;
|
||||||
|
|
||||||
|
tll(struct custom_regex) custom_regexes;
|
||||||
|
|
||||||
|
struct color_theme colors_dark;
|
||||||
|
struct color_theme colors_light;
|
||||||
|
enum which_color_theme initial_color_theme;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
enum cursor_style style;
|
||||||
|
enum cursor_unfocused_style unfocused_style;
|
||||||
|
struct {
|
||||||
|
bool enabled;
|
||||||
|
uint32_t rate_ms;
|
||||||
|
} blink;
|
||||||
|
struct pt_or_px beam_thickness;
|
||||||
|
struct pt_or_px underline_thickness;
|
||||||
|
} cursor;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool hide_when_typing;
|
||||||
|
bool alternate_scroll_mode;
|
||||||
|
//struct config_key_modifiers selection_override_modifiers;
|
||||||
|
config_modifier_list_t selection_override_modifiers;
|
||||||
|
} mouse;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
/* Bindings for "normal" mode */
|
||||||
|
struct config_key_binding_list key;
|
||||||
|
struct config_key_binding_list mouse;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Special modes
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* While searching (not - action to *start* a search is in the
|
||||||
|
* 'key' bindings above */
|
||||||
|
struct config_key_binding_list search;
|
||||||
|
|
||||||
|
/* While showing URL jump labels */
|
||||||
|
struct config_key_binding_list url;
|
||||||
|
} bindings;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
enum { CONF_CSD_PREFER_NONE, CONF_CSD_PREFER_SERVER, CONF_CSD_PREFER_CLIENT } preferred;
|
||||||
|
|
||||||
|
uint16_t title_height;
|
||||||
|
uint16_t border_width;
|
||||||
|
uint16_t border_width_visible;
|
||||||
|
uint16_t button_width;
|
||||||
|
|
||||||
|
bool hide_when_maximized;
|
||||||
|
bool double_click_to_maximize;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool title_set:1;
|
||||||
|
bool buttons_set:1;
|
||||||
|
bool minimize_set:1;
|
||||||
|
bool maximize_set:1;
|
||||||
|
bool close_set:1;
|
||||||
|
bool border_set:1;
|
||||||
|
uint32_t title;
|
||||||
|
uint32_t buttons;
|
||||||
|
uint32_t minimize;
|
||||||
|
uint32_t maximize;
|
||||||
|
uint32_t quit; /* 'close' collides with #define in epoll-shim */
|
||||||
|
uint32_t border;
|
||||||
|
} color;
|
||||||
|
|
||||||
|
struct config_font_list font;
|
||||||
|
} csd;
|
||||||
|
|
||||||
|
uint16_t render_worker_count;
|
||||||
|
char *server_socket_path;
|
||||||
|
bool presentation_timings;
|
||||||
|
bool hold_at_exit;
|
||||||
|
enum {
|
||||||
|
SELECTION_TARGET_NONE,
|
||||||
|
SELECTION_TARGET_PRIMARY,
|
||||||
|
SELECTION_TARGET_CLIPBOARD,
|
||||||
|
SELECTION_TARGET_BOTH
|
||||||
|
} selection_target;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct config_spawn_template command;
|
||||||
|
struct config_spawn_template command_action_arg;
|
||||||
|
struct config_spawn_template close;
|
||||||
|
bool inhibit_when_focused;
|
||||||
|
} desktop_notifications;
|
||||||
|
|
||||||
|
env_var_list_t env_vars;
|
||||||
|
|
||||||
|
char *utmp_helper_path;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
enum fcft_scaling_filter fcft_filter;
|
||||||
|
bool overflowing_glyphs;
|
||||||
|
bool grapheme_shaping;
|
||||||
|
enum {
|
||||||
|
GRAPHEME_WIDTH_WCSWIDTH,
|
||||||
|
GRAPHEME_WIDTH_DOUBLE,
|
||||||
|
GRAPHEME_WIDTH_MAX,
|
||||||
|
} grapheme_width_method;
|
||||||
|
enum {
|
||||||
|
RENDER_TIMER_NONE,
|
||||||
|
RENDER_TIMER_OSD,
|
||||||
|
RENDER_TIMER_LOG,
|
||||||
|
RENDER_TIMER_BOTH
|
||||||
|
} render_timer;
|
||||||
|
bool damage_whole_window;
|
||||||
|
uint32_t delayed_render_lower_ns;
|
||||||
|
uint32_t delayed_render_upper_ns;
|
||||||
|
off_t max_shm_pool_size;
|
||||||
|
float box_drawing_base_thickness;
|
||||||
|
bool box_drawing_solid_shades;
|
||||||
|
bool font_monospace_warn;
|
||||||
|
bool sixel;
|
||||||
|
enum shm_bit_depth surface_bit_depth;
|
||||||
|
uint32_t min_stride_alignment;
|
||||||
|
bool preapply_damage;
|
||||||
|
} tweak;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t long_press_delay;
|
||||||
|
} touch;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool enabled;
|
||||||
|
enum {
|
||||||
|
CONF_TABS_POSITION_TOP,
|
||||||
|
CONF_TABS_POSITION_BOTTOM,
|
||||||
|
} position;
|
||||||
|
enum {
|
||||||
|
CONF_TABS_STYLE_ROUNDED,
|
||||||
|
CONF_TABS_STYLE_SQUARE,
|
||||||
|
} style;
|
||||||
|
enum {
|
||||||
|
CONF_TABS_LAYOUT_SPAN,
|
||||||
|
CONF_TABS_LAYOUT_FLOATING,
|
||||||
|
} layout;
|
||||||
|
uint16_t height; /* pill height; bar = height + margin in floating mode */
|
||||||
|
uint16_t tab_width; /* max tab width in floating mode */
|
||||||
|
uint16_t tab_padding; /* gap between tabs in floating mode */
|
||||||
|
uint16_t label_padding; /* horizontal padding around the label inside each tab pill */
|
||||||
|
uint16_t margin; /* edge gap in floating mode (added to bar height) */
|
||||||
|
uint16_t corner_radius;
|
||||||
|
bool inherit_cwd;
|
||||||
|
char *unread_indicator; /* UTF-8 string drawn before label when unread; NULL or empty disables */
|
||||||
|
struct {
|
||||||
|
uint32_t bg;
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t active_bg;
|
||||||
|
uint32_t active_fg;
|
||||||
|
uint32_t unread_fg;
|
||||||
|
} colors;
|
||||||
|
} tabs;
|
||||||
|
|
||||||
|
user_notifications_t notifications;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool config_override_apply(struct config *conf, config_override_t *overrides,
|
||||||
|
bool errors_are_fatal);
|
||||||
|
bool config_load(
|
||||||
|
struct config *conf, const char *path,
|
||||||
|
user_notifications_t *initial_user_notifications,
|
||||||
|
config_override_t *overrides, bool errors_are_fatal,
|
||||||
|
bool as_server);
|
||||||
|
void config_free(struct config *conf);
|
||||||
|
struct config *config_clone(const struct config *old);
|
||||||
|
|
||||||
|
bool config_font_parse(const char *pattern, struct config_font *font);
|
||||||
|
void config_font_list_destroy(struct config_font_list *font_list);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
struct seat;
|
||||||
|
xkb_mod_mask_t
|
||||||
|
conf_modifiers_to_mask(
|
||||||
|
const struct seat *seat, const struct config_key_modifiers *modifiers);
|
||||||
|
#endif
|
||||||
|
bool check_if_font_is_monospaced(
|
||||||
|
const char *pattern, user_notifications_t *notifications);
|
||||||
6
csi.h
Normal file
6
csi.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
void csi_dispatch(struct terminal *term, uint8_t final);
|
||||||
130
cursor-shape.c
Normal file
130
cursor-shape.c
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "cursor-shape"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include "cursor-shape.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
const char *const *
|
||||||
|
cursor_shape_to_string(enum cursor_shape shape)
|
||||||
|
{
|
||||||
|
static const char *const table[][CURSOR_SHAPE_COUNT]= {
|
||||||
|
[CURSOR_SHAPE_NONE] = {NULL},
|
||||||
|
[CURSOR_SHAPE_HIDDEN] = {"hidden", NULL},
|
||||||
|
[CURSOR_SHAPE_LEFT_PTR] = {"default", "left_ptr", NULL},
|
||||||
|
[CURSOR_SHAPE_POINTER] = {"pointer", "hand1", NULL},
|
||||||
|
[CURSOR_SHAPE_TEXT] = {"text", "xterm", NULL},
|
||||||
|
[CURSOR_SHAPE_TOP_LEFT_CORNER] = {"nw-resize", "top_left_corner", NULL},
|
||||||
|
[CURSOR_SHAPE_TOP_RIGHT_CORNER] = {"ne-resize", "top_right_corner", NULL},
|
||||||
|
[CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = {"sw-resize", "bottom_left_corner", NULL},
|
||||||
|
[CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = {"se-resize", "bottom_right_corner", NULL},
|
||||||
|
[CURSOR_SHAPE_LEFT_SIDE] = {"w-resize", "left_side", NULL},
|
||||||
|
[CURSOR_SHAPE_RIGHT_SIDE] = {"e-resize", "right_side", NULL},
|
||||||
|
[CURSOR_SHAPE_TOP_SIDE] = {"n-resize", "top_side", NULL},
|
||||||
|
[CURSOR_SHAPE_BOTTOM_SIDE] = {"s-resize", "bottom_side", NULL},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
xassert(shape <= ALEN(table));
|
||||||
|
return table[shape];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum wp_cursor_shape_device_v1_shape
|
||||||
|
cursor_shape_to_server_shape(enum cursor_shape shape)
|
||||||
|
{
|
||||||
|
static const enum wp_cursor_shape_device_v1_shape table[CURSOR_SHAPE_COUNT] = {
|
||||||
|
[CURSOR_SHAPE_LEFT_PTR] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT,
|
||||||
|
[CURSOR_SHAPE_POINTER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER,
|
||||||
|
[CURSOR_SHAPE_TEXT] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT,
|
||||||
|
[CURSOR_SHAPE_TOP_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE,
|
||||||
|
[CURSOR_SHAPE_TOP_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE,
|
||||||
|
[CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE,
|
||||||
|
[CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE,
|
||||||
|
[CURSOR_SHAPE_LEFT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE,
|
||||||
|
[CURSOR_SHAPE_RIGHT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE,
|
||||||
|
[CURSOR_SHAPE_TOP_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE,
|
||||||
|
[CURSOR_SHAPE_BOTTOM_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
xassert(shape <= ALEN(table));
|
||||||
|
xassert(table[shape] != 0);
|
||||||
|
return table[shape];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum wp_cursor_shape_device_v1_shape
|
||||||
|
cursor_string_to_server_shape(const char *xcursor, int bound_version)
|
||||||
|
{
|
||||||
|
if (xcursor == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
static const char *const table[][2] = {
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT] = {"default", "left_ptr"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU] = {"context-menu"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP] = {"help", "question_arrow"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER] = {"pointer", "hand"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS] = {"progress", "left_ptr_watch"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT] = {"wait", "watch"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL] = {"cell"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR] = {"crosshair", "cross"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT] = {"text", "xterm"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT] = {"vertical-text"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS] = {"alias", "dnd-link"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY] = {"copy", "dnd-copy"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = {"move", "dnd-move"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP] = {"no-drop", "dnd-no-drop"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED] = {"not-allowed", "crossed_circle"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB] = {"grab", "hand1"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING] = {"grabbing"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE] = {"e-resize", "right_side"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE] = {"n-resize", "top_side"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE] = {"ne-resize", "top_right_corner"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE] = {"nw-resize", "top_left_corner"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE] = {"s-resize", "bottom_side"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE] = {"se-resize", "bottom_right_corner"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE] = {"sw-resize", "bottom_left_corner"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE] = {"w-resize", "left_side"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE] = {"ew-resize", "sb_h_double_arrow"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE] = {"ns-resize", "sb_v_double_arrow"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE] = {"nesw-resize", "fd_double_arrow"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE] = {"nwse-resize", "bd_double_arrow"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE] = {"col-resize", "sb_h_double_arrow"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE] = {"row-resize", "sb_v_double_arrow"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = {"all-scroll", "fleur"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = {"zoom-in"},
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = {"zoom-out"},
|
||||||
|
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION) /* 1.42 */
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK] = {"dnd-ask"},
|
||||||
|
#endif
|
||||||
|
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION) /* 1.42 */
|
||||||
|
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE] = {"all-resize"},
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ALEN(table); i++) {
|
||||||
|
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION)
|
||||||
|
if (i == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK &&
|
||||||
|
bound_version < WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION)
|
||||||
|
if (i == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE &&
|
||||||
|
bound_version < WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (size_t j = 0; j < ALEN(table[i]); j++) {
|
||||||
|
if (table[i][j] != NULL && streq(xcursor, table[i][j])) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
30
cursor-shape.h
Normal file
30
cursor-shape.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cursor-shape-v1.h>
|
||||||
|
|
||||||
|
enum cursor_shape {
|
||||||
|
CURSOR_SHAPE_NONE,
|
||||||
|
CURSOR_SHAPE_CUSTOM,
|
||||||
|
CURSOR_SHAPE_HIDDEN,
|
||||||
|
|
||||||
|
CURSOR_SHAPE_LEFT_PTR,
|
||||||
|
CURSOR_SHAPE_POINTER,
|
||||||
|
CURSOR_SHAPE_TEXT,
|
||||||
|
CURSOR_SHAPE_TOP_LEFT_CORNER,
|
||||||
|
CURSOR_SHAPE_TOP_RIGHT_CORNER,
|
||||||
|
CURSOR_SHAPE_BOTTOM_LEFT_CORNER,
|
||||||
|
CURSOR_SHAPE_BOTTOM_RIGHT_CORNER,
|
||||||
|
CURSOR_SHAPE_LEFT_SIDE,
|
||||||
|
CURSOR_SHAPE_RIGHT_SIDE,
|
||||||
|
CURSOR_SHAPE_TOP_SIDE,
|
||||||
|
CURSOR_SHAPE_BOTTOM_SIDE,
|
||||||
|
|
||||||
|
CURSOR_SHAPE_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *const *cursor_shape_to_string(enum cursor_shape shape);
|
||||||
|
|
||||||
|
enum wp_cursor_shape_device_v1_shape cursor_shape_to_server_shape(
|
||||||
|
enum cursor_shape shape);
|
||||||
|
enum wp_cursor_shape_device_v1_shape cursor_string_to_server_shape(
|
||||||
|
const char *xcursor, int bound_version);
|
||||||
533
dcs.c
Normal file
533
dcs.c
Normal file
|
|
@ -0,0 +1,533 @@
|
||||||
|
#include "dcs.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "dcs"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "foot-terminfo.h"
|
||||||
|
#include "sixel.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "vt.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
#include "xsnprintf.h"
|
||||||
|
|
||||||
|
static bool
|
||||||
|
ensure_size(struct terminal *term, size_t required_size)
|
||||||
|
{
|
||||||
|
if (required_size <= term->vt.dcs.size)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
uint8_t *new_data = realloc(term->vt.dcs.data, required_size);
|
||||||
|
if (new_data == NULL) {
|
||||||
|
LOG_ERRNO("failed to increase size of DCS buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
term->vt.dcs.data = new_data;
|
||||||
|
term->vt.dcs.size = required_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decode hex-encoded string *inline*. NULL terminates */
|
||||||
|
static char *
|
||||||
|
hex_decode(const char *s, size_t len)
|
||||||
|
{
|
||||||
|
if (len % 2)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char *hex = xmalloc(len / 2 + 1);
|
||||||
|
char *o = hex;
|
||||||
|
|
||||||
|
/* TODO: error checking */
|
||||||
|
for (size_t i = 0; i < len; i += 2) {
|
||||||
|
uint8_t nib1 = hex2nibble(*s); s++;
|
||||||
|
uint8_t nib2 = hex2nibble(*s); s++;
|
||||||
|
|
||||||
|
if (nib1 == HEX_DIGIT_INVALID || nib2 == HEX_DIGIT_INVALID)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
*o = nib1 << 4 | nib2; o++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*o = '\0';
|
||||||
|
return hex;
|
||||||
|
|
||||||
|
err:
|
||||||
|
free(hex);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
/* Verify table is sorted */
|
||||||
|
const char *p = terminfo_capabilities;
|
||||||
|
size_t left = sizeof(terminfo_capabilities);
|
||||||
|
|
||||||
|
const char *last_cap = NULL;
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
const char *cap = p;
|
||||||
|
const char *val = cap + strlen(cap) + 1;
|
||||||
|
|
||||||
|
size_t size = strlen(cap) + 1 + strlen(val) + 1;;
|
||||||
|
xassert(size <= left);
|
||||||
|
p += size;
|
||||||
|
left -= size;
|
||||||
|
|
||||||
|
if (last_cap != NULL)
|
||||||
|
xassert(strcmp(last_cap, cap) < 0);
|
||||||
|
|
||||||
|
last_cap = cap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
lookup_capability(const char *name, const char **value)
|
||||||
|
{
|
||||||
|
const char *p = terminfo_capabilities;
|
||||||
|
size_t left = sizeof(terminfo_capabilities);
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
const char *cap = p;
|
||||||
|
const char *val = cap + strlen(cap) + 1;
|
||||||
|
|
||||||
|
size_t size = strlen(cap) + 1 + strlen(val) + 1;;
|
||||||
|
xassert(size <= left);
|
||||||
|
p += size;
|
||||||
|
left -= size;
|
||||||
|
|
||||||
|
int r = strcmp(cap, name);
|
||||||
|
if (r == 0) {
|
||||||
|
*value = val;
|
||||||
|
return true;
|
||||||
|
} else if (r > 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*value = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
xtgettcap_reply(struct terminal *term, const char *hex_cap_name, size_t len)
|
||||||
|
{
|
||||||
|
char *name = hex_decode(hex_cap_name, len);
|
||||||
|
if (name == NULL) {
|
||||||
|
LOG_WARN("XTGETTCAP: invalid hex encoding, ignoring capability");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *value;
|
||||||
|
bool valid_capability = lookup_capability(name, &value);
|
||||||
|
xassert(!valid_capability || value != NULL);
|
||||||
|
|
||||||
|
LOG_DBG("XTGETTCAP: cap=%s (%.*s), value=%s",
|
||||||
|
name, (int)len, hex_cap_name,
|
||||||
|
valid_capability ? value : "<invalid>");
|
||||||
|
|
||||||
|
if (!valid_capability)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (value[0] == '\0') {
|
||||||
|
/* Boolean */
|
||||||
|
term_to_slave(term, "\033P1+r", 5);
|
||||||
|
term_to_slave(term, hex_cap_name, len);
|
||||||
|
term_to_slave(term, "\033\\", 2);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reply format:
|
||||||
|
* \EP 1 + r cap=value \E\\
|
||||||
|
* Where 'cap' and 'value are hex encoded ascii strings
|
||||||
|
*/
|
||||||
|
char *reply = xmalloc(
|
||||||
|
5 + /* DCS 1 + r (\EP1+r) */
|
||||||
|
len + /* capability name, hex encoded */
|
||||||
|
1 + /* '=' */
|
||||||
|
strlen(value) * 2 + /* capability value, hex encoded */
|
||||||
|
2 + /* ST (\E\\) */
|
||||||
|
1);
|
||||||
|
|
||||||
|
int idx = sprintf(reply, "\033P1+r%.*s=", (int)len, hex_cap_name);
|
||||||
|
|
||||||
|
for (const char *c = value; *c != '\0'; c++) {
|
||||||
|
uint8_t nib1 = (uint8_t)*c >> 4;
|
||||||
|
uint8_t nib2 = (uint8_t)*c & 0xf;
|
||||||
|
|
||||||
|
reply[idx] = nib1 >= 0xa ? 'A' + nib1 - 0xa : '0' + nib1; idx++;
|
||||||
|
reply[idx] = nib2 >= 0xa ? 'A' + nib2 - 0xa : '0' + nib2; idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply[idx] = '\033'; idx++;
|
||||||
|
reply[idx] = '\\'; idx++;
|
||||||
|
term_to_slave(term, reply, idx);
|
||||||
|
|
||||||
|
free(reply);
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
err:
|
||||||
|
term_to_slave(term, "\033P0+r", 5);
|
||||||
|
term_to_slave(term, hex_cap_name, len);
|
||||||
|
term_to_slave(term, "\033\\", 2);
|
||||||
|
|
||||||
|
out:
|
||||||
|
free(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
xtgettcap_put(struct terminal *term, uint8_t c)
|
||||||
|
{
|
||||||
|
struct vt *vt = &term->vt;
|
||||||
|
|
||||||
|
/* Grow buffer expontentially */
|
||||||
|
if (vt->dcs.idx >= vt->dcs.size) {
|
||||||
|
size_t new_size = vt->dcs.size * 2;
|
||||||
|
if (new_size == 0)
|
||||||
|
new_size = 128;
|
||||||
|
|
||||||
|
if (!ensure_size(term, new_size))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vt->dcs.data[vt->dcs.idx++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
xtgettcap_unhook(struct terminal *term)
|
||||||
|
{
|
||||||
|
size_t left = term->vt.dcs.idx;
|
||||||
|
|
||||||
|
const char *const end = (const char *)&term->vt.dcs.data[left];
|
||||||
|
const char *p = (const char *)term->vt.dcs.data;
|
||||||
|
|
||||||
|
if (p == NULL) {
|
||||||
|
/* Request is empty; send an error reply, without any capabilities */
|
||||||
|
term_to_slave(term, "\033P0+r\033\\", 7);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const char *sep = memchr(p, ';', left);
|
||||||
|
size_t cap_len;
|
||||||
|
|
||||||
|
if (sep == NULL) {
|
||||||
|
/* Last capability */
|
||||||
|
cap_len = end - p;
|
||||||
|
} else {
|
||||||
|
cap_len = sep - p;
|
||||||
|
}
|
||||||
|
|
||||||
|
xtgettcap_reply(term, p, cap_len);
|
||||||
|
|
||||||
|
left -= cap_len + 1;
|
||||||
|
p += cap_len + 1;
|
||||||
|
|
||||||
|
if (sep == NULL)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NOINLINE
|
||||||
|
append_sgr_attr_n(char **reply, size_t *len, const char *attr, size_t n)
|
||||||
|
{
|
||||||
|
size_t new_len = *len + n + 1;
|
||||||
|
*reply = xrealloc(*reply, new_len);
|
||||||
|
memcpy(&(*reply)[*len], attr, n);
|
||||||
|
(*reply)[new_len - 1] = ';';
|
||||||
|
*len = new_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
decrqss_put(struct terminal *term, uint8_t c)
|
||||||
|
{
|
||||||
|
/* Largest request we support is two bytes */
|
||||||
|
if (!ensure_size(term, 2))
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct vt *vt = &term->vt;
|
||||||
|
if (vt->dcs.idx >= 2)
|
||||||
|
return;
|
||||||
|
vt->dcs.data[vt->dcs.idx++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
decrqss_unhook(struct terminal *term)
|
||||||
|
{
|
||||||
|
const uint8_t *query = term->vt.dcs.data;
|
||||||
|
const size_t n = term->vt.dcs.idx;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A note on the Ps parameter in the reply: many DEC manual
|
||||||
|
* instances (e.g. https://vt100.net/docs/vt510-rm/DECRPSS) claim
|
||||||
|
* that 0 means "request is valid", and 1 means "request is
|
||||||
|
* invalid".
|
||||||
|
*
|
||||||
|
* However, this appears to be a typo; actual hardware inverts the
|
||||||
|
* response (as does XTerm and mlterm):
|
||||||
|
* https://github.com/hackerb9/vt340test/issues/13
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (n == 1 && query[0] == 'r') {
|
||||||
|
/* DECSTBM - Set Top and Bottom Margins */
|
||||||
|
char reply[64];
|
||||||
|
size_t len = xsnprintf(reply, sizeof(reply), "\033P1$r%d;%dr\033\\",
|
||||||
|
term->scroll_region.start + 1,
|
||||||
|
term->scroll_region.end);
|
||||||
|
term_to_slave(term, reply, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (n == 1 && query[0] == 'm') {
|
||||||
|
/* SGR - Set Graphic Rendition */
|
||||||
|
char *reply = NULL;
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
#define append_sgr_attr(num_as_str) \
|
||||||
|
append_sgr_attr_n(&reply, &len, num_as_str, sizeof(num_as_str) - 1)
|
||||||
|
|
||||||
|
/* Always present, both in the example from the VT510 manual
|
||||||
|
* (https://vt100.net/docs/vt510-rm/DECRPSS), and in XTerm and
|
||||||
|
* mlterm */
|
||||||
|
append_sgr_attr("0");
|
||||||
|
|
||||||
|
struct attributes *a = &term->vt.attrs;
|
||||||
|
if (a->bold)
|
||||||
|
append_sgr_attr("1");
|
||||||
|
if (a->dim)
|
||||||
|
append_sgr_attr("2");
|
||||||
|
if (a->italic)
|
||||||
|
append_sgr_attr("3");
|
||||||
|
if (a->underline) {
|
||||||
|
if (term->vt.underline.style > UNDERLINE_SINGLE) {
|
||||||
|
char value[4];
|
||||||
|
size_t val_len =
|
||||||
|
xsnprintf(value, sizeof(value), "4:%d", term->vt.underline.style);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
} else
|
||||||
|
append_sgr_attr("4");
|
||||||
|
}
|
||||||
|
if (a->blink)
|
||||||
|
append_sgr_attr("5");
|
||||||
|
if (a->reverse)
|
||||||
|
append_sgr_attr("7");
|
||||||
|
if (a->conceal)
|
||||||
|
append_sgr_attr("8");
|
||||||
|
if (a->strikethrough)
|
||||||
|
append_sgr_attr("9");
|
||||||
|
|
||||||
|
switch (a->fg_src) {
|
||||||
|
case COLOR_DEFAULT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COLOR_BASE16: {
|
||||||
|
char value[4];
|
||||||
|
size_t val_len = xsnprintf(
|
||||||
|
value, sizeof(value), "%u",
|
||||||
|
a->fg >= 8 ? a->fg - 8 + 90 : a->fg + 30);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COLOR_BASE256: {
|
||||||
|
char value[16];
|
||||||
|
size_t val_len = xsnprintf(value, sizeof(value), "38:5:%u", a->fg);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COLOR_RGB: {
|
||||||
|
uint8_t r = a->fg >> 16;
|
||||||
|
uint8_t g = a->fg >> 8;
|
||||||
|
uint8_t b = a->fg >> 0;
|
||||||
|
|
||||||
|
char value[32];
|
||||||
|
size_t val_len = xsnprintf(
|
||||||
|
value, sizeof(value), "38:2::%hhu:%hhu:%hhu", r, g, b);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (a->bg_src) {
|
||||||
|
case COLOR_DEFAULT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COLOR_BASE16: {
|
||||||
|
char value[4];
|
||||||
|
size_t val_len = xsnprintf(
|
||||||
|
value, sizeof(value), "%u",
|
||||||
|
a->bg >= 8 ? a->bg - 8 + 100 : a->bg + 40);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COLOR_BASE256: {
|
||||||
|
char value[16];
|
||||||
|
size_t val_len = xsnprintf(value, sizeof(value), "48:5:%u", a->bg);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COLOR_RGB: {
|
||||||
|
uint8_t r = a->bg >> 16;
|
||||||
|
uint8_t g = a->bg >> 8;
|
||||||
|
uint8_t b = a->bg >> 0;
|
||||||
|
|
||||||
|
char value[32];
|
||||||
|
size_t val_len = xsnprintf(
|
||||||
|
value, sizeof(value), "48:2::%hhu:%hhu:%hhu", r, g, b);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (term->vt.underline.color_src) {
|
||||||
|
case COLOR_DEFAULT:
|
||||||
|
case COLOR_BASE16:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COLOR_BASE256: {
|
||||||
|
char value[16];
|
||||||
|
size_t val_len = xsnprintf(
|
||||||
|
value, sizeof(value), "58:5:%u", term->vt.underline.color);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COLOR_RGB: {
|
||||||
|
uint8_t r = term->vt.underline.color >> 16;
|
||||||
|
uint8_t g = term->vt.underline.color >> 8;
|
||||||
|
uint8_t b = term->vt.underline.color >> 0;
|
||||||
|
|
||||||
|
char value[32];
|
||||||
|
size_t val_len = xsnprintf(
|
||||||
|
value, sizeof(value), "58:2::%hhu:%hhu:%hhu", r, g, b);
|
||||||
|
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef append_sgr_attr_n
|
||||||
|
|
||||||
|
reply[len - 1] = 'm';
|
||||||
|
|
||||||
|
term_to_slave(term, "\033P1$r", 5);
|
||||||
|
term_to_slave(term, reply, len);
|
||||||
|
term_to_slave(term, "\033\\", 2);
|
||||||
|
free(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (n == 2 && memcmp(query, " q", 2) == 0) {
|
||||||
|
/* DECSCUSR - Set Cursor Style */
|
||||||
|
int mode;
|
||||||
|
|
||||||
|
switch (term->cursor_style) {
|
||||||
|
case CURSOR_HOLLOW: /* FALLTHROUGH */
|
||||||
|
case CURSOR_BLOCK: mode = 2; break;
|
||||||
|
case CURSOR_UNDERLINE: mode = 4; break;
|
||||||
|
case CURSOR_BEAM: mode = 6; break;
|
||||||
|
default: BUG("invalid cursor style"); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (term->cursor_blink.deccsusr)
|
||||||
|
mode--;
|
||||||
|
|
||||||
|
char reply[16];
|
||||||
|
size_t len = xsnprintf(reply, sizeof(reply), "\033P1$r%d q\033\\", mode);
|
||||||
|
term_to_slave(term, reply, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
static const char err[] = "\033P0$r\033\\";
|
||||||
|
term_to_slave(term, err, sizeof(err) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dcs_hook(struct terminal *term, uint8_t final)
|
||||||
|
{
|
||||||
|
LOG_DBG("hook: %c (intermediate(s): %.2s, param=%d)", final,
|
||||||
|
(const char *)&term->vt.private, vt_param_get(term, 0, 0));
|
||||||
|
|
||||||
|
xassert(term->vt.dcs.data == NULL);
|
||||||
|
xassert(term->vt.dcs.size == 0);
|
||||||
|
xassert(term->vt.dcs.put_handler == NULL);
|
||||||
|
xassert(term->vt.dcs.unhook_handler == NULL);
|
||||||
|
|
||||||
|
switch (term->vt.private) {
|
||||||
|
case 0:
|
||||||
|
switch (final) {
|
||||||
|
case 'q': {
|
||||||
|
if (!term->conf->tweak.sixel) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int p1 = vt_param_get(term, 0, 0);
|
||||||
|
int p2 = vt_param_get(term, 1, 0);
|
||||||
|
int p3 = vt_param_get(term, 2, 0);
|
||||||
|
|
||||||
|
term->vt.dcs.put_handler = sixel_init(term, p1, p2, p3);
|
||||||
|
term->vt.dcs.unhook_handler = &sixel_unhook;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '$':
|
||||||
|
switch (final) {
|
||||||
|
case 'q':
|
||||||
|
term->vt.dcs.put_handler = &decrqss_put;
|
||||||
|
term->vt.dcs.unhook_handler = &decrqss_unhook;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '=':
|
||||||
|
switch (final) {
|
||||||
|
case 's':
|
||||||
|
/* BSU/ESU: https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
|
||||||
|
switch (vt_param_get(term, 0, 0)) {
|
||||||
|
case 1:
|
||||||
|
term->vt.dcs.unhook_handler = &term_enable_app_sync_updates;
|
||||||
|
return;
|
||||||
|
case 2:
|
||||||
|
term->vt.dcs.unhook_handler = &term_disable_app_sync_updates;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '+':
|
||||||
|
switch (final) {
|
||||||
|
case 'q': /* XTGETTCAP */
|
||||||
|
term->vt.dcs.put_handler = &xtgettcap_put;
|
||||||
|
term->vt.dcs.unhook_handler = &xtgettcap_unhook;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dcs_put(struct terminal *term, uint8_t c)
|
||||||
|
{
|
||||||
|
/* LOG_DBG("PUT: %c", c); */
|
||||||
|
|
||||||
|
if (term->vt.dcs.put_handler != NULL)
|
||||||
|
term->vt.dcs.put_handler(term, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dcs_unhook(struct terminal *term)
|
||||||
|
{
|
||||||
|
if (term->vt.dcs.unhook_handler != NULL)
|
||||||
|
term->vt.dcs.unhook_handler(term);
|
||||||
|
|
||||||
|
term->vt.dcs.unhook_handler = NULL;
|
||||||
|
term->vt.dcs.put_handler = NULL;
|
||||||
|
|
||||||
|
free(term->vt.dcs.data);
|
||||||
|
term->vt.dcs.data = NULL;
|
||||||
|
term->vt.dcs.size = 0;
|
||||||
|
term->vt.dcs.idx = 0;
|
||||||
|
}
|
||||||
8
dcs.h
Normal file
8
dcs.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
void dcs_hook(struct terminal *term, uint8_t final);
|
||||||
|
void dcs_put(struct terminal *term, uint8_t c);
|
||||||
|
void dcs_unhook(struct terminal *term);
|
||||||
47
debug.c
Normal file
47
debug.c
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#if defined(__SANITIZE_ADDRESS__) || HAS_FEATURE(address_sanitizer)
|
||||||
|
#include <sanitizer/asan_interface.h>
|
||||||
|
#define ASAN_ENABLED 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_stack_trace(void)
|
||||||
|
{
|
||||||
|
#ifdef ASAN_ENABLED
|
||||||
|
fputs("\nStack trace:\n", stderr);
|
||||||
|
__sanitizer_print_stack_trace();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
noreturn void
|
||||||
|
fatal_error(const char *file, int line, const char *msg, int err)
|
||||||
|
{
|
||||||
|
log_msg(LOG_CLASS_ERROR, "debug", file, line, "%s: %s", msg, strerror(err));
|
||||||
|
print_stack_trace();
|
||||||
|
fflush(stderr);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
noreturn void
|
||||||
|
bug(const char *file, int line, const char *func, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char buf[4096];
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
int n = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
const char *msg = likely(n >= 0) ? buf : "??";
|
||||||
|
log_msg(LOG_CLASS_ERROR, "debug", file, line, "BUG in %s(): %s", func, msg);
|
||||||
|
print_stack_trace();
|
||||||
|
fflush(stderr);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
32
debug.h
Normal file
32
debug.h
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "macros.h"
|
||||||
|
|
||||||
|
#define FATAL_ERROR(...) fatal_error(__FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
#define BUG(...) UNREACHABLE()
|
||||||
|
#else
|
||||||
|
#define BUG(...) bug(__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define xassert(x) do { \
|
||||||
|
IGNORE_WARNING("-Wtautological-compare") \
|
||||||
|
if (unlikely(!(x))) { \
|
||||||
|
BUG("assertion failed: '%s'", #x); \
|
||||||
|
} \
|
||||||
|
UNIGNORE_WARNINGS \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#ifndef static_assert
|
||||||
|
#if __STDC_VERSION__ >= 201112L
|
||||||
|
#define static_assert(x, msg) _Static_assert((x), msg)
|
||||||
|
#elif GNUC_AT_LEAST(4, 6) || HAS_EXTENSION(c_static_assert)
|
||||||
|
#define static_assert(x, msg) __extension__ _Static_assert((x), msg)
|
||||||
|
#else
|
||||||
|
#define static_assert(x, msg)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
noreturn void fatal_error(const char *file, int line, const char *msg, int err) COLD;
|
||||||
|
noreturn void bug(const char *file, int line, const char *func, const char *fmt, ...) PRINTF(4) COLD;
|
||||||
81
doc/benchmark.md
Normal file
81
doc/benchmark.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Benchmarks
|
||||||
|
|
||||||
|
## vtebench
|
||||||
|
|
||||||
|
All benchmarks are done using [vtebench](https://github.com/alacritty/vtebench):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./target/release/vtebench -b ./benchmarks --dat /tmp/<terminal>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2022-05-12
|
||||||
|
|
||||||
|
### System
|
||||||
|
|
||||||
|
CPU: i9-9900
|
||||||
|
|
||||||
|
RAM: 64GB
|
||||||
|
|
||||||
|
Graphics: Radeon RX 5500XT
|
||||||
|
|
||||||
|
|
||||||
|
### Terminal configuration
|
||||||
|
|
||||||
|
Geometry: 2040x1884
|
||||||
|
|
||||||
|
Font: Fantasque Sans Mono 10.00pt/23px
|
||||||
|
|
||||||
|
Scrollback: 10000 lines
|
||||||
|
|
||||||
|
|
||||||
|
### Results
|
||||||
|
|
||||||
|
| Benchmark (times in ms) | Foot (GCC+PGO) 1.12.1 | Foot 1.12.1 | Alacritty 0.10.1 | URxvt 9.26 | XTerm 372 |
|
||||||
|
|-------------------------------|----------------------:|------------:|-----------------:|-----------:|----------:|
|
||||||
|
| cursor motion | 10.40 | 14.07 | 24.97 | 23.38 | 1622.86 |
|
||||||
|
| dense cells | 29.58 | 45.46 | 97.45 | 10828.00 | 2323.00 |
|
||||||
|
| light cells | 4.34 | 4.40 | 12.84 | 12.17 | 49.81 |
|
||||||
|
| scrollling | 135.31 | 116.35 | 121.69 | 108.30 | 4041.33 |
|
||||||
|
| scrolling bottom region | 118.19 | 109.70 | 105.26 | 118.80 | 3875.00 |
|
||||||
|
| scrolling bottom small region | 132.41 | 122.11 | 122.83 | 151.30 | 3839.67 |
|
||||||
|
| scrolling fullscreen | 5.70 | 5.66 | 10.92 | 12.09 | 124.25 |
|
||||||
|
| scrolling top region | 144.19 | 121.78 | 135.81 | 159.24 | 3858.33 |
|
||||||
|
| scrolling top small region | 135.95 | 119.01 | 115.46 | 216.55 | 3872.67 |
|
||||||
|
| unicode | 11.56 | 10.92 | 15.94 | 1012.27 | 4779.33 |
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-05-12
|
||||||
|
|
||||||
|
### System
|
||||||
|
|
||||||
|
CPU: i5-8250U
|
||||||
|
|
||||||
|
RAM: 8GB
|
||||||
|
|
||||||
|
Graphics: Intel UHD Graphics 620
|
||||||
|
|
||||||
|
|
||||||
|
### Terminal configuration
|
||||||
|
|
||||||
|
Geometry: 945x1020
|
||||||
|
|
||||||
|
Font: Dina:pixelsize=12
|
||||||
|
|
||||||
|
Scrollback=10000 lines
|
||||||
|
|
||||||
|
|
||||||
|
### Results
|
||||||
|
|
||||||
|
|
||||||
|
| Benchmark (times in ms) | Foot (GCC+PGO) 1.12.1 | Foot 1.12.1 | Alacritty 0.10.1 | URxvt 9.26 | XTerm 372 |
|
||||||
|
|-------------------------------|----------------------:|------------:|-----------------:|-----------:|----------:|
|
||||||
|
| cursor motion | 15.03 | 16.74 | 23.22 | 24.14 | 1381.63 |
|
||||||
|
| dense cells | 43.56 | 54.10 | 89.43 | 1807.17 | 1945.50 |
|
||||||
|
| light cells | 7.96 | 9.66 | 20.19 | 21.31 | 122.44 |
|
||||||
|
| scrollling | 146.02 | 150.47 | 129.22 | 129.84 | 10140.00 |
|
||||||
|
| scrolling bottom region | 138.36 | 137.42 | 117.06 | 141.87 | 10136.00 |
|
||||||
|
| scrolling bottom small region | 137.40 | 134.66 | 128.97 | 208.77 | 9930.00 |
|
||||||
|
| scrolling fullscreen | 11.66 | 12.02 | 19.69 | 21.96 | 315.80 |
|
||||||
|
| scrolling top region | 143.81 | 133.47 | 132.51 | 475.81 | 10267.00 |
|
||||||
|
| scrolling top small region | 133.72 | 135.32 | 145.10 | 314.13 | 10074.00 |
|
||||||
|
| unicode | 20.89 | 21.78 | 26.11 | 5687.00 | 15740.00 |
|
||||||
813
doc/foot-ctlseqs.7.scd
Normal file
813
doc/foot-ctlseqs.7.scd
Normal file
|
|
@ -0,0 +1,813 @@
|
||||||
|
foot-ctlseqs(7)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
foot-ctlseqs - terminal control sequences supported by foot
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
This document describes all the control sequences supported by foot.
|
||||||
|
|
||||||
|
- Control characters
|
||||||
|
- Sequences beginning with ESC
|
||||||
|
- CSI - Control Sequence Introducer
|
||||||
|
- SGR
|
||||||
|
- Indexed and RGB colors (256-color palette and 24-bit colors)
|
||||||
|
- Private modes
|
||||||
|
- Window manipulation
|
||||||
|
- Other
|
||||||
|
- OSC - Operating System Command
|
||||||
|
- DCS - Device Control String
|
||||||
|
|
||||||
|
# Control characters
|
||||||
|
|
||||||
|
[[ *Sequence*
|
||||||
|
:[ *Name*
|
||||||
|
:< *Description*
|
||||||
|
| \\a
|
||||||
|
: BEL
|
||||||
|
: Depends on what *bell* in *foot.ini*(5) is set to.
|
||||||
|
| \\b
|
||||||
|
: BS
|
||||||
|
: Backspace; move the cursor left one step. Wrap if _bw_ is enabled.
|
||||||
|
| \\t
|
||||||
|
: HT
|
||||||
|
: Horizontal tab; move the cursor to the next tab stop.
|
||||||
|
| \\n
|
||||||
|
: LF
|
||||||
|
: Line feed; move the cursor down one step, or scroll content up if
|
||||||
|
at the bottom line.
|
||||||
|
| \\v
|
||||||
|
: VT
|
||||||
|
: Vertical tab; identical to _LF_.
|
||||||
|
| \\f
|
||||||
|
: FF
|
||||||
|
: Form feed; identical to _LF_.
|
||||||
|
| \\r
|
||||||
|
: CR
|
||||||
|
: Carriage ret; move the cursor to the leftmost column.
|
||||||
|
| \\x0E
|
||||||
|
: SO
|
||||||
|
: Shift out; select the _G1_ character set.
|
||||||
|
| \\x0F
|
||||||
|
: SI
|
||||||
|
: Shift in; select the _G0_ character set.
|
||||||
|
|
||||||
|
# Sequences beginning with ESC
|
||||||
|
|
||||||
|
Note: this table excludes sequences where ESC is part of a 7-bit
|
||||||
|
equivalent to 8-bit C1 controls.
|
||||||
|
|
||||||
|
[[ *Sequence*
|
||||||
|
:[ *Name*
|
||||||
|
:[ *Origin*
|
||||||
|
:< *Description*
|
||||||
|
| \\E 7
|
||||||
|
: DECSC
|
||||||
|
: VT100
|
||||||
|
: Save cursor position.
|
||||||
|
| \\E 8
|
||||||
|
: DECRC
|
||||||
|
: VT100
|
||||||
|
: Restore cursor position.
|
||||||
|
| \\E c
|
||||||
|
: RIS
|
||||||
|
: VT100
|
||||||
|
: Reset terminal to initial state.
|
||||||
|
| \\E D
|
||||||
|
: IND
|
||||||
|
: VT100
|
||||||
|
: Line feed; move the cursor down one step, or scroll content up if
|
||||||
|
at the bottom margin.
|
||||||
|
| \\E E
|
||||||
|
: NEL
|
||||||
|
: VT100
|
||||||
|
: Next line; move the cursor down one step, and to the first
|
||||||
|
column. Content is scrolled up if at the bottom line.
|
||||||
|
| \\E H
|
||||||
|
: HTS
|
||||||
|
: VT100
|
||||||
|
: Set one horizontal tab stop at the current position.
|
||||||
|
| \\E M
|
||||||
|
: RI
|
||||||
|
: VT100
|
||||||
|
: Reverse index; move the cursor up one step, or scroll content down
|
||||||
|
if at the top margin.
|
||||||
|
| \\E N
|
||||||
|
: SS2
|
||||||
|
: VT220
|
||||||
|
: Single shift select of G2 character set (affects next character only).
|
||||||
|
| \\E O
|
||||||
|
: SS3
|
||||||
|
: VT220
|
||||||
|
: Single shift select of G3 character set (affects next character only).
|
||||||
|
| \\E =
|
||||||
|
: DECKPAM
|
||||||
|
: VT100
|
||||||
|
: Switch keypad to _application_ mode.
|
||||||
|
| \\E >
|
||||||
|
: DECKPNM
|
||||||
|
: VT100
|
||||||
|
: Switch keypad to _numeric_ mode.
|
||||||
|
| \\E ( _C_
|
||||||
|
: SCS
|
||||||
|
: VT100
|
||||||
|
: Designate G0 character set. Supported values for _C_ are: *0* (DEC
|
||||||
|
Special Character and Line Drawing Set), and *B* (USASCII).
|
||||||
|
| \\E ) _C_
|
||||||
|
: SCS
|
||||||
|
: VT100
|
||||||
|
: Designate G1 character set. Same supported values for _C_ as in _G0_.
|
||||||
|
| \\E \* _C_
|
||||||
|
: SCS
|
||||||
|
: VT220
|
||||||
|
: Designate G2 character set. Same supported values for _C_ as in _G0_.
|
||||||
|
| \\E + _C_
|
||||||
|
: SCS
|
||||||
|
: VT220
|
||||||
|
: Designate G3 character set. Same supported values for _C_ as in _G0_.
|
||||||
|
|
||||||
|
# CSI
|
||||||
|
|
||||||
|
All sequences begin with *\\E[*, sometimes abbreviated "CSI". Spaces
|
||||||
|
are used in the sequence strings to make them easier to read, but are
|
||||||
|
not actually part of the string (i.e. *\\E[ 1 m* is really *\\E[1m*).
|
||||||
|
|
||||||
|
## SGR
|
||||||
|
|
||||||
|
All SGR sequences are in the form *\\E[* _N_ *m*, where _N_ is a decimal
|
||||||
|
number - the _parameter_. Multiple parameters can be combined in a
|
||||||
|
single CSI sequence by separating them with semicolons: *\\E[ 1;2;3
|
||||||
|
m*.
|
||||||
|
|
||||||
|
[[ *Parameter*
|
||||||
|
:< *Description*
|
||||||
|
| 0
|
||||||
|
: Reset all attributes
|
||||||
|
| 1
|
||||||
|
: Bold
|
||||||
|
| 2
|
||||||
|
: Dim
|
||||||
|
| 3
|
||||||
|
: Italic
|
||||||
|
| 4
|
||||||
|
: Underline, including styled underlines
|
||||||
|
| 5
|
||||||
|
: Blink
|
||||||
|
| 7
|
||||||
|
: Reverse video; swap foreground and background colors
|
||||||
|
| 8
|
||||||
|
: Conceal; text is not visible, but is copiable
|
||||||
|
| 9
|
||||||
|
: Crossed-out/strike
|
||||||
|
| 21
|
||||||
|
: Double underline
|
||||||
|
| 22
|
||||||
|
: Disable *bold* and *dim*
|
||||||
|
| 23
|
||||||
|
: Disable italic
|
||||||
|
| 24
|
||||||
|
: Disable underline
|
||||||
|
| 25
|
||||||
|
: Disable blink
|
||||||
|
| 27
|
||||||
|
: Disable reverse video
|
||||||
|
| 28
|
||||||
|
: Disable conceal
|
||||||
|
| 29
|
||||||
|
: Disable crossed-out
|
||||||
|
| 30-37
|
||||||
|
: Select foreground color (using *regularN* in *foot.ini*(5))
|
||||||
|
| 38
|
||||||
|
: Select foreground color, see "indexed and RGB colors" below
|
||||||
|
| 39
|
||||||
|
: Use the default foreground color (*foreground* in *foot.ini*(5))
|
||||||
|
| 40-47
|
||||||
|
: Select background color (using *regularN* in *foot.ini*(5))
|
||||||
|
| 48
|
||||||
|
: Select background color, see "indexed and RGB colors" below
|
||||||
|
| 49
|
||||||
|
: Use the default background color (*background* in *foot.ini*(5))
|
||||||
|
| 58
|
||||||
|
: Select underline color, see "indexed and RGB colors" below
|
||||||
|
| 59
|
||||||
|
: Use the default underline color
|
||||||
|
| 90-97
|
||||||
|
: Select foreground color (using *brightN* in *foot.ini*(5))
|
||||||
|
| 100-107
|
||||||
|
: Select background color (using *brightN* in *foot.ini*(5))
|
||||||
|
|
||||||
|
## Indexed and RGB colors (256-color palette and 24-bit colors)
|
||||||
|
|
||||||
|
Foot supports both the new sub-parameter based variants, and the older
|
||||||
|
parameter based variants for setting foreground and background colors.
|
||||||
|
|
||||||
|
Indexed colors:
|
||||||
|
|
||||||
|
- *\\E[ 38 : 5 :* _idx_ *m*
|
||||||
|
- *\\E[ 38 ; 5 ;* _idx_ *m*
|
||||||
|
|
||||||
|
RGB colors:
|
||||||
|
|
||||||
|
- *\\E[ 38 : 2 :* _cs_ *:* _r_ *:* _g_ *:* _b_ *m*
|
||||||
|
- *\\E[ 38 : 2 :* _r_ *:* _g_ *:* _b_ *m*
|
||||||
|
- *\\E[ 38 ; 2 ;* _r_ *;* _g_ *;* _b_ *m*
|
||||||
|
|
||||||
|
The first variant is the "correct" one (and foot also recognizes, but
|
||||||
|
ignores, the optional _tolerance_ parameters).
|
||||||
|
|
||||||
|
The second one is allowed since many programs "forget" the color space
|
||||||
|
ID, _cs_.
|
||||||
|
|
||||||
|
The sub-parameter based variants are preferred, and are what foot's
|
||||||
|
*terminfo*(5) entry uses.
|
||||||
|
|
||||||
|
## Private Modes
|
||||||
|
|
||||||
|
There are several Boolean-like "modes" that affect certain aspects
|
||||||
|
of the terminal's behavior. These modes can be manipulated with the
|
||||||
|
following 4 escape sequences:
|
||||||
|
|
||||||
|
[[ *Sequence*
|
||||||
|
:[ *Name*
|
||||||
|
:< *Description*
|
||||||
|
| \\E[ ? _Pm_ h
|
||||||
|
: DECSET
|
||||||
|
: Enable private mode
|
||||||
|
| \\E[ ? _Pm_ l
|
||||||
|
: DECRST
|
||||||
|
: Disable private mode
|
||||||
|
| \\E[ ? _Pm_ s
|
||||||
|
: XTSAVE
|
||||||
|
: Save private mode
|
||||||
|
| \\E[ ? _Pm_ r
|
||||||
|
: XTRESTORE
|
||||||
|
: Restore private mode
|
||||||
|
|
||||||
|
|
||||||
|
The _Pm_ parameter in the above sequences denotes a numerical ID
|
||||||
|
that corresponds to one of the following modes:
|
||||||
|
|
||||||
|
[[ *Parameter*
|
||||||
|
:[ *Origin*
|
||||||
|
:< *Description*
|
||||||
|
| 1
|
||||||
|
: VT100
|
||||||
|
: Cursor keys mode (DECCKM)
|
||||||
|
| 5
|
||||||
|
: VT100
|
||||||
|
: Reverse video (DECSCNM)
|
||||||
|
| 6
|
||||||
|
: VT100
|
||||||
|
: Origin mode (DECOM)
|
||||||
|
| 7
|
||||||
|
: VT100
|
||||||
|
: Auto-wrap mode (DECAWM)
|
||||||
|
| 12
|
||||||
|
: AT&T 610
|
||||||
|
: Cursor blink
|
||||||
|
| 25
|
||||||
|
: VT220
|
||||||
|
: Cursor visibility (DECTCEM)
|
||||||
|
| 45
|
||||||
|
: xterm
|
||||||
|
: Reverse-wraparound mode
|
||||||
|
| 47
|
||||||
|
: xterm
|
||||||
|
: Same as 1047 (see below)
|
||||||
|
| 66
|
||||||
|
: VT320
|
||||||
|
: Numeric keypad mode (DECNKM); same as DECKPAM/DECKPNM when enabled/disabled
|
||||||
|
| 1000
|
||||||
|
: xterm
|
||||||
|
: Send mouse x/y on button press/release
|
||||||
|
| 1001
|
||||||
|
: xterm
|
||||||
|
: Use hilite mouse tracking
|
||||||
|
| 1002
|
||||||
|
: xterm
|
||||||
|
: Use cell motion mouse tracking
|
||||||
|
| 1003
|
||||||
|
: xterm
|
||||||
|
: Use all motion mouse tracking
|
||||||
|
| 1004
|
||||||
|
: xterm
|
||||||
|
: Send FocusIn/FocusOut events
|
||||||
|
| 1006
|
||||||
|
: xterm
|
||||||
|
: SGR mouse mode
|
||||||
|
| 1007
|
||||||
|
: xterm
|
||||||
|
: Alternate scroll mode
|
||||||
|
| 1015
|
||||||
|
: urxvt
|
||||||
|
: urxvt mouse mode
|
||||||
|
| 1016
|
||||||
|
: xterm
|
||||||
|
: SGR-Pixels mouse mode
|
||||||
|
| 1034
|
||||||
|
: xterm
|
||||||
|
: 8-bit Meta mode
|
||||||
|
| 1035
|
||||||
|
: xterm
|
||||||
|
: Num Lock modifier (see xterm numLock option)
|
||||||
|
| 1036
|
||||||
|
: xterm
|
||||||
|
: Send ESC when Meta modifies a key (see xterm metaSendsEscape option)
|
||||||
|
| 1042
|
||||||
|
: xterm
|
||||||
|
: Perform action for BEL character (see *bell* in *foot.ini*(5))
|
||||||
|
| 1047
|
||||||
|
: xterm
|
||||||
|
: Use alternate screen buffer
|
||||||
|
| 1048
|
||||||
|
: xterm
|
||||||
|
: Save/restore cursor (DECSET=save, DECRST=restore)
|
||||||
|
| 1049
|
||||||
|
: xterm
|
||||||
|
: Equivalent to 1048 and 1047 combined
|
||||||
|
| 1070
|
||||||
|
: xterm
|
||||||
|
: Use private color registers for each sixel
|
||||||
|
| 2004
|
||||||
|
: xterm
|
||||||
|
: Wrap pasted text with start/end delimiters (bracketed paste mode)
|
||||||
|
| 2026
|
||||||
|
: terminal-wg
|
||||||
|
: Application synchronized updates mode
|
||||||
|
| 2027
|
||||||
|
: contour
|
||||||
|
: Grapheme cluster processing
|
||||||
|
| 2031
|
||||||
|
: contour
|
||||||
|
: Request color theme updates
|
||||||
|
| 2048
|
||||||
|
: TODO
|
||||||
|
: In-band window resize notifications
|
||||||
|
| 8452
|
||||||
|
: xterm
|
||||||
|
: Position cursor to the right of sixels, instead of on the next line
|
||||||
|
| 737769
|
||||||
|
: foot
|
||||||
|
: Input Method Editor (IME) mode
|
||||||
|
|
||||||
|
## Window manipulation
|
||||||
|
|
||||||
|
Foot implements a sub-set of XTerm's (originally dtterm's) window
|
||||||
|
manipulation sequences. The generic format is:
|
||||||
|
|
||||||
|
*\\E[ *_Ps_* ; *_Ps_* ; *_Ps_* t*
|
||||||
|
|
||||||
|
[[ *Parameter 1*
|
||||||
|
:[ *Parameter 2*
|
||||||
|
:< *Description*
|
||||||
|
| 11
|
||||||
|
: -
|
||||||
|
: Report if window is iconified. Foot always reports *1* - not iconified.
|
||||||
|
| 13
|
||||||
|
: -
|
||||||
|
: Report window position. Foot always reports (0,0), due to Wayland
|
||||||
|
limitations.
|
||||||
|
| 13
|
||||||
|
: 2
|
||||||
|
: Report text area position. Foot always reports (0,0) due to Wayland
|
||||||
|
limitations.
|
||||||
|
| 14
|
||||||
|
: -
|
||||||
|
: Report text area size, in pixels. Foot reports the grid size,
|
||||||
|
excluding the margins.
|
||||||
|
| 14
|
||||||
|
: 2
|
||||||
|
: Report window size, in pixels. Foot reports the grid size plus the
|
||||||
|
margins.
|
||||||
|
| 15
|
||||||
|
: -
|
||||||
|
: Report the screen size, in pixels.
|
||||||
|
| 16
|
||||||
|
: -
|
||||||
|
: Report the cell size, in pixels.
|
||||||
|
| 18
|
||||||
|
: -
|
||||||
|
: Report text area size, in characters.
|
||||||
|
| 19
|
||||||
|
: -
|
||||||
|
: Report screen size, in characters.
|
||||||
|
| 20
|
||||||
|
: -
|
||||||
|
: Report icon label.
|
||||||
|
| 22
|
||||||
|
: -
|
||||||
|
: Push window title+icon.
|
||||||
|
| 22
|
||||||
|
: 1
|
||||||
|
: Push window icon.
|
||||||
|
| 22
|
||||||
|
: 2
|
||||||
|
: Push window title.
|
||||||
|
| 23
|
||||||
|
: -
|
||||||
|
: Pop window title+icon.
|
||||||
|
| 23
|
||||||
|
: 1
|
||||||
|
: Pop window icon.
|
||||||
|
| 23
|
||||||
|
: 2
|
||||||
|
: Pop window title.
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
[[ *Parameter*
|
||||||
|
:[ *Name*
|
||||||
|
:[ *Origin*
|
||||||
|
:< *Description*
|
||||||
|
| \\E[ _Ps_ c
|
||||||
|
: DA
|
||||||
|
: VT100
|
||||||
|
: Send primary device attributes. Foot responds with "I'm a VT220 with
|
||||||
|
sixel and ANSI color support".
|
||||||
|
| \\E[ _Ps_ A
|
||||||
|
: CUU
|
||||||
|
: VT100
|
||||||
|
: Cursor up - move cursor up _Ps_ times.
|
||||||
|
| \\E[ _Ps_ B
|
||||||
|
: CUD
|
||||||
|
: VT100
|
||||||
|
: Cursor down - move cursor down _Ps_ times.
|
||||||
|
| \\E[ _Ps_ C
|
||||||
|
: CUF
|
||||||
|
: VT100
|
||||||
|
: Cursor forward - move cursor to the right _Ps_ times.
|
||||||
|
| \\E[ _Ps_ D
|
||||||
|
: CUB
|
||||||
|
: VT100
|
||||||
|
: Cursor backward - move cursor to the left _Ps_ times.
|
||||||
|
| \\E[ _Ps_ g
|
||||||
|
: TBC
|
||||||
|
: VT100
|
||||||
|
: Tab clear. _Ps_=0 -> clear current column. _Ps_=3 -> clear all.
|
||||||
|
| \\E[ _Ps_ ; _Ps_ f
|
||||||
|
: HVP
|
||||||
|
: VT100
|
||||||
|
: Horizontal and vertical position - move cursor to _row_ ; _column_.
|
||||||
|
| \\E[ _Ps_ ; _Ps_ H
|
||||||
|
: CUP
|
||||||
|
: VT100
|
||||||
|
: Cursor position - move cursor to _row_ ; _column_.
|
||||||
|
| \\E[ _Ps_ J
|
||||||
|
: ED
|
||||||
|
: VT100
|
||||||
|
: Erase in display. _Ps_=0 -> below cursor. _Ps_=1 -> above
|
||||||
|
| \\E[ _Ps_ K
|
||||||
|
: EL
|
||||||
|
: VT100
|
||||||
|
: Erase in line. _Ps_=0 -> right of cursor. _Ps_=1 -> left of
|
||||||
|
cursor. _Ps_=2 -> all.
|
||||||
|
| \\E[ _Pm_ h
|
||||||
|
: SM
|
||||||
|
: VT100
|
||||||
|
: Set mode. _Pm_=4 -> enable IRM (Insertion Replacement Mode). All
|
||||||
|
other values of _Pm_ are unsupported.
|
||||||
|
| \\E[ _Pm_ l
|
||||||
|
: RM
|
||||||
|
: VT100
|
||||||
|
: Reset mode. _Pm_=4 -> disable IRM (Insertion Replacement Mode). All
|
||||||
|
other values of _Pm_ are unsupported.
|
||||||
|
| \\E[ _Ps_ n
|
||||||
|
: DSR
|
||||||
|
: VT100
|
||||||
|
: Device status report. _Ps_=5 -> device status. _Ps_=6 -> cursor
|
||||||
|
position.
|
||||||
|
| \\E[ _Ps_ L
|
||||||
|
: IL
|
||||||
|
: VT220
|
||||||
|
: Insert _Ps_ lines.
|
||||||
|
| \\E[ _Ps_ M
|
||||||
|
: DL
|
||||||
|
: VT220
|
||||||
|
: Delete _Ps_ lines.
|
||||||
|
| \\E[ _Ps_ P
|
||||||
|
: DCH
|
||||||
|
: VT220
|
||||||
|
: Delete _Ps_ characters.
|
||||||
|
| \\E[ _Ps_ @
|
||||||
|
: ICH
|
||||||
|
: VT220
|
||||||
|
: Insert _Ps_ blank characters.
|
||||||
|
| \\E[ _Ps_ X
|
||||||
|
: ECH
|
||||||
|
: VT220
|
||||||
|
: Erase _Ps_ characters.
|
||||||
|
| \\E[ > c
|
||||||
|
: DA2
|
||||||
|
: VT220
|
||||||
|
: Send secondary device attributes. Foot responds with "I'm a VT220
|
||||||
|
and here's my version number".
|
||||||
|
| \\E[ ! p
|
||||||
|
: DECSTR
|
||||||
|
: VT220
|
||||||
|
: Soft terminal reset.
|
||||||
|
| \\E[ ? _Ps_ $ p
|
||||||
|
: DECRQM
|
||||||
|
: VT320
|
||||||
|
: Request status of DEC private mode. The _Ps_ parameter corresponds
|
||||||
|
to one of the values mentioned in the "Private Modes" section above
|
||||||
|
(as set with DECSET/DECRST).
|
||||||
|
| \\E[ _Ps_ $ p
|
||||||
|
: DECRQM
|
||||||
|
: VT320
|
||||||
|
: Request status of ECMA-48/ANSI mode. See the descriptions for SM/RM
|
||||||
|
above for recognized _Ps_ values.
|
||||||
|
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pm_ $ r
|
||||||
|
: DECCARA
|
||||||
|
: VT400
|
||||||
|
: Change attributes in rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_
|
||||||
|
denotes the rectangle, _Pm_ denotes the SGR attributes.
|
||||||
|
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pm_ $ t
|
||||||
|
: DECRARA
|
||||||
|
: VT400
|
||||||
|
: Invert attributes in rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_
|
||||||
|
denotes the rectangle, _Pm_ denotes the SGR attributes.
|
||||||
|
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pp_ ; _Pt_ ; _Pl_ ; _Pp_ $ v
|
||||||
|
: DECCRA
|
||||||
|
: VT400
|
||||||
|
: Copy rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_ denotes the
|
||||||
|
rectangle, _Pt_ and _Pl_ denotes the target location.
|
||||||
|
| \\E[ _Pc_ ; _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ $ x
|
||||||
|
: DECFRA
|
||||||
|
: VT420
|
||||||
|
: Fill rectangular area. _Pc_ is the character to use, _Pt_, _Pl_,
|
||||||
|
_Pb_ and _Pr_ denotes the rectangle.
|
||||||
|
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ $ z
|
||||||
|
: DECERA
|
||||||
|
: VT400
|
||||||
|
: Erase rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_ denotes the
|
||||||
|
rectangle.
|
||||||
|
| \\E[ _Ps_ T
|
||||||
|
: SD
|
||||||
|
: VT420
|
||||||
|
: Scroll down _Ps_ lines.
|
||||||
|
| \\E[ s
|
||||||
|
: SCOSC
|
||||||
|
: SCO, VT510
|
||||||
|
: Save cursor position.
|
||||||
|
| \\E[ u
|
||||||
|
: SCORC
|
||||||
|
: SCO, VT510
|
||||||
|
: Restore cursor position.
|
||||||
|
| \\E[ _Ps_ SP q
|
||||||
|
: DECSCUSR
|
||||||
|
: VT510
|
||||||
|
: Set cursor style. In foot, _Ps_=0 means "use style from foot.ini".
|
||||||
|
| \\E[ = _Ps_ c
|
||||||
|
: DA3
|
||||||
|
: VT510
|
||||||
|
: Send tertiary device attributes. Foot responds with "FOOT", in
|
||||||
|
hexadecimal.
|
||||||
|
| \\E[ _Pm_ d
|
||||||
|
: VPA
|
||||||
|
: ECMA-48
|
||||||
|
: Line position absolute - move cursor to line _Pm_.
|
||||||
|
| \\E[ _Pm_ e
|
||||||
|
: VPR
|
||||||
|
: ECMA-48
|
||||||
|
: Line position relative - move cursor down _Pm_ lines.
|
||||||
|
| \\E[ _Pm_ a
|
||||||
|
: HPR
|
||||||
|
: ECMA-48
|
||||||
|
: Character position relative - move cursor to the right _Pm_ times.
|
||||||
|
| \\E[ _Ps_ E
|
||||||
|
: CNL
|
||||||
|
: ECMA-48
|
||||||
|
: Cursor next line - move the cursor down _Ps_ times.
|
||||||
|
| \\E[ _Ps_ F
|
||||||
|
: CPL
|
||||||
|
: ECMA-48
|
||||||
|
: Cursor preceding line - move the cursor up _Ps_ times.
|
||||||
|
| \\E[ _Pm_ `
|
||||||
|
: HPA
|
||||||
|
: ECMA-48
|
||||||
|
: Character position absolute - move cursor to column _Pm_.
|
||||||
|
| \\E[ _Ps_ G
|
||||||
|
: CHA
|
||||||
|
: ECMA-48
|
||||||
|
: Cursor character absolute - move cursor to column _Ps_.
|
||||||
|
cursor. _Ps_=2 -> all. _Ps_=3 -> saved lines.
|
||||||
|
| \\E[ _Ps_ S
|
||||||
|
: SU
|
||||||
|
: ECMA-48
|
||||||
|
: Scroll up _Ps_ lines.
|
||||||
|
| \\E[ _Ps_ I
|
||||||
|
: CHT
|
||||||
|
: ECMA-48
|
||||||
|
: Cursor forward tabulation _Ps_ tab stops.
|
||||||
|
| \\E[ _Ps_ Z
|
||||||
|
: CBT
|
||||||
|
: ECMA-48
|
||||||
|
: Cursor backward tabulation _Ps_ tab stops.
|
||||||
|
| \\E[ _Ps_ b
|
||||||
|
: REP
|
||||||
|
: ECMA-48
|
||||||
|
: Repeat the preceding printable character _Ps_ times.
|
||||||
|
| \\E[ ? _Pi_ ; _Pa_ ; _Pv_ S
|
||||||
|
: XTSMGRAPHICS
|
||||||
|
: xterm
|
||||||
|
: Set or request sixel attributes.
|
||||||
|
| \\E[ > _Ps_ q
|
||||||
|
: XTVERSION
|
||||||
|
: xterm
|
||||||
|
: _Ps_=0 -> report terminal name and version, in the form
|
||||||
|
*\\EP>|foot(version)\\E\\*.
|
||||||
|
| \\E[ > 4 ; _Pv_ m
|
||||||
|
: XTMODKEYS
|
||||||
|
: xterm
|
||||||
|
: Set level of the _modifyOtherKeys_ property to _Pv_. Note that foot
|
||||||
|
only supports level 1 and 2, where level 1 is the default setting.
|
||||||
|
| \\E[ ? _Pp_ m
|
||||||
|
: XTQMODKEYS
|
||||||
|
: xterm
|
||||||
|
: Query key modifier options
|
||||||
|
| \\E[ > 4 n
|
||||||
|
: <unnamed>
|
||||||
|
: xterm
|
||||||
|
: Resets the _modifyOtherKeys_ property to level 1. Note that in foot,
|
||||||
|
this sequence does not completely disable _modifyOtherKeys_, since
|
||||||
|
foot only supports level 1 and level 2 (and not level 0).
|
||||||
|
| \\E[ ? u
|
||||||
|
: <unnamed>
|
||||||
|
: kitty
|
||||||
|
: Query current values of the Kitty keyboard flags.
|
||||||
|
| \\E[ > _flags_ u
|
||||||
|
: <unnamed>
|
||||||
|
: kitty
|
||||||
|
: Push a new entry, _flags_, to the Kitty keyboard stack.
|
||||||
|
| \\E[ < _number_ u
|
||||||
|
: <unnamed>
|
||||||
|
: kitty
|
||||||
|
: Pop _number_ of entries from the Kitty keyboard stack.
|
||||||
|
| \\E[ = _flags_ ; _mode_ u
|
||||||
|
: <unnamed>
|
||||||
|
: kitty
|
||||||
|
: Update current Kitty keyboard flags, according to _mode_.
|
||||||
|
| \\E[ # P
|
||||||
|
: XTPUSHCOLORS
|
||||||
|
: xterm
|
||||||
|
: Push current color palette onto stack
|
||||||
|
| \\E[ # Q
|
||||||
|
: XTPOPCOLORS
|
||||||
|
: xterm
|
||||||
|
: Pop color palette from stack
|
||||||
|
| \\E[ # R
|
||||||
|
: XTREPORTCOLORS
|
||||||
|
: xterm
|
||||||
|
: Report the current entry on the palette stack, and the number of
|
||||||
|
palettes stored on the stack.
|
||||||
|
| \\E[ ? 996 n
|
||||||
|
: Query the current (color) theme mode
|
||||||
|
: contour
|
||||||
|
: The current color theme mode (light or dark) is reported as *CSI ?
|
||||||
|
997 ; 1|2 n*, where *1* means dark and *2* light. By convention, the
|
||||||
|
primary theme in foot is considered dark, and the alternative theme
|
||||||
|
light.
|
||||||
|
|
||||||
|
|
||||||
|
# OSC
|
||||||
|
|
||||||
|
All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_.
|
||||||
|
|
||||||
|
[[ *Sequence*
|
||||||
|
:[ *Origin*
|
||||||
|
:< *Description*
|
||||||
|
| \\E] 0 ; _Pt_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Set window icon and title to _Pt_.
|
||||||
|
| \\E] 1 ; _Pt_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Set window icon to _Pt_.
|
||||||
|
| \\E] 2 ; _Pt_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Set window title to _Pt_
|
||||||
|
| \\E] 4 ; _c_ ; _spec_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Change color number _c_ to _spec_, where _spec_ is a color in
|
||||||
|
XParseColor format. foot only supports RGB colors; either
|
||||||
|
*rgb:<red>/<green>/<blue>*, or the legacy format (*#rgb*).
|
||||||
|
| \\E] 7 ; _Uri_ \\E\\
|
||||||
|
: iTerm2
|
||||||
|
: Update the terminal's current working directory. Newly spawned
|
||||||
|
terminals will launch in this directory. _Uri_ must be in the format
|
||||||
|
*file://<hostname>/<path>*. *hostname* must refer to your local host.
|
||||||
|
| \\E] 8 ; id=_ID_ ; _Uri_ \\E\\
|
||||||
|
: VTE+iTerm2
|
||||||
|
: Hyperlink (a.k.a HTML-like anchors). id=_ID_ is optional; if assigned,
|
||||||
|
all URIs with the same _ID_ will be treated as a single
|
||||||
|
hyperlink. An empty URI closes the hyperlink.
|
||||||
|
| \\E] 9 ; _msg_ \\E\\
|
||||||
|
: iTerm2
|
||||||
|
: Desktop notification, uses *notify* in *foot.ini*(5).
|
||||||
|
| \\E] 10 ; _spec_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Change the default foreground color to _spec_, a color in
|
||||||
|
XParseColor format.
|
||||||
|
| \\E] 11 ; _spec_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Change the default background color to _spec_, a color in
|
||||||
|
XParseColor format. Foot implements URxvt's transparency extension;
|
||||||
|
e.g. _spec_=*[75]#ff00ff* or _spec_=*rgba:ff/00/ff/bf* (pink with
|
||||||
|
75% alpha).
|
||||||
|
| \\E] 12 ; _spec_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Change cursor color to _spec_, a color in XParseColor format.
|
||||||
|
| \\E] 17 ; _spec_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Change selection background color to _spec_, a color in
|
||||||
|
XParseColor format.
|
||||||
|
| \\E] 19 ; _spec_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Change selection foreground color to _spec_, a color in XParseColor
|
||||||
|
format.
|
||||||
|
| \\E] 22 ; _xcursor-pointer-name_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Sets the xcursor pointer. An empty name, or an invalid name resets
|
||||||
|
it.
|
||||||
|
| \\E] 52 ; _Pc_ ; ? \\E\\
|
||||||
|
: xterm
|
||||||
|
: Send clipboard data. _Pc_ can be either *c*, *s* or *p*. *c* uses
|
||||||
|
the clipboard as source, and *s* and *p* uses the primary
|
||||||
|
selection. The response is *\\E] 52 ; Pc ; <base64-encoded data>
|
||||||
|
\E\\*, where _Pc_ denotes the source used.
|
||||||
|
| \\E] 52 ; _Pc_ ; _Pd_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Copy _Pd_ (base64 encoded text) to the clipboard. _Pc_ denotes the
|
||||||
|
target: *c* targets the clipboard and *s* and *p* the primary
|
||||||
|
selection.
|
||||||
|
| \\E] 66 ; _params_ ; text \\E\\
|
||||||
|
: kitty
|
||||||
|
: Text sizing protocol (only 'w', width, supported)
|
||||||
|
| \\E] 99 ; _params_ ; _payload_ \\E\\
|
||||||
|
: kitty
|
||||||
|
: Desktop notification; uses *desktop-notifications.command* in
|
||||||
|
*foot.ini*(5).
|
||||||
|
| \\E] 104 ; _c_ \\E\\
|
||||||
|
: xterm
|
||||||
|
: Reset color number _c_ (multiple semicolon separated _c_ values may
|
||||||
|
be provided), or all colors (excluding the default
|
||||||
|
foreground/background colors) if _c_ is omitted.
|
||||||
|
| \\E] 110 \\E\\
|
||||||
|
: xterm
|
||||||
|
: Reset default foreground color
|
||||||
|
| \\E] 111 \\E\\
|
||||||
|
: xterm
|
||||||
|
: Reset default background color
|
||||||
|
| \\E] 112 \\E\\
|
||||||
|
: xterm
|
||||||
|
: Reset cursor color
|
||||||
|
| \\E] 117 \\E\\
|
||||||
|
: xterm
|
||||||
|
: Reset selection background color
|
||||||
|
| \\E] 119 \\E\\
|
||||||
|
: xterm
|
||||||
|
: Reset selection foreground color
|
||||||
|
| \\E] 133 ; A \\E\\
|
||||||
|
: FinalTerm
|
||||||
|
: Mark start of shell prompt
|
||||||
|
| \\E] 133 ; C \\E\\
|
||||||
|
: FinalTerm
|
||||||
|
: Mark start of command output
|
||||||
|
| \\E] 133 ; D \\E\\
|
||||||
|
: FinalTerm
|
||||||
|
: Mark end of command output
|
||||||
|
| \\E] 176 ; _app-id_ \\E\\
|
||||||
|
: foot
|
||||||
|
: Set app ID. _app-id_ is optional; if assigned,
|
||||||
|
the terminal window App ID will be set to the value.
|
||||||
|
An empty App ID resets the value to the default.
|
||||||
|
| \\E] 555 \\E\\
|
||||||
|
: foot
|
||||||
|
: Flash the entire terminal (foot extension)
|
||||||
|
| \\E] 777;notify;_title_;_msg_ \\E\\
|
||||||
|
: urxvt
|
||||||
|
: Desktop notification, uses *desktop-notifications.command* in
|
||||||
|
*foot.ini*(5).
|
||||||
|
|
||||||
|
# DCS
|
||||||
|
|
||||||
|
All _DCS_ sequences begin with *\\EP* (sometimes abbreviated _DCS_),
|
||||||
|
and are terminated by *\\E\\* (ST).
|
||||||
|
|
||||||
|
[[ *Sequence*
|
||||||
|
:< *Description*
|
||||||
|
| \\EP q <sixel data> \\E\\
|
||||||
|
: Emit a sixel image at the current cursor position
|
||||||
|
| \\EP $ q <query> \\E\\
|
||||||
|
: Request selection or setting (DECRQSS). Implemented queries:
|
||||||
|
DECSTBM, SGR and DECSCUSR.
|
||||||
|
| \\EP = _C_ s \\E\\
|
||||||
|
: 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 <hex encoded capability name> \\E\\
|
||||||
|
: Query builtin terminfo database (XTGETTCAP)
|
||||||
|
|
||||||
|
|
||||||
|
# FOOTNOTE
|
||||||
|
|
||||||
|
Foot does not support 8-bit control characters ("C1").
|
||||||
735
doc/foot.1.scd
Normal file
735
doc/foot.1.scd
Normal file
|
|
@ -0,0 +1,735 @@
|
||||||
|
foot(1)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
foot - Wayland terminal emulator
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
|
||||||
|
*foot* [_OPTIONS_]++
|
||||||
|
*foot* [_OPTIONS_] <_command_> [_COMMAND OPTIONS_]
|
||||||
|
|
||||||
|
All trailing (non-option) arguments are treated as a command, and its
|
||||||
|
arguments, to execute (instead of the default shell).
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
*foot* is a Wayland terminal emulator. Running it without arguments
|
||||||
|
will start a new terminal window with your default shell.
|
||||||
|
|
||||||
|
You can override the default shell by appending a custom command to
|
||||||
|
the foot command line
|
||||||
|
|
||||||
|
*foot htop*
|
||||||
|
|
||||||
|
# OPTIONS
|
||||||
|
|
||||||
|
*-c*,*--config*=_PATH_
|
||||||
|
Path to configuration file, see *foot.ini*(5) for details.
|
||||||
|
|
||||||
|
The configuration file is automatically passed to new terminals
|
||||||
|
spawned via *spawn-terminal* (see *foot.ini*(5)).
|
||||||
|
|
||||||
|
*-C*,*--check-config*
|
||||||
|
Verify configuration and then exit with 0 if ok, otherwise exit
|
||||||
|
with 230 (see *EXIT STATUS*).
|
||||||
|
|
||||||
|
*-o*,*--override*=[_SECTION_.]_KEY_=_VALUE_
|
||||||
|
Override an option set in the configuration file. If _SECTION_ is not
|
||||||
|
given, defaults to _main_.
|
||||||
|
|
||||||
|
*-f*,*--font*=_FONT_
|
||||||
|
Comma separated list of fonts to use, in fontconfig format (see
|
||||||
|
*FONT FORMAT*).
|
||||||
|
|
||||||
|
The first font is the primary font. The remaining fonts are
|
||||||
|
fallback fonts that will be used whenever a glyph cannot be found
|
||||||
|
in the primary font.
|
||||||
|
|
||||||
|
The fallback fonts are searched in the order they appear. If a
|
||||||
|
glyph cannot be found in any of the fallback fonts, the dynamic
|
||||||
|
fallback list from fontconfig (for the primary font) is
|
||||||
|
searched.
|
||||||
|
|
||||||
|
Default: _monospace_.
|
||||||
|
|
||||||
|
*-w*,*--window-size-pixels*=_WIDTHxHEIGHT_
|
||||||
|
Set initial window width and height, in pixels. Default: _700x500_.
|
||||||
|
|
||||||
|
*-W*,*--window-size-chars*=_WIDTHxHEIGHT_
|
||||||
|
Set initial window width and height, in characters. Default: _not set_.
|
||||||
|
|
||||||
|
*-t*,*--term*=_TERM_
|
||||||
|
Value to set the environment variable *TERM* to (see *TERMINFO*
|
||||||
|
and *ENVIRONMENT*). Default: _@default_terminfo@_.
|
||||||
|
|
||||||
|
*-T*,*--title*=_TITLE_
|
||||||
|
Initial window title. Default: _foot_.
|
||||||
|
|
||||||
|
*-a*,*--app-id*=_ID_
|
||||||
|
Value to set the *app-id* property on the Wayland window
|
||||||
|
to. Default: _foot_ (normal mode), or _footclient_ (server mode).
|
||||||
|
|
||||||
|
*toplevel-tag*=_TAG_
|
||||||
|
Value to set the *toplevel-tag* property on the Wayland window
|
||||||
|
to. The compositor can use this value for session management,
|
||||||
|
window rules etc. Default: _not set_
|
||||||
|
|
||||||
|
*-m*,*--maximized*
|
||||||
|
Start in maximized mode. If both *--maximized* and *--fullscreen*
|
||||||
|
are specified, the _last_ one takes precedence.
|
||||||
|
|
||||||
|
*-F*,*--fullscreen*
|
||||||
|
Start in fullscreen mode. If both *--maximized* and *--fullscreen*
|
||||||
|
are specified, the _last_ one takes precedence.
|
||||||
|
|
||||||
|
*-L*,*--login-shell*
|
||||||
|
Start a login shell, by prepending a '-' to argv[0].
|
||||||
|
|
||||||
|
*--pty*
|
||||||
|
Display an existing pty instead of creating one. This is useful
|
||||||
|
for interacting with VM consoles.
|
||||||
|
|
||||||
|
This option is not currently supported in combination with
|
||||||
|
*-s*,*--server*.
|
||||||
|
|
||||||
|
*-D*,*--working-directory*=_DIR_
|
||||||
|
Initial working directory for the client application. Default:
|
||||||
|
_CWD of foot_.
|
||||||
|
|
||||||
|
*-s*,*--server*[=_PATH_|_FD_]
|
||||||
|
Run as a server. In this mode, a single foot instance hosts
|
||||||
|
multiple terminals (windows). Use *footclient*(1) to launch new
|
||||||
|
terminals.
|
||||||
|
|
||||||
|
This saves some memory since for example fonts and glyph caches
|
||||||
|
can be shared between the terminals.
|
||||||
|
|
||||||
|
It also saves upstart time since the config has already been
|
||||||
|
loaded and parsed, and most importantly, fonts have already been
|
||||||
|
loaded (and their glyph caches are likely to already have been
|
||||||
|
populated).
|
||||||
|
|
||||||
|
Each terminal will have its own rendering threads, but all Wayland
|
||||||
|
communication, as well as input/output to the shell, is
|
||||||
|
multiplexed in the main thread. Thus, this mode might result in
|
||||||
|
slightly worse performance when multiple terminals are under heavy
|
||||||
|
load.
|
||||||
|
|
||||||
|
Also be aware that should one terminal crash, it will take all the
|
||||||
|
others with it.
|
||||||
|
|
||||||
|
The default path is
|
||||||
|
*$XDG\_RUNTIME\_DIR/foot-$WAYLAND\_DISPLAY.sock*.
|
||||||
|
|
||||||
|
If *$XDG\_RUNTIME\_DIR* is not set, the default path is instead
|
||||||
|
*/tmp/foot.sock*.
|
||||||
|
|
||||||
|
If *$XDG\_RUNTIME\_DIR* is set, but *$WAYLAND\_DISPLAY* is not,
|
||||||
|
the default path is *$XDG\_RUNTIME\_DIR/foot.sock*.
|
||||||
|
|
||||||
|
Note that if you change the default, you will also need to use the
|
||||||
|
*--server-socket* option in *footclient*(1) and point it to your
|
||||||
|
custom socket path.
|
||||||
|
|
||||||
|
If the argument is a number, foot will interpret it as the file descriptor
|
||||||
|
of a socket provided by a supervision daemon (such as systemd or s6), and
|
||||||
|
use that socket as it's own.
|
||||||
|
|
||||||
|
Two systemd units (foot-server.{service,socket}) are provided to use that
|
||||||
|
feature with systemd. To use socket activation, only enable the
|
||||||
|
socket unit.
|
||||||
|
|
||||||
|
Note that starting *foot --server* as a systemd service will use
|
||||||
|
the environment of the systemd user instance; thus, you'll need
|
||||||
|
to import *$WAYLAND_DISPLAY* in it using *systemctl --user
|
||||||
|
import-environment WAYLAND_DISPLAY*.
|
||||||
|
|
||||||
|
*-H*,*--hold*
|
||||||
|
Remain open after child process exits.
|
||||||
|
|
||||||
|
*-p*,*--print-pid*=_FILE_|_FD_
|
||||||
|
Print PID to this file, or FD, when successfully started. The file
|
||||||
|
(or FD) is closed immediately after writing the PID. When a _FILE_
|
||||||
|
as been specified, the file is unlinked at exit.
|
||||||
|
|
||||||
|
This option can only be used in combination with *-s*,*--server*.
|
||||||
|
|
||||||
|
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
|
||||||
|
Log level, used both for log output on stderr as well as
|
||||||
|
syslog. Default: _warning_.
|
||||||
|
|
||||||
|
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
|
||||||
|
Enables or disables colorization of log output on stderr. Default:
|
||||||
|
_auto_.
|
||||||
|
|
||||||
|
*-S*,*--log-no-syslog*
|
||||||
|
Disables syslog logging. Logging is only done on stderr. This
|
||||||
|
option can only be used in combination with *-s*,*--server*.
|
||||||
|
|
||||||
|
*-v*,*--version*
|
||||||
|
Show the version number and quit.
|
||||||
|
|
||||||
|
*-e*
|
||||||
|
Ignored; for compatibility with *xterm -e*.
|
||||||
|
|
||||||
|
This option was added in response to several program launchers
|
||||||
|
passing *-e* to arbitrary terminals, under the assumption that
|
||||||
|
they all implement the same semantics for it as *xterm*(1).
|
||||||
|
Ignoring it allows foot to be invoked as e.g. *foot -e man foot*
|
||||||
|
with the same results as with xterm, instead of producing an
|
||||||
|
"invalid option" error.
|
||||||
|
|
||||||
|
# KEYBOARD SHORTCUTS
|
||||||
|
|
||||||
|
The following keyboard shortcuts are available by default. They can be
|
||||||
|
changed in *foot.ini*(5). There are also more actions (disabled by
|
||||||
|
default) available; see *foot.ini*(5).
|
||||||
|
|
||||||
|
## NORMAL MODE
|
||||||
|
|
||||||
|
*shift*+*page up*/*page down*
|
||||||
|
Scroll up/down in history
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*c*, *XF86Copy*
|
||||||
|
Copy selected text to the _clipboard_
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*v*, *XF86Paste*
|
||||||
|
Paste from _clipboard_
|
||||||
|
|
||||||
|
*shift*+*insert*
|
||||||
|
Paste from the _primary selection_
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*r*
|
||||||
|
Start a scrollback search
|
||||||
|
|
||||||
|
*ctrl*+*+*, *ctrl*+*=*
|
||||||
|
Increase font size
|
||||||
|
|
||||||
|
*ctrl*+*-*
|
||||||
|
Decrease font size
|
||||||
|
|
||||||
|
*ctrl*+*0*
|
||||||
|
Reset font size
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*n*
|
||||||
|
Spawn a new terminal. If the shell has been configured to emit the
|
||||||
|
_OSC 7_ escape sequence, the new terminal will start in the
|
||||||
|
current working directory.
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*o*
|
||||||
|
Activate URL mode, allowing you to "launch" URLs.
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*u*
|
||||||
|
Activate Unicode input.
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*z*
|
||||||
|
Jump to the previous, currently not visible, prompt. Requires
|
||||||
|
shell integration.
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*x*
|
||||||
|
Jump to the next prompt. Requires shell integration.
|
||||||
|
|
||||||
|
## SCROLLBACK SEARCH
|
||||||
|
|
||||||
|
These keyboard shortcuts affect the search selection:
|
||||||
|
|
||||||
|
*ctrl*+*r*
|
||||||
|
Search _backward_ for the next match. If the search string is
|
||||||
|
empty, the last searched-for string is used.
|
||||||
|
|
||||||
|
*ctrl*+*s*
|
||||||
|
Search _forward_ for the next match. If the search string is
|
||||||
|
empty, the last searched-for string is used.
|
||||||
|
|
||||||
|
*shift*+*right*
|
||||||
|
Extend current selection to the right by one character.
|
||||||
|
|
||||||
|
*shift*+*left*
|
||||||
|
Extend current selection to the left by one character.
|
||||||
|
|
||||||
|
*ctrl*+*w*, *ctrl*+*shift*+*right*
|
||||||
|
Extend current selection (and thus the search criteria) to the end
|
||||||
|
of the word, or the next word if currently at a word separating
|
||||||
|
character.
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*w*
|
||||||
|
Same as *ctrl*+*w*, except that the only word separating
|
||||||
|
characters are whitespace characters.
|
||||||
|
|
||||||
|
*ctrl*+*shift*+*left*
|
||||||
|
Extend current selection to the left to the last word boundary.
|
||||||
|
|
||||||
|
*shift*+*down*
|
||||||
|
Extend current selection down one line
|
||||||
|
|
||||||
|
*shift*+*up*
|
||||||
|
Extend current selection up one line.
|
||||||
|
|
||||||
|
*ctrl*+*v*, *ctrl*+*shift*+*v*, *ctrl*+*y*, *XF86Paste*
|
||||||
|
Paste from clipboard into the search buffer.
|
||||||
|
|
||||||
|
*shift*+*insert*
|
||||||
|
Paste from primary selection into the search buffer.
|
||||||
|
|
||||||
|
*escape*, *ctrl*+*g*, *ctrl*+*c*
|
||||||
|
Cancel the search
|
||||||
|
|
||||||
|
*return*
|
||||||
|
Finish the search and copy the current match to the primary
|
||||||
|
selection. The terminal selection is kept, allowing you to press
|
||||||
|
*ctrl*+*shift*+*c* to copy it to the clipboard.
|
||||||
|
|
||||||
|
These shortcuts affect the search box in scrollback-search mode:
|
||||||
|
|
||||||
|
*ctrl*+*b*
|
||||||
|
Moves the cursor in the search box one **character** to the left.
|
||||||
|
|
||||||
|
*ctrl*+*left*, *alt*+*b*
|
||||||
|
Moves the cursor in the search box one **word** to the left.
|
||||||
|
|
||||||
|
*ctrl*+*f*
|
||||||
|
Moves the cursor in the search box one **character** to the right.
|
||||||
|
|
||||||
|
*ctrl*+*right*, *alt*+*f*
|
||||||
|
Moves the cursor in the search box one **word** to the right.
|
||||||
|
|
||||||
|
*Home*, *ctrl*+*a*
|
||||||
|
Moves the cursor in the search box to the beginning of the input.
|
||||||
|
|
||||||
|
*End*, *ctrl*+*e*
|
||||||
|
Moves the cursor in the search box to the end of the input.
|
||||||
|
|
||||||
|
*alt*+*backspace*, *ctrl*+*backspace*
|
||||||
|
Deletes the **word before** the cursor.
|
||||||
|
|
||||||
|
*alt*+*delete*, *ctrl*+*delete*
|
||||||
|
Deletes the **word after** the cursor.
|
||||||
|
|
||||||
|
*ctrl*+*u*
|
||||||
|
Deletes from the cursor to the start of the input
|
||||||
|
|
||||||
|
*ctrl*+*k*
|
||||||
|
Deletes from the cursor to the end of the input
|
||||||
|
|
||||||
|
These shortcuts affect scrolling in scrollback-search mode:
|
||||||
|
|
||||||
|
*shift*+*page-up*
|
||||||
|
Scrolls up/back one page in history.
|
||||||
|
|
||||||
|
*shift*+*page-down*
|
||||||
|
Scroll down/forward one page in history.
|
||||||
|
|
||||||
|
## URL MODE
|
||||||
|
|
||||||
|
*t*
|
||||||
|
Toggle URL visibility in jump label.
|
||||||
|
|
||||||
|
*escape*, *ctrl*+*g*, *ctrl*+*c*, *ctrl*+*d*
|
||||||
|
Exit URL mode without launching a URL.
|
||||||
|
|
||||||
|
## MOUSE SHORTCUTS
|
||||||
|
|
||||||
|
*left*, single-click
|
||||||
|
Drag to select; when released, the selected text is copied to the
|
||||||
|
_primary_ selection. This feature is normally *disabled* whenever
|
||||||
|
the client has enabled _mouse tracking_, but can be forced by
|
||||||
|
holding *shift*.
|
||||||
|
|
||||||
|
Holding *ctrl* will create a block selection.
|
||||||
|
|
||||||
|
*left*, double-click
|
||||||
|
Selects the _word_ (separated by spaces, period, comma,
|
||||||
|
parenthesis etc) under the pointer. Hold *ctrl* to select
|
||||||
|
everything under the pointer up to, and until, the next space
|
||||||
|
characters.
|
||||||
|
|
||||||
|
*left*, triple-click
|
||||||
|
Selects the everything between enclosing quotes, or the entire row
|
||||||
|
if not inside a quote.
|
||||||
|
|
||||||
|
*left*, quad-click
|
||||||
|
Selects the entire row
|
||||||
|
|
||||||
|
*middle*
|
||||||
|
Paste from the _primary_ selection
|
||||||
|
|
||||||
|
*right*
|
||||||
|
Extend current selection. Clicking immediately extends the
|
||||||
|
selection, while hold-and-drag allows you to interactively resize
|
||||||
|
the selection.
|
||||||
|
|
||||||
|
*ctrl*+*right*
|
||||||
|
Extend the current selection, but force it to be character wise,
|
||||||
|
rather than depending on the original selection mode.
|
||||||
|
|
||||||
|
*wheel*
|
||||||
|
Scroll up/down in history
|
||||||
|
|
||||||
|
*ctrl*+*wheel*
|
||||||
|
Increase/decrease font size
|
||||||
|
|
||||||
|
## TOUCHSCREEN
|
||||||
|
|
||||||
|
*tap*
|
||||||
|
Emulates mouse left button click.
|
||||||
|
|
||||||
|
*drag*
|
||||||
|
Scrolls up/down in history.
|
||||||
|
|
||||||
|
Holding for a while before dragging (time delay can be configured)
|
||||||
|
emulates mouse dragging with left button held.
|
||||||
|
|
||||||
|
|
||||||
|
# FONT FORMAT
|
||||||
|
|
||||||
|
The font is specified in FontConfig syntax. That is, a colon-separated
|
||||||
|
list of font name and font options.
|
||||||
|
|
||||||
|
_Examples_:
|
||||||
|
- Dina:weight=bold:slant=italic
|
||||||
|
- Courier New:size=12
|
||||||
|
|
||||||
|
# URLs
|
||||||
|
|
||||||
|
Foot supports URL detection. But, unlike many other terminal
|
||||||
|
emulators, where URLs are highlighted when they are hovered and opened
|
||||||
|
by clicking on them, foot uses a keyboard driven approach.
|
||||||
|
|
||||||
|
Pressing *ctrl*+*shift*+*o* enters _"Open URL mode"_, where all currently
|
||||||
|
visible URLs are underlined, and is associated with a
|
||||||
|
_"jump-label"_. The jump-label indicates the _key sequence_
|
||||||
|
(e.g. *"AF"*) to use to activate the URL.
|
||||||
|
|
||||||
|
The key binding can, of course, be customized, like all other key
|
||||||
|
bindings in foot. See *show-urls-launch* and *show-urls-copy* in
|
||||||
|
*foot.ini*(5).
|
||||||
|
|
||||||
|
*show-urls-launch* by default opens the URL with *xdg-open*. This can
|
||||||
|
be changed with the *url-launch* option.
|
||||||
|
|
||||||
|
*show-urls-copy* is an alternative to *show-urls-launch*, that changes
|
||||||
|
what activating a URL _does_; instead of opening it, it copies it to
|
||||||
|
the clipboard. It is unbound by default.
|
||||||
|
|
||||||
|
Jump label colors, the URL underline color, and the letters used in
|
||||||
|
the jump label key sequences can be configured.
|
||||||
|
|
||||||
|
# ALT/META CHARACTERS
|
||||||
|
|
||||||
|
By default, foot prefixes meta characters with *ESC*. This corresponds
|
||||||
|
to XTerm's *metaSendsEscape* option set to *true*.
|
||||||
|
|
||||||
|
This can be disabled programmatically with *\E[?1036l* (and enabled
|
||||||
|
again with *\E[?1036h*).
|
||||||
|
|
||||||
|
When disabled, foot will instead set the 8:th bit of meta character
|
||||||
|
and then UTF-8 encode it. This corresponds to XTerm's *eightBitMeta*
|
||||||
|
option set to *true*.
|
||||||
|
|
||||||
|
This can also be disabled programmatically with *rmm* (Reset Meta Mode,
|
||||||
|
*\E[?1034l*), and enabled again with *smm* (Set Meta Mode,
|
||||||
|
*\E[?1034h*).
|
||||||
|
|
||||||
|
# BACKSPACE
|
||||||
|
|
||||||
|
Foot transmits DEL (*^?*) on backspace. This corresponds to XTerm's
|
||||||
|
*backarrowKey* option set to *false*, and to DECBKM being _reset_.
|
||||||
|
|
||||||
|
To instead transmit BS (*^H*), press *ctrl*+*backspace*.
|
||||||
|
|
||||||
|
Note that foot does *not* implement DECBKM, and that the behavior
|
||||||
|
described above *cannot* be changed.
|
||||||
|
|
||||||
|
Finally, pressing *alt* will prefix the transmitted byte with ESC.
|
||||||
|
|
||||||
|
# KEYPAD
|
||||||
|
|
||||||
|
By default, *Num Lock* overrides the run-time configuration keypad
|
||||||
|
mode; when active, the keypad is always considered to be in
|
||||||
|
_numerical_ mode. This corresponds to XTerm's *numLock* option set to
|
||||||
|
*true*.
|
||||||
|
|
||||||
|
In this mode, the keypad keys always sends either numbers (Num Lock is
|
||||||
|
active) or cursor movement keys (up, down, left, right, page up, page
|
||||||
|
down etc).
|
||||||
|
|
||||||
|
This can be disabled programmatically with *\E[?1035l* (and enabled
|
||||||
|
again with *\E[?1035h*).
|
||||||
|
|
||||||
|
When disabled, the keypad sends custom escape sequences instead of
|
||||||
|
numbers, when in _application_ mode.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
foot will search for a configuration file in the following locations,
|
||||||
|
in this order:
|
||||||
|
|
||||||
|
- *XDG_CONFIG_HOME/foot/foot.ini* (defaulting to
|
||||||
|
*$HOME/.config/foot/foot.ini* if unset)
|
||||||
|
- *XDG_CONFIG_DIRS/foot/foot.ini* (defaulting to
|
||||||
|
*/etc/xdg/foot/foot.ini* if unset)
|
||||||
|
|
||||||
|
An example configuration file containing all options with their default value
|
||||||
|
commented out will usually be installed to */etc/xdg/foot/foot.ini*.
|
||||||
|
|
||||||
|
For more information, see *foot.ini*(5).
|
||||||
|
|
||||||
|
# SHELL INTEGRATION
|
||||||
|
|
||||||
|
## Current working directory
|
||||||
|
|
||||||
|
New foot terminal instances (bound to *ctrl*+*shift*+*n* by default)
|
||||||
|
will open in the current working directory, if the shell in the
|
||||||
|
"parent" terminal reports directory changes.
|
||||||
|
|
||||||
|
This is done with the OSC-7 escape sequence. Most shells can be
|
||||||
|
scripted to do this, if they do not support it natively. See the wiki
|
||||||
|
(https://codeberg.org/dnkl/foot/wiki#user-content-spawning-new-terminal-instances-in-the-current-working-directory)
|
||||||
|
for details.
|
||||||
|
|
||||||
|
|
||||||
|
## Jumping between prompts
|
||||||
|
|
||||||
|
Foot can move the current viewport to focus prompts of already
|
||||||
|
executed commands (bound to *ctrl*+*shift*+*z*/*x* by default).
|
||||||
|
|
||||||
|
For this to work, the shell needs to emit an OSC-133;A
|
||||||
|
(*\\E]133;A\\E\\\\*) sequence before each prompt.
|
||||||
|
|
||||||
|
In zsh, one way to do this is to add a _precmd_ hook:
|
||||||
|
|
||||||
|
*precmd() {
|
||||||
|
print -Pn "\\e]133;A\\e\\\\"
|
||||||
|
}*
|
||||||
|
|
||||||
|
See the wiki
|
||||||
|
(https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
|
||||||
|
for details, and examples for other shells.
|
||||||
|
|
||||||
|
## Piping last command's output
|
||||||
|
|
||||||
|
The key binding *pipe-command-output* can pipe the last command's
|
||||||
|
output to an application of your choice (similar to the other
|
||||||
|
*pipe-\** key bindings):
|
||||||
|
|
||||||
|
*\[key-bindings\]++
|
||||||
|
pipe-command-output=[sh -c "f=$(mktemp); cat - > $f; footclient emacsclient -nw $f; rm $f"] Control+Shift+g*
|
||||||
|
|
||||||
|
When pressing *ctrl*+*shift*+*g*, the last command's output is written
|
||||||
|
to a temporary file, then an emacsclient is started in a new
|
||||||
|
footclient instance. The temporary file is removed after the
|
||||||
|
footclient instance has closed.
|
||||||
|
|
||||||
|
For this to work, the shell must emit an OSC-133;C (*\\E]133;C\\E\\\\*)
|
||||||
|
sequence before command output starts, and an OSC-133;D
|
||||||
|
(*\\E]133;D\\E\\\\*) when the command output ends.
|
||||||
|
|
||||||
|
In fish, one way to do this is to add _preexec_ and _postexec_ hooks:
|
||||||
|
|
||||||
|
*function foot_cmd_start --on-event fish_preexec
|
||||||
|
echo -en "\\e]133;C\\e\\\\"
|
||||||
|
end*
|
||||||
|
|
||||||
|
*function foot_cmd_end --on-event fish_postexec
|
||||||
|
echo -en "\\e]133;D\\e\\\\"
|
||||||
|
end*
|
||||||
|
|
||||||
|
See the wiki
|
||||||
|
(https://codeberg.org/dnkl/foot/wiki#user-content-piping-last-commands-output)
|
||||||
|
for details, and examples for other shells
|
||||||
|
|
||||||
|
# TERMINFO
|
||||||
|
|
||||||
|
Client applications use the terminfo identifier specified by the
|
||||||
|
environment variable *TERM* (set by foot) to determine terminal
|
||||||
|
capabilities.
|
||||||
|
|
||||||
|
Foot has two terminfo definitions: *foot* and *foot-direct*, with
|
||||||
|
*foot* being the default.
|
||||||
|
|
||||||
|
The difference between the two is in the number of colors they
|
||||||
|
describe; *foot* describes 256 colors and *foot-direct* 16.7 million
|
||||||
|
colors (24-bit truecolor).
|
||||||
|
|
||||||
|
Note that using the *foot* terminfo does not limit the number of
|
||||||
|
usable colors to 256; applications can still use 24-bit RGB colors. In
|
||||||
|
fact, most applications work best with *foot* (including 24-bit
|
||||||
|
colors). Using *\*-direct* terminfo entries has been known to crash
|
||||||
|
some ncurses applications even.
|
||||||
|
|
||||||
|
There are however applications that need a *\*-direct* terminfo entry
|
||||||
|
for 24-bit support. Emacs is one such example.
|
||||||
|
|
||||||
|
While using either *foot* or *foot-direct* is strongly recommended, it
|
||||||
|
is possible to use e.g. *xterm-256color* as well. This can be useful
|
||||||
|
when remoting to a system where foot's terminfo entries cannot easily
|
||||||
|
be installed.
|
||||||
|
|
||||||
|
Note that terminfo entries can be installed in the user's home
|
||||||
|
directory. I.e. if you do not have root access, or if there is no
|
||||||
|
distro package for foot's terminfo entries, you can install foot's
|
||||||
|
terminfo entries manually, by copying *foot* and *foot-direct* to
|
||||||
|
*~/.terminfo/f/*.
|
||||||
|
|
||||||
|
# XTGETTCAP
|
||||||
|
|
||||||
|
*XTGETTCAP* is an escape sequence initially introduced by XTerm, and
|
||||||
|
also implemented (and extended, to some degree) by Kitty.
|
||||||
|
|
||||||
|
It allows querying the terminal for terminfo classic, file-based,
|
||||||
|
terminfo definition. For example, if all applications used this
|
||||||
|
feature, you would no longer have to install foot's terminfo on remote
|
||||||
|
hosts you SSH into.
|
||||||
|
|
||||||
|
XTerm's implementation (as of XTerm-370) only supports querying key
|
||||||
|
(as in keyboard keys) capabilities, and three custom capabilities:
|
||||||
|
|
||||||
|
- TN - terminal name
|
||||||
|
- Co - number of colors (alias for the colors capability)
|
||||||
|
- RGB - number of bits per color channel (different semantics from
|
||||||
|
the RGB capability in file-based terminfo definitions!).
|
||||||
|
|
||||||
|
Kitty has extended this, and also supports querying all integer and
|
||||||
|
string capabilities.
|
||||||
|
|
||||||
|
Foot supports this, and extends it even further, to also include
|
||||||
|
boolean capabilities. This means foot's entire terminfo can be queried
|
||||||
|
via *XTGETTCAP*.
|
||||||
|
|
||||||
|
Note that both Kitty and foot handles responses to multi-capability
|
||||||
|
queries slightly differently, compared to XTerm.
|
||||||
|
|
||||||
|
XTerm will send a single DCS reply, with ;-separated
|
||||||
|
capability/value pairs. There are a couple of issues with this:
|
||||||
|
|
||||||
|
- The success/fail flag in the beginning of the response is always 1
|
||||||
|
(success), unless the very first queried capability is invalid.
|
||||||
|
- XTerm will not respond at all to an invalid capability, unless it's
|
||||||
|
the first one in the XTGETTCAP query.
|
||||||
|
- XTerm will end the response at the first invalid capability.
|
||||||
|
|
||||||
|
In other words, if you send a large multi-capability query, you will
|
||||||
|
only get responses up to, but not including, the first invalid
|
||||||
|
capability. All subsequent capabilities will be dropped.
|
||||||
|
|
||||||
|
Kitty and foot on the other hand, send one DCS response for each
|
||||||
|
capability in the multi query. This allows us to send a proper
|
||||||
|
success/fail flag for each queried capability. Responses for all
|
||||||
|
queried capabilities are always sent. No queries are ever dropped.
|
||||||
|
|
||||||
|
# EXIT STATUS
|
||||||
|
|
||||||
|
Foot will exit with code 230 if there is a failure in foot itself.
|
||||||
|
|
||||||
|
In all other cases, the exit code is that of the client application
|
||||||
|
(i.e. the shell).
|
||||||
|
|
||||||
|
# ENVIRONMENT
|
||||||
|
|
||||||
|
## Variables used by foot
|
||||||
|
|
||||||
|
*SHELL*
|
||||||
|
The default child process to run, when no _command_ argument is
|
||||||
|
specified and the *shell* option in *foot.ini*(5) is not set.
|
||||||
|
|
||||||
|
*HOME*
|
||||||
|
Used to determine the location of the configuration file, see
|
||||||
|
*foot.ini*(5) for details.
|
||||||
|
|
||||||
|
*XDG\_CONFIG\_HOME*
|
||||||
|
Used to determine the location of the configuration file, see
|
||||||
|
*foot.ini*(5) for details.
|
||||||
|
|
||||||
|
*XDG\_CONFIG\_DIRS*
|
||||||
|
Used to determine the location of the configuration file, see
|
||||||
|
*foot.ini*(5) for details.
|
||||||
|
|
||||||
|
*XDG\_RUNTIME\_DIR*
|
||||||
|
Used to construct the default _PATH_ for the *--server*
|
||||||
|
option, when no explicit argument is given (see above).
|
||||||
|
|
||||||
|
*WAYLAND\_DISPLAY*
|
||||||
|
Used to construct the default _PATH_ for the *--server*
|
||||||
|
option, when no explicit argument is given (see above).
|
||||||
|
|
||||||
|
*XCURSOR\_THEME*
|
||||||
|
The name of the *Xcursor*(3) theme to use for pointers (typically
|
||||||
|
set by the Wayland compositor).
|
||||||
|
|
||||||
|
*XCURSOR\_SIZE*
|
||||||
|
The size to use for *Xcursor*(3) pointers (typically set by the
|
||||||
|
Wayland compositor).
|
||||||
|
|
||||||
|
## Variables set in the child process
|
||||||
|
|
||||||
|
*TERM*
|
||||||
|
terminfo/termcap identifier. This is used by client applications
|
||||||
|
to determine which capabilities a terminal supports. The value is
|
||||||
|
set according to either the *--term* command-line option or the
|
||||||
|
*term* config option in *foot.ini*(5).
|
||||||
|
|
||||||
|
*COLORTERM*
|
||||||
|
This variable is set to *truecolor*, to indicate to client
|
||||||
|
applications that 24-bit RGB colors are supported.
|
||||||
|
|
||||||
|
*PWD*
|
||||||
|
Current working directory (at the time of launching foot)
|
||||||
|
|
||||||
|
*SHELL*
|
||||||
|
Set to the launched shell, if the shell is valid (it is listed in
|
||||||
|
*/etc/shells*).
|
||||||
|
|
||||||
|
In addition to the variables listed above, custom environment
|
||||||
|
variables may be defined in *foot.ini*(5).
|
||||||
|
|
||||||
|
## Variables *unset* in the child process
|
||||||
|
|
||||||
|
*TERM_PROGRAM*
|
||||||
|
*TERM_PROGRAM_VERSION*
|
||||||
|
These environment variables are set by certain other terminal
|
||||||
|
emulators. We unset them, to prevent applications from
|
||||||
|
misdetecting foot.
|
||||||
|
|
||||||
|
In addition to the variables listed above, custom environment
|
||||||
|
variables to unset may be defined in *foot.ini*(5).
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
|
||||||
|
The following signals have special meaning in foot:
|
||||||
|
|
||||||
|
- SIGUSR1: switch to the dark color theme (*[colors-dark]*).
|
||||||
|
- SIGUSR2: switch to the light color theme (*[colors-light]*).
|
||||||
|
|
||||||
|
Note: you can send SIGUSR1/SIGUSR2 to a *foot --server* process too,
|
||||||
|
in which case all client instances will switch theme. Furthermore, all
|
||||||
|
future client instances will also use the selected theme.
|
||||||
|
|
||||||
|
You can also send SIGUSR1/SIGUSR2 to a footclient instance, see
|
||||||
|
*footclient*(1) for details.
|
||||||
|
|
||||||
|
|
||||||
|
# BUGS
|
||||||
|
|
||||||
|
Please report bugs to https://codeberg.org/dnkl/foot/issues
|
||||||
|
|
||||||
|
Before you open a new issue, please search existing bug reports, both
|
||||||
|
open and closed ones. Chances are someone else has already reported
|
||||||
|
the same issue.
|
||||||
|
|
||||||
|
The report should contain the following:
|
||||||
|
|
||||||
|
- Foot version (*foot --version*).
|
||||||
|
- 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.
|
||||||
|
- Steps to reproduce. The more details the better.
|
||||||
|
|
||||||
|
# IRC
|
||||||
|
|
||||||
|
\#foot on irc.libera.chat
|
||||||
|
|
||||||
|
# SEE ALSO
|
||||||
|
|
||||||
|
*foot.ini*(5), *footclient*(1)
|
||||||
2166
doc/foot.ini.5.scd
Normal file
2166
doc/foot.ini.5.scd
Normal file
File diff suppressed because it is too large
Load diff
214
doc/footclient.1.scd
Normal file
214
doc/footclient.1.scd
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
footclient(1)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
footclient - start new terminals in a foot server
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
*footclient* [_OPTIONS_]++
|
||||||
|
*footclient* [_OPTIONS_] <_command_> [_COMMAND OPTIONS_]
|
||||||
|
|
||||||
|
All trailing (non-option) arguments are treated as a command, and its
|
||||||
|
arguments, to execute (instead of the default shell).
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
*footclient* is used together with *foot*(1) in *--server*
|
||||||
|
mode.
|
||||||
|
|
||||||
|
Running it without arguments will open a new terminal window (hosted
|
||||||
|
in the foot server), with your default shell. The exit code will be
|
||||||
|
that of the terminal. I.e *footclient* does not exit until the
|
||||||
|
terminal has terminated.
|
||||||
|
|
||||||
|
# OPTIONS
|
||||||
|
|
||||||
|
*-t*,*--term*=_TERM_
|
||||||
|
Value to set the environment variable *TERM* to (see *TERMINFO*
|
||||||
|
and *ENVIRONMENT*). Default: _@default_terminfo@_.
|
||||||
|
|
||||||
|
*-T*,*--title*=_TITLE_
|
||||||
|
Initial window title. Default: _foot_.
|
||||||
|
|
||||||
|
*-a*,*--app-id*=_ID_
|
||||||
|
Value to set the *app-id* property on the Wayland window
|
||||||
|
to. Default: _foot_ (normal mode), or _footclient_ (server mode).
|
||||||
|
|
||||||
|
*toplevel-tag*=_TAG_
|
||||||
|
Value to set the *toplevel-tag* property on the Wayland window
|
||||||
|
to. The compositor can use this value for session management,
|
||||||
|
window rules etc. Default: _not set_
|
||||||
|
|
||||||
|
*-w*,*--window-size-pixels*=_WIDTHxHEIGHT_
|
||||||
|
Set initial window width and height, in pixels. Default: _700x500_.
|
||||||
|
|
||||||
|
*-W*,*--window-size-chars*=_WIDTHxHEIGHT_
|
||||||
|
Set initial window width and height, in characters. Default: _not set_.
|
||||||
|
|
||||||
|
*-m*,*--maximized*
|
||||||
|
Start in maximized mode. If both *--maximized* and *--fullscreen*
|
||||||
|
are specified, the _last_ one takes precedence.
|
||||||
|
|
||||||
|
*-F*,*--fullscreen*
|
||||||
|
Start in fullscreen mode. If both *--maximized* and *--fullscreen*
|
||||||
|
are specified, the _last_ one takes precedence.
|
||||||
|
|
||||||
|
*-L*,*--login-shell*
|
||||||
|
Start a login shell, by prepending a '-' to argv[0].
|
||||||
|
|
||||||
|
*-D*,*--working-directory*=_DIR_
|
||||||
|
Initial working directory for the client application. Default:
|
||||||
|
_CWD of footclient_.
|
||||||
|
|
||||||
|
*-s*,*--server-socket*=_PATH_
|
||||||
|
Connect to _PATH_ instead of
|
||||||
|
*$XDG\_RUNTIME\_DIR/foot-$WAYLAND\_DISPLAY.sock*.
|
||||||
|
|
||||||
|
*-H*,*--hold*
|
||||||
|
Remain open after child process exits.
|
||||||
|
|
||||||
|
*-N*,*--no-wait*
|
||||||
|
Detach the client process from the running terminal, exiting
|
||||||
|
immediately.
|
||||||
|
|
||||||
|
*-o*,*--override*=[_SECTION_.]_KEY_=_VALUE_
|
||||||
|
Override an option set in the configuration file. If _SECTION_ is not
|
||||||
|
given, defaults to _main_.
|
||||||
|
|
||||||
|
*-E*,*--client-environment*
|
||||||
|
The child process in the new terminal instance will use
|
||||||
|
footclient's environment, instead of the server's.
|
||||||
|
|
||||||
|
Environment variables listed in the *Variables set in the child
|
||||||
|
process* section will be overwritten by the foot server. For
|
||||||
|
example, the new terminal will use *TERM* from the configuration,
|
||||||
|
not footclient's environment.
|
||||||
|
|
||||||
|
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
|
||||||
|
Log level, used both for log output on stderr as well as
|
||||||
|
syslog. Default: _warning_.
|
||||||
|
|
||||||
|
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
|
||||||
|
Enables or disables colorization of log output on stderr.
|
||||||
|
|
||||||
|
*-v*,*--version*
|
||||||
|
Show the version number and quit
|
||||||
|
|
||||||
|
*-e*
|
||||||
|
Ignored; for compatibility with *xterm -e*. See *foot*(1) for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
# EXIT STATUS
|
||||||
|
|
||||||
|
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
|
||||||
|
the foot server has been instructed to open a new window.
|
||||||
|
|
||||||
|
If not, footclient may also exit with code 230. This indicates a
|
||||||
|
failure in the foot server.
|
||||||
|
|
||||||
|
In all other cases the exit code is that of the client application
|
||||||
|
(i.e. the shell).
|
||||||
|
|
||||||
|
# TERMINFO
|
||||||
|
|
||||||
|
Client applications use the terminfo identifier specified by the
|
||||||
|
environment variable *TERM* (set by foot) to determine terminal
|
||||||
|
capabilities.
|
||||||
|
|
||||||
|
Foot has two terminfo definitions: *foot* and *foot-direct*, with
|
||||||
|
*foot* being the default.
|
||||||
|
|
||||||
|
The difference between the two is in the number of colors they
|
||||||
|
describe; *foot* describes 256 colors and *foot-direct* 16.7 million
|
||||||
|
colors (24-bit truecolor).
|
||||||
|
|
||||||
|
Note that using the *foot* terminfo does not limit the number of
|
||||||
|
usable colors to 256; applications can still use 24-bit RGB colors. In
|
||||||
|
fact, most applications work best with *foot* (including 24-bit
|
||||||
|
colors)). Using *\*-direct* terminfo entries has been known to crash
|
||||||
|
some ncurses applications even.
|
||||||
|
|
||||||
|
There are however applications that need a *\*-direct* terminfo entry
|
||||||
|
for 24-bit support. Emacs is one such example.
|
||||||
|
|
||||||
|
While using either *foot* or *foot-direct* is strongly recommended, it
|
||||||
|
is possible to use e.g. *xterm-256color* as well. This can be useful
|
||||||
|
when remoting to a system where foot's terminfo entries cannot easily
|
||||||
|
be installed.
|
||||||
|
|
||||||
|
Note that terminfo entries can be installed in the user's home
|
||||||
|
directory. I.e. if you do not have root access, or if there is no
|
||||||
|
distro package for foot's terminfo entries, you can install foot's
|
||||||
|
terminfo entries manually, by copying *foot* and *foot-direct* to
|
||||||
|
*~/.terminfo/f/*.
|
||||||
|
|
||||||
|
# ENVIRONMENT
|
||||||
|
|
||||||
|
## Variables used by footclient
|
||||||
|
|
||||||
|
*XDG\_RUNTIME\_DIR*
|
||||||
|
Used to construct the default _PATH_ for the *--server-socket*
|
||||||
|
option, when no explicit argument is given (see above).
|
||||||
|
|
||||||
|
*WAYLAND\_DISPLAY*
|
||||||
|
Used to construct the default _PATH_ for the *--server-socket*
|
||||||
|
option, when no explicit argument is given (see above).
|
||||||
|
|
||||||
|
If the socket at default _PATH_ does not exist, *footclient* will
|
||||||
|
fallback to the less specific path, with the following priority:
|
||||||
|
*$XDG\_RUNTIME\_DIR/foot-$WAYLAND\_DISPLAY.sock*,
|
||||||
|
*$XDG\_RUNTIME\_DIR/foot.sock*, */tmp/foot.sock*.
|
||||||
|
|
||||||
|
## Variables set in the child process
|
||||||
|
|
||||||
|
*TERM*
|
||||||
|
terminfo/termcap identifier. This is used by client applications
|
||||||
|
to determine which capabilities a terminal supports. The value is
|
||||||
|
set according to either the *--term* command-line option or the
|
||||||
|
*term* config option in *foot.ini*(5).
|
||||||
|
|
||||||
|
*COLORTERM*
|
||||||
|
This variable is set to *truecolor*, to indicate to client
|
||||||
|
applications that 24-bit RGB colors are supported.
|
||||||
|
|
||||||
|
*PWD*
|
||||||
|
Current working directory (at the time of launching foot)
|
||||||
|
|
||||||
|
*SHELL*
|
||||||
|
Set to the launched shell, if the shell is valid (it is listed in
|
||||||
|
*/etc/shells*).
|
||||||
|
|
||||||
|
In addition to the variables listed above, custom environment
|
||||||
|
variables may be defined in *foot.ini*(5).
|
||||||
|
|
||||||
|
## Variables *unset* in the child process
|
||||||
|
|
||||||
|
*TERM_PROGRAM*
|
||||||
|
*TERM_PROGRAM_VERSION*
|
||||||
|
These environment variables are set by certain other terminal
|
||||||
|
emulators. We unset them, to prevent applications from
|
||||||
|
misdetecting foot.
|
||||||
|
|
||||||
|
In addition to the variables listed above, custom environment
|
||||||
|
variables to unset may be defined in *foot.ini*(5).
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
|
||||||
|
The following signals have special meaning in footclient:
|
||||||
|
|
||||||
|
- SIGUSR1: switch to the dark color theme (*[colors-dark]*).
|
||||||
|
- SIGUSR2: switch to the light color theme (*[colors-light]*).
|
||||||
|
|
||||||
|
When sending SIGUSR1/SIGUSR2 to a footclient instance, the theme is
|
||||||
|
changed in that instance only. This is different from when you send
|
||||||
|
SIGUSR1/SIGUSR2 to the server process, where all instances change the
|
||||||
|
theme.
|
||||||
|
|
||||||
|
Note: for obvious reasons, this is not supported when footclient is
|
||||||
|
started with *--no-wait*.
|
||||||
|
|
||||||
|
# SEE ALSO
|
||||||
|
|
||||||
|
*foot*(1)
|
||||||
49
doc/meson.build
Normal file
49
doc/meson.build
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
|
||||||
|
|
||||||
|
if utmp_backend != 'none'
|
||||||
|
utmp_add_args = '@0@ $WAYLAND_DISPLAY'.format(utmp_add)
|
||||||
|
utmp_del_args = (utmp_del_have_argument
|
||||||
|
? '@0@ $WAYLAND_DISPLAY'.format(utmp_del)
|
||||||
|
: '@0@'.format(utmp_del))
|
||||||
|
utmp_path = utmp_default_helper_path
|
||||||
|
else
|
||||||
|
utmp_add_args = '<no utmp support in foot>'
|
||||||
|
utmp_del_args = '<no utmp support in foot>'
|
||||||
|
utmp_path = 'none'
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
conf_data = configuration_data(
|
||||||
|
{
|
||||||
|
'default_terminfo': get_option('default-terminfo'),
|
||||||
|
'utmp_backend': utmp_backend,
|
||||||
|
'utmp_add_args': utmp_add_args,
|
||||||
|
'utmp_del_args': utmp_del_args,
|
||||||
|
'utmp_helper_path': utmp_path,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach man_src : [{'name': 'foot', 'section' : 1},
|
||||||
|
{'name': 'foot.ini', 'section': 5},
|
||||||
|
{'name': 'footclient', 'section': 1},
|
||||||
|
{'name': 'foot-ctlseqs', 'section': 7}]
|
||||||
|
name = man_src['name']
|
||||||
|
section = man_src['section']
|
||||||
|
out = '@0@.@1@'.format(name, section)
|
||||||
|
|
||||||
|
preprocessed = configure_file(
|
||||||
|
input: '@0@.@1@.scd'.format(name, section),
|
||||||
|
output: '@0@.preprocessed'.format(out),
|
||||||
|
configuration: conf_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
custom_target(
|
||||||
|
out,
|
||||||
|
output: out,
|
||||||
|
input: preprocessed,
|
||||||
|
command: scdoc_prog.full_path(),
|
||||||
|
capture: true,
|
||||||
|
feed: true,
|
||||||
|
install: true,
|
||||||
|
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))
|
||||||
|
endforeach
|
||||||
BIN
doc/sixel-tux-foot.png
Normal file
BIN
doc/sixel-tux-foot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
BIN
doc/tux-foot-ok.png
Normal file
BIN
doc/tux-foot-ok.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 394 KiB |
271
extract.c
Normal file
271
extract.c
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
#include "extract.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "extract"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "char32.h"
|
||||||
|
|
||||||
|
struct extraction_context {
|
||||||
|
char32_t *buf;
|
||||||
|
size_t size;
|
||||||
|
size_t idx;
|
||||||
|
size_t tab_spaces_left;
|
||||||
|
size_t empty_count;
|
||||||
|
size_t newline_count;
|
||||||
|
bool strip_trailing_empty;
|
||||||
|
bool failed;
|
||||||
|
const struct row *last_row;
|
||||||
|
const struct cell *last_cell;
|
||||||
|
enum selection_kind selection_kind;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct extraction_context *
|
||||||
|
extract_begin(enum selection_kind kind, bool strip_trailing_empty)
|
||||||
|
{
|
||||||
|
struct extraction_context *ctx = malloc(sizeof(*ctx));
|
||||||
|
if (unlikely(ctx == NULL)) {
|
||||||
|
LOG_ERRNO("malloc() failed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ctx = (struct extraction_context){
|
||||||
|
.selection_kind = kind,
|
||||||
|
.strip_trailing_empty = strip_trailing_empty,
|
||||||
|
};
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
ensure_size(struct extraction_context *ctx, size_t additional_chars)
|
||||||
|
{
|
||||||
|
while (ctx->size < ctx->idx + additional_chars) {
|
||||||
|
size_t new_size = ctx->size == 0 ? 512 : ctx->size * 2;
|
||||||
|
char32_t *new_buf = realloc(ctx->buf, new_size * sizeof(new_buf[0]));
|
||||||
|
|
||||||
|
if (new_buf == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ctx->buf = new_buf;
|
||||||
|
ctx->size = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
xassert(ctx->size >= ctx->idx + additional_chars);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
extract_finish_wide(struct extraction_context *ctx, char32_t **text, size_t *len)
|
||||||
|
{
|
||||||
|
if (text == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*text = NULL;
|
||||||
|
if (len != NULL)
|
||||||
|
*len = 0;
|
||||||
|
|
||||||
|
if (ctx->failed)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (!ctx->strip_trailing_empty) {
|
||||||
|
/* Insert pending newlines, and replace empty cells with spaces */
|
||||||
|
if (!ensure_size(ctx, ctx->newline_count + ctx->empty_count))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ctx->newline_count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = U'\n';
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = U' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->idx == 0) {
|
||||||
|
/* Selection of empty cells only */
|
||||||
|
if (!ensure_size(ctx, 1))
|
||||||
|
goto err;
|
||||||
|
ctx->buf[ctx->idx++] = U'\0';
|
||||||
|
} else {
|
||||||
|
xassert(ctx->idx > 0);
|
||||||
|
xassert(ctx->idx <= ctx->size);
|
||||||
|
|
||||||
|
switch (ctx->selection_kind) {
|
||||||
|
default:
|
||||||
|
if (ctx->buf[ctx->idx - 1] == U'\n')
|
||||||
|
ctx->buf[ctx->idx - 1] = U'\0';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SELECTION_LINE_WISE:
|
||||||
|
if (ctx->buf[ctx->idx - 1] != U'\n') {
|
||||||
|
if (!ensure_size(ctx, 1))
|
||||||
|
goto err;
|
||||||
|
ctx->buf[ctx->idx++] = U'\n';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->buf[ctx->idx - 1] != U'\0') {
|
||||||
|
if (!ensure_size(ctx, 1))
|
||||||
|
goto err;
|
||||||
|
ctx->buf[ctx->idx++] = U'\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*text = ctx->buf;
|
||||||
|
if (len != NULL)
|
||||||
|
*len = ctx->idx - 1;
|
||||||
|
free(ctx);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
err:
|
||||||
|
free(ctx->buf);
|
||||||
|
free(ctx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
||||||
|
{
|
||||||
|
if (text == NULL)
|
||||||
|
return false;
|
||||||
|
if (len != NULL)
|
||||||
|
*len = 0;
|
||||||
|
|
||||||
|
char32_t *wtext;
|
||||||
|
if (!extract_finish_wide(ctx, &wtext, NULL))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
*text = ac32tombs(wtext);
|
||||||
|
if (*text == NULL) {
|
||||||
|
LOG_ERR("failed to convert selection to UTF-8");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len != NULL)
|
||||||
|
*len = strlen(*text);
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
out:
|
||||||
|
free(wtext);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
extract_one(const struct terminal *term, const struct row *row,
|
||||||
|
const struct cell *cell, int col, void *context)
|
||||||
|
{
|
||||||
|
struct extraction_context *ctx = context;
|
||||||
|
|
||||||
|
if (cell->wc >= CELL_SPACER)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ctx->last_row != NULL && row != ctx->last_row) {
|
||||||
|
/* New row - determine if we should insert a newline or not */
|
||||||
|
|
||||||
|
if (ctx->selection_kind != SELECTION_BLOCK) {
|
||||||
|
if (ctx->last_row->linebreak ||
|
||||||
|
ctx->empty_count > 0 ||
|
||||||
|
cell->wc == 0)
|
||||||
|
{
|
||||||
|
/* Row has a hard linebreak, or either last cell or
|
||||||
|
* current cell is empty */
|
||||||
|
|
||||||
|
/* Don't emit newline just yet - only if there are
|
||||||
|
* non-empty cells following it */
|
||||||
|
ctx->newline_count++;
|
||||||
|
|
||||||
|
if (!ctx->strip_trailing_empty) {
|
||||||
|
if (!ensure_size(ctx, ctx->empty_count))
|
||||||
|
goto err;
|
||||||
|
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = U' ';
|
||||||
|
}
|
||||||
|
ctx->empty_count = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Always insert a linebreak */
|
||||||
|
if (!ensure_size(ctx, 1))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ctx->buf[ctx->idx++] = U'\n';
|
||||||
|
|
||||||
|
if (!ctx->strip_trailing_empty) {
|
||||||
|
if (!ensure_size(ctx, ctx->empty_count))
|
||||||
|
goto err;
|
||||||
|
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = U' ';
|
||||||
|
}
|
||||||
|
ctx->empty_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->tab_spaces_left = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell->wc == U' ' && ctx->tab_spaces_left > 0) {
|
||||||
|
ctx->tab_spaces_left--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->tab_spaces_left = 0;
|
||||||
|
|
||||||
|
if (cell->wc == 0) {
|
||||||
|
ctx->empty_count++;
|
||||||
|
ctx->last_row = row;
|
||||||
|
ctx->last_cell = cell;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Insert pending newlines, and replace empty cells with spaces */
|
||||||
|
if (!ensure_size(ctx, ctx->newline_count + ctx->empty_count))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ctx->newline_count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = U'\n';
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = U' ';
|
||||||
|
|
||||||
|
ctx->newline_count = 0;
|
||||||
|
ctx->empty_count = 0;
|
||||||
|
|
||||||
|
if (cell->wc >= CELL_COMB_CHARS_LO && cell->wc <= CELL_COMB_CHARS_HI)
|
||||||
|
{
|
||||||
|
const struct composed *composed = composed_lookup(
|
||||||
|
term->composed, cell->wc - CELL_COMB_CHARS_LO);
|
||||||
|
|
||||||
|
if (!ensure_size(ctx, composed->count))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < composed->count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = composed->chars[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
if (!ensure_size(ctx, 1))
|
||||||
|
goto err;
|
||||||
|
ctx->buf[ctx->idx++] = cell->wc;
|
||||||
|
|
||||||
|
if (cell->wc == U'\t') {
|
||||||
|
int next_tab_stop = term->cols - 1;
|
||||||
|
tll_foreach(term->tab_stops, it) {
|
||||||
|
if (it->item > col) {
|
||||||
|
next_tab_stop = it->item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_tab_stop > col)
|
||||||
|
ctx->tab_spaces_left = next_tab_stop - col - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->last_row = row;
|
||||||
|
ctx->last_cell = cell;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
err:
|
||||||
|
ctx->failed = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
21
extract.h
Normal file
21
extract.h
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <uchar.h>
|
||||||
|
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
struct extraction_context;
|
||||||
|
|
||||||
|
struct extraction_context *extract_begin(
|
||||||
|
enum selection_kind kind, bool strip_trailing_empty);
|
||||||
|
|
||||||
|
bool extract_one(
|
||||||
|
const struct terminal *term, const struct row *row, const struct cell *cell,
|
||||||
|
int col, void *context);
|
||||||
|
|
||||||
|
bool extract_finish(
|
||||||
|
struct extraction_context *context, char **text, size_t *len);
|
||||||
|
bool extract_finish_wide(
|
||||||
|
struct extraction_context *context, char32_t **text, size_t *len);
|
||||||
496
fdm.c
Normal file
496
fdm.c
Normal file
|
|
@ -0,0 +1,496 @@
|
||||||
|
#include "fdm.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
|
#include <tllist.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "fdm"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
#if !defined(SIGABBREV_NP)
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
sigabbrev_np(int sig)
|
||||||
|
{
|
||||||
|
static char buf[16];
|
||||||
|
snprintf(buf, sizeof(buf), "<%d>", sig);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct fd_handler {
|
||||||
|
int fd;
|
||||||
|
int events;
|
||||||
|
fdm_fd_handler_t callback;
|
||||||
|
void *callback_data;
|
||||||
|
bool deleted;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sig_handler {
|
||||||
|
fdm_signal_handler_t callback;
|
||||||
|
void *callback_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hook {
|
||||||
|
fdm_hook_t callback;
|
||||||
|
void *callback_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef tll(struct hook) hooks_t;
|
||||||
|
|
||||||
|
struct fdm {
|
||||||
|
int epoll_fd;
|
||||||
|
bool is_polling;
|
||||||
|
tll(struct fd_handler *) fds;
|
||||||
|
tll(struct fd_handler *) deferred_delete;
|
||||||
|
|
||||||
|
sigset_t sigmask;
|
||||||
|
struct sig_handler *signal_handlers;
|
||||||
|
|
||||||
|
hooks_t hooks_low;
|
||||||
|
hooks_t hooks_normal;
|
||||||
|
hooks_t hooks_high;
|
||||||
|
};
|
||||||
|
|
||||||
|
static volatile sig_atomic_t got_signal = false;
|
||||||
|
static volatile sig_atomic_t *received_signals = NULL;
|
||||||
|
|
||||||
|
struct fdm *
|
||||||
|
fdm_init(void)
|
||||||
|
{
|
||||||
|
sigset_t sigmask;
|
||||||
|
if (sigprocmask(0, NULL, &sigmask) < 0) {
|
||||||
|
LOG_ERRNO("failed to get process signal mask");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
||||||
|
if (epoll_fd == -1) {
|
||||||
|
LOG_ERRNO("failed to create epoll FD");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
xassert(received_signals == NULL); /* Only one FDM instance supported */
|
||||||
|
received_signals = xcalloc(SIGRTMAX, sizeof(received_signals[0]));
|
||||||
|
got_signal = false;
|
||||||
|
|
||||||
|
struct fdm *fdm = malloc(sizeof(*fdm));
|
||||||
|
if (unlikely(fdm == NULL)) {
|
||||||
|
LOG_ERRNO("malloc() failed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sig_handler *sig_handlers = calloc(SIGRTMAX, sizeof(sig_handlers[0]));
|
||||||
|
|
||||||
|
if (sig_handlers == NULL) {
|
||||||
|
LOG_ERRNO("failed to allocate signal handler array");
|
||||||
|
free(fdm);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*fdm = (struct fdm){
|
||||||
|
.epoll_fd = epoll_fd,
|
||||||
|
.is_polling = false,
|
||||||
|
.fds = tll_init(),
|
||||||
|
.deferred_delete = tll_init(),
|
||||||
|
.sigmask = sigmask,
|
||||||
|
.signal_handlers = sig_handlers,
|
||||||
|
.hooks_low = tll_init(),
|
||||||
|
.hooks_normal = tll_init(),
|
||||||
|
.hooks_high = tll_init(),
|
||||||
|
};
|
||||||
|
return fdm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fdm_destroy(struct fdm *fdm)
|
||||||
|
{
|
||||||
|
if (fdm == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tll_length(fdm->fds) > 0)
|
||||||
|
LOG_WARN("FD list not empty");
|
||||||
|
|
||||||
|
for (int i = 0; i < SIGRTMAX; i++) {
|
||||||
|
if (fdm->signal_handlers[i].callback != NULL)
|
||||||
|
LOG_WARN("handler for signal %d (SIG%s) not removed",
|
||||||
|
i, sigabbrev_np(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tll_length(fdm->hooks_low) > 0 ||
|
||||||
|
tll_length(fdm->hooks_normal) > 0 ||
|
||||||
|
tll_length(fdm->hooks_high) > 0)
|
||||||
|
{
|
||||||
|
LOG_WARN("hook list not empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
xassert(tll_length(fdm->fds) == 0);
|
||||||
|
xassert(tll_length(fdm->deferred_delete) == 0);
|
||||||
|
xassert(tll_length(fdm->hooks_low) == 0);
|
||||||
|
xassert(tll_length(fdm->hooks_normal) == 0);
|
||||||
|
xassert(tll_length(fdm->hooks_high) == 0);
|
||||||
|
|
||||||
|
sigprocmask(SIG_SETMASK, &fdm->sigmask, NULL);
|
||||||
|
free(fdm->signal_handlers);
|
||||||
|
|
||||||
|
tll_free(fdm->fds);
|
||||||
|
tll_free(fdm->deferred_delete);
|
||||||
|
tll_free(fdm->hooks_low);
|
||||||
|
tll_free(fdm->hooks_normal);
|
||||||
|
tll_free(fdm->hooks_high);
|
||||||
|
close(fdm->epoll_fd);
|
||||||
|
free(fdm);
|
||||||
|
|
||||||
|
free((void *)received_signals);
|
||||||
|
received_signals = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t cb, void *data)
|
||||||
|
{
|
||||||
|
#if defined(_DEBUG)
|
||||||
|
tll_foreach(fdm->fds, it) {
|
||||||
|
if (it->item->fd == fd) {
|
||||||
|
BUG("FD=%d already registered", fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct fd_handler *handler = malloc(sizeof(*handler));
|
||||||
|
if (unlikely(handler == NULL)) {
|
||||||
|
LOG_ERRNO("malloc() failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*handler = (struct fd_handler) {
|
||||||
|
.fd = fd,
|
||||||
|
.events = events,
|
||||||
|
.callback = cb,
|
||||||
|
.callback_data = data,
|
||||||
|
.deleted = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
tll_push_back(fdm->fds, handler);
|
||||||
|
|
||||||
|
struct epoll_event ev = {
|
||||||
|
.events = events,
|
||||||
|
.data = {.ptr = handler},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
||||||
|
LOG_ERRNO("failed to register FD=%d with epoll", fd);
|
||||||
|
free(handler);
|
||||||
|
tll_pop_back(fdm->fds);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
fdm_del_internal(struct fdm *fdm, int fd, bool close_fd)
|
||||||
|
{
|
||||||
|
if (fd == -1)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
tll_foreach(fdm->fds, it) {
|
||||||
|
if (it->item->fd != fd)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0)
|
||||||
|
LOG_ERRNO("failed to unregister FD=%d from epoll", fd);
|
||||||
|
|
||||||
|
if (close_fd)
|
||||||
|
close(it->item->fd);
|
||||||
|
|
||||||
|
it->item->deleted = true;
|
||||||
|
if (fdm->is_polling)
|
||||||
|
tll_push_back(fdm->deferred_delete, it->item);
|
||||||
|
else
|
||||||
|
free(it->item);
|
||||||
|
|
||||||
|
tll_remove(fdm->fds, it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERR("no such FD: %d", fd);
|
||||||
|
close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_del(struct fdm *fdm, int fd)
|
||||||
|
{
|
||||||
|
return fdm_del_internal(fdm, fd, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_del_no_close(struct fdm *fdm, int fd)
|
||||||
|
{
|
||||||
|
return fdm_del_internal(fdm, fd, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
event_modify(struct fdm *fdm, struct fd_handler *fd, int new_events)
|
||||||
|
{
|
||||||
|
if (new_events == fd->events)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
struct epoll_event ev = {
|
||||||
|
.events = new_events,
|
||||||
|
.data = {.ptr = fd},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_MOD, fd->fd, &ev) < 0) {
|
||||||
|
LOG_ERRNO("failed to modify FD=%d with epoll (events 0x%08x -> 0x%08x)",
|
||||||
|
fd->fd, fd->events, new_events);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd->events = new_events;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_event_add(struct fdm *fdm, int fd, int events)
|
||||||
|
{
|
||||||
|
tll_foreach(fdm->fds, it) {
|
||||||
|
if (it->item->fd != fd)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return event_modify(fdm, it->item, it->item->events | events);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERR("FD=%d not registered with the FDM", fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_event_del(struct fdm *fdm, int fd, int events)
|
||||||
|
{
|
||||||
|
tll_foreach(fdm->fds, it) {
|
||||||
|
if (it->item->fd != fd)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return event_modify(fdm, it->item, it->item->events & ~events);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERR("FD=%d not registered with the FDM", fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hooks_t *
|
||||||
|
hook_priority_to_list(struct fdm *fdm, enum fdm_hook_priority priority)
|
||||||
|
{
|
||||||
|
switch (priority) {
|
||||||
|
case FDM_HOOK_PRIORITY_LOW: return &fdm->hooks_low;
|
||||||
|
case FDM_HOOK_PRIORITY_NORMAL: return &fdm->hooks_normal;
|
||||||
|
case FDM_HOOK_PRIORITY_HIGH: return &fdm->hooks_high;
|
||||||
|
}
|
||||||
|
|
||||||
|
BUG("unhandled priority type");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_hook_add(struct fdm *fdm, fdm_hook_t hook, void *data,
|
||||||
|
enum fdm_hook_priority priority)
|
||||||
|
{
|
||||||
|
hooks_t *hooks = hook_priority_to_list(fdm, priority);
|
||||||
|
|
||||||
|
#if defined(_DEBUG)
|
||||||
|
tll_foreach(*hooks, it) {
|
||||||
|
if (it->item.callback == hook) {
|
||||||
|
LOG_ERR("hook=0x%" PRIxPTR " already registered", (uintptr_t)hook);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
tll_push_back(*hooks, ((struct hook){hook, data}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority)
|
||||||
|
{
|
||||||
|
hooks_t *hooks = hook_priority_to_list(fdm, priority);
|
||||||
|
|
||||||
|
tll_foreach(*hooks, it) {
|
||||||
|
if (it->item.callback != hook)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tll_remove(*hooks, it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_WARN("hook=0x%" PRIxPTR " not registered", (uintptr_t)hook);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
signal_handler(int signo)
|
||||||
|
{
|
||||||
|
got_signal = true;
|
||||||
|
received_signals[signo] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data)
|
||||||
|
{
|
||||||
|
if (fdm->signal_handlers[signo].callback != NULL) {
|
||||||
|
LOG_ERR("signal %d (SIG%s) already has a handler",
|
||||||
|
signo, sigabbrev_np(signo));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sigset_t mask, original;
|
||||||
|
sigemptyset(&mask);
|
||||||
|
sigaddset(&mask, signo);
|
||||||
|
|
||||||
|
if (sigprocmask(SIG_BLOCK, &mask, &original) < 0) {
|
||||||
|
LOG_ERRNO("failed to block signal %d (SIG%s)",
|
||||||
|
signo, sigabbrev_np(signo));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigaction action = {.sa_handler = &signal_handler};
|
||||||
|
sigemptyset(&action.sa_mask);
|
||||||
|
if (sigaction(signo, &action, NULL) < 0) {
|
||||||
|
LOG_ERRNO("failed to set signal handler for signal %d (SIG%s)",
|
||||||
|
signo, sigabbrev_np(signo));
|
||||||
|
sigprocmask(SIG_SETMASK, &original, NULL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
received_signals[signo] = false;
|
||||||
|
fdm->signal_handlers[signo].callback = handler;
|
||||||
|
fdm->signal_handlers[signo].callback_data = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_signal_del(struct fdm *fdm, int signo)
|
||||||
|
{
|
||||||
|
if (fdm->signal_handlers[signo].callback == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
struct sigaction action = {.sa_handler = SIG_DFL};
|
||||||
|
sigemptyset(&action.sa_mask);
|
||||||
|
if (sigaction(signo, &action, NULL) < 0) {
|
||||||
|
LOG_ERRNO("failed to restore signal handler for signal %d (SIG%s)",
|
||||||
|
signo, sigabbrev_np(signo));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
received_signals[signo] = false;
|
||||||
|
fdm->signal_handlers[signo].callback = NULL;
|
||||||
|
fdm->signal_handlers[signo].callback_data = NULL;
|
||||||
|
|
||||||
|
sigset_t mask;
|
||||||
|
sigemptyset(&mask);
|
||||||
|
sigaddset(&mask, signo);
|
||||||
|
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) < 0) {
|
||||||
|
LOG_ERRNO("failed to unblock signal %d (SIG%s)",
|
||||||
|
signo, sigabbrev_np(signo));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_poll(struct fdm *fdm)
|
||||||
|
{
|
||||||
|
xassert(!fdm->is_polling && "nested calls to fdm_poll() not allowed");
|
||||||
|
if (fdm->is_polling) {
|
||||||
|
LOG_ERR("nested calls to fdm_poll() not allowed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tll_foreach(fdm->hooks_high, it) {
|
||||||
|
LOG_DBG(
|
||||||
|
"executing high priority hook 0x%" PRIxPTR" (fdm=%p, data=%p)",
|
||||||
|
(uintptr_t)it->item.callback, (void *)fdm,
|
||||||
|
(void *)it->item.callback_data);
|
||||||
|
it->item.callback(fdm, it->item.callback_data);
|
||||||
|
}
|
||||||
|
tll_foreach(fdm->hooks_normal, it) {
|
||||||
|
LOG_DBG(
|
||||||
|
"executing normal priority hook 0x%" PRIxPTR " (fdm=%p, data=%p)",
|
||||||
|
(uintptr_t)it->item.callback, (void *)fdm,
|
||||||
|
(void *)it->item.callback_data);
|
||||||
|
it->item.callback(fdm, it->item.callback_data);
|
||||||
|
}
|
||||||
|
tll_foreach(fdm->hooks_low, it) {
|
||||||
|
LOG_DBG(
|
||||||
|
"executing low priority hook 0x%" PRIxPTR " (fdm=%p, data=%p)",
|
||||||
|
(uintptr_t)it->item.callback, (void *)fdm,
|
||||||
|
(void *)it->item.callback_data);
|
||||||
|
it->item.callback(fdm, it->item.callback_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct epoll_event events[tll_length(fdm->fds)];
|
||||||
|
|
||||||
|
int r = epoll_pwait(
|
||||||
|
fdm->epoll_fd, events, tll_length(fdm->fds), -1, &fdm->sigmask);
|
||||||
|
|
||||||
|
int errno_copy = errno;
|
||||||
|
|
||||||
|
if (unlikely(got_signal)) {
|
||||||
|
got_signal = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < SIGRTMAX; i++) {
|
||||||
|
if (received_signals[i]) {
|
||||||
|
received_signals[i] = false;
|
||||||
|
struct sig_handler *handler = &fdm->signal_handlers[i];
|
||||||
|
|
||||||
|
xassert(handler->callback != NULL);
|
||||||
|
if (!handler->callback(fdm, i, handler->callback_data))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(r < 0)) {
|
||||||
|
if (errno_copy == EINTR)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
LOG_ERRNO_P(errno_copy, "failed to epoll");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = true;
|
||||||
|
|
||||||
|
fdm->is_polling = true;
|
||||||
|
for (int i = 0; i < r; i++) {
|
||||||
|
struct fd_handler *fd = events[i].data.ptr;
|
||||||
|
if (fd->deleted)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!fd->callback(fdm, fd->fd, events[i].events, fd->callback_data)) {
|
||||||
|
ret = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fdm->is_polling = false;
|
||||||
|
|
||||||
|
tll_foreach(fdm->deferred_delete, it) {
|
||||||
|
free(it->item);
|
||||||
|
tll_remove(fdm->deferred_delete, it);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
34
fdm.h
Normal file
34
fdm.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct fdm;
|
||||||
|
|
||||||
|
typedef bool (*fdm_fd_handler_t)(struct fdm *fdm, int fd, int events, void *data);
|
||||||
|
typedef bool (*fdm_signal_handler_t)(struct fdm *fdm, int signo, void *data);
|
||||||
|
typedef void (*fdm_hook_t)(struct fdm *fdm, void *data);
|
||||||
|
|
||||||
|
enum fdm_hook_priority {
|
||||||
|
FDM_HOOK_PRIORITY_LOW,
|
||||||
|
FDM_HOOK_PRIORITY_NORMAL,
|
||||||
|
FDM_HOOK_PRIORITY_HIGH
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fdm *fdm_init(void);
|
||||||
|
void fdm_destroy(struct fdm *fdm);
|
||||||
|
|
||||||
|
bool fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t handler, void *data);
|
||||||
|
bool fdm_del(struct fdm *fdm, int fd);
|
||||||
|
bool fdm_del_no_close(struct fdm *fdm, int fd);
|
||||||
|
|
||||||
|
bool fdm_event_add(struct fdm *fdm, int fd, int events);
|
||||||
|
bool fdm_event_del(struct fdm *fdm, int fd, int events);
|
||||||
|
|
||||||
|
bool fdm_hook_add(struct fdm *fdm, fdm_hook_t hook, void *data,
|
||||||
|
enum fdm_hook_priority priority);
|
||||||
|
bool fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority);
|
||||||
|
|
||||||
|
bool fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data);
|
||||||
|
bool fdm_signal_del(struct fdm *fdm, int signo);
|
||||||
|
|
||||||
|
bool fdm_poll(struct fdm *fdm);
|
||||||
42
foot-features.c
Normal file
42
foot-features.c
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#include "foot-features.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
const char version_and_features[] =
|
||||||
|
"version: " FOOT_VERSION
|
||||||
|
|
||||||
|
#if defined(FOOT_PGO_ENABLED) && FOOT_PGO_ENABLED
|
||||||
|
" +pgo"
|
||||||
|
#else
|
||||||
|
" -pgo"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||||
|
" +ime"
|
||||||
|
#else
|
||||||
|
" -ime"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING
|
||||||
|
" +graphemes"
|
||||||
|
#else
|
||||||
|
" -graphemes"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_XDG_TOPLEVEL_TAG)
|
||||||
|
" +toplevel-tag"
|
||||||
|
#else
|
||||||
|
" -toplevel-tag"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
|
||||||
|
" +blur"
|
||||||
|
#else
|
||||||
|
" -blur"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
" +assertions"
|
||||||
|
#else
|
||||||
|
" -assertions"
|
||||||
|
#endif
|
||||||
|
;
|
||||||
13
foot-features.h
Normal file
13
foot-features.h
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
extern const char version_and_features[];
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
print_version_and_features(const char *prefix)
|
||||||
|
{
|
||||||
|
fputs(prefix, stdout);
|
||||||
|
fputs(version_and_features, stdout);
|
||||||
|
fputc('\n', stdout);
|
||||||
|
}
|
||||||
11
foot-server.desktop
Normal file
11
foot-server.desktop
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Exec=foot --server
|
||||||
|
Icon=foot
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;TerminalEmulator;
|
||||||
|
Keywords=shell;prompt;command;commandline;
|
||||||
|
|
||||||
|
Name=Foot Server
|
||||||
|
GenericName=Terminal
|
||||||
|
Comment=A wayland native terminal emulator (server)
|
||||||
15
foot-server.service.in
Normal file
15
foot-server.service.in
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[Service]
|
||||||
|
ExecStart=@bindir@/foot --server=3
|
||||||
|
UnsetEnvironment=LISTEN_PID LISTEN_FDS LISTEN_FDNAMES
|
||||||
|
NonBlocking=true
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Requires=%N.socket
|
||||||
|
Description=Foot terminal server mode
|
||||||
|
Documentation=man:foot(1)
|
||||||
|
PartOf=graphical-session.target
|
||||||
|
After=graphical-session.target
|
||||||
|
ConditionEnvironment=WAYLAND_DISPLAY
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical-session.target
|
||||||
10
foot-server.socket
Normal file
10
foot-server.socket
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Socket]
|
||||||
|
ListenStream=%t/foot.sock
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
PartOf=graphical-session.target
|
||||||
|
After=graphical-session.target
|
||||||
|
ConditionEnvironment=WAYLAND_DISPLAY
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical-session.target
|
||||||
11
foot.desktop
Normal file
11
foot.desktop
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Exec=foot
|
||||||
|
Icon=foot
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;TerminalEmulator;
|
||||||
|
Keywords=shell;prompt;command;commandline;
|
||||||
|
|
||||||
|
Name=Foot
|
||||||
|
GenericName=Terminal
|
||||||
|
Comment=A wayland native terminal emulator
|
||||||
285
foot.info
Normal file
285
foot.info
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
@default_terminfo@|foot terminal emulator,
|
||||||
|
use=@default_terminfo@+base,
|
||||||
|
colors#256,
|
||||||
|
setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48\:5\:%p1%d%;m,
|
||||||
|
setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38\:5\:%p1%d%;m,
|
||||||
|
|
||||||
|
@default_terminfo@-direct|foot with direct color indexing,
|
||||||
|
use=@default_terminfo@+base,
|
||||||
|
colors#16777216,
|
||||||
|
RGB,
|
||||||
|
setab=\E[%?%p1%{8}%<%t4%p1%d%e48\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m,
|
||||||
|
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,
|
||||||
|
Su,
|
||||||
|
Tc,
|
||||||
|
XF,
|
||||||
|
XT,
|
||||||
|
am,
|
||||||
|
bce,
|
||||||
|
bw,
|
||||||
|
ccc,
|
||||||
|
hs,
|
||||||
|
mir,
|
||||||
|
msgr,
|
||||||
|
npc,
|
||||||
|
xenl,
|
||||||
|
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,
|
||||||
|
Rect=\E[%p1%d;%p2%d;%p3%d;%p4%d;%p5%d$x,
|
||||||
|
Se=\E[ q,
|
||||||
|
Setulc=\E[58\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m,
|
||||||
|
Smulx=\E[4:%p1%dm,
|
||||||
|
Ss=\E[%p1%d q,
|
||||||
|
Sync=\E[?2026%?%p1%{1}%-%tl%eh%;,
|
||||||
|
TS=\E]2;,
|
||||||
|
XM=\E[?1006;1000%?%p1%{1}%=%th%el%;,
|
||||||
|
XR=\E[>0q,
|
||||||
|
acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
|
||||||
|
bel=^G,
|
||||||
|
blink=\E[5m,
|
||||||
|
bold=\E[1m,
|
||||||
|
cbt=\E[Z,
|
||||||
|
civis=\E[?25l,
|
||||||
|
clear=\E[H\E[2J,
|
||||||
|
cnorm=\E[?12l\E[?25h,
|
||||||
|
cr=\r,
|
||||||
|
csr=\E[%i%p1%d;%p2%dr,
|
||||||
|
cub1=^H,
|
||||||
|
cub=\E[%p1%dD,
|
||||||
|
cud1=\n,
|
||||||
|
cud=\E[%p1%dB,
|
||||||
|
cuf1=\E[C,
|
||||||
|
cuf=\E[%p1%dC,
|
||||||
|
cup=\E[%i%p1%d;%p2%dH,
|
||||||
|
cuu1=\E[A,
|
||||||
|
cuu=\E[%p1%dA,
|
||||||
|
cvvis=\E[?12;25h,
|
||||||
|
dch1=\E[P,
|
||||||
|
dch=\E[%p1%dP,
|
||||||
|
dim=\E[2m,
|
||||||
|
dl1=\E[M,
|
||||||
|
dl=\E[%p1%dM,
|
||||||
|
dsl=\E]2;\E\\,
|
||||||
|
ech=\E[%p1%dX,
|
||||||
|
ed=\E[J,
|
||||||
|
el1=\E[1K,
|
||||||
|
el=\E[K,
|
||||||
|
fd=\E[?1004l,
|
||||||
|
fe=\E[?1004h,
|
||||||
|
flash=\E]555\E\\,
|
||||||
|
fsl=\E\\,
|
||||||
|
home=\E[H,
|
||||||
|
hpa=\E[%i%p1%dG,
|
||||||
|
ht=^I,
|
||||||
|
hts=\EH,
|
||||||
|
ich=\E[%p1%d@,
|
||||||
|
il1=\E[L,
|
||||||
|
il=\E[%p1%dL,
|
||||||
|
ind=\n,
|
||||||
|
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[4l\E>,
|
||||||
|
kDC3=\E[3;3~,
|
||||||
|
kDC4=\E[3;4~,
|
||||||
|
kDC5=\E[3;5~,
|
||||||
|
kDC6=\E[3;6~,
|
||||||
|
kDC7=\E[3;7~,
|
||||||
|
kDC=\E[3;2~,
|
||||||
|
kDN3=\E[1;3B,
|
||||||
|
kDN4=\E[1;4B,
|
||||||
|
kDN5=\E[1;5B,
|
||||||
|
kDN6=\E[1;6B,
|
||||||
|
kDN7=\E[1;7B,
|
||||||
|
kDN=\E[1;2B,
|
||||||
|
kEND3=\E[1;3F,
|
||||||
|
kEND4=\E[1;4F,
|
||||||
|
kEND5=\E[1;5F,
|
||||||
|
kEND6=\E[1;6F,
|
||||||
|
kEND7=\E[1;7F,
|
||||||
|
kEND=\E[1;2F,
|
||||||
|
kHOM3=\E[1;3H,
|
||||||
|
kHOM4=\E[1;4H,
|
||||||
|
kHOM5=\E[1;5H,
|
||||||
|
kHOM6=\E[1;6H,
|
||||||
|
kHOM7=\E[1;7H,
|
||||||
|
kHOM=\E[1;2H,
|
||||||
|
kIC3=\E[2;3~,
|
||||||
|
kIC4=\E[2;4~,
|
||||||
|
kIC5=\E[2;5~,
|
||||||
|
kIC6=\E[2;6~,
|
||||||
|
kIC7=\E[2;7~,
|
||||||
|
kIC=\E[2;2~,
|
||||||
|
kLFT3=\E[1;3D,
|
||||||
|
kLFT4=\E[1;4D,
|
||||||
|
kLFT5=\E[1;5D,
|
||||||
|
kLFT6=\E[1;6D,
|
||||||
|
kLFT7=\E[1;7D,
|
||||||
|
kLFT=\E[1;2D,
|
||||||
|
kNXT3=\E[6;3~,
|
||||||
|
kNXT4=\E[6;4~,
|
||||||
|
kNXT5=\E[6;5~,
|
||||||
|
kNXT6=\E[6;6~,
|
||||||
|
kNXT7=\E[6;7~,
|
||||||
|
kNXT=\E[6;2~,
|
||||||
|
kPRV3=\E[5;3~,
|
||||||
|
kPRV4=\E[5;4~,
|
||||||
|
kPRV5=\E[5;5~,
|
||||||
|
kPRV6=\E[5;6~,
|
||||||
|
kPRV7=\E[5;7~,
|
||||||
|
kPRV=\E[5;2~,
|
||||||
|
kRIT3=\E[1;3C,
|
||||||
|
kRIT4=\E[1;4C,
|
||||||
|
kRIT5=\E[1;5C,
|
||||||
|
kRIT6=\E[1;6C,
|
||||||
|
kRIT7=\E[1;7C,
|
||||||
|
kRIT=\E[1;2C,
|
||||||
|
kUP3=\E[1;3A,
|
||||||
|
kUP4=\E[1;4A,
|
||||||
|
kUP5=\E[1;5A,
|
||||||
|
kUP6=\E[1;6A,
|
||||||
|
kUP7=\E[1;7A,
|
||||||
|
kUP=\E[1;2A,
|
||||||
|
kbs=^?,
|
||||||
|
kcbt=\E[Z,
|
||||||
|
kcub1=\EOD,
|
||||||
|
kcud1=\EOB,
|
||||||
|
kcuf1=\EOC,
|
||||||
|
kcuu1=\EOA,
|
||||||
|
kdch1=\E[3~,
|
||||||
|
kend=\EOF,
|
||||||
|
kf10=\E[21~,
|
||||||
|
kf11=\E[23~,
|
||||||
|
kf12=\E[24~,
|
||||||
|
kf13=\E[1;2P,
|
||||||
|
kf14=\E[1;2Q,
|
||||||
|
kf15=\E[1;2R,
|
||||||
|
kf16=\E[1;2S,
|
||||||
|
kf17=\E[15;2~,
|
||||||
|
kf18=\E[17;2~,
|
||||||
|
kf19=\E[18;2~,
|
||||||
|
kf1=\EOP,
|
||||||
|
kf20=\E[19;2~,
|
||||||
|
kf21=\E[20;2~,
|
||||||
|
kf22=\E[21;2~,
|
||||||
|
kf23=\E[23;2~,
|
||||||
|
kf24=\E[24;2~,
|
||||||
|
kf25=\E[1;5P,
|
||||||
|
kf26=\E[1;5Q,
|
||||||
|
kf27=\E[1;5R,
|
||||||
|
kf28=\E[1;5S,
|
||||||
|
kf29=\E[15;5~,
|
||||||
|
kf2=\EOQ,
|
||||||
|
kf30=\E[17;5~,
|
||||||
|
kf31=\E[18;5~,
|
||||||
|
kf32=\E[19;5~,
|
||||||
|
kf33=\E[20;5~,
|
||||||
|
kf34=\E[21;5~,
|
||||||
|
kf35=\E[23;5~,
|
||||||
|
kf36=\E[24;5~,
|
||||||
|
kf37=\E[1;6P,
|
||||||
|
kf38=\E[1;6Q,
|
||||||
|
kf39=\E[1;6R,
|
||||||
|
kf3=\EOR,
|
||||||
|
kf40=\E[1;6S,
|
||||||
|
kf41=\E[15;6~,
|
||||||
|
kf42=\E[17;6~,
|
||||||
|
kf43=\E[18;6~,
|
||||||
|
kf44=\E[19;6~,
|
||||||
|
kf45=\E[20;6~,
|
||||||
|
kf46=\E[21;6~,
|
||||||
|
kf47=\E[23;6~,
|
||||||
|
kf48=\E[24;6~,
|
||||||
|
kf49=\E[1;3P,
|
||||||
|
kf4=\EOS,
|
||||||
|
kf50=\E[1;3Q,
|
||||||
|
kf51=\E[1;3R,
|
||||||
|
kf52=\E[1;3S,
|
||||||
|
kf53=\E[15;3~,
|
||||||
|
kf54=\E[17;3~,
|
||||||
|
kf55=\E[18;3~,
|
||||||
|
kf56=\E[19;3~,
|
||||||
|
kf57=\E[20;3~,
|
||||||
|
kf58=\E[21;3~,
|
||||||
|
kf59=\E[23;3~,
|
||||||
|
kf5=\E[15~,
|
||||||
|
kf60=\E[24;3~,
|
||||||
|
kf61=\E[1;4P,
|
||||||
|
kf62=\E[1;4Q,
|
||||||
|
kf63=\E[1;4R,
|
||||||
|
kf6=\E[17~,
|
||||||
|
kf7=\E[18~,
|
||||||
|
kf8=\E[19~,
|
||||||
|
kf9=\E[20~,
|
||||||
|
khome=\EOH,
|
||||||
|
kich1=\E[2~,
|
||||||
|
kind=\E[1;2B,
|
||||||
|
kmous=\E[<,
|
||||||
|
knp=\E[6~,
|
||||||
|
kpp=\E[5~,
|
||||||
|
kri=\E[1;2A,
|
||||||
|
kxIN=\E[I,
|
||||||
|
kxOUT=\E[O,
|
||||||
|
nel=\EE,
|
||||||
|
oc=\E]104\E\\,
|
||||||
|
op=\E[39;49m,
|
||||||
|
rc=\E8,
|
||||||
|
rep=%p1%c\E[%p2%{1}%-%db,
|
||||||
|
rev=\E[7m,
|
||||||
|
ri=\EM,
|
||||||
|
rin=\E[%p1%dT,
|
||||||
|
ritm=\E[23m,
|
||||||
|
rmacs=\E(B,
|
||||||
|
rmam=\E[?7l,
|
||||||
|
rmcup=\E[?1049l\E[23;0;0t,
|
||||||
|
rmir=\E[4l,
|
||||||
|
rmkx=\E[?1l\E>,
|
||||||
|
rmm=\E[?1036h\E[?1034l,
|
||||||
|
rmso=\E[27m,
|
||||||
|
rmul=\E[24m,
|
||||||
|
rmxx=\E[29m,
|
||||||
|
rs1=\Ec,
|
||||||
|
rs2=\E[!p\E[4l\E>,
|
||||||
|
rv=\E\\[>1;[0-9][0-9][0-9][0-9][0-9][0-9];0c,
|
||||||
|
sc=\E7,
|
||||||
|
setal=\E[58\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m,
|
||||||
|
setrgbb=\E[48\:2\:\:%p1%d\:%p2%d\:%p3%dm,
|
||||||
|
setrgbf=\E[38\:2\:\:%p1%d\:%p2%d\:%p3%dm,
|
||||||
|
sgr0=\E(B\E[m,
|
||||||
|
sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p5%t;2%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m,
|
||||||
|
sitm=\E[3m,
|
||||||
|
smacs=\E(0,
|
||||||
|
smam=\E[?7h,
|
||||||
|
smcup=\E[?1049h\E[22;0;0t,
|
||||||
|
smir=\E[4h,
|
||||||
|
smkx=\E[?1h\E=,
|
||||||
|
smm=\E[?1036l\E[?1034h,
|
||||||
|
smso=\E[7m,
|
||||||
|
smul=\E[4m,
|
||||||
|
smxx=\E[9m,
|
||||||
|
tbc=\E[3g,
|
||||||
|
tsl=\E]2;,
|
||||||
|
u6=\E[%i%d;%dR,
|
||||||
|
u7=\E[6n,
|
||||||
|
u8=\E[?%[;0123456789]c,
|
||||||
|
u9=\E[c,
|
||||||
|
vpa=\E[%i%p1%dd,
|
||||||
|
xm=\E[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;,
|
||||||
|
xr=\EP>\\|foot\\([0-9]+\\.[0-9]+\\.[0-9]+(-[0-9]+-g[a-f[0-9]+)?\\)?\E\\\\,
|
||||||
|
|
||||||
|
# XT,
|
||||||
|
# AX,
|
||||||
319
foot.ini
Normal file
319
foot.ini
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
# -*- conf -*-
|
||||||
|
|
||||||
|
# shell=$SHELL (if set, otherwise user's default shell from /etc/passwd)
|
||||||
|
# term=foot (or xterm-256color if built with -Dterminfo=disabled)
|
||||||
|
# login-shell=no
|
||||||
|
|
||||||
|
# app-id=foot # globally set wayland app-id. Default values are "foot" and "footclient" for desktop and server mode
|
||||||
|
# title=foot
|
||||||
|
# locked-title=no
|
||||||
|
|
||||||
|
# font=monospace:size=8
|
||||||
|
# font-bold=<bold variant of regular font>
|
||||||
|
# font-italic=<italic variant of regular font>
|
||||||
|
# font-bold-italic=<bold+italic variant of regular font>
|
||||||
|
# font-size-adjustment=0.5
|
||||||
|
# line-height=<font metrics>
|
||||||
|
# letter-spacing=0
|
||||||
|
# horizontal-letter-offset=0
|
||||||
|
# vertical-letter-offset=0
|
||||||
|
# underline-offset=<font metrics>
|
||||||
|
# underline-thickness=<font underline thickness>
|
||||||
|
# strikeout-thickness=<font strikeout thickness>
|
||||||
|
# box-drawings-uses-font-glyphs=no
|
||||||
|
# dpi-aware=no
|
||||||
|
# gamma-correct-blending=no
|
||||||
|
|
||||||
|
# initial-color-theme=dark
|
||||||
|
# initial-window-size-pixels=700x500 # Or,
|
||||||
|
# initial-window-size-chars=<COLSxROWS>
|
||||||
|
# initial-window-mode=windowed
|
||||||
|
# pad=0x0 center-when-maximized-and-fullscreen
|
||||||
|
# resize-by-cells=yes
|
||||||
|
# resize-keep-grid=yes
|
||||||
|
# resize-delay-ms=100
|
||||||
|
|
||||||
|
# bold-text-in-bright=no
|
||||||
|
# word-delimiters=,│`|:"'()[]{}<>
|
||||||
|
# selection-target=primary
|
||||||
|
# workers=<number of logical CPUs>
|
||||||
|
# utmp-helper=/usr/lib/utempter/utempter # When utmp backend is ‘libutempter’ (Linux)
|
||||||
|
# utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ‘ulog’ (FreeBSD)
|
||||||
|
|
||||||
|
# uppercase-regex-insert=yes
|
||||||
|
|
||||||
|
[environment]
|
||||||
|
# name=value
|
||||||
|
|
||||||
|
[security]
|
||||||
|
# osc52=enabled # disabled|copy-enabled|paste-enabled|enabled
|
||||||
|
|
||||||
|
[bell]
|
||||||
|
# system=yes
|
||||||
|
# urgent=no
|
||||||
|
# notify=no
|
||||||
|
# visual=no
|
||||||
|
# command=
|
||||||
|
# command-focused=no
|
||||||
|
|
||||||
|
[desktop-notifications]
|
||||||
|
# command=notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --hint BOOLEAN:suppress-sound:${muted} --hint STRING:sound-name:${sound-name} --replace-id ${replace-id} ${action-argument} --print-id -- ${title} ${body}
|
||||||
|
# command-action-argument=--action ${action-name}=${action-label}
|
||||||
|
# close=""
|
||||||
|
# inhibit-when-focused=yes
|
||||||
|
|
||||||
|
|
||||||
|
[scrollback]
|
||||||
|
# lines=1000
|
||||||
|
# multiplier=3.0
|
||||||
|
# indicator-position=relative
|
||||||
|
# indicator-format=""
|
||||||
|
|
||||||
|
[url]
|
||||||
|
# launch=xdg-open ${url}
|
||||||
|
# label-letters=sadfjklewcmpgh
|
||||||
|
# style=dotted (none|single|double|curly|dotted|dashed)
|
||||||
|
# osc8-underline=url-mode
|
||||||
|
# regex=(((https?://|mailto:|ftp://|file:|ssh:|ssh://|git://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:)|www\.)([0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]+|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*')+([0-9a-zA-Z/#@$&*+=~_%^\-]|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*'))
|
||||||
|
|
||||||
|
# You can define your own regex's, by adding a section called
|
||||||
|
# 'regex:<ID>' with a 'regex' and 'launch' key. These can then be tied
|
||||||
|
# to a key-binding. See foot.ini(5) for details
|
||||||
|
|
||||||
|
# [regex:your-fancy-name]
|
||||||
|
# regex=<a POSIX-Extended Regular Expression>
|
||||||
|
# launch=<path to script or application> ${match}
|
||||||
|
#
|
||||||
|
# [key-bindings]
|
||||||
|
# regex-launch=[your-fancy-name] Control+Shift+q
|
||||||
|
# regex-copy=[your-fancy-name] Control+Alt+Shift+q
|
||||||
|
|
||||||
|
[cursor]
|
||||||
|
# style=block
|
||||||
|
# blink=no
|
||||||
|
# blink-rate=500
|
||||||
|
# beam-thickness=1.5
|
||||||
|
# underline-thickness=<font underline thickness>
|
||||||
|
|
||||||
|
[mouse]
|
||||||
|
# hide-when-typing=no
|
||||||
|
# alternate-scroll-mode=yes
|
||||||
|
|
||||||
|
[touch]
|
||||||
|
# long-press-delay=400
|
||||||
|
|
||||||
|
[colors-dark]
|
||||||
|
# alpha=1.0
|
||||||
|
# alpha-mode=default # Can be `default`, `matching` or `all`
|
||||||
|
# background=242424
|
||||||
|
# foreground=ffffff
|
||||||
|
# flash=7f7f00
|
||||||
|
# flash-alpha=0.5
|
||||||
|
|
||||||
|
# cursor=<inverse foreground/background>
|
||||||
|
|
||||||
|
## Normal/regular colors (color palette 0-7)
|
||||||
|
# regular0=242424 # black
|
||||||
|
# regular1=f62b5a # red
|
||||||
|
# regular2=47b413 # green
|
||||||
|
# regular3=e3c401 # yellow
|
||||||
|
# regular4=24acd4 # blue
|
||||||
|
# regular5=f2affd # magenta
|
||||||
|
# regular6=13c299 # cyan
|
||||||
|
# regular7=e6e6e6 # white
|
||||||
|
|
||||||
|
## Bright colors (color palette 8-15)
|
||||||
|
# bright0=616161 # bright black
|
||||||
|
# bright1=ff4d51 # bright red
|
||||||
|
# bright2=35d450 # bright green
|
||||||
|
# bright3=e9e836 # bright yellow
|
||||||
|
# bright4=5dc5f8 # bright blue
|
||||||
|
# bright5=feabf2 # bright magenta
|
||||||
|
# bright6=24dfc4 # bright cyan
|
||||||
|
# bright7=ffffff # bright white
|
||||||
|
|
||||||
|
## dimmed colors (see foot.ini(5) man page)
|
||||||
|
# dim-blend-towards=black
|
||||||
|
# dim0=<not set>
|
||||||
|
# ...
|
||||||
|
# dim7=<not-set>
|
||||||
|
|
||||||
|
## The remaining 256-color palette
|
||||||
|
# 16 = <256-color palette #16>
|
||||||
|
# ...
|
||||||
|
# 255 = <256-color palette #255>
|
||||||
|
|
||||||
|
## Sixel colors
|
||||||
|
# sixel0 = 000000
|
||||||
|
# sixel1 = 3333cc
|
||||||
|
# sixel2 = cc2121
|
||||||
|
# sixel3 = 33cc33
|
||||||
|
# sixel4 = cc33cc
|
||||||
|
# sixel5 = 33cccc
|
||||||
|
# sixel6 = cccc33
|
||||||
|
# sixel7 = 878787
|
||||||
|
# sixel8 = 424242
|
||||||
|
# sixel9 = 545499
|
||||||
|
# sixel10 = 994242
|
||||||
|
# sixel11 = 549954
|
||||||
|
# sixel12 = 995499
|
||||||
|
# sixel13 = 549999
|
||||||
|
# sixel14 = 999954
|
||||||
|
# sixel15 = cccccc
|
||||||
|
|
||||||
|
## Misc colors
|
||||||
|
# selection-foreground=<inverse foreground/background>
|
||||||
|
# selection-background=<inverse foreground/background>
|
||||||
|
# jump-labels=<regular0> <regular3> # black-on-yellow
|
||||||
|
# scrollback-indicator=<regular0> <bright4> # black-on-bright-blue
|
||||||
|
# search-box-no-match=<regular0> <regular1> # black-on-red
|
||||||
|
# search-box-match=<regular0> <regular3> # black-on-yellow
|
||||||
|
# urls=<regular3>
|
||||||
|
|
||||||
|
[colors-light]
|
||||||
|
# Alternative color theme, see man page foot.ini(5)
|
||||||
|
# Same builtin defaults as [color], except for:
|
||||||
|
# dim-blend-towards=white
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
enabled=yes
|
||||||
|
position=bottom
|
||||||
|
style=rounded
|
||||||
|
layout=floating
|
||||||
|
height=26
|
||||||
|
# tab-width=200 (max width per tab in floating mode)
|
||||||
|
# tab-padding=8 (gap between tabs in floating mode)
|
||||||
|
# label-padding=8 (horizontal padding around the label inside each tab pill)
|
||||||
|
# margin=4 (edge gap; auto-added to bar height, does not squish pill)
|
||||||
|
# corner-radius=6 (corner rounding in pixels)
|
||||||
|
# background=1c1c1c
|
||||||
|
# foreground=b0b0b0
|
||||||
|
# active-background=3a3a3a
|
||||||
|
# active-foreground=ffffff
|
||||||
|
# inherit-cwd=no (new tabs open in the active tab's cwd; requires OSC 7 shell support)
|
||||||
|
# unread-indicator=● (string drawn before label when tab has unseen output; empty disables)
|
||||||
|
# unread-color=fabd2f (color of the unread-indicator)
|
||||||
|
|
||||||
|
[csd]
|
||||||
|
# preferred=server
|
||||||
|
# size=26
|
||||||
|
# font=<primary font>
|
||||||
|
# color=<foreground color>
|
||||||
|
# hide-when-maximized=no
|
||||||
|
# double-click-to-maximize=yes
|
||||||
|
# border-width=0
|
||||||
|
# border-color=<csd.color>
|
||||||
|
# button-width=26
|
||||||
|
# button-color=<background color>
|
||||||
|
# button-minimize-color=<regular4>
|
||||||
|
# button-maximize-color=<regular2>
|
||||||
|
# button-close-color=<regular1>
|
||||||
|
|
||||||
|
[key-bindings]
|
||||||
|
# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up
|
||||||
|
# scrollback-up-half-page=none
|
||||||
|
# scrollback-up-line=none
|
||||||
|
# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down
|
||||||
|
# scrollback-down-half-page=none
|
||||||
|
# scrollback-down-line=none
|
||||||
|
# scrollback-home=none
|
||||||
|
# scrollback-end=none
|
||||||
|
# clipboard-copy=Control+Shift+c XF86Copy
|
||||||
|
# clipboard-paste=Control+Shift+v XF86Paste
|
||||||
|
# primary-paste=Shift+Insert
|
||||||
|
# search-start=Control+Shift+r
|
||||||
|
# font-increase=Control+plus Control+equal Control+KP_Add
|
||||||
|
# font-decrease=Control+minus Control+KP_Subtract
|
||||||
|
# font-reset=Control+0 Control+KP_0
|
||||||
|
# spawn-terminal=Control+Shift+n
|
||||||
|
# minimize=none
|
||||||
|
# maximize=none
|
||||||
|
# fullscreen=none
|
||||||
|
# pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none
|
||||||
|
# pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none
|
||||||
|
# pipe-selected=[xargs -r firefox] none
|
||||||
|
# pipe-command-output=[wl-copy] none # Copy last command's output to the clipboard
|
||||||
|
# show-urls-launch=Control+Shift+o
|
||||||
|
# show-urls-copy=none
|
||||||
|
# show-urls-persistent=none
|
||||||
|
# prompt-prev=Control+Shift+z
|
||||||
|
# prompt-next=Control+Shift+x
|
||||||
|
# unicode-input=Control+Shift+u
|
||||||
|
# color-theme-switch-1=none
|
||||||
|
# color-theme-switch-2=none
|
||||||
|
# color-theme-toggle=none
|
||||||
|
# noop=none
|
||||||
|
# quit=none
|
||||||
|
# tab-new=Control+Shift+t
|
||||||
|
# tab-close=Control+Shift+w
|
||||||
|
# tab-next=Control+Tab
|
||||||
|
# tab-prev=Control+Shift+Tab
|
||||||
|
# tab-overview=Control+Shift+space
|
||||||
|
|
||||||
|
[search-bindings]
|
||||||
|
# cancel=Control+g Control+c Escape
|
||||||
|
# commit=Return KP_Enter
|
||||||
|
# commit-line=Control+Return
|
||||||
|
# find-prev=Control+r
|
||||||
|
# find-next=Control+s
|
||||||
|
# toggle-case=Mod1+c
|
||||||
|
# toggle-whole-word=Mod1+w
|
||||||
|
# toggle-regex=Mod1+r
|
||||||
|
# history-prev=Up
|
||||||
|
# history-next=Down
|
||||||
|
# cursor-left=Left Control+b
|
||||||
|
# cursor-left-word=Control+Left Mod1+b
|
||||||
|
# cursor-right=Right Control+f
|
||||||
|
# cursor-right-word=Control+Right Mod1+f
|
||||||
|
# cursor-home=Home Control+a
|
||||||
|
# cursor-end=End Control+e
|
||||||
|
# delete-prev=BackSpace
|
||||||
|
# delete-prev-word=Mod1+BackSpace Control+BackSpace
|
||||||
|
# delete-next=Delete
|
||||||
|
# delete-next-word=Mod1+d Control+Delete
|
||||||
|
# delete-to-start=Control+u
|
||||||
|
# delete-to-end=Control+k
|
||||||
|
# extend-char=Shift+Right
|
||||||
|
# extend-to-word-boundary=Control+w Control+Shift+Right
|
||||||
|
# extend-to-next-whitespace=Control+Shift+w
|
||||||
|
# extend-line-down=Shift+Down
|
||||||
|
# extend-backward-char=Shift+Left
|
||||||
|
# extend-backward-to-word-boundary=Control+Shift+Left
|
||||||
|
# extend-backward-to-next-whitespace=none
|
||||||
|
# extend-line-up=Shift+Up
|
||||||
|
# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste
|
||||||
|
# primary-paste=Shift+Insert
|
||||||
|
# unicode-input=none
|
||||||
|
# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up
|
||||||
|
# scrollback-up-half-page=none
|
||||||
|
# scrollback-up-line=none
|
||||||
|
# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down
|
||||||
|
# scrollback-down-half-page=none
|
||||||
|
# scrollback-down-line=none
|
||||||
|
# scrollback-home=none
|
||||||
|
# scrollback-end=none
|
||||||
|
|
||||||
|
[url-bindings]
|
||||||
|
# cancel=Control+g Control+c Control+d Escape
|
||||||
|
# toggle-url-visible=t
|
||||||
|
|
||||||
|
[text-bindings]
|
||||||
|
# \x03=Mod4+c # Map Super+c -> Ctrl+c
|
||||||
|
|
||||||
|
[mouse-bindings]
|
||||||
|
# scrollback-up-mouse=BTN_WHEEL_BACK
|
||||||
|
# scrollback-down-mouse=BTN_WHEEL_FORWARD
|
||||||
|
# font-increase=Control+BTN_WHEEL_BACK
|
||||||
|
# font-decrease=Control+BTN_WHEEL_FORWARD
|
||||||
|
# selection-override-modifiers=Shift
|
||||||
|
# primary-paste=BTN_MIDDLE
|
||||||
|
# select-begin=BTN_LEFT
|
||||||
|
# select-begin-block=Control+BTN_LEFT
|
||||||
|
# select-extend=BTN_RIGHT
|
||||||
|
# select-extend-character-wise=Control+BTN_RIGHT
|
||||||
|
# select-word=BTN_LEFT-2
|
||||||
|
# select-word-whitespace=Control+BTN_LEFT-2
|
||||||
|
# select-quote = BTN_LEFT-3
|
||||||
|
# select-row=BTN_LEFT-4
|
||||||
|
|
||||||
|
# vim: ft=dosini
|
||||||
11
footclient.desktop
Normal file
11
footclient.desktop
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Exec=footclient
|
||||||
|
Icon=foot
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;TerminalEmulator;
|
||||||
|
Keywords=shell;prompt;command;commandline;
|
||||||
|
|
||||||
|
Name=Foot Client
|
||||||
|
GenericName=Terminal
|
||||||
|
Comment=A wayland native terminal emulator (client)
|
||||||
60
generate-version.sh
Executable file
60
generate-version.sh
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ ${#} -ne 3 ]; then
|
||||||
|
echo "Usage: ${0} <default_version> <src_dir> <out_file>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
default_version=${1}
|
||||||
|
src_dir=${2}
|
||||||
|
out_file=${3}
|
||||||
|
|
||||||
|
# echo "default version: ${default_version}"
|
||||||
|
# echo "source directory: ${src_dir}"
|
||||||
|
# echo "output file: ${out_file}"
|
||||||
|
|
||||||
|
if [ -d "${src_dir}/.git" ] && command -v git > /dev/null; then
|
||||||
|
workdir=$(pwd)
|
||||||
|
cd "${src_dir}"
|
||||||
|
|
||||||
|
if git describe --tags > /dev/null 2>&1; then
|
||||||
|
git_version=$(git describe --always --tags)
|
||||||
|
else
|
||||||
|
# No tags available, happens in e.g. CI builds
|
||||||
|
git_version="${default_version}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
cd "${workdir}"
|
||||||
|
|
||||||
|
new_version="${git_version} ($(date "+%b %d %Y"), branch '${git_branch}')"
|
||||||
|
else
|
||||||
|
new_version="${default_version}"
|
||||||
|
extra=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
major=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1/')
|
||||||
|
minor=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\2/')
|
||||||
|
patch=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\3/')
|
||||||
|
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_MAJOR ${major}
|
||||||
|
#define FOOT_MINOR ${minor}
|
||||||
|
#define FOOT_PATCH ${patch}
|
||||||
|
#define FOOT_EXTRA \"${extra}\""
|
||||||
|
|
||||||
|
if [ -f "${out_file}" ]; then
|
||||||
|
old_version=$(cat "${out_file}")
|
||||||
|
else
|
||||||
|
old_version=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# echo "old version: ${old_version}"
|
||||||
|
# echo "new version: ${new_version}"
|
||||||
|
|
||||||
|
if [ "${old_version}" != "${new_version}" ]; then
|
||||||
|
echo "${new_version}" > "${out_file}"
|
||||||
|
fi
|
||||||
138
grid.h
Normal file
138
grid.h
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "debug.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
struct grid *grid_snapshot(const struct grid *grid);
|
||||||
|
void grid_free(struct grid *grid);
|
||||||
|
|
||||||
|
void grid_swap_row(struct grid *grid, int row_a, int row_b);
|
||||||
|
struct row *grid_row_alloc(int cols, bool initialize);
|
||||||
|
void grid_row_free(struct row *row);
|
||||||
|
|
||||||
|
void grid_resize_without_reflow(
|
||||||
|
struct grid *grid, int new_rows, int new_cols,
|
||||||
|
int old_screen_rows, int new_screen_rows);
|
||||||
|
|
||||||
|
void grid_resize_and_reflow(
|
||||||
|
struct grid *grid, const struct terminal *term, int new_rows, int new_cols,
|
||||||
|
int old_screen_rows, int new_screen_rows,
|
||||||
|
size_t tracking_points_count,
|
||||||
|
struct coord *const _tracking_points[static tracking_points_count]);
|
||||||
|
|
||||||
|
/* Convert row numbers between scrollback-relative and absolute coordinates */
|
||||||
|
int grid_row_abs_to_sb(const struct grid *grid, int screen_rows, int abs_row);
|
||||||
|
int grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row);
|
||||||
|
|
||||||
|
int grid_sb_start_ignore_uninitialized(const struct grid *grid, int screen_rows);
|
||||||
|
int grid_row_abs_to_sb_precalc_sb_start(
|
||||||
|
const struct grid *grid, int sb_start, int abs_row);
|
||||||
|
int grid_row_sb_to_abs_precalc_sb_start(
|
||||||
|
const struct grid *grid, int sb_start, int sb_rel_row);
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
grid_row_absolute(const struct grid *grid, int row_no)
|
||||||
|
{
|
||||||
|
return (grid->offset + row_no) & (grid->num_rows - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
grid_row_absolute_in_view(const struct grid *grid, int row_no)
|
||||||
|
{
|
||||||
|
return (grid->view + row_no) & (grid->num_rows - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct row *
|
||||||
|
_grid_row_maybe_alloc(struct grid *grid, int row_no, bool alloc_if_null)
|
||||||
|
{
|
||||||
|
xassert(grid->offset >= 0);
|
||||||
|
|
||||||
|
int real_row = grid_row_absolute(grid, row_no);
|
||||||
|
struct row *row = grid->rows[real_row];
|
||||||
|
|
||||||
|
if (row == NULL && alloc_if_null) {
|
||||||
|
row = grid_row_alloc(grid->num_cols, false);
|
||||||
|
grid->rows[real_row] = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
xassert(row != NULL);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct row *
|
||||||
|
grid_row(struct grid *grid, int row_no)
|
||||||
|
{
|
||||||
|
return _grid_row_maybe_alloc(grid, row_no, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct row *
|
||||||
|
grid_row_and_alloc(struct grid *grid, int row_no)
|
||||||
|
{
|
||||||
|
return _grid_row_maybe_alloc(grid, row_no, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct row *
|
||||||
|
grid_row_in_view(struct grid *grid, int row_no)
|
||||||
|
{
|
||||||
|
xassert(grid->view >= 0);
|
||||||
|
|
||||||
|
int real_row = grid_row_absolute_in_view(grid, row_no);
|
||||||
|
struct row *row = grid->rows[real_row];
|
||||||
|
|
||||||
|
xassert(row != NULL);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
void grid_row_uri_range_put(
|
||||||
|
struct row *row, int col, const char *uri, uint64_t id);
|
||||||
|
void grid_row_uri_range_erase(struct row *row, int start, int end);
|
||||||
|
|
||||||
|
void grid_row_underline_range_put(
|
||||||
|
struct row *row, int col, struct underline_range_data data);
|
||||||
|
void grid_row_underline_range_erase(struct row *row, int start, int end);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
grid_row_uri_range_destroy(struct row_range *range)
|
||||||
|
{
|
||||||
|
free(range->uri.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
grid_row_underline_range_destroy(struct row_range *range)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
grid_row_range_destroy(struct row_range *range, enum row_range_type type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case ROW_RANGE_URI: grid_row_uri_range_destroy(range); break;
|
||||||
|
case ROW_RANGE_UNDERLINE: grid_row_underline_range_destroy(range); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
grid_row_ranges_destroy(struct row_ranges *ranges, enum row_range_type type)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ranges->count; i++) {
|
||||||
|
grid_row_range_destroy(&ranges->v[i], type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
grid_row_reset_extra(struct row *row)
|
||||||
|
{
|
||||||
|
struct row_data *extra = row->extra;
|
||||||
|
|
||||||
|
if (likely(extra == NULL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
grid_row_ranges_destroy(&extra->uri_ranges, ROW_RANGE_URI);
|
||||||
|
grid_row_ranges_destroy(&extra->underline_ranges, ROW_RANGE_UNDERLINE);
|
||||||
|
free(extra->uri_ranges.v);
|
||||||
|
free(extra->underline_ranges.v);
|
||||||
|
|
||||||
|
free(extra);
|
||||||
|
row->extra = NULL;
|
||||||
|
}
|
||||||
54
hsl.c
Normal file
54
hsl.c
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#include "hsl.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
hsl_to_rgb(int hue, int sat, int lum)
|
||||||
|
{
|
||||||
|
double L = lum / 100.0;
|
||||||
|
double S = sat / 100.0;
|
||||||
|
double C = (1. - fabs(2. * L - 1.)) * S;
|
||||||
|
|
||||||
|
double X = C * (1. - fabs(fmod((double)hue / 60., 2.) - 1.));
|
||||||
|
double m = L - C / 2.;
|
||||||
|
|
||||||
|
double r, g, b;
|
||||||
|
if (hue >= 0 && hue <= 60) {
|
||||||
|
r = C;
|
||||||
|
g = X;
|
||||||
|
b = 0.;
|
||||||
|
} else if (hue >= 60 && hue <= 120) {
|
||||||
|
r = X;
|
||||||
|
g = C;
|
||||||
|
b = 0.;
|
||||||
|
} else if (hue >= 120 && hue <= 180) {
|
||||||
|
r = 0.;
|
||||||
|
g = C;
|
||||||
|
b = X;
|
||||||
|
} else if (hue >= 180 && hue <= 240) {
|
||||||
|
r = 0.;
|
||||||
|
g = X;
|
||||||
|
b = C;
|
||||||
|
} else if (hue >= 240 && hue <= 300) {
|
||||||
|
r = X;
|
||||||
|
g = 0.;
|
||||||
|
b = C;
|
||||||
|
} else if (hue >= 300 && hue <= 360) {
|
||||||
|
r = C;
|
||||||
|
g = 0.;
|
||||||
|
b = X;
|
||||||
|
} else {
|
||||||
|
r = 0.;
|
||||||
|
g = 0.;
|
||||||
|
b = 0.;
|
||||||
|
}
|
||||||
|
|
||||||
|
r += m;
|
||||||
|
g += m;
|
||||||
|
b += m;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(uint8_t)round(r * 255.) << 16 |
|
||||||
|
(uint8_t)round(g * 255.) << 8 |
|
||||||
|
(uint8_t)round(b * 255.) << 0);
|
||||||
|
}
|
||||||
5
hsl.h
Normal file
5
hsl.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
uint32_t hsl_to_rgb(int hue, int sat, int lum);
|
||||||
BIN
icons/hicolor/48x48/apps/foot.png
Normal file
BIN
icons/hicolor/48x48/apps/foot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 977 B |
88
icons/hicolor/scalable/apps/foot.svg
Normal file
88
icons/hicolor/scalable/apps/foot.svg
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="128" height="128"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
|
||||||
|
<title>foot logo</title>
|
||||||
|
<sodipodi:namedview showgrid="true">
|
||||||
|
<inkscape:grid empspacing="4" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata>
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work>
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:creator>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>Lennard Hofmann</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:creator>
|
||||||
|
<dc:source>https://freesvg.org/human-footprints</dc:source>
|
||||||
|
<cc:license
|
||||||
|
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||||
|
<dc:title>foot logo</dc:title>
|
||||||
|
<dc:subject>
|
||||||
|
<rdf:Bag>
|
||||||
|
<rdf:li>terminal emulator</rdf:li>
|
||||||
|
<rdf:li>footprint</rdf:li>
|
||||||
|
</rdf:Bag>
|
||||||
|
</dc:subject>
|
||||||
|
<dc:date>2020-06-23</dc:date>
|
||||||
|
<dc:description>Black square representing a terminal showing a human footprint as a prompt symbol and an underscore as the cursor</dc:description>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<rect
|
||||||
|
id="terminal-border"
|
||||||
|
ry="7.9238095" rx="7.9238095"
|
||||||
|
y="12.495239" x="12.495239"
|
||||||
|
height="103.00952" width="103.00952"
|
||||||
|
style="fill:#c0bfbc" />
|
||||||
|
<rect
|
||||||
|
id="terminal-bg"
|
||||||
|
style="fill:#282828"
|
||||||
|
width="96" height="96"
|
||||||
|
x="16" y="16"
|
||||||
|
rx="7.3846154" ry="7.3846154" />
|
||||||
|
<g
|
||||||
|
transform="matrix(0.85714209,0,0,0.85714209,3.4285132,-145.99985)" id="foot">
|
||||||
|
<path
|
||||||
|
style="fill:#fbf1c7"
|
||||||
|
id="main"
|
||||||
|
d="m 37.0057,207.12866 c -0.9539,-0.24232 -1.8766,-0.41748 -2.7117,-0.49721 -4.1121,-0.39258 -5.433,-0.0326 -7.5067,2.04908 -2.8434,2.85385 -2.2667,6.90676 1.4644,10.38038 8.0896,7.53109 8.2202,12.581 0.4808,18.2752 -3.1863,2.34409 -5.492,7.85321 -4.4994,10.73911 1.2501,3.63469 6.0098,5.02447 10.1635,2.97341 3.8964,-1.92373 8.3104,-8.49971 11.3208,-16.36234 0.2509,-0.65526 0.4966,-1.31942 0.7268,-1.9912 0.6909,-2.01525 1.2796,-4.09527 1.7466,-6.17933 1.4109,-6.29466 1.0666,-11.19321 -0.9956,-14.13789 -1.287,-1.83788 -6.056,-4.19923 -10.1892,-5.24924 z" />
|
||||||
|
<g
|
||||||
|
transform="translate(-1890.6933,-335.56903)" id="toes" style="fill:#fbf1c7">
|
||||||
|
<ellipse
|
||||||
|
id="toe3"
|
||||||
|
style="fill:#fbf1c7"
|
||||||
|
cx="1934.6934" cy="540.29291"
|
||||||
|
rx="2" ry="2.2761035" />
|
||||||
|
<ellipse
|
||||||
|
id="toe4"
|
||||||
|
style="fill:#fbf1c7"
|
||||||
|
cx="1939.0691" cy="543.35553"
|
||||||
|
rx="1.6242591" ry="1.7864922" />
|
||||||
|
<ellipse
|
||||||
|
id="toe5"
|
||||||
|
style="fill:#fbf1c7"
|
||||||
|
cx="1941.3607" cy="547.06903"
|
||||||
|
rx="1.33263" ry="1.5" />
|
||||||
|
<ellipse
|
||||||
|
id="toe1"
|
||||||
|
style="fill:#fbf1c7"
|
||||||
|
cx="1921.8835" cy="536.06903"
|
||||||
|
rx="3.8097539" ry="4.5" />
|
||||||
|
<ellipse
|
||||||
|
id="toe2"
|
||||||
|
style="fill:#fbf1c7"
|
||||||
|
cx="1929.1934" cy="537.56903"
|
||||||
|
rx="2.5" ry="3" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
id="cursor"
|
||||||
|
y="64" x="48"
|
||||||
|
height="4" width="22"
|
||||||
|
style="fill:#fbf1c7" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
1
icons/meson.build
Normal file
1
icons/meson.build
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
install_subdir('hicolor', install_dir : join_paths(get_option('datadir'), 'icons'))
|
||||||
525
ime.c
Normal file
525
ime.c
Normal file
|
|
@ -0,0 +1,525 @@
|
||||||
|
#include "ime.h"
|
||||||
|
|
||||||
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "text-input-unstable-v3.h"
|
||||||
|
|
||||||
|
#define LOG_MODULE "ime"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "char32.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "search.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "wayland.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
ime_reset_pending_preedit(struct seat *seat)
|
||||||
|
{
|
||||||
|
free(seat->ime.preedit.pending.text);
|
||||||
|
seat->ime.preedit.pending.text = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ime_reset_pending_commit(struct seat *seat)
|
||||||
|
{
|
||||||
|
free(seat->ime.commit.pending.text);
|
||||||
|
seat->ime.commit.pending.text = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ime_reset_pending(struct seat *seat)
|
||||||
|
{
|
||||||
|
ime_reset_pending_preedit(seat);
|
||||||
|
ime_reset_pending_commit(seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ime_reset_preedit(struct seat *seat)
|
||||||
|
{
|
||||||
|
if (seat->ime.preedit.cells == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
free(seat->ime.preedit.text);
|
||||||
|
free(seat->ime.preedit.cells);
|
||||||
|
seat->ime.preedit.text = NULL;
|
||||||
|
seat->ime.preedit.cells = NULL;
|
||||||
|
seat->ime.preedit.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
enter(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||||
|
struct wl_surface *surface)
|
||||||
|
{
|
||||||
|
struct seat *seat = data;
|
||||||
|
struct wl_window *win = wl_surface_get_user_data(surface);
|
||||||
|
struct terminal *term = win->term;
|
||||||
|
|
||||||
|
LOG_DBG("enter: seat=%s, term=%p", seat->name, (const void *)term);
|
||||||
|
|
||||||
|
if (seat->kbd_focus != term) {
|
||||||
|
LOG_WARN("compositor sent ime::enter() event before the "
|
||||||
|
"corresponding keyboard_enter() event");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The main grid is the *only* input-receiving surface we have */
|
||||||
|
seat->ime_focus = term;
|
||||||
|
|
||||||
|
const struct coord *cursor = &term->grid->cursor.point;
|
||||||
|
|
||||||
|
term_ime_set_cursor_rect(
|
||||||
|
term,
|
||||||
|
term->margins.left + cursor->col * term->cell_width,
|
||||||
|
term->margins.top + cursor->row * term->cell_height,
|
||||||
|
term->cell_width,
|
||||||
|
term->cell_height);
|
||||||
|
|
||||||
|
ime_enable(seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
leave(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||||
|
struct wl_surface *surface)
|
||||||
|
{
|
||||||
|
struct seat *seat = data;
|
||||||
|
LOG_DBG("leave: seat=%s", seat->name);
|
||||||
|
|
||||||
|
ime_disable(seat);
|
||||||
|
seat->ime_focus = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
preedit_string(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||||
|
const char *text, int32_t cursor_begin, int32_t cursor_end)
|
||||||
|
{
|
||||||
|
LOG_DBG("preedit-string: text=%s, begin=%d, end=%d", text, cursor_begin, cursor_end);
|
||||||
|
|
||||||
|
struct seat *seat = data;
|
||||||
|
|
||||||
|
ime_reset_pending_preedit(seat);
|
||||||
|
|
||||||
|
if (text != NULL) {
|
||||||
|
seat->ime.preedit.pending.text = xstrdup(text);
|
||||||
|
seat->ime.preedit.pending.cursor_begin = cursor_begin;
|
||||||
|
seat->ime.preedit.pending.cursor_end = cursor_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
commit_string(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||||
|
const char *text)
|
||||||
|
{
|
||||||
|
LOG_DBG("commit: text=%s", text);
|
||||||
|
|
||||||
|
struct seat *seat = data;
|
||||||
|
|
||||||
|
ime_reset_pending_commit(seat);
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
seat->ime.commit.pending.text = xstrdup(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
delete_surrounding_text(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||||
|
uint32_t before_length, uint32_t after_length)
|
||||||
|
{
|
||||||
|
LOG_DBG("delete-surrounding: before=%d, after=%d", before_length, after_length);
|
||||||
|
|
||||||
|
struct seat *seat = data;
|
||||||
|
seat->ime.surrounding.pending.before_length = before_length;
|
||||||
|
seat->ime.surrounding.pending.after_length = after_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||||
|
uint32_t serial)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* From text-input-unstable-v3.h:
|
||||||
|
*
|
||||||
|
* The application must proceed by evaluating the changes in the
|
||||||
|
* following order:
|
||||||
|
*
|
||||||
|
* 1. Replace existing preedit string with the cursor.
|
||||||
|
* 2. Delete requested surrounding text.
|
||||||
|
* 3. Insert commit string with the cursor at its end.
|
||||||
|
* 4. Calculate surrounding text to send.
|
||||||
|
* 5. Insert new preedit text in cursor position.
|
||||||
|
* 6. Place cursor inside preedit text.
|
||||||
|
*/
|
||||||
|
|
||||||
|
LOG_DBG("done: serial=%u", serial);
|
||||||
|
struct seat *seat = data;
|
||||||
|
struct terminal *term = seat->ime_focus;
|
||||||
|
|
||||||
|
if (seat->ime.serial != serial) {
|
||||||
|
LOG_DBG("IME serial mismatch: expected=0x%08x, got 0x%08x",
|
||||||
|
seat->ime.serial, serial);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (term == NULL) {
|
||||||
|
static bool have_warned = false;
|
||||||
|
if (!have_warned) {
|
||||||
|
LOG_WARN(
|
||||||
|
"%s: text-input::done() received on seat that isn't "
|
||||||
|
"focusing a terminal window", seat->name);
|
||||||
|
have_warned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. Delete existing pre-edit text */
|
||||||
|
if (seat->ime.preedit.cells != NULL) {
|
||||||
|
ime_reset_preedit(seat);
|
||||||
|
|
||||||
|
if (term != NULL) {
|
||||||
|
if (term->is_searching)
|
||||||
|
render_refresh_search(term);
|
||||||
|
else
|
||||||
|
render_refresh(term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 2. Delete requested surrounding text
|
||||||
|
*
|
||||||
|
* We don't support deleting surrounding text. But, we also never
|
||||||
|
* call set_surrounding_text() so hopefully we should never
|
||||||
|
* receive any requests to delete surrounding text.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 3. Insert commit string */
|
||||||
|
if (seat->ime.commit.pending.text != NULL) {
|
||||||
|
const char *text = seat->ime.commit.pending.text;
|
||||||
|
size_t len = strlen(text);
|
||||||
|
|
||||||
|
if (term != NULL) {
|
||||||
|
if (term->is_searching) {
|
||||||
|
search_add_chars(term, text, len);
|
||||||
|
render_refresh_search(term);
|
||||||
|
} else
|
||||||
|
term_to_slave(term, text, len);
|
||||||
|
}
|
||||||
|
ime_reset_pending_commit(seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Calculate surrounding text to send - not supported */
|
||||||
|
|
||||||
|
/* 5. Insert new pre-edit text */
|
||||||
|
char32_t *allocated_preedit_text = NULL;
|
||||||
|
|
||||||
|
if (seat->ime.preedit.pending.text == NULL ||
|
||||||
|
seat->ime.preedit.pending.text[0] == '\0' ||
|
||||||
|
(allocated_preedit_text = ambstoc32(seat->ime.preedit.pending.text)) == NULL)
|
||||||
|
{
|
||||||
|
ime_reset_pending_preedit(seat);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
xassert(seat->ime.preedit.pending.text != NULL);
|
||||||
|
xassert(allocated_preedit_text != NULL);
|
||||||
|
|
||||||
|
seat->ime.preedit.text = allocated_preedit_text;
|
||||||
|
|
||||||
|
size_t wchars = c32len(seat->ime.preedit.text);
|
||||||
|
|
||||||
|
/* Next, count number of cells needed */
|
||||||
|
size_t cell_count = 0;
|
||||||
|
size_t widths[wchars + 1];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < wchars; i++) {
|
||||||
|
int width = max(c32width(seat->ime.preedit.text[i]), 1);
|
||||||
|
widths[i] = width;
|
||||||
|
cell_count += width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate cells */
|
||||||
|
seat->ime.preedit.cells = xmalloc(
|
||||||
|
cell_count * sizeof(seat->ime.preedit.cells[0]));
|
||||||
|
seat->ime.preedit.count = cell_count;
|
||||||
|
|
||||||
|
/* Populate cells */
|
||||||
|
for (size_t i = 0, cell_idx = 0; i < wchars; i++) {
|
||||||
|
struct cell *cell = &seat->ime.preedit.cells[cell_idx];
|
||||||
|
|
||||||
|
int width = widths[i];
|
||||||
|
|
||||||
|
cell->wc = seat->ime.preedit.text[i];
|
||||||
|
cell->attrs = (struct attributes){.clean = 0};
|
||||||
|
|
||||||
|
for (int j = 1; j < width; j++) {
|
||||||
|
cell = &seat->ime.preedit.cells[cell_idx + j];
|
||||||
|
cell->wc = CELL_SPACER + width - j;
|
||||||
|
cell->attrs = (struct attributes){.clean = 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
cell_idx += width;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t byte_len = strlen(seat->ime.preedit.pending.text);
|
||||||
|
|
||||||
|
/* Pre-edit cursor - hidden */
|
||||||
|
if (seat->ime.preedit.pending.cursor_begin == -1 ||
|
||||||
|
seat->ime.preedit.pending.cursor_end == -1)
|
||||||
|
{
|
||||||
|
/* Note: docs says *both* begin and end should be -1,
|
||||||
|
* but what else can we do if only one is -1? */
|
||||||
|
LOG_DBG("pre-edit cursor is hidden");
|
||||||
|
seat->ime.preedit.cursor.hidden = true;
|
||||||
|
seat->ime.preedit.cursor.start = -1;
|
||||||
|
seat->ime.preedit.cursor.end = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (seat->ime.preedit.pending.cursor_begin == byte_len &&
|
||||||
|
seat->ime.preedit.pending.cursor_end == byte_len)
|
||||||
|
{
|
||||||
|
/* Cursor is *after* the entire pre-edit string */
|
||||||
|
seat->ime.preedit.cursor.hidden = false;
|
||||||
|
seat->ime.preedit.cursor.start = cell_count;
|
||||||
|
seat->ime.preedit.cursor.end = cell_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
/*
|
||||||
|
* Translate cursor position to cell indices
|
||||||
|
*
|
||||||
|
* The cursor_begin and cursor_end are counted in
|
||||||
|
* *bytes*. We want to map them to *cell* indices.
|
||||||
|
*
|
||||||
|
* To do this, we use mblen() to step though the utf-8
|
||||||
|
* pre-edit string, advancing a unicode character index as
|
||||||
|
* we go, *and* advancing a *cell* index using c32width()
|
||||||
|
* of the unicode character.
|
||||||
|
*
|
||||||
|
* When we find the matching *byte* index, we at the same
|
||||||
|
* time know both the unicode *and* cell index.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int cell_begin = -1, cell_end = -1;
|
||||||
|
for (size_t byte_idx = 0, wc_idx = 0, cell_idx = 0;
|
||||||
|
byte_idx < byte_len &&
|
||||||
|
wc_idx < wchars &&
|
||||||
|
cell_idx < cell_count &&
|
||||||
|
(cell_begin < 0 || cell_end < 0);
|
||||||
|
cell_idx += widths[wc_idx], wc_idx++)
|
||||||
|
{
|
||||||
|
if (seat->ime.preedit.pending.cursor_begin == byte_idx)
|
||||||
|
cell_begin = cell_idx;
|
||||||
|
if (seat->ime.preedit.pending.cursor_end == byte_idx)
|
||||||
|
cell_end = cell_idx;
|
||||||
|
|
||||||
|
/* Number of bytes of *next* utf-8 character */
|
||||||
|
size_t left = byte_len - byte_idx;
|
||||||
|
int wc_bytes = mblen(&seat->ime.preedit.pending.text[byte_idx], left);
|
||||||
|
|
||||||
|
if (wc_bytes <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
byte_idx += wc_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seat->ime.preedit.pending.cursor_end >= byte_len)
|
||||||
|
cell_end = cell_count;
|
||||||
|
|
||||||
|
/* Bounded by number of screen columns */
|
||||||
|
cell_begin = min(max(cell_begin, 0), cell_count - 1);
|
||||||
|
cell_end = min(max(cell_end, 0), cell_count);
|
||||||
|
|
||||||
|
if (cell_end < cell_begin)
|
||||||
|
cell_end = cell_begin;
|
||||||
|
|
||||||
|
/* Expand cursor end to end of glyph */
|
||||||
|
while (cell_end > cell_begin && cell_end < cell_count &&
|
||||||
|
seat->ime.preedit.cells[cell_end].wc >= CELL_SPACER)
|
||||||
|
{
|
||||||
|
cell_end++;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("pre-edit cursor: begin=%d, end=%d", cell_begin, cell_end);
|
||||||
|
|
||||||
|
xassert(cell_begin >= 0);
|
||||||
|
xassert(cell_begin < cell_count);
|
||||||
|
xassert(cell_begin <= cell_end);
|
||||||
|
xassert(cell_end >= 0);
|
||||||
|
xassert(cell_end <= cell_count);
|
||||||
|
|
||||||
|
seat->ime.preedit.cursor.hidden = false;
|
||||||
|
seat->ime.preedit.cursor.start = cell_begin;
|
||||||
|
seat->ime.preedit.cursor.end = cell_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Underline pre-edit string that is *not* covered by the cursor */
|
||||||
|
bool hidden = seat->ime.preedit.cursor.hidden;
|
||||||
|
int start = seat->ime.preedit.cursor.start;
|
||||||
|
int end = seat->ime.preedit.cursor.end;
|
||||||
|
|
||||||
|
for (size_t i = 0, cell_idx = 0; i < wchars; cell_idx += widths[i], i++) {
|
||||||
|
if (hidden || start == end || cell_idx < start || cell_idx >= end) {
|
||||||
|
struct cell *cell = &seat->ime.preedit.cells[cell_idx];
|
||||||
|
cell->attrs.underline = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ime_reset_pending_preedit(seat);
|
||||||
|
|
||||||
|
if (term != NULL) {
|
||||||
|
if (term->is_searching)
|
||||||
|
render_refresh_search(term);
|
||||||
|
else
|
||||||
|
render_refresh(term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ime_send_cursor_rect(struct seat *seat)
|
||||||
|
{
|
||||||
|
if (unlikely(seat->wayl->text_input_manager == NULL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (seat->ime_focus == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct terminal *term = seat->ime_focus;
|
||||||
|
|
||||||
|
if (!term->ime_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (seat->ime.cursor_rect.pending.x == seat->ime.cursor_rect.sent.x &&
|
||||||
|
seat->ime.cursor_rect.pending.y == seat->ime.cursor_rect.sent.y &&
|
||||||
|
seat->ime.cursor_rect.pending.width == seat->ime.cursor_rect.sent.width &&
|
||||||
|
seat->ime.cursor_rect.pending.height == seat->ime.cursor_rect.sent.height)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
zwp_text_input_v3_set_cursor_rectangle(
|
||||||
|
seat->wl_text_input,
|
||||||
|
seat->ime.cursor_rect.pending.x / term->scale,
|
||||||
|
seat->ime.cursor_rect.pending.y / term->scale,
|
||||||
|
seat->ime.cursor_rect.pending.width / term->scale,
|
||||||
|
seat->ime.cursor_rect.pending.height / term->scale);
|
||||||
|
|
||||||
|
zwp_text_input_v3_commit(seat->wl_text_input);
|
||||||
|
seat->ime.serial++;
|
||||||
|
|
||||||
|
seat->ime.cursor_rect.sent = seat->ime.cursor_rect.pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ime_enable(struct seat *seat)
|
||||||
|
{
|
||||||
|
if (unlikely(seat->wayl->text_input_manager == NULL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (seat->ime_focus == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct terminal *term = seat->ime_focus;
|
||||||
|
if (term == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!term->ime_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ime_reset_pending(seat);
|
||||||
|
ime_reset_preedit(seat);
|
||||||
|
|
||||||
|
zwp_text_input_v3_enable(seat->wl_text_input);
|
||||||
|
zwp_text_input_v3_set_content_type(
|
||||||
|
seat->wl_text_input,
|
||||||
|
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
|
||||||
|
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
|
||||||
|
|
||||||
|
zwp_text_input_v3_set_cursor_rectangle(
|
||||||
|
seat->wl_text_input,
|
||||||
|
seat->ime.cursor_rect.pending.x / term->scale,
|
||||||
|
seat->ime.cursor_rect.pending.y / term->scale,
|
||||||
|
seat->ime.cursor_rect.pending.width / term->scale,
|
||||||
|
seat->ime.cursor_rect.pending.height / term->scale);
|
||||||
|
|
||||||
|
seat->ime.cursor_rect.sent = seat->ime.cursor_rect.pending;
|
||||||
|
|
||||||
|
zwp_text_input_v3_commit(seat->wl_text_input);
|
||||||
|
seat->ime.serial++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ime_disable(struct seat *seat)
|
||||||
|
{
|
||||||
|
if (unlikely(seat->wayl->text_input_manager == NULL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (seat->ime_focus == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ime_reset_pending(seat);
|
||||||
|
ime_reset_preedit(seat);
|
||||||
|
|
||||||
|
zwp_text_input_v3_disable(seat->wl_text_input);
|
||||||
|
zwp_text_input_v3_commit(seat->wl_text_input);
|
||||||
|
seat->ime.serial++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ime_update_cursor_rect(struct seat *seat)
|
||||||
|
{
|
||||||
|
struct terminal *term = seat->ime_focus;
|
||||||
|
|
||||||
|
/* Set in render_ime_preedit() */
|
||||||
|
if (seat->ime.preedit.cells != NULL)
|
||||||
|
goto update;
|
||||||
|
|
||||||
|
/* Set in render_search_box() */
|
||||||
|
if (term->is_searching)
|
||||||
|
goto update;
|
||||||
|
|
||||||
|
int x, y, width, height;
|
||||||
|
int col = term->grid->cursor.point.col;
|
||||||
|
int row = term->grid->cursor.point.row;
|
||||||
|
row += term->grid->offset;
|
||||||
|
row -= term->grid->view;
|
||||||
|
row &= term->grid->num_rows - 1;
|
||||||
|
x = term->margins.left + col * term->cell_width;
|
||||||
|
y = term->margins.top + row * term->cell_height;
|
||||||
|
|
||||||
|
if (term->cursor_style == CURSOR_BEAM)
|
||||||
|
width = 1;
|
||||||
|
else
|
||||||
|
width = term->cell_width;
|
||||||
|
|
||||||
|
height = term->cell_height;
|
||||||
|
|
||||||
|
seat->ime.cursor_rect.pending.x = x;
|
||||||
|
seat->ime.cursor_rect.pending.y = y;
|
||||||
|
seat->ime.cursor_rect.pending.width = width;
|
||||||
|
seat->ime.cursor_rect.pending.height = height;
|
||||||
|
|
||||||
|
update:
|
||||||
|
ime_send_cursor_rect(seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct zwp_text_input_v3_listener text_input_listener = {
|
||||||
|
.enter = &enter,
|
||||||
|
.leave = &leave,
|
||||||
|
.preedit_string = &preedit_string,
|
||||||
|
.commit_string = &commit_string,
|
||||||
|
.delete_surrounding_text = &delete_surrounding_text,
|
||||||
|
.done = &done,
|
||||||
|
};
|
||||||
|
|
||||||
|
#else /* !FOOT_IME_ENABLED */
|
||||||
|
|
||||||
|
void ime_enable(struct seat *seat) {}
|
||||||
|
void ime_disable(struct seat *seat) {}
|
||||||
|
void ime_update_cursor_rect(struct seat *seat) {}
|
||||||
|
|
||||||
|
void ime_reset_pending_preedit(struct seat *seat) {}
|
||||||
|
void ime_reset_pending_commit(struct seat *seat) {}
|
||||||
|
void ime_reset_pending(struct seat *seat) {}
|
||||||
|
void ime_reset_preedit(struct seat *seat) {}
|
||||||
|
|
||||||
|
#endif
|
||||||
19
ime.h
Normal file
19
ime.h
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||||
|
|
||||||
|
#include "text-input-unstable-v3.h"
|
||||||
|
|
||||||
|
extern const struct zwp_text_input_v3_listener text_input_listener;
|
||||||
|
|
||||||
|
#endif /* FOOT_IME_ENABLED */
|
||||||
|
|
||||||
|
struct seat;
|
||||||
|
struct terminal;
|
||||||
|
|
||||||
|
void ime_enable(struct seat *seat);
|
||||||
|
void ime_disable(struct seat *seat);
|
||||||
|
void ime_update_cursor_rect(struct seat *seat);
|
||||||
|
|
||||||
|
void ime_reset_pending(struct seat *seat);
|
||||||
|
void ime_reset_preedit(struct seat *seat);
|
||||||
40
input.h
Normal file
40
input.h
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#include "cursor-shape.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "wayland.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom defines for mouse wheel left/right buttons.
|
||||||
|
*
|
||||||
|
* Libinput does not define these. On Wayland, all scroll events (both
|
||||||
|
* vertical and horizontal) are reported not as buttons, as 'axis'
|
||||||
|
* events.
|
||||||
|
*
|
||||||
|
* Libinput _does_ define BTN_BACK and BTN_FORWARD, which is
|
||||||
|
* what we use for vertical scroll events. But for horizontal scroll
|
||||||
|
* events, there aren't any pre-defined mouse buttons.
|
||||||
|
*
|
||||||
|
* Mouse buttons are in the range 0x110 - 0x11f, with joystick defines
|
||||||
|
* starting at 0x120.
|
||||||
|
*/
|
||||||
|
#define BTN_WHEEL_BACK 0x11c
|
||||||
|
#define BTN_WHEEL_FORWARD 0x11d
|
||||||
|
#define BTN_WHEEL_LEFT 0x11e
|
||||||
|
#define BTN_WHEEL_RIGHT 0x11f
|
||||||
|
|
||||||
|
extern const struct wl_keyboard_listener keyboard_listener;
|
||||||
|
extern const struct wl_pointer_listener pointer_listener;
|
||||||
|
extern const struct wl_touch_listener touch_listener;
|
||||||
|
|
||||||
|
void input_repeat(struct seat *seat, uint32_t key);
|
||||||
|
|
||||||
|
void get_current_modifiers(const struct seat *seat,
|
||||||
|
xkb_mod_mask_t *effective,
|
||||||
|
xkb_mod_mask_t *consumed,
|
||||||
|
uint32_t key, bool filter_locked);
|
||||||
|
|
||||||
|
enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y);
|
||||||
661
key-binding.c
Normal file
661
key-binding.c
Normal file
|
|
@ -0,0 +1,661 @@
|
||||||
|
#include "key-binding.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "key-binding"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "wayland.h"
|
||||||
|
#include "xkbcommon-vmod.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
struct vmod_map {
|
||||||
|
const char *name;
|
||||||
|
xkb_mod_mask_t virtual_mask;
|
||||||
|
xkb_mod_mask_t real_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct key_set {
|
||||||
|
struct key_binding_set public;
|
||||||
|
|
||||||
|
const struct config *conf;
|
||||||
|
const struct seat *seat;
|
||||||
|
size_t conf_ref_count;
|
||||||
|
|
||||||
|
/* Virtual to real modifier mappings */
|
||||||
|
struct vmod_map vmods[8];
|
||||||
|
};
|
||||||
|
typedef tll(struct key_set) bind_set_list_t;
|
||||||
|
|
||||||
|
struct key_binding_manager {
|
||||||
|
struct key_set *last_used_set;
|
||||||
|
bind_set_list_t binding_sets;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void load_keymap(struct key_set *set);
|
||||||
|
static void unload_keymap(struct key_set *set);
|
||||||
|
|
||||||
|
struct key_binding_manager *
|
||||||
|
key_binding_manager_new(void)
|
||||||
|
{
|
||||||
|
struct key_binding_manager *mgr = xcalloc(1, sizeof(*mgr));
|
||||||
|
return mgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
key_binding_manager_destroy(struct key_binding_manager *mgr)
|
||||||
|
{
|
||||||
|
xassert(tll_length(mgr->binding_sets) == 0);
|
||||||
|
free(mgr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
initialize_vmod_mappings(struct key_set *set)
|
||||||
|
{
|
||||||
|
if (set->seat == NULL || set->seat->kbd.xkb_keymap == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
set->vmods[0].name = XKB_VMOD_NAME_ALT;
|
||||||
|
set->vmods[1].name = XKB_VMOD_NAME_HYPER;
|
||||||
|
set->vmods[2].name = XKB_VMOD_NAME_LEVEL3;
|
||||||
|
set->vmods[3].name = XKB_VMOD_NAME_LEVEL5;
|
||||||
|
set->vmods[4].name = XKB_VMOD_NAME_META;
|
||||||
|
set->vmods[5].name = XKB_VMOD_NAME_NUM;
|
||||||
|
set->vmods[6].name = XKB_VMOD_NAME_SCROLL;
|
||||||
|
set->vmods[7].name = XKB_VMOD_NAME_SUPER;
|
||||||
|
|
||||||
|
struct xkb_state *scratch_state = xkb_state_new(set->seat->kbd.xkb_keymap);
|
||||||
|
xassert(scratch_state != NULL);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ALEN(set->vmods); i++) {
|
||||||
|
xkb_mod_index_t virt_idx = xkb_keymap_mod_get_index(
|
||||||
|
set->seat->kbd.xkb_keymap, set->vmods[i].name);
|
||||||
|
|
||||||
|
if (virt_idx != XKB_MOD_INVALID) {
|
||||||
|
xkb_mod_mask_t vmask = 1 << virt_idx;
|
||||||
|
xkb_state_update_mask(scratch_state, vmask, 0, 0, 0, 0, 0);
|
||||||
|
set->vmods[i].real_mask = xkb_state_serialize_mods(
|
||||||
|
scratch_state, XKB_STATE_MODS_DEPRESSED) & ~vmask;
|
||||||
|
set->vmods[i].virtual_mask = vmask;
|
||||||
|
|
||||||
|
LOG_DBG("%s: 0x%04x -> 0x%04x",
|
||||||
|
set->vmods[i].name,
|
||||||
|
set->vmods[i].virtual_mask,
|
||||||
|
set->vmods[i].real_mask);
|
||||||
|
} else {
|
||||||
|
set->vmods[i].virtual_mask = 0;
|
||||||
|
set->vmods[i].real_mask = 0;
|
||||||
|
|
||||||
|
LOG_DBG("%s: virtual modifier not available", set->vmods[i].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xkb_state_unref(scratch_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
key_binding_new_for_seat(struct key_binding_manager *mgr,
|
||||||
|
const struct seat *seat)
|
||||||
|
{
|
||||||
|
#if defined(_DEBUG)
|
||||||
|
tll_foreach(mgr->binding_sets, it)
|
||||||
|
xassert(it->item.seat != seat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
tll_foreach(seat->wayl->terms, it) {
|
||||||
|
struct key_set set = {
|
||||||
|
.public = {
|
||||||
|
.key = tll_init(),
|
||||||
|
.search = tll_init(),
|
||||||
|
.url = tll_init(),
|
||||||
|
.mouse = tll_init(),
|
||||||
|
},
|
||||||
|
.conf = it->item->conf,
|
||||||
|
.seat = seat,
|
||||||
|
.conf_ref_count = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
tll_push_back(mgr->binding_sets, set);
|
||||||
|
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
|
||||||
|
|
||||||
|
LOG_DBG("new (seat): set=%p, seat=%p, conf=%p, ref-count=1",
|
||||||
|
(void *)&tll_back(mgr->binding_sets),
|
||||||
|
(void *)set.seat, (void *)set.conf);
|
||||||
|
|
||||||
|
load_keymap(&tll_back(mgr->binding_sets));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("new (seat): total number of sets: %zu",
|
||||||
|
tll_length(mgr->binding_sets));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
key_binding_new_for_conf(struct key_binding_manager *mgr,
|
||||||
|
const struct wayland *wayl, const struct config *conf)
|
||||||
|
{
|
||||||
|
tll_foreach(wayl->seats, it) {
|
||||||
|
struct seat *seat = &it->item;
|
||||||
|
|
||||||
|
struct key_set *existing =
|
||||||
|
(struct key_set *)key_binding_for(mgr, conf, seat);
|
||||||
|
|
||||||
|
if (existing != NULL) {
|
||||||
|
existing->conf_ref_count++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct key_set set = {
|
||||||
|
.public = {
|
||||||
|
.key = tll_init(),
|
||||||
|
.search = tll_init(),
|
||||||
|
.url = tll_init(),
|
||||||
|
.mouse = tll_init(),
|
||||||
|
},
|
||||||
|
.conf = conf,
|
||||||
|
.seat = seat,
|
||||||
|
.conf_ref_count = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
tll_push_back(mgr->binding_sets, set);
|
||||||
|
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
|
||||||
|
|
||||||
|
load_keymap(&tll_back(mgr->binding_sets));
|
||||||
|
|
||||||
|
/* Chances are high this set will be requested next */
|
||||||
|
mgr->last_used_set = &tll_back(mgr->binding_sets);
|
||||||
|
|
||||||
|
LOG_DBG("new (conf): set=%p, seat=%p, conf=%p, ref-count=1",
|
||||||
|
(void *)&tll_back(mgr->binding_sets),
|
||||||
|
(void *)set.seat, (void *)set.conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("new (conf): total number of sets: %zu",
|
||||||
|
tll_length(mgr->binding_sets));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct key_binding_set * NOINLINE
|
||||||
|
key_binding_for(struct key_binding_manager *mgr, const struct config *conf,
|
||||||
|
const struct seat *seat)
|
||||||
|
{
|
||||||
|
struct key_set *last_used = mgr->last_used_set;
|
||||||
|
if (last_used != NULL &&
|
||||||
|
last_used->conf == conf &&
|
||||||
|
last_used->seat == seat)
|
||||||
|
{
|
||||||
|
// LOG_DBG("lookup: last used");
|
||||||
|
return &last_used->public;
|
||||||
|
}
|
||||||
|
|
||||||
|
tll_foreach(mgr->binding_sets, it) {
|
||||||
|
struct key_set *set = &it->item;
|
||||||
|
|
||||||
|
if (set->conf != conf)
|
||||||
|
continue;
|
||||||
|
if (set->seat != seat)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
LOG_DBG("lookup: set=%p, seat=%p, conf=%p, ref-count=%zu",
|
||||||
|
(void *)set, (void *)seat, (void *)conf, set->conf_ref_count);
|
||||||
|
#endif
|
||||||
|
mgr->last_used_set = set;
|
||||||
|
return &set->public;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
key_binding_set_destroy(struct key_binding_manager *mgr,
|
||||||
|
struct key_set *set)
|
||||||
|
{
|
||||||
|
unload_keymap(set);
|
||||||
|
if (mgr->last_used_set == set)
|
||||||
|
mgr->last_used_set = NULL;
|
||||||
|
|
||||||
|
/* Note: caller must remove from binding_sets */
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
key_binding_remove_seat(struct key_binding_manager *mgr,
|
||||||
|
const struct seat *seat)
|
||||||
|
{
|
||||||
|
tll_foreach(mgr->binding_sets, it) {
|
||||||
|
struct key_set *set = &it->item;
|
||||||
|
|
||||||
|
if (set->seat != seat)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
key_binding_set_destroy(mgr, set);
|
||||||
|
tll_remove(mgr->binding_sets, it);
|
||||||
|
|
||||||
|
LOG_DBG("remove seat: set=%p, seat=%p, total number of sets: %zu",
|
||||||
|
(void *)set, (void *)seat, tll_length(mgr->binding_sets));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("remove seat: total number of sets: %zu",
|
||||||
|
tll_length(mgr->binding_sets));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
key_binding_unref(struct key_binding_manager *mgr, const struct config *conf)
|
||||||
|
{
|
||||||
|
tll_foreach(mgr->binding_sets, it) {
|
||||||
|
struct key_set *set = &it->item;
|
||||||
|
|
||||||
|
if (set->conf != conf)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
xassert(set->conf_ref_count > 0);
|
||||||
|
if (--set->conf_ref_count == 0) {
|
||||||
|
LOG_DBG("unref conf: set=%p, seat=%p, conf=%p",
|
||||||
|
(void *)set, (void *)set->seat, (void *)conf);
|
||||||
|
|
||||||
|
key_binding_set_destroy(mgr, set);
|
||||||
|
tll_remove(mgr->binding_sets, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("unref conf: total number of sets: %zu",
|
||||||
|
tll_length(mgr->binding_sets));
|
||||||
|
}
|
||||||
|
|
||||||
|
static xkb_keycode_list_t
|
||||||
|
key_codes_for_xkb_sym(struct xkb_keymap *keymap, xkb_keysym_t sym)
|
||||||
|
{
|
||||||
|
xkb_keycode_list_t key_codes = tll_init();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find all key codes that map to this symbol.
|
||||||
|
*
|
||||||
|
* This allows us to match bindings in other layouts
|
||||||
|
* too.
|
||||||
|
*/
|
||||||
|
struct xkb_state *state = xkb_state_new(keymap);
|
||||||
|
|
||||||
|
for (xkb_keycode_t code = xkb_keymap_min_keycode(keymap);
|
||||||
|
code <= xkb_keymap_max_keycode(keymap);
|
||||||
|
code++)
|
||||||
|
{
|
||||||
|
if (xkb_state_key_get_one_sym(state, code) == sym)
|
||||||
|
tll_push_back(key_codes, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
xkb_state_unref(state);
|
||||||
|
return key_codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static xkb_keysym_t
|
||||||
|
maybe_repair_key_combo(const struct seat *seat,
|
||||||
|
xkb_keysym_t sym, xkb_mod_mask_t mods)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Detect combos containing a shifted symbol and the corresponding
|
||||||
|
* modifier, and replace the shifted symbol with its unshifted
|
||||||
|
* variant.
|
||||||
|
*
|
||||||
|
* For example, the combo is "Control+Shift+U". In this case,
|
||||||
|
* Shift is the modifier used to "shift" 'u' to 'U', after which
|
||||||
|
* 'Shift' will have been "consumed". Since we filter out consumed
|
||||||
|
* modifiers when matching key combos, this key combo will never
|
||||||
|
* trigger (we will never be able to match the 'Shift' modifier).
|
||||||
|
*
|
||||||
|
* There are two correct variants of the above key combo:
|
||||||
|
* - "Control+U" (upper case 'U')
|
||||||
|
* - "Control+Shift+u" (lower case 'u')
|
||||||
|
*
|
||||||
|
* What we do here is, for each key *code*, check if there are any
|
||||||
|
* (shifted) levels where it produces 'sym'. If there are, check
|
||||||
|
* *which* sets of modifiers are needed to produce it, and compare
|
||||||
|
* with 'mods'.
|
||||||
|
*
|
||||||
|
* If there is at least one common modifier, it means 'sym' is a
|
||||||
|
* "shifted" symbol, with the corresponding shifting modifier
|
||||||
|
* explicitly included in the key combo. I.e. the key combo will
|
||||||
|
* never trigger.
|
||||||
|
*
|
||||||
|
* We then proceed and "repair" the key combo by replacing 'sym'
|
||||||
|
* with the corresponding unshifted symbol.
|
||||||
|
*
|
||||||
|
* To reduce the noise, we ignore all key codes where the shifted
|
||||||
|
* symbol is the same as the unshifted symbol.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (xkb_keycode_t code = xkb_keymap_min_keycode(seat->kbd.xkb_keymap);
|
||||||
|
code <= xkb_keymap_max_keycode(seat->kbd.xkb_keymap);
|
||||||
|
code++)
|
||||||
|
{
|
||||||
|
xkb_layout_index_t layout_idx =
|
||||||
|
xkb_state_key_get_layout(seat->kbd.xkb_state, code);
|
||||||
|
|
||||||
|
/* Get all unshifted symbols for this key */
|
||||||
|
const xkb_keysym_t *base_syms = NULL;
|
||||||
|
size_t base_count = xkb_keymap_key_get_syms_by_level(
|
||||||
|
seat->kbd.xkb_keymap, code, layout_idx, 0, &base_syms);
|
||||||
|
|
||||||
|
if (base_count == 0 || sym == base_syms[0]) {
|
||||||
|
/* No unshifted symbols, or unshifted symbol is same as 'sym' */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name of the unshifted symbol, for logging */
|
||||||
|
char base_name[100];
|
||||||
|
xkb_keysym_get_name(base_syms[0], base_name, sizeof(base_name));
|
||||||
|
|
||||||
|
/* Iterate all shift levels */
|
||||||
|
for (xkb_level_index_t level_idx = 1;
|
||||||
|
level_idx < xkb_keymap_num_levels_for_key(
|
||||||
|
seat->kbd.xkb_keymap, code, layout_idx);
|
||||||
|
level_idx++) {
|
||||||
|
|
||||||
|
/* Get all symbols for current shift level */
|
||||||
|
const xkb_keysym_t *shifted_syms = NULL;
|
||||||
|
size_t shifted_count = xkb_keymap_key_get_syms_by_level(
|
||||||
|
seat->kbd.xkb_keymap, code,
|
||||||
|
layout_idx, level_idx, &shifted_syms);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < shifted_count; i++) {
|
||||||
|
if (shifted_syms[i] != sym)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Get modifier sets that produces the current shift level */
|
||||||
|
xkb_mod_mask_t mod_masks[16];
|
||||||
|
size_t mod_mask_count = xkb_keymap_key_get_mods_for_level(
|
||||||
|
seat->kbd.xkb_keymap, code, layout_idx, level_idx,
|
||||||
|
mod_masks, ALEN(mod_masks));
|
||||||
|
|
||||||
|
/* Check if key combo's modifier set intersects */
|
||||||
|
for (size_t j = 0; j < mod_mask_count; j++) {
|
||||||
|
if ((mod_masks[j] & mods) != mod_masks[j])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char combo[64] = {0};
|
||||||
|
|
||||||
|
for (int k = 0; k < sizeof(xkb_mod_mask_t) * 8; k++) {
|
||||||
|
if (!(mods & (1u << k)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const char *mod_name = xkb_keymap_mod_get_name(
|
||||||
|
seat->kbd.xkb_keymap, k);
|
||||||
|
strcat(combo, mod_name);
|
||||||
|
strcat(combo, "+");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = strlen(combo);
|
||||||
|
xkb_keysym_get_name(
|
||||||
|
sym, &combo[len], sizeof(combo) - len);
|
||||||
|
|
||||||
|
LOG_WARN(
|
||||||
|
"%s: combo with both explicit modifier and shifted symbol "
|
||||||
|
"(level=%d, mod-mask=0x%08x), "
|
||||||
|
"replacing with %s",
|
||||||
|
combo, level_idx, mod_masks[j], base_name);
|
||||||
|
|
||||||
|
/* Replace with unshifted symbol */
|
||||||
|
return base_syms[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 xkb_mod_mask_t
|
||||||
|
mods_to_mask(const struct seat *seat,
|
||||||
|
const struct vmod_map *vmods, size_t vmod_count,
|
||||||
|
const config_modifier_list_t *mods)
|
||||||
|
{
|
||||||
|
xkb_mod_mask_t mask = 0;
|
||||||
|
tll_foreach(*mods, it) {
|
||||||
|
const xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item);
|
||||||
|
|
||||||
|
if (idx == XKB_MOD_INVALID) {
|
||||||
|
LOG_ERR("%s: invalid modifier name", it->item);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
xkb_mod_mask_t mod = 1 << idx;
|
||||||
|
|
||||||
|
/* Check if this is a virtual modifier, and if so, use the
|
||||||
|
real modifier it maps to instead */
|
||||||
|
for (size_t i = 0; i < vmod_count; i++) {
|
||||||
|
if (vmods[i].virtual_mask == mod) {
|
||||||
|
mask |= vmods[i].real_mask;
|
||||||
|
mod = 0;
|
||||||
|
|
||||||
|
LOG_DBG("%s: virtual modifier, mapped to 0x%04x",
|
||||||
|
it->item, vmods[i].real_mask);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mask |= mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NOINLINE
|
||||||
|
convert_key_binding(struct key_set *set,
|
||||||
|
const struct config_key_binding *conf_binding,
|
||||||
|
key_binding_list_t *bindings)
|
||||||
|
{
|
||||||
|
const struct seat *seat = set->seat;
|
||||||
|
|
||||||
|
xkb_mod_mask_t mods = mods_to_mask(
|
||||||
|
seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers);
|
||||||
|
xkb_keysym_t sym = maybe_repair_key_combo(seat, conf_binding->k.sym, mods);
|
||||||
|
|
||||||
|
struct key_binding binding = {
|
||||||
|
.type = KEY_BINDING,
|
||||||
|
.action = conf_binding->action,
|
||||||
|
.aux = &conf_binding->aux,
|
||||||
|
.mods = mods,
|
||||||
|
.k = {
|
||||||
|
.sym = sym,
|
||||||
|
.key_codes = key_codes_for_xkb_sym(seat->kbd.xkb_keymap, sym),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
tll_push_back(*bindings, binding);
|
||||||
|
sort_binding_list(bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_key_bindings(struct key_set *set)
|
||||||
|
{
|
||||||
|
const struct config *conf = set->conf;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < conf->bindings.key.count; i++) {
|
||||||
|
const struct config_key_binding *binding = &conf->bindings.key.arr[i];
|
||||||
|
convert_key_binding(set, binding, &set->public.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_search_bindings(struct key_set *set)
|
||||||
|
{
|
||||||
|
const struct config *conf = set->conf;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < conf->bindings.search.count; i++) {
|
||||||
|
const struct config_key_binding *binding = &conf->bindings.search.arr[i];
|
||||||
|
convert_key_binding(set, binding, &set->public.search);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_url_bindings(struct key_set *set)
|
||||||
|
{
|
||||||
|
const struct config *conf = set->conf;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < conf->bindings.url.count; i++) {
|
||||||
|
const struct config_key_binding *binding = &conf->bindings.url.arr[i];
|
||||||
|
convert_key_binding(set, binding, &set->public.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_mouse_binding(struct key_set *set,
|
||||||
|
const struct config_key_binding *conf_binding)
|
||||||
|
{
|
||||||
|
struct key_binding binding = {
|
||||||
|
.type = MOUSE_BINDING,
|
||||||
|
.action = conf_binding->action,
|
||||||
|
.aux = &conf_binding->aux,
|
||||||
|
.mods = mods_to_mask(set->seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers),
|
||||||
|
.m = {
|
||||||
|
.button = conf_binding->m.button,
|
||||||
|
.count = conf_binding->m.count,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
tll_push_back(set->public.mouse, binding);
|
||||||
|
sort_binding_list(&set->public.mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_mouse_bindings(struct key_set *set)
|
||||||
|
{
|
||||||
|
const struct config *conf = set->conf;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < conf->bindings.mouse.count; i++) {
|
||||||
|
const struct config_key_binding *binding =
|
||||||
|
&conf->bindings.mouse.arr[i];
|
||||||
|
convert_mouse_binding(set, binding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NOINLINE
|
||||||
|
load_keymap(struct key_set *set)
|
||||||
|
{
|
||||||
|
LOG_DBG("load keymap: set=%p, seat=%p, conf=%p",
|
||||||
|
(void *)set, (void *)set->seat, (void *)set->conf);
|
||||||
|
|
||||||
|
if (set->seat->kbd.xkb_state == NULL ||
|
||||||
|
set->seat->kbd.xkb_keymap == NULL)
|
||||||
|
{
|
||||||
|
LOG_DBG("no XKB keymap");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_key_bindings(set);
|
||||||
|
convert_search_bindings(set);
|
||||||
|
convert_url_bindings(set);
|
||||||
|
convert_mouse_bindings(set);
|
||||||
|
|
||||||
|
set->public.selection_overrides = mods_to_mask(
|
||||||
|
set->seat, set->vmods, ALEN(set->vmods),
|
||||||
|
&set->conf->mouse.selection_override_modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
key_binding_load_keymap(struct key_binding_manager *mgr,
|
||||||
|
const struct seat *seat)
|
||||||
|
{
|
||||||
|
tll_foreach(mgr->binding_sets, it) {
|
||||||
|
struct key_set *set = &it->item;
|
||||||
|
|
||||||
|
if (set->seat == seat) {
|
||||||
|
initialize_vmod_mappings(set);
|
||||||
|
load_keymap(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NOINLINE
|
||||||
|
key_bindings_destroy(key_binding_list_t *bindings)
|
||||||
|
{
|
||||||
|
tll_foreach(*bindings, it) {
|
||||||
|
struct key_binding *bind = &it->item;
|
||||||
|
switch (bind->type) {
|
||||||
|
case KEY_BINDING: tll_free(it->item.k.key_codes); break;
|
||||||
|
case MOUSE_BINDING: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tll_remove(*bindings, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NOINLINE
|
||||||
|
unload_keymap(struct key_set *set)
|
||||||
|
{
|
||||||
|
key_bindings_destroy(&set->public.key);
|
||||||
|
key_bindings_destroy(&set->public.search);
|
||||||
|
key_bindings_destroy(&set->public.url);
|
||||||
|
key_bindings_destroy(&set->public.mouse);
|
||||||
|
set->public.selection_overrides = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
key_binding_unload_keymap(struct key_binding_manager *mgr,
|
||||||
|
const struct seat *seat)
|
||||||
|
{
|
||||||
|
tll_foreach(mgr->binding_sets, it) {
|
||||||
|
struct key_set *set = &it->item;
|
||||||
|
if (set->seat != seat)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LOG_DBG("unload keymap: set=%p, seat=%p, conf=%p",
|
||||||
|
(void *)set, (void *)seat, (void *)set->conf);
|
||||||
|
|
||||||
|
unload_keymap(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
200
key-binding.h
Normal file
200
key-binding.h
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include <tllist.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
enum bind_action_normal {
|
||||||
|
BIND_ACTION_NONE,
|
||||||
|
BIND_ACTION_NOOP,
|
||||||
|
BIND_ACTION_SCROLLBACK_UP_PAGE,
|
||||||
|
BIND_ACTION_SCROLLBACK_UP_HALF_PAGE,
|
||||||
|
BIND_ACTION_SCROLLBACK_UP_LINE,
|
||||||
|
BIND_ACTION_SCROLLBACK_DOWN_PAGE,
|
||||||
|
BIND_ACTION_SCROLLBACK_DOWN_HALF_PAGE,
|
||||||
|
BIND_ACTION_SCROLLBACK_DOWN_LINE,
|
||||||
|
BIND_ACTION_SCROLLBACK_HOME,
|
||||||
|
BIND_ACTION_SCROLLBACK_END,
|
||||||
|
BIND_ACTION_CLIPBOARD_COPY,
|
||||||
|
BIND_ACTION_CLIPBOARD_PASTE,
|
||||||
|
BIND_ACTION_PRIMARY_PASTE,
|
||||||
|
BIND_ACTION_SEARCH_START,
|
||||||
|
BIND_ACTION_FONT_SIZE_UP,
|
||||||
|
BIND_ACTION_FONT_SIZE_DOWN,
|
||||||
|
BIND_ACTION_FONT_SIZE_RESET,
|
||||||
|
BIND_ACTION_SPAWN_TERMINAL,
|
||||||
|
BIND_ACTION_MINIMIZE,
|
||||||
|
BIND_ACTION_MAXIMIZE,
|
||||||
|
BIND_ACTION_FULLSCREEN,
|
||||||
|
BIND_ACTION_PIPE_SCROLLBACK,
|
||||||
|
BIND_ACTION_PIPE_VIEW,
|
||||||
|
BIND_ACTION_PIPE_SELECTED,
|
||||||
|
BIND_ACTION_PIPE_COMMAND_OUTPUT,
|
||||||
|
BIND_ACTION_SHOW_URLS_COPY,
|
||||||
|
BIND_ACTION_SHOW_URLS_LAUNCH,
|
||||||
|
BIND_ACTION_SHOW_URLS_PERSISTENT,
|
||||||
|
BIND_ACTION_TEXT_BINDING,
|
||||||
|
BIND_ACTION_PROMPT_PREV,
|
||||||
|
BIND_ACTION_PROMPT_NEXT,
|
||||||
|
BIND_ACTION_UNICODE_INPUT,
|
||||||
|
BIND_ACTION_QUIT,
|
||||||
|
BIND_ACTION_REGEX_LAUNCH,
|
||||||
|
BIND_ACTION_REGEX_COPY,
|
||||||
|
BIND_ACTION_THEME_SWITCH_1,
|
||||||
|
BIND_ACTION_THEME_SWITCH_2,
|
||||||
|
BIND_ACTION_THEME_SWITCH_DARK,
|
||||||
|
BIND_ACTION_THEME_SWITCH_LIGHT,
|
||||||
|
BIND_ACTION_THEME_TOGGLE,
|
||||||
|
|
||||||
|
/* Tab actions */
|
||||||
|
BIND_ACTION_TAB_NEW,
|
||||||
|
BIND_ACTION_TAB_CLOSE,
|
||||||
|
BIND_ACTION_TAB_NEXT,
|
||||||
|
BIND_ACTION_TAB_PREV,
|
||||||
|
BIND_ACTION_TAB_1,
|
||||||
|
BIND_ACTION_TAB_2,
|
||||||
|
BIND_ACTION_TAB_3,
|
||||||
|
BIND_ACTION_TAB_4,
|
||||||
|
BIND_ACTION_TAB_5,
|
||||||
|
BIND_ACTION_TAB_6,
|
||||||
|
BIND_ACTION_TAB_7,
|
||||||
|
BIND_ACTION_TAB_8,
|
||||||
|
BIND_ACTION_TAB_9,
|
||||||
|
BIND_ACTION_TAB_OVERVIEW,
|
||||||
|
|
||||||
|
/* Mouse specific actions - i.e. they require a mouse coordinate */
|
||||||
|
BIND_ACTION_SCROLLBACK_UP_MOUSE,
|
||||||
|
BIND_ACTION_SCROLLBACK_DOWN_MOUSE,
|
||||||
|
BIND_ACTION_SELECT_BEGIN,
|
||||||
|
BIND_ACTION_SELECT_BEGIN_BLOCK,
|
||||||
|
BIND_ACTION_SELECT_EXTEND,
|
||||||
|
BIND_ACTION_SELECT_EXTEND_CHAR_WISE,
|
||||||
|
BIND_ACTION_SELECT_WORD,
|
||||||
|
BIND_ACTION_SELECT_WORD_WS,
|
||||||
|
BIND_ACTION_SELECT_QUOTE,
|
||||||
|
BIND_ACTION_SELECT_ROW,
|
||||||
|
|
||||||
|
BIND_ACTION_KEY_COUNT = BIND_ACTION_TAB_OVERVIEW + 1,
|
||||||
|
BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum bind_action_search {
|
||||||
|
BIND_ACTION_SEARCH_NONE,
|
||||||
|
BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE,
|
||||||
|
BIND_ACTION_SEARCH_SCROLLBACK_UP_HALF_PAGE,
|
||||||
|
BIND_ACTION_SEARCH_SCROLLBACK_UP_LINE,
|
||||||
|
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE,
|
||||||
|
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_HALF_PAGE,
|
||||||
|
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_LINE,
|
||||||
|
BIND_ACTION_SEARCH_SCROLLBACK_HOME,
|
||||||
|
BIND_ACTION_SEARCH_SCROLLBACK_END,
|
||||||
|
BIND_ACTION_SEARCH_CANCEL,
|
||||||
|
BIND_ACTION_SEARCH_COMMIT,
|
||||||
|
BIND_ACTION_SEARCH_FIND_PREV,
|
||||||
|
BIND_ACTION_SEARCH_FIND_NEXT,
|
||||||
|
BIND_ACTION_SEARCH_EDIT_LEFT,
|
||||||
|
BIND_ACTION_SEARCH_EDIT_LEFT_WORD,
|
||||||
|
BIND_ACTION_SEARCH_EDIT_RIGHT,
|
||||||
|
BIND_ACTION_SEARCH_EDIT_RIGHT_WORD,
|
||||||
|
BIND_ACTION_SEARCH_EDIT_HOME,
|
||||||
|
BIND_ACTION_SEARCH_EDIT_END,
|
||||||
|
BIND_ACTION_SEARCH_DELETE_PREV,
|
||||||
|
BIND_ACTION_SEARCH_DELETE_PREV_WORD,
|
||||||
|
BIND_ACTION_SEARCH_DELETE_NEXT,
|
||||||
|
BIND_ACTION_SEARCH_DELETE_NEXT_WORD,
|
||||||
|
BIND_ACTION_SEARCH_DELETE_TO_START,
|
||||||
|
BIND_ACTION_SEARCH_DELETE_TO_END,
|
||||||
|
BIND_ACTION_SEARCH_EXTEND_CHAR,
|
||||||
|
BIND_ACTION_SEARCH_EXTEND_WORD,
|
||||||
|
BIND_ACTION_SEARCH_EXTEND_WORD_WS,
|
||||||
|
BIND_ACTION_SEARCH_EXTEND_LINE_DOWN,
|
||||||
|
BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR,
|
||||||
|
BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD,
|
||||||
|
BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS,
|
||||||
|
BIND_ACTION_SEARCH_EXTEND_LINE_UP,
|
||||||
|
BIND_ACTION_SEARCH_CLIPBOARD_PASTE,
|
||||||
|
BIND_ACTION_SEARCH_PRIMARY_PASTE,
|
||||||
|
BIND_ACTION_SEARCH_UNICODE_INPUT,
|
||||||
|
BIND_ACTION_SEARCH_TOGGLE_CASE,
|
||||||
|
BIND_ACTION_SEARCH_TOGGLE_WHOLE_WORD,
|
||||||
|
BIND_ACTION_SEARCH_TOGGLE_REGEX,
|
||||||
|
BIND_ACTION_SEARCH_HISTORY_PREV,
|
||||||
|
BIND_ACTION_SEARCH_HISTORY_NEXT,
|
||||||
|
BIND_ACTION_SEARCH_COMMIT_LINE,
|
||||||
|
BIND_ACTION_SEARCH_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum bind_action_url {
|
||||||
|
BIND_ACTION_URL_NONE,
|
||||||
|
BIND_ACTION_URL_CANCEL,
|
||||||
|
BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL,
|
||||||
|
BIND_ACTION_URL_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef tll(xkb_keycode_t) xkb_keycode_list_t;
|
||||||
|
|
||||||
|
struct key_binding {
|
||||||
|
enum key_binding_type type;
|
||||||
|
|
||||||
|
int action; /* enum bind_action_* */
|
||||||
|
xkb_mod_mask_t mods;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
xkb_keysym_t sym;
|
||||||
|
xkb_keycode_list_t key_codes;
|
||||||
|
} k;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t button;
|
||||||
|
int count;
|
||||||
|
} m;
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct binding_aux *aux;
|
||||||
|
};
|
||||||
|
typedef tll(struct key_binding) key_binding_list_t;
|
||||||
|
|
||||||
|
struct terminal;
|
||||||
|
struct seat;
|
||||||
|
struct wayland;
|
||||||
|
|
||||||
|
struct key_binding_set {
|
||||||
|
key_binding_list_t key;
|
||||||
|
key_binding_list_t search;
|
||||||
|
key_binding_list_t url;
|
||||||
|
key_binding_list_t mouse;
|
||||||
|
xkb_mod_mask_t selection_overrides;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct key_binding_manager;
|
||||||
|
|
||||||
|
struct key_binding_manager *key_binding_manager_new(void);
|
||||||
|
void key_binding_manager_destroy(struct key_binding_manager *mgr);
|
||||||
|
|
||||||
|
void key_binding_new_for_seat(
|
||||||
|
struct key_binding_manager *mgr, const struct seat *seat);
|
||||||
|
|
||||||
|
void key_binding_new_for_conf(
|
||||||
|
struct key_binding_manager *mgr, const struct wayland *wayl,
|
||||||
|
const struct config *conf);
|
||||||
|
|
||||||
|
/* Returns the set of key bindings associated with this seat/conf pair */
|
||||||
|
struct key_binding_set *key_binding_for(
|
||||||
|
struct key_binding_manager *mgr, const struct config *conf,
|
||||||
|
const struct seat *seat);
|
||||||
|
|
||||||
|
/* Remove all key bindings tied to the specified seat */
|
||||||
|
void key_binding_remove_seat(
|
||||||
|
struct key_binding_manager *mgr, const struct seat *seat);
|
||||||
|
|
||||||
|
void key_binding_unref(
|
||||||
|
struct key_binding_manager *mgr, const struct config *conf);
|
||||||
|
|
||||||
|
void key_binding_load_keymap(
|
||||||
|
struct key_binding_manager *mgr, const struct seat *seat);
|
||||||
|
void key_binding_unload_keymap(
|
||||||
|
struct key_binding_manager *mgr, const struct seat *seat);
|
||||||
414
keymap.h
Normal file
414
keymap.h
Normal file
|
|
@ -0,0 +1,414 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
enum modifier {
|
||||||
|
MOD_NONE = 0x0,
|
||||||
|
MOD_ANY = 0x1,
|
||||||
|
MOD_SHIFT = 0x2,
|
||||||
|
MOD_ALT = 0x4,
|
||||||
|
MOD_CTRL = 0x8,
|
||||||
|
MOD_META = 0x10,
|
||||||
|
MOD_MODIFY_OTHER_KEYS_STATE1 = 0x20,
|
||||||
|
MOD_MODIFY_OTHER_KEYS_STATE2 = 0x40,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct key_data {
|
||||||
|
enum modifier modifiers;
|
||||||
|
enum cursor_keys cursor_keys_mode;
|
||||||
|
enum keypad_keys keypad_keys_mode;
|
||||||
|
const char *seq;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_escape[] = {
|
||||||
|
{MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;2;27~"},
|
||||||
|
{MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\033"},
|
||||||
|
{MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;4;27~"},
|
||||||
|
{MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;5;27~"},
|
||||||
|
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;27~"},
|
||||||
|
{MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;7;27~"},
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;8;27~"},
|
||||||
|
{MOD_META, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;9;27~"},
|
||||||
|
{MOD_META | MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;10;27~"},
|
||||||
|
{MOD_META | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;11;27~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;12;27~"},
|
||||||
|
{MOD_META | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;13;27~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;14;27~"},
|
||||||
|
{MOD_META | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;15;27~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;16;27~"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_return[] = {
|
||||||
|
{MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;2;13~"},
|
||||||
|
{MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\r"},
|
||||||
|
{MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;3;13~"},
|
||||||
|
{MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;4;13~"},
|
||||||
|
{MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;5;13~"},
|
||||||
|
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;13~"},
|
||||||
|
{MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;7;13~"},
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;8;13~"},
|
||||||
|
{MOD_META, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;9;13~"},
|
||||||
|
{MOD_META | MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;10;13~"},
|
||||||
|
{MOD_META | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;11;13~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;12;13~"},
|
||||||
|
{MOD_META | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;13;13~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;14;13~"},
|
||||||
|
{MOD_META | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;15;13~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;16;13~"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\r"},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Tab isn't covered by the regular "modifyOtherKeys" handling */
|
||||||
|
static const struct key_data key_tab[] = {
|
||||||
|
{MOD_SHIFT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[Z"},
|
||||||
|
{MOD_SHIFT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;2;9~"},
|
||||||
|
{MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\t"},
|
||||||
|
{MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;3;9~"},
|
||||||
|
{MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;4;9~"},
|
||||||
|
{MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;5;9~"},
|
||||||
|
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;9~"},
|
||||||
|
{MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;7;9~"},
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;8;9~"},
|
||||||
|
{MOD_META, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;9;9~"},
|
||||||
|
{MOD_META | MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;10;9~"},
|
||||||
|
{MOD_META | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;11;9~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;12;9~"},
|
||||||
|
{MOD_META | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;13;9~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;14;9~"},
|
||||||
|
{MOD_META | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;15;9~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;16;9~"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\t"},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shift+Tab produces ISO_Left_Tab
|
||||||
|
*
|
||||||
|
* However, all combos (except Shift+Tab) acts as if we pressed
|
||||||
|
* mods+shift+tab.
|
||||||
|
*/
|
||||||
|
static const struct key_data key_iso_left_tab[] = {
|
||||||
|
{MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;4;9~"},
|
||||||
|
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;9~"},
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;8;9~"},
|
||||||
|
{MOD_SHIFT | MOD_META, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;10;9~"},
|
||||||
|
{MOD_SHIFT | MOD_META | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;12;9~"},
|
||||||
|
{MOD_SHIFT | MOD_META | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;14;9~"},
|
||||||
|
{MOD_SHIFT | MOD_META | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;16;9~"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[Z"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_backspace[] = {
|
||||||
|
{MOD_SHIFT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\x7f"},
|
||||||
|
{MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\x7f"},
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\x7f"},
|
||||||
|
{MOD_SHIFT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\x08"},
|
||||||
|
{MOD_ALT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\x08"},
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\x08"},
|
||||||
|
{MOD_META | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\x7f"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\x7f"},
|
||||||
|
{MOD_META | MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\x7f"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\x7f"},
|
||||||
|
{MOD_META | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\x08"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\x08"},
|
||||||
|
{MOD_META | MOD_ALT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\x08"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE1, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\x08"},
|
||||||
|
|
||||||
|
{MOD_SHIFT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;2;127~"},
|
||||||
|
{MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;3;127~"},
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;4;127~"},
|
||||||
|
{MOD_SHIFT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;8~"},
|
||||||
|
{MOD_ALT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;7;8~"},
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;8;8~"},
|
||||||
|
{MOD_META | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;9;127~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;10;127~"},
|
||||||
|
{MOD_META | MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;11;127~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;12;127~"},
|
||||||
|
{MOD_META | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;13;8~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;14;8~"},
|
||||||
|
{MOD_META | MOD_ALT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;15;8~"},
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL | MOD_MODIFY_OTHER_KEYS_STATE2, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;16;8~"},
|
||||||
|
|
||||||
|
{MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\x08"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\x7f"},
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DEFAULT_MODS_FOR_SINGLE(sym) \
|
||||||
|
{MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;2"#sym}, \
|
||||||
|
{MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;3"#sym}, \
|
||||||
|
{MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;4"#sym}, \
|
||||||
|
{MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;5"#sym}, \
|
||||||
|
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;6"#sym}, \
|
||||||
|
{MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;7"#sym}, \
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;8"#sym}, \
|
||||||
|
{MOD_META, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;9"#sym}, \
|
||||||
|
{MOD_META | MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;10"#sym}, \
|
||||||
|
{MOD_META | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;11"#sym}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;12"#sym}, \
|
||||||
|
{MOD_META | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;13"#sym}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;14"#sym}, \
|
||||||
|
{MOD_META | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;15"#sym}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;16"#sym}
|
||||||
|
|
||||||
|
#define DEFAULT_MODS_FOR_TILDE(sym) \
|
||||||
|
{MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";2~"}, \
|
||||||
|
{MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";3~"}, \
|
||||||
|
{MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";4~"}, \
|
||||||
|
{MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";5~"}, \
|
||||||
|
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";6~"}, \
|
||||||
|
{MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";7~"}, \
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";8~"}, \
|
||||||
|
{MOD_META, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";9~"}, \
|
||||||
|
{MOD_META | MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";10~"}, \
|
||||||
|
{MOD_META | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";11~"}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";12~"}, \
|
||||||
|
{MOD_META | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";13~"}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";14~"}, \
|
||||||
|
{MOD_META | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";15~"}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033["#sym";16~"}
|
||||||
|
|
||||||
|
|
||||||
|
static const struct key_data key_up[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(A),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OA"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[A"},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_down[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(B),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OB"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[B"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_right[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(C),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OC"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[C"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_left[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(D),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OD"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[D"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_home[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(H),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OH"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[H"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_end[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(F),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OF"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[F"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_insert[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(2),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[2~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_delete[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(3),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[3~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_pageup[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(5),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[5~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_pagedown[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(6),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[6~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f1[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(P),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033OP"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f2[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(Q),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033OQ"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f3[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(R),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033OR"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f4[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(S),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033OS"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f5[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(15),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[15~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f6[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(17),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[17~"},
|
||||||
|
};
|
||||||
|
static const struct key_data key_f7[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(18),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[18~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f8[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(19),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[19~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f9[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(20),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[20~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f10[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(21),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[21~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f11[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(23),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[23~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f12[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(24),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[24~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_f13[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;2P"}};
|
||||||
|
static const struct key_data key_f14[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;2Q"}};
|
||||||
|
static const struct key_data key_f15[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;2R"}};
|
||||||
|
static const struct key_data key_f16[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;2S"}};
|
||||||
|
static const struct key_data key_f17[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[15;2~"}};
|
||||||
|
static const struct key_data key_f18[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[17;2~"}};
|
||||||
|
static const struct key_data key_f19[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[18;2~"}};
|
||||||
|
static const struct key_data key_f20[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[19;2~"}};
|
||||||
|
static const struct key_data key_f21[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[20;2~"}};
|
||||||
|
static const struct key_data key_f22[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[21;2~"}};
|
||||||
|
static const struct key_data key_f23[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[23;2~"}};
|
||||||
|
static const struct key_data key_f24[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[24;2~"}};
|
||||||
|
static const struct key_data key_f25[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;5P"}};
|
||||||
|
static const struct key_data key_f26[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;5Q"}};
|
||||||
|
static const struct key_data key_f27[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;5R"}};
|
||||||
|
static const struct key_data key_f28[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[1;5S"}};
|
||||||
|
static const struct key_data key_f29[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[15;5~"}};
|
||||||
|
static const struct key_data key_f30[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[17;5~"}};
|
||||||
|
static const struct key_data key_f31[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[18;5~"}};
|
||||||
|
static const struct key_data key_f32[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[19;5~"}};
|
||||||
|
static const struct key_data key_f33[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[20;5~"}};
|
||||||
|
static const struct key_data key_f34[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[21;5~"}};
|
||||||
|
static const struct key_data key_f35[] = {{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[23;5~"}};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_up[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(A),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[A"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OA"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_down[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(B),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[B"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OB"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_right[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(C),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[C"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OC"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_left[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(D),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[D"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OD"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_begin[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(E),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[E"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OE"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_home[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(H),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[H"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OH"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_end[] = {
|
||||||
|
DEFAULT_MODS_FOR_SINGLE(F),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_NORMAL, KEYPAD_DONTCARE, "\033[F"},
|
||||||
|
{MOD_ANY, CURSOR_KEYS_APPLICATION, KEYPAD_DONTCARE, "\033OF"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_insert[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(2),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[2~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_delete[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(3),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[3~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_pageup[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(5),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[5~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct key_data key_kp_pagedown[] = {
|
||||||
|
DEFAULT_MODS_FOR_TILDE(6),
|
||||||
|
{MOD_ANY, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[6~"},
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef DEFAULT_MODS_FOR_SINGLE
|
||||||
|
#undef DEFAULT_MODS_FOR_TILDE
|
||||||
|
|
||||||
|
#define DEFAULT_MODS_FOR_KP(sym) \
|
||||||
|
{MOD_NONE, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O"#sym}, \
|
||||||
|
{MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O2"#sym}, \
|
||||||
|
{MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O3"#sym}, \
|
||||||
|
{MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O4"#sym}, \
|
||||||
|
{MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O5"#sym}, \
|
||||||
|
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O6"#sym}, \
|
||||||
|
{MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O7"#sym}, \
|
||||||
|
{MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O8"#sym}, \
|
||||||
|
{MOD_META, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O9"#sym}, \
|
||||||
|
{MOD_META | MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O10"#sym}, \
|
||||||
|
{MOD_META | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O11"#sym}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O12"#sym}, \
|
||||||
|
{MOD_META | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O13"#sym}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O14"#sym}, \
|
||||||
|
{MOD_META | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O15"#sym}, \
|
||||||
|
{MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_APPLICATION, "\033O16"#sym}
|
||||||
|
|
||||||
|
static const struct key_data key_kp_enter[] = {DEFAULT_MODS_FOR_KP(M)};
|
||||||
|
static const struct key_data key_kp_divide[] = {DEFAULT_MODS_FOR_KP(o)};
|
||||||
|
static const struct key_data key_kp_multiply[] = {DEFAULT_MODS_FOR_KP(j)};
|
||||||
|
static const struct key_data key_kp_subtract[] = {DEFAULT_MODS_FOR_KP(m)};
|
||||||
|
static const struct key_data key_kp_add[] = {DEFAULT_MODS_FOR_KP(k)};
|
||||||
|
static const struct key_data key_kp_separator[] = {DEFAULT_MODS_FOR_KP(l)};
|
||||||
|
static const struct key_data key_kp_decimal[] = {DEFAULT_MODS_FOR_KP(n)};
|
||||||
|
static const struct key_data key_kp_0[] = {DEFAULT_MODS_FOR_KP(p)};
|
||||||
|
static const struct key_data key_kp_1[] = {DEFAULT_MODS_FOR_KP(q)};
|
||||||
|
static const struct key_data key_kp_2[] = {DEFAULT_MODS_FOR_KP(r)};
|
||||||
|
static const struct key_data key_kp_3[] = {DEFAULT_MODS_FOR_KP(s)};
|
||||||
|
static const struct key_data key_kp_4[] = {DEFAULT_MODS_FOR_KP(t)};
|
||||||
|
static const struct key_data key_kp_5[] = {DEFAULT_MODS_FOR_KP(u)};
|
||||||
|
static const struct key_data key_kp_6[] = {DEFAULT_MODS_FOR_KP(v)};
|
||||||
|
static const struct key_data key_kp_7[] = {DEFAULT_MODS_FOR_KP(w)};
|
||||||
|
static const struct key_data key_kp_8[] = {DEFAULT_MODS_FOR_KP(x)};
|
||||||
|
static const struct key_data key_kp_9[] = {DEFAULT_MODS_FOR_KP(y)};
|
||||||
|
|
||||||
|
#undef DEFAULT_MODS_FOR_KP
|
||||||
136
kitty-keymap.h
Normal file
136
kitty-keymap.h
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
struct kitty_key_data {
|
||||||
|
xkb_keysym_t sym;
|
||||||
|
uint16_t key;
|
||||||
|
uint8_t final:7;
|
||||||
|
bool is_modifier:1;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
_Static_assert(sizeof(struct kitty_key_data) == 7, "bad size");
|
||||||
|
|
||||||
|
/* Note! *Must* Be kept sorted (on 'sym') */
|
||||||
|
static const struct kitty_key_data kitty_keymap[] = {
|
||||||
|
{XKB_KEY_ISO_Level3_Shift, 57453, 'u', true},
|
||||||
|
{XKB_KEY_ISO_Level5_Shift, 57454, 'u', true},
|
||||||
|
{XKB_KEY_ISO_Left_Tab, 9, 'u', false},
|
||||||
|
|
||||||
|
{XKB_KEY_BackSpace, 127, 'u', false},
|
||||||
|
{XKB_KEY_Tab, 9, 'u', false},
|
||||||
|
{XKB_KEY_Return, 13, 'u', false},
|
||||||
|
{XKB_KEY_Pause, 57362, 'u', false},
|
||||||
|
{XKB_KEY_Scroll_Lock, 57359, 'u', false},
|
||||||
|
{XKB_KEY_Escape, 27, 'u', false},
|
||||||
|
{XKB_KEY_Home, 1, 'H', false},
|
||||||
|
{XKB_KEY_Left, 1, 'D', false},
|
||||||
|
{XKB_KEY_Up, 1, 'A', false},
|
||||||
|
{XKB_KEY_Right, 1, 'C', false},
|
||||||
|
{XKB_KEY_Down, 1, 'B', false},
|
||||||
|
{XKB_KEY_Prior, 5, '~', false},
|
||||||
|
{XKB_KEY_Next, 6, '~', false},
|
||||||
|
{XKB_KEY_End, 1, 'F', false},
|
||||||
|
{XKB_KEY_Print, 57361, 'u', false},
|
||||||
|
{XKB_KEY_Insert, 2, '~', false},
|
||||||
|
{XKB_KEY_Menu, 57363, 'u', false},
|
||||||
|
{XKB_KEY_Num_Lock, 57360, 'u', true},
|
||||||
|
|
||||||
|
{XKB_KEY_KP_Enter, 57414, 'u', false},
|
||||||
|
{XKB_KEY_KP_Home, 57423, 'u', false},
|
||||||
|
{XKB_KEY_KP_Left, 57417, 'u', false},
|
||||||
|
{XKB_KEY_KP_Up, 57419, 'u', false},
|
||||||
|
{XKB_KEY_KP_Right, 57418, 'u', false},
|
||||||
|
{XKB_KEY_KP_Down, 57420, 'u', false},
|
||||||
|
{XKB_KEY_KP_Prior, 57421, 'u', false},
|
||||||
|
{XKB_KEY_KP_Next, 57422, 'u', false},
|
||||||
|
{XKB_KEY_KP_End, 57424, 'u', false},
|
||||||
|
{XKB_KEY_KP_Begin, 1, 'E', false},
|
||||||
|
{XKB_KEY_KP_Insert, 57425, 'u', false},
|
||||||
|
{XKB_KEY_KP_Delete, 57426, 'u', false},
|
||||||
|
{XKB_KEY_KP_Multiply, 57411, 'u', false},
|
||||||
|
{XKB_KEY_KP_Add, 57413, 'u', false},
|
||||||
|
{XKB_KEY_KP_Separator, 57416, 'u', false},
|
||||||
|
{XKB_KEY_KP_Subtract, 57412, 'u', false},
|
||||||
|
{XKB_KEY_KP_Decimal, 57409, 'u', false},
|
||||||
|
{XKB_KEY_KP_Divide, 57410, 'u', false},
|
||||||
|
{XKB_KEY_KP_0, 57399, 'u', false},
|
||||||
|
{XKB_KEY_KP_1, 57400, 'u', false},
|
||||||
|
{XKB_KEY_KP_2, 57401, 'u', false},
|
||||||
|
{XKB_KEY_KP_3, 57402, 'u', false},
|
||||||
|
{XKB_KEY_KP_4, 57403, 'u', false},
|
||||||
|
{XKB_KEY_KP_5, 57404, 'u', false},
|
||||||
|
{XKB_KEY_KP_6, 57405, 'u', false},
|
||||||
|
{XKB_KEY_KP_7, 57406, 'u', false},
|
||||||
|
{XKB_KEY_KP_8, 57407, 'u', false},
|
||||||
|
{XKB_KEY_KP_9, 57408, 'u', false},
|
||||||
|
{XKB_KEY_KP_Equal, 57415, 'u', false},
|
||||||
|
|
||||||
|
{XKB_KEY_F1, 1, 'P', false},
|
||||||
|
{XKB_KEY_F2, 1, 'Q', false},
|
||||||
|
{XKB_KEY_F3, 13, '~', false},
|
||||||
|
{XKB_KEY_F4, 1, 'S', false},
|
||||||
|
{XKB_KEY_F5, 15, '~', false},
|
||||||
|
{XKB_KEY_F6, 17, '~', false},
|
||||||
|
{XKB_KEY_F7, 18, '~', false},
|
||||||
|
{XKB_KEY_F8, 19, '~', false},
|
||||||
|
{XKB_KEY_F9, 20, '~', false},
|
||||||
|
{XKB_KEY_F10, 21, '~', false},
|
||||||
|
{XKB_KEY_F11, 23, '~', false},
|
||||||
|
{XKB_KEY_F12, 24, '~', false},
|
||||||
|
{XKB_KEY_F13, 57376, 'u', false},
|
||||||
|
{XKB_KEY_F14, 57377, 'u', false},
|
||||||
|
{XKB_KEY_F15, 57378, 'u', false},
|
||||||
|
{XKB_KEY_F16, 57379, 'u', false},
|
||||||
|
{XKB_KEY_F17, 57380, 'u', false},
|
||||||
|
{XKB_KEY_F18, 57381, 'u', false},
|
||||||
|
{XKB_KEY_F19, 57382, 'u', false},
|
||||||
|
{XKB_KEY_F20, 57383, 'u', false},
|
||||||
|
{XKB_KEY_F21, 57384, 'u', false},
|
||||||
|
{XKB_KEY_F22, 57385, 'u', false},
|
||||||
|
{XKB_KEY_F23, 57386, 'u', false},
|
||||||
|
{XKB_KEY_F24, 57387, 'u', false},
|
||||||
|
{XKB_KEY_F25, 57388, 'u', false},
|
||||||
|
{XKB_KEY_F26, 57389, 'u', false},
|
||||||
|
{XKB_KEY_F27, 57390, 'u', false},
|
||||||
|
{XKB_KEY_F28, 57391, 'u', false},
|
||||||
|
{XKB_KEY_F29, 57392, 'u', false},
|
||||||
|
{XKB_KEY_F30, 57393, 'u', false},
|
||||||
|
{XKB_KEY_F31, 57394, 'u', false},
|
||||||
|
{XKB_KEY_F32, 57395, 'u', false},
|
||||||
|
{XKB_KEY_F33, 57396, 'u', false},
|
||||||
|
{XKB_KEY_F34, 57397, 'u', false},
|
||||||
|
{XKB_KEY_F35, 57398, 'u', false},
|
||||||
|
|
||||||
|
{XKB_KEY_Shift_L, 57441, 'u', true},
|
||||||
|
{XKB_KEY_Shift_R, 57447, 'u', true},
|
||||||
|
{XKB_KEY_Control_L, 57442, 'u', true},
|
||||||
|
{XKB_KEY_Control_R, 57448, 'u', true},
|
||||||
|
{XKB_KEY_Caps_Lock, 57358, 'u', true},
|
||||||
|
{XKB_KEY_Meta_L, 57446, 'u', true},
|
||||||
|
{XKB_KEY_Meta_R, 57452, 'u', true},
|
||||||
|
{XKB_KEY_Alt_L, 57443, 'u', true},
|
||||||
|
{XKB_KEY_Alt_R, 57449, 'u', true},
|
||||||
|
{XKB_KEY_Super_L, 57444, 'u', true},
|
||||||
|
{XKB_KEY_Super_R, 57450, 'u', true},
|
||||||
|
{XKB_KEY_Hyper_L, 57445, 'u', true},
|
||||||
|
{XKB_KEY_Hyper_R, 57451, 'u', true},
|
||||||
|
|
||||||
|
{XKB_KEY_Delete, 3, '~', false},
|
||||||
|
|
||||||
|
{XKB_KEY_XF86AudioLowerVolume, 57438, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioMute, 57440, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioRaiseVolume, 57439, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioPlay, 57428, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioStop, 57432, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioPrev, 57436, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioNext, 57435, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioRecord, 57437, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioPause, 57429, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioRewind, 57434, 'u', false},
|
||||||
|
{XKB_KEY_XF86AudioForward, 57433, 'u', false},
|
||||||
|
//{XKB_KEY_XF86AudioPlayPause, 57430, 'u', false},
|
||||||
|
//{XKB_KEY_XF86AudioReverse, 57431, 'u', false},
|
||||||
|
};
|
||||||
231
log.c
Normal file
231
log.c
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "xsnprintf.h"
|
||||||
|
|
||||||
|
static bool colorize = false;
|
||||||
|
static bool do_syslog = false;
|
||||||
|
static enum log_class log_level = LOG_CLASS_NONE;
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
const char name[8];
|
||||||
|
const char log_prefix[7];
|
||||||
|
uint8_t color;
|
||||||
|
int syslog_equivalent;
|
||||||
|
} log_level_map[] = {
|
||||||
|
[LOG_CLASS_NONE] = {"none", "none", 5, -1},
|
||||||
|
[LOG_CLASS_ERROR] = {"error", " err", 31, LOG_ERR},
|
||||||
|
[LOG_CLASS_WARNING] = {"warning", "warn", 33, LOG_WARNING},
|
||||||
|
[LOG_CLASS_INFO] = {"info", "info", 97, LOG_INFO},
|
||||||
|
[LOG_CLASS_DEBUG] = {"debug", " dbg", 36, LOG_DEBUG},
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
log_init(enum log_colorize _colorize, bool _do_syslog,
|
||||||
|
enum log_facility syslog_facility, enum log_class _log_level)
|
||||||
|
{
|
||||||
|
static const int facility_map[] = {
|
||||||
|
[LOG_FACILITY_USER] = LOG_USER,
|
||||||
|
[LOG_FACILITY_DAEMON] = LOG_DAEMON,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Don't use colors if NO_COLOR is defined and not empty */
|
||||||
|
const char *no_color_str = getenv("NO_COLOR");
|
||||||
|
const bool no_color = no_color_str != NULL && no_color_str[0] != '\0';
|
||||||
|
|
||||||
|
colorize = _colorize == LOG_COLORIZE_ALWAYS
|
||||||
|
|| (_colorize == LOG_COLORIZE_AUTO
|
||||||
|
&& !no_color && isatty(STDERR_FILENO));
|
||||||
|
do_syslog = _do_syslog;
|
||||||
|
log_level = _log_level;
|
||||||
|
|
||||||
|
int slvl = log_level_map[_log_level].syslog_equivalent;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
log_deinit(void)
|
||||||
|
{
|
||||||
|
if (do_syslog)
|
||||||
|
closelog();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_log(enum log_class log_class, const char *module, const char *file, int lineno,
|
||||||
|
const char *fmt, int sys_errno, va_list va)
|
||||||
|
{
|
||||||
|
xassert(log_class > LOG_CLASS_NONE);
|
||||||
|
xassert(log_class < ALEN(log_level_map));
|
||||||
|
|
||||||
|
if (log_class > log_level)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const char *prefix = log_level_map[log_class].log_prefix;
|
||||||
|
unsigned int class_clr = log_level_map[log_class].color;
|
||||||
|
|
||||||
|
char clr[16];
|
||||||
|
xsnprintf(clr, sizeof(clr), "\033[%um", class_clr);
|
||||||
|
fprintf(stderr, "%s%s%s: ", colorize ? clr : "", prefix, colorize ? "\033[0m" : "");
|
||||||
|
|
||||||
|
if (colorize)
|
||||||
|
fputs("\033[2m", stderr);
|
||||||
|
fprintf(stderr, "%s:%d: ", file, lineno);
|
||||||
|
if (colorize)
|
||||||
|
fputs("\033[0m", stderr);
|
||||||
|
|
||||||
|
vfprintf(stderr, fmt, va);
|
||||||
|
|
||||||
|
if (sys_errno != 0)
|
||||||
|
fprintf(stderr, ": %s", strerror(sys_errno));
|
||||||
|
|
||||||
|
fputc('\n', stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_sys_log(enum log_class log_class, const char *module,
|
||||||
|
const char UNUSED *file, int UNUSED lineno,
|
||||||
|
const char *fmt, int sys_errno, va_list va)
|
||||||
|
{
|
||||||
|
xassert(log_class > LOG_CLASS_NONE);
|
||||||
|
xassert(log_class < ALEN(log_level_map));
|
||||||
|
|
||||||
|
if (!do_syslog)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (log_class > log_level)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Map our log level to syslog's level */
|
||||||
|
int level = log_level_map[log_class].syslog_equivalent;
|
||||||
|
|
||||||
|
char msg[4096];
|
||||||
|
int n = vsnprintf(msg, sizeof(msg), fmt, va);
|
||||||
|
xassert(n >= 0);
|
||||||
|
|
||||||
|
if (sys_errno != 0 && (size_t)n < sizeof(msg))
|
||||||
|
snprintf(msg + n, sizeof(msg) - n, ": %s", strerror(sys_errno));
|
||||||
|
|
||||||
|
syslog(level, "%s: %s", module, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
log_msg_va(enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno, const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
va_list va2;
|
||||||
|
va_copy(va2, va);
|
||||||
|
_log(log_class, module, file, lineno, fmt, 0, va);
|
||||||
|
_sys_log(log_class, module, file, lineno, fmt, 0, va2);
|
||||||
|
va_end(va2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
log_msg(enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, fmt);
|
||||||
|
log_msg_va(log_class, module, file, lineno, fmt, va);
|
||||||
|
va_end(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
log_errno_va(enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno,
|
||||||
|
const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
log_errno_provided_va(log_class, module, file, lineno, errno, fmt, va);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
log_errno(enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno,
|
||||||
|
const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, fmt);
|
||||||
|
log_errno_va(log_class, module, file, lineno, fmt, va);
|
||||||
|
va_end(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
log_errno_provided_va(enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno, int errno_copy,
|
||||||
|
const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
va_list va2;
|
||||||
|
va_copy(va2, va);
|
||||||
|
_log(log_class, module, file, lineno, fmt, errno_copy, va);
|
||||||
|
_sys_log(log_class, module, file, lineno, fmt, errno_copy, va2);
|
||||||
|
va_end(va2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
log_errno_provided(enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno, int errno_copy,
|
||||||
|
const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list va;
|
||||||
|
va_start(va, fmt);
|
||||||
|
log_errno_provided_va(log_class, module, file, lineno, errno_copy, fmt, va);
|
||||||
|
va_end(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
map_len(void)
|
||||||
|
{
|
||||||
|
size_t len = ALEN(log_level_map);
|
||||||
|
#ifndef _DEBUG
|
||||||
|
/* Exclude "debug" entry for non-debug builds */
|
||||||
|
len--;
|
||||||
|
#endif
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
log_level_from_string(const char *str)
|
||||||
|
{
|
||||||
|
if (unlikely(str[0] == '\0'))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
for (int i = 0, n = map_len(); i < n; i++)
|
||||||
|
if (streq(str, log_level_map[i].name))
|
||||||
|
return i;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
log_level_string_hint(void)
|
||||||
|
{
|
||||||
|
static char buf[64];
|
||||||
|
if (buf[0] != '\0')
|
||||||
|
return buf;
|
||||||
|
|
||||||
|
for (size_t i = 0, pos = 0, n = map_len(); i < n; i++) {
|
||||||
|
const char *entry = log_level_map[i].name;
|
||||||
|
const char *delim = (i + 1 < n) ? ", " : "";
|
||||||
|
pos += xsnprintf(buf + pos, sizeof(buf) - pos, "'%s'%s", entry, delim);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
70
log.h
Normal file
70
log.h
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include "macros.h"
|
||||||
|
|
||||||
|
enum log_colorize { LOG_COLORIZE_NEVER, LOG_COLORIZE_ALWAYS, LOG_COLORIZE_AUTO };
|
||||||
|
enum log_facility { LOG_FACILITY_USER, LOG_FACILITY_DAEMON };
|
||||||
|
|
||||||
|
enum log_class {
|
||||||
|
LOG_CLASS_NONE,
|
||||||
|
LOG_CLASS_ERROR,
|
||||||
|
LOG_CLASS_WARNING,
|
||||||
|
LOG_CLASS_INFO,
|
||||||
|
LOG_CLASS_DEBUG,
|
||||||
|
LOG_CLASS_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
void log_init(enum log_colorize colorize, bool do_syslog,
|
||||||
|
enum log_facility syslog_facility, enum log_class log_level);
|
||||||
|
void log_deinit(void);
|
||||||
|
|
||||||
|
void log_msg(
|
||||||
|
enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno,
|
||||||
|
const char *fmt, ...) PRINTF(5);
|
||||||
|
|
||||||
|
void log_errno(
|
||||||
|
enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno,
|
||||||
|
const char *fmt, ...) PRINTF(5);
|
||||||
|
|
||||||
|
void log_errno_provided(
|
||||||
|
enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno, int _errno,
|
||||||
|
const char *fmt, ...) PRINTF(6);
|
||||||
|
|
||||||
|
void log_msg_va(
|
||||||
|
enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno, const char *fmt, va_list va) VPRINTF(5);
|
||||||
|
void log_errno_va(
|
||||||
|
enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno,
|
||||||
|
const char *fmt, va_list va) VPRINTF(5);
|
||||||
|
void log_errno_provided_va(
|
||||||
|
enum log_class log_class, const char *module,
|
||||||
|
const char *file, int lineno, int _errno,
|
||||||
|
const char *fmt, va_list va) VPRINTF(6);
|
||||||
|
|
||||||
|
|
||||||
|
int log_level_from_string(const char *str);
|
||||||
|
const char *log_level_string_hint(void);
|
||||||
|
|
||||||
|
#define LOG_ERR(...) \
|
||||||
|
log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#define LOG_ERRNO(...) \
|
||||||
|
log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#define LOG_ERRNO_P(_errno, ...) \
|
||||||
|
log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, \
|
||||||
|
_errno, __VA_ARGS__)
|
||||||
|
#define LOG_WARN(...) \
|
||||||
|
log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#define LOG_INFO(...) \
|
||||||
|
log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
|
||||||
|
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
||||||
|
#define LOG_DBG(...) \
|
||||||
|
log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_DBG(...)
|
||||||
|
#endif
|
||||||
211
macros.h
Normal file
211
macros.h
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define PASTE(a, b) a##b
|
||||||
|
#define XPASTE(a, b) PASTE(a, b)
|
||||||
|
#define STRLEN(str) (sizeof("" str "") - 1)
|
||||||
|
#define DO_PRAGMA(x) _Pragma(#x)
|
||||||
|
#define VERCMP(x, y, cx, cy) ((cx > x) || ((cx == x) && (cy >= y)))
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && defined(__GNUC_MINOR__)
|
||||||
|
#define GNUC_AT_LEAST(x, y) VERCMP(x, y, __GNUC__, __GNUC_MINOR__)
|
||||||
|
#else
|
||||||
|
#define GNUC_AT_LEAST(x, y) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__clang_major__) && defined(__clang_minor__)
|
||||||
|
#define CLANG_AT_LEAST(x, y) VERCMP(x, y, __clang_major__, __clang_minor__)
|
||||||
|
#else
|
||||||
|
#define CLANG_AT_LEAST(x, y) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __has_attribute
|
||||||
|
#define HAS_ATTRIBUTE(x) __has_attribute(x)
|
||||||
|
#else
|
||||||
|
#define HAS_ATTRIBUTE(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __has_builtin
|
||||||
|
#define HAS_BUILTIN(x) __has_builtin(x)
|
||||||
|
#else
|
||||||
|
#define HAS_BUILTIN(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __has_include
|
||||||
|
#define HAS_INCLUDE(x) __has_include(x)
|
||||||
|
#else
|
||||||
|
#define HAS_INCLUDE(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __has_feature
|
||||||
|
#define HAS_FEATURE(x) __has_feature(x)
|
||||||
|
#else
|
||||||
|
#define HAS_FEATURE(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// __has_extension() is a Clang macro used to determine if a feature is
|
||||||
|
// available even if not standardized in the current "-std" mode.
|
||||||
|
#ifdef __has_extension
|
||||||
|
#define HAS_EXTENSION(x) __has_extension(x)
|
||||||
|
#else
|
||||||
|
// Clang versions prior to 3.0 only supported __has_feature()
|
||||||
|
#define HAS_EXTENSION(x) HAS_FEATURE(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(unused) || defined(__TINYC__)
|
||||||
|
#define UNUSED __attribute__((__unused__))
|
||||||
|
#else
|
||||||
|
#define UNUSED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(const)
|
||||||
|
#define CONST __attribute__((__const__))
|
||||||
|
#else
|
||||||
|
#define CONST
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(malloc)
|
||||||
|
#define MALLOC __attribute__((__malloc__))
|
||||||
|
#else
|
||||||
|
#define MALLOC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(constructor)
|
||||||
|
#define CONSTRUCTOR __attribute__((__constructor__))
|
||||||
|
#define HAVE_ATTR_CONSTRUCTOR 1
|
||||||
|
#else
|
||||||
|
#define CONSTRUCTOR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(destructor)
|
||||||
|
#define DESTRUCTOR __attribute__((__destructor__))
|
||||||
|
#else
|
||||||
|
#define DESTRUCTOR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(format)
|
||||||
|
#define PRINTF(x) __attribute__((__format__(__printf__, (x), (x + 1))))
|
||||||
|
#define VPRINTF(x) __attribute__((__format__(__printf__, (x), 0)))
|
||||||
|
#else
|
||||||
|
#define PRINTF(x)
|
||||||
|
#define VPRINTF(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (GNUC_AT_LEAST(3, 0) || HAS_BUILTIN(__builtin_expect)) && defined(__OPTIMIZE__)
|
||||||
|
#define likely(x) __builtin_expect(!!(x), 1)
|
||||||
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||||
|
#else
|
||||||
|
#define likely(x) (x)
|
||||||
|
#define unlikely(x) (x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 1) || HAS_ATTRIBUTE(noinline)
|
||||||
|
#define NOINLINE __attribute__((__noinline__))
|
||||||
|
#else
|
||||||
|
#define NOINLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 1) || HAS_ATTRIBUTE(always_inline)
|
||||||
|
#define ALWAYS_INLINE __attribute__((__always_inline__))
|
||||||
|
#else
|
||||||
|
#define ALWAYS_INLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 3) || HAS_ATTRIBUTE(nonnull)
|
||||||
|
#define NONNULL_ARGS __attribute__((__nonnull__))
|
||||||
|
#define NONNULL_ARG(...) __attribute__((__nonnull__(__VA_ARGS__)))
|
||||||
|
#else
|
||||||
|
#define NONNULL_ARGS
|
||||||
|
#define NONNULL_ARG(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(3, 4) || HAS_ATTRIBUTE(warn_unused_result)
|
||||||
|
#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
|
||||||
|
#else
|
||||||
|
#define WARN_UNUSED_RESULT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(4, 1) || HAS_ATTRIBUTE(flatten)
|
||||||
|
#define FLATTEN __attribute__((__flatten__))
|
||||||
|
#else
|
||||||
|
#define FLATTEN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(4, 3) || HAS_ATTRIBUTE(hot)
|
||||||
|
#define HOT __attribute__((__hot__))
|
||||||
|
#else
|
||||||
|
#define HOT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(4, 3) || HAS_ATTRIBUTE(cold)
|
||||||
|
#define COLD __attribute__((__cold__))
|
||||||
|
#else
|
||||||
|
#define COLD
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(4, 5) || HAS_BUILTIN(__builtin_unreachable)
|
||||||
|
#define UNREACHABLE() __builtin_unreachable()
|
||||||
|
#else
|
||||||
|
#define UNREACHABLE()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if GNUC_AT_LEAST(5, 0) || HAS_ATTRIBUTE(returns_nonnull)
|
||||||
|
#define RETURNS_NONNULL __attribute__((__returns_nonnull__))
|
||||||
|
#else
|
||||||
|
#define RETURNS_NONNULL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if HAS_ATTRIBUTE(diagnose_if)
|
||||||
|
#define DIAGNOSE_IF(x) __attribute__((diagnose_if((x), (#x), "error")))
|
||||||
|
#else
|
||||||
|
#define DIAGNOSE_IF(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define XMALLOC MALLOC RETURNS_NONNULL WARN_UNUSED_RESULT
|
||||||
|
#define XSTRDUP XMALLOC NONNULL_ARGS
|
||||||
|
|
||||||
|
#if __STDC_VERSION__ >= 201112L
|
||||||
|
#define noreturn _Noreturn
|
||||||
|
#elif GNUC_AT_LEAST(3, 0)
|
||||||
|
#define noreturn __attribute__((__noreturn__))
|
||||||
|
#else
|
||||||
|
#define noreturn
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CLANG_AT_LEAST(3, 6)
|
||||||
|
#define UNROLL_LOOP(n) DO_PRAGMA(clang loop unroll_count(n))
|
||||||
|
#elif GNUC_AT_LEAST(8, 0)
|
||||||
|
#define UNROLL_LOOP(n) DO_PRAGMA(GCC unroll (n))
|
||||||
|
#else
|
||||||
|
#define UNROLL_LOOP(n)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __COUNTER__
|
||||||
|
// Supported by GCC 4.3+ and Clang
|
||||||
|
#define COUNTER_ __COUNTER__
|
||||||
|
#else
|
||||||
|
#define COUNTER_ __LINE__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_DEBUG) && defined(HAVE_ATTR_CONSTRUCTOR)
|
||||||
|
#define UNITTEST static void CONSTRUCTOR XPASTE(unittest_, COUNTER_)(void)
|
||||||
|
#else
|
||||||
|
#define UNITTEST static void UNUSED XPASTE(unittest_, COUNTER_)(void)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
#define IGNORE_WARNING(wflag) \
|
||||||
|
DO_PRAGMA(clang diagnostic push) \
|
||||||
|
DO_PRAGMA(clang diagnostic ignored "-Wunknown-pragmas") \
|
||||||
|
DO_PRAGMA(clang diagnostic ignored "-Wunknown-warning-option") \
|
||||||
|
DO_PRAGMA(clang diagnostic ignored wflag)
|
||||||
|
#define UNIGNORE_WARNINGS DO_PRAGMA(clang diagnostic pop)
|
||||||
|
#elif GNUC_AT_LEAST(4, 6)
|
||||||
|
#define IGNORE_WARNING(wflag) \
|
||||||
|
DO_PRAGMA(GCC diagnostic push) \
|
||||||
|
DO_PRAGMA(GCC diagnostic ignored "-Wpragmas") \
|
||||||
|
DO_PRAGMA(GCC diagnostic ignored wflag)
|
||||||
|
#define UNIGNORE_WARNINGS DO_PRAGMA(GCC diagnostic pop)
|
||||||
|
#else
|
||||||
|
#define IGNORE_WARNING(wflag)
|
||||||
|
#define UNIGNORE_WARNINGS
|
||||||
|
#endif
|
||||||
726
main.c
Normal file
726
main.c
Normal file
|
|
@ -0,0 +1,726 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <locale.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <fcft/fcft.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "main"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "fdm.h"
|
||||||
|
#include "foot-features.h"
|
||||||
|
#include "key-binding.h"
|
||||||
|
#include "macros.h"
|
||||||
|
#include "reaper.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "server.h"
|
||||||
|
#include "shm.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
#include "xsnprintf.h"
|
||||||
|
|
||||||
|
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
|
||||||
|
#error "char32_t does not use UTF-32"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool
|
||||||
|
fdm_sigint(struct fdm *fdm, int signo, void *data)
|
||||||
|
{
|
||||||
|
*(volatile sig_atomic_t *)data = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigusr_context {
|
||||||
|
struct terminal *term;
|
||||||
|
struct server *server;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool
|
||||||
|
fdm_sigusr(struct fdm *fdm, int signo, void *data)
|
||||||
|
{
|
||||||
|
xassert(signo == SIGUSR1 || signo == SIGUSR2);
|
||||||
|
|
||||||
|
struct sigusr_context *ctx = data;
|
||||||
|
|
||||||
|
if (ctx->server != NULL) {
|
||||||
|
if (signo == SIGUSR1)
|
||||||
|
server_global_theme_switch_to_dark(ctx->server);
|
||||||
|
else
|
||||||
|
server_global_theme_switch_to_light(ctx->server);
|
||||||
|
} else {
|
||||||
|
if (signo == SIGUSR1)
|
||||||
|
term_theme_switch_to_dark(ctx->term);
|
||||||
|
else
|
||||||
|
term_theme_switch_to_light(ctx->term);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_usage(const char *prog_name)
|
||||||
|
{
|
||||||
|
static const char options[] =
|
||||||
|
"\nOptions:\n"
|
||||||
|
" -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/foot/foot.ini)\n"
|
||||||
|
" -C,--check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n"
|
||||||
|
" -o,--override=[section.]key=value override configuration option\n"
|
||||||
|
" -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n"
|
||||||
|
" -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n"
|
||||||
|
" -T,--title=TITLE initial window title (foot)\n"
|
||||||
|
" -a,--app-id=ID window application ID (foot)\n"
|
||||||
|
" --toplevel-tag=TAG set a custom toplevel tag\n"
|
||||||
|
" -m,--maximized start in maximized mode\n"
|
||||||
|
" -F,--fullscreen start in fullscreen mode\n"
|
||||||
|
" -L,--login-shell start shell as a login shell\n"
|
||||||
|
" --pty=PATH display an existing PTY instead of creating one\n"
|
||||||
|
" -D,--working-directory=DIR directory to start in (CWD)\n"
|
||||||
|
" -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n"
|
||||||
|
" -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n"
|
||||||
|
" -s,--server[=PATH] run as a server (use 'footclient' to start terminals).\n"
|
||||||
|
" 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 (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"
|
||||||
|
" -e ignored (for compatibility with xterm -e)\n";
|
||||||
|
|
||||||
|
printf("Usage: %s [OPTIONS...]\n", prog_name);
|
||||||
|
printf("Usage: %s [OPTIONS...] command [ARGS...]\n", prog_name);
|
||||||
|
puts(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
locale_is_utf8(void)
|
||||||
|
{
|
||||||
|
static const char u8[] = u8"ö";
|
||||||
|
xassert(strlen(u8) == 2);
|
||||||
|
|
||||||
|
char32_t w;
|
||||||
|
if (mbrtoc32(&w, u8, 2, &(mbstate_t){0}) != 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return w == U'ö';
|
||||||
|
}
|
||||||
|
|
||||||
|
struct shutdown_context {
|
||||||
|
struct terminal **term;
|
||||||
|
int exit_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
term_shutdown_cb(void *data, int exit_code)
|
||||||
|
{
|
||||||
|
struct shutdown_context *ctx = data;
|
||||||
|
*ctx->term = NULL;
|
||||||
|
ctx->exit_code = exit_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
print_pid(const char *pid_file, bool *unlink_at_exit)
|
||||||
|
{
|
||||||
|
LOG_DBG("printing PID to %s", pid_file);
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
char *end;
|
||||||
|
int pid_fd = strtoul(pid_file, &end, 10);
|
||||||
|
|
||||||
|
if (errno != 0 || *end != '\0') {
|
||||||
|
if ((pid_fd = open(pid_file,
|
||||||
|
O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC,
|
||||||
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
|
||||||
|
LOG_ERRNO("%s: failed to open", pid_file);
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
*unlink_at_exit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid_fd >= 0) {
|
||||||
|
char pid[32];
|
||||||
|
size_t n = xsnprintf(pid, sizeof(pid), "%u\n", getpid());
|
||||||
|
|
||||||
|
ssize_t bytes = write(pid_fd, pid, n);
|
||||||
|
close(pid_fd);
|
||||||
|
|
||||||
|
if (bytes < 0) {
|
||||||
|
LOG_ERRNO("failed to write PID to FD=%u", pid_fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("wrote %zd bytes to FD=%d", bytes, pid_fd);
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sanitize_signals(void)
|
||||||
|
{
|
||||||
|
sigset_t mask;
|
||||||
|
sigemptyset(&mask);
|
||||||
|
sigprocmask(SIG_SETMASK, &mask, NULL);
|
||||||
|
|
||||||
|
struct sigaction dfl = {.sa_handler = SIG_DFL};
|
||||||
|
sigemptyset(&dfl.sa_mask);
|
||||||
|
|
||||||
|
for (int i = 1; i < SIGRTMAX; i++)
|
||||||
|
sigaction(i, &dfl, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PTY_OPTION = CHAR_MAX + 1,
|
||||||
|
TOPLEVEL_TAG_OPTION = CHAR_MAX + 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char *const *argv)
|
||||||
|
{
|
||||||
|
/* Custom exit code, to enable users to differentiate between foot
|
||||||
|
* itself failing, and the client application failing */
|
||||||
|
static const int foot_exit_failure = -26;
|
||||||
|
int ret = foot_exit_failure;
|
||||||
|
|
||||||
|
sanitize_signals();
|
||||||
|
|
||||||
|
/* XDG startup notifications */
|
||||||
|
const char *token = getenv("XDG_ACTIVATION_TOKEN");
|
||||||
|
unsetenv("XDG_ACTIVATION_TOKEN");
|
||||||
|
|
||||||
|
/* Startup notifications; we don't support it, but must ensure we
|
||||||
|
* don't pass this on to programs launched by us */
|
||||||
|
unsetenv("DESKTOP_STARTUP_ID");
|
||||||
|
|
||||||
|
const char *const prog_name = argc > 0 ? argv[0] : "<nullptr>";
|
||||||
|
|
||||||
|
static const struct option longopts[] = {
|
||||||
|
{"config", required_argument, NULL, 'c'},
|
||||||
|
{"check-config", no_argument, NULL, 'C'},
|
||||||
|
{"override", required_argument, NULL, 'o'},
|
||||||
|
{"term", required_argument, NULL, 't'},
|
||||||
|
{"title", required_argument, NULL, 'T'},
|
||||||
|
{"app-id", required_argument, NULL, 'a'},
|
||||||
|
{"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION},
|
||||||
|
{"login-shell", no_argument, NULL, 'L'},
|
||||||
|
{"working-directory", required_argument, NULL, 'D'},
|
||||||
|
{"font", required_argument, NULL, 'f'},
|
||||||
|
{"window-size-pixels", required_argument, NULL, 'w'},
|
||||||
|
{"window-size-chars", required_argument, NULL, 'W'},
|
||||||
|
{"server", optional_argument, NULL, 's'},
|
||||||
|
{"hold", no_argument, NULL, 'H'},
|
||||||
|
{"maximized", no_argument, NULL, 'm'},
|
||||||
|
{"fullscreen", no_argument, NULL, 'F'},
|
||||||
|
{"presentation-timings", no_argument, NULL, 'P'}, /* Undocumented */
|
||||||
|
{"pty", required_argument, NULL, PTY_OPTION},
|
||||||
|
{"print-pid", required_argument, NULL, 'p'},
|
||||||
|
{"log-level", required_argument, NULL, 'd'},
|
||||||
|
{"log-colorize", optional_argument, NULL, 'l'},
|
||||||
|
{"log-no-syslog", no_argument, NULL, 'S'},
|
||||||
|
{"version", no_argument, NULL, 'v'},
|
||||||
|
{"help", no_argument, NULL, 'h'},
|
||||||
|
{NULL, no_argument, NULL, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
bool check_config = false;
|
||||||
|
const char *conf_path = NULL;
|
||||||
|
const char *custom_cwd = NULL;
|
||||||
|
const char *pty_path = NULL;
|
||||||
|
bool as_server = false;
|
||||||
|
const char *conf_server_socket_path = NULL;
|
||||||
|
bool presentation_timings = false;
|
||||||
|
bool hold = false;
|
||||||
|
bool unlink_pid_file = false;
|
||||||
|
const char *pid_file = NULL;
|
||||||
|
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();
|
||||||
|
config_override_t overrides = tll_init();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int c = getopt_long(argc, argv, "+c:Co:t:T:a:LD:f:w:W:s::HmFPp:d:l::Sveh", longopts, NULL);
|
||||||
|
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case 'c':
|
||||||
|
conf_path = optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'C':
|
||||||
|
check_config = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
tll_push_back(overrides, xstrdup(optarg));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
tll_push_back(overrides, xstrjoin("term=", optarg));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'L':
|
||||||
|
tll_push_back(overrides, xstrdup("login-shell=yes"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'T':
|
||||||
|
tll_push_back(overrides, xstrjoin("title=", optarg));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
tll_push_back(overrides, xstrjoin("app-id=", optarg));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOPLEVEL_TAG_OPTION:
|
||||||
|
tll_push_back(overrides, xstrjoin("toplevel-tag=", optarg));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D': {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(optarg, &st) < 0 || !(st.st_mode & S_IFDIR)) {
|
||||||
|
fprintf(stderr, "error: %s: not a directory\n", optarg);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
custom_cwd = optarg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'f': {
|
||||||
|
char *font_override = xstrjoin("font=", optarg);
|
||||||
|
tll_push_back(overrides, font_override);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'w': {
|
||||||
|
unsigned width, height;
|
||||||
|
if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
|
||||||
|
fprintf(stderr, "error: invalid window-size-pixels: %s\n", optarg);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
tll_push_back(
|
||||||
|
overrides, xasprintf("initial-window-size-pixels=%ux%u",
|
||||||
|
width, height));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'W': {
|
||||||
|
unsigned width, height;
|
||||||
|
if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
|
||||||
|
fprintf(stderr, "error: invalid window-size-chars: %s\n", optarg);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
tll_push_back(
|
||||||
|
overrides, xasprintf("initial-window-size-chars=%ux%u",
|
||||||
|
width, height));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
as_server = true;
|
||||||
|
if (optarg != NULL)
|
||||||
|
conf_server_socket_path = optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PTY_OPTION:
|
||||||
|
pty_path = optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P':
|
||||||
|
presentation_timings = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'H':
|
||||||
|
hold = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
tll_push_back(overrides, xstrdup("initial-window-mode=maximized"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'F':
|
||||||
|
tll_push_back(overrides, xstrdup("initial-window-mode=fullscreen"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
pid_file = optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd': {
|
||||||
|
int lvl = log_level_from_string(optarg);
|
||||||
|
if (unlikely(lvl < 0)) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"-d,--log-level: %s: argument must be one of %s\n",
|
||||||
|
optarg,
|
||||||
|
log_level_string_hint());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
log_level = lvl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
if (optarg == NULL || streq(optarg, "auto"))
|
||||||
|
log_colorize = LOG_COLORIZE_AUTO;
|
||||||
|
else if (streq(optarg, "never"))
|
||||||
|
log_colorize = LOG_COLORIZE_NEVER;
|
||||||
|
else if (streq(optarg, "always"))
|
||||||
|
log_colorize = LOG_COLORIZE_ALWAYS;
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S':
|
||||||
|
log_syslog = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
print_version_and_features("foot ");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
print_usage(prog_name);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '?':
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (as_server && pty_path) {
|
||||||
|
fputs("error: --pty is incompatible with server mode\n", stderr);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_init(log_colorize, as_server && log_syslog,
|
||||||
|
as_server ? LOG_FACILITY_DAEMON : LOG_FACILITY_USER, log_level);
|
||||||
|
|
||||||
|
if (argc > 0) {
|
||||||
|
argc -= optind;
|
||||||
|
argv += optind;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("%s", version_and_features);
|
||||||
|
|
||||||
|
{
|
||||||
|
struct utsname name;
|
||||||
|
if (uname(&name) < 0)
|
||||||
|
LOG_ERRNO("uname() failed");
|
||||||
|
else
|
||||||
|
LOG_INFO("arch: %s %s/%zu-bit",
|
||||||
|
name.sysname, name.machine, sizeof(void *) * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
srand(time(NULL));
|
||||||
|
|
||||||
|
const char *locale = setlocale(LC_CTYPE, "");
|
||||||
|
if (locale == NULL) {
|
||||||
|
/*
|
||||||
|
* 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_ERR("setlocale() failed. The most common cause is that the "
|
||||||
|
"configured locale is not available, or has been misspelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("locale: %s", locale != NULL ? locale : "<invalid>");
|
||||||
|
|
||||||
|
bool bad_locale = locale == NULL || !locale_is_utf8();
|
||||||
|
if (bad_locale) {
|
||||||
|
static const char fallback_locales[][12] = {
|
||||||
|
"C.UTF-8",
|
||||||
|
"en_US.UTF-8",
|
||||||
|
};
|
||||||
|
char *saved_locale = locale != NULL ? xstrdup(locale) : NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to force an UTF-8 locale. If we succeed, launch the
|
||||||
|
* user's shell as usual, but add a user-notification saying
|
||||||
|
* the locale has been changed.
|
||||||
|
*/
|
||||||
|
for (size_t i = 0; i < ALEN(fallback_locales); i++) {
|
||||||
|
const char *const fallback_locale = fallback_locales[i];
|
||||||
|
|
||||||
|
if (setlocale(LC_CTYPE, fallback_locale) != NULL) {
|
||||||
|
if (saved_locale != NULL) {
|
||||||
|
LOG_WARN(
|
||||||
|
"'%s' is not a UTF-8 locale, falling back to '%s'",
|
||||||
|
saved_locale, fallback_locale);
|
||||||
|
|
||||||
|
user_notification_add_fmt(
|
||||||
|
&user_notifications, USER_NOTIFICATION_WARNING,
|
||||||
|
"'%s' is not a UTF-8 locale, falling back to '%s'",
|
||||||
|
saved_locale, fallback_locale);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
LOG_WARN(
|
||||||
|
"invalid locale, falling back to '%s'", fallback_locale);
|
||||||
|
user_notification_add_fmt(
|
||||||
|
&user_notifications, USER_NOTIFICATION_WARNING,
|
||||||
|
"invalid locale, falling back to '%s'", fallback_locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
bad_locale = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bad_locale) {
|
||||||
|
if (saved_locale != NULL) {
|
||||||
|
LOG_ERR(
|
||||||
|
"'%s' is not a UTF-8 locale, and failed to find a fallback",
|
||||||
|
saved_locale);
|
||||||
|
|
||||||
|
user_notification_add_fmt(
|
||||||
|
&user_notifications, USER_NOTIFICATION_ERROR,
|
||||||
|
"'%s' is not a UTF-8 locale, and failed to find a fallback",
|
||||||
|
saved_locale);
|
||||||
|
} else {
|
||||||
|
LOG_ERR("invalid locale, and failed to find a fallback");
|
||||||
|
|
||||||
|
user_notification_add_fmt(
|
||||||
|
&user_notifications, USER_NOTIFICATION_ERROR,
|
||||||
|
"invalid locale, and failed to find a fallback");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(saved_locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct config conf = {NULL};
|
||||||
|
bool conf_successful = config_load(
|
||||||
|
&conf, conf_path, &user_notifications, &overrides, check_config, as_server);
|
||||||
|
|
||||||
|
tll_free_and_free(overrides, free);
|
||||||
|
if (!conf_successful) {
|
||||||
|
config_free(&conf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_config) {
|
||||||
|
config_free(&conf);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR,
|
||||||
|
"fcft log level enum offset");
|
||||||
|
_Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS,
|
||||||
|
"fcft colorize enum mismatch");
|
||||||
|
fcft_init(
|
||||||
|
(enum fcft_log_colorize)log_colorize,
|
||||||
|
as_server && log_syslog,
|
||||||
|
(enum fcft_log_class)log_level);
|
||||||
|
|
||||||
|
if (conf_server_socket_path != NULL) {
|
||||||
|
free(conf.server_socket_path);
|
||||||
|
conf.server_socket_path = xstrdup(conf_server_socket_path);
|
||||||
|
}
|
||||||
|
conf.presentation_timings = presentation_timings;
|
||||||
|
conf.hold_at_exit = hold;
|
||||||
|
|
||||||
|
if (conf.tweak.font_monospace_warn && conf.fonts[0].count > 0) {
|
||||||
|
check_if_font_is_monospaced(
|
||||||
|
conf.fonts[0].arr[0].pattern, &conf.notifications);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (bad_locale) {
|
||||||
|
static char *const bad_locale_fake_argv[] = {"/bin/sh", "-c", "", NULL};
|
||||||
|
argc = 1;
|
||||||
|
argv = bad_locale_fake_argv;
|
||||||
|
conf.hold_at_exit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fdm *fdm = NULL;
|
||||||
|
struct reaper *reaper = NULL;
|
||||||
|
struct key_binding_manager *key_binding_manager = NULL;
|
||||||
|
struct wayland *wayl = NULL;
|
||||||
|
struct renderer *renderer = NULL;
|
||||||
|
struct terminal *term = NULL;
|
||||||
|
struct server *server = NULL;
|
||||||
|
struct shutdown_context shutdown_ctx = {.term = &term, .exit_code = foot_exit_failure};
|
||||||
|
|
||||||
|
const char *cwd = custom_cwd;
|
||||||
|
char *_cwd = NULL;
|
||||||
|
|
||||||
|
if (cwd == NULL) {
|
||||||
|
size_t buf_len = 1024;
|
||||||
|
do {
|
||||||
|
_cwd = xrealloc(_cwd, buf_len);
|
||||||
|
errno = 0;
|
||||||
|
if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) {
|
||||||
|
LOG_ERRNO("failed to get current working directory");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
buf_len *= 2;
|
||||||
|
} while (errno == ERANGE);
|
||||||
|
cwd = _cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *pwd = getenv("PWD");
|
||||||
|
if (pwd != NULL) {
|
||||||
|
char *resolved_path_cwd = realpath(cwd, NULL);
|
||||||
|
char *resolved_path_pwd = realpath(pwd, NULL);
|
||||||
|
|
||||||
|
if (resolved_path_cwd != NULL &&
|
||||||
|
resolved_path_pwd != NULL &&
|
||||||
|
streq(resolved_path_cwd, resolved_path_pwd))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The resolved path of $PWD matches the resolved path of
|
||||||
|
* the *actual* working directory - use $PWD.
|
||||||
|
*
|
||||||
|
* This makes a difference when $PWD refers to a symlink.
|
||||||
|
*/
|
||||||
|
cwd = pwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(resolved_path_cwd);
|
||||||
|
free(resolved_path_pwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
shm_set_max_pool_size(conf.tweak.max_shm_pool_size);
|
||||||
|
shm_set_min_stride_alignment(conf.tweak.min_stride_alignment);
|
||||||
|
|
||||||
|
if ((fdm = fdm_init()) == NULL)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if ((reaper = reaper_init(fdm)) == NULL)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if ((key_binding_manager = key_binding_manager_new()) == NULL)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if ((wayl = wayl_init(
|
||||||
|
fdm, key_binding_manager, conf.presentation_timings)) == NULL)
|
||||||
|
{
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((renderer = render_init(fdm, wayl)) == NULL)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (!as_server && (term = term_init(
|
||||||
|
&conf, fdm, reaper, wayl, "foot", cwd, token, pty_path,
|
||||||
|
argc, argv, NULL,
|
||||||
|
&term_shutdown_cb, &shutdown_ctx)) == NULL) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
free(_cwd);
|
||||||
|
_cwd = NULL;
|
||||||
|
|
||||||
|
if (as_server && (server = server_init(&conf, fdm, reaper, wayl)) == NULL)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
volatile sig_atomic_t aborted = false;
|
||||||
|
if (!fdm_signal_add(fdm, SIGINT, &fdm_sigint, (void *)&aborted) ||
|
||||||
|
!fdm_signal_add(fdm, SIGTERM, &fdm_sigint, (void *)&aborted))
|
||||||
|
{
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigusr_context sigusr_context = {
|
||||||
|
.term = term,
|
||||||
|
.server = server,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr, &sigusr_context) ||
|
||||||
|
!fdm_signal_add(fdm, SIGUSR2, &fdm_sigusr, &sigusr_context))
|
||||||
|
{
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigaction sig_ign = {.sa_handler = SIG_IGN};
|
||||||
|
sigemptyset(&sig_ign.sa_mask);
|
||||||
|
if (sigaction(SIGHUP, &sig_ign, NULL) < 0 ||
|
||||||
|
sigaction(SIGPIPE, &sig_ign, NULL) < 0)
|
||||||
|
{
|
||||||
|
LOG_ERRNO("failed to ignore SIGHUP+SIGPIPE");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (as_server)
|
||||||
|
LOG_INFO("running as server; launch terminals by running footclient");
|
||||||
|
|
||||||
|
if (as_server && pid_file != NULL) {
|
||||||
|
if (!print_pid(pid_file, &unlink_pid_file))
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = EXIT_SUCCESS;
|
||||||
|
while (likely(!aborted && (as_server || tll_length(wayl->terms) > 0))) {
|
||||||
|
if (unlikely(!fdm_poll(fdm))) {
|
||||||
|
ret = foot_exit_failure;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
free(_cwd);
|
||||||
|
server_destroy(server);
|
||||||
|
term_destroy(term);
|
||||||
|
|
||||||
|
shm_fini();
|
||||||
|
render_destroy(renderer);
|
||||||
|
wayl_destroy(wayl);
|
||||||
|
key_binding_manager_destroy(key_binding_manager);
|
||||||
|
reaper_destroy(reaper);
|
||||||
|
fdm_signal_del(fdm, SIGUSR1);
|
||||||
|
fdm_signal_del(fdm, SIGUSR2);
|
||||||
|
fdm_signal_del(fdm, SIGTERM);
|
||||||
|
fdm_signal_del(fdm, SIGINT);
|
||||||
|
fdm_destroy(fdm);
|
||||||
|
|
||||||
|
config_free(&conf);
|
||||||
|
|
||||||
|
if (unlink_pid_file)
|
||||||
|
unlink(pid_file);
|
||||||
|
|
||||||
|
LOG_INFO("goodbye");
|
||||||
|
fcft_fini();
|
||||||
|
log_deinit();
|
||||||
|
return ret == EXIT_SUCCESS && !as_server ? shutdown_ctx.exit_code : ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
char *s = xstrjoin("foo", "bar");
|
||||||
|
xassert(streq(s, "foobar"));
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
s = xstrjoin3("foo", " ", "bar");
|
||||||
|
xassert(streq(s, "foo bar"));
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
s = xstrjoin3("foo", ",", "bar");
|
||||||
|
xassert(streq(s, "foo,bar"));
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
s = xstrjoin3("foo", "bar", "baz");
|
||||||
|
xassert(streq(s, "foobarbaz"));
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
454
meson.build
Normal file
454
meson.build
Normal file
|
|
@ -0,0 +1,454 @@
|
||||||
|
project('foot', 'c',
|
||||||
|
version: '1.26.1',
|
||||||
|
license: 'MIT',
|
||||||
|
meson_version: '>=0.59.0',
|
||||||
|
default_options: [
|
||||||
|
'c_std=c11',
|
||||||
|
'warning_level=1',
|
||||||
|
'werror=true',
|
||||||
|
'b_ndebug=if-release'])
|
||||||
|
|
||||||
|
is_debug_build = get_option('buildtype').startswith('debug')
|
||||||
|
|
||||||
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
|
# Newer clang versions warns when using __COUNTER__ without -std=c2y
|
||||||
|
if cc.has_argument('-Wc2y-extensions')
|
||||||
|
add_project_arguments('-Wno-c2y-extensions', language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
if cc.has_function('memfd_create',
|
||||||
|
args: ['-D_GNU_SOURCE'],
|
||||||
|
prefix: '#include <sys/mman.h>')
|
||||||
|
add_project_arguments('-DMEMFD_CREATE', language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Missing on DragonFly, FreeBSD < 14.1
|
||||||
|
if cc.has_function('execvpe',
|
||||||
|
args: ['-D_GNU_SOURCE'],
|
||||||
|
prefix: '#include <unistd.h>')
|
||||||
|
add_project_arguments('-DEXECVPE', language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
if cc.has_function('sigabbrev_np',
|
||||||
|
args: ['-D_GNU_SOURCE'],
|
||||||
|
prefix: '#include <string.h>')
|
||||||
|
add_project_arguments('-DSIGABBREV_NP', language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
utmp_backend = get_option('utmp-backend')
|
||||||
|
if utmp_backend == 'auto'
|
||||||
|
host_os = host_machine.system()
|
||||||
|
if host_os == 'linux'
|
||||||
|
utmp_backend = 'libutempter'
|
||||||
|
elif host_os == 'freebsd'
|
||||||
|
utmp_backend = 'ulog'
|
||||||
|
else
|
||||||
|
utmp_backend = 'none'
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
utmp_default_helper_path = get_option('utmp-default-helper-path')
|
||||||
|
|
||||||
|
if utmp_backend == 'none'
|
||||||
|
utmp_add = ''
|
||||||
|
utmp_del = ''
|
||||||
|
utmp_del_have_argument = false
|
||||||
|
utmp_default_helper_path = ''
|
||||||
|
elif utmp_backend == 'libutempter'
|
||||||
|
utmp_add = 'add'
|
||||||
|
utmp_del = 'del'
|
||||||
|
utmp_del_have_argument = false
|
||||||
|
if utmp_default_helper_path == 'auto'
|
||||||
|
utmp_default_helper_path = join_paths('/usr', get_option('libdir'), 'utempter', 'utempter')
|
||||||
|
endif
|
||||||
|
elif utmp_backend == 'ulog'
|
||||||
|
utmp_add = 'login'
|
||||||
|
utmp_del = 'logout'
|
||||||
|
utmp_del_have_argument = false
|
||||||
|
if utmp_default_helper_path == 'auto'
|
||||||
|
utmp_default_helper_path = join_paths('/usr', get_option('libexecdir'), 'ulog-helper')
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
error('invalid utmp backend')
|
||||||
|
endif
|
||||||
|
|
||||||
|
add_project_arguments(
|
||||||
|
['-D_GNU_SOURCE=200809L',
|
||||||
|
'-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo'))] +
|
||||||
|
(utmp_backend != 'none'
|
||||||
|
? ['-DUTMP_ADD="@0@"'.format(utmp_add),
|
||||||
|
'-DUTMP_DEL="@0@"'.format(utmp_del),
|
||||||
|
'-DUTMP_DEFAULT_HELPER_PATH="@0@"'.format(utmp_default_helper_path)]
|
||||||
|
: []) +
|
||||||
|
(utmp_del_have_argument
|
||||||
|
? ['-DUTMP_DEL_HAVE_ARGUMENT=1']
|
||||||
|
: []) +
|
||||||
|
(is_debug_build
|
||||||
|
? ['-D_DEBUG']
|
||||||
|
: [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) +
|
||||||
|
(get_option('ime')
|
||||||
|
? ['-DFOOT_IME_ENABLED=1']
|
||||||
|
: []) +
|
||||||
|
(get_option('b_pgo') == 'use'
|
||||||
|
? ['-DFOOT_PGO_ENABLED=1']
|
||||||
|
: []) +
|
||||||
|
cc.get_supported_arguments(
|
||||||
|
['-pedantic',
|
||||||
|
'-fstrict-aliasing',
|
||||||
|
'-Wstrict-aliasing']),
|
||||||
|
language: 'c',
|
||||||
|
)
|
||||||
|
|
||||||
|
terminfo_install_location = get_option('custom-terminfo-install-location')
|
||||||
|
|
||||||
|
if terminfo_install_location != ''
|
||||||
|
add_project_arguments(
|
||||||
|
['-DFOOT_TERMINFO_PATH="@0@"'.format(
|
||||||
|
join_paths(get_option('prefix'), terminfo_install_location))],
|
||||||
|
language: 'c')
|
||||||
|
else
|
||||||
|
terminfo_install_location = join_paths(get_option('datadir'), 'terminfo')
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Compute the relative path used by compiler invocations.
|
||||||
|
source_root = meson.current_source_dir().split('/')
|
||||||
|
build_root = meson.global_build_root().split('/')
|
||||||
|
relative_dir_parts = []
|
||||||
|
i = 0
|
||||||
|
in_prefix = true
|
||||||
|
foreach p : build_root
|
||||||
|
if i >= source_root.length() or not in_prefix or p != source_root[i]
|
||||||
|
in_prefix = false
|
||||||
|
relative_dir_parts += '..'
|
||||||
|
endif
|
||||||
|
i += 1
|
||||||
|
endforeach
|
||||||
|
i = 0
|
||||||
|
in_prefix = true
|
||||||
|
foreach p : source_root
|
||||||
|
if i >= build_root.length() or not in_prefix or build_root[i] != p
|
||||||
|
in_prefix = false
|
||||||
|
relative_dir_parts += p
|
||||||
|
endif
|
||||||
|
i += 1
|
||||||
|
endforeach
|
||||||
|
relative_dir = join_paths(relative_dir_parts) + '/'
|
||||||
|
|
||||||
|
if cc.has_argument('-fmacro-prefix-map=/foo=')
|
||||||
|
add_project_arguments('-fmacro-prefix-map=@0@='.format(relative_dir), language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
math = cc.find_library('m')
|
||||||
|
threads = [dependency('threads'), cc.find_library('stdthreads', required: false)]
|
||||||
|
libepoll = dependency('epoll-shim', required: false)
|
||||||
|
pixman = dependency('pixman-1')
|
||||||
|
wayland_protocols = dependency('wayland-protocols', version: '>=1.41',
|
||||||
|
fallback: 'wayland-protocols',
|
||||||
|
default_options: ['tests=false'])
|
||||||
|
wayland_client = dependency('wayland-client')
|
||||||
|
wayland_cursor = dependency('wayland-cursor')
|
||||||
|
xkb = dependency('xkbcommon', version: '>=1.0.0')
|
||||||
|
fontconfig = dependency('fontconfig')
|
||||||
|
utf8proc = dependency('libutf8proc', required: get_option('grapheme-clustering'))
|
||||||
|
|
||||||
|
if utf8proc.found()
|
||||||
|
add_project_arguments('-DFOOT_GRAPHEME_CLUSTERING=1', language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
if pixman.version().version_compare('>=0.46.0')
|
||||||
|
add_project_arguments('-DHAVE_PIXMAN_RGBA_16', language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist')
|
||||||
|
fcft = dependency('fcft', version: ['>=3.3.1', '<4.0.0'], fallback: 'fcft')
|
||||||
|
|
||||||
|
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
|
||||||
|
|
||||||
|
wscanner = dependency('wayland-scanner', native: true)
|
||||||
|
wscanner_prog = find_program(
|
||||||
|
wscanner.get_variable('wayland_scanner'), native: true)
|
||||||
|
|
||||||
|
wl_proto_headers = []
|
||||||
|
wl_proto_src = []
|
||||||
|
wl_proto_xml = [
|
||||||
|
wayland_protocols_datadir / 'stable/xdg-shell/xdg-shell.xml',
|
||||||
|
wayland_protocols_datadir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'unstable/xdg-output/xdg-output-unstable-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'unstable/primary-selection/primary-selection-unstable-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'stable/presentation-time/presentation-time.xml',
|
||||||
|
wayland_protocols_datadir / 'unstable/text-input/text-input-unstable-v3.xml',
|
||||||
|
wayland_protocols_datadir / 'staging/xdg-activation/xdg-activation-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'stable/viewporter/viewporter.xml',
|
||||||
|
wayland_protocols_datadir / 'staging/fractional-scale/fractional-scale-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1
|
||||||
|
wayland_protocols_datadir / 'staging/cursor-shape/cursor-shape-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml',
|
||||||
|
wayland_protocols_datadir / 'staging/color-management/color-management-v1.xml',
|
||||||
|
]
|
||||||
|
|
||||||
|
if (wayland_protocols.version().version_compare('>=1.43'))
|
||||||
|
wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml']
|
||||||
|
add_project_arguments('-DHAVE_XDG_TOPLEVEL_TAG=1', language: 'c')
|
||||||
|
endif
|
||||||
|
if (wayland_protocols.version().version_compare('>=1.45'))
|
||||||
|
wl_proto_xml += [wayland_protocols_datadir / 'staging/ext-background-effect/ext-background-effect-v1.xml']
|
||||||
|
add_project_arguments('-DHAVE_EXT_BACKGROUND_EFFECT=1', language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
foreach prot : wl_proto_xml
|
||||||
|
wl_proto_headers += custom_target(
|
||||||
|
prot.underscorify() + '-client-header',
|
||||||
|
output: '@BASENAME@.h',
|
||||||
|
input: prot,
|
||||||
|
command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@'])
|
||||||
|
|
||||||
|
wl_proto_src += custom_target(
|
||||||
|
prot.underscorify() + '-private-code',
|
||||||
|
output: '@BASENAME@.c',
|
||||||
|
input: prot,
|
||||||
|
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
env = find_program('env', native: true)
|
||||||
|
generate_version_sh = files('generate-version.sh')
|
||||||
|
version = custom_target(
|
||||||
|
'generate_version',
|
||||||
|
build_always_stale: true,
|
||||||
|
output: 'version.h',
|
||||||
|
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
|
||||||
|
|
||||||
|
python = find_program('python3', native: true)
|
||||||
|
generate_builtin_terminfo_py = files('scripts/generate-builtin-terminfo.py')
|
||||||
|
foot_terminfo = files('foot.info')
|
||||||
|
builtin_terminfo = custom_target(
|
||||||
|
'generate_builtin_terminfo',
|
||||||
|
output: 'foot-terminfo.h',
|
||||||
|
command: [python, generate_builtin_terminfo_py,
|
||||||
|
'@default_terminfo@', foot_terminfo, 'foot', '@OUTPUT@']
|
||||||
|
)
|
||||||
|
|
||||||
|
generate_emoji_variation_sequences = files('scripts/generate-emoji-variation-sequences.py')
|
||||||
|
emoji_variation_sequences = custom_target(
|
||||||
|
'generate_emoji_variation_sequences',
|
||||||
|
input: 'unicode/emoji-variation-sequences.txt',
|
||||||
|
output: 'emoji-variation-sequences.h',
|
||||||
|
command: [python, generate_emoji_variation_sequences, '@INPUT@', '@OUTPUT@']
|
||||||
|
)
|
||||||
|
|
||||||
|
generate_srgb_funcs = files('scripts/srgb.py')
|
||||||
|
srgb_funcs = custom_target(
|
||||||
|
'generate_srgb_funcs',
|
||||||
|
output: ['srgb.c', 'srgb.h'],
|
||||||
|
command: [python, generate_srgb_funcs, '@OUTPUT0@', '@OUTPUT1@']
|
||||||
|
)
|
||||||
|
|
||||||
|
common = static_library(
|
||||||
|
'common',
|
||||||
|
'log.c', 'log.h',
|
||||||
|
'char32.c', 'char32.h',
|
||||||
|
'debug.c', 'debug.h',
|
||||||
|
'macros.h',
|
||||||
|
'xmalloc.c', 'xmalloc.h',
|
||||||
|
'xsnprintf.c', 'xsnprintf.h',
|
||||||
|
dependencies: [utf8proc]
|
||||||
|
)
|
||||||
|
|
||||||
|
misc = static_library(
|
||||||
|
'misc',
|
||||||
|
'hsl.c', 'hsl.h',
|
||||||
|
'macros.h',
|
||||||
|
'misc.c', 'misc.h',
|
||||||
|
'uri.c', 'uri.h',
|
||||||
|
dependencies: [utf8proc],
|
||||||
|
link_with: [common]
|
||||||
|
)
|
||||||
|
|
||||||
|
vtlib = static_library(
|
||||||
|
'vtlib',
|
||||||
|
'base64.c', 'base64.h',
|
||||||
|
'composed.c', 'composed.h',
|
||||||
|
'cursor-shape.c', 'cursor-shape.h',
|
||||||
|
'csi.c', 'csi.h',
|
||||||
|
'dcs.c', 'dcs.h',
|
||||||
|
'macros.h',
|
||||||
|
'osc.c', 'osc.h',
|
||||||
|
'sixel.c', 'sixel.h',
|
||||||
|
'vt.c', 'vt.h',
|
||||||
|
builtin_terminfo, srgb_funcs,
|
||||||
|
wl_proto_src + wl_proto_headers,
|
||||||
|
version,
|
||||||
|
dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc],
|
||||||
|
link_with: [common, misc],
|
||||||
|
)
|
||||||
|
|
||||||
|
pgolib = static_library(
|
||||||
|
'pgolib',
|
||||||
|
'grid.c', 'grid.h',
|
||||||
|
'selection.c', 'selection.h',
|
||||||
|
'terminal.c', 'terminal.h',
|
||||||
|
emoji_variation_sequences,
|
||||||
|
wl_proto_src + wl_proto_headers,
|
||||||
|
dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc],
|
||||||
|
link_with: vtlib,
|
||||||
|
)
|
||||||
|
|
||||||
|
tokenize = static_library(
|
||||||
|
'tokenizelib',
|
||||||
|
'tokenize.c',
|
||||||
|
dependencies: [utf8proc],
|
||||||
|
link_with: [common],
|
||||||
|
)
|
||||||
|
|
||||||
|
if get_option('b_pgo') == 'generate'
|
||||||
|
executable(
|
||||||
|
'pgo',
|
||||||
|
'pgo/pgo.c',
|
||||||
|
wl_proto_src + wl_proto_headers,
|
||||||
|
dependencies: [math, threads, libepoll, pixman, wayland_client, xkb, utf8proc, fcft, tllist],
|
||||||
|
link_with: pgolib,
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'foot',
|
||||||
|
'async.c', 'async.h',
|
||||||
|
'box-drawing.c', 'box-drawing.h',
|
||||||
|
'config.c', 'config.h',
|
||||||
|
'commands.c', 'commands.h',
|
||||||
|
'extract.c', 'extract.h',
|
||||||
|
'fdm.c', 'fdm.h',
|
||||||
|
'foot-features.c', 'foot-features.h',
|
||||||
|
'ime.c', 'ime.h',
|
||||||
|
'input.c', 'input.h',
|
||||||
|
'key-binding.c', 'key-binding.h',
|
||||||
|
'main.c',
|
||||||
|
'notify.c', 'notify.h',
|
||||||
|
'quirks.c', 'quirks.h',
|
||||||
|
'reaper.c', 'reaper.h',
|
||||||
|
'render.c', 'render.h',
|
||||||
|
'search.c', 'search.h',
|
||||||
|
'server.c', 'server.h', 'client-protocol.h',
|
||||||
|
'shm.c', 'shm.h',
|
||||||
|
'slave.c', 'slave.h',
|
||||||
|
'spawn.c', 'spawn.h',
|
||||||
|
'tokenize.c', 'tokenize.h',
|
||||||
|
'unicode-mode.c', 'unicode-mode.h',
|
||||||
|
'url-mode.c', 'url-mode.h',
|
||||||
|
'user-notification.c', 'user-notification.h',
|
||||||
|
'wayland.c', 'wayland.h', 'shm-formats.h',
|
||||||
|
'xkbcommon-vmod.h',
|
||||||
|
srgb_funcs, wl_proto_src + wl_proto_headers, version,
|
||||||
|
dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc,
|
||||||
|
tllist, fcft],
|
||||||
|
link_with: pgolib,
|
||||||
|
install: true)
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'footclient',
|
||||||
|
'client.c', 'client-protocol.h',
|
||||||
|
'foot-features.c', 'foot-features.h',
|
||||||
|
'macros.h',
|
||||||
|
'util.h',
|
||||||
|
version,
|
||||||
|
dependencies: [tllist, utf8proc],
|
||||||
|
link_with: common,
|
||||||
|
install: true)
|
||||||
|
|
||||||
|
install_data(
|
||||||
|
'foot.desktop', 'foot-server.desktop', 'footclient.desktop',
|
||||||
|
install_dir: join_paths(get_option('datadir'), 'applications'))
|
||||||
|
|
||||||
|
systemd = dependency('systemd', required: false)
|
||||||
|
custom_systemd_units_dir = get_option('systemd-units-dir')
|
||||||
|
|
||||||
|
if systemd.found() or custom_systemd_units_dir != ''
|
||||||
|
configuration = configuration_data()
|
||||||
|
configuration.set('bindir', join_paths(get_option('prefix'), get_option('bindir')))
|
||||||
|
|
||||||
|
if (custom_systemd_units_dir == '')
|
||||||
|
systemd_units_dir = systemd.get_variable('systemduserunitdir')
|
||||||
|
else
|
||||||
|
systemd_units_dir = custom_systemd_units_dir
|
||||||
|
endif
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
configuration: configuration,
|
||||||
|
input: 'foot-server.service.in',
|
||||||
|
output: '@BASENAME@',
|
||||||
|
install_dir: systemd_units_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
install_data(
|
||||||
|
'foot-server.socket',
|
||||||
|
install_dir: systemd_units_dir)
|
||||||
|
endif
|
||||||
|
|
||||||
|
scdoc = dependency('scdoc', native: true, required: get_option('docs'))
|
||||||
|
install_data('foot.ini', install_dir: join_paths(get_option('sysconfdir'), 'xdg', 'foot'))
|
||||||
|
if scdoc.found()
|
||||||
|
install_data(
|
||||||
|
'LICENSE', 'README.md', 'CHANGELOG.md',
|
||||||
|
install_dir: join_paths(get_option('datadir'), 'doc', 'foot'))
|
||||||
|
subdir('doc')
|
||||||
|
endif
|
||||||
|
|
||||||
|
if get_option('themes')
|
||||||
|
install_subdir('themes', install_dir: join_paths(get_option('datadir'), 'foot'))
|
||||||
|
endif
|
||||||
|
|
||||||
|
terminfo_base_name = get_option('terminfo-base-name')
|
||||||
|
if terminfo_base_name == ''
|
||||||
|
terminfo_base_name = get_option('default-terminfo')
|
||||||
|
endif
|
||||||
|
|
||||||
|
tic = find_program('tic', native: true, required: get_option('terminfo'))
|
||||||
|
if tic.found()
|
||||||
|
conf_data = configuration_data(
|
||||||
|
{
|
||||||
|
'default_terminfo': terminfo_base_name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
preprocessed = configure_file(
|
||||||
|
input: 'foot.info',
|
||||||
|
output: 'foot.info.preprocessed',
|
||||||
|
configuration: conf_data,
|
||||||
|
)
|
||||||
|
custom_target(
|
||||||
|
'terminfo',
|
||||||
|
output: terminfo_base_name[0],
|
||||||
|
input: preprocessed,
|
||||||
|
command: [tic, '-x', '-o', '@OUTDIR@', '-e', '@0@,@0@-direct'.format(terminfo_base_name), '@INPUT@'],
|
||||||
|
install: true,
|
||||||
|
install_dir: terminfo_install_location
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
|
subdir('completions')
|
||||||
|
subdir('icons')
|
||||||
|
subdir('utils')
|
||||||
|
|
||||||
|
if (get_option('tests'))
|
||||||
|
subdir('tests')
|
||||||
|
endif
|
||||||
|
|
||||||
|
summary(
|
||||||
|
{
|
||||||
|
'Documentation': scdoc.found(),
|
||||||
|
'Themes': get_option('themes'),
|
||||||
|
'IME': get_option('ime'),
|
||||||
|
'Grapheme clustering': utf8proc.found(),
|
||||||
|
'utmp backend': utmp_backend,
|
||||||
|
'utmp helper default path': utmp_default_helper_path,
|
||||||
|
'Build terminfo': tic.found(),
|
||||||
|
'Terminfo base name': terminfo_base_name,
|
||||||
|
'Terminfo install location': terminfo_install_location,
|
||||||
|
'Default TERM': get_option('default-terminfo'),
|
||||||
|
'Set TERMINFO': get_option('custom-terminfo-install-location') != '',
|
||||||
|
'Build tests': get_option('tests'),
|
||||||
|
},
|
||||||
|
bool_yn: true
|
||||||
|
)
|
||||||
29
meson_options.txt
Normal file
29
meson_options.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
option('docs', type: 'feature',
|
||||||
|
description: 'Build and install documentation (man pages, example foot.ini, readme, changelog, license etc).')
|
||||||
|
|
||||||
|
option('themes', type: 'boolean', value: true,
|
||||||
|
description: 'Install themes (predefined color schemes)')
|
||||||
|
|
||||||
|
option('ime', type: 'boolean', value: true,
|
||||||
|
description: 'IME (Input Method Editor) support')
|
||||||
|
|
||||||
|
option('grapheme-clustering', type: 'feature',
|
||||||
|
description: 'Enables grapheme clustering using libutf8proc. Requires fcft with harfbuzz support to be useful.')
|
||||||
|
|
||||||
|
option('tests', type: 'boolean', value: true, description: 'Build tests')
|
||||||
|
|
||||||
|
option('terminfo', type: 'feature', value: 'enabled', description: 'Build and install foot\'s terminfo files.')
|
||||||
|
option('default-terminfo', type: 'string', value: 'foot',
|
||||||
|
description: 'Default value of the "term" option in foot.ini.')
|
||||||
|
option('terminfo-base-name', type: 'string',
|
||||||
|
description: 'Base name of the generated terminfo files. Defaults to the value of the \'default-terminfo\' meson option')
|
||||||
|
option('custom-terminfo-install-location', type: 'string', value: '',
|
||||||
|
description: 'Path to foot\'s terminfo, relative to ${prefix}. If set, foot will set $TERMINFO to this value in the client process.')
|
||||||
|
|
||||||
|
option('systemd-units-dir', type: 'string', value: '',
|
||||||
|
description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}')
|
||||||
|
|
||||||
|
option('utmp-backend', type: 'combo', value: 'auto', choices: ['none', 'libutempter', 'ulog', 'auto'],
|
||||||
|
description: 'Which utmp logging backend to use. This affects how (with what arguments) the utmp helper binary (see \'utmp-default-helper-path\')is called. Default: auto (linux=libutempter, freebsd=ulog, others=none)')
|
||||||
|
option('utmp-default-helper-path', type: 'string', value: 'auto',
|
||||||
|
description: 'Default path to the utmp helper binary. Default: auto-detect')
|
||||||
63
misc.c
Normal file
63
misc.c
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include "misc.h"
|
||||||
|
#include "char32.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
isword(char32_t wc, bool spaces_only, const char32_t *delimiters)
|
||||||
|
{
|
||||||
|
if (spaces_only)
|
||||||
|
return isc32graph(wc);
|
||||||
|
|
||||||
|
if (c32chr(delimiters, wc) != NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return isc32graph(wc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
timespec_add(const struct timespec *a, const struct timespec *b,
|
||||||
|
struct timespec *res)
|
||||||
|
{
|
||||||
|
const long one_sec_in_ns = 1000000000;
|
||||||
|
|
||||||
|
res->tv_sec = a->tv_sec + b->tv_sec;
|
||||||
|
res->tv_nsec = a->tv_nsec + b->tv_nsec;
|
||||||
|
/* tv_nsec may be negative */
|
||||||
|
if (res->tv_nsec >= one_sec_in_ns) {
|
||||||
|
res->tv_sec++;
|
||||||
|
res->tv_nsec -= one_sec_in_ns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
timespec_sub(const struct timespec *a, const struct timespec *b,
|
||||||
|
struct timespec *res)
|
||||||
|
{
|
||||||
|
const long one_sec_in_ns = 1000000000;
|
||||||
|
|
||||||
|
res->tv_sec = a->tv_sec - b->tv_sec;
|
||||||
|
res->tv_nsec = a->tv_nsec - b->tv_nsec;
|
||||||
|
/* tv_nsec may be negative */
|
||||||
|
if (res->tv_nsec < 0) {
|
||||||
|
res->tv_sec--;
|
||||||
|
res->tv_nsec += one_sec_in_ns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_valid_utf8_and_printable(const char *value)
|
||||||
|
{
|
||||||
|
char32_t *wide = ambstoc32(value);
|
||||||
|
if (wide == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const char32_t *c = wide; *c != U'\0'; c++) {
|
||||||
|
if (!isc32print(*c)) {
|
||||||
|
free(wide);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(wide);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
12
misc.h
Normal file
12
misc.h
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <uchar.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
bool isword(char32_t wc, bool spaces_only, const char32_t *delimiters);
|
||||||
|
|
||||||
|
void timespec_add(const struct timespec *a, const struct timespec *b, struct timespec *res);
|
||||||
|
void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res);
|
||||||
|
|
||||||
|
bool is_valid_utf8_and_printable(const char *value);
|
||||||
6
notes.txt
Normal file
6
notes.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
1. uses wrong cursor when switching to another tab, only first tab shows
|
||||||
|
the correct cursor.
|
||||||
|
2. if i resize a tab the next tab will be the same
|
||||||
|
size it was previously until i resize the window, but then i will have
|
||||||
|
to do that for each consecutive window. if i dont resize the window at
|
||||||
|
all the tabs look fine.
|
||||||
765
notify.c
Normal file
765
notify.c
Normal file
|
|
@ -0,0 +1,765 @@
|
||||||
|
#include "notify.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "notify"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "log.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "spawn.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "wayland.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
#include "xsnprintf.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
notify_free(struct terminal *term, struct notification *notif)
|
||||||
|
{
|
||||||
|
if (notif->pid > 0)
|
||||||
|
fdm_del(term->fdm, notif->stdout_fd);
|
||||||
|
|
||||||
|
free(notif->id);
|
||||||
|
free(notif->title);
|
||||||
|
free(notif->body);
|
||||||
|
free(notif->category);
|
||||||
|
free(notif->app_id);
|
||||||
|
free(notif->icon_cache_id);
|
||||||
|
free(notif->icon_symbolic_name);
|
||||||
|
free(notif->icon_data);
|
||||||
|
free(notif->sound_name);
|
||||||
|
free(notif->xdg_token);
|
||||||
|
free(notif->stdout_data);
|
||||||
|
|
||||||
|
tll_free_and_free(notif->actions, free);
|
||||||
|
|
||||||
|
if (notif->icon_path != NULL) {
|
||||||
|
unlink(notif->icon_path);
|
||||||
|
free(notif->icon_path);
|
||||||
|
|
||||||
|
if (notif->icon_fd >= 0)
|
||||||
|
close(notif->icon_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(notif, 0, sizeof(*notif));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
write_icon_file(const void *data, size_t data_sz, int *fd, char **filename,
|
||||||
|
char **symbolic_name)
|
||||||
|
{
|
||||||
|
xassert(*filename == NULL);
|
||||||
|
xassert(*symbolic_name == NULL);
|
||||||
|
|
||||||
|
char name[64] = "/tmp/foot-notification-icon-XXXXXX";
|
||||||
|
|
||||||
|
*filename = NULL;
|
||||||
|
*symbolic_name = NULL;
|
||||||
|
*fd = mkostemp(name, O_CLOEXEC);
|
||||||
|
|
||||||
|
if (*fd < 0) {
|
||||||
|
LOG_ERRNO("failed to create temporary file for icon cache");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write(*fd, data, data_sz) != (ssize_t)data_sz) {
|
||||||
|
LOG_ERRNO("failed to write icon data to temporary file");
|
||||||
|
close(*fd);
|
||||||
|
*fd = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("wrote icon data to %s", name);
|
||||||
|
*filename = xstrdup(name);
|
||||||
|
*symbolic_name = xstrjoin("file://", *filename);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
to_integer(const char *line, size_t len, uint32_t *res)
|
||||||
|
{
|
||||||
|
bool is_id = true;
|
||||||
|
uint32_t maybe_id = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
char digit = line[i];
|
||||||
|
if (digit < '0' || digit > '9') {
|
||||||
|
is_id = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_id *= 10;
|
||||||
|
maybe_id += digit - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
*res = maybe_id;
|
||||||
|
return is_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
consume_stdout(struct notification *notif, bool eof)
|
||||||
|
{
|
||||||
|
char *data = notif->stdout_data;
|
||||||
|
const char *line = data;
|
||||||
|
size_t left = notif->stdout_sz;
|
||||||
|
|
||||||
|
/* Process stdout, line-by-line */
|
||||||
|
while (left > 0) {
|
||||||
|
line = data;
|
||||||
|
size_t len = left;
|
||||||
|
char *eol = (char *)memchr(line, '\n', left);
|
||||||
|
|
||||||
|
if (eol != NULL) {
|
||||||
|
*eol = '\0';
|
||||||
|
len = strlen(line);
|
||||||
|
data = eol + 1;
|
||||||
|
} else if (!eof)
|
||||||
|
break;
|
||||||
|
|
||||||
|
uint32_t maybe_id = 0;
|
||||||
|
uint32_t maybe_button_nr = 0;
|
||||||
|
|
||||||
|
/* Check for daemon assigned ID, either '123', or 'id=123' */
|
||||||
|
if ((notif->external_id == 0 && to_integer(line, len, &maybe_id)) ||
|
||||||
|
(len > 3 && memcmp(line, "id=", 3) == 0 &&
|
||||||
|
to_integer(&line[3], len - 3, &maybe_id)))
|
||||||
|
{
|
||||||
|
notif->external_id = maybe_id;
|
||||||
|
LOG_DBG("external ID: %u", notif->external_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for triggered action, either 'default' or 'action=default' */
|
||||||
|
else if ((len == 7 && memcmp(line, "default", 7) == 0) ||
|
||||||
|
(len == 7 + 7 && memcmp(line, "action=default", 7 + 7) == 0))
|
||||||
|
{
|
||||||
|
notif->activated = true;
|
||||||
|
LOG_DBG("notification's default action was triggered");
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (len > 7 && memcmp(line, "action=", 7) == 0) {
|
||||||
|
notif->activated = true;
|
||||||
|
|
||||||
|
if (to_integer(&line[7], len - 7, &maybe_button_nr)) {
|
||||||
|
notif->activated_button = maybe_button_nr;
|
||||||
|
LOG_DBG("custom action %u triggered", notif->activated_button);
|
||||||
|
} else {
|
||||||
|
LOG_DBG("unrecognized action triggered: %.*s",
|
||||||
|
(int)(len - 7), &line[7]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (notif->external_id > 0 &&
|
||||||
|
to_integer(line, len, &maybe_button_nr) &&
|
||||||
|
maybe_button_nr > 0 &&
|
||||||
|
maybe_button_nr <= notif->button_count)
|
||||||
|
{
|
||||||
|
/* Single integer, appearing *after* the ID, and is within
|
||||||
|
the custom button/action range */
|
||||||
|
notif->activated = true;
|
||||||
|
notif->activated_button = maybe_button_nr;
|
||||||
|
LOG_DBG("custom action %u triggered", notif->activated_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for XDG activation token, 'xdgtoken=xyz' */
|
||||||
|
else if (len > 9 && memcmp(line, "xdgtoken=", 9) == 0) {
|
||||||
|
notif->xdg_token = xstrndup(&line[9], len - 9);
|
||||||
|
LOG_DBG("XDG token: \"%s\"", notif->xdg_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= len + (eol != NULL ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left > 0)
|
||||||
|
memmove(notif->stdout_data, data, left);
|
||||||
|
|
||||||
|
notif->stdout_sz = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
fdm_notify_stdout(struct fdm *fdm, int fd, int events, void *data)
|
||||||
|
{
|
||||||
|
const struct terminal *term = data;
|
||||||
|
struct notification *notif = NULL;
|
||||||
|
|
||||||
|
/* Find notification */
|
||||||
|
tll_foreach(term->active_notifications, it) {
|
||||||
|
if (it->item.stdout_fd == fd) {
|
||||||
|
notif = &it->item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events & EPOLLIN) {
|
||||||
|
char buf[512];
|
||||||
|
ssize_t count = read(fd, buf, sizeof(buf) - 1);
|
||||||
|
|
||||||
|
if (count < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
LOG_ERRNO("failed to read notification activation token");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0 && notif != NULL) {
|
||||||
|
if (notif->stdout_data == NULL) {
|
||||||
|
xassert(notif->stdout_sz == 0);
|
||||||
|
notif->stdout_data = xmemdup(buf, count);
|
||||||
|
} else {
|
||||||
|
notif->stdout_data = xrealloc(notif->stdout_data, notif->stdout_sz + count);
|
||||||
|
memcpy(¬if->stdout_data[notif->stdout_sz], buf, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
notif->stdout_sz += count;
|
||||||
|
consume_stdout(notif, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events & EPOLLHUP) {
|
||||||
|
fdm_del(fdm, fd);
|
||||||
|
if (notif != NULL) {
|
||||||
|
notif->stdout_fd = -1;
|
||||||
|
consume_stdout(notif, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
notif_done(struct reaper *reaper, pid_t pid, int status, void *data)
|
||||||
|
{
|
||||||
|
struct terminal *term = data;
|
||||||
|
|
||||||
|
tll_foreach(term->active_notifications, it) {
|
||||||
|
struct notification *notif = &it->item;
|
||||||
|
if (notif->pid != pid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LOG_DBG("notification %s closed",
|
||||||
|
notif->id != NULL ? notif->id : "<unset>");
|
||||||
|
|
||||||
|
if (notif->activated && notif->focus) {
|
||||||
|
LOG_DBG("focus window on notification activation: \"%s\"",
|
||||||
|
notif->xdg_token);
|
||||||
|
|
||||||
|
if (notif->xdg_token == NULL)
|
||||||
|
LOG_WARN("cannot focus window: no activation token available");
|
||||||
|
else
|
||||||
|
wayl_activate(term->wl, term->window, notif->xdg_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notif->activated && notif->report_activated) {
|
||||||
|
LOG_DBG("sending notification activation event to client");
|
||||||
|
|
||||||
|
const char *id = notif->id != NULL ? notif->id : "0";
|
||||||
|
|
||||||
|
char button_nr[16] = {0};
|
||||||
|
if (notif->activated_button > 0) {
|
||||||
|
xsnprintf(
|
||||||
|
button_nr, sizeof(button_nr), "%u", notif->activated_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
char reply[7 + strlen(id) + 1 + strlen(button_nr) + 2 + 1];
|
||||||
|
size_t n = xsnprintf(
|
||||||
|
reply, sizeof(reply), "\033]99;i=%s;%s\033\\", id, button_nr);
|
||||||
|
term_to_slave(term, reply, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notif->report_closed) {
|
||||||
|
LOG_DBG("sending notification close event to client");
|
||||||
|
|
||||||
|
const char *id = notif->id != NULL ? notif->id : "0";
|
||||||
|
char reply[7 + strlen(id) + 1 + 7 + 1 + 2 + 1];
|
||||||
|
size_t n = xsnprintf(
|
||||||
|
reply, sizeof(reply), "\033]99;i=%s:p=close;\033\\", id);
|
||||||
|
term_to_slave(term, reply, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_free(term, notif);
|
||||||
|
tll_remove(term->active_notifications, it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
expand_action_to_argv(struct terminal *term, const char *name, const char *label,
|
||||||
|
size_t *argc, char ***argv)
|
||||||
|
{
|
||||||
|
char **expanded = NULL;
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
if (!spawn_expand_template(
|
||||||
|
&term->conf->desktop_notifications.command_action_arg, 2,
|
||||||
|
(const char *[]){"action-name", "action-label"},
|
||||||
|
(const char *[]){name, label},
|
||||||
|
&count, &expanded))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append to the "global" actions argv */
|
||||||
|
*argv = xrealloc(*argv, (*argc + count) * sizeof((*argv)[0]));
|
||||||
|
memcpy(&(*argv)[*argc], expanded, count * sizeof(expanded[0]));
|
||||||
|
*argc += count;
|
||||||
|
|
||||||
|
free(expanded);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
notify_notify(struct terminal *term, struct notification *notif)
|
||||||
|
{
|
||||||
|
xassert(notif->xdg_token == NULL);
|
||||||
|
xassert(notif->external_id == 0);
|
||||||
|
xassert(notif->pid == 0);
|
||||||
|
xassert(notif->stdout_fd <= 0);
|
||||||
|
xassert(notif->stdout_data == NULL);
|
||||||
|
xassert(notif->icon_path == NULL);
|
||||||
|
xassert(notif->icon_fd <= 0);
|
||||||
|
|
||||||
|
notif->pid = -1;
|
||||||
|
notif->stdout_fd = -1;
|
||||||
|
notif->icon_fd = -1;
|
||||||
|
|
||||||
|
if (term->conf->desktop_notifications.command.argv.args == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((term->conf->desktop_notifications.inhibit_when_focused ||
|
||||||
|
notif->when != NOTIFY_ALWAYS)
|
||||||
|
&& term->kbd_focus)
|
||||||
|
{
|
||||||
|
/* No notifications while we're focused */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *app_id = notif->app_id != NULL
|
||||||
|
? notif->app_id
|
||||||
|
: term->app_id != NULL
|
||||||
|
? term->app_id
|
||||||
|
: term->conf->app_id;
|
||||||
|
const char *title = notif->title != NULL ? notif->title : notif->body;
|
||||||
|
const char *body = notif->title != NULL && notif->body != NULL ? notif->body : "";
|
||||||
|
|
||||||
|
/* Icon: symbolic name if present, otherwise a filename */
|
||||||
|
const char *icon_name_or_path = "";
|
||||||
|
|
||||||
|
if (notif->icon_cache_id != NULL) {
|
||||||
|
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||||
|
const struct notification_icon *icon = &term->notification_icons[i];
|
||||||
|
|
||||||
|
if (icon->id != NULL && streq(icon->id, notif->icon_cache_id)) {
|
||||||
|
/* For now, we set the symbolic name to 'file:///path'
|
||||||
|
* when using a file based icon. */
|
||||||
|
xassert(icon->symbolic_name != NULL);
|
||||||
|
icon_name_or_path = icon->symbolic_name;
|
||||||
|
|
||||||
|
LOG_DBG("using icon from cache (cache ID: %s): %s",
|
||||||
|
icon->id, icon_name_or_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (notif->icon_symbolic_name != NULL) {
|
||||||
|
icon_name_or_path = notif->icon_symbolic_name;
|
||||||
|
LOG_DBG("using symbolic icon from notification: %s", icon_name_or_path);
|
||||||
|
} else if (notif->icon_data_sz > 0) {
|
||||||
|
xassert(notif->icon_data != NULL);
|
||||||
|
|
||||||
|
if (write_icon_file(
|
||||||
|
notif->icon_data, notif->icon_data_sz,
|
||||||
|
¬if->icon_fd,
|
||||||
|
¬if->icon_path,
|
||||||
|
¬if->icon_symbolic_name))
|
||||||
|
icon_name_or_path = notif->icon_symbolic_name;
|
||||||
|
|
||||||
|
LOG_DBG("using icon data from notification: %s", icon_name_or_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool track_notification = notif->focus ||
|
||||||
|
notif->report_activated ||
|
||||||
|
notif->may_be_programatically_closed;
|
||||||
|
|
||||||
|
uint32_t replaces_id = 0;
|
||||||
|
if (notif->id != NULL) {
|
||||||
|
tll_foreach(term->active_notifications, it) {
|
||||||
|
struct notification *existing = &it->item;
|
||||||
|
|
||||||
|
if (existing->id == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When replacing/updating a notification, we may have
|
||||||
|
* *multiple* notification helpers running for the "same"
|
||||||
|
* notification. Make sure only the *last* notification's
|
||||||
|
* report closed/activated are honored, to avoid sending
|
||||||
|
* multiple reports.
|
||||||
|
*
|
||||||
|
* This also means we cannot 'break' out of the loop - we
|
||||||
|
* must check *all* notifications.
|
||||||
|
*/
|
||||||
|
if (existing->external_id != 0 && streq(existing->id, notif->id)) {
|
||||||
|
replaces_id = existing->external_id;
|
||||||
|
existing->report_activated = false;
|
||||||
|
existing->report_closed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char replaces_id_str[16];
|
||||||
|
xsnprintf(replaces_id_str, sizeof(replaces_id_str), "%u", replaces_id);
|
||||||
|
|
||||||
|
const char *urgency_str =
|
||||||
|
notif->urgency == NOTIFY_URGENCY_LOW
|
||||||
|
? "low"
|
||||||
|
: notif->urgency == NOTIFY_URGENCY_NORMAL
|
||||||
|
? "normal" : "critical";
|
||||||
|
|
||||||
|
LOG_DBG("notify: title=\"%s\", body=\"%s\", app-id=\"%s\", category=\"%s\", "
|
||||||
|
"urgency=\"%s\", icon=\"%s\", expires=%d, replaces=%u, muted=%s, "
|
||||||
|
"sound-name=%s (tracking: %s)",
|
||||||
|
title, body, app_id, notif->category, urgency_str, icon_name_or_path,
|
||||||
|
notif->expire_time, replaces_id,
|
||||||
|
notif->muted ? "yes" : "no", notif->sound_name,
|
||||||
|
track_notification ? "yes" : "no");
|
||||||
|
|
||||||
|
xassert(title != NULL);
|
||||||
|
if (title == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char **argv = NULL;
|
||||||
|
size_t argc = 0;
|
||||||
|
char **action_argv = NULL;
|
||||||
|
size_t action_argc = 0;
|
||||||
|
|
||||||
|
char expire_time[16];
|
||||||
|
xsnprintf(expire_time, sizeof(expire_time), "%d", notif->expire_time);
|
||||||
|
|
||||||
|
if (term->conf->desktop_notifications.command_action_arg.argv.args) {
|
||||||
|
if (!expand_action_to_argv(
|
||||||
|
term, "default", "Activate", &action_argc, &action_argv))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t action_idx = 1;
|
||||||
|
tll_foreach(notif->actions, it) {
|
||||||
|
|
||||||
|
/* Custom actions use a numerical name, starting at 1 */
|
||||||
|
char name[16];
|
||||||
|
xsnprintf(name, sizeof(name), "%zu", action_idx++);
|
||||||
|
|
||||||
|
if (!expand_action_to_argv(
|
||||||
|
term, name, it->item, &action_argc, &action_argv))
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < action_argc; i++)
|
||||||
|
free(action_argv[i]);
|
||||||
|
free(action_argv);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spawn_expand_template(
|
||||||
|
&term->conf->desktop_notifications.command, 12,
|
||||||
|
(const char *[]){
|
||||||
|
"app-id", "window-title", "icon", "title", "body", "category",
|
||||||
|
"urgency", "muted", "sound-name", "expire-time", "replace-id",
|
||||||
|
"action-argument"},
|
||||||
|
(const char *[]){
|
||||||
|
app_id, term->window_title, icon_name_or_path, title,
|
||||||
|
body != NULL ? body : "",
|
||||||
|
notif->category != NULL ? notif->category : "", urgency_str,
|
||||||
|
notif->muted ? "true" : "false",
|
||||||
|
notif->sound_name != NULL ? notif->sound_name : "",
|
||||||
|
expire_time, replaces_id_str,
|
||||||
|
|
||||||
|
/* Custom expansion below, since we need to expand to multiple arguments */
|
||||||
|
"${action-argument}"},
|
||||||
|
&argc, &argv))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Post-process the expanded argv, and patch in all the --action
|
||||||
|
arguments we expanded earlier */
|
||||||
|
for (size_t i = 0; i < argc; i++) {
|
||||||
|
if (!streq(argv[i], "${action-argument}"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (action_argc == 0) {
|
||||||
|
free(argv[i]);
|
||||||
|
|
||||||
|
/* Remove ${command-argument}, but include terminating NULL */
|
||||||
|
memmove(&argv[i], &argv[i + 1], (argc - i) * sizeof(argv[0]));
|
||||||
|
argc--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the "${action-argument}" entry, add all actions argument
|
||||||
|
from earlier, but include terminating NULL */
|
||||||
|
argv = xrealloc(argv, (argc + action_argc) * sizeof(argv[0]));
|
||||||
|
|
||||||
|
/* Move remaining arguments to after the action arguments */
|
||||||
|
memmove(&argv[i + action_argc],
|
||||||
|
&argv[i + 1],
|
||||||
|
(argc - i) * sizeof(argv[0])); /* Include terminating NULL */
|
||||||
|
|
||||||
|
free(argv[i]); /* Free xstrdup("${action-argument}"); */
|
||||||
|
|
||||||
|
/* Insert the action arguments */
|
||||||
|
for (size_t j = 0; j < action_argc; j++) {
|
||||||
|
argv[i + j] = action_argv[j];
|
||||||
|
action_argv[j] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
argc += action_argc;
|
||||||
|
argc--; /* The ${action-argument} option has been removed */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("notify command:");
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
LOG_DBG(" argv[%zu] = \"%s\"", i, argv[i]);
|
||||||
|
xassert(argv[argc] == NULL);
|
||||||
|
|
||||||
|
int stdout_fds[2] = {-1, -1};
|
||||||
|
if (track_notification) {
|
||||||
|
if (pipe2(stdout_fds, O_CLOEXEC | O_NONBLOCK) < 0) {
|
||||||
|
LOG_WARN("failed to create stdout pipe");
|
||||||
|
track_notification = false;
|
||||||
|
/* Non-fatal */
|
||||||
|
} else {
|
||||||
|
tll_push_back(term->active_notifications, *notif);
|
||||||
|
|
||||||
|
/* We've taken over ownership of all data; clear, so that
|
||||||
|
notify_free() doesn't double free */
|
||||||
|
notif->id = NULL;
|
||||||
|
notif->title = NULL;
|
||||||
|
notif->body = NULL;
|
||||||
|
notif->category = NULL;
|
||||||
|
notif->app_id = NULL;
|
||||||
|
notif->icon_cache_id = NULL;
|
||||||
|
notif->icon_symbolic_name = NULL;
|
||||||
|
notif->icon_data = NULL;
|
||||||
|
notif->icon_data_sz = 0;
|
||||||
|
notif->icon_path = NULL;
|
||||||
|
notif->sound_name = NULL;
|
||||||
|
notif->icon_fd = -1;
|
||||||
|
notif->stdout_fd = -1;
|
||||||
|
struct notification *new_notif = &tll_back(term->active_notifications);
|
||||||
|
|
||||||
|
/* We don't need these anymore. They'll be free:d by the caller */
|
||||||
|
new_notif->button_count = tll_length(notif->actions);
|
||||||
|
memset(&new_notif->actions, 0, sizeof(new_notif->actions));
|
||||||
|
notif = new_notif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdout_fds[0] >= 0) {
|
||||||
|
fdm_add(term->fdm, stdout_fds[0], EPOLLIN,
|
||||||
|
&fdm_notify_stdout, (void *)term);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Redirect stdin to /dev/null, but ignore failure to open */
|
||||||
|
int devnull = open("/dev/null", O_RDONLY);
|
||||||
|
pid_t pid = spawn(
|
||||||
|
term->reaper, NULL, argv, devnull, stdout_fds[1], -1,
|
||||||
|
track_notification ? ¬if_done : NULL, (void *)term, NULL);
|
||||||
|
|
||||||
|
if (stdout_fds[1] >= 0) {
|
||||||
|
/* Close write-end of stdout pipe */
|
||||||
|
close(stdout_fds[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid < 0 && stdout_fds[0] >= 0) {
|
||||||
|
/* Remove FDM callback if we failed to spawn */
|
||||||
|
fdm_del(term->fdm, stdout_fds[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devnull >= 0)
|
||||||
|
close(devnull);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
free(argv[i]);
|
||||||
|
free(argv);
|
||||||
|
for (size_t i = 0; i < action_argc; i++)
|
||||||
|
free(action_argv[i]);
|
||||||
|
free(action_argv);
|
||||||
|
|
||||||
|
notif->pid = pid;
|
||||||
|
notif->stdout_fd = stdout_fds[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notify_close(struct terminal *term, const char *id)
|
||||||
|
{
|
||||||
|
xassert(id != NULL);
|
||||||
|
LOG_DBG("close notification %s", id);
|
||||||
|
|
||||||
|
tll_foreach(term->active_notifications, it) {
|
||||||
|
const struct notification *notif = &it->item;
|
||||||
|
if (notif->id == NULL || !streq(notif->id, id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (term->conf->desktop_notifications.close.argv.args == NULL) {
|
||||||
|
LOG_DBG(
|
||||||
|
"trying to close notification \"%s\" by sending SIGINT to %u",
|
||||||
|
id, notif->pid);
|
||||||
|
|
||||||
|
if (notif->pid == 0) {
|
||||||
|
LOG_WARN(
|
||||||
|
"cannot close notification \"%s\": no helper process running",
|
||||||
|
id);
|
||||||
|
} else {
|
||||||
|
/* Best-effort... */
|
||||||
|
kill(notif->pid, SIGINT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_DBG(
|
||||||
|
"trying to close notification \"%s\" "
|
||||||
|
"by running user defined command", id);
|
||||||
|
|
||||||
|
if (notif->external_id == 0) {
|
||||||
|
LOG_WARN("cannot close notification \"%s\": "
|
||||||
|
"no daemon assigned notification ID available", id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char **argv = NULL;
|
||||||
|
size_t argc = 0;
|
||||||
|
|
||||||
|
char external_id[16];
|
||||||
|
xsnprintf(external_id, sizeof(external_id), "%u", notif->external_id);
|
||||||
|
|
||||||
|
if (!spawn_expand_template(
|
||||||
|
&term->conf->desktop_notifications.close, 1,
|
||||||
|
(const char *[]){"id"},
|
||||||
|
(const char *[]){external_id},
|
||||||
|
&argc, &argv))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devnull = open("/dev/null", O_RDONLY);
|
||||||
|
spawn(
|
||||||
|
term->reaper, NULL, argv, devnull, -1, -1,
|
||||||
|
NULL, (void *)term, NULL);
|
||||||
|
|
||||||
|
if (devnull >= 0)
|
||||||
|
close(devnull);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
free(argv[i]);
|
||||||
|
free(argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_WARN("cannot close notification \"%s\": no such notification", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_icon(struct notification_icon *icon, const char *id, const char *symbolic_name,
|
||||||
|
const uint8_t *data, size_t data_sz)
|
||||||
|
{
|
||||||
|
icon->id = xstrdup(id);
|
||||||
|
icon->symbolic_name = symbolic_name != NULL ? xstrdup(symbolic_name) : NULL;
|
||||||
|
icon->tmp_file_name = NULL;
|
||||||
|
icon->tmp_file_fd = -1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dump in-line data to a temporary file. This allows us to pass
|
||||||
|
* the filename as a parameter to notification helpers
|
||||||
|
* (i.e. notify-send -i <path>).
|
||||||
|
*
|
||||||
|
* Optimization: since we always prefer (i.e. use) the symbolic
|
||||||
|
* name if present, there's no need to create a file on disk if we
|
||||||
|
* have a symbolic name.
|
||||||
|
*/
|
||||||
|
if (symbolic_name == NULL && data_sz > 0) {
|
||||||
|
write_icon_file(
|
||||||
|
data, data_sz,
|
||||||
|
&icon->tmp_file_fd,
|
||||||
|
&icon->tmp_file_name,
|
||||||
|
&icon->symbolic_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("added icon to cache: ID=%s: sym=%s, file=%s",
|
||||||
|
icon->id, icon->symbolic_name, icon->tmp_file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notify_icon_add(struct terminal *term, const char *id,
|
||||||
|
const char *symbolic_name, const uint8_t *data, size_t data_sz)
|
||||||
|
{
|
||||||
|
#if defined(_DEBUG)
|
||||||
|
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||||
|
struct notification_icon *icon = &term->notification_icons[i];
|
||||||
|
if (icon->id != NULL && streq(icon->id, id)) {
|
||||||
|
BUG("notification icon cache already contains \"%s\"", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||||
|
struct notification_icon *icon = &term->notification_icons[i];
|
||||||
|
if (icon->id == NULL) {
|
||||||
|
add_icon(icon, id, symbolic_name, data, data_sz);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cache full - throw out first entry, add new entry last */
|
||||||
|
notify_icon_free(&term->notification_icons[0]);
|
||||||
|
memmove(&term->notification_icons[0],
|
||||||
|
&term->notification_icons[1],
|
||||||
|
((ALEN(term->notification_icons) - 1) *
|
||||||
|
sizeof(term->notification_icons[0])));
|
||||||
|
|
||||||
|
add_icon(
|
||||||
|
&term->notification_icons[ALEN(term->notification_icons) - 1],
|
||||||
|
id, symbolic_name, data, data_sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notify_icon_del(struct terminal *term, const char *id)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||||
|
struct notification_icon *icon = &term->notification_icons[i];
|
||||||
|
|
||||||
|
if (icon->id == NULL || !streq(icon->id, id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LOG_DBG("expelled %s from the notification icon cache", icon->id);
|
||||||
|
notify_icon_free(icon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notify_icon_free(struct notification_icon *icon)
|
||||||
|
{
|
||||||
|
if (icon->tmp_file_name != NULL) {
|
||||||
|
unlink(icon->tmp_file_name);
|
||||||
|
if (icon->tmp_file_fd >= 0)
|
||||||
|
close(icon->tmp_file_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(icon->id);
|
||||||
|
free(icon->symbolic_name);
|
||||||
|
free(icon->tmp_file_name);
|
||||||
|
|
||||||
|
icon->id = NULL;
|
||||||
|
icon->symbolic_name = NULL;
|
||||||
|
icon->tmp_file_name = NULL;
|
||||||
|
icon->tmp_file_fd = -1;
|
||||||
|
}
|
||||||
95
notify.h
Normal file
95
notify.h
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <tllist.h>
|
||||||
|
|
||||||
|
struct terminal;
|
||||||
|
|
||||||
|
enum notify_when {
|
||||||
|
/* First, so that it can be left out of initializer and still be
|
||||||
|
the default */
|
||||||
|
NOTIFY_ALWAYS,
|
||||||
|
|
||||||
|
NOTIFY_UNFOCUSED,
|
||||||
|
NOTIFY_INVISIBLE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum notify_urgency {
|
||||||
|
/* First, so that it can be left out of initializer and still be
|
||||||
|
the default */
|
||||||
|
NOTIFY_URGENCY_NORMAL,
|
||||||
|
|
||||||
|
NOTIFY_URGENCY_LOW,
|
||||||
|
NOTIFY_URGENCY_CRITICAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct notification {
|
||||||
|
/*
|
||||||
|
* Set by caller of notify_notify()
|
||||||
|
*/
|
||||||
|
char *id; /* Internal notification ID */
|
||||||
|
|
||||||
|
char *app_id; /* Custom app-id, overrides the terminal's app-id if set */
|
||||||
|
char *title; /* Required */
|
||||||
|
char *body;
|
||||||
|
char *category;
|
||||||
|
|
||||||
|
enum notify_when when;
|
||||||
|
enum notify_urgency urgency;
|
||||||
|
int32_t expire_time;
|
||||||
|
|
||||||
|
tll(char *) actions;
|
||||||
|
|
||||||
|
char *icon_cache_id;
|
||||||
|
char *icon_symbolic_name;
|
||||||
|
uint8_t *icon_data;
|
||||||
|
size_t icon_data_sz;
|
||||||
|
|
||||||
|
bool focus; /* Focus the foot window when notification is activated */
|
||||||
|
bool may_be_programatically_closed; /* OSC-99: notification may be programmatically closed by the client */
|
||||||
|
bool report_activated; /* OSC-99: report notification activation to client */
|
||||||
|
bool report_closed; /* OSC-99: report notification closed to client */
|
||||||
|
|
||||||
|
bool muted; /* Explicitly mute the notification */
|
||||||
|
char *sound_name; /* Should be set to NULL if muted == true */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used internally by notify
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint32_t external_id; /* Daemon assigned notification ID */
|
||||||
|
bool activated; /* User 'activated' the notification */
|
||||||
|
uint32_t button_count; /* Number of buttons (custom actions) in notification */
|
||||||
|
uint32_t activated_button; /* User activated one of the custom actions */
|
||||||
|
char *xdg_token; /* XDG activation token, from daemon */
|
||||||
|
|
||||||
|
pid_t pid; /* Notifier command PID */
|
||||||
|
int stdout_fd; /* Notifier command's stdout */
|
||||||
|
|
||||||
|
char *stdout_data; /* Data we've reado from command's stdout */
|
||||||
|
size_t stdout_sz;
|
||||||
|
|
||||||
|
/* Used when notification provides raw icon data, and it's
|
||||||
|
bypassing the icon cache */
|
||||||
|
char *icon_path;
|
||||||
|
int icon_fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct notification_icon {
|
||||||
|
char *id;
|
||||||
|
char *symbolic_name;
|
||||||
|
char *tmp_file_name;
|
||||||
|
int tmp_file_fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool notify_notify(struct terminal *term, struct notification *notif);
|
||||||
|
void notify_close(struct terminal *term, const char *id);
|
||||||
|
void notify_free(struct terminal *term, struct notification *notif);
|
||||||
|
|
||||||
|
void notify_icon_add(struct terminal *term, const char *id,
|
||||||
|
const char *symbolic_name, const uint8_t *data,
|
||||||
|
size_t data_sz);
|
||||||
|
void notify_icon_del(struct terminal *term, const char *id);
|
||||||
|
void notify_icon_free(struct notification_icon *icon);
|
||||||
7
osc.h
Normal file
7
osc.h
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
bool osc_ensure_size(struct terminal *term, size_t required_size);
|
||||||
|
void osc_dispatch(struct terminal *term);
|
||||||
8
pgo/full-current-session.sh
Executable file
8
pgo/full-current-session.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
srcdir=$(realpath "${1}")
|
||||||
|
blddir=$(realpath "${2}")
|
||||||
|
|
||||||
|
"${srcdir}"/pgo/full-inner.sh "${srcdir}" "${blddir}"
|
||||||
14
pgo/full-headless-cage.sh
Executable file
14
pgo/full-headless-cage.sh
Executable file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
srcdir=$(realpath "${1}")
|
||||||
|
blddir=$(realpath "${2}")
|
||||||
|
|
||||||
|
runtime_dir=$(mktemp -d)
|
||||||
|
trap "rm -rf '${runtime_dir}'" EXIT INT HUP TERM
|
||||||
|
|
||||||
|
XDG_RUNTIME_DIR="${runtime_dir}" WLR_RENDERER=pixman WLR_BACKENDS=headless cage "${srcdir}"/pgo/full-inner.sh "${srcdir}" "${blddir}"
|
||||||
|
|
||||||
|
# Cage's exit code doesn't reflect our script's exit code
|
||||||
|
[ -f "${blddir}"/pgo-ok ] || exit 1
|
||||||
9
pgo/full-headless-sway-inner.sh
Executable file
9
pgo/full-headless-sway-inner.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -ux
|
||||||
|
|
||||||
|
srcdir=$(realpath "${1}")
|
||||||
|
blddir=$(realpath "${2}")
|
||||||
|
|
||||||
|
"${srcdir}"/pgo/full-inner.sh "${srcdir}" "${blddir}"
|
||||||
|
swaymsg exit
|
||||||
24
pgo/full-headless-sway.sh
Executable file
24
pgo/full-headless-sway.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
srcdir=$(realpath "${1}")
|
||||||
|
blddir=$(realpath "${2}")
|
||||||
|
|
||||||
|
runtime_dir=$(mktemp -d)
|
||||||
|
sway_conf=$(mktemp)
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -f "${sway_conf}"
|
||||||
|
rm -rf "${runtime_dir}"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT HUP TERM
|
||||||
|
|
||||||
|
# Generate a custom config that executes our generate-pgo-data script
|
||||||
|
> "${sway_conf}" echo "exec '${srcdir}'/pgo/full-headless-sway-inner.sh '${srcdir}' '${blddir}'"
|
||||||
|
|
||||||
|
# Run Sway. full-headless-sway-inner.sh ends with a 'swaymsg exit'
|
||||||
|
XDG_RUNTIME_DIR="${runtime_dir}" WLR_RENDERER=pixman WLR_BACKENDS=headless sway -c "${sway_conf}" --unsupported-gpu
|
||||||
|
|
||||||
|
# Sway's exit code doesn't reflect our script's exit code
|
||||||
|
[ -f "${blddir}"/pgo-ok ] || exit 1
|
||||||
32
pgo/full-inner.sh
Executable file
32
pgo/full-inner.sh
Executable file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
srcdir=$(realpath "${1}")
|
||||||
|
blddir=$(realpath "${2}")
|
||||||
|
|
||||||
|
. "${srcdir}"/pgo/options
|
||||||
|
|
||||||
|
pgo_data=$(mktemp)
|
||||||
|
trap "rm -f '${pgo_data}'" EXIT INT HUP TERM
|
||||||
|
|
||||||
|
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 \
|
||||||
|
--override tweak.grapheme-shaping=no \
|
||||||
|
--term=xterm \
|
||||||
|
sh -c "
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
'${srcdir}/scripts/generate-alt-random-writes.py' \
|
||||||
|
${script_options} \"${pgo_data}\"
|
||||||
|
|
||||||
|
cat \"${pgo_data}\"
|
||||||
|
"
|
||||||
|
touch "${blddir}"/pgo-ok
|
||||||
1
pgo/options
Normal file
1
pgo/options
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
script_options="--scroll --scroll-region --colors-regular --colors-bright --colors-256 --colors-rgb --attr-bold --attr-italic --attr-underline --sixel"
|
||||||
29
pgo/partial.sh
Executable file
29
pgo/partial.sh
Executable file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
srcdir=$(realpath "${1}")
|
||||||
|
blddir=$(realpath "${2}")
|
||||||
|
|
||||||
|
. "${srcdir}"/pgo/options
|
||||||
|
|
||||||
|
pgo_data=$(mktemp)
|
||||||
|
trap "rm -f ${pgo_data}" EXIT INT HUP TERM
|
||||||
|
|
||||||
|
rm -f "${blddir}"/pgo-ok
|
||||||
|
|
||||||
|
"${srcdir}"/scripts/generate-alt-random-writes.py \
|
||||||
|
--rows=67 \
|
||||||
|
--cols=135 \
|
||||||
|
${script_options} \
|
||||||
|
"${pgo_data}"
|
||||||
|
|
||||||
|
# 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}"
|
||||||
|
|
||||||
|
touch "${blddir}"/pgo-ok
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue